Browse Source

Merge branch 'main' of http://139.155.244.27:822/LSR/gouhuo

DESKTOP-5BUCSUT\LSR 1 year ago
parent
commit
573e4d087c
96 changed files with 3289 additions and 0 deletions
  1. 10 0
      .gitignore
  2. 8 0
      Server/SM.Core/Attributes/SingleInstanceAttribute.cs
  3. 21 0
      Server/SM.Core/Auth/ClaimAttributes.cs
  4. 15 0
      Server/SM.Core/Auth/IUser.cs
  5. 20 0
      Server/SM.Core/Auth/IUserAuthorization.cs
  6. 58 0
      Server/SM.Core/Auth/User.cs
  7. 66 0
      Server/SM.Core/Auth/UserAuthorization.cs
  8. 18 0
      Server/SM.Core/Config/AppConfig.cs
  9. 21 0
      Server/SM.Core/Config/JwtConfig.cs
  10. 12 0
      Server/SM.Core/Config/MongoDBConfig.cs
  11. 8 0
      Server/SM.Core/Config/MySQLConfig.cs
  12. 8 0
      Server/SM.Core/Config/RedisConfig.cs
  13. 16 0
      Server/SM.Core/Config/WeChatConfig.cs
  14. 14 0
      Server/SM.Core/Const/CommonConst.cs
  15. 21 0
      Server/SM.Core/Const/MemoryConst.cs
  16. 23 0
      Server/SM.Core/Const/StatusCode.cs
  17. 28 0
      Server/SM.Core/Extensions/DateTimeExtensions.cs
  18. 50 0
      Server/SM.Core/Extensions/EnumExtensions.cs
  19. 36 0
      Server/SM.Core/Extensions/StringExtensions.cs
  20. 209 0
      Server/SM.Core/Extensions/UtilConvertExtensions.cs
  21. 66 0
      Server/SM.Core/Helper/ConfigHelper.cs
  22. 111 0
      Server/SM.Core/Helper/DesEncryptHelper.cs
  23. 107 0
      Server/SM.Core/Helper/EncryptorHelper.cs
  24. 175 0
      Server/SM.Core/Helper/RandomCodeHelper.cs
  25. 124 0
      Server/SM.Core/Helper/TimeHelper.cs
  26. 25 0
      Server/SM.Core/Log/LogHelper.cs
  27. 11 0
      Server/SM.Core/Output/FindInput.cs
  28. 20 0
      Server/SM.Core/Output/IResponseOutput.cs
  29. 18 0
      Server/SM.Core/Output/OptionOutput.cs
  30. 74 0
      Server/SM.Core/Output/ResponseOutput.cs
  31. 21 0
      Server/SM.Core/Output/ServiceResponse.cs
  32. 17 0
      Server/SM.Core/SM.Core.csproj
  33. 16 0
      Server/SM.Model/MongoDB/Base/Entity.cs
  34. 17 0
      Server/SM.Model/MongoDB/Base/EntityAdd.cs
  35. 23 0
      Server/SM.Model/MongoDB/Base/EntityBase.cs
  36. 32 0
      Server/SM.Model/MongoDB/Base/EntityHelper.cs
  37. 9 0
      Server/SM.Model/MongoDB/Base/EntitySoftDelete.cs
  38. 9 0
      Server/SM.Model/MongoDB/Base/EntityVersion.cs
  39. 8 0
      Server/SM.Model/MongoDB/Base/IEntityAdd.cs
  40. 7 0
      Server/SM.Model/MongoDB/Base/IEntitySoftDelete.cs
  41. 7 0
      Server/SM.Model/MongoDB/Base/IEntityVersion.cs
  42. 10 0
      Server/SM.Model/MongoDB/Context/IMongoContext.cs
  43. 56 0
      Server/SM.Model/MongoDB/Context/MongoContext.cs
  44. 60 0
      Server/SM.Model/MongoDB/Repository/IMongoRepository.cs
  45. 169 0
      Server/SM.Model/MongoDB/Repository/MongoRepository.cs
  46. 24 0
      Server/SM.Model/SM.Model.csproj
  47. 34 0
      Server/SM.Model/SQL/Base/Entity.cs
  48. 39 0
      Server/SM.Model/SQL/Base/EntityAdd.cs
  49. 15 0
      Server/SM.Model/SQL/Base/EntityBase.cs
  50. 31 0
      Server/SM.Model/SQL/Base/EntityData.cs
  51. 24 0
      Server/SM.Model/SQL/Base/EntityDelete.cs
  52. 46 0
      Server/SM.Model/SQL/Base/EntityMember.cs
  53. 26 0
      Server/SM.Model/SQL/Base/EntityMemberWithTenant.cs
  54. 28 0
      Server/SM.Model/SQL/Base/EntityTenant.cs
  55. 31 0
      Server/SM.Model/SQL/Base/EntityTenantWithData.cs
  56. 46 0
      Server/SM.Model/SQL/Base/EntityUpdate.cs
  57. 24 0
      Server/SM.Model/SQL/Base/EntityVersion.cs
  58. 17 0
      Server/SM.Model/SQL/Base/IData.cs
  59. 12 0
      Server/SM.Model/SQL/Base/IDelete.cs
  60. 21 0
      Server/SM.Model/SQL/Base/IEntityAdd.cs
  61. 21 0
      Server/SM.Model/SQL/Base/IEntityUpdate.cs
  62. 12 0
      Server/SM.Model/SQL/Base/IMember.cs
  63. 12 0
      Server/SM.Model/SQL/Base/ITenant.cs
  64. 12 0
      Server/SM.Model/SQL/Base/IVersion.cs
  65. 21 0
      Server/SM.Model/SQL/Project/AccountEntity.cs
  66. 106 0
      Server/SM.Services/Account/AccountService.cs
  67. 14 0
      Server/SM.Services/Account/IAccountService.cs
  68. 10 0
      Server/SM.Services/Account/Input/Account_Login_Input.cs
  69. 8 0
      Server/SM.Services/Account/Input/Account_SecretKey_Input.cs
  70. 11 0
      Server/SM.Services/MapConfig.cs
  71. 20 0
      Server/SM.Services/SM.Services.csproj
  72. 36 0
      Server/SM.Web/Auth/PermissionAttribute.cs
  73. 61 0
      Server/SM.Web/Auth/ResponseAuthenticationHandler.cs
  74. 14 0
      Server/SM.Web/Configs/AppConfig.json
  75. 12 0
      Server/SM.Web/Configs/JwtConfig.json
  76. 8 0
      Server/SM.Web/Configs/MongoDBConfig.json
  77. 4 0
      Server/SM.Web/Configs/MySQLConfig.json
  78. 4 0
      Server/SM.Web/Configs/RedisConfig.json
  79. 12 0
      Server/SM.Web/Configs/WeChatConfig.json
  80. 12 0
      Server/SM.Web/Controllers/Base/InternalController.cs
  81. 12 0
      Server/SM.Web/Controllers/Base/WebController.cs
  82. 34 0
      Server/SM.Web/Controllers/Web/AccountController.cs
  83. 47 0
      Server/SM.Web/Other/ExceptionMiddleware.cs
  84. 32 0
      Server/SM.Web/Other/StatusCodes.cs
  85. 3 0
      Server/SM.Web/Program.cs
  86. 17 0
      Server/SM.Web/Properties/PublishProfiles/Server.pubxml
  87. 11 0
      Server/SM.Web/Properties/PublishProfiles/Server.pubxml.user
  88. 13 0
      Server/SM.Web/Properties/launchSettings.json
  89. 24 0
      Server/SM.Web/SM.Web.csproj
  90. 7 0
      Server/SM.Web/SM.Web.csproj.user
  91. 56 0
      Server/SM.Web/SM.Web.xml
  92. 245 0
      Server/SM.Web/Startup.cs
  93. 8 0
      Server/SM.Web/appsettings.Development.json
  94. 9 0
      Server/SM.Web/appsettings.json
  95. 46 0
      Server/SM.Web/nlog.config
  96. 43 0
      Server/SMServer.sln

+ 10 - 0
.gitignore

@@ -24,6 +24,16 @@ SFGgouhuo/Assembly-CSharp-firstpass.csproj
 SFGgouhuo/BakeryEditorAssembly.csproj
 SFGgouhuo/BakeryRuntimeAssembly.csproj
 SFGgouhuo/Project/
+Server/.vs/
+Server/SM.Services/obj/
+Server/SM.Web/bin/
+Server/SM.Web/obj/
+Server/SM.Services/bin/
+Server/SM.Model/obj/
+Server/SM.Model/bin/
+Server/SM.Core/obj/
+Server/SM.Core/bin/
+Server/Build-Server/
 SFGgouhuo/Assembly-CSharp.Player.csproj
 SFGgouhuo/Assembly-CSharp-firstpass.Player.csproj
 SFGgouhuo/BakeryRuntimeAssembly.Player.csproj

+ 8 - 0
Server/SM.Core/Attributes/SingleInstanceAttribute.cs

@@ -0,0 +1,8 @@
+namespace SM.Core
+{
+    /// <summary>单例注入</summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)]
+    public class SingleInstanceAttribute : Attribute
+    {
+    }
+}

+ 21 - 0
Server/SM.Core/Auth/ClaimAttributes.cs

@@ -0,0 +1,21 @@
+namespace SM.Core
+{
+    /// <summary>Claim属性</summary>
+    public static class ClaimAttributes
+    {
+        /// <summary>用户Id</summary>
+        public const string UserId = "id";
+
+        /// <summary>认证授权用户Id
+        public const string IdentityServerUserId = "sub";
+
+        /// <summary>账号</summary>
+        public const string Account = "ac";
+
+        /// <summary>姓名</summary>
+        public const string UserNickName = "nn";
+
+        /// <summary>刷新有效期</summary>
+        public const string RefreshExpires = "re";
+    }
+}

+ 15 - 0
Server/SM.Core/Auth/IUser.cs

@@ -0,0 +1,15 @@
+namespace SM.Core
+{
+    ///<summary>用户信息接口</summary>
+    public interface IUser
+    {
+        ///<summary>主键</summary>
+        long Id { get; }
+
+        ///<summary>账号</summary>
+        string Account { get; }
+
+        ///<summary>昵称</summary>
+        string NickName { get; }
+    }
+}

+ 20 - 0
Server/SM.Core/Auth/IUserAuthorization.cs

@@ -0,0 +1,20 @@
+using System.Security.Claims;
+
+namespace SM.Core
+{
+    ///<summary>用户授权</summary>
+    public interface IUserAuthorization
+    {
+        ///<summary>创建Token</summary>
+        string Token(Claim[] claims);
+
+        ///<summary>解析Token</summary>
+        Claim[] Decode(string jwtToken);
+
+        ///<summary>解析Token</summary>
+        Task CookieSignIn(Claim[] claims);
+
+        ///<summary>Cookie登出</summary>
+        Task CookieSignOut();
+    }
+}

+ 58 - 0
Server/SM.Core/Auth/User.cs

@@ -0,0 +1,58 @@
+using Microsoft.AspNetCore.Http;
+
+namespace SM.Core
+{
+    ///<summary>用户信息</summary>
+    [SingleInstanceAttribute]
+    public class User : IUser
+    {
+        private readonly IHttpContextAccessor _accessor;
+
+        public User(IHttpContextAccessor accessor)
+        {
+            _accessor = accessor;
+        }
+
+        ///<summary>Id</summary>
+        public long Id
+        {
+            get
+            {
+                var id = _accessor?.HttpContext.User?.FindFirst(ClaimAttributes.UserId);
+                if (id != null && id.Value.NotNull())
+                {
+                    return id.Value.ToLong();
+                }
+                return 0;
+            }
+        }
+
+        ///<summary>用户名</summary>
+        public string Account
+        {
+            get
+            {
+                var account = _accessor?.HttpContext?.User?.FindFirst(ClaimAttributes.Account);
+                if (account != null && account.Value.NotNull())
+                {
+                    return account.Value;
+                }
+                return "";
+            }
+        }
+
+        ///<summary>昵称</summary>
+        public string NickName
+        {
+            get
+            {
+                var name = _accessor?.HttpContext?.User?.FindFirst(ClaimAttributes.UserNickName);
+                if (name != null && name.Value.NotNull())
+                {
+                    return name.Value;
+                }
+                return "";
+            }
+        }
+    }
+}

+ 66 - 0
Server/SM.Core/Auth/UserAuthorization.cs

@@ -0,0 +1,66 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Http;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
+
+namespace SM.Core
+{
+    ///<summary>用户授权</summary>
+    [SingleInstance]
+    public class UserAuthorization : IUserAuthorization
+    {
+        private readonly JwtConfig _jwtConfig;
+        private IHttpContextAccessor _IHttpContextAccessor;
+
+        public UserAuthorization(JwtConfig jwtConfig, IHttpContextAccessor ih)
+        {
+            _jwtConfig = jwtConfig;
+            _IHttpContextAccessor = ih;
+        }
+
+        ///<summary>创建Token</summary>
+        public string Token(Claim[] claims)
+        {
+            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtConfig.SecurityKey));
+            var signingCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
+            var timestamp = DateTime.Now.AddMinutes(_jwtConfig.Expires + _jwtConfig.RefreshExpires).ToTimestamp().ToString();
+            claims = claims.Append(new Claim(ClaimAttributes.RefreshExpires, timestamp)).ToArray();
+
+            var token = new JwtSecurityToken(
+                issuer: _jwtConfig.Issuer,
+                audience: _jwtConfig.Audience,
+                claims: claims,
+                notBefore: DateTime.Now,
+                expires: DateTime.Now.AddMinutes(_jwtConfig.Expires),
+                signingCredentials: signingCredentials
+            );
+            return new JwtSecurityTokenHandler().WriteToken(token);
+        }
+
+        ///<summary>解析Token</summary>
+        public Claim[] Decode(string jwtToken)
+        {
+            var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
+            var jwtSecurityToken = jwtSecurityTokenHandler.ReadJwtToken(jwtToken);
+            return jwtSecurityToken?.Claims?.ToArray();
+        }
+
+        ///<summary>Cookie登录</summary>
+        public async Task CookieSignIn(Claim[] claims)
+        {
+            ClaimsIdentity claimsIdentity = new ClaimsIdentity();
+            claimsIdentity.AddClaims(claims);
+            ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal();
+            claimsPrincipal.AddIdentity(claimsIdentity);
+            await _IHttpContextAccessor.HttpContext.SignInAsync(claimsPrincipal);
+        }
+
+        ///<summary>Cookie登出</summary>
+        public async Task CookieSignOut()
+        {
+            await _IHttpContextAccessor.HttpContext.SignOutAsync();
+        }
+    }
+}

