qinchaoyue 5 сар өмнө
parent
commit
40296fd259
56 өөрчлөгдсөн 2475 нэмэгдсэн , 25 устгасан
  1. 13 1
      Hotline.sln
  2. 20 0
      src/Hotline.Api/Controllers/IdentityController.cs
  3. 88 0
      src/Hotline.Api/Controllers/Snapshot/SnapshotBaseController.cs
  4. 18 0
      src/Hotline.Api/Controllers/Snapshot/ZiGongSnapshotController.cs
  5. 2 0
      src/Hotline.Api/Hotline.Api.csproj
  6. 18 5
      src/Hotline.Api/StartupExtensions.cs
  7. 159 0
      src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs
  8. 4 0
      src/Hotline.Application.Tests/Hotline.Application.Tests.csproj
  9. 20 4
      src/Hotline.Application.Tests/Startup.cs
  10. 10 1
      src/Hotline.Application.Tests/appsettings.Development.json
  11. 1 0
      src/Hotline.Application/Hotline.Application.csproj
  12. 18 0
      src/Hotline.Application/Identity/IIdentityAppService.cs
  13. 129 14
      src/Hotline.Application/Identity/IdentityAppService.cs
  14. 6 0
      src/Hotline.Application/Mappers/MapperConfigs.cs
  15. 59 0
      src/Hotline.Application/Snapshot/ISnapshotApplication.cs
  16. 210 0
      src/Hotline.Application/Snapshot/SnapshotApplication.cs
  17. 29 0
      src/Hotline.Repository.SqlSugar/Snapshot/ThirdAccountRepository.cs
  18. 33 0
      src/Hotline.Share/Dtos/Article/BulletinDto.cs
  19. 135 0
      src/Hotline.Share/Dtos/Snapshot/HomePageDto.cs
  20. 71 0
      src/Hotline.Share/Dtos/Snapshot/OrderDto.cs
  21. 62 0
      src/Hotline.Share/Dtos/Snapshot/RedPackDto.cs
  22. 38 0
      src/Hotline.Share/Dtos/Snapshot/SnapshotUserInfoDto.cs
  23. 74 0
      src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs
  24. 6 0
      src/Hotline.Share/Enums/Article/EPushRange.cs
  25. 28 0
      src/Hotline.Share/Enums/Snapshot/EOrderQueryStatus.cs
  26. 23 0
      src/Hotline.Share/Enums/Snapshot/EReadPackSendStatus.cs
  27. 20 0
      src/Hotline.Share/Enums/Snapshot/EReadPackUserType.cs
  28. 26 0
      src/Hotline.Share/Enums/Snapshot/ERedPackAuditStatus.cs
  29. 33 0
      src/Hotline.Share/Enums/Snapshot/ERedPackPickupStatus.cs
  30. 26 0
      src/Hotline.Share/Enums/Snapshot/ESnapshopApproveType.cs
  31. 7 0
      src/Hotline.Share/Tools/DateTimeExtensions.cs
  32. 23 0
      src/Hotline.Share/Tools/MapperExtensions.cs
  33. 20 0
      src/Hotline.WeChat/Hotline.WeChat.csproj
  34. 48 0
      src/Hotline.WeChat/WeChatService.cs
  35. 4 0
      src/Hotline/Hotline.csproj
  36. 74 0
      src/Hotline/SeedData/SnapshotSeedData.cs
  37. 10 0
      src/Hotline/Settings/SettingConstants.cs
  38. 36 0
      src/Hotline/Snapshot/CommunityInfo.cs
  39. 22 0
      src/Hotline/Snapshot/GuiderInfo.cs
  40. 7 0
      src/Hotline/Snapshot/IThirdAccountRepository.cs
  41. 160 0
      src/Hotline/Snapshot/Industry.cs
  42. 48 0
      src/Hotline/Snapshot/IndustryCase.cs
  43. 60 0
      src/Hotline/Snapshot/IndustryChangeRecord.cs
  44. 59 0
      src/Hotline/Snapshot/InteractionLog.cs
  45. 49 0
      src/Hotline/Snapshot/InviteCode.cs
  46. 47 0
      src/Hotline/Snapshot/InviteCodeRecord.cs
  47. 82 0
      src/Hotline/Snapshot/OrderSnapshot.cs
  48. 101 0
      src/Hotline/Snapshot/RedPackAudit.cs
  49. 81 0
      src/Hotline/Snapshot/RedPackRecord.cs
  50. 49 0
      src/Hotline/Snapshot/SnapshotMessageTemplate.cs
  51. 28 0
      src/Hotline/Snapshot/Test/ThirdTestService.cs
  52. 59 0
      src/Hotline/Snapshot/ThirdAccount.cs
  53. 13 0
      src/Hotline/Users/IThirdIdentiyService.cs
  54. 1 0
      src/XF.Domain/Authentications/AppClaimTypes.cs
  55. 3 0
      src/XF.Domain/Authentications/DefaultSessionContext.cs
  56. 5 0
      src/XF.Domain/Authentications/ISessionContext.cs

+ 13 - 1
Hotline.sln

@@ -53,7 +53,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XingTang.Sdk", "src\XingTan
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.XingTang", "src\Hotline.XingTang\Hotline.XingTang.csproj", "{9F99C272-5BC2-452C-9D97-BC756AF04669}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Application.Tests", "src\Hotline.Application.Tests\Hotline.Application.Tests.csproj", "{801A8807-F95E-428B-B8C3-3F9244B9E080}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Application.Tests", "src\Hotline.Application.Tests\Hotline.Application.Tests.csproj", "{21E8510A-9D78-44B6-AC9C-2A9D4023853D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.WeChat", "src\Hotline.WeChat\Hotline.WeChat.csproj", "{1A71273C-0ACC-4B70-8405-E443DE278D9F}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hotline.Logger", "src\Hotline.Logger\Hotline.Logger.csproj", "{37784861-ABC0-41F4-87B4-2E08A89A2C42}"
 EndProject
@@ -147,6 +149,14 @@ Global
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42}.Release|Any CPU.Build.0 = Release|Any CPU
+		{21E8510A-9D78-44B6-AC9C-2A9D4023853D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{21E8510A-9D78-44B6-AC9C-2A9D4023853D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{21E8510A-9D78-44B6-AC9C-2A9D4023853D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{21E8510A-9D78-44B6-AC9C-2A9D4023853D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1A71273C-0ACC-4B70-8405-E443DE278D9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1A71273C-0ACC-4B70-8405-E443DE278D9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1A71273C-0ACC-4B70-8405-E443DE278D9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1A71273C-0ACC-4B70-8405-E443DE278D9F}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -177,6 +187,8 @@ Global
 		{9F99C272-5BC2-452C-9D97-BC756AF04669} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 		{801A8807-F95E-428B-B8C3-3F9244B9E080} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
 		{37784861-ABC0-41F4-87B4-2E08A89A2C42} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
+		{21E8510A-9D78-44B6-AC9C-2A9D4023853D} = {08D63205-1445-430F-A4AB-EF1744E3AC11}
+		{1A71273C-0ACC-4B70-8405-E443DE278D9F} = {D041C554-B78E-4AAF-B597-E309DC8EEF4F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {4B8EA790-BD13-4422-8D63-D6DBB77B823F}

+ 20 - 0
src/Hotline.Api/Controllers/IdentityController.cs

@@ -13,6 +13,8 @@ using Microsoft.Extensions.Options;
 using XC.RSAUtil;
 using XF.Domain.Constants;
 using XF.Domain.Exceptions;
+using Hotline.Share.Dtos.Snapshot;
+using Swashbuckle.AspNetCore.Annotations;
 
 namespace Hotline.Api.Controllers;
 
@@ -89,6 +91,24 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
 		return res ;
     }
 
+    /// <summary>
+    /// 获取微信openId
+    /// </summary>
+    /// <param name="dto">微信小程序 Code</param>
+    /// <returns>登录成功, 用户是新用户, 需要调用获取用户手机号码 third/phone 接口</returns>
+    [AllowAnonymous]
+    [HttpPost("third/token")]
+    public async Task<IActionResult> GetThirdTokenAsync([FromBody] ThirdTokenInDto dto)
+        => Ok(await _identityAppService.GetThredTokenAsync(dto));
+
+    /// <summary>
+    /// 获取微信用户手机号码
+    /// </summary>
+    /// <param name="dto">微信小程序Code</param>
+    [HttpPost("third/phone")]
+    public async Task GetThirdPhoneAsync([FromBody]ThirdPhoneInDto dto)
+        => await _identityAppService.GetThirdPhoneAsync(dto);
+
     [AllowAnonymous]
     [ApiExplorerSettings(IgnoreApi = true)]
     [HttpPost("token")]

+ 88 - 0
src/Hotline.Api/Controllers/Snapshot/SnapshotBaseController.cs

@@ -0,0 +1,88 @@
+using Hotline.Application.Snapshot;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Article;
+using Hotline.Share.Dtos.Snapshot;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Snapshot
+{
+    /// <summary>
+    /// 快照接口
+    /// </summary>
+    // [NonController]
+    public abstract class SnapshotBaseController : BaseController
+    {
+        private readonly ISnapshotApplication _snapshotApplication;
+
+        public SnapshotBaseController(ISnapshotApplication snapshotApplication)
+        {
+            _snapshotApplication = snapshotApplication;
+        }
+
+        /// <summary>
+        /// 获取小程序公告列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("bulletions")]
+        [AllowAnonymous]
+        public virtual async Task<IReadOnlyList<BulletinOutDto>> QueryBulletinsAsync([FromQuery] BulletinInDto dto)
+            => await _snapshotApplication.GetBulletinsAsync(dto);
+
+        /// <summary>
+        /// 公告详情
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("bulletions/{id}")]
+        [AllowAnonymous]
+        public virtual async Task<BulletinOutDto> QueryBulletionsDetailAsync([FromQuery] string id)
+            => await _snapshotApplication.GetBulletinsDetailAsync(id);
+
+        /// <summary>
+        /// 获取用户页面数据
+        /// </summary>
+        /// <returns></returns>
+        [AllowAnonymous]
+        [HttpGet("user")]
+        public virtual async Task<SnapshotUserInfoOutDto> GetUserInfo()
+            => await _snapshotApplication.GetSnapshotUserInfoAsync();
+
+        /// <summary>
+        /// 获取我提交的线索列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("order")]
+        public virtual async Task<PagedDto<OrderOutDto>> QueryOrderListAsync([FromQuery] OrderInDto dto)
+            => await _snapshotApplication.GetSnapshotOrdersAsync(dto);
+
+        /// <summary>
+        /// 获取我提交的线索详情
+        /// </summary>
+        /// <param name="id"></param>
+        /// <returns></returns>
+        [HttpGet("order/{id}")]
+        public virtual async Task<OrderDetailOutDto> QueryOrderListAsync([FromQuery] string id)
+            => await _snapshotApplication.GetSnapshotOrderDetailAsync(id);
+
+        /// <summary>
+        /// 统计红包金额, 每月的总金额
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("redpack")]
+        public virtual async Task<IReadOnlyList<RedPackDateOutDto>> QueryRedPackDateAsync([FromQuery] RedPackDateInDto dto)
+            => await _snapshotApplication.GetRedPackDateAsync(dto);
+
+        /// <summary>
+        /// 获取当月详细红包列表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpGet("redpack/month")]
+        public virtual async Task<PagedDto<RedPackOutDto>> QueryRedPackDateAsync([FromQuery] RedPacksInDto dto)
+            => await _snapshotApplication.GetRedPacksAsync(dto);
+    }
+}

+ 18 - 0
src/Hotline.Api/Controllers/Snapshot/ZiGongSnapshotController.cs

@@ -0,0 +1,18 @@
+using Hotline.Application.Snapshot;
+using Hotline.DI;
+using Hotline.Share.Dtos.Article;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Hotline.Api.Controllers.Snapshot;
+
+/// <summary>
+/// 快照接口
+/// </summary>
+[Route("/api/v1/snapshot")]
+[Injection(AppScopes = EAppScope.ZiGong)]
+public class ZiGongSnapshotController : SnapshotBaseController
+{
+    public ZiGongSnapshotController(ISnapshotApplication snapshotApplication) : base(snapshotApplication)
+    {
+    }
+}

+ 2 - 0
src/Hotline.Api/Hotline.Api.csproj

@@ -21,10 +21,12 @@
     <PackageReference Include="Serilog.Sinks.MongoDB" Version="6.0.0" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
     <PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
+    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="6.7.3" />
   </ItemGroup>
 
   <ItemGroup>
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
+    <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
     <ProjectReference Include="..\Hotline.Logger\Hotline.Logger.csproj" />
   </ItemGroup>
 

+ 18 - 5
src/Hotline.Api/StartupExtensions.cs

@@ -37,6 +37,10 @@ using Hotline.XingTang;
 using Hotline.Logger;
 using HotPot.Mvc.Filters;
 using Microsoft.AspNetCore.ResponseCompression;
+using Hotline.WeChat;
+using Senparc.Weixin.RegisterServices;
+using Senparc.Weixin.AspNet;
+
 
 namespace Hotline.Api;
 
@@ -207,6 +211,10 @@ internal static class StartupExtensions
         services.AddScoped<IExpireTimeSupplier, WorkDaySupplier>();
         services.AddScoped<IExpireTimeSupplier, HourSupplier>();
 
+        services.AddScoped<Users.IThirdIdentiyService, WeChatService>();
+
+        services.AddSenparcWeixin(configuration);
+
         //services.AddScoped<LogFilterAttribute>();
         //ServiceLocator.Instance = services.BuildServiceProvider();
         return builder.Build();
@@ -221,11 +229,12 @@ internal static class StartupExtensions
         if (swaggerEnable)
         {
             app.UseSwagger();
-            app.UseSwaggerUI(c =>
-            {
-                //c.DocExpansion(DocExpansion.None);
-                //c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
-            });
+            app.UseSwaggerUI();
+            //app.UseSwaggerUI(c =>
+            //{
+            //    //c.DocExpansion(DocExpansion.None);
+            //    //c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
+            //});
         }
 
         app.UseCors(CorsOrigins);
@@ -244,6 +253,10 @@ internal static class StartupExtensions
         
         app.UseResponseCompression();
         
+        var registerService = app.UseSenparcWeixin(app.Environment, null, null,
+            register => { },
+            (register, weixinSetting) => { }
+            );
         return app;
     }
 }

