feat: 新增签到、宠物、期刊绑定、补偿任务等业务模块,优化微信登录流程

本次提交完成了多个核心业务模块的开发与优化:
1.  宠物模块:新增宠物实体、服务接口与实现,支持创建默认宠物、激活、喂养、进化以及喂养记录查询
2.  签到模块:新增签到实体、服务接口、控制器以及相关DTO,支持用户签到和签到信息查询,新增成长值奖励字段
3.  期刊绑定模块:新增用户期刊关联实体、服务接口与控制器,支持扫码绑定期刊、解绑和查询绑定列表
4.  补偿任务模块:新增补偿任务实体、服务接口与实现,用于处理业务失败后的异步重试补偿
5.  优化微信登录流程:拆分登录与快捷登录接口,支持手机号获取,新增首次登录自动创建默认宠物逻辑
6.  调整基础路由与实体状态:修改微信控制器路由前缀,更新宠物状态枚举与默认值
This commit is contained in:
glz
2026-06-04 15:54:30 +08:00
parent 018d7e1733
commit 903ccd3073
25 changed files with 2411 additions and 32 deletions

View File

@ -0,0 +1,136 @@
using QYZH.InteractiveMagazine.Models.Dto;
namespace QYZH.InteractiveMagazine.Models.Dto.CheckIn;
/// <summary>
/// 签到输出
/// </summary>
public class CheckInOutput
{
/// <summary>
/// 签到记录Id
/// </summary>
public long RecordId { get; set; }
/// <summary>
/// 签到日期
/// </summary>
public DateTime CheckInDate { get; set; }
/// <summary>
/// 连续签到天数
/// </summary>
public int ConsecutiveDays { get; set; }
/// <summary>
/// 本次获得积分
/// </summary>
public int PointsAwarded { get; set; }
/// <summary>
/// 本次获得成长值
/// </summary>
public int GrowthPointsAwarded { get; set; }
/// <summary>
/// 签到后用户积分余额
/// </summary>
public int PointsBalance { get; set; }
/// <summary>
/// 签到后用户成长值余额
/// </summary>
public int GrowthPointsBalance { get; set; }
/// <summary>
/// 是否有宠物(成长值是否喂养到宠物)
/// </summary>
public bool HasPet { get; set; }
/// <summary>
/// 宠物是否触发进化
/// </summary>
public bool HasEvolved { get; set; }
/// <summary>
/// 进化后的阶段名称(未进化则为空)
/// </summary>
public string? EvolvedStageName { get; set; }
}
/// <summary>
/// 签到信息输出(查询用)
/// </summary>
public class CheckInInfoOutput
{
/// <summary>
/// 今日是否已签到
/// </summary>
public bool HasCheckedInToday { get; set; }
/// <summary>
/// 当前连续签到天数
/// </summary>
public int ConsecutiveDays { get; set; }
/// <summary>
/// 累计签到天数
/// </summary>
public int TotalCheckInDays { get; set; }
/// <summary>
/// 用户当前积分余额
/// </summary>
public int PointsBalance { get; set; }
/// <summary>
/// 用户当前成长值余额
/// </summary>
public int GrowthPointsBalance { get; set; }
/// <summary>
/// 最近签到记录
/// </summary>
public List<CheckInRecordOutput> RecentRecords { get; set; } = [];
}
/// <summary>
/// 签到记录输出
/// </summary>
public class CheckInRecordOutput
{
/// <summary>
/// 记录Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 签到日期
/// </summary>
public DateTime CheckInDate { get; set; }
/// <summary>
/// 连续签到天数
/// </summary>
public int ConsecutiveDays { get; set; }
/// <summary>
/// 本次获得积分
/// </summary>
public int PointsAwarded { get; set; }
/// <summary>
/// 本次获得成长值
/// </summary>
public int GrowthPointsAwarded { get; set; }
/// <summary>
/// 签到类型: Normal, MakeUp
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; } = string.Empty;
}

View File

