Explorar o código

Merge branch 'test' of http://110.188.24.182:10023/Fengwo/hotline into test

田爽 hai 1 mes
pai
achega
92a748ad7e

+ 39 - 10
src/Hotline.Api/Controllers/IdentityController.cs

@@ -15,6 +15,7 @@ using XF.Domain.Constants;
 using XF.Domain.Exceptions;
 using Hotline.Share.Dtos.Snapshot;
 using Swashbuckle.AspNetCore.Annotations;
+using NETCore.Encrypt;
 
 namespace Hotline.Api.Controllers;
 
@@ -25,7 +26,7 @@ public class IdentityController : BaseController
     private readonly ISystemSettingCacheManager _systemSettingCacheManager;
     private readonly ISystemLogApplication _iSystemLogApplication;
 
-	private const string PublicKey = @"-----BEGIN PUBLIC KEY-----
+    private const string PublicKey = @"-----BEGIN PUBLIC KEY-----
 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgw+/x6IQPkH0A4eoF63j
 kLThsOXWyNBdcL9LATGy/G1yTHOr1RyKJB//iNug+V8DIoIHuFTlhgLHDbSqxvRW
 MONxIIF289riS6bDI4Ox/pFmOfmElFRk0lKGihaTE2Aefd6g/N+RfLLaHWztY+/v
@@ -62,17 +63,20 @@ Q9PP8NTEmKqdI3WVFYqW/OlOFC6sjiscTOOn9Tc5Mrcn8ocCjAPjkhkCCVRMiJnv
 jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
 -----END RSA PRIVATE KEY-----";
 
+    private const string AesKey = "qlzeJrbj0CPkHdFBvEAxX47Y4nCbBPZW";
+    private const string AesIv = "JxeDP0sgnPJdH9fE";
+
     public IdentityController(
         IOptionsSnapshot<AppConfiguration> appOptions,
-        IIdentityAppService identityAppService, 
-        ISystemSettingCacheManager systemSettingCacheManager, 
+        IIdentityAppService identityAppService,
+        ISystemSettingCacheManager systemSettingCacheManager,
         ISystemLogApplication iSystemLogApplication)
     {
         _appOptions = appOptions;
         _identityAppService = identityAppService;
         _systemSettingCacheManager = systemSettingCacheManager;
         _iSystemLogApplication = iSystemLogApplication;
-	}
+    }
 
     /// <summary>
     /// 登录
@@ -81,14 +85,30 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
     /// <returns></returns>
     [AllowAnonymous]
     [HttpPost("login")]
-    [LogFilter("",false)]
-	public async Task<string> Login([FromBody] LoginDto dto)
+    [LogFilter("", false)]
+    public async Task<string> Login([FromBody] LoginDto dto)
     {
         dto = Decrypt(dto);
         var res = await _identityAppService.LoginAsync(dto, HttpContext.RequestAborted);
         dto.Password = string.Empty;
-        await _iSystemLogApplication.AddLog("账号登录", res, dto, HttpContext,dto.Username);
-		return res ;
+        await _iSystemLogApplication.AddLog("账号登录", res, dto, HttpContext, dto.Username);
+        return res;
+    }
+
+    /// <summary>
+    /// 登录
+    /// </summary>
+    [AllowAnonymous]
+    [HttpPost("login-sign")]
+    public async Task<string> Login([FromBody] LoginSignatureDto dto)
+    {
+        var request = Decrypt(dto.Signature);
+        if (request is null)
+            throw UserFriendlyException.SameMessage("用户名或密码错误!");
+        var res = await _identityAppService.LoginWithSignatureAsync(request, HttpContext.RequestAborted);
+        request.Password = string.Empty;
+        await _iSystemLogApplication.AddLog("账号登录", res, request, HttpContext, request.Username);
+        return res;
     }
 
     /// <summary>
@@ -133,7 +153,7 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
         var faviconImage = _systemSettingCacheManager.GetSetting(SettingConstants.FaviconImage).SettingValue?.FirstOrDefault();
         var menuLogoImage = _systemSettingCacheManager.GetSetting(SettingConstants.MenuLogoImage).SettingValue?.FirstOrDefault();
         var menuLogoImageMini = _systemSettingCacheManager.GetSetting(SettingConstants.MenuLogoImageMini).SettingValue?.FirstOrDefault();
