diff --git a/Infrastructure/App/App.cs b/Infrastructure/App/App.cs index 663e8ef..eb083f2 100644 --- a/Infrastructure/App/App.cs +++ b/Infrastructure/App/App.cs @@ -1,5 +1,9 @@ -using Microsoft.AspNetCore.Http; +using Infrastructure.Model; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using System; using System.Security.Claims; @@ -7,19 +11,35 @@ namespace Infrastructure { public static class App { + /// + /// 全局配置文件 + /// + public static OptionsSetting OptionsSetting => CatchOrDefault(() => ServiceProvider?.GetService>()?.Value); + /// /// 服务提供器 /// - public static IServiceProvider ServiceProvider => HttpContext?.RequestServices ?? InternalApp.ServiceProvider; + public static IServiceProvider ServiceProvider => InternalApp.ServiceProvider; /// /// 获取请求上下文 /// - public static HttpContext HttpContext => HttpContextLocal.Current(); + public static HttpContext HttpContext => CatchOrDefault(() => ServiceProvider?.GetService()?.HttpContext); /// /// 获取请求上下文用户 /// public static ClaimsPrincipal User => HttpContext?.User; - + /// + /// 获取用户名 + /// + public static string UserName => User?.Identity?.Name; + /// + /// 获取Web主机环境 + /// + public static IWebHostEnvironment WebHostEnvironment => InternalApp.WebHostEnvironment; + /// + /// 获取全局配置 + /// + public static IConfiguration Configuration => CatchOrDefault(() => InternalApp.Configuration, new ConfigurationBuilder().Build()); /// /// 获取请求生命周期的服务 /// @@ -61,5 +81,25 @@ namespace Infrastructure { return ServiceProvider.GetRequiredService(type); } + + /// + /// 处理获取对象异常问题 + /// + /// 类型 + /// 获取对象委托 + /// 默认值 + /// T + private static T CatchOrDefault(Func action, T defaultValue = null) + where T : class + { + try + { + return action(); + } + catch + { + return defaultValue ?? null; + } + } } } diff --git a/Infrastructure/App/Web/HttpContextLocal.cs b/Infrastructure/App/Web/HttpContextLocal.cs deleted file mode 100644 index 8c0a96d..0000000 --- a/Infrastructure/App/Web/HttpContextLocal.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading; - -namespace Microsoft.AspNetCore.Http -{ - public static class HttpContextLocal - { - private static Func _asyncLocalAccessor; - private static Func _holderAccessor; - private static Func _httpContextAccessor; - - /// - /// 获取当前 HttpContext 对象 - /// - /// - public static HttpContext Current() - { - var asyncLocal = (_asyncLocalAccessor ??= CreateAsyncLocalAccessor())(); - if (asyncLocal == null) return null; - - var holder = (_holderAccessor ??= CreateHolderAccessor(asyncLocal))(asyncLocal); - if (holder == null) return null; - - return (_httpContextAccessor ??= CreateHttpContextAccessor(holder))(holder); - - // 创建异步本地访问器 - static Func CreateAsyncLocalAccessor() - { - var fieldInfo = typeof(HttpContextAccessor).GetField("_httpContextCurrent", BindingFlags.Static | BindingFlags.NonPublic); - var field = Expression.Field(null, fieldInfo); - return Expression.Lambda>(field).Compile(); - } - - // 创建常驻 HttpContext 访问器 - static Func CreateHolderAccessor(object asyncLocal) - { - var holderType = asyncLocal.GetType().GetGenericArguments()[0]; - var method = typeof(AsyncLocal<>).MakeGenericType(holderType).GetProperty("Value").GetGetMethod(); - var target = Expression.Parameter(typeof(object)); - var convert = Expression.Convert(target, asyncLocal.GetType()); - var getValue = Expression.Call(convert, method); - return Expression.Lambda>(getValue, target).Compile(); - } - - // 获取 HttpContext 访问器 - static Func CreateHttpContextAccessor(object holder) - { - var target = Expression.Parameter(typeof(object)); - var convert = Expression.Convert(target, holder.GetType()); - var field = Expression.Field(convert, "Context"); - var convertAsResult = Expression.Convert(field, typeof(HttpContext)); - return Expression.Lambda>(convertAsResult, target).Compile(); - } - } - } -} diff --git a/ZR.Common/Cache/CacheHelper.cs b/Infrastructure/Cache/CacheHelper.cs similarity index 100% rename from ZR.Common/Cache/CacheHelper.cs rename to Infrastructure/Cache/CacheHelper.cs diff --git a/ZR.Common/Cache/RedisServer.cs b/Infrastructure/Cache/RedisServer.cs similarity index 100% rename from ZR.Common/Cache/RedisServer.cs rename to Infrastructure/Cache/RedisServer.cs diff --git a/ZR.Admin.WebApi/Controllers/BaseController.cs b/Infrastructure/Controllers/BaseController.cs similarity index 83% rename from ZR.Admin.WebApi/Controllers/BaseController.cs rename to Infrastructure/Controllers/BaseController.cs index 5311026..9a88f4e 100644 --- a/ZR.Admin.WebApi/Controllers/BaseController.cs +++ b/Infrastructure/Controllers/BaseController.cs @@ -1,15 +1,20 @@ -using Infrastructure; -using Infrastructure.Extensions; +using Infrastructure.Extensions; using Infrastructure.Model; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.IO; using System.Web; -using Io = System.IO; -namespace ZR.Admin.WebApi.Controllers +namespace Infrastructure.Controllers { + /// + /// web层通用数据处理 + /// public class BaseController : ControllerBase { public static string TIME_FORMAT_FULL = "yyyy-MM-dd HH:mm:ss"; @@ -58,10 +63,12 @@ namespace ZR.Admin.WebApi.Controllers /// protected IActionResult ExportExcel(string path, string fileName) { - //IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment)); - //string fileDir = Path.Combine(webHostEnvironment.WebRootPath, path, fileName); - - var stream = Io.File.OpenRead(path); //创建文件流 + //var webHostEnvironment = App.WebHostEnvironment; + if (!Path.Exists(path)) + { + throw new CustomException(fileName + "文件不存在"); + } + var stream = System.IO.File.OpenRead(path); //创建文件流 Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition"); return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", HttpUtility.UrlEncode(fileName)); @@ -92,6 +99,10 @@ namespace ZR.Admin.WebApi.Controllers return new ApiResult((int)resultCode, msg, data); } + protected ApiResult Success() + { + return GetApiResult(ResultCode.SUCCESS); + } /// /// @@ -169,23 +180,26 @@ namespace ZR.Admin.WebApi.Controllers /// /// 下载导入模板 /// - /// - /// - /// + /// 数据类型 + /// 空数据类型集合 /// 下载文件名 /// - protected string DownloadImportTemplate(List list, Stream stream, string fileName) + protected (string, string) DownloadImportTemplate(List list, string fileName) { - IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment)); - string sFileName = $"{fileName}模板.xlsx"; - string newFileName = Path.Combine(webHostEnvironment.WebRootPath, "ImportTemplate", sFileName); + IWebHostEnvironment webHostEnvironment = App.WebHostEnvironment; + string sFileName = $"{fileName}.xlsx"; + string fullPath = Path.Combine(webHostEnvironment.WebRootPath, "ImportTemplate", sFileName); - if (!Directory.Exists(newFileName)) + //不存在模板创建模板 + if (!Directory.Exists(fullPath)) { - Directory.CreateDirectory(Path.GetDirectoryName(newFileName)); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); } - MiniExcel.SaveAs(newFileName, list); - return sFileName; + if (!Path.Exists(fullPath)) + { + MiniExcel.SaveAs(fullPath, list, overwriteFile: true); + } + return (sFileName, fullPath); } /// diff --git a/ZR.Admin.WebApi/Framework/JsonConverterUtil.cs b/Infrastructure/Converter/JsonConverterUtil.cs similarity index 89% rename from ZR.Admin.WebApi/Framework/JsonConverterUtil.cs rename to Infrastructure/Converter/JsonConverterUtil.cs index e66783e..bd32f10 100644 --- a/ZR.Admin.WebApi/Framework/JsonConverterUtil.cs +++ b/Infrastructure/Converter/JsonConverterUtil.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace ZR.Admin.WebApi.Framework +namespace Infrastructure.Converter { public class JsonConverterUtil { @@ -29,7 +29,7 @@ namespace ZR.Admin.WebApi.Framework public static DateTime? ParseDateTime(string dateStr) { - if (System.Text.RegularExpressions.Regex.IsMatch(dateStr, @"^\d{4}[/-]") && DateTime.TryParse(dateStr, null,System.Globalization.DateTimeStyles.AssumeLocal, out var dateVal)) + if (System.Text.RegularExpressions.Regex.IsMatch(dateStr, @"^\d{4}[/-]") && DateTime.TryParse(dateStr, null, System.Globalization.DateTimeStyles.AssumeLocal, out var dateVal)) return dateVal; return null; } diff --git a/Infrastructure/Converter/StringConverter.cs b/Infrastructure/Converter/StringConverter.cs new file mode 100644 index 0000000..0a0a05a --- /dev/null +++ b/Infrastructure/Converter/StringConverter.cs @@ -0,0 +1,48 @@ +using System; +using System.Buffers; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace Infrastructure.Converter +{ + /// + /// Json任何类型读取到字符串属性 + /// 因为 System.Text.Json 必须严格遵守类型一致,当非字符串读取到字符属性时报错: + /// The JSON value could not be converted to System.String. + /// + public class StringConverter : System.Text.Json.Serialization.JsonConverter + { + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + return reader.GetString(); + } + else + { + //非字符类型,返回原生内容 + return GetRawPropertyValue(reader); + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + /// + /// 非字符类型,返回原生内容 + /// + /// + /// + private static string GetRawPropertyValue(Utf8JsonReader jsonReader) + { + ReadOnlySpan utf8Bytes = jsonReader.HasValueSequence ? + jsonReader.ValueSequence.ToArray() : + jsonReader.ValueSpan; + return Encoding.UTF8.GetString(utf8Bytes); + } + } +} diff --git a/Infrastructure/CustomException/CustomException.cs b/Infrastructure/CustomException/CustomException.cs index 19f8fc5..90f4d57 100644 --- a/Infrastructure/CustomException/CustomException.cs +++ b/Infrastructure/CustomException/CustomException.cs @@ -5,9 +5,19 @@ namespace Infrastructure public class CustomException : Exception { public int Code { get; set; } + /// + /// 前端提示语 + /// public string Msg { get; set; } + /// + /// 记录到日志的详细内容 + /// public string LogMsg { get; set; } - + /// + /// 是否通知 + /// + public bool Notice { get; set; } = true; + public CustomException(string msg) : base(msg) { } @@ -17,9 +27,10 @@ namespace Infrastructure Msg = msg; } - public CustomException(ResultCode resultCode, string msg) : base(msg) + public CustomException(ResultCode resultCode, string msg, bool notice = true) : base(msg) { Code = (int)resultCode; + Notice = notice; } /// diff --git a/Infrastructure/Enums/BusinessType.cs b/Infrastructure/Enums/BusinessType.cs index a4f09e1..1c1d150 100644 --- a/Infrastructure/Enums/BusinessType.cs +++ b/Infrastructure/Enums/BusinessType.cs @@ -54,5 +54,10 @@ /// 清空数据 /// CLEAN = 9, + + /// + /// 下载 + /// + DOWNLOAD = 10, } } diff --git a/Infrastructure/GlobalConstant.cs b/Infrastructure/GlobalConstant.cs index 25fc8d0..ff72abe 100644 --- a/Infrastructure/GlobalConstant.cs +++ b/Infrastructure/GlobalConstant.cs @@ -1,14 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Infrastructure +namespace Infrastructure { /// /// 全局静态常量 /// public class GlobalConstant { + /// + /// 代码生成常量 + /// + public static readonly string CodeGenDbConfig; + /// /// 管理员权限 /// diff --git a/Infrastructure/Helper/DateTimeHelper.cs b/Infrastructure/Helper/DateTimeHelper.cs index 37aa6b9..bc07775 100644 --- a/Infrastructure/Helper/DateTimeHelper.cs +++ b/Infrastructure/Helper/DateTimeHelper.cs @@ -91,7 +91,7 @@ namespace Infrastructure #region 获取unix时间戳 /// - /// 获取unix时间戳 + /// 获取unix时间戳(毫秒) /// /// /// @@ -100,6 +100,12 @@ namespace Infrastructure long unixTime = ((DateTimeOffset)dt).ToUnixTimeMilliseconds(); return unixTime; } + + public static long GetUnixTimeSeconds(DateTime dt) + { + long unixTime = ((DateTimeOffset)dt).ToUnixTimeSeconds(); + return unixTime; + } #endregion #region 获取日期天的最小时间 diff --git a/ZR.Common/JnHelper.cs b/Infrastructure/Helper/JnHelper.cs similarity index 96% rename from ZR.Common/JnHelper.cs rename to Infrastructure/Helper/JnHelper.cs index 85e326b..5a42e6b 100644 --- a/ZR.Common/JnHelper.cs +++ b/Infrastructure/Helper/JnHelper.cs @@ -2,7 +2,7 @@ using System; using System.IO; -namespace ZR.Common +namespace Infrastructure.Helper { public class JnHelper { diff --git a/Infrastructure/Infrastructure.csproj b/Infrastructure/Infrastructure.csproj deleted file mode 100644 index 16b2da3..0000000 --- a/Infrastructure/Infrastructure.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net7.0 - - - - - - - - - - - - - - - diff --git a/Infrastructure/InternalApp.cs b/Infrastructure/InternalApp.cs index 78e24d9..3be8301 100644 --- a/Infrastructure/InternalApp.cs +++ b/Infrastructure/InternalApp.cs @@ -1,6 +1,5 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; using System; namespace Infrastructure @@ -15,12 +14,12 @@ namespace Infrastructure /// /// 全局配置构建器 /// - //public static IConfigurationBuilder ConfigurationBuilder; + public static IConfiguration Configuration; /// /// 获取Web主机环境 /// - //internal static IWebHostEnvironment WebHostEnvironment; + public static IWebHostEnvironment WebHostEnvironment; /// /// 获取泛型主机环境 diff --git a/ZR.Admin.WebApi/Framework/JwtUtil.cs b/Infrastructure/JwtUtil.cs similarity index 76% rename from ZR.Admin.WebApi/Framework/JwtUtil.cs rename to Infrastructure/JwtUtil.cs index c6ef113..bf6a71d 100644 --- a/ZR.Admin.WebApi/Framework/JwtUtil.cs +++ b/Infrastructure/JwtUtil.cs @@ -1,18 +1,19 @@ -using Infrastructure; -using Infrastructure.Extensions; +using Infrastructure.Extensions; +using Infrastructure.Model; +using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; +using System; +using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; +using System.Linq; using System.Security.Claims; using System.Text; -using ZR.Admin.WebApi.Extensions; -using ZR.Model.System.Dto; -using ZR.Service.System; -namespace ZR.Admin.WebApi.Framework +namespace Infrastructure { /// - /// 2020-11-20 + /// 2023-8-29已从WebApi移至此 /// public class JwtUtil { @@ -21,7 +22,7 @@ namespace ZR.Admin.WebApi.Framework /// /// /// - public static LoginUser GetLoginUser(HttpContext httpContext) + public static TokenModel GetLoginUser(HttpContext httpContext) { string token = httpContext.GetToken(); @@ -36,10 +37,12 @@ namespace ZR.Admin.WebApi.Framework /// 生成token /// /// - /// /// - public static string GenerateJwtToken(List claims, JwtSettings jwtSettings) + public static string GenerateJwtToken(List claims) { + JwtSettings jwtSettings = new(); + AppSettings.Bind("JwtSettings", jwtSettings); + var authTime = DateTime.Now; var expiresAt = authTime.AddMinutes(jwtSettings.Expire); var tokenHandler = new JwtSecurityTokenHandler(); @@ -55,7 +58,7 @@ namespace ZR.Admin.WebApi.Framework IssuedAt = authTime,//token生成时间 Expires = expiresAt, //NotBefore = authTime, - TokenType = "Bearer", + TokenType = jwtSettings.TokenType, //对称秘钥,签名证书 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; @@ -96,7 +99,7 @@ namespace ZR.Admin.WebApi.Framework /// /// 令牌 /// - public static IEnumerable? ParseToken(string token) + public static JwtSecurityToken? ParseToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var validateParameter = ValidParameters(); @@ -105,8 +108,7 @@ namespace ZR.Admin.WebApi.Framework { tokenHandler.ValidateToken(token, validateParameter, out SecurityToken validatedToken); - var jwtToken = tokenHandler.ReadJwtToken(token); - return jwtToken.Claims; + return tokenHandler.ReadJwtToken(token); } catch (Exception ex) { @@ -119,25 +121,21 @@ namespace ZR.Admin.WebApi.Framework /// /// jwt token校验 /// - /// + /// /// - public static LoginUser? ValidateJwtToken(IEnumerable jwtToken) + public static TokenModel? ValidateJwtToken(JwtSecurityToken jwtSecurityToken) { try { - LoginUser loginUser = null; - - var userData = jwtToken.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value; + if (jwtSecurityToken == null) return null; + IEnumerable claims = jwtSecurityToken?.Claims; + TokenModel loginUser = null; + + var userData = claims.FirstOrDefault(x => x.Type == ClaimTypes.UserData)?.Value; if (userData != null) { - loginUser = JsonConvert.DeserializeObject(userData); - var permissions = CacheService.GetUserPerms(GlobalConstant.UserPermKEY + loginUser?.UserId); - if (loginUser?.UserName == GlobalConstant.AdminRole) - { - permissions = new List() { GlobalConstant.AdminPerm }; - } - if (permissions == null) return null; - loginUser.Permissions = permissions; + loginUser = JsonConvert.DeserializeObject(userData); + loginUser.ExpireTime = jwtSecurityToken.ValidTo; } return loginUser; } @@ -153,7 +151,7 @@ namespace ZR.Admin.WebApi.Framework /// /// /// - public static List AddClaims(LoginUser user) + public static List AddClaims(TokenModel user) { var claims = new List() { diff --git a/Infrastructure/Log.cs b/Infrastructure/Log.cs new file mode 100644 index 0000000..309b544 --- /dev/null +++ b/Infrastructure/Log.cs @@ -0,0 +1,14 @@ +using System; + +namespace Infrastructure +{ + public class Log + { + public static void WriteLine(ConsoleColor color = ConsoleColor.Black, string msg = "") + { + Console.ForegroundColor = color; + Console.WriteLine($"{DateTime.Now} {msg}"); + Console.ResetColor(); + } + } +} diff --git a/Infrastructure/Model/ApiResult.cs b/Infrastructure/Model/ApiResult.cs index cc72c73..eb675cc 100644 --- a/Infrastructure/Model/ApiResult.cs +++ b/Infrastructure/Model/ApiResult.cs @@ -1,17 +1,26 @@ using Infrastructure.Constant; using Newtonsoft.Json; +using System.Collections.Generic; namespace Infrastructure.Model { - public class ApiResult + public class ApiResult : Dictionary { - public int Code { get; set; } - public string Msg { get; set; } + /** 状态码 */ + public static readonly string CODE_TAG = "code"; + + /** 返回内容 */ + public static readonly string MSG_TAG = "msg"; + + /** 数据对象 */ + public static readonly string DATA_TAG = "data"; + //public int Code { get; set; } + //public string Msg { get; set; } /// /// 如果data值为null,则忽略序列化将不会返回data字段 /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public object Data { get; set; } + //[JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + //public object Data { get; set; } /// /// 初始化一个新创建的APIResult对象,使其表示一个空消息 @@ -27,8 +36,8 @@ namespace Infrastructure.Model /// public ApiResult(int code, string msg) { - Code = code; - Msg = msg; + Add(CODE_TAG, code); + Add(MSG_TAG, msg); } /// @@ -36,33 +45,28 @@ namespace Infrastructure.Model /// /// /// + /// public ApiResult(int code, string msg, object data) { - Code = code; - Msg = msg; + Add(CODE_TAG, code); + Add(MSG_TAG, msg); if (data != null) { - Data = data; + Add(DATA_TAG, data); } } + /// + /// 返回成功消息 + /// + /// < returns > 成功消息 + public static ApiResult Success() { return new ApiResult(HttpStatus.SUCCESS, "success"); } /// /// 返回成功消息 /// - /// - public ApiResult Success() - { - Code = (int)ResultCode.SUCCESS; - Msg = "success"; - return this; - } - - ///// - ///// 返回成功消息 - ///// - ///// 数据对象 - ///// < returns > 成功消息 - //public static ApiResult Success(object data) { return new ApiResult(HttpStatus.SUCCESS, "success", data); } + /// + /// 成功消息 + public static ApiResult Success(object data) { return new ApiResult(HttpStatus.SUCCESS, "success", data); } /// /// 返回成功消息 @@ -79,21 +83,9 @@ namespace Infrastructure.Model /// 成功消息 public static ApiResult Success(string msg, object data) { return new ApiResult(HttpStatus.SUCCESS, msg, data); } - /// - /// 访问被拒 - /// - /// - public ApiResult On401() + public static ApiResult Error(ResultCode code, string msg = "") { - Code = (int)ResultCode.DENY; - Msg = "access denyed"; - return this; - } - public ApiResult Error(ResultCode resultCode, string msg = "") - { - Code = (int)resultCode; - Msg = msg; - return this; + return Error((int)code, msg); } /// @@ -111,9 +103,26 @@ namespace Infrastructure.Model /// public static ApiResult Error(string msg) { return new ApiResult((int)ResultCode.CUSTOM_ERROR, msg); } - public override string ToString() + + /// + /// 是否为成功消息 + /// + /// + public bool IsSuccess() { - return $"msg={Msg},data={Data}"; + return HttpStatus.SUCCESS == (int)this[CODE_TAG]; + } + + /// + /// 方便链式调用 + /// + /// + /// + /// + public ApiResult Put(string key, object value) + { + Add(key, value); + return this; } } diff --git a/Infrastructure/OptionsSetting.cs b/Infrastructure/Model/OptionsSetting.cs similarity index 68% rename from Infrastructure/OptionsSetting.cs rename to Infrastructure/Model/OptionsSetting.cs index 160ced5..552b781 100644 --- a/Infrastructure/OptionsSetting.cs +++ b/Infrastructure/Model/OptionsSetting.cs @@ -1,23 +1,31 @@ - -using System.Collections.Generic; +using System.Collections.Generic; -namespace Infrastructure +namespace Infrastructure.Model { /// /// 获取配置文件POCO实体类 /// public class OptionsSetting { + /// + /// 是否单点登录 + /// + public bool SingleLogin { get; set; } /// /// 是否演示模式 /// public bool DemoMode { get; set; } + /// + /// 初始化db + /// + public bool InitDb { get; set; } public MailOptions MailOptions { get; set; } public Upload Upload { get; set; } public ALIYUN_OSS ALIYUN_OSS { get; set; } public JwtSettings JwtSettings { get; set; } public Gen Gen { get; set; } public List DbConfigs { get; set; } + public DbConfigs CodeGenDbConfig { get; set; } } /// /// 发送邮件数据配置 @@ -76,11 +84,24 @@ namespace Infrastructure /// token时间(分) /// public int Expire { get; set; } = 1440; + /// + /// 刷新token时长 + /// + public int RefreshTokenTime { get; set; } + /// + /// token类型 + /// + public string TokenType { get; set; } = "Bearer"; } public class Gen { - public string Database { get; set; } + public bool ShowApp { get; set; } + public bool AutoPre { get; set; } + public string VuePath { get; set; } + public string Author { get; set; } + public DbConfigs GenDbConfig { get; set; } + public CsharpTypeArr CsharpTypeArr { get; set; } } public class DbConfigs @@ -89,10 +110,17 @@ namespace Infrastructure public int DbType { get; set; } public string ConfigId { get; set; } public bool IsAutoCloseConnection { get; set; } - /// - /// 是否代码生成使用库 - /// - public bool IsGenerateDb { get; set; } public string DbName { get; set; } } + + public class CsharpTypeArr + { + public string[] String { get; set; } + public string[] Int { get; set; } + public string[] Long { get; set; } + public string[] DateTime { get; set; } + public string[] Float { get; set; } + public string[] Decimal { get; set; } + public string[] Bool { get; set; } + } } diff --git a/Infrastructure/Model/TokenModel.cs b/Infrastructure/Model/TokenModel.cs new file mode 100644 index 0000000..bdfd86a --- /dev/null +++ b/Infrastructure/Model/TokenModel.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Infrastructure.Model +{ + public class TokenModel + { + public long UserId { get; set; } + public long DeptId { get; set; } + public string UserName { get; set; } + /// + /// 角色集合 + /// + public List RoleIds { get; set; } + /// + /// 角色集合(数据权限过滤使用) + /// + public List Roles { get; set; } + /// + /// Jwt过期时间 + /// + public DateTime ExpireTime { get; set; } + /// + /// 权限集合 + /// + //public List Permissions { get; set; } = new List(); + public TokenModel() + { + } + + public TokenModel(TokenModel info, List roles) + { + UserId = info.UserId; + UserName = info.UserName; + DeptId = info.DeptId; + Roles = roles; + RoleIds = roles.Select(f => f.RoleKey).ToList(); + } + } + + public class Roles + { + public long RoleId { get; set; } + public string RoleKey { get; set; } + public int DataScope { get; set; } + } +} diff --git a/ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs b/Infrastructure/WebExtensions/AppServiceExtensions.cs similarity index 94% rename from ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs rename to Infrastructure/WebExtensions/AppServiceExtensions.cs index 466f372..ce694fa 100644 --- a/ZR.Admin.WebApi/Extensions/AppServiceExtensions.cs +++ b/Infrastructure/WebExtensions/AppServiceExtensions.cs @@ -1,8 +1,10 @@ -using Infrastructure; -using Infrastructure.Attribute; +using Infrastructure.Attribute; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; using System.Reflection; -namespace ZR.Admin.WebApi.Extensions +namespace Infrastructure { /// /// App服务注册 diff --git a/ZR.Admin.WebApi/Extensions/CorsExtension.cs b/Infrastructure/WebExtensions/CorsExtension.cs similarity index 73% rename from ZR.Admin.WebApi/Extensions/CorsExtension.cs rename to Infrastructure/WebExtensions/CorsExtension.cs index 1240d80..37fc224 100644 --- a/ZR.Admin.WebApi/Extensions/CorsExtension.cs +++ b/Infrastructure/WebExtensions/CorsExtension.cs @@ -1,5 +1,12 @@ -namespace ZR.Admin.WebApi.Extensions +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Infrastructure { + /// + /// 跨域扩展 + /// public static class CorsExtension { /// @@ -9,7 +16,7 @@ /// public static void AddCors(this IServiceCollection services, IConfiguration configuration) { - var corsUrls = configuration["corsUrls"]?.Split(',', StringSplitOptions.RemoveEmptyEntries); + var corsUrls = configuration.GetSection("corsUrls").Get(); //配置跨域 services.AddCors(c => diff --git a/Infrastructure/WebExtensions/EntityExtension.cs b/Infrastructure/WebExtensions/EntityExtension.cs new file mode 100644 index 0000000..3022abf --- /dev/null +++ b/Infrastructure/WebExtensions/EntityExtension.cs @@ -0,0 +1,41 @@ + +using Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; +using System; +using System.Reflection; + +namespace Infrastructure +{ + public static class EntityExtension + { + public static TSource ToCreate(this TSource source, HttpContext? context = null) + { + var types = source?.GetType(); + if (types == null || context == null) return source; + BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance; + + types.GetProperty("CreateTime", flag)?.SetValue(source, DateTime.Now, null); + types.GetProperty("AddTime", flag)?.SetValue(source, DateTime.Now, null); + types.GetProperty("CreateBy", flag)?.SetValue(source, context.GetName(), null); + types.GetProperty("Create_by", flag)?.SetValue(source, context.GetName(), null); + types.GetProperty("UserId", flag)?.SetValue(source, context.GetUId(), null); + + return source; + } + + public static TSource ToUpdate(this TSource source, HttpContext? context = null) + { + var types = source?.GetType(); + if (types == null || context == null) return source; + BindingFlags flag = BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance; + + types.GetProperty("UpdateTime", flag)?.SetValue(source, DateTime.Now, null); + types.GetProperty("Update_time", flag)?.SetValue(source, DateTime.Now, null); + types.GetProperty("UpdateBy", flag)?.SetValue(source, context.GetName(), null); + types.GetProperty("Update_by", flag)?.SetValue(source, context.GetName(), null); + + return source; + } + + } +} diff --git a/ZR.Admin.WebApi/Extensions/HttpContextExtension.cs b/Infrastructure/WebExtensions/HttpContextExtension.cs similarity index 70% rename from ZR.Admin.WebApi/Extensions/HttpContextExtension.cs rename to Infrastructure/WebExtensions/HttpContextExtension.cs index 8940c00..23efa03 100644 --- a/ZR.Admin.WebApi/Extensions/HttpContextExtension.cs +++ b/Infrastructure/WebExtensions/HttpContextExtension.cs @@ -1,12 +1,15 @@ -using Infrastructure; -using Infrastructure.Extensions; +using IPTools.Core; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Security.Claims; using System.Text; using System.Text.RegularExpressions; using UAParser; -using ZR.Model.System; -namespace ZR.Admin.WebApi.Extensions +namespace Infrastructure.Extensions { /// /// HttpContext扩展类 @@ -50,7 +53,7 @@ namespace ZR.Admin.WebApi.Extensions if (result.Contains("::1")) result = "127.0.0.1"; - result = result.Replace("::ffff:", "127.0.0.1"); + result = result.Replace("::ffff:", ""); result = result.Split(':')?.FirstOrDefault() ?? "127.0.0.1"; result = IsIP(result) ? result : "127.0.0.1"; return result; @@ -82,7 +85,7 @@ namespace ZR.Admin.WebApi.Extensions /// /// /// - public static string? GetName(this HttpContext context) + public static string GetName(this HttpContext context) { var uid = context.User?.Identity?.Name; @@ -105,7 +108,7 @@ namespace ZR.Admin.WebApi.Extensions /// /// /// - public static IEnumerable? GetClaims(this HttpContext context) + public static IEnumerable GetClaims(this HttpContext context) { return context.User?.Identities; } @@ -131,6 +134,55 @@ namespace ZR.Admin.WebApi.Extensions return context.Request.Headers["Authorization"]; } + /// + /// 获取请求Url + /// + /// + /// + public static string GetRequestUrl(this HttpContext context) + { + return context != null ? context.Request.Path.Value : ""; + } + + /// + /// 获取请求参数 + /// + /// + /// + public static string GetQueryString(this HttpContext context) + { + return context != null ? context.Request.QueryString.Value : ""; + } + + /// + /// 获取body请求参数 + /// + /// + /// + public static string GetBody(this HttpContext context) + { + context.Request.EnableBuffering(); + //context.Request.Body.Seek(0, SeekOrigin.Begin); + //using var reader = new StreamReader(context.Request.Body, Encoding.UTF8); + ////需要使用异步方式才能获取 + //return reader.ReadToEndAsync().Result; + string body = string.Empty; + var buffer = new MemoryStream(); + context.Request.Body.Seek(0, SeekOrigin.Begin); + context.Request.Body.CopyToAsync(buffer); + buffer.Position = 0; + try + { + using StreamReader streamReader = new(buffer, Encoding.UTF8); + body = streamReader.ReadToEndAsync().Result; + } + finally + { + buffer?.Dispose(); + } + return body; + } + /// /// 获取浏览器信息 /// @@ -146,55 +198,36 @@ namespace ZR.Admin.WebApi.Extensions } /// - /// 获取请求Url + /// 根据IP获取地理位置 /// - /// /// - public static string? GetRequestUrl(this HttpContext context) + public static string GetIpInfo(string IP) { - return context != null ? context.Request.Path.Value : ""; + var ipInfo = IpTool.Search(IP); + return ipInfo?.Province + "-" + ipInfo?.City + "-" + ipInfo?.NetworkOperator; } - /// - /// 获取请求参数 - /// - /// - /// - public static string GetQueryString(this HttpContext context) - { - return context != null ? context.Request.QueryString.Value : ""; - } /// /// 设置请求参数 /// - /// + /// /// - public static void GetRequestValue(this HttpContext context, SysOperLog operLog) + public static string GetRequestValue(this HttpContext context, string reqMethod) { - string reqMethod = operLog.RequestMethod; - string param; + string param = string.Empty; if (HttpMethods.IsPost(reqMethod) || HttpMethods.IsPut(reqMethod) || HttpMethods.IsDelete(reqMethod)) { - context.Request.Body.Seek(0, SeekOrigin.Begin); - using var reader = new StreamReader(context.Request.Body, Encoding.UTF8); - //需要使用异步方式才能获取 - param = reader.ReadToEndAsync().Result; - if (param.IsEmpty()) - { - param = context.GetQueryString(); - } - param = PwdRep().Replace(param, "***"); + param = context.GetBody(); + string regex = "(?<=\"password\":\")[^\",]*"; + param = Regex.Replace(param, regex, "***"); } - else + if (param.IsEmpty()) { param = context.GetQueryString(); } - operLog.OperParam = param; + return param; } - - [GeneratedRegex("(?<=\"password\":\")[^\",]*")] - private static partial Regex PwdRep(); } } diff --git a/ZR.Admin.WebApi/Extensions/IPRateExtension.cs b/Infrastructure/WebExtensions/IPRateExtension.cs similarity index 88% rename from ZR.Admin.WebApi/Extensions/IPRateExtension.cs rename to Infrastructure/WebExtensions/IPRateExtension.cs index 8fabf81..2eccadf 100644 --- a/ZR.Admin.WebApi/Extensions/IPRateExtension.cs +++ b/Infrastructure/WebExtensions/IPRateExtension.cs @@ -1,6 +1,9 @@ using AspNetCoreRateLimit; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; -namespace ZR.Admin.WebApi.Extensions +namespace ZR.Infrastructure.WebExtensions { public static class IPRateExtension { diff --git a/Infrastructure/WebExtensions/JwtExtension.cs b/Infrastructure/WebExtensions/JwtExtension.cs new file mode 100644 index 0000000..bd30ca1 --- /dev/null +++ b/Infrastructure/WebExtensions/JwtExtension.cs @@ -0,0 +1,39 @@ +using Infrastructure; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Threading.Tasks; + +namespace ZR.Infrastructure.WebExtensions +{ + public static class JwtExtension + { + public static void AddJwt(this IServiceCollection services) + { + services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }).AddCookie() + .AddJwtBearer(o => + { + o.TokenValidationParameters = JwtUtil.ValidParameters(); + o.Events = new JwtBearerEvents + { + OnAuthenticationFailed = context => + { + // 如果过期,把过期信息添加到头部 + if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) + { + Console.WriteLine("jwt过期了"); + context.Response.Headers.Add("Token-Expired", "true"); + } + + return Task.CompletedTask; + }, + }; + }); + } + } +} diff --git a/Infrastructure/WebExtensions/LogoExtension.cs b/Infrastructure/WebExtensions/LogoExtension.cs new file mode 100644 index 0000000..52252a7 --- /dev/null +++ b/Infrastructure/WebExtensions/LogoExtension.cs @@ -0,0 +1,25 @@ +using Infrastructure.Helper; +using JinianNet.JNTemplate; +using Microsoft.Extensions.DependencyInjection; +using System; + +namespace Infrastructure +{ + public static class LogoExtension + { + public static void AddLogo(this IServiceCollection services) + { + Console.ForegroundColor = ConsoleColor.Blue; + var contentTpl = JnHelper.ReadTemplate("", "logo.txt"); + var content = contentTpl?.Render(); + var context = App.HttpContext; + Console.WriteLine(content); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine("🎉源码地址: https://gitee.com/izory/ZrAdminNetCore"); + Console.WriteLine("📖官方文档:http://www.izhaorui.cn/doc"); + Console.WriteLine("💰打赏作者:http://www.izhaorui.cn/doc/support.html"); + Console.WriteLine("📱移动端体验:http://www.izhaorui.cn/h5"); + Console.WriteLine($"Swagger地址:[后端启动地址]/swagger/index.html"); + } + } +} diff --git a/Infrastructure/ZR.Infrastructure.csproj b/Infrastructure/ZR.Infrastructure.csproj new file mode 100644 index 0000000..f96d4e0 --- /dev/null +++ b/Infrastructure/ZR.Infrastructure.csproj @@ -0,0 +1,26 @@ + + + net7.0 + + + 8632 + + + + + + + + + + + + + + + + + + + + diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..1582c2a --- /dev/null +++ b/README.en.md @@ -0,0 +1,199 @@ +

ZR.Admin.NET Back-end management system

+

base .Net7 + vue2.x/vue3.x/uniapp Front-end and back-end separation of .NET rapid development framework

+ +

+ +fork + + +

+ +--- + + + +--- + +## 🍟 overview + +- This project is suitable for developers with some NetCore and vue foundation + -Based on. NET5/. A common rights management platform (RBAC model) implemented by NET7. Integrate the latest technology for efficient and rapid development, front-end and back-end separation mode, out of the box. +- Less code, simple to learn, easy to understand, powerful, easy to extend, lightweight, make web development faster, simpler and more efficient (say goodbye to 996), solve 70% of repetitive work, focus on your business, easy development from now on! +- 提供了技术栈(Ant Design Vue)版[Ant Design Vue](https://gitee.com/billzh/mc-dull.git) + +``` +If it helps you, you can click "Star" in the upper right corner to collect it, so that the author has the motivation to continue to go on for free, thank you! ~ +``` + +## 🍿 Online experience + +- Official documentation:http://www.izhaorui.cn/doc +- Join a group chat:[立即加入](http://www.izhaorui.cn/doc/contact.html) +- Vue3.x experience:[http://www.izhaorui.cn/vue3](http://www.izhaorui.cn/vue3) +- Vue2.x experience:[http://www.izhaorui.cn/admin](http://www.izhaorui.cn/admin) +- Uniapp experience:[http://www.izhaorui.cn/h5](http://www.izhaorui.cn/h5) +- account/password:admin/123456 + +| H5 | WeChat mini program | +| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcode.jpg) | + +``` +Since it is a personal project, the funds are limited, and the experience server is low-fied, please cherish it, poke it lightly, and appreciate it!! +``` + +## 💒 Code repository + +| repository | Github | Gitee | +| -------------- | ------------------------------------------------------ | ---------------------------------------------------------------- | +| Net7 | [Clone/Download](https://github.com/izhaorui/Zr.Admin.NET) | [Clone/Download](https://gitee.com/izory/ZrAdminNetCore) | +| Vue3(Hot) | [Clone/Download](https://github.com/izhaorui/ZR.Admin.Vue3) | [Clone/Download](https://gitee.com/izory/ZRAdmin-vue) | + +## 🍁 Front-end technology + +Vue Front-end technology stack: Based on Vue2.x/Vue3.x/UniApp, Vue, Vue-router, Vue-CLI, AXIOS, Element-UI, Echats, i18N Internationalization, etc., the front-end adopts VSCODE tool development + +## 🍀 Back-end technology + +- Core Framework: . Net7.0 + Web API + sqlsugar + swagger + signalR + IpRateLimit + Quartz.net + Redis +- Scheduled tasks: Quartz.Net component that supports the execution of assemblies or HTTP network requests +- Security support: filters (data permission filtering), SQL injection, request forgery +- Log management: NLog, login log, operation log, scheduled task log +- Tools: Captcha, rich public functions +- Interface throttling: Supports interface throttling to avoid excessive pressure on the service layer caused by malicious requests +- Code generation: efficient development, the code generator can generate all front-end and back-end code with one click +- Data dictionary: Support data dictionary, which can facilitate the management of some states +- Sharding and sharding: Using ORM SQLSUGAR, you can easily achieve superior sharding and sharding performance +- Multi-tenant: Support multi-tenancy function +- Cache data: Built-in memory cache and Redis + +## 🍖 Built-in features + +1. User management: The user is the system operator, and this function mainly completes the system user configuration. +2. Department management: configure the system organization (company, department, group), tree structure display. +3. Job management: configure the position of the system user. +4. Menu management: configure system menus, operation permissions, button permission identification, etc. +5. Role Management: Role menu permission assignment. +6. Dictionary management: maintain some relatively fixed data that is often used in the system. +7. Operation log: system normal operation log records and queries; System exception information logging and querying. +8. Logon logon: The system logon log record query contains logon exceptions. +9. System Interface: Use Swagger to generate relevant API interface documentation. +10. Service monitoring: Monitor the current system CPU, memory, disk, stack, and other related information. +11. Online Builder: Drag form elements to generate the corresponding VUE code (only VUE2 supported). +12. Task system: Based on the Quartz.NET, you can schedule tasks online (add, modify, delete, manually execute) including execution result logs. +13. Article management: You can write article records. +14. Code generation: You can generate front-end and back-end code (.cs, .vue, .js, .sql, etc.) with one click, support download, customize the configuration of front-end display controls, and make development faster and more efficient (the strongest in history). +15. Parameter management: dynamically configure common parameters for the system. +16. Send Mail: You can send mail to multiple users. +17. File management: You can manage uploaded files, which currently supports uploading to on-premises and Alibaba Cloud. +18. Notification management: The system notifies and announces information release and maintenance, and uses SignalR to realize real-time notification to users. +19. Account Registration: You can register an account to log in to the system. +20. Multi-language management: support static and back-end dynamic configuration internationalization. Currently only supports Chinese, English, and Traditional characters (only VUE3 is supported) + +## 🍻 Project structure + +``` +├─ZR.Service ->[你的业务服务层类库]:提供WebApi接口调用; +├─ZR.ServiceCore ->[系统服务层类库]:提供WebApi接口调用; +├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作; +├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象; +├─ZR.Admin.WebApi ->[webapi接口]:为Vue版或其他三方系统提供接口服务。 +├─ZR.Tasks ->[定时任务类库]:提供项目定时任务实现功能; +├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。 +├─ZR.Vue ->[前端UI]:vue2.0版本UI层(已经不再更新推荐使用vue3)。 +├─document ->[文档]:数据库脚本 +``` + +## 🍎 Storyplate + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +## Mobile Storyplate + + + + + + + + + + + + + + + + + + + + + + +
+ +## 🎉 Advantages + +1. The front-end system does not need to write login, authorization, and authentication modules; Just write the business module +2. The background system can be used directly after release without any secondary development +3. The front-end and back-end systems are separated, and they are separate systems (domain names can be independent) +4. Unified handling of global exceptions +5. Custom code generation features +6. Less dependence, easy to get started +7. Comprehensive documentation + +## 💐 Special thanks + +- 👉Ruoyi.vue:[Ruoyi](http://www.ruoyi.vip/) +- 👉SqlSugar:[SqlSugar](https://gitee.com/dotnetchina/SqlSugar) +- 👉vue-element-admin:[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) +- 👉Meiam.System:[Meiam.System](https://github.com/91270/Meiam.System) + +## 🎀 donation + +If you feel that the project has helped you, you can ask the author for a cup of coffee as a sign of encouragement ☕️ + diff --git a/README.md b/README.md index b7afa87..9671147 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,21 @@

ZR.Admin.NET后台管理系统

-

基于.NET5/.Net7 + vue2.x/vue3.x/uniapp前后端分离的.net快速开发框架

+

基于.Net7 + vue2.x/vue3.x/uniapp前后端分离的.net快速开发框架

fork - +

+--- + + + +--- + ## 🍟 概述 - 本项目适合有一定 NetCore 和 vue 基础的开发人员 @@ -20,7 +28,7 @@ - 阿里云特惠专区:[☛☛ 点我进入 ☚☚](https://www.aliyun.com/minisite/goods?userCode=uotn5vt1&share_source=copy_link) ``` -如果对您有帮助,您可以点右上角 “Star” 收藏一下 ,这样作者才有继续免费下去的动力,谢谢!~ +如果对您有帮助,您可以点右上角 “Star” 收藏一下 ,谢谢!~ ``` ## 🍿 在线体验 @@ -30,26 +38,26 @@ - Vue3.x 版本体验:[http://www.izhaorui.cn/vue3](http://www.izhaorui.cn/vue3) - Vue2.x 版本体验:[http://www.izhaorui.cn/admin](http://www.izhaorui.cn/admin) - Uniapp 版本体验:[http://www.izhaorui.cn/h5](http://www.izhaorui.cn/h5) -- 账号密码:admin/123456 +- 账号密码:admin/123456,普通用户 user/123456 | H5 | 微信小程序 | | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/net7.0/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/net7.0/document/images/qrcode.jpg) | +| ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcodeH5.png) | ![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/qrcode.jpg) | ``` -由于是个人项目,资金有限,体验服是低配,请大家爱惜,轻戳,不胜感激!!! +由于是个人项目,资金有限,体验服务器是低配,请大家爱惜,轻戳,不胜感激!!! ``` ## 💒 代码仓库 -| 仓库 | Github | Gitee | -| ---- | ------------------------------------------------------ | ---------------------------------------------------------------- | -| Net7 | [克隆/下载](https://github.com/izhaorui/Zr.Admin.NET) | [克隆/下载](https://gitee.com/izory/ZrAdminNetCore/tree/net7.0/) | -| Vue3 | [克隆/下载](https://github.com/izhaorui/ZR.Admin.Vue3) | [克隆/下载](https://gitee.com/izory/ZRAdmin-vue) | +| 仓库 | Github | Gitee | +| -------------- | ------------------------------------------------------ | --------------------------------------------------- | +| Net7 | [克隆/下载](https://github.com/izhaorui/Zr.Admin.NET) | [克隆/下载](https://gitee.com/izory/ZrAdminNetCore) | +| Vue3(推荐使用) | [克隆/下载](https://github.com/izhaorui/ZR.Admin.Vue3) | [克隆/下载](https://gitee.com/izory/ZRAdmin-vue) | ## 🍁 前端技术 -Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli 、axios、 element-ui、echats、i18n 国际化等,前端采用 vscode 工具开发 +Vue 版前端技术栈 :基于 vue2.x/vue3.x/uniapp、vuex、vue-router 、vue-cli 、axios、 element-ui、echats、i18n 国际化等,前端采用 vscode 工具开发 ## 🍀 后端技术 @@ -64,6 +72,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli - 分库分表:使用 orm sqlsugar 可以很轻松的实现分库分库性能优越 - 多 租 户:支持多租户功能 - 缓存数据:内置内存缓存和 Redis +- signalR:使用 signalr 管理用户在线状态 ## 🍖 内置功能 @@ -80,24 +89,30 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli 11. 在线构建器:拖动表单元素生成相应的 VUE 代码(仅支持 vue2)。 12. 任务系统:基于 Quartz.NET,可以在线(添加、修改、删除、手动执行)任务调度包含执行结果日志。 13. 文章管理:可以写文章记录。 -14. 代码生成:可以一键生成前后端代码(.cs、.vue、.js、.sql 等)支持下载,自定义配置前端展示控件、让开发更快捷高效(史上最强)。 +14. 代码生成:可以一键生成前后端代码(.cs、.vue、.js、.sql、uniapp 等)支持下载,自定义配置前端展示控件、让开发更快捷高效(史上最强)。 15. 参数管理:对系统动态配置常用参数。 16. 发送邮件:可以对多个用户进行发送邮件。 17. 文件管理:可以进行上传文件管理,目前支持上传到本地、阿里云。 18. 通知管理:系统通知公告信息发布维护,使用 signalr 实现对用户实时通知。 19. 账号注册:可以注册账号登录系统。 20. 多语言管理:支持静态、后端动态配置国际化。目前只支持中、英、繁体(仅支持 vue3) +21. 在线用户:可以查看正在登录使用的用户,可以对其踢出、通知操作 +22. db 审计日志:数据库审计功能 +23. 三方登录:提供三方登录实现逻辑 ## 🍻 项目结构 +![alt](https://gitee.com/izory/ZrAdminNetCore/raw/master/document/images/kj.png) + ``` -├─ZR.Service ->[服务层类库]:提供WebApi接口调用; -├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作; -├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象; -├─ZR.Admin.WebApi ->[webapi接口]:为Vue版或其他三方系统提供接口服务。 +├─ZR.Service ->[你的业务服务层类库]:提供WebApi接口调用; +├─ZR.ServiceCore ->[系统服务层类库]:提供WebApi接口调用; +├─ZR.Repository ->[仓库层类库]:方便提供有执行存储过程的操作; +├─ZR.Model ->[实体层类库]:提供项目中的数据库表、数据传输对象; +├─ZR.Admin.WebApi ->[webapi接口]:为Vue版或其他三方系统提供接口服务。 ├─ZR.Tasks ->[定时任务类库]:提供项目定时任务实现功能; -├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。 -├─ZR.Vue ->[前端UI]:vue2.0版本UI层。 +├─ZR.CodeGenerator ->[代码生成功能]:包含代码生成的模板、方法、代码生成的下载。 +├─ZR.Vue ->[前端UI]:vue2.0版本UI层(已经不再更新推荐使用vue3)。 ├─document ->[文档]:数据库脚本 ``` @@ -138,12 +153,15 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli - + + + + ## 移动端演示图 @@ -160,6 +178,13 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli + + + + + + + @@ -173,6 +198,7 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli 5. 自定义的代码生成功能 6. 依赖少,上手容易 7. 文档全面 +8. 支持中文表头导入数据 ## 💐 特别鸣谢 @@ -180,8 +206,9 @@ Vue 版前端技术栈 :基于 vue2.x/vue3.x、vuex、vue-router 、vue-cli - 👉SqlSugar:[SqlSugar](https://gitee.com/dotnetchina/SqlSugar) - 👉vue-element-admin:[vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) - 👉Meiam.System:[Meiam.System](https://github.com/91270/Meiam.System) +- 👉Furion:[Furion](https://gitee.com/dotnetchina/Furion) ## 🎀 捐赠 如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励 ☕️ - + diff --git a/ZR.Admin.WebApi/Controllers/CommonController.cs b/ZR.Admin.WebApi/Controllers/CommonController.cs index 20590bc..b568eb6 100644 --- a/ZR.Admin.WebApi/Controllers/CommonController.cs +++ b/ZR.Admin.WebApi/Controllers/CommonController.cs @@ -1,15 +1,9 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Extensions; -using Infrastructure.Model; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Newtonsoft.Json; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model.System; +using ZR.Service.IService; using ZR.Service.System; using ZR.Service.System.IService; @@ -19,6 +13,8 @@ namespace ZR.Admin.WebApi.Controllers /// 公共模块 ///
[Route("[controller]/[action]")] + [ApiExplorerSettings(GroupName = "sys")] + //[Produces("application/json")] public class CommonController : BaseController { private OptionsSetting OptionsSetting; @@ -26,18 +22,22 @@ namespace ZR.Admin.WebApi.Controllers private IWebHostEnvironment WebHostEnvironment; private ISysFileService SysFileService; + private IHelloService HelloService; + public CommonController( IOptions options, IWebHostEnvironment webHostEnvironment, - ISysFileService fileService) + ISysFileService fileService, + IHelloService helloService) { WebHostEnvironment = webHostEnvironment; SysFileService = fileService; OptionsSetting = options.Value; + HelloService = helloService; } /// - /// hello + /// home /// /// [Route("/")] @@ -48,6 +48,18 @@ namespace ZR.Admin.WebApi.Controllers "如果觉得项目有用,打赏作者喝杯咖啡作为奖励\n☛☛http://www.izhaorui.cn/doc/support.html\n"); } + /// + /// hello + /// + /// + /// + [Route("/hello")] + [HttpGet] + public IActionResult Hello(string name) + { + return Ok(HelloService.SayHello(name)); + } + /// /// 企业消息测试 /// diff --git a/ZR.Admin.WebApi/Controllers/System/ArticleCategoryController.cs b/ZR.Admin.WebApi/Controllers/System/ArticleCategoryController.cs index 75fd135..5a81d8a 100644 --- a/ZR.Admin.WebApi/Controllers/System/ArticleCategoryController.cs +++ b/ZR.Admin.WebApi/Controllers/System/ArticleCategoryController.cs @@ -1,15 +1,9 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; using Microsoft.AspNetCore.Mvc; -using ZR.Model.Dto; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; -using ZR.Service.System.IService; +using ZR.Model.Dto; using ZR.Model.System; +using ZR.Service.System.IService; namespace ZR.Admin.WebApi.Controllers { @@ -17,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers /// 文章目录Controller ///
[Route("article/ArticleCategory")] + [ApiExplorerSettings(GroupName = "article")] public class ArticleCategoryController : BaseController { /// @@ -65,7 +60,7 @@ namespace ZR.Admin.WebApi.Controllers public IActionResult GetArticleCategory(int CategoryId) { var response = _ArticleCategoryService.GetFirst(x => x.CategoryId == CategoryId); - + return SUCCESS(response); } diff --git a/ZR.Admin.WebApi/Controllers/System/ArticleController.cs b/ZR.Admin.WebApi/Controllers/System/ArticleController.cs index c9a67d1..b111ace 100644 --- a/ZR.Admin.WebApi/Controllers/System/ArticleController.cs +++ b/ZR.Admin.WebApi/Controllers/System/ArticleController.cs @@ -1,8 +1,4 @@ -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Mapster; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using SqlSugar; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; @@ -17,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers /// [Verify] [Route("article")] + [ApiExplorerSettings(GroupName = "article")] public class ArticleController : BaseController { /// @@ -36,7 +33,7 @@ namespace ZR.Admin.WebApi.Controllers /// /// [HttpGet("list")] - [ActionPermissionFilter(Permission = "system:article:list")] + [ActionPermissionFilter(RolePermi = "admin")] public IActionResult Query([FromQuery] ArticleQueryDto parm) { var response = _ArticleService.GetList(parm); @@ -91,7 +88,7 @@ namespace ZR.Admin.WebApi.Controllers var model = response.Adapt(); if (model.IsPublic == 0 && userId != model.UserId) { - return ToResponse(Infrastructure.ResultCode.CUSTOM_ERROR, "访问失败"); + return ToResponse(ResultCode.CUSTOM_ERROR, "访问失败"); } if (model != null) { diff --git a/ZR.Admin.WebApi/Controllers/System/CodeGeneratorController.cs b/ZR.Admin.WebApi/Controllers/System/CodeGeneratorController.cs index dcfe5c7..61fb659 100644 --- a/ZR.Admin.WebApi/Controllers/System/CodeGeneratorController.cs +++ b/ZR.Admin.WebApi/Controllers/System/CodeGeneratorController.cs @@ -1,19 +1,11 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Extensions; -using IP2Region.Ex.Models; -using Mapster; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using SqlSugar; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; using ZR.CodeGenerator; using ZR.CodeGenerator.Model; using ZR.CodeGenerator.Service; -using ZR.Common; using ZR.Model; -using ZR.Model.System; using ZR.Model.System.Dto; using ZR.Model.System.Generate; using ZR.Service.System.IService; @@ -25,6 +17,7 @@ namespace ZR.Admin.WebApi.Controllers ///
[Verify] [Route("tool/gen")] + [ApiExplorerSettings(GroupName = "sys")] public class CodeGeneratorController : BaseController { private readonly CodeGeneraterService _CodeGeneraterService = new CodeGeneraterService(); @@ -153,7 +146,7 @@ namespace ZR.Admin.WebApi.Controllers { throw new CustomException("表不能为空"); } - var dbConfig = AppSettings.Get>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb); + DbConfigs dbConfig = AppSettings.Get(nameof(GlobalConstant.CodeGenDbConfig)); string[] tableNames = tables.Split(',', StringSplitOptions.RemoveEmptyEntries); int result = 0; foreach (var tableName in tableNames) @@ -218,29 +211,25 @@ namespace ZR.Admin.WebApi.Controllers /// /// 预览代码 /// + /// /// - /// /// [HttpPost("preview/{tableId}")] [ActionPermissionFilter(Permission = "tool:gen:preview")] - public IActionResult Preview(long tableId = 0, int VueVersion = 0) + public IActionResult Preview([FromQuery] GenerateDto dto, [FromRoute] int tableId = 0) { - GenerateDto dto = new() - { - TableId = tableId, - VueVersion = VueVersion - }; + dto.TableId = tableId; if (dto == null || dto.TableId <= 0) { throw new CustomException(ResultCode.CUSTOM_ERROR, "请求参数为空"); } var genTableInfo = GenTableService.GetGenTableInfo(dto.TableId); - var dbConfig = AppSettings.Get>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb); + var dbConfig = AppSettings.Get(nameof(GlobalConstant.CodeGenDbConfig)); dto.DbType = dbConfig.DbType; dto.GenTable = genTableInfo; dto.IsPreview = true; - //生成代码 + CodeGeneratorTool.Generate(dto); return SUCCESS(dto.GenCodes); @@ -261,7 +250,7 @@ namespace ZR.Admin.WebApi.Controllers throw new CustomException(ResultCode.CUSTOM_ERROR, "请求参数为空"); } var genTableInfo = GenTableService.GetGenTableInfo(dto.TableId); - var dbConfig = AppSettings.Get>("dbConfigs").FirstOrDefault(f => f.IsGenerateDb); + var dbConfig = AppSettings.Get(nameof(GlobalConstant.CodeGenDbConfig)); dto.DbType = dbConfig.DbType; dto.GenTable = genTableInfo; @@ -284,12 +273,20 @@ namespace ZR.Admin.WebApi.Controllers //生成代码到指定文件夹 CodeGeneratorTool.Generate(dto); - //下载文件 - FileUtil.ZipGenCode(dto.ZipPath, dto.GenCodePath, zipReturnFileName); if (genTableInfo.Options.GenerateMenu) { - SysMenuService.AddSysMenu(genTableInfo, dto.ReplaceDto.PermissionPrefix, dto.ReplaceDto.ShowBtnEdit, dto.ReplaceDto.ShowBtnExport); + SysMenuService.AddSysMenu(genTableInfo, dto.ReplaceDto.PermissionPrefix, dto.ReplaceDto.ShowBtnEdit, dto.ReplaceDto.ShowBtnExport, dto.ReplaceDto.ShowBtnImport); } + + foreach (var item in dto.GenCodes) + { + item.Path = Path.Combine(dto.GenCodePath, item.Path); + FileUtil.WriteAndSave(item.Path, item.Content); + } + + //下载文件 + FileUtil.ZipGenCode(dto.ZipPath, dto.GenCodePath, zipReturnFileName); + return SUCCESS(new { path = "/Generatecode/" + zipReturnFileName, fileName = dto.ZipFileName }); } diff --git a/ZR.Admin.WebApi/Controllers/System/CommonLangController.cs b/ZR.Admin.WebApi/Controllers/System/CommonLangController.cs index eedd9f1..e24cb0e 100644 --- a/ZR.Admin.WebApi/Controllers/System/CommonLangController.cs +++ b/ZR.Admin.WebApi/Controllers/System/CommonLangController.cs @@ -1,15 +1,10 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; using Infrastructure.Extensions; -using Infrastructure.Model; -using Mapster; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MiniExcelLibs; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model; using ZR.Model.Dto; +using ZR.Model.Models; using ZR.Service.System.IService; namespace ZR.Admin.WebApi.Controllers @@ -19,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers ///
[Verify] [Route("system/CommonLang")] + [ApiExplorerSettings(GroupName = "sys")] public class CommonLangController : BaseController { /// @@ -135,6 +131,25 @@ namespace ZR.Admin.WebApi.Controllers return ToResponse(response); } + /// + /// 删除多语言配置 + /// + /// + [HttpDelete("ByKey")] + [ActionPermissionFilter(Permission = "system:lang:delete")] + [Log(Title = "多语言配置", BusinessType = BusinessType.DELETE)] + public IActionResult DeleteCommonLangByKey(string langkey) + { + if (langkey.IsEmpty()) { return ToResponse(ApiResult.Error($"删除失败Id 不能为空")); } + + var response = _CommonLangService + .Deleteable() + .EnableDiffLogEvent() + .Where(f => f.LangKey == langkey) + .ExecuteCommand(); + return ToResponse(response); + } + /// /// 导出多语言配置 /// @@ -145,11 +160,50 @@ namespace ZR.Admin.WebApi.Controllers public IActionResult Export([FromQuery] CommonLangQueryDto parm) { parm.PageSize = 10000; - var list = _CommonLangService.GetList(parm).Result; + var list = _CommonLangService.GetListToPivot(parm); string sFileName = ExportExcel(list, "CommonLang", "多语言配置"); return SUCCESS(new { path = "/export/" + sFileName, fileName = sFileName }); } + /// + /// 导入 + /// + /// + /// + [HttpPost("importData")] + [Log(Title = "多语言设置导入", BusinessType = BusinessType.IMPORT, IsSaveRequestData = false, IsSaveResponseData = true)] + [ActionPermissionFilter(Permission = "system:lang:import")] + public IActionResult ImportData([FromForm(Name = "file")] IFormFile formFile) + { + List list = new(); + var nowTime = DateTime.Now; + using (var stream = formFile.OpenReadStream()) + { + var rows = stream.Query(startCell: "A2").ToList(); + + foreach (var item in rows) + { + list.Add(new CommonLang() { LangCode = "zh-cn", LangKey = item.A, LangName = item.B, Addtime = nowTime }); + list.Add(new CommonLang() { LangCode = "en", LangKey = item.A, LangName = item.C, Addtime = nowTime }); + list.Add(new CommonLang() { LangCode = "zh-tw", LangKey = item.A, LangName = item.D, Addtime = nowTime }); + } + } + + return SUCCESS(_CommonLangService.ImportCommonLang(list)); + } + + /// + /// 多语言设置导入模板下载 + /// + /// + [HttpGet("importTemplate")] + [Log(Title = "多语言设置模板", BusinessType = BusinessType.EXPORT, IsSaveRequestData = true, IsSaveResponseData = false)] + [AllowAnonymous] + public IActionResult ImportTemplateExcel() + { + var result = DownloadImportTemplate(new List() { }, "lang"); + return ExportExcel(result.Item2, result.Item1); + } } } \ No newline at end of file diff --git a/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs b/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs index 1d33ee3..51bae9c 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysConfigController.cs @@ -1,15 +1,8 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; using Infrastructure.Extensions; -using Infrastructure.Model; -using Mapster; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SqlSugar; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model.System; using ZR.Model.System.Dto; using ZR.Service.System.IService; @@ -21,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers /// [Verify] [Route("system/config")] + [ApiExplorerSettings(GroupName = "sys")] public class SysConfigController : BaseController { /// diff --git a/ZR.Admin.WebApi/Controllers/System/SysDeptController.cs b/ZR.Admin.WebApi/Controllers/System/SysDeptController.cs index e762c8b..2b53ac6 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysDeptController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysDeptController.cs @@ -1,12 +1,8 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using System.Collections; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model.System; +using ZR.Model.System.Dto; using ZR.Service.System.IService; namespace ZR.Admin.WebApi.Controllers.System @@ -16,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/dept")] + [ApiExplorerSettings(GroupName = "sys")] public class SysDeptController : BaseController { public ISysDeptService DeptService; @@ -33,7 +30,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [ActionPermissionFilter(Permission = "system:dept:list")] [HttpGet("list")] - public IActionResult List([FromQuery] SysDept dept) + public IActionResult List([FromQuery] SysDeptQueryDto dept) { return SUCCESS(DeptService.GetSysDepts(dept), TIME_FORMAT_FULL); } @@ -46,7 +43,7 @@ namespace ZR.Admin.WebApi.Controllers.System [HttpGet("list/exclude/{deptId}")] public IActionResult ExcludeChild(long deptId) { - var depts = DeptService.GetSysDepts(new SysDept()); + var depts = DeptService.GetSysDepts(new SysDeptQueryDto()); for (int i = 0; i < depts.Count; i++) { @@ -66,7 +63,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// /// [HttpGet("treeselect")] - public IActionResult TreeSelect(SysDept dept) + public IActionResult TreeSelect(SysDeptQueryDto dept) { var depts = DeptService.GetSysDepts(dept); @@ -82,7 +79,7 @@ namespace ZR.Admin.WebApi.Controllers.System [HttpGet("roleDeptTreeselect/{roleId}")] public IActionResult RoleMenuTreeselect(int roleId) { - var depts = DeptService.GetSysDepts(new SysDept()); + var depts = DeptService.GetSysDepts(new SysDeptQueryDto()); var checkedKeys = DeptService.SelectRoleDepts(roleId); return SUCCESS(new { @@ -152,7 +149,7 @@ namespace ZR.Admin.WebApi.Controllers.System [Log(Title = "部门管理", BusinessType = BusinessType.DELETE)] public IActionResult Remove(long deptId) { - if (DeptService.Queryable().Count(it => it.ParentId == deptId && it.DelFlag == "0") > 0) + if (DeptService.Queryable().Count(it => it.ParentId == deptId && it.DelFlag == 0) > 0) { return ToResponse(ResultCode.CUSTOM_ERROR, $"存在下级部门,不允许删除"); } diff --git a/ZR.Admin.WebApi/Controllers/System/SysDictDataController.cs b/ZR.Admin.WebApi/Controllers/System/SysDictDataController.cs index a3cab52..05d7dac 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysDictDataController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysDictDataController.cs @@ -1,8 +1,4 @@ -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using ZR.Admin.WebApi.Extensions; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Filters; using ZR.Model; using ZR.Model.System; @@ -16,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/dict/data")] + [ApiExplorerSettings(GroupName = "sys")] public class SysDictDataController : BaseController { private readonly ISysDictDataService SysDictDataService; diff --git a/ZR.Admin.WebApi/Controllers/System/SysDictTypeController.cs b/ZR.Admin.WebApi/Controllers/System/SysDictTypeController.cs index bdfc22f..a978d22 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysDictTypeController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysDictTypeController.cs @@ -1,11 +1,6 @@ -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model; using ZR.Model.System; using ZR.Model.System.Dto; @@ -18,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/dict/type")] + [ApiExplorerSettings(GroupName = "sys")] public class SysDictTypeController : BaseController { private readonly ISysDictService SysDictService; diff --git a/ZR.Admin.WebApi/Controllers/System/SysFileController.cs b/ZR.Admin.WebApi/Controllers/System/SysFileController.cs index 8900235..b084c21 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysFileController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysFileController.cs @@ -1,13 +1,9 @@ using Microsoft.AspNetCore.Mvc; using SqlSugar; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model.System; -using ZR.Service.System.IService; using ZR.Model.System.Dto; +using ZR.Service.System.IService; namespace ZR.Admin.WebApi.Controllers { @@ -16,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers /// [Verify] [Route("tool/file")] + [ApiExplorerSettings(GroupName = "sys")] public class SysFileController : BaseController { /// @@ -37,15 +34,13 @@ namespace ZR.Admin.WebApi.Controllers [ActionPermissionFilter(Permission = "tool:file:list")] public IActionResult QuerySysFile([FromQuery] SysFileQueryDto parm) { - //开始拼装查询条件 var predicate = Expressionable.Create(); - //搜索条件查询语法参考Sqlsugar + predicate = predicate.AndIF(parm.BeginCreate_time != null, it => it.Create_time >= parm.BeginCreate_time); predicate = predicate.AndIF(parm.EndCreate_time != null, it => it.Create_time <= parm.EndCreate_time); predicate = predicate.AndIF(parm.StoreType != null, m => m.StoreType == parm.StoreType); predicate = predicate.AndIF(parm.FileId != null, m => m.Id == parm.FileId); - //搜索条件查询语法参考Sqlsugar var response = _SysFileService.GetPages(predicate.ToExpression(), parm, x => x.Id, OrderByType.Desc); return SUCCESS(response); } diff --git a/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs b/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs index a4f3658..7e256cf 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysLoginController.cs @@ -1,20 +1,7 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Model; -using IPTools.Core; -using Lazy.Captcha.Core; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; +using Lazy.Captcha.Core; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System; -using System.Collections.Generic; -using NETCore.Encrypt; -using UAParser; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Admin.WebApi.Framework; -using ZR.Common; using ZR.Model.System; using ZR.Model.System.Dto; using ZR.Service.System; @@ -25,9 +12,10 @@ namespace ZR.Admin.WebApi.Controllers.System /// /// 登录 /// + [ApiExplorerSettings(GroupName = "sys")] public class SysLoginController : BaseController { - static readonly NLog.Logger logger = NLog.LogManager.GetLogger("LoginController"); + //static readonly NLog.Logger logger = NLog.LogManager.GetLogger("LoginController"); private readonly IHttpContextAccessor httpContextAccessor; private readonly ISysUserService sysUserService; private readonly ISysMenuService sysMenuService; @@ -36,7 +24,7 @@ namespace ZR.Admin.WebApi.Controllers.System private readonly ICaptcha SecurityCodeHelper; private readonly ISysConfigService sysConfigService; private readonly ISysRoleService roleService; - private readonly OptionsSetting jwtSettings; + private readonly OptionsSetting optionSettings; public SysLoginController( IHttpContextAccessor contextAccessor, @@ -47,7 +35,7 @@ namespace ZR.Admin.WebApi.Controllers.System ISysConfigService configService, ISysRoleService sysRoleService, ICaptcha captcha, - IOptions jwtSettings) + IOptions optionSettings) { httpContextAccessor = contextAccessor; SecurityCodeHelper = captcha; @@ -57,11 +45,9 @@ namespace ZR.Admin.WebApi.Controllers.System this.permissionService = permissionService; this.sysConfigService = configService; roleService = sysRoleService; - this.jwtSettings = jwtSettings.Value; + this.optionSettings = optionSettings.Value; } - - // RSA私钥 - private static string _privateKey; + /// /// 登录 @@ -80,17 +66,18 @@ namespace ZR.Admin.WebApi.Controllers.System { return ToResponse(ResultCode.CAPTCHA_ERROR, "验证码错误"); } - // RSA解密 - loginBody.Password = EncryptProvider.RSADecryptWithPem(_privateKey, loginBody.Password); - var user = sysLoginService.Login(loginBody, RecordLogInfo(httpContextAccessor.HttpContext)); + + sysLoginService.CheckLockUser(loginBody.Username); + string location = HttpContextExtension.GetIpInfo(loginBody.LoginIP); + var user = sysLoginService.Login(loginBody, new SysLogininfor() { LoginLocation = location }); List roles = roleService.SelectUserRoleListByUserId(user.UserId); //权限集合 eg *:*:*,system:user:list List permissions = permissionService.GetMenuPermission(user); - LoginUser loginUser = new(user, roles, permissions); + TokenModel loginUser = new(user.Adapt(), roles.Adapt>()); CacheService.SetUserPerms(GlobalConstant.UserPermKEY + user.UserId, permissions); - return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser), jwtSettings.JwtSettings)); + return SUCCESS(JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(loginUser))); } /// @@ -130,6 +117,7 @@ namespace ZR.Admin.WebApi.Controllers.System //权限集合 eg *:*:*,system:user:list List permissions = permissionService.GetMenuPermission(user); user.WelcomeContent = GlobalConstant.WelcomeMessages[new Random().Next(0, GlobalConstant.WelcomeMessages.Length)]; + return SUCCESS(new { user, roles, permissions }); } @@ -164,28 +152,6 @@ namespace ZR.Admin.WebApi.Controllers.System return SUCCESS(obj); } - /// - /// 记录用户登陆信息 - /// - /// - /// - public SysLogininfor RecordLogInfo(HttpContext context) - { - var ipAddr = context.GetClientUserIp(); - var ip_info = IpTool.Search(ipAddr); - ClientInfo clientInfo = context.GetClientInfo(); - SysLogininfor sysLogininfor = new() - { - Browser = clientInfo.ToString(), - Os = clientInfo.OS.ToString(), - Ipaddr = ipAddr, - UserName = context.GetName(), - LoginLocation = ip_info?.Province + "-" + ip_info?.City - }; - - return sysLogininfor; - } - /// /// 注册 /// @@ -193,7 +159,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [HttpPost("/register")] [AllowAnonymous] - [Log(Title = "注册", BusinessType = Infrastructure.Enums.BusinessType.INSERT)] + [Log(Title = "注册", BusinessType = BusinessType.INSERT)] public IActionResult Register([FromBody] RegisterDto dto) { SysConfig config = sysConfigService.GetSysConfigByKey("sys.account.register"); @@ -206,7 +172,7 @@ namespace ZR.Admin.WebApi.Controllers.System { return ToResponse(ResultCode.CAPTCHA_ERROR, "验证码错误"); } - + SysUser user = sysUserService.Register(dto); if (user.UserId > 0) { @@ -214,19 +180,86 @@ namespace ZR.Admin.WebApi.Controllers.System } return ToResponse(ResultCode.CUSTOM_ERROR, "注册失败,请联系管理员"); } - + + #region 二维码登录 + /// - /// 获取RSA公钥和密钥 + /// 生成二维码 /// + /// + /// /// - [HttpGet("getRsaKey")] - public IActionResult GetRsaKey() + [HttpGet("/GenerateQrcode")] + public IActionResult GenerateQrcode(string uuid, string deviceId) { - var (publicPem, privatePem) = EncryptProvider.RSAToPem(true); - _privateKey = privatePem; - return SUCCESS(new { - PublicKey = publicPem - }); + var state = Guid.NewGuid().ToString(); + var dict = new Dictionary + { + { "state", state } + }; + CacheService.SetScanLogin(uuid, dict); + return SUCCESS(new + { + status = 1, + state, + uuid, + codeContent = new { uuid, deviceId }// "https://qm.qq.com/cgi-bin/qm/qr?k=kgt4HsckdljU0VM-0kxND6d_igmfuPlL&authKey=r55YUbruiKQ5iwC/folG7KLCmZ++Y4rQVgNlvLbUniUMkbk24Y9+zNuOmOnjAjRc&noverify=0" + }); } + + /// + /// 轮询判断扫码状态 + /// + /// + /// + [HttpPost("/VerifyScan")] + [AllowAnonymous] + public IActionResult VerifyScan([FromBody] ScanDto dto) + { + int status = -1; + object token = string.Empty; + if (CacheService.GetScanLogin(dto.Uuid) is Dictionary str) + { + status = 0; + str.TryGetValue("token", out token); + if (str.ContainsKey("status") && (string)str.GetValueOrDefault("status") == "success") + { + status = 2;//扫码成功 + CacheService.RemoveScanLogin(dto.Uuid); + } + } + + return SUCCESS(new { status, token }); + } + + /// + /// 移动端扫码登录 + /// + /// + /// + [HttpPost("/ScanLogin")] + [Log(Title = "扫码登录")] + [Verify] + public IActionResult ScanLogin([FromBody] ScanDto dto) + { + if (dto == null) { return ToResponse(ResultCode.CUSTOM_ERROR, "扫码失败"); } + var name = App.HttpContext.GetName(); + + sysLoginService.CheckLockUser(name); + + TokenModel tokenModel = JwtUtil.GetLoginUser(HttpContext); + if (CacheService.GetScanLogin(dto.Uuid) is not null) + { + Dictionary dict = new() { }; + dict.Add("status", "success"); + dict.Add("token", JwtUtil.GenerateJwtToken(JwtUtil.AddClaims(tokenModel))); + CacheService.SetScanLogin(dto.Uuid, dict); + + return SUCCESS(1); + } + return ToResponse(ResultCode.FAIL, "二维码已失效"); + } + #endregion + } } diff --git a/ZR.Admin.WebApi/Controllers/System/SysMenuController.cs b/ZR.Admin.WebApi/Controllers/System/SysMenuController.cs index ebfe538..a6cff1f 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysMenuController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysMenuController.cs @@ -1,10 +1,4 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; -using Microsoft.AspNetCore.Mvc; -using ZR.Admin.WebApi.Extensions; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Filters; using ZR.Model.System; using ZR.Model.System.Dto; @@ -17,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("/system/menu")] + [ApiExplorerSettings(GroupName = "sys")] public class SysMenuController : BaseController { private readonly ISysRoleService sysRoleService; diff --git a/ZR.Admin.WebApi/Controllers/System/SysNoticeController.cs b/ZR.Admin.WebApi/Controllers/System/SysNoticeController.cs index 1027660..18e304e 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysNoticeController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysNoticeController.cs @@ -1,19 +1,12 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Constant; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using SqlSugar; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Admin.WebApi.Hubs; -using ZR.Common; +using ZR.Model; using ZR.Model.System; using ZR.Model.System.Dto; using ZR.Service.System.IService; +using ZR.ServiceCore.Signalr; namespace ZR.Admin.WebApi.Controllers.System { @@ -22,6 +15,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/notice")] + [ApiExplorerSettings(GroupName = "sys")] public class SysNoticeController : BaseController { /// @@ -58,13 +52,7 @@ namespace ZR.Admin.WebApi.Controllers.System [ActionPermissionFilter(Permission = "system:notice:list")] public IActionResult QuerySysNotice([FromQuery] SysNoticeQueryDto parm) { - var predicate = Expressionable.Create(); - - predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.NoticeTitle), m => m.NoticeTitle.Contains(parm.NoticeTitle)); - predicate = predicate.AndIF(parm.NoticeType != null, m => m.NoticeType == parm.NoticeType); - predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.CreateBy), m => m.Create_by.Contains(parm.CreateBy) || m.Update_by.Contains(parm.CreateBy)); - predicate = predicate.AndIF(parm.Status != null, m => m.Status == parm.Status); - var response = _SysNoticeService.GetPages(predicate.ToExpression(), parm); + PagedInfo response = _SysNoticeService.GetPageList(parm); return SUCCESS(response); } @@ -74,7 +62,6 @@ namespace ZR.Admin.WebApi.Controllers.System /// /// [HttpGet("{NoticeId}")] - [ActionPermissionFilter(Permission = "system:notice:query")] public IActionResult GetSysNotice(int NoticeId) { var response = _SysNoticeService.GetFirst(x => x.NoticeId == NoticeId); @@ -92,9 +79,7 @@ namespace ZR.Admin.WebApi.Controllers.System public IActionResult AddSysNotice([FromBody] SysNoticeDto parm) { var modal = parm.Adapt().ToCreate(HttpContext); - modal.Create_by = HttpContext.GetName(); - modal.Create_time = DateTime.Now; - + int result = _SysNoticeService.Insert(modal, it => new { it.NoticeTitle, diff --git a/ZR.Admin.WebApi/Controllers/System/SysPostController.cs b/ZR.Admin.WebApi/Controllers/System/SysPostController.cs index f9820fd..8e65053 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysPostController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysPostController.cs @@ -1,12 +1,6 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Extensions; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using SqlSugar; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model; using ZR.Model.System; using ZR.Service.System.IService; @@ -18,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/post")] + [ApiExplorerSettings(GroupName = "sys")] public class SysPostController : BaseController { private readonly ISysPostService PostService; @@ -71,8 +66,8 @@ namespace ZR.Admin.WebApi.Controllers.System { throw new CustomException($"修改岗位{post.PostName}失败,岗位编码已存在"); } + post.ToCreate(HttpContext); - post.Create_by = HttpContext.GetName(); return ToResponse(PostService.Add(post)); } @@ -94,7 +89,7 @@ namespace ZR.Admin.WebApi.Controllers.System { throw new CustomException($"修改岗位{post.PostName}失败,岗位编码已存在"); } - post.Update_by = HttpContext.GetName(); + post.ToUpdate(HttpContext); return ToResponse(PostService.Update(post)); } @@ -126,7 +121,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// 岗位导出 /// /// - [Log(BusinessType = BusinessType.EXPORT, IsSaveResponseData = false, Title= "岗位导出")] + [Log(BusinessType = BusinessType.EXPORT, IsSaveResponseData = false, Title = "岗位导出")] [HttpGet("export")] [ActionPermissionFilter(Permission = "system:post:export")] public IActionResult Export() diff --git a/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs b/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs index 875538b..ca2738a 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysProfileController.cs @@ -1,10 +1,4 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; using Microsoft.AspNetCore.Mvc; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; using ZR.Model.System; using ZR.Model.System.Dto; @@ -17,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/user/profile")] + [ApiExplorerSettings(GroupName = "sys")] public class SysProfileController : BaseController { private readonly ISysUserService UserService; diff --git a/ZR.Admin.WebApi/Controllers/System/SysRoleController.cs b/ZR.Admin.WebApi/Controllers/System/SysRoleController.cs index 10b89d9..35954c3 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysRoleController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysRoleController.cs @@ -1,21 +1,9 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Microsoft.AspNetCore.Mvc; -using ZR.Common; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Filters; using ZR.Model; using ZR.Model.System; -using ZR.Service.System.IService; -using ZR.Admin.WebApi.Extensions; using ZR.Model.System.Dto; -using Mapster; -using ZR.Service; -using Microsoft.AspNetCore.Authorization; -using Aliyun.OSS; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs; +using ZR.Service.System.IService; namespace ZR.Admin.WebApi.Controllers.System { @@ -24,6 +12,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/role")] + [ApiExplorerSettings(GroupName = "sys")] public class SysRoleController : BaseController { private readonly ISysRoleService sysRoleService; diff --git a/ZR.Admin.WebApi/Controllers/System/SysUserController.cs b/ZR.Admin.WebApi/Controllers/System/SysUserController.cs index 0f2e2e3..9242360 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysUserController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysUserController.cs @@ -1,11 +1,6 @@ -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MiniExcelLibs; using SqlSugar; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; using ZR.Model; using ZR.Model.System; @@ -19,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/user")] + [ApiExplorerSettings(GroupName = "sys")] public class SysUserController : BaseController { private readonly ISysUserService UserService; @@ -49,7 +45,7 @@ namespace ZR.Admin.WebApi.Controllers.System { var list = UserService.SelectUserList(user, pager); - return SUCCESS(list, TIME_FORMAT_FULL); + return SUCCESS(list); } /// @@ -97,9 +93,10 @@ namespace ZR.Admin.WebApi.Controllers.System } user.Create_by = HttpContext.GetName(); + user.Create_time = DateTime.Now; user.Password = NETCore.Encrypt.EncryptProvider.Md5(user.Password); - return ToResponse(UserService.InsertUser(user)); + return SUCCESS(UserService.InsertUser(user)); } /// @@ -147,7 +144,7 @@ namespace ZR.Admin.WebApi.Controllers.System public IActionResult Remove(int userid = 0) { if (userid <= 0) { return ToResponse(ApiResult.Error(101, "请求参数错误")); } - if (userid == 1) return ToResponse(Infrastructure.ResultCode.FAIL, "不能删除管理员账号"); + if (userid == 1) return ToResponse(ResultCode.FAIL, "不能删除管理员账号"); int result = UserService.DeleteUser(userid); return ToResponse(result); @@ -160,7 +157,7 @@ namespace ZR.Admin.WebApi.Controllers.System [HttpPut("resetPwd")] [Log(Title = "重置密码", BusinessType = BusinessType.UPDATE)] [ActionPermissionFilter(Permission = "system:user:resetPwd")] - public IActionResult ResetPwd([FromBody] SysUser sysUser) + public IActionResult ResetPwd([FromBody] SysUserDto sysUser) { //密码md5 sysUser.Password = NETCore.Encrypt.EncryptProvider.Md5(sysUser.Password); diff --git a/ZR.Admin.WebApi/Controllers/System/SysUserRoleController.cs b/ZR.Admin.WebApi/Controllers/System/SysUserRoleController.cs index 6a145c8..366e1c2 100644 --- a/ZR.Admin.WebApi/Controllers/System/SysUserRoleController.cs +++ b/ZR.Admin.WebApi/Controllers/System/SysUserRoleController.cs @@ -1,6 +1,4 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Filters; using ZR.Model.System.Dto; using ZR.Service.System.IService; @@ -12,6 +10,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("system/userRole")] + [ApiExplorerSettings(GroupName = "sys")] public class SysUserRoleController : BaseController { private readonly ISysUserRoleService SysUserRoleService; @@ -45,7 +44,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [HttpPost("create")] [ActionPermissionFilter(Permission = "system:roleusers:add")] - [Log(Title = "添加角色用户", BusinessType = Infrastructure.Enums.BusinessType.INSERT)] + [Log(Title = "添加角色用户", BusinessType = BusinessType.INSERT)] public IActionResult Create([FromBody] RoleUsersCreateDto roleUsersCreateDto) { var response = SysUserRoleService.InsertRoleUser(roleUsersCreateDto); @@ -60,7 +59,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [HttpPost("delete")] [ActionPermissionFilter(Permission = "system:roleusers:remove")] - [Log(Title = "删除角色用户", BusinessType = Infrastructure.Enums.BusinessType.DELETE)] + [Log(Title = "删除角色用户", BusinessType = BusinessType.DELETE)] public IActionResult Delete([FromBody] RoleUsersCreateDto roleUsersCreateDto) { return SUCCESS(SysUserRoleService.DeleteRoleUserByUserIds(roleUsersCreateDto.RoleId, roleUsersCreateDto.UserIds)); diff --git a/ZR.Admin.WebApi/Controllers/System/TasksController.cs b/ZR.Admin.WebApi/Controllers/System/TasksController.cs index 66fcc42..c5daacd 100644 --- a/ZR.Admin.WebApi/Controllers/System/TasksController.cs +++ b/ZR.Admin.WebApi/Controllers/System/TasksController.cs @@ -1,14 +1,7 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Extensions; -using Mapster; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Quartz; using SqlSugar; -using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Model; using ZR.Model.System; using ZR.Model.System.Dto; using ZR.Service.System.IService; @@ -21,6 +14,7 @@ namespace ZR.Admin.WebApi.Controllers /// [Verify] [Route("system/Tasks")] + [ApiExplorerSettings(GroupName = "sys")] public class TasksController : BaseController { private ISysTasksQzService _tasksQzService; @@ -40,19 +34,9 @@ namespace ZR.Admin.WebApi.Controllers /// [HttpGet("list")] [ActionPermissionFilter(Permission = "monitor:job:list")] - public IActionResult ListTask([FromQuery] TasksQueryDto parm, [FromQuery] PagerInfo pager) + public IActionResult ListTask([FromQuery] TasksQueryDto parm) { - //开始拼装查询条件 - var predicate = Expressionable.Create(); - - predicate = predicate.AndIF(!string.IsNullOrEmpty(parm.QueryText), - m => m.Name.Contains(parm.QueryText) || - m.JobGroup.Contains(parm.QueryText) || - m.AssemblyName.Contains(parm.QueryText)); - predicate.AndIF(parm.TaskType != null, m => m.TaskType == parm.TaskType); - - var response = _tasksQzService.GetPages(predicate.ToExpression(), pager); - + var response = _tasksQzService.SelectTaskList(parm); return SUCCESS(response, TIME_FORMAT_FULL); } @@ -102,7 +86,7 @@ namespace ZR.Admin.WebApi.Controllers throw new CustomException($"程序集或者类名不能为空"); } //从 Dto 映射到 实体 - var tasksQz = parm.Adapt().ToCreate(); + var tasksQz = parm.Adapt().ToCreate(HttpContext); tasksQz.Create_by = HttpContext.GetName(); tasksQz.ID = SnowFlakeSingle.Instance.NextId().ToString(); @@ -174,7 +158,7 @@ namespace ZR.Admin.WebApi.Controllers var tasksQz = _tasksQzService.GetFirst(m => m.ID == id); var taskResult = await _schedulerServer.DeleteTaskScheduleAsync(tasksQz); - if (taskResult.Code == 200) + if (taskResult.IsSuccess()) { _tasksQzService.Delete(id); } @@ -203,7 +187,7 @@ namespace ZR.Admin.WebApi.Controllers var tasksQz = _tasksQzService.GetFirst(m => m.ID == id); var taskResult = await _schedulerServer.AddTaskScheduleAsync(tasksQz); - if (taskResult.Code == 200) + if (taskResult.IsSuccess()) { tasksQz.IsStart = 1; _tasksQzService.Update(tasksQz); @@ -234,7 +218,7 @@ namespace ZR.Admin.WebApi.Controllers var tasksQz = _tasksQzService.GetFirst(m => m.ID == id); var taskResult = await _schedulerServer.DeleteTaskScheduleAsync(tasksQz);//await _schedulerServer.PauseTaskScheduleAsync(tasksQz); - if (taskResult.Code == 200) + if (taskResult.IsSuccess()) { tasksQz.IsStart = 0; _tasksQzService.Update(tasksQz); @@ -253,11 +237,12 @@ namespace ZR.Admin.WebApi.Controllers [Log(Title = "执行任务", BusinessType = BusinessType.OTHER)] public async Task Run(string id) { - if (!_tasksQzService.Any(m => m.ID == id)) + var result = await _tasksQzService.IsAnyAsync(m => m.ID == id); + if (!result) { throw new CustomException("任务不存在"); } - var tasksQz = _tasksQzService.GetFirst(m => m.ID == id); + var tasksQz = await _tasksQzService.GetFirstAsync(m => m.ID == id); var taskResult = await _schedulerServer.RunTaskScheduleAsync(tasksQz); return ToResponse(taskResult); diff --git a/ZR.Admin.WebApi/Controllers/System/TasksLogController.cs b/ZR.Admin.WebApi/Controllers/System/TasksLogController.cs index 115db6b..02dee80 100644 --- a/ZR.Admin.WebApi/Controllers/System/TasksLogController.cs +++ b/ZR.Admin.WebApi/Controllers/System/TasksLogController.cs @@ -1,11 +1,6 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Extensions; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using SqlSugar; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model; using ZR.Model.System; using ZR.Model.System.Dto; @@ -18,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.System /// [Verify] [Route("/monitor/jobLog")] + [ApiExplorerSettings(GroupName = "sys")] public class TasksLogController : BaseController { private readonly ISysTasksLogService tasksLogService; diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs index 4dc5009..e350369 100644 --- a/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs +++ b/ZR.Admin.WebApi/Controllers/System/monitor/MonitorController.cs @@ -1,11 +1,6 @@ -using Infrastructure; using Infrastructure.Extensions; -using Infrastructure.Model; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; -using System; using System.Diagnostics; using System.Runtime.InteropServices; @@ -14,6 +9,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor /// /// 系统监控 /// + [ApiExplorerSettings(GroupName = "sys")] public class MonitorController : BaseController { private OptionsSetting Options; diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/SqlDiffLogController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/SqlDiffLogController.cs new file mode 100644 index 0000000..7550e92 --- /dev/null +++ b/ZR.Admin.WebApi/Controllers/System/monitor/SqlDiffLogController.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Mvc; +using ZR.Admin.WebApi.Filters; +using ZR.Model.System.Dto; +using ZR.Service.System.IService; + +//创建时间:2023-08-17 +namespace ZR.Admin.WebApi.Controllers +{ + /// + /// 数据差异日志 + /// + [Verify] + [Route("monitor/SqlDiffLog")] + [ApiExplorerSettings(GroupName = "sys")] + public class SqlDiffLogController : BaseController + { + /// + /// 数据差异日志接口 + /// + private readonly ISqlDiffLogService _SqlDiffLogService; + + public SqlDiffLogController(ISqlDiffLogService SqlDiffLogService) + { + _SqlDiffLogService = SqlDiffLogService; + } + + /// + /// 查询数据差异日志列表 + /// + /// + /// + [HttpGet("list")] + [ActionPermissionFilter(Permission = "sqldifflog:list")] + public IActionResult QuerySqlDiffLog([FromQuery] SqlDiffLogQueryDto parm) + { + var response = _SqlDiffLogService.GetList(parm); + return SUCCESS(response); + } + + /// + /// 删除数据差异日志 + /// + /// + [HttpDelete("{ids}")] + [ActionPermissionFilter(Permission = "sqldifflog:delete")] + [Log(Title = "数据差异日志", BusinessType = BusinessType.DELETE)] + public IActionResult DeleteSqlDiffLog(string ids) + { + long[] idsArr = Tools.SpitLongArrary(ids); + if (idsArr.Length <= 0) { return ToResponse(ApiResult.Error($"删除失败Id 不能为空")); } + + var response = _SqlDiffLogService.Delete(idsArr); + + return ToResponse(response); + } + + /// + /// 导出数据差异日志 + /// + /// + [Log(Title = "数据差异日志", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)] + [HttpGet("export")] + [ActionPermissionFilter(Permission = "sqldifflog:export")] + public IActionResult Export([FromQuery] SqlDiffLogQueryDto parm) + { + parm.PageNum = 1; + parm.PageSize = 100000; + var list = _SqlDiffLogService.GetList(parm).Result; + if (list == null || list.Count <= 0) + { + return ToResponse(ResultCode.FAIL, "没有要导出的数据"); + } + var result = ExportExcelMini(list, "数据差异日志", "数据差异日志"); + return ExportExcel(result.Item2, result.Item1); + } + } +} \ No newline at end of file diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/SysLogininforController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/SysLogininforController.cs index 0b6e61d..712f022 100644 --- a/ZR.Admin.WebApi/Controllers/System/monitor/SysLogininforController.cs +++ b/ZR.Admin.WebApi/Controllers/System/monitor/SysLogininforController.cs @@ -1,12 +1,7 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using SqlSugar; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model; using ZR.Model.System; using ZR.Service.System.IService; @@ -18,6 +13,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor /// [Verify] [Route("/monitor/logininfor")] + [ApiExplorerSettings(GroupName = "sys")] public class SysLogininforController : BaseController { private ISysLoginService sysLoginService; diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/SysOperlogController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/SysOperlogController.cs index a374833..a30baeb 100644 --- a/ZR.Admin.WebApi/Controllers/System/monitor/SysOperlogController.cs +++ b/ZR.Admin.WebApi/Controllers/System/monitor/SysOperlogController.cs @@ -1,10 +1,6 @@ -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using ZR.Admin.WebApi.Extensions; using ZR.Admin.WebApi.Filters; -using ZR.Common; using ZR.Model.System.Dto; using ZR.Service.System.IService; @@ -15,6 +11,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor /// [Verify] [Route("/monitor/operlog")] + [ApiExplorerSettings(GroupName = "sys")] public class SysOperlogController : BaseController { private ISysOperLogService sysOperLogService; @@ -69,7 +66,7 @@ namespace ZR.Admin.WebApi.Controllers.monitor { if (!HttpContextExtension.IsAdmin(HttpContext)) { - return ToResponse(Infrastructure.ResultCode.CUSTOM_ERROR,"操作失败"); + return ToResponse(ResultCode.CUSTOM_ERROR,"操作失败"); } sysOperLogService.CleanOperLog(); diff --git a/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs b/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs index 96a9674..e0d6109 100644 --- a/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs +++ b/ZR.Admin.WebApi/Controllers/System/monitor/SysUserOnlineController.cs @@ -1,18 +1,23 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; using ZR.Admin.WebApi.Filters; -using ZR.Admin.WebApi.Hubs; using ZR.Model; +using ZR.Model.System.Dto; +using ZR.ServiceCore.Signalr; namespace ZR.Admin.WebApi.Controllers.monitor { + /// + /// 在线用户 + /// [Verify] [Route("monitor/online")] + [ApiExplorerSettings(GroupName = "sys")] public class SysUserOnlineController : BaseController { - private IHubContext HubContext; + private readonly IHubContext HubContext; - public SysUserOnlineController(IHubContext hubContext) + public SysUserOnlineController(IHubContext hubContext) { HubContext = hubContext; } @@ -25,11 +30,47 @@ namespace ZR.Admin.WebApi.Controllers.monitor [HttpGet("list")] public IActionResult Index([FromQuery] PagerInfo parm) { - var result = MessageHub.clientUsers + var result = MessageHub.onlineClients .OrderByDescending(f => f.LoginTime) .Skip(parm.PageNum - 1).Take(parm.PageSize); - return SUCCESS(new { result, totalNum = MessageHub.clientUsers.Count }); + return SUCCESS(new { result, totalNum = MessageHub.onlineClients.Count }); + } + + /// + /// 单个强退 + /// + /// + [HttpDelete("force")] + [Log(Title = "强退", BusinessType = BusinessType.FORCE)] + [ActionPermissionFilter(Permission = "monitor:online:forceLogout")] + public async Task Force([FromBody] LockUserDto dto) + { + if (dto == null) { return ToResponse(ResultCode.PARAM_ERROR); } + + await HubContext.Clients.Client(dto.ConnnectionId) + .SendAsync(HubsConstant.ForceUser, new { dto.Reason, dto.Time }); + + //var expirTime = DateTimeHelper.GetUnixTimeSeconds(DateTime.Now.AddMinutes(dto.Time)); + ////PC 端采用设备 + 用户名的方式进行封锁 + //CacheService.SetLockUser(dto.ClientId + dto.Name, expirTime, dto.Time); + return SUCCESS(1); + } + + /// + /// 批量强退 + /// + /// + [HttpDelete("batchForce")] + [Log(Title = "强退", BusinessType = BusinessType.FORCE)] + [ActionPermissionFilter(Permission = "monitor:online:batchLogout")] + public async Task BatchforceLogout([FromBody] LockUserDto dto) + { + if (dto == null) { return ToResponse(ResultCode.PARAM_ERROR); } + + await HubContext.Clients.All.SendAsync(HubsConstant.ForceUser, new { dto.Reason }); + + return SUCCESS(1); } } } diff --git a/ZR.Admin.WebApi/Controllers/WxOpenController.cs b/ZR.Admin.WebApi/Controllers/WxOpenController.cs new file mode 100644 index 0000000..53bbc10 --- /dev/null +++ b/ZR.Admin.WebApi/Controllers/WxOpenController.cs @@ -0,0 +1,39 @@ +using Microsoft.AspNetCore.Mvc; +using System.Web; + +namespace ZR.Admin.WebApi.Controllers +{ + /// + /// 微信公众号 + /// + [Route("[controller]/[action]")] + [AllowAnonymous] + public class WxOpenController : BaseController + { + /// + /// 获取签名 + /// + /// + /// + [Log(Title = "获取微信签名")] + [HttpGet] + public IActionResult GetSignature(string url = "") + { + url = HttpUtility.UrlDecode(url); + var appId = AppSettings.App(new string[] { "WxOpen", "AppID" }); + var noncestr = Guid.NewGuid().ToString().Replace("-", ""); + var timestamp = DateTimeHelper.GetUnixTimeSeconds(DateTime.Now); + var ticketResult = WxHelper.GetTicket(); + if (appId.IsEmpty()) return ToResponse(ResultCode.CUSTOM_ERROR, "appId未配置"); + if (ticketResult?.errcode != 0) + { + return ToResponse(ResultCode.CUSTOM_ERROR, "获取配置失败"); + } + + var signature = WxHelper.GetSignature(ticketResult.ticket, timestamp.ToString(), noncestr, url); + + return SUCCESS(new { appId, signature, noncestr, timestamp, url }); + } + + } +} diff --git a/ZR.Admin.WebApi/Extensions/DbExtension.cs b/ZR.Admin.WebApi/Extensions/DbExtension.cs deleted file mode 100644 index 5840448..0000000 --- a/ZR.Admin.WebApi/Extensions/DbExtension.cs +++ /dev/null @@ -1,268 +0,0 @@ -using Infrastructure; -using Infrastructure.Extensions; -using SqlSugar; -using SqlSugar.IOC; -using ZR.Admin.WebApi.Framework; -using ZR.Model; -using ZR.Model.System; - -namespace ZR.Admin.WebApi.Extensions -{ - /// - /// sqlsugar 数据处理 - /// - public static class DbExtension - { - private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - //全部数据权限 - public static long DATA_SCOPE_ALL = 1; - //自定数据权限 - public static long DATA_SCOPE_CUSTOM = 2; - //部门数据权限 - public static long DATA_SCOPE_DEPT = 3; - //部门及以下数据权限 - public static long DATA_SCOPE_DEPT_AND_CHILD = 4; - //仅本人数据权限 - public static long DATA_SCOPE_SELF = 5; - - /// - /// 初始化db - /// - /// - /// - /// - public static void AddDb(this IServiceCollection services, IConfiguration Configuration, IWebHostEnvironment environment) - { - List dbConfigs = Configuration.GetSection("DbConfigs").Get>(); - - var iocList = new List(); - foreach (var item in dbConfigs.FindAll(f => !f.IsGenerateDb)) - { - iocList.Add(new IocConfig() - { - ConfigId = item.ConfigId, - ConnectionString = item.Conn, - DbType = (IocDbType)item.DbType, - IsAutoCloseConnection = item.IsAutoCloseConnection - }); - } - SugarIocServices.AddSqlSugar(iocList); - ICacheService cache = new SqlSugarCache(); - SugarIocServices.ConfigurationSugar(db => - { - var u = App.User; - if (u != null) - { - FilterData(0); - //ConfigId = 1的数据权限过滤 - //FilterData1(1); - } - - iocList.ForEach(iocConfig => - { - SetSugarAop(db, iocConfig, cache); - }); - }); - - if (Configuration["InitDb"].ParseToBool() == true && environment.IsDevelopment()) - { - InitTable.InitDb(); - } - } - - /// - /// 数据库Aop设置 - /// - /// - /// - /// - private static void SetSugarAop(SqlSugarClient db, IocConfig iocConfig, ICacheService cache) - { - var config = db.GetConnectionScope(iocConfig.ConfigId).CurrentConnectionConfig; - - string configId = config.ConfigId; - db.GetConnectionScope(configId).Aop.OnLogExecuting = (sql, pars) => - { - string log = $"【db{configId} SQL语句】{UtilMethods.GetSqlString(config.DbType, sql, pars)}\n"; - if (sql.TrimStart().StartsWith("SELECT", StringComparison.OrdinalIgnoreCase)) - { - logger.Info(log); - } - else if (sql.StartsWith("UPDATE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("INSERT", StringComparison.OrdinalIgnoreCase)) - { - logger.Warn(log); - } - else if (sql.StartsWith("DELETE", StringComparison.OrdinalIgnoreCase) || sql.StartsWith("TRUNCATE", StringComparison.OrdinalIgnoreCase)) - { - logger.Error(log); - } - else - { - log = $"【db{configId} SQL语句】dbo.{sql} {string.Join(", ", pars.Select(x => x.ParameterName + " = " + GetParsValue(x)))};\n"; - logger.Info(log); - } - }; - db.GetConnectionScope(configId).Aop.OnError = (ex) => - { - var pars = db.Utilities.SerializeObject(((SugarParameter[])ex.Parametres).ToDictionary(it => it.ParameterName, it => it.Value)); - - string sql = "【错误SQL】" + UtilMethods.GetSqlString(config.DbType, ex.Sql, (SugarParameter[])ex.Parametres) + "\r\n"; - logger.Error(ex, $"{sql}\r\n{ex.Message}\r\n{ex.StackTrace}"); - }; - db.GetConnectionScope(configId).Aop.DataExecuting = (oldValue, entiyInfo) => - { - }; - - db.GetConnectionScope(configId).CurrentConnectionConfig.MoreSettings = new ConnMoreSettings() - { - IsAutoRemoveDataCache = true - }; - db.GetConnectionScope(configId).CurrentConnectionConfig.ConfigureExternalServices = new ConfigureExternalServices() - { - DataInfoCacheService = cache, - EntityService = (c, p) => - { - if (p.IsPrimarykey == true)//主键不能为null - { - p.IsNullable = false; - } - else if (p.ExtendedAttribute?.ToString() == ProteryConstant.NOTNULL.ToString()) - { - p.IsNullable = false; - } - else//则否默认为null - { - p.IsNullable = true; - } - - if (config.DbType == DbType.PostgreSQL) - { - if (c.Name == nameof(SysMenu.IsCache) || c.Name == nameof(SysMenu.IsFrame)) - { - p.DataType = "char(1)"; - } - } - #region 兼容Oracle - if (config.DbType == DbType.Oracle) - { - if (p.IsIdentity == true) - { - if (p.EntityName == nameof(SysUser)) - { - p.OracleSequenceName = "SEQ_SYS_USER_USERID"; - } - else if (p.EntityName == nameof(SysRole)) - { - p.OracleSequenceName = "SEQ_SYS_ROLE_ROLEID"; - } - else if (p.EntityName == nameof(SysDept)) - { - p.OracleSequenceName = "SEQ_SYS_DEPT_DEPTID"; - } - else if (p.EntityName == nameof(SysMenu)) - { - p.OracleSequenceName = "SEQ_SYS_MENU_MENUID"; - } - else - { - p.OracleSequenceName = "SEQ_ID"; - } - } - } - #endregion - } - }; - } - - private static object GetParsValue(SugarParameter x) - { - if (x.DbType == System.Data.DbType.String || x.DbType == System.Data.DbType.DateTime || x.DbType == System.Data.DbType.String) - { - return "'" + x.Value + "'"; - } - return x.Value; - } - - /// - /// 数据过滤 - /// - /// 多库id - private static void FilterData(int configId) - { - //获取当前用户的信息 - var user = JwtUtil.GetLoginUser(App.HttpContext); - if (user == null) return; - //管理员不过滤 - if (user.RoleIds.Any(f => f.Equals(GlobalConstant.AdminRole))) return; - var db = DbScoped.SugarScope.GetConnectionScope(configId); - var expUser = Expressionable.Create().Or(it => 1 == 1); - var expRole = Expressionable.Create().Or(it => 1 == 1); - var expLoginlog = Expressionable.Create(); - - foreach (var role in user.Roles.OrderBy(f => f.DataScope)) - { - long dataScope = role.DataScope; - if (DATA_SCOPE_ALL.Equals(dataScope))//所有权限 - { - break; - } - else if (DATA_SCOPE_CUSTOM.Equals(dataScope))//自定数据权限 - { - //" OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, role.getRoleId())); - - expUser.Or(it => SqlFunc.Subqueryable().Where(f => f.DeptId == it.DeptId && f.RoleId == role.RoleId).Any()); - } - else if (DATA_SCOPE_DEPT.Equals(dataScope))//本部门数据 - { - expUser.Or(it => it.DeptId == user.DeptId); - } - else if (DATA_SCOPE_DEPT_AND_CHILD.Equals(dataScope))//本部门及以下数据 - { - //SQl OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) ) - var allChildDepts = db.Queryable().ToChildList(it => it.ParentId, user.DeptId); - - expUser.Or(it => allChildDepts.Select(f => f.DeptId).ToList().Contains(it.DeptId)); - } - else if (DATA_SCOPE_SELF.Equals(dataScope))//仅本人数据 - { - expUser.Or(it => it.UserId == user.UserId); - expRole.Or(it => user.RoleIds.Contains(it.RoleKey)); - expLoginlog.And(it => it.UserName == user.UserName); - } - db.QueryFilter.AddTableFilter(expUser.ToExpression()); - db.QueryFilter.AddTableFilter(expRole.ToExpression()); - db.QueryFilter.AddTableFilter(expLoginlog.ToExpression()); - } - } - - private static void FilterData1(int configId) - { - //获取当前用户的信息 - var user = JwtUtil.GetLoginUser(App.HttpContext); - if (user == null) return; - var db = DbScoped.SugarScope.GetConnectionScope(configId); - - foreach (var role in user.Roles.OrderBy(f => f.DataScope)) - { - long dataScope = role.DataScope; - if (DATA_SCOPE_ALL.Equals(dataScope))//所有权限 - { - break; - } - else if (DATA_SCOPE_CUSTOM.Equals(dataScope))//自定数据权限 - { - } - else if (DATA_SCOPE_DEPT.Equals(dataScope))//本部门数据 - { - } - else if (DATA_SCOPE_DEPT_AND_CHILD.Equals(dataScope))//本部门及以下数据 - { - - } - else if (DATA_SCOPE_SELF.Equals(dataScope))//仅本人数据 - { - } - } - } - } -} diff --git a/ZR.Admin.WebApi/Extensions/EntityExtension.cs b/ZR.Admin.WebApi/Extensions/EntityExtension.cs deleted file mode 100644 index 20d7023..0000000 --- a/ZR.Admin.WebApi/Extensions/EntityExtension.cs +++ /dev/null @@ -1,48 +0,0 @@ - -namespace ZR.Admin.WebApi.Extensions -{ - public static class EntityExtension - { - public static TSource ToCreate(this TSource source, HttpContext? context = null) - { - var types = source?.GetType(); - if (types == null) return source; - - types.GetProperty("CreateTime")?.SetValue(source, DateTime.Now, null); - types.GetProperty("AddTime")?.SetValue(source, DateTime.Now, null); - types.GetProperty("UpdateTime")?.SetValue(source, DateTime.Now, null); - if (types.GetProperty("Create_by") != null && context != null) - { - types.GetProperty("Create_by")?.SetValue(source, context.GetName(), null); - } - if (types.GetProperty("Create_By") != null && context != null) - { - types.GetProperty("Create_By")?.SetValue(source, context.GetName(), null); - } - if (types.GetProperty("CreateBy") != null && context != null) - { - types.GetProperty("CreateBy")?.SetValue(source, context.GetName(), null); - } - if (types.GetProperty("UserId") != null && context != null) - { - types.GetProperty("UserId")?.SetValue(source, context.GetUId(), null); - } - return source; - } - - public static TSource ToUpdate(this TSource source, HttpContext? context = null) - { - var types = source?.GetType(); - if (types == null) return source; - - types.GetProperty("UpdateTime")?.SetValue(source, DateTime.Now, null); - types.GetProperty("Update_time")?.SetValue(source, DateTime.Now, null); - - types.GetProperty("UpdateBy")?.SetValue(source,context.GetName(), null); - types.GetProperty("Update_by")?.SetValue(source, context.GetName(), null); - - return source; - } - - } -} diff --git a/ZR.Admin.WebApi/Extensions/LogoExtension.cs b/ZR.Admin.WebApi/Extensions/LogoExtension.cs deleted file mode 100644 index e9291ed..0000000 --- a/ZR.Admin.WebApi/Extensions/LogoExtension.cs +++ /dev/null @@ -1,21 +0,0 @@ -using JinianNet.JNTemplate; -using ZR.Common; - -namespace ZR.Admin.WebApi.Extensions -{ - public static class LogoExtension - { - public static void AddLogo(this IServiceCollection services) - { - Console.ForegroundColor = ConsoleColor.Blue; - var contentTpl = JnHelper.ReadTemplate("", "logo.txt"); - var content = contentTpl?.Render(); - - Console.WriteLine(content); - Console.ForegroundColor = ConsoleColor.Blue; - Console.WriteLine("源码地址: https://gitee.com/izory/ZrAdminNetCore"); - Console.WriteLine("官方文档:http://www.izhaorui.cn/doc"); - Console.WriteLine("打赏作者:http://www.izhaorui.cn/doc/support.html"); - } - } -} diff --git a/ZR.Admin.WebApi/Extensions/SwaggerExtension.cs b/ZR.Admin.WebApi/Extensions/SwaggerExtension.cs index a4c12f9..a8a7336 100644 --- a/ZR.Admin.WebApi/Extensions/SwaggerExtension.cs +++ b/ZR.Admin.WebApi/Extensions/SwaggerExtension.cs @@ -1,7 +1,7 @@ -using Infrastructure; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.Filters; +using Swashbuckle.AspNetCore.SwaggerUI; using System.Reflection; namespace ZR.Admin.WebApi.Extensions @@ -33,7 +33,13 @@ namespace ZR.Admin.WebApi.Extensions }; }); }); - app.UseSwaggerUI(c => c.SwaggerEndpoint("v1/swagger.json", "ZrAdmin v1")); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("sys/swagger.json", "系统管理"); + c.SwaggerEndpoint("article/swagger.json", "文章管理"); + c.SwaggerEndpoint("v1/swagger.json", "business"); + c.DocExpansion(DocExpansion.None); //->修改界面打开时自动折叠 + }); } public static void AddSwaggerConfig(this IServiceCollection services) @@ -43,6 +49,20 @@ namespace ZR.Admin.WebApi.Extensions services.AddSwaggerGen(c => { + c.SwaggerDoc("sys", new OpenApiInfo + { + Title = "ZrAdmin.NET Api", + Version = "v1", + Description = "系统管理", + Contact = new OpenApiContact { Name = "ZRAdmin doc", Url = new Uri("https://www.izhaorui.cn/doc") } + }); + c.SwaggerDoc("article", new OpenApiInfo + { + Title = "ZrAdmin.NET Api", + Version = "v1", + Description = "文章管理", + Contact = new OpenApiContact { Name = "ZRAdmin doc", Url = new Uri("https://www.izhaorui.cn/doc") } + }); c.SwaggerDoc("v1", new OpenApiInfo { Title = "ZrAdmin.NET Api", @@ -55,6 +75,9 @@ namespace ZR.Admin.WebApi.Extensions //添加文档注释 var baseDir = AppContext.BaseDirectory; c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Model.xml"), true); + c.IncludeXmlComments(Path.Combine(baseDir, "ZR.ServiceCore.xml"), true); + c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Service.xml"), true); + c.IncludeXmlComments(Path.Combine(baseDir, "ZR.Admin.WebApi.xml"), true); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(baseDir, xmlFile); @@ -94,6 +117,20 @@ namespace ZR.Admin.WebApi.Extensions new List() } }); + + //判断接口归于哪个分组 + c.DocInclusionPredicate((docName, apiDescription) => + { + if (docName == "v1") + { + //当分组为NoGroup时,只要没加特性的都属于这个组 + return string.IsNullOrEmpty(apiDescription.GroupName); + } + else + { + return apiDescription.GroupName == docName; + } + }); }); } } diff --git a/ZR.Admin.WebApi/Extensions/TasksExtension.cs b/ZR.Admin.WebApi/Extensions/TasksExtension.cs index 3464bc7..b1b11b1 100644 --- a/ZR.Admin.WebApi/Extensions/TasksExtension.cs +++ b/ZR.Admin.WebApi/Extensions/TasksExtension.cs @@ -40,7 +40,7 @@ namespace ZR.Admin.WebApi.Extensions foreach (var task in tasks.Result) { var result = _schedulerServer.AddTaskScheduleAsync(task); - if (result.Result.Code == 200) + if (result.Result.IsSuccess()) { Console.WriteLine($"注册任务[{task.Name}]ID:{task.ID}成功"); } diff --git a/ZR.Admin.WebApi/Framework/CookieUtil.cs b/ZR.Admin.WebApi/Framework/CookieUtil.cs deleted file mode 100644 index 82081fd..0000000 --- a/ZR.Admin.WebApi/Framework/CookieUtil.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Http; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace ZR.Admin.WebApi.Framework -{ - public class CookieUtil - { - public static void WhiteCookie(HttpContext context, List claims) - { - //2.创建声明主题 指定认证方式 这里使用cookie - var claimsIdentity = new ClaimsIdentity(claims, "Login"); - - Task.Run(async () => - { - await context.SignInAsync( - JwtBearerDefaults.AuthenticationScheme,//这里要注意的是HttpContext.SignInAsync(AuthenticationType,…) 所设置的Scheme一定要与前面的配置一样,这样对应的登录授权才会生效。 - new ClaimsPrincipal(claimsIdentity), - new AuthenticationProperties() - { - IsPersistent = true, - AllowRefresh = true, - ExpiresUtc = DateTimeOffset.Now.AddDays(1),//有效时间 - }); - }).Wait(); - } - } -} diff --git a/ZR.Admin.WebApi/GlobalUsing.cs b/ZR.Admin.WebApi/GlobalUsing.cs new file mode 100644 index 0000000..3a674ba --- /dev/null +++ b/ZR.Admin.WebApi/GlobalUsing.cs @@ -0,0 +1,10 @@ +global using ZR.Common; +global using Microsoft.AspNetCore.Authorization; +global using Infrastructure; +global using Infrastructure.Attribute; +global using Infrastructure.Enums; +global using Infrastructure.Model; +global using Mapster; +global using Infrastructure.Extensions; +global using Infrastructure.Controllers; +global using ZR.ServiceCore.Middleware; \ No newline at end of file diff --git a/ZR.Admin.WebApi/Hubs/MessageHub.cs b/ZR.Admin.WebApi/Hubs/MessageHub.cs deleted file mode 100644 index 356cd98..0000000 --- a/ZR.Admin.WebApi/Hubs/MessageHub.cs +++ /dev/null @@ -1,107 +0,0 @@ -using Infrastructure; -using Infrastructure.Constant; -using Infrastructure.Model; -using IPTools.Core; -using Microsoft.AspNetCore.SignalR; -using UAParser; -using ZR.Admin.WebApi.Extensions; -using ZR.Service.System.IService; - -namespace ZR.Admin.WebApi.Hubs -{ - /// - /// msghub - /// - public class MessageHub : Hub - { - //创建用户集合,用于存储所有链接的用户数据 - public static readonly List clientUsers = new(); - private readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - private readonly ISysNoticeService SysNoticeService; - - public MessageHub(ISysNoticeService noticeService) - { - SysNoticeService = noticeService; - } - - private ApiResult SendNotice() - { - var result = SysNoticeService.GetSysNotices(); - - return new ApiResult(200, "success", result); - } - - #region 客户端连接 - - /// - /// 客户端连接的时候调用 - /// - /// - public override Task OnConnectedAsync() - { - var name = HttpContextExtension.GetName(App.HttpContext); - var ip = HttpContextExtension.GetClientUserIp(App.HttpContext); - var ip_info = IpTool.Search(ip); - - ClientInfo clientInfo = HttpContextExtension.GetClientInfo(App.HttpContext); - string device = clientInfo.ToString(); - - var userid = HttpContextExtension.GetUId(App.HttpContext); - string uuid = device + userid + ip; - var user = clientUsers.Any(u => u.ConnnectionId == Context.ConnectionId); - var user2 = clientUsers.Any(u => u.Uuid == uuid); - - //判断用户是否存在,否则添加集合!user2 && !user && - if (!user2 && !user && Context.User.Identity.IsAuthenticated) - { - OnlineUsers users = new(Context.ConnectionId, name, userid, ip, device) - { - Location = ip_info.City, - Uuid = uuid - }; - clientUsers.Add(users); - Console.WriteLine($"{DateTime.Now}:{name},{Context.ConnectionId}连接服务端success,当前已连接{clientUsers.Count}个"); - //Clients.All.SendAsync("welcome", $"欢迎您:{name},当前时间:{DateTime.Now}"); - Clients.All.SendAsync(HubsConstant.MoreNotice, SendNotice()); - } - - Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count); - return base.OnConnectedAsync(); - } - - /// - /// 连接终止时调用。 - /// - /// - public override Task OnDisconnectedAsync(Exception? exception) - { - var user = clientUsers.Where(p => p.ConnnectionId == Context.ConnectionId).FirstOrDefault(); - //判断用户是否存在,否则添加集合 - if (user != null) - { - clientUsers.Remove(user); - Clients.All.SendAsync(HubsConstant.OnlineNum, clientUsers.Count); - - Console.WriteLine($"用户{user?.Name}离开了,当前已连接{clientUsers.Count}个"); - } - return base.OnDisconnectedAsync(exception); - } - - #endregion - - /// - /// 注册信息 - /// - /// - /// - /// - /// - [HubMethodName("SendMessage")] - public async Task SendMessage(string connectId, string userName, string message) - { - Console.WriteLine($"{connectId},message={message}"); - - await Clients.Client(connectId).SendAsync("receiveChat", new { userName, message }); - } - } -} diff --git a/ZR.Admin.WebApi/Hubs/OnlineUsers.cs b/ZR.Admin.WebApi/Hubs/OnlineUsers.cs deleted file mode 100644 index cdfce13..0000000 --- a/ZR.Admin.WebApi/Hubs/OnlineUsers.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace ZR.Admin.WebApi.Hubs -{ - public class OnlineUsers - { - /// - /// 客户端连接Id - /// - public string ConnnectionId { get; set; } - /// - /// 用户id - /// - public long? Userid { get; set; } - public string Name { get; set; } - public DateTime LoginTime { get; set; } - public string UserIP { get; set; } - /// - /// 登录地点 - /// - public string? Location { get; set; } - - /// - /// 判断用户唯一 - /// - public string? Uuid{ get; set; } - /// - /// 浏览器 - /// - public string Browser { get; set; } - - public OnlineUsers(string clientid, string name, long? userid, string userip, string browser) - { - ConnnectionId = clientid; - Name = name; - LoginTime = DateTime.Now; - Userid = userid; - UserIP = userip; - Browser = browser; - } - } -} diff --git a/ZR.Admin.WebApi/NLog.config b/ZR.Admin.WebApi/NLog.config index 9936650..02a7cc2 100644 --- a/ZR.Admin.WebApi/NLog.config +++ b/ZR.Admin.WebApi/NLog.config @@ -24,29 +24,29 @@ + layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${aspnet-request-connection-id}|${uppercase:${level}}|${logger}|${aspnet-request-iP:CheckForwardedForHeader=true}|${event-properties:item=user:whenEmpty=-}|url: ${aspnet-request-url}|${message:whenEmpty=-}|${event-properties:item=requestParam}|${event-properties:item=jsonResult}"/> + layout="${longdate}|${event-properties:item=EventId:whenEmpty=0}|${uppercase:${level}}|${logger}${newline}用户IP:${aspnet-request-iP:CheckForwardedForHeader=true}|${event-properties:item=user}${newline}请求地址:${aspnet-request-url}${newline}错误消息:${message}${newline}请求参数:${event-properties:item=requestParam}${newline}请求结果:${event-properties:item=jsonResult}${newline}${onexception:${exception:format=tostring}"/> + layout="${date:format=MM-dd HH\:mm\:ss}|${aspnet-request-iP}|${aspnet-request-url}${newline}${message}"> - + + + - - @@ -80,11 +79,10 @@ - - - - + + + diff --git a/ZR.Admin.WebApi/Program.cs b/ZR.Admin.WebApi/Program.cs index 9684a36..abb03a4 100644 --- a/ZR.Admin.WebApi/Program.cs +++ b/ZR.Admin.WebApi/Program.cs @@ -1,19 +1,21 @@ using AspNetCoreRateLimit; -using Infrastructure; -using Microsoft.AspNetCore.Authentication.JwtBearer; +using Infrastructure.Converter; using Microsoft.AspNetCore.DataProtection; -using Microsoft.IdentityModel.Tokens; +using NLog.Web; +using System.Text.Json; +using System.Text.Json.Serialization; using ZR.Admin.WebApi.Extensions; -using ZR.Admin.WebApi.Filters; -using ZR.Admin.WebApi.Framework; -using ZR.Admin.WebApi.Hubs; -using ZR.Admin.WebApi.Middleware; using ZR.Common.Cache; +using ZR.Infrastructure.WebExtensions; +using ZR.ServiceCore.Signalr; +using ZR.ServiceCore.SqlSugar; var builder = WebApplication.CreateBuilder(args); +// NLog: Setup NLog for Dependency injection +//builder.Logging.ClearProviders(); +builder.Host.UseNLog(); // Add services to the container. - builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); @@ -24,8 +26,6 @@ builder.Services.AddSingleton(); builder.Services.AddCors(builder.Configuration); // 显示logo builder.Services.AddLogo(); -//注入SignalR实时通讯,默认用json传输 -builder.Services.AddSignalR(); //消除Error unprotecting the session cookie警告 builder.Services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection")); @@ -39,31 +39,10 @@ builder.Services.AddHttpContextAccessor(); builder.Services.Configure(builder.Configuration); //jwt 认证 -builder.Services.AddAuthentication(options => -{ - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; -}).AddCookie() -.AddJwtBearer(o => -{ - o.TokenValidationParameters = JwtUtil.ValidParameters(); - o.Events = new JwtBearerEvents - { - OnAuthenticationFailed = context => - { - // 如果过期,把过期信息添加到头部 - if (context.Exception.GetType() == typeof(SecurityTokenExpiredException)) - { - context.Response.Headers.Add("Token-Expired", "true"); - } - - return Task.CompletedTask; - } - }; -}); - -//InternalApp.InternalServices = builder.Services; +builder.Services.AddJwt(); +//配置文件 builder.Services.AddSingleton(new AppSettings(builder.Configuration)); +//app服务注册 builder.Services.AddAppService(); //开启计划任务 builder.Services.AddTaskSchedulers(); @@ -81,23 +60,43 @@ builder.Services.AddMvc(options => }) .AddJsonOptions(options => { + //options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString; + options.JsonSerializerOptions.WriteIndented = true; options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter()); options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeNullConverter()); + options.JsonSerializerOptions.Converters.Add(new StringConverter()); + //PropertyNamingPolicy属性用于前端传过来的属性的格式策略,目前内置的仅有一种策略CamelCase + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + //options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;//属性可以忽略大小写格式,开启后性能会降低 +}); +//注入SignalR实时通讯,默认用json传输 +builder.Services.AddSignalR() +.AddJsonProtocol(options => +{ + options.PayloadSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; }); - builder.Services.AddSwaggerConfig(); var app = builder.Build(); InternalApp.ServiceProvider = app.Services; +InternalApp.Configuration = builder.Configuration; +InternalApp.WebHostEnvironment = app.Environment; //初始化db -builder.Services.AddDb(builder.Configuration, app.Environment); +builder.Services.AddDb(app.Environment); //使用全局异常中间件 app.UseMiddleware(); -//使可以多次多去body内容 +//请求头转发 +//ForwardedHeaders中间件会自动把反向代理服务器转发过来的X-Forwarded-For(客户端真实IP)以及X-Forwarded-Proto(客户端请求的协议)自动填充到HttpContext.Connection.RemoteIPAddress和HttpContext.Request.Scheme中,这样应用代码中读取到的就是真实的IP和真实的协议了,不需要应用做特殊处理。 +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor | Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto +}); + app.Use((context, next) => { + //设置可以多次获取body内容 context.Request.EnableBuffering(); if (context.Request.Query.TryGetValue("access_token", out var token)) { @@ -117,8 +116,11 @@ app.UseAuthorization(); //开启缓存 app.UseResponseCaching(); -//恢复/启动任务 -app.UseAddTaskSchedulers(); +if (builder.Environment.IsProduction()) +{ + //恢复/启动任务 + app.UseAddTaskSchedulers(); +} //使用swagger app.UseSwagger(); //启用客户端IP限制速率 @@ -132,5 +134,4 @@ app.MapControllerRoute( pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapControllers(); - app.Run(); \ No newline at end of file diff --git a/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj b/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj index 927843a..6a9ff24 100644 --- a/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj +++ b/ZR.Admin.WebApi/ZR.Admin.WebApi.csproj @@ -6,7 +6,7 @@ true - 1701;1702;1591,8603,8602,8604,8600 + 1701;1702;1591,8603,8602,8604,8600,8618 @@ -15,21 +15,15 @@ - - - - - - + - + - @@ -37,5 +31,11 @@ Always + + Never + + + Never + diff --git a/ZR.Admin.WebApi/appsettings.json b/ZR.Admin.WebApi/appsettings.json index 2060e94..147f029 100644 --- a/ZR.Admin.WebApi/appsettings.json +++ b/ZR.Admin.WebApi/appsettings.json @@ -12,32 +12,35 @@ "DbType": 0, //数据库类型 MySql = 0, SqlServer = 1, Oracle = 3,PgSql = 4 "ConfigId": "0", //多租户唯一标识 "IsAutoCloseConnection": true - }, - { - //代码生成连接字符串,注意{dbName}为固定格式,不要填写数据库名 - "Conn": "Data Source=LAPTOP-STKF2M8H\\SQLEXPRESS;User ID=admin;Password=admin123;Initial Catalog={dbName};", - "DbType": 1, - "ConfigId": "0", - "IsAutoCloseConnection": true, - "DbName": "ZrAdmin", //代码生成默认连接数据库 - "IsGenerateDb": true //是否代码生成使用库,不要改动 } //...下面添加更多的数据库源 ], + //代码生成数据库配置 + "CodeGenDbConfig": { + //代码生成连接字符串,注意{dbName}为固定格式,不要填写数据库名 + "Conn": "Data Source=LAPTOP-STKF2M8H\\SQLEXPRESS;User ID=admin;Password=admin123;Initial Catalog={dbName};", + "DbType": 1, + "IsAutoCloseConnection": true, + "DbName": "ZrAdmin" //代码生成默认连接数据库,Oracle库是实例的名称 + }, "urls": "http://localhost:8888", //项目启动url,如果改动端口前端对应devServer也需要进行修改 - "corsUrls": "http://localhost:8887", //跨域地址(前端启动项目,前后端分离单独部署需要设置),多个用","隔开 + "corsUrls": [ "http://localhost:8887", "http://localhost:8886" ], //跨域地址(前端启动项目,前后端分离单独部署需要设置),多个用","隔开 "JwtSettings": { "Issuer": "ZRAdmin.NET", //即token的签发者。 "Audience": "ZRAdmin.NET", //指该token是服务于哪个群体的(群体范围) "SecretKey": "SecretKey-ZRADMIN.NET-20210101", - "Expire": 1440 //jwt登录过期时间(分) + "Expire": 1440, //jwt登录过期时间(分) + "RefreshTokenTime": 5,//分钟 + "TokenType": "Bearer" }, - "InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks" ], //自动注入类 + "InjectClass": [ "ZR.Repository", "ZR.Service", "ZR.Tasks", "ZR.ServiceCore" ], //自动注入类 + "ShowDbLog": true, //是否打印db日志 "InitDb": false, //是否初始化db "DemoMode": false, //是否演示模式 + "SingleLogin": false,//是否单点登录 "Upload": { "uploadUrl": "http://localhost:8888", //本地存储资源访问路径 - "localSavePath": "uploads", //本地上传默认文件存储目录 wwwroot/uploads, 如果saveType= 2 (请使用完整路径,比如 /home/resource) + "localSavePath": "", //本地上传默认文件存储目录 wwwroot "maxSize": 15, //上传文件大小限制 15M "notAllowedExt": [ ".bat", ".exe", ".jar", ".js" ] }, @@ -57,12 +60,30 @@ "CorpSecret": "", "SendUser": "@all" }, + //微信公众号设置 + "WxOpen": { + "AppID": "", + "AppSecret": "" + }, //代码生成配置 "gen": { - "autoPre": true, //自动去除表前缀 + //是否显示移动端代码生成 + "showApp": false, + //自动去除表前缀 + "autoPre": true, "author": "admin", "tablePrefix": "sys_", //"表前缀(生成类名不会包含表前缀,多个用逗号分隔)", - "vuePath": "" //前端代码存储路径eg:D:\Work\ZRAdmin-Vue3 + "vuePath": "", //前端代码存储路径eg:D:\Work\ZRAdmin-Vue3 + "csharpTypeArr": { + "string": [ "varchar", "nvarchar", "text", "longtext" ], + "int": [ "int", "integer", "smallint", "int4", "int8", "int2" ], + "long": [ "bigint", "number" ], + "float": [ "numeric", "real", "float" ], + "decimal": [ "money", "decimal", "smallmoney" ], + "dateTime": [ "date", "datetime", "datetime2", "smalldatetime", "timestamp" ], + "byte": [ "tinyint" ], + "bool": [ "bit" ] + } }, //邮箱配置信息 "MailOptions": { @@ -71,11 +92,12 @@ //发送人邮箱 "FromEmail": "", //eg:xxxx@qq.com //发送人邮箱密码 - "Password": "123456", + "Password": "", //协议 "Smtp": "smtp.qq.com", "Port": 587, - "Signature": "系统邮件,请勿回复!" + "Signature": "系统邮件,请勿回复!", + "UseSsl": true }, //redis服务配置 "RedisServer": { @@ -90,7 +112,7 @@ "RealIpHeader": "X-Real-IP", "ClientIdHeader": "X-ClientId", "HttpStatusCode": 429, - "EndpointWhitelist": [ "post:/system/dict/data/types", "*:/msghub/negotiate", "*:/LogOut", "*:/common/uploadfile" ], + "EndpointWhitelist": [ "post:/system/dict/data/types", "*:/msghub/negotiate", "*:/LogOut", "*:/common/uploadfile", "*:/VerifyScan" ], "QuotaExceededResponse": { "Content": "{{\"code\":429,\"msg\":\"访问过于频繁,请稍后重试\"}}", "ContentType": "application/json", @@ -109,12 +131,7 @@ "Period": "3s", "Limit": 1 } - ], - "IpRateLimitPolicies": { - //ip规则 - "IpRules": [ - ] - } + ] }, //验证码配置 "CaptchaOptions": { diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplControllers.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplControllers.txt index a15b37d..fec4063 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplControllers.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplControllers.txt @@ -1,15 +1,11 @@ -using Infrastructure; -using Infrastructure.Attribute; -using Infrastructure.Enums; -using Infrastructure.Model; -using Mapster; using Microsoft.AspNetCore.Mvc; using ${options.DtosNamespace}; using ${options.ModelsNamespace}.${options.SubNamespace}; using ${options.IServicsNamespace}.${options.SubNamespace}.I${options.SubNamespace}Service; -using ${options.ApiControllerNamespace}.Extensions; using ${options.ApiControllerNamespace}.Filters; -using ${options.BaseNamespace}Common; +$if(replaceDto.ShowBtnImport) +using MiniExcelLibs; +$end //创建时间:${replaceDto.AddTime} namespace ${options.ApiControllerNamespace}.Controllers @@ -68,7 +64,7 @@ $end [ActionPermissionFilter(Permission = "${replaceDto.PermissionPrefix}:query")] public IActionResult Get${replaceDto.ModelTypeName}(${replaceDto.PKType} ${replaceDto.PKName}) { - var response = _${replaceDto.ModelTypeName}Service.GetFirst(x => x.${replaceDto.PKName} == ${replaceDto.PKName}); + var response = _${replaceDto.ModelTypeName}Service.GetInfo(${replaceDto.PKName}); var info = response.Adapt<${replaceDto.ModelTypeName}>(); return SUCCESS(info); @@ -88,7 +84,7 @@ $if(replaceDto.ShowBtnAdd) var response = _${replaceDto.ModelTypeName}Service.Add${replaceDto.ModelTypeName}(modal); - return ToResponse(response); + return SUCCESS(response); } $end @@ -168,6 +164,40 @@ $if(replaceDto.ShowBtnTruncate) } $end +$if(replaceDto.ShowBtnImport) + /// + /// 导入 + /// + /// + /// + [HttpPost("importData")] + [Log(Title = "${genTable.FunctionName}导入", BusinessType = BusinessType.IMPORT, IsSaveRequestData = false)] + [ActionPermissionFilter(Permission = "${replaceDto.PermissionPrefix}:import")] + public IActionResult ImportData([FromForm(Name = "file")] IFormFile formFile) + { + List<${replaceDto.ModelTypeName}Dto> list = new(); + using (var stream = formFile.OpenReadStream()) + { + list = stream.Query<${replaceDto.ModelTypeName}Dto>(startCell: "A1").ToList(); + } + + return SUCCESS(_${replaceDto.ModelTypeName}Service.Import${replaceDto.ModelTypeName}(list.Adapt>())); + } + + /// + /// ${genTable.FunctionName}导入模板下载 + /// + /// + [HttpGet("importTemplate")] + [Log(Title = "${genTable.FunctionName}模板", BusinessType = BusinessType.EXPORT, IsSaveResponseData = false)] + [AllowAnonymous] + public IActionResult ImportTemplateExcel() + { + var result = DownloadImportTemplate(new List<${replaceDto.ModelTypeName}Dto>() { }, "${replaceDto.ModelTypeName}"); + return ExportExcel(result.Item2, result.Item1); + } +$end + $if(showCustomInput) /// /// 保存排序 diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplDto.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplDto.txt index 2639a9b..802ed6f 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplDto.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplDto.txt @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -//using ${options.ModelsNamespace}.${options.SubNamespace}; $if(replaceDto.ShowBtnExport) using MiniExcelLibs.Attributes; $end @@ -42,6 +39,7 @@ $end $if(replaceDto.ShowBtnExport) $if(item.IsExport) [ExcelColumn(Name = "$if(item.ColumnComment == "")${item.CsharpField}${else}${item.ColumnComment}${end}"$if(item.CsharpType == "DateTime"), Format = "yyyy-MM-dd HH:mm:ss"$end)] + [ExcelColumnName("$if(item.ColumnComment == "")${item.CsharpField}${else}${item.ColumnComment}${end}")] $else [ExcelIgnore] $end @@ -57,14 +55,14 @@ $if(genTable.TplCategory == "subNav" && genTable.SubTable != null) $if(replaceDto.ShowBtnExport) [ExcelIgnore] $end - public ${genTable.SubTable.ClassName} ${genTable.SubTable.ClassName}Nav { get; set; } + public ${genTable.SubTable.ClassName}Dto ${genTable.SubTable.ClassName}Nav { get; set; } $end $if(genTable.TplCategory == "subNavMore" && genTable.SubTable != null) $if(replaceDto.ShowBtnExport) [ExcelIgnore] $end - public List<${genTable.SubTable.ClassName}> ${genTable.SubTable.ClassName}Nav { get; set; } + public List<${genTable.SubTable.ClassName}Dto> ${genTable.SubTable.ClassName}Nav { get; set; } $end } } \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplIService.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplIService.txt index a48f27b..b2ae84b 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplIService.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplIService.txt @@ -13,14 +13,20 @@ namespace ${options.IServicsNamespace}.${options.SubNamespace}.I${options.SubNam { PagedInfo<${replaceDto.ModelTypeName}Dto> GetList(${replaceDto.ModelTypeName}QueryDto parm); + ${replaceDto.ModelTypeName} GetInfo(${replaceDto.PKType} ${replaceDto.PKName}); + $if(genTable.TplCategory == "tree") List<${replaceDto.ModelTypeName}> GetTreeList(${replaceDto.ModelTypeName}QueryDto parm); $end - int Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm); + ${replaceDto.ModelTypeName} Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm); int Update${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} parm); $if(replaceDto.ShowBtnTruncate) bool Truncate${replaceDto.ModelTypeName}(); $end + +$if(replaceDto.ShowBtnImport) + (string, object, object) Import${replaceDto.ModelTypeName}(List<${replaceDto.ModelTypeName}> list); +$end } } diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplModel.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplModel.txt index 172a316..31735d9 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplModel.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplModel.txt @@ -1,7 +1,4 @@ -using System; -using SqlSugar; -using System.Collections.Generic; -$if(genTable.TplCategory.Contains("subNav") && genTable.SubTable != null) +$if(genTable.TplCategory.Contains("subNav") && genTable.SubTable != null) using ${subTableOptions.ModelsNamespace}.${subTableOptions.SubNamespace}; $end @@ -37,7 +34,7 @@ $if(genTable.TplCategory == "tree") [SugarColumn(IsIgnore = true)] public List<${replaceDto.ModelTypeName}> Children { get; set; } $end -$if(genTable.TplCategory == "subNav" && genTable.SubTable != null) +$if((genTable.TplCategory == "subNav" || genTable.TplCategory == "sub") && genTable.SubTable != null) [Navigate(NavigateType.OneToOne, nameof(${replaceDto.PKName}))] //自定义关系映射 public ${genTable.SubTable.ClassName} ${genTable.SubTable.ClassName}Nav { get; set; } $end diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplService.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplService.txt index dd0e30a..f8d525e 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplService.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/TplService.txt @@ -51,7 +51,7 @@ $end $end var response = Queryable() $if(null != genTable.SubTableName && "" != genTable.SubTableName) - .Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象 + //.Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象 $end $if(genTable.Options.SortField != "" && genTable.Options.SortField != null) //.OrderBy("${genTable.Options.SortField} ${genTable.Options.SortType}") @@ -90,14 +90,35 @@ $end } $end + /// + /// 获取详情 + /// + /// + /// + public ${replaceDto.ModelTypeName} GetInfo(${replaceDto.PKType} ${replaceDto.PKName}) + { + var response = Queryable() +$if(null != genTable.SubTableName && "" != genTable.SubTableName) + .Includes(x => x.${genTable.SubTable.ClassName}Nav) //填充子对象 +$end + .Where(x => x.${replaceDto.PKName} == ${replaceDto.PKName}) + .First(); + + return response; + } + /// /// 添加${genTable.FunctionName} /// /// /// - public int Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} model) + public ${replaceDto.ModelTypeName} Add${replaceDto.ModelTypeName}(${replaceDto.ModelTypeName} model) { - return Add(model, true); +$if(null != genTable.SubTableName && "" != genTable.SubTableName) + return Context.InsertNav(model).Include(s1 => s1.${genTable.SubTable.ClassName}Nav).ExecuteReturnEntity(); +$else + return Context.Insertable(model).ExecuteReturnEntity(); +$end } /// @@ -116,7 +137,11 @@ $end ${end} //}); //return response; +$if(null != genTable.SubTableName && "" != genTable.SubTableName) + return Context.UpdateNav(model).Include(z1 => z1.${genTable.SubTable.ClassName}Nav).ExecuteCommand() ? 1 : 0; +$else return Update(model, true); +$end } $if(replaceDto.ShowBtnTruncate) /// @@ -134,5 +159,40 @@ $if(replaceDto.ShowBtnTruncate) return Truncate(); } $end + +$if(replaceDto.ShowBtnImport) + /// + /// 导入${genTable.FunctionName} + /// + /// + public (string, object, object) Import${replaceDto.ModelTypeName}(List<${replaceDto.ModelTypeName}> list) + { + var x = Context.Storageable(list) + .SplitInsert(it => !it.Any()) +$foreach(column in genTable.Columns) +$if(column.IsRequired && column.IsIncrement == false) + .SplitError(x => x.Item.${column.CsharpField}.IsEmpty(), "${column.ColumnComment}不能为空") +$end +$end + //.WhereColumns(it => it.UserName)//如果不是主键可以这样实现(多字段it=>new{it.x1,it.x2}) + .ToStorage(); + var result = x.AsInsertable.ExecuteCommand();//插入可插入部分; + + string msg = $"插入{x.InsertList.Count} 更新{x.UpdateList.Count} 错误数据{x.ErrorList.Count} 不计算数据{x.IgnoreList.Count} 删除数据{x.DeleteList.Count} 总共{x.TotalList.Count}"; + Console.WriteLine(msg); + + //输出错误信息 + foreach (var item in x.ErrorList) + { + Console.WriteLine("错误" + item.StorageMessage); + } + foreach (var item in x.IgnoreList) + { + Console.WriteLine("忽略" + item.StorageMessage); + } + + return (msg, x.ErrorList, x.IgnoreList); + } +$end } } \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/api.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/api.txt new file mode 100644 index 0000000..d1f318e --- /dev/null +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/api.txt @@ -0,0 +1,78 @@ +import request from '@/utils/request' + +/** +* ${genTable.functionName}分页查询 +* @param {查询条件} data +*/ +export function list${genTable.BusinessName}(query) { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}/list', + method: 'get', + data: query, + }) +} + +$if(replaceDto.ShowBtnAdd) +/** +* 新增${genTable.functionName} +* @param data +*/ +export function add${genTable.BusinessName}(data) { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}', + method: 'post', + data: data, + }) +} +$end +$if(replaceDto.ShowBtnEdit) +/** +* 修改${genTable.functionName} +* @param data +*/ +export function update${genTable.BusinessName}(data) { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}', + method: 'PUT', + data: data, + }) +} +$end +/** +* 获取${genTable.functionName}详情 +* @param {Id} +*/ +export function get${genTable.BusinessName}(id) { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}/' + id, + method: 'get' + }) +} + +$if(replaceDto.ShowBtnDelete || replaceDto.ShowBtnMultiDel) +/** +* 删除${genTable.functionName} +* @param {主键} pid +*/ +export function del${genTable.BusinessName}(pid) { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}/' + pid, + method: 'delete' + }) +} +$end +$if(replaceDto.ShowBtnTruncate) +// 清空${genTable.functionName} +export function clear${genTable.BusinessName}() { + return request({ + url: '/${genTable.ModuleName}/${genTable.BusinessName}/clean', + method: 'delete' + }) +} +$end +$if(replaceDto.ShowBtnExport) +// 导出${genTable.functionName} +export async function export${genTable.BusinessName}(query) { + await downFile('$/{genTable.ModuleName}/${genTable.BusinessName}/export', { ...query }) +} +$end \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/form.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/form.txt new file mode 100644 index 0000000..4d6dd90 --- /dev/null +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/form.txt @@ -0,0 +1,210 @@ + + + + + \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/vue2.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/vue2.txt new file mode 100644 index 0000000..c964ad9 --- /dev/null +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/app/vue2.txt @@ -0,0 +1,288 @@ + + + \ No newline at end of file diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/TreeVue.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/TreeVue.txt index 2b888d6..76ed5c2 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/TreeVue.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/TreeVue.txt @@ -197,7 +197,7 @@ $elseif(tool.CheckTree(genTable ,column.CsharpField)) $elseif(column.IsPK || column.IsIncrement) - + $if(column.IsIncrement == false) @@ -208,13 +208,13 @@ $end $else $if(column.HtmlType == "inputNumber") - + $elseif(column.HtmlType == "datetime") - + @@ -232,7 +232,7 @@ $elseif(column.HtmlType == "fileUpload") $elseif(column.HtmlType == "radio") - + {{item.dictLabel}} @@ -252,7 +252,7 @@ $elseif(column.HtmlType == "editor") $elseif(column.HtmlType == "select" || column.HtmlType == "selectMulti") - + @@ -268,7 +268,7 @@ $elseif(column.HtmlType == "checkbox") $else - + @@ -301,10 +301,8 @@ import Editor from '@/components/Editor' $end const { proxy } = getCurrentInstance() -// 是否展开,默认全部折叠 const isExpandAll = ref(false) const refreshTable = ref(true) -// 展开/折叠操作 function toggleExpandAll() { refreshTable.value = false isExpandAll.value = !isExpandAll.value diff --git a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/Vue.txt b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/Vue.txt index e79e9c2..702c64b 100644 --- a/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/Vue.txt +++ b/ZR.Admin.WebApi/wwwroot/CodeGenTemplate/v3/Vue.txt @@ -53,7 +53,7 @@ $elseif(column.HtmlType == "datePicker") :default-time="defaultTime"> -$elseif(column.HtmlType.Contains("select") || column.HtmlType == "radio") +$elseif(column.HtmlType.Contains("select")) @@ -65,6 +65,7 @@ $elseif(column.HtmlType.Contains("select") || column.HtmlType == "radio") $elseif(column.HtmlType == "radio") + 全部 {{item.dictLabel}} @@ -81,7 +82,7 @@ $end - + $if(replaceDto.ShowBtnAdd) @@ -108,6 +109,25 @@ $if(replaceDto.ShowBtnTruncate) $end +$if(replaceDto.ShowBtnImport) + + + + {{ ${t}t('btn.import') }} + + + + +$end $if(replaceDto.ShowBtnExport) @@ -118,13 +138,13 @@ $end - - $end -$if(null != genTable.SubTableName && "" != genTable.SubTableName && genTable.TplCategory == "subNav") - - - -$end -$if(null != genTable.SubTableName && "" != genTable.SubTableName && genTable.TplCategory == "subNavMore") - -