上传源代码版本

This commit is contained in:
Im-Jenisson
2026-06-01 16:30:29 +08:00
commit b2a9b7d3c2
462 changed files with 104365 additions and 0 deletions

557
定时任务暂停指南.md Normal file
View File

@@ -0,0 +1,557 @@
# 定时任务暂停指南
## 概述
项目中的PDF标签缓存定时任务是通过 `LabelPdfCacheBackgroundService` 实现的,它是一个 ASP.NET Core `BackgroundService`每5分钟执行一次。
---
## 定时任务信息
### 基本参数
- **服务类**: `LabelPdfCacheBackgroundService`
- **执行间隔**: 5分钟
- **功能**: 处理待处理的PDF缓存任务
- **执行方法**: `ProcessPendingTasksAsync()`
### 当前工作流程
1. 每5分钟检查一次待处理任务
2. 获取失效的缓存、未处理的任务、新订单
3. 批量处理这些任务
4. 同步保存PDF缓存异步识别条码
---
## 暂停定时任务的方法
### 方法1修改配置文件推荐用于生产环境
#### 步骤1修改 appsettings.json
`appsettings.json``appsettings.Production.json` 中添加一个配置开关:
```json
{
"BackgroundServices": {
"LabelPdfCacheServiceEnabled": false
}
}
```
#### 步骤2修改 Program.cs
修改Program.cs中的注册代码
```csharp
// 从这样:
builder.Services.AddHostedService<CONTROLLER.BackgroundServices.LabelPdfCacheBackgroundService>();
// 改为:
var enableLabelPdfCache = builder.Configuration.GetValue<bool>("BackgroundServices:LabelPdfCacheServiceEnabled", true);
if (enableLabelPdfCache)
{
builder.Services.AddHostedService<CONTROLLER.BackgroundServices.LabelPdfCacheBackgroundService>();
}
```
#### 优点
- 无需重新编译代码
- 支持配置热更新
- 适合生产环境
#### 缺点
- 需要修改两个文件
- 需要重启应用
---
### 方法2使用环境变量
#### 步骤1修改 Program.cs
```csharp
var enableLabelPdfCache =
!string.Equals(
Environment.GetEnvironmentVariable("DISABLE_LABEL_PDF_CACHE"),
"true",
StringComparison.OrdinalIgnoreCase);
if (enableLabelPdfCache)
{
builder.Services.AddHostedService<CONTROLLER.BackgroundServices.LabelPdfCacheBackgroundService>();
}
```
#### 步骤2设置环境变量
**Windows命令行**
```bash
set DISABLE_LABEL_PDF_CACHE=true
```
**Linux/Mac**
```bash
export DISABLE_LABEL_PDF_CACHE=true
```
**Docker**
```dockerfile
ENV DISABLE_LABEL_PDF_CACHE=true
```
#### 优点
- 不需要修改配置文件
- 容易在Docker容器中配置
- 支持运行时切换
#### 缺点
- 需要修改代码
- 需要重启应用
---
### 方法3创建暂停/恢复接口(最灵活)
#### 步骤1创建定时任务管理类
创建文件:`BackgroundServices/BackgroundServiceManager.cs`
```csharp
using System.Threading;
namespace CONTROLLER.BackgroundServices
{
/// <summary>
/// 后台服务管理器 - 用于控制后台服务的启停
/// </summary>
public class BackgroundServiceManager
{
private static CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
/// <summary>
/// 获取取消令牌
/// </summary>
public static CancellationToken GetCancellationToken()
{
return _cancellationTokenSource.Token;
}
/// <summary>
/// 暂停服务
/// </summary>
public static void PauseService()
{
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
{
_cancellationTokenSource.Cancel();
}
}
/// <summary>
/// 恢复服务
/// </summary>
public static void ResumeService()
{
if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested)
{
_cancellationTokenSource = new CancellationTokenSource();
}
}
/// <summary>
/// 获取服务状态
/// </summary>
public static bool IsRunning()
{
return _cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested;
}
}
}
```
#### 步骤2修改 LabelPdfCacheBackgroundService
```csharp
using BLL.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CONTROLLER.BackgroundServices
{
public class LabelPdfCacheBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<LabelPdfCacheBackgroundService> _logger;
private const int TaskIntervalMinutes = 5;
public LabelPdfCacheBackgroundService(
IServiceProvider serviceProvider,
ILogger<LabelPdfCacheBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Label PDF Cache Background Service is starting");
while (!stoppingToken.IsCancellationRequested)
{
try
{
// 检查服务是否被暂停
if (!BackgroundServiceManager.IsRunning())
{
_logger.LogInformation("Service is paused, waiting for resume...");
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
continue;
}
_logger.LogInformation("Starting label PDF cache processing task");
using var scope = _serviceProvider.CreateScope();
var cacheService = scope.ServiceProvider.GetRequiredService<ILabelPdfCacheService>();
var successCount = await cacheService.ProcessPendingTasksAsync();
_logger.LogInformation("Completed label PDF cache processing task, success count: {count}", successCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred in label PDF cache background service");
}
await Task.Delay(TimeSpan.FromMinutes(TaskIntervalMinutes), stoppingToken);
}
_logger.LogInformation("Label PDF Cache Background Service is stopping");
}
}
}
```
#### 步骤3在 LabelController 中添加管理接口
```csharp
/// <summary>
/// 暂停PDF缓存定时任务
/// </summary>
[HttpPost("background-service/pause")]
public IActionResult PauseBackgroundService()
{
try
{
BackgroundServiceManager.PauseService();
_logger.LogInformation("Background service paused");
return Ok(new
{
status = "success",
message = "后台定时任务已暂停",
data = new { isRunning = false }
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error pausing background service");
return Ok(new
{
status = "error",
message = "暂停定时任务失败",
errorDetails = ex.Message
});
}
}
/// <summary>
/// 恢复PDF缓存定时任务
/// </summary>
[HttpPost("background-service/resume")]
public IActionResult ResumeBackgroundService()
{
try
{
BackgroundServiceManager.ResumeService();
_logger.LogInformation("Background service resumed");
return Ok(new
{
status = "success",
message = "后台定时任务已恢复",
data = new { isRunning = true }
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error resuming background service");
return Ok(new
{
status = "error",
message = "恢复定时任务失败",
errorDetails = ex.Message
});
}
}
/// <summary>
/// 获取后台定时任务状态
/// </summary>
[HttpGet("background-service/status")]
public IActionResult GetBackgroundServiceStatus()
{
try
{
var isRunning = BackgroundServiceManager.IsRunning();
return Ok(new
{
status = "success",
message = "获取后台服务状态成功",
data = new
{
isRunning = isRunning,
service = "LabelPdfCacheBackgroundService",
interval = "5 minutes"
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting background service status");
return Ok(new
{
status = "error",
message = "获取状态失败",
errorDetails = ex.Message
});
}
}
```
#### 优点
- 可以在运行时动态控制
- 无需重启应用
- 最灵活,适合生产环境
#### 缺点
- 需要修改更多代码
- 状态只在内存中保存,重启后会重置
---
## 方法对比
| 方法 | 修改代码 | 重启应用 | 实时性 | 推荐场景 |
|------|--------|--------|-------|---------|
| 方法1配置文件 | 中等 | 是 | 低 | 生产环境固定配置 |
| 方法2环境变量 | 中等 | 是 | 低 | Docker容器部署 |
| 方法3管理接口 | 高 | 否 | 高 | 需要灵活控制 |
---
## 使用方法3的API调用示例
### JavaScript/Fetch
```javascript
// 暂停定时任务
async function pauseBackgroundService() {
const response = await fetch('http://localhost:5002/api/label/background-service/pause', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
console.log(data);
}
// 恢复定时任务
async function resumeBackgroundService() {
const response = await fetch('http://localhost:5002/api/label/background-service/resume', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
console.log(data);
}
// 获取定时任务状态
async function getBackgroundServiceStatus() {
const response = await fetch('http://localhost:5002/api/label/background-service/status');
const data = await response.json();
console.log(data);
}
```
### Python
```python
import requests
BASE_URL = "http://localhost:5002/api/label"
# 暂停定时任务
def pause_service():
response = requests.post(f"{BASE_URL}/background-service/pause")
print(response.json())
# 恢复定时任务
def resume_service():
response = requests.post(f"{BASE_URL}/background-service/resume")
print(response.json())
# 获取定时任务状态
def get_status():
response = requests.get(f"{BASE_URL}/background-service/status")
print(response.json())
```
### cURL
```bash
# 暂停定时任务
curl -X POST http://localhost:5002/api/label/background-service/pause
# 恢复定时任务
curl -X POST http://localhost:5002/api/label/background-service/resume
# 获取定时任务状态
curl -X GET http://localhost:5002/api/label/background-service/status
```
---
## 预期响应
### 成功暂停
```json
{
"status": "success",
"message": "后台定时任务已暂停",
"data": {
"isRunning": false
}
}
```
### 成功恢复
```json
{
"status": "success",
"message": "后台定时任务已恢复",
"data": {
"isRunning": true
}
}
```
### 获取状态
```json
{
"status": "success",
"message": "获取后台服务状态成功",
"data": {
"isRunning": true,
"service": "LabelPdfCacheBackgroundService",
"interval": "5 minutes"
}
}
```
---
## 注意事项
1. **数据不会丢失** - 暂停定时任务只是停止周期性执行,已有的待处理任务不会被删除
2. **可以手动处理** - 暂停后可以通过 `/batch-parse` 接口手动触发处理
3. **性能考虑** - 长时间暂停可能导致待处理任务堆积
4. **内存状态** - 如果使用方法3重启应用后服务会自动恢复运行
5. **监控建议** - 建议定期检查任务状态,确保没有任务堆积
---
## 常见场景
### 场景1维护期间暂停
如需进行数据库维护或其他重要操作,可以暂停定时任务避免并发冲突:
```bash
# 暂停
curl -X POST http://localhost:5002/api/label/background-service/pause
# 执行维护操作
# ...
# 恢复
curl -X POST http://localhost:5002/api/label/background-service/resume
```
### 场景2定点手动处理
暂停自动定时任务改为手动通过API按需处理
```bash
# 暂停自动任务
curl -X POST http://localhost:5002/api/label/background-service/pause
# 手动触发解析(当需要时)
curl -X POST http://localhost:5002/api/label/batch-parse \
-H "Content-Type: application/json" \
-d '{"mode":"all","limit":500}'
```
### 场景3监控异常时暂停
如发现定时任务出现异常,可以快速暂停避免继续出错:
```bash
# 检查状态
curl -X GET http://localhost:5002/api/label/background-service/status
# 如果出现异常,暂停
curl -X POST http://localhost:5002/api/label/background-service/pause
# 调查问题后恢复
curl -X POST http://localhost:5002/api/label/background-service/resume
```
---
## 故障排查
### Q: 暂停后定时任务仍在执行
**A:** 可能使用的是方法1或方法2需要重启应用才能生效
### Q: 暂停状态在重启后丢失
**A:** 这是正常的。如需持久化暂停状态,可以将状态保存到数据库
### Q: 恢复后任务堆积
**A:** 这是正常的。系统会逐个处理堆积的任务。可以调整 `ProcessPendingTasksAsync()` 的批处理大小来优化处理速度
---
## 推荐实施方案
根据部署环境选择:
- **开发环境**: 使用方法3API接口方便调试
- **生产环境单机**: 使用方法1配置文件稳定可靠
- **生产环境Docker**: 使用方法2环境变量配置灵活
- **生产环境微服务**: 使用方法3API接口需要分布式协调
---
## 参考信息
- **定时任务执行间隔**: 5分钟`LabelPdfCacheBackgroundService.cs` 第18行
- **处理方法**: `ProcessPendingTasksAsync()`
- **处理范围**: 失效缓存、待处理任务、新订单
- **错误处理**: 自动捕获异常,记录日志