+ 159 - 0
src/Hotline.Application.Tests/Application/SnapshotApplicationTest.cs

@@ -0,0 +1,159 @@
+using Hotline.Application.Identity;
+using Hotline.Application.Snapshot;
+using Hotline.Share.Dtos.Article;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Enums;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Shouldly;
+using XF.Domain.Repository;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Application.Tests.Application;
+public class SnapshotApplicationTest
+{
+    private readonly ISnapshotApplication _snapshotApplication;
+    private readonly IIdentityAppService _identityAppService;
+    private readonly IRepository<RedPackRecord> _redPackRecordRepository;
+
+    public SnapshotApplicationTest(ISnapshotApplication snapshotApplication, IIdentityAppService identityAppService, IRepository<RedPackRecord> redPackRecordRepository)
+    {
+        _snapshotApplication = snapshotApplication;
+        _identityAppService = identityAppService;
+        _redPackRecordRepository = redPackRecordRepository;
+    }
+
+    [Fact]
+    public async Task GetHomePage_Test()
+    {
+        var result = await _snapshotApplication.GetHomePageAsync();
+        result.Any().ShouldBe(true, "首页数据为空");
+        result.First().DisplayOrder.ShouldBe(1, "排序异常");
+    }
+
+    [Fact]
+    public async Task GetBulletins_Test()
+    {
+        var homePage = await _snapshotApplication.GetHomePageAsync();
+        var inDto = new BulletinInDto
+        {
+            IndustryId = homePage.First(m => m.Name == "文化旅游").Id,
+        };
+        var items = await _snapshotApplication.GetBulletinsAsync(inDto);
+        items.ShouldNotBeNull();
+        items.Any().ShouldBe(true, "公告数据为空");
+        items.Any(m => m.Title.IsNullOrEmpty()).ShouldBe(false, "标题错误");
+        items.Any(m => m.Content.IsNullOrEmpty()).ShouldBe(false, "内容错误");
+        items.Any(m => m.Id.IsNullOrEmpty()).ShouldBe(false, "Id错误");
+    }
+
+    [Fact]
+    public async Task GetSnapshotUserInfo_Test()
+    {
+        var result = await _snapshotApplication.GetSnapshotUserInfoAsync();
+        result.ShouldNotBeNull();
+        result.PhoneNumber.ShouldNotBeNullOrEmpty();
+    }
+
+    [Fact]
+    public async Task GetThirdToken_Test()
+    {
+        var result = await _identityAppService.GetThredTokenAsync(new ThirdTokenInDto { Code = "0e3dql000zdwLS1ZRn000Z3SFR3dql00" });
+        result.Code.ShouldBe(0);
+        result.Result.Token.ShouldNotBeNull();
+        result.Result.UserType.ShouldBe(EReadPackUserType.Citizen);
+    }
+
+    [Theory]
+    [InlineData("")]
+    [InlineData("测")]
+    public async Task SnapshotOrder_Test(string key)
+    {
+        var dto = new OrderInDto();
+        dto.KeyWords = key;
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
+        page.Total.ShouldNotBe(0);
+        page.Items.FirstOrDefault()?.IndustryName.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.OrderNo.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.StatusText.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.Area.ShouldNotBeNullOrEmpty();
+    }
+
+    [Theory]
+    [InlineData(EOrderQueryStatus.All, 3)]
+    [InlineData(EOrderQueryStatus.Reply, 2)]
+    [InlineData(EOrderQueryStatus.NoReply, 1)]
+    [InlineData(EOrderQueryStatus.Appraise, 1)]
+    public async Task SnapshotOrderStatus_Test(EOrderQueryStatus status, int count)
+    {
+        var dto = new OrderInDto { Status = status };
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
+        page.Total.ShouldNotBe(0, $"状态:{status.GetDescription()} 数据为空");
+        page.Total.ShouldBe(count, $"状态:{status.GetDescription()} 数据条数错误");
+        page.Items.FirstOrDefault()?.IndustryName.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.OrderNo.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.StatusText.ShouldNotBeNullOrEmpty();
+        page.Items.FirstOrDefault()?.Area.ShouldNotBeNullOrEmpty();
+
+        dto.PageIndex = 2;
+        page = await _snapshotApplication.GetSnapshotOrdersAsync(dto);
+        page.Items.Count.ShouldBe(0);
+    }
+
+    [Fact]
+    public async Task GetSnapshotOrderDetail_Test()
+    {
+        var page = await _snapshotApplication.GetSnapshotOrdersAsync(new OrderInDto());
+        var id = page.Items.First().Id;
+        var detail = await _snapshotApplication.GetSnapshotOrderDetailAsync(id);
+        detail.Id.ShouldBe(id);
+        detail.IndustryName.ShouldNotBeNull();
+    }
+
+    [Theory]
+    [InlineData(2, 2)]
+    [InlineData(12, 12)]
+    public async Task GetRedPackDateAsync(int count, int exp)
+    {
+        var items = await _snapshotApplication.GetRedPackDateAsync(new RedPackDateInDto { Count = count});
+        items.Count.ShouldNotBe(0, "0数据");
+        items.Count.ShouldBe(exp, $"应该:{exp}, 实际 {items.Count}");
+    }
+
+    [Theory]
+    [InlineData(ERedPackPickupStatus.Unreceived)]
+    [InlineData(ERedPackPickupStatus.Received)]
+    public async Task GetRedPacksAsync(ERedPackPickupStatus status)
+    {
+        var page = await _snapshotApplication.GetRedPacksAsync(new RedPacksInDto { Status = status});
+        page.Total.ShouldNotBe(0, "数据不应该为空");
+    }
+
+    [Fact]
+    public async Task GetBulletinsDetail_Test()
+    {
+        var detail = await _snapshotApplication.GetBulletinsDetailAsync("08dc788f-20f4-4bf1-83d3-b5a8a4f395b0");
+        detail.Id.ShouldNotBeNullOrEmpty();
+        detail.Title.ShouldNotBeNullOrEmpty();
+        detail.Content.ShouldNotBeNullOrEmpty();
+    }
+
+    [Fact]
+    public async Task InitRedPackDataAsync()
+    {
+        for (int i = 0; i < 12; i++)
+        {
+            var now = DateTime.Now;
+            var entity = new RedPackRecord
+            {
+                OrderId = "111111111",
+                Amount = 10 * 1000,
+                CreationTime = new DateTime(2024, i + 1, 02, now.Hour, now.Minute, now.Second),
+                WXOpenId = "测试生成的OpenId",
+                PickupStatus = ERedPackPickupStatus.Received,
+            };
+            await _redPackRecordRepository.AddAsync(entity);
+        }
+    }
+}

+ 4 - 0
src/Hotline.Application.Tests/Hotline.Application.Tests.csproj

@@ -26,6 +26,9 @@
     <PackageReference Include="coverlet.collector" Version="3.2.0" />
     <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="7.0.20" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
+    <PackageReference Include="Senparc.Weixin" Version="6.19.1" />
+    <PackageReference Include="Senparc.Weixin.AspNet" Version="1.3.1" />
+    <PackageReference Include="Senparc.Weixin.WxOpen" Version="3.20.1" />
     <PackageReference Include="Moq" Version="4.20.72" />
     <PackageReference Include="Shouldly" Version="4.2.1" />
     <PackageReference Include="xunit" Version="2.4.2" />
@@ -39,6 +42,7 @@
     <ProjectReference Include="..\Hotline.Application\Hotline.Application.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
     <ProjectReference Include="..\Tr.Sdk\Tr.Sdk.csproj" />
+    <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
     <ProjectReference Include="..\XF.Domain.Repository\XF.Domain.Repository.csproj" />
     <ProjectReference Include="..\XF.Domain\XF.Domain.csproj" />
   </ItemGroup>

+ 20 - 4
src/Hotline.Application.Tests/Startup.cs

@@ -4,6 +4,12 @@ using Hotline.Repository.SqlSugar.Extensions;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Hotline.Application.Snapshot;
 using Hotline.Api;
 using Microsoft.AspNetCore.Identity;
 using XF.Domain.Dependency;
@@ -13,8 +19,18 @@ using XF.Domain.Repository;
 using Hotline.Repository.SqlSugar;
 using Hotline.Repository.SqlSugar.DataPermissions;
 using Hotline.Configurations;
+using Microsoft.AspNetCore.Http;
+using Senparc.Weixin.RegisterServices;
+using Senparc.Weixin;
 using Microsoft.AspNetCore.Builder;
+using Senparc.CO2NET.RegisterServices;
 using Xunit.DependencyInjection.AspNetCoreTesting;
+using Polly;
+using Senparc.Weixin.AspNet;
+using Hotline.Share.Tools;
+using Hotline.Users;
+using Hotline.Snapshot.Test;
+using Hotline.Identity;
 using XF.Domain.Cache;
 using XF.EasyCaching;
 using Mapster;
@@ -95,7 +111,7 @@ public class Startup
             services.AddSqlSugar(configuration);
 
             // application services
-            // services.AddScoped<ISnapshotApplication, SnapshotApplication>();
+            services.AddScoped<ISnapshotApplication, SnapshotApplication>();
 
             //mq
             services.AddTestMq(configuration);
@@ -121,7 +137,7 @@ public class Startup
             services.RegisterMediatR(appConfiguration);
             services.RegisterSignalR(configuration);
 
-            // services.AddSenparcWeixinServices(configuration);
+            services.AddSenparcWeixinServices(configuration);
             AppDomain.CurrentDomain.GetAssemblies().ToList().SelectMany(d => d.GetTypes())
                 .Where(d => d.GetInterfaces().Any(x =>
                     x == typeof(IScopeDependency)
@@ -131,7 +147,7 @@ public class Startup
                 .ToList()
                 .ForEach(d => ServiceRegister.Register(services, d));
 
-            //services.AddScoped<IThirdIdentiyService, ThirdTestService>();
+            services.AddScoped<IThirdIdentiyService, ThirdTestService>();
             // services.AddScoped<IThirdIdentiyService, WeChatService>();
 
             //services.AddScoped<IThirdAccountRepository, ThirdAccountRepository>();
@@ -162,7 +178,7 @@ public class Startup
 
         public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
         {
-            // app.UseSenparcWeixin(env, null, null, register => { }, (register, weixinSetting) => { });
+            app.UseSenparcWeixin(env, null, null, register => { }, (register, weixinSetting) => { });
         }
     }
 }

