diff --git a/Masa.Mc.sln b/Masa.Mc.sln index fcb59b75..3bc31fc1 100644 --- a/Masa.Mc.sln +++ b/Masa.Mc.sln @@ -73,190 +73,292 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Mc.EntityFrameworkCore EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Mc.Domain.Shared", "src\Domain\Masa.Mc.Domain.Shared\Masa.Mc.Domain.Shared.csproj", "{E837F43F-2C47-4BEC-AB71-74622867CDDD}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Masa.Mc.Infrastructure.Weixin.MiniProgram", "src\Infrastructure\Masa.Mc.Infrastructure.Weixin.MiniProgram\Masa.Mc.Infrastructure.Weixin.MiniProgram.csproj", "{AC9C0545-7C25-4415-9C1D-017F4C690C30}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x86 = Debug|x86 + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU Release|x86 = Release|x86 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|x86.ActiveCfg = Debug|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|x86.Build.0 = Debug|Any CPU + {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Debug|x64.Build.0 = Debug|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|Any CPU.Build.0 = Release|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|x86.ActiveCfg = Release|Any CPU {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|x86.Build.0 = Release|Any CPU + {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|x64.ActiveCfg = Release|Any CPU + {87A8E21E-DF58-4BEA-8EA1-EF333BE4D2B3}.Release|x64.Build.0 = Release|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|x86.ActiveCfg = Debug|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|x86.Build.0 = Debug|Any CPU + {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Debug|x64.Build.0 = Debug|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|Any CPU.Build.0 = Release|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|x86.ActiveCfg = Release|Any CPU {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|x86.Build.0 = Release|Any CPU + {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|x64.ActiveCfg = Release|Any CPU + {40685EC0-64DF-4742-B8F6-602AFAEFC0EA}.Release|x64.Build.0 = Release|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|x86.ActiveCfg = Debug|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|x86.Build.0 = Debug|Any CPU + {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|x64.ActiveCfg = Debug|Any CPU + {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Debug|x64.Build.0 = Debug|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|Any CPU.Build.0 = Release|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|x86.ActiveCfg = Release|Any CPU {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|x86.Build.0 = Release|Any CPU + {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|x64.ActiveCfg = Release|Any CPU + {D38D7297-FC98-4126-B320-1322B6F8CCFB}.Release|x64.Build.0 = Release|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|Any CPU.Build.0 = Debug|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|x86.ActiveCfg = Debug|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|x86.Build.0 = Debug|Any CPU + {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Debug|x64.Build.0 = Debug|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|Any CPU.ActiveCfg = Release|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|Any CPU.Build.0 = Release|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|x86.ActiveCfg = Release|Any CPU {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|x86.Build.0 = Release|Any CPU + {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|x64.ActiveCfg = Release|Any CPU + {C47B7220-BDF3-4DFA-93FE-0F6885DB89A4}.Release|x64.Build.0 = Release|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|x86.ActiveCfg = Debug|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|x86.Build.0 = Debug|Any CPU + {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|x64.ActiveCfg = Debug|Any CPU + {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Debug|x64.Build.0 = Debug|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|Any CPU.Build.0 = Release|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|x86.ActiveCfg = Release|Any CPU {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|x86.Build.0 = Release|Any CPU + {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|x64.ActiveCfg = Release|Any CPU + {6CAC62DA-A3AF-4BFE-8E1C-0DB9D0D81CD6}.Release|x64.Build.0 = Release|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|x86.ActiveCfg = Debug|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|x86.Build.0 = Debug|Any CPU + {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|x64.ActiveCfg = Debug|Any CPU + {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Debug|x64.Build.0 = Debug|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|Any CPU.Build.0 = Release|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|x86.ActiveCfg = Release|Any CPU {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|x86.Build.0 = Release|Any CPU + {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|x64.ActiveCfg = Release|Any CPU + {FAFDC558-45A7-4FF7-BFCE-015EDF58F3AE}.Release|x64.Build.0 = Release|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|x86.ActiveCfg = Debug|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|x86.Build.0 = Debug|Any CPU + {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Debug|x64.Build.0 = Debug|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|Any CPU.Build.0 = Release|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|x86.ActiveCfg = Release|Any CPU {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|x86.Build.0 = Release|Any CPU + {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|x64.ActiveCfg = Release|Any CPU + {4024A9CA-D6CA-44D7-BEA4-D96891AFC6DD}.Release|x64.Build.0 = Release|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|Any CPU.Build.0 = Debug|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|x86.ActiveCfg = Debug|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|x86.Build.0 = Debug|Any CPU + {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|x64.ActiveCfg = Debug|Any CPU + {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Debug|x64.Build.0 = Debug|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|Any CPU.ActiveCfg = Release|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|Any CPU.Build.0 = Release|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|x86.ActiveCfg = Release|Any CPU {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|x86.Build.0 = Release|Any CPU + {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|x64.ActiveCfg = Release|Any CPU + {D965E2A5-3B97-4903-8FF6-E2E08847F16F}.Release|x64.Build.0 = Release|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Debug|Any CPU.Build.0 = Debug|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Debug|x86.ActiveCfg = Debug|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Debug|x86.Build.0 = Debug|Any CPU + {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Debug|x64.ActiveCfg = Debug|x64 {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Release|Any CPU.ActiveCfg = Release|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Release|Any CPU.Build.0 = Release|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Release|x86.ActiveCfg = Release|Any CPU {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Release|x86.Build.0 = Release|Any CPU + {C147F2DF-F545-4263-9FBE-C7A833BB2A73}.Release|x64.ActiveCfg = Release|x64 {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|Any CPU.Build.0 = Debug|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|x86.ActiveCfg = Debug|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|x86.Build.0 = Debug|Any CPU + {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|x64.ActiveCfg = Debug|Any CPU + {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Debug|x64.Build.0 = Debug|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|Any CPU.ActiveCfg = Release|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|Any CPU.Build.0 = Release|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|x86.ActiveCfg = Release|Any CPU {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|x86.Build.0 = Release|Any CPU + {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|x64.ActiveCfg = Release|Any CPU + {C816B922-18A4-4F6A-9A7A-4D0E7F2AE129}.Release|x64.Build.0 = Release|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|x86.ActiveCfg = Debug|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|x86.Build.0 = Debug|Any CPU + {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|x64.ActiveCfg = Debug|Any CPU + {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Debug|x64.Build.0 = Debug|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|Any CPU.Build.0 = Release|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|x86.ActiveCfg = Release|Any CPU {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|x86.Build.0 = Release|Any CPU + {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|x64.ActiveCfg = Release|Any CPU + {E71E792D-4812-4A7E-AB78-7DB4B7FB4D3D}.Release|x64.Build.0 = Release|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|x86.ActiveCfg = Debug|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|x86.Build.0 = Debug|Any CPU + {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|x64.ActiveCfg = Debug|Any CPU + {52353216-56EB-4F38-A6BA-7A23801569C3}.Debug|x64.Build.0 = Debug|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|Any CPU.Build.0 = Release|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|x86.ActiveCfg = Release|Any CPU {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|x86.Build.0 = Release|Any CPU + {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|x64.ActiveCfg = Release|Any CPU + {52353216-56EB-4F38-A6BA-7A23801569C3}.Release|x64.Build.0 = Release|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|Any CPU.Build.0 = Debug|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|x86.ActiveCfg = Debug|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|x86.Build.0 = Debug|Any CPU + {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|x64.ActiveCfg = Debug|Any CPU + {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Debug|x64.Build.0 = Debug|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|Any CPU.ActiveCfg = Release|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|Any CPU.Build.0 = Release|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|x86.ActiveCfg = Release|Any CPU {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|x86.Build.0 = Release|Any CPU + {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|x64.ActiveCfg = Release|Any CPU + {3FDC5EC6-5B52-46E4-82D8-E9B39F017D7F}.Release|x64.Build.0 = Release|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|x86.ActiveCfg = Debug|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|x86.Build.0 = Debug|Any CPU + {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Debug|x64.Build.0 = Debug|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|Any CPU.Build.0 = Release|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|x86.ActiveCfg = Release|Any CPU {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|x86.Build.0 = Release|Any CPU + {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|x64.ActiveCfg = Release|Any CPU + {7F2708C3-F1EE-4BFD-9321-F8D90390BF3C}.Release|x64.Build.0 = Release|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|Any CPU.Build.0 = Debug|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|x86.ActiveCfg = Debug|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|x86.Build.0 = Debug|Any CPU + {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|x64.ActiveCfg = Debug|Any CPU + {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Debug|x64.Build.0 = Debug|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|Any CPU.ActiveCfg = Release|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|Any CPU.Build.0 = Release|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|x86.ActiveCfg = Release|Any CPU {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|x86.Build.0 = Release|Any CPU + {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|x64.ActiveCfg = Release|Any CPU + {58E1DBB5-409C-4DF7-BDCA-A36715E0BB86}.Release|x64.Build.0 = Release|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|x86.ActiveCfg = Debug|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|x86.Build.0 = Debug|Any CPU + {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Debug|x64.Build.0 = Debug|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|Any CPU.Build.0 = Release|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|x86.ActiveCfg = Release|Any CPU {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|x86.Build.0 = Release|Any CPU + {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|x64.ActiveCfg = Release|Any CPU + {7F815157-EB5A-4DF5-9B10-D9D751CB6FC9}.Release|x64.Build.0 = Release|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|Any CPU.Build.0 = Debug|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|x86.ActiveCfg = Debug|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|x86.Build.0 = Debug|Any CPU + {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|x64.ActiveCfg = Debug|Any CPU + {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Debug|x64.Build.0 = Debug|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|Any CPU.ActiveCfg = Release|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|Any CPU.Build.0 = Release|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|x86.ActiveCfg = Release|Any CPU {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|x86.Build.0 = Release|Any CPU + {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|x64.ActiveCfg = Release|Any CPU + {01FB3AEA-B756-4F53-8CD1-3C77D072E8E6}.Release|x64.Build.0 = Release|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|Any CPU.Build.0 = Debug|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|x86.ActiveCfg = Debug|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|x86.Build.0 = Debug|Any CPU + {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|x64.ActiveCfg = Debug|Any CPU + {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Debug|x64.Build.0 = Debug|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|Any CPU.ActiveCfg = Release|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|Any CPU.Build.0 = Release|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|x86.ActiveCfg = Release|Any CPU {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|x86.Build.0 = Release|Any CPU + {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|x64.ActiveCfg = Release|Any CPU + {ACEEA1C1-1F50-4F6A-AB11-AF2B375254D7}.Release|x64.Build.0 = Release|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|Any CPU.Build.0 = Debug|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|x86.ActiveCfg = Debug|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|x86.Build.0 = Debug|Any CPU + {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|x64.ActiveCfg = Debug|Any CPU + {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Debug|x64.Build.0 = Debug|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|Any CPU.ActiveCfg = Release|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|Any CPU.Build.0 = Release|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|x86.ActiveCfg = Release|Any CPU {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|x86.Build.0 = Release|Any CPU + {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|x64.ActiveCfg = Release|Any CPU + {932CF64F-91F4-43B0-AD16-CFC24FEE0362}.Release|x64.Build.0 = Release|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|Any CPU.Build.0 = Debug|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|x86.ActiveCfg = Debug|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|x86.Build.0 = Debug|Any CPU + {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|x64.ActiveCfg = Debug|Any CPU + {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Debug|x64.Build.0 = Debug|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|Any CPU.ActiveCfg = Release|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|Any CPU.Build.0 = Release|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|x86.ActiveCfg = Release|Any CPU {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|x86.Build.0 = Release|Any CPU + {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|x64.ActiveCfg = Release|Any CPU + {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE}.Release|x64.Build.0 = Release|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Debug|x86.ActiveCfg = Debug|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Debug|x86.Build.0 = Debug|Any CPU + {2E998A42-E121-4072-B162-B8111CF92194}.Debug|x64.ActiveCfg = Debug|Any CPU + {2E998A42-E121-4072-B162-B8111CF92194}.Debug|x64.Build.0 = Debug|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Release|Any CPU.Build.0 = Release|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Release|x86.ActiveCfg = Release|Any CPU {2E998A42-E121-4072-B162-B8111CF92194}.Release|x86.Build.0 = Release|Any CPU + {2E998A42-E121-4072-B162-B8111CF92194}.Release|x64.ActiveCfg = Release|Any CPU + {2E998A42-E121-4072-B162-B8111CF92194}.Release|x64.Build.0 = Release|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|Any CPU.Build.0 = Debug|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|x86.ActiveCfg = Debug|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|x86.Build.0 = Debug|Any CPU + {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|x64.ActiveCfg = Debug|Any CPU + {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Debug|x64.Build.0 = Debug|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|Any CPU.ActiveCfg = Release|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|Any CPU.Build.0 = Release|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|x86.ActiveCfg = Release|Any CPU {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|x86.Build.0 = Release|Any CPU + {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|x64.ActiveCfg = Release|Any CPU + {E837F43F-2C47-4BEC-AB71-74622867CDDD}.Release|x64.Build.0 = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|x86.Build.0 = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Debug|x64.Build.0 = Debug|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|Any CPU.Build.0 = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|x86.ActiveCfg = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|x86.Build.0 = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|x64.ActiveCfg = Release|Any CPU + {AC9C0545-7C25-4415-9C1D-017F4C690C30}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -289,6 +391,7 @@ Global {86E3CEA5-BCAD-4E90-A059-6BE09B80CEFE} = {4660010F-BB9B-4C8E-B4F7-34AF77080EA0} {2E998A42-E121-4072-B162-B8111CF92194} = {4660010F-BB9B-4C8E-B4F7-34AF77080EA0} {E837F43F-2C47-4BEC-AB71-74622867CDDD} = {078ED3F9-3B21-4F76-AA0A-51E054F8D0DD} + {AC9C0545-7C25-4415-9C1D-017F4C690C30} = {4660010F-BB9B-4C8E-B4F7-34AF77080EA0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B83BA2BA-19E5-41FB-A92A-16A03DA229E4} diff --git a/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/McCaller.cs b/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/McCaller.cs index 7b59672e..dcd1e9aa 100644 --- a/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/McCaller.cs +++ b/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/McCaller.cs @@ -8,6 +8,7 @@ public class McCaller : StackHttpClientCaller #region Field ChannelService? _channelService; MessageTemplateService? _messageTemplateService; + WeixinMiniProgramTemplateService? _weixinMiniProgramTemplateService; ReceiverGroupService? _receiverGroupService; SmsTemplateService? _smsTemplateService; MessageTaskService? _messageTaskService; @@ -24,6 +25,8 @@ public class McCaller : StackHttpClientCaller public MessageTemplateService MessageTemplateService => _messageTemplateService ?? (_messageTemplateService = new(Caller)); + public WeixinMiniProgramTemplateService WeixinMiniProgramTemplateService => _weixinMiniProgramTemplateService ?? (_weixinMiniProgramTemplateService = new(Caller)); + public ReceiverGroupService ReceiverGroupService => _receiverGroupService ?? (_receiverGroupService = new(Caller)); public SmsTemplateService SmsTemplateService => _smsTemplateService ?? (_smsTemplateService = new(Caller)); diff --git a/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/Services/MessageTemplates/WeixinMiniProgramTemplateService.cs b/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/Services/MessageTemplates/WeixinMiniProgramTemplateService.cs new file mode 100644 index 00000000..7f86f02b --- /dev/null +++ b/src/ApiGateways/Caller/Masa.Mc.ApiGateways.Caller/Services/MessageTemplates/WeixinMiniProgramTemplateService.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.ApiGateways.Caller.Services.MessageTemplates; + +public class WeixinMiniProgramTemplateService : ServiceBase +{ + protected override string BaseUrl { get; set; } + + internal WeixinMiniProgramTemplateService(ICaller caller) : base(caller) + { + BaseUrl = "api/weixin-mini-program-template"; + } + + public async Task> GetListAsync(Guid channelId) + { + return await GetAsync>($"channels/{channelId}/templates") ?? new(); + } +} diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageReceipts/WeixinMiniProgramReceiptInput.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageReceipts/WeixinMiniProgramReceiptInput.cs new file mode 100644 index 00000000..5a3bca66 --- /dev/null +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageReceipts/WeixinMiniProgramReceiptInput.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Contracts.Admin.Dtos.MessageReceipts; + +public class WeixinMiniProgramReceiptInput +{ + public string OpenId { get; set; } = string.Empty; + + public string TemplateId { get; set; } = string.Empty; + + public string MsgId { get; set; } = string.Empty; + + public string ErrorCode { get; set; } = string.Empty; + + public string ErrorStatus { get; set; } = string.Empty; + + public DateTimeOffset ReceiveTime { get; set; } = DateTimeOffset.UtcNow; +} diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Imports/WeixinMiniProgramReceiverImportDto.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Imports/WeixinMiniProgramReceiverImportDto.cs new file mode 100644 index 00000000..fe33e243 --- /dev/null +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Imports/WeixinMiniProgramReceiverImportDto.cs @@ -0,0 +1,11 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Contracts.Admin.Dtos.MessageTasks.Imports; + +public class WeixinMiniProgramReceiverImportDto +{ + [ImporterHeader(Name = "OpenId", IsAllowRepeat = false)] + [Required(ErrorMessage = "OpenId是必填的")] + public string OpenId { get; set; } = string.Empty; +} diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/MessageTaskReceiverDto.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/MessageTaskReceiverDto.cs index d86d7189..715f7881 100644 --- a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/MessageTaskReceiverDto.cs +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/MessageTaskReceiverDto.cs @@ -36,6 +36,11 @@ public void SetChannelUserIdentity(ChannelTypes channelType, string channelUserI case ChannelTypes.WebsiteMessage: SubjectId = new Guid(channelUserIdentity); break; + case ChannelTypes.WeixinWork: + case ChannelTypes.WeixinWorkWebhook: + case ChannelTypes.WeixinMiniProgram: + Account = channelUserIdentity; + break; default: break; } diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Validator/MessageTaskUpsertDtoValidator.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Validator/MessageTaskUpsertDtoValidator.cs index 6e93960f..4a7dc6b9 100644 --- a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Validator/MessageTaskUpsertDtoValidator.cs +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTasks/Validator/MessageTaskUpsertDtoValidator.cs @@ -11,7 +11,15 @@ public MessageTaskUpsertDtoValidator() RuleFor(inputDto => inputDto.ChannelId).Required().When(m => !m.IsDraft); RuleFor(inputDto => inputDto.EntityId).Required().When(m => m.EntityType == MessageEntityTypes.Template).When(m => !m.IsDraft); RuleFor(inputDto => inputDto.EntityType).IsInEnum().When(m => !m.IsDraft); + RuleFor(inputDto => inputDto.EntityType) + .Equal(MessageEntityTypes.Template) + .WithMessage("WeixinMiniProgramOnlySupportsTemplateMessage") + .When(m => m.ChannelType == ChannelTypes.WeixinMiniProgram && !m.IsDraft); RuleFor(inputDto => inputDto.ReceiverType).IsInEnum().When(m => !m.IsDraft); + RuleFor(inputDto => inputDto.ReceiverType) + .NotEqual(ReceiverTypes.Broadcast) + .WithMessage("WeixinMiniProgramDoesNotSupportBroadcast") + .When(m => m.ChannelType == ChannelTypes.WeixinMiniProgram && !m.IsDraft); RuleFor(inputDto => inputDto.SelectReceiverType).IsInEnum().When(m => !m.IsDraft); RuleFor(inputDto => inputDto.Receivers).Required().When(m => m.ReceiverType == ReceiverTypes.Assign && !m.IsDraft); RuleFor(inputDto => inputDto.MessageInfo).SetValidator(new MessageInfoUpsertDtoValidator()).When(m => m.EntityType == MessageEntityTypes.Ordinary).When(m => !m.IsDraft); diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTemplates/WeixinMiniProgramTemplateDto.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTemplates/WeixinMiniProgramTemplateDto.cs new file mode 100644 index 00000000..10e94694 --- /dev/null +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Dtos/MessageTemplates/WeixinMiniProgramTemplateDto.cs @@ -0,0 +1,21 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Contracts.Admin.Dtos.MessageTemplates; + +public class WeixinMiniProgramTemplateDto +{ + public Guid ChannelId { get; set; } + + public string TemplateId { get; set; } = string.Empty; + + public string Title { get; set; } = string.Empty; + + public string Content { get; set; } = string.Empty; + + public string Example { get; set; } = string.Empty; + + public int TemplateType { get; set; } + + public List Items { get; set; } = new(); +} diff --git a/src/Contracts/Masa.Mc.Contracts.Admin/Options/Channels/WeixinMiniProgramChannelOptions.cs b/src/Contracts/Masa.Mc.Contracts.Admin/Options/Channels/WeixinMiniProgramChannelOptions.cs new file mode 100644 index 00000000..54a146be --- /dev/null +++ b/src/Contracts/Masa.Mc.Contracts.Admin/Options/Channels/WeixinMiniProgramChannelOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Contracts.Admin.Options.Channels; + +public class WeixinMiniProgramChannelOptions +{ + public string AppId { get; set; } = string.Empty; + + public string AppSecret { get; set; } = string.Empty; + + public string Token { get; set; } = string.Empty; + + public string EncodingAESKey { get; set; } = string.Empty; + + public string MiniprogramState { get; set; } = "formal"; + + public string Lang { get; set; } = "zh_CN"; +} diff --git a/src/Domain/Masa.Mc.Domain.Shared/Channels/ChannelTypes.cs b/src/Domain/Masa.Mc.Domain.Shared/Channels/ChannelTypes.cs index 50b4adb3..77c49eb9 100644 --- a/src/Domain/Masa.Mc.Domain.Shared/Channels/ChannelTypes.cs +++ b/src/Domain/Masa.Mc.Domain.Shared/Channels/ChannelTypes.cs @@ -11,4 +11,5 @@ public enum ChannelTypes App, WeixinWork, WeixinWorkWebhook, -} \ No newline at end of file + WeixinMiniProgram, +} diff --git a/src/Domain/Masa.Mc.Domain.Shared/MessageTemplates/WeixinMiniProgramTemplateTypes.cs b/src/Domain/Masa.Mc.Domain.Shared/MessageTemplates/WeixinMiniProgramTemplateTypes.cs new file mode 100644 index 00000000..e05afa70 --- /dev/null +++ b/src/Domain/Masa.Mc.Domain.Shared/MessageTemplates/WeixinMiniProgramTemplateTypes.cs @@ -0,0 +1,10 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Domain.Shared.MessageTemplates; + +public enum WeixinMiniProgramTemplateTypes +{ + OneTime = 2, + LongTerm = 3 +} diff --git a/src/Domain/Masa.Mc.Domain/Channels/Aggregates/ChannelType.cs b/src/Domain/Masa.Mc.Domain/Channels/Aggregates/ChannelType.cs index 8b7242f8..74debbd6 100644 --- a/src/Domain/Masa.Mc.Domain/Channels/Aggregates/ChannelType.cs +++ b/src/Domain/Masa.Mc.Domain/Channels/Aggregates/ChannelType.cs @@ -17,6 +17,8 @@ public class ChannelType : Enumeration public static ChannelType WeixinWorkWebhook = new WeixinWorkWebhookChannel(); + public static ChannelType WeixinMiniProgram = new WeixinMiniProgramChannel(); + public ChannelType(int id, string name) : base(id, name) { } @@ -193,4 +195,35 @@ public override string GetChannelUserIdentity(UserModel user) public override bool SupportsBroadcast => false; } -} \ No newline at end of file + + private class WeixinMiniProgramChannel : ChannelType + { + public WeixinMiniProgramChannel() : base(7, nameof(WeixinMiniProgram)) { } + + public override SendMessageEvent GetSendMessageEvent(Guid channelId, MessageData messageData, MessageTaskHistory messageTaskHistory) + { + return new SendWeixinMiniProgramMessageEvent(channelId, messageData, messageTaskHistory); + } + + public override SendSimpleMessageEvent GetSendSimpleMessageEvent(string channelUserIdentity, string channelCode, MessageData messageData, ExtraPropertyDictionary variables, string systemId) + { + return new SendSimpleWeixinMiniProgramMessageEvent(channelUserIdentity, channelCode, messageData) + { + Variables = variables, + SystemId = systemId + }; + } + + public override RetryMessageEvent GetRetryMessageEvent(Guid messageRecordId) + { + return new RetryWeixinMiniProgramMessageEvent(messageRecordId); + } + + public override string GetChannelUserIdentity(UserModel user) + { + return string.Empty; + } + + public override bool SupportsBroadcast => false; + } +} diff --git a/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs b/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs index cca6945c..8225bb07 100644 --- a/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs +++ b/src/Domain/Masa.Mc.Domain/MessageRecords/Aggregates/MessageRecord.cs @@ -87,6 +87,11 @@ public void UpdateResult(bool success, string failureReason) { Success = success; FailureReason = failureReason; + + if (MessageTaskHistoryId != Guid.Empty) + { + AddDomainEvent(new UpdateMessageTaskHistoryStatusEvent(MessageTaskHistoryId)); + } } public virtual T GetDataValue(string name) diff --git a/src/Domain/Masa.Mc.Domain/MessageRecords/Events/RetryWeixinMiniProgramMessageEvent.cs b/src/Domain/Masa.Mc.Domain/MessageRecords/Events/RetryWeixinMiniProgramMessageEvent.cs new file mode 100644 index 00000000..f6dc0a34 --- /dev/null +++ b/src/Domain/Masa.Mc.Domain/MessageRecords/Events/RetryWeixinMiniProgramMessageEvent.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Domain.MessageRecords.Events; + +public record RetryWeixinMiniProgramMessageEvent : RetryMessageEvent +{ + public RetryWeixinMiniProgramMessageEvent(Guid messageRecordId) + : base(messageRecordId) + { + + } +} diff --git a/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendSimpleWeixinMiniProgramMessageEvent.cs b/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendSimpleWeixinMiniProgramMessageEvent.cs new file mode 100644 index 00000000..7dd76cae --- /dev/null +++ b/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendSimpleWeixinMiniProgramMessageEvent.cs @@ -0,0 +1,17 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Domain.MessageTasks.Events; + +public record SendSimpleWeixinMiniProgramMessageEvent : SendSimpleMessageEvent +{ + public ExtraPropertyDictionary Variables { get; set; } + + public string SystemId { get; set; } = string.Empty; + + public SendSimpleWeixinMiniProgramMessageEvent(string channelUserIdentity, string channelCode, MessageData messageData) + : base(channelUserIdentity, channelCode, messageData) + { + + } +} diff --git a/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendWeixinMiniProgramMessageEvent.cs b/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendWeixinMiniProgramMessageEvent.cs new file mode 100644 index 00000000..9a19ec4d --- /dev/null +++ b/src/Domain/Masa.Mc.Domain/MessageTasks/Events/SendWeixinMiniProgramMessageEvent.cs @@ -0,0 +1,13 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Domain.MessageTasks.Events; + +public record SendWeixinMiniProgramMessageEvent : SendMessageEvent +{ + public SendWeixinMiniProgramMessageEvent(Guid channelId, MessageData messageData, MessageTaskHistory messageTaskHistory) + : base(channelId, messageData, messageTaskHistory) + { + + } +} diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs new file mode 100644 index 00000000..c4df7a42 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.Designer.cs @@ -0,0 +1,1442 @@ +// +using System; +using Masa.Mc.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Masa.Mc.EntityFrameworkCore.PostgreSql.Migrations +{ + [DbContext(typeof(McDbContext))] + [Migration("20260701032951_AddMessageRecordHistorySuccessIndex")] + partial class AddMessageRecordHistorySuccessIndex + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Masa.BuildingBlocks.Dispatcher.IntegrationEvents.Logs.IntegrationEventLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("EventId") + .HasColumnType("uuid"); + + b.Property("EventTypeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExpandContent") + .IsRequired() + .HasColumnType("text"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .HasMaxLength(36) + .HasColumnType("character varying(36)") + .HasColumnName("RowVersion"); + + b.Property("State") + .HasColumnType("integer"); + + b.Property("TimesSent") + .HasColumnType("integer"); + + b.Property("TransactionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex(new[] { "EventId", "RowVersion" }, "IX_EventId_Version"); + + b.HasIndex(new[] { "State", "ModificationTime" }, "IX_State_MTime"); + + b.HasIndex(new[] { "State", "TimesSent", "ModificationTime" }, "IX_State_TimesSent_MTime"); + + b.ToTable("IntegrationEventLog", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Apps.Aggregates.AppDeviceToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DeviceToken") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Platform") + .HasColumnType("integer"); + + b.Property("RegisteredTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId", "UserId", "Platform"); + + b.ToTable("AppDeviceTokens", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Apps.Aggregates.AppVendorConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Options") + .IsRequired() + .HasColumnType("text"); + + b.Property("Vendor") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId", "Vendor"); + + b.ToTable("AppVendorConfigs", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.AppChannel", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Code"); + + b.Property("Color") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Color"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("Provider") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Provider"); + + b.Property("Scheme") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Scheme"); + + b.Property("SchemeField") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("SchemeField"); + + b.Property("Type") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("Id"); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.Channel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasColumnName("Code"); + + b.Property("Color") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Color"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayName") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("DisplayName"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Provider") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Provider"); + + b.Property("Scheme") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Scheme"); + + b.Property("SchemeField") + .IsRequired() + .ValueGeneratedOnUpdateSometimes() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("SchemeField"); + + b.Property("Type") + .ValueGeneratedOnUpdateSometimes() + .HasColumnType("integer") + .HasColumnName("Type"); + + b.HasKey("Id"); + + b.HasIndex("Code"); + + b.HasIndex("Type"); + + b.ToTable("Channels", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageInfos.Aggregates.MessageInfo", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.ToTable("MessageInfos", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageReceipts.Aggregates.SmsInbound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AddSerial") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Mobile") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SmsContent") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.HasKey("Id"); + + b.HasIndex("Provider"); + + b.HasIndex("SendTime"); + + b.HasIndex("ChannelId", "SendTime"); + + b.HasIndex("Mobile", "SendTime"); + + b.ToTable("SmsInbounds", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageRecords.Aggregates.MessageRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ExpectSendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("FailureReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("MessageEntityId") + .HasColumnType("uuid"); + + b.Property("MessageEntityType") + .HasColumnType("integer"); + + b.Property("MessageId") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("MessageTaskId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Success") + .HasColumnType("boolean"); + + b.Property("SystemId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("MessageId"); + + b.HasIndex("UserId"); + + b.HasIndex("MessageTaskHistoryId", "Success"); + + b.ToTable("MessageRecords", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageReceiverUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("Platform") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MessageTaskHistoryId", "ChannelUserIdentity", "Platform"); + + b.ToTable("MessageReceiverUsers", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("EntityId") + .HasColumnType("uuid"); + + b.Property("EntityType") + .HasColumnType("integer"); + + b.Property("ExpectSendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDraft") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("ReceiverType") + .HasColumnType("integer"); + + b.Property("Receivers") + .IsRequired() + .HasColumnType("text"); + + b.Property("SchedulerJobId") + .HasColumnType("uuid"); + + b.Property("SelectReceiverType") + .HasColumnType("integer"); + + b.Property("SendRules") + .IsRequired() + .HasColumnType("text"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Sign") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Source") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("SystemId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Variables") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("Source"); + + b.ToTable("MessageTasks", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CompletionTime") + .HasColumnType("timestamp with time zone"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsTest") + .HasColumnType("boolean"); + + b.Property("MessageTaskId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("SchedulerTaskId") + .HasColumnType("uuid"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TaskHistoryNo") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("WithdrawTime") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("MessageTaskId"); + + b.ToTable("MessageTaskHistorys", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("integer"); + + b.Property("AuditTime") + .HasColumnType("timestamp with time zone"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Example") + .IsRequired() + .HasColumnType("text"); + + b.Property("InvalidTime") + .HasColumnType("timestamp with time zone"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsStatic") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Options") + .IsRequired() + .HasColumnType("text"); + + b.Property("PerDayLimit") + .HasColumnType("bigint"); + + b.Property("Sign") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TemplateId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("Code"); + + b.ToTable("MessageTemplates", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("DisplayText") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("MappingCode") + .IsRequired() + .HasColumnType("text"); + + b.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("MessageTemplateId"); + + b.HasIndex("Code", "MessageTemplateId"); + + b.ToTable("MessageTemplateItems", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.SmsTemplate", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AuditReason") + .IsRequired() + .HasColumnType("text"); + + b.Property("AuditStatus") + .HasColumnType("integer"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("TemplateCode") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateContent") + .IsRequired() + .HasColumnType("text"); + + b.Property("TemplateName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("TemplateType") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.ToTable("SmsTemplates", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("ReceiverGroups", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("ChannelProvider") + .HasColumnType("integer"); + + b.Property("ChannelType") + .HasColumnType("integer"); + + b.Property("ChannelUserIdentity") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("Keyword") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("LastInboundMessageId") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("ResubscribedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("ScopeRefId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("ScopeType") + .HasColumnType("integer"); + + b.Property("Source") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UnsubscribedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("LastInboundMessageId"); + + b.HasIndex("UserId"); + + b.HasIndex("ChannelType", "ChannelProvider", "UnsubscribedAt"); + + b.HasIndex("ChannelUserIdentity", "Status", "ScopeType"); + + b.HasIndex("ChannelId", "ChannelUserIdentity", "Status", "UnsubscribedAt"); + + b.HasIndex("ChannelId", "ChannelUserIdentity", "ScopeType", "ScopeRefId", "Status", "UnsubscribedAt"); + + b.ToTable("Unsubscriptions", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.UnsubscriptionTimeline", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Action") + .HasColumnType("integer"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("Detail") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("OccurredAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UnsubscriptionId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UnsubscriptionId", "OccurredAt"); + + b.HasIndex("OccurredAt", "Action", "UnsubscriptionId"); + + b.ToTable("UnsubscriptionTimelines", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ChannelId") + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsRead") + .HasColumnType("boolean"); + + b.Property("IsWithdrawn") + .HasColumnType("boolean"); + + b.Property("LinkUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("MessageTaskHistoryId") + .HasColumnType("uuid"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("ReadTime") + .HasColumnType("timestamp with time zone"); + + b.Property("SendTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChannelId"); + + b.HasIndex("UserId", "ChannelId"); + + b.ToTable("WebsiteMessages", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessageCursor", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Creator") + .HasColumnType("uuid"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("ModificationTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Modifier") + .HasColumnType("uuid"); + + b.Property("UpdateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("WebsiteMessageCursors", (string)null); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Channels.Aggregates.AppChannel", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.Channel", null) + .WithOne() + .HasForeignKey("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageInfos.Aggregates.MessageInfo", b => + { + b.OwnsOne("Masa.Mc.Domain.MessageInfos.Aggregates.MessageContent", "MessageContent", b1 => + { + b1.Property("MessageInfoId") + .HasColumnType("uuid"); + + b1.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Content"); + + b1.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b1.Property("IsJump") + .HasMaxLength(128) + .HasColumnType("boolean") + .HasColumnName("IsJump"); + + b1.Property("JumpUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("JumpUrl"); + + b1.Property("Markdown") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Markdown"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Title"); + + b1.HasKey("MessageInfoId"); + + b1.ToTable("MessageInfos"); + + b1.WithOwner() + .HasForeignKey("MessageInfoId"); + }); + + b.Navigation("MessageContent") + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageRecords.Aggregates.MessageRecord", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageReceiverUser", b => + { + b.HasOne("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", null) + .WithMany("ReceiverUsers") + .HasForeignKey("MessageTaskHistoryId"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId"); + + b.Navigation("Channel"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.HasOne("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTask", "MessageTask") + .WithMany() + .HasForeignKey("MessageTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MessageTask"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.OwnsOne("Masa.Mc.Domain.MessageInfos.Aggregates.MessageContent", "MessageContent", b1 => + { + b1.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b1.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Content"); + + b1.Property("ExtraProperties") + .IsRequired() + .HasColumnType("text") + .HasColumnName("ExtraProperties"); + + b1.Property("IsJump") + .HasMaxLength(128) + .HasColumnType("boolean") + .HasColumnName("IsJump"); + + b1.Property("JumpUrl") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasColumnName("JumpUrl"); + + b1.Property("Markdown") + .IsRequired() + .HasColumnType("text") + .HasColumnName("Markdown"); + + b1.Property("Title") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Title"); + + b1.HasKey("MessageTemplateId"); + + b1.ToTable("MessageTemplates"); + + b1.WithOwner() + .HasForeignKey("MessageTemplateId"); + }); + + b.OwnsOne("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateUnsubscribeConfig", "UnsubscribeConfig", b1 => + { + b1.Property("MessageTemplateId") + .HasColumnType("uuid"); + + b1.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasColumnName("Enabled"); + + b1.Property("ResubscribeAutoReplyTemplateId") + .HasColumnType("uuid") + .HasColumnName("ResubscribeAutoReplyTemplateId"); + + b1.Property("ResubscribeKeyword") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("") + .HasColumnName("ResubscribeKeyword"); + + b1.Property("UnsubscribeAutoReplyTemplateId") + .HasColumnType("uuid") + .HasColumnName("UnsubscribeAutoReplyTemplateId"); + + b1.Property("UnsubscribeKeyword") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasDefaultValue("") + .HasColumnName("UnsubscribeKeyword"); + + b1.HasKey("MessageTemplateId"); + + b1.ToTable("MessageTemplateUnsubscribeConfigs", (string)null); + + b1.WithOwner() + .HasForeignKey("MessageTemplateId"); + }); + + b.Navigation("MessageContent") + .IsRequired(); + + b.Navigation("UnsubscribeConfig"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplateItem", b => + { + b.HasOne("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", null) + .WithMany("Items") + .HasForeignKey("MessageTemplateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroup", b => + { + b.OwnsMany("Masa.Mc.Domain.ReceiverGroups.Aggregates.ReceiverGroupItem", "Items", b1 => + { + b1.Property("GroupId") + .HasColumnType("uuid"); + + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b1.Property("Avatar") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b1.Property("DisplayName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b1.Property("SubjectId") + .HasColumnType("uuid"); + + b1.Property("Type") + .HasColumnType("integer"); + + b1.HasKey("GroupId", "Id"); + + b1.ToTable("ReceiverGroupItems", (string)null); + + b1.WithOwner() + .HasForeignKey("GroupId"); + }); + + b.Navigation("Items"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.UnsubscriptionTimeline", b => + { + b.HasOne("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", null) + .WithMany("Timelines") + .HasForeignKey("UnsubscriptionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessage", b => + { + b.HasOne("Masa.Mc.Domain.Channels.Aggregates.AppChannel", "Channel") + .WithMany() + .HasForeignKey("ChannelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.OwnsMany("Masa.Mc.Domain.WebsiteMessages.Aggregates.WebsiteMessageTag", "Tags", b1 => + { + b1.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b1.Property("ChannelId") + .HasColumnType("uuid"); + + b1.Property("CreationTime") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone"); + + b1.Property("Tag") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("Tag"); + + b1.Property("UserId") + .HasColumnType("uuid"); + + b1.Property("WebsiteMessageId") + .HasColumnType("uuid"); + + b1.HasKey("Id"); + + b1.HasIndex("ChannelId"); + + b1.HasIndex("Tag"); + + b1.HasIndex("UserId"); + + b1.HasIndex("WebsiteMessageId"); + + b1.ToTable("WebsiteMessageTags", (string)null); + + b1.WithOwner() + .HasForeignKey("WebsiteMessageId"); + }); + + b.Navigation("Channel"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTasks.Aggregates.MessageTaskHistory", b => + { + b.Navigation("ReceiverUsers"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.MessageTemplates.Aggregates.MessageTemplate", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Masa.Mc.Domain.Unsubscriptions.Aggregates.Unsubscription", b => + { + b.Navigation("Timelines"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs new file mode 100644 index 00000000..80d52b3f --- /dev/null +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/20260701032951_AddMessageRecordHistorySuccessIndex.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Masa.Mc.EntityFrameworkCore.PostgreSql.Migrations +{ + public partial class AddMessageRecordHistorySuccessIndex : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_MessageRecords_MessageTaskHistoryId", + table: "MessageRecords"); + + migrationBuilder.CreateIndex( + name: "IX_MessageRecords_MessageTaskHistoryId_Success", + table: "MessageRecords", + columns: new[] { "MessageTaskHistoryId", "Success" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_MessageRecords_MessageTaskHistoryId_Success", + table: "MessageRecords"); + + migrationBuilder.CreateIndex( + name: "IX_MessageRecords_MessageTaskHistoryId", + table: "MessageRecords", + column: "MessageTaskHistoryId"); + } + } +} diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs index 197a0898..0f8f6f2c 100644 --- a/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore.PostgreSql/Migrations/McDbContextModelSnapshot.cs @@ -478,10 +478,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("MessageId"); - b.HasIndex("MessageTaskHistoryId"); - b.HasIndex("UserId"); + b.HasIndex("MessageTaskHistoryId", "Success"); + b.ToTable("MessageRecords", (string)null); }); diff --git a/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs b/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs index 71f0f63b..478cdac6 100644 --- a/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs +++ b/src/Infrastructure/Masa.Mc.EntityFrameworkCore/McDbContextModelBuilderExtensions.cs @@ -135,7 +135,7 @@ public static void ConfigureMC(this ModelBuilder builder) b.Property(x => x.ExtraProperties).HasConversion(new ExtraPropertiesValueConverter()).Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer()); b.Property(x => x.Variables).HasConversion(new ExtraPropertiesValueConverter()).Metadata.SetValueComparer(new ExtraPropertyDictionaryValueComparer()); b.HasIndex(x => x.UserId); - b.HasIndex(x => x.MessageTaskHistoryId); + b.HasIndex(x => new { x.MessageTaskHistoryId, x.Success }); b.HasIndex(x => x.MessageId); }); diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Extensions/ServiceCollectionExtensions.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..73fb700c --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,24 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Extensions; + +public static class ServiceCollectionExtensions +{ + public static IServiceCollection AddWeixinMiniProgram(this IServiceCollection services) + { + services.Configure>(options => + { + if (!options.Contributors.Exists(x => x.Name == AsyncLocalOptionsResolveContributor.CONTRIBUTOR_NAME)) + { + options.Contributors.Insert(0, new AsyncLocalOptionsResolveContributor()); + } + }); + services.TryAddSingleton, AsyncLocalAccessor>(); + services.TryAddTransient, ProviderAsyncLocal>(); + services.TryAddTransient, WeixinMiniProgramOptionsResolver>(); + services.TryAddSingleton(); + services.TryAddSingleton(); + return services; + } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/IWeixinMiniProgramOptions.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/IWeixinMiniProgramOptions.cs new file mode 100644 index 00000000..f368be66 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/IWeixinMiniProgramOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Infrastructure.OptionsResolve; + +public interface IWeixinMiniProgramOptions : IOptions +{ + string AppId { get; set; } + + string AppSecret { get; set; } + + string Token { get; set; } + + string EncodingAESKey { get; set; } + + string MiniprogramState { get; set; } + + string Lang { get; set; } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/WeixinMiniProgramOptionsResolver.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/WeixinMiniProgramOptionsResolver.cs new file mode 100644 index 00000000..485cb1ed --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Infrastructure/OptionsResolve/WeixinMiniProgramOptionsResolver.cs @@ -0,0 +1,37 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Infrastructure.OptionsResolve; + +public class WeixinMiniProgramOptionsResolver : IOptionsResolver +{ + private readonly IServiceProvider _serviceProvider; + private readonly ResolveOptions _options; + + public WeixinMiniProgramOptionsResolver(IServiceProvider serviceProvider, + IOptions> resolveOptions) + { + _serviceProvider = serviceProvider; + _options = resolveOptions.Value; + } + + public async Task ResolveAsync() + { + using (var serviceScope = _serviceProvider.CreateScope()) + { + var context = new OptionsResolveContext(serviceScope.ServiceProvider); + + foreach (var contributor in _options.Contributors) + { + await contributor.ResolveAsync(context); + + if (context.Options != null) + { + return context.Options; + } + } + } + + return new WeixinMiniProgramOptions(); + } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Masa.Mc.Infrastructure.Weixin.MiniProgram.csproj b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Masa.Mc.Infrastructure.Weixin.MiniProgram.csproj new file mode 100644 index 00000000..35ded913 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Masa.Mc.Infrastructure.Weixin.MiniProgram.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/Response/WeixinMiniProgramMessageResponse.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/Response/WeixinMiniProgramMessageResponse.cs new file mode 100644 index 00000000..2bd05c00 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/Response/WeixinMiniProgramMessageResponse.cs @@ -0,0 +1,15 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Model.Response; + +public class WeixinMiniProgramMessageResponse : WxJsonResult +{ + public int ErrCode => ErrorCodeValue; + + public string ErrMsg => errmsg ?? string.Empty; + + public string msgid { get; set; } = string.Empty; + + public string MsgId => msgid; +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/WeixinMiniProgramSubscribeMessage.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/WeixinMiniProgramSubscribeMessage.cs new file mode 100644 index 00000000..e289cf5f --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Model/WeixinMiniProgramSubscribeMessage.cs @@ -0,0 +1,23 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Model; + +public class WeixinMiniProgramSubscribeMessage +{ + public string ToUser { get; } + + public string TemplateId { get; } + + public string Page { get; } + + public Dictionary Data { get; } + + public WeixinMiniProgramSubscribeMessage(string toUser, string templateId, string page, Dictionary data) + { + ToUser = toUser; + TemplateId = templateId; + Page = page; + Data = data; + } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/IWeixinMiniProgramSender.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/IWeixinMiniProgramSender.cs new file mode 100644 index 00000000..413cf08f --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/IWeixinMiniProgramSender.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Sender; + +public interface IWeixinMiniProgramSender +{ + Task SendSubscribeMessageAsync(WeixinMiniProgramSubscribeMessage message); +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/WeixinMiniProgramSender.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/WeixinMiniProgramSender.cs new file mode 100644 index 00000000..2e1f6146 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Sender/WeixinMiniProgramSender.cs @@ -0,0 +1,40 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Sender; + +public class WeixinMiniProgramSender : IWeixinMiniProgramSender +{ + private static readonly Regex CharacterStringRegex = new(@"^[\x21-\x7E]{1,32}$", RegexOptions.Compiled); + + private readonly IOptionsResolver _optionsResolver; + + public WeixinMiniProgramSender(IOptionsResolver optionsResolver) + { + _optionsResolver = optionsResolver; + } + + public async Task SendSubscribeMessageAsync(WeixinMiniProgramSubscribeMessage message) + { + var options = await _optionsResolver.ResolveAsync(); + // 稳定版接口(stable_token): 同一 AppId 跨项目/实例返回同一 token 且刷新不使别处失效, + // 避免普通 access_token 互相顶掉导致的 40001 invalid credential。 + var accessTokenResult = await Senparc.Weixin.MP.CommonAPIs.CommonApi.GetStableAccessTokenAsync(options.AppId, options.AppSecret); + var accessToken = accessTokenResult.access_token; + var data = new TemplateMessageData(message.Data.ToDictionary(x => x.Key, x => new TemplateMessageDataValue(x.Value))); + var result = await MessageApi.SendSubscribeAsync( + accessToken, + message.ToUser, + message.TemplateId, + data, + message.Page, + options.MiniprogramState, + options.Lang); + + return new WeixinMiniProgramMessageResponse + { + errcode = result.errcode, + errmsg = result.errmsg ?? string.Empty + }; + } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/IWeixinMiniProgramTemplateProvider.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/IWeixinMiniProgramTemplateProvider.cs new file mode 100644 index 00000000..722367d9 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/IWeixinMiniProgramTemplateProvider.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Template; + +public interface IWeixinMiniProgramTemplateProvider +{ + Task> GetTemplateListAsync(CancellationToken cancellationToken = default); +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplate.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplate.cs new file mode 100644 index 00000000..f809dd36 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplate.cs @@ -0,0 +1,24 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Template; + +public class WeixinMiniProgramTemplate +{ + public string TemplateId { get; set; } = string.Empty; + + public string Title { get; set; } = string.Empty; + + public string Content { get; set; } = string.Empty; + + public string Example { get; set; } = string.Empty; + + public int Type { get; set; } + + public List Items { get; set; } = new(); +} + +public class WeixinMiniProgramTemplateItem +{ + public string Code { get; set; } = string.Empty; +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplateProvider.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplateProvider.cs new file mode 100644 index 00000000..c890e861 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/Template/WeixinMiniProgramTemplateProvider.cs @@ -0,0 +1,61 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram.Template; + +public class WeixinMiniProgramTemplateProvider : IWeixinMiniProgramTemplateProvider +{ + private static readonly Regex TemplateItemRegex = new(@"\{\{(?[^}.]+)(?:\.DATA)?\}\}", RegexOptions.Compiled); + + private readonly IOptionsResolver _optionsResolver; + + public WeixinMiniProgramTemplateProvider(IOptionsResolver optionsResolver) + { + _optionsResolver = optionsResolver; + } + + public async Task> GetTemplateListAsync(CancellationToken cancellationToken = default) + { + var options = await _optionsResolver.ResolveAsync(); + // 稳定版接口(stable_token): 同一 AppId 跨项目/实例返回同一 token 且刷新不使别处失效, + // 避免普通 access_token 互相顶掉导致的 40001 invalid credential。 + var accessTokenResult = await Senparc.Weixin.MP.CommonAPIs.CommonApi.GetStableAccessTokenAsync(options.AppId, options.AppSecret); + var accessToken = accessTokenResult.access_token; + cancellationToken.ThrowIfCancellationRequested(); + + var response = await Senparc.Weixin.Open.WxaAPIs.NewTmpl.NewTmplApi.GetTemplateListAsync(accessToken); + if (response.errcode != Senparc.Weixin.ReturnCode.请求成功) + { + throw new InvalidOperationException(response.errmsg); + } + + return (response.data ?? new()) + .Select(x => + { + var content = x.content ?? string.Empty; + return new WeixinMiniProgramTemplate + { + TemplateId = x.priTmplId ?? string.Empty, + Title = x.title ?? string.Empty, + Content = content, + Example = x.example ?? string.Empty, + Type = x.type, + Items = ParseTemplateItems(content) + }; + }) + .ToList(); + } + + private static List ParseTemplateItems(string content) + { + return TemplateItemRegex.Matches(content) + .Select(match => match.Groups["code"].Value) + .Where(code => !string.IsNullOrWhiteSpace(code)) + .Distinct() + .Select(code => new WeixinMiniProgramTemplateItem + { + Code = code + }) + .ToList(); + } +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/WeixinMiniProgramOptions.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/WeixinMiniProgramOptions.cs new file mode 100644 index 00000000..c4b783a5 --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/WeixinMiniProgramOptions.cs @@ -0,0 +1,19 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Infrastructure.Weixin.MiniProgram; + +public class WeixinMiniProgramOptions : IWeixinMiniProgramOptions +{ + public string AppId { get; set; } = string.Empty; + + public string AppSecret { get; set; } = string.Empty; + + public string Token { get; set; } = string.Empty; + + public string EncodingAESKey { get; set; } = string.Empty; + + public string MiniprogramState { get; set; } = "formal"; + + public string Lang { get; set; } = "zh_CN"; +} diff --git a/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/_Imports.cs b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/_Imports.cs new file mode 100644 index 00000000..6e6c314b --- /dev/null +++ b/src/Infrastructure/Masa.Mc.Infrastructure.Weixin.MiniProgram/_Imports.cs @@ -0,0 +1,24 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +global using System.Text.Json.Serialization; +global using System.Text.RegularExpressions; +global using System.Security.Cryptography; +global using System.Text; +global using System.Text.Json; +global using System.Xml.Linq; +global using Masa.Mc.Infrastructure.OptionsResolve; +global using Masa.Mc.Infrastructure.OptionsResolve.Contributors; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Infrastructure.OptionsResolve; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Model; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Model.Response; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Sender; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Template; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.DependencyInjection.Extensions; +global using Microsoft.Extensions.Options; +global using Senparc.Weixin.Entities; +global using Senparc.Weixin.Entities.TemplateMessage; +global using Senparc.Weixin.Tencent; +global using Senparc.Weixin.WxOpen.AdvancedAPIs; +global using Senparc.Weixin.WxOpen.Containers; diff --git a/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryEmailMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryEmailMessageEventHandler.cs index edf85d51..8536b42f 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryEmailMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryEmailMessageEventHandler.cs @@ -42,15 +42,7 @@ public async Task HandleEventAsync(RetryEmailMessageEvent eto) var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == messageRecord.ChannelId); if (channel == null) return; - var options = new SmtpEmailOptions - { - Host = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Smtp)), - Port = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Port)), - UserName = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)), - Password = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Password)), - EnableSsl = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Ssl)), - DefaultFromAddress = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)) - }; + var options = channel.GetSmtpEmailOptions(); using (_emailAsyncLocal.Change(options)) { var messageData = await _taskDomainService.GetMessageDataAsync(messageRecord.MessageTaskId, messageRecord.Variables); diff --git a/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryWeixinMiniProgramMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryWeixinMiniProgramMessageEventHandler.cs new file mode 100644 index 00000000..c29f8aba --- /dev/null +++ b/src/Services/Masa.Mc.Service/Application/MessageRecords/EventHandler/RetryWeixinMiniProgramMessageEventHandler.cs @@ -0,0 +1,124 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Application.MessageRecords.EventHandler; + +public class RetryWeixinMiniProgramMessageEventHandler +{ + private readonly IProviderAsyncLocal _asyncLocal; + private readonly IWeixinMiniProgramSender _sender; + private readonly IChannelRepository _channelRepository; + private readonly IMessageRecordRepository _messageRecordRepository; + private readonly IMessageTemplateRepository _templateRepository; + private readonly MessageTemplateDomainService _messageTemplateDomainService; + private readonly II18n _i18n; + private readonly ILogger _logger; + + public RetryWeixinMiniProgramMessageEventHandler(IProviderAsyncLocal asyncLocal + , IWeixinMiniProgramSender sender + , IChannelRepository channelRepository + , IMessageRecordRepository messageRecordRepository + , IMessageTemplateRepository templateRepository + , MessageTemplateDomainService messageTemplateDomainService + , II18n i18n + , ILogger logger) + { + _asyncLocal = asyncLocal; + _sender = sender; + _channelRepository = channelRepository; + _messageRecordRepository = messageRecordRepository; + _templateRepository = templateRepository; + _messageTemplateDomainService = messageTemplateDomainService; + _i18n = i18n; + _logger = logger; + } + + [EventHandler] + public async Task HandleEventAsync(RetryWeixinMiniProgramMessageEvent eto) + { + var messageRecord = await _messageRecordRepository.FindAsync(x => x.Id == eto.MessageRecordId); + if (messageRecord == null) return; + + var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == messageRecord.ChannelId); + if (channel == null) return; + + var messageTemplate = await _templateRepository.FindAsync(x => x.Id == messageRecord.MessageEntityId, false); + if (messageTemplate is null) + { + messageRecord.SetResult(false, _i18n.T("MessageTemplate")); + await _messageRecordRepository.UpdateAsync(messageRecord); + return; + } + + if (!await _messageTemplateDomainService.CheckSendUpperLimitAsync(messageTemplate, messageRecord.ChannelUserIdentity)) + { + messageRecord.SetResult(false, _i18n.T("DailySendingLimit")); + await _messageRecordRepository.UpdateAsync(messageRecord); + return; + } + + var templateId = messageRecord.GetDataValue(nameof(MessageTemplate.TemplateId)); + if (string.IsNullOrWhiteSpace(templateId)) + { + messageRecord.SetResult(false, "TemplateId is required"); + await _messageRecordRepository.UpdateAsync(messageRecord); + return; + } + + if (string.IsNullOrWhiteSpace(messageRecord.ChannelUserIdentity)) + { + messageRecord.SetResult(false, "OpenId is required"); + await _messageRecordRepository.UpdateAsync(messageRecord); + return; + } + + var page = messageTemplate.MessageContent.IsJump + ? Render(messageTemplate.MessageContent.JumpUrl, messageRecord.Variables) + : string.Empty; + var message = new WeixinMiniProgramSubscribeMessage( + messageRecord.ChannelUserIdentity, + templateId, + page, + BuildTemplateData(messageTemplate, messageRecord.Variables)); + + using (_asyncLocal.Change(channel.GetWeixinMiniProgramOptions())) + { + try + { + var response = await _sender.SendSubscribeMessageAsync(message); + if (response.ErrCode == 0) + { + messageRecord.SetResult(true, string.Empty, DateTimeOffset.UtcNow, response.MsgId); + } + else + { + messageRecord.SetResult(false, response.ErrMsg, DateTimeOffset.UtcNow, response.MsgId); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "RetryWeixinMiniProgramMessageEventHandler"); + messageRecord.SetResult(false, ex.Message); + } + } + + await _messageRecordRepository.UpdateAsync(messageRecord); + } + + private Dictionary BuildTemplateData(MessageTemplate messageTemplate, ExtraPropertyDictionary variables) + { + var convertedVariables = _messageTemplateDomainService.ConvertVariables(messageTemplate, variables); + return convertedVariables.ToDictionary(x => x.Key, x => x.Value?.ToString() ?? string.Empty); + } + + private string Render(string context, ExtraPropertyDictionary variables) + { + foreach (var item in variables) + { + context = context.Replace($"{{{{{item.Key}}}}}", item.Value?.ToString() ?? string.Empty); + } + + return context; + } + +} diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendEmailMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendEmailMessageEventHandler.cs index eb9a90cb..36e8f686 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendEmailMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendEmailMessageEventHandler.cs @@ -49,15 +49,7 @@ public async Task HandleEventAsync(SendEmailMessageEvent eto) return; } - var options = new SmtpEmailOptions - { - Host = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Smtp)) ?? string.Empty, - Port = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Port)), - UserName = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)) ?? string.Empty, - Password = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Password)) ?? string.Empty, - EnableSsl = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Ssl)), - DefaultFromAddress = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)) ?? string.Empty - }; + var options = channel.GetSmtpEmailOptions(); using (_emailAsyncLocal.Change(options)) { int okCount = 0; diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs index 27e4edf2..45bd8fb8 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendSmsMessageEventHandler.cs @@ -155,6 +155,12 @@ public async Task SaveAsync(SendSmsMessageEvent eto) var messageTaskHistory = eto.MessageTaskHistory; + // 存在待回执记录时保持发送中,等待回执完成后再汇总最终状态。 + if (eto.MessageRecords.Any(x => x.Success == null)) + { + return; + } + var result = !eto.MessageRecords.Any(x => x.Success != true) ? MessageTaskHistoryStatuses.Success : (!eto.MessageRecords.Any(x => x.Success == true) ? MessageTaskHistoryStatuses.Fail : MessageTaskHistoryStatuses.PartialFailure); messageTaskHistory.SetResult(result); await _messageTaskHistoryRepository.UpdateAsync(messageTaskHistory); diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinMiniProgramMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinMiniProgramMessageEventHandler.cs new file mode 100644 index 00000000..b01737d1 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinMiniProgramMessageEventHandler.cs @@ -0,0 +1,173 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Application.MessageTasks.EventHandler; + +public class SendWeixinMiniProgramMessageEventHandler +{ + private const string OnlySupportsTemplateMessage = "Weixin mini program channel only supports subscribe template messages"; + private readonly IProviderAsyncLocal _asyncLocal; + private readonly IWeixinMiniProgramSender _sender; + private readonly IChannelRepository _channelRepository; + private readonly IMessageRecordRepository _messageRecordRepository; + private readonly IMessageTaskHistoryRepository _messageTaskHistoryRepository; + private readonly IMessageTemplateRepository _templateRepository; + private readonly MessageTemplateDomainService _messageTemplateDomainService; + private readonly II18n _i18n; + private readonly ILogger _logger; + + public SendWeixinMiniProgramMessageEventHandler(IProviderAsyncLocal asyncLocal + , IWeixinMiniProgramSender sender + , IChannelRepository channelRepository + , IMessageRecordRepository messageRecordRepository + , IMessageTaskHistoryRepository messageTaskHistoryRepository + , IMessageTemplateRepository templateRepository + , MessageTemplateDomainService messageTemplateDomainService + , II18n i18n + , ILogger logger) + { + _asyncLocal = asyncLocal; + _sender = sender; + _channelRepository = channelRepository; + _messageRecordRepository = messageRecordRepository; + _messageTaskHistoryRepository = messageTaskHistoryRepository; + _templateRepository = templateRepository; + _messageTemplateDomainService = messageTemplateDomainService; + _i18n = i18n; + _logger = logger; + } + + [EventHandler] + public async Task HandleEventAsync(SendWeixinMiniProgramMessageEvent eto) + { + var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == eto.ChannelId); + var taskHistory = eto.MessageTaskHistory; + if (channel is null) + { + taskHistory.SetResult(MessageTaskHistoryStatuses.Fail); + await _messageTaskHistoryRepository.UpdateAsync(taskHistory); + return; + } + + var messageTemplate = eto.MessageData.MessageType == MessageEntityTypes.Template + ? await _templateRepository.FindAsync(x => x.Id == taskHistory.MessageTask.EntityId, false) + : null; + var templateId = eto.MessageData.GetDataValue(nameof(MessageTemplate.TemplateId)); + var options = channel.GetWeixinMiniProgramOptions(); + var messageRecords = new List(); + + using (_asyncLocal.Change(options)) + { + foreach (var item in taskHistory.ReceiverUsers) + { + var renderedData = eto.MessageData.RenderForReceiver(item.Variables); + var displayName = string.IsNullOrEmpty(renderedData.MessageContent.Title) ? taskHistory.MessageTask.DisplayName : renderedData.MessageContent.Title; + var messageRecord = new MessageRecord(item.UserId, item.ChannelUserIdentity, channel.Id, taskHistory.MessageTaskId, taskHistory.Id, item.Variables, displayName, taskHistory.SendTime, taskHistory.MessageTask.SystemId); + messageRecord.SetMessageEntity(taskHistory.MessageTask.EntityType, taskHistory.MessageTask.EntityId); + messageRecord.SetDataValue(nameof(MessageTemplate.TemplateId), templateId); + + await SendOrBlockAsync(messageRecord, messageTemplate, renderedData, templateId); + messageRecords.Add(messageRecord); + } + } + + await _messageRecordRepository.AddRangeAsync(messageRecords); + await UpdateTaskHistoryAsync(taskHistory, messageRecords); + } + + [EventHandler] + public async Task HandleEventAsync(SendSimpleWeixinMiniProgramMessageEvent eto) + { + var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Code == eto.ChannelCode); + if (channel is null) + { + return; + } + + var templateId = eto.MessageData.GetDataValue(nameof(MessageTemplate.TemplateId)); + var messageEntityId = eto.MessageData.GetDataValue(nameof(MessageTemplate.Id)); + var messageTemplate = eto.MessageData.MessageType == MessageEntityTypes.Template + ? await _templateRepository.FindAsync(x => x.Id == messageEntityId, false) + : null; + var messageRecord = new MessageRecord(Guid.Empty, eto.ChannelUserIdentity, channel.Id, Guid.Empty, Guid.Empty, eto.Variables, eto.MessageData.MessageContent.Title, DateTimeOffset.UtcNow, eto.SystemId); + messageRecord.SetMessageEntity(eto.MessageData.MessageType, messageEntityId); + messageRecord.SetDataValue(nameof(MessageTemplate.TemplateId), templateId); + messageRecord.SetDisplayName(eto.MessageData.GetDataValue(nameof(MessageTemplate.DisplayName))); + + using (_asyncLocal.Change(channel.GetWeixinMiniProgramOptions())) + { + await SendOrBlockAsync(messageRecord, messageTemplate, eto.MessageData, templateId); + } + + await _messageRecordRepository.AddAsync(messageRecord); + } + + private async Task SendOrBlockAsync(MessageRecord messageRecord, MessageTemplate? messageTemplate, MessageData messageData, string templateId) + { + if (messageData.MessageType != MessageEntityTypes.Template) + { + messageRecord.SetResult(false, OnlySupportsTemplateMessage); + return; + } + + if (messageTemplate is null) + { + messageRecord.SetResult(false, _i18n.T("MessageTemplate")); + return; + } + + if (string.IsNullOrWhiteSpace(templateId)) + { + messageRecord.SetResult(false, "TemplateId is required"); + return; + } + + if (string.IsNullOrWhiteSpace(messageRecord.ChannelUserIdentity)) + { + messageRecord.SetResult(false, "OpenId is required"); + return; + } + + if (!await _messageTemplateDomainService.CheckSendUpperLimitAsync(messageTemplate, messageRecord.ChannelUserIdentity)) + { + messageRecord.SetResult(false, _i18n.T("DailySendingLimit")); + return; + } + + var message = new WeixinMiniProgramSubscribeMessage( + messageRecord.ChannelUserIdentity, + templateId, + messageData.MessageContent.GetJumpUrl(), + BuildTemplateData(messageTemplate, messageRecord.Variables)); + + try + { + var response = await _sender.SendSubscribeMessageAsync(message); + if (response.ErrCode == 0) + { + messageRecord.SetResult(true, string.Empty, DateTimeOffset.UtcNow, response.MsgId); + } + else + { + messageRecord.SetResult(false, response.ErrMsg, DateTimeOffset.UtcNow, response.MsgId); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "SendWeixinMiniProgramMessageEventHandler"); + messageRecord.SetResult(false, ex.Message); + } + } + + private Dictionary BuildTemplateData(MessageTemplate messageTemplate, ExtraPropertyDictionary variables) + { + var convertedVariables = _messageTemplateDomainService.ConvertVariables(messageTemplate, variables); + return convertedVariables.ToDictionary(x => x.Key, x => x.Value?.ToString() ?? string.Empty); + } + + private async Task UpdateTaskHistoryAsync(MessageTaskHistory taskHistory, List messageRecords) + { + taskHistory.SetResult(!messageRecords.Any(x => x.Success != true) ? MessageTaskHistoryStatuses.Success : (messageRecords.Any(x => x.Success == true) ? MessageTaskHistoryStatuses.PartialFailure : MessageTaskHistoryStatuses.Fail)); + await _messageTaskHistoryRepository.UpdateAsync(taskHistory); + } +} diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkMessageEventHandler.cs index bbde4d7d..19280562 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkMessageEventHandler.cs @@ -36,7 +36,8 @@ public SendWeixinWorkMessageEventHandler(IProviderAsyncLocal [EventHandler] public async Task HandleEventAsync(SendWeixinWorkMessageEvent eto) { - var options = await GetOptionsAsync(eto.ChannelId); + var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == eto.ChannelId); + var options = channel.GetWeixinWorkOptions(); var taskHistory = eto.MessageTaskHistory; var messageRecords = new List(); @@ -101,18 +102,6 @@ private async Task SendAsync(MessageData messageData, } } - private async Task GetOptionsAsync(Guid channelId) - { - var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == channelId); - var options = new WeixinWorkOptions - { - CorpId = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.CorpId)) ?? string.Empty, - CorpSecret = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.CorpSecret)) ?? string.Empty, - AgentId = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.AgentId)) ?? string.Empty, - }; - return options; - } - private async Task> GetCheckChannelUserIdentitysAsync(SendWeixinWorkMessageEvent eto) { var checkChannelUserIdentitys = new List(); diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkWebhookMessageEventHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkWebhookMessageEventHandler.cs index e738555f..88cfa86b 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkWebhookMessageEventHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/EventHandler/SendWeixinWorkWebhookMessageEventHandler.cs @@ -36,7 +36,8 @@ public SendWeixinWorkWebhookMessageEventHandler(IProviderAsyncLocal x.Id == eto.ChannelId); + var options = channel.GetWeixinWorkWebhookOptions(); var taskHistory = eto.MessageTaskHistory; var messageRecords = new List(); @@ -87,16 +88,6 @@ private async Task SendAsync(MessageData messageD return await _sender.SendTextAsync(message); } - private async Task GetOptionsAsync(Guid channelId) - { - var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == channelId); - var options = new WeixinWorkWebhookOptions - { - Key = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkWebhookOptions.Key)) ?? string.Empty - }; - return options; - } - private async Task> GetCheckChannelUserIdentitysAsync(SendWeixinWorkWebhookMessageEvent eto) { var checkChannelUserIdentitys = new List(); diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/ImportReceiversCommandHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/ImportReceiversCommandHandler.cs index fb27af8c..36cedb01 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/ImportReceiversCommandHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/ImportReceiversCommandHandler.cs @@ -86,6 +86,7 @@ private async Task> GetDynamicImportDtos(ChannelTypes chan ChannelTypes.App => await _importer.DynamicImport(stream), ChannelTypes.WeixinWork => await _importer.DynamicImport(stream), ChannelTypes.WeixinWorkWebhook => await _importer.DynamicImport(stream), + ChannelTypes.WeixinMiniProgram => await _importer.DynamicImport(stream), _ => throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.UNKNOWN_CHANNEL_TYPE) }; } @@ -117,6 +118,9 @@ private MessageTaskReceiverDto GetMessageTaskReceiverDto(ChannelTypes channelTyp case ChannelTypes.WeixinWorkWebhook: receiver.ChannelUserIdentity = obj?.GetOrDefault(GetImporterHeaderDisplayName(typeof(WeixinWorkReceiverImportDto), nameof(WeixinWorkReceiverImportDto.Account)))?.ToString() ?? string.Empty; break; + case ChannelTypes.WeixinMiniProgram: + receiver.ChannelUserIdentity = obj?.GetOrDefault(GetImporterHeaderDisplayName(typeof(WeixinMiniProgramReceiverImportDto), nameof(WeixinMiniProgramReceiverImportDto.OpenId)))?.ToString() ?? string.Empty; + break; default: throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.UNKNOWN_CHANNEL_TYPE); } diff --git a/src/Services/Masa.Mc.Service/Application/MessageTasks/MessageTaskQueryHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTasks/MessageTaskQueryHandler.cs index f75c30e6..b5871a4e 100644 --- a/src/Services/Masa.Mc.Service/Application/MessageTasks/MessageTaskQueryHandler.cs +++ b/src/Services/Masa.Mc.Service/Application/MessageTasks/MessageTaskQueryHandler.cs @@ -232,6 +232,7 @@ private Type GetReceiverImportDtoType(ChannelTypes channelType) ChannelTypes.App => typeof(AppReceiverImportDto), ChannelTypes.WeixinWork => typeof(WeixinWorkReceiverImportDto), ChannelTypes.WeixinWorkWebhook => typeof(WeixinWorkReceiverImportDto), + ChannelTypes.WeixinMiniProgram => typeof(WeixinMiniProgramReceiverImportDto), _ => throw new UserFriendlyException(errorCode: UserFriendlyExceptionCodes.UNKNOWN_CHANNEL_TYPE) }; } diff --git a/src/Services/Masa.Mc.Service/Application/MessageTemplates/Queries/GetWeixinMiniProgramTemplateListQuery.cs b/src/Services/Masa.Mc.Service/Application/MessageTemplates/Queries/GetWeixinMiniProgramTemplateListQuery.cs new file mode 100644 index 00000000..4215de54 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Application/MessageTemplates/Queries/GetWeixinMiniProgramTemplateListQuery.cs @@ -0,0 +1,9 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Application.MessageTemplates.Queries; + +public record GetWeixinMiniProgramTemplateListQuery(Guid ChannelId) : Query> +{ + public override List Result { get; set; } = new(); +} diff --git a/src/Services/Masa.Mc.Service/Application/MessageTemplates/WeixinMiniProgramTemplateQueryHandler.cs b/src/Services/Masa.Mc.Service/Application/MessageTemplates/WeixinMiniProgramTemplateQueryHandler.cs new file mode 100644 index 00000000..f663d635 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Application/MessageTemplates/WeixinMiniProgramTemplateQueryHandler.cs @@ -0,0 +1,53 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Application.MessageTemplates; + +public class WeixinMiniProgramTemplateQueryHandler +{ + private readonly IChannelRepository _channelRepository; + private readonly IProviderAsyncLocal _asyncLocal; + private readonly IWeixinMiniProgramTemplateProvider _templateProvider; + + public WeixinMiniProgramTemplateQueryHandler( + IChannelRepository channelRepository, + IProviderAsyncLocal asyncLocal, + IWeixinMiniProgramTemplateProvider templateProvider) + { + _channelRepository = channelRepository; + _asyncLocal = asyncLocal; + _templateProvider = templateProvider; + } + + [EventHandler] + public async Task GetListAsync(GetWeixinMiniProgramTemplateListQuery query) + { + var channel = await _channelRepository.AsNoTracking().FirstOrDefaultAsync(x => x.Id == query.ChannelId); + if (channel == null || channel.Type.Id != ChannelType.WeixinMiniProgram.Id) + { + return; + } + + var options = channel.GetWeixinMiniProgramOptions(); + + using (_asyncLocal.Change(options)) + { + var templates = await _templateProvider.GetTemplateListAsync(); + query.Result = templates.Select(x => new WeixinMiniProgramTemplateDto + { + ChannelId = channel.Id, + TemplateId = x.TemplateId, + Title = x.Title, + Content = x.Content, + Example = x.Example, + TemplateType = x.Type, + Items = x.Items.Select(item => new MessageTemplateItemDto + { + Code = item.Code, + MappingCode = item.Code, + DisplayText = item.Code + }).ToList() + }).ToList(); + } + } +} diff --git a/src/Services/Masa.Mc.Service/Assets/I18n/en-US.json b/src/Services/Masa.Mc.Service/Assets/I18n/en-US.json index 0fb905e4..7cc00564 100644 --- a/src/Services/Masa.Mc.Service/Assets/I18n/en-US.json +++ b/src/Services/Masa.Mc.Service/Assets/I18n/en-US.json @@ -30,6 +30,8 @@ "ChannelCannotDeleted": "This channel cannot be deleted", "ChannelCannotRepeated": "Channel code cannot be repeated", "ChannelTypeDoesNotMatchChannel": "Channel type does not match the channel", + "WeixinMiniProgramOnlySupportsTemplateMessage": "Weixin mini program channel only supports subscribe template messages", + "WeixinMiniProgramDoesNotSupportBroadcast": "Weixin mini program channel does not support broadcast sending", "TemplateCodeCannotRepeated": "Template code cannot be repeated", "TemplateIdCannotRepeated": "Template id cannot be repeated", "UnsubscribeKeywordRequired": "Please enter unsubscribe keyword", @@ -55,4 +57,4 @@ "UserNotLoggedIn": "User not logged in", "EndTimeMustBeGreaterThanOrEqualStartTime": "End time must be greater than or equal to start time", "TimeRangeMustNotExceedOneYear": "Time range must not exceed one year" -} \ No newline at end of file +} diff --git a/src/Services/Masa.Mc.Service/Assets/I18n/zh-CN.json b/src/Services/Masa.Mc.Service/Assets/I18n/zh-CN.json index 42cd1c55..23624310 100644 --- a/src/Services/Masa.Mc.Service/Assets/I18n/zh-CN.json +++ b/src/Services/Masa.Mc.Service/Assets/I18n/zh-CN.json @@ -31,6 +31,8 @@ "ChannelCannotDeleted": "这个渠道不能被删除", "ChannelCannotRepeated": "渠道编码不能重复", "ChannelTypeDoesNotMatchChannel": "渠道类型与渠道不匹配", + "WeixinMiniProgramOnlySupportsTemplateMessage": "微信小程序渠道仅支持订阅模板消息", + "WeixinMiniProgramDoesNotSupportBroadcast": "微信小程序渠道不支持广播发送", "TemplateCodeCannotRepeated": "模板编码不能重复", "TemplateIdCannotRepeated": "模板不能重复", "UnsubscribeKeywordRequired": "请输入退订关键词", @@ -56,4 +58,4 @@ "UserNotLoggedIn": "用户未登录", "EndTimeMustBeGreaterThanOrEqualStartTime": "结束时间不能早于开始时间", "TimeRangeMustNotExceedOneYear": "时间跨度不能超过一年" -} \ No newline at end of file +} diff --git a/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs b/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs index 1bf8d025..3dc4c416 100644 --- a/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs +++ b/src/Services/Masa.Mc.Service/Domain/MessageTasks/EventHandler/UpdateMessageTaskHistoryStatusHandler.cs @@ -21,19 +21,45 @@ public async Task HandleEventAsync(UpdateMessageTaskHistoryStatusEvent eto) var history = await _historyRepository.FindAsync(x => x.Id == eto.MessageTaskHistoryId, false); if (history == null) return; + if (history.Status == MessageTaskHistoryStatuses.Withdrawn) + { + return; + } + + if (await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == null)) + { + return; + } + var hasSuccessMessageRecord = await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == true); + var hasFailMessageRecord = await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success == false); + + if (!hasSuccessMessageRecord && !hasFailMessageRecord) + { + return; + } - if (!await _messageRecordRepository.AnyAsync(x => x.MessageTaskHistoryId == history.Id && x.Success != true)) + MessageTaskHistoryStatuses targetStatus; + if (!hasFailMessageRecord) { - history.SetResult(MessageTaskHistoryStatuses.Success); + targetStatus = MessageTaskHistoryStatuses.Success; } else if (!hasSuccessMessageRecord) { - history.SetResult(MessageTaskHistoryStatuses.Fail); + targetStatus = MessageTaskHistoryStatuses.Fail; } - else if (hasSuccessMessageRecord) + else { - history.SetResult(MessageTaskHistoryStatuses.PartialFailure); + targetStatus = MessageTaskHistoryStatuses.PartialFailure; } + + if (history.Status == targetStatus) + { + return; + } + + history.SetResult(targetStatus); + + await _historyRepository.UpdateAsync(history); } } diff --git a/src/Services/Masa.Mc.Service/Infrastructure/Extensions/ChannelExtensions.cs b/src/Services/Masa.Mc.Service/Infrastructure/Extensions/ChannelExtensions.cs new file mode 100644 index 00000000..1d28dde6 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Infrastructure/Extensions/ChannelExtensions.cs @@ -0,0 +1,51 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Infrastructure.Extensions; + +public static class ChannelExtensions +{ + public static WeixinMiniProgramOptions GetWeixinMiniProgramOptions(this Channel channel) + { + return new WeixinMiniProgramOptions + { + AppId = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.AppId)) ?? string.Empty, + AppSecret = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.AppSecret)) ?? string.Empty, + Token = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.Token)) ?? string.Empty, + EncodingAESKey = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.EncodingAESKey)) ?? string.Empty, + MiniprogramState = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.MiniprogramState)) ?? "formal", + Lang = channel.ExtraProperties.GetProperty(nameof(WeixinMiniProgramChannelOptions.Lang)) ?? "zh_CN", + }; + } + + public static WeixinWorkOptions GetWeixinWorkOptions(this Channel? channel) + { + return new WeixinWorkOptions + { + CorpId = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.CorpId)) ?? string.Empty, + CorpSecret = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.CorpSecret)) ?? string.Empty, + AgentId = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkOptions.AgentId)) ?? string.Empty, + }; + } + + public static WeixinWorkWebhookOptions GetWeixinWorkWebhookOptions(this Channel? channel) + { + return new WeixinWorkWebhookOptions + { + Key = channel?.ExtraProperties.GetProperty(nameof(WeixinWorkWebhookOptions.Key)) ?? string.Empty + }; + } + + public static SmtpEmailOptions GetSmtpEmailOptions(this Channel channel) + { + return new SmtpEmailOptions + { + Host = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Smtp)) ?? string.Empty, + Port = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Port)), + UserName = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)) ?? string.Empty, + Password = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Password)) ?? string.Empty, + EnableSsl = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.Ssl)), + DefaultFromAddress = channel.ExtraProperties.GetProperty(nameof(EmailChannelOptions.UserName)) ?? string.Empty + }; + } +} diff --git a/src/Services/Masa.Mc.Service/Masa.Mc.Service.Admin.csproj b/src/Services/Masa.Mc.Service/Masa.Mc.Service.Admin.csproj index ad162f25..6d039c6b 100644 --- a/src/Services/Masa.Mc.Service/Masa.Mc.Service.Admin.csproj +++ b/src/Services/Masa.Mc.Service/Masa.Mc.Service.Admin.csproj @@ -78,6 +78,7 @@ + diff --git a/src/Services/Masa.Mc.Service/Program.cs b/src/Services/Masa.Mc.Service/Program.cs index cd2858c7..8ee7bce3 100644 --- a/src/Services/Masa.Mc.Service/Program.cs +++ b/src/Services/Masa.Mc.Service/Program.cs @@ -96,6 +96,7 @@ builder.Services.AddMailKit(); builder.Services.AddAppNotification(redisOptions); builder.Services.AddWeixinWork(builder.Configuration); +builder.Services.AddWeixinMiniProgram(); builder.Services.AddCsv(); builder.Services.AddSingleton(); builder.Services.AddTransient(); @@ -218,4 +219,4 @@ }); app.UseHttpsRedirection(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/src/Services/Masa.Mc.Service/Services/MessageReceiptService.cs b/src/Services/Masa.Mc.Service/Services/MessageReceiptService.cs index a19a8b3c..fecadfb2 100644 --- a/src/Services/Masa.Mc.Service/Services/MessageReceiptService.cs +++ b/src/Services/Masa.Mc.Service/Services/MessageReceiptService.cs @@ -65,5 +65,4 @@ public async Task ReceiveYunMasReceiptAsync([FromBody] Y await _eventBus.PublishAsync(command); return command.Result; } - } diff --git a/src/Services/Masa.Mc.Service/Services/WeixinMiniProgramTemplateService.cs b/src/Services/Masa.Mc.Service/Services/WeixinMiniProgramTemplateService.cs new file mode 100644 index 00000000..0f1e0286 --- /dev/null +++ b/src/Services/Masa.Mc.Service/Services/WeixinMiniProgramTemplateService.cs @@ -0,0 +1,26 @@ +// Copyright (c) MASA Stack All rights reserved. +// Licensed under the Apache License. See LICENSE.txt in the project root for license information. + +namespace Masa.Mc.Service.Admin.Services; + +public class WeixinMiniProgramTemplateService : ServiceBase +{ + public WeixinMiniProgramTemplateService(IServiceCollection services) : base("api/weixin-mini-program-template") + { + RouteHandlerBuilder = builder => + { + builder.RequireAuthorization(); + }; + } + + [RoutePattern("channels/{channelId}/templates", StartWithBaseUri = true, HttpMethod = "Get")] + public async Task> GetListAsync( + IEventBus eventBus, + [FromRoute] + Guid channelId) + { + var query = new GetWeixinMiniProgramTemplateListQuery(channelId); + await eventBus.PublishAsync(query); + return query.Result; + } +} diff --git a/src/Services/Masa.Mc.Service/_Imports.cs b/src/Services/Masa.Mc.Service/_Imports.cs index 121efcce..8646aa95 100644 --- a/src/Services/Masa.Mc.Service/_Imports.cs +++ b/src/Services/Masa.Mc.Service/_Imports.cs @@ -142,6 +142,13 @@ global using Masa.Mc.Infrastructure.Weixin.Work.Sender; global using Masa.Mc.Infrastructure.Weixin.Work.Work; global using Masa.Mc.Infrastructure.Weixin.Work.WorkWebhook; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Extensions; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Infrastructure.OptionsResolve; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Model; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Model.Response; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Sender; +global using Masa.Mc.Infrastructure.Weixin.MiniProgram.Template; global using Masa.Mc.Service.Admin.Application.Apps.Commands; global using Masa.Mc.Service.Admin.Application.Unsubscriptions; global using Masa.Mc.Service.Admin.Application.Unsubscriptions.Queries; @@ -213,4 +220,4 @@ global using Masa.Mc.Infrastructure.Sms.Model.Response; global using Masa.Mc.Infrastructure.Sms.YunMas; global using Masa.Mc.Service.Admin.Application.MessageReceipts; -global using Masa.Mc.Service.Admin.Infrastructure.Mock.Fakes; \ No newline at end of file +global using Masa.Mc.Service.Admin.Infrastructure.Mock.Fakes;