+ 18 - 0
Server/SM.Core/Config/AppConfig.cs

@@ -0,0 +1,18 @@
+namespace SM.Core
+{
+    public class AppConfig
+    {
+        ///<summary>启动地址</summary>
+        public string[] Urls { get; set; }
+        ///<summary>访问地址</summary>
+        public string WebUrl { get; set; }
+        ///<summary>跨域忽略地址</summary>
+        public string[] Cors { get; set; }
+        ///<summary>调试模式</summary>
+        public bool IsDebug { get; set; }
+        ///<summary>访问URL</summary>
+        public string ServiceUrl { get; set; }
+        ///<summary>限制请求次数(小时)</summary>
+        public int LimitRequests { get; set; }
+    }
+}

+ 21 - 0
Server/SM.Core/Config/JwtConfig.cs

@@ -0,0 +1,21 @@
+namespace SM.Core
+{
+    ///<summary>Jwt配置</summary>
+    public class JwtConfig
+    {
+        /// <summary>发行者</summary>
+        public string Issuer { get; set; }
+
+        /// <summary>订阅者</summary>
+        public string Audience { get; set; }
+
+        /// <summary>密钥</summary>
+        public string SecurityKey { get; set; }
+
+        /// <summary>有效期(分钟)</summary>
+        public int Expires { get; set; }
+
+        /// <summary>刷新有效期(分钟)</summary>
+        public int RefreshExpires { get; set; }
+    }
+}

+ 12 - 0
Server/SM.Core/Config/MongoDBConfig.cs

@@ -0,0 +1,12 @@
+namespace SM.Core
+{
+    public class MongoDBConfig
+    {
+        ///<summary>数据库名称</summary>
+        public string DBName { get; set; }
+        ///<summary>数据库连接路径</summary>
+        public string DBPath { get; set; }
+        ///<summary>证书路径</summary>
+        public string CAPath { get; set; }
+    }
+}

+ 8 - 0
Server/SM.Core/Config/MySQLConfig.cs

@@ -0,0 +1,8 @@
+namespace SM.Core
+{
+    public class MySQLConfig
+    {
+        ///<summary>链接地址</summary>
+        public string Connect { get; set; }
+    }
+}

+ 8 - 0
Server/SM.Core/Config/RedisConfig.cs

@@ -0,0 +1,8 @@
+namespace SM.Core
+{
+    public class RedisConfig
+    {
+        ///<summary>链接地址</summary>
+        public string Connect { get; set; }
+    }
+}

+ 16 - 0
Server/SM.Core/Config/WeChatConfig.cs

@@ -0,0 +1,16 @@
+namespace SM.Core
+{
+    public class WeChatConfig
+    {
+        ///<summary>唯一凭证</summary>
+        public string AppId { get; set; }
+        ///<summary>唯一凭证密钥</summary>
+        public string Secret { get; set; }
+        ///<summary>小程序唯一凭证</summary>
+        public string AppletAppId { get; set; }
+        ///<summary>小程序唯一凭证密钥</summary>
+        public string AppletSecret { get; set; }
+        ///<summary>安全域名</summary>
+        public string URL { get; set; }
+    }
+}

+ 14 - 0
Server/SM.Core/Const/CommonConst.cs

@@ -0,0 +1,14 @@
+namespace SM.Core
+{
+    public class CommonConst
+    {
+        ///<summary>200兆字节数</summary>
+        public const long MBTO200 = 209715200;
+        ///<summary>2兆字节数</summary>
+        public const long MBTO2 = 2097152;
+        ///<summary>300Kb节数</summary>
+        public const long KBTO300 = 307200;
+        ///<summary>32Kb节数</summary>
+        public const long KBTO32 = 32768;
+    }
+}

+ 21 - 0
Server/SM.Core/Const/MemoryConst.cs

@@ -0,0 +1,21 @@
+using Microsoft.Extensions.Caching.Memory;
+
+namespace SM.Core
+{
+    ///<summary>内存常量</summary>
+    public static class MemoryConst
+    {
+        ///<summary>验证请求次数 Key</summary>
+        public static string Verify = "Verify_";
+        ///<summary>账号 Key</summary>
+        public static string Account = "Account_";
+        ///<summary>秘钥 Key</summary>
+        public static string SecretKey = "SecretKey_";
+
+        ///<summary>异步获取</summary>
+        public static Task<T> GetAsync<T>(this IMemoryCache cache, string key)
+        {
+            return Task.FromResult(cache.Get<T>(key));
+        }
+    }
+}

+ 23 - 0
Server/SM.Core/Const/StatusCode.cs

@@ -0,0 +1,23 @@
+namespace SM.Core
+{
+    public class StatusCode
+    {
+        ///<summary>请求成功</summary>
+        public const int Success = 0;
+
+        ///<summary>服务器错误</summary>
+        public const int Fail = 500;
+
+        ///<summary>参数为空</summary>
+        public const int FieldNull_Error = -1;
+
+        ///<summary>登录失败Mac地址错误</summary>
+        public const int Login_Error_Mac = 1001;
+
+        ///<summary>登录超时</summary>
+        public const int Login_Error_TimeOut = 1002;
+        
+        ///<summary>频繁请求</summary>
+        public const int Error_Frequently = 1003;
+    }
+}

+ 28 - 0
Server/SM.Core/Extensions/DateTimeExtensions.cs

@@ -0,0 +1,28 @@
+namespace SM.Core
+{
+    public static class DateTimeExtensions
+    {
+        /// <summary>时间戳起始日期</summary>
+        /// 
+        /// 
+        public static DateTime TimestampStart = new DateTime(1970, 1, 1, 0, 0, 0, 0);
+
+        /// <summary>转换为时间戳</summary>
+        /// <param name="dateTime"></param>
+        /// <param name="milliseconds">是否使用毫秒</param>
+        public static long ToTimestamp(this DateTime dateTime, bool milliseconds = false)
+        {
+            var timestamp = dateTime.ToUniversalTime() - TimestampStart;
+            return (long)(milliseconds ? timestamp.TotalMilliseconds : timestamp.TotalSeconds);
+        }
+
+        /// <summary>获取周几</summary>
+        /// <param name="datetime"></param>
+        public static string GetWeekName(this DateTime datetime)
+        {
+            var day = (int)datetime.DayOfWeek;
+            var week = new string[] { "周日", "周一", "周二", "周三", "周四", "周五", "周六" };
+            return week[day];
+        }
+    }
+}

+ 50 - 0
Server/SM.Core/Extensions/EnumExtensions.cs

@@ -0,0 +1,50 @@
+using System.ComponentModel;
+using System.Reflection;
+
+namespace SM.Core
+{
+    public static class EnumExtensions
+    {
+        public static string ToDescription(this Enum item)
+        {
+            string name = item.ToString();
+            var desc = item.GetType().GetField(name)?.GetCustomAttribute<DescriptionAttribute>();
+            return desc?.Description ?? name;
+        }
+
+        public static long ToInt64(this Enum item)
+        {
+            return Convert.ToInt64(item);
+        }
+
+        public static List<OptionOutput> ToList(this Enum value, bool ignoreUnKnown = false)
+        {
+            var enumType = value.GetType();
+
+            if (!enumType.IsEnum)
+                return null;
+
+            return Enum.GetValues(enumType).Cast<Enum>()
+                .Where(m => !ignoreUnKnown || !m.ToString().Equals("UnKnown")).Select(x => new OptionOutput
+                {
+                    Label = x.ToDescription(),
+                    Value = x
+                }).ToList();
+        }
+
+        public static List<OptionOutput> ToList<T>(bool ignoreUnKnown = false)
+        {
+            var enumType = typeof(T);
+
+            if (!enumType.IsEnum)
+                return null;
+
+            return Enum.GetValues(enumType).Cast<Enum>()
+                 .Where(m => !ignoreUnKnown || !m.ToString().Equals("UnKnown")).Select(x => new OptionOutput
+                 {
+                     Label = x.ToDescription(),
+                     Value = x
+                 }).ToList();
+        }
+    }
+}

+ 36 - 0
Server/SM.Core/Extensions/StringExtensions.cs

@@ -0,0 +1,36 @@
+using System.Text.RegularExpressions;
+
+namespace SM.Core
+{
+    public static class StringExtensions
+    {
+        /// <summary>判断字符串是否为Null、空</summary>
+        public static bool IsNull(this string s)
+        {
+            return string.IsNullOrWhiteSpace(s);
+        }
+
+        ///<summary>判断字符串是否不为Null、空</summary>
+        public static bool NotNull(this string s)
+        {
+            return !string.IsNullOrWhiteSpace(s);
+        }
+
+        ///<summary>判断是否手机号</summary>
+        public static bool IsPhone(this string s)
+        {
+            if (string.IsNullOrEmpty(s)) return false;
+            if (s.Length < 11) return false;
+            Regex regex = new Regex(@"^1[012345678]\d{9}$");
+            if (regex.IsMatch(s))
+                return true;
+            return false;
+        }
+
+        /// <summary>是否为ip</summary>
+        public static bool IsIP(this string ip)
+        {
+            return Regex.IsMatch(ip, @"^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)$");
+        }
+    }
+}

+ 209 - 0
Server/SM.Core/Extensions/UtilConvertExtensions.cs