+ 10 - 1
src/Hotline.Application.Tests/appsettings.Development.json

@@ -1,4 +1,13 @@
 {
+    "SenparcWeixinSetting": {
+        "IsDebug": true,
+
+        //小程序
+        "WxOpenAppId": "#{WxOpenAppId}#",
+        "WxOpenAppSecret": "#{WxOpenAppSecret}#",
+        "WxOpenToken": "#{WxOpenToken}#",
+        "WxOpenEncodingAESKey": "#{WxOpenEncodingAESKey}#"
+    },
     "AllowedHosts": "*",
     "AppConfiguration": {
         "AppScope": "ZiGong",
@@ -110,7 +119,7 @@
             "UserName": "dev",
             "Password": "123456",
             "HostName": "110.188.24.182",
-            "VirtualHost": "fwt-unit-test"
+            "VirtualHost": "fwt-master"
         }
     },
     //"SmsAccountInfo": {

+ 1 - 0
src/Hotline.Application/Hotline.Application.csproj

@@ -20,6 +20,7 @@
     <ProjectReference Include="..\Hotline.Application.Contracts\Hotline.Application.Contracts.csproj" />
     <ProjectReference Include="..\Hotline.NewRock\Hotline.NewRock.csproj" />
     <ProjectReference Include="..\Hotline.Repository.SqlSugar\Hotline.Repository.SqlSugar.csproj" />
+    <ProjectReference Include="..\Hotline.WeChat\Hotline.WeChat.csproj" />
     <ProjectReference Include="..\Hotline.Wex\Hotline.Wex.csproj" />
     <ProjectReference Include="..\Hotline.XingTang\Hotline.XingTang.csproj" />
     <ProjectReference Include="..\Hotline.YbEnterprise.Sdk\Hotline.YbEnterprise.Sdk.csproj" />

+ 18 - 0
src/Hotline.Application/Identity/IIdentityAppService.cs

@@ -4,13 +4,31 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Hotline.Settings;
+using Fw.Utility.UnifyResponse;
 using Hotline.Share.Dtos.Identity;
 using Hotline.Users;
+using Hotline.Share.Dtos.Snapshot;
 
 namespace Hotline.Application.Identity
 {
     public interface IIdentityAppService
     {
+        /// <summary>
+        /// 获取微信用户手机号码
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        /// <exception cref="UserFriendlyException"></exception>
+        Task GetThirdPhoneAsync(ThirdPhoneInDto dto);
+
+        /// <summary>
+        /// 获取三方令牌
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        /// <exception cref="UserFriendlyException"></exception>
+        Task<ApiResponse<TokenOutDto>> GetThredTokenAsync(ThirdTokenInDto dto);
+
         Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken);
 
         Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken);

+ 129 - 14
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -1,5 +1,7 @@
 using System.Security.Claims;
+using Fw.Utility.UnifyResponse;
 using Hotline.Caching.Interfaces;
+using Hotline.Caching.Services;
 using Hotline.Identity;
 using Hotline.Identity.Accounts;
 using Hotline.Orders;
@@ -9,16 +11,18 @@ using Hotline.SeedData;
 using Hotline.Settings;
 using Hotline.Share.Dtos.FlowEngine;
 using Hotline.Share.Dtos.Identity;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Enums.FlowEngine;
 using Hotline.Share.Enums.Identity;
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
 using Hotline.Users;
 using IdentityModel;
-using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.Extensions.Options;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
-using XF.Domain.Constants;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
 using XF.Domain.Options;
@@ -29,6 +33,8 @@ namespace Hotline.Application.Identity;
 public class IdentityAppService : IIdentityAppService, IScopeDependency
 {
     private readonly IAccountRepository _accountRepository;
+    private readonly IRepository<Citizen> _citizenRepository;
+    private readonly ISessionContext _sessionContext;
     private readonly IAccountDomainService _accountDomainService;
     private readonly IRepository<User> _userRepository;
     private readonly IJwtSecurity _jwtSecurity;
@@ -38,8 +44,11 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
     private readonly IRepository<Scheduling> _schedulingRepository;
     private readonly IOrderDomainService _orderDomainService;
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
+    private readonly IThirdIdentiyService _thirdIdentiyService;
+    private readonly IThirdAccountRepository _thirdAccountRepository;
+    private readonly IRepository<GuiderInfo> _guiderInfoRepository;
 
-	public IdentityAppService(
+    public IdentityAppService(
         IAccountRepository accountRepository,
         IAccountDomainService accountDomainService,
         IRepository<User> userRepository,
@@ -49,7 +58,12 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         IMessageCodeDomainService messageCodeDomainService,
         IRepository<Scheduling> schedulingRepository,
         IOrderDomainService orderDomainService,
-        ISystemSettingCacheManager systemSettingCacheManager)
+        ISystemSettingCacheManager systemSettingCacheManager,
+        IThirdIdentiyService thirdIdentiyService,
+        IThirdAccountRepository thirdAccountRepository,
+        ISessionContext sessionContext,
+        IRepository<Citizen> citizenRepository,
+        IRepository<GuiderInfo> guiderInfoRepository)
     {
         _accountRepository = accountRepository;
         _accountDomainService = accountDomainService;
@@ -61,8 +75,12 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         _schedulingRepository = schedulingRepository;
         _orderDomainService = orderDomainService;
         _systemSettingCacheManager = systemSettingCacheManager;
-
-	}
+        _thirdIdentiyService = thirdIdentiyService;
+        _thirdAccountRepository = thirdAccountRepository;
+        _sessionContext = sessionContext;
+        _citizenRepository = citizenRepository;
+        _guiderInfoRepository = guiderInfoRepository;
+    }
 
     public async Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken)
     {
@@ -214,12 +232,12 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
             .FirstAsync(d => d.Id == account.Id);
         if (user == null)
             throw UserFriendlyException.SameMessage("未查询到用户数据");
-		//平均派单
-		var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
-		if (averageSendOrder)
-		{
-			await AverageOrderScheduling(account.Id, cancellationToken);
-		}
+        //平均派单
+        var averageSendOrder = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.AverageSendOrder).SettingValue[0]);
+        if (averageSendOrder)
+        {
+            await AverageOrderScheduling(account.Id, cancellationToken);
+        }
         var jwtOptions = _identityOptionsAccessor.Value.Jwt;
         var claims = new List<Claim>
         {
@@ -259,13 +277,13 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         try
         {
             DateTime time = DateTime.Parse(DateTime.Now.ToString("yyyy-MM-dd"));
-            
+
             //&& x.AtWork!.Value != true
             //根据当前时间获取排班信息
             var scheduling = await _schedulingRepository.Queryable()
                 .Includes(x => x.SchedulingUser)
                 .Where(x => x.SchedulingTime == time &&
-                            (x.AtWork == true || x.AtWork == null) && 
+                            (x.AtWork == true || x.AtWork == null) &&
                             x.SchedulingUser.UserId == id)
                 .OrderBy(x => x.SendOrderNum).FirstAsync(cancellationToken);
             if (scheduling != null)
@@ -280,4 +298,101 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
             // ignored
         }
     }
+
+    /// <summary>
+    /// 获取三方令牌
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    /// <exception cref="UserFriendlyException"></exception>
+    public async Task<ApiResponse<TokenOutDto>> GetThredTokenAsync(ThirdTokenInDto dto)
+    {
+        var thirdDto = dto.MapTo<ThirdTokenDto>();
+        thirdDto.AppId = _systemSettingCacheManager.GetSetting(SettingConstants.WxOpenAppId)!.SettingValue.First();
+        thirdDto.Secret = _systemSettingCacheManager.GetSetting(SettingConstants.WxOpenAppSecret)!.SettingValue.First();
+        var thirdToken = await _thirdIdentiyService.GetTokenAsync(thirdDto);
+        if (thirdToken.OpenId.IsNullOrEmpty()) throw new UserFriendlyException("获取微信用户信息失败");
+        var thirdAccount = await _thirdAccountRepository.QueryByOpenIdAsync(thirdToken.OpenId);
+
+        // 新用户注册
+        if (thirdAccount is null)
+        {
+            thirdAccount = thirdToken.MapTo<ThirdAccount>();
+            thirdAccount.Id = await _thirdAccountRepository.AddAsync(thirdAccount);
+        }
+
+        var jwtOptions = _identityOptionsAccessor.Value.Jwt;
+        var claims = new List<Claim>
+        {
+            new(JwtClaimTypes.Subject, thirdAccount.Id),
+            new(JwtClaimTypes.PhoneNumber, thirdAccount.PhoneNumber ?? string.Empty),
+            new(JwtClaimTypes.Scope, jwtOptions.Scope),
+            new(AppClaimTypes.OpenId, thirdAccount.WXOpenId),
+        };
+        var audience = new AudienceTicket(thirdAccount.Id);
+        var expiredSeconds = jwtOptions.Expired <= 0 ? 3600 : jwtOptions.Expired;
+        await _cacheAudience.SetAsync(audience.Id, audience, TimeSpan.FromSeconds(expiredSeconds));
+        var token = _jwtSecurity.EncodeJwtToken(claims, audience.Ticket);
+        if (thirdAccount.PhoneNumber.IsNullOrEmpty())
+            return new ApiResponse<TokenOutDto> 
+            {
+                Code = 201,
+                Message = "请绑定手机号码 '/Identity/third/phone'",
+                Result = new TokenOutDto(thirdAccount.CitizenType, token),
+            };
+        return new ApiResponse<TokenOutDto> { 
+            Code = 0,
+            Result = new TokenOutDto(thirdAccount.CitizenType, token),
+        };
+    }
+
+    /// <summary>
+    /// 获取微信用户手机号码
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    /// <exception cref="UserFriendlyException"></exception>
+    public async Task GetThirdPhoneAsync(ThirdPhoneInDto dto)
+    {
+        var thirdAccount = await _thirdAccountRepository.QueryByOpenIdAsync(_sessionContext.OpenId)
+             ?? throw new UserFriendlyException(401, "请重新登录");
+
+        var thirdDto = dto.MapTo<ThirdTokenDto>();
+        thirdDto.AppId = _systemSettingCacheManager.GetSetting(SettingConstants.WxOpenAppId)!.SettingValue.First();
+        var thirdPhone = await _thirdIdentiyService.GetPhoneNumberAsync(thirdDto);
+        if (thirdPhone.IsError)
+            throw new UserFriendlyException(thirdPhone.ErrorCode + ":" + thirdPhone.ErrorMessage);
+
+        thirdAccount.PhoneNumber = thirdPhone.PhoneNumber;
+        var guider = await _guiderInfoRepository.Queryable()
+            .Where(m => m.PhoneNumber == thirdAccount.PhoneNumber)
+            .FirstAsync();
+        if (guider is not null)
+        { // 回填网格员信息
+            thirdAccount.UserId = guider.Id;
+            thirdAccount.CitizenType = EReadPackUserType.Gukder;
+            await _thirdAccountRepository.UpdateAsync(thirdAccount);
+            return;
+        }
+
+        var citizen = await _citizenRepository.Queryable()
+            .Where(m => m.PhoneNumber == thirdAccount.PhoneNumber)
+            .FirstAsync();
+        if (citizen is not null)
+        {
+            thirdAccount.CitizenType = EReadPackUserType.Citizen;
+            thirdAccount.UserId = citizen.Id;
+            await _thirdAccountRepository.UpdateAsync(thirdAccount);
+        }
+        else
+        {
+            citizen = new Citizen
+            {
+                PhoneNumber = thirdAccount.PhoneNumber
+            };
+            thirdAccount.UserId = await _citizenRepository.AddAsync(citizen);
+            thirdAccount.CitizenType = EReadPackUserType.Citizen;
+            await _thirdAccountRepository.UpdateAsync(thirdAccount);
+        }
+    }
 }

+ 6 - 0
src/Hotline.Application/Mappers/MapperConfigs.cs

@@ -15,7 +15,9 @@ using Hotline.Share.Dtos.Org;
 using Hotline.Share.Dtos.Push.FWMessage;
 using Hotline.Share.Dtos.Settings;
 using Hotline.Share.Dtos.TrCallCenter;
