docs: 更新项目文档,完善需求和技术细节
This commit is contained in:
355
backend/routes/identifications.js
Normal file
355
backend/routes/identifications.js
Normal file
@@ -0,0 +1,355 @@
|
||||
const express = require('express');
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const dbConnector = require('../utils/dbConnector');
|
||||
const { asyncHandler } = require('../middlewares/errorHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 配置multer用于文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const uploadDir = path.join(__dirname, '../uploads/identifications');
|
||||
|
||||
// 确保上传目录存在
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
cb(null, uploadDir);
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, 'identification-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024, // 10MB限制
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// 只允许图片文件
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('只支持图片文件'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取识别历史记录
|
||||
*/
|
||||
router.get('/', asyncHandler(async (req, res) => {
|
||||
const { page = 1, limit = 10 } = req.query;
|
||||
const userId = req.user.id;
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 查询识别记录
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[userId, parseInt(limit), offset]
|
||||
);
|
||||
|
||||
// 查询总数
|
||||
const totalResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
identifications,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total: totalResult[0].total,
|
||||
pages: Math.ceil(totalResult[0].total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取单条识别记录详情
|
||||
*/
|
||||
router.get('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, user_id, image_url, result, confidence, created_at
|
||||
FROM identifications
|
||||
WHERE id = ? AND user_id = ?`,
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identifications.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: identifications[0]
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 花卉识别接口
|
||||
*/
|
||||
router.post('/identify', upload.single('image'), asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: '请上传图片文件',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 这里应该调用AI识别服务
|
||||
// 由于AI识别服务需要额外配置,这里先模拟识别结果
|
||||
|
||||
const imageUrl = `/uploads/identifications/${req.file.filename}`;
|
||||
|
||||
// 模拟AI识别结果(实际项目中应该调用真实的AI服务)
|
||||
const mockResults = [
|
||||
{ name: '玫瑰', confidence: 0.95, scientificName: 'Rosa rugosa', description: '玫瑰是蔷薇科蔷薇属的植物,具有浓郁的芳香和美丽的花朵。' },
|
||||
{ name: '百合', confidence: 0.87, scientificName: 'Lilium brownii', description: '百合是百合科百合属的植物,象征纯洁和高雅。' },
|
||||
{ name: '菊花', confidence: 0.82, scientificName: 'Chrysanthemum morifolium', description: '菊花是菊科菊属的植物,具有很高的观赏和药用价值。' }
|
||||
];
|
||||
|
||||
// 选择置信度最高的结果
|
||||
const bestResult = mockResults[0];
|
||||
|
||||
// 保存识别记录到数据库
|
||||
const result = await dbConnector.query(
|
||||
'INSERT INTO identifications (user_id, image_url, result, confidence) VALUES (?, ?, ?, ?)',
|
||||
[userId, imageUrl, JSON.stringify(mockResults), bestResult.confidence]
|
||||
);
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '识别成功',
|
||||
data: {
|
||||
identification_id: result.insertId,
|
||||
image_url: imageUrl,
|
||||
results: mockResults,
|
||||
best_result: bestResult,
|
||||
created_at: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 批量识别历史记录
|
||||
*/
|
||||
router.get('/batch/history', asyncHandler(async (req, res) => {
|
||||
const { start_date, end_date, min_confidence = 0.7 } = req.query;
|
||||
const userId = req.user.id;
|
||||
|
||||
let whereClause = 'WHERE user_id = ? AND confidence >= ?';
|
||||
let queryParams = [userId, parseFloat(min_confidence)];
|
||||
|
||||
if (start_date) {
|
||||
whereClause += ' AND created_at >= ?';
|
||||
queryParams.push(start_date);
|
||||
}
|
||||
|
||||
if (end_date) {
|
||||
whereClause += ' AND created_at <= ?';
|
||||
queryParams.push(end_date + ' 23:59:59');
|
||||
}
|
||||
|
||||
const identifications = await dbConnector.query(
|
||||
`SELECT
|
||||
id, image_url, result, confidence, created_at,
|
||||
DATE(created_at) as identify_date
|
||||
FROM identifications
|
||||
${whereClause}
|
||||
ORDER BY created_at DESC`,
|
||||
queryParams
|
||||
);
|
||||
|
||||
// 按日期分组
|
||||
const groupedByDate = {};
|
||||
identifications.forEach(record => {
|
||||
const date = record.identify_date;
|
||||
if (!groupedByDate[date]) {
|
||||
groupedByDate[date] = [];
|
||||
}
|
||||
groupedByDate[date].push(record);
|
||||
});
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total: identifications.length,
|
||||
by_date: groupedByDate,
|
||||
statistics: {
|
||||
total_count: identifications.length,
|
||||
avg_confidence: identifications.reduce((sum, item) => sum + item.confidence, 0) / identifications.length,
|
||||
date_range: {
|
||||
start: identifications.length > 0 ? identifications[identifications.length - 1].identify_date : null,
|
||||
end: identifications.length > 0 ? identifications[0].identify_date : null
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 识别统计信息
|
||||
*/
|
||||
router.get('/stats/summary', asyncHandler(async (req, res) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
// 总识别次数
|
||||
const totalCountResult = await dbConnector.query(
|
||||
'SELECT COUNT(*) as total FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 平均置信度
|
||||
const avgConfidenceResult = await dbConnector.query(
|
||||
'SELECT AVG(confidence) as avg_confidence FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 最近识别时间
|
||||
const lastIdentificationResult = await dbConnector.query(
|
||||
'SELECT created_at FROM identifications WHERE user_id = ? ORDER BY created_at DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
|
||||
// 识别最多的花卉类型(需要解析result字段)
|
||||
const allIdentifications = await dbConnector.query(
|
||||
'SELECT result FROM identifications WHERE user_id = ?',
|
||||
[userId]
|
||||
);
|
||||
|
||||
const flowerCounts = {};
|
||||
allIdentifications.forEach(item => {
|
||||
try {
|
||||
const results = JSON.parse(item.result);
|
||||
if (results && results.length > 0) {
|
||||
const bestResult = results[0];
|
||||
flowerCounts[bestResult.name] = (flowerCounts[bestResult.name] || 0) + 1;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析识别结果失败:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// 找出识别最多的花卉
|
||||
let mostIdentifiedFlower = null;
|
||||
let maxCount = 0;
|
||||
for (const [flower, count] of Object.entries(flowerCounts)) {
|
||||
if (count > maxCount) {
|
||||
mostIdentifiedFlower = flower;
|
||||
maxCount = count;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '获取成功',
|
||||
data: {
|
||||
total_count: totalCountResult[0].total,
|
||||
avg_confidence: avgConfidenceResult[0].avg_confidence || 0,
|
||||
last_identification: lastIdentificationResult[0] ? lastIdentificationResult[0].created_at : null,
|
||||
most_identified_flower: mostIdentifiedFlower,
|
||||
flower_counts: flowerCounts,
|
||||
weekly_trend: await getWeeklyTrend(userId)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
/**
|
||||
* 获取周趋势数据
|
||||
*/
|
||||
async function getWeeklyTrend(userId) {
|
||||
const weeklyData = await dbConnector.query(
|
||||
`SELECT
|
||||
DATE(created_at) as date,
|
||||
COUNT(*) as count,
|
||||
AVG(confidence) as avg_confidence
|
||||
FROM identifications
|
||||
WHERE user_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date DESC`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
return weeklyData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除识别记录
|
||||
*/
|
||||
router.delete('/:id', asyncHandler(async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 先获取记录信息以删除图片文件
|
||||
const identification = await dbConnector.query(
|
||||
'SELECT image_url FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (identification.length === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
// 删除图片文件
|
||||
if (identification[0].image_url) {
|
||||
const imagePath = path.join(__dirname, '../', identification[0].image_url);
|
||||
if (fs.existsSync(imagePath)) {
|
||||
fs.unlinkSync(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据库记录
|
||||
const result = await dbConnector.query(
|
||||
'DELETE FROM identifications WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
code: 404,
|
||||
message: '识别记录不存在',
|
||||
data: null
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
code: 200,
|
||||
message: '删除成功',
|
||||
data: null
|
||||
});
|
||||
}));
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user