@ -0,0 +1,163 @@
namespace QYZH.InteractiveMagazine.Models.Dto.Compensation;
/// <summary>
/// 补偿任务类型常量
/// </summary>
public static class CompensationTaskType
{
/// <summary>
/// 宠物喂养补偿
/// Payload: { "PetId": long, "GrowthPoints": int }
/// </summary>
public const string PetFeeding = "PetFeeding";
/// <summary>
/// 用户积分补偿
/// Payload: { "Points": int, "ChangeType": string, "Description": string }
/// </summary>
public const string UserPoints = "UserPoints";
/// <summary>
/// 用户成长值补偿
/// Payload: { "GrowthPoints": int }
/// </summary>
public const string UserGrowth = "UserGrowth";
/// <summary>
/// 发送通知补偿
/// Payload: { "TemplateId": string, "Data": object }
/// </summary>
public const string SendNotification = "SendNotification";
}
/// <summary>
/// 补偿任务状态常量
/// </summary>
public static class CompensationTaskStatus
{
public const string Pending = "Pending";
public const string Processing = "Processing";
public const string Success = "Success";
public const string Failed = "Failed";
public const string Cancelled = "Cancelled";
}
/// <summary>
/// 创建补偿任务输入
/// </summary>
public class CreateCompensationTaskInput
{
/// <summary>
/// 任务类型(使用 CompensationTaskType 常量)
/// </summary>
public string TaskType { get; set; } = string.Empty;
/// <summary>
/// 业务来源(如 CheckIn、Purchase
/// </summary>
public string BusinessSource { get; set; } = string.Empty;
/// <summary>
/// 关联业务记录Id
/// </summary>
public string? BusinessId { get; set; }
/// <summary>
/// 关联用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 处理参数匿名对象或字典会自动序列化为JSON
/// </summary>
public object Payload { get; set; } = new { };
/// <summary>
/// 异常消息
/// </summary>
public string ErrorMessage { get; set; } = string.Empty;
/// <summary>
/// 异常来源(类名.方法名,如 CheckInService.CheckInAsync
/// </summary>
public string ErrorSource { get; set; } = string.Empty;
/// <summary>
/// 最大重试次数默认3次
/// </summary>
public int MaxRetries { get; set; } = 3;
}
/// <summary>
/// 补偿任务输出
/// </summary>
public class CompensationTaskOutput
{
public long Id { get; set; }
public string TaskType { get; set; } = string.Empty;
public string BusinessSource { get; set; } = string.Empty;
public string? BusinessId { get; set; }
public long UserId { get; set; }
public string Payload { get; set; } = string.Empty;
public string ErrorMessage { get; set; } = string.Empty;
public string ErrorSource { get; set; } = string.Empty;
public int RetryCount { get; set; }
public int MaxRetries { get; set; }
public string Status { get; set; } = string.Empty;
public DateTime? ProcessedAt { get; set; }
public DateTime? ScheduledAt { get; set; }
public string? ResultMessage { get; set; }
public DateTime CreatedAt { get; set; }
}
/// <summary>
/// 查询补偿任务输入(供外部项目按条件拉取)
/// </summary>
public class GetCompensationTasksInput
{
/// <summary>
/// 按状态筛选
/// </summary>
public string? Status { get; set; }
/// <summary>
/// 按任务类型筛选
/// </summary>
public string? TaskType { get; set; }
/// <summary>
/// 按业务来源筛选
/// </summary>
public string? BusinessSource { get; set; }
/// <summary>
/// 获取数量上限默认50
/// </summary>
public int Limit { get; set; } = 50;
}
/// <summary>
/// 更新补偿任务状态输入(供外部处理项目回调)
/// </summary>
public class UpdateCompensationStatusInput
{
/// <summary>
/// 目标状态(使用 CompensationTaskStatus 常量)
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 处理结果描述
/// </summary>
public string? ResultMessage { get; set; }
/// <summary>
/// 更新重试次数(可选)
/// </summary>
public int? RetryCount { get; set; }
/// <summary>
/// 下次计划执行时间(用于退避重试,可选)
/// </summary>
public DateTime? ScheduledAt { get; set; }
}

View File