+using Hotline.Share.Dtos.Snapshot;
 using Hotline.Share.Enums.Order;
+using Hotline.Snapshot;
 using Mapster;
 using XF.Domain.Entities;
 
@@ -64,6 +66,10 @@ namespace Hotline.Application.Mappers
             config.ForType<TimeLimitSetting, TimeConfig>()
                 .Map(d => d.Count, x => x.TimeValue);
 
+            config.ForType<ThirdAccount, SnapshotUserInfoOutDto>();
+            config.ForType<ThirdTokenOutDto, ThirdAccount>()
+                .Map(d => d.WXOpenId, m => m.OpenId);
+
             config.ForType<AddBlacklistDto, Blacklist>()
                 .Ignore(d => d.Expired)
                 .AfterMapping((s, t) => t.InitExpired());

+ 59 - 0
src/Hotline.Application/Snapshot/ISnapshotApplication.cs

@@ -0,0 +1,59 @@
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Article;
+using Hotline.Share.Dtos.Snapshot;
+
+namespace Hotline.Application.Snapshot;
+public interface ISnapshotApplication
+{
+    /// <summary>
+    /// 获取用户首页数据
+    /// </summary>
+    /// <returns></returns>
+    Task<SnapshotUserInfoOutDto> GetSnapshotUserInfoAsync();
+
+    /// <summary>
+    /// 获取小程序首页数据
+    /// </summary>
+    /// <returns></returns>
+    Task<IReadOnlyList<HomePageOutDto>> GetHomePageAsync();
+
+    /// <summary>
+    /// 获取小程序公告列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto);
+    
+    /// <summary>
+    /// 获取工单列表
+    /// </summary>
+    Task<PagedDto<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto);
+
+    /// <summary>
+    /// 获取工单详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<OrderDetailOutDto> GetSnapshotOrderDetailAsync(string id);
+
+    /// <summary>
+    /// 统计红包金额, 每月的总金额
+    /// </summary>
+    /// <param name="count"></param>
+    /// <returns></returns>
+    Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto);
+
+    /// <summary>
+    /// 获取当月详细红包列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    Task<PagedDto<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto);
+
+    /// <summary>
+    /// 获取公告详情
+    /// </summary>
+    /// <param name="id"></param>
+    /// <returns></returns>
+    Task<BulletinOutDto> GetBulletinsDetailAsync(string id);
+}

+ 210 - 0
src/Hotline.Application/Snapshot/SnapshotApplication.cs

@@ -0,0 +1,210 @@
+using Hotline.Orders;
+using Hotline.Share.Dtos;
+using Hotline.Share.Dtos.Article;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Share.Tools;
+using Hotline.Snapshot;
+using Hotline.Users;
+using SqlSugar;
+using XF.Domain.Authentications;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+using Hotline.Repository.SqlSugar.Extensions;
+using Hotline.Share.Enums;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Requests;
+using Hotline.Share.Enums.Snapshot;
+
+namespace Hotline.Application.Snapshot;
+
+/// <summary>
+/// 随手拍应用层
+/// </summary>
+public class SnapshotApplication : ISnapshotApplication, IScopeDependency
+{
+    private readonly IThirdAccountRepository _thirdAccountRepository;
+    private readonly IRepository<Order> _orderRepository;
+    private readonly IRepository<Article.Bulletin> _bulletinRepository;
+    private readonly IRepository<Industry> _industryRepository;
+    private readonly IThirdIdentiyService _thirdLoginService;
+    private readonly ISessionContext _sessionContext;
+    private readonly IRepository<RedPackRecord> _redPackRecordRepository;
+    private readonly IRepository<OrderSnapshot> _orderSnapshotRepository;
+
+    public SnapshotApplication(IThirdIdentiyService thirdLoginService, IRepository<Industry> industryRepository, IRepository<Article.Bulletin> bulletinRepository, ISessionContext sessionContext, IRepository<RedPackRecord> redPackRecordRepository, IRepository<Order> orderRepository, IThirdAccountRepository thirdAccountRepository, IRepository<OrderSnapshot> orderSnapshotRepository)
+    {
+        _thirdLoginService = thirdLoginService;
+        _industryRepository = industryRepository;
+        _bulletinRepository = bulletinRepository;
+        _sessionContext = sessionContext;
+        _redPackRecordRepository = redPackRecordRepository;
+        _orderRepository = orderRepository;
+        _thirdAccountRepository = thirdAccountRepository;
+        _orderSnapshotRepository = orderSnapshotRepository;
+    }
+
+    /// <summary>
+    /// 获取随手拍小程序首页数据
+    /// </summary>
+    /// <returns></returns>
+    public async Task<IReadOnlyList<HomePageOutDto>> GetHomePageAsync()
+    {
+        return await _industryRepository.Queryable()
+            .Where(m => m.IsEnable)
+            .OrderBy(m => m.DisplayOrder)
+            .ToListAsync(m => new HomePageOutDto());
+    }
+
+    /// <summary>
+    /// 获取随手拍小程序公告
+    /// </summary>
+    /// <returns></returns>
+    public async Task<IReadOnlyList<BulletinOutDto>> GetBulletinsAsync(BulletinInDto dto)
+    {
+        var items = await _bulletinRepository.Queryable()
+            .Where(m => m.BulletinState == Share.Enums.Article.EBulletinState.ReviewPass)
+            .LeftJoin<Industry>((bulletin, industry) => bulletin.BulletinTypeId == industry.BulletinTypePublicityId)
+            .Where((bulletin, industry) => industry.Id == dto.IndustryId)
+            .ToPageListAsync(dto.PageIndex, dto.PageSize);
+
+        return items.MapTo<IReadOnlyList<BulletinOutDto>>();
+    }
+
+    /// <summary>
+    /// 获取个人中心数据
+    /// </summary>
+    /// <returns></returns>
+    public async Task<SnapshotUserInfoOutDto> GetSnapshotUserInfoAsync()
+    {
+        var openId = _sessionContext.OpenId;
+        var thirdAccount = await _thirdAccountRepository.QueryByOpenIdAsync(openId);
+        var dayTime = DateTime.Now;
+        var readPack = await _redPackRecordRepository.Queryable()
+            .Where(m => m.WXOpenId == openId && m.PickupStatus == Share.Enums.Snapshot.ERedPackPickupStatus.Received)
+            .Where(m => m.CreationTime.Date == dayTime.Date)
+            .Select(m => SqlFunc.AggregateSum(m.Amount))
+            .FirstAsync();
+
+        var outDto = await _orderRepository.Queryable()
+            .Where(m => m.Contact == thirdAccount.PhoneNumber)
+            .Select(m => new SnapshotUserInfoOutDto
+            {
+                NoReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status < Share.Enums.Order.EOrderStatus.Filed, 1, 0)),
+                ReplyCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status >= Share.Enums.Order.EOrderStatus.Filed, 1, 0)),
+                AppraiseCount = SqlFunc.AggregateSum(SqlFunc.IIF(m.Status == Share.Enums.Order.EOrderStatus.Visited, 1, 0)),
+            }).FirstAsync();
+            
+        outDto.DayAmount = readPack;
+        outDto.TotalAmount = thirdAccount.TotalAmount;
+        outDto.PhoneNumber = thirdAccount.PhoneNumber;
+        return outDto;
+    }
+
+    public async Task<PagedDto<OrderOutDto>> GetSnapshotOrdersAsync(OrderInDto dto)
+    {
+        var member = await _thirdAccountRepository.QueryByOpenIdAsync(_sessionContext.OpenId);
+        var (total, items) = await _orderSnapshotRepository.Queryable()
+            .LeftJoin<Order>((snapshot, order) => snapshot.OrderId == order.Id)
+            .Where((snapshot, order) => order.Contact == member.PhoneNumber)
+            .WhereIF(dto.Status == EOrderQueryStatus.Appraise, (snapshot, order) => order.Status == EOrderStatus.Visited)
+            .WhereIF(dto.Status == EOrderQueryStatus.NoReply, (snapshot, order) => order.Status < EOrderStatus.Filed)
+            .WhereIF(dto.Status == EOrderQueryStatus.Reply, (snapshot, order) => order.Status >= EOrderStatus.Filed)
+            .WhereIF(dto.KeyWords.NotNullOrEmpty(), (snapshot, order) => order.Title.Contains(dto.KeyWords))
+            .Select((snapshot, order) => new OrderOutDto 
+            {
+                Id = snapshot.Id,
+                OrderNo = order.No,
+                Title = order.Title,
+                Status = order.Status,
+                IndustryName = snapshot.IndustryName,
+                CreationTime = order.CreationTime,
+                Area = order.City
+            })
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize);
+
+        return new PagedDto<OrderOutDto>(total, items);
+    }
+
+    public async Task<OrderDetailOutDto> GetSnapshotOrderDetailAsync(string id)
+    {
+        var detail = await _orderSnapshotRepository.Queryable()
+            .Where(m => m.Id == id)
+            .LeftJoin<Order>((snapshot, order) => snapshot.OrderId == order.Id)
+            .Select((snapshot, order) => new OrderDetailOutDto
+            {
+                Id = snapshot.Id,
+                OrderNo = order.No,
+                Title = order.Title,
+                Status = order.Status,
+                IndustryName = snapshot.IndustryName,
+                CreationTime = order.CreationTime,
+                Area = order.City
+            })
+            .FirstAsync();
+
+        return detail;
+    }
+
+    /// <summary>
+    /// 获取当月详细红包列表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<PagedDto<RedPackOutDto>> GetRedPacksAsync(RedPacksInDto dto)
+    {
+        var openId = _sessionContext.OpenId;
+        var (total, items) = await _redPackRecordRepository.Queryable()
+            .Where(m => m.WXOpenId == openId)
+            .Where(m => m.PickupStatus == dto.Status)
+            .Where(m => m.CreationTime.ToString("yyyy-MM") == dto.Time)
+            .LeftJoin<Order>((red, order) => red.OrderId == order.Id)
+            .Select((red, order) => new RedPackOutDto 
+            {
+                Amount = red.Amount,
+                Title = order.Title,
+                CreationTime = red.CreationTime
+            })
+            .ToPagedListAsync(dto.PageIndex, dto.PageSize);
+
+        return new PagedDto<RedPackOutDto>(total, items);
+    }
+
+    /// <summary>
+    /// 按月统计红包金额
+    /// </summary>
+    /// <param name="count"></param>
+    /// <returns></returns>
+    public async Task<IReadOnlyList<RedPackDateOutDto>> GetRedPackDateAsync(RedPackDateInDto dto)
+    { 
+        var openId = _sessionContext.OpenId;
+        var item = await _redPackRecordRepository.Queryable()
+            .Where(m => m.WXOpenId == openId)
+            .Where(m => m.PickupStatus == dto.Status)
+            .GroupBy(m => m.CreationTime.ToString("yyyy-MM"))
+            .OrderByDescending(m => m.CreationTime)
+            .Select(m => new RedPackDateOutDto
+            {
+                CreationTime = SqlFunc.AggregateMax(m.CreationTime.Date),
+                Amount = SqlFunc.AggregateSum(m.Amount)
+            })
+            .Take(dto.Count)
+            .ToListAsync();
+
+        return item;
+    }
+
+    public async Task<BulletinOutDto> GetBulletinsDetailAsync(string id)
+    {
+        var detail = await _bulletinRepository.Queryable()
+            .Where(m => m.Id == id)
+            .Where(m => m.BulletinState == Share.Enums.Article.EBulletinState.ReviewPass)
+            .Select(m => new BulletinOutDto
+            { 
+                Id = m.Id,
+                Title = m.Title,
+                Content = m.Content
+            })
+            .FirstAsync();
+        return detail;
+    }
+}

+ 29 - 0
src/Hotline.Repository.SqlSugar/Snapshot/ThirdAccountRepository.cs