-        var IsLoginMessageCode = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsLoginMessageCode).SettingValue[0]); 
+        var IsLoginMessageCode = bool.Parse(_systemSettingCacheManager.GetSetting(SettingConstants.IsLoginMessageCode).SettingValue[0]);
         var recordNumber = _systemSettingCacheManager.GetSetting(SettingConstants.RecordNumber).SettingValue?.FirstOrDefault();
         var cityAbbr = _systemSettingCacheManager.GetSetting(SettingConstants.CityAbbr).SettingValue?.FirstOrDefault();
         var operate = _systemSettingCacheManager.GetSetting(SettingConstants.Operate).SettingValue?.FirstOrDefault();
@@ -172,11 +192,20 @@ jxrWXHbT1FB6DqkdOnBbQqS1Azqz5HxLlSyEK3F60e3SgB5iZsDZ
             var uname = pkcs1.Decrypt(dto.Username, RSAEncryptionPadding.Pkcs1);
             var pwd = pkcs1.Decrypt(dto.Password, RSAEncryptionPadding.Pkcs1);
             var msgCode = pkcs1.Decrypt(dto.MsgCode, RSAEncryptionPadding.Pkcs1);
-            return new LoginDto { Username = uname, Password = pwd,MsgCode=msgCode };
+            return new LoginDto { Username = uname, Password = pwd, MsgCode = msgCode };
         }
         catch (Exception e)
         {
             throw new UserFriendlyException($"解密失败:{e.Message}", "无效参数");
         }
     }
+
+    private LoginWithSignatureRequest? Decrypt(string signature)
+    {
+        if (string.IsNullOrEmpty(signature)) return null;
+        var decrypted = EncryptProvider.AESDecrypt(signature, AesKey, AesIv);
+        if(string.IsNullOrEmpty(decrypted)) return null;
+        return System.Text.Json.JsonSerializer.Deserialize<LoginWithSignatureRequest>(decrypted,
+            JsonDefaults.DefaultJsonSerializerOptionsWithCamelCase);
+    }
 }

+ 12 - 7
src/Hotline.Api/Controllers/OrderController.cs

@@ -2990,6 +2990,7 @@ public class OrderController : BaseController
     {
         var model = await _orderScreenRepository.Queryable(canView: false)
             .Includes(x => x.Order)
+            .Includes(x=>x.VisitDetail)
             .Includes(x => x.Workflow, d => d.Steps)
             .Includes(x => x.Visit, d => d.Order)
             .FirstAsync(x => x.Id == id);
@@ -3949,7 +3950,7 @@ public class OrderController : BaseController
         var orders = await _orderApplication.QueryOrders(dto)
             .ToPageListWithoutTotalAsync(dto, HttpContext.RequestAborted);
         var list = _mapper.Map<IReadOnlyList<OrderDto>>(orders);
-        if (_appOptions.Value.IsLuZhou && !_sessionContext.OrgIsCenter)
+        if ((_appOptions.Value.IsLuZhou || _appOptions.Value.IsZiGong) && !_sessionContext.OrgIsCenter)
             list = list.Select(p => p.DataMask()).ToList();
         return list;
     }
@@ -4397,12 +4398,16 @@ public class OrderController : BaseController
             await _orderSnapshotRepository.Queryable()
                 .LeftJoin<IndustryCase>((snapshot, industryCase) => snapshot.IndustryCase == industryCase.Id)
                 .Where((snapshot, industryCase) => snapshot.Id == order.Id)
