6.3冲刺
昨天的成就: 完成了AI识别控制器开发,实现了单张/批量图片识别接口,集成了进度跟踪模块的AI功能,编写了使用文档,耗时11小时
遇到的困难: 批量识别并发控制导致部分失败,AI结果存储结构设计复杂,前端展示杂乱,大图片API超时
今天的任务: 功能测试与Bug修复,优化提示词工程,添加识别历史记录功能
📝 工作代码展示
- AI识别控制器 (backend/src/controllers/aiController.js)
javascript
const express = require('express');
const multer = require('multer');
const aiService = require('../services/aiService');
const router = express.Router();
// 配置Multer:内存存储 + 10MB限制
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
// 模糊照片检测:小于2KB视为无效
if (file.size < 2048) {
return cb(new Error('图片过于模糊或文件损坏'), false);
}
cb(null, true);
} else {
cb(new Error('只支持图片格式'), false);
}
}
});
/**
-
单张图片识别
-
POST /api/progress/recognize
*/
router.post('/recognize', upload.single('image'), async (req, res) => {
try {
if (!req.file) throw new Error('未上传图片');const result = await aiService.recognizeImage(
req.file.buffer,
req.file.mimetype,
req.file.originalname
);res.json({ success: true, data: result });
} catch (error) {
// 自动清理机制已在aiService中处理(内存存储无需手动删除)
res.status(500).json({ success: false, message: error.message });
}
});
/**
-
批量图片识别(最多10张)
-
POST /api/progress/recognize-multiple
*/
router.post('/recognize-multiple', upload.array('images', 10), async (req, res) => {
try {
if (!req.files || req.files.length === 0) {
throw new Error('未上传图片');
}// 使用 Promise.allSettled 确保单张失败不影响其他
const tasks = req.files.map(file =>
aiService.recognizeImage(file.buffer, file.mimetype, file.originalname)
.then(result => ({ ...result, fileName: file.originalname }))
.catch(err => ({
success: false,
fileName: file.originalname,
error: err.message
}))
);const results = await Promise.allSettled(tasks);
// 提取最终结果
const processedResults = results.map(r => r.value);res.json({
success: true,
total: req.files.length,
successCount: processedResults.filter(r => r.success).length,
data: processedResults
});
} catch (error) {
res.status(500).json({ success: false, message: '批量识别失败' });
}
});
module.exports = router;
2. 进度跟踪模块集成 (backend/src/controllers/progressController.js 修改部分)
javascript
const aiService = require('../services/aiService');
const ConstructionProgress = require('../models/ConstructionProgress');
// 在原有的上传照片方法中集成AI识别
exports.uploadPhoto = async (req, res) => {
try {
const { projectId, stageId } = req.body;
const photoUrl = req.file.path; // 假设已上传到OSS或本地存储
// 1. 保存进度记录
const progress = await ConstructionProgress.create({
projectId,
stageId,
photoUrl,
uploadTime: new Date()
});
// 2. 异步调用AI识别(不阻塞主流程)
// 注意:生产环境建议使用消息队列(如RabbitMQ)处理异步任务
processAIRecognition(progress.id, req.file.buffer, req.file.mimetype);
res.json({
success: true,
message: '照片上传成功,AI识别进行中...',
data: progress
});
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
};
// 独立的AI识别处理函数
async function processAIRecognition(progressId, imageBuffer, mimeType) {
try {
console.log([AI Task] 开始识别进度ID: ${progressId});
// 调用AI服务
const aiResult = await aiService.recognizeImage(imageBuffer, mimeType, progress_${progressId}.jpg);
// 将结构化结果存入 review_remark 字段
await ConstructionProgress.update(
{ reviewRemark: JSON.stringify(aiResult.structured) },
{ where: { id: progressId } }
);
console.log([AI Task] 识别完成并保存: ${progressId});
} catch (error) {
console.error([AI Task] 识别失败 ID: ${progressId}, error.message);
// 可选:记录错误日志表
}
}
3. 前端展示优化 (frontend/src/pages/ProgressTracking.js 修改部分)
jsx
import React, { useState } from 'react';
import { Modal, Card, Tag, Button, Spin } from 'antd';
import { WarningOutlined, CheckCircleOutlined, BulbOutlined } from '@ant-design/icons';
const ProgressTracking = () => {
const [aiModalVisible, setAiModalVisible] = useState(false);
const [aiData, setAiData] = useState(null);
const [loading, setLoading] = useState(false);
// 获取AI结果并显示
const showAIResult = async (progressId) => {
setLoading(true);
try {
// 假设已有API获取该进度的AI结果
const response = await fetch(/api/progress/${progressId}/ai-result);
const data = await response.json();
if (data.success) {
setAiData(JSON.parse(data.data.reviewRemark));
setAiModalVisible(true);
} else {
message.error('暂无AI识别结果');
}
} catch (err) {
message.error('获取AI结果失败');
} finally {
setLoading(false);
}
};
return (
{/* AI结果弹窗 */}
<Modal
title="🤖 AI 智能分析报告"
visible={aiModalVisible}
onCancel={() => setAiModalVisible(false)}
footer={[
<Button key="copy" onClick={() => copyToClipboard(aiData)}>复制结果,
<Button key="close" type="primary" onClick={() => setAiModalVisible(false)}>关闭
]}
width={700}
>
{aiData && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
{/* 安全风险卡片 */}
<Card title={<WarningOutlined style={{color: '#ff4d4f'}} /> 安全风险} bordered={false} size="small">
{aiData.safetyRisks?.length > 0 ? (
aiData.safetyRisks.map((risk, idx) => (
<Tag color="red" key={idx} style={{ marginBottom: '8px', display: 'block' }}>
{risk}
))
) : (
)}
{/* 施工质量卡片 */}
<Card title={<CheckCircleOutlined style={{color: '#52c41a'}} /> 施工质量} bordered={false} size="small">
{aiData.qualityIssues?.length > 0 ? (
aiData.qualityIssues.map((issue, idx) => (
<Tag color="orange" key={idx} style={{ marginBottom: '8px', display: 'block' }}>
{issue}
))
) : (
)}
{/* 改进建议卡片 */}
<Card title={<BulbOutlined style={{color: '#faad14'}} /> 改进建议} bordered={false} size="small">
{aiData.suggestions?.map((sug, idx) => (
<p key={idx} style={{ margin: '4px 0', fontSize: '14px' }}>• {sug}
))}
)}
);
};
export default ProgressTracking;
4. 前端图片压缩工具 (frontend/src/utils/imageCompressor.js)
javascript
/**
-
图片压缩工具
-
@param {File} file - 原始文件
-
@param {number} maxWidth - 最大宽度
-
@param {number} maxHeight - 最大高度
-
@param {number} quality - 压缩质量 (0-1)
*/
export const compressImage = (file, maxWidth = 1920, maxHeight = 1080, quality = 0.8) => {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();img.onload = () => {
// 计算缩放比例,保持宽高比
let width = img.width;
let height = img.height;if (width > height) {
if (width > maxWidth) {
height *= maxWidth / width;
width = maxWidth;
}
} else {
if (height > maxHeight) {
width *= maxHeight / height;
height = maxHeight;
}
}canvas.width = width;
canvas.height = height;// 绘制图片
ctx.drawImage(img, 0, 0, width, height);// 转换为Blob
canvas.toBlob((blob) => {
resolve(new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
}));
}, 'image/jpeg', quality);
};img.src = URL.createObjectURL(file);
});
};

浙公网安备 33010602011771号