From 831f8ba7f56bc3c13f6d4ddb7a8bbdcdecbb373e Mon Sep 17 00:00:00 2001 From: glz <694770232@qq.com> Date: Mon, 1 Jun 2026 17:59:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E7=AE=A1=E7=90=86=E6=A8=A1=E5=9D=97=E4=B8=8E=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E4=B8=9A=E5=8A=A1=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增依赖注入生命周期标记接口、基础仓储与管理员仓储实现 2. 新增管理员认证与用户服务接口,补充认证相关DTO 3. 重构实体审计字段命名,统一Created/UpdatedAt规范 4. 新增大量业务实体类与API版本枚举配置 5. 集成Autofac依赖注入、JWT自动刷新与跨域配置 6. 替换原有微信小程序与旧认证服务为后台管理系统架构 7. 完善Swagger文档配置与项目基础部署配置 --- .../Dto/AdminUserDto.cs | 96 +++++++ .../Dto/AuthDto.cs | 29 ++ .../IAdminAuthService.cs | 14 + .../IAdminUserService.cs | 45 +++ .../IAuthService.cs | 31 -- .../IDependency.cs | 15 + .../IWeChatMiniProgramService.cs | 23 -- .../Auth/JwtHelper.cs | 57 ++-- .../Autofac/AutofacExtension.cs | 67 +++++ .../Autofac/AutofacModuleRegister.cs | 42 +++ .../DependencyInjectionExtensions.cs | 55 +--- .../Middleware/CorsExtension.cs | 42 +++ .../Middleware/JwtAutoRefreshMiddleware.cs | 79 ++++++ ....InteractiveMagazine.Infrastructure.csproj | 5 +- .../Entity/AdminUser.cs | 51 ++++ .../Entity/BaseEntity.cs | 8 +- .../Entity/CheckInConfig.cs | 58 ++++ .../Entity/CheckInRecord.cs | 57 ++++ .../Entity/CommunityMessage.cs | 64 +++++ .../Entity/Journal.cs | 92 ++++++ .../Entity/Medal.cs | 79 ++++++ .../Entity/MessageComment.cs | 50 ++++ .../Entity/MessageLike.cs | 43 +++ QYZH.InteractiveMagazine.Models/Entity/Pet.cs | 64 +++++ .../Entity/PetEvolution.cs | 100 +++++++ .../Entity/PetFeedingRecord.cs | 71 +++++ .../Entity/PointsRecord.cs | 71 +++++ .../Entity/Product.cs | 86 ++++++ .../Entity/TemplateSentence.cs | 51 ++++ .../Entity/User.cs | 71 +++++ .../Entity/UserBag.cs | 64 +++++ .../Entity/UserMedal.cs | 50 ++++ .../Enum/ApiVersionEnum.cs | 26 ++ .../QYZH.InteractiveMagazine.Models.csproj | 3 +- .../AdminUserRepository.cs | 13 + .../BaseRepository.cs | 5 + .../IAdminUserRepository.cs | 8 + .../AdminAuthService.cs | 168 +++++++++++ .../AdminUserService.cs | 267 ++++++++++++++++++ .../AuthService.cs | 173 ------------ .../BaseService.cs | 10 +- .../QYZH.InteractiveMagazine.Service.csproj | 1 + .../WeChatMiniProgramService.cs | 167 ----------- .../Controllers/AdminController.cs | 205 ++++++++++++++ .../Controllers/AuthController.cs | 61 ---- .../Controllers/BaseController.cs | 2 + .../Controllers/WeChatController.cs | 35 --- QYZH.InteractiveMagazine.WebApi/Dockerfile | 19 ++ QYZH.InteractiveMagazine.WebApi/Program.cs | 96 ++++++- .../QYZH.InteractiveMagazine.WebApi.csproj | 8 + .../appsettings.json | 16 +- 51 files changed, 2417 insertions(+), 596 deletions(-) create mode 100644 QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs create mode 100644 QYZH.InteractiveMagazine.IService/IAdminAuthService.cs create mode 100644 QYZH.InteractiveMagazine.IService/IAdminUserService.cs delete mode 100644 QYZH.InteractiveMagazine.IService/IAuthService.cs create mode 100644 QYZH.InteractiveMagazine.IService/IDependency.cs delete mode 100644 QYZH.InteractiveMagazine.IService/IWeChatMiniProgramService.cs create mode 100644 QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs create mode 100644 QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacModuleRegister.cs create mode 100644 QYZH.InteractiveMagazine.Infrastructure/Middleware/CorsExtension.cs create mode 100644 QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/Journal.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/Medal.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/Pet.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/Product.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/User.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/UserBag.cs create mode 100644 QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs create mode 100644 QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs create mode 100644 QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs create mode 100644 QYZH.InteractiveMagazine.Repository/IAdminUserRepository.cs create mode 100644 QYZH.InteractiveMagazine.Service/AdminAuthService.cs create mode 100644 QYZH.InteractiveMagazine.Service/AdminUserService.cs delete mode 100644 QYZH.InteractiveMagazine.Service/AuthService.cs delete mode 100644 QYZH.InteractiveMagazine.Service/WeChatMiniProgramService.cs create mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs delete mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/AuthController.cs delete mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/WeChatController.cs create mode 100644 QYZH.InteractiveMagazine.WebApi/Dockerfile diff --git a/QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs b/QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs new file mode 100644 index 0000000..8f331e5 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs @@ -0,0 +1,96 @@ +using QYZH.InteractiveMagazine.Models.Dto; + +namespace QYZH.InteractiveMagazine.IService.Dto; + +/// +/// 管理员创建/更新输入 +/// +public class AdminUserInput +{ + /// + /// 用户名 + /// + public string UserName { get; set; } = string.Empty; + + /// + /// 密码(创建时必填,更新时为空表示不修改密码) + /// + public string? Password { get; set; } + + /// + /// 管理员类型: SuperAdmin, Editor + /// + public string Type { get; set; } = "Editor"; + + /// + /// 状态: Active, Inactive + /// + public string Status { get; set; } = "Active"; +} + +/// +/// 管理员输出 +/// +public class AdminUserOutput +{ + /// + /// 主键ID + /// + public long Id { get; set; } + + /// + /// 用户名 + /// + public string UserName { get; set; } = string.Empty; + + /// + /// 管理员类型 + /// + public string Type { get; set; } = string.Empty; + + /// + /// 状态 + /// + public string Status { get; set; } = string.Empty; + + /// + /// 创建人 + /// + public string? CreatedBy { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 更新人 + /// + public string? UpdatedBy { get; set; } + + /// + /// 更新时间 + /// + public DateTime? UpdatedAt { get; set; } +} + +/// +/// 管理员分页查询输入 +/// +public class AdminUserQueryInput : PageQueryModel +{ + /// + /// 用户名(模糊查询) + /// + public string? UserName { get; set; } + + /// + /// 管理员类型 + /// + public string? Type { get; set; } + + /// + /// 状态 + /// + public string? Status { get; set; } +} diff --git a/QYZH.InteractiveMagazine.IService/Dto/AuthDto.cs b/QYZH.InteractiveMagazine.IService/Dto/AuthDto.cs index 08a0d41..512eb85 100644 --- a/QYZH.InteractiveMagazine.IService/Dto/AuthDto.cs +++ b/QYZH.InteractiveMagazine.IService/Dto/AuthDto.cs @@ -62,3 +62,32 @@ public class LoginOutput /// public string RefreshToken { get; set; } = string.Empty; } + +public class AdminLoginInput +{ + public string UserName { get; set; } = string.Empty; + + public string Password { get; set; } = string.Empty; +} + +public class AdminLoginOutput +{ + public string Token { get; set; } = string.Empty; + + public long UserId { get; set; } + + public string UserName { get; set; } = string.Empty; + + public string Type { get; set; } = string.Empty; +} + +public class AdminUserInfoOutput +{ + public long UserId { get; set; } + + public string UserName { get; set; } = string.Empty; + + public string Type { get; set; } = string.Empty; + + public string Status { get; set; } = string.Empty; +} diff --git a/QYZH.InteractiveMagazine.IService/IAdminAuthService.cs b/QYZH.InteractiveMagazine.IService/IAdminAuthService.cs new file mode 100644 index 0000000..d04a986 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/IAdminAuthService.cs @@ -0,0 +1,14 @@ +using QYZH.InteractiveMagazine.IService.Dto; + +namespace QYZH.InteractiveMagazine.IService; + +public interface IAdminAuthService +{ + Task LoginAsync(AdminLoginInput input); + + Task LogoutAsync(long userId); + + Task GetAdminInfoAsync(long userId); + + Task ChangePasswordAsync(long userId, string oldPassword, string newPassword); +} diff --git a/QYZH.InteractiveMagazine.IService/IAdminUserService.cs b/QYZH.InteractiveMagazine.IService/IAdminUserService.cs new file mode 100644 index 0000000..9849629 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/IAdminUserService.cs @@ -0,0 +1,45 @@ +using QYZH.InteractiveMagazine.IService.Dto; +using QYZH.InteractiveMagazine.Models.Dto; + +namespace QYZH.InteractiveMagazine.IService; + +/// +/// 管理员用户服务接口 +/// +public interface IAdminUserService +{ + /// + /// 创建管理员 + /// + /// 管理员输入 + /// 创建的管理员信息 + Task CreateAsync(AdminUserInput input); + + /// + /// 更新管理员 + /// + /// 管理员ID + /// 管理员输入 + /// 更新后的管理员信息 + Task UpdateAsync(long id, AdminUserInput input); + + /// + /// 删除管理员(软删除) + /// + /// 管理员ID + Task DeleteAsync(long id); + + /// + /// 根据ID获取管理员 + /// + /// 管理员ID + /// 管理员信息 + Task GetByIdAsync(long id); + + /// + /// 分页查询管理员列表 + /// + /// 查询条件 + /// 分页结果 + Task> GetListAsync(AdminUserQueryInput input); +} diff --git a/QYZH.InteractiveMagazine.IService/IAuthService.cs b/QYZH.InteractiveMagazine.IService/IAuthService.cs deleted file mode 100644 index f1ea5a2..0000000 --- a/QYZH.InteractiveMagazine.IService/IAuthService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using QYZH.InteractiveMagazine.IService.Dto; - -namespace QYZH.InteractiveMagazine.IService; - -/// -/// 认证服务接口 -/// -public interface IAuthService -{ - /// - /// 用户登录 - /// - /// 账号 - /// 密码 - /// 登录结果 - Task LoginAsync(string account, string password); - - /// - /// 用户注册 - /// - /// 注册输入参数 - /// 是否成功 - Task RegisterAsync(RegisterInput input); - - /// - /// 刷新令牌 - /// - /// 刷新令牌 - /// 新的登录结果 - Task RefreshTokenAsync(string refreshToken); -} diff --git a/QYZH.InteractiveMagazine.IService/IDependency.cs b/QYZH.InteractiveMagazine.IService/IDependency.cs new file mode 100644 index 0000000..6ebb143 --- /dev/null +++ b/QYZH.InteractiveMagazine.IService/IDependency.cs @@ -0,0 +1,15 @@ +namespace QYZH.InteractiveMagazine.IService; + +/// +/// 单例生命周期标记接口 +/// +public interface ISingletonDependency +{ +} + +/// +/// 瞬时生命周期标记接口 +/// +public interface ITransientDependency +{ +} diff --git a/QYZH.InteractiveMagazine.IService/IWeChatMiniProgramService.cs b/QYZH.InteractiveMagazine.IService/IWeChatMiniProgramService.cs deleted file mode 100644 index 6559d8c..0000000 --- a/QYZH.InteractiveMagazine.IService/IWeChatMiniProgramService.cs +++ /dev/null @@ -1,23 +0,0 @@ -using QYZH.InteractiveMagazine.IService.Dto; - -namespace QYZH.InteractiveMagazine.IService; - -/// -/// 微信小程序服务接口 -/// -public interface IWeChatMiniProgramService -{ - /// - /// 微信登录 - /// - /// 微信登录凭证 - /// 微信登录结果 - Task WeChatLoginAsync(string code); - - /// - /// 获取手机号 - /// - /// 获取手机号凭证 - /// 手机号信息 - Task GetPhoneNumberAsync(string code); -} diff --git a/QYZH.InteractiveMagazine.Infrastructure/Auth/JwtHelper.cs b/QYZH.InteractiveMagazine.Infrastructure/Auth/JwtHelper.cs index 683426f..71b8f0a 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/Auth/JwtHelper.cs +++ b/QYZH.InteractiveMagazine.Infrastructure/Auth/JwtHelper.cs @@ -44,28 +44,51 @@ public static class JwtHelper } /// - /// 验证JWT令牌 + /// 获取Token过期时间 /// /// JWT令牌 - /// JWT配置 - /// ClaimsPrincipal对象 - public static ClaimsPrincipal ValidateToken(string token, JwtSettings settings) + /// 过期时间 + public static DateTime? GetTokenExpiry(string token) { var tokenHandler = new JwtSecurityTokenHandler(); - var key = Encoding.UTF8.GetBytes(settings.SecretKey!); - - var validationParameters = new TokenValidationParameters + if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken) { - ValidateIssuer = true, - ValidIssuer = settings.Issuer, - ValidateAudience = true, - ValidAudience = settings.Audience, - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(key), - ValidateLifetime = true, - ClockSkew = TimeSpan.Zero - }; + return jwtToken.ValidTo; + } + return null; + } - return tokenHandler.ValidateToken(token, validationParameters, out _); + /// + /// 从Token中获取用户ID + /// + /// JWT令牌 + /// 用户ID + public static long? GetUserIdFromToken(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken) + { + var userIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier); + if (long.TryParse(userIdClaim?.Value, out long userId)) + { + return userId; + } + } + return null; + } + + /// + /// 从Token中获取用户名 + /// + /// JWT令牌 + /// 用户名 + public static string GetUserNameFromToken(string token) + { + var tokenHandler = new JwtSecurityTokenHandler(); + if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken) + { + return jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? string.Empty; + } + return string.Empty; } } diff --git a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs new file mode 100644 index 0000000..e0a86be --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacExtension.cs @@ -0,0 +1,67 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using QYZH.InteractiveMagazine.Infrastructure.Cache; +using QYZH.InteractiveMagazine.Infrastructure.MessageQueue; +using QYZH.InteractiveMagazine.Models.Settings; +using RabbitMQ.Client; +using StackExchange.Redis; + +namespace QYZH.InteractiveMagazine.Infrastructure.Autofacs +{ + public static class AutofacExtension + { + + /// + /// 根据程序集名称获取程序集 + /// + /// Web应用程序构建器 + public static void UseAutofac(this WebApplicationBuilder builder) + { + builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()) + .ConfigureContainer((c, containerBuilder) => + { + var friendlyName = AppDomain.CurrentDomain.FriendlyName; + var source = friendlyName.Split('.'); + var assemblyNames = string.Join(".", source.Take(source.Length - 1)); + containerBuilder.RegisterModule(new AutofacModuleRegister(assemblyNames)); + + InitializeRedis(c.Configuration); + InitializeRabbitMQ(c.Configuration, containerBuilder); + }); + } + + private static void InitializeRedis(IConfiguration configuration) + { + var redisSettings = configuration.GetSection("RedisSettings").Get(); + if (redisSettings != null && !string.IsNullOrWhiteSpace(redisSettings.ConnectionString)) + { + var multiplexer = ConnectionMultiplexer.Connect(redisSettings.ConnectionString); + RedisHelper.Connection = multiplexer; + RedisHelper.SetKeyPrefix(redisSettings.InstanceName ?? string.Empty); + } + } + + private static void InitializeRabbitMQ(IConfiguration configuration, ContainerBuilder containerBuilder) + { + var rabbitMQSettings = configuration.GetSection("RabbitMQSettings").Get(); + if (rabbitMQSettings != null && !string.IsNullOrWhiteSpace(rabbitMQSettings.HostName)) + { + var factory = new ConnectionFactory + { + HostName = rabbitMQSettings.HostName, + Port = rabbitMQSettings.Port, + UserName = rabbitMQSettings.UserName ?? string.Empty, + Password = rabbitMQSettings.Password ?? string.Empty, + VirtualHost = rabbitMQSettings.VirtualHost ?? string.Empty + }; + + var connection = factory.CreateConnectionAsync().GetAwaiter().GetResult(); + + containerBuilder.RegisterInstance(connection).As().SingleInstance(); + containerBuilder.RegisterType().InstancePerDependency(); + } + } + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacModuleRegister.cs b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacModuleRegister.cs new file mode 100644 index 0000000..06fcdfe --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/Autofac/AutofacModuleRegister.cs @@ -0,0 +1,42 @@ +using Autofac; +using System.Reflection; + +namespace QYZH.InteractiveMagazine.Infrastructure.Autofacs +{ + public class AutofacModuleRegister : Autofac.Module + { + private readonly string _assemblyName; + public AutofacModuleRegister(string assemblyNames) + { + _assemblyName = assemblyNames; + } + + /// + /// 加在程序集 + /// + /// + protected override void Load(ContainerBuilder builder) + { + //注册Repository(只注册接口,遵循依赖倒置原则) + builder.RegisterAssemblyTypes(GetAssemblyByName($"{_assemblyName}.Repository")) + .Where(t => t.Name.EndsWith("Repository") && !t.IsAbstract) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + + //注册Service + builder.RegisterAssemblyTypes(GetAssemblyByName($"{_assemblyName}.Service")) + .Where(t => t.Name.EndsWith("Service") && !t.IsAbstract) + .AsImplementedInterfaces() + .InstancePerLifetimeScope(); + } + + /// + /// 根据程序集名称获取程序集 + /// + /// 程序集名称 + public static Assembly GetAssemblyByName(string AssemblyName) + { + return Assembly.Load(AssemblyName); + } + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs b/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs index a172a40..9a97769 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs +++ b/QYZH.InteractiveMagazine.Infrastructure/Extensions/DependencyInjectionExtensions.cs @@ -2,12 +2,8 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; -using QYZH.InteractiveMagazine.Infrastructure.Cache; -using QYZH.InteractiveMagazine.Infrastructure.MessageQueue; using QYZH.InteractiveMagazine.Infrastructure.Middleware; using QYZH.InteractiveMagazine.Models.Settings; -using RabbitMQ.Client; -using StackExchange.Redis; using System.Text; namespace QYZH.InteractiveMagazine.Infrastructure.Extensions; @@ -25,11 +21,10 @@ public static class DependencyInjectionExtensions public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) { AddJwtAuthentication(services, configuration); - AddRedisCache(services, configuration); - AddRabbitMQ(services, configuration); services.AddTransient(); services.AddTransient(); + } /// @@ -59,52 +54,4 @@ public static class DependencyInjectionExtensions }; }); } - - /// - /// 注册Redis缓存 - /// - /// 服务集合 - /// 配置 - private static void AddRedisCache(IServiceCollection services, IConfiguration configuration) - { - var redisSettings = configuration.GetSection("RedisSettings").Get()!; - - services.AddSingleton(redisSettings); - - services.AddSingleton(sp => - { - var multiplexer = ConnectionMultiplexer.Connect(redisSettings.ConnectionString!); - RedisHelper.Connection = multiplexer; - RedisHelper.SetKeyPrefix(redisSettings.InstanceName!); - return multiplexer; - }); - } - - /// - /// 注册RabbitMQ - /// - /// 服务集合 - /// 配置 - private static void AddRabbitMQ(IServiceCollection services, IConfiguration configuration) - { - var rabbitMQSettings = configuration.GetSection("RabbitMQSettings").Get()!; - - services.AddSingleton(rabbitMQSettings); - - services.AddSingleton(sp => - { - var factory = new ConnectionFactory - { - HostName = rabbitMQSettings.HostName!, - Port = rabbitMQSettings.Port, - UserName = rabbitMQSettings.UserName!, - Password = rabbitMQSettings.Password!, - VirtualHost = rabbitMQSettings.VirtualHost! - }; - - return factory.CreateConnectionAsync().GetAwaiter().GetResult(); - }); - - services.AddTransient(); - } } diff --git a/QYZH.InteractiveMagazine.Infrastructure/Middleware/CorsExtension.cs b/QYZH.InteractiveMagazine.Infrastructure/Middleware/CorsExtension.cs new file mode 100644 index 0000000..c1be727 --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/Middleware/CorsExtension.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace QYZH.InteractiveMagazine.Infrastructure.Middleware +{ + /// + /// 跨域扩展 + /// + public static class CorsExtension + { + /// + /// 跨域配置 + /// + /// + /// + public static void AddCorsRegister(this WebApplicationBuilder builder) + { + bool isCorsAll = builder.Configuration["corsUrls"] == "*"; + builder.Services.AddCors(options => + { + options.AddPolicy("defaultCors", policy => + { + if (isCorsAll) + { + policy.SetIsOriginAllowed(origin => true); + } + else + { + var corsUrls = builder.Configuration.GetSection("corsUrls").Get(); + policy.WithOrigins(corsUrls ?? Array.Empty()); + } + policy.AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials() + .WithExposedHeaders("X-New-Token"); + + }); + }); + } + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs b/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs new file mode 100644 index 0000000..6679d05 --- /dev/null +++ b/QYZH.InteractiveMagazine.Infrastructure/Middleware/JwtAutoRefreshMiddleware.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using QYZH.InteractiveMagazine.Infrastructure.Cache; +using QYZH.InteractiveMagazine.Models.Settings; +using System.IdentityModel.Tokens.Jwt; + +namespace QYZH.InteractiveMagazine.Infrastructure.Middleware; + +public class JwtAutoRefreshMiddleware +{ + private readonly RequestDelegate _next; + private readonly IConfiguration _configuration; + private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token"; + private const int RefreshThresholdMinutes = 10; + + public JwtAutoRefreshMiddleware(RequestDelegate next, IConfiguration configuration) + { + _next = next; + _configuration = configuration; + } + + public async Task InvokeAsync(HttpContext context) + { + var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); + if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ")) + { + var token = authHeader.Substring("Bearer ".Length).Trim(); + await TryAutoRefreshTokenAsync(context, token); + } + + await _next(context); + } + + private async Task TryAutoRefreshTokenAsync(HttpContext context, string token) + { + try + { + var tokenHandler = new JwtSecurityTokenHandler(); + if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken) + { + return; + } + + var expiryTime = jwtToken.ValidTo; + var remainingTime = expiryTime - DateTime.Now; + + if (remainingTime <= TimeSpan.FromMinutes(RefreshThresholdMinutes) && remainingTime > TimeSpan.Zero) + { + var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + var userName = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value; + + if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(userName)) + { + return; + } + + var jwtSettings = _configuration.GetSection("JwtSettings").Get(); + if (jwtSettings == null) + { + return; + } + + var newToken = QYZH.InteractiveMagazine.Infrastructure.Auth.JwtHelper.GenerateToken( + long.Parse(userId), userName, jwtSettings); + + await RedisHelper.StringSetAsync( + $"{TokenKeyPrefix}:{userId}", + newToken, + TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); + + context.Response.Headers["X-New-Token"] = newToken; + } + } + catch + { + // 忽略自动刷新异常,由后续认证中间件处理 + } + } +} diff --git a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj index 14eab45..61c1cf7 100644 --- a/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj +++ b/QYZH.InteractiveMagazine.Infrastructure/QYZH.InteractiveMagazine.Infrastructure.csproj @@ -1,11 +1,14 @@ - + + + + diff --git a/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs new file mode 100644 index 0000000..9e391b6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs @@ -0,0 +1,51 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///后台管理员表 + /// + [SugarTable("AdminUser")] + public partial class AdminUser : BaseEntity + { + public AdminUser(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id { get; set; } + + /// + /// Desc:用户名 + /// Default: + /// Nullable:False + /// + public string UserName {get;set;} + + /// + /// Desc:密码哈希 + /// Default: + /// Nullable:False + /// + public string PasswordHash {get;set;} + + /// + /// Desc:管理员类型: SuperAdmin, Editor + /// Default:Editor + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Inactive + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/BaseEntity.cs b/QYZH.InteractiveMagazine.Models/Entity/BaseEntity.cs index 8d0678f..9558235 100644 --- a/QYZH.InteractiveMagazine.Models/Entity/BaseEntity.cs +++ b/QYZH.InteractiveMagazine.Models/Entity/BaseEntity.cs @@ -23,21 +23,21 @@ public abstract class BaseEntity /// 创建人 /// [SugarColumn(Length = 50)] - public string? CreatedBy { get; set; } + public string? CreatedBy { get; set; } = "System"; /// /// 创建时间 /// - public DateTime CreatedTime { get; set; } = DateTime.Now; + public DateTime CreatedAt { get; set; } = DateTime.Now; /// /// 更新人 /// [SugarColumn(Length = 50)] - public string? UpdatedBy { get; set; } + public string? UpdatedBy { get; set; } = "System"; /// /// 更新时间 /// - public DateTime UpdatedTime { get; set; } = DateTime.Now; + public DateTime? UpdatedAt { get; set; } = DateTime.Now; } diff --git a/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs b/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs new file mode 100644 index 0000000..c3e21c7 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs @@ -0,0 +1,58 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///签到配置表 + /// + [SugarTable("CheckInConfig")] + public partial class CheckInConfig : BaseEntity + { + public CheckInConfig(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id {get;set;} + + /// + /// Desc:连续签到天数 + /// Default: + /// Nullable:False + /// + public int DayNumber {get;set;} + + /// + /// Desc:奖励积分数 + /// Default: + /// Nullable:False + /// + public int RewardPoints {get;set;} + + /// + /// Desc:额外奖励积分 + /// Default:0 + /// Nullable:False + /// + public int BonusPoints {get;set;} + + /// + /// Desc:配置类型: Daily, Streak + /// Default:Daily + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Inactive + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs new file mode 100644 index 0000000..6e45624 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs @@ -0,0 +1,57 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///签到记录表 + /// + [SugarTable("CheckInRecord")] + public partial class CheckInRecord : BaseEntity + { + public CheckInRecord(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:签到日期 + /// Default: + /// Nullable:False + /// + public DateTime CheckInDate {get;set;} + + /// + /// Desc:本次签到获得积分 + /// Default: + /// Nullable:False + /// + public int PointsAwarded {get;set;} + + /// + /// Desc:连续签到天数 + /// Default: + /// Nullable:False + /// + public int ConsecutiveDays {get;set;} + + /// + /// Desc:签到类型: Normal, MakeUp + /// Default:Normal + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Success + /// Default:Success + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs new file mode 100644 index 0000000..bf99952 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs @@ -0,0 +1,64 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///社区预置消息表 + /// + [SugarTable("CommunityMessage")] + public partial class CommunityMessage : BaseEntity + { + public CommunityMessage(){ + + + } + /// + /// Desc:期刊Id + /// Default: + /// Nullable:False + /// + public long JournalId {get;set;} + + /// + /// Desc:消息内容 + /// Default: + /// Nullable:False + /// + public string Content {get;set;} + + /// + /// Desc:配图 + /// Default: + /// Nullable:True + /// + public string ImageUrl {get;set;} + + /// + /// Desc:排序 + /// Default:0 + /// Nullable:False + /// + public int SortOrder {get;set;} + + /// + /// Desc:是否启用 + /// Default:b'1' + /// Nullable:False + /// + public bool IsActive {get;set;} + + /// + /// Desc:消息类型: Article, Quote, Announcement + /// Default:Article + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Draft, Published + /// Default:Draft + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/Journal.cs b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs new file mode 100644 index 0000000..05d39ec --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/Journal.cs @@ -0,0 +1,92 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///期刊表 + /// + [SugarTable("Journal")] + public partial class Journal : BaseEntity + { + public Journal(){ + + + } + /// + /// Desc:期刊号 + /// Default: + /// Nullable:False + /// + public int IssueNumber {get;set;} + + /// + /// Desc:期刊标题 + /// Default: + /// Nullable:False + /// + public string Title {get;set;} + + /// + /// Desc:封面图地址 + /// Default: + /// Nullable:True + /// + public string CoverImageUrl {get;set;} + + /// + /// Desc:摘要 + /// Default: + /// Nullable:True + /// + public string Summary {get;set;} + + /// + /// Desc:发布时间 + /// Default: + /// Nullable:True + /// + public DateTime? PublishDate {get;set;} + + /// + /// Desc:是否启用 + /// Default:b'1' + /// Nullable:False + /// + public bool IsActive {get;set;} + + /// + /// Desc:排序权重 + /// Default:0 + /// Nullable:False + /// + public int SortOrder {get;set;} + + /// + /// Desc:编者寄语 + /// Default: + /// Nullable:True + /// + public string EditorNote {get;set;} + + /// + /// Desc:主题色 + /// Default: + /// Nullable:True + /// + public string ThemeColor {get;set;} + + /// + /// Desc:期刊类型: Normal, Special + /// Default:Normal + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Draft, Published, Archived + /// Default:Draft + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/Medal.cs b/QYZH.InteractiveMagazine.Models/Entity/Medal.cs new file mode 100644 index 0000000..aa48eaa --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/Medal.cs @@ -0,0 +1,79 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///勋章定义表 + /// + [SugarTable("Medal")] + public partial class Medal : BaseEntity + { + public Medal(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id {get;set;} + + /// + /// Desc:勋章名称 + /// Default: + /// Nullable:False + /// + public string Name {get;set;} + + /// + /// Desc:获得条件描述 + /// Default: + /// Nullable:True + /// + public string Description {get;set;} + + /// + /// Desc:图片地址 + /// Default: + /// Nullable:True + /// + public string ImageUrl {get;set;} + + /// + /// Desc:条件类型: EvolutionCount, FeedingCount等 + /// Default: + /// Nullable:False + /// + public string ConditionType {get;set;} + + /// + /// Desc:条件阈值 + /// Default: + /// Nullable:False + /// + public int ConditionValue {get;set;} + + /// + /// Desc:排序 + /// Default:0 + /// Nullable:False + /// + public int SortOrder {get;set;} + + /// + /// Desc:勋章的类型: Pet, Community + /// Default:Pet + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Inactive + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs b/QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs new file mode 100644 index 0000000..a1d0f03 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs @@ -0,0 +1,50 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///模板评论表 + /// + [SugarTable("MessageComment")] + public partial class MessageComment : BaseEntity + { + public MessageComment(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:消息Id + /// Default: + /// Nullable:False + /// + public long MessageId {get;set;} + + /// + /// Desc:模板Id + /// Default: + /// Nullable:False + /// + public int TemplateId {get;set;} + + /// + /// Desc:评论类型 + /// Default:TemplateComment + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Published, Hidden + /// Default:Published + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs b/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs new file mode 100644 index 0000000..55a87db --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs @@ -0,0 +1,43 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///点赞记录表 + /// + [SugarTable("MessageLike")] + public partial class MessageLike : BaseEntity + { + public MessageLike(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:消息Id + /// Default: + /// Nullable:False + /// + public long MessageId {get;set;} + + /// + /// Desc:点赞类型 + /// Default:Like + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Liked, Cancelled + /// Default:Liked + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/Pet.cs b/QYZH.InteractiveMagazine.Models/Entity/Pet.cs new file mode 100644 index 0000000..180175d --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/Pet.cs @@ -0,0 +1,64 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///宠物实例表 + /// + [SugarTable("Pet")] + public partial class Pet : BaseEntity + { + public Pet(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:宠物昵称 + /// Default: + /// Nullable:True + /// + public string Name {get;set;} + + /// + /// Desc:当前进化形态Id + /// Default: + /// Nullable:False + /// + public int CurrentEvolutionId {get;set;} + + /// + /// Desc:当前成长值 + /// Default:0 + /// Nullable:False + /// + public int GrowthPoints {get;set;} + + /// + /// Desc:累计喂养次数 + /// Default:0 + /// Nullable:False + /// + public int FeedingCount {get;set;} + + /// + /// Desc:宠物类型 + /// Default:Normal + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Sleeping + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs new file mode 100644 index 0000000..7b8f9f0 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs @@ -0,0 +1,100 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///宠物进化链定义表 + /// + [SugarTable("PetEvolution")] + public partial class PetEvolution : BaseEntity + { + public PetEvolution(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id {get;set;} + + /// + /// Desc:阶段名称 + /// Default: + /// Nullable:False + /// + public string StageName {get;set;} + + /// + /// Desc:阶段等级 + /// Default: + /// Nullable:False + /// + public int StageLevel {get;set;} + + /// + /// Desc:进化所需成长值 + /// Default: + /// Nullable:False + /// + public int RequiredGrowth {get;set;} + + /// + /// Desc:形态图片 + /// Default: + /// Nullable:True + /// + public string ImageUrl {get;set;} + + /// + /// Desc:前一形态Id + /// Default: + /// Nullable:True + /// + public int? PreviousEvolutionId {get;set;} + + /// + /// Desc:基础力量 + /// Default:0 + /// Nullable:False + /// + public int BaseStrength {get;set;} + + /// + /// Desc:基础敏捷 + /// Default:0 + /// Nullable:False + /// + public int BaseAgility {get;set;} + + /// + /// Desc:基础智力 + /// Default:0 + /// Nullable:False + /// + public int BaseIntelligence {get;set;} + + /// + /// Desc:基础魅力 + /// Default:0 + /// Nullable:False + /// + public int BaseCharm {get;set;} + + /// + /// Desc:进化类型: Normal, Special + /// Default:Normal + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Inactive + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs new file mode 100644 index 0000000..02465db --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs @@ -0,0 +1,71 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///宠物喂养记录表 + /// + [SugarTable("PetFeedingRecord")] + public partial class PetFeedingRecord : BaseEntity + { + public PetFeedingRecord(){ + + + } + /// + /// Desc:宠物Id + /// Default: + /// Nullable:False + /// + public long PetId {get;set;} + + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:消耗积分 + /// Default: + /// Nullable:False + /// + public int PointsUsed {get;set;} + + /// + /// Desc:成长值变化量 + /// Default: + /// Nullable:False + /// + public int GrowthChange {get;set;} + + /// + /// Desc:喂养前成长值 + /// Default: + /// Nullable:False + /// + public int GrowthBefore {get;set;} + + /// + /// Desc:喂养后成长值 + /// Default: + /// Nullable:False + /// + public int GrowthAfter {get;set;} + + /// + /// Desc:喂养类型: Normal, Special + /// Default:Normal + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态 + /// Default:Success + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs b/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs new file mode 100644 index 0000000..30cb5eb --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs @@ -0,0 +1,71 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///积分流水记录表 + /// + [SugarTable("PointsRecord")] + public partial class PointsRecord : BaseEntity + { + public PointsRecord(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:变动数值 + /// Default: + /// Nullable:False + /// + public int ChangeAmount {get;set;} + + /// + /// Desc:变动后余额 + /// Default: + /// Nullable:False + /// + public int BalanceAfter {get;set;} + + /// + /// Desc:变动类型: Exchange, FeedPet, SignIn, TaskReward + /// Default: + /// Nullable:False + /// + public string ChangeType {get;set;} + + /// + /// Desc:关联业务Id + /// Default: + /// Nullable:True + /// + public long? RelatedId {get;set;} + + /// + /// Desc:备注 + /// Default: + /// Nullable:True + /// + public string Description {get;set;} + + /// + /// Desc:流水分类 + /// Default: + /// Nullable:True + /// + public string Type {get;set;} + + /// + /// Desc:状态: Success, Failed, Pending + /// Default:Success + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/Product.cs b/QYZH.InteractiveMagazine.Models/Entity/Product.cs new file mode 100644 index 0000000..2160df3 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/Product.cs @@ -0,0 +1,86 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///虚拟商品表 + /// + [SugarTable("Product")] + public partial class Product : BaseEntity + { + public Product(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id {get;set;} + + /// + /// Desc:商品名称 + /// Default: + /// Nullable:False + /// + public string Name {get;set;} + + /// + /// Desc:描述 + /// Default: + /// Nullable:True + /// + public string Description {get;set;} + + /// + /// Desc:商品图片 + /// Default: + /// Nullable:True + /// + public string ImageUrl {get;set;} + + /// + /// Desc:所需积分 + /// Default: + /// Nullable:False + /// + public int Price {get;set;} + + /// + /// Desc:商品类型: MakeUpCard, PetBg + /// Default: + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: OnSale, OffSale + /// Default:OnSale + /// Nullable:False + /// + public string Status {get;set;} + + /// + /// Desc:扩展数据 + /// Default: + /// Nullable:True + /// + public string MetaData {get;set;} + + /// + /// Desc:是否上架 + /// Default:b'1' + /// Nullable:False + /// + public bool IsActive {get;set;} + + /// + /// Desc:库存(-1无限) + /// Default:-1 + /// Nullable:False + /// + public int Stock {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs b/QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs new file mode 100644 index 0000000..acf5f66 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs @@ -0,0 +1,51 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///固定模板言论表 + /// + [SugarTable("TemplateSentence")] + public partial class TemplateSentence : BaseEntity + { + public TemplateSentence(){ + + + } + /// + /// Desc:主键 + /// Default: + /// Nullable:False + /// + [SugarColumn(IsPrimaryKey=true,IsIdentity=true)] + public new int Id {get;set;} + + /// + /// Desc:模板内容 + /// Default: + /// Nullable:False + /// + public string Sentence {get;set;} + + /// + /// Desc:是否启用 + /// Default:b'1' + /// Nullable:False + /// + public bool IsActive {get;set;} + + /// + /// Desc:分类: Positive, Neutral, Negative + /// Default:Neutral + /// Nullable:False + /// + public string Type {get;set;} + + /// + /// Desc:状态: Active, Inactive + /// Default:Active + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/User.cs b/QYZH.InteractiveMagazine.Models/Entity/User.cs new file mode 100644 index 0000000..67f617a --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/User.cs @@ -0,0 +1,71 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///用户表 + /// + [SugarTable("User")] + public partial class User : BaseEntity + { + public User(){ + + + } + /// + /// Desc:微信OpenId + /// Default: + /// Nullable:False + /// + public string OpenId {get;set;} + + /// + /// Desc:微信UnionId + /// Default: + /// Nullable:True + /// + public string UnionId {get;set;} + + /// + /// Desc:昵称 + /// Default: + /// Nullable:True + /// + public string NickName {get;set;} + + /// + /// Desc:头像地址 + /// Default: + /// Nullable:True + /// + public string AvatarUrl {get;set;} + + /// + /// Desc:手机号 + /// Default: + /// Nullable:True + /// + public string Phone {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/UserBag.cs b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs new file mode 100644 index 0000000..de6a09e --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/UserBag.cs @@ -0,0 +1,64 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///用户背包表 + /// + [SugarTable("UserBag")] + public partial class UserBag : BaseEntity + { + public UserBag(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:物品类型: MakeUpCard, PetBg + /// Default: + /// Nullable:False + /// + public string ItemType {get;set;} + + /// + /// Desc:关联商品Id或资源Id + /// Default: + /// Nullable:False + /// + public int ItemId {get;set;} + + /// + /// Desc:数量 + /// Default:1 + /// Nullable:False + /// + public int Quantity {get;set;} + + /// + /// Desc:扩展信息 + /// Default: + /// Nullable:True + /// + public string MetaData {get;set;} + + /// + /// Desc:背包物品分类 + /// Default: + /// Nullable:True + /// + public string Type {get;set;} + + /// + /// Desc:状态: Available, Expired + /// Default:Available + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs new file mode 100644 index 0000000..c30a3b9 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs @@ -0,0 +1,50 @@ +using SqlSugar; + +namespace QYZH.InteractiveMagazine.Models.Entity +{ + /// + ///用户勋章表 + /// + [SugarTable("UserMedal")] + public partial class UserMedal : BaseEntity + { + public UserMedal(){ + + + } + /// + /// Desc:用户Id + /// Default: + /// Nullable:False + /// + public long UserId {get;set;} + + /// + /// Desc:勋章Id + /// Default: + /// Nullable:False + /// + public int MedalId {get;set;} + + /// + /// Desc:获得时间 + /// Default: + /// Nullable:True + /// + public DateTime? AwardedAt {get;set;} + + /// + /// Desc:勋章记录类型 + /// Default: + /// Nullable:True + /// + public string Type {get;set;} + + /// + /// Desc:状态: Awarded, Revoked + /// Default:Awarded + /// Nullable:False + /// + public string Status {get;set;} + } +} diff --git a/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs b/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs new file mode 100644 index 0000000..9363da6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QYZH.InteractiveMagazine.Models.Enum +{ + public enum ApiVersionEnum + { + + + /// + /// Platform + /// + [Description("平台管理")] + Platform = 10, + /// + /// Wechat + /// + [Description("小程序管理")] + Wechat = 20, + + } +} diff --git a/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj b/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj index 33a2e64..0bb8e11 100644 --- a/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj +++ b/QYZH.InteractiveMagazine.Models/QYZH.InteractiveMagazine.Models.csproj @@ -1,9 +1,10 @@ - + net8.0 enable enable + True diff --git a/QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs b/QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs new file mode 100644 index 0000000..e81d9a9 --- /dev/null +++ b/QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs @@ -0,0 +1,13 @@ +using QYZH.InteractiveMagazine.Models.Entity; + +namespace QYZH.InteractiveMagazine.Repository; + +public class AdminUserRepository : BaseRepository, IAdminUserRepository +{ + public async Task GetByUserNameAsync(string userName) + { + return await Db.Queryable() + .Where(x => x.UserName == userName) + .FirstAsync(); + } +} diff --git a/QYZH.InteractiveMagazine.Repository/BaseRepository.cs b/QYZH.InteractiveMagazine.Repository/BaseRepository.cs index 4045146..cbb2414 100644 --- a/QYZH.InteractiveMagazine.Repository/BaseRepository.cs +++ b/QYZH.InteractiveMagazine.Repository/BaseRepository.cs @@ -146,4 +146,9 @@ public class BaseRepository : IBaseRepository where T : class, new() { return await Db.Queryable().Where(where).CountAsync(); } + + public ISugarQueryable Queryable() + { + return Db.Queryable(); + } } diff --git a/QYZH.InteractiveMagazine.Repository/IAdminUserRepository.cs b/QYZH.InteractiveMagazine.Repository/IAdminUserRepository.cs new file mode 100644 index 0000000..37537eb --- /dev/null +++ b/QYZH.InteractiveMagazine.Repository/IAdminUserRepository.cs @@ -0,0 +1,8 @@ +using QYZH.InteractiveMagazine.Models.Entity; + +namespace QYZH.InteractiveMagazine.Repository; + +public interface IAdminUserRepository : IBaseRepository +{ + Task GetByUserNameAsync(string userName); +} diff --git a/QYZH.InteractiveMagazine.Service/AdminAuthService.cs b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs new file mode 100644 index 0000000..515bda5 --- /dev/null +++ b/QYZH.InteractiveMagazine.Service/AdminAuthService.cs @@ -0,0 +1,168 @@ +using BCrypt.Net; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using QYZH.InteractiveMagazine.Infrastructure.Auth; +using QYZH.InteractiveMagazine.Infrastructure.Cache; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.IService.Dto; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Settings; +using QYZH.InteractiveMagazine.Repository; + +namespace QYZH.InteractiveMagazine.Service; + +public class AdminAuthService : IAdminAuthService +{ + private readonly IAdminUserRepository _adminUserRepository; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token"; + private const string UserInfoKeyPrefix = "InteractiveMagazine:AdminAuth:UserInfo"; + + public AdminAuthService(IAdminUserRepository adminUserRepository, IConfiguration configuration, ILogger logger) + { + _adminUserRepository = adminUserRepository; + _configuration = configuration; + _logger = logger; + } + + public async Task LoginAsync(AdminLoginInput input) + { + _logger.LogInformation("管理员登录尝试,用户名: {UserName}", input.UserName); + + if (string.IsNullOrWhiteSpace(input.UserName)) + { + throw new BusinessException("用户名不能为空", 400); + } + + if (string.IsNullOrWhiteSpace(input.Password)) + { + throw new BusinessException("密码不能为空", 400); + } + + var adminUser = await _adminUserRepository.GetByUserNameAsync(input.UserName); + if (adminUser == null) + { + _logger.LogWarning("管理员登录失败,用户名不存在: {UserName}", input.UserName); + throw new BusinessException("用户名或密码错误", 401); + } + + if (!BCrypt.Net.BCrypt.Verify(input.Password, adminUser.PasswordHash)) + { + _logger.LogWarning("管理员登录失败,密码错误: {UserName}", input.UserName); + throw new BusinessException("用户名或密码错误", 401); + } + + if (adminUser.Status != "Active") + { + _logger.LogWarning("管理员登录失败,账号已禁用: {UserName}", input.UserName); + throw new BusinessException("账号已被禁用,请联系系统管理员", 403); + } + + var jwtSettings = GetJwtSettings(); + + var token = JwtHelper.GenerateToken((long)adminUser.Id, adminUser.UserName, jwtSettings); + + await RedisHelper.StringSetAsync($"{TokenKeyPrefix}:{adminUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); + + _logger.LogInformation("管理员登录成功,用户名: {UserName}, ID: {UserId}", input.UserName, adminUser.Id); + + return new AdminLoginOutput + { + Token = token, + UserId = (long)adminUser.Id, + UserName = adminUser.UserName, + Type = adminUser.Type + }; + } + + public async Task LogoutAsync(long userId) + { + _logger.LogInformation("管理员登出,ID: {UserId}", userId); + + await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}"); + + _logger.LogInformation("管理员登出成功,ID: {UserId}", userId); + } + + public async Task GetAdminInfoAsync(long userId) + { + _logger.LogInformation("获取管理员信息,ID: {UserId}", userId); + + var adminUser = await _adminUserRepository.GetByIdAsync(userId); + if (adminUser == null) + { + _logger.LogWarning("未找到管理员,ID: {UserId}", userId); + throw new BusinessException("用户不存在", 404); + } + + return new AdminUserInfoOutput + { + UserId = adminUser.Id, + UserName = adminUser.UserName, + Type = adminUser.Type, + Status = adminUser.Status + }; + } + + public async Task ChangePasswordAsync(long userId, string oldPassword, string newPassword) + { + _logger.LogInformation("管理员修改密码尝试,ID: {UserId}", userId); + + if (string.IsNullOrWhiteSpace(oldPassword)) + { + throw new BusinessException("原密码不能为空", 400); + } + + if (string.IsNullOrWhiteSpace(newPassword)) + { + throw new BusinessException("新密码不能为空", 400); + } + + var adminUser = await _adminUserRepository.GetByIdAsync(userId); + if (adminUser == null) + { + _logger.LogWarning("未找到管理员,ID: {UserId}", userId); + throw new BusinessException("用户不存在", 404); + } + + if (!BCrypt.Net.BCrypt.Verify(oldPassword, adminUser.PasswordHash)) + { + _logger.LogWarning("管理员修改密码失败,原密码错误,ID: {UserId}", userId); + throw new BusinessException("原密码错误", 400); + } + + adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword); + + var result = await _adminUserRepository.UpdateAsync(adminUser); + if (!result) + { + throw new BusinessException("修改密码失败", 500); + } + + await RedisHelper.KeyDeleteAsync($"{TokenKeyPrefix}:{userId}"); + + _logger.LogInformation("管理员修改密码成功,ID: {UserId}", userId); + } + + private JwtSettings GetJwtSettings() + { + var jwtSettings = _configuration.GetSection("JwtSettings").Get() + ?? new JwtSettings + { + Issuer = "QYZH.InteractiveMagazine", + Audience = "QYZH.InteractiveMagazine", + SecretKey = "your-256-bit-secret-key-here-change-in-production", + ExpiryMinutes = 120 + }; + + if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey)) + { + throw new BusinessException("JWT 配置不完整", 500); + } + + return jwtSettings; + } +} diff --git a/QYZH.InteractiveMagazine.Service/AdminUserService.cs b/QYZH.InteractiveMagazine.Service/AdminUserService.cs new file mode 100644 index 0000000..a1dd6d6 --- /dev/null +++ b/QYZH.InteractiveMagazine.Service/AdminUserService.cs @@ -0,0 +1,267 @@ +using System.Linq.Expressions; +using BCrypt.Net; +using Microsoft.Extensions.Logging; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.IService.Dto; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Repository; + +namespace QYZH.InteractiveMagazine.Service; + +/// +/// 管理员用户服务实现 +/// +public class AdminUserService : IAdminUserService +{ + private readonly IAdminUserRepository _adminUserRepository; + private readonly ILogger _logger; + + public AdminUserService(IAdminUserRepository adminUserRepository, ILogger logger) + { + _adminUserRepository = adminUserRepository; + _logger = logger; + } + + /// + /// 创建管理员 + /// + public async Task CreateAsync(AdminUserInput input) + { + _logger.LogInformation("正在创建管理员,用户名: {UserName}", input.UserName); + + if (string.IsNullOrWhiteSpace(input.UserName)) + { + throw new BusinessException("用户名不能为空", 400); + } + + if (string.IsNullOrWhiteSpace(input.Password)) + { + throw new BusinessException("密码不能为空", 400); + } + + // 检查用户名是否已存在 + var existingUser = await _adminUserRepository.GetByUserNameAsync(input.UserName); + if (existingUser != null) + { + _logger.LogWarning("创建管理员失败,用户名已存在: {UserName}", input.UserName); + throw new BusinessException("用户名已存在", 400); + } + + var adminUser = new AdminUser + { + UserName = input.UserName.Trim(), + PasswordHash = BCrypt.Net.BCrypt.HashPassword(input.Password), + Type = input.Type, + Status = input.Status, + CreatedBy = "System", + UpdatedBy = "System", + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsDeleted = false + }; + + var result = await _adminUserRepository.InsertAsync(adminUser); + if (!result) + { + _logger.LogError("管理员创建失败,用户名: {UserName}", input.UserName); + throw new BusinessException("创建管理员失败", 500); + } + + _logger.LogInformation("管理员创建成功,用户名: {UserName}, ID: {Id}", input.UserName, adminUser.Id); + + return MapToOutput(adminUser); + } + + /// + /// 更新管理员 + /// + public async Task UpdateAsync(long id, AdminUserInput input) + { + _logger.LogInformation("正在更新管理员,ID: {Id}", id); + + var adminUser = await _adminUserRepository.GetByIdAsync(id); + if (adminUser == null) + { + _logger.LogWarning("未找到要更新的管理员,ID: {Id}", id); + throw new BusinessException("管理员不存在", 404); + } + + // 如果用户名有变更,检查是否与其他用户重复 + if (!string.IsNullOrWhiteSpace(input.UserName) && input.UserName != adminUser.UserName) + { + var existingUser = await _adminUserRepository.GetByUserNameAsync(input.UserName.Trim()); + if (existingUser != null && existingUser.Id != id) + { + _logger.LogWarning("更新管理员失败,用户名已存在: {UserName}", input.UserName); + throw new BusinessException("用户名已存在", 400); + } + + adminUser.UserName = input.UserName.Trim(); + } + + // 如果提供了密码,则更新密码 + if (!string.IsNullOrWhiteSpace(input.Password)) + { + adminUser.PasswordHash = BCrypt.Net.BCrypt.HashPassword(input.Password); + } + + if (!string.IsNullOrWhiteSpace(input.Type)) + { + adminUser.Type = input.Type; + } + + if (!string.IsNullOrWhiteSpace(input.Status)) + { + adminUser.Status = input.Status; + } + + adminUser.UpdatedBy = "System"; + adminUser.UpdatedAt = DateTime.Now; + + var result = await _adminUserRepository.UpdateAsync(adminUser); + if (!result) + { + _logger.LogError("管理员更新失败,ID: {Id}", id); + throw new BusinessException("更新管理员失败", 500); + } + + _logger.LogInformation("管理员更新成功,ID: {Id}", id); + + return MapToOutput(adminUser); + } + + /// + /// 删除管理员(软删除) + /// + public async Task DeleteAsync(long id) + { + _logger.LogInformation("正在删除管理员,ID: {Id}", id); + + var adminUser = await _adminUserRepository.GetByIdAsync(id); + if (adminUser == null) + { + _logger.LogWarning("未找到要删除的管理员,ID: {Id}", id); + throw new BusinessException("管理员不存在", 404); + } + + var result = await _adminUserRepository.DeleteByIdAsync(id); + if (!result) + { + _logger.LogError("管理员删除失败,ID: {Id}", id); + throw new BusinessException("删除管理员失败", 500); + } + + _logger.LogInformation("管理员删除成功,ID: {Id}", id); + } + + /// + /// 根据ID获取管理员 + /// + public async Task GetByIdAsync(long id) + { + _logger.LogInformation("正在获取管理员信息,ID: {Id}", id); + + var adminUser = await _adminUserRepository.GetByIdAsync(id); + if (adminUser == null) + { + _logger.LogWarning("未找到管理员,ID: {Id}", id); + throw new BusinessException("管理员不存在", 404); + } + + return MapToOutput(adminUser); + } + + /// + /// 分页查询管理员列表 + /// + public async Task> GetListAsync(AdminUserQueryInput input) + { + _logger.LogInformation("正在查询管理员列表,页码: {PageIndex}, 每页条数: {PageSize}", input.PageIndex, input.PageSize); + + if (input.PageIndex <= 0) + { + throw new BusinessException("页码必须大于0", 400); + } + + if (input.PageSize <= 0 || input.PageSize > 100) + { + throw new BusinessException("每页条数必须在1-100之间", 400); + } + + var pageResult = await _adminUserRepository.GetPageListAsync( + BuildQueryExpression(input), + input + ); + + // 转换为输出DTO + var outputList = pageResult.List?.Select(MapToOutput).ToList() ?? new List(); + + return new PageListModel + { + List = outputList, + TotalCount = pageResult.TotalCount, + PageIndex = pageResult.PageIndex, + PageSize = pageResult.PageSize + }; + } + + /// + /// 构建查询表达式 + /// + private static Expression> BuildQueryExpression(AdminUserQueryInput input) + { + Expression> where = x => !x.IsDeleted; + + if (!string.IsNullOrWhiteSpace(input.UserName)) + { + var userName = input.UserName; + Expression> userNameCondition = x => x.UserName.Contains(userName); + where = Expression.Lambda>( + Expression.AndAlso(where.Body, + Expression.Invoke(userNameCondition, where.Parameters[0])), + where.Parameters); + } + + if (!string.IsNullOrWhiteSpace(input.Type)) + { + var type = input.Type; + Expression> typeCondition = x => x.Type == type; + where = Expression.Lambda>( + Expression.AndAlso(where.Body, + Expression.Invoke(typeCondition, where.Parameters[0])), + where.Parameters); + } + + if (!string.IsNullOrWhiteSpace(input.Status)) + { + var status = input.Status; + Expression> statusCondition = x => x.Status == status; + where = Expression.Lambda>( + Expression.AndAlso(where.Body, + Expression.Invoke(statusCondition, where.Parameters[0])), + where.Parameters); + } + + return where; + } + + /// + /// 将实体映射为输出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/AuthService.cs b/QYZH.InteractiveMagazine.Service/AuthService.cs deleted file mode 100644 index eb4b569..0000000 --- a/QYZH.InteractiveMagazine.Service/AuthService.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using QYZH.InteractiveMagazine.IService; -using QYZH.InteractiveMagazine.IService.Dto; -using QYZH.InteractiveMagazine.Models.Common; -using QYZH.InteractiveMagazine.Models.Settings; -using QYZH.InteractiveMagazine.Infrastructure.Auth; - -namespace QYZH.InteractiveMagazine.Service; - -/// -/// 认证服务实现 -/// -public class AuthService : IAuthService -{ - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - - /// - /// 演示用管理员账号 - /// - private const string DemoAccount = "admin"; - - /// - /// 演示用管理员密码 - /// - private const string DemoPassword = "123456"; - - /// - /// 构造函数 - /// - /// 配置 - /// 日志记录器 - public AuthService(IConfiguration configuration, ILogger logger) - { - _configuration = configuration; - _logger = logger; - } - - /// - /// 用户登录 - /// - /// 账号 - /// 密码 - /// 登录结果 - public async Task LoginAsync(string account, string password) - { - _logger.LogInformation("用户登录尝试,账号: {Account}", account); - - if (string.IsNullOrWhiteSpace(account)) - { - throw new BusinessException("账号不能为空", 400); - } - - if (string.IsNullOrWhiteSpace(password)) - { - throw new BusinessException("密码不能为空", 400); - } - - // 演示版本:硬编码验证账号密码 - if (account != DemoAccount || password != DemoPassword) - { - _logger.LogWarning("用户登录失败,账号: {Account}", account); - throw new BusinessException("账号或密码错误", 401); - } - - // 获取JWT配置 - var jwtSettings = GetJwtSettings(); - - // 生成令牌 - var token = JwtHelper.GenerateToken(1, "管理员", jwtSettings); - var refreshToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings); - - _logger.LogInformation("用户登录成功,账号: {Account}", account); - - return new LoginOutput - { - Token = token, - RefreshToken = refreshToken, - UserId = 1, - UserName = "管理员" - }; - } - - /// - /// 用户注册 - /// - /// 注册输入参数 - /// 是否成功 - public async Task RegisterAsync(RegisterInput input) - { - _logger.LogInformation("用户注册尝试,账号: {Account}", input?.Account); - - if (input == null) - { - throw new BusinessException("注册参数不能为空", 400); - } - - if (string.IsNullOrWhiteSpace(input.Account)) - { - throw new BusinessException("账号不能为空", 400); - } - - if (string.IsNullOrWhiteSpace(input.Password)) - { - throw new BusinessException("密码不能为空", 400); - } - - if (string.IsNullOrWhiteSpace(input.UserName)) - { - throw new BusinessException("用户名不能为空", 400); - } - - // 演示版本:模拟注册成功 - _logger.LogInformation("用户注册成功,账号: {Account}, 用户名: {UserName}", input.Account, input.UserName); - - return await Task.FromResult(true); - } - - /// - /// 刷新令牌 - /// - /// 刷新令牌 - /// 新的登录结果 - public async Task RefreshTokenAsync(string refreshToken) - { - _logger.LogInformation("令牌刷新尝试"); - - if (string.IsNullOrWhiteSpace(refreshToken)) - { - throw new BusinessException("刷新令牌不能为空", 400); - } - - var jwtSettings = GetJwtSettings(); - - // 生成新的令牌 - var newToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings); - var newRefreshToken = JwtHelper.GenerateToken(1, "管理员", jwtSettings); - - _logger.LogInformation("令牌刷新成功"); - - return new LoginOutput - { - Token = newToken, - RefreshToken = newRefreshToken, - UserId = 1, - UserName = "管理员" - }; - } - - /// - /// 获取JWT配置 - /// - /// JWT配置对象 - private JwtSettings GetJwtSettings() - { - var jwtSettings = _configuration.GetSection("JwtSettings").Get() - ?? new JwtSettings - { - Issuer = "QYZH.InteractiveMagazine", - Audience = "QYZH.InteractiveMagazine.Client", - SecretKey = "QYZH_InteractiveMagazine_SecretKey_2024", - ExpiryMinutes = 120 - }; - - if (string.IsNullOrWhiteSpace(jwtSettings.SecretKey)) - { - throw new BusinessException("JWT配置不完整", 500); - } - - return jwtSettings; - } -} diff --git a/QYZH.InteractiveMagazine.Service/BaseService.cs b/QYZH.InteractiveMagazine.Service/BaseService.cs index b4685f2..a94d2d5 100644 --- a/QYZH.InteractiveMagazine.Service/BaseService.cs +++ b/QYZH.InteractiveMagazine.Service/BaseService.cs @@ -182,23 +182,19 @@ public class BaseService : IBaseService where T : class, new() if (entity is BaseEntity baseEntity) { var now = DateTime.Now; - baseEntity.CreatedTime = now; - baseEntity.UpdatedTime = now; + baseEntity.CreatedAt = now; + baseEntity.UpdatedAt = now; baseEntity.CreatedBy = baseEntity.CreatedBy ?? "system"; baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system"; baseEntity.IsDeleted = false; } } - /// - /// 设置更新时的审计字段 - /// - /// 实体对象 private static void SetAuditFieldsOnUpdate(T entity) { if (entity is BaseEntity baseEntity) { - baseEntity.UpdatedTime = DateTime.Now; + baseEntity.UpdatedAt = DateTime.Now; baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system"; } } diff --git a/QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj b/QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj index c4f7b10..4bc31cd 100644 --- a/QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj +++ b/QYZH.InteractiveMagazine.Service/QYZH.InteractiveMagazine.Service.csproj @@ -10,6 +10,7 @@ + diff --git a/QYZH.InteractiveMagazine.Service/WeChatMiniProgramService.cs b/QYZH.InteractiveMagazine.Service/WeChatMiniProgramService.cs deleted file mode 100644 index 2f586d1..0000000 --- a/QYZH.InteractiveMagazine.Service/WeChatMiniProgramService.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using QYZH.InteractiveMagazine.Common.Helpers; -using QYZH.InteractiveMagazine.IService; -using QYZH.InteractiveMagazine.IService.Dto; -using QYZH.InteractiveMagazine.Models.Common; -using QYZH.InteractiveMagazine.Models.Settings; -using QYZH.InteractiveMagazine.Infrastructure.Auth; - -namespace QYZH.InteractiveMagazine.Service; - -/// -/// 微信小程序服务实现 -/// -public class WeChatMiniProgramService : IWeChatMiniProgramService -{ - private readonly IConfiguration _configuration; - private readonly ILogger _logger; - private const string Code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session"; - private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber"; - - /// - /// 构造函数 - /// - /// 配置 - /// 日志记录器 - public WeChatMiniProgramService(IConfiguration configuration, ILogger logger) - { - _configuration = configuration; - _logger = logger; - } - - /// - /// 微信登录 - /// - /// 微信登录凭证 - /// 微信登录结果 - public async Task WeChatLoginAsync(string code) - { - _logger.LogInformation("微信登录尝试,code: {Code}", code); - - if (string.IsNullOrWhiteSpace(code)) - { - throw new BusinessException("登录凭证不能为空", 400); - } - - // 获取微信配置 - var weChatSettings = GetWeChatSettings(); - - if (string.IsNullOrWhiteSpace(weChatSettings.AppId) || string.IsNullOrWhiteSpace(weChatSettings.AppSecret)) - { - throw new BusinessException("微信配置不完整", 500); - } - - // 演示版本:模拟调用微信code2session接口 - var openId = await SimulateCode2SessionAsync(code, weChatSettings); - - // 获取JWT配置并生成令牌 - var jwtSettings = GetJwtSettings(); - var token = JwtHelper.GenerateToken(1, "微信用户", jwtSettings); - - _logger.LogInformation("微信登录成功,openId: {OpenId}", openId); - - return new WeChatLoginOutput - { - Token = token, - UserId = 1, - UserName = "微信用户", - OpenId = openId - }; - } - - /// - /// 获取手机号 - /// - /// 获取手机号凭证 - /// 手机号信息 - public async Task GetPhoneNumberAsync(string code) - { - _logger.LogInformation("获取微信手机号尝试,code: {Code}", code); - - if (string.IsNullOrWhiteSpace(code)) - { - throw new BusinessException("获取手机号凭证不能为空", 400); - } - - // 演示版本:模拟返回手机号 - var phoneNumber = await SimulateGetPhoneNumberAsync(code); - - _logger.LogInformation("获取微信手机号成功"); - - return new WeChatPhoneNumberOutput - { - PhoneNumber = phoneNumber - }; - } - - /// - /// 模拟调用微信code2session接口 - /// - /// 登录凭证 - /// 微信配置 - /// openId - private async Task SimulateCode2SessionAsync(string code, WeChatSettings weChatSettings) - { - // 演示版本:模拟返回openId - // 实际实现应调用微信API: - // var url = $"{Code2SessionUrl}?appid={weChatSettings.AppId}&secret={weChatSettings.AppSecret}&js_code={code}&grant_type=authorization_code"; - // var response = await HttpHelper.GetAsync(url); - // if (response?.errcode == 0) return response.openid; - - await Task.Delay(100); // 模拟网络请求延迟 - - return $"demo_openid_{code.GetHashCode():X}"; - } - - /// - /// 模拟调用微信获取手机号接口 - /// - /// 获取手机号凭证 - /// 手机号 - private async Task SimulateGetPhoneNumberAsync(string code) - { - // 演示版本:模拟返回手机号 - // 实际实现应调用微信API获取access_token,然后调用获取手机号接口: - // var tokenUrl = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={appSecret}"; - // var tokenResponse = await HttpHelper.GetAsync(tokenUrl); - // var accessToken = tokenResponse.access_token; - // var phoneUrl = $"{GetPhoneNumberUrl}?access_token={accessToken}"; - // var phoneResponse = await HttpHelper.PostAsync(phoneUrl, new { code }); - - await Task.Delay(100); // 模拟网络请求延迟 - - return "13800138000"; - } - - /// - /// 获取微信配置 - /// - /// 微信配置对象 - private WeChatSettings GetWeChatSettings() - { - return _configuration.GetSection("WeChatSettings").Get() - ?? new WeChatSettings - { - AppId = "demo_app_id", - AppSecret = "demo_app_secret" - }; - } - - /// - /// 获取JWT配置 - /// - /// JWT配置对象 - private JwtSettings GetJwtSettings() - { - return _configuration.GetSection("JwtSettings").Get() - ?? new JwtSettings - { - Issuer = "QYZH.InteractiveMagazine", - Audience = "QYZH.InteractiveMagazine.Client", - SecretKey = "QYZH_InteractiveMagazine_SecretKey_2024", - ExpiryMinutes = 120 - }; - } -} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs new file mode 100644 index 0000000..f2e9623 --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs @@ -0,0 +1,205 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using QYZH.InteractiveMagazine.IService; +using QYZH.InteractiveMagazine.IService.Dto; +using QYZH.InteractiveMagazine.Models.Common; +using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Enum; + +namespace QYZH.InteractiveMagazine.WebApi.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class AdminController : BaseController +{ + private readonly IAdminAuthService _adminAuthService; + private readonly IAdminUserService _adminUserService; + private readonly ILogger _logger; + + public AdminController(IAdminAuthService adminAuthService, IAdminUserService adminUserService, ILogger logger) + { + _adminAuthService = adminAuthService; + _adminUserService = adminUserService; + _logger = logger; + } + + [AllowAnonymous] + [HttpPost("login")] + public async Task> LoginAsync([FromBody] AdminLoginInput input) + { + var result = await _adminAuthService.LoginAsync(input); + return Success(result); + } + + [HttpPost("logout")] + public async Task> LogoutAsync() + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Fail("未获取到用户信息", 401); + } + + await _adminAuthService.LogoutAsync(userId.Value); + return Success(new object(), "登出成功"); + } + + [HttpGet("info")] + public async Task> GetAdminInfoAsync() + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return BaseResponse.Fail("未获取到用户信息", 401); + } + + var result = await _adminAuthService.GetAdminInfoAsync(userId.Value); + return Success(result); + } + + [HttpPost("changePassword")] + public async Task> ChangePasswordAsync([FromBody] ChangePasswordInput input) + { + var userId = GetCurrentUserId(); + if (userId == null) + { + return Fail("未获取到用户信息", 401); + } + + await _adminAuthService.ChangePasswordAsync(userId.Value, input.OldPassword, input.NewPassword); + return Success(new object(), "密码修改成功"); + } + + /// + /// 创建管理员 + /// + /// 管理员输入 + /// 创建的管理员信息 + [HttpPost("users")] + public async Task> CreateUserAsync([FromBody] AdminUserInput input) + { + try + { + var result = await _adminUserService.CreateAsync(input); + return Success(result, "创建管理员成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "创建管理员业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message, ex.Code); + } + catch (Exception ex) + { + _logger.LogError(ex, "创建管理员系统异常,参数:{Input}", input); + return BaseResponse.Fail("创建管理员失败,请稍后重试", 500); + } + } + + /// + /// 更新管理员 + /// + /// 管理员ID + /// 管理员输入 + /// 更新后的管理员信息 + [HttpPut("users/{id}")] + public async Task> UpdateUserAsync(long id, [FromBody] AdminUserInput input) + { + try + { + var result = await _adminUserService.UpdateAsync(id, input); + return Success(result, "更新管理员成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "更新管理员业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message, ex.Code); + } + catch (Exception ex) + { + _logger.LogError(ex, "更新管理员系统异常,ID:{Id},参数:{Input}", id, input); + return BaseResponse.Fail("更新管理员失败,请稍后重试", 500); + } + } + + /// + /// 删除管理员 + /// + /// 管理员ID + /// 操作结果 + [HttpDelete("users/{id}")] + public async Task> DeleteUserAsync(long id) + { + try + { + await _adminUserService.DeleteAsync(id); + return Success(new object(), "删除管理员成功"); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "删除管理员业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message, ex.Code); + } + catch (Exception ex) + { + _logger.LogError(ex, "删除管理员系统异常,ID:{Id}", id); + return BaseResponse.Fail("删除管理员失败,请稍后重试", 500); + } + } + + /// + /// 根据ID获取管理员 + /// + /// 管理员ID + /// 管理员信息 + [HttpGet("users/{id}")] + public async Task> GetUserByIdAsync(long id) + { + try + { + var result = await _adminUserService.GetByIdAsync(id); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "获取管理员业务异常: {Message}", ex.Message); + return BaseResponse.Fail(ex.Message, ex.Code); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取管理员系统异常,ID:{Id}", id); + return BaseResponse.Fail("获取管理员信息失败,请稍后重试", 500); + } + } + + /// + /// 分页查询管理员列表 + /// + /// 查询条件 + /// 分页结果 + [HttpPost("users/list")] + public async Task>> GetUsersListAsync([FromBody] AdminUserQueryInput input) + { + try + { + var result = await _adminUserService.GetListAsync(input); + return Success(result); + } + catch (BusinessException ex) + { + _logger.LogWarning(ex, "查询管理员列表业务异常: {Message}", ex.Message); + return BaseResponse>.Fail(ex.Message, ex.Code); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询管理员列表系统异常,参数:{Input}", input); + return BaseResponse>.Fail("查询管理员列表失败,请稍后重试", 500); + } + } +} + +public class ChangePasswordInput +{ + public string OldPassword { get; set; } = string.Empty; + + public string NewPassword { get; set; } = string.Empty; +} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/AuthController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/AuthController.cs deleted file mode 100644 index b174f22..0000000 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/AuthController.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using QYZH.InteractiveMagazine.IService; -using QYZH.InteractiveMagazine.IService.Dto; -using QYZH.InteractiveMagazine.Models.Dto; - -namespace QYZH.InteractiveMagazine.WebApi.Controllers; - -/// -/// 认证控制器 -/// -[Route("api/[controller]")] -[ApiController] -public class AuthController : BaseController -{ - private readonly IAuthService _authService; - - public AuthController(IAuthService authService) - { - _authService = authService; - } - - /// - /// 用户登录 - /// - /// 登录信息 - /// 登录结果 - [AllowAnonymous] - [HttpPost("login")] - public async Task> LoginAsync([FromBody] LoginInput input) - { - var result = await _authService.LoginAsync(input.Account, input.Password); - return Success(result); - } - - /// - /// 用户注册 - /// - /// 注册信息 - /// 是否成功 - [AllowAnonymous] - [HttpPost("register")] - public async Task> RegisterAsync([FromBody] RegisterInput input) - { - var result = await _authService.RegisterAsync(input); - return Success(result, "注册成功"); - } - - /// - /// 刷新令牌 - /// - /// 刷新令牌 - /// 新的登录结果 - [AllowAnonymous] - [HttpPost("refreshToken")] - public async Task> RefreshTokenAsync([FromQuery] string refreshToken) - { - var result = await _authService.RefreshTokenAsync(refreshToken); - return Success(result); - } -} diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs index e551629..7a45cc7 100644 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs +++ b/QYZH.InteractiveMagazine.WebApi/Controllers/BaseController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using QYZH.InteractiveMagazine.Models.Dto; +using QYZH.InteractiveMagazine.Models.Enum; namespace QYZH.InteractiveMagazine.WebApi.Controllers; @@ -8,6 +9,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers; /// [ApiController] [Route("api/[controller]")] +[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))] public abstract class BaseController : ControllerBase { /// diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/WeChatController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/WeChatController.cs deleted file mode 100644 index a468cc9..0000000 --- a/QYZH.InteractiveMagazine.WebApi/Controllers/WeChatController.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using QYZH.InteractiveMagazine.IService; -using QYZH.InteractiveMagazine.IService.Dto; -using QYZH.InteractiveMagazine.Models.Dto; - -namespace QYZH.InteractiveMagazine.WebApi.Controllers; - -/// -/// 微信小程序控制器 -/// -[Route("api/[controller]")] -[ApiController] -[AllowAnonymous] -public class WeChatController : BaseController -{ - private readonly IWeChatMiniProgramService _weChatService; - - public WeChatController(IWeChatMiniProgramService weChatService) - { - _weChatService = weChatService; - } - - /// - /// 微信登录 - /// - /// 微信登录凭证 - /// 微信登录结果 - [HttpPost("login")] - public async Task> WeChatLoginAsync([FromQuery] string code) - { - var result = await _weChatService.WeChatLoginAsync(code); - return Success(result); - } -} diff --git a/QYZH.InteractiveMagazine.WebApi/Dockerfile b/QYZH.InteractiveMagazine.WebApi/Dockerfile new file mode 100644 index 0000000..243e01e --- /dev/null +++ b/QYZH.InteractiveMagazine.WebApi/Dockerfile @@ -0,0 +1,19 @@ +# 使用 ASP.NET Core 8.0 运行时基础镜像 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base + +# 设置工作目录 +WORKDIR /app + +# 将当前目录(发布文件夹)的所有内容复制到容器内的 /app 目录 +COPY . . + +# 设置时区(可选) +ENV TZ=Asia/Shanghai +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 暴露端口(根据您的 WebApi 实际监听的端口,通常为 80 或 8080) +# 注意:这里只是声明,实际映射需要在运行容器时指定 +EXPOSE 8090 + +# 启动应用 +ENTRYPOINT ["dotnet", "QYZH.InteractiveMagazine.WebApi.dll"] \ No newline at end of file diff --git a/QYZH.InteractiveMagazine.WebApi/Program.cs b/QYZH.InteractiveMagazine.WebApi/Program.cs index 4267f96..05f178b 100644 --- a/QYZH.InteractiveMagazine.WebApi/Program.cs +++ b/QYZH.InteractiveMagazine.WebApi/Program.cs @@ -1,38 +1,98 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using BCrypt.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.OpenApi; +using QYZH.InteractiveMagazine.Common.Extensions; using QYZH.InteractiveMagazine.Infrastructure.Extensions; using QYZH.InteractiveMagazine.Infrastructure.Middleware; +using QYZH.InteractiveMagazine.Models.Entity; +using QYZH.InteractiveMagazine.Models.Enum; using QYZH.InteractiveMagazine.Repository; using Serilog; +using Swashbuckle.AspNetCore.SwaggerGen; +using Swashbuckle.AspNetCore.SwaggerUI; +using QYZH.InteractiveMagazine.Infrastructure.Autofacs; var builder = WebApplication.CreateBuilder(args); +// autofac注入 允许使用autofac作为DI容器 +builder.UseAutofac(); + // 配置Serilog Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) .Enrich.FromLogContext() - .WriteTo.Console() .CreateLogger(); builder.Host.UseSerilog(); -// 注册服务 builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); -// 基础设施服务注册(JWT、Redis、RabbitMQ) +// 跨域配置 +builder.AddCorsRegister(); + +// 注册 Swagger 文档 +builder.Services.AddSwaggerGen(option => +{ + var xmlFile = $"{AppDomain.CurrentDomain.FriendlyName}.xml"; + var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); + var modelXml = Path.Combine(AppContext.BaseDirectory, $"QYZH.InteractiveMagazine.Models.xml"); + + + Enum.GetValues().ToList().ForEach(version => + { + // 配置文档信息 + option.SwaggerDoc(version.ToString(), new OpenApiInfo + { + Title = AppDomain.CurrentDomain.FriendlyName, + Version = "互动期刊接口文档", + Description = $"{version.GetDescription()}接口,Last Modify Time:{new FileInfo(xmlPath).LastWriteTime.ToString("yyyy-MM-dd HH:mm:ss")}" + }); + }); + // 配置接口路径排序 + option.OrderActionsBy(o => o.RelativePath); + + if (File.Exists(xmlPath)) + option.IncludeXmlComments(xmlPath, true); + if (File.Exists(modelXml)) + option.IncludeXmlComments(modelXml, true); + + option.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme() + { + Description = "请输入 Token,格式为 Bearer Token", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + BearerFormat = "JWT", + Scheme = "Bearer" + }); + + + // 过滤文档/路径/方法筛选接口的响应结果 + option.DocInclusionPredicate((docName, apiDesc) => + { + // 方式 1:将接口的 [ApiExplorerSettings(GroupName = "xxx")] 特性匹配 + if (!apiDesc.TryGetMethodInfo(out var methodInfo)) return false; + var groupName = methodInfo.DeclaringType? + .GetCustomAttributes(true) + .OfType() + .FirstOrDefault()? + .GroupName; + + // 匹配当前文档(分组)则显示 + return groupName == docName; + }); +}); + builder.Services.AddInfrastructureServices(builder.Configuration); -// 注册中间件 -builder.Services.AddTransient(); -builder.Services.AddTransient(); - -// 注册业务服务 -builder.Services.AddScoped(); -builder.Services.AddScoped(); // 初始化SqlSugar SqlSugarDbContext.Init(builder.Configuration); + // 添加CORS builder.Services.AddCors(options => { @@ -46,19 +106,27 @@ builder.Services.AddCors(options => var app = builder.Build(); -// 配置HTTP请求管道 -if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(c => + { + // 根据版本名称倒序 遍历展示 + Enum.GetValues().OrderBy(e => e).ToList().ForEach(version => + { + c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口"); + }); + c.DocExpansion(DocExpansion.None); // ->修改界面打开时自动折叠 + }); } app.UseHttpsRedirection(); app.UseCors("AllowAll"); app.UseMiddleware(); app.UseMiddleware(); +app.UseMiddleware(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); + diff --git a/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj b/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj index ffb94cf..39d3e0c 100644 --- a/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj +++ b/QYZH.InteractiveMagazine.WebApi/QYZH.InteractiveMagazine.WebApi.csproj @@ -4,9 +4,13 @@ net8.0 enable enable + True + + + @@ -19,4 +23,8 @@ + + + + diff --git a/QYZH.InteractiveMagazine.WebApi/appsettings.json b/QYZH.InteractiveMagazine.WebApi/appsettings.json index c6c73cc..250560b 100644 --- a/QYZH.InteractiveMagazine.WebApi/appsettings.json +++ b/QYZH.InteractiveMagazine.WebApi/appsettings.json @@ -1,23 +1,23 @@ { "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=interactive_magazine;Uid=root;Pwd=root;Charset=utf8mb4;" + "DefaultConnection": "server=192.168.20.150;port=13306;database=InteractiveMagazine;user=user;password=n68792bu!y99r905;charset=utf8mb4;" }, "JwtSettings": { "Issuer": "QYZH.InteractiveMagazine", - "Audience": "QYZH.InteractiveMagazine.Client", - "SecretKey": "your-256-bit-secret-key-here-change-in-production", + "Audience": "QYZH.InteractiveMagazine", + "SecretKey": "zG7pLqR9xVw2bN8fYtHk3mPc5sA1dF6eUjW4gXhC7vB", "ExpiryMinutes": 120 }, "RedisSettings": { - "ConnectionString": "localhost:6379", + "ConnectionString": "192.168.20.150:16379,defaultDatabase=5", "InstanceName": "interactive_magazine" }, "RabbitMQSettings": { - "HostName": "localhost", + "HostName": "192.168.20.150", "Port": 5672, - "UserName": "guest", - "Password": "guest", - "VirtualHost": "/" + "UserName": "smartschool", + "Password": "@ss%&*otz%d*pq2S", + "VirtualHost": "InteractiveMagazine" }, "WeChatSettings": { "AppId": "your-wechat-appid",