feat: 完成多模块功能迭代与优化
1. 新增宠物皮肤初始枚举值、下拉列表接口及相关DTO 2. 完善商品实体与DTO,新增虚拟商品标记和类型ID字段 3. 重构用户认证体系,支持多用户切换并重新生成JWT令牌 4. 新增签到配置管理全套功能,包括增删改查和状态管理 5. 优化模型验证过滤器和基础响应类的命名规范 6. 新增补签功能,完善签到服务逻辑 7. 拆分用户详情DTO,新增各子数据分页查询接口 8. 重构微信控制器的用户ID获取逻辑,统一使用激活用户ID 9. 修复背包服务中补签卡的扣减逻辑 10. 新增家长姓名修改接口和相关服务实现
This commit is contained in:
52
QYZH.InteractiveMagazine.IService/ICheckInConfigService.cs
Normal file
52
QYZH.InteractiveMagazine.IService/ICheckInConfigService.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.IService;
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置服务接口
|
||||
/// </summary>
|
||||
public interface ICheckInConfigService : IBaseService<CheckInConfig>
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建签到配置
|
||||
/// </summary>
|
||||
/// <param name="input">签到配置输入</param>
|
||||
/// <returns>创建的签到配置信息</returns>
|
||||
Task<CheckInConfigOutput> CreateAsync(CheckInConfigInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <param name="input">签到配置输入</param>
|
||||
/// <returns>更新后的签到配置信息</returns>
|
||||
Task<CheckInConfigOutput> UpdateAsync(long id, CheckInConfigInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除签到配置(软删除)
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
Task DeleteAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取签到配置
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>签到配置信息</returns>
|
||||
Task<CheckInConfigOutput> GetByIdAsync(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到配置列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<PageListModel<CheckInConfigOutput>> GetListAsync(CheckInConfigQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置启用/禁用状态
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
Task<bool> UpdateStatusAsync(long id);
|
||||
}
|
||||
@ -169,4 +169,10 @@ public interface IPetService : IBaseService<UserPet>
|
||||
/// 根据皮肤Id获取各阶段图片列表(按进化阶段分组,含阶段基本信息)
|
||||
/// </summary>
|
||||
Task<List<SkinEvolutionStageOutput>> GetSkinImagesGroupedBySkinIdAsync(long skinId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有皮肤下拉列表(仅返回Id和Name,按SortOrder排序)
|
||||
/// </summary>
|
||||
/// <param name="types">皮肤类型列表(可选)</param>
|
||||
Task<List<SelectOptionDto>> GetAllSkinsForSelectAsync(List<string>? types = null);
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Points;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
|
||||
@ -12,7 +14,7 @@ public interface IUsersService : IBaseService<Users>
|
||||
Task<BaseResponse<PageListModel<UsersOutput>>> GetListAsync(UsersQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表)
|
||||
/// 获取用户详情(仅基本信息)
|
||||
/// </summary>
|
||||
Task<BaseResponse<UserDetailOutput>> GetDetailAsync(long id);
|
||||
|
||||
@ -30,4 +32,24 @@ public interface IUsersService : IBaseService<Users>
|
||||
/// 手动扣除用户积分
|
||||
/// </summary>
|
||||
Task<ManualPointsOutput> ManualDeductPointsAsync(long userId, ManualDeductPointsInput input, long operatorId, string operatorName, string? ipAddress = null);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户积分记录
|
||||
/// </summary>
|
||||
Task<BaseResponse<PageListModel<PointsRecordOutput>>> GetUserPointsRecordsAsync(long userId, PointsRecordQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户签到记录
|
||||
/// </summary>
|
||||
Task<BaseResponse<PageListModel<CheckInRecordOutput>>> GetUserCheckInRecordsAsync(long userId, PageQueryModel input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户补偿任务
|
||||
/// </summary>
|
||||
Task<BaseResponse<PageListModel<CompensationTaskOutput>>> GetUserCompensationTasksAsync(long userId, CompensationTaskQueryInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户期刊列表(含期刊详情)
|
||||
/// </summary>
|
||||
Task<BaseResponse<PageListModel<UserJournalItemOutput>>> GetUserJournalsAsync(long userId, UserJournalQueryInput input);
|
||||
}
|
||||
|
||||
@ -19,9 +19,12 @@ public interface IWeChatAuthService : IBaseService<WxUser>
|
||||
Task<WeChatLoginOutput> QuickLoginAsync(WeChatQuickLoginInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 WxUser 下切换 User 身份,JWT 基于 WxUser 无需重新生成 Token)
|
||||
/// 切换用户(同一 WxUser 下切换 User 身份,重新生成 Token)
|
||||
/// </summary>
|
||||
Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, WeChatSwitchUserInput input);
|
||||
/// <param name="wxUserId">当前 WxUser.Id(来自 JWT)</param>
|
||||
/// <param name="currentUserId">当前激活 Users.Id(来自 JWT,用于清除旧 Redis Token)</param>
|
||||
/// <param name="input">切换目标用户参数</param>
|
||||
Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, long currentUserId, WeChatSwitchUserInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前 WxUser 下所有用户列表
|
||||
@ -37,4 +40,12 @@ public interface IWeChatAuthService : IBaseService<WxUser>
|
||||
/// <param name="input">新增用户参数</param>
|
||||
/// <returns>新创建的用户信息</returns>
|
||||
Task<WxUserOutput> CreateUserAsync(long wxUserId, CreateChildUserInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 修改家长名字(WxUser.Name)
|
||||
/// </summary>
|
||||
/// <param name="wxUserId">当前登录的 WxUser.Id(来自 JWT)</param>
|
||||
/// <param name="input">修改名字参数</param>
|
||||
/// <returns>更新后的微信用户信息</returns>
|
||||
Task<WxUserInfoOutput> UpdateWxUserNameAsync(long wxUserId, UpdateWxUserNameInput input);
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ namespace QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
public static class JwtHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成JWT令牌
|
||||
/// 生成JWT令牌(管理端 / 单用户场景)
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="userName">用户名</param>
|
||||
@ -29,6 +29,39 @@ public static class JwtHelper
|
||||
new Claim(ClaimTypes.Name, userName)
|
||||
};
|
||||
|
||||
return BuildToken(claims, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成JWT令牌(小程序场景,同时携带 WxUserId 和 UserId)
|
||||
/// </summary>
|
||||
/// <param name="wxUserId">微信用户ID(WxUser.Id)</param>
|
||||
/// <param name="userId">当前激活用户ID(Users.Id,无用户时为 0)</param>
|
||||
/// <param name="userName">用户名</param>
|
||||
/// <param name="settings">JWT配置</param>
|
||||
/// <returns>JWT令牌字符串</returns>
|
||||
public static string GenerateToken(long wxUserId, long userId, string userName, JwtSettings settings)
|
||||
{
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Name, userName),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||
new Claim(ClaimTypes.Name, userName),
|
||||
new Claim(WxUserIdClaimType, wxUserId.ToString())
|
||||
};
|
||||
|
||||
return BuildToken(claims, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义 Claim 类型:WxUserId
|
||||
/// </summary>
|
||||
public const string WxUserIdClaimType = "WxUserId";
|
||||
|
||||
private static string BuildToken(Claim[] claims, JwtSettings settings)
|
||||
{
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(settings.SecretKey!));
|
||||
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
@ -77,6 +110,25 @@ public static class JwtHelper
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Token中获取微信用户ID(WxUserId)
|
||||
/// </summary>
|
||||
/// <param name="token">JWT令牌</param>
|
||||
/// <returns>WxUserId</returns>
|
||||
public static long? GetWxUserIdFromToken(string token)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
if (tokenHandler.ReadToken(token) is JwtSecurityToken jwtToken)
|
||||
{
|
||||
var wxUserIdClaim = jwtToken.Claims.FirstOrDefault(c => c.Type == WxUserIdClaimType);
|
||||
if (long.TryParse(wxUserIdClaim?.Value, out long wxUserId))
|
||||
{
|
||||
return wxUserId;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Token中获取用户名
|
||||
/// </summary>
|
||||
|
||||
@ -24,10 +24,10 @@ public class ModelValidActionFilterAttribute : ActionFilterAttribute
|
||||
errorDic.Add(key, errorStr);
|
||||
}
|
||||
}
|
||||
var result = new BaseResponse<Dictionary<string, string>>() { Code = ResultCode.FAIL };
|
||||
var result = new BaseResponse<Dictionary<string, string>>() { code = ResultCode.FAIL };
|
||||
|
||||
result.Message = string.Join("|", errorDic.Select(e => e.Value).Distinct());
|
||||
result.Result = errorDic;
|
||||
result.message = string.Join("|", errorDic.Select(e => e.Value).Distinct());
|
||||
result.result = errorDic;
|
||||
context.Result = new JsonResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ public class BaseResponse
|
||||
/// </summary>
|
||||
public BaseResponse()
|
||||
{
|
||||
Code = ResultCode.SUCCESS;
|
||||
code = ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
#region 公共属性
|
||||
@ -21,12 +21,12 @@ public class BaseResponse
|
||||
/// <summary>
|
||||
/// 操作描述
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
public string message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结果Code
|
||||
/// </summary>
|
||||
public ResultCode Code { get; set; }
|
||||
public ResultCode code { get; set; }
|
||||
|
||||
#endregion 公共属性
|
||||
|
||||
@ -37,7 +37,7 @@ public class BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse Success()
|
||||
{
|
||||
return new BaseResponse { Code = ResultCode.SUCCESS, Message = "操作成功。" };
|
||||
return new BaseResponse { code = ResultCode.SUCCESS, message = "操作成功。" };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -45,7 +45,7 @@ public class BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse Success(string message = "操作成功。")
|
||||
{
|
||||
return new BaseResponse { Code = ResultCode.SUCCESS, Message = message };
|
||||
return new BaseResponse { code = ResultCode.SUCCESS, message = message };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -53,7 +53,7 @@ public class BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse Fail(string message, ResultCode code = ResultCode.FAIL)
|
||||
{
|
||||
return new BaseResponse { Code = code, Message = message };
|
||||
return new BaseResponse { code = code, message = message };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -61,7 +61,7 @@ public class BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse AuthFail(string message, ResultCode code = ResultCode.FAIL)
|
||||
{
|
||||
return new BaseResponse { Code = code, Message = message };
|
||||
return new BaseResponse { code = code, message = message };
|
||||
}
|
||||
|
||||
#endregion 公用方法
|
||||
@ -69,7 +69,7 @@ public class BaseResponse
|
||||
/// <summary>
|
||||
/// 操作结果
|
||||
/// </summary>
|
||||
public bool IsSuccess => Code == ResultCode.SUCCESS;
|
||||
public bool isSuccess => code == ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -82,7 +82,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// <summary>
|
||||
/// 操作结果
|
||||
/// </summary>
|
||||
public T? Result { get; set; }
|
||||
public T? result { get; set; }
|
||||
|
||||
#region 公用方法
|
||||
|
||||
@ -91,7 +91,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse<T> Success(T result)
|
||||
{
|
||||
return new BaseResponse<T> { Code = ResultCode.SUCCESS, Message = "", Result = result };
|
||||
return new BaseResponse<T> { code = ResultCode.SUCCESS, message = "", result = result };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -99,7 +99,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse<T> Success(T result, string message = "操作成功。")
|
||||
{
|
||||
return new BaseResponse<T> { Code = ResultCode.SUCCESS, Message = message, Result = result };
|
||||
return new BaseResponse<T> { code = ResultCode.SUCCESS, message = message, result = result };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -107,7 +107,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse<T> Fail(string message = "fail")
|
||||
{
|
||||
return new BaseResponse<T> { Code = ResultCode.FAIL, Message = message };
|
||||
return new BaseResponse<T> { code = ResultCode.FAIL, message = message };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -115,12 +115,12 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse<T> Fail(ResultCode code, string message = "fail")
|
||||
{
|
||||
return new BaseResponse<T> { Code = code, Message = message };
|
||||
return new BaseResponse<T> { code = code, message = message };
|
||||
}
|
||||
|
||||
public static BaseResponse<T> Fail(T result, string message = "操作失败,请稍后再试")
|
||||
{
|
||||
return new BaseResponse<T> { Code = ResultCode.FAIL, Message = message, Result = result };
|
||||
return new BaseResponse<T> { code = ResultCode.FAIL, message = message, result = result };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -128,7 +128,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// </summary>
|
||||
public static BaseResponse<T> AuthFail(string message = "Permission authentication failed")
|
||||
{
|
||||
return new BaseResponse<T> { Code = ResultCode.OAUTH_FAIL, Message = message };
|
||||
return new BaseResponse<T> { code = ResultCode.OAUTH_FAIL, message = message };
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
@ -149,7 +149,7 @@ public class BaseResponse<T> : BaseResponse
|
||||
/// <summary>
|
||||
/// 操作结果
|
||||
/// </summary>
|
||||
public bool IsSuccess => Code == ResultCode.SUCCESS;
|
||||
public bool isSuccess => code == ResultCode.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置创建/更新输入
|
||||
/// </summary>
|
||||
public class CheckInConfigInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 连续签到天数
|
||||
/// </summary>
|
||||
public int DayNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖励积分数
|
||||
/// </summary>
|
||||
public int RewardPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外奖励积分
|
||||
/// </summary>
|
||||
public int BonusPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置类型: Daily, Streak
|
||||
/// </summary>
|
||||
public CheckInConfigTypeEnum Type { get; set; } = CheckInConfigTypeEnum.Daily;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置输出
|
||||
/// </summary>
|
||||
public class CheckInConfigOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连续签到天数
|
||||
/// </summary>
|
||||
public int DayNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 奖励积分数
|
||||
/// </summary>
|
||||
public int RewardPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 额外奖励积分
|
||||
/// </summary>
|
||||
public int BonusPoints { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置类型: Daily, Streak
|
||||
/// </summary>
|
||||
public CheckInConfigTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <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 CheckInConfigQueryInput : PageQueryModel
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置类型筛选: Daily, Streak
|
||||
/// </summary>
|
||||
public CheckInConfigTypeEnum? Type { get; set; }
|
||||
}
|
||||
@ -94,6 +94,17 @@ public class CheckInInfoOutput
|
||||
public List<CheckInRecordOutput> RecentRecords { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补签输入
|
||||
/// </summary>
|
||||
public class MakeUpCheckInInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 补签目标日期(yyyy-MM-dd)
|
||||
/// </summary>
|
||||
public DateTime TargetDate { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 签到记录输出
|
||||
/// </summary>
|
||||
@ -108,6 +119,10 @@ public class CheckInRecordOutput
|
||||
/// 签到日期
|
||||
/// </summary>
|
||||
public DateTime CheckInDate { get; set; }
|
||||
/// <summary>
|
||||
/// 签到时间
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连续签到天数
|
||||
|
||||
@ -52,6 +52,16 @@ public class ProductInput
|
||||
/// 库存(-1无限)
|
||||
/// </summary>
|
||||
public int Stock { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否是虚拟商品
|
||||
/// </summary>
|
||||
public bool IsVirtual { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type对应的Id
|
||||
/// </summary>
|
||||
public long TypeId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -128,6 +138,16 @@ public class ProductOutput
|
||||
/// 更新时间
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是虚拟商品
|
||||
/// </summary>
|
||||
public bool IsVirtual { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type对应的Id
|
||||
/// </summary>
|
||||
public long TypeId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -12,6 +12,16 @@ public class WxProductOutput
|
||||
public int Price { get; set; }
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 是否是虚拟商品
|
||||
/// </summary>
|
||||
public bool IsVirtual { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type对应的Id
|
||||
/// </summary>
|
||||
public long TypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 皮肤信息(仅 PetBg 类型有值)
|
||||
/// </summary>
|
||||
|
||||
17
QYZH.InteractiveMagazine.Models/Dto/SelectOptionDto.cs
Normal file
17
QYZH.InteractiveMagazine.Models/Dto/SelectOptionDto.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
/// 下拉列表选项基类
|
||||
/// </summary>
|
||||
public class SelectOptionDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 选项Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项显示名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
@ -1,7 +1,3 @@
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Points;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Models.Dto;
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +58,7 @@ public class UsersOutput
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户详情输出DTO(包含基本信息、积分记录、签到记录、补偿任务、期刊列表)
|
||||
/// 用户详情输出DTO(仅基本信息)
|
||||
/// </summary>
|
||||
public class UserDetailOutput
|
||||
{
|
||||
@ -70,26 +66,6 @@ public class UserDetailOutput
|
||||
/// 用户基本信息
|
||||
/// </summary>
|
||||
public UsersOutput BasicInfo { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 积分使用记录(最近20条)
|
||||
/// </summary>
|
||||
public List<PointsRecordOutput> PointsRecords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 签到记录(最近30条)
|
||||
/// </summary>
|
||||
public List<CheckInRecordOutput> CheckInRecords { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 失败的补偿任务(需要手动处理)
|
||||
/// </summary>
|
||||
public List<CompensationTaskOutput> FailedCompensationTasks { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// 用户拥有的期刊列表
|
||||
/// </summary>
|
||||
public List<UserJournalItemOutput> Journals { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -36,10 +36,15 @@ public class WeChatQuickLoginInput
|
||||
public class WeChatLoginOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 访问令牌(基于 WxUser,不绑定具体 User)
|
||||
/// 访问令牌(同时携带 WxUserId 和当前激活 UserId)
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 当前激活用户ID(Users.Id,无用户时为 0)
|
||||
/// </summary>
|
||||
public long CurrentUserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户信息
|
||||
/// </summary>
|
||||
@ -171,10 +176,26 @@ public class WeChatSwitchUserInput
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信切换用户输出(JWT 基于 WxUser,切换不更换 Token)
|
||||
/// 修改家长名字输入
|
||||
/// </summary>
|
||||
public class UpdateWxUserNameInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 新名字
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信切换用户输出(重新生成 Token,切换激活用户)
|
||||
/// </summary>
|
||||
public class WeChatSwitchUserOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 新的访问令牌(指向切换后的用户)
|
||||
/// </summary>
|
||||
public string Token { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 当前切换的用户详情
|
||||
/// </summary>
|
||||
|
||||
@ -9,65 +9,75 @@ namespace QYZH.InteractiveMagazine.Models.Entity
|
||||
[SugarTable("Product")]
|
||||
public partial class Product : SqlSugarBaseEntity
|
||||
{
|
||||
public Product(){
|
||||
public Product()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Desc:商品名称
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public string Name {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 Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:商品图片
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string ImageUrl {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:所需积分
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Price { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:商品类型: MakeUpCard, PetBg
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public ProductTypeEnum Type {get;set;}
|
||||
/// <summary>
|
||||
/// Desc:商品类型: MakeUpCard, PetBg
|
||||
/// Default:
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public ProductTypeEnum Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:扩展数据
|
||||
/// Default:
|
||||
/// Nullable:True
|
||||
/// </summary>
|
||||
public string MetaData {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:是否上架
|
||||
/// Default:b'1'
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:库存(-1无限)
|
||||
/// Default:-1
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Stock { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:是否是虚拟商品
|
||||
/// </summary>
|
||||
public bool IsVirtual { get; set; }
|
||||
/// <summary>
|
||||
/// Desc:Type对应的Id
|
||||
/// </summary>
|
||||
public long TypeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Desc:库存(-1无限)
|
||||
/// Default:-1
|
||||
/// Nullable:False
|
||||
/// </summary>
|
||||
public int Stock {get;set;}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@ namespace QYZH.InteractiveMagazine.Models.Enum;
|
||||
/// </summary>
|
||||
public enum PetSkinTypeEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始皮肤
|
||||
/// </summary>
|
||||
[Description("初始皮肤")]
|
||||
System = 0,
|
||||
/// <summary>
|
||||
/// 普通皮肤
|
||||
/// </summary>
|
||||
|
||||
259
QYZH.InteractiveMagazine.Service/CheckInConfigService.cs
Normal file
259
QYZH.InteractiveMagazine.Service/CheckInConfigService.cs
Normal file
@ -0,0 +1,259 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Entity;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using QYZH.InteractiveMagazine.Repository;
|
||||
using SqlSugar;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置服务实现
|
||||
/// </summary>
|
||||
public class CheckInConfigService(
|
||||
BaseRepository<CheckInConfig> checkInConfigRepository,
|
||||
ILogger<CheckInConfigService> logger)
|
||||
: BaseRepository<CheckInConfig>, ICheckInConfigService
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建签到配置
|
||||
/// </summary>
|
||||
public async Task<CheckInConfigOutput> CreateAsync(CheckInConfigInput input)
|
||||
{
|
||||
logger.LogInformation("正在创建签到配置,DayNumber: {DayNumber}, Type: {Type}", input.DayNumber, input.Type);
|
||||
|
||||
if (input.DayNumber <= 0)
|
||||
{
|
||||
throw new BusinessException("连续签到天数必须大于0", 400);
|
||||
}
|
||||
|
||||
if (input.RewardPoints < 0)
|
||||
{
|
||||
throw new BusinessException("奖励积分不能为负数", 400);
|
||||
}
|
||||
|
||||
if (input.BonusPoints < 0)
|
||||
{
|
||||
throw new BusinessException("额外奖励积分不能为负数", 400);
|
||||
}
|
||||
|
||||
// 检查同类型下是否已存在相同天数配置
|
||||
var exists = await checkInConfigRepository.Context.Queryable<CheckInConfig>()
|
||||
.Where(c => c.Type == input.Type && c.DayNumber == input.DayNumber && !c.IsDeleted)
|
||||
.AnyAsync();
|
||||
|
||||
if (exists)
|
||||
{
|
||||
throw new BusinessException($"该类型下已存在连续{input.DayNumber}天的配置", 400);
|
||||
}
|
||||
|
||||
var config = new CheckInConfig
|
||||
{
|
||||
DayNumber = input.DayNumber,
|
||||
RewardPoints = input.RewardPoints,
|
||||
BonusPoints = input.BonusPoints,
|
||||
Type = input.Type,
|
||||
Status = (int)DefaultStatusEnum.Active,
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System",
|
||||
CreatedAt = DateTime.Now,
|
||||
UpdatedAt = DateTime.Now,
|
||||
IsDeleted = false
|
||||
};
|
||||
|
||||
var result = await checkInConfigRepository.InsertAsync(config);
|
||||
if (!result)
|
||||
{
|
||||
throw new BusinessException("创建签到配置失败", 500);
|
||||
}
|
||||
|
||||
logger.LogInformation("签到配置创建成功,ID: {Id}", config.Id);
|
||||
return MapToOutput(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置
|
||||
/// </summary>
|
||||
public async Task<CheckInConfigOutput> UpdateAsync(long id, CheckInConfigInput input)
|
||||
{
|
||||
logger.LogInformation("正在更新签到配置,ID: {Id}", id);
|
||||
|
||||
var config = await checkInConfigRepository.GetByIdAsync(id);
|
||||
if (config == null)
|
||||
{
|
||||
logger.LogWarning("未找到要更新的签到配置,ID: {Id}", id);
|
||||
throw new BusinessException("签到配置不存在", 404);
|
||||
}
|
||||
|
||||
if (input.DayNumber <= 0)
|
||||
{
|
||||
throw new BusinessException("连续签到天数必须大于0", 400);
|
||||
}
|
||||
|
||||
if (input.RewardPoints < 0)
|
||||
{
|
||||
throw new BusinessException("奖励积分不能为负数", 400);
|
||||
}
|
||||
|
||||
if (input.BonusPoints < 0)
|
||||
{
|
||||
throw new BusinessException("额外奖励积分不能为负数", 400);
|
||||
}
|
||||
|
||||
// 检查同类型下是否已存在相同天数配置(排除自身)
|
||||
var exists = await checkInConfigRepository.Context.Queryable<CheckInConfig>()
|
||||
.Where(c => c.Type == input.Type && c.DayNumber == input.DayNumber && c.Id != id && !c.IsDeleted)
|
||||
.AnyAsync();
|
||||
|
||||
if (exists)
|
||||
{
|
||||
throw new BusinessException($"该类型下已存在连续{input.DayNumber}天的配置", 400);
|
||||
}
|
||||
|
||||
config.DayNumber = input.DayNumber;
|
||||
config.RewardPoints = input.RewardPoints;
|
||||
config.BonusPoints = input.BonusPoints;
|
||||
config.Type = input.Type;
|
||||
config.UpdatedBy = "System";
|
||||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
var updateResult = await checkInConfigRepository.UpdateAsync(config);
|
||||
if (!updateResult)
|
||||
{
|
||||
throw new BusinessException("更新签到配置失败", 500);
|
||||
}
|
||||
|
||||
logger.LogInformation("签到配置更新成功,ID: {Id}", id);
|
||||
return MapToOutput(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除签到配置(软删除)
|
||||
/// </summary>
|
||||
public async Task DeleteAsync(long id)
|
||||
{
|
||||
logger.LogInformation("正在删除签到配置,ID: {Id}", id);
|
||||
|
||||
var config = await checkInConfigRepository.GetByIdAsync(id);
|
||||
if (config == null)
|
||||
{
|
||||
logger.LogWarning("未找到要删除的签到配置,ID: {Id}", id);
|
||||
throw new BusinessException("签到配置不存在", 404);
|
||||
}
|
||||
|
||||
var result = await checkInConfigRepository.Context.Updateable<CheckInConfig>()
|
||||
.SetColumns(c => new CheckInConfig
|
||||
{
|
||||
IsDeleted = true,
|
||||
UpdatedBy = "System",
|
||||
UpdatedAt = DateTime.Now
|
||||
})
|
||||
.Where(c => c.Id == id)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
if (result <= 0)
|
||||
{
|
||||
throw new BusinessException("删除签到配置失败", 500);
|
||||
}
|
||||
|
||||
logger.LogInformation("签到配置删除成功,ID: {Id}", id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取签到配置
|
||||
/// </summary>
|
||||
public async Task<CheckInConfigOutput> GetByIdAsync(long id)
|
||||
{
|
||||
var config = await checkInConfigRepository.GetByIdAsync(id);
|
||||
if (config == null)
|
||||
{
|
||||
throw new BusinessException("签到配置不存在", 404);
|
||||
}
|
||||
|
||||
return MapToOutput(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到配置列表
|
||||
/// </summary>
|
||||
public async Task<PageListModel<CheckInConfigOutput>> GetListAsync(CheckInConfigQueryInput 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);
|
||||
}
|
||||
|
||||
RefAsync<int> totalNumber = 0;
|
||||
var configs = await checkInConfigRepository.Queryable()
|
||||
.Where(c => !c.IsDeleted)
|
||||
.WhereIF(input.Type.HasValue, c => c.Type == input.Type.Value)
|
||||
.OrderBy(c => c.Type)
|
||||
.OrderBy(c => c.DayNumber)
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
||||
|
||||
var pageResult = configs.Select(MapToOutput).ToList();
|
||||
|
||||
var result = new PageListModel<CheckInConfigOutput>(new List<CheckInConfigOutput>(), input.PageIndex, input.PageSize, totalNumber);
|
||||
result.Result = pageResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置启用/禁用状态
|
||||
/// </summary>
|
||||
public async Task<bool> UpdateStatusAsync(long id)
|
||||
{
|
||||
var config = await checkInConfigRepository.GetByIdAsync(id);
|
||||
if (config == null)
|
||||
{
|
||||
logger.LogWarning("未找到要更新状态的签到配置,ID: {Id}", id);
|
||||
throw new BusinessException("签到配置不存在", 404);
|
||||
}
|
||||
|
||||
config.Status = config.Status == (int)DefaultStatusEnum.Active
|
||||
? (int)DefaultStatusEnum.Inactive
|
||||
: (int)DefaultStatusEnum.Active;
|
||||
config.UpdatedBy = "System";
|
||||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
var result = await checkInConfigRepository.UpdateAsync(config);
|
||||
if (!result)
|
||||
{
|
||||
logger.LogError("签到配置状态更新失败,ID: {Id}", id);
|
||||
throw new BusinessException("更新签到配置状态失败", 500);
|
||||
}
|
||||
|
||||
logger.LogInformation("签到配置状态更新成功,ID: {Id}, Status: {Status}", id, config.Status);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体映射为输出DTO
|
||||
/// </summary>
|
||||
private static CheckInConfigOutput MapToOutput(CheckInConfig config)
|
||||
{
|
||||
return new CheckInConfigOutput
|
||||
{
|
||||
Id = config.Id,
|
||||
DayNumber = config.DayNumber,
|
||||
RewardPoints = config.RewardPoints,
|
||||
BonusPoints = config.BonusPoints,
|
||||
Type = config.Type,
|
||||
Status = config.Status,
|
||||
CreatedBy = config.CreatedBy,
|
||||
CreatedAt = config.CreatedAt,
|
||||
UpdatedBy = config.UpdatedBy,
|
||||
UpdatedAt = config.UpdatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -82,7 +82,9 @@ public class CheckInService(
|
||||
GrowthPointsAwarded = growthReward,
|
||||
ConsecutiveDays = consecutiveDays,
|
||||
Type = CheckInRecordTypeEnum.Normal,
|
||||
Status = (int)CheckInRecordStatusEnum.Success
|
||||
Status = (int)CheckInRecordStatusEnum.Success,
|
||||
CreatedBy = userId.ToString(),
|
||||
UpdatedBy = userId.ToString()
|
||||
};
|
||||
var recordId = await checkInRecordRepository.Insertable(checkInRecord).ExecuteReturnIdentityAsync();
|
||||
checkInRecord.Id = recordId;
|
||||
@ -198,8 +200,8 @@ public class CheckInService(
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
// 最近 30 天签到记录
|
||||
var thirtyDaysAgo = today.AddDays(-29);
|
||||
// 最近 7 天签到记录
|
||||
var thirtyDaysAgo = today.AddDays(-6);
|
||||
var recentRecords = await checkInRecordRepository.Context.Queryable<CheckInRecord>()
|
||||
.Where(r => r.UserId == userId && !r.IsDeleted && r.CheckInDate >= thirtyDaysAgo)
|
||||
.OrderBy(r => r.CheckInDate, SqlSugar.OrderByType.Desc)
|
||||
@ -207,6 +209,7 @@ public class CheckInService(
|
||||
{
|
||||
Id = (long)r.Id,
|
||||
CheckInDate = r.CheckInDate,
|
||||
CreatedAt = r.CreatedAt,
|
||||
ConsecutiveDays = r.ConsecutiveDays,
|
||||
PointsAwarded = r.PointsAwarded,
|
||||
GrowthPointsAwarded = r.GrowthPointsAwarded,
|
||||
@ -247,6 +250,19 @@ public class CheckInService(
|
||||
if (alreadyCheckedIn)
|
||||
throw new BusinessException($"{targetDate:yyyy-MM-dd} 已签到,无需补签", 400);
|
||||
|
||||
// 检查用户背包中是否有补签卡
|
||||
var makeUpCard = await checkInRecordRepository.Context.Queryable<UserBag>()
|
||||
.Where(b => b.UserId == userId
|
||||
&& b.ItemType == "MakeUpCard"
|
||||
&& b.Quantity > 0
|
||||
&& b.Status == (int)UserBagStatusEnum.Available
|
||||
&& !b.IsDeleted)
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.FirstAsync();
|
||||
|
||||
if (makeUpCard == null)
|
||||
throw new BusinessException("补签卡不足,无法补签", 400);
|
||||
|
||||
// 查询用户信息
|
||||
var user = await checkInRecordRepository.Context.Queryable<Users>()
|
||||
.Where(u => u.Id == userId && !u.IsDeleted)
|
||||
@ -304,6 +320,25 @@ public class CheckInService(
|
||||
Description = $"补签奖励({targetDate:yyyy-MM-dd})"
|
||||
});
|
||||
|
||||
// 扣减补签卡
|
||||
if (makeUpCard.Quantity <= 1)
|
||||
{
|
||||
await checkInRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Status == (int)UserBagStatusEnum.Expired)
|
||||
.SetColumns(b => b.Quantity == 0)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == makeUpCard.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await checkInRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Quantity == makeUpCard.Quantity - 1)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == makeUpCard.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
result.RecordId = (long)recordId;
|
||||
result.CheckInDate = targetDate;
|
||||
result.ConsecutiveDays = 0;
|
||||
@ -344,7 +379,7 @@ public class CheckInService(
|
||||
/// <summary>
|
||||
/// 获取用户漏签日期列表
|
||||
/// </summary>
|
||||
public async Task<List<DateTime>> GetMissedDatesAsync(long userId, int days = 30)
|
||||
public async Task<List<DateTime>> GetMissedDatesAsync(long userId, int days = 7)
|
||||
{
|
||||
var startDate = DateTime.Now.Date.AddDays(-days);
|
||||
|
||||
|
||||
@ -1281,4 +1281,23 @@ public class PetService(
|
||||
Images = imagesByStage.GetValueOrDefault(e.Id, new List<PetSkinImageOutput>())
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有皮肤下拉列表(仅返回Id和Name,按SortOrder排序)
|
||||
/// </summary>
|
||||
public async Task<List<SelectOptionDto>> GetAllSkinsForSelectAsync(List<string>? types = null)
|
||||
{
|
||||
var list = await petSkinRepository.Queryable()
|
||||
.Where(s => !s.IsDeleted)
|
||||
.WhereIF(types != null && types.Count > 0, s => types.Contains(s.Type.ToString()))
|
||||
.OrderBy(s => s.SortOrder)
|
||||
.Select(s => new SelectOptionDto
|
||||
{
|
||||
Id = s.Id,
|
||||
Name = s.Name
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
@ -52,6 +53,8 @@ public class ProductService(
|
||||
MetaData = input.MetaData,
|
||||
IsActive = input.IsActive,
|
||||
Stock = input.Stock,
|
||||
IsVirtual = input.IsVirtual,
|
||||
TypeId = input.TypeId,
|
||||
CreatedBy = "System",
|
||||
UpdatedBy = "System",
|
||||
CreatedAt = DateTime.Now,
|
||||
@ -90,6 +93,8 @@ public class ProductService(
|
||||
MetaData = product.MetaData,
|
||||
IsActive = product.IsActive,
|
||||
Stock = product.Stock,
|
||||
IsVirtual = product.IsVirtual,
|
||||
TypeId = product.TypeId,
|
||||
CreatedBy = product.CreatedBy,
|
||||
CreatedAt = product.CreatedAt,
|
||||
UpdatedBy = product.UpdatedBy,
|
||||
@ -144,6 +149,8 @@ public class ProductService(
|
||||
product.MetaData = input.MetaData;
|
||||
product.IsActive = input.IsActive;
|
||||
product.Stock = input.Stock;
|
||||
product.IsVirtual = input.IsVirtual;
|
||||
product.TypeId = input.TypeId;
|
||||
product.UpdatedBy = "System";
|
||||
product.UpdatedAt = DateTime.Now;
|
||||
|
||||
@ -168,6 +175,8 @@ public class ProductService(
|
||||
MetaData = product.MetaData,
|
||||
IsActive = product.IsActive,
|
||||
Stock = product.Stock,
|
||||
IsVirtual = product.IsVirtual,
|
||||
TypeId = product.TypeId,
|
||||
CreatedBy = product.CreatedBy,
|
||||
CreatedAt = product.CreatedAt,
|
||||
UpdatedBy = product.UpdatedBy,
|
||||
@ -218,13 +227,15 @@ public class ProductService(
|
||||
Id = product.Id,
|
||||
Name = product.Name,
|
||||
Description = product.Description,
|
||||
ImageUrl = product.ImageUrl,
|
||||
ImageUrl = DomainHelper.OssFullUrl(product.ImageUrl),
|
||||
Price = product.Price,
|
||||
Type = product.Type.ToString(),
|
||||
Status = product.Status.ToString(),
|
||||
MetaData = product.MetaData,
|
||||
IsActive = product.IsActive,
|
||||
Stock = product.Stock,
|
||||
IsVirtual = product.IsVirtual,
|
||||
TypeId = product.TypeId,
|
||||
CreatedBy = product.CreatedBy,
|
||||
CreatedAt = product.CreatedAt,
|
||||
UpdatedBy = product.UpdatedBy,
|
||||
@ -256,26 +267,29 @@ public class ProductService(
|
||||
.WhereIF(input.Status.HasValue, p => p.Status == (int)input.Status)
|
||||
.WhereIF(input.IsActive.HasValue, p => p.IsActive == input.IsActive.Value)
|
||||
.OrderByDescending(p => p.CreatedAt)
|
||||
.Select(p => new ProductOutput
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Description = p.Description,
|
||||
ImageUrl = p.ImageUrl,
|
||||
Price = p.Price,
|
||||
Type = p.Type.ToString(),
|
||||
Status = p.Status.ToString(),
|
||||
MetaData = p.MetaData,
|
||||
IsActive = p.IsActive,
|
||||
Stock = p.Stock,
|
||||
CreatedBy = p.CreatedBy,
|
||||
CreatedAt = p.CreatedAt,
|
||||
UpdatedBy = p.UpdatedBy,
|
||||
UpdatedAt = p.UpdatedAt
|
||||
}, true)
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
|
||||
|
||||
return new PageListModel<ProductOutput>(pageResult, input.PageIndex, input.PageSize, totalNumber);
|
||||
var result = pageResult.Select(p => new ProductOutput
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Description = p.Description,
|
||||
ImageUrl = DomainHelper.OssFullUrl(p.ImageUrl),
|
||||
Price = p.Price,
|
||||
Type = p.Type.ToString(),
|
||||
Status = p.Status.ToString(),
|
||||
MetaData = p.MetaData,
|
||||
IsActive = p.IsActive,
|
||||
Stock = p.Stock,
|
||||
IsVirtual = p.IsVirtual,
|
||||
TypeId = p.TypeId,
|
||||
CreatedBy = p.CreatedBy,
|
||||
CreatedAt = p.CreatedAt,
|
||||
UpdatedBy = p.UpdatedBy,
|
||||
UpdatedAt = p.UpdatedAt
|
||||
}).ToList();
|
||||
|
||||
return new PageListModel<ProductOutput>(result, input.PageIndex, input.PageSize, totalNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -37,7 +37,7 @@ public class UsersService(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表)
|
||||
/// 获取用户详情(仅基本信息)
|
||||
/// </summary>
|
||||
public async Task<BaseResponse<UserDetailOutput>> GetDetailAsync(long id)
|
||||
{
|
||||
@ -47,125 +47,124 @@ public class UsersService(
|
||||
return BaseResponse<UserDetailOutput>.Fail("用户不存在");
|
||||
}
|
||||
|
||||
// 并行查询关联数据
|
||||
var pointsTask = GetPointsRecordsAsync(id);
|
||||
var checkInTask = GetCheckInRecordsAsync(id);
|
||||
var compensationTask = GetFailedCompensationTasksAsync(id);
|
||||
var journalsTask = GetUserJournalsWithDetailAsync(id);
|
||||
|
||||
await Task.WhenAll(pointsTask, checkInTask, compensationTask, journalsTask);
|
||||
|
||||
return BaseResponse<UserDetailOutput>.Success(new UserDetailOutput
|
||||
{
|
||||
BasicInfo = user,
|
||||
PointsRecords = await pointsTask,
|
||||
CheckInRecords = await checkInTask,
|
||||
FailedCompensationTasks = await compensationTask,
|
||||
Journals = await journalsTask
|
||||
BasicInfo = user
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户积分记录(最近20条)
|
||||
/// 分页查询用户积分记录
|
||||
/// </summary>
|
||||
private async Task<List<PointsRecordOutput>> GetPointsRecordsAsync(long userId)
|
||||
public async Task<BaseResponse<PageListModel<PointsRecordOutput>>> GetUserPointsRecordsAsync(long userId, PointsRecordQueryInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await pointsService.GetPointsRecordsAsync(new PointsRecordQueryInput
|
||||
input.UserId = userId;
|
||||
var result = await pointsService.GetPointsRecordsAsync(input);
|
||||
return BaseResponse<PageListModel<PointsRecordOutput>>.Success(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户签到记录
|
||||
/// </summary>
|
||||
public async Task<BaseResponse<PageListModel<CheckInRecordOutput>>> GetUserCheckInRecordsAsync(long userId, PageQueryModel input)
|
||||
{
|
||||
RefAsync<int> total = 0;
|
||||
var records = await Context.Queryable<CheckInRecord>()
|
||||
.Where(r => r.UserId == userId && !r.IsDeleted)
|
||||
.OrderBy(r => r.CheckInDate, OrderByType.Desc)
|
||||
.Select(r => new CheckInRecordOutput
|
||||
{
|
||||
UserId = userId,
|
||||
PageIndex = 1,
|
||||
PageSize = 20
|
||||
});
|
||||
return result.Result ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取用户积分记录失败,UserId: {UserId}", userId);
|
||||
return [];
|
||||
}
|
||||
Id = (long)r.Id,
|
||||
CheckInDate = r.CheckInDate,
|
||||
CreatedAt = r.CreatedAt,
|
||||
ConsecutiveDays = r.ConsecutiveDays,
|
||||
PointsAwarded = r.PointsAwarded,
|
||||
GrowthPointsAwarded = r.GrowthPointsAwarded,
|
||||
Type = r.Type.ToString(),
|
||||
Status = r.Status.ToString()
|
||||
})
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, total);
|
||||
|
||||
var page = new PageListModel<CheckInRecordOutput>(records, input.PageIndex, input.PageSize, total);
|
||||
return BaseResponse<PageListModel<CheckInRecordOutput>>.Success(page);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户签到记录
|
||||
/// 分页查询用户补偿任务
|
||||
/// </summary>
|
||||
private async Task<List<CheckInRecordOutput>> GetCheckInRecordsAsync(long userId)
|
||||
public async Task<BaseResponse<PageListModel<CompensationTaskOutput>>> GetUserCompensationTasksAsync(long userId, CompensationTaskQueryInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = await checkInService.GetCheckInInfoAsync(userId);
|
||||
return info.RecentRecords ?? [];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取用户签到记录失败,UserId: {UserId}", userId);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户失败的补偿任务(需要手动处理)
|
||||
/// </summary>
|
||||
private async Task<List<CompensationTaskOutput>> GetFailedCompensationTasksAsync(long userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tasks = await compensationTaskService.GetTasksAsync(new GetCompensationTasksInput
|
||||
RefAsync<int> total = 0;
|
||||
var tasks = await Context.Queryable<CompensationTask>()
|
||||
.Where(t => t.UserId == userId && !t.IsDeleted)
|
||||
.WhereIF(input.Status.HasValue, t => t.Status == (int)input.Status)
|
||||
.WhereIF(input.TaskType.HasValue, t => t.TaskType == (int)input.TaskType)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.BusinessSource), t => t.BusinessSource == input.BusinessSource)
|
||||
.OrderBy(t => t.CreatedAt, OrderByType.Desc)
|
||||
.Select(t => new CompensationTaskOutput
|
||||
{
|
||||
UserId = userId,
|
||||
Status = CompensationTaskStatusEnum.Failed,
|
||||
Limit = 20
|
||||
});
|
||||
return tasks;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取用户失败补偿任务失败,UserId: {UserId}", userId);
|
||||
return [];
|
||||
}
|
||||
Id = t.Id,
|
||||
TaskType = (CompensationTaskTypeEnum)t.TaskType,
|
||||
BusinessSource = t.BusinessSource,
|
||||
BusinessId = t.BusinessId,
|
||||
UserId = t.UserId,
|
||||
Payload = t.Payload,
|
||||
ErrorMessage = t.ErrorMessage,
|
||||
ErrorSource = t.ErrorSource,
|
||||
RetryCount = t.RetryCount,
|
||||
MaxRetries = t.MaxRetries,
|
||||
Status = (CompensationTaskStatusEnum)t.Status,
|
||||
ProcessedAt = t.ProcessedAt,
|
||||
ScheduledAt = t.ScheduledAt,
|
||||
ResultMessage = t.ResultMessage,
|
||||
CreatedAt = t.CreatedAt
|
||||
})
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, total);
|
||||
|
||||
var page = new PageListModel<CompensationTaskOutput>(tasks, input.PageIndex, input.PageSize, total);
|
||||
return BaseResponse<PageListModel<CompensationTaskOutput>>.Success(page);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户拥有的期刊列表(含期刊详情)
|
||||
/// 分页查询用户期刊列表(含期刊详情)
|
||||
/// </summary>
|
||||
private async Task<List<UserJournalItemOutput>> GetUserJournalsWithDetailAsync(long userId)
|
||||
public async Task<BaseResponse<PageListModel<UserJournalItemOutput>>> GetUserJournalsAsync(long userId, UserJournalQueryInput input)
|
||||
{
|
||||
try
|
||||
RefAsync<int> total = 0;
|
||||
var items = await Context.Queryable<UserJournal>()
|
||||
.Where(uj => uj.UserId == userId && !uj.IsDeleted)
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Type), uj => uj.Type.ToString() == input.Type)
|
||||
.OrderByDescending(uj => uj.CreatedAt)
|
||||
.Select(uj => new UserJournalItemOutput
|
||||
{
|
||||
BindId = uj.Id,
|
||||
JournalId = uj.JournalId,
|
||||
Type = uj.Type.ToString(),
|
||||
Status = uj.Status.ToString(),
|
||||
CreatedAt = uj.CreatedAt
|
||||
})
|
||||
.ToPageListAsync(input.PageIndex, input.PageSize, total);
|
||||
|
||||
// 填充期刊详情(标题、封面)
|
||||
var journalIds = items.Select(i => i.JournalId).Distinct().ToList();
|
||||
if (journalIds.Count > 0)
|
||||
{
|
||||
var userJournals = await Context.Queryable<UserJournal>()
|
||||
.Where(uj => uj.UserId == userId && !uj.IsDeleted)
|
||||
.OrderByDescending(uj => uj.CreatedAt)
|
||||
var journals = await Context.Queryable<Journal>()
|
||||
.Where(j => journalIds.Contains(j.Id) && !j.IsDeleted)
|
||||
.ToListAsync();
|
||||
|
||||
var result = new List<UserJournalItemOutput>();
|
||||
foreach (var uj in userJournals)
|
||||
var journalDict = journals.ToDictionary(j => j.Id);
|
||||
foreach (var item in items)
|
||||
{
|
||||
var journal = await Context.Queryable<Journal>()
|
||||
.Where(j => j.Id == uj.JournalId && !j.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
if (journal != null)
|
||||
if (journalDict.TryGetValue(item.JournalId, out var journal))
|
||||
{
|
||||
result.Add(new UserJournalItemOutput
|
||||
{
|
||||
BindId = uj.Id,
|
||||
JournalId = uj.JournalId,
|
||||
JournalTitle = journal.Title,
|
||||
CoverImageUrl = journal.CoverImageUrl,
|
||||
Type = uj.Type.ToString(),
|
||||
Status = uj.Status.ToString(),
|
||||
CreatedAt = uj.CreatedAt
|
||||
});
|
||||
item.JournalTitle = journal.Title;
|
||||
item.CoverImageUrl = journal.CoverImageUrl;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取用户期刊列表失败,UserId: {UserId}", userId);
|
||||
return [];
|
||||
}
|
||||
|
||||
var page = new PageListModel<UserJournalItemOutput>(items, input.PageIndex, input.PageSize, total);
|
||||
return BaseResponse<PageListModel<UserJournalItemOutput>>.Success(page);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -152,15 +152,16 @@ public class WeChatAuthService(
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换用户(同一 WxUser 下切换 User 身份,JWT 基于 WxUser 无需重新生成)
|
||||
/// 切换用户(同一 WxUser 下切换 User 身份,重新生成 Token)
|
||||
/// </summary>
|
||||
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, WeChatSwitchUserInput input)
|
||||
public async Task<WeChatSwitchUserOutput> SwitchUserAsync(long wxUserId, long currentUserId, WeChatSwitchUserInput input)
|
||||
{
|
||||
logger.LogInformation("切换用户,WxUserId: {WxUserId}, 目标 UserId: {TargetUserId}", wxUserId, input.UserId);
|
||||
logger.LogInformation("切换用户,WxUserId: {WxUserId}, 当前 UserId: {CurrentUserId}, 目标 UserId: {TargetUserId}",
|
||||
wxUserId, currentUserId, input.UserId);
|
||||
|
||||
// 查询目标用户
|
||||
var targetUser = await wxUserRepository.Context.Queryable<Users>()
|
||||
.Where(u => u.Id == input.UserId)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
if (targetUser == null)
|
||||
@ -179,23 +180,32 @@ public class WeChatAuthService(
|
||||
// 更新 IsLastOnline(清除所有,设置目标为 true)
|
||||
await wxUserRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.IsLastOnline == false)
|
||||
.Where(u => u.WxUserId == wxUserId )
|
||||
.Where(u => u.WxUserId == wxUserId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
await wxUserRepository.Context.Updateable<Users>()
|
||||
.SetColumns(u => u.IsLastOnline == true)
|
||||
.Where(u => u.Id == input.UserId )
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
logger.LogInformation("IsLastOnline 已更新,目标用户 {UserId} 设为 true", input.UserId);
|
||||
|
||||
// 重新查询目标用户获取最新数据
|
||||
var refreshedUser = await wxUserRepository.Context.Queryable<Users>()
|
||||
.Where(u => u.Id == input.UserId)
|
||||
.Where(u => u.Id == input.UserId && !u.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
// 生成新 JWT Token(WxUserId + 新 UserId)
|
||||
var jwtSettings = GetJwtSettings();
|
||||
var token = JwtHelper.GenerateToken(wxUserId, refreshedUser.Id, refreshedUser.Name ?? string.Empty, jwtSettings);
|
||||
|
||||
// 清除旧 Redis Token,写入新 Token
|
||||
await RedisHelper.DelAsync($"{TokenKeyPrefix}:{currentUserId}");
|
||||
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{refreshedUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
|
||||
return new WeChatSwitchUserOutput
|
||||
{
|
||||
Token = token,
|
||||
User = MapUserToOutput(refreshedUser)
|
||||
};
|
||||
}
|
||||
@ -263,21 +273,66 @@ public class WeChatAuthService(
|
||||
return MapUserToOutput(newUser);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改家长名字(WxUser.Name)
|
||||
/// </summary>
|
||||
public async Task<WxUserInfoOutput> UpdateWxUserNameAsync(long wxUserId, UpdateWxUserNameInput input)
|
||||
{
|
||||
logger.LogInformation("修改家长名字,WxUserId: {WxUserId}, NewName: {Name}", wxUserId, input.Name);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(input.Name))
|
||||
throw new BusinessException("名字不能为空", 400);
|
||||
|
||||
var wxUser = await wxUserRepository.Context.Queryable<WxUser>()
|
||||
.Where(w => w.Id == wxUserId && !w.IsDeleted)
|
||||
.FirstAsync();
|
||||
|
||||
if (wxUser == null)
|
||||
throw new BusinessException("微信用户不存在", 404);
|
||||
|
||||
await wxUserRepository.Context.Updateable<WxUser>()
|
||||
.SetColumns(w => w.Name == input.Name.Trim())
|
||||
.SetColumns(w => w.UpdatedAt == DateTime.Now)
|
||||
.SetColumns(w => w.UpdatedBy == wxUserId.ToString())
|
||||
.Where(w => w.Id == wxUserId)
|
||||
.ExecuteCommandAsync();
|
||||
|
||||
wxUser.Name = input.Name.Trim();
|
||||
|
||||
logger.LogInformation("家长名字修改成功,WxUserId: {WxUserId}, NewName: {Name}", wxUserId, input.Name);
|
||||
|
||||
return new WxUserInfoOutput
|
||||
{
|
||||
Id = wxUser.Id,
|
||||
OpenId = wxUser.OpenId,
|
||||
UnionId = wxUser.UnionId,
|
||||
Name = wxUser.Name,
|
||||
AvatarUrl = wxUser.AvatarUrl,
|
||||
Phone = wxUser.Phone
|
||||
};
|
||||
}
|
||||
|
||||
#region 私有辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 构建登录输出(JWT 基于 WxUser,不绑定具体 User)
|
||||
/// 构建登录输出(JWT 同时携带 WxUserId 和当前激活 UserId)
|
||||
/// </summary>
|
||||
private async Task<WeChatLoginOutput> BuildLoginOutputAsync(WxUser wxUser, List<Users> users)
|
||||
{
|
||||
var jwtSettings = GetJwtSettings();
|
||||
var token = JwtHelper.GenerateToken(wxUser.Id, wxUser.Name ?? wxUser.OpenId, jwtSettings);
|
||||
var activeUser = users.FirstOrDefault(u => u.IsLastOnline) ?? users.FirstOrDefault();
|
||||
|
||||
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{wxUser.Id}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
var userId = activeUser?.Id ?? 0L;
|
||||
var userName = activeUser?.Name ?? wxUser.Name ?? wxUser.OpenId;
|
||||
|
||||
var jwtSettings = GetJwtSettings();
|
||||
var token = JwtHelper.GenerateToken(wxUser.Id, userId, userName, jwtSettings);
|
||||
|
||||
await RedisHelper.SetAsync($"{TokenKeyPrefix}:{userId}", token, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes));
|
||||
|
||||
return new WeChatLoginOutput
|
||||
{
|
||||
Token = token,
|
||||
CurrentUserId = userId,
|
||||
WxUser = new WxUserInfoOutput
|
||||
{
|
||||
Id = wxUser.Id,
|
||||
|
||||
@ -103,6 +103,8 @@ public class WxMallService(
|
||||
ImageUrl = p.ImageUrl,
|
||||
Price = p.Price,
|
||||
Type = p.Type.ToString(),
|
||||
IsVirtual = p.IsVirtual,
|
||||
TypeId = p.TypeId,
|
||||
Owned = owned,
|
||||
Skin = skin != null ? new PetSkinBrief
|
||||
{
|
||||
@ -169,6 +171,8 @@ public class WxMallService(
|
||||
ImageUrl = product.ImageUrl,
|
||||
Price = product.Price,
|
||||
Type = product.Type.ToString(),
|
||||
IsVirtual = product.IsVirtual,
|
||||
TypeId = product.TypeId,
|
||||
Owned = owned,
|
||||
Skin = skinBrief
|
||||
};
|
||||
@ -403,7 +407,7 @@ public class WxMallService(
|
||||
throw new BusinessException("背包物品Id无效", 400);
|
||||
|
||||
var bagItem = await exchangeRecordRepository.Context.Queryable<UserBag>()
|
||||
.Where(b => b.Id == input.BagItemId && b.UserId == userId && !b.IsDeleted && b.Status == (int)UserBagStatusEnum.Available)
|
||||
.Where(b => b.Id == input.BagItemId && b.UserId == userId && b.Status == (int)UserBagStatusEnum.Available)
|
||||
.FirstAsync();
|
||||
|
||||
if (bagItem == null)
|
||||
@ -437,28 +441,9 @@ public class WxMallService(
|
||||
if (targetDate >= DateTime.Now.Date)
|
||||
throw new BusinessException("只能补签过去的日期", 400);
|
||||
|
||||
// 调用签到服务执行补签
|
||||
// 调用签到服务执行补签(内部会检查并扣减补签卡)
|
||||
var checkInResult = await checkInService.MakeUpCheckInAsync(userId, targetDate);
|
||||
|
||||
// 扣减补签卡数量
|
||||
if (bagItem.Quantity <= 1)
|
||||
{
|
||||
await exchangeRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Status == (int)UserBagStatusEnum.Expired)
|
||||
.SetColumns(b => b.Quantity == 0)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == bagItem.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await exchangeRecordRepository.Context.Updateable<UserBag>()
|
||||
.SetColumns(b => b.Quantity == bagItem.Quantity - 1)
|
||||
.SetColumns(b => b.UpdatedAt == DateTime.Now)
|
||||
.Where(b => b.Id == bagItem.Id)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
logger.LogInformation("补签卡使用成功,UserId: {UserId}, TargetDate: {Date}", userId, targetDate);
|
||||
|
||||
return new UseItemOutput
|
||||
|
||||
@ -0,0 +1,177 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.IService;
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 签到配置管理控制器
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
|
||||
public class CheckInConfigController : BaseController
|
||||
{
|
||||
private readonly ICheckInConfigService _checkInConfigService;
|
||||
private readonly ILogger<CheckInConfigController> _logger;
|
||||
|
||||
public CheckInConfigController(ICheckInConfigService checkInConfigService, ILogger<CheckInConfigController> logger)
|
||||
{
|
||||
_checkInConfigService = checkInConfigService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建签到配置
|
||||
/// </summary>
|
||||
/// <param name="input">签到配置信息</param>
|
||||
/// <returns>创建的签到配置信息</returns>
|
||||
[HttpPost]
|
||||
public async Task<BaseResponse<CheckInConfigOutput>> CreateAsync([FromBody] CheckInConfigInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _checkInConfigService.CreateAsync(input);
|
||||
return Success(result, "创建签到配置成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "创建签到配置业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "创建签到配置系统异常,参数:{Input}", input);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail("创建签到配置失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <param name="input">签到配置信息</param>
|
||||
/// <returns>更新后的签到配置信息</returns>
|
||||
[HttpPut("{id}")]
|
||||
public async Task<BaseResponse<CheckInConfigOutput>> UpdateAsync(long id, [FromBody] CheckInConfigInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _checkInConfigService.UpdateAsync(id, input);
|
||||
return Success(result, "更新签到配置成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "更新签到配置业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新签到配置系统异常,ID:{Id},参数:{Input}", id, input);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail("更新签到配置失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除签到配置(软删除)
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<BaseResponse<object>> DeleteAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _checkInConfigService.DeleteAsync(id);
|
||||
return Success(new object(), "删除签到配置成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "删除签到配置业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<object>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除签到配置系统异常,ID:{Id}", id);
|
||||
return BaseResponse<object>.Fail("删除签到配置失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取签到配置
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>签到配置信息</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<CheckInConfigOutput>> GetByIdAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _checkInConfigService.GetByIdAsync(id);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取签到配置业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取签到配置系统异常,ID:{Id}", id);
|
||||
return BaseResponse<CheckInConfigOutput>.Fail("获取签到配置信息失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询签到配置列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>分页结果</returns>
|
||||
[HttpPost("list")]
|
||||
public async Task<BaseResponse<PageListModel<CheckInConfigOutput>>> GetListAsync([FromBody] CheckInConfigQueryInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _checkInConfigService.GetListAsync(input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "查询签到配置列表业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<PageListModel<CheckInConfigOutput>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查询签到配置列表系统异常,参数:{Input}", input);
|
||||
return BaseResponse<PageListModel<CheckInConfigOutput>>.Fail("查询签到配置列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新签到配置启用/禁用状态
|
||||
/// </summary>
|
||||
/// <param name="id">签到配置ID</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[HttpPut("{id}/status")]
|
||||
public async Task<BaseResponse<bool>> UpdateStatusAsync(long id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var res = await _checkInConfigService.UpdateStatusAsync(id);
|
||||
return Success(res, "更新签到配置状态成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "更新签到配置状态业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<bool>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新签到配置状态系统异常,ID:{Id}", id);
|
||||
return BaseResponse<bool>.Fail("更新签到配置状态失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -562,4 +562,23 @@ public class PetManageController : BaseController
|
||||
return BaseResponse<List<SkinEvolutionStageOutput>>.Fail("获取分组皮肤图片失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有皮肤下拉列表
|
||||
/// </summary>
|
||||
/// <param name="types">皮肤类型列表(可选)</param>
|
||||
[HttpGet("skins/select")]
|
||||
public async Task<BaseResponse<List<SelectOptionDto>>> GetAllSkinsForSelectAsync([FromQuery] List<string>? types = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _petService.GetAllSkinsForSelectAsync(types);
|
||||
return Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取皮肤下拉列表系统异常");
|
||||
return BaseResponse<List<SelectOptionDto>>.Fail("获取皮肤下拉列表失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ using QYZH.InteractiveMagazine.IService;
|
||||
|
||||
using QYZH.InteractiveMagazine.Models.Common;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.CheckIn;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Compensation;
|
||||
using QYZH.InteractiveMagazine.Models.Dto.Points;
|
||||
|
||||
namespace QYZH.InteractiveMagazine.WebApi.Controllers;
|
||||
@ -33,7 +35,7 @@ public class UsersController : BaseController
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户详情(包含积分记录、签到记录、补偿任务、期刊列表)
|
||||
/// 获取用户详情(仅基本信息)
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<BaseResponse<UserDetailOutput>> GetDetail(long id)
|
||||
@ -41,6 +43,42 @@ public class UsersController : BaseController
|
||||
return await _usersService.GetDetailAsync(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户积分记录
|
||||
/// </summary>
|
||||
[HttpGet("{id}/pointsRecords")]
|
||||
public async Task<BaseResponse<PageListModel<PointsRecordOutput>>> GetUserPointsRecords(long id, [FromQuery] PointsRecordQueryInput input)
|
||||
{
|
||||
return await _usersService.GetUserPointsRecordsAsync(id, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户签到记录
|
||||
/// </summary>
|
||||
[HttpGet("{id}/checkInRecords")]
|
||||
public async Task<BaseResponse<PageListModel<CheckInRecordOutput>>> GetUserCheckInRecords(long id, [FromQuery] PageQueryModel input)
|
||||
{
|
||||
return await _usersService.GetUserCheckInRecordsAsync(id, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户补偿任务
|
||||
/// </summary>
|
||||
[HttpGet("{id}/compensationTasks")]
|
||||
public async Task<BaseResponse<PageListModel<CompensationTaskOutput>>> GetUserCompensationTasks(long id, [FromQuery] CompensationTaskQueryInput input)
|
||||
{
|
||||
return await _usersService.GetUserCompensationTasksAsync(id, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询用户期刊列表
|
||||
/// </summary>
|
||||
[HttpGet("{id}/journals")]
|
||||
public async Task<BaseResponse<PageListModel<UserJournalItemOutput>>> GetUserJournals(long id, [FromQuery] UserJournalQueryInput input)
|
||||
{
|
||||
return await _usersService.GetUserJournalsAsync(id, input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新用户状态
|
||||
/// </summary>
|
||||
|
||||
@ -17,10 +17,10 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
||||
[HttpGet("items")]
|
||||
public async Task<BaseResponse<List<UserBagOutput>>> GetBagItems([FromQuery] string? itemType = null)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var items = await mallService.GetBagItemsAsync(userId.Value, itemType);
|
||||
var items = await mallService.GetBagItemsAsync(userId, itemType);
|
||||
return Success(items);
|
||||
}
|
||||
|
||||
@ -30,10 +30,10 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
||||
[HttpPost("useItem")]
|
||||
public async Task<BaseResponse<UseItemOutput>> UseItem([FromBody] UseItemInput input)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var result = await mallService.UseItemAsync(userId.Value, input);
|
||||
var result = await mallService.UseItemAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
|
||||
@ -43,10 +43,10 @@ public class BagController(IWxMallService mallService, ILogger<BagController> lo
|
||||
[HttpPost("equipSkin")]
|
||||
public async Task<BaseResponse<object>> EquipSkin([FromBody] EquipSkinInput input)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
await mallService.EquipSkinAsync(userId.Value, input);
|
||||
await mallService.EquipSkinAsync(userId, input);
|
||||
return Success<object>(null!, input.SkinId == 0 ? "已恢复默认皮肤" : "换肤成功");
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,13 +29,13 @@ public class CheckInController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.CheckInAsync(userId.Value);
|
||||
var result = await _checkInService.CheckInAsync(userId);
|
||||
return Success(result, "签到成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -59,13 +59,13 @@ public class CheckInController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.GetCheckInInfoAsync(userId.Value);
|
||||
var result = await _checkInService.GetCheckInInfoAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -79,4 +79,66 @@ public class CheckInController : WeChatBaseController
|
||||
return BaseResponse<CheckInInfoOutput>.Fail("获取签到信息失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 补签(消耗补签卡,补签历史漏签日期)
|
||||
/// </summary>
|
||||
/// <param name="input">补签输入(目标日期)</param>
|
||||
/// <returns>补签结果(含奖励详情和余额)</returns>
|
||||
[HttpPost("makeUp")]
|
||||
public async Task<BaseResponse<CheckInOutput>> MakeUpCheckInAsync([FromBody] MakeUpCheckInInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<CheckInOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.MakeUpCheckInAsync(userId, input.TargetDate);
|
||||
return Success(result, "补签成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "补签业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<CheckInOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "补签系统异常");
|
||||
return BaseResponse<CheckInOutput>.Fail("补签失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可补签的日期列表(历史漏签日期)
|
||||
/// </summary>
|
||||
/// <param name="days">往前查看天数(默认30天)</param>
|
||||
/// <returns>漏签日期列表</returns>
|
||||
[HttpGet("missedDates")]
|
||||
public async Task<BaseResponse<List<DateTime>>> GetMissedDatesAsync([FromQuery] int days = 30)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<DateTime>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _checkInService.GetMissedDatesAsync(userId, days);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "获取可补签日期业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<List<DateTime>>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取可补签日期系统异常");
|
||||
return BaseResponse<List<DateTime>>.Fail("获取可补签日期失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,13 +19,13 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.GetFeedAsync(userId.Value, cursor);
|
||||
var result = await communityService.GetFeedAsync(userId, cursor);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -48,13 +48,13 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxFeedOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.RefreshFeedAsync(userId.Value);
|
||||
var result = await communityService.RefreshFeedAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -77,13 +77,13 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.LikeAsync(userId.Value, input);
|
||||
var result = await communityService.LikeAsync(userId, input);
|
||||
return Success(result, "点赞成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -106,13 +106,13 @@ public class CommunityController(IWeChatCommunityService communityService, ILogg
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<WxLikeOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await communityService.UnlikeAsync(userId.Value, input.MessageId);
|
||||
var result = await communityService.UnlikeAsync(userId, input.MessageId);
|
||||
return Success(result, "已取消点赞");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
|
||||
@ -29,13 +29,13 @@ public class JournalController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<BindJournalOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _userJournalService.BindJournalAsync(userId.Value, input);
|
||||
var result = await _userJournalService.BindJournalAsync(userId, input);
|
||||
return Success(result, "绑定期刊成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -60,13 +60,13 @@ public class JournalController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PageListModel<BindJournalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _userJournalService.GetUserJournalsAsync(userId.Value, input);
|
||||
var result = await _userJournalService.GetUserJournalsAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
|
||||
@ -17,10 +17,10 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
||||
[HttpGet("products")]
|
||||
public async Task<BaseResponse<List<WxProductOutput>>> GetProducts([FromQuery] string? type = null)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var products = await mallService.GetProductsAsync(userId.Value, type);
|
||||
var products = await mallService.GetProductsAsync(userId, type);
|
||||
return Success(products);
|
||||
}
|
||||
|
||||
@ -31,10 +31,10 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
||||
[HttpGet("product/{id}")]
|
||||
public async Task<BaseResponse<WxProductOutput>> GetProductDetail(long id)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var product = await mallService.GetProductDetailAsync(userId.Value, id);
|
||||
var product = await mallService.GetProductDetailAsync(userId, id);
|
||||
if (product == null)
|
||||
return BaseResponse<WxProductOutput>.Fail(ResultCode.DENY, "商品不存在或已下架");
|
||||
|
||||
@ -47,10 +47,10 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
||||
[HttpPost("exchange")]
|
||||
public async Task<BaseResponse<ExchangeOutput>> Exchange([FromBody] ExchangeInput input)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var result = await mallService.ExchangeAsync(userId.Value, input);
|
||||
var result = await mallService.ExchangeAsync(userId, input);
|
||||
return Success(result);
|
||||
}
|
||||
|
||||
@ -61,10 +61,10 @@ public class MallController(IWxMallService mallService, ILogger<MallController>
|
||||
[HttpGet("exchangeRecords")]
|
||||
public async Task<BaseResponse<List<ExchangeRecordOutput>>> GetExchangeRecords([FromQuery] int limit = 20)
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null) return Fail("未获取到用户信息") as dynamic;
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0) return Fail("未获取到用户信息") as dynamic;
|
||||
|
||||
var records = await mallService.GetExchangeRecordsAsync(userId.Value, limit);
|
||||
var records = await mallService.GetExchangeRecordsAsync(userId, limit);
|
||||
return Success(records);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,13 +18,13 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<WxMedalListOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await medalService.GetAllMedalsAsync(userId.Value);
|
||||
var result = await medalService.GetAllMedalsAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -47,13 +47,13 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<List<WxUserMedalOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await medalService.GetUserMedalsAsync(userId.Value);
|
||||
var result = await medalService.GetUserMedalsAsync(userId);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -76,13 +76,13 @@ public class MedalController(IMedalService medalService, ILogger<MedalController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<object>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
await medalService.ActivateMedalAsync(userId.Value, input);
|
||||
await medalService.ActivateMedalAsync(userId, input);
|
||||
return Success<object>(null!, "勋章激活成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
|
||||
@ -28,13 +28,13 @@ public class PetController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var pet = await _petService.GetPetByUserIdAsync(userId.Value);
|
||||
var pet = await _petService.GetPetByUserIdAsync(userId);
|
||||
if (pet == null)
|
||||
{
|
||||
return BaseResponse<PetOutput>.Fail("未找到宠物信息");
|
||||
@ -62,13 +62,13 @@ public class PetController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<FeedPetOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _petService.FeedPetAsync(userId.Value, input);
|
||||
var result = await _petService.FeedPetAsync(userId, input);
|
||||
return Success(result, "喂养成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -91,13 +91,13 @@ public class PetController : WeChatBaseController
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentWxUserId();
|
||||
if (userId == null)
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId == 0)
|
||||
{
|
||||
return BaseResponse<PageListModel<FeedingRecordOutput>>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
}
|
||||
|
||||
var result = await _petService.GetFeedingRecordsAsync(userId.Value, petId, pageQuery);
|
||||
var result = await _petService.GetFeedingRecordsAsync(userId, petId, pageQuery);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
|
||||
@ -86,7 +86,8 @@ public class WeChatAuthController : WeChatBaseController
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<WeChatSwitchUserOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var result = await _weChatAuthService.SwitchUserAsync(wxUserId.Value, input);
|
||||
var currentUserId = GetCurrentUserId();
|
||||
var result = await _weChatAuthService.SwitchUserAsync(wxUserId.Value, currentUserId, input);
|
||||
return Success(result);
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
@ -154,4 +155,31 @@ public class WeChatAuthController : WeChatBaseController
|
||||
return BaseResponse<WxUserOutput>.Fail("新增用户失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改家长名字(WxUser.Name)
|
||||
/// </summary>
|
||||
[HttpPost("updateName")]
|
||||
public async Task<BaseResponse<WxUserInfoOutput>> UpdateWxUserNameAsync([FromBody] UpdateWxUserNameInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var wxUserId = GetCurrentWxUserId();
|
||||
if (wxUserId == null)
|
||||
return BaseResponse<WxUserInfoOutput>.Fail(ResultCode.DENY, "未获取到用户信息");
|
||||
|
||||
var result = await _weChatAuthService.UpdateWxUserNameAsync(wxUserId.Value, input);
|
||||
return Success(result, "修改成功");
|
||||
}
|
||||
catch (BusinessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "修改家长名字业务异常: {Message}", ex.Message);
|
||||
return BaseResponse<WxUserInfoOutput>.Fail(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "修改家长名字系统异常");
|
||||
return BaseResponse<WxUserInfoOutput>.Fail("修改家长名字失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
||||
using QYZH.InteractiveMagazine.Models.Dto;
|
||||
using QYZH.InteractiveMagazine.Models.Enum;
|
||||
using System.Security.Claims;
|
||||
@ -16,16 +17,30 @@ namespace QYZH.InteractiveMagazine.WebApi.Controllers.WeChat;
|
||||
public abstract class WeChatBaseController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前微信用户ID(WxUser.Id,来自 JWT)
|
||||
/// 获取当前激活用户ID(Users.Id,来自 JWT NameIdentifier)
|
||||
/// </summary>
|
||||
/// <returns>WxUser ID</returns>
|
||||
protected long? GetCurrentWxUserId()
|
||||
/// <returns>User ID(无激活用户时为 0)</returns>
|
||||
protected long GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && long.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前微信用户ID(WxUser.Id,来自 JWT WxUserId claim)
|
||||
/// </summary>
|
||||
/// <returns>WxUser ID</returns>
|
||||
protected long? GetCurrentWxUserId()
|
||||
{
|
||||
var wxUserIdClaim = User.Claims.FirstOrDefault(c => c.Type == JwtHelper.WxUserIdClaimType);
|
||||
if (wxUserIdClaim != null && long.TryParse(wxUserIdClaim.Value, out var wxUserId))
|
||||
{
|
||||
return wxUserId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user