@@ -0,0 +1,209 @@
+using System.Text;
+
+namespace SM.Core
+{
+    public static class UtilConvertExtensions
+    {
+
+        public static int ToInt(this object thisValue)
+        {
+            int reval = 0;
+            if (thisValue == null) return 0;
+            if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return reval;
+        }
+
+        public static int ToInt(this object thisValue, int errorValue)
+        {
+            int reval;
+            if (thisValue != null && thisValue != DBNull.Value && int.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return errorValue;
+        }
+
+        public static long ToLong(this object s)
+        {
+            if (s == null || s == DBNull.Value)
+                return 0L;
+
+            long.TryParse(s.ToString(), out long result);
+            return result;
+        }
+
+        public static double ToMoney(this object thisValue)
+        {
+            double reval;
+            if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return 0;
+        }
+
+        public static double ToMoney(this object thisValue, double errorValue)
+        {
+            double reval;
+            if (thisValue != null && thisValue != DBNull.Value && double.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return errorValue;
+        }
+
+        public static string ToString(this object thisValue)
+        {
+            if (thisValue != null) return thisValue.ToString().Trim();
+            return "";
+        }
+
+        public static string ToString(this object thisValue, string errorValue)
+        {
+            if (thisValue != null) return thisValue.ToString().Trim();
+            return errorValue;
+        }
+
+        /// <summary>转换成Double/Single</summary>
+        /// <param name="s"></param>
+        /// <param name="digits">小数位数</param>
+        public static double ToDouble(this object s, int? digits = null)
+        {
+            if (s == null || s == DBNull.Value)
+                return 0d;
+
+            double.TryParse(s.ToString(), out double result);
+
+            if (digits == null)
+                return result;
+
+            return Math.Round(result, digits.Value);
+        }
+
+        public static decimal ToDecimal(this object thisValue)
+        {
+            decimal reval;
+            if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return 0;
+        }
+
+        public static decimal ToDecimal(this object thisValue, decimal errorValue)
+        {
+            decimal reval;
+            if (thisValue != null && thisValue != DBNull.Value && decimal.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return errorValue;
+        }
+
+        public static DateTime ToDateTime(this object thisValue)
+        {
+            DateTime reval = DateTime.MinValue;
+            if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
+            {
+                reval = Convert.ToDateTime(thisValue);
+            }
+            return reval;
+        }
+
+        public static DateTime ToDateTime(this object thisValue, DateTime errorValue)
+        {
+            DateTime reval;
+            if (thisValue != null && thisValue != DBNull.Value && DateTime.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return errorValue;
+        }
+
+        public static DateTime ToDateTime(this long milliseconds)
+        {
+            return DateTimeExtensions.TimestampStart.AddMilliseconds(milliseconds);
+        }
+
+        public static bool ToBool(this object thisValue)
+        {
+            bool reval = false;
+            if (thisValue != null && thisValue != DBNull.Value && bool.TryParse(thisValue.ToString(), out reval))
+            {
+                return reval;
+            }
+            return reval;
+        }
+
+        public static byte ToByte(this object s)
+        {
+            if (s == null || s == DBNull.Value)
+                return 0;
+
+            byte.TryParse(s.ToString(), out byte result);
+            return result;
+        }
+
+        #region ==字节转换==
+        /// <summary>转换为16进制</summary>
+        /// <param name="bytes"></param>
+        /// <param name="lowerCase">是否小写</param>
+        public static string ToHex(this byte[] bytes, bool lowerCase = true)
+        {
+            if (bytes == null)
+                return null;
+
+            var result = new StringBuilder();
+            var format = lowerCase ? "x2" : "X2";
+            for (var i = 0; i < bytes.Length; i++)
+            {
+                result.Append(bytes[i].ToString(format));
+            }
+
+            return result.ToString();
+        }
+
+        /// <summary>16进制转字节数组</summary>
+        /// <param name="s"></param>
+        public static byte[] HexToBytes(this string s)
+        {
+            if (s.IsNull())
+                return null;
+            var bytes = new byte[s.Length / 2];
+
+            for (int x = 0; x < s.Length / 2; x++)
+            {
+                int i = (Convert.ToInt32(s.Substring(x * 2, 2), 16));
+                bytes[x] = (byte)i;
+            }
+
+            return bytes;
+        }
+
+        /// <summary>转换为Base64</summary>
+        /// <param name="bytes"></param>
+        public static string ToBase64(this byte[] bytes)
+        {
+            if (bytes == null)
+                return null;
+
+            return Convert.ToBase64String(bytes);
+        }
+
+        public static byte[] ToByteArray(this string str)
+        {
+            byte[] byteArray = Encoding.Default.GetBytes(str);
+            return byteArray;
+        }
+
+        public static string ToJson(this object obj)
+        {
+            return LitJson.JsonMapper.ToJson(obj);
+        }
+
+        #endregion
+    }
+}

+ 66 - 0
Server/SM.Core/Helper/ConfigHelper.cs

@@ -0,0 +1,66 @@
+using Microsoft.Extensions.Configuration;
+
+namespace SM.Core
+{
+    public class ConfigHelper
+    {
+        /// <summary>
+        /// 加载配置文件
+        /// </summary>
+        /// <param name="fileName">文件名称</param>
+        /// <param name="environmentName">环境名称</param>
+        /// <param name="reloadOnChange">自动更新</param>
+        public static IConfiguration Load(string fileName, string environmentName = "", bool reloadOnChange = false)
+        {
+            var filePath = Path.Combine(AppContext.BaseDirectory, "Configs");
+            if (!Directory.Exists(filePath))
+                return null;
+
+            LogHelper.Info(filePath + "     " + fileName + "     " + environmentName);
+            var builder = new ConfigurationBuilder()
+                .SetBasePath(filePath)
+                .AddJsonFile(fileName.ToLower() + ".json", true, reloadOnChange);
+
+            if (environmentName.NotNull())
+            {
+                builder.AddJsonFile(fileName.ToLower() + "." + environmentName + ".json", optional: true, reloadOnChange: reloadOnChange);
+            }
+
+            return builder.Build();
+        }
+
+        /// <summary>
+        /// 获得配置信息
+        /// </summary>
+        /// <typeparam name="T">配置信息</typeparam>
+        /// <param name="fileName">文件名称</param>
+        public static T Get<T>(string fileName)
+        {
+            T t;
+            fileName = $"Configs/{fileName}.json";
+            var filePath = Path.Combine(AppContext.BaseDirectory, fileName);
+            using (StreamReader sr = File.OpenText(filePath))
+            {
+                string json = sr.ReadToEnd();
+                t = LitJson.JsonMapper.ToObject<T>(json);
+            }
+            return t;
+        }
+
+        /// <summary>
+        /// 绑定实例配置信息
+        /// </summary>
+        /// <param name="fileName">文件名称</param>
+        /// <param name="instance">实例配置</param>
+        /// <param name="environmentName">环境名称</param>
+        /// <param name="reloadOnChange">自动更新</param>
+        public static void Bind(string fileName, object instance, string environmentName = "", bool reloadOnChange = false)
+        {
+            var configuration = Load(fileName, environmentName, reloadOnChange);
+            if (configuration == null || instance == null)
+                return;
+
+            configuration.Bind(instance);
+        }
+    }
+}

+ 111 - 0
Server/SM.Core/Helper/DesEncryptHelper.cs

@@ -0,0 +1,111 @@
+using SM.Core;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace AR.Core
+{
+    ///<summary>Des加密</summary>
+    public class DesEncryptHelper
+    {
+        private const string Key = "W1#x!3Cm";
+
+        /// <summary>
+        /// DES+Base64加密
+        /// <para>采用ECB、PKCS7</para>
+        /// </summary>
+        /// <param name="encryptString">加密字符串</param>
+        /// <param name="key">秘钥</param>
+        public static string Encrypt(string encryptString, string key = null)
+        {
+            return Encrypt(encryptString, key, false, true);
+        }
+
+        /// <summary>
+        /// DES+Base64解密
+        /// <para>采用ECB、PKCS7</para>
+        /// </summary>
+        /// <param name="decryptString">解密字符串</param>
+        /// <param name="key">秘钥</param>
+        public static string Decrypt(string decryptString, string key = null)
+        {
+            return Decrypt(decryptString, key, false);
+        }
+
+        /// <summary>
+        /// DES+16进制加密
+        /// <para>采用ECB、PKCS7</para>
+        /// </summary>
+        /// <param name="encryptString">加密字符串</param>
+        /// <param name="key">秘钥</param>
+        /// <param name="lowerCase">是否小写</param>
+        public static string Encrypt4Hex(string encryptString, string key = null, bool lowerCase = false)
+        {
+            return Encrypt(encryptString, key, true, lowerCase);
+        }
+
+        /// <summary>
+        /// DES+16进制解密
+        /// <para>采用ECB、PKCS7</para>
+        /// </summary>
+        /// <param name="decryptString">解密字符串</param>
+        /// <param name="key">秘钥</param>
+        public static string Decrypt4Hex(string decryptString, string key = null)
+        {
+            return Decrypt(decryptString, key, true);
+        }
+
+        /// <summary>DES加密</summary>
+        private static string Encrypt(string encryptString, string key, bool hex, bool lowerCase = false)
+        {
+            if (string.IsNullOrEmpty(encryptString))
+                return null;
+            if (string.IsNullOrEmpty(key))
+                key = Key;
+            if (key.Length < 8)
+                throw new ArgumentException("秘钥长度为8位", nameof(key));
+
+            var keyBytes = Encoding.UTF8.GetBytes(key.Substring(0, 8));
+            var inputByteArray = Encoding.UTF8.GetBytes(encryptString);
+            DES des = DES.Create();
+            des.Mode = CipherMode.ECB;
+            des.Key = keyBytes;
+            des.Padding = PaddingMode.PKCS7;
+
+            using (var stream = new MemoryStream())
+            {
+                var cStream = new CryptoStream(stream, des.CreateEncryptor(), CryptoStreamMode.Write);
+                cStream.Write(inputByteArray, 0, inputByteArray.Length);
+                cStream.FlushFinalBlock();
+
+                var bytes = stream.ToArray();
+                return hex ? bytes.ToHex(lowerCase) : bytes.ToBase64();
+            }
+        }
+
+        /// <summary>DES解密</summary>
+        private static string Decrypt(string decryptString, string key, bool hex)
+        {
+            if (string.IsNullOrEmpty(decryptString))
+                return null;
+            if (string.IsNullOrEmpty(key))
+                key = Key;
+            if (key.Length < 8)
+                throw new ArgumentException("秘钥长度为8位", nameof(key));
+
+            var keyBytes = Encoding.UTF8.GetBytes(key.Substring(0, 8));
+            var inputByteArray = hex ? decryptString.HexToBytes() : Convert.FromBase64String(decryptString);
+            DES des = DES.Create();
+            des.Mode = CipherMode.ECB;
+            des.Key = keyBytes;
+            des.Padding = PaddingMode.PKCS7;
+
+            using (var mStream = new MemoryStream())
+            {
+                var cStream = new CryptoStream(mStream, des.CreateDecryptor(), CryptoStreamMode.Write);
+                cStream.Write(inputByteArray, 0, inputByteArray.Length);
+                cStream.FlushFinalBlock();
+                return Encoding.UTF8.GetString(mStream.ToArray());
+            }
+        }
+    }
+}

+ 107 - 0
Server/SM.Core/Helper/EncryptorHelper.cs

@@ -0,0 +1,107 @@
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SM.Core
+{
+    public class EncryptorHelper
+    {
+        // Table of CRC values for high-order byte
+        private static readonly byte[] _auchCRCHi = new byte[] { 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40 };
+
+        // Table of CRC values for low-order byte
+        private static readonly byte[] _auchCRCLo = new byte[] { 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40 };
+
+        /// <summary>异或因子</summary>
+        private static readonly byte[] xorScale = new byte[] { 45, 66, 38, 55, 23, 254, 9, 165, 90, 19, 41, 45, 201, 58, 55, 37, 254, 185, 165, 169, 19, 171 };//.data文件的xor加解密因子
+
+        /// <summary>MD5加密</summary>
+        public static string GetMD5(string sourceString, bool isletter = false)
+        {
+            if (string.IsNullOrEmpty(sourceString)) return null;
+            MD5 md5 = MD5.Create();
+            byte[] source = md5.ComputeHash(Encoding.UTF8.GetBytes(sourceString));
+            StringBuilder sBuilder = new StringBuilder();
+            for (int i = 0; i < source.Length; i++)
+            {
+                if (!isletter)
+                    sBuilder.Append(source[i].ToString("X2"));
+                else
+                    sBuilder.Append(source[i].ToString("x2"));
+            }
+            return sBuilder.ToString();
+        }
+
+        /// <summary>生成随机值</summary>
+        public static string CreateRandomKey(int size = 16)
+        {
+            var rng = RandomNumberGenerator.Create();
+            var buff = new byte[size];
+            rng.GetBytes(buff);
+            return Convert.ToBase64String(buff);
+        }
+
+        /// <summary>生成ToKen值</summary>
+        public static string CreateToKen()
+        {
+            string token = CreateRandomKey();
+            byte[] buffer = Encoding.UTF8.GetBytes(token);
+            buffer = Xor(buffer);
+            token = Convert.ToBase64String(buffer);
+            return token;
+        }
+
+        ///<summary>异或</summary>
+        public static byte[] Xor(byte[] buffer)
+        {
+            int iScaleLen = xorScale.Length;
+            for (int i = 0; i < buffer.Length; i++)
+            {
+                buffer[i] = (byte)(buffer[i] ^ xorScale[i % iScaleLen]);
+            }
+            return buffer;
+        }
+
+        /// <summary>获得CRC16效验码</summary>
+        public static ushort CalculateCrc16(byte[] buffer)
+        {
+            byte crcHi = 0xff;  // high crc byte initialized
+            byte crcLo = 0xff;  // low crc byte initialized
+            for (int i = 0; i < buffer.Length; i++)
+            {
+                int crcIndex = crcHi ^ buffer[i];
+                // calculate the crc lookup index
+                crcHi = (byte)(crcLo ^ _auchCRCHi[crcIndex]);
+                crcLo = _auchCRCLo[crcIndex];
+            }
+            return (ushort)(crcHi << 8 | crcLo);
+        }
+
+        ///<summary>Sha1 加密</summary>
+        public static string Sha1(string str)
+        {
+            byte[] buffer = Encoding.Default.GetBytes(str);
+            byte[] data = SHA1.Create().ComputeHash(buffer);
+
+            StringBuilder sub = new StringBuilder();
+            foreach (var t in data)
+            {
+                sub.Append(t.ToString("x2"));
+            }
+            return sub.ToString();
+        }
+
+        /// <summary>参数名ascii码从小到大排序(字典序)拼接</summary>
+        public static string AsciiDicToStr(Dictionary<string, string> dir)
+        {
+            string[] arrKeys = dir.Keys.ToArray();
+            Array.Sort(arrKeys, string.CompareOrdinal);
+            var sb = new StringBuilder();
+            foreach (var key in arrKeys)
+            {
+                string value = dir[key];
+                sb.Append(key + "=" + value + "&");
+            }
+            return sb.ToString().Substring(0, sb.ToString().Length - 1);
+        }
+    }
+}

+ 175 - 0
Server/SM.Core/Helper/RandomCodeHelper.cs

@@ -0,0 +1,175 @@
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SM.Core
+{
+    public class RandomCodeHelper
+    {
+        public static readonly string[] RandomCodeNumber = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+        public static readonly string[] RandomCode = new string[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "g", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "G", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
+
+        private static int letterWidth = 16;  //单个字体的宽度范围
+        private static int letterHeight = 20; //单个字体的高度范围
+        private static byte[] randb;
+        private static RandomNumberGenerator rand;
+        private static Font[] fonts;
+
+        public RandomCodeHelper()
+        {
+            randb = new byte[4];
+            rand = RandomNumberGenerator.Create();
+            fonts = new Font[4]{
+                  new Font(new FontFamily("Times New Roman"), 10 + Next(1), FontStyle.Regular),
+                  new Font(new FontFamily("Georgia"), 10 + Next(1), FontStyle.Regular),
+                  new Font(new FontFamily("Arial"), 10 + Next(1), FontStyle.Regular),
+                  new Font(new FontFamily("Comic Sans MS"), 10 + Next(1), FontStyle.Regular)
+            };
+        }
+
+        ///<summary>生成随机码</summary>
+        public static string GenerateCode(int length, bool isNumber = false)
+        {
+            StringBuilder sb = new StringBuilder();
+            Random rand = new Random();
+            for (int i = 0; i < length; i++)
+            {
+                if (isNumber)
+                {
+                    sb.Append(RandomCodeNumber[rand.Next(RandomCodeNumber.Length)]);
+                }
+                else
+                {
+                    sb.Append(RandomCode[rand.Next(RandomCode.Length)]);
+                }
+            }
+            string code = sb.ToString();
+            sb.Clear();
+            return code;
+        }
+
+        ///<summary>生成图片随机码</summary>
+        public static async Task<(string, string)> GenerateImageCode(int length)
+        {
+            string code = GenerateCode(length);
+            byte[] buffer = await CreateImage(code);
+            string base64 = Convert.ToBase64String(buffer);
+            return (code, base64);
+        }
+
+        /// <summary>获得下一个随机数</summary>
+        /// <param name="max">最大值</param>
+        private static int Next(int max)
+        {
+            rand.GetBytes(randb);
+            int value = BitConverter.ToInt32(randb, 0);
+            value = value % (max + 1);
+            if (value < 0) value = -value;
+            return value;
+        }
+
+        /// <summary>获得下一个随机数</summary>
+        /// <param name="min">最小值</param>
+        /// <param name="max">最大值</param>
+        private static int Next(int min, int max)
+        {
+            int value = Next(max - min) + min;
+            return value;
+        }
+
+        /// <summary>绘制验证码</summary>
+        private static Task<byte[]> CreateImage(string code)
+        {
+            return Task.Run(() =>
+            {
+                int int_ImageWidth = code.Length * letterWidth;
+                Bitmap image = new Bitmap(int_ImageWidth, letterHeight);
+                Graphics g = Graphics.FromImage(image);
+                g.Clear(Color.White);
+                for (int i = 0; i < 2; i++)
+                {
+                    int x1 = Next(image.Width - 1);
+                    int x2 = Next(image.Width - 1);
+                    int y1 = Next(image.Height - 1);
+                    int y2 = Next(image.Height - 1);
+                    g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
+                }
+                int _x = -12, _y = 0;
+                for (int int_index = 0; int_index < code.Length; int_index++)
+                {
+                    _x += Next(12, 16);
+                    _y = Next(-2, 2);
+                    string str_char = code.Substring(int_index, 1);
+                    str_char = Next(1) == 1 ? str_char.ToLower() : str_char.ToUpper();
+                    Brush newBrush = new SolidBrush(GetRandomColor());
+                    Point thePos = new Point(_x, _y);
+                    g.DrawString(str_char, fonts[Next(fonts.Length - 1)], newBrush, thePos);
+                }
+                for (int i = 0; i < 10; i++)
+                {
+                    int x = Next(image.Width - 1);
+                    int y = Next(image.Height - 1);
+                    image.SetPixel(x, y, Color.FromArgb(Next(0, 255), Next(0, 255), Next(0, 255)));
+                }
+                image = TwistImage(image, true, Next(1, 3), Next(4, 6));
+                g.DrawRectangle(new Pen(Color.LightGray, 1), 0, 0, int_ImageWidth - 1, (letterHeight - 1));
+                MemoryStream msss = new MemoryStream();
+                image.Save(msss, ImageFormat.Bmp);
+                image = null;
+                return msss.GetBuffer();
+            });
+        }
+
+        /// <summary>字体随机颜色</summary>
+        private static Color GetRandomColor()
+        {
+            Random RandomNum_First = new Random((int)DateTime.Now.Ticks);
+            System.Threading.Thread.Sleep(RandomNum_First.Next(50));
+            Random RandomNum_Sencond = new Random((int)DateTime.Now.Ticks);
+            int int_Red = RandomNum_First.Next(180);
+            int int_Green = RandomNum_Sencond.Next(180);
+            int int_Blue = (int_Red + int_Green > 300) ? 0 : 400 - int_Red - int_Green;
+            int_Blue = (int_Blue > 255) ? 255 : int_Blue;
+            return Color.FromArgb(int_Red, int_Green, int_Blue);
+        }
+
+        /// <summary>正弦曲线Wave扭曲图片</summary>
+        /// <param name="srcBmp">图片路径</param>
+        /// <param name="bXDir">如果扭曲则选择为True</param>
+        /// <param name="dMultValue">波形的幅度倍数,越大扭曲的程度越高,一般为3</param>
+        /// <param name="dPhase">波形的起始相位,取值区间[0-2*PI)</param>
+        private static Bitmap TwistImage(Bitmap srcBmp, bool bXDir, double dMultValue, double dPhase)
+        {
+            double PI = 6.283185307179586476925286766559;
+            Bitmap destBmp = new Bitmap(srcBmp.Width, srcBmp.Height);
+            Graphics graph = Graphics.FromImage(destBmp);
+            graph.FillRectangle(new SolidBrush(Color.White), 0, 0, destBmp.Width, destBmp.Height);
+            graph.Dispose();
+            double dBaseAxisLen = bXDir ? (double)destBmp.Height : (double)destBmp.Width;
+            for (int i = 0; i < destBmp.Width; i++)
+            {
+                for (int j = 0; j < destBmp.Height; j++)
+                {
+                    double dx = 0;
+                    dx = bXDir ? (PI * (double)j) / dBaseAxisLen : (PI * (double)i) / dBaseAxisLen;
+                    dx += dPhase;
+                    double dy = Math.Sin(dx);
+                    int nOldX = 0, nOldY = 0;
+                    nOldX = bXDir ? i + (int)(dy * dMultValue) : i;
+                    nOldY = bXDir ? j : j + (int)(dy * dMultValue);
+
+                    Color color = srcBmp.GetPixel(i, j);
+                    if (nOldX >= 0 && nOldX < destBmp.Width
+                     && nOldY >= 0 && nOldY < destBmp.Height)
+                    {
+                        destBmp.SetPixel(nOldX, nOldY, color);
+                    }
+                }
+            }
+            srcBmp.Dispose();
+            return destBmp;
+        }
+    }
+}

+ 124 - 0
Server/SM.Core/Helper/TimeHelper.cs

@@ -0,0 +1,124 @@
+namespace SM.Core
+{
+    ///<summary>时间帮助类</summary>
+    public static class TimeHelper
+    {
+        public static readonly long epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks;
+
+        ///<summary>转换到当天的开始时间</summary>
+        public static DateTime ToStartDay(DateTime time)
+        {
+            return new DateTime(time.Year, time.Month, time.Day);
+        }
+
+        ///<summary>转换到当天的结束时间</summary>
+        public static DateTime ToEndDay(DateTime time)
+        {
+            return new DateTime(time.Year, time.Month, time.Day).AddDays(1);
+        }
+
+        ///<summary>转换到当月的开始时间</summary>
+        public static DateTime ToStartMonth(DateTime time)
+        {
+            return new DateTime(time.Year, time.Month, 1);
+        }
+
+        ///<summary>转换到当月的结束时间</summary>
+        public static DateTime ToEndMonth(DateTime time)
+        {
+            return new DateTime(time.Year, time.Month, 1).AddMonths(1);
+        }
+
+        ///<summary>当天的开始时间</summary>
+        public static DateTime StartDay
+        {
+            get
+            {
+                DateTime date = DateTime.Now;
+                return new DateTime(date.Year, date.Month, date.Day);
+            }
+        }
+
+        ///<summary>当天的结束时间</summary>
+        public static DateTime EndDay
+        {
+            get
+            {
+                return StartDay.AddDays(1);
+            }
+        }
+
+        ///<summary>当月的开始时间</summary>
+        public static DateTime StartMonth
+        {
+            get
+            {
+                DateTime date = DateTime.Now;
+                return new DateTime(date.Year, date.Month, 1);
+            }
+        }
+
+        ///<summary>当月的结束时间</summary>
+        public static DateTime EndMonth
+        {
+            get
+            {
+                return StartMonth.AddMonths(1);
+            }
+        }
+
+        /// <summary>时间戳(毫秒)</summary>
+        public static long ClientNow()
+        {
+            return (DateTime.UtcNow.Ticks - epoch) / 10000;
+        }
+
+        /// <summary>时间戳(秒)</summary>
+        public static long ClientNowSeconds()
+        {
+            return (DateTime.UtcNow.Ticks - epoch) / 10000000;
+        }
+
+        /// <summary>自定义 时间戳(秒)</summary>
+        public static long SetNowSeconds(DateTime time)
+        {
+            return (time.Ticks - epoch) / 10000000;
+        }
+
+        ///<summary>是否在同一年</summary>
+        public static bool IsYear(DateTime t1, DateTime t2)
+        {
+            if (t1.Year == t2.Year)
+            {
+                return true;
+            }
+            return false;
+        }
+
+        ///<summary>是否在同一年同一月</summary>
+        public static bool IsMonth(DateTime t1, DateTime t2)
+        {
+            if (IsYear(t1, t2))
+            {
+                if (t2.Month == t1.Month)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        ///<summary>是否在同一天</summary>
+        public static bool IsDay(DateTime t1, DateTime t2)
+        {
+            if (IsMonth(t1, t2))
+            {
+                if (t1.Day == t2.Day)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+}

+ 25 - 0
Server/SM.Core/Log/LogHelper.cs

@@ -0,0 +1,25 @@
+using NLog;
+using NLog.Web;
+
+namespace SM.Core
+{
+    public static class LogHelper
+    {
+        private static Logger Log => LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
+       
+        public static void Debug(string message)
+        {
+            Log.Debug(message);
+        }
+
+        public static void Info(string message)
+        {
+            Log.Info(message);
+        }
+
+        public static void Error(string message, Exception ex)
+        {
+            Log.Error(ex, message);
+        }
+    }
+}

+ 11 - 0
Server/SM.Core/Output/FindInput.cs

@@ -0,0 +1,11 @@
+namespace SM.Core
+{
+    public class FindInput
+    {
+        ///<summary>每页数量</summary>
+        public int Size { get; set; }
+
+        ///<summary>当前页</summary>
+        public int Page { get; set; }
+    }
+}

+ 20 - 0
Server/SM.Core/Output/IResponseOutput.cs

@@ -0,0 +1,20 @@
+namespace SM.Core
+{
+    /// <summary>
+    /// 响应数据输出接口
+    /// </summary>
+    public interface IResponseOutput
+    {
+        ///<summary>状态码</summary>
+        public int code { get; set; }
+        /// <summary>消息</summary>
+        public string message { get; set; }
+    }
+
+    /// <summary>响应数据输出泛型接口</summary>
+    public interface IResponseOutput<T> : IResponseOutput
+    {
+        /// <summary>返回数据</summary>
+        T data { get; set; }
+    }
+}

+ 18 - 0
Server/SM.Core/Output/OptionOutput.cs

@@ -0,0 +1,18 @@
+namespace SM.Core
+{
+    /// <summary>下拉选项输出</summary>
+    public class OptionOutput
+    {
+        /// <summary>名称</summary>
+        public string Label { get; set; }
+
+        /// <summary></summary>
+        public object Value { get; set; }
+
+        /// <summary>禁用</summary>
+        public bool Disabled { get; set; }
+
+        /// <summary>额外数据</summary>
+        public object Data { get; set; }
+    }
+}

+ 74 - 0
Server/SM.Core/Output/ResponseOutput.cs

@@ -0,0 +1,74 @@
+namespace SM.Core
+{
+    public class ResponseOutput<T> : IResponseOutput<T>
+    {
+        /// <summary>状态码</summary>
+        public int code { get; set; } = -1;
+
+        /// <summary>消息</summary>
+        public string message { get; set; }
+
+        /// <summary>数据</summary>
+        public T data { get; set; }
+
+        /// <summary>成功</summary>
+        /// <param name="code">状态码</param>
+        /// <param name="data">数据</param>
+        /// <param name="msg">消息</param>
+        public ResponseOutput<T> Ok(int code, T data, string msg = null)
+        {
+            this.code = code;
+            this.data = data;
+            message = msg;
+            return this;
+        }
+
+        /// <summary>失败</summary>
+        /// <param name="code">状态码</param>
+        /// <param name="msg">消息</param>
+        /// <param name="data">数据</param>
+        public ResponseOutput<T> NotOk(int code, string msg = null, T data = default(T))
+        {
+            this.code = code;
+            message = msg;
+            this.data = data;
+            return this;
+        }
+    }
+
+    /// <summary>响应数据静态输出</summary>
+    public static partial class ResponseOutput
+    {
+        /// <summary>成功</summary>
+        /// <param name="msg">消息</param>
+        public static IResponseOutput Ok(string msg)
+        {
+            return new ResponseOutput<string>().Ok(StatusCode.Success, "", msg);
+        }
+
+        /// <summary>成功</summary>
+        /// <param name="data">数据</param>
+        /// <param name="msg">消息</param>
+        public static IResponseOutput Ok<T>(T data = default(T), string msg = null)
+        {
+            return new ResponseOutput<T>().Ok(StatusCode.Success, data, msg);
+        }
+
+        /// <summary>失败</summary>
+        /// <param name="code">状态码</param>
+        /// <param name="msg">消息</param>
+        public static IResponseOutput NotOk(int code, string msg)
+        {
+            return new ResponseOutput<string>().NotOk(code, msg, null);
+        }
+
+        /// <summary>失败</summary>
+        /// <param name="code">状态码</param>
+        /// <param name="data">数据</param>
+        /// <param name="msg">消息</param>
+        public static IResponseOutput NotOk<T>(int code, string msg = null, T data = default(T))
+        {
+            return new ResponseOutput<T>().NotOk(code, msg, data);
+        }
+    }
+}

+ 21 - 0
Server/SM.Core/Output/ServiceResponse.cs

@@ -0,0 +1,21 @@
+namespace SM.Core.Output
+{
+    public class ServiceResponse<T>
+    {
+        ///<summary>状态码</summary>
+        public int code { get; set; }
+
+        ///<summary>消息</summary>
+        public string msg { get; set; }
+
+        ///<summary>数据</summary>
+        public T data { get; set; }
+    }
+
+    public class UserInfo
+    {
+        public string nickname { get; set; }
+
+        public string mobile { get; set; }
+    }
+}

+ 17 - 0
Server/SM.Core/SM.Core.csproj

@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="LitJson" Version="0.19.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+    <PackageReference Include="NLog.Web.AspNetCore" Version="5.3.8" />
+    <PackageReference Include="System.Drawing.Common" Version="8.0.1" />
+  </ItemGroup>
+
+</Project>

+ 16 - 0
Server/SM.Model/MongoDB/Base/Entity.cs

@@ -0,0 +1,16 @@
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace SM.Model.MongoDB
+{
+    ///<summary>实体</summary>
+    [BsonIgnoreExtraElements]
+    public class Entity
+    {
+        ///<summary>实体ID</summary>
+        [BsonIgnoreIfDefault]
+        [BsonDefaultValue(0L)]
+        [BsonElement]
+        [BsonId]
+        public long Id { get; set; }
+    }
+}

+ 17 - 0
Server/SM.Model/MongoDB/Base/EntityAdd.cs

@@ -0,0 +1,17 @@
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace SM.Model.MongoDB
+{
+    public class EntityAdd : Entity, IEntityAdd
+    {
+        /// <summary>创建者Id</summary>
+        public long CreatedUserId { get; set; }
+
+        ///<summary>创建者账号</summary>
+        public string CreatedAccount { get; set; }
+
+        /// <summary>创建时间</summary>
+        [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
+        public DateTime CreatedTime { get; set; }
+    }
+}

+ 23 - 0
Server/SM.Model/MongoDB/Base/EntityBase.cs

@@ -0,0 +1,23 @@
+using MongoDB.Bson.Serialization.Attributes;
+
+namespace SM.Model.MongoDB
+{
+    public class EntityBase : Entity, IEntityAdd, IEntitySoftDelete, IEntityVersion
+    {
+        /// <summary>创建者Id</summary>
+        public long CreatedUserId { get; set; }
+
+        /// <summary>创建者</summary>
+        public string CreatedUserName { get; set; }
+
+        /// <summary>创建时间</summary>
+        [BsonDateTimeOptions(Kind = DateTimeKind.Local)]
+        public DateTime CreatedTime { get; set; }
+
+        /// <summary>是否删除</summary>
+        public bool IsDeleted { get; set; } = false;
+
+        /// <summary>版本</summary>
+        public int Version { get; set; }
+    }
+}

+ 32 - 0
Server/SM.Model/MongoDB/Base/EntityHelper.cs

@@ -0,0 +1,32 @@
+using SM.Core;
+
+namespace SM.Model.MongoDB
+{
+    public static class EntityHelper
+    {
+        public static long AppId = 80;
+
+        private static ushort value;
+
+        ///<summary>创建Entity</summary>
+        public static T CreateEntity<T>() where T : Entity, new()
+        {
+            T entity = new T();
+            entity.Id = GenerateId();
+            return entity;
+        }
+
+        ///<summary>生成唯一ID</summary>
+        public static long GenerateId()
+        {
+            long time = TimeHelper.ClientNow();
+
+            return (AppId << 48) + (time << 16) + ++value;
+        }
+
+        public static int GetAppIdFromId(long id)
+        {
+            return (int)(id >> 48);
+        }
+    }
+}

+ 9 - 0
Server/SM.Model/MongoDB/Base/EntitySoftDelete.cs

@@ -0,0 +1,9 @@
+namespace SM.Model.MongoDB
+{
+    ///<summary>实体软删除</summary>
+    public class EntitySoftDelete : Entity, IEntitySoftDelete
+    {
+        /// <summary>是否删除</summary>
+        public bool IsDeleted { get; set; } = false;
+    }
+}

+ 9 - 0
Server/SM.Model/MongoDB/Base/EntityVersion.cs

@@ -0,0 +1,9 @@
+namespace SM.Model.MongoDB
+{
+    /// <summary>实体版本</summary>
+    public class EntityVersion : Entity, IEntityVersion
+    {
+        /// <summary>版本</summary>
+        public int Version { get; set; }
+    }
+}

+ 8 - 0
Server/SM.Model/MongoDB/Base/IEntityAdd.cs

@@ -0,0 +1,8 @@
+namespace SM.Model.MongoDB
+{
+    public interface IEntityAdd
+    {
+        long CreatedUserId { get; set; }
+        DateTime CreatedTime { get; set; }
+    }
+}

+ 7 - 0
Server/SM.Model/MongoDB/Base/IEntitySoftDelete.cs

@@ -0,0 +1,7 @@
+namespace SM.Model.MongoDB
+{
+    public interface IEntitySoftDelete
+    {
+        bool IsDeleted { get; set; }
+    }
+}

+ 7 - 0
Server/SM.Model/MongoDB/Base/IEntityVersion.cs

@@ -0,0 +1,7 @@
+namespace SM.Model.MongoDB
+{
+    public interface IEntityVersion
+    {
+        int Version { get; set; }
+    }
+}

+ 10 - 0
Server/SM.Model/MongoDB/Context/IMongoContext.cs

@@ -0,0 +1,10 @@
+using MongoDB.Driver;
+
+namespace SM.Model.MongoDB
+{
+    ///<summary>数据库链接</summary>
+    public interface IMongoContext
+    {
+        IMongoDatabase Database { get; }
+    }
+}

+ 56 - 0
Server/SM.Model/MongoDB/Context/MongoContext.cs

@@ -0,0 +1,56 @@
+using MongoDB.Driver;
+using SM.Core;
+using System.Security.Cryptography.X509Certificates;
+
+namespace SM.Model.MongoDB
+{
+    ///<summary>数据库链接</summary>
+    public class MongoContext : IMongoContext
+    {
+        ///<summary>配置文档</summary>
+        private MongoDBConfig _MongoDBConfig;
+
+        ///<summary>数据库操作</summary>
+        private MongoClient _Client;
+
+        private IMongoDatabase _Database;
+        public IMongoDatabase Database
+        {
+            get
+            {
+                if (_Database == null)
+                {
+                    if (!string.IsNullOrEmpty(_MongoDBConfig.CAPath))
+                    {
+                        X509Store localTrustStore = new X509Store(StoreName.Root);
+                        X509Certificate2Collection certificateCollection = new X509Certificate2Collection();
+                        certificateCollection.Import(_MongoDBConfig.CAPath);
+                        try
+                        {
+                            localTrustStore.Open(OpenFlags.ReadWrite);
+                            localTrustStore.AddRange(certificateCollection);
+                        }
+                        catch (Exception ex)
+                        {
+                            LogHelper.Error("根证书导入失败:" + ex.Message, ex);
+                            throw;
+                        }
+                        finally
+                        {
+                            localTrustStore.Close();
+                        }
+                    }
+                    MongoClientSettings settings = MongoClientSettings.FromUrl(new MongoUrl(_MongoDBConfig.DBPath));
+                    _Client = new MongoClient(settings);
+                    _Database = _Client.GetDatabase(_MongoDBConfig.DBName);
+                }
+                return _Database;
+            }
+        }
+
+        public MongoContext(MongoDBConfig config)
+        {
+            _MongoDBConfig = config;
+        }
+    }
+}

+ 60 - 0
Server/SM.Model/MongoDB/Repository/IMongoRepository.cs

@@ -0,0 +1,60 @@
+using System.Linq.Expressions;
+
+namespace SM.Model.MongoDB
+{
+    ///<summary>数据库操作</summary>
+    public interface IMongoRepository<T> where T : Entity, new()
+    { 
+        ///<summary>异步保存单个</summary>
+        Task DBSaveAsync(T entity);
+
+        ///<summary>异步保存多个</summary>
+        Task DBSavesAsync(List<T> list);
+
+        ///<summary>数量</summary>
+        Task<long> DBCount(Expression<Func<T, bool>> exp);
+
+        ///<summary>异步删除单个</summary>
+        Task DBRemoveAsync(long id);
+
+        ///<summary>异步删除多个</summary>
+        Task DBRemovesAsync(Expression<Func<T, bool>> exp);
+
+        ///<summary>根据id异步查找单个实体</summary>
+        Task<T> DBQueryAsync(long id);
+
+        ///<summary>根据id异步查找多个实体</summary>
+        Task<List<T>> DBQuerysAsync(List<long> ids);
+
+        ///<summary>根据条件异步查找单个实体</summary>
+        Task<T> DBQueryAsync(Expression<Func<T, bool>> exp);
+
+        ///<summary>根据条件异步查找多个实体</summary>
+        Task<List<T>> DBQuerysAsync(Expression<Func<T, bool>> exp);
+
+        ///<summary>根据Json异步查找单个实体</summary>
+        Task<T> DBQueryAsync(string json);
+
+        ///<summary>根据Json异步查找多个实体</summary>
+        Task<List<T>> DBQuerysAsync(string json);
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="filter">过滤器</param>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        Task<(List<T>, long)> GetQueryByPageAsync(Expression<Func<T, bool>> filter, int size, int page);
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="json">过滤器</param>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        Task<(List<T>, long)> GetQueryByPageAsync(string json, int size, int page);
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="filter">过滤器</param>
+        /// <param name="pattern">查询条件</param>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        Task<(List<T>, long)> GetQueryByPageAsync(Expression<Func<T, object>> filter, string pattern, int size, int page);
+    }
+}

+ 169 - 0
Server/SM.Model/MongoDB/Repository/MongoRepository.cs

@@ -0,0 +1,169 @@
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Driver;
+using System.Linq.Expressions;
+
+namespace SM.Model.MongoDB
+{
+    public class MongoRepository<T> : IMongoRepository<T> where T : Entity, new()
+    {
+        ///<summary>文档集合</summary>
+        private IMongoCollection<T> _Collection { get; }
+
+        public MongoRepository(IMongoContext context)
+        {
+            string name = typeof(T).Name;
+            _Collection = context.Database.GetCollection<T>(name);
+        }
+
+        #region -- 添加&更新 --
+
+        ///<summary>异步保存单个</summary>
+        public async Task DBSaveAsync(T entity)
+        {
+            await _Collection.ReplaceOneAsync(s => s.Id == entity.Id, entity, new ReplaceOptions { IsUpsert = true });
+        }
+
+        ///<summary>异步保存多个</summary>
+        public async Task DBSavesAsync(List<T> list)
+        {
+            if (list == null) throw new Exception($"保存[更新]数据失败! 传入的 {typeof(T).Name} 对象的List集合为空!");
+
+            for (int i = 0; i < list.Count; i++)
+            {
+                if (list[i] == null) continue;
+
+                await _Collection.ReplaceOneAsync(s => s.Id == list[i].Id, list[i], new ReplaceOptions { IsUpsert = true });
+            }
+        }
+
+        public async Task Insert(T entity)
+        {
+             await _Collection.InsertOneAsync(entity);
+        }
+
+        #endregion
+
+        ///<summary>数量</summary>
+        public async Task<long> DBCount(Expression<Func<T, bool>> exp)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(ToJson(exp));
+            long count = await _Collection.CountDocumentsAsync(exp);
+            return count;
+        }
+
+        #region -- 删除 --
+
+        ///<summary>异步删除单个</summary>
+        public async Task DBRemoveAsync(long id)
+        {
+            await _Collection.DeleteOneAsync(s => s.Id == id);
+        }
+
+        ///<summary>异步删除多个</summary>
+        public async Task DBRemovesAsync(Expression<Func<T, bool>> exp)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(ToJson(exp));
+            await _Collection.DeleteManyAsync(filterDefinition);
+        }
+
+        #endregion
+
+        #region -- 查询 --
+
+        ///<summary>根据id异步查找单个实体</summary>
+        public async Task<T> DBQueryAsync(long id)
+        {
+            IAsyncCursor<T> cursor = await _Collection.FindAsync((s) => s.Id == id);
+            return await cursor.FirstOrDefaultAsync();
+        }
+
+        ///<summary>根据id异步查找多个实体</summary>
+        public async Task<List<T>> DBQuerysAsync(List<long> ids)
+        {
+            if (ids == null) throw new Exception($"查询失败! 传入的 {typeof(T).Name} 对象的id List集合为空!");
+            List<T> list = new List<T>();
+            for (int i = 0; i < ids.Count; i++)
+            {
+                T t = await DBQueryAsync(ids[i]);
+                if (t == null) continue;
+                list.Add(t);
+            }
+            return list;
+        }
+
+        ///<summary>根据条件异步查找单个实体</summary>
+        public async Task<T> DBQueryAsync(Expression<Func<T, bool>> exp)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(ToJson(exp));
+            return await _Collection.Find(filterDefinition).FirstOrDefaultAsync();
+        }
+
+        ///<summary>根据条件异步查找多个实体</summary>
+        public async Task<List<T>> DBQuerysAsync(Expression<Func<T, bool>> exp)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(ToJson(exp));
+            IAsyncCursor<T> cursor = await _Collection.FindAsync(filterDefinition);
+            return await cursor.ToListAsync();
+        }
+
+        ///<summary>根据Json异步查找单个实体</summary>
+        public async Task<T> DBQueryAsync(string json)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(json);
+            return await _Collection.Find(filterDefinition).FirstOrDefaultAsync();
+        }
+
+        ///<summary>根据Json异步查找多个实体</summary>
+        public async Task<List<T>> DBQuerysAsync(string json)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(json);
+            IAsyncCursor<T> cursor = await _Collection.FindAsync(filterDefinition);
+            return await cursor.ToListAsync();
+        }
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="pattern">查询字段</param>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        public async Task<(List<T>, long)> GetQueryByPageAsync(Expression<Func<T, object>> filter, string pattern, int size, int page)
+        {
+            FilterDefinition<T> filterDefinition = Builders<T>.Filter.Regex(filter, new BsonRegularExpression(pattern));
+            long count = _Collection.CountDocuments(filterDefinition);
+            List<T> list = await _Collection.Find(filterDefinition).Skip((page - 1) * size).Limit(size).ToListAsync();
+            return (list, count);
+        }
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        public async Task<(List<T>, long)> GetQueryByPageAsync(Expression<Func<T, bool>> filter, int size, int page)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(ToJson(filter));
+            long count = _Collection.CountDocuments(filterDefinition);
+            List<T> list = await _Collection.Find(filterDefinition).Skip((page - 1) * size).Limit(size).ToListAsync();
+            return (list, count);
+        }
+
+        /// <summary>异步分页查询</summary>
+        /// <param name="size">每页数量</param>
+        /// <param name="page">当前页</param>
+        public async Task<(List<T>, long)> GetQueryByPageAsync(string json, int size, int page)
+        {
+            FilterDefinition<T> filterDefinition = new JsonFilterDefinition<T>(json);
+            long count = _Collection.CountDocuments(filterDefinition);
+            List<T> list = await _Collection.Find(filterDefinition).Skip((page - 1) * size).Limit(size).ToListAsync();
+            return (list, count);
+        }
+
+        ///<summary>转换为Json</summary>
+        public static string ToJson(Expression<Func<T, bool>> exp)
+        {
+            ExpressionFilterDefinition<T> filter = new ExpressionFilterDefinition<T>(exp);
+            IBsonSerializerRegistry serializerRegistry = BsonSerializer.SerializerRegistry;
+            IBsonSerializer<T> documentSerializer = serializerRegistry.GetSerializer<T>();
+            return filter.Render(documentSerializer, serializerRegistry).ToJson();
+        }
+        #endregion
+    }
+}

+ 24 - 0
Server/SM.Model/SM.Model.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="FreeRedis" Version="1.2.13" />
+    <PackageReference Include="FreeSql" Version="3.2.810" />
+    <PackageReference Include="FreeSql.Provider.MySql" Version="3.2.810" />
+    <PackageReference Include="FreeSql.Repository" Version="3.2.810" />
+    <PackageReference Include="MongoDB.Bson" Version="2.23.1" />
+    <PackageReference Include="MongoDB.Driver" Version="2.23.1" />
+    <PackageReference Include="MongoDB.Driver.Core" Version="2.23.1" />
+    <PackageReference Include="MongoDB.Driver.GridFS" Version="2.23.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SM.Core\SM.Core.csproj" />
+  </ItemGroup>
+
+</Project>

+ 34 - 0
Server/SM.Model/SQL/Base/Entity.cs

@@ -0,0 +1,34 @@
+using FreeSql.DataAnnotations;
+using Newtonsoft.Json;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+
+namespace SM.Model.SQL;
+
+public interface IEntity<TKey>
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    TKey Id { get; set; }
+}
+
+public interface IEntity : IEntity<long>
+{
+}
+
+public class Entity<TKey> : IEntity<TKey>
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    [Description("主键Id")]
+    [Column(Position = 1, IsIdentity = true, IsPrimary = true)]
+    [JsonProperty(Order = -30)]
+    [JsonPropertyOrder(-30)]
+    public virtual TKey Id { get; set; }
+}
+
+public class Entity : Entity<long>
+{
+}

+ 39 - 0
Server/SM.Model/SQL/Base/EntityAdd.cs

@@ -0,0 +1,39 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体创建
+/// </summary>
+public class EntityAdd<TKey> : Entity<TKey>, IEntityAdd<TKey> where TKey : struct
+{
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    [Description("创建者Id")]
+    [Column(Position = -22, CanUpdate = false)]
+    public virtual long? CreatedUserId { get; set; }
+
+    /// <summary>
+    /// 创建者
+    /// </summary>
+    [Description("创建者")]
+    [Column(Position = -21, CanUpdate = false), MaxLength(50)]
+    public virtual string CreatedUserName { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    [Description("创建时间")]
+    [Column(Position = -20, CanUpdate = false, ServerTime = DateTimeKind.Local)]
+    public virtual DateTime? CreatedTime { get; set; }
+}
+
+/// <summary>
+/// 实体创建
+/// </summary>
+public class EntityAdd : EntityAdd<long>
+{
+}

+ 15 - 0
Server/SM.Model/SQL/Base/EntityBase.cs

@@ -0,0 +1,15 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体基类
+/// </summary>
+public class EntityBase<TKey> : EntityDelete<TKey> where TKey : struct
+{
+}
+
+/// <summary>
+/// 实体基类
+/// </summary>
+public class EntityBase : EntityBase<long>
+{
+}

+ 31 - 0
Server/SM.Model/SQL/Base/EntityData.cs

@@ -0,0 +1,31 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体数据权限
+/// </summary>
+public class EntityData<TKey> : EntityBase, IData
+{
+    /// <summary>
+    /// 拥有者Id
+    /// </summary>
+    [Description("拥有者Id")]
+    [Column(Position = -41)]
+    public virtual long? OwnerId { get; set; }
+
+    /// <summary>
+    /// 拥有者部门Id
+    /// </summary>
+    [Description("拥有者部门Id")]
+    [Column(Position = -40)]
+    public virtual long? OwnerOrgId { get; set; }
+}
+
+/// <summary>
+/// 实体数据权限
+/// </summary>
+public class EntityData : EntityData<long>
+{
+}

+ 24 - 0
Server/SM.Model/SQL/Base/EntityDelete.cs

@@ -0,0 +1,24 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体删除
+/// </summary>
+public class EntityDelete<TKey> : EntityUpdate, IDelete
+{
+    /// <summary>
+    /// 是否删除
+    /// </summary>
+    [Description("是否删除")]
+    [Column(Position = -9)]
+    public virtual bool IsDeleted { get; set; } = false;
+}
+
+/// <summary>
+/// 实体删除
+/// </summary>
+public class EntityDelete : EntityDelete<long>
+{
+}

+ 46 - 0
Server/SM.Model/SQL/Base/EntityMember.cs

@@ -0,0 +1,46 @@
+using FreeSql.DataAnnotations;
+using System;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体会员
+/// </summary>
+public class EntityMember<TKey> : Entity<TKey>, IMember, IDelete
+{
+    /// <summary>
+    /// 会员Id
+    /// </summary>
+    [Description("会员Id")]
+    [Column(Position = -23, CanUpdate = false)]
+    public virtual long? MemberId { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    [Description("创建时间")]
+    [Column(Position = -20, CanUpdate = false, ServerTime = DateTimeKind.Local)]
+    public virtual DateTime? CreatedTime { get; set; }
+
+    /// <summary>
+    /// 修改时间
+    /// </summary>
+    [Description("修改时间")]
+    [Column(Position = -10, CanInsert = false, ServerTime = DateTimeKind.Local)]
+    public virtual DateTime? ModifiedTime { get; set; }
+
+    /// <summary>
+    /// 是否删除
+    /// </summary>
+    [Description("是否删除")]
+    [Column(Position = -9)]
+    public virtual bool IsDeleted { get; set; } = false;
+}
+
+/// <summary>
+/// 实体会员
+/// </summary>
+public class EntityMember : EntityMember<long>
+{
+}

+ 26 - 0
Server/SM.Model/SQL/Base/EntityMemberWithTenant.cs

@@ -0,0 +1,26 @@
+using FreeSql.DataAnnotations;
+using Newtonsoft.Json;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体会员租户
+/// </summary>
+public class EntityMemberWithTenant<TKey> : EntityMember, ITenant
+{
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    [Description("租户Id")]
+    [Column(Position = 2, CanUpdate = false)]
+    [JsonProperty(Order = -20)]
+    public virtual long? TenantId { get; set; }
+}
+
+/// <summary>
+/// 实体会员租户
+/// </summary>
+public class EntityMemberWithTenant : EntityMemberWithTenant<long>
+{
+}

+ 28 - 0
Server/SM.Model/SQL/Base/EntityTenant.cs

@@ -0,0 +1,28 @@
+using FreeSql.DataAnnotations;
+using Newtonsoft.Json;
+using System.ComponentModel;
+using System.Text.Json.Serialization;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体租户
+/// </summary>
+public class EntityTenant<TKey> : EntityBase, ITenant
+{
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    [Description("租户Id")]
+    [Column(Position = 2, CanUpdate = false)]
+    [JsonProperty(Order = -20)]
+    [JsonPropertyOrder(-20)]
+    public virtual long? TenantId { get; set; }
+}
+
+/// <summary>
+/// 实体租户
+/// </summary>
+public class EntityTenant : EntityTenant<long>
+{
+}

+ 31 - 0
Server/SM.Model/SQL/Base/EntityTenantWithData.cs

@@ -0,0 +1,31 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体租户数据权限
+/// </summary>
+public class EntityTenantWithData<TKey> : EntityTenant, IData
+{
+    /// <summary>
+    /// 拥有者Id
+    /// </summary>
+    [Description("拥有者Id")]
+    [Column(Position = -41)]
+    public virtual long? OwnerId { get; set; }
+
+    /// <summary>
+    /// 拥有者部门Id
+    /// </summary>
+    [Description("拥有者部门Id")]
+    [Column(Position = -40)]
+    public virtual long? OwnerOrgId { get; set; }
+}
+
+/// <summary>
+/// 实体租户数据权限
+/// </summary>
+public class EntityTenantWithData : EntityTenantWithData<long>
+{
+}

+ 46 - 0
Server/SM.Model/SQL/Base/EntityUpdate.cs

@@ -0,0 +1,46 @@
+using FreeSql.DataAnnotations;
+using Newtonsoft.Json;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace SM.Model.SQL;
+/// <summary>
+/// 实体修改
+/// </summary>
+public class EntityUpdate<TKey> : EntityAdd, IEntityUpdate<TKey> where TKey : struct
+{
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    [Description("修改者Id")]
+    [Column(Position = -12, CanInsert = false)]
+    [JsonProperty(Order = 10000)]
+    [JsonPropertyOrder(10000)]
+    public virtual long? ModifiedUserId { get; set; }
+
+    /// <summary>
+    /// 修改者
+    /// </summary>
+    [Description("修改者")]
+    [Column(Position = -11, CanInsert = false), MaxLength(50)]
+    [JsonProperty(Order = 10001)]
+    [JsonPropertyOrder(10001)]
+    public virtual string ModifiedUserName { get; set; }
+
+    /// <summary>
+    /// 修改时间
+    /// </summary>
+    [Description("修改时间")]
+    [JsonProperty(Order = 10002)]
+    [JsonPropertyOrder(10002)]
+    [Column(Position = -10, CanInsert = false, ServerTime = DateTimeKind.Local)]
+    public virtual DateTime? ModifiedTime { get; set; }
+}
+
+/// <summary>
+/// 实体修改
+/// </summary>
+public class EntityUpdate : EntityUpdate<long>
+{
+}

+ 24 - 0
Server/SM.Model/SQL/Base/EntityVersion.cs

@@ -0,0 +1,24 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 实体版本
+/// </summary>
+public class EntityVersion<TKey> : EntityBase, IVersion
+{
+    /// <summary>
+    /// 版本
+    /// </summary>
+    [Description("版本")]
+    [Column(Position = -30, IsVersion = true)]
+    public virtual long Version { get; set; }
+}
+
+/// <summary>
+/// 实体版本
+/// </summary>
+public class EntityVersion : EntityVersion<long>
+{
+}

+ 17 - 0
Server/SM.Model/SQL/Base/IData.cs

@@ -0,0 +1,17 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 数据权限接口
+/// </summary>
+public interface IData
+{
+    /// <summary>
+    /// 拥有者Id
+    /// </summary>
+    long? OwnerId { get; set; }
+
+    /// <summary>
+    /// 拥有者部门Id
+    /// </summary>
+    long? OwnerOrgId { get; set; }
+}

+ 12 - 0
Server/SM.Model/SQL/Base/IDelete.cs

@@ -0,0 +1,12 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 删除接口
+/// </summary>
+public interface IDelete
+{
+    /// <summary>
+    /// 是否删除
+    /// </summary>
+    bool IsDeleted { get; set; }
+}

+ 21 - 0
Server/SM.Model/SQL/Base/IEntityAdd.cs

@@ -0,0 +1,21 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 添加接口
+/// </summary>
+/// <typeparam name="TKey"></typeparam>
+public interface IEntityAdd<TKey> where TKey : struct
+{
+    /// <summary>
+    /// 创建者用户Id
+    /// </summary>
+    long? CreatedUserId { get; set; }
+    /// <summary>
+    /// 创建者
+    /// </summary>
+    string CreatedUserName { get; set; }
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    DateTime? CreatedTime { get; set; }
+}

+ 21 - 0
Server/SM.Model/SQL/Base/IEntityUpdate.cs

@@ -0,0 +1,21 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 修改接口
+/// </summary>
+/// <typeparam name="TKey"></typeparam>
+public interface IEntityUpdate<TKey> where TKey : struct
+{
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    long? ModifiedUserId { get; set; }
+    /// <summary>
+    /// 修改者
+    /// </summary>
+    string ModifiedUserName { get; set; }
+    /// <summary>
+    /// 修改时间
+    /// </summary>
+    DateTime? ModifiedTime { get; set; }
+}

+ 12 - 0
Server/SM.Model/SQL/Base/IMember.cs

@@ -0,0 +1,12 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 会员接口
+/// </summary>
+public interface IMember
+{
+    /// <summary>
+    /// 顾客Id
+    /// </summary>
+    long? MemberId { get; set; }
+}

+ 12 - 0
Server/SM.Model/SQL/Base/ITenant.cs

@@ -0,0 +1,12 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 租户接口
+/// </summary>
+public interface ITenant
+{
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    long? TenantId { get; set; }
+}

+ 12 - 0
Server/SM.Model/SQL/Base/IVersion.cs

@@ -0,0 +1,12 @@
+namespace SM.Model.SQL;
+
+/// <summary>
+/// 版本接口
+/// </summary>
+public interface IVersion
+{
+    /// <summary>
+    /// 数据版本
+    /// </summary>
+    long Version { get; set; }
+}

+ 21 - 0
Server/SM.Model/SQL/Project/AccountEntity.cs

@@ -0,0 +1,21 @@
+using FreeSql.DataAnnotations;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace SM.Model.SQL
+{
+    ///<summary>账号实体</summary>
+    [Table(Name = "ar_account")]
+    public class AccountEntity : EntityAdd
+    {
+        ///<summary>账号</summary>
+        [Description("账号")]
+        [MaxLength(255)]
+        public string Account { get; set; }
+
+        ///<summary>Mac地址</summary>
+        [Description("Mac地址")]
+        [MaxLength(255)]
+        public string Mac { get; set; }
+    }
+}

+ 106 - 0
Server/SM.Services/Account/AccountService.cs

@@ -0,0 +1,106 @@
+using AR.Core;
+using FreeRedis;
+using SM.Core;
+using SM.Model.SQL;
+
+namespace SM.Services
+{
+    ///<summary>账号服务</summary>
+    public class AccountService : IAccountService
+    {
+        private AppConfig _AppConfig;
+        //数据库实例
+        private IFreeSql _IFreeSql;
+        //Redis实例
+        private RedisClient _RedisClient;
+
+        public AccountService(AppConfig ac, IFreeSql fs, RedisClient redis)
+        {
+            _AppConfig = ac;
+            _IFreeSql = fs;
+            _RedisClient = redis;
+        }
+
+        ///<summary>登录</summary>
+        public async Task<IResponseOutput> Login(Account_Login_Input input)
+        {
+            bool verify = await RequestVerify(input.Account);
+            if (verify)
+                return ResponseOutput.NotOk(StatusCode.Error_Frequently, "Frequent requests, limit requests");
+
+            string s_key = MemoryConst.SecretKey + input.Account;
+            bool s_exists = await _RedisClient.ExistsAsync(s_key);
+            if (s_exists)
+            {
+                string secret = await _RedisClient.GetAsync<string>(s_key);
+                if (!secret.IsNull())
+                {
+                    LogHelper.Info($"{secret}  -----  {input.Mac}");
+                    string mac = DesEncryptHelper.Decrypt(input.Mac, secret);
+                    string a_key = MemoryConst.Account + input.Account;
+                    AccountEntity account;
+                    bool a_exists = await _RedisClient.ExistsAsync(a_key);
+                    if (!a_exists)
+                    {
+                        var repo = _IFreeSql.GetRepository<AccountEntity>();
+                        account = await repo.Where(a => a.Account == input.Account).ToOneAsync();
+                        if (account == null)
+                        {
+                            account = new AccountEntity();
+                            account.Account = input.Account;
+                            account.Mac = mac;
+                            account.CreatedTime = DateTime.Now;
+                            await repo.InsertAsync(account);
+                        }
+                        await _RedisClient.SetAsync(a_key, account);
+                    }
+                    else
+                        account = await _RedisClient.GetAsync<AccountEntity>(a_key);
+                    if (account.Mac == mac)
+                    {
+                        LogHelper.Info($"用户:{input.Account} 登录成功!");
+                        return ResponseOutput.Ok("login successful");
+                    }
+                    else
+                    {
+                        LogHelper.Debug($"用户:{input.Account} Mac:{account.Mac} 登录失败! Mac错误! {input.Mac}");
+                        return ResponseOutput.NotOk(StatusCode.Login_Error_Mac, "Login failed Mac address error");
+                    }
+                }
+            }
+            LogHelper.Debug($"用户:{input.Account} 登录失败! 登录超时!");
+            return ResponseOutput.NotOk(StatusCode.Login_Error_TimeOut, "login timeout");
+        }
+
+        ///<summary>获取秘钥</summary>
+        public async Task<IResponseOutput> SecretKey(Account_SecretKey_Input input)
+        {
+            bool verify = await RequestVerify(input.Account);
+            if (verify)
+                return ResponseOutput.NotOk(StatusCode.Error_Frequently, "Frequent requests, limit requests");
+
+            string secret;
+            string s_key = MemoryConst.SecretKey + input.Account;
+            bool exists = await _RedisClient.ExistsAsync(s_key);
+            if (!exists)
+            {
+                secret = EncryptorHelper.CreateRandomKey();
+                await _RedisClient.SetAsync(s_key, secret, TimeSpan.FromMinutes(5));
+            }
+            secret = await _RedisClient.GetAsync<string>(s_key);
+            LogHelper.Info(DesEncryptHelper.Encrypt("123456-123456", secret));
+            return ResponseOutput.Ok("get success", secret);
+        }
+
+        ///<summary>验证请求次数</summary>
+        private async Task<bool> RequestVerify(string account)
+        {
+            string key = MemoryConst.Verify + account;
+            bool exists = await _RedisClient.ExistsAsync(key);
+            if (!exists)
+                await _RedisClient.SetAsync(key, 0, TimeSpan.FromHours(1));
+            long num = await _RedisClient.IncrAsync(key);
+            return num > _AppConfig.LimitRequests;
+        }
+    }
+}

+ 14 - 0
Server/SM.Services/Account/IAccountService.cs

@@ -0,0 +1,14 @@
+using SM.Core;
+
+namespace SM.Services
+{
+    ///<summary>账号服务</summary>
+    public interface IAccountService
+    {
+        ///<summary>登录</summary>
+        Task<IResponseOutput> Login(Account_Login_Input input);
+
+        ///<summary>获取秘钥</summary>
+        Task<IResponseOutput> SecretKey(Account_SecretKey_Input input);
+    }
+}

+ 10 - 0
Server/SM.Services/Account/Input/Account_Login_Input.cs

@@ -0,0 +1,10 @@
+namespace SM.Services
+{
+    public class Account_Login_Input
+    {
+        ///<summary>账号</summary>
+        public string Account { get; set; }
+        ///<summary>Mac地址</summary>
+        public string Mac { get; set; }
+    }
+}

+ 8 - 0
Server/SM.Services/Account/Input/Account_SecretKey_Input.cs

@@ -0,0 +1,8 @@
+namespace SM.Services
+{
+    public class Account_SecretKey_Input
+    {
+        ///<summary>账号</summary>
+        public string Account { get; set; }
+    }
+}

+ 11 - 0
Server/SM.Services/MapConfig.cs

@@ -0,0 +1,11 @@
+using AutoMapper;
+
+namespace SM.Services
+{
+    public class MapConfig : Profile
+    {
+        public MapConfig()
+        {
+        }
+    }
+}

+ 20 - 0
Server/SM.Services/SM.Services.csproj

@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="AutoMapper" Version="12.0.1" />
+    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
+    <PackageReference Include="UAParser" Version="3.1.47" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SM.Core\SM.Core.csproj" />
+    <ProjectReference Include="..\SM.Model\SM.Model.csproj" />
+  </ItemGroup>
+
+</Project>

+ 36 - 0
Server/SM.Web/Auth/PermissionAttribute.cs

@@ -0,0 +1,36 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+using SM.Core;
+
+namespace SM.Web
+{
+    ///<summary>启用权限</summary>
+    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+    public class PermissionAttribute : AuthorizeAttribute, IAuthorizationFilter, IAsyncAuthorizationFilter
+    {
+        private async Task PermissionAuthorization(AuthorizationFilterContext context)
+        {
+            //排除匿名访问
+            if (context.ActionDescriptor.EndpointMetadata.Any(m => m.GetType() == typeof(AllowAnonymousAttribute)))
+                return;
+
+            IUser user = context.HttpContext.RequestServices.GetService<IUser>();
+            if (user == null || user?.Id < 0)
+            {
+                context.Result = new ChallengeResult();
+                return;
+            }
+        }
+
+        public async void OnAuthorization(AuthorizationFilterContext context)
+        {
+            await PermissionAuthorization(context);
+        }
+
+        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
+        {
+            await PermissionAuthorization(context);
+        }
+    }
+}

+ 61 - 0
Server/SM.Web/Auth/ResponseAuthenticationHandler.cs

@@ -0,0 +1,61 @@
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.Options;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using SM.Core;
+using System.Text.Encodings.Web;
+
+namespace SM.Web
+{
+    public class ResponseAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
+    {
+        public ResponseAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
+        {
+        }
+
+        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
+        {
+            Response.ContentType = "application/json";
+            Response.StatusCode = Microsoft.AspNetCore.Http.StatusCodes.Status401Unauthorized;
+            await Response.WriteAsync(JsonConvert.SerializeObject(
+                new ResponseStatusData
+                {
+                    Code = StatusCodes.Status401Unauthorized,
+                    Msg = StatusCodes.Status401Unauthorized.ToDescription()
+                },
+                new JsonSerializerSettings()
+                {
+                    ContractResolver = new CamelCasePropertyNamesContractResolver()
+                }
+            ));
+        }
+
+        protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
+        {
+            Response.ContentType = "application/json";
+            Response.StatusCode = Microsoft.AspNetCore.Http.StatusCodes.Status403Forbidden;
+            await Response.WriteAsync(JsonConvert.SerializeObject(
+                new ResponseStatusData
+                {
+                    Code = StatusCodes.Status403Forbidden,
+                    Msg = StatusCodes.Status403Forbidden.ToDescription()
+                },
+                new JsonSerializerSettings()
+                {
+                    ContractResolver = new CamelCasePropertyNamesContractResolver()
+                }
+            ));
+        }
+    }
+
+    public class ResponseStatusData
+    {
+        public StatusCodes Code { get; set; } = StatusCodes.Status1Ok;
+        public string Msg { get; set; }
+    }
+}

+ 14 - 0
Server/SM.Web/Configs/AppConfig.json

@@ -0,0 +1,14 @@
+{
+  //启动地址
+  "Urls": [ "http://*:8000" ],
+  //访问地址
+  "WebUrl": "http://172.10.18.189:8000",
+  //跨域忽略地址
+  "Cors": [ "http://localhost:8099", "http://localhost:8888" ],
+  //调试模式
+  "IsDebug": true,
+  //服务URL
+  "ServiceUrl": "",
+  //限制请求次数(小时)
+  "LimitRequests": 50
+}

+ 12 - 0
Server/SM.Web/Configs/JwtConfig.json

@@ -0,0 +1,12 @@
+{
+  //发行者
+  "Issuer": "http://172.10.18.189:8000",
+  //订阅者
+  "Audience": "http://172.10.18.189:8000",
+  //密钥
+  "SecurityKey": "wikPLF^B9bNbSXfPL%6!LXrrykpC*AEG",
+  //有效期(分钟) 120 = 2小时
+  "Expires": 1440,
+  //刷新有效期(分钟) 1440 = 1天
+  "RefreshExpires": 1440
+}

+ 8 - 0
Server/SM.Web/Configs/MongoDBConfig.json

@@ -0,0 +1,8 @@
+{
+  //数据库名称
+  "DBName": "",
+  //数据库连接路径
+  "DBPath": "mongodb://127.0.0.1:27017",
+  //证书路径
+  "CAPath": ""
+}

+ 4 - 0
Server/SM.Web/Configs/MySQLConfig.json

@@ -0,0 +1,4 @@
+{
+  //链接地址
+  "Connect": "Server=localhost; Port=3306; Database=sm; Uid=root; Pwd=123456; Charset=utf8mb4;SslMode=none;Min pool size=1"
+}

+ 4 - 0
Server/SM.Web/Configs/RedisConfig.json

@@ -0,0 +1,4 @@
+{
+  //链接地址
+  "Connect": "127.0.0.1:6379"
+}

+ 12 - 0
Server/SM.Web/Configs/WeChatConfig.json

@@ -0,0 +1,12 @@
+{
+  //唯一凭证
+  "AppId": "",
+  //唯一凭证密钥
+  "Secret": "",
+  //小程序唯一凭证
+  "AppletAppId": "",
+  //小程序唯一凭证密钥
+  "AppletSecret": "",
+  //JS安全域名
+  "URL": ""
+}

+ 12 - 0
Server/SM.Web/Controllers/Base/InternalController.cs

@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace SM.Web
+{
+    /// <summary>内网基础控制器</summary>
+    [Route("api/internal/[controller]/[action]")]
+    [ApiController]
+    public class InternalController : ControllerBase
+    {
+
+    }
+}

+ 12 - 0
Server/SM.Web/Controllers/Base/WebController.cs

@@ -0,0 +1,12 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace SM.Web
+{
+    /// <summary>基础控制器</summary>
+    [Route("api/[controller]/[action]")]
+    [ApiController]
+    [Permission]
+    public class WebController : ControllerBase
+    {
+    }
+}

+ 34 - 0
Server/SM.Web/Controllers/Web/AccountController.cs

@@ -0,0 +1,34 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SM.Core;
+using SM.Services;
+
+namespace SM.Web.Controllers
+{
+    ///<summary>账号服务</summary>
+    public class AccountController : WebController
+    {
+        private IAccountService _AccountService;
+
+        public AccountController(IAccountService ia)
+        {
+            _AccountService = ia;
+        }
+
+        ///<summary>登录</summary>
+        [HttpPost]
+        [AllowAnonymous]
+        public async Task<IResponseOutput> Login(Account_Login_Input input)
+        {
+            return await _AccountService.Login(input);
+        }
+
+        ///<summary>获取秘钥</summary>
+        [HttpPost]
+        [AllowAnonymous]
+        public async Task<IResponseOutput> SecretKey(Account_SecretKey_Input input)
+        {
+            return await _AccountService.SecretKey(input);
+        }
+    }
+}

+ 47 - 0
Server/SM.Web/Other/ExceptionMiddleware.cs

@@ -0,0 +1,47 @@
+using SM.Core;
+using NLog;
+
+namespace SM.Web
+{
+    ///<summary>异常中间件</summary>
+    public class ExceptionMiddleware
+    {
+        //程序配置信息
+        private AppConfig _AppConfig;
+        private readonly RequestDelegate next;
+        private Logger logger;
+
+        public ExceptionMiddleware(AppConfig ac, RequestDelegate request)
+        {
+            _AppConfig = ac;
+            next = request;
+            logger = LogManager.GetCurrentClassLogger();
+        }
+
+        public async Task Invoke(HttpContext context)
+        {
+            try
+            {
+                await next.Invoke(context);
+            }
+            catch (Exception ex)
+            {
+                await HandleExceptionAsync(context, ex);
+            }
+        }
+
+        private async Task HandleExceptionAsync(HttpContext context, Exception e)
+        {
+            logger.Error(e, e.Message);
+            IResponseOutput<Exception> output = (IResponseOutput<Exception>)ResponseOutput.NotOk(StatusCode.Fail, null, "Server Error");
+            if (_AppConfig.IsDebug)
+            {
+                output.message = e.Message;
+                output.data = e;
+            }
+            string message = LitJson.JsonMapper.ToJson(output);
+            context.Response.StatusCode = 200;
+            await context.Response.WriteAsync(message);
+        }
+    }
+}

+ 32 - 0
Server/SM.Web/Other/StatusCodes.cs

@@ -0,0 +1,32 @@
+using System.ComponentModel;
+
+namespace SM.Web
+{
+    /// <summary>状态码枚举</summary>
+    public enum StatusCodes
+    {
+        /// <summary>操作失败</summary>
+        [Description("操作失败")]
+        Status0NotOk = 0,
+
+        /// <summary>操作成功</summary>
+        [Description("操作成功")]
+        Status1Ok = 1,
+
+        /// <summary>未登录(需要重新登录)</summary>
+        [Description("未登录")]
+        Status401Unauthorized = 401,
+
+        /// <summary>没有同意开发者协议</summary>
+        [Description("没有同意开发者协议")]
+        Status403Forbidden = 403,
+
+        /// <summary>资源不存在</summary>
+        [Description("资源不存在")]
+        Status404NotFound = 404,
+
+        /// <summary>系统内部错误(非业务代码里显式抛出的异常,例如由于数据不正确导致空指针异常、数据库异常等等)</summary>
+        [Description("系统内部错误")]
+        Status500InternalServerError = 500
+    }
+}

+ 3 - 0
Server/SM.Web/Program.cs

@@ -0,0 +1,3 @@
+using SM.Web;
+
+new Startup().Run(args);

+ 17 - 0
Server/SM.Web/Properties/PublishProfiles/Server.pubxml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <DeleteExistingFiles>false</DeleteExistingFiles>
+    <ExcludeApp_Data>false</ExcludeApp_Data>
+    <LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
+    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
+    <LastUsedPlatform>Any CPU</LastUsedPlatform>
+    <PublishProvider>FileSystem</PublishProvider>
+    <PublishUrl>F:\UnityProject\gouhuo\Server\Build-Server</PublishUrl>
+    <WebPublishMethod>FileSystem</WebPublishMethod>
+    <_TargetId>Folder</_TargetId>
+  </PropertyGroup>
+</Project>

+ 11 - 0
Server/SM.Web/Properties/PublishProfiles/Server.pubxml.user

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121.
+-->
+<Project>
+  <PropertyGroup>
+    <_PublishTargetUrl>F:\UnityProject\gouhuo\Server\Build-Server</_PublishTargetUrl>
+    <History>True|2024-01-22T06:21:51.7970900Z;</History>
+    <LastFailureDetails />
+  </PropertyGroup>
+</Project>

+ 13 - 0
Server/SM.Web/Properties/launchSettings.json

@@ -0,0 +1,13 @@
+{
+  "profiles": {
+    "SM.Web": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 24 - 0
Server/SM.Web/SM.Web.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+    <DocumentationFile>F:\UnityProject\gouhuo\Server\SM.Web\SM.Web.xml</DocumentationFile>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="9.0.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.1" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\SM.Core\SM.Core.csproj" />
+    <ProjectReference Include="..\SM.Model\SM.Model.csproj" />
+    <ProjectReference Include="..\SM.Services\SM.Services.csproj" />
+  </ItemGroup>
+
+</Project>

+ 7 - 0
Server/SM.Web/SM.Web.csproj.user

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <ActiveDebugProfile>https</ActiveDebugProfile>
+    <NameOfLastUsedPublishProfile>F:\UnityProject\gouhuo\Server\SM.Web\Properties\PublishProfiles\Server.pubxml</NameOfLastUsedPublishProfile>
+  </PropertyGroup>
+</Project>

+ 56 - 0
Server/SM.Web/SM.Web.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<doc>
+    <assembly>
+        <name>SM.Web</name>
+    </assembly>
+    <members>
+        <member name="T:SM.Web.PermissionAttribute">
+            <summary>启用权限</summary>
+        </member>
+        <member name="T:SM.Web.InternalController">
+            <summary>内网基础控制器</summary>
+        </member>
+        <member name="T:SM.Web.WebController">
+            <summary>基础控制器</summary>
+        </member>
+        <member name="T:SM.Web.Controllers.AccountController">
+            <summary>账号服务</summary>
+        </member>
+        <member name="M:SM.Web.Controllers.AccountController.Login(SM.Services.Account_Login_Input)">
+            <summary>登录</summary>
+        </member>
+        <member name="M:SM.Web.Controllers.AccountController.SecretKey(SM.Services.Account_SecretKey_Input)">
+            <summary>获取秘钥</summary>
+        </member>
+        <member name="T:SM.Web.ExceptionMiddleware">
+            <summary>异常中间件</summary>
+        </member>
+        <member name="T:SM.Web.StatusCodes">
+            <summary>状态码枚举</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status0NotOk">
+            <summary>操作失败</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status1Ok">
+            <summary>操作成功</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status401Unauthorized">
+            <summary>未登录(需要重新登录)</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status403Forbidden">
+            <summary>没有同意开发者协议</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status404NotFound">
+            <summary>资源不存在</summary>
+        </member>
+        <member name="F:SM.Web.StatusCodes.Status500InternalServerError">
+            <summary>系统内部错误(非业务代码里显式抛出的异常,例如由于数据不正确导致空指针异常、数据库异常等等)</summary>
+        </member>
+        <member name="T:SM.Web.Startup">
+            <summary>启动器</summary>
+        </member>
+        <member name="M:SM.Web.Startup.Run(System.String[])">
+            <summary>执行方法</summary>
+        </member>
+    </members>
+</doc>

+ 245 - 0
Server/SM.Web/Startup.cs

@@ -0,0 +1,245 @@
+using Autofac;
+using Autofac.Extensions.DependencyInjection;
+using FreeRedis;
+using FreeSql;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using NLog.Web;
+using SM.Core;
+using SM.Model.SQL;
+using System.Reflection;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace SM.Web
+{
+    ///<summary>启动器</summary>
+    public class Startup
+    {
+        ///<summary>执行方法</summary>
+        public void Run(string[] args)
+        {
+            try
+            {
+                WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+                builder.Logging.ClearProviders().SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
+                builder.Host.UseNLog();
+
+                IServiceCollection services = builder.Services;
+                IWebHostEnvironment env = builder.Environment;
+
+                builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
+
+                AppConfig appConfig = ConfigHelper.Get<AppConfig>("AppConfig");
+                services.AddSingleton(appConfig);
+
+                builder.WebHost.UseUrls(appConfig.Urls);
+
+                for (int i = 0; i < appConfig.Urls.Length; i++)
+                {
+                    LogHelper.Debug(appConfig.Urls[i]);
+                }
+
+                ConfigureServices(services, appConfig, env);
+
+                builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
+                {
+                    ConfigureContainer(builder);
+                });
+                WebApplication app = builder.Build();
+
+                Configure(app, appConfig);
+
+                app.Run();
+            }
+            catch (Exception ex)
+            {
+                LogHelper.Error($"启动错误:{ex.Message}", ex);
+            }
+        }
+
+        private void ConfigureServices(IServiceCollection services, AppConfig appConfig, IWebHostEnvironment env)
+        {
+            services.AddHttpClient();
+            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+            //自动映射
+            Assembly serviceAssembly = Assembly.Load("SM.Services");
+            services.AddAutoMapper(serviceAssembly);
+            services.AddCors(options =>
+            {
+                options.AddPolicy("Cors", policy =>
+                {
+                    if (appConfig.IsDebug)
+                    {
+                        policy
+                        .AllowAnyMethod()
+                        .SetIsOriginAllowed(_ => true)
+                        .AllowAnyHeader()
+                        .AllowCredentials();
+                    }
+                    else
+                    {
+                        policy
+                        .WithOrigins(appConfig.Cors)
+                        .AllowAnyHeader()
+                        .AllowAnyMethod()
+                        .AllowCredentials();
+                    }
+                });
+            });
+
+            JwtConfig jwtConfig = ConfigHelper.Get<JwtConfig>("JwtConfig");
+            services.AddSingleton(jwtConfig);
+
+            services.AddAuthentication(options =>
+            {
+                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
+                options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
+                options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler);    //403
+            }).AddJwtBearer(options =>
+            {
+                options.TokenValidationParameters = new TokenValidationParameters
+                {
+                    ValidateIssuer = true,
+                    ValidateAudience = true,
+                    ValidateLifetime = true,
+                    ValidateIssuerSigningKey = true,
+                    ValidIssuer = jwtConfig.Issuer,
+                    ValidAudience = jwtConfig.Audience,
+                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey)),
+                    ClockSkew = TimeSpan.Zero
+                };
+            }).AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
+
+            //内存
+            //services.AddMemoryCache();
+
+            //MySQL
+            MySQLConfig mySQLConfig = ConfigHelper.Get<MySQLConfig>("MySQLConfig");
+            Func<IServiceProvider, IFreeSql> fsqlFactory = r =>
+            {
+                IFreeSql fsql = new FreeSqlBuilder()
+                    .UseConnectionString(DataType.MySql, mySQLConfig.Connect)
+                    //.UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}"))//监听SQL语句
+                    .UseAutoSyncStructure(true) //自动同步实体结构到数据库,FreeSql不会扫描程序集,只有CRUD时才会生成表。
+                    .Build();
+                return fsql;
+            };
+            services.AddSingleton<IFreeSql>(fsqlFactory);
+
+            //Redis
+            RedisConfig redisConfig = ConfigHelper.Get<RedisConfig>("RedisConfig");
+            RedisClient redis = new RedisClient(redisConfig.Connect)
+            {
+                Serialize = JsonConvert.SerializeObject,
+                Deserialize = JsonConvert.DeserializeObject
+            };
+            services.AddSingleton(redis);
+
+            //MongoDB
+            //services.AddScoped<IMongoContext, MongoContext>();
+            //services.AddScoped(typeof(IMongoRepository<>), typeof(MongoRepository<>));
+
+            //设置输出的首字母大小写
+            services.AddControllers().AddNewtonsoftJson(options =>
+            {
+                //忽略循环引用
+                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
+                //默认属性输出 
+                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
+                //使用驼峰 首字母小写
+                //options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+                //设置时间格式
+                options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
+            });
+            //截取客户端提交的空参数
+            services.Configure<ApiBehaviorOptions>(options =>
+            {
+                options.InvalidModelStateResponseFactory = actionContext =>
+                {
+                    List<string> errors = actionContext.ModelState.Where(e => e.Value.Errors.Count > 0).Select(e => e.Value.Errors.First().ErrorMessage).ToList();
+                    string str = string.Join(" | ", errors);
+                    IResponseOutput response = ResponseOutput.NotOk(StatusCode.FieldNull_Error, str);
+                    return new BadRequestObjectResult(response);
+                };
+            });
+            if (appConfig.IsDebug)
+            {
+                services.AddSwaggerGen(c =>
+                {
+                    c.SwaggerDoc("v1", new OpenApiInfo { Title = "SM.Web", Version = "v1" });
+                    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+                    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
+                    c.IncludeXmlComments(xmlPath);
+                });
+            }
+        }
+
+        private void ConfigureContainer(ContainerBuilder builder)
+        {
+            Assembly assemblyCore = Assembly.Load("SM.Core");
+            builder.RegisterAssemblyTypes(assemblyCore).Where(t => t.GetCustomAttribute<SingleInstanceAttribute>() != null).SingleInstance();
+            builder.RegisterAssemblyTypes(assemblyCore).Where(t => t.GetCustomAttribute<SingleInstanceAttribute>() != null).AsImplementedInterfaces().SingleInstance();
+            var assemblyServices = Assembly.Load("SM.Services");
+            builder.RegisterAssemblyTypes(assemblyServices).AsImplementedInterfaces().InstancePerLifetimeScope();
+        }
+
+        private void Configure(WebApplication app, AppConfig appConfig)
+        {
+            if (appConfig.IsDebug)
+            {
+                app.UseSwagger();
+                app.UseSwaggerUI(c =>
+                {
+                    c.SwaggerEndpoint("/swagger/v1/swagger.json", "SM.Web");
+                });
+            }
+
+            //异常处理
+            app.UseMiddleware<ExceptionMiddleware>();
+
+            //路由
+            app.UseRouting();
+
+            //跨域
+            app.UseCors("Cors");
+
+            //认证
+            app.UseAuthentication();
+
+            //授权
+            app.UseAuthorization();
+
+            //默认访问
+            //app.UseDefaultFiles();
+            //静态文件访问(开启所以文件访问权限)
+            //app.UseStaticFiles(new StaticFileOptions
+            //{
+            //    ServeUnknownFileTypes = true
+            //});
+
+            if (!app.Environment.IsDevelopment())
+            {
+                //https
+                app.UseHttpsRedirection();
+            }
+
+            //配置端点
+            app.MapControllers();
+
+            //在项目启动时,从容器中获取IFreeSql实例,并执行一些操作:同步表,种子数据,FluentAPI等
+            using (IServiceScope serviceScope = app.Services.CreateScope())
+            {
+                var fsql = serviceScope.ServiceProvider.GetRequiredService<IFreeSql>();
+                //同步的实体类
+                fsql.CodeFirst.SyncStructure<AccountEntity>();
+            }
+            LogHelper.Debug("启动成功");
+        }
+    }
+}

+ 8 - 0
Server/SM.Web/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 9 - 0
Server/SM.Web/appsettings.json

@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}

+ 46 - 0
Server/SM.Web/nlog.config

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 配置文档 https://nlog-project.org/config -->
+<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" autoReload="true" internalLogLevel="Info">
+	<!-- 启用.net core的核心布局渲染器 -->
+	<extensions>
+		<add assembly="NLog.Web.AspNetCore" />
+	</extensions>
+	<!-- 写入日志的目标配置 archiveAboveSize="102400" maxArchiveDays="60" -->
+	<targets>
+		<target xsi:type="File" name="allfile" fileName="${basedir}/WebLogs/Log-${shortdate}/All-${shortdate}.log"
+				layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} ${exception:format=tostring}" />
+	</targets>
+
+	<targets>
+		<target xsi:type="File" name="debug" fileName="${basedir}/WebLogs/Log-${shortdate}/Debug-${shortdate}.log"
+				layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} " />
+		<target name="debugConsole" type="ColoredConsole"
+					 layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} " />
+	</targets>
+
+	<targets>
+		<target xsi:type="File" name="error" fileName="${basedir}/WebLogs/Log-${shortdate}/Error-${shortdate}.log"
+				layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} ${exception:format=tostring}" />
+		<target name="errorConsole" type="ColoredConsole"
+					 layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} ${exception:format=tostring}" />
+	</targets>
+
+	<targets>
+		<target xsi:type="File" name="info" fileName="${basedir}/WebLogs/Log-${shortdate}/Info-${shortdate}.log"
+				layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} " />
+		<target name="infoConsole" type="ColoredConsole"
+					 layout="${longdate} ${uppercase:${level}} ${callsite:className=false:methodName=false:fileName=true:includeSourcePath=false:skipFrames=2} ${message} " />
+	</targets>
+
+	<rules>
+		<logger name="Microsoft.*" maxLevel="info" final="true" />
+		<logger name="Microsoft.*" maxLevel="debug" final="true" />
+		<logger name="*" minlevel="Error" maxlevel="Error" writeTo="error" />
+		<logger name="*" minlevel="Error" maxlevel="Error" writeTo="errorConsole" />
+		<logger name="*" minlevel="Debug" maxlevel="Debug" writeTo="debug" />
+		<logger name="*" minlevel="Debug" maxlevel="Debug" writeTo="debugConsole" />
+		<logger name="*" minlevel="Info" maxlevel="Info" writeTo="info" />
+		<logger name="*" minlevel="Info" maxlevel="Info" writeTo="infoConsole" />
+		<logger name="*" minlevel="Trace" maxlevel="Fatal" writeTo="allfile" />
+	</rules>
+</nlog>