@ -0,0 +1,84 @@
namespace QYZH.InteractiveMagazine.Models.Dto;
/// <summary>
/// 绑定期刊输入DTO
/// </summary>
public class BindJournalInput
{
/// <summary>
/// 期刊模板Id扫码解析的期刊定义Id
/// </summary>
public long JournalId { get; set; }
/// <summary>
/// 实例化期刊Id扫码解析的具体期刊实例Id可选
/// </summary>
public long? JournalInstanceId { get; set; }
/// <summary>
/// 关联类型: Read(已读), Favorite(收藏), Subscribe(订阅),默认 Subscribe
/// </summary>
public string Type { get; set; } = "Subscribe";
}
/// <summary>
/// 绑定期刊输出DTO
/// </summary>
public class BindJournalOutput
{
/// <summary>
/// 绑定记录Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 期刊模板Id
/// </summary>
public long JournalId { get; set; }
/// <summary>
/// 实例化期刊Id
/// </summary>
public long? JournalInstanceId { get; set; }
/// <summary>
/// 关联类型
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 绑定时间
/// </summary>
public DateTime CreatedAt { get; set; }
}
/// <summary>
/// 用户期刊关联查询输入DTO
/// </summary>
public class UserJournalQueryInput : PageQueryModel
{
/// <summary>
/// 期刊模板Id
/// </summary>
public long? JournalId { get; set; }
/// <summary>
/// 实例化期刊Id
/// </summary>
public long? JournalInstanceId { get; set; }
/// <summary>
/// 关联类型: Read, Favorite, Subscribe
/// </summary>
public string? Type { get; set; }
}

View File