@@ -0,0 +1,29 @@
+using Hotline.Snapshot;
+using XF.Domain.Dependency;
+using XF.Domain.Repository;
+
+namespace Hotline.Repository.SqlSugar.Snapshot;
+public class ThirdAccountRepository : IThirdAccountRepository , IScopeDependency
+{
+    private readonly IRepository<ThirdAccount> _repository;
+
+    public ThirdAccountRepository(IRepository<ThirdAccount> repository)
+    {
+        _repository = repository;
+    }
+
+    public async Task<string> AddAsync(ThirdAccount entity)
+    {
+        return await _repository.AddAsync(entity);
+    }
+
+    public async Task<ThirdAccount> QueryByOpenIdAsync(string openId)
+        => await _repository.Queryable()
+        .Where(p => p.WXOpenId == openId)
+        .FirstAsync();
+
+    public async Task UpdateAsync(ThirdAccount entity)
+    {
+        await _repository.UpdateAsync(entity);
+    }
+}

+ 33 - 0
src/Hotline.Share/Dtos/Article/BulletinDto.cs

@@ -504,4 +504,37 @@ namespace Hotline.Share.Dtos.Article
 
         public int OrgCount { get; set; }
     }
+
+    /// <summary>
+    /// 微信小程序获取宣传学习列表入参
+    /// </summary>
+    public record BulletinInDto : PagedRequest
+    {
+        /// <summary>
+        /// 行业Id
+        /// <inheritdoc cref="Hotline.Snapshot.Industry" />表的Id
+        /// </summary>
+        public string IndustryId { get; set; }
+    }
+
+    /// <summary>
+    /// 微信小程序获取宣传学习列表出参
+    /// </summary>
+    public class BulletinOutDto
+    { 
+        /// <summary>
+        /// 公告ID
+        /// </summary>
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 标题
+        /// </summary>
+        public string Title { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+    }
 }

+ 135 - 0
src/Hotline.Share/Dtos/Snapshot/HomePageDto.cs

@@ -0,0 +1,135 @@
+namespace Hotline.Share.Dtos.Snapshot;
+
+/// <summary>
+/// 微信小程序首页数据
+/// </summary>
+public class HomePageOutDto
+{
+    /// <summary>
+    /// 行业Id
+    /// </summary>
+    public string Id { get; set; }
+
+    /// <summary>
+    /// 行业名称
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 审批部门Id
+    /// </summary>
+    public string? ApproveOrgId { get; set; }
+
+    /// <summary>
+    /// 审批部门名字
+    /// </summary>
+    public string? ApproveOrgName { get; set; }
+
+    /// <summary>
+    /// 受理类型
+    /// </summary>
+    public string AcceptType { get; set; }
+
+    /// <summary>
+    /// 受理类型代码
+    /// </summary>
+    public string? AcceptTypeCode { get; set; }
+
+    /// <summary>
+    /// 市民发放红包金额(单位:分)
+    /// </summary>
+    public int CitizenReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 网络员发放红包金额(单位:分)
+    /// </summary>
+    public int GuiderReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool IsEnable { get; set; }
+
+    /// <summary>
+    /// 帮助引导用语
+    /// </summary>
+    public string? TxtHelpRemarks { get; set; }
+
+    /// <summary>
+    /// 宫格说明文本
+    /// </summary>
+    public string TxtRemarks { get; set; }
+
+    /// <summary>
+    /// 关怀说明
+    /// </summary>
+    public string TxtCareRemarks { get; set; }
+
+    /// <summary>
+    /// 排序
+    /// </summary>
+    public int DisplayOrder { get; set; }
+
+    /// <summary>
+    /// 阶段性回复间隔时间(小时)
+    /// </summary>
+    public int IntervalTime { get; set; }
+
+    /// <summary>
+    /// 页面Url
+    /// </summary>
+    public string PageUrl { get; set; }
+
+    /// <summary>
+    /// 关怀页面Url
+    /// </summary>
+    public string PageCareUrl { get; set; }
+
+    /// <summary>
+    /// 关联宣传学习
+    /// 从字典中取"公告类型"
+    /// </summary>
+    public string BulletinTypePublicityId { get; set; }
+
+    /// <summary>
+    /// 关联宣传学习
+    /// 从字典中取"公告类型"
+    /// </summary>
+    public string BulletinTypePublicityName { get; set; }
+
+    /// <summary>
+    /// 关联操作指引
+    /// 从字典中取"公告类型"
+    /// </summary>
+    public string BulletinTypeGuideId { get; set; }
+
+    /// <summary>
+    /// 关联操作指引
+    /// 从字典中取"公告类型"
+    /// </summary>
+    public string BulletinTypeGuideame { get; set; }
+
+    /// <summary>
+    /// 背景图片 url
+    /// </summary>
+    public string BackgroundImgUrl { get; set; }
+
+    /// <summary>
+    /// Banner 图片 url
+    /// </summary>
+    public string BannerImgUrl { get; set; }
+
+    /// <summary>
+    /// 宫格图
+    /// </summary>
+    public string CellImgUrl { get; set; }
+
+    /// <summary>
+    /// 关怀宫格图
+    /// </summary>
+    public string CareCellImgUrl { get; set; }
+}
+
+internal class HomePageDto
+{
+}

+ 71 - 0
src/Hotline.Share/Dtos/Snapshot/OrderDto.cs

@@ -0,0 +1,71 @@
+using Hotline.Share.Enums;
+using Hotline.Share.Enums.Order;
+using Hotline.Share.Requests;
+using XF.Utility.EnumExtensions;
+
+namespace Hotline.Share.Dtos.Snapshot;
+
+public record OrderInDto : PagedRequest
+{
+    /// <summary>
+    /// 关键字
+    /// </summary>
+    public string? KeyWords { get; set; }
+
+    /// <summary>
+    /// 工单状态
+    /// </summary>
+    public EOrderQueryStatus Status {get;set;}
+}
+
+public class OrderDetailOutDto : OrderOutDto
+{ 
+}
+
+public class OrderOutDto
+{ 
+    public string Id { get; set; }
+
+    /// <summary>
+    /// 工单编号
+    /// </summary>
+    public string OrderNo { get; set; }
+
+    /// <summary>
+    /// 标题
+    /// </summary>
+    public string Title { get; set; }
+
+    /// <summary>
+    /// 工单状态
+    /// </summary>
+    public EOrderStatus Status { get; set; }
+
+    /// <summary>
+    /// 行业名字
+    /// </summary>
+    public string IndustryName { get; set; }
+
+    /// <summary>
+    /// 工单状态描述
+    /// </summary>
+    public string StatusText => this.Status.GetDescription();
+
+    /// <summary>
+    /// 时间
+    /// </summary>
+    public DateTime CreationTime { get; set; }
+
+    /// <summary>
+    /// 时间文本
+    /// </summary>
+    public string CreationTimeText => this.CreationTime.ToString("yyyy-MM-dd hh:mm:ss");
+
+    /// <summary>
+    /// 区域
+    /// </summary>
+    public string Area { get; set; }
+}
+public class OrderDto 
+{
+}

+ 62 - 0
src/Hotline.Share/Dtos/Snapshot/RedPackDto.cs

@@ -0,0 +1,62 @@
+using Hotline.Share.Enums.Snapshot;
+using Hotline.Share.Requests;
+using System.ComponentModel.DataAnnotations;
+
+namespace Hotline.Share.Dtos.Snapshot;
+public class RedPackOutDto 
+{ 
+    public string Title { get; set; }    
+    public DateTime CreationTime { get; set; }
+    public string CreationTimeText => CreationTime.ToString("yyyy-MM-dd HH:mm:ss");
+    /// <summary>
+    /// 金额(单位:分)
+    /// </summary>
+    public int Amount { get; set; }
+    public string AmountText => (Amount / 1000).ToString();
+}
+
+public record RedPacksInDto : PagedRequest
+{ 
+    /// <summary>
+    /// 时间; 格式:yyyy-MM
+    /// </summary>
+    [Required]
+    public string Time { get; set; } = DateTime.Now.ToString("yyyy-MM");
+
+    /// <summary>
+    /// 红包状态
+    /// </summary>
+    public ERedPackPickupStatus Status { get; set; }
+}
+
+public class RedPackDateInDto
+{
+    /// <summary>
+    /// 查询条数
+    /// </summary>
+    public int Count { get; set; } = 12;
+
+    /// <summary>
+    /// 红包状态
+    /// </summary>
+    public ERedPackPickupStatus Status { get; set; }
+}
+
+public class RedPackDateOutDto
+{ 
+    /// <summary>
+    /// 时间
+    /// </summary>
+    public DateTime CreationTime { get; set; }
+
+    public string CreationTimeText => CreationTime.ToString("yyyy-MM");
+
+    /// <summary>
+    /// 金额(单位:分)
+    /// </summary>
+    public int Amount { get; set; }
+}
+
+internal class RedPackDto
+{
+}

+ 38 - 0
src/Hotline.Share/Dtos/Snapshot/SnapshotUserInfoDto.cs

@@ -0,0 +1,38 @@
+namespace Hotline.Share.Dtos.Snapshot;
+public class SnapshotUserInfoOutDto
+{
+    /// <summary>
+    /// 电话号码
+    /// </summary>
+    public string PhoneNumber { get; set; }
+
+    /// <summary>
+    /// 当日奖励
+    /// </summary>
+    public int DayAmount { get; set; }
+
+    /// <summary>
+    /// 总奖励(单位:分)
+    /// </summary>
+    public long TotalAmount { get; set; }
+
+    /// <summary>
+    /// 未回复数量
+    /// </summary>
+    public int NoReplyCount { get; set; }
+
+    /// <summary>
+    /// 已回复数量
+    /// </summary>
+    public int ReplyCount { get; set; }
+
+    /// <summary>
+    /// 已评论
+    /// </summary>
+    public int AppraiseCount { get; set; }
+}
+
+public class SnapshotUserInfoDto
+{
+}
+

+ 74 - 0
src/Hotline.Share/Dtos/Snapshot/ThirdTokenDto.cs

@@ -0,0 +1,74 @@
+using Hotline.Share.Enums.Snapshot;
+
+namespace Hotline.Share.Dtos.Snapshot;
+
+public class TokenOutDto
+{
+    public TokenOutDto(EReadPackUserType citizenType, string token)
+    {
+        UserType = citizenType;
+        Token = token;
+    }
+
+    /// <summary>
+    /// 用户类型
+    /// </summary>
+    public EReadPackUserType UserType { get; set; }
+
+    /// <summary>
+    /// 登录token
+    /// </summary>
+    public string Token { get; set; }
+}
+
+public class ThirdTokenInDto
+{
+    /// <summary>
+    /// 微信小程序中获取的Code
+    /// </summary>
+    public string Code { get; set; }
+}
+
+public class ThirdPhoneInDto : ThirdTokenInDto { }
+
+public class ThirdPhoneOutDto
+{
+    /// <summary>
+    /// 电话号码
+    /// </summary>
+    public string PhoneNumber { get; set; }
+
+    /// <summary>
+    /// 是否异常
+    /// </summary>
+    public bool IsError { get; set; }
+
+    /// <summary>
+    /// 微信返回异常信息
+    /// </summary>
+    public string ErrorMessage { get; set; }
+
+    /// <summary>
+    /// 微信返回异常Code
+    /// </summary>
+    public int ErrorCode { get; set; }
+}
+
+public class ThirdTokenOutDto
+{
+    public string SessionKey { get; set; }
+    public string OpenId { get; set; }
+}
+
+public class ThirdTokenDto : ThirdTokenInDto
+{
+    /// <summary>
+    /// 微信AppId
+    /// </summary>
+    public string AppId { get; set; }
+
+    /// <summary>
+    /// 微信AppSecret
+    /// </summary>
+    public string Secret { get; set; }
+}

+ 6 - 0
src/Hotline.Share/Enums/Article/EPushRange.cs

@@ -30,5 +30,11 @@ namespace Hotline.Share.Enums.Article
         /// </summary>
         [Description("部门APP")]
         OrgApp = 8,
+
+        /// <summary>
+        /// 随手拍
+        /// </summary>
+        [Description("随手拍")]
+        Snapshot = 9,
     }
 }

+ 28 - 0
src/Hotline.Share/Enums/Snapshot/EOrderQueryStatus.cs

@@ -0,0 +1,28 @@
+using System.ComponentModel;
+namespace Hotline.Share.Enums;
+public enum EOrderQueryStatus
+{
+    /// <summary>
+    /// 全部
+    /// </summary>
+    [Description("全部")]
+    All = 0,
+
+    /// <summary>
+    /// 未回复
+    /// </summary>
+    [Description("未回复")]
+    NoReply = 1,
+
+    /// <summary>
+    /// 已回复
+    /// </summary>
+    [Description("已回复")]
+    Reply = 2,
+
+    /// <summary>
+    /// 已评价
+    /// </summary>
+    [Description("已评价")]
+    Appraise = 3
+}

