From 55452a2d21b01a85dbebf4b1b5666a588c5e5a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E6=B5=B7=E6=B4=8B?= Date: Tue, 26 May 2026 09:30:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + app.js | 2 + config/database.js | 24 +++++++----- routes/ai.js | 13 +++++- routes/orders.js | 1 + routes/recognize.js | 87 +++++++++++++++++++++++++++++++++++++++++ scripts/init-db-root.js | 17 ++++++-- scripts/test-db.js | 12 +++--- utils/image-url.js | 14 +++++-- 9 files changed, 147 insertions(+), 24 deletions(-) create mode 100644 routes/recognize.js diff --git a/.env.example b/.env.example index 5dc975c..2c40c44 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ DB_HOST=localhost DB_PORT=3306 DB_USER=root DB_PASSWORD=your_password +DB_ROOT_PASSWORD=your_root_password DB_NAME=miniprogram # AI 配置(阿里云 DashScope) diff --git a/app.js b/app.js index 3da476c..8f0fd01 100644 --- a/app.js +++ b/app.js @@ -55,6 +55,7 @@ const pointsGoodsRoutes = require('./routes/points-goods') const statsRoutes = require('./routes/stats') const priceListRoutes = require('./routes/price-list') const pointsLogsRoutes = require('./routes/points-logs') +const recognizeRoutes = require('./routes/recognize') router.use('/api/orders', orderRoutes) router.use('/api/categories', categoryRoutes) @@ -69,6 +70,7 @@ router.use('/api/points-goods', pointsGoodsRoutes) router.use('/api/stats', statsRoutes) router.use('/api/price-list', priceListRoutes) router.use('/api/points/logs', pointsLogsRoutes) +router.use('/api/recognize', recognizeRoutes) app.use(router.routes()) app.use(router.allowedMethods()) diff --git a/config/database.js b/config/database.js index efd76a0..d8f205c 100644 --- a/config/database.js +++ b/config/database.js @@ -1,21 +1,25 @@ const mysql = require('mysql2/promise') require('dotenv').config() +function requireEnv(name, fallback) { + const value = process.env[name] || fallback + if (!value && !fallback) { + throw new Error(`Missing required environment variable: ${name}. Check .env file.`) + } + return value +} + const config = { - host: process.env.DB_HOST || '110.42.255.239', - port: parseInt(process.env.DB_PORT || '3306'), - user: process.env.DB_USER || 'admin', - password: process.env.DB_PASSWORD || 'Admin@123', - database: process.env.DB_NAME || 'miniprogram', + host: requireEnv('DB_HOST', 'localhost'), + port: parseInt(requireEnv('DB_PORT', '3306')), + user: requireEnv('DB_USER', 'root'), + password: requireEnv('DB_PASSWORD', ''), + database: requireEnv('DB_NAME', 'miniprogram'), waitForConnections: true, connectionLimit: 10, queueLimit: 0 } -/* -# 登录服务器,找到 Koa 进程 -ssh ubuntu@110.42.255.239 -pm2 restart weixin -*/ + const pool = mysql.createPool(config) async function query(sql, params = []) { diff --git a/routes/ai.js b/routes/ai.js index 697c5ec..c1d8f88 100644 --- a/routes/ai.js +++ b/routes/ai.js @@ -8,9 +8,16 @@ const router = new Router(); const AI_API_KEY = process.env.DASHSCOPE_API_KEY; const AI_API_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'; -// 2026-05-24 21:31:40 +if (!AI_API_KEY) { + console.error('DASHSCOPE_API_KEY is not set - AI features will fail') +} + router.post('/generate-product', async (ctx) => { try { + if (!AI_API_KEY) { + ctx.body = { code: 500, message: 'AI 功能未配置(缺少 DASHSCOPE_API_KEY)' } + return + } const { imageUrl, keywords } = ctx.request.body; let prompt = '你是一个专业的便利店商品管理助手。'; @@ -127,6 +134,10 @@ router.post('/generate-product', async (ctx) => { router.post('/recognize-product', async (ctx) => { try { + if (!AI_API_KEY) { + ctx.body = { code: 500, message: 'AI 功能未配置(缺少 DASHSCOPE_API_KEY)' } + return + } const { imageUrl } = ctx.request.body; if (!imageUrl) { diff --git a/routes/orders.js b/routes/orders.js index de71292..ab077ad 100644 --- a/routes/orders.js +++ b/routes/orders.js @@ -14,5 +14,6 @@ router.post('/', orderController.createOrder) // 更新订单状态 router.put('/:id', orderController.updateOrder) +router.put('/:id/status', orderController.updateOrder) module.exports = router.routes() diff --git a/routes/recognize.js b/routes/recognize.js new file mode 100644 index 0000000..c8a0ef2 --- /dev/null +++ b/routes/recognize.js @@ -0,0 +1,87 @@ +const Router = require('koa-router') +const { query } = require('../config/database') +const fetch = require('node-fetch') +require('dotenv').config() + +const router = new Router() +const AI_API_KEY = process.env.DASHSCOPE_API_KEY +const AI_API_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' + +router.post('/barcode', async (ctx) => { + try { + const { barcode } = ctx.request.body + if (!barcode) { + ctx.body = { code: 400, message: '请提供条形码' } + return + } + + const goods = await query( + 'SELECT * FROM goods WHERE barcode = ? LIMIT 1', + [barcode] + ) + + if (goods.length > 0) { + ctx.body = { code: 200, data: goods[0] } + } else { + ctx.body = { code: 404, message: '未找到该商品' } + } + } catch (error) { + console.error('Barcode lookup failed:', error) + ctx.body = { code: 500, message: '查询失败' } + } +}) + +router.post('/image', async (ctx) => { + try { + const { imageData } = ctx.request.body + if (!imageData) { + ctx.body = { code: 400, message: '请提供图片数据' } + return + } + + if (!AI_API_KEY) { + ctx.body = { code: 500, message: 'AI 识别未配置' } + return + } + + const response = await fetch(AI_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${AI_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'qwen-vl-max', + messages: [ + { + role: 'user', + content: [ + { type: 'text', text: '请识别这张图片中的商品,返回商品名称。只返回名称,不要其他内容。' }, + { type: 'image_url', image_url: { url: imageData } } + ] + } + ] + }) + }) + + const result = await response.json() + const name = result?.choices?.[0]?.message?.content?.trim() || '' + + const goods = name + ? await query('SELECT * FROM goods WHERE name LIKE ? LIMIT 5', [`%${name}%`]) + : [] + + ctx.body = { + code: 200, + data: { + message: name ? `识别到: ${name}` : '未识别到商品', + goods: goods || [] + } + } + } catch (error) { + console.error('Image recognition failed:', error) + ctx.body = { code: 500, message: '识别失败' } + } +}) + +module.exports = router.routes() diff --git a/scripts/init-db-root.js b/scripts/init-db-root.js index 952bc78..7040723 100644 --- a/scripts/init-db-root.js +++ b/scripts/init-db-root.js @@ -1,17 +1,26 @@ const fs = require('fs') const path = require('path') const mysql = require('mysql2/promise') +require('dotenv').config({ path: path.join(__dirname, '../.env') }) const categoriesData = require('../data/categories.json') const goodsData = require('../data/goods.json') const usersData = require('../data/users.json') +function requireEnv(name, fallback) { + const value = process.env[name] || fallback + if (!value && !fallback) { + throw new Error(`Missing ${name} in .env`) + } + return value +} + const config = { - host: '110.42.255.239', - port: 3306, + host: requireEnv('DB_HOST'), + port: parseInt(requireEnv('DB_PORT', '3306')), user: 'root', - password: 'Wentian9588.', - database: 'miniprogram' + password: requireEnv('DB_ROOT_PASSWORD'), + database: requireEnv('DB_NAME', 'miniprogram') } async function run() { diff --git a/scripts/test-db.js b/scripts/test-db.js index bac2d90..c670e70 100644 --- a/scripts/test-db.js +++ b/scripts/test-db.js @@ -1,11 +1,13 @@ const mysql = require('mysql2/promise'); +const path = require('path'); +require('dotenv').config({ path: path.join(__dirname, '../.env') }); const config = { - host: '110.42.255.239', - port: 3306, - user: 'admin', - password: 'Admin@123', - database: 'miniprogram' + host: process.env.DB_HOST || 'localhost', + port: parseInt(process.env.DB_PORT || '3306'), + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'miniprogram' }; async function testConnection() { diff --git a/utils/image-url.js b/utils/image-url.js index dd4f569..9baf7a2 100644 --- a/utils/image-url.js +++ b/utils/image-url.js @@ -9,12 +9,18 @@ function toRelativeUrl(url) { if (url.startsWith(baseUrl)) { return url.replace(baseUrl, ''); } - // 移除其他可能的前缀 + // 从 BASE_URL 中提取主机名用于构建动态正则 + let hostname = '' + try { + hostname = new URL(baseUrl).hostname + } catch (e) { + hostname = '' + } + // 移除其他已知前缀 const patterns = [ - /^https?:\/\/donghy\.top/, - /^https?:\/\/110\.42\.255\.239(:\d+)?/, + hostname ? new RegExp(`^https?://${hostname.replace(/\./g, '\\.')}(:\\d+)?`) : null, /^https?:\/\/localhost(:\d+)?/ - ]; + ].filter(Boolean) for (const pattern of patterns) { if (pattern.test(url)) { return url.replace(pattern, '');