上传源代码版本
This commit is contained in:
557
定时任务暂停指南.md
Normal file
557
定时任务暂停指南.md
Normal 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()` 的批处理大小来优化处理速度
|
||||
|
||||
---
|
||||
|
||||
## 推荐实施方案
|
||||
|
||||
根据部署环境选择:
|
||||
|
||||
- **开发环境**: 使用方法3(API接口),方便调试
|
||||
- **生产环境单机**: 使用方法1(配置文件),稳定可靠
|
||||
- **生产环境Docker**: 使用方法2(环境变量),配置灵活
|
||||
- **生产环境微服务**: 使用方法3(API接口),需要分布式协调
|
||||
|
||||
---
|
||||
|
||||
## 参考信息
|
||||
|
||||
- **定时任务执行间隔**: 5分钟(见 `LabelPdfCacheBackgroundService.cs` 第18行)
|
||||
- **处理方法**: `ProcessPendingTasksAsync()`
|
||||
- **处理范围**: 失效缓存、待处理任务、新订单
|
||||
- **错误处理**: 自动捕获异常,记录日志
|
||||
|
||||
Reference in New Issue
Block a user