-                .Select((snapshot, industryCase) => new {
-                    snapshot.IndustryId, snapshot.IndustryName, industryCase.Name, 
+                .Select((snapshot, industryCase) => new
+                {
+                    snapshot.IndustryId,
+                    snapshot.IndustryName,
+                    industryCase.Name,
                     snapshot.IsRectifyDepartment,
                     snapshot.IsDangerDepartment,
                     snapshot.IsSafetyDepartment,
-                    snapshot.SignRemark,snapshot.SignUserId,
+                    snapshot.SignRemark,
+                    snapshot.SignUserId,
                     snapshot.SignUserName,
                     snapshot.SignTime
                 })
@@ -5044,7 +5049,7 @@ public class OrderController : BaseController
         if (isSnapshotEnable)
         {
             var orderSnapShot = await _orderSnapshotRepository.GetAsync(orderId, HttpContext.RequestAborted);
-            if(orderSnapShot != null && string.CompareOrdinal(orderSnapShot.IndustryName, "安全隐患") == 0)
+            if (orderSnapShot != null && string.CompareOrdinal(orderSnapShot.IndustryName, "安全隐患") == 0)
             {
                 isAqyh = true;
                 dto.Steps.RemoveAll(d => d.BusinessType == EBusinessType.Send);
@@ -5305,7 +5310,7 @@ public class OrderController : BaseController
                 orderHandleFlowDto.CrossSteps = orderHandleFlowDto.CrossSteps.OrderBy(d => d.Sort).ToList();
                 var stepCount = orderHandleFlowDto.CrossSteps.Count;
                 var unhandleSteps = new List<WorkflowStep> { startStep };
-                for (int i = 0;i < stepCount;i++)
+                for (int i = 0; i < stepCount; i++)
                 {
                     var crossStep = orderHandleFlowDto.CrossSteps[i];
                     var tempSteps = new List<WorkflowStep>();
@@ -5503,7 +5508,7 @@ public class OrderController : BaseController
             var orderSnapShot = await _orderSnapshotRepository.GetAsync(orderId, HttpContext.RequestAborted);
             isAqyh = orderSnapShot != null && string.CompareOrdinal(orderSnapShot.IndustryName, "安全隐患") == 0;
         }
-        if(!isAqyh)
+        if (!isAqyh)
             dto.Steps = dto.Steps.Where(d => string.CompareOrdinal(d.Value, "网格员") != 0
                                              && string.CompareOrdinal(d.Value, "工单标记") != 0).ToList();
 

+ 24 - 2
src/Hotline.Api/Controllers/TestController.cs

@@ -1,4 +1,5 @@
 using System.Text;
+using System.Text.Json;
 using DotNetCore.CAP;
 using Hotline.Ai.Quality;
 using Hotline.Application.CallCenter;
@@ -31,6 +32,7 @@ using Hotline.Settings.TimeLimits;
 using Hotline.Share.Dtos;
 using Hotline.Share.Dtos.FlowEngine.Workflow;
 using Hotline.Share.Dtos.Home;
+using Hotline.Share.Dtos.Identity;
 using Hotline.Share.Dtos.Order;
 using Hotline.Share.Dtos.Realtime;
 using Hotline.Share.Dtos.Snapshot;
@@ -50,10 +52,12 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using MiniExcelLibs;
 using NETCore.Encrypt;
+using NETCore.Encrypt.Internal;
 using SqlSugar;
 using XC.RSAUtil;
 using XF.Domain.Authentications;
 using XF.Domain.Cache;
+using XF.Domain.Constants;
 using XF.Domain.Filters;
 using XF.Domain.Locks;
 using XF.Domain.Queues;
@@ -1290,8 +1294,26 @@ public class TestController : BaseController
     [AllowAnonymous]
     public async Task<string> CheckAES([FromBody] CheckTokenDto dto)
     {
-        var strString = dto.AppId + dto.Timestamp;
-        return dto.AppId + EncryptProvider.AESEncrypt(strString, dto.Secret, dto.AppId);
+        var aesKey = EncryptProvider.CreateAesKey();
+
+        var key = aesKey.Key;
+        var iv = aesKey.IV;
+
+        //var key = "qlzeJrbj0CPkHdFBvEAxX47Y4nCbBPZW";
+        //var iv = "JxeDP0sgnPJdH9fE";
+
+        var encrypted = EncryptProvider.AESEncrypt(dto.Str, dto.Key);
+        
+        //带加密向量
+        var encrypted1 = EncryptProvider.AESEncrypt(dto.Str, dto.Key, dto.Iv);
+
+        var decrypted = EncryptProvider.AESDecrypt(dto.Encrypted, dto.Key);
+        //带加密向量
+        var decrypted1 = EncryptProvider.AESDecrypt(dto.Encrypted, dto.Key, dto.Iv);
+
+        //var strString = dto.AppId + dto.Timestamp;
+        //return dto.AppId + EncryptProvider.AESEncrypt(strString, dto.Secret, dto.AppId);
+        return $"key: {key}, iv: {iv}";
     }
 
     /// <summary>

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

@@ -34,5 +34,13 @@ namespace Hotline.Application.Identity
         Task<string> OldToNewLoginAsync(HotlineLoginOldToNewDto dto, CancellationToken cancellationToken);
 
         Task<(bool,bool,User)> IsCheckAdmin(string userName);
+
+        /// <summary>
+        /// 登录
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<string> LoginWithSignatureAsync(LoginWithSignatureRequest request, CancellationToken cancellationToken);
     }
 }

+ 21 - 0
src/Hotline.Application/Identity/IdentityAppService.cs

@@ -23,6 +23,7 @@ using Hotline.ThirdAccountDomainServices.Interfaces;
 using Hotline.Users;
 using IdentityModel;
 using Mapster;
+using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Identity;
 using Microsoft.Extensions.Options;
 using SqlSugar;
@@ -39,6 +40,7 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
 {
     private readonly IAccountRepository _accountRepository;
     private readonly IAccountDomainService _accountDomainService;
+    private readonly IIdentityDomainService _identityDomainService;
     private readonly IRepository<User> _userRepository;
     private readonly IJwtSecurity _jwtSecurity;
     private readonly IOptionsSnapshot<IdentityConfiguration> _identityOptionsAccessor;
@@ -55,6 +57,7 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
     public IdentityAppService(
         IAccountRepository accountRepository,
         IAccountDomainService accountDomainService,
+        IIdentityDomainService identityDomainService,
         IRepository<User> userRepository,
         IJwtSecurity jwtSecurity,
         IOptionsSnapshot<IdentityConfiguration> identityOptionsAccessor,
@@ -70,6 +73,7 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
     {
         _accountRepository = accountRepository;
         _accountDomainService = accountDomainService;
+        _identityDomainService = identityDomainService;
         _userRepository = userRepository;
         _jwtSecurity = jwtSecurity;
         _identityOptionsAccessor = identityOptionsAccessor;
@@ -190,6 +194,23 @@ public class IdentityAppService : IIdentityAppService, IScopeDependency
         return (isAdmin, isCenter,user);
     }
 
+    /// <summary>
+    /// 登录
+    /// </summary>
+    /// <param name="request"></param>
+    /// <param name="cancellationToken"></param>
+    /// <returns></returns>
+    public async Task<string> LoginWithSignatureAsync(LoginWithSignatureRequest request, CancellationToken cancellationToken)
+    {
+        if (!_identityDomainService.IsIdentityReal(request))
+            throw UserFriendlyException.SameMessage("用户名或密码错误!");
+        var token = await LoginAsync(request, cancellationToken);
+        if(string.IsNullOrEmpty(token))
+            throw UserFriendlyException.SameMessage("用户名或密码错误!");
+        _identityDomainService.SetAccountNonce(request.Username, request.Nonce);
+        return token;
+    }
+
     public async Task<string> LoginAsync(LoginDto dto, CancellationToken cancellationToken)
     {
         var account = await _accountRepository.GetExtAsync(

+ 2 - 0
src/Hotline.Application/OrderApp/OrderApplication.cs

@@ -5175,6 +5175,8 @@ public class OrderApplication : IOrderApplication, IScopeDependency
             .OrderByIF(dto is { SortField: "startTime", SortRule: 1 }, d => d.StartTime, OrderByType.Desc) //受理时间降序
             .OrderByIF(dto is { SortField: "expiredTime", SortRule: 0 }, d => d.ExpiredTime, OrderByType.Asc) //期满时间升序
             .OrderByIF(dto is { SortField: "expiredTime", SortRule: 1 }, d => d.ExpiredTime, OrderByType.Desc) //期满时间降序
+            .OrderByIF(dto is { SortField: "currentStepAcceptTime", SortRule: 0 }, d => d.CurrentStepAcceptTime, OrderByType.Asc) //接办时间升序
+            .OrderByIF(dto is { SortField: "currentStepAcceptTime", SortRule: 1 }, d => d.CurrentStepAcceptTime, OrderByType.Desc) //接办时间降序
             ;
 
         return query;

+ 17 - 12
src/Hotline.Share/Dtos/Home/CheckTokenDto.cs

@@ -7,18 +7,23 @@ using System.Threading.Tasks;
 namespace Hotline.Share.Dtos.Home;
 public class CheckTokenDto
 {
-    /// <summary>
-    /// AppId
-    /// </summary>
-    public string AppId { get; set; }
+    ///// <summary>
+    ///// AppId
+    ///// </summary>
+    //public string AppId { get; set; }
 
-    /// <summary>
-    /// Secret
-    /// </summary>
-    public string Secret { get; set; }
+    ///// <summary>
+    ///// Secret
+    ///// </summary>
+    //public string Secret { get; set; }
 
-    /// <summary>
-    /// timestamp
-    /// </summary>
-    public long Timestamp { get; set; }
+    ///// <summary>
+    ///// timestamp
+    ///// </summary>
+    //public long Timestamp { get; set; }
+
+    public string Key { get; set; }
+    public string Iv { get; set; }
+    public string Str { get; set; }
+    public string Encrypted { get; set; }
 }

+ 3 - 2
src/Hotline.Share/Dtos/Identity/LoginDto.cs

@@ -18,7 +18,8 @@ namespace Hotline.Share.Dtos.Identity
         public string UserName { get; set; }
     }
 
-    public class HotlineStateResult { 
+    public class HotlineStateResult
+    {
         public string Code { get; set; }
 
         public bool? Result { get; set; }
@@ -47,7 +48,7 @@ namespace Hotline.Share.Dtos.Identity
         /// <summary>
         /// 菜单logo图片
         /// </summary>
-        public string? MenuLogoImage{ get; set; }
+        public string? MenuLogoImage { get; set; }
 
         /// <summary>
         /// 菜单logo图片mini

+ 6 - 0
src/Hotline.Share/Dtos/Identity/LoginSignatureDto.cs

@@ -0,0 +1,6 @@
+namespace Hotline.Share.Dtos.Identity;
+
+public class LoginSignatureDto
+{
+    public string Signature { get; set; }
+}

+ 7 - 0
src/Hotline.Share/Dtos/Identity/LoginWithSignatureRequest.cs

@@ -0,0 +1,7 @@
+namespace Hotline.Share.Dtos.Identity;
+
+public class LoginWithSignatureRequest : LoginDto
+{
+    public long Timestamp { get; set; }
+    public string Nonce { get; set; }
+}

+ 7 - 4
src/Hotline.Share/Dtos/Order/OrderDto.cs

@@ -881,10 +881,13 @@ namespace Hotline.Share.Dtos.Order
             this.FromName = maskString;
             this.FromGender = EGender.Unknown;
             this.FromPhone = maskString;
-            this.FullAddress = maskString;
-            this.Address = maskString;
-            this.City = maskString;
-            this.Street = maskString;
+            if (SourceChannelCode != "ZGSSP")
+            {
+                this.FullAddress = maskString;
+                this.Address = maskString;
+                this.City = maskString;
+                this.Street = maskString;
+            }
             this.ContactMask = maskString;
             return this;
         }

+ 5 - 4
src/Hotline/FlowEngine/Workflows/WorkflowDomainService.cs

@@ -1193,8 +1193,8 @@ namespace Hotline.FlowEngine.Workflows
                         handler.orgId, handler.orgName, handler.roleId, handler.roleName);
                     //更新节点CreationTime  派单量统计  待派单数据使用
                     step.CreationTime = DateTime.Now;
-					step.WorkflowTrace.CreationTime = DateTime.Now;
-				}
+                    step.WorkflowTrace.CreationTime = DateTime.Now;
+                }
             }
 
             var steps = handlers.SelectMany(d => d.steps).ToList();
@@ -2245,7 +2245,7 @@ namespace Hotline.FlowEngine.Workflows
                 await _workflowTraceRepository.UpdateAsync(pubTrace, cancellation);
 
                 //create visit trace
-                await CreateVisitTraceAsync(pubTrace, visitAcceptor, orderVisitId, cancellation);
+                await CreateVisitTraceAsync(pubTrace, visitAcceptor, orderVisitId, handleTime.AddSeconds(1), cancellation);
             }
             else
             {
@@ -3497,7 +3497,7 @@ namespace Hotline.FlowEngine.Workflows
         }
 
         private async Task<WorkflowTrace> CreateVisitTraceAsync(WorkflowTrace pubTrace, UserInfo acceptor, string orderVisitId,
-            CancellationToken cancellation)
+          DateTime creationTime, CancellationToken cancellation)
         {
             if (string.IsNullOrEmpty(orderVisitId))
                 throw new UserFriendlyException($"参数异常,orderVisitId不能为空, pubTraceId: {pubTrace.Id}");
@@ -3525,6 +3525,7 @@ namespace Hotline.FlowEngine.Workflows
             visitTrace.AcceptorOrgName = acceptor.OrgName;
 
             visitTrace.StepExpiredTime = null;
+            visitTrace.CreationTime = creationTime;
 
             await _workflowTraceRepository.AddAsync(visitTrace, cancellation);
 

+ 2 - 0
src/Hotline/Identity/AudienceTicket.cs

@@ -17,4 +17,6 @@
 
         public string Ticket { get; set; }
     }