@ -0,0 +1,155 @@
namespace QYZH.InteractiveMagazine.Models.Dto.Pet;
/// <summary>
/// 宠物信息输出
/// </summary>
public class PetOutput
{
/// <summary>
/// 主键ID
/// </summary>
public long Id { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 宠物昵称
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 当前进化形态Id
/// </summary>
public int CurrentEvolutionId { get; set; }
/// <summary>
/// 当前成长值
/// </summary>
public int GrowthPoints { get; set; }
/// <summary>
/// 累计喂养次数
/// </summary>
public int FeedingCount { get; set; }
/// <summary>
/// 宠物类型: Normal
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 状态: Inactive, Active, Sleeping
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
}
/// <summary>
/// 喂养宠物输入
/// </summary>
public class FeedPetInput
{
/// <summary>
/// 宠物Id
/// </summary>
public long PetId { get; set; }
/// <summary>
/// 增加的成长值
/// </summary>
public int GrowthPoints { get; set; }
}
/// <summary>
/// 喂养宠物输出
/// </summary>
public class FeedPetOutput
{
/// <summary>
/// 宠物Id
/// </summary>
public long PetId { get; set; }
/// <summary>
/// 喂养前成长值
/// </summary>
public int GrowthBefore { get; set; }
/// <summary>
/// 喂养后成长值
/// </summary>
public int GrowthAfter { get; set; }
/// <summary>
/// 本次成长变化量
/// </summary>
public int GrowthChange { get; set; }
/// <summary>
/// 是否触发进化
/// </summary>
public bool HasEvolved { get; set; }
/// <summary>
/// 进化后的阶段名称(未进化则为空)
/// </summary>
public string? EvolvedStageName { get; set; }
}
/// <summary>
/// 喂养记录输出
/// </summary>
public class FeedingRecordOutput
{
/// <summary>
/// 记录Id
/// </summary>
public long Id { get; set; }
/// <summary>
/// 宠物Id
/// </summary>
public long PetId { get; set; }
/// <summary>
/// 用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 成长值变化量
/// </summary>
public int GrowthChange { get; set; }
/// <summary>
/// 喂养前成长值
/// </summary>
public int GrowthBefore { get; set; }
/// <summary>
/// 喂养后成长值
/// </summary>
public int GrowthAfter { get; set; }
/// <summary>
/// 喂养类型: Normal, Special
/// </summary>
public string Type { get; set; } = string.Empty;
/// <summary>
/// 状态
/// </summary>
public string Status { get; set; } = string.Empty;
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreatedAt { get; set; }
}

View File

@ -0,0 +1,37 @@
namespace QYZH.InteractiveMagazine.Models.Dto.Pet;
/// <summary>
/// 宠物喂养补偿消息
/// </summary>
public class PetFeedingCompensationMessage
{
/// <summary>
/// 用户Id
/// </summary>
public long UserId { get; set; }
/// <summary>
/// 宠物Id
/// </summary>
public long PetId { get; set; }
/// <summary>
/// 需要补偿的成长值
/// </summary>
public int GrowthPoints { get; set; }
/// <summary>
/// 签到记录Id关联来源
/// </summary>
public long CheckInRecordId { get; set; }
/// <summary>
/// 补偿原因
/// </summary>
public string Reason { get; set; } = "签到成功但宠物喂养失败";
/// <summary>
/// 消息创建时间
/// </summary>
public DateTime CreatedAt { get; set; } = DateTime.Now;
}

View File

@ -4,7 +4,7 @@ using Newtonsoft.Json;
namespace QYZH.InteractiveMagazine.IService.Dto;
/// <summary>
/// 微信小程序登录输入
/// 微信小程序初次登录输入
/// </summary>
public class WeChatLoginInput
{
@ -12,6 +12,22 @@ public class WeChatLoginInput
/// 微信登录凭证wx.login 获取的 code
/// </summary>
public string Code { get; set; } = string.Empty;
/// <summary>
/// 手机号获取凭证getPhoneNumber 按钮回调中的 code可选
/// </summary>
public string? PhoneCode { get; set; }
}
/// <summary>
/// 微信小程序快捷登录输入(通过 OpenId 登录)
/// </summary>
public class WeChatQuickLoginInput
{
/// <summary>
/// 微信OpenId初次登录后客户端缓存的 OpenId
/// </summary>
public string OpenId { get; set; } = string.Empty;
}
/// <summary>
@ -225,6 +241,84 @@ public class WxCode2SessionResponse
public string? ErrMsg { get; set; }
}
/// <summary>
/// 微信获取手机号接口响应
/// </summary>
public class WxPhoneNumberResponse
{
/// <summary>
/// 错误码
/// </summary>
[JsonProperty("errcode")]
public int ErrCode { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonProperty("errmsg")]
public string? ErrMsg { get; set; }
/// <summary>
/// 手机号信息
/// </summary>
[JsonProperty("phone_info")]
public WxPhoneInfo? PhoneInfo { get; set; }
}
/// <summary>
/// 微信手机号信息
/// </summary>
public class WxPhoneInfo
{
/// <summary>
/// 用户绑定的手机号(国外手机号会有区号)
/// </summary>
[JsonProperty("phoneNumber")]
public string? PhoneNumber { get; set; }
/// <summary>
/// 没有区号的手机号
/// </summary>
[JsonProperty("purePhoneNumber")]
public string? PurePhoneNumber { get; set; }
/// <summary>
/// 区号
/// </summary>
[JsonProperty("countryCode")]
public string? CountryCode { get; set; }
}
/// <summary>
/// 微信 access_token 接口响应
/// </summary>
public class WxAccessTokenResponse
{
/// <summary>
/// 获取到的凭证
/// </summary>
[JsonProperty("access_token")]
public string? AccessToken { get; set; }
/// <summary>
/// 凭证有效时间(秒)
/// </summary>
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
/// <summary>
/// 错误码
/// </summary>
[JsonProperty("errcode")]
public int ErrCode { get; set; }
/// <summary>
/// 错误信息
/// </summary>
[JsonProperty("errmsg")]
public string? ErrMsg { get; set; }
}
/// <summary>
/// 微信切换用户输入
/// </summary>

View File

@ -33,6 +33,13 @@ namespace QYZH.InteractiveMagazine.Models.Entity
/// </summary>
public int PointsAwarded {get;set;}
/// <summary>
/// Desc:本次签到获得成长值(喂养宠物)
/// Default:0
/// Nullable:False
/// </summary>
public int GrowthPointsAwarded {get;set;}
/// <summary>
/// Desc:连续签到天数
/// Default:

View File

