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

283 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.Extensions.Logging;
using QYZH.InteractiveMagazine.IService;
using QYZH.InteractiveMagazine.Models.Common;
using QYZH.InteractiveMagazine.Models.Dto;
using QYZH.InteractiveMagazine.Models.Dto.Pet;
using QYZH.InteractiveMagazine.Models.Entity;
using QYZH.InteractiveMagazine.Repository;
using SqlSugar;
namespace QYZH.InteractiveMagazine.Service;
/// <summary>
/// 宠物服务实现
/// </summary>
public class PetService(
BaseRepository<Pet> petRepository,
BaseRepository<PetFeedingRecord> feedingRecordRepository,
BaseRepository<PetEvolution> petEvolutionRepository,
ILogger<PetService> logger)
: BaseRepository<Pet>, IPetService
{
/// <summary>
/// 获取用户宠物信息
/// </summary>
public async Task<PetOutput?> GetPetByUserIdAsync(long userId)
{
logger.LogInformation("获取用户宠物信息UserId: {UserId}", userId);
var pet = await petRepository.Queryable()
.Where(p => p.UserId == userId)
.Select(p => new PetOutput
{
Id = p.Id,
UserId = p.UserId,
Name = p.Name,
CurrentEvolutionId = p.CurrentEvolutionId,
GrowthPoints = p.GrowthPoints,
FeedingCount = p.FeedingCount,
Type = p.Type,
Status = p.Status,
CreatedAt = p.CreatedAt
})
.FirstAsync();
return pet;
}
/// <summary>
/// 为用户创建默认宠物最低形态、成长值为0、未激活状态
/// </summary>
public async Task CreateDefaultPetAsync(long userId)
{
logger.LogInformation("为用户创建默认宠物UserId: {UserId}", userId);
// 检查用户是否已有宠物
var exists = petRepository.Context.Queryable<Pet>()
.Any(p => p.UserId == userId);
if (exists)
{
logger.LogWarning("用户已存在宠物跳过创建UserId: {UserId}", userId);
return;
}
var pet = new Pet
{
UserId = userId,
Name = "小精灵",
CurrentEvolutionId = 1,
GrowthPoints = 0,
FeedingCount = 0,
Type = "Normal",
Status = "Inactive",
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
var result = await petRepository.InsertAsync(pet);
if (!result)
{
logger.LogError("创建默认宠物失败UserId: {UserId}", userId);
throw new Exception("创建宠物失败");
}
logger.LogInformation("用户默认宠物创建成功UserId: {UserId}, PetId: {PetId}", userId, pet.Id);
}
/// <summary>
/// 激活宠物(将状态从 Inactive 改为 Active
/// </summary>
public async Task ActivatePetAsync(long userId)
{
logger.LogInformation("激活用户宠物UserId: {UserId}", userId);
var pet = await petRepository.Queryable()
.Where(p => p.UserId == userId)
.FirstAsync();
if (pet == null)
{
logger.LogWarning("用户宠物不存在无法激活UserId: {UserId}", userId);
return;
}
if (pet.Status != "Inactive")
{
logger.LogInformation("用户宠物已非未激活状态跳过激活UserId: {UserId}, Status: {Status}", userId, pet.Status);
return;
}
var result = await petRepository.UpdateAsync(
p => new Pet { Status = "Active" },
p => p.UserId == userId);
if (!result)
{
logger.LogError("激活宠物失败UserId: {UserId}", userId);
throw new Exception("激活宠物失败");
}
logger.LogInformation("用户宠物激活成功UserId: {UserId}", userId);
}
/// <summary>
/// 喂养宠物(增加成长值 + 记录喂养记录 + 触发进化检查)
/// </summary>
public async Task<FeedPetOutput> FeedPetAsync(long userId, FeedPetInput input)
{
logger.LogInformation("喂养宠物UserId: {UserId}, PetId: {PetId}, GrowthPoints: {GrowthPoints}",
userId, input.PetId, input.GrowthPoints);
if (input.PetId <= 0)
{
throw new BusinessException("宠物Id不能为空", 400);
}
if (input.GrowthPoints <= 0)
{
throw new BusinessException("成长值必须大于0", 400);
}
// 查询宠物
var pet = await petRepository.GetByIdAsync(input.PetId);
if (pet == null || pet.IsDeleted)
{
logger.LogWarning("喂养失败宠物不存在PetId: {PetId}", input.PetId);
throw new BusinessException("宠物不存在", 404);
}
// 校验宠物归属
if (pet.UserId != userId)
{
logger.LogWarning("喂养失败无权操作该宠物UserId: {UserId}, PetUserId: {PetUserId}", userId, pet.UserId);
throw new BusinessException("无权操作该宠物", 403);
}
// 校验宠物状态
if (pet.Status != "Active")
{
logger.LogWarning("喂养失败宠物未激活PetId: {PetId}, Status: {Status}", input.PetId, pet.Status);
throw new BusinessException("宠物未激活,无法喂养", 400);
}
var growthBefore = pet.GrowthPoints;
var growthAfter = growthBefore + input.GrowthPoints;
var hasEvolved = false;
string? evolvedStageName = null;
// 事务保证一致性
await UseTranAsync(async () =>
{
// 累加成长值和喂养次数
var updateResult = await petRepository.Context.Updateable<Pet>()
.SetColumns(p => p.GrowthPoints == growthAfter)
.SetColumns(p => p.FeedingCount == p.FeedingCount + 1)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (updateResult <= 0)
{
throw new BusinessException("更新宠物成长值失败", 500);
}
// 进化检查:查找下一阶段进化形态
var nextEvolution = await petEvolutionRepository.Queryable()
.Where(e => e.PreviousEvolutionId == pet.CurrentEvolutionId
&& e.RequiredGrowth <= growthAfter
&& e.Status == "Active")
.OrderBy(e => e.RequiredGrowth, OrderByType.Desc)
.FirstAsync();
if (nextEvolution != null)
{
// 触发进化
var evolveResult = await petRepository.Context.Updateable<Pet>()
.SetColumns(p => p.CurrentEvolutionId == nextEvolution.Id)
.SetColumns(p => p.UpdatedAt == DateTime.Now)
.SetColumns(p => p.UpdatedBy == userId.ToString())
.Where(p => p.Id == input.PetId)
.ExecuteCommandAsync();
if (evolveResult > 0)
{
hasEvolved = true;
evolvedStageName = nextEvolution.StageName;
logger.LogInformation("宠物进化成功PetId: {PetId}, 新形态: {StageName} (Level {StageLevel})",
input.PetId, nextEvolution.StageName, nextEvolution.StageLevel);
}
}
// 写入喂养记录
var record = new PetFeedingRecord
{
PetId = input.PetId,
UserId = userId,
PointsUsed = 0, // 预留:后期可扩展为消耗积分喂养
GrowthChange = input.GrowthPoints,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
Type = "Normal",
Status = "Success",
IsDeleted = false,
CreatedBy = userId.ToString(),
CreatedAt = DateTime.Now,
UpdatedBy = userId.ToString(),
UpdatedAt = DateTime.Now
};
var insertResult = await feedingRecordRepository.InsertAsync(record);
if (!insertResult)
{
throw new BusinessException("写入喂养记录失败", 500);
}
});
logger.LogInformation("喂养宠物成功PetId: {PetId}, 成长值: {Before} -> {After}, 进化: {HasEvolved}",
input.PetId, growthBefore, growthAfter, hasEvolved);
return new FeedPetOutput
{
PetId = input.PetId,
GrowthBefore = growthBefore,
GrowthAfter = growthAfter,
GrowthChange = input.GrowthPoints,
HasEvolved = hasEvolved,
EvolvedStageName = evolvedStageName
};
}
/// <summary>
/// 获取宠物喂养记录列表
/// </summary>
public async Task<PageListModel<FeedingRecordOutput>> GetFeedingRecordsAsync(long userId, long petId, PageQueryModel pageQuery)
{
logger.LogInformation("查询喂养记录UserId: {UserId}, PetId: {PetId}, PageIndex: {PageIndex}, PageSize: {PageSize}",
userId, petId, pageQuery.PageIndex, pageQuery.PageSize);
RefAsync<int> totalNumber = 0;
var records = await feedingRecordRepository.Queryable()
.Where(r => r.UserId == userId && r.PetId == petId)
.OrderByDescending(r => r.CreatedAt)
.Select(r => new FeedingRecordOutput
{
Id = r.Id,
PetId = r.PetId,
UserId = r.UserId,
GrowthChange = r.GrowthChange,
GrowthBefore = r.GrowthBefore,
GrowthAfter = r.GrowthAfter,
Type = r.Type,
Status = r.Status,
CreatedAt = r.CreatedAt
}, true)
.ToPageListAsync(pageQuery.PageIndex, pageQuery.PageSize, totalNumber);
return new PageListModel<FeedingRecordOutput>(records, pageQuery.PageIndex, pageQuery.PageSize, totalNumber);
}
}