+ 23 - 0
src/Hotline.Share/Enums/Snapshot/EReadPackSendStatus.cs

@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.Snapshot;
+
+/// <summary>
+/// 红包发放状态
+/// </summary>
+public enum EReadPackSendStatus
+{
+    [Description("发放成功")]
+    Successful = 0,
+
+    [Description("发放失败")]
+    Fail = 1,
+
+    [Description("未发放")]
+    Unsend = 2
+}

+ 20 - 0
src/Hotline.Share/Enums/Snapshot/EReadPackUserType.cs

@@ -0,0 +1,20 @@
+using System.ComponentModel;
+namespace Hotline.Share.Enums.Snapshot;
+
+/// <summary>
+/// 红包领取人类型
+/// </summary>
+public enum EReadPackUserType
+{
+    /// <summary>
+    /// 市民
+    /// </summary>
+    [Description("市民")]
+    Citizen = 0,
+
+    /// <summary>
+    /// 网络员
+    /// </summary>
+    [Description("网络员")]
+    Gukder = 1
+}

+ 26 - 0
src/Hotline.Share/Enums/Snapshot/ERedPackAuditStatus.cs

@@ -0,0 +1,26 @@
+using System.ComponentModel;
+
+namespace Hotline.Share.Enums.Snapshot;
+/// <summary>
+/// 红包审核状态
+/// </summary>
+public enum ERedPackAuditStatus
+{
+    /// <summary>
+    /// 待审批
+    /// </summary>
+    [Description("待审批")]
+    Pending,
+
+    /// <summary>
+    /// 同意
+    /// </summary>
+    [Description("同意")]
+    Agree,
+
+    /// <summary>
+    /// 拒绝
+    /// </summary>
+    [Description("拒绝")]
+    Refuse,
+}

+ 33 - 0
src/Hotline.Share/Enums/Snapshot/ERedPackPickupStatus.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.Snapshot;
+
+/// <summary>
+/// 红包领取状态
+/// </summary>
+[Description("红包领取状态")]
+public enum ERedPackPickupStatus
+{
+    /// <summary>
+    /// 未领取
+    /// </summary>
+    [Description("未领取")]
+    Unreceived = 0,
+
+    /// <summary>
+    /// 已领取
+    /// </summary>
+    [Description("已领取")]
+    Received = 1,
+
+    /// <summary>
+    /// 退回
+    /// </summary>
+    [Description("退回")]
+    Back = 2
+}

+ 26 - 0
src/Hotline.Share/Enums/Snapshot/ESnapshopApproveType.cs

@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Share.Enums.Snapshot;
+
+/// <summary>
+/// 短信审批类型
+/// </summary>
+public enum ESnapshopApproveType
+{
+    /// <summary>
+    /// 同意
+    /// </summary>
+    [Description("同意")]
+    Agree = 0,
+
+    /// <summary>
+    /// 不同意
+    /// </summary>
+    [Description("不同意")]
+    Disagree = 1
+}

+ 7 - 0
src/Hotline.Share/Tools/DateTimeExtensions.cs

@@ -17,4 +17,11 @@ public static class DateTimeExtensions
     {
         return (int)(beginDateTime - endDateTime).TotalSeconds;
     }
+
+    public static (DateTime, DateTime) DayStartAndEndTime(this DateTime dateTime)
+    {
+        var start = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day);
+        var end = start.AddDays(1).AddTicks(-1);
+        return (start, end);
+    }
 }

+ 23 - 0
src/Hotline.Share/Tools/MapperExtensions.cs

@@ -0,0 +1,23 @@
+using MapsterMapper;
+
+namespace Hotline.Share.Tools
+{
+    /// <summary>
+    /// mapper 扩展
+    /// </summary>
+    public static class MapperExtensions
+    {
+        /// <summary>
+        /// 创建映射,使用源对象创建目标对象
+        /// </summary>
+        /// <typeparam name="TTarget">目标类型</typeparam>
+        /// <param name="source">源对象</param>
+        /// <returns>创建的目标对象</returns>
+        public static TTarget MapTo<TTarget>(this object source)
+        {
+            var mapper = (IMapper)ServiceLocator.Instance.GetService(typeof(IMapper));
+            if (mapper == null) return default;
+            return mapper.Map<TTarget>(source);
+        }
+    }
+}

+ 20 - 0
src/Hotline.WeChat/Hotline.WeChat.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Senparc.Weixin" Version="6.19.1" />
+    <PackageReference Include="Senparc.Weixin.AspNet" Version="1.3.1" />
+    <PackageReference Include="Senparc.Weixin.Open" Version="4.20.0" />
+    <PackageReference Include="Senparc.Weixin.WxOpen" Version="3.20.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Hotline\Hotline.csproj" />
+  </ItemGroup>
+
+</Project>

+ 48 - 0
src/Hotline.WeChat/WeChatService.cs

@@ -0,0 +1,48 @@
+using Hotline.CallCenter.Calls;
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Users;
+using Microsoft.Extensions.Logging;
+using Senparc.CO2NET.Extensions;
+using Senparc.Weixin;
+using Senparc.Weixin.WxOpen.AdvancedAPIs.Sns;
+using Senparc.Weixin.WxOpen.AdvancedAPIs.WxApp;
+using System.Configuration;
+using XF.Domain.Dependency;
+
+namespace Hotline.WeChat;
+
+public class WeChatService : IThirdIdentiyService
+{
+    private readonly ILogger<WeChatService> _logger;
+
+    public WeChatService(ILogger<WeChatService> logger)
+    {
+        _logger = logger;
+    }
+
+    public async Task<ThirdTokenOutDto> GetTokenAsync(ThirdTokenDto dto)
+    {
+        var result = await SnsApi.JsCode2JsonAsync(dto.AppId, dto.Secret, dto.Code);
+        if (result.errcode != ReturnCode.请求成功) return new ThirdTokenOutDto();
+        return new ThirdTokenOutDto() { SessionKey = result.session_key , OpenId = result.openid};
+    }
+
+    /// <summary>
+    /// 获取手机号码
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <returns></returns>
+    public async Task<ThirdPhoneOutDto> GetPhoneNumberAsync(ThirdTokenDto dto)
+    {
+        _logger.LogInformation($"GetPhoneNumberAsync: {dto.ToJson()}");
+        var result = await BusinessApi.GetUserPhoneNumberAsync(dto.AppId, dto.Code);
+        if (result.errcode != ReturnCode.请求成功)
+            _logger.LogError($"GetPhoneNumberAsync: {result.ToJson()}");
+        return new ThirdPhoneOutDto() {
+            ErrorCode = (int)result.errcode,
+            IsError = result.errcode != ReturnCode.请求成功,
+            ErrorMessage = result.errmsg,
+            PhoneNumber = result.phone_info.phoneNumber 
+        };
+    }
+}

+ 4 - 0
src/Hotline/Hotline.csproj

@@ -22,4 +22,8 @@
     <ProjectReference Include="..\XF.Domain.Repository\XF.Domain.Repository.csproj" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Folder Include="Snapshot\Interfaces\" />
+  </ItemGroup>
+
 </Project>

+ 74 - 0
src/Hotline/SeedData/SnapshotSeedData.cs

@@ -0,0 +1,74 @@
+using Hotline.Snapshot;
+using XF.Domain.Entities;
+
+namespace Hotline.SeedData;
+public class IndustrySeedData : ISeedData<Industry>
+{
+    public IEnumerable<Industry> HasData()
+    {
+        return new List<Industry>
+        {
+            new() { 
+                Name = "电气焊作业申报",
+                TitleSuffix = "的申报",
+                ApproveOrgId = "", ApproveOrgName = "自贡市应急管理局",
+                AcceptType = "申报", AcceptTypeCode = "",
+                CitizenReadPackAmount = 2000, GuiderReadPackAmount = 0, IsEnable = true,
+                TxtHelpRemarks = "不需要申报作业的情形",
+                TxtRemarks = "主动申报奖励20元",
+                TxtCareRemarks = "主动申报奖励20元",
+                DisplayOrder = 1,
+                IntervalTime = 0,
+                PageUrl = "/pagesBase/Write/WeldingOperations",
+                PageCareUrl = "/pagesCare/Write/WeldingOperations",
+                BulletinTypePublicityId = "", BulletinTypePublicityName = "",
+                BulletinTypeGuideId = "", BulletinTypeGuideame = "电气焊作业申报操作指引" ,
+                BackgroundImgUrl = "",
+                BannerImgUrl = "",
+                CellImgUrl = "/App_Themes/_Temp/%e7%94%b5%e6%b0%94%e7%84%8a%e4%bd%9c%e4%b8%9a_SSP_Industry_ICO_9.png",
+                CareCellImgUrl = "/App_Themes/_Temp/%e7%94%b5%e6%b0%94%e7%84%8a%e4%bd%9c%e4%b8%9a_SSP_Industry_CareICO_9.png"
+            },
+            new() {
+                Name = "文化旅游",
+                TitleSuffix = "的线索",
+                ApproveOrgId = "", ApproveOrgName = "自贡市文化广播电视和旅游局",
+                AcceptType = "举报", AcceptTypeCode = "",
+                CitizenReadPackAmount = 1000, GuiderReadPackAmount = 0, IsEnable = true,
+                DisplayOrder = 2,
+                IntervalTime = 1,
+                PageUrl = "/pagesBase/Write/ReportHiddenDanger",
+                PageCareUrl = "/pagesCare/Write/ReportHiddenDanger",
+                TxtHelpRemarks = "文化旅游受理及奖励范围:A级景区内设施设备安全隐患;二、A级景区旅游秩序;三、A级景区服务质量问题;四、星级旅游价格问题、服务标准、经营问题等,更多内容点击查看《操作指引》",
+                TxtRemarks = "A级景区/星级酒店/文保单位安全隐患及服务质量等问题",
+                TxtCareRemarks = "A级景区/星级酒店/文保单位安全隐患及服务质量等问题",
+                CellImgUrl = "/App_Themes/_Temp/%e6%96%87%e5%8c%96%e6%97%85%e6%b8%b8_SSP_Industry_ICO_8.png",
+                CareCellImgUrl = "/App_Themes/_Temp/%e6%96%87%e5%8c%96%e6%97%85%e6%b8%b8_SSP_Industry_CareICO_8.png",
+                BulletinTypePublicityId = "", BulletinTypePublicityName = "文化旅游宣传学习",
+                BulletinTypeGuideId = "", BulletinTypeGuideame = "文化旅游操作指引" ,
+                BackgroundImgUrl = "",
+                BannerImgUrl = "",
+            },
+            new() {
+                Name = "交通管理设施隐患",
+                TitleSuffix = "的线索",
+                AcceptType = "举报", AcceptTypeCode = "",
+                CitizenReadPackAmount = 1000, GuiderReadPackAmount = 0, IsEnable = true,
+                DisplayOrder = 3,
+                IntervalTime = 0,
+                ApproveOrgId = "", ApproveOrgName = "自贡市公安局交通警察支队",
+                BulletinTypePublicityId = "", BulletinTypePublicityName = "交通管理设施隐患宣传学习",
+                BulletinTypeGuideId = "", BulletinTypeGuideame = "交通管理设施隐患操作指引",
+                PageUrl = "/pagesBase/Write/ReportHiddenDanger",
+                PageCareUrl = "/pagesCare/Write/ReportHiddenDanger",
+                TxtHelpRemarks = "交通管理设施隐患受理范围:一、交通信号灯故障,如:路口单方向或多方向灯组熄灯、单灯组多灯全亮、红灯闪烁、路口多方向全绿灯等;二、LED显示屏故障,如:显示屏中间断字(黑屏)、异常闪烁等情形。更多内容点击查看《操作指引》",
+                TxtRemarks = "交通信号故障/交通诱导屏(LED)显示屏故障",
+                TxtCareRemarks = "交通信号故障/交通诱导屏(LED)显示屏故障",
+                CellImgUrl = "/App_Themes/_Temp/%e4%ba%a4%e9%80%9a%e7%ae%a1%e7%90%86_SSP_Industry_ICO_7.png",
+                CareCellImgUrl = "/App_Themes/_Temp/%e4%ba%a4%e9%80%9a%e7%ae%a1%e7%90%86_SSP_Industry_CareICO_7.png",
+                BackgroundImgUrl = "/App_Themes/_Temp/iwEcAqNwbmcDAQTRArwF0QCWBrDKpdDh3jYgLwZj6roYgSUAB9ICVXjkCAAJomltCgAL0gABP28.png_720x720q90_SSP_Industry_BJ_7.jpg",
+                BannerImgUrl = "/App_Themes/_Temp/%e4%ba%a4%e9%80%9a%e7%ae%a1%e7%90%86_SSP_Industry_banner_7.jpg",
+            },
+
+        };
+    }
+}

