Files
QYZH.InteractiveMagazine/QYZH.InteractiveMagazine.Service/MedalService.cs
glz 78b686f9e5 refactor: 批量新增枚举类型并完成实体、DTO、服务层枚举替换
- 新增30+业务枚举类型覆盖用户、宠物、商城、社区、积分等模块
- 完成实体类、DTO、服务层的字符串枚举替换为强类型枚举
- 修复用户状态枚举名称变更,将Frozen改为Disabled
- 新增批量发布社区消息接口与控制器实现
- 新增操作日志、积分管理、补偿任务相关服务与DTO
2026-06-08 16:57:44 +08:00

650 lines
22 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 System.Text.Json;
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.Models.Enum;
using QYZH.InteractiveMagazine.Repository;
using SqlSugar;
namespace QYZH.InteractiveMagazine.Service;
/// <summary>
/// 勋章服务实现
/// </summary>
public class MedalService(BaseRepository<Medal> medalRepository, ILogger<MedalService> logger) : BaseRepository<Medal>, IMedalService
{
/// <summary>
/// 创建勋章(含规则,事务)
/// </summary>
public async Task<MedalOutput> CreateAsync(MedalInput input)
{
logger.LogInformation("正在创建勋章,勋章名称: {Name}", input.Name);
if (string.IsNullOrWhiteSpace(input.Name))
{
throw new BusinessException("勋章名称不能为空", 400);
}
if (string.IsNullOrWhiteSpace(input.Type))
{
throw new BusinessException("勋章类型不能为空", 400);
}
var medal = new Medal
{
Name = input.Name.Trim(),
Description = input.Description,
ImageUrl = input.ImageUrl,
SortOrder = input.SortOrder,
Type = Enum.Parse<MedalTypeEnum>(input.Type, true),
JournalId = input.JournalId,
CreatedBy = "System",
UpdatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
IsDeleted = false
};
try
{
await UseTranAsync(async () =>
{
var result = await medalRepository.InsertAsync(medal);
if (!result)
{
throw new BusinessException("创建勋章失败", 500);
}
if (input.Rules != null && input.Rules.Count > 0)
{
await InsertRulesAsync(medal.Id, input.Rules);
}
});
}
catch (BusinessException)
{
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "勋章创建事务失败,勋章名称: {Name}", input.Name);
throw new BusinessException("创建勋章失败", 500);
}
logger.LogInformation("勋章创建成功,勋章名称: {Name}, ID: {Id}", input.Name, medal.Id);
var rules = await GetRulesByMedalIdAsync(medal.Id);
return BuildMedalOutput(medal, rules);
}
/// <summary>
/// 更新勋章(含规则,事务)
/// </summary>
public async Task<MedalOutput> 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.Type))
{
throw new BusinessException("勋章类型不能为空", 400);
}
medal.Name = input.Name.Trim();
medal.Description = input.Description;
medal.ImageUrl = input.ImageUrl;
medal.SortOrder = input.SortOrder;
medal.Type = Enum.Parse<MedalTypeEnum>(input.Type, true);
medal.JournalId = input.JournalId;
medal.UpdatedBy = "System";
medal.UpdatedAt = DateTime.Now;
try
{
await UseTranAsync(async () =>
{
var result = await medalRepository.UpdateAsync(medal);
if (!result)
{
throw new BusinessException("更新勋章失败", 500);
}
// 删除旧规则,重新插入新规则
await Context.Updateable<MedalRule>()
.SetColumns(r => new MedalRule { IsDeleted = true, UpdatedBy = "System", UpdatedAt = DateTime.Now })
.Where(r => r.MedalId == medal.Id)
.ExecuteCommandAsync();
if (input.Rules != null && input.Rules.Count > 0)
{
await InsertRulesAsync(medal.Id, input.Rules);
}
});
}
catch (BusinessException)
{
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "勋章更新事务失败ID: {Id}", id);
throw new BusinessException("更新勋章失败", 500);
}
logger.LogInformation("勋章更新成功ID: {Id}", id);
var rules = await GetRulesByMedalIdAsync(medal.Id);
return BuildMedalOutput(medal, rules);
}
/// <summary>
/// 删除勋章(软删除,含规则)
/// </summary>
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);
}
try
{
await UseTranAsync(async () =>
{
var result = await medalRepository.DeleteByIdAsync(id);
if (!result)
{
throw new BusinessException("删除勋章失败", 500);
}
// 同时软删除关联规则
await Context.Updateable<MedalRule>()
.SetColumns(r => new MedalRule { IsDeleted = true, UpdatedBy = "System", UpdatedAt = DateTime.Now })
.Where(r => r.MedalId == id)
.ExecuteCommandAsync();
});
}
catch (BusinessException)
{
throw;
}
catch (Exception ex)
{
logger.LogError(ex, "勋章删除事务失败ID: {Id}", id);
throw new BusinessException("删除勋章失败", 500);
}
logger.LogInformation("勋章删除成功ID: {Id}", id);
}
/// <summary>
/// 根据ID获取勋章含规则
/// </summary>
public async Task<MedalOutput> 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);
}
var rules = await GetRulesByMedalIdAsync(medal.Id);
return BuildMedalOutput(medal, rules);
}
/// <summary>
/// 分页查询勋章列表(含规则)
/// </summary>
public async Task<PageListModel<MedalOutput>> 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<int> totalNumber = 0;
var medals = await medalRepository.Queryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), m => m.Name.Contains(input.Name))
.WhereIF(!string.IsNullOrWhiteSpace(input.Type), m => m.Type.ToString() == input.Type)
.OrderBy(m => m.SortOrder)
.OrderByDescending(m => m.CreatedAt)
.ToPageListAsync(input.PageIndex, input.PageSize, totalNumber);
// 批量查询所有勋章的规则
var medalIds = medals.Select(m => m.Id).ToList();
var allRules = await Context.Queryable<MedalRule>()
.Where(r => medalIds.Contains(r.MedalId) && !r.IsDeleted)
.ToListAsync();
var rulesDict = allRules.GroupBy(r => r.MedalId)
.ToDictionary(g => g.Key, g => g.OrderBy(r => r.RuleOrder).Select(r => MapRuleOutput(r)).ToList());
var pageResult = medals.Select(m => BuildMedalOutput(m, rulesDict.GetValueOrDefault(m.Id))).ToList();
var result = new PageListModel<MedalOutput>(new List<MedalOutput>(), input.PageIndex, input.PageSize, totalNumber);
result.Result = pageResult;
return result;
}
/// <summary>
/// 更新勋章启用/禁用状态
/// </summary>
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);
}
/// <summary>
/// 获取勋章的规则列表
/// </summary>
public async Task<List<MedalRuleOutput>> GetRulesByMedalIdAsync(long medalId)
{
var rules = await Context.Queryable<MedalRule>()
.Where(r => r.MedalId == medalId && !r.IsDeleted)
.OrderBy(r => r.RuleOrder)
.ToListAsync();
return rules.Select(r => MapRuleOutput(r)).ToList();
}
/// <summary>
/// 获取所有启用勋章列表(含当前用户拥有状态)
/// </summary>
public async Task<List<WxMedalListOutput>> GetAllMedalsAsync(long userId)
{
logger.LogInformation("正在获取所有勋章列表用户ID: {UserId}", userId);
var medals = await medalRepository.Queryable()
.Where(m => m.Status == 1)
.OrderBy(m => m.SortOrder)
.OrderByDescending(m => m.CreatedAt)
.ToListAsync();
var userMedals = await Context.Queryable<UserMedal>()
.Where(um => um.UserId == userId && um.Status == UserMedalStatusEnum.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,
SortOrder = m.SortOrder,
Type = m.Type.ToString(),
IsOwned = userMedalDict.ContainsKey((int)m.Id),
AwardedAt = userMedalDict.TryGetValue((int)m.Id, out var awardedAt) ? awardedAt : null
}).ToList();
}
/// <summary>
/// 获取用户已拥有的勋章列表
/// </summary>
public async Task<List<WxUserMedalOutput>> GetUserMedalsAsync(long userId)
{
logger.LogInformation("正在获取用户勋章列表用户ID: {UserId}", userId);
var result = await Context.Queryable<UserMedal>()
.InnerJoin<Medal>((um, m) => um.MedalId == m.Id)
.Where((um, m) => um.UserId == userId && um.Status == UserMedalStatusEnum.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.ToString(),
AwardedAt = um.AwardedAt,
Status = um.Status.ToString()
})
.ToListAsync();
return result;
}
/// <summary>
/// 激活/获得勋章
/// </summary>
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 (passed, failReason) = await ValidateMedalRulesAsync(medal.Id, userId);
if (!passed)
{
logger.LogWarning("勋章规则校验未通过用户ID: {UserId}, 勋章ID: {MedalId}, 原因: {Reason}", userId, input.MedalId, failReason);
throw new BusinessException(failReason!, 400);
}
var existingUserMedal = await Context.Queryable<UserMedal>()
.Where(um => um.UserId == userId && um.MedalId == (int)input.MedalId && um.Status == UserMedalStatusEnum.Awarded)
.FirstAsync();
if (existingUserMedal != null)
{
throw new BusinessException("您已拥有该勋章", 400);
}
var userMedal = new UserMedal
{
UserId = userId,
MedalId = (int)input.MedalId,
AwardedAt = DateTime.Now,
Type = (UserMedalTypeEnum)medal.Type,
Status = UserMedalStatusEnum.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);
}
#region
/// <summary>
/// 批量插入规则
/// </summary>
private async Task InsertRulesAsync(long medalId, List<MedalRuleInput> ruleInputs)
{
var rules = ruleInputs.Select(r => new MedalRule
{
MedalId = medalId,
TargetTable = r.TargetTable,
TargetField = r.TargetField,
Operator = r.Operator,
ThresholdValue = r.ThresholdValue,
FilterCondition = r.FilterCondition,
DateRangeType = r.DateRangeType,
DateRangeStart = r.DateRangeStart,
DateRangeEnd = r.DateRangeEnd,
LogicOperator = r.LogicOperator,
RuleOrder = r.RuleOrder,
Description = r.Description,
CreatedBy = "System",
UpdatedBy = "System",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
IsDeleted = false
}).ToList();
var count = await Context.Insertable(rules).ExecuteCommandAsync();
if (count <= 0)
{
logger.LogError("勋章规则插入失败MedalId: {MedalId}", medalId);
throw new BusinessException("创建勋章规则失败", 500);
}
logger.LogInformation("勋章规则创建成功MedalId: {MedalId}, 规则数: {Count}", medalId, rules.Count);
}
/// <summary>
/// 构建MedalOutput
/// </summary>
private static MedalOutput BuildMedalOutput(Medal medal, List<MedalRuleOutput>? rules)
{
return new MedalOutput
{
Id = medal.Id,
Name = medal.Name,
Description = medal.Description,
ImageUrl = medal.ImageUrl,
SortOrder = medal.SortOrder,
Type = medal.Type.ToString(),
JournalId = medal.JournalId,
Rules = rules,
CreatedBy = medal.CreatedBy,
CreatedAt = medal.CreatedAt,
UpdatedBy = medal.UpdatedBy,
UpdatedAt = medal.UpdatedAt
};
}
/// <summary>
/// 映射规则实体到输出DTO
/// </summary>
private static MedalRuleOutput MapRuleOutput(MedalRule r)
{
return new MedalRuleOutput
{
Id = r.Id,
MedalId = r.MedalId,
TargetTable = r.TargetTable,
TargetField = r.TargetField,
Operator = r.Operator,
ThresholdValue = r.ThresholdValue,
FilterCondition = r.FilterCondition,
DateRangeType = r.DateRangeType,
DateRangeStart = r.DateRangeStart,
DateRangeEnd = r.DateRangeEnd,
LogicOperator = r.LogicOperator,
RuleOrder = r.RuleOrder,
Description = r.Description,
CreatedAt = r.CreatedAt,
UpdatedAt = r.UpdatedAt
};
}
/// <summary>
/// 校验勋章规则是否满足
/// </summary>
/// <param name="medalId">勋章ID</param>
/// <param name="userId">用户ID</param>
/// <returns>(是否通过, 失败原因)</returns>
private async Task<(bool passed, string? failReason)> ValidateMedalRulesAsync(long medalId, long userId)
{
var rules = await Context.Queryable<MedalRule>()
.Where(r => r.MedalId == medalId && !r.IsDeleted)
.OrderBy(r => r.RuleOrder)
.ToListAsync();
if (!rules.Any())
{
return (true, null);
}
var failReasons = new List<string>();
foreach (var rule in rules)
{
var rulePassed = await EvaluateRuleAsync(rule, userId);
if (!rulePassed)
{
var reason = !string.IsNullOrWhiteSpace(rule.Description)
? rule.Description
: $"未满足条件: {rule.TargetField} {rule.Operator} {rule.ThresholdValue}";
failReasons.Add(reason);
}
var logicOp = rule.LogicOperator?.ToUpper() ?? "AND";
if (logicOp == "AND" && !rulePassed)
{
return (false, $"未满足获得条件:{string.Join("", failReasons)}");
}
if (logicOp == "OR" && rulePassed)
{
return (true, null);
}
}
// AND 模式下所有规则都通过才到这里
// OR 模式下所有规则都没通过才到这里
var firstLogicOp = rules.First().LogicOperator?.ToUpper() ?? "AND";
if (firstLogicOp == "AND")
{
return (true, null);
}
else
{
return (false, $"未满足获得条件:{string.Join("", failReasons)}");
}
}
/// <summary>
/// 评估单条规则是否满足
/// </summary>
/// <param name="rule">规则配置</param>
/// <param name="userId">用户ID</param>
/// <returns>规则是否满足</returns>
private async Task<bool> EvaluateRuleAsync(MedalRule rule, long userId)
{
try
{
var sql = $"SELECT COUNT(1) FROM [{rule.TargetTable}] WHERE [{rule.TargetField}] {rule.Operator} @ThresholdValue AND IsDeleted = 0 AND UserId = @UserId";
var parameters = new List<SugarParameter>
{
new SugarParameter("@ThresholdValue", rule.ThresholdValue),
new SugarParameter("@UserId", userId)
};
// 处理额外筛选条件
if (!string.IsNullOrWhiteSpace(rule.FilterCondition))
{
var filters = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(rule.FilterCondition);
if (filters != null)
{
var index = 0;
foreach (var kv in filters)
{
var paramName = $"@Filter_{index}";
sql += $" AND [{kv.Key}] = {paramName}";
var paramValue = kv.Value.ValueKind switch
{
JsonValueKind.Number => kv.Value.GetInt32() as object,
JsonValueKind.String => kv.Value.GetString() as object,
JsonValueKind.True => true as object,
JsonValueKind.False => false as object,
_ => kv.Value.GetRawText()
};
parameters.Add(new SugarParameter(paramName, paramValue!));
index++;
}
}
}
// 处理时间范围
var dateRange = GetDateRange(rule);
if (dateRange.HasValue)
{
sql += " AND CreatedAt >= @DateStart AND CreatedAt <= @DateEnd";
parameters.Add(new SugarParameter("@DateStart", dateRange.Value.start));
parameters.Add(new SugarParameter("@DateEnd", dateRange.Value.end));
}
var count = await Context.Ado.GetIntAsync(sql, parameters.ToArray());
return count > 0;
}
catch (Exception ex)
{
logger.LogError(ex, "规则评估异常规则ID: {RuleId}, 目标表: {Table}, 目标字段: {Field}",
rule.Id, rule.TargetTable, rule.TargetField);
return false;
}
}
/// <summary>
/// 根据规则的时间范围类型计算起止时间
/// </summary>
private static (DateTime start, DateTime end)? GetDateRange(MedalRule rule)
{
var now = DateTime.Now;
return rule.DateRangeType?.ToLower() switch
{
"currentmonth" => (new DateTime(now.Year, now.Month, 1), now),
"currentweek" => (now.AddDays(-(int)now.DayOfWeek), now),
"last7days" => (now.AddDays(-7), now),
"custom" when rule.DateRangeStart.HasValue && rule.DateRangeEnd.HasValue => (rule.DateRangeStart.Value, rule.DateRangeEnd.Value),
_ => null
};
}
#endregion
}