355 lines
9.3 KiB
JavaScript
355 lines
9.3 KiB
JavaScript
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; |