From 0a32754740d38ba1c53aa2240aa652bce46af1cd Mon Sep 17 00:00:00 2001 From: glz <694770232@qq.com> Date: Tue, 2 Jun 2026 17:58:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=AE=8C=E6=88=90=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E5=9F=BA=E7=A1=80=E6=9E=B6=E6=9E=84=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=B8=8E=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次提交包含多项核心变更: 1. **用户体系重构**:将管理员用户状态从字符串改为整型枚举,移除微信用户冗余字段Points和GrowthPoints,新增通用基础实体主键配置 2. **代码清理**:删除HealthController、WxUserMedal、WxUserBag等废弃文件,移除WeChatDto中冗余查询字段 3. **响应格式统一**:重构BaseResponse与ResultCode枚举,标准化全局响应格式 4. **权限与验证优化**:添加JWT认证与开发环境免认证逻辑,新增模型验证过滤器,统一控制器认证配置 5. **工具与配置更新**:新增dotnet-tools.json配置EF工具,调整Swagger与压缩中间件配置,优化SqlSugar默认值处理逻辑 6. **业务逻辑简化**:移除AutoMapper映射,改为手动映射DTO以提升性能,修复异常处理与响应返回逻辑 --- .../DependencyInjectionExtensions.cs | 50 +++- .../Middleware/GlobalExceptionMiddleware.cs | 2 +- .../Middleware/ModelValidActionFilter.cs | 45 ++++ .../Dto/Admin/AdminUserDto.cs | 4 +- .../Dto/Admin/AuthDto.cs | 2 +- .../Dto/BaseResponse.cs | 224 ++++++++++++++---- .../Dto/WeChat/WeChatDto.cs | 19 -- .../Entity/AdminUser.cs | 6 - .../Entity/SqlSugarBaseEntity.cs | 7 +- .../Entity/User .cs | 58 +++++ .../Entity/{WxUserBag.cs => UserBag.cs} | 6 +- .../Entity/{WxUserMedal.cs => UserMedal.cs} | 6 +- .../Entity/WxUser.cs | 12 - .../Core/SqlSugarExtension.cs | 4 +- .../AdminAuthService.cs | 2 +- .../AdminUserService.cs | 51 ++-- .../WxUserService.cs | 94 +++++--- .../Controllers/AdminController.cs | 23 +- .../Controllers/BaseController.cs | 9 +- .../Controllers/HealthController.cs | 22 -- .../Controllers/WxUserController.cs | 52 +++- QYZH.InteractiveMagazine.WebApi/Program.cs | 46 +++- .../QYZH.InteractiveMagazine.WebApi.csproj | 3 +- .../dotnet-tools.json | 13 + 24 files changed, 551 insertions(+), 209 deletions(-) create mode 100644 QYZH.InteractiveMagazine.Infrastructure/Middleware/ModelValidActionFilter.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/User .cs rename QYZH.InteractiveMagazine.Models/Entity/{WxUserBag.cs => UserBag.cs} (92%) rename QYZH.InteractiveMagazine.Models/Entity/{WxUserMedal.cs => UserMedal.cs} (90%) delete mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/HealthController.cs create mode 100644 QYZH.InteractiveMagazine.WebApi/dotnet-tools.json diff --git a/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs b/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs index 9a97769..19f6a2e 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs +++ b/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs @@ -1,10 +1,16 @@ +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using QYZH.InteractiveMagazine.Infrastructure.Middleware; using QYZH.InteractiveMagazine.Models.Settings; using System.Text; +using System.Text.Encodings.Web; namespace QYZH.InteractiveMagazine.Infrastructure.Extensions; @@ -16,11 +22,12 @@ public static class DependencyInjectionExtensions /// /// 注册基础设施服务 /// - /// 服务集合 + /// 服务集合 /// 配置 - public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) + /// 运行环境 + public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null) { - AddJwtAuthentication(services, configuration); + AddJwtAuthentication(services, configuration, environment); services.AddTransient(); services.AddTransient(); @@ -32,8 +39,17 @@ public static class DependencyInjectionExtensions /// /// 服务集合 /// 配置 - private static void AddJwtAuthentication(IServiceCollection services, IConfiguration configuration) + /// 运行环境 + private static void AddJwtAuthentication(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment? environment = null) { + // 开发环境下跳过 JWT 验证 + if (environment?.IsDevelopment() == true) + { + services.AddAuthentication("NoAuth") + .AddScheme("NoAuth", options => { }); + return; + } + var jwtSettings = configuration.GetSection("JwtSettings").Get()!; services.AddSingleton(jwtSettings); @@ -55,3 +71,29 @@ public static class DependencyInjectionExtensions }); } } + +/// +/// 开发环境免认证处理器 +/// +public class NoAuthHandler : AuthenticationHandler +{ + public NoAuthHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) + : base(options, logger, encoder, clock) + { + } + + protected override Task HandleAuthenticateAsync() + { + // 开发环境下始终认证成功 + var claims = new[] + { + new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Name, "DevUser"), + new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.NameIdentifier, "0") + }; + var identity = new System.Security.Claims.ClaimsIdentity(claims, Scheme.Name); + var principal = new System.Security.Claims.ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/Middleware/GlobalExceptionMiddleware.cs b/QYZH.InteractiveMagazine.Infrastructure/Middleware/GlobalExceptionMiddleware.cs index 4f0bef2..39b8601 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/Middleware/GlobalExceptionMiddleware.cs +++ b/QYZH.InteractiveMagazine.Infrastructure/Middleware/GlobalExceptionMiddleware.cs @@ -55,7 +55,7 @@ public class GlobalExceptionMiddleware : IMiddleware context.Response.ContentType = "application/json"; context.Response.StatusCode = StatusCodes.Status400BadRequest; - var response = BaseResponse.Fail(ex.Message, ex.Code); + var response = BaseResponse.Fail(ResultCode.FAIL,ex.Message); var json = JsonSerializer.Serialize(response); await context.Response.WriteAsync(json); diff --git a/QYZH.InteractiveMagazine.Infrastructure/Middleware/ModelValidActionFilter.cs b/QYZH.InteractiveMagazine.Infrastructure/Middleware/ModelValidActionFilter.cs new file mode 100644 index 0000000..0b208f3 --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/Middleware/ModelValidActionFilter.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using QYZH.InteractiveMagazine.Models.Dto; + + +namespace QYZH.InteractiveMagazine.Infrastructure.Middleware; + +/// +/// 数据验证过滤器 +/// +public class ModelValidActionFilterAttribute : ActionFilterAttribute +{ + public override void OnActionExecuting(ActionExecutingContext context) + { + if (!context.ModelState.IsValid) + { + Dictionary errorDic = new Dictionary(); + foreach (var key in context.ModelState.Keys) + { + var modelstate = context.ModelState[key]; + if (modelstate.Errors.Any()) + { + string errorStr = string.Join(",", modelstate.Errors.Select(e => e.ErrorMessage).ToList()); + errorDic.Add(key, errorStr); + } + } + var result = new BaseResponse>() { Code = ResultCode.FAIL }; + + result.Message = string.Join("|", errorDic.Select(e => e.Value).Distinct()); + result.Result = errorDic; + context.Result = new JsonResult(result); + } + } + + //public override void OnActionExecuted(ActionExecutedContext context) + //{ + // if (context.Exception != null) + // { + // BaseResponse result = new BaseResponse() { Code = ResultCode.Fail }; + // result.Message = context.Exception.Message; + // context.Result = new JsonResult(result); + // } + //} +} + diff --git a/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs index 51584b3..762eb56 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Admin/AdminUserDto.cs @@ -25,7 +25,7 @@ public class AdminUserInput /// /// 状态: Active, Inactive /// - public string Status { get; set; } = "Active"; + public int Status { get; set; } = 1; } /// @@ -51,7 +51,7 @@ public class AdminUserOutput /// /// 状态 /// - public string Status { get; set; } = string.Empty; + public int Status { get; set; } /// /// 创建人 diff --git a/QYZH.InteractiveMagazine.Models/Dto/Admin/AuthDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Admin/AuthDto.cs index 88214e8..1335868 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Admin/AuthDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Admin/AuthDto.cs @@ -36,5 +36,5 @@ public class AdminUserInfoOutput public string Type { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; + public int Status { get; set; } } diff --git a/QYZH.InteractiveMagazine.Models/Dto/BaseResponse.cs b/QYZH.InteractiveMagazine.Models/Dto/BaseResponse.cs index 23e569a..4e105ce 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/BaseResponse.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/BaseResponse.cs @@ -1,55 +1,199 @@ +using System.ComponentModel; +using System.DirectoryServices.Protocols; + namespace QYZH.InteractiveMagazine.Models.Dto; /// -/// 统一响应模型 +/// 操作响应基类 /// -/// 数据类型 -public class BaseResponse +public class BaseResponse { /// - /// 状态码 + /// 构造函数,初始化操作响应基类 /// - public int Code { get; set; } - - /// - /// 提示信息 - /// - public string? Message { get; set; } - - /// - /// 数据 - /// - public T? Data { get; set; } - - /// - /// 成功响应 - /// - /// 数据 - /// 提示信息 - /// 统一响应对象 - public static BaseResponse Success(T data, string message = "操作成功") + public BaseResponse() { - return new BaseResponse - { - Code = 200, - Message = message, - Data = data - }; + Code = ResultCode.SUCCESS; + } + + #region 公共属性 + + /// + /// 操作描述 + /// + public string Message { get; set; } + + /// + /// 结果Code + /// + public ResultCode Code { get; set; } + + #endregion 公共属性 + + #region 公用方法 + + /// + /// 成功 + /// + public static BaseResponse Success() + { + return new BaseResponse { Code = ResultCode.SUCCESS, Message = "操作成功。" }; } /// - /// 失败响应 + /// 成功 /// - /// 提示信息 - /// 状态码 - /// 统一响应对象 - public static BaseResponse Fail(string message, int code = 500) + public static BaseResponse Success(string message = "操作成功。") { - return new BaseResponse - { - Code = code, - Message = message, - Data = default - }; + return new BaseResponse { Code = ResultCode.SUCCESS, Message = message }; } + + /// + /// 失败 + /// + public static BaseResponse Fail(string message, ResultCode code = ResultCode.FAIL) + { + return new BaseResponse { Code = code, Message = message }; + } + + /// + /// 权限验证失败 + /// + public static BaseResponse AuthFail(string message, ResultCode code = ResultCode.FAIL) + { + return new BaseResponse { Code = code, Message = message }; + } + + #endregion 公用方法 + + /// + /// 操作结果 + /// + public bool IsSuccess => Code == ResultCode.SUCCESS; +} + +/// +/// 响应结果类型 +/// +/// +/// +public class BaseResponse : BaseResponse +{ + /// + /// 操作结果 + /// + public T? Result { get; set; } + + #region 公用方法 + + /// + /// 成功 + /// + public static BaseResponse Success(T result) + { + return new BaseResponse { Code = ResultCode.SUCCESS, Message = "", Result = result }; + } + + /// + /// 成功 + /// + public static BaseResponse Success(T result, string message = "操作成功。") + { + return new BaseResponse { Code = ResultCode.SUCCESS, Message = message, Result = result }; + } + + /// + /// 失败 + /// + public static BaseResponse Fail(string message = "fail") + { + return new BaseResponse { Code = ResultCode.FAIL, Message = message }; + } + + /// + /// 失败 自定义结果 + /// + public static BaseResponse Fail(ResultCode code, string message = "fail") + { + return new BaseResponse { Code = code, Message = message }; + } + + public static BaseResponse Fail(T result, string message = "操作失败,请稍后再试") + { + return new BaseResponse { Code = ResultCode.FAIL, Message = message, Result = result }; + } + + /// + /// 权限验证失败 + /// + public static BaseResponse AuthFail(string message = "Permission authentication failed") + { + return new BaseResponse { Code = ResultCode.OAUTH_FAIL, Message = message }; + } + + ///// + ///// 成功 + ///// + //public static BaseResponse Assert(T result, string message = "") + //{ + // if (result is bool b) + // return b ? BaseResponse.Success(result) : BaseResponse.Fail(message); + // if (result is object o) + // return (data == null || data.IsNull() == true) ? BaseResponse.Success(result) : BaseResponse.Fail(message); + // return new BaseResponse { Code = ResultCode.SUCCESS, Message = message, Result = result }; + //} + + + #endregion 公用方法 + + /// + /// 操作结果 + /// + public bool IsSuccess => Code == ResultCode.SUCCESS; +} + + +public enum ResultCode +{ + [Description("success")] + SUCCESS = 200, + + [Description("没有更多数据")] + NO_DATA = 210, + + [Description("参数错误")] + PARAM_ERROR = 101, + + [Description("验证码错误")] + CAPTCHA_ERROR = 103, + + [Description("登录错误")] + LOGIN_ERROR = 105, + + [Description("操作失败")] + FAIL = 1, + + [Description("服务端出错啦")] + GLOBAL_ERROR = 500, + + [Description("自定义异常")] + CUSTOM_ERROR = 110, + + [Description("非法请求")] + INVALID_REQUEST = 116, + + [Description("授权失败")] + OAUTH_FAIL = 201, + + [Description("请先绑定手机号")] + PHONE_BIND = 202, + + [Description("未授权")] + DENY = 401, + + [Description("授权访问失败")] + FORBIDDEN = 403, + + [Description("Bad Request")] + BAD_REQUEST = 400 } diff --git a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs index 9502b5e..9318662 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/WeChat/WeChatDto.cs @@ -175,24 +175,5 @@ public class WxUserQueryInput : PageQueryModel /// public string? Phone { get; set; } - /// - /// 用户类型 - /// - public string? Type { get; set; } - - /// - /// 状态 - /// - public string? Status { get; set; } } -/// -/// 微信手机号获取输出 -/// -public class WeChatPhoneNumberOutput -{ - /// - /// 手机号 - /// - public string PhoneNumber { get; set; } = string.Empty; -} diff --git a/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs index 9c625e5..3218802 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs @@ -34,11 +34,5 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// public string Type {get;set;} - /// - /// Desc:状态: Active, Inactive - /// Default:Active - /// Nullable:False - /// - public string Status {get;set;} } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs b/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs index 4de11ee..b563260 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/SqlSugarBaseEntity.cs @@ -11,6 +11,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:False /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "Id")] public long Id { get; set; } = YitIdHelper.NextId(); /// /// Desc:状态 0禁用 1启用 @@ -18,7 +19,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Nullable:False /// [SugarColumn(ColumnName = "Status")] - public string Status { get; set; } + public int Status { get; set; } /// /// Desc:是否删除 @@ -49,7 +50,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:True /// - [SugarColumn(ColumnName = "updatedBy", IsOnlyIgnoreInsert = true)] + [SugarColumn(ColumnName = "UpdatedBy")] public string UpdatedBy { get; set; } /// @@ -57,7 +58,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Default: /// Nullable:True /// - [SugarColumn(ColumnName = "UpdatedAt", IsOnlyIgnoreInsert = true)] + [SugarColumn(ColumnName = "UpdatedAt")] public DateTime? UpdatedAt { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Entity/User .cs b/QYZH.InteractiveMagazine.Models/Entity/User .cs new file mode 100644 index 0000000..ce975e9 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/User .cs @@ -0,0 +1,58 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///用户表 + /// + [SugarTable("User")] + public partial class User : SqlSugarBaseEntity + { + /// + /// Desc:微信用户ID + /// Default: + /// Nullable:True + /// + public string WxUserId { get; set; } + /// + /// Desc:昵称 + /// Default: + /// Nullable:True + /// + public string Name { get; set; } + /// + /// Desc:当前成长值 + /// Default:0 + /// Nullable:False + /// + public int GrowthPoints { get; set; } + /// + /// Desc:头像地址 + /// Default: + /// Nullable:True + /// + public string AvatarUrl { get; set; } + /// + /// Desc:积分余额 + /// Default:0 + /// Nullable:False + /// + public int Points { get; set; } + + /// + /// Desc:用户类型: Normal, VIP + /// Default:Normal + /// Nullable:False + /// + public string Type { get; set; } + + /// + /// Desc:状态: Active, Disabled + /// Default:Active + /// Nullable:False + /// + public string Status { get; set; } + + + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/WxUserBag.cs b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs similarity index 92% rename from QYZH.InteractiveMagazine.Models/Entity/WxUserBag.cs rename to QYZH.InteractiveMagazine.Models/Entity/UserBag.cs index 7f10440..a0f72c9 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/WxUserBag.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs @@ -5,10 +5,10 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// ///用户背包表 /// - [SugarTable("WxUserBag")] - public partial class WxUserBag : SqlSugarBaseEntity + [SugarTable("UserBag")] + public partial class UserBag : SqlSugarBaseEntity { - public WxUserBag(){ + public UserBag(){ } diff --git a/QYZH.InteractiveMagazine.Models/Entity/WxUserMedal.cs b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs similarity index 90% rename from QYZH.InteractiveMagazine.Models/Entity/WxUserMedal.cs rename to QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs index 884c68e..9332d6b 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/WxUserMedal.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs @@ -5,10 +5,10 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// ///用户勋章表 /// - [SugarTable("WxUserMedal")] - public partial class WxUserMedal : SqlSugarBaseEntity + [SugarTable("UserMedal")] + public partial class UserMedal : SqlSugarBaseEntity { - public WxUserMedal(){ + public UserMedal(){ } diff --git a/QYZH.InteractiveMagazine.Models/Entity/WxUser.cs b/QYZH.InteractiveMagazine.Models/Entity/WxUser.cs index 1fc011b..79b985a 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/WxUser.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/WxUser.cs @@ -43,12 +43,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// public string Phone { get; set; } - /// - /// Desc:积分余额 - /// Default:0 - /// Nullable:False - /// - public int Points { get; set; } /// /// Desc:用户类型: Normal, VIP @@ -70,11 +64,5 @@ namespace QYZH.InteractiveMagazine.Models.Entity /// Nullable:False /// public string Pwd { get; set; } - /// - /// Desc:当前成长值 - /// Default:0 - /// Nullable:False - /// - public int GrowthPoints { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Repository/Core/SqlSugarExtension.cs b/QYZH.InteractiveMagazine.Repository/Core/SqlSugarExtension.cs index f2c3a2d..47defe5 100644 --- a/QYZH.InteractiveMagazine.Repository/Core/SqlSugarExtension.cs +++ b/QYZH.InteractiveMagazine.Repository/Core/SqlSugarExtension.cs @@ -2,6 +2,7 @@ using SqlSugar; using System.Linq.Expressions; using System.Reflection; +using Yitter.IdGenerator; namespace QYZH.InteractiveMagazine.Repository.Core @@ -112,7 +113,7 @@ namespace QYZH.InteractiveMagazine.Repository.Core /// public static ISqlSugarClient SetDefaultValue(this ISqlSugarClient db) { - #region 创建人 创建时间 更新人 更新时间 默认值 + #region Id 创建人 创建时间 更新人 更新时间 默认值 db.Aop.DataExecuting = (oldValue, entityInfo) => { var entityValue = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue)?.ToString(); @@ -126,7 +127,6 @@ namespace QYZH.InteractiveMagazine.Repository.Core entityInfo.SetValue(currnetUserName);//修改创建人字段 else if (entityInfo.PropertyName == "IsDeleted" && string.IsNullOrWhiteSpace(entityValue)) entityInfo.SetValue("0");//修改CreateTime字段 - } /*** update生效 ***/ diff --git a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs index 655783f..3d40684 100644 --- a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs +++ b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs @@ -47,7 +47,7 @@ public class AdminAuthService(BaseRepository adminUserRepository, ICo throw new BusinessException("用户名或密码错误", 401); } - if (adminUser.Status != "Active") + if (adminUser.Status != 1) { logger.LogWarning("管理员登录失败,账号已禁用: {UserName}", input.UserName); throw new BusinessException("账号已被禁用,请联系系统管理员", 403); diff --git a/QYZH.InteractiveMagazine.Service/AdminUserService.cs b/QYZH.InteractiveMagazine.Service/AdminUserService.cs index 027ea62..0f27182 100644 --- a/QYZH.InteractiveMagazine.Service/AdminUserService.cs +++ b/QYZH.InteractiveMagazine.Service/AdminUserService.cs @@ -64,7 +64,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id); - return MapToOutput(adminUser); + return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type, Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; } /// @@ -105,10 +105,6 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo adminUser.Type = input.Type; } - if (!string.IsNullOrWhiteSpace(input.Status)) - { - adminUser.Status = input.Status; - } adminUser.UpdatedBy = "System"; adminUser.UpdatedAt = DateTime.Now; @@ -122,7 +118,7 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo logger.LogInformation("管理员更新成功,ID: {Id}", id); - return MapToOutput(adminUser); + return new AdminUserOutput { Id = adminUser.Id, UserName = adminUser.UserName, Type = adminUser.Type, Status = adminUser.Status, CreatedBy = adminUser.CreatedBy, CreatedAt = adminUser.CreatedAt, UpdatedBy = adminUser.UpdatedBy, UpdatedAt = adminUser.UpdatedAt }; } /// @@ -162,8 +158,17 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo logger.LogWarning("未找到管理员,ID: {Id}", id); throw new BusinessException("管理员不存在", 404); } - - return MapToOutput(adminUser); + return new AdminUserOutput + { + Id = adminUser.Id, + UserName = adminUser.UserName, + Type = adminUser.Type, + Status = adminUser.Status, + CreatedBy = adminUser.CreatedBy, + CreatedAt = adminUser.CreatedAt, + UpdatedBy = adminUser.UpdatedBy, + UpdatedAt = adminUser.UpdatedAt + }; } /// @@ -186,26 +191,18 @@ public class AdminUserService(BaseRepository adminUserRepository, ILo var pageResult = await adminUserRepository.Queryable() .WhereIF(!string.IsNullOrWhiteSpace(input.UserName), a => a.UserName == input.UserName) .OrderByDescending(a => a.CreatedAt) - .Select(a => MapToOutput(a), true) + .Select(a => new AdminUserOutput + { + Id = a.Id, + UserName = a.UserName, + Type = a.Type, + Status = a.Status, + CreatedBy = a.CreatedBy, + CreatedAt = a.CreatedAt, + UpdatedBy = a.UpdatedBy, + UpdatedAt = a.UpdatedAt + }, true) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber); } - - /// - /// 将实体映射为输出DTO - /// - private static AdminUserOutput MapToOutput(AdminUser adminUser) - { - return new AdminUserOutput - { - Id = adminUser.Id, - UserName = adminUser.UserName, - Type = adminUser.Type, - Status = adminUser.Status, - CreatedBy = adminUser.CreatedBy, - CreatedAt = adminUser.CreatedAt, - UpdatedBy = adminUser.UpdatedBy, - UpdatedAt = adminUser.UpdatedAt - }; - } } diff --git a/QYZH.InteractiveMagazine.Service/WxUserService.cs b/QYZH.InteractiveMagazine.Service/WxUserService.cs index 826de96..ab42cdb 100644 --- a/QYZH.InteractiveMagazine.Service/WxUserService.cs +++ b/QYZH.InteractiveMagazine.Service/WxUserService.cs @@ -37,11 +37,9 @@ public class WxUserService(BaseRepository wxUserRepository, ILogger wxUserRepository, ILogger UpdateAsync(long id, WxUserInput input) @@ -88,11 +100,9 @@ public class WxUserService(BaseRepository wxUserRepository, ILogger wxUserRepository, ILogger wxUserRepository, ILogger GetByIdAsync(long id) { - _logger.LogInformation("正在获取微信用户信息,ID: {Id}", id); - var wxUser = await wxUserRepository.GetByIdAsync(id); if (wxUser == null) { - _logger.LogWarning("未找到微信用户,ID: {Id}", id); + throw new BusinessException("微信用户不存在", 404); } - return MapToOutput(wxUser); + return new WxUserOutput + { + Id = wxUser.Id, + OpenId = wxUser.OpenId, + UnionId = wxUser.UnionId, + NickName = wxUser.NickName, + AvatarUrl = wxUser.AvatarUrl, + Phone = wxUser.Phone, + Type = wxUser.Type, + Status = wxUser.Status, + CreatedBy = wxUser.CreatedBy, + CreatedAt = wxUser.CreatedAt, + UpdatedBy = wxUser.UpdatedBy, + UpdatedAt = wxUser.UpdatedAt + }; } public async Task> GetListAsync(WxUserQueryInput input) { - _logger.LogInformation("正在查询微信用户列表,页码: {PageIndex}, 每页条数: {PageSize}", input.PageIndex, input.PageSize); if (input.PageIndex <= 0) { @@ -162,30 +197,25 @@ public class WxUserService(BaseRepository wxUserRepository, ILogger a.NickName == input.NickName) .OrderByDescending(a => a.CreatedAt) - .Select(a => MapToOutput(a), true) + .Select(wxUser => new WxUserOutput + { + Id = wxUser.Id, + OpenId = wxUser.OpenId, + UnionId = wxUser.UnionId, + NickName = wxUser.NickName, + AvatarUrl = wxUser.AvatarUrl, + Phone = wxUser.Phone, + Type = wxUser.Type, + Status = wxUser.Status, + CreatedBy = wxUser.CreatedBy, + CreatedAt = wxUser.CreatedAt, + UpdatedBy = wxUser.UpdatedBy, + UpdatedAt = wxUser.UpdatedAt + }, true) .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber); return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber); } - private static WxUserOutput MapToOutput(WxUser wxUser) - { - return new WxUserOutput - { - Id = wxUser.Id, - OpenId = wxUser.OpenId, - UnionId = wxUser.UnionId, - NickName = wxUser.NickName, - AvatarUrl = wxUser.AvatarUrl, - Phone = wxUser.Phone, - Points = wxUser.Points, - Type = wxUser.Type, - Status = wxUser.Status, - GrowthPoints = wxUser.GrowthPoints, - CreatedBy = wxUser.CreatedBy, - CreatedAt = wxUser.CreatedAt, - UpdatedBy = wxUser.UpdatedBy, - UpdatedAt = wxUser.UpdatedAt - }; - } + } diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs index 85cb6bf..608c6e6 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs @@ -8,6 +8,7 @@ using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.WebApi.Controllers; + [Route("api/[controller]")] [ApiController] public class AdminController : BaseController @@ -63,7 +64,7 @@ public class AdminController : BaseController var userId = GetCurrentUserId(); if (userId == null) { - return BaseResponse.Fail("未获取到用户信息", 401); + return BaseResponse.Fail(ResultCode.DENY, "未获取到用户信息"); } var result = await _adminAuthService.GetAdminInfoAsync(userId.Value); @@ -104,12 +105,12 @@ public class AdminController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "创建管理员业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "创建管理员系统异常,参数:{Input}", input); - return BaseResponse.Fail("创建管理员失败,请稍后重试", 500); + return BaseResponse.Fail("创建管理员失败,请稍后重试"); } } @@ -130,12 +131,12 @@ public class AdminController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "更新管理员业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "更新管理员系统异常,ID:{Id},参数:{Input}", id, input); - return BaseResponse.Fail("更新管理员失败,请稍后重试", 500); + return BaseResponse.Fail("更新管理员失败,请稍后重试"); } } @@ -155,12 +156,12 @@ public class AdminController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "删除管理员业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "删除管理员系统异常,ID:{Id}", id); - return BaseResponse.Fail("删除管理员失败,请稍后重试", 500); + return BaseResponse.Fail("删除管理员失败,请稍后重试"); } } @@ -180,12 +181,12 @@ public class AdminController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "获取管理员业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "获取管理员系统异常,ID:{Id}", id); - return BaseResponse.Fail("获取管理员信息失败,请稍后重试", 500); + return BaseResponse.Fail("获取管理员信息失败,请稍后重试"); } } @@ -205,12 +206,12 @@ public class AdminController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "查询管理员列表业务异常: {Message}", ex.Message); - return BaseResponse>.Fail(ex.Message, ex.Code); + return BaseResponse>.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "查询管理员列表系统异常,参数:{Input}", input); - return BaseResponse>.Fail("查询管理员列表失败,请稍后重试", 500); + return BaseResponse>.Fail("查询管理员列表失败,请稍后重试"); } } } diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs index 7a45cc7..87421f0 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs @@ -1,12 +1,15 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using QYZH.InteractiveMagazine.Models.Dto; using QYZH.InteractiveMagazine.Models.Enum; +using System.Security.Claims; namespace QYZH.InteractiveMagazine.WebApi.Controllers; /// /// 基础控制器 /// +[Authorize] [ApiController] [Route("api/[controller]")] [ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))] @@ -18,7 +21,7 @@ public abstract class BaseController : ControllerBase /// 用户ID protected long? GetCurrentUserId() { - var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == "userId"); + var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier); if (userIdClaim != null && long.TryParse(userIdClaim.Value, out var userId)) { return userId; @@ -32,7 +35,7 @@ public abstract class BaseController : ControllerBase /// 用户名 protected string? GetCurrentUserName() { - return User.Claims.FirstOrDefault(c => c.Type == "userName")?.Value; + return User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value; } /// @@ -55,6 +58,6 @@ public abstract class BaseController : ControllerBase /// 统一响应对象 protected BaseResponse Fail(string message, int code = 500) { - return BaseResponse.Fail(message, code); + return BaseResponse.Fail(ResultCode.GLOBAL_ERROR, message); } } diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/HealthController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/HealthController.cs deleted file mode 100644 index 3d07fee..0000000 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/HealthController.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using QYZH.InteractiveMagazine.Models.Dto; - -namespace QYZH.InteractiveMagazine.WebApi.Controllers; - -/// -/// 健康检查控制器 -/// -[Route("api/[controller]")] -[ApiController] -public class HealthController : BaseController -{ - /// - /// 健康检查 - /// - /// pong - [HttpGet("ping")] - public BaseResponse Ping() - { - return Success("pong"); - } -} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/WxUserController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/WxUserController.cs index 539e81b..c07bf9a 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/WxUserController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/WxUserController.cs @@ -6,6 +6,9 @@ using QYZH.InteractiveMagazine.Models.Dto; namespace QYZH.InteractiveMagazine.WebApi.Controllers; +/// +/// 微信用户控制器 +/// [Route("api/[controller]")] [ApiController] public class WxUserController : BaseController @@ -19,6 +22,11 @@ public class WxUserController : BaseController _logger = logger; } + /// + /// 创建微信用户 + /// + /// 创建微信用户输入参数 + /// 创建的微信用户输出参数 [HttpPost("users")] public async Task> CreateUserAsync([FromBody] WxUserInput input) { @@ -30,15 +38,21 @@ public class WxUserController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "创建微信用户业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "创建微信用户系统异常,参数:{Input}", input); - return BaseResponse.Fail("创建微信用户失败,请稍后重试", 500); + return BaseResponse.Fail("创建微信用户失败,请稍后重试"); } } + /// + /// 更新微信用户 + /// + /// 微信用户ID + /// 更新微信用户输入参数 + /// 更新的微信用户输出参数 [HttpPut("users/{id}")] public async Task> UpdateUserAsync(long id, [FromBody] WxUserInput input) { @@ -50,15 +64,19 @@ public class WxUserController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "更新微信用户业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "更新微信用户系统异常,ID:{Id},参数:{Input}", id, input); - return BaseResponse.Fail("更新微信用户失败,请稍后重试", 500); + return BaseResponse.Fail("更新微信用户失败,请稍后重试"); } } - + /// + /// 删除微信用户 + /// + /// 微信用户ID + /// 删除的微信用户输出参数 [HttpDelete("users/{id}")] public async Task> DeleteUserAsync(long id) { @@ -70,15 +88,19 @@ public class WxUserController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "删除微信用户业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "删除微信用户系统异常,ID:{Id}", id); - return BaseResponse.Fail("删除微信用户失败,请稍后重试", 500); + return BaseResponse.Fail("删除微信用户失败,请稍后重试"); } } - + /// + /// 获取微信用户信息 + /// + /// 微信用户ID + /// 微信用户信息输出参数 [HttpGet("users/{id}")] public async Task> GetUserByIdAsync(long id) { @@ -90,15 +112,19 @@ public class WxUserController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "获取微信用户业务异常: {Message}", ex.Message); - return BaseResponse.Fail(ex.Message, ex.Code); + return BaseResponse.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "获取微信用户系统异常,ID:{Id}", id); - return BaseResponse.Fail("获取微信用户信息失败,请稍后重试", 500); + return BaseResponse.Fail("获取微信用户信息失败,请稍后重试"); } } - + /// + /// 获取微信用户列表 + /// + /// 查询微信用户列表输入参数 + /// 微信用户列表输出参数 [HttpPost("users/list")] public async Task>> GetUsersListAsync([FromBody] WxUserQueryInput input) { @@ -110,12 +136,12 @@ public class WxUserController : BaseController catch (BusinessException ex) { _logger.LogWarning(ex, "查询微信用户列表业务异常: {Message}", ex.Message); - return BaseResponse>.Fail(ex.Message, ex.Code); + return BaseResponse>.Fail(ex.Message); } catch (Exception ex) { _logger.LogError(ex, "查询微信用户列表系统异常,参数:{Input}", input); - return BaseResponse>.Fail("查询微信用户列表失败,请稍后重试", 500); + return BaseResponse>.Fail("查询微信用户列表失败,请稍后重试"); } } } diff --git a/QYZH.InteractiveMagazine.WebApi/Program.cs b/QYZH.InteractiveMagazine.WebApi/Program.cs index f93ab28..93ea8b9 100644 --- a/QYZH.InteractiveMagazine.WebApi/Program.cs +++ b/QYZH.InteractiveMagazine.WebApi/Program.cs @@ -2,7 +2,9 @@ using Autofac; using Autofac.Extensions.DependencyInjection; using BCrypt.Net; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.ResponseCompression; using Microsoft.OpenApi; +using Microsoft.OpenApi.Models; using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.Common.Helpers; using QYZH.InteractiveMagazine.Infrastructure.Autofacs; @@ -46,9 +48,12 @@ builder.Host.UseSerilog(); builder.Services.AddControllers(options => { options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true;//Required 不作为必填 + options.Filters.Add();//添加自定义模型验证 }) .AddJsonOptions(options => { + // 配置 JSON 不区分大小写(支持 camelCase 和 PascalCase) + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; // 配置返回时间格式转换 options.JsonSerializerOptions.Converters.Add(new JsonConverterUtil.DateTimeConverter()); options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; @@ -94,7 +99,20 @@ builder.Services.AddSwaggerGen(option => BearerFormat = "JWT", Scheme = "Bearer" }); - + option.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] { } + } + }); // 过滤文档/路径/方法筛选接口的响应结果 option.DocInclusionPredicate((docName, apiDesc) => @@ -112,7 +130,7 @@ builder.Services.AddSwaggerGen(option => }); }); -builder.Services.AddInfrastructureServices(builder.Configuration); +builder.Services.AddInfrastructureServices(builder.Configuration, builder.Environment); //注册 HttpContextAccessor builder.Services.AddHttpContextAccessor(); @@ -128,7 +146,30 @@ builder.Services.AddCors(options => .AllowAnyHeader(); }); }); +#region 配置数据压缩选项 +// 1.首先配置压缩选项的Options> +builder.Services.Configure(options => +{ + options.Level = System.IO.Compression.CompressionLevel.Optimal; +}); +builder.Services.Configure(options => +{ + options.Level = System.IO.Compression.CompressionLevel.Fastest; +}); + +// 2.在服务容器中注册响应压缩服务 +builder.Services.AddResponseCompression(options => +{ + // 可以在这里进行详细配置 + options.EnableForHttps = true; // 启用对HTTPS响应的压缩(请注意安全风险) + options.Providers.Add(); + options.Providers.Add(); + //指定哪些类型的响应应该被压缩。默认列表包含常见的文本类类型,如 text/html, text/css, application/javascript, application/json, text/plain等 + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(["image/svg+xml", "application/json", "text/plain"]); +}); + +#endregion var app = builder.Build(); { @@ -143,6 +184,7 @@ var app = builder.Build(); c.DocExpansion(DocExpansion.None); // ->修改界面打开时自动折叠 }); } + app.UseServiceContext(); app.UseHttpsRedirection(); app.UseCors("AllowAll"); diff --git a/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj b/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj index 39d3e0c..387988d 100644 --- a/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj +++ b/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj @@ -11,8 +11,7 @@ - - + diff --git a/QYZH.InteractiveMagazine.WebApi/dotnet-tools.json b/QYZH.InteractiveMagazine.WebApi/dotnet-tools.json new file mode 100644 index 0000000..7dcefc3 --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.8", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file