From f9b79417a059bcae96c76a989dc259bb90c12f2a Mon Sep 17 00:00:00 2001
From: glz <694770232@qq.com>
Date: Fri, 5 Jun 2026 15:16:49 +0800
Subject: [PATCH] feat: add medal module and optimize community entities
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1. 新增勋章管理全流程功能,包含实体、DTO、服务层、控制器
2. 重构DotFile实体继承基础实体类
3. 删除UserJournal的无用JournalInstanceId字段
4. 重构CommunityMessage实体字段并新增点赞数、精选标记字段
5. 新增社区点赞、评论、模板言论实体并替换旧有实体文件
6. 更新gitignore忽略PROJECT_STRUCTURE.md文件
---
.gitignore | 3 +-
.../IMedalService.cs | 73 ++++
.../Dto/Medal/MedalDto.cs | 247 +++++++++++
.../Entity/CommunityMessage.cs | 110 +++--
...eComment.cs => CommunityMessageComment.cs} | 0
.../Entity/CommunityMessageLike.cs | 38 ++
...ntence.cs => CommunityTemplateSentence.cs} | 0
.../Entity/DotFile.cs | 2 +-
.../Entity/MessageLike.cs | 43 --
.../Entity/UserJournal.cs | 8 -
.../MedalService.cs | 410 ++++++++++++++++++
.../Controllers/MedalController.cs | 177 ++++++++
.../WeChat/WeChatMedalController.cs | 99 +++++
13 files changed, 1118 insertions(+), 92 deletions(-)
create mode 100644 QYZH.InteractiveMagazine.IService/IMedalService.cs
create mode 100644 QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs
rename QYZH.InteractiveMagazine.Models/Entity/{MessageComment.cs => CommunityMessageComment.cs} (100%)
create mode 100644 QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs
rename QYZH.InteractiveMagazine.Models/Entity/{TemplateSentence.cs => CommunityTemplateSentence.cs} (100%)
delete mode 100644 QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs
create mode 100644 QYZH.InteractiveMagazine.Service/MedalService.cs
create mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/MedalController.cs
create mode 100644 QYZH.InteractiveMagazine.WebApi/Controllers/WeChat/WeChatMedalController.cs
diff --git a/.gitignore b/.gitignore
index 47d45e0..a2effb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -363,4 +363,5 @@ MigrationBackup/
FodyWeavers.xsd
# Trae IDE related files
-.trae/
\ No newline at end of file
+.trae/
+PROJECT_STRUCTURE.md
diff --git a/QYZH.InteractiveMagazine.IService/IMedalService.cs b/QYZH.InteractiveMagazine.IService/IMedalService.cs
new file mode 100644
index 0000000..3bede1f
--- /dev/null
+++ b/QYZH.InteractiveMagazine.IService/IMedalService.cs
@@ -0,0 +1,73 @@
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Entity;
+
+namespace QYZH.InteractiveMagazine.IService;
+
+///
+/// 勋章服务接口
+///
+public interface IMedalService : IBaseService
+{
+ ///
+ /// 创建勋章
+ ///
+ /// 勋章输入
+ /// 创建的勋章信息
+ Task CreateAsync(MedalInput input);
+
+ ///
+ /// 更新勋章
+ ///
+ /// 勋章ID
+ /// 勋章输入
+ /// 更新后的勋章信息
+ Task UpdateAsync(long id, MedalInput input);
+
+ ///
+ /// 删除勋章(软删除)
+ ///
+ /// 勋章ID
+ Task DeleteAsync(long id);
+
+ ///
+ /// 根据ID获取勋章
+ ///
+ /// 勋章ID
+ /// 勋章信息
+ Task GetByIdAsync(long id);
+
+ ///
+ /// 分页查询勋章列表
+ ///
+ /// 查询条件
+ /// 分页结果
+ Task> GetListAsync(MedalQueryInput input);
+
+ ///
+ /// 更新勋章启用/禁用状态
+ ///
+ /// 勋章ID
+ /// 状态: 0=禁用, 1=启用
+ Task UpdateStatusAsync(long id, int status);
+
+ ///
+ /// 获取所有启用勋章列表(含当前用户拥有状态)
+ ///
+ /// 当前用户ID
+ /// 勋章列表
+ Task> GetAllMedalsAsync(long userId);
+
+ ///
+ /// 获取用户已拥有的勋章列表
+ ///
+ /// 用户ID
+ /// 用户勋章列表
+ Task> GetUserMedalsAsync(long userId);
+
+ ///
+ /// 激活/获得勋章
+ ///
+ /// 用户ID
+ /// 激活输入
+ Task ActivateMedalAsync(long userId, WxMedalActivateInput input);
+}
diff --git a/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs
new file mode 100644
index 0000000..01371f9
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Dto/Medal/MedalDto.cs
@@ -0,0 +1,247 @@
+namespace QYZH.InteractiveMagazine.Models.Dto;
+
+///
+/// 勋章创建/更新输入
+///
+public class MedalInput
+{
+ ///
+ /// 勋章名称
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 获得条件描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 条件类型: EvolutionCount, FeedingCount等
+ ///
+ public string ConditionType { get; set; } = string.Empty;
+
+ ///
+ /// 条件阈值
+ ///
+ public int ConditionValue { get; set; }
+
+ ///
+ /// 排序
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// 勋章的类型: Pet, Community
+ ///
+ public string Type { get; set; } = "Pet";
+
+ ///
+ /// 书id
+ ///
+ public long JournalId { get; set; }
+}
+
+///
+/// 勋章输出
+///
+public class MedalOutput
+{
+ ///
+ /// 主键ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 勋章名称
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 获得条件描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 条件类型
+ ///
+ public string ConditionType { get; set; } = string.Empty;
+
+ ///
+ /// 条件阈值
+ ///
+ public int ConditionValue { get; set; }
+
+ ///
+ /// 排序
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// 勋章的类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 书id
+ ///
+ public long JournalId { get; set; }
+
+ ///
+ /// 创建人
+ ///
+ public string? CreatedBy { get; set; }
+
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreatedAt { get; set; }
+
+ ///
+ /// 更新人
+ ///
+ public string? UpdatedBy { get; set; }
+
+ ///
+ /// 更新时间
+ ///
+ public DateTime? UpdatedAt { get; set; }
+}
+
+///
+/// 勋章分页查询输入
+///
+public class MedalQueryInput : PageQueryModel
+{
+ ///
+ /// 勋章名称(模糊查询)
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// 勋章的类型: Pet, Community
+ ///
+ public string? Type { get; set; }
+
+ ///
+ /// 条件类型
+ ///
+ public string? ConditionType { get; set; }
+}
+
+///
+/// 微信端勋章列表输出(含拥有状态)
+///
+public class WxMedalListOutput
+{
+ ///
+ /// 勋章ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// 勋章名称
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 获得条件描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 条件类型
+ ///
+ public string ConditionType { get; set; } = string.Empty;
+
+ ///
+ /// 条件阈值
+ ///
+ public int ConditionValue { get; set; }
+
+ ///
+ /// 排序
+ ///
+ public int SortOrder { get; set; }
+
+ ///
+ /// 勋章的类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 是否已拥有
+ ///
+ public bool IsOwned { get; set; }
+
+ ///
+ /// 获得时间(未拥有则为null)
+ ///
+ public DateTime? AwardedAt { get; set; }
+}
+
+///
+/// 微信端用户已拥有勋章输出
+///
+public class WxUserMedalOutput
+{
+ ///
+ /// 勋章ID
+ ///
+ public long MedalId { get; set; }
+
+ ///
+ /// 勋章名称
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// 获得条件描述
+ ///
+ public string? Description { get; set; }
+
+ ///
+ /// 图片地址
+ ///
+ public string? ImageUrl { get; set; }
+
+ ///
+ /// 勋章的类型
+ ///
+ public string Type { get; set; } = string.Empty;
+
+ ///
+ /// 获得时间
+ ///
+ public DateTime? AwardedAt { get; set; }
+
+ ///
+ /// 状态: Awarded, Revoked
+ ///
+ public string Status { get; set; } = string.Empty;
+}
+
+///
+/// 微信端激活勋章输入
+///
+public class WxMedalActivateInput
+{
+ ///
+ /// 勋章ID
+ ///
+ public long MedalId { get; set; }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs
index 5e7286e..df72a07 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessage.cs
@@ -8,58 +8,90 @@ namespace QYZH.InteractiveMagazine.Models.Entity
[SugarTable("CommunityMessage")]
public partial class CommunityMessage : SqlSugarBaseEntity
{
- public CommunityMessage(){
+ public CommunityMessage()
+ {
- }
+ }
///
/// Desc:期刊Id
/// Default:
/// Nullable:False
///
- public long JournalId {get;set;}
+ public long JournalId { get; set; }
+ ///
+ /// Desc:用户Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long UserId { get; set; }
- ///
- /// Desc:消息内容
- /// Default:
- /// Nullable:False
- ///
- public string Content {get;set;}
- ///
- /// Desc:配图
- /// Default:
- /// Nullable:True
- ///
- public string ImageUrl {get;set;}
+ ///
+ /// Desc:用户实例期刊Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long UserJournalId { get; set; }
+ ///
+ /// Desc:期刊任务Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long JournalTaskId { get; set; }
+ ///
+ /// Desc:用户期刊任务回答Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long JournalTaskAnswerId { get; set; }
+ ///
+ /// Desc:消息内容
+ /// Default:
+ /// Nullable:False
+ ///
+ public string Content { get; set; }
- ///
- /// Desc:排序
- /// Default:0
- /// Nullable:False
- ///
- public int SortOrder {get;set;}
+ ///
+ /// Desc:配图
+ /// Default:
+ /// Nullable:True
+ ///
+ public string ImageUrl { get; set; }
- ///
- /// Desc:是否启用
- /// Default:b'1'
- /// Nullable:False
- ///
- public bool IsActive {get;set;}
+ ///
+ /// Desc:排序
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int SortOrder { get; set; }
- ///
- /// Desc:消息类型: Article, Quote, Announcement
- /// Default:Article
- /// Nullable:False
- ///
- public string Type {get;set;}
+ ///
+ /// Desc:是否启用
+ /// Default:b'1'
+ /// Nullable:False
+ ///
+ public bool IsActive { get; set; }
- ///
- /// Desc:状态: Draft, Published
- /// Default:Draft
- /// Nullable:False
- ///
- public string Status {get;set;}
+ ///
+ /// Desc:消息类型: Article, Quote, Announcement
+ /// Default:Article
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
+
+ ///
+ /// Desc:点赞数
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int LikeCount { get; set; }
+ ///
+ /// Desc:是否是精选消息
+ /// Default:0
+ /// Nullable:False
+ ///
+ public int IsFeatured { get; set; }
}
}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs
similarity index 100%
rename from QYZH.InteractiveMagazine.Models/Entity/MessageComment.cs
rename to QYZH.InteractiveMagazine.Models/Entity/CommunityMessageComment.cs
diff --git a/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs
new file mode 100644
index 0000000..7f29b78
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Models/Entity/CommunityMessageLike.cs
@@ -0,0 +1,38 @@
+using SqlSugar;
+
+namespace QYZH.InteractiveMagazine.Models.Entity
+{
+ ///
+ ///点赞记录表
+ ///
+ [SugarTable("MessageLike")]
+ public partial class CommunityMessageLike : SqlSugarBaseEntity
+ {
+ public CommunityMessageLike()
+ {
+
+
+ }
+ ///
+ /// Desc:用户Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long UserId { get; set; }
+
+ ///
+ /// Desc:消息Id
+ /// Default:
+ /// Nullable:False
+ ///
+ public long MessageId { get; set; }
+
+ ///
+ /// Desc:点赞类型
+ /// Default:Like
+ /// Nullable:False
+ ///
+ public string Type { get; set; }
+
+ }
+}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs b/QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs
similarity index 100%
rename from QYZH.InteractiveMagazine.Models/Entity/TemplateSentence.cs
rename to QYZH.InteractiveMagazine.Models/Entity/CommunityTemplateSentence.cs
diff --git a/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs b/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs
index 3f94716..a92a498 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/DotFile.cs
@@ -9,7 +9,7 @@ namespace QYZH.InteractiveMagazine.Models.Entity
///
///
[SugarTable("DotFile")]
- public partial class DotFile
+ public partial class DotFile : SqlSugarBaseEntity
{
public DotFile(){
diff --git a/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs b/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs
deleted file mode 100644
index aa65c12..0000000
--- a/QYZH.InteractiveMagazine.Models/Entity/MessageLike.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using SqlSugar;
-
-namespace QYZH.InteractiveMagazine.Models.Entity
-{
- ///
- ///点赞记录表
- ///
- [SugarTable("MessageLike")]
- public partial class MessageLike : SqlSugarBaseEntity
- {
- public MessageLike(){
-
-
- }
- ///
- /// Desc:用户Id
- /// Default:
- /// Nullable:False
- ///
- public long UserId {get;set;}
-
- ///
- /// Desc:消息Id
- /// Default:
- /// Nullable:False
- ///
- public long MessageId {get;set;}
-
- ///
- /// Desc:点赞类型
- /// Default:Like
- /// Nullable:False
- ///
- public string Type {get;set;}
-
- ///
- /// Desc:状态: Liked, Cancelled
- /// Default:Liked
- /// Nullable:False
- ///
- public string Status {get;set;}
- }
-}
diff --git a/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs b/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs
index bea9529..138409a 100644
--- a/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs
+++ b/QYZH.InteractiveMagazine.Models/Entity/UserJournal.cs
@@ -29,14 +29,6 @@ namespace QYZH.InteractiveMagazine.Models.Entity
[SugarColumn(ColumnName = "JournalId")]
public long JournalId { get; set; }
- ///
- /// Desc:实例化期刊Id(扫码获取的具体期刊实例,可为空表示绑定到期刊模板本身)
- /// Default:
- /// Nullable:True
- ///
- [SugarColumn(ColumnName = "JournalInstanceId", IsNullable = true)]
- public long? JournalInstanceId { get; set; }
-
///
/// Desc:关联类型: Read(已读), Favorite(收藏), Subscribe(订阅)
/// Default:Read
diff --git a/QYZH.InteractiveMagazine.Service/MedalService.cs b/QYZH.InteractiveMagazine.Service/MedalService.cs
new file mode 100644
index 0000000..1c0957f
--- /dev/null
+++ b/QYZH.InteractiveMagazine.Service/MedalService.cs
@@ -0,0 +1,410 @@
+using Microsoft.Extensions.Logging;
+using QYZH.InteractiveMagazine.IService;
+using QYZH.InteractiveMagazine.Models.Common;
+using QYZH.InteractiveMagazine.Models.Dto;
+using QYZH.InteractiveMagazine.Models.Entity;
+using QYZH.InteractiveMagazine.Repository;
+using SqlSugar;
+
+namespace QYZH.InteractiveMagazine.Service;
+
+///
+/// 勋章服务实现
+///
+public class MedalService(BaseRepository medalRepository, ILogger logger) : BaseRepository, IMedalService
+{
+
+ ///
+ /// 创建勋章
+ ///
+ public async Task CreateAsync(MedalInput input)
+ {
+ logger.LogInformation("正在创建勋章,勋章名称: {Name}", input.Name);
+
+ if (string.IsNullOrWhiteSpace(input.Name))
+ {
+ throw new BusinessException("勋章名称不能为空", 400);
+ }
+
+ if (string.IsNullOrWhiteSpace(input.ConditionType))
+ {
+ throw new BusinessException("条件类型不能为空", 400);
+ }
+
+ if (string.IsNullOrWhiteSpace(input.Type))
+ {
+ throw new BusinessException("勋章类型不能为空", 400);
+ }
+
+ if (input.ConditionValue < 0)
+ {
+ throw new BusinessException("条件阈值不能为负数", 400);
+ }
+
+ var medal = new Medal
+ {
+ Name = input.Name.Trim(),
+ Description = input.Description,
+ ImageUrl = input.ImageUrl,
+ ConditionType = input.ConditionType,
+ ConditionValue = input.ConditionValue,
+ SortOrder = input.SortOrder,
+ Type = input.Type,
+ JournalId = input.JournalId,
+ CreatedBy = "System",
+ UpdatedBy = "System",
+ CreatedAt = DateTime.Now,
+ UpdatedAt = DateTime.Now,
+ IsDeleted = false
+ };
+
+ var result = await medalRepository.InsertAsync(medal);
+ if (!result)
+ {
+ logger.LogError("勋章创建失败,勋章名称: {Name}", input.Name);
+ throw new BusinessException("创建勋章失败", 500);
+ }
+
+ logger.LogInformation("勋章创建成功,勋章名称: {Name}, ID: {Id}", input.Name, medal.Id);
+
+ return new MedalOutput
+ {
+ Id = medal.Id,
+ Name = medal.Name,
+ Description = medal.Description,
+ ImageUrl = medal.ImageUrl,
+ ConditionType = medal.ConditionType,
+ ConditionValue = medal.ConditionValue,
+ SortOrder = medal.SortOrder,
+ Type = medal.Type,
+ JournalId = medal.JournalId,
+ CreatedBy = medal.CreatedBy,
+ CreatedAt = medal.CreatedAt,
+ UpdatedBy = medal.UpdatedBy,
+ UpdatedAt = medal.UpdatedAt
+ };
+ }
+
+ ///
+ /// 更新勋章
+ ///
+ public async Task UpdateAsync(long id, MedalInput input)
+ {
+ logger.LogInformation("正在更新勋章,ID: {Id}", id);
+
+ var medal = await medalRepository.GetByIdAsync(id);
+ if (medal == null)
+ {
+ logger.LogWarning("未找到要更新的勋章,ID: {Id}", id);
+ throw new BusinessException("勋章不存在", 404);
+ }
+
+ if (string.IsNullOrWhiteSpace(input.Name))
+ {
+ throw new BusinessException("勋章名称不能为空", 400);
+ }
+
+ if (string.IsNullOrWhiteSpace(input.ConditionType))
+ {
+ throw new BusinessException("条件类型不能为空", 400);
+ }
+
+ if (string.IsNullOrWhiteSpace(input.Type))
+ {
+ throw new BusinessException("勋章类型不能为空", 400);
+ }
+
+ if (input.ConditionValue < 0)
+ {
+ throw new BusinessException("条件阈值不能为负数", 400);
+ }
+
+ medal.Name = input.Name.Trim();
+ medal.Description = input.Description;
+ medal.ImageUrl = input.ImageUrl;
+ medal.ConditionType = input.ConditionType;
+ medal.ConditionValue = input.ConditionValue;
+ medal.SortOrder = input.SortOrder;
+ medal.Type = input.Type;
+ medal.JournalId = input.JournalId;
+ medal.UpdatedBy = "System";
+ medal.UpdatedAt = DateTime.Now;
+
+ var result = await medalRepository.UpdateAsync(medal);
+ if (!result)
+ {
+ logger.LogError("勋章更新失败,ID: {Id}", id);
+ throw new BusinessException("更新勋章失败", 500);
+ }
+
+ logger.LogInformation("勋章更新成功,ID: {Id}", id);
+
+ return new MedalOutput
+ {
+ Id = medal.Id,
+ Name = medal.Name,
+ Description = medal.Description,
+ ImageUrl = medal.ImageUrl,
+ ConditionType = medal.ConditionType,
+ ConditionValue = medal.ConditionValue,
+ SortOrder = medal.SortOrder,
+ Type = medal.Type,
+ JournalId = medal.JournalId,
+ CreatedBy = medal.CreatedBy,
+ CreatedAt = medal.CreatedAt,
+ UpdatedBy = medal.UpdatedBy,
+ UpdatedAt = medal.UpdatedAt
+ };
+ }
+
+ ///
+ /// 删除勋章(软删除)
+ ///
+ public async Task DeleteAsync(long id)
+ {
+ logger.LogInformation("正在删除勋章,ID: {Id}", id);
+
+ var medal = await medalRepository.GetByIdAsync(id);
+ if (medal == null)
+ {
+ logger.LogWarning("未找到要删除的勋章,ID: {Id}", id);
+ throw new BusinessException("勋章不存在", 404);
+ }
+
+ var result = await medalRepository.DeleteByIdAsync(id);
+ if (!result)
+ {
+ logger.LogError("勋章删除失败,ID: {Id}", id);
+ throw new BusinessException("删除勋章失败", 500);
+ }
+
+ logger.LogInformation("勋章删除成功,ID: {Id}", id);
+ }
+
+ ///
+ /// 根据ID获取勋章
+ ///
+ public async Task GetByIdAsync(long id)
+ {
+ logger.LogInformation("正在获取勋章信息,ID: {Id}", id);
+
+ var medal = await medalRepository.GetByIdAsync(id);
+ if (medal == null)
+ {
+ logger.LogWarning("未找到勋章,ID: {Id}", id);
+ throw new BusinessException("勋章不存在", 404);
+ }
+
+ return new MedalOutput
+ {
+ Id = medal.Id,
+ Name = medal.Name,
+ Description = medal.Description,
+ ImageUrl = medal.ImageUrl,
+ ConditionType = medal.ConditionType,
+ ConditionValue = medal.ConditionValue,
+ SortOrder = medal.SortOrder,
+ Type = medal.Type,
+ JournalId = medal.JournalId,
+ CreatedBy = medal.CreatedBy,
+ CreatedAt = medal.CreatedAt,
+ UpdatedBy = medal.UpdatedBy,
+ UpdatedAt = medal.UpdatedAt
+ };
+ }
+
+ ///
+ /// 分页查询勋章列表
+ ///
+ public async Task> GetListAsync(MedalQueryInput 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 totalNumber = 0;
+ var pageResult = await medalRepository.Queryable()
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Name), m => m.Name.Contains(input.Name))
+ .WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type == input.Type)
+ .WhereIF(!string.IsNullOrWhiteSpace(input.ConditionType), m => m.ConditionType == input.ConditionType)
+ .OrderBy(m => m.SortOrder, SqlSugar.OrderByType.Asc)
+ .OrderByDescending(m => m.CreatedAt)
+ .Select(m => new MedalOutput
+ {
+ Id = m.Id,
+ Name = m.Name,
+ Description = m.Description,
+ ImageUrl = m.ImageUrl,
+ ConditionType = m.ConditionType,
+ ConditionValue = m.ConditionValue,
+ SortOrder = m.SortOrder,
+ Type = m.Type,
+ JournalId = m.JournalId,
+ CreatedBy = m.CreatedBy,
+ CreatedAt = m.CreatedAt,
+ UpdatedBy = m.UpdatedBy,
+ UpdatedAt = m.UpdatedAt
+ }, true)
+ .ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
+
+ return new PageListModel(pageResult, input.PageIndex, input.PageSize, totalNumber);
+ }
+
+ ///
+ /// 更新勋章启用/禁用状态
+ ///
+ public async Task UpdateStatusAsync(long id, int status)
+ {
+ logger.LogInformation("正在更新勋章状态,ID: {Id}, Status: {Status}", id, status);
+
+ if (status != 0 && status != 1)
+ {
+ throw new BusinessException("状态值无效,只能为0(禁用)或1(启用)", 400);
+ }
+
+ var medal = await medalRepository.GetByIdAsync(id);
+ if (medal == null)
+ {
+ logger.LogWarning("未找到要更新状态的勋章,ID: {Id}", id);
+ throw new BusinessException("勋章不存在", 404);
+ }
+
+ medal.Status = status;
+ medal.UpdatedBy = "System";
+ medal.UpdatedAt = DateTime.Now;
+
+ var result = await medalRepository.UpdateAsync(medal);
+ if (!result)
+ {
+ logger.LogError("勋章状态更新失败,ID: {Id}", id);
+ throw new BusinessException("更新勋章状态失败", 500);
+ }
+
+ logger.LogInformation("勋章状态更新成功,ID: {Id}, Status: {Status}", id, status);
+ }
+
+ ///
+ /// 获取所有启用勋章列表(含当前用户拥有状态)
+ ///
+ public async Task> GetAllMedalsAsync(long userId)
+ {
+ logger.LogInformation("正在获取所有勋章列表,用户ID: {UserId}", userId);
+
+ var medals = await medalRepository.Queryable()
+ .Where(m => m.Status == 1)
+ .OrderBy(m => m.SortOrder, SqlSugar.OrderByType.Asc)
+ .OrderByDescending(m => m.CreatedAt)
+ .ToListAsync();
+
+ var userMedals = await Context.Queryable()
+ .Where(um => um.UserId == userId && um.Status == "Awarded")
+ .ToListAsync();
+
+ var userMedalDict = userMedals.ToDictionary(um => um.MedalId, um => um.AwardedAt);
+
+ return medals.Select(m => new WxMedalListOutput
+ {
+ Id = m.Id,
+ Name = m.Name,
+ Description = m.Description,
+ ImageUrl = m.ImageUrl,
+ ConditionType = m.ConditionType,
+ ConditionValue = m.ConditionValue,
+ SortOrder = m.SortOrder,
+ Type = m.Type,
+ IsOwned = userMedalDict.ContainsKey((int)m.Id),
+ AwardedAt = userMedalDict.TryGetValue((int)m.Id, out var awardedAt) ? awardedAt : null
+ }).ToList();
+ }
+
+ ///
+ /// 获取用户已拥有的勋章列表
+ ///
+ public async Task> GetUserMedalsAsync(long userId)
+ {
+ logger.LogInformation("正在获取用户勋章列表,用户ID: {UserId}", userId);
+
+ var result = await Context.Queryable()
+ .InnerJoin((um, m) => um.MedalId == m.Id)
+ .Where((um, m) => um.UserId == userId && um.Status == "Awarded")
+ .OrderByDescending((um, m) => um.AwardedAt)
+ .Select((um, m) => new WxUserMedalOutput
+ {
+ MedalId = m.Id,
+ Name = m.Name,
+ Description = m.Description,
+ ImageUrl = m.ImageUrl,
+ Type = m.Type,
+ AwardedAt = um.AwardedAt,
+ Status = um.Status
+ })
+ .ToListAsync();
+
+ return result;
+ }
+
+ ///
+ /// 激活/获得勋章
+ ///
+ public async Task ActivateMedalAsync(long userId, WxMedalActivateInput input)
+ {
+ logger.LogInformation("用户正在激活勋章,用户ID: {UserId}, 勋章ID: {MedalId}", userId, input.MedalId);
+
+ if (input.MedalId <= 0)
+ {
+ throw new BusinessException("勋章ID无效", 400);
+ }
+
+ var medal = await medalRepository.GetByIdAsync(input.MedalId);
+ if (medal == null)
+ {
+ logger.LogWarning("未找到要激活的勋章,勋章ID: {MedalId}", input.MedalId);
+ throw new BusinessException("勋章不存在", 404);
+ }
+
+ if (medal.Status != 1)
+ {
+ throw new BusinessException("该勋章当前不可获得", 400);
+ }
+
+ var existingUserMedal = await Context.Queryable()
+ .Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == "Awarded")
+ .FirstAsync();
+
+ if (existingUserMedal != null)
+ {
+ throw new BusinessException("您已拥有该勋章", 400);
+ }
+
+ var userMedal = new UserMedal
+ {
+ UserId = userId,
+ MedalId = (int)input.MedalId,
+ AwardedAt = DateTime.Now,
+ Type = medal.Type,
+ Status = "Awarded",
+ CreatedBy = "System",
+ UpdatedBy = "System",
+ CreatedAt = DateTime.Now,
+ UpdatedAt = DateTime.Now,
+ IsDeleted = false
+ };
+
+ var insertResult = await Context.Insertable(userMedal).ExecuteCommandAsync();
+ if (insertResult <= 0)
+ {
+ logger.LogError("勋章激活失败,用户ID: {UserId}, 勋章ID: {MedalId}", userId, input.MedalId);
+ throw new BusinessException("激活勋章失败", 500);
+ }
+
+ logger.LogInformation("勋章激活成功,用户ID: {UserId}, 勋章ID: {MedalId}", userId, input.MedalId);
+ }
+}
diff --git a/QYZH.InteractiveMagazine.WebApi/Controllers/MedalController.cs b/QYZH.InteractiveMagazine.WebApi/Controllers/MedalController.cs
new file mode 100644
index 0000000..7d9eaa1
--- /dev/null
+++ b/QYZH.InteractiveMagazine.WebApi/Controllers/MedalController.cs
@@ -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.Enum;
+
+namespace QYZH.InteractiveMagazine.WebApi.Controllers;
+
+///
+/// 勋章管理控制器
+///
+[Route("api/[controller]")]
+[ApiController]
+[ApiExplorerSettings(GroupName = nameof(ApiVersionEnum.Platform))]
+public class MedalController : BaseController
+{
+ private readonly IMedalService _medalService;
+ private readonly ILogger _logger;
+
+ public MedalController(IMedalService medalService, ILogger logger)
+ {
+ _medalService = medalService;
+ _logger = logger;
+ }
+
+ ///
+ /// 创建勋章
+ ///
+ /// 勋章信息
+ /// 创建的勋章信息
+ [HttpPost]
+ public async Task> CreateAsync([FromBody] MedalInput input)
+ {
+ try
+ {
+ var result = await _medalService.CreateAsync(input);
+ return Success(result, "创建勋章成功");
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "创建勋章业务异常: {Message}", ex.Message);
+ return BaseResponse.Fail(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "创建勋章系统异常,参数:{Input}", input);
+ return BaseResponse.Fail("创建勋章失败,请稍后重试");
+ }
+ }
+
+ ///
+ /// 更新勋章
+ ///
+ /// 勋章ID
+ /// 勋章信息
+ /// 更新后的勋章信息
+ [HttpPut("{id}")]
+ public async Task> UpdateAsync(long id, [FromBody] MedalInput input)
+ {
+ try
+ {
+ var result = await _medalService.UpdateAsync(id, input);
+ return Success(result, "更新勋章成功");
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "更新勋章业务异常: {Message}", ex.Message);
+ return BaseResponse.Fail(ex.Message);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "更新勋章系统异常,ID:{Id},参数:{Input}", id, input);
+ return BaseResponse.Fail("更新勋章失败,请稍后重试");
+ }
+ }
+
+ ///
+ /// 删除勋章
+ ///
+ /// 勋章ID
+ /// 操作结果
+ [HttpDelete("{id}")]
+ public async Task> DeleteAsync(long id)
+ {
+ try
+ {
+ await _medalService.DeleteAsync(id);
+ return Success(new object(), "删除勋章成功");
+ }
+ catch (BusinessException ex)
+ {
+ _logger.LogWarning(ex, "删除勋章业务异常: {Message}", ex.Message);
+ return BaseResponse