Files
LabelChange-server/定时任务暂停指南.md
2026-06-01 16:30:29 +08:00

558 lines
14 KiB
Markdown
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.

# 定时任务暂停指南
## 概述
项目中的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()`
- **处理范围**: 失效缓存、待处理任务、新订单
- **错误处理**: 自动捕获异常,记录日志