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;
|