-- iCONAbpFX 的闭环补丁:库从 staging 拷来后执行一次(loop.ps1 reset-abp -Init),之后 BACKUP 到实例默认备份目录留底。 -- -- 签名密钥不在库里:ABP 用 AddDeveloperSigningCredential,从宿主目录的 tempkey.jwk 读, -- 没有就生成。loop.ps1 deploy 会删掉发布目录里的 tempkey.*,让本机生成一把只在 run\abp 里的新密钥, -- 所以闭环签出的 token 在 staging / 生产都验不过。(方案 v4 §2.1 以为密钥入库,实际不是。) -- -- 这里只换三样凭据,让闭环不必知道 staging 的明文: -- setsail 的 client secret → loop-setsail-secret -- FX_App 的 client secret → loop-fxapp-secret -- admin 的密码(iXserver 用 FX_App + password 授权取模板)→ Loop@123456 -- 明文都在 config\loop.psd1 里,是闭环专用的假值。 -- -- 另外改一处模板配置(第 3 节):epro_ig_demo 的条款组补上「Agreement Button (Line IG)」。 -- 整段可重复执行,结果不变。 SET NOCOUNT ON; SET XACT_ABORT ON; BEGIN TRANSACTION; ------------------------------------------------------------------------------------------------ -- 1. setsail 的 secret:IdentityServer4 存的是 Base64(SHA256(UTF8(secret))) ------------------------------------------------------------------------------------------------ DECLARE @setsail UNIQUEIDENTIFIER = (SELECT Id FROM dbo.IdentityServerClients WHERE ClientId = N'setsail'); IF @setsail IS NULL THROW 50001, N'iCONAbpFX 里没有 setsail client,库是不是从 staging 拷的?', 1; DELETE FROM dbo.IdentityServerClientSecrets WHERE ClientId = @setsail; INSERT INTO dbo.IdentityServerClientSecrets (ClientId, Type, Value, Description, Expiration) VALUES (@setsail, N'SharedSecret', N'ooFrp78vixE5Hq+OfnE3FXBedZPUiT8mjNzICr2EB80=', N'loop env', NULL); -- FX_App 同理 DECLARE @fxapp UNIQUEIDENTIFIER = (SELECT Id FROM dbo.IdentityServerClients WHERE ClientId = N'FX_App'); IF @fxapp IS NULL THROW 50003, N'iCONAbpFX 里没有 FX_App client', 1; DELETE FROM dbo.IdentityServerClientSecrets WHERE ClientId = @fxapp; INSERT INTO dbo.IdentityServerClientSecrets (ClientId, Type, Value, Description, Expiration) VALUES (@fxapp, N'SharedSecret', N'4wsAVCVjGIuA3sm8w1VV1yvrlD9+nrONjlWXabX6cJw=', N'loop env', NULL); ------------------------------------------------------------------------------------------------ -- 2. admin 的密码:ASP.NET Core Identity V3 格式(HMACSHA256,10000 次,16 字节盐) -- 盐是固定的 'loop-env-salt-01',补丁可重复执行、结果不变 ------------------------------------------------------------------------------------------------ IF NOT EXISTS (SELECT 1 FROM dbo.AbpUsers WHERE UserName = N'admin' AND TenantId IS NULL) THROW 50002, N'iCONAbpFX 里没有 host 的 admin 用户', 1; UPDATE dbo.AbpUsers SET PasswordHash = N'AQAAAAEAACcQAAAAEGxvb3AtZW52LXNhbHQtMDHzcKLvk4EOtgWerM+7p4SzG/uo/xPnsQw7mAP1Jo6Gvw==', SecurityStamp = CONVERT(NVARCHAR(36), NEWID()), AccessFailedCount = 0, LockoutEnd = NULL WHERE UserName = N'admin' AND TenantId IS NULL; ------------------------------------------------------------------------------------------------ -- 3. epro_ig_demo 的条款组补上 Agree 按钮 -- Line 从问候模板按位置切条款:[Agreement (Line IG), Agreement Button (Line IG), Agreed, Select Language, …]; -- IG 走条款专用分组,从 staging 拷来时 AgreementMessageTemplateIdsJson 只有 Agreement (Line IG), -- 缺了第二条「Please click Agree」,用户收到条款没有按钮可点。这里照 Line 的顺序追加到第二位。 -- staging 本身没改,闭环在这一处与 staging 不同 ------------------------------------------------------------------------------------------------ DECLARE @agreeButton NVARCHAR(36) = (SELECT LOWER(CONVERT(NVARCHAR(36), Id)) FROM dbo.iCON_MT_MessageTemplates WHERE Name = N'Agreement Button (Line IG)' AND IsDeleted = 0); IF @agreeButton IS NULL THROW 50004, N'iCONAbpFX 里没有模板 Agreement Button (Line IG)', 1; DECLARE @igReply UNIQUEIDENTIFIER = (SELECT s.Id FROM dbo.iCON_Chat_AutoReplySettings s JOIN dbo.iCON_Chat_ChatServiceAccounts a ON a.Id = s.ChatServiceAccountId WHERE a.Code = N'epro_ig_demo' AND a.IsDeleted = 0 AND s.IsDeleted = 0 AND s.Name = N'Default'); IF @igReply IS NULL THROW 50005, N'iCONAbpFX 里没有 epro_ig_demo 的 Default 自动回复设置', 1; UPDATE dbo.iCON_Chat_AutoReplySettings SET AgreementMessageTemplateIdsJson = JSON_MODIFY(AgreementMessageTemplateIdsJson, 'append $', @agreeButton), ConcurrencyStamp = REPLACE(CONVERT(NVARCHAR(36), NEWID()), '-', ''), LastModificationTime = GETDATE() WHERE Id = @igReply AND ISJSON(AgreementMessageTemplateIdsJson) = 1 AND NOT EXISTS (SELECT 1 FROM OPENJSON(AgreementMessageTemplateIdsJson) WHERE LOWER(value) = @agreeButton); COMMIT TRANSACTION; SELECT c.ClientId, c.Enabled, c.AccessTokenLifetime, (SELECT COUNT(*) FROM dbo.IdentityServerClientSecrets s WHERE s.ClientId = c.Id) AS secrets FROM dbo.IdentityServerClients c WHERE c.ClientId IN (N'setsail', N'FX_App'); SELECT a.Code, t.[key] AS position, m.Name AS agreement_template FROM dbo.iCON_Chat_AutoReplySettings s JOIN dbo.iCON_Chat_ChatServiceAccounts a ON a.Id = s.ChatServiceAccountId CROSS APPLY OPENJSON(s.AgreementMessageTemplateIdsJson) t LEFT JOIN dbo.iCON_MT_MessageTemplates m ON LOWER(CONVERT(NVARCHAR(36), m.Id)) = LOWER(t.value) WHERE s.Id = @igReply ORDER BY CONVERT(INT, t.[key]);