Compare commits
2 Commits
de769ccf12
...
831f8ba7f5
| Author | SHA1 | Date | |
|---|---|---|---|
| 831f8ba7f5 | |||
| ab53492cc4 |
96
QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs
Normal file
96
QYZH.InteractiveMagazine.IService/Dto/AdminUserDto.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.IService.Dto;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员创建/更新输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminUserInput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 密码(创建时必填,更新时为空表示不修改密码)
|
||||||
|
/// </summary>
|
||||||
|
public string? Password { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员类型: SuperAdmin, Editor
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = "Editor";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态: Active, Inactive
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; } = "Active";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员输出
|
||||||
|
/// </summary>
|
||||||
|
public class AdminUserOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 主键ID
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名
|
||||||
|
/// </summary>
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员类型
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public string Status { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建人
|
||||||
|
/// </summary>
|
||||||
|
public string? CreatedBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CreatedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新人
|
||||||
|
/// </summary>
|
||||||
|
public string? UpdatedBy { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员分页查询输入
|
||||||
|
/// </summary>
|
||||||
|
public class AdminUserQueryInput : PageQueryModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 用户名(模糊查询)
|
||||||
|
/// </summary>
|
||||||
|
public string? UserName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员类型
|
||||||
|
/// </summary>
|
||||||
|
public string? Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public string? Status { get; set; }
|
||||||
|
}
|
||||||
@ -62,3 +62,32 @@ public class LoginOutput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string RefreshToken { get; set; } = string.Empty;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
14
QYZH.InteractiveMagazine.IService/IAdminAuthService.cs
Normal file
14
QYZH.InteractiveMagazine.IService/IAdminAuthService.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using QYZH.InteractiveMagazine.IService.Dto;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.IService;
|
||||||
|
|
||||||
|
public interface IAdminAuthService
|
||||||
|
{
|
||||||
|
Task<AdminLoginOutput> LoginAsync(AdminLoginInput input);
|
||||||
|
|
||||||
|
Task LogoutAsync(long userId);
|
||||||
|
|
||||||
|
Task<AdminUserInfoOutput> GetAdminInfoAsync(long userId);
|
||||||
|
|
||||||
|
Task ChangePasswordAsync(long userId, string oldPassword, string newPassword);
|
||||||
|
}
|
||||||
45
QYZH.InteractiveMagazine.IService/IAdminUserService.cs
Normal file
45
QYZH.InteractiveMagazine.IService/IAdminUserService.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using QYZH.InteractiveMagazine.IService.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.IService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员用户服务接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IAdminUserService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 创建管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">管理员输入</param>
|
||||||
|
/// <returns>创建的管理员信息</returns>
|
||||||
|
Task<AdminUserOutput> CreateAsync(AdminUserInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
/// <param name="input">管理员输入</param>
|
||||||
|
/// <returns>更新后的管理员信息</returns>
|
||||||
|
Task<AdminUserOutput> UpdateAsync(long id, AdminUserInput input);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除管理员(软删除)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
Task DeleteAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据ID获取管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
/// <returns>管理员信息</returns>
|
||||||
|
Task<AdminUserOutput> GetByIdAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分页查询管理员列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询条件</param>
|
||||||
|
/// <returns>分页结果</returns>
|
||||||
|
Task<PageListModel<AdminUserOutput>> GetListAsync(AdminUserQueryInput input);
|
||||||
|
}
|
||||||
@ -1,31 +0,0 @@
|
|||||||
using QYZH.InteractiveMagazine.IService.Dto;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.IService;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 认证服务接口
|
|
||||||
/// </summary>
|
|
||||||
public interface IAuthService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 用户登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="account">账号</param>
|
|
||||||
/// <param name="password">密码</param>
|
|
||||||
/// <returns>登录结果</returns>
|
|
||||||
Task<LoginOutput> LoginAsync(string account, string password);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户注册
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">注册输入参数</param>
|
|
||||||
/// <returns>是否成功</returns>
|
|
||||||
Task<bool> RegisterAsync(RegisterInput input);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新令牌
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="refreshToken">刷新令牌</param>
|
|
||||||
/// <returns>新的登录结果</returns>
|
|
||||||
Task<LoginOutput> RefreshTokenAsync(string refreshToken);
|
|
||||||
}
|
|
||||||
15
QYZH.InteractiveMagazine.IService/IDependency.cs
Normal file
15
QYZH.InteractiveMagazine.IService/IDependency.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace QYZH.InteractiveMagazine.IService;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单例生命周期标记接口
|
||||||
|
/// </summary>
|
||||||
|
public interface ISingletonDependency
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 瞬时生命周期标记接口
|
||||||
|
/// </summary>
|
||||||
|
public interface ITransientDependency
|
||||||
|
{
|
||||||
|
}
|
||||||
@ -1,23 +0,0 @@
|
|||||||
using QYZH.InteractiveMagazine.IService.Dto;
|
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.IService;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信小程序服务接口
|
|
||||||
/// </summary>
|
|
||||||
public interface IWeChatMiniProgramService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 微信登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">微信登录凭证</param>
|
|
||||||
/// <returns>微信登录结果</returns>
|
|
||||||
Task<WeChatLoginOutput> WeChatLoginAsync(string code);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取手机号
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">获取手机号凭证</param>
|
|
||||||
/// <returns>手机号信息</returns>
|
|
||||||
Task<WeChatPhoneNumberOutput> GetPhoneNumberAsync(string code);
|
|
||||||
}
|
|
||||||
@ -44,28 +44,51 @@ public static class JwtHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证JWT令牌
|
/// 获取Token过期时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="token">JWT令牌</param>
|
/// <param name="token">JWT令牌</param>
|
||||||
/// <param name="settings">JWT配置</param>
|
/// <returns>过期时间</returns>
|
||||||
/// <returns>ClaimsPrincipal对象</returns>
|
public static DateTime? GetTokenExpiry(string token)
|
||||||
public static ClaimsPrincipal ValidateToken(string token, JwtSettings settings)
|
|
||||||
{
|
{
|
||||||
var tokenHandler = new JwtSecurityTokenHandler();
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
var key = Encoding.UTF8.GetBytes(settings.SecretKey!);
|
if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken)
|
||||||
|
|
||||||
var validationParameters = new TokenValidationParameters
|
|
||||||
{
|
{
|
||||||
ValidateIssuer = true,
|
return jwtToken.ValidTo;
|
||||||
ValidIssuer = settings.Issuer,
|
}
|
||||||
ValidateAudience = true,
|
return null;
|
||||||
ValidAudience = settings.Audience,
|
}
|
||||||
ValidateIssuerSigningKey = true,
|
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
|
||||||
ValidateLifetime = true,
|
|
||||||
ClockSkew = TimeSpan.Zero
|
|
||||||
};
|
|
||||||
|
|
||||||
return tokenHandler.ValidateToken(token, validationParameters, out _);
|
/// <summary>
|
||||||
|
/// 从Token中获取用户ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">JWT令牌</param>
|
||||||
|
/// <returns>用户ID</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从Token中获取用户名
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="token">JWT令牌</param>
|
||||||
|
/// <returns>用户名</returns>
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据程序集名称获取程序集
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">Web应用程序构建器</param>
|
||||||
|
public static void UseAutofac(this WebApplicationBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory())
|
||||||
|
.ConfigureContainer<ContainerBuilder>((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<RedisSettings>();
|
||||||
|
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<RabbitMQSettings>();
|
||||||
|
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<IConnection>().SingleInstance();
|
||||||
|
containerBuilder.RegisterType<RabbitMQPublisher>().InstancePerDependency();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加在程序集
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder"></param>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据程序集名称获取程序集
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="AssemblyName">程序集名称</param>
|
||||||
|
public static Assembly GetAssemblyByName(string AssemblyName)
|
||||||
|
{
|
||||||
|
return Assembly.Load(AssemblyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,12 +2,8 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.Cache;
|
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.MessageQueue;
|
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||||
using QYZH.InteractiveMagazine.Models.Settings;
|
using QYZH.InteractiveMagazine.Models.Settings;
|
||||||
using RabbitMQ.Client;
|
|
||||||
using StackExchange.Redis;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
namespace QYZH.InteractiveMagazine.Infrastructure.Extensions;
|
||||||
@ -25,11 +21,10 @@ public static class DependencyInjectionExtensions
|
|||||||
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
public static void AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
AddJwtAuthentication(services, configuration);
|
AddJwtAuthentication(services, configuration);
|
||||||
AddRedisCache(services, configuration);
|
|
||||||
AddRabbitMQ(services, configuration);
|
|
||||||
|
|
||||||
services.AddTransient<GlobalExceptionMiddleware>();
|
services.AddTransient<GlobalExceptionMiddleware>();
|
||||||
services.AddTransient<OperationLogMiddleware>();
|
services.AddTransient<OperationLogMiddleware>();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -59,52 +54,4 @@ public static class DependencyInjectionExtensions
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册Redis缓存
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="services">服务集合</param>
|
|
||||||
/// <param name="configuration">配置</param>
|
|
||||||
private static void AddRedisCache(IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var redisSettings = configuration.GetSection("RedisSettings").Get<RedisSettings>()!;
|
|
||||||
|
|
||||||
services.AddSingleton(redisSettings);
|
|
||||||
|
|
||||||
services.AddSingleton<IConnectionMultiplexer>(sp =>
|
|
||||||
{
|
|
||||||
var multiplexer = ConnectionMultiplexer.Connect(redisSettings.ConnectionString!);
|
|
||||||
RedisHelper.Connection = multiplexer;
|
|
||||||
RedisHelper.SetKeyPrefix(redisSettings.InstanceName!);
|
|
||||||
return multiplexer;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册RabbitMQ
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="services">服务集合</param>
|
|
||||||
/// <param name="configuration">配置</param>
|
|
||||||
private static void AddRabbitMQ(IServiceCollection services, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
var rabbitMQSettings = configuration.GetSection("RabbitMQSettings").Get<RabbitMQSettings>()!;
|
|
||||||
|
|
||||||
services.AddSingleton(rabbitMQSettings);
|
|
||||||
|
|
||||||
services.AddSingleton<IConnection>(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<RabbitMQPublisher>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 跨域扩展
|
||||||
|
/// </summary>
|
||||||
|
public static class CorsExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 跨域配置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services"></param>
|
||||||
|
/// <param name="configuration"></param>
|
||||||
|
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<string[]>();
|
||||||
|
policy.WithOrigins(corsUrls ?? Array.Empty<string>());
|
||||||
|
}
|
||||||
|
policy.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials()
|
||||||
|
.WithExposedHeaders("X-New-Token");
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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<JwtSettings>();
|
||||||
|
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
|
||||||
|
{
|
||||||
|
// 忽略自动刷新异常,由后续认证中间件处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,17 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Common\QYZH.InteractiveMagazine.Common.csproj" />
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Common\QYZH.InteractiveMagazine.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.IService\QYZH.InteractiveMagazine.IService.csproj" />
|
||||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac" Version="9.1.0" />
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
|
||||||
<PackageReference Include="RabbitMQ.Client" Version="7.2.1" />
|
<PackageReference Include="RabbitMQ.Client" Version="7.2.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
<PackageReference Include="SqlSugar" Version="5.1.4.207" />
|
|
||||||
<PackageReference Include="StackExchange.Redis" Version="2.13.17" />
|
<PackageReference Include="StackExchange.Redis" Version="2.13.17" />
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.18.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
51
QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs
Normal file
51
QYZH.InteractiveMagazine.Models/Entity/AdminUser.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///后台管理员表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("AdminUser")]
|
||||||
|
public partial class AdminUser : BaseEntity
|
||||||
|
{
|
||||||
|
public AdminUser(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户名
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string UserName {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:密码哈希
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string PasswordHash {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:管理员类型: SuperAdmin, Editor
|
||||||
|
/// Default:Editor
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Inactive
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,21 +23,21 @@ public abstract class BaseEntity
|
|||||||
/// 创建人
|
/// 创建人
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(Length = 50)]
|
[SugarColumn(Length = 50)]
|
||||||
public string? CreatedBy { get; set; }
|
public string? CreatedBy { get; set; } = "System";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建时间
|
/// 创建时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime CreatedTime { get; set; } = DateTime.Now;
|
public DateTime CreatedAt { get; set; } = DateTime.Now;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新人
|
/// 更新人
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SugarColumn(Length = 50)]
|
[SugarColumn(Length = 50)]
|
||||||
public string? UpdatedBy { get; set; }
|
public string? UpdatedBy { get; set; } = "System";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新时间
|
/// 更新时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime UpdatedTime { get; set; } = DateTime.Now;
|
public DateTime? UpdatedAt { get; set; } = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|||||||
58
QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs
Normal file
58
QYZH.InteractiveMagazine.Models/Entity/CheckInConfig.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///签到配置表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("CheckInConfig")]
|
||||||
|
public partial class CheckInConfig : BaseEntity
|
||||||
|
{
|
||||||
|
public CheckInConfig(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:连续签到天数
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int DayNumber {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:奖励积分数
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int RewardPoints {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:额外奖励积分
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BonusPoints {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:配置类型: Daily, Streak
|
||||||
|
/// Default:Daily
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Inactive
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs
Normal file
57
QYZH.InteractiveMagazine.Models/Entity/CheckInRecord.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///签到记录表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("CheckInRecord")]
|
||||||
|
public partial class CheckInRecord : BaseEntity
|
||||||
|
{
|
||||||
|
public CheckInRecord(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:签到日期
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public DateTime CheckInDate {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:本次签到获得积分
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int PointsAwarded {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:连续签到天数
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int ConsecutiveDays {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:签到类型: Normal, MakeUp
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Success
|
||||||
|
/// Default:Success
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs
Normal file
64
QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///社区预置消息表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("CommunityMessage")]
|
||||||
|
public partial class CommunityMessage : BaseEntity
|
||||||
|
{
|
||||||
|
public CommunityMessage(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:期刊Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long JournalId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:消息内容
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Content {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:配图
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string ImageUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:排序
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int SortOrder {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:是否启用
|
||||||
|
/// Default:b'1'
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:消息类型: Article, Quote, Announcement
|
||||||
|
/// Default:Article
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Draft, Published
|
||||||
|
/// Default:Draft
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
QYZH.InteractiveMagazine.Models/Entity/Journal.cs
Normal file
92
QYZH.InteractiveMagazine.Models/Entity/Journal.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///期刊表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("Journal")]
|
||||||
|
public partial class Journal : BaseEntity
|
||||||
|
{
|
||||||
|
public Journal(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:期刊号
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int IssueNumber {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:期刊标题
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Title {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:封面图地址
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string CoverImageUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:摘要
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Summary {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:发布时间
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? PublishDate {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:是否启用
|
||||||
|
/// Default:b'1'
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:排序权重
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int SortOrder {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:编者寄语
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string EditorNote {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主题色
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string ThemeColor {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:期刊类型: Normal, Special
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Draft, Published, Archived
|
||||||
|
/// Default:Draft
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
QYZH.InteractiveMagazine.Models/Entity/Medal.cs
Normal file
79
QYZH.InteractiveMagazine.Models/Entity/Medal.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///勋章定义表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("Medal")]
|
||||||
|
public partial class Medal : BaseEntity
|
||||||
|
{
|
||||||
|
public Medal(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:勋章名称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Name {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:获得条件描述
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Description {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:图片地址
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string ImageUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:条件类型: EvolutionCount, FeedingCount等
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string ConditionType {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:条件阈值
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int ConditionValue {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:排序
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int SortOrder {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:勋章的类型: Pet, Community
|
||||||
|
/// Default:Pet
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Inactive
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs
Normal file
50
QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///模板评论表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("MessageComment")]
|
||||||
|
public partial class MessageComment : BaseEntity
|
||||||
|
{
|
||||||
|
public MessageComment(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:消息Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long MessageId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:模板Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int TemplateId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:评论类型
|
||||||
|
/// Default:TemplateComment
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Published, Hidden
|
||||||
|
/// Default:Published
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs
Normal file
43
QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///点赞记录表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("MessageLike")]
|
||||||
|
public partial class MessageLike : BaseEntity
|
||||||
|
{
|
||||||
|
public MessageLike(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:消息Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long MessageId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:点赞类型
|
||||||
|
/// Default:Like
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Liked, Cancelled
|
||||||
|
/// Default:Liked
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
QYZH.InteractiveMagazine.Models/Entity/Pet.cs
Normal file
64
QYZH.InteractiveMagazine.Models/Entity/Pet.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///宠物实例表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("Pet")]
|
||||||
|
public partial class Pet : BaseEntity
|
||||||
|
{
|
||||||
|
public Pet(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:宠物昵称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Name {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:当前进化形态Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentEvolutionId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:当前成长值
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthPoints {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:累计喂养次数
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int FeedingCount {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:宠物类型
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Sleeping
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs
Normal file
100
QYZH.InteractiveMagazine.Models/Entity/PetEvolution.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///宠物进化链定义表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("PetEvolution")]
|
||||||
|
public partial class PetEvolution : BaseEntity
|
||||||
|
{
|
||||||
|
public PetEvolution(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:阶段名称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string StageName {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:阶段等级
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int StageLevel {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:进化所需成长值
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int RequiredGrowth {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:形态图片
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string ImageUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:前一形态Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public int? PreviousEvolutionId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:基础力量
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BaseStrength {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:基础敏捷
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BaseAgility {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:基础智力
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BaseIntelligence {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:基础魅力
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BaseCharm {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:进化类型: Normal, Special
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Inactive
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs
Normal file
71
QYZH.InteractiveMagazine.Models/Entity/PetFeedingRecord.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///宠物喂养记录表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("PetFeedingRecord")]
|
||||||
|
public partial class PetFeedingRecord : BaseEntity
|
||||||
|
{
|
||||||
|
public PetFeedingRecord(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:宠物Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long PetId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:消耗积分
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int PointsUsed {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:成长值变化量
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthChange {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:喂养前成长值
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthBefore {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:喂养后成长值
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int GrowthAfter {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:喂养类型: Normal, Special
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态
|
||||||
|
/// Default:Success
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs
Normal file
71
QYZH.InteractiveMagazine.Models/Entity/PointsRecord.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///积分流水记录表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("PointsRecord")]
|
||||||
|
public partial class PointsRecord : BaseEntity
|
||||||
|
{
|
||||||
|
public PointsRecord(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:变动数值
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int ChangeAmount {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:变动后余额
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int BalanceAfter {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:变动类型: Exchange, FeedPet, SignIn, TaskReward
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string ChangeType {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:关联业务Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public long? RelatedId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:备注
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Description {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:流水分类
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Success, Failed, Pending
|
||||||
|
/// Default:Success
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
QYZH.InteractiveMagazine.Models/Entity/Product.cs
Normal file
86
QYZH.InteractiveMagazine.Models/Entity/Product.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///虚拟商品表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("Product")]
|
||||||
|
public partial class Product : BaseEntity
|
||||||
|
{
|
||||||
|
public Product(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:商品名称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Name {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:描述
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Description {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:商品图片
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string ImageUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:所需积分
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int Price {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:商品类型: MakeUpCard, PetBg
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: OnSale, OffSale
|
||||||
|
/// Default:OnSale
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:扩展数据
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string MetaData {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:是否上架
|
||||||
|
/// Default:b'1'
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:库存(-1无限)
|
||||||
|
/// Default:-1
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int Stock {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs
Normal file
51
QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///固定模板言论表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("TemplateSentence")]
|
||||||
|
public partial class TemplateSentence : BaseEntity
|
||||||
|
{
|
||||||
|
public TemplateSentence(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:主键
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
[SugarColumn(IsPrimaryKey=true,IsIdentity=true)]
|
||||||
|
public new int Id {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:模板内容
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Sentence {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:是否启用
|
||||||
|
/// Default:b'1'
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public bool IsActive {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:分类: Positive, Neutral, Negative
|
||||||
|
/// Default:Neutral
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Inactive
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
QYZH.InteractiveMagazine.Models/Entity/User.cs
Normal file
71
QYZH.InteractiveMagazine.Models/Entity/User.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///用户表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("User")]
|
||||||
|
public partial class User : BaseEntity
|
||||||
|
{
|
||||||
|
public User(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:微信OpenId
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string OpenId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:微信UnionId
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string UnionId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:昵称
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string NickName {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:头像地址
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string AvatarUrl {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:手机号
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Phone {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:积分余额
|
||||||
|
/// Default:0
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int Points {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户类型: Normal, VIP
|
||||||
|
/// Default:Normal
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Active, Disabled
|
||||||
|
/// Default:Active
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
QYZH.InteractiveMagazine.Models/Entity/UserBag.cs
Normal file
64
QYZH.InteractiveMagazine.Models/Entity/UserBag.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///用户背包表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("UserBag")]
|
||||||
|
public partial class UserBag : BaseEntity
|
||||||
|
{
|
||||||
|
public UserBag(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:物品类型: MakeUpCard, PetBg
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string ItemType {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:关联商品Id或资源Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int ItemId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:数量
|
||||||
|
/// Default:1
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int Quantity {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:扩展信息
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string MetaData {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:背包物品分类
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Available, Expired
|
||||||
|
/// Default:Available
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs
Normal file
50
QYZH.InteractiveMagazine.Models/Entity/UserMedal.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using SqlSugar;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Models.Entity
|
||||||
|
{
|
||||||
|
///<summary>
|
||||||
|
///用户勋章表
|
||||||
|
///</summary>
|
||||||
|
[SugarTable("UserMedal")]
|
||||||
|
public partial class UserMedal : BaseEntity
|
||||||
|
{
|
||||||
|
public UserMedal(){
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:用户Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public long UserId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:勋章Id
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public int MedalId {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:获得时间
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? AwardedAt {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:勋章记录类型
|
||||||
|
/// Default:
|
||||||
|
/// Nullable:True
|
||||||
|
/// </summary>
|
||||||
|
public string Type {get;set;}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Desc:状态: Awarded, Revoked
|
||||||
|
/// Default:Awarded
|
||||||
|
/// Nullable:False
|
||||||
|
/// </summary>
|
||||||
|
public string Status {get;set;}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs
Normal file
26
QYZH.InteractiveMagazine.Models/Enum/ApiVersionEnum.cs
Normal file
@ -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
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Platform
|
||||||
|
/// </summary>
|
||||||
|
[Description("平台管理")]
|
||||||
|
Platform = 10,
|
||||||
|
/// <summary>
|
||||||
|
/// Wechat
|
||||||
|
/// </summary>
|
||||||
|
[Description("小程序管理")]
|
||||||
|
Wechat = 20,
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
13
QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs
Normal file
13
QYZH.InteractiveMagazine.Repository/AdminUserRepository.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Repository;
|
||||||
|
|
||||||
|
public class AdminUserRepository : BaseRepository<AdminUser>, IAdminUserRepository
|
||||||
|
{
|
||||||
|
public async Task<AdminUser?> GetByUserNameAsync(string userName)
|
||||||
|
{
|
||||||
|
return await Db.Queryable<AdminUser>()
|
||||||
|
.Where(x => x.UserName == userName)
|
||||||
|
.FirstAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -146,4 +146,9 @@ public class BaseRepository<T> : IBaseRepository<T> where T : class, new()
|
|||||||
{
|
{
|
||||||
return await Db.Queryable<T>().Where(where).CountAsync();
|
return await Db.Queryable<T>().Where(where).CountAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ISugarQueryable<T> Queryable()
|
||||||
|
{
|
||||||
|
return Db.Queryable<T>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.Repository;
|
||||||
|
|
||||||
|
public interface IAdminUserRepository : IBaseRepository<AdminUser>
|
||||||
|
{
|
||||||
|
Task<AdminUser?> GetByUserNameAsync(string userName);
|
||||||
|
}
|
||||||
@ -8,7 +8,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||||
<PackageReference Include="MySqlConnector" Version="2.5.0" />
|
<PackageReference Include="MySqlConnector" Version="2.5.0" />
|
||||||
<PackageReference Include="SqlSugar" Version="5.1.4.207" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
168
QYZH.InteractiveMagazine.Service/AdminAuthService.cs
Normal file
168
QYZH.InteractiveMagazine.Service/AdminAuthService.cs
Normal file
@ -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<AdminAuthService> _logger;
|
||||||
|
|
||||||
|
private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||||||
|
private const string UserInfoKeyPrefix = "InteractiveMagazine:AdminAuth:UserInfo";
|
||||||
|
|
||||||
|
public AdminAuthService(IAdminUserRepository adminUserRepository, IConfiguration configuration, ILogger<AdminAuthService> logger)
|
||||||
|
{
|
||||||
|
_adminUserRepository = adminUserRepository;
|
||||||
|
_configuration = configuration;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AdminLoginOutput> 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<AdminUserInfoOutput> 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<JwtSettings>()
|
||||||
|
?? 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
267
QYZH.InteractiveMagazine.Service/AdminUserService.cs
Normal file
267
QYZH.InteractiveMagazine.Service/AdminUserService.cs
Normal file
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 管理员用户服务实现
|
||||||
|
/// </summary>
|
||||||
|
public class AdminUserService : IAdminUserService
|
||||||
|
{
|
||||||
|
private readonly IAdminUserRepository _adminUserRepository;
|
||||||
|
private readonly ILogger<AdminUserService> _logger;
|
||||||
|
|
||||||
|
public AdminUserService(IAdminUserRepository adminUserRepository, ILogger<AdminUserService> logger)
|
||||||
|
{
|
||||||
|
_adminUserRepository = adminUserRepository;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建管理员
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminUserOutput> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新管理员
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminUserOutput> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除管理员(软删除)
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据ID获取管理员
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AdminUserOutput> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分页查询管理员列表
|
||||||
|
/// </summary>
|
||||||
|
public async Task<PageListModel<AdminUserOutput>> 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<AdminUserOutput>();
|
||||||
|
|
||||||
|
return new PageListModel<AdminUserOutput>
|
||||||
|
{
|
||||||
|
List = outputList,
|
||||||
|
TotalCount = pageResult.TotalCount,
|
||||||
|
PageIndex = pageResult.PageIndex,
|
||||||
|
PageSize = pageResult.PageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建查询表达式
|
||||||
|
/// </summary>
|
||||||
|
private static Expression<Func<AdminUser, bool>> BuildQueryExpression(AdminUserQueryInput input)
|
||||||
|
{
|
||||||
|
Expression<Func<AdminUser, bool>> where = x => !x.IsDeleted;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(input.UserName))
|
||||||
|
{
|
||||||
|
var userName = input.UserName;
|
||||||
|
Expression<Func<AdminUser, bool>> userNameCondition = x => x.UserName.Contains(userName);
|
||||||
|
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||||
|
Expression.AndAlso(where.Body,
|
||||||
|
Expression.Invoke(userNameCondition, where.Parameters[0])),
|
||||||
|
where.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(input.Type))
|
||||||
|
{
|
||||||
|
var type = input.Type;
|
||||||
|
Expression<Func<AdminUser, bool>> typeCondition = x => x.Type == type;
|
||||||
|
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||||
|
Expression.AndAlso(where.Body,
|
||||||
|
Expression.Invoke(typeCondition, where.Parameters[0])),
|
||||||
|
where.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(input.Status))
|
||||||
|
{
|
||||||
|
var status = input.Status;
|
||||||
|
Expression<Func<AdminUser, bool>> statusCondition = x => x.Status == status;
|
||||||
|
where = Expression.Lambda<Func<AdminUser, bool>>(
|
||||||
|
Expression.AndAlso(where.Body,
|
||||||
|
Expression.Invoke(statusCondition, where.Parameters[0])),
|
||||||
|
where.Parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将实体映射为输出DTO
|
||||||
|
/// </summary>
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 认证服务实现
|
|
||||||
/// </summary>
|
|
||||||
public class AuthService : IAuthService
|
|
||||||
{
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
private readonly ILogger<AuthService> _logger;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 演示用管理员账号
|
|
||||||
/// </summary>
|
|
||||||
private const string DemoAccount = "admin";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 演示用管理员密码
|
|
||||||
/// </summary>
|
|
||||||
private const string DemoPassword = "123456";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configuration">配置</param>
|
|
||||||
/// <param name="logger">日志记录器</param>
|
|
||||||
public AuthService(IConfiguration configuration, ILogger<AuthService> logger)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="account">账号</param>
|
|
||||||
/// <param name="password">密码</param>
|
|
||||||
/// <returns>登录结果</returns>
|
|
||||||
public async Task<LoginOutput> 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 = "管理员"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户注册
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">注册输入参数</param>
|
|
||||||
/// <returns>是否成功</returns>
|
|
||||||
public async Task<bool> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新令牌
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="refreshToken">刷新令牌</param>
|
|
||||||
/// <returns>新的登录结果</returns>
|
|
||||||
public async Task<LoginOutput> 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 = "管理员"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取JWT配置
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>JWT配置对象</returns>
|
|
||||||
private JwtSettings GetJwtSettings()
|
|
||||||
{
|
|
||||||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
|
||||||
?? 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -182,23 +182,19 @@ public class BaseService<T> : IBaseService<T> where T : class, new()
|
|||||||
if (entity is BaseEntity baseEntity)
|
if (entity is BaseEntity baseEntity)
|
||||||
{
|
{
|
||||||
var now = DateTime.Now;
|
var now = DateTime.Now;
|
||||||
baseEntity.CreatedTime = now;
|
baseEntity.CreatedAt = now;
|
||||||
baseEntity.UpdatedTime = now;
|
baseEntity.UpdatedAt = now;
|
||||||
baseEntity.CreatedBy = baseEntity.CreatedBy ?? "system";
|
baseEntity.CreatedBy = baseEntity.CreatedBy ?? "system";
|
||||||
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
||||||
baseEntity.IsDeleted = false;
|
baseEntity.IsDeleted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置更新时的审计字段
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">实体对象</param>
|
|
||||||
private static void SetAuditFieldsOnUpdate(T entity)
|
private static void SetAuditFieldsOnUpdate(T entity)
|
||||||
{
|
{
|
||||||
if (entity is BaseEntity baseEntity)
|
if (entity is BaseEntity baseEntity)
|
||||||
{
|
{
|
||||||
baseEntity.UpdatedTime = DateTime.Now;
|
baseEntity.UpdatedAt = DateTime.Now;
|
||||||
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
baseEntity.UpdatedBy = baseEntity.UpdatedBy ?? "system";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
<PackageReference Include="AutoMapper" Version="16.1.1" />
|
||||||
|
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信小程序服务实现
|
|
||||||
/// </summary>
|
|
||||||
public class WeChatMiniProgramService : IWeChatMiniProgramService
|
|
||||||
{
|
|
||||||
private readonly IConfiguration _configuration;
|
|
||||||
private readonly ILogger<WeChatMiniProgramService> _logger;
|
|
||||||
private const string Code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
|
|
||||||
private const string GetPhoneNumberUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="configuration">配置</param>
|
|
||||||
/// <param name="logger">日志记录器</param>
|
|
||||||
public WeChatMiniProgramService(IConfiguration configuration, ILogger<WeChatMiniProgramService> logger)
|
|
||||||
{
|
|
||||||
_configuration = configuration;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">微信登录凭证</param>
|
|
||||||
/// <returns>微信登录结果</returns>
|
|
||||||
public async Task<WeChatLoginOutput> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取手机号
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">获取手机号凭证</param>
|
|
||||||
/// <returns>手机号信息</returns>
|
|
||||||
public async Task<WeChatPhoneNumberOutput> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 模拟调用微信code2session接口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">登录凭证</param>
|
|
||||||
/// <param name="weChatSettings">微信配置</param>
|
|
||||||
/// <returns>openId</returns>
|
|
||||||
private async Task<string> 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<dynamic>(url);
|
|
||||||
// if (response?.errcode == 0) return response.openid;
|
|
||||||
|
|
||||||
await Task.Delay(100); // 模拟网络请求延迟
|
|
||||||
|
|
||||||
return $"demo_openid_{code.GetHashCode():X}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 模拟调用微信获取手机号接口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">获取手机号凭证</param>
|
|
||||||
/// <returns>手机号</returns>
|
|
||||||
private async Task<string> 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<dynamic>(tokenUrl);
|
|
||||||
// var accessToken = tokenResponse.access_token;
|
|
||||||
// var phoneUrl = $"{GetPhoneNumberUrl}?access_token={accessToken}";
|
|
||||||
// var phoneResponse = await HttpHelper.PostAsync<dynamic>(phoneUrl, new { code });
|
|
||||||
|
|
||||||
await Task.Delay(100); // 模拟网络请求延迟
|
|
||||||
|
|
||||||
return "13800138000";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取微信配置
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>微信配置对象</returns>
|
|
||||||
private WeChatSettings GetWeChatSettings()
|
|
||||||
{
|
|
||||||
return _configuration.GetSection("WeChatSettings").Get<WeChatSettings>()
|
|
||||||
?? new WeChatSettings
|
|
||||||
{
|
|
||||||
AppId = "demo_app_id",
|
|
||||||
AppSecret = "demo_app_secret"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取JWT配置
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>JWT配置对象</returns>
|
|
||||||
private JwtSettings GetJwtSettings()
|
|
||||||
{
|
|
||||||
return _configuration.GetSection("JwtSettings").Get<JwtSettings>()
|
|
||||||
?? new JwtSettings
|
|
||||||
{
|
|
||||||
Issuer = "QYZH.InteractiveMagazine",
|
|
||||||
Audience = "QYZH.InteractiveMagazine.Client",
|
|
||||||
SecretKey = "QYZH_InteractiveMagazine_SecretKey_2024",
|
|
||||||
ExpiryMinutes = 120
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
205
QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs
Normal file
205
QYZH.InteractiveMagazine.WebApi/Controllers/AdminController.cs
Normal file
@ -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<AdminController> _logger;
|
||||||
|
|
||||||
|
public AdminController(IAdminAuthService adminAuthService, IAdminUserService adminUserService, ILogger<AdminController> logger)
|
||||||
|
{
|
||||||
|
_adminAuthService = adminAuthService;
|
||||||
|
_adminUserService = adminUserService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AllowAnonymous]
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<BaseResponse<AdminLoginOutput>> LoginAsync([FromBody] AdminLoginInput input)
|
||||||
|
{
|
||||||
|
var result = await _adminAuthService.LoginAsync(input);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("logout")]
|
||||||
|
public async Task<BaseResponse<object>> LogoutAsync()
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return Fail("未获取到用户信息", 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _adminAuthService.LogoutAsync(userId.Value);
|
||||||
|
return Success(new object(), "登出成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("info")]
|
||||||
|
public async Task<BaseResponse<AdminUserInfoOutput>> GetAdminInfoAsync()
|
||||||
|
{
|
||||||
|
var userId = GetCurrentUserId();
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
return BaseResponse<AdminUserInfoOutput>.Fail("未获取到用户信息", 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _adminAuthService.GetAdminInfoAsync(userId.Value);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("changePassword")]
|
||||||
|
public async Task<BaseResponse<object>> 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(), "密码修改成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">管理员输入</param>
|
||||||
|
/// <returns>创建的管理员信息</returns>
|
||||||
|
[HttpPost("users")]
|
||||||
|
public async Task<BaseResponse<AdminUserOutput>> 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<AdminUserOutput>.Fail(ex.Message, ex.Code);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "创建管理员系统异常,参数:{Input}", input);
|
||||||
|
return BaseResponse<AdminUserOutput>.Fail("创建管理员失败,请稍后重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
/// <param name="input">管理员输入</param>
|
||||||
|
/// <returns>更新后的管理员信息</returns>
|
||||||
|
[HttpPut("users/{id}")]
|
||||||
|
public async Task<BaseResponse<AdminUserOutput>> 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<AdminUserOutput>.Fail(ex.Message, ex.Code);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "更新管理员系统异常,ID:{Id},参数:{Input}", id, input);
|
||||||
|
return BaseResponse<AdminUserOutput>.Fail("更新管理员失败,请稍后重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 删除管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
/// <returns>操作结果</returns>
|
||||||
|
[HttpDelete("users/{id}")]
|
||||||
|
public async Task<BaseResponse<object>> DeleteUserAsync(long id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _adminUserService.DeleteAsync(id);
|
||||||
|
return Success(new object(), "删除管理员成功");
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "删除管理员业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<object>.Fail(ex.Message, ex.Code);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "删除管理员系统异常,ID:{Id}", id);
|
||||||
|
return BaseResponse<object>.Fail("删除管理员失败,请稍后重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据ID获取管理员
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">管理员ID</param>
|
||||||
|
/// <returns>管理员信息</returns>
|
||||||
|
[HttpGet("users/{id}")]
|
||||||
|
public async Task<BaseResponse<AdminUserOutput>> GetUserByIdAsync(long id)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await _adminUserService.GetByIdAsync(id);
|
||||||
|
return Success(result);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "获取管理员业务异常: {Message}", ex.Message);
|
||||||
|
return BaseResponse<AdminUserOutput>.Fail(ex.Message, ex.Code);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "获取管理员系统异常,ID:{Id}", id);
|
||||||
|
return BaseResponse<AdminUserOutput>.Fail("获取管理员信息失败,请稍后重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分页查询管理员列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">查询条件</param>
|
||||||
|
/// <returns>分页结果</returns>
|
||||||
|
[HttpPost("users/list")]
|
||||||
|
public async Task<BaseResponse<PageListModel<AdminUserOutput>>> 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<PageListModel<AdminUserOutput>>.Fail(ex.Message, ex.Code);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "查询管理员列表系统异常,参数:{Input}", input);
|
||||||
|
return BaseResponse<PageListModel<AdminUserOutput>>.Fail("查询管理员列表失败,请稍后重试", 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChangePasswordInput
|
||||||
|
{
|
||||||
|
public string OldPassword { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string NewPassword { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 认证控制器
|
|
||||||
/// </summary>
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
public class AuthController : BaseController
|
|
||||||
{
|
|
||||||
private readonly IAuthService _authService;
|
|
||||||
|
|
||||||
public AuthController(IAuthService authService)
|
|
||||||
{
|
|
||||||
_authService = authService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">登录信息</param>
|
|
||||||
/// <returns>登录结果</returns>
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("login")]
|
|
||||||
public async Task<BaseResponse<LoginOutput>> LoginAsync([FromBody] LoginInput input)
|
|
||||||
{
|
|
||||||
var result = await _authService.LoginAsync(input.Account, input.Password);
|
|
||||||
return Success(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 用户注册
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">注册信息</param>
|
|
||||||
/// <returns>是否成功</returns>
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("register")]
|
|
||||||
public async Task<BaseResponse<bool>> RegisterAsync([FromBody] RegisterInput input)
|
|
||||||
{
|
|
||||||
var result = await _authService.RegisterAsync(input);
|
|
||||||
return Success(result, "注册成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新令牌
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="refreshToken">刷新令牌</param>
|
|
||||||
/// <returns>新的登录结果</returns>
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpPost("refreshToken")]
|
|
||||||
public async Task<BaseResponse<LoginOutput>> RefreshTokenAsync([FromQuery] string refreshToken)
|
|
||||||
{
|
|
||||||
var result = await _authService.RefreshTokenAsync(refreshToken);
|
|
||||||
return Success(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using QYZH.InteractiveMagazine.Models.Dto;
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
|
||||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
||||||
|
|
||||||
@ -8,6 +9,7 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
|
||||||
public abstract class BaseController : ControllerBase
|
public abstract class BaseController : ControllerBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信小程序控制器
|
|
||||||
/// </summary>
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class WeChatController : BaseController
|
|
||||||
{
|
|
||||||
private readonly IWeChatMiniProgramService _weChatService;
|
|
||||||
|
|
||||||
public WeChatController(IWeChatMiniProgramService weChatService)
|
|
||||||
{
|
|
||||||
_weChatService = weChatService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 微信登录
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="code">微信登录凭证</param>
|
|
||||||
/// <returns>微信登录结果</returns>
|
|
||||||
[HttpPost("login")]
|
|
||||||
public async Task<BaseResponse<WeChatLoginOutput>> WeChatLoginAsync([FromQuery] string code)
|
|
||||||
{
|
|
||||||
var result = await _weChatService.WeChatLoginAsync(code);
|
|
||||||
return Success(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
19
QYZH.InteractiveMagazine.WebApi/Dockerfile
Normal file
19
QYZH.InteractiveMagazine.WebApi/Dockerfile
Normal file
@ -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"]
|
||||||
@ -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.Extensions;
|
||||||
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
using QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
using QYZH.InteractiveMagazine.Repository;
|
using QYZH.InteractiveMagazine.Repository;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerUI;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Autofacs;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// autofac注入 允许使用autofac作为DI容器
|
||||||
|
builder.UseAutofac();
|
||||||
|
|
||||||
// 配置Serilog
|
// 配置Serilog
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.ReadFrom.Configuration(builder.Configuration)
|
.ReadFrom.Configuration(builder.Configuration)
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.WriteTo.Console()
|
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
builder.Host.UseSerilog();
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
// 注册服务
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
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<ApiVersionEnum>().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<ApiExplorerSettingsAttribute>()
|
||||||
|
.FirstOrDefault()?
|
||||||
|
.GroupName;
|
||||||
|
|
||||||
|
// 匹配当前文档(分组)则显示
|
||||||
|
return groupName == docName;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddInfrastructureServices(builder.Configuration);
|
builder.Services.AddInfrastructureServices(builder.Configuration);
|
||||||
|
|
||||||
// 注册中间件
|
|
||||||
builder.Services.AddTransient<GlobalExceptionMiddleware>();
|
|
||||||
builder.Services.AddTransient<OperationLogMiddleware>();
|
|
||||||
|
|
||||||
// 注册业务服务
|
|
||||||
builder.Services.AddScoped<QYZH.InteractiveMagazine.IService.IAuthService, QYZH.InteractiveMagazine.Service.AuthService>();
|
|
||||||
builder.Services.AddScoped<QYZH.InteractiveMagazine.IService.IWeChatMiniProgramService, QYZH.InteractiveMagazine.Service.WeChatMiniProgramService>();
|
|
||||||
|
|
||||||
// 初始化SqlSugar
|
// 初始化SqlSugar
|
||||||
SqlSugarDbContext.Init(builder.Configuration);
|
SqlSugarDbContext.Init(builder.Configuration);
|
||||||
|
|
||||||
|
|
||||||
// 添加CORS
|
// 添加CORS
|
||||||
builder.Services.AddCors(options =>
|
builder.Services.AddCors(options =>
|
||||||
{
|
{
|
||||||
@ -46,19 +106,27 @@ builder.Services.AddCors(options =>
|
|||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// 配置HTTP请求管道
|
|
||||||
if (app.Environment.IsDevelopment())
|
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI(c =>
|
||||||
|
{
|
||||||
|
// 根据版本名称倒序 遍历展示
|
||||||
|
Enum.GetValues<ApiVersionEnum>().OrderBy(e => e).ToList().ForEach(version =>
|
||||||
|
{
|
||||||
|
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"{version.GetDescription()}接口");
|
||||||
|
});
|
||||||
|
c.DocExpansion(DocExpansion.None); // ->修改界面打开时自动折叠
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseCors("AllowAll");
|
app.UseCors("AllowAll");
|
||||||
app.UseMiddleware<GlobalExceptionMiddleware>();
|
app.UseMiddleware<GlobalExceptionMiddleware>();
|
||||||
app.UseMiddleware<OperationLogMiddleware>();
|
app.UseMiddleware<OperationLogMiddleware>();
|
||||||
|
app.UseMiddleware<JwtAutoRefreshMiddleware>();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,13 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autofac" Version="9.1.0" />
|
||||||
|
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="11.0.0" />
|
||||||
|
<PackageReference Include="BCrypt.Net-Next" Version="4.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.27" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.27" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@ -19,4 +23,8 @@
|
|||||||
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
<ProjectReference Include="..\QYZH.InteractiveMagazine.Models\QYZH.InteractiveMagazine.Models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Controllers\WeChat\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"ConnectionStrings": {
|
"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": {
|
"JwtSettings": {
|
||||||
"Issuer": "QYZH.InteractiveMagazine",
|
"Issuer": "QYZH.InteractiveMagazine",
|
||||||
"Audience": "QYZH.InteractiveMagazine.Client",
|
"Audience": "QYZH.InteractiveMagazine",
|
||||||
"SecretKey": "your-256-bit-secret-key-here-change-in-production",
|
"SecretKey": "zG7pLqR9xVw2bN8fYtHk3mPc5sA1dF6eUjW4gXhC7vB",
|
||||||
"ExpiryMinutes": 120
|
"ExpiryMinutes": 120
|
||||||
},
|
},
|
||||||
"RedisSettings": {
|
"RedisSettings": {
|
||||||
"ConnectionString": "localhost:6379",
|
"ConnectionString": "192.168.20.150:16379,defaultDatabase=5",
|
||||||
"InstanceName": "interactive_magazine"
|
"InstanceName": "interactive_magazine"
|
||||||
},
|
},
|
||||||
"RabbitMQSettings": {
|
"RabbitMQSettings": {
|
||||||
"HostName": "localhost",
|
"HostName": "192.168.20.150",
|
||||||
"Port": 5672,
|
"Port": 5672,
|
||||||
"UserName": "guest",
|
"UserName": "smartschool",
|
||||||
"Password": "guest",
|
"Password": "@ss%&*otz%d*pq2S",
|
||||||
"VirtualHost": "/"
|
"VirtualHost": "InteractiveMagazine"
|
||||||
},
|
},
|
||||||
"WeChatSettings": {
|
"WeChatSettings": {
|
||||||
"AppId": "your-wechat-appid",
|
"AppId": "your-wechat-appid",
|
||||||
|
|||||||
Reference in New Issue
Block a user