From eb4f09e3912cced5f268cc3b6acf1cdc37b209e4 Mon Sep 17 00:00:00 2001 From: glz <694770232@qq.com> Date: Wed, 1 Jul 2026 10:56:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=89=93=E5=8D=B0?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=9C=8D=E5=8A=A1=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 调整回调响应的Code字段类型为object以兼容更多返回值 2. 为JournalDto新增Url和QuestionNo属性 3. 为JournalPageTaskOutput新增Task字段并完善注释 4. 新增打印工作服务的发布、安装卸载脚本与README文档 5. 调整项目配置,添加运行时标识与资源拷贝配置 6. 优化程序启动逻辑,修复工作目录与日志路径问题 7. 简化日志配置,移除多余的Hangfire与AI配置项 8. 完善期刊删除逻辑,关联删除相关子数据 9. 新增期刊发布时的书页任务排序与回调地址补全逻辑 --- .../Dto/Journal/JournalDto.cs | 2 + .../Dto/Journal/JournalPageTaskOutput.cs | 5 ++- .../Consumers/AutoDotCodeConsumer.cs | 2 +- .../Program.cs | 19 ++++++-- ...YZH.InteractiveMagazine.PrintWorker.csproj | 8 ++++ .../README.md | 30 +++++++++++++ .../appsettings.json | 44 ++---------------- .../scripts/install-service.ps1 | 35 +++++++++++++++ .../scripts/publish-win-x64.ps1 | 28 ++++++++++++ .../scripts/uninstall-service.ps1 | 23 ++++++++++ .../JournalPageTaskService.cs | 1 + .../JournalService.cs | 45 +++++++++++++++++-- 12 files changed, 193 insertions(+), 49 deletions(-) create mode 100644 QYZH.InteractiveMagazine.PrintWorker/README.md create mode 100644 QYZH.InteractiveMagazine.PrintWorker/scripts/install-service.ps1 create mode 100644 QYZH.InteractiveMagazine.PrintWorker/scripts/publish-win-x64.ps1 create mode 100644 QYZH.InteractiveMagazine.PrintWorker/scripts/uninstall-service.ps1 diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs index 31329c7..a99fa83 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalDto.cs @@ -146,7 +146,9 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal { public long BookId { get; set; } public long PageId { get; set; } + public string Url { get; set; } public string? PageNo { get; set; } public string? Layout { get; set; } + public string QuestionNo { get; set; } } } diff --git a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs index 44f11b6..be24c64 100644 --- a/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs +++ b/QYZH.InteractiveMagazine.Models/Dto/Journal/JournalPageTaskOutput.cs @@ -18,7 +18,10 @@ namespace QYZH.InteractiveMagazine.Models.Dto.Journal /// 书页Id /// public long JournalPageId { get; set; } - + /// + /// 任务信息 + /// + public string Task { get; set; } /// /// 题号 /// diff --git a/QYZH.InteractiveMagazine.PrintWorker/Consumers/AutoDotCodeConsumer.cs b/QYZH.InteractiveMagazine.PrintWorker/Consumers/AutoDotCodeConsumer.cs index 5580094..cead891 100644 --- a/QYZH.InteractiveMagazine.PrintWorker/Consumers/AutoDotCodeConsumer.cs +++ b/QYZH.InteractiveMagazine.PrintWorker/Consumers/AutoDotCodeConsumer.cs @@ -330,7 +330,7 @@ internal class CallbackUpdateJournalStatusResponse { public string? Message { get; set; } - public string? Code { get; set; } + public object? Code { get; set; } public bool Result { get; set; } diff --git a/QYZH.InteractiveMagazine.PrintWorker/Program.cs b/QYZH.InteractiveMagazine.PrintWorker/Program.cs index 0694b35..9f9ed11 100644 --- a/QYZH.InteractiveMagazine.PrintWorker/Program.cs +++ b/QYZH.InteractiveMagazine.PrintWorker/Program.cs @@ -5,21 +5,34 @@ using QYZH.InteractiveMagazine.PrintWorker.Consumers; using Serilog; using SqlSugar; using SqlSugar.IOC; +using System.Text; using Yitter.IdGenerator; -var builder = Host.CreateApplicationBuilder(args); +Directory.SetCurrentDirectory(AppContext.BaseDirectory); + +var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings +{ + Args = args, + ContentRootPath = AppContext.BaseDirectory +}); +const string serviceName = "QYZH.InteractiveMagazine.PrintWorker"; builder.Configuration .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); +var logDirectory = Path.Combine(AppContext.BaseDirectory, "logs"); +Directory.CreateDirectory(logDirectory); +var logFilePath = Path.Combine(logDirectory, "printworker-log-.txt"); + Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(builder.Configuration) + .WriteTo.File(logFilePath, rollingInterval: RollingInterval.Day, encoding: Encoding.UTF8) .Enrich.FromLogContext() .CreateLogger(); builder.Services.AddSerilog(); -builder.Services.AddWindowsService(options => options.ServiceName = "QYZH InteractiveMagazine PrintWorker"); +builder.Services.AddWindowsService(options => options.ServiceName = serviceName); YitIdHelper.SetIdGenerator(new IdGeneratorOptions { WorkerId = 3 }); @@ -54,6 +67,6 @@ builder.Services.AddHostedService(); var app = builder.Build(); -Log.Information("PrintWorker 已启动,仅消费自动铺码队列"); +Log.Information("{ServiceName} 已启动,仅消费自动铺码队列", serviceName); await app.RunAsync(); diff --git a/QYZH.InteractiveMagazine.PrintWorker/QYZH.InteractiveMagazine.PrintWorker.csproj b/QYZH.InteractiveMagazine.PrintWorker/QYZH.InteractiveMagazine.PrintWorker.csproj index 741c9bd..3be3c98 100644 --- a/QYZH.InteractiveMagazine.PrintWorker/QYZH.InteractiveMagazine.PrintWorker.csproj +++ b/QYZH.InteractiveMagazine.PrintWorker/QYZH.InteractiveMagazine.PrintWorker.csproj @@ -1,7 +1,9 @@ + Exe net8.0 + win-x64 enable enable @@ -28,6 +30,12 @@ Always + + Always + + + Always + diff --git a/QYZH.InteractiveMagazine.PrintWorker/README.md b/QYZH.InteractiveMagazine.PrintWorker/README.md new file mode 100644 index 0000000..afa8b19 --- /dev/null +++ b/QYZH.InteractiveMagazine.PrintWorker/README.md @@ -0,0 +1,30 @@ +# PrintWorker Windows Service + +## 发布 + +```powershell +cd C:\项目\QYZH.InteractiveMagazine\QYZH.InteractiveMagazine.PrintWorker +.\scripts\publish-win-x64.ps1 +``` + +发布目录默认是 `.\publish\win-x64`,其中包含 `PrintToolV2.7`、`appsettings.json` 和 `QYZH.InteractiveMagazine.PrintWorker.exe`。 + +## 安装服务 + +用管理员 PowerShell 执行: + +```powershell +cd C:\Web\QYZH.InteractiveMagazine.PrintWorker +.\scripts\install-service.ps1 +``` + +默认服务名:`QYZH.InteractiveMagazine.PrintWorker`。 + +## 卸载服务 + +用管理员 PowerShell 执行: + +```powershell +cd C:\项目\QYZH.InteractiveMagazine\QYZH.InteractiveMagazine.PrintWorker +.\scripts\uninstall-service.ps1 +``` diff --git a/QYZH.InteractiveMagazine.PrintWorker/appsettings.json b/QYZH.InteractiveMagazine.PrintWorker/appsettings.json index f571c74..028a828 100644 --- a/QYZH.InteractiveMagazine.PrintWorker/appsettings.json +++ b/QYZH.InteractiveMagazine.PrintWorker/appsettings.json @@ -19,48 +19,15 @@ "Default": "Information", "Override": { "Microsoft": "Warning", - "System": "Warning", - "Hangfire": "Information" + "System": "Warning" } }, "WriteTo": [ { "Name": "Console" - }, - { - "Name": "File", - "Args": { - "path": "logs/worker-log-.txt", - "rollingInterval": "Day" - } } ] }, - "HangfireJobs": { - "_说明": "定时任务配置。新增任务只需在 Jobs 数组中追加一项。Cron格式:分 时 日 月 星期(5位)。常用:* * * * * 每分钟 | */5 * * * * 每5分钟 | 0 * * * * 每小时 | 0 9 * * * 每天9点 | 0 0 * * 1 每周一午夜", - "Jobs": [ - { - "Name": "sample-job", - "JobType": "QYZH.InteractiveMagazine.WorkService.Jobs.SampleJob", - "MethodName": "ExecuteAsync", - "Cron": "* * * * *", - "Enabled": false, - "Description": "示例任务(默认关闭,仅用于验证框架运行)" - } - ] - }, - "AiChat": { - "ApiKey": "Ollama", - "BaseUrl": "http://172.16.10.130:11434/v1/", - "Model": "qwen2.5vl:7b", - "TimeoutSeconds": 300, - "MaxTokens": 2000, - "Temperature": 0.5, - "ScoreMaxRetryCount": 3, - "ScoreRetryDelayMilliseconds": 1000, - "MaxImageBytes": 10485760 - }, - "AllowedHosts": "*", "AliyunOSSConfigs": { "AccessKeyID": "LTAI5tEBXGewpHSLiSxyx6Bf", "AccessKeySecret": "w29b8wkw6XQVL8GWXgp3ZesgYeDKvf", @@ -68,18 +35,15 @@ "BucketName": "qyzh2025test", "Region": "beijing", "RoleArn": "acs:ram::1064745380176636:role/aliyunosstokengeneratorrole", - "DurationSeconds": 3600, //过期时间(秒) + "DurationSeconds": 3600, "Endpoint": "oss-cn-beijing.aliyuncs.com", "ProjectName": "InteractiveMagazine", "Domain": "http://oss-test.qyzhjy.com/" }, "PrintConfig": { "TimeoutSeconds": 300, - "DPrint": 0, //打印场景,0:普通激光打印机,1:工业印刷,默认为0(可选) - "CallBackApiUrl": "http://localhost:8080/api/page/callbackupdatepageno", // 开发环境 打印成功回调API地址修改打印状态 - //"CallBackApiUrl": "http://192.168.20.150:8000/api/icr/page/callbackupdatepageno", // 测试环境 打印成功回调API地址修改打印状态 - //"CallBackApiUrl": "https://zyb.qyzhjy.com/zybback/api/icr/page/callbackupdatepageno", // 正式环境 打印成功回调API地址修改打印状态 - //这个ID执行的xml文件是:sublic_license_1761.172.8.16_1000.xml,如果想执行sublic_license_1761.211.21.48_10000.xml 换成 765837655859269 ---暂时没有导入页码 + "DPrint": 0, + "CallBackApiUrl": "http://localhost:8080/api/page/callbackupdatepageno", "DotId": 765837655859269 } } diff --git a/QYZH.InteractiveMagazine.PrintWorker/scripts/install-service.ps1 b/QYZH.InteractiveMagazine.PrintWorker/scripts/install-service.ps1 new file mode 100644 index 0000000..5f3727f --- /dev/null +++ b/QYZH.InteractiveMagazine.PrintWorker/scripts/install-service.ps1 @@ -0,0 +1,35 @@ +param( + [string]$ServiceName = "QYZH.InteractiveMagazine.PrintWorker", + [string]$DisplayName = "QYZH InteractiveMagazine PrintWorker", + [string]$Description = "InteractiveMagazine automatic dot-code print worker.", + [string]$PublishPath = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +) + +$ErrorActionPreference = "Stop" + +if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + throw "Please run this script in an elevated PowerShell window." +} + +$exePath = Resolve-Path (Join-Path $PublishPath "QYZH.InteractiveMagazine.PrintWorker.exe") +$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue + +if ($existing) { + if ($existing.Status -ne "Stopped") { + Stop-Service -Name $ServiceName -Force + $existing.WaitForStatus("Stopped", "00:00:30") + } + + sc.exe delete $ServiceName | Out-Null + Start-Sleep -Seconds 2 +} + +New-Service ` + -Name $ServiceName ` + -BinaryPathName "`"$exePath`"" ` + -DisplayName $DisplayName ` + -Description $Description ` + -StartupType Automatic + +Start-Service -Name $ServiceName +Get-Service -Name $ServiceName diff --git a/QYZH.InteractiveMagazine.PrintWorker/scripts/publish-win-x64.ps1 b/QYZH.InteractiveMagazine.PrintWorker/scripts/publish-win-x64.ps1 new file mode 100644 index 0000000..2398b4c --- /dev/null +++ b/QYZH.InteractiveMagazine.PrintWorker/scripts/publish-win-x64.ps1 @@ -0,0 +1,28 @@ +param( + [string]$Configuration = "Release", + [string]$Output = ".\publish\win-x64", + [switch]$SelfContained +) + +$ErrorActionPreference = "Stop" + +$projectPath = Join-Path $PSScriptRoot "..\QYZH.InteractiveMagazine.PrintWorker.csproj" +$publishArgs = @( + "publish", + $projectPath, + "-c", $Configuration, + "-r", "win-x64", + "-o", $Output +) + +if ($SelfContained) { + $publishArgs += "--self-contained" + $publishArgs += "true" +} else { + $publishArgs += "--self-contained" + $publishArgs += "false" +} + +dotnet @publishArgs + +Write-Host "PrintWorker published to: $Output" diff --git a/QYZH.InteractiveMagazine.PrintWorker/scripts/uninstall-service.ps1 b/QYZH.InteractiveMagazine.PrintWorker/scripts/uninstall-service.ps1 new file mode 100644 index 0000000..a10f4f1 --- /dev/null +++ b/QYZH.InteractiveMagazine.PrintWorker/scripts/uninstall-service.ps1 @@ -0,0 +1,23 @@ +param( + [string]$ServiceName = "QYZH.InteractiveMagazine.PrintWorker" +) + +$ErrorActionPreference = "Stop" + +if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + throw "Please run this script in an elevated PowerShell window." +} + +$existing = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if (-not $existing) { + Write-Host "Service not found: $ServiceName" + return +} + +if ($existing.Status -ne "Stopped") { + Stop-Service -Name $ServiceName -Force + $existing.WaitForStatus("Stopped", "00:00:30") +} + +sc.exe delete $ServiceName | Out-Null +Write-Host "Service deleted: $ServiceName" diff --git a/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs b/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs index de9c047..9ec041c 100644 --- a/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs +++ b/QYZH.InteractiveMagazine.Service/JournalPageTaskService.cs @@ -97,6 +97,7 @@ public class JournalPageTaskService(BaseRepository journalRepository, O JournalId = task.JournalId, JournalPageId = task.JournalPageId, No = task.No, + Task =task.Task, Type = task.Type, Points = task.Points, GrowthPoint = task.GrowthPoint, diff --git a/QYZH.InteractiveMagazine.Service/JournalService.cs b/QYZH.InteractiveMagazine.Service/JournalService.cs index 6194492..32fbea3 100644 --- a/QYZH.InteractiveMagazine.Service/JournalService.cs +++ b/QYZH.InteractiveMagazine.Service/JournalService.cs @@ -1,6 +1,7 @@ using Mapster; using Microsoft.Extensions.Logging; using QYZH.InteractiveMagazine.Common.Extensions; +using QYZH.InteractiveMagazine.Common.Helpers; using QYZH.InteractiveMagazine.Infrastructure.OSS; using QYZH.InteractiveMagazine.Infrastructure.RabbitMQ; using QYZH.InteractiveMagazine.IService; @@ -225,19 +226,19 @@ public class JournalService(BaseRepository JournalPageRepository, await base.DeleteAsync(d => ids.Contains(d.Id)); await JournalPageRepository.DeleteAsync(d => ids.Contains(d.JournalId)); await JournalCatalogRepository.DeleteAsync(d => ids.Contains(d.JournalId)); - + // 先查询要删除的 JournalPageTask Id 列表 var taskIds = await JournalPageTaskRepository.Queryable() .Where(d => ids.Contains(d.JournalId)) .Select(d => d.Id) .ToListAsync(); - + // 删除 JournalPageTaskAnswer if (taskIds.Any()) { await JournalPageTaskAnswerRepository.DeleteAsync(d => taskIds.Contains(d.JournalPageTaskId)); } - + // 再删除 JournalPageTask await JournalPageTaskRepository.DeleteAsync(d => ids.Contains(d.JournalId)); return true; @@ -344,6 +345,13 @@ public class JournalService(BaseRepository JournalPageRepository, .Where(x => x.JournalId == id && !x.IsDeleted) .OrderBy(x => x.PageNum) .OrderBy(x => x.Sort) + .Select(x => new + { + x.Id, + x.PageNo, + x.Layout, + x.Url + }) .ToListAsync(); BusinessException.ThrowIf(pages.Count == 0, "书籍未添加任何书页,无法发布", ResultCode.UNPROCESSABLE_ENTITY); @@ -351,6 +359,22 @@ public class JournalService(BaseRepository JournalPageRepository, BusinessException.ThrowIf(!book.EndTime.HasValue, "发布结束时间不能为空", ResultCode.UNPROCESSABLE_ENTITY); BusinessException.ThrowIf(book.EndTime < book.StartTime, "发布结束时间不能早于开始时间", ResultCode.UNPROCESSABLE_ENTITY); + var pageIds = pages.Select(x => x.Id).ToList(); + var pageQuestions = await JournalPageTaskRepository.Queryable() + .Where(x => x.JournalId == id && pageIds.Contains(x.JournalPageId) && !x.IsDeleted) + .Select(x => new + { + x.JournalPageId, + x.Id, + x.No + }) + .ToListAsync(); + var questionIdsByPageId = pageQuestions + .GroupBy(x => x.JournalPageId) + .ToDictionary( + x => x.Key, + x => string.Join(',', x.OrderBy(q => ParseTaskNo(q.No)).Select(q => q.Id))); + var bookMessageSent = await rabbitMqService.SendAsync(new RabbitMQSendParam { Exchange = JournalExchange, @@ -377,7 +401,9 @@ public class JournalService(BaseRepository JournalPageRepository, BookId = id, PageId = page.Id, PageNo = page.PageNo, - Layout = page.Layout + Layout = page.Layout, + Url = DomainHelper.OssFullUrl(page.Url), + QuestionNo = questionIdsByPageId.GetValueOrDefault(page.Id) ?? string.Empty } }); BusinessException.ThrowIf(!pageMessageSent, $"发布书页消息发送失败,PageId: {page.Id}", ResultCode.GLOBAL_ERROR); @@ -388,5 +414,16 @@ public class JournalService(BaseRepository JournalPageRepository, .SetColumns(s => s.UpdatedAt, DateTime.Now) .Where(w => w.Id == id) .ExecuteCommandAsync() > 0; + + static (int First, int Second, int Third, int Fourth) ParseTaskNo(string? no) + { + var parts = no?.Split('-') ?? Array.Empty(); + return (GetNoPart(parts, 0), GetNoPart(parts, 1), GetNoPart(parts, 2), GetNoPart(parts, 3)); + } + + static int GetNoPart(string[] parts, int index) + { + return parts.Length > index && int.TryParse(parts[index], out var value) ? value : int.MaxValue; + } } }