+ 43 - 0
Server/SMServer.sln

@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34330.188
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SM.Core", "SM.Core\SM.Core.csproj", "{C16EEFD9-5B74-43F4-94C8-AFC2B290F2D6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SM.Model", "SM.Model\SM.Model.csproj", "{B6AAE8E8-EB2A-41A9-AC05-7041F5466094}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SM.Services", "SM.Services\SM.Services.csproj", "{475DDD09-D7C1-4C83-ACFF-E234778EF5FD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SM.Web", "SM.Web\SM.Web.csproj", "{0B2C8B87-066D-4266-AA47-37AA0E6884B2}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{C16EEFD9-5B74-43F4-94C8-AFC2B290F2D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C16EEFD9-5B74-43F4-94C8-AFC2B290F2D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C16EEFD9-5B74-43F4-94C8-AFC2B290F2D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C16EEFD9-5B74-43F4-94C8-AFC2B290F2D6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B6AAE8E8-EB2A-41A9-AC05-7041F5466094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B6AAE8E8-EB2A-41A9-AC05-7041F5466094}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B6AAE8E8-EB2A-41A9-AC05-7041F5466094}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B6AAE8E8-EB2A-41A9-AC05-7041F5466094}.Release|Any CPU.Build.0 = Release|Any CPU
+		{475DDD09-D7C1-4C83-ACFF-E234778EF5FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{475DDD09-D7C1-4C83-ACFF-E234778EF5FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{475DDD09-D7C1-4C83-ACFF-E234778EF5FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{475DDD09-D7C1-4C83-ACFF-E234778EF5FD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{0B2C8B87-066D-4266-AA47-37AA0E6884B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{0B2C8B87-066D-4266-AA47-37AA0E6884B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{0B2C8B87-066D-4266-AA47-37AA0E6884B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{0B2C8B87-066D-4266-AA47-37AA0E6884B2}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {E2000243-F63F-452C-8880-92BAA65343A9}
+	EndGlobalSection
+EndGlobal