6.4冲刺
昨天的成就: 完成了AI识别功能的全面测试与Bug修复,优化了提示词工程使准确率从72%提升至86%,建立了性能监控与日志系统,耗时9小时
遇到的困难: Windows路径分隔符兼容性问题,JSON序列化循环引用错误,提示词迭代周期长,API并发限流导致429错误
今天的任务: 添加AI识别历史记录功能,实现结果导出Excel/PDF,编写团队培训材料
📝 工作代码展示
- 单元测试用例 (backend/tests/aiService.test.js)
javascript
const aiService = require('../src/services/aiService');
const path = require('path');
const fs = require('fs');
describe('AI Service 单元测试', () => {
test('单张图片正常识别', async () => {
const imageBuffer = fs.readFileSync(path.join(__dirname, 'fixtures', 'valid_image.jpg'));
const result = await aiService.recognizeImage(imageBuffer, 'image/jpeg', 'test.jpg');
expect(result.success).toBe(true);
expect(result.structured).toHaveProperty('safetyRisks');
expect(result.structured).toHaveProperty('qualityIssues');
});
test('批量图片识别(10张)', async () => {
const files = Array.from({ length: 10 }, (_, i) => ({
buffer: fs.readFileSync(path.join(__dirname, 'fixtures', img_${i}.jpg)),
mimetype: 'image/jpeg',
originalname: img_${i}.jpg
}));
// 使用 p-limit 控制并发数为3
const pLimit = require('p-limit');
const limit = pLimit(3);
const results = await Promise.all(
files.map(file => limit(() =>
aiService.recognizeImage(file.buffer, file.mimetype, file.originalname)
))
);
const successCount = results.filter(r => r.success).length;
expect(successCount).toBeGreaterThanOrEqual(8); // 允许2张失败
});
test('文件格式错误处理', async () => {
const txtBuffer = Buffer.from('This is not an image');
await expect(aiService.recognizeImage(txtBuffer, 'text/plain', 'test.txt'))
.rejects.toThrow('只支持图片格式');
});
test('文件过大处理(超过10MB)', async () => {
const largeBuffer = Buffer.alloc(11 * 1024 * 1024); // 11MB
await expect(aiService.recognizeImage(largeBuffer, 'image/jpeg', 'large.jpg'))
.rejects.toThrow('图片大小超过限制');
});
test('API密钥缺失处理', async () => {
// 临时清除环境变量
const originalKey = process.env.DASHSCOPE_API_KEY;
delete process.env.DASHSCOPE_API_KEY;
// 重新初始化服务(模拟无密钥情况)
const aiServiceNoKey = require('../src/services/aiService');
const imageBuffer = fs.readFileSync(path.join(__dirname, 'fixtures', 'valid_image.jpg'));
await expect(aiServiceNoKey.recognizeImage(imageBuffer, 'image/jpeg', 'test.jpg'))
.rejects.toThrow('API密钥未配置');
// 恢复环境变量
process.env.DASHSCOPE_API_KEY = originalKey;
});
test('网络超时处理', async () => {
// Mock axios 超时
jest.mock('axios', () => ({
post: jest.fn().mockRejectedValue(new Error('Network Timeout'))
}));
const imageBuffer = fs.readFileSync(path.join(__dirname, 'fixtures', 'valid_image.jpg'));
await expect(aiService.recognizeImage(imageBuffer, 'image/jpeg', 'test.jpg'))
.rejects.toThrow('AI服务异常');
});
test('JSON序列化防循环引用', () => {
// 验证提取的数据对象可以正常序列化
const mockData = {
rawText: '测试文本',
structured: {
safetyRisks: ['风险1'],
qualityIssues: [],
suggestions: ['建议1']
},
timestamp: new Date()
};
expect(() => JSON.stringify(mockData)).not.toThrow();
});
});
2. 优化后的提示词 v2.0 (backend/src/services/aiService.js 修改部分)
javascript
buildQwenRequest(base64Image, mimeType, fileName) {
const prompt = `你是一位拥有10年经验的施工现场安全总监。请分析这张照片,严格按照以下标准判断:
【安全风险判定标准】
- 🔴 高风险:明确看到未戴安全帽、高空作业无安全带、明火作业无防护、触电隐患
- 🟠 中风险:疑似违规但证据不足、防护措施不完善、警示标识缺失
- 🟢 低风险:基本合规但有改进空间、轻微违规但不构成直接威胁
【常见误区提醒】
⚠️ 反光背心 ≠ 安全帽(黄色/橙色背心不是头部防护)
️ 室内作业 ≠ 高空作业(楼层内地面作业不算高空)
⚠️ 临时堆放 ≠ 危险物品(工具材料在指定区域正常摆放是允许的)
【施工专业术语】
- "临边防护":指楼层边缘、楼梯口、电梯井口的护栏
- "脚手架搭设":检查立杆间距、扫地杆、剪刀撑是否规范
- "三宝四口":安全帽、安全带、安全网;楼梯口、电梯井口、预留洞口、通道口
请按以下固定格式输出(不要使用Markdown符号):
【安全风险】
- [风险等级] 具体问题描述
- [风险等级] 具体问题描述
(如果没有问题,写"无明显安全风险")
【施工质量】
简要评估工艺是否符合规范,材料堆放是否整齐
【改进建议】
- 针对上述问题的具体整改措施
- 预防类似问题的管理建议`;
return JSON.stringify({
model: "qwen-vl-max",
input: {
messages: [
{
role: "user",
content: [
{ image: data:${mimeType};base64,${base64Image} },
{ text: prompt }
]
}
]
},
parameters: {
top_p: 0.8,
temperature: 0.3 // 降低随机性,提高一致性
}
});
}
3. 性能监控与日志系统 (backend/src/utils/logger.js)
javascript
const winston = require('winston');
const path = require('path');
// 创建logger实例
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
winston.format.printf(({ timestamp, level, message }) => {
return [${timestamp}] [${level.toUpperCase()}] ${message};
})
),
transports: [
// 控制台输出
new winston.transports.Console(),
// 文件输出(每天一个文件,保留7天)
new winston.transports.File({
filename: path.join('logs', 'ai-service-%DATE%.log'),
datePattern: 'YYYY-MM-DD',
maxFiles: 7,
zippedArchive: true
})
]
});
// 性能监控装饰器
function monitorPerformance(targetMethod) {
return async function(...args) {
const startTime = Date.now();
const fileName = args[2] || 'unknown';
logger.info(📸 Processing image: ${fileName});
logger.info( Image size: ${(args[0].length / 1024 / 1024).toFixed(2)} MB);
try {
const result = await targetMethod.apply(this, args);
const duration = Date.now() - startTime;
logger.info(️ API响应时间: ${duration}ms);
logger.info(✅ Response received);
// 记录风险等级统计
if (result.structured) {
const riskLevel = result.structured.safetyRisks?.length > 0 ? 'medium/high' : 'low';
logger.info( Risk level: ${riskLevel});
}
// Prometheus Metrics(可选)
if (global.prometheusMetrics) {
global.prometheusMetrics.aiRequestDuration.observe(duration);
global.prometheusMetrics.aiRequestTotal.inc();
}
return result;
} catch (error) {
const duration = Date.now() - startTime;
logger.error(❌ AI识别失败: ${error.message} (耗时: ${duration}ms));
if (global.prometheusMetrics) {
global.prometheusMetrics.aiErrorTotal.inc();
}
throw error;
}
};
}
module.exports = {
logger,
monitorPerformance
};
4. 并发限流与重试机制 (backend/src/services/aiService.js 修改部分)
javascript
const pLimit = require('p-limit');
const limit = pLimit(3); // 最多3个并发请求,避免触发API限流
class AIService {
constructor() {
this.apiKey = process.env.DASHSCOPE_API_KEY;
this.apiUrl = 'https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation';
}
/**
-
带重试机制的识别方法
*/
async recognizeWithRetry(imageBuffer, mimeType, fileName, maxRetries = 2) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await this._recognizeImage(imageBuffer, mimeType, fileName);} catch (error) {
// 如果是429限流错误,且还有重试次数
if (error.message.includes('429') && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // 指数退避:1s, 2s, 4s
logger.warn(⚠️ Rate limited, retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries}));
await this.sleep(delay);
continue;
}// 其他错误或重试耗尽,抛出异常
throw error;
}
}
}
/**
- 核心识别逻辑(被监控装饰器包裹)
*/
async _recognizeImage(imageBuffer, mimeType, fileName) {
// ... 原有的识别逻辑 ...
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 导出带限流的批量处理方法
async function recognizeMultipleImages(files) {
const results = await Promise.all(
files.map(file =>
limit(() => aiService.recognizeWithRetry(
file.buffer,
file.mimetype,
file.originalname
))
)
);
return results;
}

浙公网安备 33010602011771号