+ 10 - 0
src/Hotline/Settings/SettingConstants.cs

@@ -601,5 +601,15 @@ namespace Hotline.Settings
         /// 如果回访通话记录有多条, 需要关联通话时长最长的那条
         /// </summary>
         public const string VisitCallDelaySecond = "VisitCallDelaySecond";
+
+        /// <summary>
+        /// 微信小程序OpenId
+        /// </summary>
+        public const string WxOpenAppId = "WxOpenAppId";
+
+        /// <summary>
+        /// 微信小程序Secret
+        /// </summary>
+        public const string WxOpenAppSecret = "WxOpenAppSecret";
     }
 }

+ 36 - 0
src/Hotline/Snapshot/CommunityInfo.cs

@@ -0,0 +1,36 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 社区信息
+/// 定期从网格员系统获取
+/// </summary>
+[Description("社区信息")]
+public class CommunityInfo : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 社区Code
+    /// </summary>
+    [SugarColumn(ColumnDescription = "社区Code")]
+    public string Code { get; set; }
+
+    /// <summary>
+    /// 社区名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "社区名称")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 父社区Code
+    /// </summary>
+    [SugarColumn(ColumnDescription = "父社区Code")]
+    public string ParentCode { get; set; }
+}

+ 22 - 0
src/Hotline/Snapshot/GuiderInfo.cs

@@ -0,0 +1,22 @@
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 网格员信息
+/// 当网格系统回推信息时, 填入网格员信息
+/// </summary>
+[Description("网格员信息")]
+public class GuiderInfo : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 姓名
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 手机号码
+    /// </summary>
+    public string PhoneNumber { get; set; }
+}

+ 7 - 0
src/Hotline/Snapshot/IThirdAccountRepository.cs

@@ -0,0 +1,7 @@
+namespace Hotline.Snapshot;
+public interface IThirdAccountRepository
+{
+    Task<string> AddAsync(ThirdAccount entity);
+    Task<ThirdAccount> QueryByOpenIdAsync(string openId);
+    Task UpdateAsync(ThirdAccount entity);
+}

+ 160 - 0
src/Hotline/Snapshot/Industry.cs

@@ -0,0 +1,160 @@
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 行业
+/// </summary>
+[Description("行业")]
+public class Industry : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 行业名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行业名称")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 标题追加信息
+    /// </summary>
+    [SugarColumn(ColumnDescription ="标题追加信息")]
+    public string TitleSuffix { get; set; }
+
+    /// <summary>
+    /// 审批部门Id
+    /// </summary>
+    [SugarColumn(ColumnDescription ="审批部门Id")]
+    public string? ApproveOrgId { get; set; }
+
+    /// <summary>
+    /// 审批部门名字
+    /// </summary>
+    [SugarColumn(ColumnDescription ="审批部门名字")]
+    public string? ApproveOrgName { get; set; }
+
+    /// <summary>
+    /// 受理类型
+    /// </summary>
+    [SugarColumn(ColumnDescription ="受理类型")]
+    public string AcceptType { get; set; }
+
+    /// <summary>
+    /// 受理类型代码
+    /// </summary>
+    [SugarColumn(ColumnDescription ="受理类型代码")]
+    public string? AcceptTypeCode { get; set; }
+
+    /// <summary>
+    /// 市民发放红包金额(单位:分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市民发放红包金额(单位:分)")]
+    public int CitizenReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 网络员发放红包金额(单位:分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网络员发放红包金额(单位:分)")]
+    public int? GuiderReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否启用")]
+    public bool IsEnable { get; set; }
+
+    /// <summary>
+    /// 帮助引导用语
+    /// </summary>
+    [SugarColumn(ColumnDescription ="帮助引导用语")]
+    public string? TxtHelpRemarks    { get; set; }
+
+    /// <summary>
+    /// 宫格说明文本
+    /// </summary>
+    [SugarColumn(ColumnDescription ="宫格说明文本")]
+    public string TxtRemarks { get; set; }
+
+    /// <summary>
+    /// 关怀说明
+    /// </summary>
+    [SugarColumn(ColumnDescription ="关怀说明")]
+    public string TxtCareRemarks { get; set; }
+
+    /// <summary>
+    /// 排序
+    /// </summary>
+    [SugarColumn(ColumnDescription ="排序")]
+    public int DisplayOrder { get; set; }
+
+    /// <summary>
+    /// 阶段性回复间隔时间(小时)
+    /// </summary>
+    [SugarColumn(ColumnDescription ="阶段性回复间隔时间(小时)")]
+    public int IntervalTime {get;set;}
+
+    /// <summary>
+    /// 页面Url
+    /// </summary>
+    [SugarColumn(ColumnDescription ="页面Url")]
+    public string PageUrl { get; set; }
+
+    /// <summary>
+    /// 关怀页面Url
+    /// </summary>
+    [SugarColumn(ColumnDescription ="关怀页面Url")]
+    public string PageCareUrl { get; set; }
+
+    /// <summary>
+    /// 关联宣传学习
+    /// 从字典中取"公告类型"
+    /// </summary>
+    [SugarColumn(ColumnDescription ="关联宣传学习")]
+    public string BulletinTypePublicityId { get; set; }
+
+    /// <summary>
+    /// 关联宣传学习
+    /// 从字典中取"公告类型"
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关联宣传学习")]
+    public string BulletinTypePublicityName { get; set; }
+
+    /// <summary>
+    /// 关联操作指引
+    /// 从字典中取"公告类型"
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关联操作指引")]
+    public string BulletinTypeGuideId { get; set; }
+
+    /// <summary>
+    /// 关联操作指引
+    /// 从字典中取"公告类型"
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关联操作指引")]
+    public string BulletinTypeGuideame { get; set; }
+
+    /// <summary>
+    /// 背景图片 url
+    /// </summary>
+    [SugarColumn(ColumnDescription = "背景图片 url")]
+    public string BackgroundImgUrl { get; set; }
+
+    /// <summary>
+    /// Banner 图片 url
+    /// </summary>
+    [SugarColumn(ColumnDescription = "Banner 图片 url")]
+    public string BannerImgUrl { get; set; }
+
+    /// <summary>
+    /// 宫格图
+    /// </summary>
+    [SugarColumn(ColumnDescription = "宫格图")]
+    public string CellImgUrl { get; set; }
+
+    /// <summary>
+    /// 关怀宫格图
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关怀宫格图")]
+    public string CareCellImgUrl { get; set; }
+}

+ 48 - 0
src/Hotline/Snapshot/IndustryCase.cs

@@ -0,0 +1,48 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 行业线索
+/// </summary>
+[Description("行业线索")]
+public class IndustryCase : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 线索名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "线索名称")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 市民发放红包金额(单位:分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市民发放红包金额(单位:分)")]
+    public int CitizenReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 网络员发放红包金额(单位:分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网络员发放红包金额(单位:分)")]
+    public int GuiderReadPackAmount { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否启用")]
+    public bool IsEnable { get; set; }
+
+    /// <summary>
+    /// 排序
+    /// </summary>
+    [SugarColumn(ColumnDescription = "排序")]
+    public int DisplayOrder { get; set; }
+
+}

+ 60 - 0
src/Hotline/Snapshot/IndustryChangeRecord.cs

@@ -0,0 +1,60 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 工单行业修改记录
+/// </summary>
+[Description("工单行业修改记录")]
+public class IndustryChangeRecord : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 关联工单编号
+    /// </summary>    
+    [SugarColumn(ColumnDescription = "关联工单编号")]
+    public string OrderId { get; set; }
+
+    /// <summary>
+    /// 操作人
+    /// </summary>
+    [SugarColumn(ColumnDescription = "操作人")]
+    public string OperatorId { get; set; }
+
+    /// <summary>
+    /// 操作人
+    /// </summary>
+    [SugarColumn(ColumnDescription = "操作人")]
+    public string OperatorName { get; set; }
+
+    /// <summary>
+    /// 原行业类型
+    /// </summary>
+    [SugarColumn(ColumnDescription = "原行业类型")]
+    public string OriginalIndustryName{get;set;}
+
+    /// <summary>
+    /// 原行业类型ID
+    /// </summary>
+    [SugarColumn(ColumnDescription = "原行业类型ID")]
+    public string OriginalIndustryId { get; set; }
+
+    /// <summary>
+    /// 新行业类型
+    /// </summary>
+    [SugarColumn(ColumnDescription = "新行业类型")]
+    public string NewIndustryName { get; set; }
+
+    /// <summary>
+    /// 新行业类型ID
+    /// </summary>
+    [SugarColumn(ColumnDescription = "新行业类型ID")]
+    public string NewIndustryId { get; set; }
+
+}

+ 59 - 0
src/Hotline/Snapshot/InteractionLog.cs

@@ -0,0 +1,59 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 微信接口交互日志
+/// </summary>
+[Description("微信接口交互日志")]
+public class InteractionLog : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 红包记录Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包记录Id")]
+    public string RedPackRecordId { get; set; }
+
+    /// <summary>
+    /// 微信Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "微信Id")]
+    public string WXOpenId { get; set; }
+
+    /// <summary>
+    /// 商户号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "商户号")]
+    public string MerchantCode { get; set; }
+
+    /// <summary>
+    /// 调用入参
+    /// </summary>
+    [SugarColumn(ColumnDescription = "调用入参")]
+    public string RequestData { get; set; }
+
+    /// <summary>
+    /// 调用反参数
+    /// </summary>
+    [SugarColumn(ColumnDescription = "调用反参数")]
+    public string ResponseData { get; set; }
+
+    /// <summary>
+    /// 是否成功
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否成功")]
+    public bool IsSuccess { get; set; }
+
+    /// <summary>
+    /// 错误信息
+    /// </summary>
+    [SugarColumn(ColumnDescription = "错误信息")]
+    public string? ErrorMessage { get; set; }
+}

+ 49 - 0
src/Hotline/Snapshot/InviteCode.cs

@@ -0,0 +1,49 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 邀请码
+/// </summary>
+//[Description("邀请码")]
+//public class InviteCode : CreationSoftDeleteEntity
+//{
+//    /// <summary>
+//    /// 邀请码开始
+//    /// 邀请码开始 -> 结束 范围内的邀请码都有效
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "邀请码开始")]
+//    public string BeginCode { get; set; }
+
+//    /// <summary>
+//    /// 邀请码结束
+//    /// 邀请码开始 -> 结束 范围内的邀请码都有效
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "邀请码结束")]
+//    public string EndCode { get; set; }
+
+//    /// <summary>
+//    /// 部门名称
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "部门名称")]
+//    public string OrgName { get; set; }
+
+//    /// <summary>
+//    /// 上级部门ID
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "上级部门ID")]
+//    public string ParentOrgId { get; set; }
+
+//    /// <summary>
+//    /// 邀请码Url
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "邀请码Url")]
+//    public string QRCodeUrl { get; set; }
+//}

+ 47 - 0
src/Hotline/Snapshot/InviteCodeRecord.cs

@@ -0,0 +1,47 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 邀请记录
+/// </summary>
+//[Description("邀请记录")]
+//public class InviteCodeRecord : CreationSoftDeleteEntity
+//{
+//    /// <summary>
+//    /// 部门名称
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "部门名称")]
+//    public string OrgName { get; set; }
+
+//    /// <summary>
+//    /// 邀请码
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "邀请码")]
+//    public string InviteCode { get; set; }
+
+//    /// <summary>
+//    /// 微信OpenId
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "微信OpenId")]
+//    public string WXOpenId { get; set; }
+
+//    /// <summary>
+//    /// 电话
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "电话")]
+//    public string PhoneNumber { get; set; }
+
+//    /// <summary>
+//    /// 姓名
+//    /// </summary>
+//    [SugarColumn(ColumnDescription = "姓名")]
+//    public string Name { get; set; }
+//}