@ -0,0 +1,108 @@
using SqlSugar;
namespace QYZH.InteractiveMagazine.Models.Entity
{
/// <summary>
/// 补偿任务表 — 记录业务执行失败后需要异步重试的操作
/// </summary>
[SugarTable("CompensationTask")]
public partial class CompensationTask : SqlSugarBaseEntity
{
public CompensationTask() { }
/// <summary>
/// Desc:任务类型,决定补偿处理逻辑
/// PetFeeding — 宠物喂养补偿
/// UserPoints — 用户积分补偿
/// UserGrowth — 用户成长值补偿
/// SendNotification — 发送通知补偿
/// Default:
/// Nullable:False
/// </summary>
public string TaskType { get; set; }
/// <summary>
/// Desc:业务来源(如 CheckIn、Purchase、Activity
/// Default:
/// Nullable:False
/// </summary>
public string BusinessSource { get; set; }
/// <summary>
/// Desc:关联业务记录Id如签到记录Id、订单Id
/// Default:
/// Nullable:True
/// </summary>
public string? BusinessId { get; set; }
/// <summary>
/// Desc:关联用户Id
/// Default:
/// Nullable:False
/// </summary>
public long UserId { get; set; }
/// <summary>
/// Desc:处理参数JSON格式不同TaskType对应不同结构
/// Default:
/// Nullable:False
/// </summary>
public string Payload { get; set; }
/// <summary>
/// Desc:原始异常消息
/// Default:
/// Nullable:False
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// Desc:异常来源(如 CheckInService.CheckInAsync
/// Default:
/// Nullable:False
/// </summary>
public string ErrorSource { get; set; }
/// <summary>
/// Desc:已重试次数
/// Default:0
/// Nullable:False
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// Desc:最大重试次数
/// Default:3
/// Nullable:False
/// </summary>
public int MaxRetries { get; set; }
/// <summary>
/// Desc:处理状态 Pending / Processing / Success / Failed / Cancelled
/// Default:Pending
/// Nullable:False
/// </summary>
public new string Status { get; set; }
/// <summary>
/// Desc:最后处理时间
/// Default:
/// Nullable:True
/// </summary>
public DateTime? ProcessedAt { get; set; }
/// <summary>
/// Desc:下次计划执行时间(用于退避重试)
/// Default:
/// Nullable:True
/// </summary>
public DateTime? ScheduledAt { get; set; }
/// <summary>
/// Desc:处理结果描述(成功/失败原因)
/// Default:
/// Nullable:True
/// </summary>
public string? ResultMessage { get; set; }
}
}

View File

@ -55,8 +55,8 @@ namespace QYZH.InteractiveMagazine.Models.Entity
public string Type {get;set;}
/// <summary>
/// Desc:状态: Active, Sleeping
/// Default:Active
/// Desc:状态: Inactive, Active, Sleeping
/// Default:Inactive
/// Nullable:False
/// </summary>
public string Status {get;set;}

View File

@ -0,0 +1,56 @@
using SqlSugar;
namespace QYZH.InteractiveMagazine.Models.Entity
{
///<summary>
///用户与期刊关联表
///</summary>
[SugarTable("UserJournal")]
public partial class UserJournal : SqlSugarBaseEntity
{
public UserJournal()
{
}
/// <summary>
/// Desc:用户Id
/// Default:
/// Nullable:False
/// </summary>
[SugarColumn(ColumnName = "UserId")]
public long UserId { get; set; }
/// <summary>
/// Desc:期刊模板Id对应Journal表的期刊定义
/// Default:
/// Nullable:False
/// </summary>
[SugarColumn(ColumnName = "JournalId")]
public long JournalId { get; set; }
/// <summary>
/// Desc:实例化期刊Id扫码获取的具体期刊实例可为空表示绑定到期刊模板本身
/// Default:
/// Nullable:True
/// </summary>
[SugarColumn(ColumnName = "JournalInstanceId", IsNullable = true)]
public long? JournalInstanceId { get; set; }
/// <summary>
/// Desc:关联类型: Read(已读), Favorite(收藏), Subscribe(订阅)
/// Default:Read
/// Nullable:False
/// </summary>
[SugarColumn(ColumnName = "Type")]
public string Type { get; set; }
/// <summary>
/// Desc:状态: Active(正常), Inactive(失效)
/// Default:Active
/// Nullable:False
/// </summary>
[SugarColumn(ColumnName = "Status")]
public new string Status { get; set; }
}
}