+
+    public record AccountNonce(string Nonce);
 }

+ 69 - 0
src/Hotline/Identity/IIdentityDomainService.cs

@@ -0,0 +1,69 @@
+using Hotline.Share.Dtos.Identity;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Cache;
+using XF.Domain.Dependency;
+
+namespace Hotline.Identity
+{
+    public interface IIdentityDomainService
+    {
+        /// <summary>
+        /// 校验用户是否真实
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        bool IsIdentityReal(LoginWithSignatureRequest request);
+
+        /// <summary>
+        /// 设置账户随机数
+        /// </summary>
+        /// <param name="username"></param>
+        /// <param name="nonce"></param>
+        void SetAccountNonce(string username, string nonce);
+    }
+
+    public class IdentityDomainService : IIdentityDomainService, IScopeDependency
+    {
+        private readonly ITypedCache<AccountNonce> _cacheAccountNonce;
+
+        public IdentityDomainService(
+            ITypedCache<AccountNonce> cacheAccountNonce)
+        {
+            _cacheAccountNonce = cacheAccountNonce;
+        }
+
+        /// <summary>
+        /// 校验用户是否真实
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        public bool IsIdentityReal(LoginWithSignatureRequest request)
+        {
+            /*
+             *时间戳timestamp与服务器时间戳相差不能超过60s大于服务器时间戳
+               随机数nonce60s内不能重复
+             */
+            if (string.IsNullOrEmpty(request.Nonce)) return false;
+            var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            if (request.Timestamp >= now) return false;
+            if ((now - request.Timestamp) >= 60000) return false;
+            var nonce = _cacheAccountNonce.Get(request.Username)?.Nonce;
+            if (!string.IsNullOrEmpty(nonce) && string.CompareOrdinal(nonce, request.Nonce) == 0) return false;
+            return true;
+        }
+
+        /// <summary>
+        /// 设置账户随机数
+        /// </summary>
+        /// <param name="username"></param>
+        /// <param name="nonce"></param>
+        public void SetAccountNonce(string username, string nonce)
+        {
+            _cacheAccountNonce.Set(username, new AccountNonce(nonce), TimeSpan.FromSeconds(60));
+        }
+    }
+}

+ 26 - 0
src/Hotline/Identity/README.md

@@ -0,0 +1,26 @@
+### 登录加密方案
+
+#### 1. 登录参数
+
+| 名称 | 说明                                     |
+|-----|----------------------------------------|
+| signature | 签名字符 |
+
+#### 2. signature生成规则
+
+a. 签名包含参数:
+
+| 参数 | 说明                                     |
+|-----|----------------------------------------|
+| username | 账号 |
+| password | 密码 |
+| verifyCode | 验证码(开启短信验证码才有) |
+| timestamp | 时间戳 |
+| nonce | 随机数 |
+
+b. 以上参数序列化为json格式字符串,采用AES加密方式生成签名字符
+
+#### 3. signature校验规则
+
+时间戳timestamp与服务器时间戳相差不能超过60s大于服务器时间戳
+随机数nonce60s内不能重复

+ 5 - 0
src/XF.Domain/Constants/JsonDefaults.cs

@@ -15,5 +15,10 @@ namespace XF.Domain.Constants
         {
             Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.CjkUnifiedIdeographs)
         };
+
+        public static JsonSerializerOptions DefaultJsonSerializerOptionsWithCamelCase = new JsonSerializerOptions
+        {
+            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+        };
     }
 }