+ 82 - 0
src/Hotline/Snapshot/OrderSnapshot.cs

@@ -0,0 +1,82 @@
+using Hotline.Orders;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 工单表扩展
+/// </summary>
+[Description("工单表扩展")]
+public class OrderSnapshot : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 关联工单编号
+    /// <inheritdoc cref="Order"/>表的Id字段
+    /// </summary>    
+    [SugarColumn(ColumnDescription = "关联工单编号")]
+    public string OrderId { get; set; }
+
+    /// <summary>
+    /// 行业Id
+    /// <inheritdoc cref="Industry"/> 表的Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行业Id")]
+    public string IndustryId { get; set; }
+
+    /// <summary>
+    /// 行业名称
+    /// <inheritdoc cref="Industry"/> 表的Name
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行业名称")]
+    public string? IndustryName { get; set; }
+
+    /// <summary>
+    /// 社区Id
+    /// <inheritdoc cref="CommunityInfo"/> 表的Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "社区Id")]
+    public string CommunityId { get; set; }
+
+    #region 网格员回复
+
+    /// <summary>
+    /// 网格员是否办理
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格员是否办理")]
+    public bool IsDeal { get; set; }
+
+    /// <summary>
+    /// 网格E通编号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格E通编号")]
+    public string? NetworkENumber { get; set; }
+
+    /// <summary>
+    /// 是否属实
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否属实")]
+    public bool IsTruth { get; set; }
+
+    /// <summary>
+    /// 是否重复
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否重复")]
+    public bool IsRepetition { get; set; }
+
+    /// <summary>
+    /// 是否隐患
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否隐患")]
+    public bool IsDanger { get; set; }
+
+    /// <summary>
+    /// 网格员回复内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "网格员回复内容")]
+    public string NetworkRemark { get; set; }
+
+
+    #endregion
+}

+ 101 - 0
src/Hotline/Snapshot/RedPackAudit.cs

@@ -0,0 +1,101 @@
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+using Hotline.Orders;
+using Hotline.Share.Enums.Snapshot;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 市民红包审核
+/// </summary>
+[Description("市民红包审核")]
+public class RedPackAudit : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 关联工单编号
+    /// <inheritdoc cref="Order"/>表的Id字段
+    /// </summary>    
+    [SugarColumn(ColumnDescription = "关联工单编号")]
+    public string OrderId { get; set; }
+
+    /// <summary>
+    /// 审核状态
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审核状态")]
+    public ERedPackAuditStatus Status { get; set; }
+
+    /// <summary>
+    /// 审批时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批时间")]
+    public DateTime? AuditTime { get; set; }
+
+    /// <summary>
+    /// 配置金额(分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "配置金额(分)")]
+    public int ShouldAmount { get; set; }
+
+    /// <summary>
+    /// 审批金额(分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批金额(分)")]
+    public int? ApprovedAmount { get; set; }
+
+    /// <summary>
+    /// 实发金额(分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "实发金额(分)")]
+    public int? AcutalAmount { get; set; }
+
+    /// <summary>
+    /// 市民奖励发放结果
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市民奖励发放结果")]
+    public bool IsSend { get; set; }
+
+    /// <summary>
+    /// 市民奖励发放备注
+    /// </summary>
+    [SugarColumn(ColumnDescription = "市民奖励发放备注")]
+    public string? SendRemarks { get; set; }
+
+    /// <summary>
+    /// 操作人
+    /// </summary>
+    [SugarColumn(ColumnDescription = "操作人")]
+    public string? AuditId { get; set; }
+
+    /// <summary>
+    /// 操作人
+    /// </summary>
+    [SugarColumn(ColumnDescription = "操作人")]
+    public string? AuditName { get; set; }
+
+    /// <summary>
+    /// 审批部门
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批部门")]
+    public string? AuditOrgId { get; set; }
+
+    /// <summary>
+    /// 审批部门名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批部门名称")]
+    public string? AuditOrgName { get; set; }
+
+    /// <summary>
+    /// 审批意见
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批意见")]
+    public string? AuditRemark { get; set; }
+
+    public string? Remark { get; set; }
+
+}

+ 81 - 0
src/Hotline/Snapshot/RedPackRecord.cs

@@ -0,0 +1,81 @@
+using Hotline.Share.Enums.Snapshot;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+using Hotline.Orders;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 红包发放记录
+/// </summary>
+[Description("红包发放记录")]
+public class RedPackRecord : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 关联工单编号
+    /// <inheritdoc cref="Order"/>表的Id字段
+    /// </summary>    
+    [SugarColumn(ColumnDescription = "关联工单编号")]
+    public string OrderId { get; set; }
+
+    /// <summary>
+    /// 微信返回的订单号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "微信返回的订单号")]
+    public string? WxOrderNo { get; set; }
+
+    /// <summary>
+    /// 红包金额
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包金额")]
+    public int Amount {get;set;}
+
+    /// <summary>
+    /// 用户微信OpenId
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户微信OpenId")]
+    public string WXOpenId { get; set; }
+
+    /// <summary>
+    /// 红包领取人类型
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包领取人类型")]
+    public EReadPackUserType PeopleType { get; set; }
+
+    /// <summary>
+    /// 领取人姓名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "领取人姓名")]
+    public string? Name { get; set; }
+
+    /// <summary>
+    /// 商户号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "商户号")]
+    public string? MerchantCode { get; set; }
+
+    /// <summary>
+    /// 红包发放状态
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包发放状态")]
+    public EReadPackSendStatus DistributionState {get;set;}
+
+    /// <summary>
+    /// 红包领取状态
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包领取状态")]
+    public ERedPackPickupStatus PickupStatus { get; set; }
+
+    /// <summary>
+    /// 备注
+    /// </summary>
+    [SugarColumn(ColumnDescription = "备注")]
+    public string? Remark { get; set; }
+
+    /// <summary>
+    /// 红包领取时间
+    /// </summary>
+    [SugarColumn(ColumnDescription = "红包领取时间")]
+    public DateTime? ReceiveTime { get; set; }
+}

+ 49 - 0
src/Hotline/Snapshot/SnapshotMessageTemplate.cs

@@ -0,0 +1,49 @@
+using SqlSugar;
+using XF.Domain.Repository;
+using Hotline.Push;
+using System.ComponentModel;
+using Hotline.Share.Enums.Snapshot;
+
+namespace Hotline.Snapshot;
+
+[Description("随手拍短信模板设置")]
+public class SnapshotMessageTemplate : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 短信模板代码
+    /// 表<inheritdoc cref="MessageTemplate"/>中的Code字段
+    /// </summary>
+    [SugarColumn(ColumnDescription = "MessageTemplateCode")]
+    public string MessageTemplateCode { get; set; }
+
+    /// <summary>
+    /// 行业页面Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "行业页面Id")]
+    public string IndustrPageId { get; set; }
+
+    /// <summary>
+    /// 审批类型
+    /// </summary>
+    [SugarColumn(ColumnDescription = "审批类型")]
+    public ESnapshopApproveType ApproveType { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否启用")]
+    public bool IsEnable { get; set; }
+
+    /// <summary>
+    /// 是否公用
+    /// 公用后不受行业页面限制
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否公用")]
+    public bool IsPublic { get; set; }
+
+    /// <summary>
+    /// 排序
+    /// </summary>
+    [SugarColumn(ColumnDescription = "排序")]
+    public int DisplayOrder { get; set; }
+}

+ 28 - 0
src/Hotline/Snapshot/Test/ThirdTestService.cs

@@ -0,0 +1,28 @@
+using Hotline.Share.Dtos.Snapshot;
+using Hotline.Users;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Hotline.Snapshot.Test;
+public class ThirdTestService : IThirdIdentiyService
+{
+    public async Task<ThirdPhoneOutDto> GetPhoneNumberAsync(ThirdTokenDto dto)
+    {
+        return new ThirdPhoneOutDto
+        {
+            PhoneNumber = "13800138000"
+        };
+    }
+
+    public async Task<ThirdTokenOutDto> GetTokenAsync(ThirdTokenDto dto)
+    {
+        return new ThirdTokenOutDto 
+        {
+            SessionKey = "sessionKeyfjdklsafjdskla",
+            OpenId = "测试生成的OpenId"
+        };
+    }
+}

+ 59 - 0
src/Hotline/Snapshot/ThirdAccount.cs

@@ -0,0 +1,59 @@
+using Hotline.Share.Enums.Snapshot;
+using SqlSugar;
+using System.ComponentModel;
+using XF.Domain.Repository;
+using Hotline.Orders;
+
+namespace Hotline.Snapshot;
+
+/// <summary>
+/// 随手拍用户微信小程序登录信息
+/// </summary>
+[Description("小程序账号信息")]
+public class ThirdAccount : CreationSoftDeleteEntity
+{
+    /// <summary>
+    /// 关联用户信息
+    /// 如果是市民: 关联 <inheritdoc cref="Citizen"/>
+    /// 如果是网格员: 关联 <inheritdoc cref="GuiderInfo"/>
+    /// </summary>
+    [SugarColumn(ColumnDescription = "关联用户信息")]
+    public string? UserId { get; set; }
+
+    /// <summary>
+    /// 用户类型
+    /// 注册时根据手机号码判断是否是 网格员
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户类型")]
+    public EReadPackUserType CitizenType { get; set; }
+
+    /// <summary>
+    /// 电话
+    /// </summary>
+    [SugarColumn(ColumnDescription = "电话")]
+    public string? PhoneNumber { get; set; }
+
+    /// <summary>
+    /// 微信Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "微信Id")]
+    public string WXOpenId { get; set; }
+    
+    /// <summary>
+    /// 微信SessionKey
+    /// </summary>
+    [SugarColumn(ColumnDescription = "微信SessionKey")]
+    public string? WXSessionKey { get; set; }
+
+    /// <summary>
+    /// 历史已经领取金额总和(分)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "历史已经领取金额总和(分)")]
+    public long TotalAmount { get; set; }
+
+    /// <summary>
+    /// 用户头像Url
+    /// </summary>
+    [SugarColumn(ColumnDescription = "用户头像Url")]
+    public string? HeadImgUrl { get; set; }
+}

+ 13 - 0
src/Hotline/Users/IThirdIdentiyService.cs

@@ -0,0 +1,13 @@
+using Hotline.Share.Dtos.Snapshot;
+
+namespace Hotline.Users;
+
+/// <summary>
+/// 第三方认证服务
+/// </summary>
+public interface IThirdIdentiyService
+{
+    Task<ThirdTokenOutDto> GetTokenAsync(ThirdTokenDto dto);
+
+    Task<ThirdPhoneOutDto> GetPhoneNumberAsync(ThirdTokenDto dto);
+}

+ 1 - 0
src/XF.Domain/Authentications/AppClaimTypes.cs

@@ -12,5 +12,6 @@
         public const string DepartmentAreaName = "department_areaname";
         public const string AreaId = "area_id";
         public const string StaffNo = "staff_no";
+        public const string OpenId = "open_id";
     }
 }

+ 3 - 0
src/XF.Domain/Authentications/DefaultSessionContext.cs

@@ -32,6 +32,7 @@ namespace XF.Domain.Authentications
             AreaId = user.FindFirstValue(AppClaimTypes.AreaId);
             ClientId = user.FindFirstValue(JwtClaimTypes.ClientId);
             StaffNo = user.FindFirstValue(AppClaimTypes.StaffNo);
+            OpenId = user.FindFirstValue(AppClaimTypes.OpenId);
         }
 
         /// <summary>
@@ -76,6 +77,8 @@ namespace XF.Domain.Authentications
         /// 工号
         /// </summary>
         public string? StaffNo { get; init; }
+
+        public string? OpenId { get; }
     }
 
     public static class ClaimsPrincipalExtensions

+ 5 - 0
src/XF.Domain/Authentications/ISessionContext.cs

@@ -45,4 +45,9 @@ public interface ISessionContext
     /// 工号
     /// </summary>
     string? StaffNo { get; init; }
+
+    /// <summary>
+    /// 微信OpenId
+    /// </summary>
+    string? OpenId { get; }
 }