feat: 新增期刊二维码批量生成功能及相关配套
1. 新增UserJournalQrCodeGenerateConsumer消费者处理二维码生成队列任务 2. 新增用户期刊状态枚举的生成中、失败状态 3. 新增批量生成二维码服务方法和相关DTO 4. 优化SqlSugar自动填充创建/更新人字段逻辑 5. 调整接口参数从操作人ID改为操作人名称 6. 新增获取未绑定二维码的API接口和服务方法
This commit is contained in:
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoWarn>$(NoWarn);CS0105;CS0108;CS0168;CS0169;CS0618;CS1570;CS1572;CS1573;CS1591;CS8600;CS8601;CS8602;CS8603;CS8604;CS8618;CS8625;CS8629;CS8634;CS8714;CS9113</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@ -35,9 +35,17 @@ public interface IUserJournalService : IBaseService<UserJournal>
|
|||||||
/// 生成期刊二维码记录
|
/// 生成期刊二维码记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">生成输入</param>
|
/// <param name="input">生成输入</param>
|
||||||
/// <param name="operatorId">操作人Id</param>
|
/// <param name="operatorName">操作人名称</param>
|
||||||
/// <returns>二维码记录</returns>
|
/// <returns>二维码记录</returns>
|
||||||
Task<UserJournalQrCodeOutput> CreateQrCodeAsync(CreateUserJournalQrCodeInput input, long operatorId);
|
Task<UserJournalQrCodeOutput> CreateQrCodeAsync(CreateUserJournalQrCodeInput input, string operatorName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 批量提交期刊二维码生成任务
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">生成输入</param>
|
||||||
|
/// <param name="operatorName">操作人名称</param>
|
||||||
|
/// <returns>提交结果</returns>
|
||||||
|
Task<CreateUserJournalQrCodeOutput> CreateQrCodesAsync(CreateUserJournalQrCodeInput input, string operatorName);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询期刊二维码记录
|
/// 分页查询期刊二维码记录
|
||||||
@ -53,11 +61,18 @@ public interface IUserJournalService : IBaseService<UserJournal>
|
|||||||
/// <returns>二维码记录</returns>
|
/// <returns>二维码记录</returns>
|
||||||
Task<UserJournalQrCodeOutput> GetQrCodeDetailAsync(long id);
|
Task<UserJournalQrCodeOutput> GetQrCodeDetailAsync(long id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据期刊Id获取未绑定用户的二维码列表
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalId">期刊Id</param>
|
||||||
|
/// <returns>未绑定二维码列表</returns>
|
||||||
|
Task<List<UserJournalQrCodeOutput>> GetUnboundQrCodesByJournalIdAsync(long journalId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除未绑定的期刊二维码记录
|
/// 删除未绑定的期刊二维码记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">删除输入</param>
|
/// <param name="input">删除输入</param>
|
||||||
/// <param name="operatorId">操作人Id</param>
|
/// <param name="operatorName">操作人名称</param>
|
||||||
/// <returns>是否成功</returns>
|
/// <returns>是否成功</returns>
|
||||||
Task<bool> DeleteQrCodeAsync(DeleteUserJournalQrCodeInput input, long operatorId);
|
Task<bool> DeleteQrCodeAsync(DeleteUserJournalQrCodeInput input, string operatorName);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,11 +117,67 @@ public class CreateUserJournalQrCodeInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public long JournalId { get; set; }
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成数量
|
||||||
|
/// </summary>
|
||||||
|
public int Count { get; set; } = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成期刊二维码提交结果
|
||||||
|
/// </summary>
|
||||||
|
public class CreateUserJournalQrCodeOutput
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 期刊Id
|
||||||
|
/// </summary>
|
||||||
|
public long JournalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求生成数量
|
||||||
|
/// </summary>
|
||||||
|
public int RequestedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已提交生成数量
|
||||||
|
/// </summary>
|
||||||
|
public int AcceptedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 二维码记录Id列表
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RecordIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否后台生成
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAsync { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 提示信息
|
||||||
|
/// </summary>
|
||||||
|
public string Message { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 期刊二维码查询输入DTO
|
/// 期刊二维码查询输入DTO
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public class GenerateUserJournalQrCodeMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 二维码记录Id列表
|
||||||
|
/// </summary>
|
||||||
|
public List<long> RecordIds { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操作人名称
|
||||||
|
/// </summary>
|
||||||
|
public string OperatorName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鏈熷垔浜岀淮鐮佹煡璇㈣緭鍏TO
|
||||||
|
/// </summary>
|
||||||
public class UserJournalQrCodeQueryInput : PageQueryModel
|
public class UserJournalQrCodeQueryInput : PageQueryModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -17,5 +17,17 @@ public enum UserJournalStatusEnum
|
|||||||
/// 正常
|
/// 正常
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Description("正常")]
|
[Description("正常")]
|
||||||
Active = 1
|
Active = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成中
|
||||||
|
/// </summary>
|
||||||
|
[Description("生成中")]
|
||||||
|
Generating = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成失败
|
||||||
|
/// </summary>
|
||||||
|
[Description("生成失败")]
|
||||||
|
Failed = 3
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
using QYZH.InteractiveMagazine.Models.Entity;
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.Context;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Security.Claims;
|
||||||
using Yitter.IdGenerator;
|
using Yitter.IdGenerator;
|
||||||
|
|
||||||
|
|
||||||
@ -117,14 +120,18 @@ namespace QYZH.InteractiveMagazine.Repository.Core
|
|||||||
db.Aop.DataExecuting = (oldValue, entityInfo) =>
|
db.Aop.DataExecuting = (oldValue, entityInfo) =>
|
||||||
{
|
{
|
||||||
var entityValue = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue)?.ToString();
|
var entityValue = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue)?.ToString();
|
||||||
var currnetUserName = "";
|
var currentUserName = GetCurrentUserName();
|
||||||
/*** inset生效 ***/
|
/*** inset生效 ***/
|
||||||
if (entityInfo.OperationType == DataFilterType.InsertByObject)
|
if (entityInfo.OperationType == DataFilterType.InsertByObject)
|
||||||
{
|
{
|
||||||
if (entityInfo.PropertyName == "CreatedAt" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == DateTime.MinValue.ToString()))
|
if (entityInfo.PropertyName == "CreatedAt" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == DateTime.MinValue.ToString()))
|
||||||
entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
|
entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
|
||||||
else if (entityInfo.PropertyName == "CreatedBy" && (string.IsNullOrWhiteSpace(entityValue)))
|
else if (entityInfo.PropertyName == "CreatedBy" && ShouldSetOperatorName(entityValue, currentUserName))
|
||||||
entityInfo.SetValue(currnetUserName);//修改创建人字段
|
entityInfo.SetValue(currentUserName);//修改创建人字段
|
||||||
|
else if (entityInfo.PropertyName == "UpdatedAt" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == DateTime.MinValue.ToString()))
|
||||||
|
entityInfo.SetValue(DateTime.Now);//修改UpdatedTime字段
|
||||||
|
else if (entityInfo.PropertyName == "UpdatedBy" && ShouldSetOperatorName(entityValue, currentUserName))
|
||||||
|
entityInfo.SetValue(currentUserName);//修改更新人字段
|
||||||
else if (entityInfo.PropertyName == "IsDeleted" && string.IsNullOrWhiteSpace(entityValue))
|
else if (entityInfo.PropertyName == "IsDeleted" && string.IsNullOrWhiteSpace(entityValue))
|
||||||
entityInfo.SetValue("0");//修改CreateTime字段
|
entityInfo.SetValue("0");//修改CreateTime字段
|
||||||
}
|
}
|
||||||
@ -134,8 +141,8 @@ namespace QYZH.InteractiveMagazine.Repository.Core
|
|||||||
{
|
{
|
||||||
if (entityInfo.PropertyName == "UpdatedAt" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == DateTime.MinValue.ToString()))
|
if (entityInfo.PropertyName == "UpdatedAt" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == DateTime.MinValue.ToString()))
|
||||||
entityInfo.SetValue(DateTime.Now);//修改UpdatedTime字段
|
entityInfo.SetValue(DateTime.Now);//修改UpdatedTime字段
|
||||||
else if (entityInfo.PropertyName == "UpdatedBy" && (string.IsNullOrWhiteSpace(entityValue) || entityValue == "0"))
|
else if (entityInfo.PropertyName == "UpdatedBy" && ShouldSetOperatorName(entityValue, currentUserName))
|
||||||
entityInfo.SetValue(currnetUserName);//修改更新人字段
|
entityInfo.SetValue(currentUserName);//修改更新人字段
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return db;
|
return db;
|
||||||
@ -143,6 +150,39 @@ namespace QYZH.InteractiveMagazine.Repository.Core
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static string GetCurrentUserName()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var httpContextAccessor = ServiceContext.ServiceProvider?.GetService(typeof(IHttpContextAccessor)) as IHttpContextAccessor;
|
||||||
|
var user = httpContextAccessor?.HttpContext?.User;
|
||||||
|
var userName = user?.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value
|
||||||
|
?? user?.Identity?.Name;
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(userName) ? "System" : userName;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return "System";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldSetOperatorName(string? entityValue, string currentUserName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(entityValue) || entityValue == "0")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentUserName != "System" && long.TryParse(entityValue, out _))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 把一个字符串转成驼峰规则的字符串
|
/// 把一个字符串转成驼峰规则的字符串
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -29,6 +29,9 @@ public class UserJournalService(
|
|||||||
private const string JournalExchange = "ex.journal";
|
private const string JournalExchange = "ex.journal";
|
||||||
private const string BindJournalQueue = "mq.journal.bindUser";
|
private const string BindJournalQueue = "mq.journal.bindUser";
|
||||||
private const string BindJournalRoutingKey = "rk.journal.bindUser";
|
private const string BindJournalRoutingKey = "rk.journal.bindUser";
|
||||||
|
private const string QrCodeGenerateQueue = "mq.journal.qrcode.generate";
|
||||||
|
private const string QrCodeGenerateRoutingKey = "rk.journal.qrcode.generate";
|
||||||
|
private const int MaxBatchQrCodeCount = 500;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户绑定期刊(扫码绑定)
|
/// 用户绑定期刊(扫码绑定)
|
||||||
@ -137,7 +140,7 @@ public class UserJournalService(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成期刊二维码记录
|
/// 生成期刊二维码记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<UserJournalQrCodeOutput> CreateQrCodeAsync(CreateUserJournalQrCodeInput input, long operatorId)
|
public async Task<UserJournalQrCodeOutput> CreateQrCodeAsync(CreateUserJournalQrCodeInput input, string operatorName)
|
||||||
{
|
{
|
||||||
if (input.JournalId <= 0)
|
if (input.JournalId <= 0)
|
||||||
{
|
{
|
||||||
@ -162,9 +165,9 @@ public class UserJournalService(
|
|||||||
Type = 0,
|
Type = 0,
|
||||||
Status = (int)UserJournalStatusEnum.Active,
|
Status = (int)UserJournalStatusEnum.Active,
|
||||||
IsDeleted = false,
|
IsDeleted = false,
|
||||||
CreatedBy = operatorId.ToString(),
|
CreatedBy = operatorName,
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.Now,
|
||||||
UpdatedBy = operatorId.ToString(),
|
UpdatedBy = operatorName,
|
||||||
UpdatedAt = DateTime.Now
|
UpdatedAt = DateTime.Now
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -191,6 +194,88 @@ public class UserJournalService(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 分页查询期刊二维码记录
|
/// 分页查询期刊二维码记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
public async Task<CreateUserJournalQrCodeOutput> CreateQrCodesAsync(CreateUserJournalQrCodeInput input, string operatorName)
|
||||||
|
{
|
||||||
|
if (input.JournalId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException("期刊Id不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Count <= 0 || input.Count > MaxBatchQrCodeCount)
|
||||||
|
{
|
||||||
|
throw new BusinessException($"生成数量必须在1-{MaxBatchQrCodeCount}之间", ResultCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
var journal = await journalRepository.GetByIdAsync(input.JournalId);
|
||||||
|
if (journal == null || journal.IsDeleted)
|
||||||
|
{
|
||||||
|
throw new BusinessException("期刊不存在", ResultCode.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (journal.Status != (int)JournalStatusEnum.Published)
|
||||||
|
{
|
||||||
|
throw new BusinessException("该期刊暂未发布,无法生成二维码", ResultCode.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var records = Enumerable.Range(0, input.Count)
|
||||||
|
.Select(_ => new UserJournal
|
||||||
|
{
|
||||||
|
UserId = null,
|
||||||
|
JournalId = input.JournalId,
|
||||||
|
Type = 0,
|
||||||
|
Status = (int)UserJournalStatusEnum.Generating,
|
||||||
|
IsDeleted = false,
|
||||||
|
CreatedBy = operatorName,
|
||||||
|
CreatedAt = now,
|
||||||
|
UpdatedBy = operatorName,
|
||||||
|
UpdatedAt = now
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var insertCount = await userJournalRepository.Context.Insertable(records).ExecuteCommandAsync();
|
||||||
|
if (insertCount <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException("提交二维码生成任务失败,请稍后重试", ResultCode.GLOBAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
var recordIds = records.Select(r => r.Id).ToList();
|
||||||
|
var messageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam
|
||||||
|
{
|
||||||
|
Exchange = JournalExchange,
|
||||||
|
Queue = QrCodeGenerateQueue,
|
||||||
|
RoutingKey = QrCodeGenerateRoutingKey,
|
||||||
|
Data = new GenerateUserJournalQrCodeMessage
|
||||||
|
{
|
||||||
|
RecordIds = recordIds,
|
||||||
|
OperatorName = operatorName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!messageSent)
|
||||||
|
{
|
||||||
|
await userJournalRepository.Updateable()
|
||||||
|
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Failed)
|
||||||
|
.SetColumns(uj => uj.UpdatedBy == operatorName)
|
||||||
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
|
.Where(uj => recordIds.Contains(uj.Id) && !uj.IsDeleted && uj.Status == (int)UserJournalStatusEnum.Generating)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
|
||||||
|
logger.LogError("发送期刊二维码生成消息失败,RecordIds: {RecordIds}", string.Join(",", recordIds));
|
||||||
|
throw new BusinessException("二维码生成任务提交失败,请稍后重试", ResultCode.GLOBAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CreateUserJournalQrCodeOutput
|
||||||
|
{
|
||||||
|
JournalId = input.JournalId,
|
||||||
|
RequestedCount = input.Count,
|
||||||
|
AcceptedCount = insertCount,
|
||||||
|
RecordIds = recordIds,
|
||||||
|
IsAsync = true,
|
||||||
|
Message = "二维码生成任务已提交,请稍后查询未绑定二维码列表"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PageListModel<UserJournalQrCodeOutput>> GetQrCodePageListAsync(UserJournalQrCodeQueryInput input)
|
public async Task<PageListModel<UserJournalQrCodeOutput>> GetQrCodePageListAsync(UserJournalQrCodeQueryInput input)
|
||||||
{
|
{
|
||||||
if (input.PageIndex <= 0)
|
if (input.PageIndex <= 0)
|
||||||
@ -237,7 +322,7 @@ public class UserJournalService(
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 删除未绑定的期刊二维码记录
|
/// 删除未绑定的期刊二维码记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> DeleteQrCodeAsync(DeleteUserJournalQrCodeInput input, long operatorId)
|
public async Task<bool> DeleteQrCodeAsync(DeleteUserJournalQrCodeInput input, string operatorName)
|
||||||
{
|
{
|
||||||
if (input.Ids == null || input.Ids.Count == 0)
|
if (input.Ids == null || input.Ids.Count == 0)
|
||||||
{
|
{
|
||||||
@ -262,7 +347,7 @@ public class UserJournalService(
|
|||||||
var updateCount = await userJournalRepository.Updateable()
|
var updateCount = await userJournalRepository.Updateable()
|
||||||
.SetColumns(uj => uj.IsDeleted == true)
|
.SetColumns(uj => uj.IsDeleted == true)
|
||||||
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Inactive)
|
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Inactive)
|
||||||
.SetColumns(uj => uj.UpdatedBy == operatorId.ToString())
|
.SetColumns(uj => uj.UpdatedBy == operatorName)
|
||||||
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
.Where(uj => ids.Contains(uj.Id) && !uj.IsDeleted && (uj.UserId == null || uj.UserId == 0))
|
.Where(uj => ids.Contains(uj.Id) && !uj.IsDeleted && (uj.UserId == null || uj.UserId == 0))
|
||||||
.ExecuteCommandAsync();
|
.ExecuteCommandAsync();
|
||||||
@ -270,6 +355,24 @@ public class UserJournalService(
|
|||||||
return updateCount == ids.Count;
|
return updateCount == ids.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<UserJournalQrCodeOutput>> GetUnboundQrCodesByJournalIdAsync(long journalId)
|
||||||
|
{
|
||||||
|
if (journalId <= 0)
|
||||||
|
{
|
||||||
|
throw new BusinessException("期刊Id不能为空", ResultCode.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
var records = await userJournalRepository.Queryable()
|
||||||
|
.Where(uj => uj.JournalId == journalId && !uj.IsDeleted)
|
||||||
|
.Where(uj => uj.UserId == null || uj.UserId == 0)
|
||||||
|
.Where(uj => uj.Status == (int)UserJournalStatusEnum.Active)
|
||||||
|
.Where(uj => !string.IsNullOrEmpty(uj.QrCodeUrl))
|
||||||
|
.OrderByDescending(uj => uj.CreatedAt)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return await BuildQrCodeOutputsAsync(records);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<List<UserJournalQrCodeOutput>> BuildQrCodeOutputsAsync(List<UserJournal> records)
|
private async Task<List<UserJournalQrCodeOutput>> BuildQrCodeOutputsAsync(List<UserJournal> records)
|
||||||
{
|
{
|
||||||
if (records.Count == 0)
|
if (records.Count == 0)
|
||||||
|
|||||||
@ -19,10 +19,22 @@ public class UserJournalQrCodeController(IUserJournalService userJournalService)
|
|||||||
/// <param name="input">生成输入</param>
|
/// <param name="input">生成输入</param>
|
||||||
/// <returns>二维码记录</returns>
|
/// <returns>二维码记录</returns>
|
||||||
[HttpPost("add")]
|
[HttpPost("add")]
|
||||||
public async Task<BaseResponse<UserJournalQrCodeOutput>> AddAsync([FromBody] CreateUserJournalQrCodeInput input)
|
public async Task<BaseResponse<CreateUserJournalQrCodeOutput>> AddAsync([FromBody] CreateUserJournalQrCodeInput input)
|
||||||
{
|
{
|
||||||
var result = await userJournalService.CreateQrCodeAsync(input, GetCurrentUserId() ?? 0);
|
var result = await userJournalService.CreateQrCodesAsync(input, GetCurrentUserName() ?? "System");
|
||||||
return BaseResponse<UserJournalQrCodeOutput>.Success(result);
|
return BaseResponse<CreateUserJournalQrCodeOutput>.Success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据期刊Id获取未绑定用户的二维码地址
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="journalId">期刊Id</param>
|
||||||
|
/// <returns>未绑定二维码列表</returns>
|
||||||
|
[HttpGet("unbound/{journalId:long}")]
|
||||||
|
public async Task<BaseResponse<List<UserJournalQrCodeOutput>>> GetUnboundByJournalIdAsync(long journalId)
|
||||||
|
{
|
||||||
|
var result = await userJournalService.GetUnboundQrCodesByJournalIdAsync(journalId);
|
||||||
|
return BaseResponse<List<UserJournalQrCodeOutput>>.Success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -57,7 +69,7 @@ public class UserJournalQrCodeController(IUserJournalService userJournalService)
|
|||||||
[HttpPost("delete")]
|
[HttpPost("delete")]
|
||||||
public async Task<BaseResponse<bool>> DeleteAsync([FromBody] DeleteUserJournalQrCodeInput input)
|
public async Task<BaseResponse<bool>> DeleteAsync([FromBody] DeleteUserJournalQrCodeInput input)
|
||||||
{
|
{
|
||||||
var result = await userJournalService.DeleteQrCodeAsync(input, GetCurrentUserId() ?? 0);
|
var result = await userJournalService.DeleteQrCodeAsync(input, GetCurrentUserName() ?? "System");
|
||||||
return BaseResponse<bool>.Success(result);
|
return BaseResponse<bool>.Success(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,129 @@
|
|||||||
|
using QYZH.InteractiveMagazine.Common.Helpers;
|
||||||
|
using QYZH.InteractiveMagazine.Infrastructure.OSS;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Common;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Dto;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Entity;
|
||||||
|
using QYZH.InteractiveMagazine.Models.Enum;
|
||||||
|
using SqlSugar;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace QYZH.InteractiveMagazine.WorkService.Consumers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 期刊二维码生成消费者
|
||||||
|
/// </summary>
|
||||||
|
public class UserJournalQrCodeGenerateConsumer(
|
||||||
|
ILogger<UserJournalQrCodeGenerateConsumer> logger,
|
||||||
|
IServiceScopeFactory scopeFactory,
|
||||||
|
OssService ossService) : IQueueConsumer
|
||||||
|
{
|
||||||
|
public string Exchange => "ex.journal";
|
||||||
|
|
||||||
|
public string QueueName => "mq.journal.qrcode.generate";
|
||||||
|
|
||||||
|
public string RoutingKey => "rk.journal.qrcode.generate";
|
||||||
|
|
||||||
|
public async Task HandleAsync(byte[] message, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var content = Encoding.UTF8.GetString(message);
|
||||||
|
logger.LogInformation("收到期刊二维码生成消息: {Message}", content);
|
||||||
|
|
||||||
|
var data = JsonSerializer.Deserialize<GenerateUserJournalQrCodeMessage>(content, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
}) ?? throw new InvalidOperationException("期刊二维码生成消息为空");
|
||||||
|
|
||||||
|
if (data.RecordIds.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarning("期刊二维码生成消息缺少记录Id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = scopeFactory.CreateScope();
|
||||||
|
var client = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
|
||||||
|
var operatorName = string.IsNullOrWhiteSpace(data.OperatorName) ? "System" : data.OperatorName;
|
||||||
|
|
||||||
|
foreach (var recordId in data.RecordIds.Distinct())
|
||||||
|
{
|
||||||
|
await GenerateQrCodeImageAsync(client, recordId, operatorName, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnErrorAsync(byte[] message, Exception exception)
|
||||||
|
{
|
||||||
|
logger.LogError(exception, "处理期刊二维码生成消息失败: {Message}", Encoding.UTF8.GetString(message));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = JsonSerializer.Deserialize<GenerateUserJournalQrCodeMessage>(Encoding.UTF8.GetString(message), new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.RecordIds.Count > 0)
|
||||||
|
{
|
||||||
|
using var scope = scopeFactory.CreateScope();
|
||||||
|
var client = scope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
|
||||||
|
var operatorName = string.IsNullOrWhiteSpace(data.OperatorName) ? "System" : data.OperatorName;
|
||||||
|
await client.Updateable<UserJournal>()
|
||||||
|
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Failed)
|
||||||
|
.SetColumns(uj => uj.UpdatedBy == operatorName)
|
||||||
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
|
.Where(uj => data.RecordIds.Contains(uj.Id) && !uj.IsDeleted && uj.Status == (int)UserJournalStatusEnum.Generating)
|
||||||
|
.ExecuteCommandAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "标记期刊二维码生成失败状态异常");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GenerateQrCodeImageAsync(ISqlSugarClient client, long recordId, string operatorName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var record = await client.Queryable<UserJournal>()
|
||||||
|
.Where(uj => uj.Id == recordId && !uj.IsDeleted)
|
||||||
|
.FirstAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (record == null || record.Status != (int)UserJournalStatusEnum.Generating)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var qrCodeContent = BuildQrCodeContent(record.JournalId, record.Id);
|
||||||
|
var qrCodeKey = $"journal/qrcode/{record.JournalId}/{record.Id}.png";
|
||||||
|
using var qrCodeStream = new MemoryStream(QrCodeHelper.GeneratePng(qrCodeContent));
|
||||||
|
var uploadedKey = ossService.PutObject(qrCodeKey, qrCodeStream);
|
||||||
|
if (string.IsNullOrWhiteSpace(uploadedKey))
|
||||||
|
{
|
||||||
|
throw new BusinessException("二维码图片上传失败", ResultCode.GLOBAL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.Updateable<UserJournal>()
|
||||||
|
.SetColumns(uj => uj.QrCodeUrl == uploadedKey)
|
||||||
|
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Active)
|
||||||
|
.SetColumns(uj => uj.UpdatedBy == operatorName)
|
||||||
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
|
.Where(uj => uj.Id == recordId && !uj.IsDeleted && uj.Status == (int)UserJournalStatusEnum.Generating)
|
||||||
|
.ExecuteCommandAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "生成期刊二维码失败,RecordId: {RecordId}", recordId);
|
||||||
|
await client.Updateable<UserJournal>()
|
||||||
|
.SetColumns(uj => uj.Status == (int)UserJournalStatusEnum.Failed)
|
||||||
|
.SetColumns(uj => uj.UpdatedBy == operatorName)
|
||||||
|
.SetColumns(uj => uj.UpdatedAt == DateTime.Now)
|
||||||
|
.Where(uj => uj.Id == recordId && !uj.IsDeleted && uj.Status == (int)UserJournalStatusEnum.Generating)
|
||||||
|
.ExecuteCommandAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildQrCodeContent(long journalId, long id)
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(new { JournalId = journalId, Id = id });
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -77,6 +77,7 @@ builder.Services.AddRabbitMQ(builder.Configuration);
|
|||||||
|
|
||||||
// 注册队列消费者(新增消费者只需实现 IQueueConsumer 并在此注册)
|
// 注册队列消费者(新增消费者只需实现 IQueueConsumer 并在此注册)
|
||||||
builder.Services.AddScoped<IQueueConsumer, JournalTaskReceiveConsumer>();
|
builder.Services.AddScoped<IQueueConsumer, JournalTaskReceiveConsumer>();
|
||||||
|
builder.Services.AddScoped<IQueueConsumer, UserJournalQrCodeGenerateConsumer>();
|
||||||
|
|
||||||
// 注册消费者后台服务
|
// 注册消费者后台服务
|
||||||
builder.Services.AddHostedService<RabbitMQHostedService>();
|
builder.Services.AddHostedService<RabbitMQHostedService>();
|
||||||
|
|||||||
Reference in New Issue
Block a user