From f2a9fec40569277f4808ac820b8354d34108bf20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E6=B5=B7=E6=B4=8B?= Date: Sun, 24 May 2026 19:33:45 +0800 Subject: [PATCH] ai --- controllers/goods.js | 4 +- routes/ai.js | 169 ++++++++++++++++++++++++---- scripts/convert-to-relative-urls.js | 101 +++++++++++++++++ scripts/test-db.js | 34 ++++++ 4 files changed, 281 insertions(+), 27 deletions(-) create mode 100644 scripts/convert-to-relative-urls.js create mode 100644 scripts/test-db.js diff --git a/controllers/goods.js b/controllers/goods.js index c213d06..154f559 100644 --- a/controllers/goods.js +++ b/controllers/goods.js @@ -19,8 +19,8 @@ async function getGoods(ctx) { } if (ctx.query.keyword) { - sql += ' AND (name LIKE ? OR barcode LIKE ?)' - params.push(`%${ctx.query.keyword}%`, `%${ctx.query.keyword}%`) + sql += ' AND name LIKE ?' + params.push(`%${ctx.query.keyword}%`) } if (ctx.query.inStock === '1') { diff --git a/routes/ai.js b/routes/ai.js index 243d5d3..a67c751 100644 --- a/routes/ai.js +++ b/routes/ai.js @@ -1,49 +1,40 @@ const Router = require('koa-router'); const fetch = require('node-fetch'); +const path = require('path'); +const fs = require('fs'); const router = new Router(); -// NVIDIA API 配置(NVCF 云 API) -const NVIDIA_API_KEY = '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'; -const NVIDIA_API_URL = 'https://integrate.api.nvidia.com/v1/chat/completions'; +const DEEPSEEK_API_KEY = 'sk-21cc85a144874d9b902edffe6af8a971'; +const DEEPSEEK_API_URL = 'https://api.deepseek.com'; -// 生成商品信息的 API router.post('/generate-product', async (ctx) => { try { const { imageUrl, keywords } = ctx.request.body; - - if (!imageUrl && !keywords) { - ctx.status = 400; - ctx.body = { - code: 400, - message: '请提供商品图片或关键词' - }; - return; - } let prompt = '你是一个专业的便利店商品管理助手。'; - + if (imageUrl) { prompt += `\n请分析这张商品图片:${imageUrl}`; } - + if (keywords) { prompt += `\n关键词:${keywords}`; } - + prompt += ` 请生成商品的详细信息,返回JSON格式,不要包含其他内容: { "name": "商品名称(简洁明了,2-10字)", - "category": "商品分类(请从以下选择:饮料,零食,日用品,食品,烟酒,其他)", + "category": "商品分类(请从以下选择:饮料,零食,日用品,食品,生鲜,烟酒,其他)", "description": "商品详细描述(50-100字,突出产品特点)", "suggestedPrice": 建议售价(数字) }`; - const response = await fetch(NVIDIA_API_URL, { + const response = await fetch(DEEPSEEK_API_URL, { method: 'POST', headers: { - 'Authorization': `Bearer ${NVIDIA_API_KEY}`, + 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -62,8 +53,8 @@ router.post('/generate-product', async (ctx) => { if (!response.ok) { const errorText = await response.text(); - console.error('NVIDIA API Error:', response.status, errorText); - + console.error('DeepSeek API Error:', response.status, errorText); + let errorMsg = 'AI 服务调用失败'; if (response.status === 401) { errorMsg = 'API Key 无效,请检查密钥配置'; @@ -74,7 +65,7 @@ router.post('/generate-product', async (ctx) => { } else if (response.status === 503) { errorMsg = 'AI 服务暂时不可用,请稍后重试'; } - + ctx.status = response.status; ctx.body = { code: response.status, @@ -85,7 +76,7 @@ router.post('/generate-product', async (ctx) => { const data = await response.json(); const aiResponse = data.choices?.[0]?.message?.content; - + if (!aiResponse) { ctx.status = 500; ctx.body = { @@ -115,7 +106,7 @@ router.post('/generate-product', async (ctx) => { } catch (error) { console.error('生成商品信息失败:', error); - + let errorMsg = '生成失败,请稍后重试'; if (error.message.includes('timeout')) { errorMsg = 'AI 服务响应超时,请检查网络或稍后重试'; @@ -124,7 +115,135 @@ router.post('/generate-product', async (ctx) => { } else if (error.message.includes('ECONNRESET')) { errorMsg = 'AI 服务连接中断,请稍后重试'; } - + + ctx.status = 503; + ctx.body = { + code: 503, + message: errorMsg + }; + } +}); + +router.post('/recognize-product', async (ctx) => { + try { + const { imageBase64 } = ctx.request.body; + + if (!imageBase64) { + ctx.status = 400; + ctx.body = { + code: 400, + message: '请提供商品图片' + }; + return; + } + + const prompt = `你是一个专业的便利店商品识别助手。请分析这张商品图片,识别出商品信息。 + +请返回JSON格式的商品信息,只返回一个最可能的商品,不要返回多个: +{ + "name": "商品名称(根据图片识别,如果无法确定则返回空字符串)", + "category": "商品分类(从以下选择:饮料,零食,日用品,食品,生鲜,烟酒,其他,如果无法确定则返回空字符串)", + "description": "商品描述(根据图片识别,突出产品特点,如果无法确定则返回空字符串)", + "suggestedPrice": 数字(根据市场价估算,如果无法确定则返回0), + "confidence": 0到1之间的数字(识别置信度) +}`; + + const response = await fetch(DEEPSEEK_API_URL, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${DEEPSEEK_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'deepseek-v4-flash', + messages: [ + { + role: 'user', + content: [ + { + type: 'image_url', + image_url: { + url: `data:image/jpeg;base64,${imageBase64}` + } + }, + { + type: 'text', + text: prompt + } + ] + } + ], + temperature: 0.3, + max_tokens: 500 + }), + timeout: 60000 + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error('DeepSeek API Error:', response.status, errorText); + + let errorMsg = 'AI 服务调用失败'; + if (response.status === 401) { + errorMsg = 'API Key 无效,请检查密钥配置'; + } else if (response.status === 403) { + errorMsg = 'API 调用被拒绝,请检查账户权限'; + } else if (response.status === 429) { + errorMsg = 'API 调用次数超限,请稍后重试'; + } else if (response.status === 503) { + errorMsg = 'AI 服务暂时不可用,请稍后重试'; + } + + ctx.status = response.status; + ctx.body = { + code: response.status, + message: errorMsg + }; + return; + } + + const data = await response.json(); + const aiResponse = data.choices?.[0]?.message?.content; + + if (!aiResponse) { + ctx.status = 500; + ctx.body = { + code: 500, + message: 'AI 服务返回为空' + }; + return; + } + + const jsonMatch = aiResponse.match(/\{[\s\S]*\}/); + if (!jsonMatch) { + ctx.status = 500; + ctx.body = { + code: 500, + message: '无法解析 AI 响应格式' + }; + return; + } + + const productInfo = JSON.parse(jsonMatch[0]); + + ctx.body = { + code: 200, + message: '识别成功', + data: productInfo + }; + + } catch (error) { + console.error('识别商品失败:', error); + + let errorMsg = '识别失败,请稍后重试'; + if (error.message.includes('timeout')) { + errorMsg = 'AI 服务响应超时,请检查网络或稍后重试'; + } else if (error.message.includes('ENOTFOUND')) { + errorMsg = '无法连接到 AI 服务,请检查网络设置'; + } else if (error.message.includes('ECONNRESET')) { + errorMsg = 'AI 服务连接中断,请稍后重试'; + } + ctx.status = 503; ctx.body = { code: 503, diff --git a/scripts/convert-to-relative-urls.js b/scripts/convert-to-relative-urls.js new file mode 100644 index 0000000..0cbba89 --- /dev/null +++ b/scripts/convert-to-relative-urls.js @@ -0,0 +1,101 @@ +const mysql = require('mysql2/promise'); +const config = require('../config/database'); +const { DOMAIN_CONFIG } = require('../config/domain'); +const { toRelativeUrl } = require('../utils/image-url'); + +async function convertToRelativeUrls() { + try { + const connection = await mysql.createConnection(config); + console.log('✅ 数据库连接成功\n'); + + console.log('🔍 检查数据库中的图片URL...'); + const [goods] = await connection.execute('SELECT id, images FROM goods'); + + console.log(`找到 ${goods.length} 条商品记录\n`); + + let updatedCount = 0; + + // 更新图片URL + for (const item of goods) { + if (item.images) { + let oldImages = item.images; + let newImages = oldImages; + + try { + // 尝试解析JSON + let imagesArray = JSON.parse(oldImages); + + // 转换每个URL + let convertedArray = imagesArray.map(url => toRelativeUrl(url)); + + newImages = JSON.stringify(convertedArray); + + if (oldImages !== newImages) { + await connection.execute( + 'UPDATE goods SET images = ? WHERE id = ?', + [newImages, item.id] + ); + + console.log(`商品ID ${item.id}:`); + console.log(` 旧: ${oldImages}`); + console.log(` 新: ${newImages}`); + console.log('---'); + updatedCount++; + } + } catch (e) { + // 如果不是有效JSON,直接尝试转换 + let converted = toRelativeUrl(oldImages); + if (converted !== oldImages) { + newImages = converted; + await connection.execute( + 'UPDATE goods SET images = ? WHERE id = ?', + [newImages, item.id] + ); + console.log(`商品ID ${item.id} (非JSON格式):`); + console.log(` 旧: ${oldImages}`); + console.log(` 新: ${newImages}`); + console.log('---'); + updatedCount++; + } + } + } + } + + // 检查积分商品 + const [pointsGoods] = await connection.execute('SELECT id, images FROM points_goods'); + if (pointsGoods.length > 0) { + console.log(`\n检查 ${pointsGoods.length} 条积分商品记录...`); + for (const item of pointsGoods) { + if (item.images) { + let oldImages = item.images; + let newImages = oldImages; + + try { + let imagesArray = JSON.parse(oldImages); + let convertedArray = imagesArray.map(url => toRelativeUrl(url)); + newImages = JSON.stringify(convertedArray); + } catch (e) { + newImages = toRelativeUrl(oldImages); + } + + if (oldImages !== newImages) { + await connection.execute( + 'UPDATE points_goods SET images = ? WHERE id = ?', + [newImages, item.id] + ); + console.log(`积分商品ID ${item.id}: 已更新`); + updatedCount++; + } + } + } + } + + await connection.end(); + console.log(`\n✅ 完成!共更新了 ${updatedCount} 条记录`); + } catch (error) { + console.error('❌ 更新失败:', error); + process.exit(1); + } +} + +convertToRelativeUrls(); diff --git a/scripts/test-db.js b/scripts/test-db.js new file mode 100644 index 0000000..bac2d90 --- /dev/null +++ b/scripts/test-db.js @@ -0,0 +1,34 @@ +const mysql = require('mysql2/promise'); + +const config = { + host: '110.42.255.239', + port: 3306, + user: 'admin', + password: 'Admin@123', + database: 'miniprogram' +}; + +async function testConnection() { + try { + console.log('尝试连接到:', config.host, config.port); + const connection = await mysql.createConnection(config); + console.log('✅ 数据库连接成功\n'); + + // 检查所有商品的图片URL + const [goods] = await connection.execute('SELECT id, name, images FROM goods LIMIT 10'); + console.log(`找到 ${goods.length} 条商品记录\n`); + + for (const item of goods) { + console.log(`商品ID ${item.id}: ${item.name}`); + console.log(` 图片: ${item.images}`); + } + + await connection.end(); + console.log('\n✅ 检查完成'); + } catch (error) { + console.error('❌ 连接失败:', error.message); + process.exit(1); + } +} + +testConnection();