From a21dbdd7421881c28243e9a01b2196881219a858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?= <15778543+xufeiyang6017@user.noreply.gitee.com> Date: Tue, 30 Dec 2025 10:48:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=85=E7=90=86server-example=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=EF=BC=9A=E7=A7=BB=E9=99=A4=E8=B0=83=E8=AF=95=E5=92=8C?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-example/gross-weight-fix-error.json | 5 - .../gross-weight-frontend-fix-report.json | 24 - server-example/gross-weight-log-analyzer.js | 135 - server-example/query-database.js | 71 - server-example/query-personnel.js | 125 - server-example/server-mysql-backup-alias.js | 3258 ----------------- server-example/server-mysql-backup-count.js | 2970 --------------- server-example/server-mysql-backup-final.js | 3001 --------------- server-example/server-mysql.backup.js | 2960 --------------- server-example/simple-fix.js | 50 - server-example/simple-gross-weight-check.js | 96 - .../simple-gross-weight-verification.js | 145 - server-example/simple-port-check.js | 36 - server-example/simple_verify.js | 110 - server-example/sync-review-status.js | 108 - server-example/update-product-contacts.js | 139 - 16 files changed, 13233 deletions(-) delete mode 100644 server-example/gross-weight-fix-error.json delete mode 100644 server-example/gross-weight-frontend-fix-report.json delete mode 100644 server-example/gross-weight-log-analyzer.js delete mode 100644 server-example/query-database.js delete mode 100644 server-example/query-personnel.js delete mode 100644 server-example/server-mysql-backup-alias.js delete mode 100644 server-example/server-mysql-backup-count.js delete mode 100644 server-example/server-mysql-backup-final.js delete mode 100644 server-example/server-mysql.backup.js delete mode 100644 server-example/simple-fix.js delete mode 100644 server-example/simple-gross-weight-check.js delete mode 100644 server-example/simple-gross-weight-verification.js delete mode 100644 server-example/simple-port-check.js delete mode 100644 server-example/simple_verify.js delete mode 100644 server-example/sync-review-status.js delete mode 100644 server-example/update-product-contacts.js diff --git a/server-example/gross-weight-fix-error.json b/server-example/gross-weight-fix-error.json deleted file mode 100644 index b408862..0000000 --- a/server-example/gross-weight-fix-error.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "timestamp": "2025-10-08T03:56:27.607Z", - "error": "Access denied for user 'root'@'218.88.54.38' (using password: YES)", - "stack": "Error: Access denied for user 'root'@'218.88.54.38' (using password: YES)\n at Object.createConnectionPromise [as createConnection] (D:\\WeichatAPP\\miniprogram-6\\server-example\\node_modules\\mysql2\\promise.js:19:31)\n at fixGrossWeightValues (D:\\WeichatAPP\\miniprogram-6\\server-example\\fix-gross-weight-values.js:26:30)\n at Object. (D:\\WeichatAPP\\miniprogram-6\\server-example\\fix-gross-weight-values.js:143:1)\n at Module._compile (node:internal/modules/cjs/loader:1688:14)\n at Object..js (node:internal/modules/cjs/loader:1820:10)\n at Module.load (node:internal/modules/cjs/loader:1423:32)\n at Function._load (node:internal/modules/cjs/loader:1246:12)\n at TracingChannel.traceSync (node:diagnostics_channel:322:14)\n at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:171:5)" -} \ No newline at end of file diff --git a/server-example/gross-weight-frontend-fix-report.json b/server-example/gross-weight-frontend-fix-report.json deleted file mode 100644 index b0dc4da..0000000 --- a/server-example/gross-weight-frontend-fix-report.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "timestamp": "2025-10-08T03:57:52.452Z", - "modified": true, - "changes": [ - { - "name": "商品列表API增强", - "applied": true - }, - { - "name": "用户商品API增强", - "applied": false - }, - { - "name": "添加毛重处理中间件", - "applied": true - } - ], - "recommendations": [ - "重启服务器", - "检查前端页面使用的字段名", - "添加商品发布表单的毛重验证", - "检查前端数据处理逻辑" - ] -} \ No newline at end of file diff --git a/server-example/gross-weight-log-analyzer.js b/server-example/gross-weight-log-analyzer.js deleted file mode 100644 index a6c41e7..0000000 --- a/server-example/gross-weight-log-analyzer.js +++ /dev/null @@ -1,135 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 日志文件路径 -const logFilePath = path.join(__dirname, 'logs', 'output.log'); - -// 读取并分析日志文件 -function analyzeGrossWeightLogs() { - try { - console.log(`正在分析日志文件: ${logFilePath}`); - console.log('搜索与grossWeight相关的日志记录...\n'); - - // 读取日志文件内容 - const logContent = fs.readFileSync(logFilePath, 'utf-8'); - const logLines = logContent.split('\n'); - - // 存储找到的毛重相关记录 - const grossWeightRecords = []; - const publishRequestRecords = []; - - // 搜索最近24小时的日志记录 - const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); - - // 遍历日志行 - for (let i = 0; i < logLines.length; i++) { - const line = logLines[i]; - - // 检查时间戳是否在最近24小时内 - const timestampMatch = line.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/); - if (timestampMatch) { - const logDate = new Date(timestampMatch[1]); - if (logDate < twentyFourHoursAgo) { - continue; // 跳过24小时前的日志 - } - } - - // 搜索与grossWeight相关的记录 - if (line.includes('grossWeight')) { - grossWeightRecords.push({ - line: i + 1, - content: line, - timestamp: timestampMatch ? timestampMatch[1] : '未知' - }); - } - - // 搜索商品发布请求 - if (line.includes('/api/product/publish')) { - // 收集发布请求的上下文 - const contextLines = []; - // 向前收集5行 - for (let j = Math.max(0, i - 5); j < Math.min(logLines.length, i + 20); j++) { - contextLines.push(logLines[j]); - } - publishRequestRecords.push({ - line: i + 1, - content: contextLines.join('\n'), - timestamp: timestampMatch ? timestampMatch[1] : '未知' - }); - } - } - - // 输出分析结果 - console.log('===== 最近24小时毛重字段处理分析结果 =====\n'); - - console.log(`找到 ${grossWeightRecords.length} 条与grossWeight相关的日志记录\n`); - - // 显示最近的10条毛重记录 - console.log('最近的10条毛重处理记录:'); - grossWeightRecords.slice(-10).forEach((record, index) => { - console.log(`[${record.timestamp}] 第${record.line}行: ${record.content}`); - }); - - console.log('\n'); - - // 显示最近的商品发布请求及其毛重处理 - console.log(`找到 ${publishRequestRecords.length} 条商品发布请求记录`); - if (publishRequestRecords.length > 0) { - console.log('\n最近的商品发布请求及毛重处理详情:'); - const latestPublish = publishRequestRecords[publishRequestRecords.length - 1]; - console.log(`\n时间: ${latestPublish.timestamp}`); - console.log(`起始行号: ${latestPublish.line}`); - console.log('详细内容:'); - - // 解析请求体中的grossWeight值 - const requestBodyMatch = latestPublish.content.match(/请求体: \{([\s\S]*?)\}/); - if (requestBodyMatch) { - console.log(requestBodyMatch[0]); - - // 提取grossWeight值 - const grossWeightMatch = requestBodyMatch[0].match(/"grossWeight"\s*:\s*(null|\d+(\.\d+)?)/); - if (grossWeightMatch) { - console.log(`\n请求中的毛重值: ${grossWeightMatch[1]}`); - } - } - - // 查找毛重处理的相关日志 - const grossWeightProcessingMatch = latestPublish.content.match(/\[发布商品.*\] 原始毛重值:.*|毛重值.*设置为.*|最终处理的毛重值:.*|grossWeightStored:.*|毛重.*转换为数字/); - if (grossWeightProcessingMatch) { - console.log('\n毛重处理过程:'); - grossWeightProcessingMatch.forEach(processingLine => { - console.log(processingLine); - }); - } - } - - // 生成总结 - console.log('\n===== 分析总结 ====='); - if (grossWeightRecords.length === 0) { - console.log('在最近24小时内没有找到与grossWeight相关的日志记录。'); - } else { - console.log(`在最近24小时内找到了 ${grossWeightRecords.length} 条与grossWeight相关的日志记录。`); - - // 简单统计 - const nullGrossWeightCount = grossWeightRecords.filter(r => r.content.includes('grossWeight: null')).length; - const zeroGrossWeightCount = grossWeightRecords.filter(r => r.content.includes('grossWeight: 0')).length; - const numericGrossWeightCount = grossWeightRecords.filter(r => /grossWeight:\s*\d+\.\d+/.test(r.content)).length; - - console.log(`- 毛重为null的记录数: ${nullGrossWeightCount}`); - console.log(`- 毛重为0的记录数: ${zeroGrossWeightCount}`); - console.log(`- 毛重为数字的记录数: ${numericGrossWeightCount}`); - } - - console.log('\n提示: 如果需要查看更多详细信息,建议直接查看日志文件或使用更专业的日志分析工具。'); - - } catch (error) { - console.error('分析日志时发生错误:', error.message); - console.log('\n建议手动查看日志文件:'); - console.log(`1. 打开文件: ${logFilePath}`); - console.log('2. 搜索关键词: grossWeight'); - console.log('3. 特别关注: 发布商品请求中的grossWeight值和处理过程'); - } -} - -// 执行分析 -analyzeGrossWeightLogs(); \ No newline at end of file diff --git a/server-example/query-database.js b/server-example/query-database.js deleted file mode 100644 index d19a8e6..0000000 --- a/server-example/query-database.js +++ /dev/null @@ -1,71 +0,0 @@ -// 查询数据库中的用户和商品信息 -require('dotenv').config(); -const { Sequelize } = require('sequelize'); - -// 创建数据库连接 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - timezone: '+08:00' // 设置时区为UTC+8 - } -); - -// 执行查询 -async function queryDatabase() { - try { - // 测试连接 - await sequelize.authenticate(); - console.log('✅ 数据库连接成功'); - - // 查询用户信息 - const users = await sequelize.query('SELECT * FROM users LIMIT 10', { type: sequelize.QueryTypes.SELECT }); - console.log('\n👥 用户列表:'); - console.log(users.map(u => ({ - id: u.id, - openid: u.openid, - userId: u.userId, - type: u.type - }))); - - // 查询商品信息,特别是拒绝状态的商品 - const products = await sequelize.query( - 'SELECT productId, sellerId, productName, status, rejectReason, created_at FROM products LIMIT 20', - { type: sequelize.QueryTypes.SELECT } - ); - - console.log('\n🛒 商品列表:'); - console.log(products.map(p => ({ - productId: p.productId, - sellerId: p.sellerId, - productName: p.productName, - status: p.status, - rejectReason: p.rejectReason, - created_at: p.created_at - }))); - - // 特别列出拒绝状态的商品 - const rejectedProducts = products.filter(p => p.status === 'rejected'); - console.log('\n❌ 审核拒绝的商品:'); - console.log(rejectedProducts.map(p => ({ - productId: p.productId, - sellerId: p.sellerId, - productName: p.productName, - status: p.status, - rejectReason: p.rejectReason - }))); - - } catch (error) { - console.error('❌ 查询失败:', error.message); - } finally { - // 关闭连接 - await sequelize.close(); - } -} - -// 运行查询 -queryDatabase(); \ No newline at end of file diff --git a/server-example/query-personnel.js b/server-example/query-personnel.js deleted file mode 100644 index 015f1ae..0000000 --- a/server-example/query-personnel.js +++ /dev/null @@ -1,125 +0,0 @@ -// 查询userlogin数据库中的personnel表,获取所有采购员数据 -require('dotenv').config({ path: 'd:\\xt\\mian_ly\\server-example\\.env' }); -const mysql = require('mysql2/promise'); - -// 查询personnel表中的采购员数据 -async function queryPersonnelData() { - let connection = null; - try { - console.log('连接到userlogin数据库...'); - connection = await mysql.createConnection({ - host: '1.95.162.61', // 直接使用正确的主机地址 - user: process.env.DB_USER || 'root', - password: process.env.DB_PASSWORD || 'schl@2025', // 直接使用默认密码 - database: 'userlogin', - port: process.env.DB_PORT || 3306, - timezone: '+08:00' // 设置时区为UTC+8 - }); - console.log('数据库连接成功\n'); - - // 首先查询personnel表结构 - console.log('=== personnel表结构 ==='); - const [columns] = await connection.execute( - 'SHOW COLUMNS FROM personnel' - ); - console.log('字段列表:', columns.map(col => col.Field).join(', ')); - console.log(); - - // 查询projectName为采购员的数据 - console.log('=== 所有采购员数据 ==='); - const [personnelData] = await connection.execute( - 'SELECT * FROM personnel WHERE projectName = ?', - ['采购员'] - ); - - console.log(`找到 ${personnelData.length} 名采购员记录`); - console.log('\n采购员数据详情:'); - - // 格式化输出数据 - personnelData.forEach((person, index) => { - console.log(`\n${index + 1}. 采购员信息:`); - console.log(` - ID: ${person.id || 'N/A'}`); - console.log(` - 用户ID: ${person.userId || 'N/A'}`); - console.log(` - 姓名: ${person.name || 'N/A'}`); - console.log(` - 别名: ${person.alias || 'N/A'}`); - console.log(` - 电话号码: ${person.phoneNumber || 'N/A'}`); - console.log(` - 公司: ${person.managercompany || 'N/A'}`); - console.log(` - 部门: ${person.managerdepartment || 'N/A'}`); - console.log(` - 角色: ${person.role || 'N/A'}`); - }); - - // 输出JSON格式,便于复制使用 - console.log('\n=== JSON格式数据 (用于客服页面) ==='); - const customerServiceData = personnelData.map((person, index) => ({ - id: index + 1, - managerId: person.userId || `PM${String(index + 1).padStart(3, '0')}`, - managercompany: person.managercompany || '未知公司', - managerdepartment: person.managerdepartment || '采购部', - organization: person.organization || '采购组', - projectName: person.role || '采购员', - name: person.name || '未知', - alias: person.alias || person.name || '未知', - phoneNumber: person.phoneNumber || '', - avatarUrl: '', - score: 990 + (index % 10), - isOnline: index % 4 !== 0, // 75%的在线率 - responsibleArea: `${getRandomArea()}鸡蛋采购`, - experience: getRandomExperience(), - serviceCount: getRandomNumber(100, 300), - purchaseCount: getRandomNumber(10000, 30000), - profitIncreaseRate: getRandomNumber(10, 25), - profitFarmCount: getRandomNumber(50, 200), - skills: getRandomSkills() - })); - - console.log(JSON.stringify(customerServiceData, null, 2)); - - return customerServiceData; - - } catch (error) { - console.error('查询过程中发生错误:', error); - console.error('错误详情:', error.stack); - return null; - } finally { - if (connection) { - await connection.end(); - console.log('\n数据库连接已关闭'); - } - } -} - -// 辅助函数:生成随机区域 -function getRandomArea() { - const areas = ['华北区', '华东区', '华南区', '全国', '西南区', '西北区', '东北区']; - return areas[Math.floor(Math.random() * areas.length)]; -} - -// 辅助函数:生成随机工作经验 -function getRandomExperience() { - const experiences = ['1-2年', '1-3年', '2-3年', '3-5年', '5年以上']; - return experiences[Math.floor(Math.random() * experiences.length)]; -} - -// 辅助函数:生成随机数字 -function getRandomNumber(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; -} - -// 辅助函数:生成随机技能 -function getRandomSkills() { - const allSkills = ['渠道拓展', '供应商维护', '质量把控', '精准把控市场价格', '谈判技巧', '库存管理']; - const skillCount = Math.floor(Math.random() * 3) + 2; // 2-4个技能 - const selectedSkills = []; - - while (selectedSkills.length < skillCount) { - const skill = allSkills[Math.floor(Math.random() * allSkills.length)]; - if (!selectedSkills.includes(skill)) { - selectedSkills.push(skill); - } - } - - return selectedSkills; -} - -// 运行查询 -queryPersonnelData(); diff --git a/server-example/server-mysql-backup-alias.js b/server-example/server-mysql-backup-alias.js deleted file mode 100644 index 5c5ce9b..0000000 --- a/server-example/server-mysql-backup-alias.js +++ /dev/null @@ -1,3258 +0,0 @@ -// ECS服务器示例代码 - Node.js版 (MySQL版本) -const express = require('express'); -const crypto = require('crypto'); -const bodyParser = require('body-parser'); -const { Sequelize, DataTypes, Model, Op } = require('sequelize'); -require('dotenv').config(); - -// 创建Express应用 -const app = express(); -const PORT = process.env.PORT || 3002; - -// 中间件 -app.use(bodyParser.json()); - -// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后) -app.use((req, res, next) => { - // 将UTC时间转换为北京时间(UTC+8) - const now = new Date(); - const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); - const formattedTime = beijingTime.toISOString().replace('Z', '+08:00'); - - console.log(`[${formattedTime}] 收到请求: ${req.method} ${req.url}`); - console.log('请求头:', req.headers); - console.log('请求体:', req.body); - next(); -}); - -// 商品毛重处理中间件 - 确保所有返回的商品数据中毛重字段保持原始值 -app.use((req, res, next) => { - // 保存原始的json方法 - const originalJson = res.json; - - // 重写json方法来处理响应数据 - res.json = function (data) { - // 检查数据中是否包含商品列表 - if (data && typeof data === 'object') { - // 处理/products/list接口的响应 - if (data.products && Array.isArray(data.products)) { - data.products = data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - - // 处理/data字段中的商品列表 - if (data.data && data.data.products && Array.isArray(data.data.products)) { - data.data.products = data.data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - } - - // 调用原始的json方法 - return originalJson.call(this, data); - }; - - next(); -}); - -// MySQL数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - } - } -); - -// 微信小程序配置 -const WECHAT_CONFIG = { - APPID: process.env.WECHAT_APPID || 'your-wechat-appid', - APPSECRET: process.env.WECHAT_APPSECRET || 'your-wechat-appsecret', - TOKEN: process.env.WECHAT_TOKEN || 'your-wechat-token' -}; - -// 显示当前使用的数据库配置(用于调试) -console.log('当前数据库连接配置:'); -console.log(' 主机:', process.env.DB_HOST || 'localhost'); -console.log(' 端口:', process.env.DB_PORT || 3306); -console.log(' 数据库名:', process.env.DB_DATABASE || 'wechat_app'); -console.log(' 用户名:', process.env.DB_USER || 'root'); -console.log(' 密码:', process.env.DB_PASSWORD === undefined || process.env.DB_PASSWORD === '' ? '无密码' : '******'); - -// 测试数据库连接 -async function testDbConnection() { - try { - await sequelize.authenticate(); - console.log('数据库连接成功'); - } catch (error) { - console.error('数据库连接失败:', error); - console.error('\n请检查以下几点:'); - console.error('1. MySQL服务是否已经启动'); - console.error('2. wechat_app数据库是否已创建'); - console.error('3. .env文件中的数据库用户名和密码是否正确'); - console.error('4. 用户名是否有足够的权限访问数据库'); - console.error('\n如果是首次配置,请参考README文件中的数据库设置指南。'); - process.exit(1); - } -} - -testDbConnection(); - -// 定义数据模型 - -// 用户模型 -class User extends Model { } -User.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - openid: { - type: DataTypes.STRING(100), - allowNull: false - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 微信名,必填 - }, - avatarUrl: { - type: DataTypes.TEXT - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 电话号码,必填 - }, - type: { - type: DataTypes.STRING(20), - allowNull: false // 用户身份(buyer/seller/both),必填 - }, - gender: { - type: DataTypes.INTEGER - }, - country: { - type: DataTypes.STRING(50) - }, - province: { - type: DataTypes.STRING(50) - }, - city: { - type: DataTypes.STRING(50) - }, - language: { - type: DataTypes.STRING(20) - }, - session_key: { - type: DataTypes.STRING(255) - }, - // 新增字段 - company: { - type: DataTypes.STRING(255) // 客户公司 - }, - region: { - type: DataTypes.STRING(255) // 客户地区 - }, - level: { - type: DataTypes.STRING(255), - defaultValue: 'company-sea-pools' // 客户等级,默认值为company-sea-pools - }, - demand: { - type: DataTypes.TEXT // 基本需求 - }, - spec: { - type: DataTypes.TEXT // 规格 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'User', - tableName: 'users', - timestamps: false -}); - -// 商品模型 -class Product extends Model { } -Product.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - sellerId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - price: { - type: DataTypes.DECIMAL(10, 2), - allowNull: false - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - specification: { - type: DataTypes.STRING(255) - }, - imageUrls: { - type: DataTypes.TEXT - }, - region: { - type: DataTypes.STRING(100), - defaultValue: '' - }, - status: { - type: DataTypes.STRING(20), - defaultValue: 'pending_review', - validate: { - isIn: [['pending_review', 'reviewed', 'published', 'sold_out', 'rejected', 'hidden']] - } - }, - rejectReason: { - type: DataTypes.TEXT - }, - // 新增联系人相关字段 - product_contact: { - type: DataTypes.STRING(100), - defaultValue: '' - }, - contact_phone: { - type: DataTypes.STRING(20), - defaultValue: '' - }, - // 新增预约相关字段 - reservedCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: '已有几人想要' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Product', - tableName: 'products', - timestamps: false -}); - -// 购物车模型 -class CartItem extends Model { } -CartItem.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - specification: { - type: DataTypes.STRING(255) - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 1 - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - price: { - type: DataTypes.DECIMAL(10, 2) - }, - selected: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - added_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - } -}, { - sequelize, - modelName: 'CartItem', - tableName: 'cart_items', - timestamps: false -}); - -// 联系人表模型 -class Contact extends Model { } -Contact.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 联系人 - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 手机号 - }, - wechat: { - type: DataTypes.STRING(100) // 微信号 - }, - account: { - type: DataTypes.STRING(100) // 账户 - }, - accountNumber: { - type: DataTypes.STRING(100) // 账号 - }, - bank: { - type: DataTypes.STRING(100) // 开户行 - }, - address: { - type: DataTypes.TEXT // 地址 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Contact', - tableName: 'contacts', - timestamps: false -}); - -// 用户管理表模型 -class UserManagement extends Model { } -UserManagement.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - managerId: { - type: DataTypes.STRING(100), - defaultValue: null // 经理ID,默认值为null - }, - company: { - type: DataTypes.STRING(255), - defaultValue: null // 公司,默认值为null - }, - department: { - type: DataTypes.STRING(255), - defaultValue: null // 部门,默认值为null - }, - organization: { - type: DataTypes.STRING(255), - defaultValue: null // 组织,默认值为null - }, - role: { - type: DataTypes.STRING(100), - defaultValue: null // 角色,默认值为null - }, - root: { - type: DataTypes.STRING(100), - defaultValue: null // 根节点,默认值为null - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'UserManagement', - tableName: 'usermanagements', - timestamps: false -}); - -// 定义模型之间的关联关系 - -// 用户和商品的一对多关系 (卖家发布商品) -User.hasMany(Product, { - foreignKey: 'sellerId', // 外键字段名 - sourceKey: 'userId', // 源键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'products', // 别名,用于关联查询 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Product.belongsTo(User, { - foreignKey: 'sellerId', - targetKey: 'userId', // 目标键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'seller' // 别名,用于关联查询 -}); - -// 用户和购物车项的一对多关系 (买家的购物需求/购物车) -User.hasMany(CartItem, { - foreignKey: 'userId', - as: 'cartItems', // 用户的购物车(购物需求)列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(User, { - foreignKey: 'userId', - as: 'buyer' // 别名,明确表示这是购物需求的买家 -}); - -// 商品和购物车项的一对多关系 (商品被添加到购物车) -Product.hasMany(CartItem, { - foreignKey: 'productId', - as: 'cartItems', // 商品出现在哪些购物车中 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(Product, { - foreignKey: 'productId', - as: 'product' // 购物车项中的商品 -}); - -// 用户和联系人的一对多关系 -User.hasMany(Contact, { - foreignKey: 'userId', - as: 'contacts', // 用户的联系人列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Contact.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 联系人所属用户 -}); - -// 用户和用户管理的一对一关系 -User.hasOne(UserManagement, { - foreignKey: 'userId', - as: 'management', // 用户的管理信息 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -UserManagement.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 管理信息所属用户 -}); - -// 同步数据库模型到MySQL -async function syncDatabase() { - try { - // 不使用alter: true,避免尝试修改已有表结构导致的外键约束问题 - await sequelize.sync({ - force: false // 不强制重新创建表 - }); - console.log('数据库模型同步成功'); - } catch (error) { - console.error('数据库模型同步失败:', error); - // 即使同步失败也继续运行,因为我们只需要API功能 - console.log('数据库模型同步失败,但服务器继续运行,使用现有表结构'); - } -} - -syncDatabase(); - -// 解密微信加密数据 -function decryptData(encryptedData, sessionKey, iv) { - try { - // Base64解码 - const sessionKeyBuf = Buffer.from(sessionKey, 'base64'); - const encryptedDataBuf = Buffer.from(encryptedData, 'base64'); - const ivBuf = Buffer.from(iv, 'base64'); - - // AES解密 - const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKeyBuf, ivBuf); - decipher.setAutoPadding(true); - let decoded = decipher.update(encryptedDataBuf, 'binary', 'utf8'); - decoded += decipher.final('utf8'); - - // 解析JSON - return JSON.parse(decoded); - } catch (error) { - console.error('解密失败:', error); - // 提供更具体的错误信息 - if (error.code === 'ERR_OSSL_BAD_DECRYPT') { - throw new Error('登录信息已过期,请重新登录'); - } else if (error.name === 'SyntaxError') { - throw new Error('数据格式错误,解密结果无效'); - } else { - throw new Error('解密失败,请重试'); - } - } -} - -// 获取微信session_key -async function getSessionKey(code) { - const axios = require('axios'); - const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WECHAT_CONFIG.APPID}&secret=${WECHAT_CONFIG.APPSECRET}&js_code=${code}&grant_type=authorization_code`; - - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error('获取session_key失败:', error); - throw new Error('获取session_key失败'); - } -} - -// 创建用户关联记录函数 - 自动为用户创建contacts和usermanagements表的关联记录 -async function createUserAssociations(user) { - try { - if (!user || !user.userId) { - console.error('无效的用户数据,无法创建关联记录'); - return false; - } - - console.log('为用户创建关联记录:', user.userId); - - // 使用事务确保操作原子性 - await sequelize.transaction(async (transaction) => { - // 1. 处理联系人记录 - 使用INSERT ... ON DUPLICATE KEY UPDATE确保无论如何都只保留一条记录 - await sequelize.query( - `INSERT INTO contacts (userId, nickName, phoneNumber, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - nickName = VALUES(nickName), - phoneNumber = VALUES(phoneNumber), - updated_at = NOW()`, - { - replacements: [user.userId, user.nickName || '默认联系人', user.phoneNumber || ''], - transaction: transaction - } - ); - console.log('联系人记录已处理(创建或更新):', user.userId); - - // 2. 处理用户管理记录 - 使用相同策略 - await sequelize.query( - `INSERT INTO usermanagements (userId, created_at, updated_at) - VALUES (?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - updated_at = NOW()`, - { - replacements: [user.userId], - transaction: transaction - } - ); - console.log('用户管理记录已处理(创建或更新):', user.userId); - }); - - console.log('用户关联记录处理成功:', user.userId); - return true; - } catch (error) { - console.error('创建用户关联记录失败:', error.message); - return false; - } -} - -// API路由 - -// 上传用户信息 -app.post('/api/user/upload', async (req, res) => { - try { - const userData = req.body; - console.log('收到用户信息上传请求:', userData); - - // 如果用户信息中包含手机号,检查手机号是否已被其他用户使用 - if (userData.phoneNumber) { - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: userData.phoneNumber, - openid: { [Sequelize.Op.ne]: userData.openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${userData.phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 创建新对象,移除手机号字段 - const userDataWithoutPhone = { ...userData }; - delete userDataWithoutPhone.phoneNumber; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息(不包含手机号) - await User.update( - { - ...userDataWithoutPhone, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - } else { - // 创建新用户 - user = await User.create({ - ...userDataWithoutPhone, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - // 返回成功,但提示手机号已被使用 - return res.json({ - success: true, - code: 200, - message: '用户信息保存成功,但手机号已被其他账号绑定', - data: { - userId: user.userId - }, - phoneNumberConflict: true - }); - } - } - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息 - await User.update( - { - ...userData, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } else { - // 创建新用户 - user = await User.create({ - ...userData, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - res.json({ - success: true, - code: 200, - message: '用户信息保存成功', - data: { - userId: user.userId - }, - phoneNumberConflict: false - }); - } catch (error) { - console.error('保存用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '保存用户信息失败', - error: error.message - }); - } -}); - -// 解密手机号 -app.post('/api/user/decodePhone', async (req, res) => { - try { - const { encryptedData, iv, openid } = req.body; - - // 参数校验 - if (!encryptedData || !iv || !openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数' - }); - } - - // 查找用户的session_key - const user = await User.findOne({ where: { openid } }); - - if (!user || !user.session_key) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录,请先登录', - needRelogin: true - }); - } - - // 解密手机号 - let decryptedData, phoneNumber; - try { - decryptedData = decryptData(encryptedData, user.session_key, iv); - phoneNumber = decryptedData.phoneNumber; - } catch (decryptError) { - // 解密失败,可能是session_key过期,建议重新登录 - return res.status(401).json({ - success: false, - code: 401, - message: decryptError.message || '手机号解密失败', - needRelogin: true - }); - } - - // 检查手机号是否已被其他用户使用 - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: phoneNumber, - openid: { [Sequelize.Op.ne]: openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 返回成功,但不更新手机号,提示用户 - return res.json({ - success: true, - code: 200, - message: '手机号已被其他账号绑定', - phoneNumber: user.phoneNumber, // 返回原手机号 - isNewPhone: false - }); - } - - // 更新用户手机号 - await User.update( - { - phoneNumber: phoneNumber, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 更新用户手机号后,更新关联记录 - const updatedUser = await User.findOne({ where: { openid } }); - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '手机号解密成功', - phoneNumber: phoneNumber, - isNewPhone: true - }); - } catch (error) { - console.error('手机号解密失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '手机号解密失败', - error: error.message - }); - } -}); - -// 处理微信登录,获取openid和session_key -app.post('/api/wechat/getOpenid', async (req, res) => { - try { - const { code } = req.body; - - if (!code) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少code参数' - }); - } - - // 获取openid和session_key - const wxData = await getSessionKey(code); - - if (wxData.errcode) { - throw new Error(`微信接口错误: ${wxData.errmsg}`); - } - - const { openid, session_key, unionid } = wxData; - - // 生成userId - const userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid } - }); - - if (user) { - // 更新用户session_key - await User.update( - { - session_key: session_key, - updated_at: new Date() - }, - { - where: { openid } - } - ); - } else { - // 创建新用户 - // 支持从客户端传入type参数,如果没有则默认为buyer - const userType = req.body.type || 'buyer'; - await User.create({ - openid, - userId, - session_key, - nickName: '微信用户', // 临时占位,等待用户授权 - type: userType, // 使用客户端传入的类型或默认买家身份 - created_at: new Date(), - updated_at: new Date() - }); - - // 为新创建的用户创建关联记录 - const newUser = { userId, openid, nickName: '微信用户' }; - await createUserAssociations(newUser); - } - - res.json({ - success: true, - code: 200, - message: '获取openid成功', - data: { - openid, - userId: user ? user.userId : userId - } - }); - } catch (error) { - console.error('获取openid失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取openid失败', - error: error.message - }); - } -}); - -// 验证用户登录状态 -app.post('/api/user/validate', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'avatarUrl', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录' - }); - } - - res.json({ - success: true, - code: 200, - message: '验证成功', - data: user - }); - } catch (error) { - console.error('验证用户登录状态失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '验证失败', - error: error.message - }); - } -}); - -// 获取用户信息 -app.post('/api/user/get', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - include: [ - { - model: Contact, - as: 'contacts', - attributes: ['id', 'nickName', 'phoneNumber', 'wechat', 'account', 'accountNumber', 'bank', 'address'] - }, - { model: UserManagement, - as: 'management', - attributes: ['id', 'managerId', 'managercompany', 'department', 'organization', 'role', 'root'] - } - ] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - res.json({ - success: true, - code: 200, - message: '获取用户信息成功', - data: user - }); - } catch (error) { - console.error('获取用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户信息失败', - error: error.message - }); - } -}); - -// 更新用户信息 -app.post('/api/user/update', async (req, res) => { - try { - const { openid, ...updateData } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid } - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 更新用户信息 - await User.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 获取更新后的用户信息 - const updatedUser = await User.findOne({ - where: { openid } - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '更新用户信息成功', - data: updatedUser - }); - } catch (error) { - console.error('更新用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新用户信息失败', - error: error.message - }); - } -}); - -// 获取商品列表 - 优化版本确保状态筛选正确应用 -app.post('/api/product/list', async (req, res) => { - try { - const { openid, status, keyword, page = 1, pageSize = 20, testMode = false, viewMode = '' } = req.body; - - // 构建查询条件 - const where = {}; - - // 查找用户 - let user = null; - if (openid && !testMode) { - user = await User.findOne({ where: { openid } }); - // 不再因为用户不存在而返回错误,允许未登录用户访问 - if (!user) { - console.log('用户不存在,但仍允许访问公开商品'); - } - - // 如果是卖家查看自己的商品或管理员,应用相应的权限控制 - // 但对于购物模式,即使有登录信息也返回所有公开商品 - if (user && user.type !== 'admin' && viewMode !== 'shopping') { - where.sellerId = user.userId; - } - } else if (testMode) { - // 测试模式:不验证用户,查询所有已发布的商品 - console.log('测试模式:查询所有已发布的商品'); - } - - // 购物模式:无论用户是否登录,都显示公开商品 - if (viewMode === 'shopping' || !openid) { - console.log('购物模式或未登录用户:显示所有公开商品'); - } - - // 状态筛选 - 直接构建到where对象中,确保不会丢失 - console.log(`当前用户类型: ${user ? user.type : '未知'},请求状态: ${status || '未指定'},测试模式: ${testMode}`); - - // 如果有指定status参数,按参数筛选但同时排除hidden - if (status) { - console.log(`按状态筛选商品: status=${status},并排除hidden状态`); - if (status === 'all') { - // 特殊情况:请求所有商品但仍然排除hidden - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else if (Array.isArray(status)) { - // 如果status是数组,确保不包含hidden - where.status = { [Sequelize.Op.in]: status.filter(s => s !== 'hidden') }; - } else { - // 单个状态值,确保不是hidden - if (status !== 'hidden') { - where.status = { [Sequelize.Op.eq]: status }; - } else { - // 如果明确请求hidden状态,也返回空结果 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } - } - } else { - // 没有指定status参数时 - 直接在where对象中设置状态筛选 - if (user && (user.type === 'seller' || user.type === 'both') && !testMode) { - // 卖家用户且非测试模式 - console.log(`卖家用户 ${user.userId} (类型:${user.type}) 查看自己的所有商品,但排除hidden状态`); - // 卖家可以查看自己的所有商品,但仍然排除hidden状态 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else { - // 测试模式或非卖家用户 - console.log(`测试模式或非卖家用户,使用默认状态筛选: reviewed/published`); - // 默认只显示已审核和已发布的商品,排除hidden和sold_out状态 - where.status = { [Sequelize.Op.in]: ['reviewed', 'published'] }; - } - } - - console.log(`构建的完整查询条件:`, JSON.stringify(where, null, 2)); - - // 关键词搜索 - if (keyword) { - where.productName = { [Sequelize.Op.like]: `%${keyword}%` }; - } - - // 计算偏移量 - const offset = (page - 1) * pageSize; - - // 查询商品列表 - const { count, rows: products } = await Product.findAndCountAll({ - where, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ], - order: [['created_at', 'DESC']], - limit: pageSize, - offset, - // 移除GROUP BY以避免嵌套查询问题 - }); - - // 对于每个商品,单独查询预约人数(cartItems数量) - // 这是为了避免GROUP BY和嵌套查询的复杂问题 - const productsWithSelected = await Promise.all( - products.map(async (product) => { - const productJSON = product.toJSON(); - const cartItemCount = await CartItem.count({ - where: { productId: productJSON.id } - }); - productJSON.selected = cartItemCount; - return productJSON; - }) - ); - - // 添加详细日志,记录查询结果 - console.log(`商品列表查询结果 - 商品数量: ${count}, 商品列表长度: ${productsWithSelected.length}`); - if (productsWithSelected.length > 0) { - console.log(`第一个商品数据:`, JSON.stringify(productsWithSelected[0], null, 2)); - - // 添加selected字段的专门日志 - console.log('商品预约人数(selected字段)统计:'); - productsWithSelected.slice(0, 5).forEach(product => { - console.log(`- ${product.productName}: 预约人数=${product.selected || 0}, 商品ID=${product.productId}`); - }); - } - - // 处理商品列表中的grossWeight字段,确保是数字类型,同时处理imageUrls的JSON解析 - const processedProducts = productsWithSelected.map(product => { - // 创建副本以避免直接修改原始对象 - const productJSON = {...product}; - - // 详细分析毛重字段 - const grossWeightDetails = { - value: productJSON.grossWeight, - type: typeof productJSON.grossWeight, - isEmpty: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined, - isNumeric: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined || !isNaN(parseFloat(productJSON.grossWeight)) && isFinite(productJSON.grossWeight), - parsedValue: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined ? 0 : parseFloat(productJSON.grossWeight) - }; - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - productJSON.grossWeight = finalGrossWeight; - - // 确保selected字段存在并设置为数字类型 - productJSON.selected = productJSON.selected || 0; - productJSON.selected = parseInt(productJSON.selected, 10); - - // 修复imageUrls字段:使用简化但可靠的方法确保返回正确格式的URL数组 - try { - if (productJSON.imageUrls) { - // 强制转换为字符串 - let imageUrlsStr = String(productJSON.imageUrls); - - // 方法:直接查找并提取所有有效的URL - // 匹配所有可能的URL模式,避免包含特殊字符 - const allUrls = []; - - // 查找所有阿里云OSS链接 - const ossUrlPattern = /https?:\/\/my-supplier-photos\.oss-cn-chengdu\.aliyuncs\.com[^"\'\[\]\\,]+/g; - const ossUrls = imageUrlsStr.match(ossUrlPattern) || []; - ossUrls.forEach(url => allUrls.push(url)); - - // 如果没有找到OSS链接,尝试通用URL模式 - if (allUrls.length === 0) { - const genericUrlPattern = /https?:\/\/[^"\'\[\]\\,]+/g; - const genericUrls = imageUrlsStr.match(genericUrlPattern) || []; - genericUrls.forEach(url => allUrls.push(url)); - } - - // 最后的处理:确保返回的是干净的URL数组 - productJSON.imageUrls = allUrls.map(url => { - // 彻底清理每个URL - let cleanUrl = url; - - // 移除所有可能的末尾特殊字符 - while (cleanUrl && (cleanUrl.endsWith('\\') || cleanUrl.endsWith('"') || cleanUrl.endsWith("'"))) { - cleanUrl = cleanUrl.slice(0, -1); - } - - return cleanUrl; - }).filter(url => url && url.startsWith('http')); // 过滤掉空值和无效URL - - // 如果经过所有处理后仍然没有URL,设置一个默认图片或空数组 - if (productJSON.imageUrls.length === 0) { - productJSON.imageUrls = []; - } - - // 记录第一个商品的处理信息 - if (productsWithSelected.indexOf(product) === 0) { - console.log('商品列表 - imageUrls处理结果:'); - console.log('- 原始字符串长度:', imageUrlsStr.length); - console.log('- 提取到的URL数量:', productJSON.imageUrls.length); - console.log('- 最终类型:', Array.isArray(productJSON.imageUrls) ? '数组' : typeof productJSON.imageUrls); - if (productJSON.imageUrls.length > 0) { - console.log('- 第一个URL示例:', productJSON.imageUrls[0]); - console.log('- URL格式验证:', /^https?:\/\/.+$/.test(productJSON.imageUrls[0]) ? '有效' : '无效'); - } - } - } else { - // 如果imageUrls为空,设置为空数组 - productJSON.imageUrls = []; - } - } catch (error) { - console.error(`处理商品${productJSON.productId}的imageUrls时出错:`, error); - // 兜底:确保始终返回数组 - productJSON.imageUrls = []; - } - - // 记录第一个商品的转换信息用于调试 - if (productsWithSelected.indexOf(product) === 0) { - console.log('商品列表 - 第一个商品毛重字段处理:'); - console.log('- 原始值:', grossWeightDetails.value, '类型:', grossWeightDetails.type); - console.log('- 转换后的值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('- selected字段: 值=', productJSON.selected, '类型:', typeof productJSON.selected); - } - - return productJSON; - });; - - // 准备响应数据 - 修改格式以匹配前端期望 - const responseData = { - success: true, - code: 200, - message: '获取商品列表成功', - products: processedProducts, - total: count, - page: page, - pageSize: pageSize, - totalPages: Math.ceil(count / pageSize) - }; - - console.log(`准备返回的响应数据格式:`, JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - - // 添加详细的查询条件日志 - console.log(`最终查询条件:`, JSON.stringify(where, null, 2)); - - res.json(responseData); - } catch (error) { - console.error('获取商品列表失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品列表失败', - error: error.message - }); - } -}); - -// 上传商品 -app.post('/api/products/upload', async (req, res) => { - try { - // 修复毛重字段处理逻辑 - let productData = req.body; - if (productData && productData.productData) { - productData = productData.productData; // 使用正确的productData对象 - } - - // 改进的毛重字段处理逻辑,与编辑API保持一致 - // 详细分析毛重字段 - const grossWeightDetails = { - value: productData.grossWeight, - type: typeof productData.grossWeight, - isEmpty: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined, - isNumeric: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined || !isNaN(parseFloat(productData.grossWeight)) && isFinite(productData.grossWeight), - parsedValue: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined ? 0 : parseFloat(productData.grossWeight) - }; - - // 详细的日志记录 - console.log('上传商品 - 毛重字段详细分析:'); - console.log('- 原始值:', productData.grossWeight, '类型:', typeof productData.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行精确四舍五入,确保3位小数以上的值正确转换 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - productData.grossWeight = finalGrossWeight; - console.log('上传商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('收到商品上传请求:', productData); - - // 验证必要字段 - if (!productData.sellerId || !productData.productName || !productData.price || !productData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的商品信息' - }); - } - - // 检查sellerId是否为openid,如果是则查找对应的userId - let actualSellerId = productData.sellerId; - - // 如果sellerId看起来像一个openid(包含特殊字符如'-'),则尝试查找对应的userId - if (productData.sellerId.includes('-')) { - console.log('sellerId看起来像openid,尝试查找对应的userId'); - const user = await User.findOne({ - where: { - openid: productData.sellerId - } - }); - - if (user && user.userId) { - console.log(`找到了对应的userId: ${user.userId}`); - actualSellerId = user.userId; - } else { - console.error(`未找到对应的用户记录,openid: ${productData.sellerId}`); - return res.status(400).json({ - success: false, - code: 400, - message: '找不到对应的用户记录' - }); - } - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 创建商品,使用实际的sellerId - let product = await Product.create({ - ...productData, - sellerId: actualSellerId, // 使用查找到的userId - productId, - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - product = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 与编辑API保持一致的处理逻辑 - if (product) { - console.log('上传商品 - 处理前grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (product.grossWeight === undefined || product.grossWeight === null || product.grossWeight === '') { - product.grossWeight = 0; - console.log('上传商品 - 检测到空值,已设置为0'); - } else { - // 否则转换为浮点数 - product.grossWeight = parseFloat(product.grossWeight); - } - - console.log('上传商品 - 处理后grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品上传成功', - data: { - productId: product.productId, - product: product - } - }); - } catch (error) { - console.error('商品上传失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '商品上传失败', - error: error.message - }); - } -}); - -// 获取商品详情 -app.post('/api/products/detail', async (req, res) => { - try { - const { productId } = req.body; - - if (!productId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId参数' - }); - } - - // 查询商品详情 - 排除hidden状态商品 - const product = await Product.findOne({ - where: { - productId, - status: { [Sequelize.Op.not]: 'hidden' } - }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 对返回的商品数据中的grossWeight字段进行处理,确保是数字类型 - let updatedProduct = { ...product.toJSON() }; - - // 详细分析毛重字段 - const grossWeightDetails = { - value: updatedProduct.grossWeight, - type: typeof updatedProduct.grossWeight, - isEmpty: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined, - isNumeric: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined || !isNaN(parseFloat(updatedProduct.grossWeight)) && isFinite(updatedProduct.grossWeight), - parsedValue: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined ? 0 : parseFloat(updatedProduct.grossWeight) - }; - - // 详细的日志记录 - console.log('商品详情 - 毛重字段详细分析:'); - console.log('- 原始值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - updatedProduct.grossWeight = finalGrossWeight; - console.log('商品详情 - 最终返回的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - res.json({ - success: true, - code: 200, - message: '获取商品详情成功', - data: updatedProduct - }); - } catch (error) { - console.error('获取商品详情失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品详情失败', - error: error.message - }); - } -}); - -// 修改商品 -app.post('/api/products/edit', async (req, res) => { - try { - const { productId, ...updateData } = req.body; - const { sellerId } = req.body; - - if (!productId || !sellerId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - return res.status(403).json({ - success: false, - code: 403, - message: '您无权修改此商品' - }); - } - - // 更新商品信息 - await Product.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { productId } - } - ); - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '修改商品成功', - data: updatedProduct - }); - } catch (error) { - console.error('修改商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '修改商品失败', - error: error.message - }); - } -}); - -// 删除商品 - 将商品状态设置为hidden表示已删除 -app.post('/api/products/delete', async (req, res) => { - console.log('收到删除商品请求:', req.body); - try { - const { productId, sellerId } = req.body; - - if (!productId || !sellerId) { - console.error('删除商品失败: 缺少productId或sellerId参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - console.error('删除商品失败: 商品不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - console.error('删除商品失败: 权限不足 - 卖家ID不匹配', { expected: product.sellerId, actual: sellerId }); - return res.status(403).json({ - success: false, - code: 403, - message: '您无权删除此商品' - }); - } - - console.log('准备更新商品状态为hidden,当前状态:', product.status); - - // 直接使用商品实例更新状态 - product.status = 'hidden'; - product.updated_at = new Date(); - - try { - // 先尝试保存商品实例 - await product.save(); - console.log('删除商品成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - // 如果保存失败,尝试使用update方法 - try { - const updateResult = await Product.update( - { status: 'hidden', updated_at: new Date() }, - { where: { productId } } - ); - console.log('删除商品成功(使用update方法):', { productId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - // 如果update方法也失败,尝试直接执行SQL语句绕过ORM验证 - try { - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId', - { - replacements: { - status: 'hidden', - updatedAt: new Date(), - productId: productId - } - } - ); - console.log('删除商品成功(使用原始SQL):', { productId }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 从购物车中移除该商品 - const destroyResult = await CartItem.destroy({ - where: { productId } - }); - console.log('从购物车移除商品结果:', destroyResult); - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '删除商品成功', - product: { - productId: updatedProduct.productId, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('删除商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除商品失败', - error: error.message - }); - } -}); - -// 更新商品联系人信息API -app.post('/api/products/update-contacts', async (req, res) => { - try { - // 查找需要更新联系人信息的商品(已发布但联系人信息为空) - const products = await Product.findAll({ - where: { - status: 'published', - [Sequelize.Op.or]: [ - { product_contact: null }, - { product_contact: '' }, - { contact_phone: null }, - { contact_phone: '' } - ] - }, - attributes: ['productId'] - }); - - if (products.length === 0) { - return res.json({ - success: true, - code: 200, - message: '没有需要更新联系人信息的商品' - }); - } - - // 查找所有用户,用于随机分配联系人 - const users = await User.findAll({ - where: { - [Sequelize.Op.or]: [ - { role: 'seller' }, - { role: '管理员' } - ], - [Sequelize.Op.and]: [ - { phoneNumber: { [Sequelize.Op.not]: null } }, - { phoneNumber: { [Sequelize.Op.not]: '' } }, - { nickName: { [Sequelize.Op.not]: null } }, - { nickName: { [Sequelize.Op.not]: '' } } - ] - }, - attributes: ['nickName', 'phoneNumber'] - }); - - if (users.length === 0) { - return res.json({ - success: false, - code: 500, - message: '没有可用的卖家用户来分配联系人信息' - }); - } - - // 随机为每个商品分配联系人信息 - let updatedCount = 0; - for (const product of products) { - // 随机选择一个用户 - const randomUser = users[Math.floor(Math.random() * users.length)]; - - // 更新商品联系人信息 - await Product.update( - { - product_contact: randomUser.nickName, - contact_phone: randomUser.phoneNumber, - updated_at: new Date() - }, - { - where: { productId: product.productId } - } - ); - - updatedCount++; - } - - res.json({ - success: true, - code: 200, - message: `商品联系人信息更新成功,共更新了${updatedCount}个商品` - }); - } catch (error) { - console.error('更新商品联系人信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新商品联系人信息失败', - error: error.message - }); - } -}); - -// 添加商品到购物车 -app.post('/api/cart/add', async (req, res) => { - // 增加全局错误捕获,确保即使在try-catch外部的错误也能被处理 - try { - console.log('收到添加到购物车请求 - 开始处理', req.url); - let cartData = req.body; - console.log('收到添加到购物车请求数据:', cartData); - console.log('请求头:', req.headers); - console.log('请求IP:', req.ip); - - // 兼容客户端请求格式:客户端可能将数据封装在product对象中,并且使用openid而不是userId - if (cartData.product && !cartData.productId) { - // 从product对象中提取数据 - const productData = cartData.product; - console.log('从product对象提取数据:', productData); - console.log('客户端提供的openid:', cartData.openid); - - // 使用openid作为userId - cartData = { - userId: cartData.openid || productData.userId, - productId: productData.productId || productData.id, - productName: productData.productName || productData.name, - quantity: productData.quantity || 1, - price: productData.price, - specification: productData.specification || productData.spec || '', - grossWeight: productData.grossWeight || productData.weight, - yolk: productData.yolk || productData.variety || '', - testMode: productData.testMode || cartData.testMode - }; - console.log('转换后的购物车数据:', cartData); - - // 检查转换后的userId是否存在于users表中 - try { - console.log('开始查询用户信息,openid:', cartData.userId); - const user = await User.findOne({ - where: { openid: cartData.userId } - }); - if (user) { - console.log(`找到对应的用户记录: openid=${cartData.userId}, userId=${user.userId}`); - // 修正:使用数据库中真实的userId而不是openid - cartData.userId = user.userId; - console.log('修正后的userId:', cartData.userId); - } else { - console.error(`未找到openid为 ${cartData.userId} 的用户记录,无法添加到购物车`); - // 重要:找不到用户时返回错误,避免使用无效的userId导致外键约束失败 - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `未找到用户记录: ${cartData.userId}` - }); - } - } catch (error) { - console.error('查询用户信息失败:', error); - // 查询失败时也返回错误 - return res.status(400).json({ - success: false, - code: 400, - message: '查询用户信息失败', - error: error.message - }); - } - } - - // 验证必要字段 - if (!cartData.userId || !cartData.productId || !cartData.productName || !cartData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的购物车信息', - missingFields: [ - !cartData.userId ? 'userId' : '', - !cartData.productId ? 'productId' : '', - !cartData.productName ? 'productName' : '', - !cartData.quantity ? 'quantity' : '' - ].filter(Boolean) - }); - } - - // 先验证用户ID是否存在于users表中 - try { - const userExists = await User.findOne({ - where: { userId: cartData.userId } - }); - if (!userExists) { - console.error(`用户ID ${cartData.userId} 不存在于users表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `用户ID ${cartData.userId} 不存在` - }); - } else { - console.log(`用户ID ${cartData.userId} 存在于users表中,用户验证通过`); - } - } catch (error) { - console.error('验证用户ID失败:', error); - return res.status(400).json({ - success: false, - code: 400, - message: '验证用户信息失败', - error: error.message - }); - } - - // 检查商品是否存在以及是否为hidden状态 - console.log(`检查商品ID: ${cartData.productId} 是否存在于products表中`); - const product = await Product.findOne({ - where: { - productId: cartData.productId - } - }); - - if (!product) { - console.error(`商品ID ${cartData.productId} 不存在于products表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '商品不存在或已被移除', - error: `未找到商品ID: ${cartData.productId}` - }); - } else { - console.log(`商品ID ${cartData.productId} 存在于products表中,商品名称: ${product.productName}`); - } - - if (product.status === 'hidden') { - return res.status(400).json({ - success: false, - code: 400, - message: '该商品已下架,无法添加到购物车' - }); - } - - // 在testMode下,不执行实际的数据库操作,直接返回成功 - if (cartData.testMode) { - console.log('测试模式:跳过实际的数据库操作'); - res.json({ - success: true, - code: 200, - message: '测试模式:添加到购物车成功', - data: { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity - } - }); - return; - } - - // 检查是否已存在相同商品 - const existingItem = await CartItem.findOne({ - where: { - userId: cartData.userId, - productId: cartData.productId - } - }); - - // 添加try-catch捕获外键约束错误 - try { - console.log(`准备创建/更新购物车项: userId=${cartData.userId}, productId=${cartData.productId}`); - if (existingItem) { - // 已存在,更新数量 - await CartItem.update( - { - quantity: existingItem.quantity + cartData.quantity, - updated_at: new Date() - }, - { - where: { - id: existingItem.id - } - } - ); - console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); - } else { - // 不存在,创建新购物车项 - console.log('创建新购物车项,所有字段:', { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity, - price: cartData.price, - specification: cartData.specification, - grossWeight: cartData.grossWeight, - yolk: cartData.yolk - }); - // 重要:在创建前再次验证数据完整性 - if (!cartData.userId || !cartData.productId) { - throw new Error(`数据不完整: userId=${cartData.userId}, productId=${cartData.productId}`); - } - await CartItem.create({ - ...cartData, - added_at: new Date() - }); - console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); - } - } catch (createError) { - console.error('创建/更新购物车项失败,可能是外键约束问题:', createError); - console.error('详细错误信息:', { - name: createError.name, - message: createError.message, - stack: createError.stack, - sql: createError.sql || '无SQL信息', - userId: cartData.userId, - productId: cartData.productId - }); - - // 检测是否是外键约束错误 - if (createError.name === 'SequelizeForeignKeyConstraintError' || createError.message.includes('foreign key')) { - // 区分是用户ID还是商品ID问题 - let errorField = 'productId'; - let errorMessage = '商品信息已更新,请刷新页面后重试'; - - if (createError.message.includes('userId') || createError.message.includes('user') || createError.message.toLowerCase().includes('user')) { - errorField = 'userId'; - errorMessage = '用户信息无效,请重新登录后重试'; - } - - return res.status(400).json({ - success: false, - code: 400, - message: errorMessage, - error: `外键约束错误: ${errorField} 不存在或已失效`, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 其他类型的错误也返回400状态码,避免500错误 - return res.status(400).json({ - success: false, - code: 400, - message: '添加购物车项失败,请稍后重试', - error: createError.message, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 更新商品的预约人数 - 更健壮的实现 - try { - console.log(`尝试更新商品预约人数: productId=${cartData.productId}`); - - // 先验证商品是否存在 - const productCheck = await Product.findOne({where: {productId: cartData.productId}}); - if (productCheck) { - // 商品存在,才进行更新 - await Product.increment('reservedCount', {by: 1, where: {productId: cartData.productId}}); - console.log(`商品预约人数更新成功: productId=${cartData.productId}, 新数量=${productCheck.reservedCount + 1}`); - } else { - console.error(`更新商品预约人数失败: 商品ID ${cartData.productId} 不存在`); - } - } catch (updateError) { - console.error(`更新商品预约人数失败:`, updateError); - // 继续执行,不中断主要流程 - } - - res.json({ - success: true, - code: 200, - message: '添加到购物车成功' - }); - } catch (error) { - console.error('添加到购物车失败:', error); - console.error('全局错误捕获,详细信息:', { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - }); - - // 增强的错误处理 - 强制所有错误返回400状态码 - console.error('全局错误处理 - 捕获到未处理的错误:', error); - const statusCode = 400; // 强制所有错误返回400状态码,避免前端显示500错误 - let errorMessage = '添加到购物车失败'; - - // 更精确地检测外键约束错误 - if (error.name === 'SequelizeForeignKeyConstraintError' || - error.message.toLowerCase().includes('foreign key') || - error.message.toLowerCase().includes('constraint fails') || - error.message.toLowerCase().includes('constraint')) { - errorMessage = '添加到购物车失败:商品或用户信息已更新,请刷新页面后重试'; - console.error('检测到外键约束相关错误,返回400状态码'); - } - - console.log(`准备返回错误响应 - 状态码: ${statusCode}, 消息: ${errorMessage}`); - - // 确保响应能够正确发送 - try { - res.status(statusCode).json({ - success: false, - code: statusCode, - message: errorMessage, - error: error.message, - errorDetails: { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - } - }); - } catch (resError) { - console.error('发送错误响应失败:', resError); - // 即使发送响应失败,也尝试以文本格式发送 - try { - res.status(400).send('添加到购物车失败,请刷新页面后重试'); - } catch (finalError) { - console.error('无法发送任何响应:', finalError); - } - } - } -}); - -// 获取购物车信息 -app.post('/api/cart/get', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 查询购物车信息 - 排除关联商品为hidden或sold_out状态的项 - const cartItems = await CartItem.findAll({ - where: { userId }, - include: [ - { - model: Product, - as: 'product', - attributes: ['productName', 'price', 'quantity', 'status', 'specification', 'grossWeight', 'yolk'], - where: { - status: { [Sequelize.Op.notIn]: ['hidden', 'sold_out'] } - } - } - ], - order: [['added_at', 'DESC']] - }); - - res.json({ - success: true, - code: 200, - message: '获取购物车信息成功', - data: { - cartItems - } - }); - } catch (error) { - console.error('获取购物车信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取购物车信息失败', - error: error.message - }); - } -}); - -// 更新购物车项 -app.post('/api/cart/update', async (req, res) => { - try { - const { id, quantity, selected } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 构建更新数据 - const updateData = {}; - if (quantity !== undefined) updateData.quantity = quantity; - if (selected !== undefined) updateData.selected = selected; - updateData.updated_at = new Date(); - - // 更新购物车项 - await CartItem.update(updateData, { - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '更新购物车成功' - }); - } catch (error) { - console.error('更新购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新购物车失败', - error: error.message - }); - } -}); - -// 删除购物车项 -app.post('/api/cart/delete', async (req, res) => { - try { - const { id } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 删除购物车项 - await CartItem.destroy({ - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '删除购物车项成功' - }); - } catch (error) { - console.error('删除购物车项失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除购物车项失败', - error: error.message - }); - } -}); - -// 清空购物车 -app.post('/api/cart/clear', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 清空购物车 - await CartItem.destroy({ - where: { userId } - }); - - res.json({ - success: true, - code: 200, - message: '清空购物车成功' - }); - } catch (error) { - console.error('清空购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '清空购物车失败', - error: error.message - }); - } -}); - -// 测试连接接口 -app.get('/api/test-connection', async (req, res) => { - try { - // 检查数据库连接 - await sequelize.authenticate(); - - res.json({ - success: true, - code: 200, - message: '服务器连接成功,数据库可用', - timestamp: new Date().toISOString(), - serverInfo: { - port: PORT, - nodeVersion: process.version, - database: 'MySQL', - status: 'running' - } - }); - } catch (error) { - res.status(500).json({ - success: false, - code: 500, - message: '服务器连接失败', - error: error.message - }); - } -}); - -// 用户类型调试接口 - 增强版:用于排查用户类型和商品显示问题 -app.post('/api/user/debug', async (req, res) => { - try { - const { openid } = req.body; - - console.log('收到用户调试请求,openid:', openid); - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查询用户信息 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在', - debugInfo: { - searchCriteria: { openid }, - timestamp: new Date().toISOString() - } - }); - } - - // 查询该用户的商品统计信息 - const totalProducts = await Product.count({ where: { sellerId: user.userId } }); - const pendingProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'pending_review' - } - }); - const reviewedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'reviewed' - } - }); - const publishedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'published' - } - }); - const soldOutProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'sold_out' - } - }); - - // 判断用户是否有权限查看所有商品 - const canViewAllProducts = ['seller', 'both', 'admin'].includes(user.type); - - // 获取该用户的最新5个商品信息(用于调试) - const latestProducts = await Product.findAll({ - where: { sellerId: user.userId }, - limit: 5, - order: [['created_at', 'DESC']], - attributes: ['productId', 'productName', 'status', 'created_at'] - }); - - const responseData = { - success: true, - code: 200, - message: '获取用户调试信息成功', - userInfo: user, - productStats: { - total: totalProducts, - pendingReview: pendingProducts, - reviewed: reviewedProducts, - published: publishedProducts, - soldOut: soldOutProducts - }, - permissionInfo: { - canViewAllProducts: canViewAllProducts, - userType: user.type, - allowedTypesForViewingAllProducts: ['seller', 'both', 'admin'] - }, - latestProducts: latestProducts, - debugInfo: { - userCount: await User.count(), - totalProductsInSystem: await Product.count(), - timestamp: new Date().toISOString(), - serverTime: new Date().toLocaleString('zh-CN') - } - }; - - console.log('调试信息返回数据:', JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - res.json(responseData); - } catch (error) { - console.error('获取用户调试信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户调试信息失败', - error: error.message, - debugInfo: { - errorStack: error.stack, - timestamp: new Date().toISOString() - } - }); - } -}); - -// 下架商品接口 - 将商品状态设置为sold_out表示已下架 -app.post('/api/product/hide', async (req, res) => { - console.log('收到下架商品请求:', req.body); - - try { - const { openid, productId } = req.body; - - // 验证请求参数 - if (!openid || !productId) { - console.error('下架商品失败: 缺少必要参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要参数: openid和productId都是必需的' - }); - } - - // 查找用户 - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('下架商品失败: 用户不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - console.log('找到用户信息:', { userId: user.userId, nickName: user.nickName }); - - // 查找商品并验证所有权 - 直接使用userId,因为商品创建时使用的就是userId - const product = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - - if (!product) { - console.error('下架商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 记录当前状态,用于调试 - console.log('当前商品状态:', product.status, '允许的状态列表:', Product.rawAttributes.status.validate.isIn); - console.log('商品所属卖家ID:', product.sellerId); - console.log('用户ID信息对比:', { userId: user.userId, id: user.id }); - - console.log('准备更新商品状态为sold_out,当前状态:', product.status); - - // 更新商品状态为已下架(sold_out) - 尝试多种更新方式确保成功 - try { - // 方法1: 直接保存实例 - product.status = 'sold_out'; - product.updated_at = new Date(); - await product.save(); - console.log('商品下架成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - try { - // 方法2: 使用update方法 - const updateResult = await Product.update( - { status: 'sold_out', updated_at: new Date() }, - { where: { productId: productId, sellerId: user.userId } } - ); - console.log('商品下架成功(使用update方法):', { productId: productId, sellerIdType: typeof user.userId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - try { - // 方法3: 直接执行SQL语句绕过ORM验证 - const replacements = { - status: 'sold_out', - updatedAt: new Date(), - productId: productId, - sellerId: user.userId - }; - - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId AND sellerId = :sellerId', - { - replacements: replacements - } - ); - console.log('商品下架成功(使用原始SQL):', { productId: product.productId, productName: product.productName }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: product.sellerId // 使用找到的商品的sellerId进行查询 - } - }); - - res.json({ - success: true, - code: 200, - message: '商品下架成功', - product: { - productId: updatedProduct.productId, - productName: updatedProduct.productName, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('下架商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '下架商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 发布商品API -app.post('/api/product/publish', async (req, res) => { - console.log('收到发布商品请求:', req.body); // 记录完整请求体 - - try { - const { openid, product } = req.body; - - // 验证必填字段 - console.log('验证请求参数: openid=', !!openid, ', product=', !!product); - if (!openid || !product) { - console.error('缺少必要参数: openid=', openid, 'product=', product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid或product对象)' - }); - } - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price, '转换为数字=', parseFloat(product.price)); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity, '转换为数字=', parseInt(product.quantity)); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误和字段值详情 - const validationErrors = []; - const fieldDetails = {}; - - // 检查商品名称 - fieldDetails.productName = { - value: product.productName, - type: typeof product.productName, - isEmpty: !product.productName || product.productName.trim() === '' - }; - if (fieldDetails.productName.isEmpty) { - console.error('商品名称为空'); - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } - - // 检查价格 - fieldDetails.price = { - value: product.price, - type: typeof product.price, - isNumber: !isNaN(parseFloat(product.price)) && isFinite(product.price), - parsedValue: parseFloat(product.price), - isValid: !isNaN(parseFloat(product.price)) && isFinite(product.price) && parseFloat(product.price) > 0 - }; - if (!product.price) { - console.error('价格为空'); - validationErrors.push('价格为必填项'); - } else if (!fieldDetails.price.isNumber) { - console.error('价格不是有效数字: price=', product.price); - validationErrors.push('价格必须是有效数字格式'); - } else if (fieldDetails.price.parsedValue <= 0) { - console.error('价格小于等于0: price=', product.price, '转换为数字后=', fieldDetails.price.parsedValue); - validationErrors.push('价格必须大于0'); - } - - // 检查数量 - fieldDetails.quantity = { - value: product.quantity, - type: typeof product.quantity, - isNumeric: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity), - parsedValue: Math.floor(parseFloat(product.quantity)), - isValid: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity) && parseFloat(product.quantity) > 0 - }; - if (!product.quantity) { - console.error('数量为空'); - validationErrors.push('数量为必填项'); - } else if (!fieldDetails.quantity.isNumeric) { - console.error('数量不是有效数字: quantity=', product.quantity); - validationErrors.push('数量必须是有效数字格式'); - } else if (fieldDetails.quantity.parsedValue <= 0) { - console.error('数量小于等于0: quantity=', product.quantity, '转换为数字后=', fieldDetails.quantity.parsedValue); - validationErrors.push('数量必须大于0'); - } - - // 改进的毛重字段处理逻辑 - 与其他API保持一致 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('发布商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保商品名称不超过数据库字段长度限制 - if (product.productName && product.productName.length > 255) { - console.error('商品名称过长: 长度=', product.productName.length); - validationErrors.push('商品名称不能超过255个字符'); - } - - // 如果有验证错误,一次性返回所有错误信息和字段详情 - if (validationErrors.length > 0) { - console.error('验证失败 - 详细信息:', JSON.stringify({ - errors: validationErrors, - fieldDetails: fieldDetails - }, null, 2)); - - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors, - detailedMessage: validationErrors.join('; '), - fieldDetails: fieldDetails - }); - } - - // 查找用户 - console.log('开始查找用户: openid=', openid); - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - console.log('找到用户:', { userId: user.userId, nickName: user.nickName, type: user.type }); - - // 验证用户类型 - console.log(`验证用户类型: 用户ID=${user.userId}, 类型=${user.type}`); - if (user.type !== 'seller' && user.type !== 'both') { - console.error(`商品发布失败: 用户${user.userId}类型为${user.type},需要seller或both类型`); - return res.status(403).json({ - success: false, - code: 403, - message: '只有卖家才能发布商品,请在个人资料中修改用户类型' - }); - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - console.log('生成商品ID:', productId); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行正确的四舍五入 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - console.log('发布商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 创建商品 - console.log('准备创建商品:', { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, - sellerId: user.userId - }); - - const newProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk || '', - specification: product.specification || '', - status: 'pending_review', // 默认状态为待审核 - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - const createdProduct = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - if (createdProduct) { - console.log('发布商品 - 数据库查询后grossWeight:', createdProduct.grossWeight, '类型:', typeof createdProduct.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品发布成功', - product: createdProduct, - productId: productId - }); - - } catch (error) { - console.error('发布商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '发布商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`服务器运行在 http://localhost:${PORT}`); - console.log('注意:当前服务器已添加详细日志记录,用于排查发布商品问题'); - console.log('调试API: POST /api/user/debug - 用于查看用户类型信息'); - console.log(`测试连接接口: http://localhost:${PORT}/api/test-connection`); -}); - -// 编辑商品API - 用于审核失败商品重新编辑 -app.post('/api/product/edit', async (req, res) => { - console.log('收到编辑商品请求 - 详细信息:'); - console.log('- 请求路径:', req.url); - console.log('- 请求方法:', req.method); - console.log('- 请求完整body:', req.body); - console.log('- 服务器端口:', PORT); - console.log('- 环境变量:', process.env.PORT); - - try { - // 正确解析请求参数,处理嵌套的productData结构 - let openid = req.body.openid; - let productId = req.body.productId; - let status = req.body.status; - let testMode = req.body.testMode; - let product = req.body.product; - - // 处理多层嵌套的productData结构 - if (!product && req.body.productData) { - // 处理第一种情况: { productData: { openid, productId, product: { ... } } } - if (req.body.productData.product) { - product = req.body.productData.product; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - // 处理第二种情况: { productData: { openid, productId, productName, price, ... } } - else { - product = req.body.productData; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - } - - // 调试日志 - console.log('解析后参数:', { openid, productId, status, testMode, product: !!product }); - - console.log('收到编辑商品请求,包含状态参数:', { openid, productId, status, testMode }); - - // 验证必填字段 - if (!openid || !productId || !product) { - console.error('缺少必要参数: openid=', !!openid, 'productId=', !!productId, 'product=', !!product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid、productId或product对象)' - }); - } - - // 查找用户 - let user = null; - - // 测试模式下的特殊处理 - if (testMode) { - console.log('测试模式:尝试查找或创建测试用户'); - // 首先尝试查找openid为'test_openid'的用户 - user = await User.findOne({ - where: { openid: 'test_openid' } - }); - - if (!user) { - // 如果不存在,创建一个新的测试用户 - console.log('测试模式:创建测试用户'); - try { - user = await User.create({ - openid: 'test_openid', - userId: 'test_user_id', - nickName: '测试用户', - type: 'seller' - }); - } catch (createError) { - console.error('测试模式:创建测试用户失败', createError); - // 如果创建失败,尝试查找数据库中的第一个用户 - user = await User.findOne({ - order: [['id', 'ASC']] - }); - if (user) { - console.log('测试模式:使用数据库中的现有用户', user.userId); - } - } - } else { - console.log('测试模式:使用已存在的测试用户', user.userId); - } - } else { - // 非测试模式:按常规方式查找用户 - user = await User.findOne({ where: { openid } }); - } - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - // 查找商品 - let existingProduct = null; - - if (testMode) { - // 测试模式:如果找不到商品,尝试使用测试商品或创建一个新的测试商品 - existingProduct = await Product.findOne({ - where: { - productId: productId - } - }); - - // 如果找不到指定的商品,创建一个新的测试商品 - if (!existingProduct) { - console.log('测试模式:创建测试商品'); - try { - existingProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: '测试商品', - price: 99.99, - quantity: 100, - grossWeight: 0, // 默认为0而不是5,符合用户需求 - yolk: '测试描述', - specification: '测试规格', - status: 'rejected', // 设置为可编辑状态 - created_at: new Date(), - updated_at: new Date() - }); - console.log('测试模式:测试商品创建成功'); - } catch (createProductError) { - console.error('测试模式:创建测试商品失败', createProductError); - } - } - } else { - // 非测试模式:验证商品所有权 - existingProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - } - - if (!existingProduct) { - console.error('编辑商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 验证商品状态是否允许编辑 - if (!['rejected', 'sold_out', 'pending_review', 'reviewed'].includes(existingProduct.status)) { - console.error(`编辑商品失败: 商品状态(${existingProduct.status})不允许编辑`, { - productId: productId, - sellerId: user.userId, - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - }); - return res.status(403).json({ - success: false, - code: 403, - message: '只有审核失败、已下架、审核中或已审核的商品才能编辑', - debugInfo: { - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - } - }); - } - - // 记录商品编辑信息,用于调试 - console.log(`允许编辑商品: productId=${productId}, status=${existingProduct.status}, sellerId=${user.userId}`); - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误 - const validationErrors = []; - - // 检查商品名称 - if (!product.productName || product.productName.trim() === '') { - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } else if (product.productName.length > 255) { - validationErrors.push('商品名称不能超过255个字符'); - } - - // 检查价格 - if (!product.price) { - validationErrors.push('价格为必填项'); - } else if (isNaN(parseFloat(product.price)) || parseFloat(product.price) <= 0) { - validationErrors.push('价格必须是大于0的有效数字'); - } - - // 检查数量 - if (!product.quantity) { - validationErrors.push('数量为必填项'); - } else if (isNaN(parseInt(product.quantity)) || parseInt(product.quantity) <= 0) { - validationErrors.push('数量必须是大于0的有效数字'); - } - - // 改进的毛重字段处理逻辑,与其他API保持一致,空值默认设为0 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('编辑商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保grossWeight值是数字类型 - const finalGrossWeight = Number(grossWeightDetails.parsedValue); - console.log('编辑商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 如果有验证错误,返回错误信息 - if (validationErrors.length > 0) { - console.error('验证失败 - 错误:', validationErrors.join('; ')); - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors - }); - } - - // 准备更新的商品数据 - const updatedProductData = { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk, - specification: product.specification, - // 优先使用前端传递的status参数,如果没有传递则使用原来的逻辑 - status: status && ['pending_review', 'published'].includes(status) ? status : - (product.resubmit && ['rejected', 'sold_out'].includes(existingProduct.status)) ? 'pending_review' : existingProduct.status, - rejectReason: (status === 'pending_review' || (product.resubmit && existingProduct.status === 'rejected')) ? null : existingProduct.rejectReason, // 提交审核时清除拒绝原因 - updated_at: new Date() - }; - - console.log('准备更新商品数据:', { productId, updatedStatus: updatedProductData.status, fromStatus: existingProduct.status }); - - // 更新商品 - const [updatedCount] = await Product.update(updatedProductData, { - where: testMode ? { - // 测试模式:只根据productId更新 - productId: productId - } : { - // 非测试模式:验证商品所有权 - productId: productId, - sellerId: user.userId - } - }); - - // 检查更新是否成功 - if (updatedCount === 0) { - console.error('商品更新失败: 没有找到匹配的商品或权限不足'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品更新失败: 没有找到匹配的商品或权限不足' - }); - } - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ where: { productId: productId } }); - - console.log('查询数据库后 - 更新的商品信息:', { - grossWeight: updatedProduct?.grossWeight, - grossWeightType: typeof updatedProduct?.grossWeight, - productId: updatedProduct?.productId, - status: updatedProduct?.status - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 注意:这里检查undefined和null,并且对于空字符串或5的情况也进行处理 - if (updatedProduct) { - console.log('处理前 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (updatedProduct.grossWeight === undefined || updatedProduct.grossWeight === null || updatedProduct.grossWeight === '') { - updatedProduct.grossWeight = 0; - console.log('检测到空值 - 已设置为0'); - } else { - // 否则转换为浮点数 - updatedProduct.grossWeight = parseFloat(updatedProduct.grossWeight); - } - - console.log('处理后 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - } - - console.log('商品编辑成功:', { - productId: productId, - productName: product.productName, - oldStatus: existingProduct.status, // 记录更新前的状态 - newStatus: updatedProduct.status, // 记录更新后的状态 - grossWeight: updatedProduct.grossWeight // 记录处理后的毛重值 - }); - - // 根据新的状态生成适当的返回消息 - let returnMessage = ''; - if (updatedProduct.status === 'pending_review') { - returnMessage = '商品编辑成功,已重新提交审核'; - } else if (updatedProduct.status === 'published') { - returnMessage = '商品编辑成功,已上架'; - } else if (updatedProduct.status === existingProduct.status) { - returnMessage = '商品编辑成功,状态保持不变'; - } else { - returnMessage = '商品编辑成功'; - } - - res.json({ - success: true, - code: 200, - message: returnMessage, - product: updatedProduct - }); - } catch (error) { - console.error('编辑商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '编辑商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 提交入驻申请 -app.post('/api/settlement/submit', async (req, res) => { - try { - const { openid, - collaborationid, - cooperation, - company, - phoneNumber, - province, - city, - district, - businesslicenseurl, - proofurl, - brandurl - } = req.body; - - console.log('收到入驻申请:', req.body); - - // 验证必填字段 - if (!openid || !collaborationid || !cooperation || !company || !phoneNumber || !province || !city || !district) { - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整的申请信息' - }); - } - - // 查找用户信息 - const user = await User.findOne({ where: { openid } }); - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 检查用户是否已有入驻信息且状态为审核中 - if (user.collaborationid && user.partnerstatus === 'underreview') { - return res.status(400).json({ - success: false, - code: 400, - message: '您已有待审核的入驻申请,请勿重复提交' - }); - } - - // 更新用户表中的入驻信息 - // 转换collaborationid为中文(使用明确的英文标识以避免混淆) - let collaborationidCN = collaborationid; - if (collaborationid === 'chicken') { - collaborationidCN = '鸡场'; - } else if (collaborationid === 'trader') { - collaborationidCN = '贸易商'; - } - // 兼容旧的wholesale标识 - else if (collaborationid === 'wholesale') { - collaborationidCN = '贸易商'; - } - - // 转换cooperation为中文合作模式(使用明确的英文标识以避免混淆) - // 直接使用传入的中文合作模式,确保支持:资源委托、自主定义销售、区域包场合作、其他 - let cooperationCN = cooperation; - - // 如果传入的是英文值,则进行映射 - if (cooperation === 'resource_delegation') { - cooperationCN = '资源委托'; - } else if (cooperation === 'self_define_sales') { - cooperationCN = '自主定义销售'; - } else if (cooperation === 'regional_exclusive') { - cooperationCN = '区域包场合作'; - } else if (cooperation === 'other') { - cooperationCN = '其他'; - } - // 兼容旧的wholesale标识 - else if (cooperation === 'wholesale') { - cooperationCN = '资源委托'; - } - // 兼容旧的self_define标识 - else if (cooperation === 'self_define') { - cooperationCN = '自主定义销售'; - } - // 确保存储的是中文合作模式 - - // 执行更新操作 - const updateResult = await User.update({ - collaborationid: collaborationidCN, // 合作商身份(中文) - cooperation: cooperationCN, // 合作模式(中文) - company: company, // 公司名称 - phoneNumber: phoneNumber, // 电话号码 - province: province, // 省份 - city: city, // 城市 - district: district, // 区县 - businesslicenseurl: businesslicenseurl || '', // 营业执照 - NOT NULL约束,使用空字符串 - proofurl: proofurl || '', // 证明材料 - NOT NULL约束,使用空字符串 - brandurl: brandurl || '', // 品牌授权链文件 - partnerstatus: 'underreview', // 合作商状态明确设置为审核中,覆盖数据库默认值 - updated_at: new Date() - }, { - where: { userId: user.userId } - }); - - // 验证更新是否成功 - const updatedUser = await User.findOne({ where: { userId: user.userId } }); - console.log('更新后的用户状态:', updatedUser.partnerstatus); - - // 双重确认:如果状态仍不是underreview,再次更新 - if (updatedUser && updatedUser.partnerstatus !== 'underreview') { - console.warn('检测到状态未更新正确,执行二次更新:', updatedUser.partnerstatus); - await User.update({ - partnerstatus: 'underreview' - }, { - where: { userId: user.userId } - }); - } - - console.log('用户入驻信息更新成功,用户ID:', user.userId); - - res.json({ - success: true, - code: 200, - message: '入驻申请提交成功,请等待审核', - data: { - status: 'pending' - } - }); - } catch (error) { - console.error('提交入驻申请失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '提交入驻申请失败: ' + error.message - }); - } -}); - -// 导出模型和Express应用供其他模块使用 -module.exports = { - User, - Product, - CartItem, - sequelize, - createUserAssociations, - app, - PORT -}; \ No newline at end of file diff --git a/server-example/server-mysql-backup-count.js b/server-example/server-mysql-backup-count.js deleted file mode 100644 index c8dee55..0000000 --- a/server-example/server-mysql-backup-count.js +++ /dev/null @@ -1,2970 +0,0 @@ -// ECS服务器示例代码 - Node.js版 (MySQL版本) -const express = require('express'); -const crypto = require('crypto'); -const bodyParser = require('body-parser'); -const { Sequelize, DataTypes, Model, Op } = require('sequelize'); -require('dotenv').config(); - -// 创建Express应用 -const app = express(); -const PORT = process.env.PORT || 3002; - -// 中间件 -app.use(bodyParser.json()); - -// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后) -app.use((req, res, next) => { - // 将UTC时间转换为北京时间(UTC+8) - const now = new Date(); - const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); - const formattedTime = beijingTime.toISOString().replace('Z', '+08:00'); - - console.log(`[${formattedTime}] 收到请求: ${req.method} ${req.url}`); - console.log('请求头:', req.headers); - console.log('请求体:', req.body); - next(); -}); - -// 商品毛重处理中间件 - 确保所有返回的商品数据中毛重字段保持原始值 -app.use((req, res, next) => { - // 保存原始的json方法 - const originalJson = res.json; - - // 重写json方法来处理响应数据 - res.json = function (data) { - // 检查数据中是否包含商品列表 - if (data && typeof data === 'object') { - // 处理/products/list接口的响应 - if (data.products && Array.isArray(data.products)) { - data.products = data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - - // 处理/data字段中的商品列表 - if (data.data && data.data.products && Array.isArray(data.data.products)) { - data.data.products = data.data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - } - - // 调用原始的json方法 - return originalJson.call(this, data); - }; - - next(); -}); - -// MySQL数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - } - } -); - -// 微信小程序配置 -const WECHAT_CONFIG = { - APPID: process.env.WECHAT_APPID || 'your-wechat-appid', - APPSECRET: process.env.WECHAT_APPSECRET || 'your-wechat-appsecret', - TOKEN: process.env.WECHAT_TOKEN || 'your-wechat-token' -}; - -// 显示当前使用的数据库配置(用于调试) -console.log('当前数据库连接配置:'); -console.log(' 主机:', process.env.DB_HOST || 'localhost'); -console.log(' 端口:', process.env.DB_PORT || 3306); -console.log(' 数据库名:', process.env.DB_DATABASE || 'wechat_app'); -console.log(' 用户名:', process.env.DB_USER || 'root'); -console.log(' 密码:', process.env.DB_PASSWORD === undefined || process.env.DB_PASSWORD === '' ? '无密码' : '******'); - -// 测试数据库连接 -async function testDbConnection() { - try { - await sequelize.authenticate(); - console.log('数据库连接成功'); - } catch (error) { - console.error('数据库连接失败:', error); - console.error('\n请检查以下几点:'); - console.error('1. MySQL服务是否已经启动'); - console.error('2. wechat_app数据库是否已创建'); - console.error('3. .env文件中的数据库用户名和密码是否正确'); - console.error('4. 用户名是否有足够的权限访问数据库'); - console.error('\n如果是首次配置,请参考README文件中的数据库设置指南。'); - process.exit(1); - } -} - -testDbConnection(); - -// 定义数据模型 - -// 用户模型 -class User extends Model { } -User.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - openid: { - type: DataTypes.STRING(100), - allowNull: false - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 微信名,必填 - }, - avatarUrl: { - type: DataTypes.TEXT - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 电话号码,必填 - }, - type: { - type: DataTypes.STRING(20), - allowNull: false // 用户身份(buyer/seller/both),必填 - }, - gender: { - type: DataTypes.INTEGER - }, - country: { - type: DataTypes.STRING(50) - }, - province: { - type: DataTypes.STRING(50) - }, - city: { - type: DataTypes.STRING(50) - }, - language: { - type: DataTypes.STRING(20) - }, - session_key: { - type: DataTypes.STRING(255) - }, - // 新增字段 - company: { - type: DataTypes.STRING(255) // 客户公司 - }, - region: { - type: DataTypes.STRING(255) // 客户地区 - }, - level: { - type: DataTypes.STRING(255), - defaultValue: 'company-sea-pools' // 客户等级,默认值为company-sea-pools - }, - demand: { - type: DataTypes.TEXT // 基本需求 - }, - spec: { - type: DataTypes.TEXT // 规格 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'User', - tableName: 'users', - timestamps: false -}); - -// 商品模型 -class Product extends Model { } -Product.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - sellerId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - price: { - type: DataTypes.DECIMAL(10, 2), - allowNull: false - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - specification: { - type: DataTypes.STRING(255) - }, - status: { - type: DataTypes.STRING(20), - defaultValue: 'pending_review', - validate: { - isIn: [['pending_review', 'reviewed', 'published', 'sold_out', 'rejected', 'hidden']] - } - }, - rejectReason: { - type: DataTypes.TEXT - }, - // 新增预约相关字段 - reservedCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: '已有几人想要' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Product', - tableName: 'products', - timestamps: false -}); - -// 购物车模型 -class CartItem extends Model { } -CartItem.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - specification: { - type: DataTypes.STRING(255) - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 1 - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - price: { - type: DataTypes.DECIMAL(10, 2) - }, - selected: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - added_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - } -}, { - sequelize, - modelName: 'CartItem', - tableName: 'cart_items', - timestamps: false -}); - -// 联系人表模型 -class Contact extends Model { } -Contact.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 联系人 - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 手机号 - }, - wechat: { - type: DataTypes.STRING(100) // 微信号 - }, - account: { - type: DataTypes.STRING(100) // 账户 - }, - accountNumber: { - type: DataTypes.STRING(100) // 账号 - }, - bank: { - type: DataTypes.STRING(100) // 开户行 - }, - address: { - type: DataTypes.TEXT // 地址 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Contact', - tableName: 'contacts', - timestamps: false -}); - -// 用户管理表模型 -class UserManagement extends Model { } -UserManagement.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - managerId: { - type: DataTypes.STRING(100), - defaultValue: null // 经理ID,默认值为null - }, - company: { - type: DataTypes.STRING(255), - defaultValue: null // 公司,默认值为null - }, - department: { - type: DataTypes.STRING(255), - defaultValue: null // 部门,默认值为null - }, - organization: { - type: DataTypes.STRING(255), - defaultValue: null // 组织,默认值为null - }, - role: { - type: DataTypes.STRING(100), - defaultValue: null // 角色,默认值为null - }, - root: { - type: DataTypes.STRING(100), - defaultValue: null // 根节点,默认值为null - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'UserManagement', - tableName: 'usermanagements', - timestamps: false -}); - -// 定义模型之间的关联关系 - -// 用户和商品的一对多关系 (卖家发布商品) -User.hasMany(Product, { - foreignKey: 'sellerId', // 外键字段名 - sourceKey: 'userId', // 源键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'products', // 别名,用于关联查询 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Product.belongsTo(User, { - foreignKey: 'sellerId', - targetKey: 'userId', // 目标键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'seller' // 别名,用于关联查询 -}); - -// 用户和购物车项的一对多关系 (买家的购物需求/购物车) -User.hasMany(CartItem, { - foreignKey: 'userId', - as: 'cartItems', // 用户的购物车(购物需求)列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(User, { - foreignKey: 'userId', - as: 'buyer' // 别名,明确表示这是购物需求的买家 -}); - -// 商品和购物车项的一对多关系 (商品被添加到购物车) -Product.hasMany(CartItem, { - foreignKey: 'productId', - as: 'cartItems', // 商品出现在哪些购物车中 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(Product, { - foreignKey: 'productId', - as: 'product' // 购物车项中的商品 -}); - -// 用户和联系人的一对多关系 -User.hasMany(Contact, { - foreignKey: 'userId', - as: 'contacts', // 用户的联系人列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Contact.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 联系人所属用户 -}); - -// 用户和用户管理的一对一关系 -User.hasOne(UserManagement, { - foreignKey: 'userId', - as: 'management', // 用户的管理信息 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -UserManagement.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 管理信息所属用户 -}); - -// 同步数据库模型到MySQL -async function syncDatabase() { - try { - // 不使用alter: true,避免尝试修改已有表结构导致的外键约束问题 - await sequelize.sync({ - force: false // 不强制重新创建表 - }); - console.log('数据库模型同步成功'); - } catch (error) { - console.error('数据库模型同步失败:', error); - // 即使同步失败也继续运行,因为我们只需要API功能 - console.log('数据库模型同步失败,但服务器继续运行,使用现有表结构'); - } -} - -syncDatabase(); - -// 解密微信加密数据 -function decryptData(encryptedData, sessionKey, iv) { - try { - // Base64解码 - const sessionKeyBuf = Buffer.from(sessionKey, 'base64'); - const encryptedDataBuf = Buffer.from(encryptedData, 'base64'); - const ivBuf = Buffer.from(iv, 'base64'); - - // AES解密 - const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKeyBuf, ivBuf); - decipher.setAutoPadding(true); - let decoded = decipher.update(encryptedDataBuf, 'binary', 'utf8'); - decoded += decipher.final('utf8'); - - // 解析JSON - return JSON.parse(decoded); - } catch (error) { - console.error('解密失败:', error); - // 提供更具体的错误信息 - if (error.code === 'ERR_OSSL_BAD_DECRYPT') { - throw new Error('登录信息已过期,请重新登录'); - } else if (error.name === 'SyntaxError') { - throw new Error('数据格式错误,解密结果无效'); - } else { - throw new Error('解密失败,请重试'); - } - } -} - -// 获取微信session_key -async function getSessionKey(code) { - const axios = require('axios'); - const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WECHAT_CONFIG.APPID}&secret=${WECHAT_CONFIG.APPSECRET}&js_code=${code}&grant_type=authorization_code`; - - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error('获取session_key失败:', error); - throw new Error('获取session_key失败'); - } -} - -// 创建用户关联记录函数 - 自动为用户创建contacts和usermanagements表的关联记录 -async function createUserAssociations(user) { - try { - if (!user || !user.userId) { - console.error('无效的用户数据,无法创建关联记录'); - return false; - } - - console.log('为用户创建关联记录:', user.userId); - - // 使用事务确保操作原子性 - await sequelize.transaction(async (transaction) => { - // 1. 处理联系人记录 - 使用INSERT ... ON DUPLICATE KEY UPDATE确保无论如何都只保留一条记录 - await sequelize.query( - `INSERT INTO contacts (userId, nickName, phoneNumber, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - nickName = VALUES(nickName), - phoneNumber = VALUES(phoneNumber), - updated_at = NOW()`, - { - replacements: [user.userId, user.nickName || '默认联系人', user.phoneNumber || ''], - transaction: transaction - } - ); - console.log('联系人记录已处理(创建或更新):', user.userId); - - // 2. 处理用户管理记录 - 使用相同策略 - await sequelize.query( - `INSERT INTO usermanagements (userId, created_at, updated_at) - VALUES (?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - updated_at = NOW()`, - { - replacements: [user.userId], - transaction: transaction - } - ); - console.log('用户管理记录已处理(创建或更新):', user.userId); - }); - - console.log('用户关联记录处理成功:', user.userId); - return true; - } catch (error) { - console.error('创建用户关联记录失败:', error.message); - return false; - } -} - -// API路由 - -// 上传用户信息 -app.post('/api/user/upload', async (req, res) => { - try { - const userData = req.body; - console.log('收到用户信息上传请求:', userData); - - // 如果用户信息中包含手机号,检查手机号是否已被其他用户使用 - if (userData.phoneNumber) { - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: userData.phoneNumber, - openid: { [Sequelize.Op.ne]: userData.openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${userData.phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 创建新对象,移除手机号字段 - const userDataWithoutPhone = { ...userData }; - delete userDataWithoutPhone.phoneNumber; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息(不包含手机号) - await User.update( - { - ...userDataWithoutPhone, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - } else { - // 创建新用户 - user = await User.create({ - ...userDataWithoutPhone, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - // 返回成功,但提示手机号已被使用 - return res.json({ - success: true, - code: 200, - message: '用户信息保存成功,但手机号已被其他账号绑定', - data: { - userId: user.userId - }, - phoneNumberConflict: true - }); - } - } - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息 - await User.update( - { - ...userData, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } else { - // 创建新用户 - user = await User.create({ - ...userData, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - res.json({ - success: true, - code: 200, - message: '用户信息保存成功', - data: { - userId: user.userId - }, - phoneNumberConflict: false - }); - } catch (error) { - console.error('保存用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '保存用户信息失败', - error: error.message - }); - } -}); - -// 解密手机号 -app.post('/api/user/decodePhone', async (req, res) => { - try { - const { encryptedData, iv, openid } = req.body; - - // 参数校验 - if (!encryptedData || !iv || !openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数' - }); - } - - // 查找用户的session_key - const user = await User.findOne({ where: { openid } }); - - if (!user || !user.session_key) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录,请先登录', - needRelogin: true - }); - } - - // 解密手机号 - let decryptedData, phoneNumber; - try { - decryptedData = decryptData(encryptedData, user.session_key, iv); - phoneNumber = decryptedData.phoneNumber; - } catch (decryptError) { - // 解密失败,可能是session_key过期,建议重新登录 - return res.status(401).json({ - success: false, - code: 401, - message: decryptError.message || '手机号解密失败', - needRelogin: true - }); - } - - // 检查手机号是否已被其他用户使用 - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: phoneNumber, - openid: { [Sequelize.Op.ne]: openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 返回成功,但不更新手机号,提示用户 - return res.json({ - success: true, - code: 200, - message: '手机号已被其他账号绑定', - phoneNumber: user.phoneNumber, // 返回原手机号 - isNewPhone: false - }); - } - - // 更新用户手机号 - await User.update( - { - phoneNumber: phoneNumber, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 更新用户手机号后,更新关联记录 - const updatedUser = await User.findOne({ where: { openid } }); - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '手机号解密成功', - phoneNumber: phoneNumber, - isNewPhone: true - }); - } catch (error) { - console.error('手机号解密失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '手机号解密失败', - error: error.message - }); - } -}); - -// 处理微信登录,获取openid和session_key -app.post('/api/wechat/getOpenid', async (req, res) => { - try { - const { code } = req.body; - - if (!code) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少code参数' - }); - } - - // 获取openid和session_key - const wxData = await getSessionKey(code); - - if (wxData.errcode) { - throw new Error(`微信接口错误: ${wxData.errmsg}`); - } - - const { openid, session_key, unionid } = wxData; - - // 生成userId - const userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid } - }); - - if (user) { - // 更新用户session_key - await User.update( - { - session_key: session_key, - updated_at: new Date() - }, - { - where: { openid } - } - ); - } else { - // 创建新用户 - // 支持从客户端传入type参数,如果没有则默认为buyer - const userType = req.body.type || 'buyer'; - await User.create({ - openid, - userId, - session_key, - nickName: '微信用户', // 临时占位,等待用户授权 - type: userType, // 使用客户端传入的类型或默认买家身份 - created_at: new Date(), - updated_at: new Date() - }); - - // 为新创建的用户创建关联记录 - const newUser = { userId, openid, nickName: '微信用户' }; - await createUserAssociations(newUser); - } - - res.json({ - success: true, - code: 200, - message: '获取openid成功', - data: { - openid, - userId: user ? user.userId : userId - } - }); - } catch (error) { - console.error('获取openid失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取openid失败', - error: error.message - }); - } -}); - -// 验证用户登录状态 -app.post('/api/user/validate', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'avatarUrl', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录' - }); - } - - res.json({ - success: true, - code: 200, - message: '验证成功', - data: user - }); - } catch (error) { - console.error('验证用户登录状态失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '验证失败', - error: error.message - }); - } -}); - -// 获取用户信息 -app.post('/api/user/get', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - include: [ - { - model: Contact, - as: 'contacts', - attributes: ['id', 'nickName', 'phoneNumber', 'wechat', 'account', 'accountNumber', 'bank', 'address'] - }, - { - model: UserManagement, - as: 'management', - attributes: ['id', 'managerId', 'company', 'department', 'organization', 'role', 'root'] - } - ] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - res.json({ - success: true, - code: 200, - message: '获取用户信息成功', - data: user - }); - } catch (error) { - console.error('获取用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户信息失败', - error: error.message - }); - } -}); - -// 更新用户信息 -app.post('/api/user/update', async (req, res) => { - try { - const { openid, ...updateData } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid } - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 更新用户信息 - await User.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 获取更新后的用户信息 - const updatedUser = await User.findOne({ - where: { openid } - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '更新用户信息成功', - data: updatedUser - }); - } catch (error) { - console.error('更新用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新用户信息失败', - error: error.message - }); - } -}); - -// 获取商品列表 - 优化版本确保状态筛选正确应用 -app.post('/api/product/list', async (req, res) => { - try { - const { openid, status, keyword, page = 1, pageSize = 20, testMode = false } = req.body; - - // 验证openid参数(测试模式除外) - if (!openid && !testMode) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 构建查询条件 - const where = {}; - - // 查找用户 - let user = null; - if (!testMode) { - user = await User.findOne({ where: { openid } }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 只有管理员可以查看所有商品,普通用户只能查看自己的商品 - if (user.type !== 'admin') { - where.sellerId = user.userId; - } - } - - // 状态筛选 - 直接构建到where对象中,确保不会丢失 - console.log(`当前用户类型: ${user ? user.type : '未知'},请求状态: ${status || '未指定'},测试模式: ${testMode}`); - - // 如果有指定status参数,按参数筛选但同时排除hidden - if (status) { - console.log(`按状态筛选商品: status=${status},并排除hidden状态`); - if (status === 'all') { - // 特殊情况:请求所有商品但仍然排除hidden - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else if (Array.isArray(status)) { - // 如果status是数组,确保不包含hidden - where.status = { [Sequelize.Op.in]: status.filter(s => s !== 'hidden') }; - } else { - // 单个状态值,确保不是hidden - if (status !== 'hidden') { - where.status = { [Sequelize.Op.eq]: status }; - } else { - // 如果明确请求hidden状态,也返回空结果 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } - } - } else { - // 没有指定status参数时 - 直接在where对象中设置状态筛选 - if (user && (user.type === 'seller' || user.type === 'both') && !testMode) { - // 卖家用户且非测试模式 - console.log(`卖家用户 ${user.userId} (类型:${user.type}) 查看自己的所有商品,但排除hidden状态`); - // 卖家可以查看自己的所有商品,但仍然排除hidden状态 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else { - // 测试模式或非卖家用户 - console.log(`测试模式或非卖家用户,使用默认状态筛选: reviewed/published`); - // 默认只显示已审核和已发布的商品,排除hidden和sold_out状态 - where.status = { [Sequelize.Op.in]: ['reviewed', 'published'] }; - } - } - - console.log(`构建的完整查询条件:`, JSON.stringify(where, null, 2)); - - // 关键词搜索 - if (keyword) { - where.productName = { [Sequelize.Op.like]: `%${keyword}%` }; - } - - // 计算偏移量 - const offset = (page - 1) * pageSize; - - // 查询商品列表 - const { count, rows: products } = await Product.findAndCountAll({ - where, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - }, - // 添加CartItem关联以获取预约人数 - { - model: CartItem, - as: 'cartItems', // 明确指定别名 - attributes: [], - required: false // 允许没有购物车项的商品也能返回 - } - ], - // 添加selected字段,计算商品被加入购物车的次数(预约人数) - attributes: { - include: [ - [Sequelize.fn('COUNT', Sequelize.col('CartItems.id')), 'selected'] - ] - }, - order: [['created_at', 'DESC']], - limit: pageSize, - offset, - // 修复分组问题 - group: ['Product.productId', 'seller.userId'] // 使用正确的字段名 - }); - - // 添加详细日志,记录查询结果 - console.log(`商品列表查询结果 - 商品数量: ${count}, 商品列表长度: ${products.length}`); - if (products.length > 0) { - console.log(`第一个商品数据:`, JSON.stringify(products[0], null, 2)); - - // 添加selected字段的专门日志 - console.log('商品预约人数(selected字段)统计:'); - products.slice(0, 5).forEach(product => { - const productJSON = product.toJSON(); - console.log(`- ${productJSON.productName}: 预约人数=${productJSON.selected || 0}, 商品ID=${productJSON.productId}`); - }); - } - - // 处理商品列表中的grossWeight字段,确保是数字类型 - const processedProducts = products.map(product => { - const productJSON = product.toJSON(); - - // 详细分析毛重字段 - const grossWeightDetails = { - value: productJSON.grossWeight, - type: typeof productJSON.grossWeight, - isEmpty: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined, - isNumeric: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined || !isNaN(parseFloat(productJSON.grossWeight)) && isFinite(productJSON.grossWeight), - parsedValue: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined ? 0 : parseFloat(productJSON.grossWeight) - }; - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - productJSON.grossWeight = finalGrossWeight; - - // 确保selected字段存在并设置为数字类型(修复后的代码) - if ('selected' in productJSON) { - // 确保selected是数字类型 - productJSON.selected = parseInt(productJSON.selected, 10); - } else { - // 如果没有selected字段,设置默认值为0 - productJSON.selected = 0; - } - - // 记录第一个商品的转换信息用于调试 - if (products.indexOf(product) === 0) { - console.log('商品列表 - 第一个商品毛重字段处理:'); - console.log('- 原始值:', grossWeightDetails.value, '类型:', grossWeightDetails.type); - console.log('- 转换后的值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('- selected字段: 存在=', 'selected' in productJSON, '值=', productJSON.selected, '类型=', typeof productJSON.selected); - } - - return productJSON; - });; - - // 准备响应数据 - 修改格式以匹配前端期望 - const responseData = { - success: true, - code: 200, - message: '获取商品列表成功', - products: processedProducts, - total: count, - page: page, - pageSize: pageSize, - totalPages: Math.ceil(count / pageSize) - }; - - console.log(`准备返回的响应数据格式:`, JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - - // 添加详细的查询条件日志 - console.log(`最终查询条件:`, JSON.stringify(where, null, 2)); - - res.json(responseData); - } catch (error) { - console.error('获取商品列表失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品列表失败', - error: error.message - }); - } -}); - -// 上传商品 -app.post('/api/products/upload', async (req, res) => { - try { - // 修复毛重字段处理逻辑 - let productData = req.body; - if (productData && productData.productData) { - productData = productData.productData; // 使用正确的productData对象 - } - - // 改进的毛重字段处理逻辑,与编辑API保持一致 - // 详细分析毛重字段 - const grossWeightDetails = { - value: productData.grossWeight, - type: typeof productData.grossWeight, - isEmpty: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined, - isNumeric: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined || !isNaN(parseFloat(productData.grossWeight)) && isFinite(productData.grossWeight), - parsedValue: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined ? 0 : parseFloat(productData.grossWeight) - }; - - // 详细的日志记录 - console.log('上传商品 - 毛重字段详细分析:'); - console.log('- 原始值:', productData.grossWeight, '类型:', typeof productData.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行精确四舍五入,确保3位小数以上的值正确转换 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - productData.grossWeight = finalGrossWeight; - console.log('上传商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('收到商品上传请求:', productData); - - // 验证必要字段 - if (!productData.sellerId || !productData.productName || !productData.price || !productData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的商品信息' - }); - } - - // 检查sellerId是否为openid,如果是则查找对应的userId - let actualSellerId = productData.sellerId; - - // 如果sellerId看起来像一个openid(包含特殊字符如'-'),则尝试查找对应的userId - if (productData.sellerId.includes('-')) { - console.log('sellerId看起来像openid,尝试查找对应的userId'); - const user = await User.findOne({ - where: { - openid: productData.sellerId - } - }); - - if (user && user.userId) { - console.log(`找到了对应的userId: ${user.userId}`); - actualSellerId = user.userId; - } else { - console.error(`未找到对应的用户记录,openid: ${productData.sellerId}`); - return res.status(400).json({ - success: false, - code: 400, - message: '找不到对应的用户记录' - }); - } - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 创建商品,使用实际的sellerId - let product = await Product.create({ - ...productData, - sellerId: actualSellerId, // 使用查找到的userId - productId, - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - product = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 与编辑API保持一致的处理逻辑 - if (product) { - console.log('上传商品 - 处理前grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (product.grossWeight === undefined || product.grossWeight === null || product.grossWeight === '') { - product.grossWeight = 0; - console.log('上传商品 - 检测到空值,已设置为0'); - } else { - // 否则转换为浮点数 - product.grossWeight = parseFloat(product.grossWeight); - } - - console.log('上传商品 - 处理后grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品上传成功', - data: { - productId: product.productId, - product: product - } - }); - } catch (error) { - console.error('商品上传失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '商品上传失败', - error: error.message - }); - } -}); - -// 获取商品详情 -app.post('/api/products/detail', async (req, res) => { - try { - const { productId } = req.body; - - if (!productId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId参数' - }); - } - - // 查询商品详情 - 排除hidden状态商品 - const product = await Product.findOne({ - where: { - productId, - status: { [Sequelize.Op.not]: 'hidden' } - }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 对返回的商品数据中的grossWeight字段进行处理,确保是数字类型 - let updatedProduct = { ...product.toJSON() }; - - // 详细分析毛重字段 - const grossWeightDetails = { - value: updatedProduct.grossWeight, - type: typeof updatedProduct.grossWeight, - isEmpty: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined, - isNumeric: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined || !isNaN(parseFloat(updatedProduct.grossWeight)) && isFinite(updatedProduct.grossWeight), - parsedValue: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined ? 0 : parseFloat(updatedProduct.grossWeight) - }; - - // 详细的日志记录 - console.log('商品详情 - 毛重字段详细分析:'); - console.log('- 原始值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - updatedProduct.grossWeight = finalGrossWeight; - console.log('商品详情 - 最终返回的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - res.json({ - success: true, - code: 200, - message: '获取商品详情成功', - data: updatedProduct - }); - } catch (error) { - console.error('获取商品详情失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品详情失败', - error: error.message - }); - } -}); - -// 修改商品 -app.post('/api/products/edit', async (req, res) => { - try { - const { productId, ...updateData } = req.body; - const { sellerId } = req.body; - - if (!productId || !sellerId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - return res.status(403).json({ - success: false, - code: 403, - message: '您无权修改此商品' - }); - } - - // 更新商品信息 - await Product.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { productId } - } - ); - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '修改商品成功', - data: updatedProduct - }); - } catch (error) { - console.error('修改商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '修改商品失败', - error: error.message - }); - } -}); - -// 删除商品 - 将商品状态设置为hidden表示已删除 -app.post('/api/products/delete', async (req, res) => { - console.log('收到删除商品请求:', req.body); - try { - const { productId, sellerId } = req.body; - - if (!productId || !sellerId) { - console.error('删除商品失败: 缺少productId或sellerId参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - console.error('删除商品失败: 商品不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - console.error('删除商品失败: 权限不足 - 卖家ID不匹配', { expected: product.sellerId, actual: sellerId }); - return res.status(403).json({ - success: false, - code: 403, - message: '您无权删除此商品' - }); - } - - console.log('准备更新商品状态为hidden,当前状态:', product.status); - - // 直接使用商品实例更新状态 - product.status = 'hidden'; - product.updated_at = new Date(); - - try { - // 先尝试保存商品实例 - await product.save(); - console.log('删除商品成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - // 如果保存失败,尝试使用update方法 - try { - const updateResult = await Product.update( - { status: 'hidden', updated_at: new Date() }, - { where: { productId } } - ); - console.log('删除商品成功(使用update方法):', { productId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - // 如果update方法也失败,尝试直接执行SQL语句绕过ORM验证 - try { - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId', - { - replacements: { - status: 'hidden', - updatedAt: new Date(), - productId: productId - } - } - ); - console.log('删除商品成功(使用原始SQL):', { productId }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 从购物车中移除该商品 - const destroyResult = await CartItem.destroy({ - where: { productId } - }); - console.log('从购物车移除商品结果:', destroyResult); - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '删除商品成功', - product: { - productId: updatedProduct.productId, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('删除商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除商品失败', - error: error.message - }); - } -}); - -// 添加商品到购物车 -app.post('/api/cart/add', async (req, res) => { - // 增加全局错误捕获,确保即使在try-catch外部的错误也能被处理 - try { - console.log('收到添加到购物车请求 - 开始处理', req.url); - let cartData = req.body; - console.log('收到添加到购物车请求数据:', cartData); - console.log('请求头:', req.headers); - console.log('请求IP:', req.ip); - - // 兼容客户端请求格式:客户端可能将数据封装在product对象中,并且使用openid而不是userId - if (cartData.product && !cartData.productId) { - // 从product对象中提取数据 - const productData = cartData.product; - console.log('从product对象提取数据:', productData); - console.log('客户端提供的openid:', cartData.openid); - - // 使用openid作为userId - cartData = { - userId: cartData.openid || productData.userId, - productId: productData.productId || productData.id, - productName: productData.productName || productData.name, - quantity: productData.quantity || 1, - price: productData.price, - specification: productData.specification || productData.spec || '', - grossWeight: productData.grossWeight || productData.weight, - yolk: productData.yolk || productData.variety || '', - testMode: productData.testMode || cartData.testMode - }; - console.log('转换后的购物车数据:', cartData); - - // 检查转换后的userId是否存在于users表中 - try { - console.log('开始查询用户信息,openid:', cartData.userId); - const user = await User.findOne({ - where: { openid: cartData.userId } - }); - if (user) { - console.log(`找到对应的用户记录: openid=${cartData.userId}, userId=${user.userId}`); - // 修正:使用数据库中真实的userId而不是openid - cartData.userId = user.userId; - console.log('修正后的userId:', cartData.userId); - } else { - console.error(`未找到openid为 ${cartData.userId} 的用户记录,无法添加到购物车`); - // 重要:找不到用户时返回错误,避免使用无效的userId导致外键约束失败 - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `未找到用户记录: ${cartData.userId}` - }); - } - } catch (error) { - console.error('查询用户信息失败:', error); - // 查询失败时也返回错误 - return res.status(400).json({ - success: false, - code: 400, - message: '查询用户信息失败', - error: error.message - }); - } - } - - // 验证必要字段 - if (!cartData.userId || !cartData.productId || !cartData.productName || !cartData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的购物车信息', - missingFields: [ - !cartData.userId ? 'userId' : '', - !cartData.productId ? 'productId' : '', - !cartData.productName ? 'productName' : '', - !cartData.quantity ? 'quantity' : '' - ].filter(Boolean) - }); - } - - // 先验证用户ID是否存在于users表中 - try { - const userExists = await User.findOne({ - where: { userId: cartData.userId } - }); - if (!userExists) { - console.error(`用户ID ${cartData.userId} 不存在于users表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `用户ID ${cartData.userId} 不存在` - }); - } else { - console.log(`用户ID ${cartData.userId} 存在于users表中,用户验证通过`); - } - } catch (error) { - console.error('验证用户ID失败:', error); - return res.status(400).json({ - success: false, - code: 400, - message: '验证用户信息失败', - error: error.message - }); - } - - // 检查商品是否存在以及是否为hidden状态 - console.log(`检查商品ID: ${cartData.productId} 是否存在于products表中`); - const product = await Product.findOne({ - where: { - productId: cartData.productId - } - }); - - if (!product) { - console.error(`商品ID ${cartData.productId} 不存在于products表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '商品不存在或已被移除', - error: `未找到商品ID: ${cartData.productId}` - }); - } else { - console.log(`商品ID ${cartData.productId} 存在于products表中,商品名称: ${product.productName}`); - } - - if (product.status === 'hidden') { - return res.status(400).json({ - success: false, - code: 400, - message: '该商品已下架,无法添加到购物车' - }); - } - - // 在testMode下,不执行实际的数据库操作,直接返回成功 - if (cartData.testMode) { - console.log('测试模式:跳过实际的数据库操作'); - res.json({ - success: true, - code: 200, - message: '测试模式:添加到购物车成功', - data: { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity - } - }); - return; - } - - // 检查是否已存在相同商品 - const existingItem = await CartItem.findOne({ - where: { - userId: cartData.userId, - productId: cartData.productId - } - }); - - // 添加try-catch捕获外键约束错误 - try { - console.log(`准备创建/更新购物车项: userId=${cartData.userId}, productId=${cartData.productId}`); - if (existingItem) { - // 已存在,更新数量 - await CartItem.update( - { - quantity: existingItem.quantity + cartData.quantity, - updated_at: new Date() - }, - { - where: { - id: existingItem.id - } - } - ); - console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); - } else { - // 不存在,创建新购物车项 - console.log('创建新购物车项,所有字段:', { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity, - price: cartData.price, - specification: cartData.specification, - grossWeight: cartData.grossWeight, - yolk: cartData.yolk - }); - // 重要:在创建前再次验证数据完整性 - if (!cartData.userId || !cartData.productId) { - throw new Error(`数据不完整: userId=${cartData.userId}, productId=${cartData.productId}`); - } - await CartItem.create({ - ...cartData, - added_at: new Date() - }); - console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); - } - } catch (createError) { - console.error('创建/更新购物车项失败,可能是外键约束问题:', createError); - console.error('详细错误信息:', { - name: createError.name, - message: createError.message, - stack: createError.stack, - sql: createError.sql || '无SQL信息', - userId: cartData.userId, - productId: cartData.productId - }); - - // 检测是否是外键约束错误 - if (createError.name === 'SequelizeForeignKeyConstraintError' || createError.message.includes('foreign key')) { - // 区分是用户ID还是商品ID问题 - let errorField = 'productId'; - let errorMessage = '商品信息已更新,请刷新页面后重试'; - - if (createError.message.includes('userId') || createError.message.includes('user') || createError.message.toLowerCase().includes('user')) { - errorField = 'userId'; - errorMessage = '用户信息无效,请重新登录后重试'; - } - - return res.status(400).json({ - success: false, - code: 400, - message: errorMessage, - error: `外键约束错误: ${errorField} 不存在或已失效`, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 其他类型的错误也返回400状态码,避免500错误 - return res.status(400).json({ - success: false, - code: 400, - message: '添加购物车项失败,请稍后重试', - error: createError.message, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 更新商品的预约人数 - 更健壮的实现 - try { - console.log(`尝试更新商品预约人数: productId=${cartData.productId}`); - - // 先验证商品是否存在 - const productCheck = await Product.findOne({where: {productId: cartData.productId}}); - if (productCheck) { - // 商品存在,才进行更新 - await Product.increment('reservedCount', {by: 1, where: {productId: cartData.productId}}); - console.log(`商品预约人数更新成功: productId=${cartData.productId}, 新数量=${productCheck.reservedCount + 1}`); - } else { - console.error(`更新商品预约人数失败: 商品ID ${cartData.productId} 不存在`); - } - } catch (updateError) { - console.error(`更新商品预约人数失败:`, updateError); - // 继续执行,不中断主要流程 - } - - res.json({ - success: true, - code: 200, - message: '添加到购物车成功' - }); - } catch (error) { - console.error('添加到购物车失败:', error); - console.error('全局错误捕获,详细信息:', { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - }); - - // 增强的错误处理 - 强制所有错误返回400状态码 - console.error('全局错误处理 - 捕获到未处理的错误:', error); - const statusCode = 400; // 强制所有错误返回400状态码,避免前端显示500错误 - let errorMessage = '添加到购物车失败'; - - // 更精确地检测外键约束错误 - if (error.name === 'SequelizeForeignKeyConstraintError' || - error.message.toLowerCase().includes('foreign key') || - error.message.toLowerCase().includes('constraint fails') || - error.message.toLowerCase().includes('constraint')) { - errorMessage = '添加到购物车失败:商品或用户信息已更新,请刷新页面后重试'; - console.error('检测到外键约束相关错误,返回400状态码'); - } - - console.log(`准备返回错误响应 - 状态码: ${statusCode}, 消息: ${errorMessage}`); - - // 确保响应能够正确发送 - try { - res.status(statusCode).json({ - success: false, - code: statusCode, - message: errorMessage, - error: error.message, - errorDetails: { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - } - }); - } catch (resError) { - console.error('发送错误响应失败:', resError); - // 即使发送响应失败,也尝试以文本格式发送 - try { - res.status(400).send('添加到购物车失败,请刷新页面后重试'); - } catch (finalError) { - console.error('无法发送任何响应:', finalError); - } - } - } -}); - -// 获取购物车信息 -app.post('/api/cart/get', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 查询购物车信息 - 排除关联商品为hidden或sold_out状态的项 - const cartItems = await CartItem.findAll({ - where: { userId }, - include: [ - { - model: Product, - as: 'product', - attributes: ['productName', 'price', 'quantity', 'status', 'specification', 'grossWeight', 'yolk'], - where: { - status: { [Sequelize.Op.notIn]: ['hidden', 'sold_out'] } - } - } - ], - order: [['added_at', 'DESC']] - }); - - res.json({ - success: true, - code: 200, - message: '获取购物车信息成功', - data: { - cartItems - } - }); - } catch (error) { - console.error('获取购物车信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取购物车信息失败', - error: error.message - }); - } -}); - -// 更新购物车项 -app.post('/api/cart/update', async (req, res) => { - try { - const { id, quantity, selected } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 构建更新数据 - const updateData = {}; - if (quantity !== undefined) updateData.quantity = quantity; - if (selected !== undefined) updateData.selected = selected; - updateData.updated_at = new Date(); - - // 更新购物车项 - await CartItem.update(updateData, { - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '更新购物车成功' - }); - } catch (error) { - console.error('更新购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新购物车失败', - error: error.message - }); - } -}); - -// 删除购物车项 -app.post('/api/cart/delete', async (req, res) => { - try { - const { id } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 删除购物车项 - await CartItem.destroy({ - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '删除购物车项成功' - }); - } catch (error) { - console.error('删除购物车项失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除购物车项失败', - error: error.message - }); - } -}); - -// 清空购物车 -app.post('/api/cart/clear', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 清空购物车 - await CartItem.destroy({ - where: { userId } - }); - - res.json({ - success: true, - code: 200, - message: '清空购物车成功' - }); - } catch (error) { - console.error('清空购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '清空购物车失败', - error: error.message - }); - } -}); - -// 测试连接接口 -app.get('/api/test-connection', async (req, res) => { - try { - // 检查数据库连接 - await sequelize.authenticate(); - - res.json({ - success: true, - code: 200, - message: '服务器连接成功,数据库可用', - timestamp: new Date().toISOString(), - serverInfo: { - port: PORT, - nodeVersion: process.version, - database: 'MySQL', - status: 'running' - } - }); - } catch (error) { - res.status(500).json({ - success: false, - code: 500, - message: '服务器连接失败', - error: error.message - }); - } -}); - -// 用户类型调试接口 - 增强版:用于排查用户类型和商品显示问题 -app.post('/api/user/debug', async (req, res) => { - try { - const { openid } = req.body; - - console.log('收到用户调试请求,openid:', openid); - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查询用户信息 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在', - debugInfo: { - searchCriteria: { openid }, - timestamp: new Date().toISOString() - } - }); - } - - // 查询该用户的商品统计信息 - const totalProducts = await Product.count({ where: { sellerId: user.userId } }); - const pendingProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'pending_review' - } - }); - const reviewedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'reviewed' - } - }); - const publishedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'published' - } - }); - const soldOutProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'sold_out' - } - }); - - // 判断用户是否有权限查看所有商品 - const canViewAllProducts = ['seller', 'both', 'admin'].includes(user.type); - - // 获取该用户的最新5个商品信息(用于调试) - const latestProducts = await Product.findAll({ - where: { sellerId: user.userId }, - limit: 5, - order: [['created_at', 'DESC']], - attributes: ['productId', 'productName', 'status', 'created_at'] - }); - - const responseData = { - success: true, - code: 200, - message: '获取用户调试信息成功', - userInfo: user, - productStats: { - total: totalProducts, - pendingReview: pendingProducts, - reviewed: reviewedProducts, - published: publishedProducts, - soldOut: soldOutProducts - }, - permissionInfo: { - canViewAllProducts: canViewAllProducts, - userType: user.type, - allowedTypesForViewingAllProducts: ['seller', 'both', 'admin'] - }, - latestProducts: latestProducts, - debugInfo: { - userCount: await User.count(), - totalProductsInSystem: await Product.count(), - timestamp: new Date().toISOString(), - serverTime: new Date().toLocaleString('zh-CN') - } - }; - - console.log('调试信息返回数据:', JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - res.json(responseData); - } catch (error) { - console.error('获取用户调试信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户调试信息失败', - error: error.message, - debugInfo: { - errorStack: error.stack, - timestamp: new Date().toISOString() - } - }); - } -}); - -// 下架商品接口 - 将商品状态设置为sold_out表示已下架 -app.post('/api/product/hide', async (req, res) => { - console.log('收到下架商品请求:', req.body); - - try { - const { openid, productId } = req.body; - - // 验证请求参数 - if (!openid || !productId) { - console.error('下架商品失败: 缺少必要参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要参数: openid和productId都是必需的' - }); - } - - // 查找用户 - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('下架商品失败: 用户不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - console.log('找到用户信息:', { userId: user.userId, nickName: user.nickName }); - - // 查找商品并验证所有权 - 直接使用userId,因为商品创建时使用的就是userId - const product = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - - if (!product) { - console.error('下架商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 记录当前状态,用于调试 - console.log('当前商品状态:', product.status, '允许的状态列表:', Product.rawAttributes.status.validate.isIn); - console.log('商品所属卖家ID:', product.sellerId); - console.log('用户ID信息对比:', { userId: user.userId, id: user.id }); - - console.log('准备更新商品状态为sold_out,当前状态:', product.status); - - // 更新商品状态为已下架(sold_out) - 尝试多种更新方式确保成功 - try { - // 方法1: 直接保存实例 - product.status = 'sold_out'; - product.updated_at = new Date(); - await product.save(); - console.log('商品下架成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - try { - // 方法2: 使用update方法 - const updateResult = await Product.update( - { status: 'sold_out', updated_at: new Date() }, - { where: { productId: productId, sellerId: user.userId } } - ); - console.log('商品下架成功(使用update方法):', { productId: productId, sellerIdType: typeof user.userId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - try { - // 方法3: 直接执行SQL语句绕过ORM验证 - const replacements = { - status: 'sold_out', - updatedAt: new Date(), - productId: productId, - sellerId: user.userId - }; - - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId AND sellerId = :sellerId', - { - replacements: replacements - } - ); - console.log('商品下架成功(使用原始SQL):', { productId: product.productId, productName: product.productName }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: product.sellerId // 使用找到的商品的sellerId进行查询 - } - }); - - res.json({ - success: true, - code: 200, - message: '商品下架成功', - product: { - productId: updatedProduct.productId, - productName: updatedProduct.productName, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('下架商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '下架商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 发布商品API -app.post('/api/product/publish', async (req, res) => { - console.log('收到发布商品请求:', req.body); // 记录完整请求体 - - try { - const { openid, product } = req.body; - - // 验证必填字段 - console.log('验证请求参数: openid=', !!openid, ', product=', !!product); - if (!openid || !product) { - console.error('缺少必要参数: openid=', openid, 'product=', product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid或product对象)' - }); - } - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price, '转换为数字=', parseFloat(product.price)); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity, '转换为数字=', parseInt(product.quantity)); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误和字段值详情 - const validationErrors = []; - const fieldDetails = {}; - - // 检查商品名称 - fieldDetails.productName = { - value: product.productName, - type: typeof product.productName, - isEmpty: !product.productName || product.productName.trim() === '' - }; - if (fieldDetails.productName.isEmpty) { - console.error('商品名称为空'); - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } - - // 检查价格 - fieldDetails.price = { - value: product.price, - type: typeof product.price, - isNumber: !isNaN(parseFloat(product.price)) && isFinite(product.price), - parsedValue: parseFloat(product.price), - isValid: !isNaN(parseFloat(product.price)) && isFinite(product.price) && parseFloat(product.price) > 0 - }; - if (!product.price) { - console.error('价格为空'); - validationErrors.push('价格为必填项'); - } else if (!fieldDetails.price.isNumber) { - console.error('价格不是有效数字: price=', product.price); - validationErrors.push('价格必须是有效数字格式'); - } else if (fieldDetails.price.parsedValue <= 0) { - console.error('价格小于等于0: price=', product.price, '转换为数字后=', fieldDetails.price.parsedValue); - validationErrors.push('价格必须大于0'); - } - - // 检查数量 - fieldDetails.quantity = { - value: product.quantity, - type: typeof product.quantity, - isNumeric: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity), - parsedValue: Math.floor(parseFloat(product.quantity)), - isValid: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity) && parseFloat(product.quantity) > 0 - }; - if (!product.quantity) { - console.error('数量为空'); - validationErrors.push('数量为必填项'); - } else if (!fieldDetails.quantity.isNumeric) { - console.error('数量不是有效数字: quantity=', product.quantity); - validationErrors.push('数量必须是有效数字格式'); - } else if (fieldDetails.quantity.parsedValue <= 0) { - console.error('数量小于等于0: quantity=', product.quantity, '转换为数字后=', fieldDetails.quantity.parsedValue); - validationErrors.push('数量必须大于0'); - } - - // 改进的毛重字段处理逻辑 - 与其他API保持一致 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('发布商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保商品名称不超过数据库字段长度限制 - if (product.productName && product.productName.length > 255) { - console.error('商品名称过长: 长度=', product.productName.length); - validationErrors.push('商品名称不能超过255个字符'); - } - - // 如果有验证错误,一次性返回所有错误信息和字段详情 - if (validationErrors.length > 0) { - console.error('验证失败 - 详细信息:', JSON.stringify({ - errors: validationErrors, - fieldDetails: fieldDetails - }, null, 2)); - - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors, - detailedMessage: validationErrors.join('; '), - fieldDetails: fieldDetails - }); - } - - // 查找用户 - console.log('开始查找用户: openid=', openid); - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - console.log('找到用户:', { userId: user.userId, nickName: user.nickName, type: user.type }); - - // 验证用户类型 - console.log(`验证用户类型: 用户ID=${user.userId}, 类型=${user.type}`); - if (user.type !== 'seller' && user.type !== 'both') { - console.error(`商品发布失败: 用户${user.userId}类型为${user.type},需要seller或both类型`); - return res.status(403).json({ - success: false, - code: 403, - message: '只有卖家才能发布商品,请在个人资料中修改用户类型' - }); - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - console.log('生成商品ID:', productId); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行正确的四舍五入 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - console.log('发布商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 创建商品 - console.log('准备创建商品:', { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, - sellerId: user.userId - }); - - const newProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk || '', - specification: product.specification || '', - status: 'pending_review', // 默认状态为待审核 - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - const createdProduct = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - if (createdProduct) { - console.log('发布商品 - 数据库查询后grossWeight:', createdProduct.grossWeight, '类型:', typeof createdProduct.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品发布成功', - product: createdProduct, - productId: productId - }); - - } catch (error) { - console.error('发布商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '发布商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`服务器运行在 http://localhost:${PORT}`); - console.log('注意:当前服务器已添加详细日志记录,用于排查发布商品问题'); - console.log('调试API: POST /api/user/debug - 用于查看用户类型信息'); - console.log(`测试连接接口: http://localhost:${PORT}/api/test-connection`); -}); - -// 编辑商品API - 用于审核失败商品重新编辑 -app.post('/api/product/edit', async (req, res) => { - console.log('收到编辑商品请求 - 详细信息:'); - console.log('- 请求路径:', req.url); - console.log('- 请求方法:', req.method); - console.log('- 请求完整body:', req.body); - console.log('- 服务器端口:', PORT); - console.log('- 环境变量:', process.env.PORT); - - try { - // 正确解析请求参数,处理嵌套的productData结构 - let openid = req.body.openid; - let productId = req.body.productId; - let status = req.body.status; - let testMode = req.body.testMode; - let product = req.body.product; - - // 处理多层嵌套的productData结构 - if (!product && req.body.productData) { - // 处理第一种情况: { productData: { openid, productId, product: { ... } } } - if (req.body.productData.product) { - product = req.body.productData.product; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - // 处理第二种情况: { productData: { openid, productId, productName, price, ... } } - else { - product = req.body.productData; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - } - - // 调试日志 - console.log('解析后参数:', { openid, productId, status, testMode, product: !!product }); - - console.log('收到编辑商品请求,包含状态参数:', { openid, productId, status, testMode }); - - // 验证必填字段 - if (!openid || !productId || !product) { - console.error('缺少必要参数: openid=', !!openid, 'productId=', !!productId, 'product=', !!product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid、productId或product对象)' - }); - } - - // 查找用户 - let user = null; - - // 测试模式下的特殊处理 - if (testMode) { - console.log('测试模式:尝试查找或创建测试用户'); - // 首先尝试查找openid为'test_openid'的用户 - user = await User.findOne({ - where: { openid: 'test_openid' } - }); - - if (!user) { - // 如果不存在,创建一个新的测试用户 - console.log('测试模式:创建测试用户'); - try { - user = await User.create({ - openid: 'test_openid', - userId: 'test_user_id', - nickName: '测试用户', - type: 'seller' - }); - } catch (createError) { - console.error('测试模式:创建测试用户失败', createError); - // 如果创建失败,尝试查找数据库中的第一个用户 - user = await User.findOne({ - order: [['id', 'ASC']] - }); - if (user) { - console.log('测试模式:使用数据库中的现有用户', user.userId); - } - } - } else { - console.log('测试模式:使用已存在的测试用户', user.userId); - } - } else { - // 非测试模式:按常规方式查找用户 - user = await User.findOne({ where: { openid } }); - } - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - // 查找商品 - let existingProduct = null; - - if (testMode) { - // 测试模式:如果找不到商品,尝试使用测试商品或创建一个新的测试商品 - existingProduct = await Product.findOne({ - where: { - productId: productId - } - }); - - // 如果找不到指定的商品,创建一个新的测试商品 - if (!existingProduct) { - console.log('测试模式:创建测试商品'); - try { - existingProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: '测试商品', - price: 99.99, - quantity: 100, - grossWeight: 0, // 默认为0而不是5,符合用户需求 - yolk: '测试描述', - specification: '测试规格', - status: 'rejected', // 设置为可编辑状态 - created_at: new Date(), - updated_at: new Date() - }); - console.log('测试模式:测试商品创建成功'); - } catch (createProductError) { - console.error('测试模式:创建测试商品失败', createProductError); - } - } - } else { - // 非测试模式:验证商品所有权 - existingProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - } - - if (!existingProduct) { - console.error('编辑商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 验证商品状态是否允许编辑 - if (!['rejected', 'sold_out', 'pending_review', 'reviewed'].includes(existingProduct.status)) { - console.error(`编辑商品失败: 商品状态(${existingProduct.status})不允许编辑`, { - productId: productId, - sellerId: user.userId, - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - }); - return res.status(403).json({ - success: false, - code: 403, - message: '只有审核失败、已下架、审核中或已审核的商品才能编辑', - debugInfo: { - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - } - }); - } - - // 记录商品编辑信息,用于调试 - console.log(`允许编辑商品: productId=${productId}, status=${existingProduct.status}, sellerId=${user.userId}`); - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误 - const validationErrors = []; - - // 检查商品名称 - if (!product.productName || product.productName.trim() === '') { - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } else if (product.productName.length > 255) { - validationErrors.push('商品名称不能超过255个字符'); - } - - // 检查价格 - if (!product.price) { - validationErrors.push('价格为必填项'); - } else if (isNaN(parseFloat(product.price)) || parseFloat(product.price) <= 0) { - validationErrors.push('价格必须是大于0的有效数字'); - } - - // 检查数量 - if (!product.quantity) { - validationErrors.push('数量为必填项'); - } else if (isNaN(parseInt(product.quantity)) || parseInt(product.quantity) <= 0) { - validationErrors.push('数量必须是大于0的有效数字'); - } - - // 改进的毛重字段处理逻辑,与其他API保持一致,空值默认设为0 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('编辑商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保grossWeight值是数字类型 - const finalGrossWeight = Number(grossWeightDetails.parsedValue); - console.log('编辑商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 如果有验证错误,返回错误信息 - if (validationErrors.length > 0) { - console.error('验证失败 - 错误:', validationErrors.join('; ')); - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors - }); - } - - // 准备更新的商品数据 - const updatedProductData = { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk, - specification: product.specification, - // 优先使用前端传递的status参数,如果没有传递则使用原来的逻辑 - status: status && ['pending_review', 'published'].includes(status) ? status : - (product.resubmit && ['rejected', 'sold_out'].includes(existingProduct.status)) ? 'pending_review' : existingProduct.status, - rejectReason: (status === 'pending_review' || (product.resubmit && existingProduct.status === 'rejected')) ? null : existingProduct.rejectReason, // 提交审核时清除拒绝原因 - updated_at: new Date() - }; - - console.log('准备更新商品数据:', { productId, updatedStatus: updatedProductData.status, fromStatus: existingProduct.status }); - - // 更新商品 - const [updatedCount] = await Product.update(updatedProductData, { - where: testMode ? { - // 测试模式:只根据productId更新 - productId: productId - } : { - // 非测试模式:验证商品所有权 - productId: productId, - sellerId: user.userId - } - }); - - // 检查更新是否成功 - if (updatedCount === 0) { - console.error('商品更新失败: 没有找到匹配的商品或权限不足'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品更新失败: 没有找到匹配的商品或权限不足' - }); - } - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ where: { productId: productId } }); - - console.log('查询数据库后 - 更新的商品信息:', { - grossWeight: updatedProduct?.grossWeight, - grossWeightType: typeof updatedProduct?.grossWeight, - productId: updatedProduct?.productId, - status: updatedProduct?.status - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 注意:这里检查undefined和null,并且对于空字符串或5的情况也进行处理 - if (updatedProduct) { - console.log('处理前 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (updatedProduct.grossWeight === undefined || updatedProduct.grossWeight === null || updatedProduct.grossWeight === '') { - updatedProduct.grossWeight = 0; - console.log('检测到空值 - 已设置为0'); - } else { - // 否则转换为浮点数 - updatedProduct.grossWeight = parseFloat(updatedProduct.grossWeight); - } - - console.log('处理后 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - } - - console.log('商品编辑成功:', { - productId: productId, - productName: product.productName, - oldStatus: existingProduct.status, // 记录更新前的状态 - newStatus: updatedProduct.status, // 记录更新后的状态 - grossWeight: updatedProduct.grossWeight // 记录处理后的毛重值 - }); - - // 根据新的状态生成适当的返回消息 - let returnMessage = ''; - if (updatedProduct.status === 'pending_review') { - returnMessage = '商品编辑成功,已重新提交审核'; - } else if (updatedProduct.status === 'published') { - returnMessage = '商品编辑成功,已上架'; - } else if (updatedProduct.status === existingProduct.status) { - returnMessage = '商品编辑成功,状态保持不变'; - } else { - returnMessage = '商品编辑成功'; - } - - res.json({ - success: true, - code: 200, - message: returnMessage, - product: updatedProduct - }); - } catch (error) { - console.error('编辑商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '编辑商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 导出模型和Express应用供其他模块使用 -module.exports = { - User, - Product, - CartItem, - sequelize, - createUserAssociations, - app, - PORT -}; \ No newline at end of file diff --git a/server-example/server-mysql-backup-final.js b/server-example/server-mysql-backup-final.js deleted file mode 100644 index 4398050..0000000 --- a/server-example/server-mysql-backup-final.js +++ /dev/null @@ -1,3001 +0,0 @@ -// ECS服务器示例代码 - Node.js版 (MySQL版本) -const express = require('express'); -const crypto = require('crypto'); -const bodyParser = require('body-parser'); -const { Sequelize, DataTypes, Model, Op } = require('sequelize'); -require('dotenv').config(); - -// 创建Express应用 -const app = express(); -const PORT = process.env.PORT || 3002; - -// 中间件 -app.use(bodyParser.json()); - -// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后) -app.use((req, res, next) => { - // 将UTC时间转换为北京时间(UTC+8) - const now = new Date(); - const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); - const formattedTime = beijingTime.toISOString().replace('Z', '+08:00'); - - console.log(`[${formattedTime}] 收到请求: ${req.method} ${req.url}`); - console.log('请求头:', req.headers); - console.log('请求体:', req.body); - next(); -}); - -// 商品毛重处理中间件 - 确保所有返回的商品数据中毛重字段保持原始值 -app.use((req, res, next) => { - // 保存原始的json方法 - const originalJson = res.json; - - // 重写json方法来处理响应数据 - res.json = function (data) { - // 检查数据中是否包含商品列表 - if (data && typeof data === 'object') { - // 处理/products/list接口的响应 - if (data.products && Array.isArray(data.products)) { - data.products = data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - - // 处理/data字段中的商品列表 - if (data.data && data.data.products && Array.isArray(data.data.products)) { - data.data.products = data.data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - } - - // 调用原始的json方法 - return originalJson.call(this, data); - }; - - next(); -}); - -// MySQL数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - } - } -); - -// 微信小程序配置 -const WECHAT_CONFIG = { - APPID: process.env.WECHAT_APPID || 'your-wechat-appid', - APPSECRET: process.env.WECHAT_APPSECRET || 'your-wechat-appsecret', - TOKEN: process.env.WECHAT_TOKEN || 'your-wechat-token' -}; - -// 显示当前使用的数据库配置(用于调试) -console.log('当前数据库连接配置:'); -console.log(' 主机:', process.env.DB_HOST || 'localhost'); -console.log(' 端口:', process.env.DB_PORT || 3306); -console.log(' 数据库名:', process.env.DB_DATABASE || 'wechat_app'); -console.log(' 用户名:', process.env.DB_USER || 'root'); -console.log(' 密码:', process.env.DB_PASSWORD === undefined || process.env.DB_PASSWORD === '' ? '无密码' : '******'); - -// 测试数据库连接 -async function testDbConnection() { - try { - await sequelize.authenticate(); - console.log('数据库连接成功'); - } catch (error) { - console.error('数据库连接失败:', error); - console.error('\n请检查以下几点:'); - console.error('1. MySQL服务是否已经启动'); - console.error('2. wechat_app数据库是否已创建'); - console.error('3. .env文件中的数据库用户名和密码是否正确'); - console.error('4. 用户名是否有足够的权限访问数据库'); - console.error('\n如果是首次配置,请参考README文件中的数据库设置指南。'); - process.exit(1); - } -} - -testDbConnection(); - -// 定义数据模型 - -// 用户模型 -class User extends Model { } -User.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - openid: { - type: DataTypes.STRING(100), - allowNull: false - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 微信名,必填 - }, - avatarUrl: { - type: DataTypes.TEXT - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 电话号码,必填 - }, - type: { - type: DataTypes.STRING(20), - allowNull: false // 用户身份(buyer/seller/both),必填 - }, - gender: { - type: DataTypes.INTEGER - }, - country: { - type: DataTypes.STRING(50) - }, - province: { - type: DataTypes.STRING(50) - }, - city: { - type: DataTypes.STRING(50) - }, - language: { - type: DataTypes.STRING(20) - }, - session_key: { - type: DataTypes.STRING(255) - }, - // 新增字段 - company: { - type: DataTypes.STRING(255) // 客户公司 - }, - region: { - type: DataTypes.STRING(255) // 客户地区 - }, - level: { - type: DataTypes.STRING(255), - defaultValue: 'company-sea-pools' // 客户等级,默认值为company-sea-pools - }, - demand: { - type: DataTypes.TEXT // 基本需求 - }, - spec: { - type: DataTypes.TEXT // 规格 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'User', - tableName: 'users', - timestamps: false -}); - -// 商品模型 -class Product extends Model { } -Product.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - sellerId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - price: { - type: DataTypes.DECIMAL(10, 2), - allowNull: false - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - specification: { - type: DataTypes.STRING(255) - }, - status: { - type: DataTypes.STRING(20), - defaultValue: 'pending_review', - validate: { - isIn: [['pending_review', 'reviewed', 'published', 'sold_out', 'rejected', 'hidden']] - } - }, - rejectReason: { - type: DataTypes.TEXT - }, - // 新增预约相关字段 - reservedCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: '已有几人想要' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Product', - tableName: 'products', - timestamps: false -}); - -// 购物车模型 -class CartItem extends Model { } -CartItem.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - specification: { - type: DataTypes.STRING(255) - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 1 - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - price: { - type: DataTypes.DECIMAL(10, 2) - }, - selected: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - added_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - } -}, { - sequelize, - modelName: 'CartItem', - tableName: 'cart_items', - timestamps: false -}); - -// 联系人表模型 -class Contact extends Model { } -Contact.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 联系人 - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 手机号 - }, - wechat: { - type: DataTypes.STRING(100) // 微信号 - }, - account: { - type: DataTypes.STRING(100) // 账户 - }, - accountNumber: { - type: DataTypes.STRING(100) // 账号 - }, - bank: { - type: DataTypes.STRING(100) // 开户行 - }, - address: { - type: DataTypes.TEXT // 地址 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Contact', - tableName: 'contacts', - timestamps: false -}); - -// 用户管理表模型 -class UserManagement extends Model { } -UserManagement.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - managerId: { - type: DataTypes.STRING(100), - defaultValue: null // 经理ID,默认值为null - }, - company: { - type: DataTypes.STRING(255), - defaultValue: null // 公司,默认值为null - }, - department: { - type: DataTypes.STRING(255), - defaultValue: null // 部门,默认值为null - }, - organization: { - type: DataTypes.STRING(255), - defaultValue: null // 组织,默认值为null - }, - role: { - type: DataTypes.STRING(100), - defaultValue: null // 角色,默认值为null - }, - root: { - type: DataTypes.STRING(100), - defaultValue: null // 根节点,默认值为null - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'UserManagement', - tableName: 'usermanagements', - timestamps: false -}); - -// 定义模型之间的关联关系 - -// 用户和商品的一对多关系 (卖家发布商品) -User.hasMany(Product, { - foreignKey: 'sellerId', // 外键字段名 - sourceKey: 'userId', // 源键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'products', // 别名,用于关联查询 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Product.belongsTo(User, { - foreignKey: 'sellerId', - targetKey: 'userId', // 目标键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'seller' // 别名,用于关联查询 -}); - -// 用户和购物车项的一对多关系 (买家的购物需求/购物车) -User.hasMany(CartItem, { - foreignKey: 'userId', - as: 'cartItems', // 用户的购物车(购物需求)列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(User, { - foreignKey: 'userId', - as: 'buyer' // 别名,明确表示这是购物需求的买家 -}); - -// 商品和购物车项的一对多关系 (商品被添加到购物车) -Product.hasMany(CartItem, { - foreignKey: 'productId', - as: 'cartItems', // 商品出现在哪些购物车中 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(Product, { - foreignKey: 'productId', - as: 'product' // 购物车项中的商品 -}); - -// 用户和联系人的一对多关系 -User.hasMany(Contact, { - foreignKey: 'userId', - as: 'contacts', // 用户的联系人列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Contact.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 联系人所属用户 -}); - -// 用户和用户管理的一对一关系 -User.hasOne(UserManagement, { - foreignKey: 'userId', - as: 'management', // 用户的管理信息 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -UserManagement.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 管理信息所属用户 -}); - -// 同步数据库模型到MySQL -async function syncDatabase() { - try { - // 不使用alter: true,避免尝试修改已有表结构导致的外键约束问题 - await sequelize.sync({ - force: false // 不强制重新创建表 - }); - console.log('数据库模型同步成功'); - } catch (error) { - console.error('数据库模型同步失败:', error); - // 即使同步失败也继续运行,因为我们只需要API功能 - console.log('数据库模型同步失败,但服务器继续运行,使用现有表结构'); - } -} - -syncDatabase(); - -// 解密微信加密数据 -function decryptData(encryptedData, sessionKey, iv) { - try { - // Base64解码 - const sessionKeyBuf = Buffer.from(sessionKey, 'base64'); - const encryptedDataBuf = Buffer.from(encryptedData, 'base64'); - const ivBuf = Buffer.from(iv, 'base64'); - - // AES解密 - const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKeyBuf, ivBuf); - decipher.setAutoPadding(true); - let decoded = decipher.update(encryptedDataBuf, 'binary', 'utf8'); - decoded += decipher.final('utf8'); - - // 解析JSON - return JSON.parse(decoded); - } catch (error) { - console.error('解密失败:', error); - // 提供更具体的错误信息 - if (error.code === 'ERR_OSSL_BAD_DECRYPT') { - throw new Error('登录信息已过期,请重新登录'); - } else if (error.name === 'SyntaxError') { - throw new Error('数据格式错误,解密结果无效'); - } else { - throw new Error('解密失败,请重试'); - } - } -} - -// 获取微信session_key -async function getSessionKey(code) { - const axios = require('axios'); - const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WECHAT_CONFIG.APPID}&secret=${WECHAT_CONFIG.APPSECRET}&js_code=${code}&grant_type=authorization_code`; - - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error('获取session_key失败:', error); - throw new Error('获取session_key失败'); - } -} - -// 创建用户关联记录函数 - 自动为用户创建contacts和usermanagements表的关联记录 -async function createUserAssociations(user) { - try { - if (!user || !user.userId) { - console.error('无效的用户数据,无法创建关联记录'); - return false; - } - - console.log('为用户创建关联记录:', user.userId); - - // 使用事务确保操作原子性 - await sequelize.transaction(async (transaction) => { - // 1. 处理联系人记录 - 使用INSERT ... ON DUPLICATE KEY UPDATE确保无论如何都只保留一条记录 - await sequelize.query( - `INSERT INTO contacts (userId, nickName, phoneNumber, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - nickName = VALUES(nickName), - phoneNumber = VALUES(phoneNumber), - updated_at = NOW()`, - { - replacements: [user.userId, user.nickName || '默认联系人', user.phoneNumber || ''], - transaction: transaction - } - ); - console.log('联系人记录已处理(创建或更新):', user.userId); - - // 2. 处理用户管理记录 - 使用相同策略 - await sequelize.query( - `INSERT INTO usermanagements (userId, created_at, updated_at) - VALUES (?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - updated_at = NOW()`, - { - replacements: [user.userId], - transaction: transaction - } - ); - console.log('用户管理记录已处理(创建或更新):', user.userId); - }); - - console.log('用户关联记录处理成功:', user.userId); - return true; - } catch (error) { - console.error('创建用户关联记录失败:', error.message); - return false; - } -} - -// API路由 - -// 上传用户信息 -app.post('/api/user/upload', async (req, res) => { - try { - const userData = req.body; - console.log('收到用户信息上传请求:', userData); - - // 如果用户信息中包含手机号,检查手机号是否已被其他用户使用 - if (userData.phoneNumber) { - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: userData.phoneNumber, - openid: { [Sequelize.Op.ne]: userData.openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${userData.phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 创建新对象,移除手机号字段 - const userDataWithoutPhone = { ...userData }; - delete userDataWithoutPhone.phoneNumber; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息(不包含手机号) - await User.update( - { - ...userDataWithoutPhone, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - } else { - // 创建新用户 - user = await User.create({ - ...userDataWithoutPhone, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - // 返回成功,但提示手机号已被使用 - return res.json({ - success: true, - code: 200, - message: '用户信息保存成功,但手机号已被其他账号绑定', - data: { - userId: user.userId - }, - phoneNumberConflict: true - }); - } - } - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息 - await User.update( - { - ...userData, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } else { - // 创建新用户 - user = await User.create({ - ...userData, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - res.json({ - success: true, - code: 200, - message: '用户信息保存成功', - data: { - userId: user.userId - }, - phoneNumberConflict: false - }); - } catch (error) { - console.error('保存用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '保存用户信息失败', - error: error.message - }); - } -}); - -// 解密手机号 -app.post('/api/user/decodePhone', async (req, res) => { - try { - const { encryptedData, iv, openid } = req.body; - - // 参数校验 - if (!encryptedData || !iv || !openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数' - }); - } - - // 查找用户的session_key - const user = await User.findOne({ where: { openid } }); - - if (!user || !user.session_key) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录,请先登录', - needRelogin: true - }); - } - - // 解密手机号 - let decryptedData, phoneNumber; - try { - decryptedData = decryptData(encryptedData, user.session_key, iv); - phoneNumber = decryptedData.phoneNumber; - } catch (decryptError) { - // 解密失败,可能是session_key过期,建议重新登录 - return res.status(401).json({ - success: false, - code: 401, - message: decryptError.message || '手机号解密失败', - needRelogin: true - }); - } - - // 检查手机号是否已被其他用户使用 - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: phoneNumber, - openid: { [Sequelize.Op.ne]: openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 返回成功,但不更新手机号,提示用户 - return res.json({ - success: true, - code: 200, - message: '手机号已被其他账号绑定', - phoneNumber: user.phoneNumber, // 返回原手机号 - isNewPhone: false - }); - } - - // 更新用户手机号 - await User.update( - { - phoneNumber: phoneNumber, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 更新用户手机号后,更新关联记录 - const updatedUser = await User.findOne({ where: { openid } }); - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '手机号解密成功', - phoneNumber: phoneNumber, - isNewPhone: true - }); - } catch (error) { - console.error('手机号解密失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '手机号解密失败', - error: error.message - }); - } -}); - -// 处理微信登录,获取openid和session_key -app.post('/api/wechat/getOpenid', async (req, res) => { - try { - const { code } = req.body; - - if (!code) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少code参数' - }); - } - - // 获取openid和session_key - const wxData = await getSessionKey(code); - - if (wxData.errcode) { - throw new Error(`微信接口错误: ${wxData.errmsg}`); - } - - const { openid, session_key, unionid } = wxData; - - // 生成userId - const userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid } - }); - - if (user) { - // 更新用户session_key - await User.update( - { - session_key: session_key, - updated_at: new Date() - }, - { - where: { openid } - } - ); - } else { - // 创建新用户 - // 支持从客户端传入type参数,如果没有则默认为buyer - const userType = req.body.type || 'buyer'; - await User.create({ - openid, - userId, - session_key, - nickName: '微信用户', // 临时占位,等待用户授权 - type: userType, // 使用客户端传入的类型或默认买家身份 - created_at: new Date(), - updated_at: new Date() - }); - - // 为新创建的用户创建关联记录 - const newUser = { userId, openid, nickName: '微信用户' }; - await createUserAssociations(newUser); - } - - res.json({ - success: true, - code: 200, - message: '获取openid成功', - data: { - openid, - userId: user ? user.userId : userId - } - }); - } catch (error) { - console.error('获取openid失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取openid失败', - error: error.message - }); - } -}); - -// 验证用户登录状态 -app.post('/api/user/validate', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'avatarUrl', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录' - }); - } - - res.json({ - success: true, - code: 200, - message: '验证成功', - data: user - }); - } catch (error) { - console.error('验证用户登录状态失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '验证失败', - error: error.message - }); - } -}); - -// 获取用户信息 -app.post('/api/user/get', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - include: [ - { - model: Contact, - as: 'contacts', - attributes: ['id', 'nickName', 'phoneNumber', 'wechat', 'account', 'accountNumber', 'bank', 'address'] - }, - { - model: UserManagement, - as: 'management', - attributes: ['id', 'managerId', 'company', 'department', 'organization', 'role', 'root'] - } - ] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - res.json({ - success: true, - code: 200, - message: '获取用户信息成功', - data: user - }); - } catch (error) { - console.error('获取用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户信息失败', - error: error.message - }); - } -}); - -// 更新用户信息 -app.post('/api/user/update', async (req, res) => { - try { - const { openid, ...updateData } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid } - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 更新用户信息 - await User.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 获取更新后的用户信息 - const updatedUser = await User.findOne({ - where: { openid } - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '更新用户信息成功', - data: updatedUser - }); - } catch (error) { - console.error('更新用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新用户信息失败', - error: error.message - }); - } -}); - -// 获取商品列表 - 优化版本确保状态筛选正确应用 -app.post('/api/product/list', async (req, res) => { - try { - const { openid, status, keyword, page = 1, pageSize = 20, testMode = false } = req.body; - - // 验证openid参数(测试模式除外) - if (!openid && !testMode) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 构建查询条件 - const where = {}; - - // 查找用户 - let user = null; - if (!testMode) { - user = await User.findOne({ where: { openid } }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 只有管理员可以查看所有商品,普通用户只能查看自己的商品 - if (user.type !== 'admin') { - where.sellerId = user.userId; - } - } - - // 状态筛选 - 直接构建到where对象中,确保不会丢失 - console.log(`当前用户类型: ${user ? user.type : '未知'},请求状态: ${status || '未指定'},测试模式: ${testMode}`); - - // 如果有指定status参数,按参数筛选但同时排除hidden - if (status) { - console.log(`按状态筛选商品: status=${status},并排除hidden状态`); - if (status === 'all') { - // 特殊情况:请求所有商品但仍然排除hidden - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else if (Array.isArray(status)) { - // 如果status是数组,确保不包含hidden - where.status = { [Sequelize.Op.in]: status.filter(s => s !== 'hidden') }; - } else { - // 单个状态值,确保不是hidden - if (status !== 'hidden') { - where.status = { [Sequelize.Op.eq]: status }; - } else { - // 如果明确请求hidden状态,也返回空结果 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } - } - } else { - // 没有指定status参数时 - 直接在where对象中设置状态筛选 - if (user && (user.type === 'seller' || user.type === 'both') && !testMode) { - // 卖家用户且非测试模式 - console.log(`卖家用户 ${user.userId} (类型:${user.type}) 查看自己的所有商品,但排除hidden状态`); - // 卖家可以查看自己的所有商品,但仍然排除hidden状态 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else { - // 测试模式或非卖家用户 - console.log(`测试模式或非卖家用户,使用默认状态筛选: reviewed/published`); - // 默认只显示已审核和已发布的商品,排除hidden和sold_out状态 - where.status = { [Sequelize.Op.in]: ['reviewed', 'published'] }; - } - } - - console.log(`构建的完整查询条件:`, JSON.stringify(where, null, 2)); - - // 关键词搜索 - if (keyword) { - where.productName = { [Sequelize.Op.like]: `%${keyword}%` }; - } - - // 计算偏移量 - const offset = (page - 1) * pageSize; - - // 查询商品列表 - const { count, rows: products } = await Product.findAndCountAll({ - where, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - }, - // 添加CartItem关联以获取预约人数 - { - model: CartItem, - as: 'cartItems', // 明确指定别名 - attributes: [], - required: false // 允许没有购物车项的商品也能返回 - } - ], - // 添加selected字段,计算商品被加入购物车的次数(预约人数) - attributes: { - include: [ - [Sequelize.fn('COUNT', Sequelize.col('cartItems.id')), 'selected'] - ] - }, - order: [['created_at', 'DESC']], - limit: pageSize, - offset, - // 修复分组问题 - group: ['Product.productId', 'seller.userId'] // 使用正确的字段名 - }); - - // 添加详细日志,记录查询结果 - console.log(`商品列表查询结果 - 商品数量: ${count}, 商品列表长度: ${products.length}`); - if (products.length > 0) { - console.log(`第一个商品数据:`, JSON.stringify(products[0], null, 2)); - - // 添加selected字段的专门日志 - console.log('商品预约人数(selected字段)统计:'); - products.slice(0, 5).forEach(product => { - const productJSON = product.toJSON(); - console.log(`- ${productJSON.productName}: 预约人数=${productJSON.selected || 0}, 商品ID=${productJSON.productId}`); - }); - } - - // 处理商品列表中的grossWeight字段,确保是数字类型 - const processedProducts = products.map(product => { - const productJSON = product.toJSON(); - - // 详细分析毛重字段 - const grossWeightDetails = { - value: productJSON.grossWeight, - type: typeof productJSON.grossWeight, - isEmpty: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined, - isNumeric: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined || !isNaN(parseFloat(productJSON.grossWeight)) && isFinite(productJSON.grossWeight), - parsedValue: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined ? 0 : parseFloat(productJSON.grossWeight) - }; - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - productJSON.grossWeight = finalGrossWeight; - - // 确保selected字段存在并设置为数字类型(修复后的代码) - if ('selected' in productJSON) { - // 确保selected是数字类型 - productJSON.selected = parseInt(productJSON.selected, 10); - } else { - // 如果没有selected字段,设置默认值为0 - productJSON.selected = 0; - } - - // 记录第一个商品的转换信息用于调试 - if (products.indexOf(product) === 0) { - console.log('商品列表 - 第一个商品毛重字段处理:'); - console.log('- 原始值:', grossWeightDetails.value, '类型:', grossWeightDetails.type); - console.log('- 转换后的值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('- selected字段: 存在=', 'selected' in productJSON, '值=', productJSON.selected, '类型=', typeof productJSON.selected); - } - - return productJSON; - });; - - // 准备响应数据 - 修改格式以匹配前端期望 - const responseData = { - success: true, - code: 200, - message: '获取商品列表成功', - products: processedProducts, - total: count, - page: page, - pageSize: pageSize, - totalPages: Math.ceil(count / pageSize) - }; - - console.log(`准备返回的响应数据格式:`, JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - - // 添加详细的查询条件日志 - console.log(`最终查询条件:`, JSON.stringify(where, null, 2)); - - res.json(responseData); - } catch (error) { - console.error('获取商品列表失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品列表失败', - error: error.message - }); - } -}); - -// 上传商品 -app.post('/api/products/upload', async (req, res) => { - try { - // 修复毛重字段处理逻辑 - let productData = req.body; - if (productData && productData.productData) { - productData = productData.productData; // 使用正确的productData对象 - } - - // 改进的毛重字段处理逻辑,与编辑API保持一致 - // 详细分析毛重字段 - const grossWeightDetails = { - value: productData.grossWeight, - type: typeof productData.grossWeight, - isEmpty: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined, - isNumeric: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined || !isNaN(parseFloat(productData.grossWeight)) && isFinite(productData.grossWeight), - parsedValue: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined ? 0 : parseFloat(productData.grossWeight) - }; - - // 详细的日志记录 - console.log('上传商品 - 毛重字段详细分析:'); - console.log('- 原始值:', productData.grossWeight, '类型:', typeof productData.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行精确四舍五入,确保3位小数以上的值正确转换 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - productData.grossWeight = finalGrossWeight; - console.log('上传商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('收到商品上传请求:', productData); - - // 验证必要字段 - if (!productData.sellerId || !productData.productName || !productData.price || !productData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的商品信息' - }); - } - - // 检查sellerId是否为openid,如果是则查找对应的userId - let actualSellerId = productData.sellerId; - - // 如果sellerId看起来像一个openid(包含特殊字符如'-'),则尝试查找对应的userId - if (productData.sellerId.includes('-')) { - console.log('sellerId看起来像openid,尝试查找对应的userId'); - const user = await User.findOne({ - where: { - openid: productData.sellerId - } - }); - - if (user && user.userId) { - console.log(`找到了对应的userId: ${user.userId}`); - actualSellerId = user.userId; - } else { - console.error(`未找到对应的用户记录,openid: ${productData.sellerId}`); - return res.status(400).json({ - success: false, - code: 400, - message: '找不到对应的用户记录' - }); - } - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 创建商品,使用实际的sellerId - let product = await Product.create({ - ...productData, - sellerId: actualSellerId, // 使用查找到的userId - productId, - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - product = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 与编辑API保持一致的处理逻辑 - if (product) { - console.log('上传商品 - 处理前grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (product.grossWeight === undefined || product.grossWeight === null || product.grossWeight === '') { - product.grossWeight = 0; - console.log('上传商品 - 检测到空值,已设置为0'); - } else { - // 否则转换为浮点数 - product.grossWeight = parseFloat(product.grossWeight); - } - - console.log('上传商品 - 处理后grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品上传成功', - data: { - productId: product.productId, - product: product - } - }); - } catch (error) { - console.error('商品上传失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '商品上传失败', - error: error.message - }); - } -}); - -// 获取商品详情 -app.post('/api/products/detail', async (req, res) => { - try { - const { productId } = req.body; - - if (!productId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId参数' - }); - } - - // 查询商品详情 - 排除hidden状态商品 - const product = await Product.findOne({ - where: { - productId, - status: { [Sequelize.Op.not]: 'hidden' } - }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 对返回的商品数据中的grossWeight字段进行处理,确保是数字类型 - let updatedProduct = { ...product.toJSON() }; - - // 详细分析毛重字段 - const grossWeightDetails = { - value: updatedProduct.grossWeight, - type: typeof updatedProduct.grossWeight, - isEmpty: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined, - isNumeric: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined || !isNaN(parseFloat(updatedProduct.grossWeight)) && isFinite(updatedProduct.grossWeight), - parsedValue: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined ? 0 : parseFloat(updatedProduct.grossWeight) - }; - - // 详细的日志记录 - console.log('商品详情 - 毛重字段详细分析:'); - console.log('- 原始值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - updatedProduct.grossWeight = finalGrossWeight; - console.log('商品详情 - 最终返回的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - res.json({ - success: true, - code: 200, - message: '获取商品详情成功', - data: updatedProduct - }); - } catch (error) { - console.error('获取商品详情失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品详情失败', - error: error.message - }); - } -}); - -// 修改商品 -app.post('/api/products/edit', async (req, res) => { - try { - const { productId, ...updateData } = req.body; - const { sellerId } = req.body; - - if (!productId || !sellerId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - return res.status(403).json({ - success: false, - code: 403, - message: '您无权修改此商品' - }); - } - - // 更新商品信息 - await Product.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { productId } - } - ); - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '修改商品成功', - data: updatedProduct - }); - } catch (error) { - console.error('修改商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '修改商品失败', - error: error.message - }); - } -}); - -// 删除商品 - 将商品状态设置为hidden表示已删除 -app.post('/api/products/delete', async (req, res) => { - console.log('收到删除商品请求:', req.body); - try { - const { productId, sellerId } = req.body; - - if (!productId || !sellerId) { - console.error('删除商品失败: 缺少productId或sellerId参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - console.error('删除商品失败: 商品不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - console.error('删除商品失败: 权限不足 - 卖家ID不匹配', { expected: product.sellerId, actual: sellerId }); - return res.status(403).json({ - success: false, - code: 403, - message: '您无权删除此商品' - }); - } - - console.log('准备更新商品状态为hidden,当前状态:', product.status); - - // 直接使用商品实例更新状态 - product.status = 'hidden'; - product.updated_at = new Date(); - - try { - // 先尝试保存商品实例 - await product.save(); - console.log('删除商品成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - // 如果保存失败,尝试使用update方法 - try { - const updateResult = await Product.update( - { status: 'hidden', updated_at: new Date() }, - { where: { productId } } - ); - console.log('删除商品成功(使用update方法):', { productId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - // 如果update方法也失败,尝试直接执行SQL语句绕过ORM验证 - try { - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId', - { - replacements: { - status: 'hidden', - updatedAt: new Date(), - productId: productId - } - } - ); - console.log('删除商品成功(使用原始SQL):', { productId }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 从购物车中移除该商品 - const destroyResult = await CartItem.destroy({ - where: { productId } - }); - console.log('从购物车移除商品结果:', destroyResult); - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '删除商品成功', - product: { - productId: updatedProduct.productId, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('删除商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除商品失败', - error: error.message - }); - } -}); - -// 添加商品到购物车 -app.post('/api/cart/add', async (req, res) => { - // 增加全局错误捕获,确保即使在try-catch外部的错误也能被处理 - try { - console.log('收到添加到购物车请求 - 开始处理', req.url); - let cartData = req.body; - console.log('收到添加到购物车请求数据:', cartData); - console.log('请求头:', req.headers); - console.log('请求IP:', req.ip); - - // 兼容客户端请求格式:客户端可能将数据封装在product对象中,并且使用openid而不是userId - if (cartData.product && !cartData.productId) { - // 从product对象中提取数据 - const productData = cartData.product; - console.log('从product对象提取数据:', productData); - console.log('客户端提供的openid:', cartData.openid); - - // 使用openid作为userId - cartData = { - userId: cartData.openid || productData.userId, - productId: productData.productId || productData.id, - productName: productData.productName || productData.name, - quantity: productData.quantity || 1, - price: productData.price, - specification: productData.specification || productData.spec || '', - grossWeight: productData.grossWeight || productData.weight, - yolk: productData.yolk || productData.variety || '', - testMode: productData.testMode || cartData.testMode - }; - console.log('转换后的购物车数据:', cartData); - - // 检查转换后的userId是否存在于users表中 - try { - console.log('开始查询用户信息,openid:', cartData.userId); - const user = await User.findOne({ - where: { openid: cartData.userId } - }); - if (user) { - console.log(`找到对应的用户记录: openid=${cartData.userId}, userId=${user.userId}`); - // 修正:使用数据库中真实的userId而不是openid - cartData.userId = user.userId; - console.log('修正后的userId:', cartData.userId); - } else { - console.error(`未找到openid为 ${cartData.userId} 的用户记录,无法添加到购物车`); - // 重要:找不到用户时返回错误,避免使用无效的userId导致外键约束失败 - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `未找到用户记录: ${cartData.userId}` - }); - } - } catch (error) { - console.error('查询用户信息失败:', error); - // 查询失败时也返回错误 - return res.status(400).json({ - success: false, - code: 400, - message: '查询用户信息失败', - error: error.message - }); - } - } - - // 验证必要字段 - if (!cartData.userId || !cartData.productId || !cartData.productName || !cartData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的购物车信息', - missingFields: [ - !cartData.userId ? 'userId' : '', - !cartData.productId ? 'productId' : '', - !cartData.productName ? 'productName' : '', - !cartData.quantity ? 'quantity' : '' - ].filter(Boolean) - }); - } - - // 先验证用户ID是否存在于users表中 - try { - const userExists = await User.findOne({ - where: { userId: cartData.userId } - }); - if (!userExists) { - console.error(`用户ID ${cartData.userId} 不存在于users表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `用户ID ${cartData.userId} 不存在` - }); - } else { - console.log(`用户ID ${cartData.userId} 存在于users表中,用户验证通过`); - } - } catch (error) { - console.error('验证用户ID失败:', error); - return res.status(400).json({ - success: false, - code: 400, - message: '验证用户信息失败', - error: error.message - }); - } - - // 检查商品是否存在以及是否为hidden状态 - console.log(`检查商品ID: ${cartData.productId} 是否存在于products表中`); - const product = await Product.findOne({ - where: { - productId: cartData.productId - } - }); - - if (!product) { - console.error(`商品ID ${cartData.productId} 不存在于products表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '商品不存在或已被移除', - error: `未找到商品ID: ${cartData.productId}` - }); - } else { - console.log(`商品ID ${cartData.productId} 存在于products表中,商品名称: ${product.productName}`); - } - - if (product.status === 'hidden') { - return res.status(400).json({ - success: false, - code: 400, - message: '该商品已下架,无法添加到购物车' - }); - } - - // 在testMode下,不执行实际的数据库操作,直接返回成功 - if (cartData.testMode) { - console.log('测试模式:跳过实际的数据库操作'); - res.json({ - success: true, - code: 200, - message: '测试模式:添加到购物车成功', - data: { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity - } - }); - return; - } - - // 检查是否已存在相同商品 - const existingItem = await CartItem.findOne({ - where: { - userId: cartData.userId, - productId: cartData.productId - } - }); - - // 添加try-catch捕获外键约束错误 - try { - console.log(`准备创建/更新购物车项: userId=${cartData.userId}, productId=${cartData.productId}`); - if (existingItem) { - // 已存在,更新数量 - await CartItem.update( - { - quantity: existingItem.quantity + cartData.quantity, - updated_at: new Date() - }, - { - where: { - id: existingItem.id - } - } - ); - console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); - } else { - // 不存在,创建新购物车项 - console.log('创建新购物车项,所有字段:', { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity, - price: cartData.price, - specification: cartData.specification, - grossWeight: cartData.grossWeight, - yolk: cartData.yolk - }); - // 重要:在创建前再次验证数据完整性 - if (!cartData.userId || !cartData.productId) { - throw new Error(`数据不完整: userId=${cartData.userId}, productId=${cartData.productId}`); - } - await CartItem.create({ - ...cartData, - added_at: new Date() - }); - console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); - } - } catch (createError) { - console.error('创建/更新购物车项失败,可能是外键约束问题:', createError); - console.error('详细错误信息:', { - name: createError.name, - message: createError.message, - stack: createError.stack, - sql: createError.sql || '无SQL信息', - userId: cartData.userId, - productId: cartData.productId - }); - - // 检测是否是外键约束错误 - if (createError.name === 'SequelizeForeignKeyConstraintError' || createError.message.includes('foreign key')) { - // 区分是用户ID还是商品ID问题 - let errorField = 'productId'; - let errorMessage = '商品信息已更新,请刷新页面后重试'; - - if (createError.message.includes('userId') || createError.message.includes('user') || createError.message.toLowerCase().includes('user')) { - errorField = 'userId'; - errorMessage = '用户信息无效,请重新登录后重试'; - } - - return res.status(400).json({ - success: false, - code: 400, - message: errorMessage, - error: `外键约束错误: ${errorField} 不存在或已失效`, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 其他类型的错误也返回400状态码,避免500错误 - return res.status(400).json({ - success: false, - code: 400, - message: '添加购物车项失败,请稍后重试', - error: createError.message, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 更新商品的预约人数 - 更健壮的实现 - try { - console.log(`尝试更新商品预约人数: productId=${cartData.productId}`); - - // 先验证商品是否存在 - const productCheck = await Product.findOne({where: {productId: cartData.productId}}); - if (productCheck) { - // 商品存在,才进行更新 - await Product.increment('reservedCount', {by: 1, where: {productId: cartData.productId}}); - console.log(`商品预约人数更新成功: productId=${cartData.productId}, 新数量=${productCheck.reservedCount + 1}`); - } else { - console.error(`更新商品预约人数失败: 商品ID ${cartData.productId} 不存在`); - } - } catch (updateError) { - console.error(`更新商品预约人数失败:`, updateError); - // 继续执行,不中断主要流程 - } - - res.json({ - success: true, - code: 200, - message: '添加到购物车成功' - }); - } catch (error) { - console.error('添加到购物车失败:', error); - console.error('全局错误捕获,详细信息:', { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - }); - - // 增强的错误处理 - 强制所有错误返回400状态码 - console.error('全局错误处理 - 捕获到未处理的错误:', error); - const statusCode = 400; // 强制所有错误返回400状态码,避免前端显示500错误 - let errorMessage = '添加到购物车失败'; - - // 更精确地检测外键约束错误 - if (error.name === 'SequelizeForeignKeyConstraintError' || - error.message.toLowerCase().includes('foreign key') || - error.message.toLowerCase().includes('constraint fails') || - error.message.toLowerCase().includes('constraint')) { - errorMessage = '添加到购物车失败:商品或用户信息已更新,请刷新页面后重试'; - console.error('检测到外键约束相关错误,返回400状态码'); - } - - console.log(`准备返回错误响应 - 状态码: ${statusCode}, 消息: ${errorMessage}`); - - // 确保响应能够正确发送 - try { - res.status(statusCode).json({ - success: false, - code: statusCode, - message: errorMessage, - error: error.message, - errorDetails: { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - } - }); - } catch (resError) { - console.error('发送错误响应失败:', resError); - // 即使发送响应失败,也尝试以文本格式发送 - try { - res.status(400).send('添加到购物车失败,请刷新页面后重试'); - } catch (finalError) { - console.error('无法发送任何响应:', finalError); - } - } - } -}); - -// 获取购物车信息 -app.post('/api/cart/get', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 查询购物车信息 - 排除关联商品为hidden或sold_out状态的项 - const cartItems = await CartItem.findAll({ - where: { userId }, - include: [ - { - model: Product, - as: 'product', - attributes: ['productName', 'price', 'quantity', 'status', 'specification', 'grossWeight', 'yolk'], - where: { - status: { [Sequelize.Op.notIn]: ['hidden', 'sold_out'] } - } - } - ], - order: [['added_at', 'DESC']] - }); - - res.json({ - success: true, - code: 200, - message: '获取购物车信息成功', - data: { - cartItems - } - }); - } catch (error) { - console.error('获取购物车信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取购物车信息失败', - error: error.message - }); - } -}); - -// 更新购物车项 -app.post('/api/cart/update', async (req, res) => { - try { - const { id, quantity, selected } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 构建更新数据 - const updateData = {}; - if (quantity !== undefined) updateData.quantity = quantity; - if (selected !== undefined) updateData.selected = selected; - updateData.updated_at = new Date(); - - // 更新购物车项 - await CartItem.update(updateData, { - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '更新购物车成功' - }); - } catch (error) { - console.error('更新购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新购物车失败', - error: error.message - }); - } -}); - -// 删除购物车项 -app.post('/api/cart/delete', async (req, res) => { - try { - const { id } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 删除购物车项 - await CartItem.destroy({ - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '删除购物车项成功' - }); - } catch (error) { - console.error('删除购物车项失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除购物车项失败', - error: error.message - }); - } -}); - -// 清空购物车 -app.post('/api/cart/clear', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 清空购物车 - await CartItem.destroy({ - where: { userId } - }); - - res.json({ - success: true, - code: 200, - message: '清空购物车成功' - }); - } catch (error) { - console.error('清空购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '清空购物车失败', - error: error.message - }); - } -}); - -// 测试连接接口 -app.get('/api/test-connection', async (req, res) => { - try { - // 检查数据库连接 - await sequelize.authenticate(); - - res.json({ - success: true, - code: 200, - message: '服务器连接成功,数据库可用', - timestamp: new Date().toISOString(), - serverInfo: { - port: PORT, - nodeVersion: process.version, - database: 'MySQL', - status: 'running' - } - }); - } catch (error) { - res.status(500).json({ - success: false, - code: 500, - message: '服务器连接失败', - error: error.message - }); - } -}); - -// 用户类型调试接口 - 增强版:用于排查用户类型和商品显示问题 -app.post('/api/user/debug', async (req, res) => { - try { - const { openid } = req.body; - - console.log('收到用户调试请求,openid:', openid); - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查询用户信息 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在', - debugInfo: { - searchCriteria: { openid }, - timestamp: new Date().toISOString() - } - }); - } - - // 查询该用户的商品统计信息 - const totalProducts = await Product.count({ where: { sellerId: user.userId } }); - const pendingProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'pending_review' - } - }); - const reviewedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'reviewed' - } - }); - const publishedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'published' - } - }); - const soldOutProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'sold_out' - } - }); - - // 判断用户是否有权限查看所有商品 - const canViewAllProducts = ['seller', 'both', 'admin'].includes(user.type); - - // 获取该用户的最新5个商品信息(用于调试) - const latestProducts = await Product.findAll({ - where: { sellerId: user.userId }, - limit: 5, - order: [['created_at', 'DESC']], - attributes: ['productId', 'productName', 'status', 'created_at'] - }); - - const responseData = { - success: true, - code: 200, - message: '获取用户调试信息成功', - userInfo: user, - productStats: { - total: totalProducts, - pendingReview: pendingProducts, - reviewed: reviewedProducts, - published: publishedProducts, - soldOut: soldOutProducts - }, - permissionInfo: { - canViewAllProducts: canViewAllProducts, - userType: user.type, - allowedTypesForViewingAllProducts: ['seller', 'both', 'admin'] - }, - latestProducts: latestProducts, - debugInfo: { - userCount: await User.count(), - totalProductsInSystem: await Product.count(), - timestamp: new Date().toISOString(), - serverTime: new Date().toLocaleString('zh-CN') - } - }; - - console.log('调试信息返回数据:', JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - res.json(responseData); - } catch (error) { - console.error('获取用户调试信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户调试信息失败', - error: error.message, - debugInfo: { - errorStack: error.stack, - timestamp: new Date().toISOString() - } - }); - } -}); - -// 下架商品接口 - 将商品状态设置为sold_out表示已下架 -app.post('/api/product/hide', async (req, res) => { - console.log('收到下架商品请求:', req.body); - - try { - const { openid, productId } = req.body; - - // 验证请求参数 - if (!openid || !productId) { - console.error('下架商品失败: 缺少必要参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要参数: openid和productId都是必需的' - }); - } - - // 查找用户 - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('下架商品失败: 用户不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - console.log('找到用户信息:', { userId: user.userId, nickName: user.nickName }); - - // 查找商品并验证所有权 - 直接使用userId,因为商品创建时使用的就是userId - const product = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - - if (!product) { - console.error('下架商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 记录当前状态,用于调试 - console.log('当前商品状态:', product.status, '允许的状态列表:', Product.rawAttributes.status.validate.isIn); - console.log('商品所属卖家ID:', product.sellerId); - console.log('用户ID信息对比:', { userId: user.userId, id: user.id }); - - console.log('准备更新商品状态为sold_out,当前状态:', product.status); - - // 更新商品状态为已下架(sold_out) - 尝试多种更新方式确保成功 - try { - // 方法1: 直接保存实例 - product.status = 'sold_out'; - product.updated_at = new Date(); - await product.save(); - console.log('商品下架成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - try { - // 方法2: 使用update方法 - const updateResult = await Product.update( - { status: 'sold_out', updated_at: new Date() }, - { where: { productId: productId, sellerId: user.userId } } - ); - console.log('商品下架成功(使用update方法):', { productId: productId, sellerIdType: typeof user.userId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - try { - // 方法3: 直接执行SQL语句绕过ORM验证 - const replacements = { - status: 'sold_out', - updatedAt: new Date(), - productId: productId, - sellerId: user.userId - }; - - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId AND sellerId = :sellerId', - { - replacements: replacements - } - ); - console.log('商品下架成功(使用原始SQL):', { productId: product.productId, productName: product.productName }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: product.sellerId // 使用找到的商品的sellerId进行查询 - } - }); - - res.json({ - success: true, - code: 200, - message: '商品下架成功', - product: { - productId: updatedProduct.productId, - productName: updatedProduct.productName, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('下架商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '下架商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 发布商品API -app.post('/api/product/publish', async (req, res) => { - console.log('收到发布商品请求:', req.body); // 记录完整请求体 - - try { - const { openid, product } = req.body; - - // 验证必填字段 - console.log('验证请求参数: openid=', !!openid, ', product=', !!product); - if (!openid || !product) { - console.error('缺少必要参数: openid=', openid, 'product=', product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid或product对象)' - }); - } - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price, '转换为数字=', parseFloat(product.price)); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity, '转换为数字=', parseInt(product.quantity)); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误和字段值详情 - const validationErrors = []; - const fieldDetails = {}; - - // 检查商品名称 - fieldDetails.productName = { - value: product.productName, - type: typeof product.productName, - isEmpty: !product.productName || product.productName.trim() === '' - }; - if (fieldDetails.productName.isEmpty) { - console.error('商品名称为空'); - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } - - // 检查价格 - fieldDetails.price = { - value: product.price, - type: typeof product.price, - isNumber: !isNaN(parseFloat(product.price)) && isFinite(product.price), - parsedValue: parseFloat(product.price), - isValid: !isNaN(parseFloat(product.price)) && isFinite(product.price) && parseFloat(product.price) > 0 - }; - if (!product.price) { - console.error('价格为空'); - validationErrors.push('价格为必填项'); - } else if (!fieldDetails.price.isNumber) { - console.error('价格不是有效数字: price=', product.price); - validationErrors.push('价格必须是有效数字格式'); - } else if (fieldDetails.price.parsedValue <= 0) { - console.error('价格小于等于0: price=', product.price, '转换为数字后=', fieldDetails.price.parsedValue); - validationErrors.push('价格必须大于0'); - } - - // 检查数量 - fieldDetails.quantity = { - value: product.quantity, - type: typeof product.quantity, - isNumeric: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity), - parsedValue: Math.floor(parseFloat(product.quantity)), - isValid: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity) && parseFloat(product.quantity) > 0 - }; - if (!product.quantity) { - console.error('数量为空'); - validationErrors.push('数量为必填项'); - } else if (!fieldDetails.quantity.isNumeric) { - console.error('数量不是有效数字: quantity=', product.quantity); - validationErrors.push('数量必须是有效数字格式'); - } else if (fieldDetails.quantity.parsedValue <= 0) { - console.error('数量小于等于0: quantity=', product.quantity, '转换为数字后=', fieldDetails.quantity.parsedValue); - validationErrors.push('数量必须大于0'); - } - - // 改进的毛重字段处理逻辑 - 与其他API保持一致 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('发布商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保商品名称不超过数据库字段长度限制 - if (product.productName && product.productName.length > 255) { - console.error('商品名称过长: 长度=', product.productName.length); - validationErrors.push('商品名称不能超过255个字符'); - } - - // 如果有验证错误,一次性返回所有错误信息和字段详情 - if (validationErrors.length > 0) { - console.error('验证失败 - 详细信息:', JSON.stringify({ - errors: validationErrors, - fieldDetails: fieldDetails - }, null, 2)); - - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors, - detailedMessage: validationErrors.join('; '), - fieldDetails: fieldDetails - }); - } - - // 查找用户 - console.log('开始查找用户: openid=', openid); - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - console.log('找到用户:', { userId: user.userId, nickName: user.nickName, type: user.type }); - - // 验证用户类型 - console.log(`验证用户类型: 用户ID=${user.userId}, 类型=${user.type}`); - if (user.type !== 'seller' && user.type !== 'both') { - console.error(`商品发布失败: 用户${user.userId}类型为${user.type},需要seller或both类型`); - return res.status(403).json({ - success: false, - code: 403, - message: '只有卖家才能发布商品,请在个人资料中修改用户类型' - }); - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - console.log('生成商品ID:', productId); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行正确的四舍五入 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - console.log('发布商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 创建商品 - console.log('准备创建商品:', { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, - sellerId: user.userId - }); - - const newProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk || '', - specification: product.specification || '', - status: 'pending_review', // 默认状态为待审核 - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - const createdProduct = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - if (createdProduct) { - console.log('发布商品 - 数据库查询后grossWeight:', createdProduct.grossWeight, '类型:', typeof createdProduct.grossWeight); - } - - // 更新用户类型:如果字段为空则填入seller,如果为buyer则修改为both - try { - // 重新获取用户信息以确保获取到最新数据 - const currentUser = await User.findOne({ where: { userId: user.userId } }); - if (currentUser) { - console.log('更新用户类型前 - 当前用户类型:', currentUser.type); - - // 检查用户类型并根据需求更新 - if ((currentUser.type === null || currentUser.type === undefined || currentUser.type === '') || currentUser.type === 'buyer') { - let newType = ''; - if (currentUser.type === 'buyer') { - newType = 'both'; - } else { - newType = 'seller'; - } - - // 更新用户类型 - await User.update( - { type: newType }, - { where: { userId: user.userId } } - ); - console.log('用户类型更新成功 - 用户ID:', user.userId, '旧类型:', currentUser.type, '新类型:', newType); - } else { - console.log('不需要更新用户类型 - 用户ID:', user.userId, '当前类型:', currentUser.type); - } - } - } catch (updateError) { - console.error('更新用户类型失败:', updateError); - // 不影响商品发布结果,仅记录错误 - } - - res.json({ - success: true, - code: 200, - message: '商品发布成功', - product: createdProduct, - productId: productId - }); - - } catch (error) { - console.error('发布商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '发布商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`服务器运行在 http://localhost:${PORT}`); - console.log('注意:当前服务器已添加详细日志记录,用于排查发布商品问题'); - console.log('调试API: POST /api/user/debug - 用于查看用户类型信息'); - console.log(`测试连接接口: http://localhost:${PORT}/api/test-connection`); -}); - -// 编辑商品API - 用于审核失败商品重新编辑 -app.post('/api/product/edit', async (req, res) => { - console.log('收到编辑商品请求 - 详细信息:'); - console.log('- 请求路径:', req.url); - console.log('- 请求方法:', req.method); - console.log('- 请求完整body:', req.body); - console.log('- 服务器端口:', PORT); - console.log('- 环境变量:', process.env.PORT); - - try { - // 正确解析请求参数,处理嵌套的productData结构 - let openid = req.body.openid; - let productId = req.body.productId; - let status = req.body.status; - let testMode = req.body.testMode; - let product = req.body.product; - - // 处理多层嵌套的productData结构 - if (!product && req.body.productData) { - // 处理第一种情况: { productData: { openid, productId, product: { ... } } } - if (req.body.productData.product) { - product = req.body.productData.product; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - // 处理第二种情况: { productData: { openid, productId, productName, price, ... } } - else { - product = req.body.productData; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - } - - // 调试日志 - console.log('解析后参数:', { openid, productId, status, testMode, product: !!product }); - - console.log('收到编辑商品请求,包含状态参数:', { openid, productId, status, testMode }); - - // 验证必填字段 - if (!openid || !productId || !product) { - console.error('缺少必要参数: openid=', !!openid, 'productId=', !!productId, 'product=', !!product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid、productId或product对象)' - }); - } - - // 查找用户 - let user = null; - - // 测试模式下的特殊处理 - if (testMode) { - console.log('测试模式:尝试查找或创建测试用户'); - // 首先尝试查找openid为'test_openid'的用户 - user = await User.findOne({ - where: { openid: 'test_openid' } - }); - - if (!user) { - // 如果不存在,创建一个新的测试用户 - console.log('测试模式:创建测试用户'); - try { - user = await User.create({ - openid: 'test_openid', - userId: 'test_user_id', - nickName: '测试用户', - type: 'seller' - }); - } catch (createError) { - console.error('测试模式:创建测试用户失败', createError); - // 如果创建失败,尝试查找数据库中的第一个用户 - user = await User.findOne({ - order: [['id', 'ASC']] - }); - if (user) { - console.log('测试模式:使用数据库中的现有用户', user.userId); - } - } - } else { - console.log('测试模式:使用已存在的测试用户', user.userId); - } - } else { - // 非测试模式:按常规方式查找用户 - user = await User.findOne({ where: { openid } }); - } - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - // 查找商品 - let existingProduct = null; - - if (testMode) { - // 测试模式:如果找不到商品,尝试使用测试商品或创建一个新的测试商品 - existingProduct = await Product.findOne({ - where: { - productId: productId - } - }); - - // 如果找不到指定的商品,创建一个新的测试商品 - if (!existingProduct) { - console.log('测试模式:创建测试商品'); - try { - existingProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: '测试商品', - price: 99.99, - quantity: 100, - grossWeight: 0, // 默认为0而不是5,符合用户需求 - yolk: '测试描述', - specification: '测试规格', - status: 'rejected', // 设置为可编辑状态 - created_at: new Date(), - updated_at: new Date() - }); - console.log('测试模式:测试商品创建成功'); - } catch (createProductError) { - console.error('测试模式:创建测试商品失败', createProductError); - } - } - } else { - // 非测试模式:验证商品所有权 - existingProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - } - - if (!existingProduct) { - console.error('编辑商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 验证商品状态是否允许编辑 - if (!['rejected', 'sold_out', 'pending_review', 'reviewed'].includes(existingProduct.status)) { - console.error(`编辑商品失败: 商品状态(${existingProduct.status})不允许编辑`, { - productId: productId, - sellerId: user.userId, - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - }); - return res.status(403).json({ - success: false, - code: 403, - message: '只有审核失败、已下架、审核中或已审核的商品才能编辑', - debugInfo: { - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - } - }); - } - - // 记录商品编辑信息,用于调试 - console.log(`允许编辑商品: productId=${productId}, status=${existingProduct.status}, sellerId=${user.userId}`); - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误 - const validationErrors = []; - - // 检查商品名称 - if (!product.productName || product.productName.trim() === '') { - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } else if (product.productName.length > 255) { - validationErrors.push('商品名称不能超过255个字符'); - } - - // 检查价格 - if (!product.price) { - validationErrors.push('价格为必填项'); - } else if (isNaN(parseFloat(product.price)) || parseFloat(product.price) <= 0) { - validationErrors.push('价格必须是大于0的有效数字'); - } - - // 检查数量 - if (!product.quantity) { - validationErrors.push('数量为必填项'); - } else if (isNaN(parseInt(product.quantity)) || parseInt(product.quantity) <= 0) { - validationErrors.push('数量必须是大于0的有效数字'); - } - - // 改进的毛重字段处理逻辑,与其他API保持一致,空值默认设为0 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('编辑商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保grossWeight值是数字类型 - const finalGrossWeight = Number(grossWeightDetails.parsedValue); - console.log('编辑商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 如果有验证错误,返回错误信息 - if (validationErrors.length > 0) { - console.error('验证失败 - 错误:', validationErrors.join('; ')); - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors - }); - } - - // 准备更新的商品数据 - const updatedProductData = { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk, - specification: product.specification, - // 优先使用前端传递的status参数,如果没有传递则使用原来的逻辑 - status: status && ['pending_review', 'published'].includes(status) ? status : - (product.resubmit && ['rejected', 'sold_out'].includes(existingProduct.status)) ? 'pending_review' : existingProduct.status, - rejectReason: (status === 'pending_review' || (product.resubmit && existingProduct.status === 'rejected')) ? null : existingProduct.rejectReason, // 提交审核时清除拒绝原因 - updated_at: new Date() - }; - - console.log('准备更新商品数据:', { productId, updatedStatus: updatedProductData.status, fromStatus: existingProduct.status }); - - // 更新商品 - const [updatedCount] = await Product.update(updatedProductData, { - where: testMode ? { - // 测试模式:只根据productId更新 - productId: productId - } : { - // 非测试模式:验证商品所有权 - productId: productId, - sellerId: user.userId - } - }); - - // 检查更新是否成功 - if (updatedCount === 0) { - console.error('商品更新失败: 没有找到匹配的商品或权限不足'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品更新失败: 没有找到匹配的商品或权限不足' - }); - } - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ where: { productId: productId } }); - - console.log('查询数据库后 - 更新的商品信息:', { - grossWeight: updatedProduct?.grossWeight, - grossWeightType: typeof updatedProduct?.grossWeight, - productId: updatedProduct?.productId, - status: updatedProduct?.status - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 注意:这里检查undefined和null,并且对于空字符串或5的情况也进行处理 - if (updatedProduct) { - console.log('处理前 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (updatedProduct.grossWeight === undefined || updatedProduct.grossWeight === null || updatedProduct.grossWeight === '') { - updatedProduct.grossWeight = 0; - console.log('检测到空值 - 已设置为0'); - } else { - // 否则转换为浮点数 - updatedProduct.grossWeight = parseFloat(updatedProduct.grossWeight); - } - - console.log('处理后 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - } - - console.log('商品编辑成功:', { - productId: productId, - productName: product.productName, - oldStatus: existingProduct.status, // 记录更新前的状态 - newStatus: updatedProduct.status, // 记录更新后的状态 - grossWeight: updatedProduct.grossWeight // 记录处理后的毛重值 - }); - - // 根据新的状态生成适当的返回消息 - let returnMessage = ''; - if (updatedProduct.status === 'pending_review') { - returnMessage = '商品编辑成功,已重新提交审核'; - } else if (updatedProduct.status === 'published') { - returnMessage = '商品编辑成功,已上架'; - } else if (updatedProduct.status === existingProduct.status) { - returnMessage = '商品编辑成功,状态保持不变'; - } else { - returnMessage = '商品编辑成功'; - } - - res.json({ - success: true, - code: 200, - message: returnMessage, - product: updatedProduct - }); - } catch (error) { - console.error('编辑商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '编辑商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 导出模型和Express应用供其他模块使用 -module.exports = { - User, - Product, - CartItem, - sequelize, - createUserAssociations, - app, - PORT -}; \ No newline at end of file diff --git a/server-example/server-mysql.backup.js b/server-example/server-mysql.backup.js deleted file mode 100644 index bd8f719..0000000 --- a/server-example/server-mysql.backup.js +++ /dev/null @@ -1,2960 +0,0 @@ -// ECS服务器示例代码 - Node.js版 (MySQL版本) -const express = require('express'); -const crypto = require('crypto'); -const bodyParser = require('body-parser'); -const { Sequelize, DataTypes, Model, Op } = require('sequelize'); -require('dotenv').config(); - -// 创建Express应用 -const app = express(); -const PORT = process.env.PORT || 3002; - -// 中间件 -app.use(bodyParser.json()); - -// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在bodyParser之后) -app.use((req, res, next) => { - // 将UTC时间转换为北京时间(UTC+8) - const now = new Date(); - const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); - const formattedTime = beijingTime.toISOString().replace('Z', '+08:00'); - - console.log(`[${formattedTime}] 收到请求: ${req.method} ${req.url}`); - console.log('请求头:', req.headers); - console.log('请求体:', req.body); - next(); -}); - -// 商品毛重处理中间件 - 确保所有返回的商品数据中毛重字段保持原始值 -app.use((req, res, next) => { - // 保存原始的json方法 - const originalJson = res.json; - - // 重写json方法来处理响应数据 - res.json = function (data) { - // 检查数据中是否包含商品列表 - if (data && typeof data === 'object') { - // 处理/products/list接口的响应 - if (data.products && Array.isArray(data.products)) { - data.products = data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - - // 处理/data字段中的商品列表 - if (data.data && data.data.products && Array.isArray(data.data.products)) { - data.data.products = data.data.products.map(product => { - // 保持毛重字段的原始值,只做类型转换确保是数字 - if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') { - product.grossWeight = 0; // 空值设置为0 - } else { - product.grossWeight = parseFloat(product.grossWeight); - } - return product; - }); - } - } - - // 调用原始的json方法 - return originalJson.call(this, data); - }; - - next(); -}); - -// MySQL数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - } - } -); - -// 微信小程序配置 -const WECHAT_CONFIG = { - APPID: process.env.WECHAT_APPID || 'your-wechat-appid', - APPSECRET: process.env.WECHAT_APPSECRET || 'your-wechat-appsecret', - TOKEN: process.env.WECHAT_TOKEN || 'your-wechat-token' -}; - -// 显示当前使用的数据库配置(用于调试) -console.log('当前数据库连接配置:'); -console.log(' 主机:', process.env.DB_HOST || 'localhost'); -console.log(' 端口:', process.env.DB_PORT || 3306); -console.log(' 数据库名:', process.env.DB_DATABASE || 'wechat_app'); -console.log(' 用户名:', process.env.DB_USER || 'root'); -console.log(' 密码:', process.env.DB_PASSWORD === undefined || process.env.DB_PASSWORD === '' ? '无密码' : '******'); - -// 测试数据库连接 -async function testDbConnection() { - try { - await sequelize.authenticate(); - console.log('数据库连接成功'); - } catch (error) { - console.error('数据库连接失败:', error); - console.error('\n请检查以下几点:'); - console.error('1. MySQL服务是否已经启动'); - console.error('2. wechat_app数据库是否已创建'); - console.error('3. .env文件中的数据库用户名和密码是否正确'); - console.error('4. 用户名是否有足够的权限访问数据库'); - console.error('\n如果是首次配置,请参考README文件中的数据库设置指南。'); - process.exit(1); - } -} - -testDbConnection(); - -// 定义数据模型 - -// 用户模型 -class User extends Model { } -User.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - openid: { - type: DataTypes.STRING(100), - allowNull: false - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 微信名,必填 - }, - avatarUrl: { - type: DataTypes.TEXT - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 电话号码,必填 - }, - type: { - type: DataTypes.STRING(20), - allowNull: false // 用户身份(buyer/seller/both),必填 - }, - gender: { - type: DataTypes.INTEGER - }, - country: { - type: DataTypes.STRING(50) - }, - province: { - type: DataTypes.STRING(50) - }, - city: { - type: DataTypes.STRING(50) - }, - language: { - type: DataTypes.STRING(20) - }, - session_key: { - type: DataTypes.STRING(255) - }, - // 新增字段 - company: { - type: DataTypes.STRING(255) // 客户公司 - }, - region: { - type: DataTypes.STRING(255) // 客户地区 - }, - level: { - type: DataTypes.STRING(255), - defaultValue: 'company-sea-pools' // 客户等级,默认值为company-sea-pools - }, - demand: { - type: DataTypes.TEXT // 基本需求 - }, - spec: { - type: DataTypes.TEXT // 规格 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'User', - tableName: 'users', - timestamps: false -}); - -// 商品模型 -class Product extends Model { } -Product.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - sellerId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - price: { - type: DataTypes.DECIMAL(10, 2), - allowNull: false - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - specification: { - type: DataTypes.STRING(255) - }, - status: { - type: DataTypes.STRING(20), - defaultValue: 'pending_review', - validate: { - isIn: [['pending_review', 'reviewed', 'published', 'sold_out', 'rejected', 'hidden']] - } - }, - rejectReason: { - type: DataTypes.TEXT - }, - // 新增预约相关字段 - reservedCount: { - type: DataTypes.INTEGER, - defaultValue: 0, - allowNull: false, - comment: '已有几人想要' - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Product', - tableName: 'products', - timestamps: false -}); - -// 购物车模型 -class CartItem extends Model { } -CartItem.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - productId: { - type: DataTypes.STRING(100), - allowNull: false - }, - productName: { - type: DataTypes.STRING(255), - allowNull: false - }, - specification: { - type: DataTypes.STRING(255) - }, - quantity: { - type: DataTypes.INTEGER, - allowNull: false, - defaultValue: 1 - }, - grossWeight: { - type: DataTypes.DECIMAL(10, 2) - }, - yolk: { - type: DataTypes.STRING(100) - }, - price: { - type: DataTypes.DECIMAL(10, 2) - }, - selected: { - type: DataTypes.BOOLEAN, - defaultValue: true - }, - added_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - } -}, { - sequelize, - modelName: 'CartItem', - tableName: 'cart_items', - timestamps: false -}); - -// 联系人表模型 -class Contact extends Model { } -Contact.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - nickName: { - type: DataTypes.STRING(100), - allowNull: false // 联系人 - }, - phoneNumber: { - type: DataTypes.STRING(20), - allowNull: false // 手机号 - }, - wechat: { - type: DataTypes.STRING(100) // 微信号 - }, - account: { - type: DataTypes.STRING(100) // 账户 - }, - accountNumber: { - type: DataTypes.STRING(100) // 账号 - }, - bank: { - type: DataTypes.STRING(100) // 开户行 - }, - address: { - type: DataTypes.TEXT // 地址 - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'Contact', - tableName: 'contacts', - timestamps: false -}); - -// 用户管理表模型 -class UserManagement extends Model { } -UserManagement.init({ - id: { - type: DataTypes.INTEGER, - autoIncrement: true, - primaryKey: true - }, - userId: { - type: DataTypes.STRING(100), - allowNull: false, - unique: true - }, - managerId: { - type: DataTypes.STRING(100), - defaultValue: null // 经理ID,默认值为null - }, - company: { - type: DataTypes.STRING(255), - defaultValue: null // 公司,默认值为null - }, - department: { - type: DataTypes.STRING(255), - defaultValue: null // 部门,默认值为null - }, - organization: { - type: DataTypes.STRING(255), - defaultValue: null // 组织,默认值为null - }, - role: { - type: DataTypes.STRING(100), - defaultValue: null // 角色,默认值为null - }, - root: { - type: DataTypes.STRING(100), - defaultValue: null // 根节点,默认值为null - }, - created_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW - }, - updated_at: { - type: DataTypes.DATE, - defaultValue: Sequelize.NOW, - onUpdate: Sequelize.NOW - } -}, { - sequelize, - modelName: 'UserManagement', - tableName: 'usermanagements', - timestamps: false -}); - -// 定义模型之间的关联关系 - -// 用户和商品的一对多关系 (卖家发布商品) -User.hasMany(Product, { - foreignKey: 'sellerId', // 外键字段名 - sourceKey: 'userId', // 源键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'products', // 别名,用于关联查询 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Product.belongsTo(User, { - foreignKey: 'sellerId', - targetKey: 'userId', // 目标键,使用userId字段(STRING类型)而非默认的id字段(INTEGER类型) - as: 'seller' // 别名,用于关联查询 -}); - -// 用户和购物车项的一对多关系 (买家的购物需求/购物车) -User.hasMany(CartItem, { - foreignKey: 'userId', - as: 'cartItems', // 用户的购物车(购物需求)列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(User, { - foreignKey: 'userId', - as: 'buyer' // 别名,明确表示这是购物需求的买家 -}); - -// 商品和购物车项的一对多关系 (商品被添加到购物车) -Product.hasMany(CartItem, { - foreignKey: 'productId', - as: 'cartItems', // 商品出现在哪些购物车中 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -CartItem.belongsTo(Product, { - foreignKey: 'productId', - as: 'product' // 购物车项中的商品 -}); - -// 用户和联系人的一对多关系 -User.hasMany(Contact, { - foreignKey: 'userId', - as: 'contacts', // 用户的联系人列表 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -Contact.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 联系人所属用户 -}); - -// 用户和用户管理的一对一关系 -User.hasOne(UserManagement, { - foreignKey: 'userId', - as: 'management', // 用户的管理信息 - onDelete: 'CASCADE', // 级联删除 - onUpdate: 'CASCADE' // 级联更新 -}); - -UserManagement.belongsTo(User, { - foreignKey: 'userId', - as: 'user' // 管理信息所属用户 -}); - -// 同步数据库模型到MySQL -async function syncDatabase() { - try { - // 不使用alter: true,避免尝试修改已有表结构导致的外键约束问题 - await sequelize.sync({ - force: false // 不强制重新创建表 - }); - console.log('数据库模型同步成功'); - } catch (error) { - console.error('数据库模型同步失败:', error); - // 即使同步失败也继续运行,因为我们只需要API功能 - console.log('数据库模型同步失败,但服务器继续运行,使用现有表结构'); - } -} - -syncDatabase(); - -// 解密微信加密数据 -function decryptData(encryptedData, sessionKey, iv) { - try { - // Base64解码 - const sessionKeyBuf = Buffer.from(sessionKey, 'base64'); - const encryptedDataBuf = Buffer.from(encryptedData, 'base64'); - const ivBuf = Buffer.from(iv, 'base64'); - - // AES解密 - const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKeyBuf, ivBuf); - decipher.setAutoPadding(true); - let decoded = decipher.update(encryptedDataBuf, 'binary', 'utf8'); - decoded += decipher.final('utf8'); - - // 解析JSON - return JSON.parse(decoded); - } catch (error) { - console.error('解密失败:', error); - // 提供更具体的错误信息 - if (error.code === 'ERR_OSSL_BAD_DECRYPT') { - throw new Error('登录信息已过期,请重新登录'); - } else if (error.name === 'SyntaxError') { - throw new Error('数据格式错误,解密结果无效'); - } else { - throw new Error('解密失败,请重试'); - } - } -} - -// 获取微信session_key -async function getSessionKey(code) { - const axios = require('axios'); - const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${WECHAT_CONFIG.APPID}&secret=${WECHAT_CONFIG.APPSECRET}&js_code=${code}&grant_type=authorization_code`; - - try { - const response = await axios.get(url); - return response.data; - } catch (error) { - console.error('获取session_key失败:', error); - throw new Error('获取session_key失败'); - } -} - -// 创建用户关联记录函数 - 自动为用户创建contacts和usermanagements表的关联记录 -async function createUserAssociations(user) { - try { - if (!user || !user.userId) { - console.error('无效的用户数据,无法创建关联记录'); - return false; - } - - console.log('为用户创建关联记录:', user.userId); - - // 使用事务确保操作原子性 - await sequelize.transaction(async (transaction) => { - // 1. 处理联系人记录 - 使用INSERT ... ON DUPLICATE KEY UPDATE确保无论如何都只保留一条记录 - await sequelize.query( - `INSERT INTO contacts (userId, nickName, phoneNumber, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - nickName = VALUES(nickName), - phoneNumber = VALUES(phoneNumber), - updated_at = NOW()`, - { - replacements: [user.userId, user.nickName || '默认联系人', user.phoneNumber || ''], - transaction: transaction - } - ); - console.log('联系人记录已处理(创建或更新):', user.userId); - - // 2. 处理用户管理记录 - 使用相同策略 - await sequelize.query( - `INSERT INTO usermanagements (userId, created_at, updated_at) - VALUES (?, NOW(), NOW()) - ON DUPLICATE KEY UPDATE - updated_at = NOW()`, - { - replacements: [user.userId], - transaction: transaction - } - ); - console.log('用户管理记录已处理(创建或更新):', user.userId); - }); - - console.log('用户关联记录处理成功:', user.userId); - return true; - } catch (error) { - console.error('创建用户关联记录失败:', error.message); - return false; - } -} - -// API路由 - -// 上传用户信息 -app.post('/api/user/upload', async (req, res) => { - try { - const userData = req.body; - console.log('收到用户信息上传请求:', userData); - - // 如果用户信息中包含手机号,检查手机号是否已被其他用户使用 - if (userData.phoneNumber) { - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: userData.phoneNumber, - openid: { [Sequelize.Op.ne]: userData.openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${userData.phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 创建新对象,移除手机号字段 - const userDataWithoutPhone = { ...userData }; - delete userDataWithoutPhone.phoneNumber; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息(不包含手机号) - await User.update( - { - ...userDataWithoutPhone, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - } else { - // 创建新用户 - user = await User.create({ - ...userDataWithoutPhone, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - // 返回成功,但提示手机号已被使用 - return res.json({ - success: true, - code: 200, - message: '用户信息保存成功,但手机号已被其他账号绑定', - data: { - userId: user.userId - }, - phoneNumberConflict: true - }); - } - } - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid: userData.openid } - }); - - if (user) { - // 更新用户信息 - await User.update( - { - ...userData, - updated_at: new Date() - }, - { - where: { openid: userData.openid } - } - ); - user = await User.findOne({ where: { openid: userData.openid } }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } else { - // 创建新用户 - user = await User.create({ - ...userData, - created_at: new Date(), - updated_at: new Date() - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(user); - } - - res.json({ - success: true, - code: 200, - message: '用户信息保存成功', - data: { - userId: user.userId - }, - phoneNumberConflict: false - }); - } catch (error) { - console.error('保存用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '保存用户信息失败', - error: error.message - }); - } -}); - -// 解密手机号 -app.post('/api/user/decodePhone', async (req, res) => { - try { - const { encryptedData, iv, openid } = req.body; - - // 参数校验 - if (!encryptedData || !iv || !openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数' - }); - } - - // 查找用户的session_key - const user = await User.findOne({ where: { openid } }); - - if (!user || !user.session_key) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录,请先登录', - needRelogin: true - }); - } - - // 解密手机号 - let decryptedData, phoneNumber; - try { - decryptedData = decryptData(encryptedData, user.session_key, iv); - phoneNumber = decryptedData.phoneNumber; - } catch (decryptError) { - // 解密失败,可能是session_key过期,建议重新登录 - return res.status(401).json({ - success: false, - code: 401, - message: decryptError.message || '手机号解密失败', - needRelogin: true - }); - } - - // 检查手机号是否已被其他用户使用 - const existingUserWithPhone = await User.findOne({ - where: { - phoneNumber: phoneNumber, - openid: { [Sequelize.Op.ne]: openid } // 排除当前用户 - } - }); - - if (existingUserWithPhone) { - // 手机号已被其他用户使用,不更新手机号 - console.warn(`手机号 ${phoneNumber} 已被其他用户使用,用户ID: ${existingUserWithPhone.userId}`); - - // 返回成功,但不更新手机号,提示用户 - return res.json({ - success: true, - code: 200, - message: '手机号已被其他账号绑定', - phoneNumber: user.phoneNumber, // 返回原手机号 - isNewPhone: false - }); - } - - // 更新用户手机号 - await User.update( - { - phoneNumber: phoneNumber, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 更新用户手机号后,更新关联记录 - const updatedUser = await User.findOne({ where: { openid } }); - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '手机号解密成功', - phoneNumber: phoneNumber, - isNewPhone: true - }); - } catch (error) { - console.error('手机号解密失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '手机号解密失败', - error: error.message - }); - } -}); - -// 处理微信登录,获取openid和session_key -app.post('/api/wechat/getOpenid', async (req, res) => { - try { - const { code } = req.body; - - if (!code) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少code参数' - }); - } - - // 获取openid和session_key - const wxData = await getSessionKey(code); - - if (wxData.errcode) { - throw new Error(`微信接口错误: ${wxData.errmsg}`); - } - - const { openid, session_key, unionid } = wxData; - - // 生成userId - const userId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 查找用户是否已存在 - let user = await User.findOne({ - where: { openid } - }); - - if (user) { - // 更新用户session_key - await User.update( - { - session_key: session_key, - updated_at: new Date() - }, - { - where: { openid } - } - ); - } else { - // 创建新用户 - // 支持从客户端传入type参数,如果没有则默认为buyer - const userType = req.body.type || 'buyer'; - await User.create({ - openid, - userId, - session_key, - nickName: '微信用户', // 临时占位,等待用户授权 - type: userType, // 使用客户端传入的类型或默认买家身份 - created_at: new Date(), - updated_at: new Date() - }); - - // 为新创建的用户创建关联记录 - const newUser = { userId, openid, nickName: '微信用户' }; - await createUserAssociations(newUser); - } - - res.json({ - success: true, - code: 200, - message: '获取openid成功', - data: { - openid, - userId: user ? user.userId : userId - } - }); - } catch (error) { - console.error('获取openid失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取openid失败', - error: error.message - }); - } -}); - -// 验证用户登录状态 -app.post('/api/user/validate', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'avatarUrl', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(401).json({ - success: false, - code: 401, - message: '用户未登录' - }); - } - - res.json({ - success: true, - code: 200, - message: '验证成功', - data: user - }); - } catch (error) { - console.error('验证用户登录状态失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '验证失败', - error: error.message - }); - } -}); - -// 获取用户信息 -app.post('/api/user/get', async (req, res) => { - try { - const { openid } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid }, - include: [ - { - model: Contact, - as: 'contacts', - attributes: ['id', 'nickName', 'phoneNumber', 'wechat', 'account', 'accountNumber', 'bank', 'address'] - }, - { - model: UserManagement, - as: 'management', - attributes: ['id', 'managerId', 'company', 'department', 'organization', 'role', 'root'] - } - ] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - res.json({ - success: true, - code: 200, - message: '获取用户信息成功', - data: user - }); - } catch (error) { - console.error('获取用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户信息失败', - error: error.message - }); - } -}); - -// 更新用户信息 -app.post('/api/user/update', async (req, res) => { - try { - const { openid, ...updateData } = req.body; - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查找用户 - const user = await User.findOne({ - where: { openid } - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 更新用户信息 - await User.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { openid } - } - ); - - // 获取更新后的用户信息 - const updatedUser = await User.findOne({ - where: { openid } - }); - - // 使用统一的关联记录创建函数 - await createUserAssociations(updatedUser); - - res.json({ - success: true, - code: 200, - message: '更新用户信息成功', - data: updatedUser - }); - } catch (error) { - console.error('更新用户信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新用户信息失败', - error: error.message - }); - } -}); - -// 获取商品列表 - 优化版本确保状态筛选正确应用 -app.post('/api/product/list', async (req, res) => { - try { - const { openid, status, keyword, page = 1, pageSize = 20, testMode = false } = req.body; - - // 验证openid参数(测试模式除外) - if (!openid && !testMode) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 构建查询条件 - const where = {}; - - // 查找用户 - let user = null; - if (!testMode) { - user = await User.findOne({ where: { openid } }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - // 只有管理员可以查看所有商品,普通用户只能查看自己的商品 - if (user.type !== 'admin') { - where.sellerId = user.userId; - } - } - - // 状态筛选 - 直接构建到where对象中,确保不会丢失 - console.log(`当前用户类型: ${user ? user.type : '未知'},请求状态: ${status || '未指定'},测试模式: ${testMode}`); - - // 如果有指定status参数,按参数筛选但同时排除hidden - if (status) { - console.log(`按状态筛选商品: status=${status},并排除hidden状态`); - if (status === 'all') { - // 特殊情况:请求所有商品但仍然排除hidden - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else if (Array.isArray(status)) { - // 如果status是数组,确保不包含hidden - where.status = { [Sequelize.Op.in]: status.filter(s => s !== 'hidden') }; - } else { - // 单个状态值,确保不是hidden - if (status !== 'hidden') { - where.status = { [Sequelize.Op.eq]: status }; - } else { - // 如果明确请求hidden状态,也返回空结果 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } - } - } else { - // 没有指定status参数时 - 直接在where对象中设置状态筛选 - if (user && (user.type === 'seller' || user.type === 'both') && !testMode) { - // 卖家用户且非测试模式 - console.log(`卖家用户 ${user.userId} (类型:${user.type}) 查看自己的所有商品,但排除hidden状态`); - // 卖家可以查看自己的所有商品,但仍然排除hidden状态 - where.status = { [Sequelize.Op.not]: 'hidden' }; - } else { - // 测试模式或非卖家用户 - console.log(`测试模式或非卖家用户,使用默认状态筛选: reviewed/published`); - // 默认只显示已审核和已发布的商品,排除hidden和sold_out状态 - where.status = { [Sequelize.Op.in]: ['reviewed', 'published'] }; - } - } - - console.log(`构建的完整查询条件:`, JSON.stringify(where, null, 2)); - - // 关键词搜索 - if (keyword) { - where.productName = { [Sequelize.Op.like]: `%${keyword}%` }; - } - - // 计算偏移量 - const offset = (page - 1) * pageSize; - - // 查询商品列表 - const { count, rows: products } = await Product.findAndCountAll({ - where, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - }, - // 添加CartItem关联以获取预约人数 - { - model: CartItem, - as: 'CartItems', // 明确指定别名 - attributes: [], - required: false // 允许没有购物车项的商品也能返回 - } - ], - // 添加selected字段,计算商品被加入购物车的次数(预约人数) - attributes: { - include: [ - [Sequelize.fn('COUNT', Sequelize.col('CartItems.id')), 'selected'] - ] - }, - order: [['created_at', 'DESC']], - limit: pageSize, - offset, - // 修复分组问题 - group: ['Product.productId', 'seller.userId'] // 使用正确的字段名 - }); - - // 添加详细日志,记录查询结果 - console.log(`商品列表查询结果 - 商品数量: ${count}, 商品列表长度: ${products.length}`); - if (products.length > 0) { - console.log(`第一个商品数据:`, JSON.stringify(products[0], null, 2)); - - // 添加selected字段的专门日志 - console.log('商品预约人数(selected字段)统计:'); - products.slice(0, 5).forEach(product => { - const productJSON = product.toJSON(); - console.log(`- ${productJSON.productName}: 预约人数=${productJSON.selected || 0}, 商品ID=${productJSON.productId}`); - }); - } - - // 处理商品列表中的grossWeight字段,确保是数字类型 - const processedProducts = products.map(product => { - const productJSON = product.toJSON(); - - // 详细分析毛重字段 - const grossWeightDetails = { - value: productJSON.grossWeight, - type: typeof productJSON.grossWeight, - isEmpty: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined, - isNumeric: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined || !isNaN(parseFloat(productJSON.grossWeight)) && isFinite(productJSON.grossWeight), - parsedValue: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined ? 0 : parseFloat(productJSON.grossWeight) - }; - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - productJSON.grossWeight = finalGrossWeight; - - // 记录第一个商品的转换信息用于调试 - if (products.indexOf(product) === 0) { - console.log('商品列表 - 第一个商品毛重字段处理:'); - console.log('- 原始值:', grossWeightDetails.value, '类型:', grossWeightDetails.type); - console.log('- 转换后的值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - } - - return productJSON; - }); - - // 准备响应数据 - 修改格式以匹配前端期望 - const responseData = { - success: true, - code: 200, - message: '获取商品列表成功', - products: processedProducts, - total: count, - page: page, - pageSize: pageSize, - totalPages: Math.ceil(count / pageSize) - }; - - console.log(`准备返回的响应数据格式:`, JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - - // 添加详细的查询条件日志 - console.log(`最终查询条件:`, JSON.stringify(where, null, 2)); - - res.json(responseData); - } catch (error) { - console.error('获取商品列表失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品列表失败', - error: error.message - }); - } -}); - -// 上传商品 -app.post('/api/products/upload', async (req, res) => { - try { - // 修复毛重字段处理逻辑 - let productData = req.body; - if (productData && productData.productData) { - productData = productData.productData; // 使用正确的productData对象 - } - - // 改进的毛重字段处理逻辑,与编辑API保持一致 - // 详细分析毛重字段 - const grossWeightDetails = { - value: productData.grossWeight, - type: typeof productData.grossWeight, - isEmpty: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined, - isNumeric: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined || !isNaN(parseFloat(productData.grossWeight)) && isFinite(productData.grossWeight), - parsedValue: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined ? 0 : parseFloat(productData.grossWeight) - }; - - // 详细的日志记录 - console.log('上传商品 - 毛重字段详细分析:'); - console.log('- 原始值:', productData.grossWeight, '类型:', typeof productData.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行精确四舍五入,确保3位小数以上的值正确转换 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - productData.grossWeight = finalGrossWeight; - console.log('上传商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - console.log('收到商品上传请求:', productData); - - // 验证必要字段 - if (!productData.sellerId || !productData.productName || !productData.price || !productData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的商品信息' - }); - } - - // 检查sellerId是否为openid,如果是则查找对应的userId - let actualSellerId = productData.sellerId; - - // 如果sellerId看起来像一个openid(包含特殊字符如'-'),则尝试查找对应的userId - if (productData.sellerId.includes('-')) { - console.log('sellerId看起来像openid,尝试查找对应的userId'); - const user = await User.findOne({ - where: { - openid: productData.sellerId - } - }); - - if (user && user.userId) { - console.log(`找到了对应的userId: ${user.userId}`); - actualSellerId = user.userId; - } else { - console.error(`未找到对应的用户记录,openid: ${productData.sellerId}`); - return res.status(400).json({ - success: false, - code: 400, - message: '找不到对应的用户记录' - }); - } - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - - // 创建商品,使用实际的sellerId - let product = await Product.create({ - ...productData, - sellerId: actualSellerId, // 使用查找到的userId - productId, - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - product = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 与编辑API保持一致的处理逻辑 - if (product) { - console.log('上传商品 - 处理前grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (product.grossWeight === undefined || product.grossWeight === null || product.grossWeight === '') { - product.grossWeight = 0; - console.log('上传商品 - 检测到空值,已设置为0'); - } else { - // 否则转换为浮点数 - product.grossWeight = parseFloat(product.grossWeight); - } - - console.log('上传商品 - 处理后grossWeight:', product.grossWeight, '类型:', typeof product.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品上传成功', - data: { - productId: product.productId, - product: product - } - }); - } catch (error) { - console.error('商品上传失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '商品上传失败', - error: error.message - }); - } -}); - -// 获取商品详情 -app.post('/api/products/detail', async (req, res) => { - try { - const { productId } = req.body; - - if (!productId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId参数' - }); - } - - // 查询商品详情 - 排除hidden状态商品 - const product = await Product.findOne({ - where: { - productId, - status: { [Sequelize.Op.not]: 'hidden' } - }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 对返回的商品数据中的grossWeight字段进行处理,确保是数字类型 - let updatedProduct = { ...product.toJSON() }; - - // 详细分析毛重字段 - const grossWeightDetails = { - value: updatedProduct.grossWeight, - type: typeof updatedProduct.grossWeight, - isEmpty: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined, - isNumeric: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined || !isNaN(parseFloat(updatedProduct.grossWeight)) && isFinite(updatedProduct.grossWeight), - parsedValue: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined ? 0 : parseFloat(updatedProduct.grossWeight) - }; - - // 详细的日志记录 - console.log('商品详情 - 毛重字段详细分析:'); - console.log('- 原始值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - const finalGrossWeight = parseFloat(grossWeightDetails.parsedValue.toFixed(2)); - updatedProduct.grossWeight = finalGrossWeight; - console.log('商品详情 - 最终返回的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - res.json({ - success: true, - code: 200, - message: '获取商品详情成功', - data: updatedProduct - }); - } catch (error) { - console.error('获取商品详情失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取商品详情失败', - error: error.message - }); - } -}); - -// 修改商品 -app.post('/api/products/edit', async (req, res) => { - try { - const { productId, ...updateData } = req.body; - const { sellerId } = req.body; - - if (!productId || !sellerId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - return res.status(403).json({ - success: false, - code: 403, - message: '您无权修改此商品' - }); - } - - // 更新商品信息 - await Product.update( - { - ...updateData, - updated_at: new Date() - }, - { - where: { productId } - } - ); - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '修改商品成功', - data: updatedProduct - }); - } catch (error) { - console.error('修改商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '修改商品失败', - error: error.message - }); - } -}); - -// 删除商品 - 将商品状态设置为hidden表示已删除 -app.post('/api/products/delete', async (req, res) => { - console.log('收到删除商品请求:', req.body); - try { - const { productId, sellerId } = req.body; - - if (!productId || !sellerId) { - console.error('删除商品失败: 缺少productId或sellerId参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少productId或sellerId参数' - }); - } - - // 查找商品 - const product = await Product.findOne({ - where: { productId } - }); - - if (!product) { - console.error('删除商品失败: 商品不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在' - }); - } - - // 检查是否为卖家本人 - if (product.sellerId !== sellerId) { - console.error('删除商品失败: 权限不足 - 卖家ID不匹配', { expected: product.sellerId, actual: sellerId }); - return res.status(403).json({ - success: false, - code: 403, - message: '您无权删除此商品' - }); - } - - console.log('准备更新商品状态为hidden,当前状态:', product.status); - - // 直接使用商品实例更新状态 - product.status = 'hidden'; - product.updated_at = new Date(); - - try { - // 先尝试保存商品实例 - await product.save(); - console.log('删除商品成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - // 如果保存失败,尝试使用update方法 - try { - const updateResult = await Product.update( - { status: 'hidden', updated_at: new Date() }, - { where: { productId } } - ); - console.log('删除商品成功(使用update方法):', { productId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - // 如果update方法也失败,尝试直接执行SQL语句绕过ORM验证 - try { - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId', - { - replacements: { - status: 'hidden', - updatedAt: new Date(), - productId: productId - } - } - ); - console.log('删除商品成功(使用原始SQL):', { productId }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 从购物车中移除该商品 - const destroyResult = await CartItem.destroy({ - where: { productId } - }); - console.log('从购物车移除商品结果:', destroyResult); - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { productId } - }); - - res.json({ - success: true, - code: 200, - message: '删除商品成功', - product: { - productId: updatedProduct.productId, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('删除商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除商品失败', - error: error.message - }); - } -}); - -// 添加商品到购物车 -app.post('/api/cart/add', async (req, res) => { - // 增加全局错误捕获,确保即使在try-catch外部的错误也能被处理 - try { - console.log('收到添加到购物车请求 - 开始处理', req.url); - let cartData = req.body; - console.log('收到添加到购物车请求数据:', cartData); - console.log('请求头:', req.headers); - console.log('请求IP:', req.ip); - - // 兼容客户端请求格式:客户端可能将数据封装在product对象中,并且使用openid而不是userId - if (cartData.product && !cartData.productId) { - // 从product对象中提取数据 - const productData = cartData.product; - console.log('从product对象提取数据:', productData); - console.log('客户端提供的openid:', cartData.openid); - - // 使用openid作为userId - cartData = { - userId: cartData.openid || productData.userId, - productId: productData.productId || productData.id, - productName: productData.productName || productData.name, - quantity: productData.quantity || 1, - price: productData.price, - specification: productData.specification || productData.spec || '', - grossWeight: productData.grossWeight || productData.weight, - yolk: productData.yolk || productData.variety || '', - testMode: productData.testMode || cartData.testMode - }; - console.log('转换后的购物车数据:', cartData); - - // 检查转换后的userId是否存在于users表中 - try { - console.log('开始查询用户信息,openid:', cartData.userId); - const user = await User.findOne({ - where: { openid: cartData.userId } - }); - if (user) { - console.log(`找到对应的用户记录: openid=${cartData.userId}, userId=${user.userId}`); - // 修正:使用数据库中真实的userId而不是openid - cartData.userId = user.userId; - console.log('修正后的userId:', cartData.userId); - } else { - console.error(`未找到openid为 ${cartData.userId} 的用户记录,无法添加到购物车`); - // 重要:找不到用户时返回错误,避免使用无效的userId导致外键约束失败 - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `未找到用户记录: ${cartData.userId}` - }); - } - } catch (error) { - console.error('查询用户信息失败:', error); - // 查询失败时也返回错误 - return res.status(400).json({ - success: false, - code: 400, - message: '查询用户信息失败', - error: error.message - }); - } - } - - // 验证必要字段 - if (!cartData.userId || !cartData.productId || !cartData.productName || !cartData.quantity) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的购物车信息', - missingFields: [ - !cartData.userId ? 'userId' : '', - !cartData.productId ? 'productId' : '', - !cartData.productName ? 'productName' : '', - !cartData.quantity ? 'quantity' : '' - ].filter(Boolean) - }); - } - - // 先验证用户ID是否存在于users表中 - try { - const userExists = await User.findOne({ - where: { userId: cartData.userId } - }); - if (!userExists) { - console.error(`用户ID ${cartData.userId} 不存在于users表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '用户信息无效,请重新登录后重试', - error: `用户ID ${cartData.userId} 不存在` - }); - } else { - console.log(`用户ID ${cartData.userId} 存在于users表中,用户验证通过`); - } - } catch (error) { - console.error('验证用户ID失败:', error); - return res.status(400).json({ - success: false, - code: 400, - message: '验证用户信息失败', - error: error.message - }); - } - - // 检查商品是否存在以及是否为hidden状态 - console.log(`检查商品ID: ${cartData.productId} 是否存在于products表中`); - const product = await Product.findOne({ - where: { - productId: cartData.productId - } - }); - - if (!product) { - console.error(`商品ID ${cartData.productId} 不存在于products表中`); - return res.status(400).json({ - success: false, - code: 400, - message: '商品不存在或已被移除', - error: `未找到商品ID: ${cartData.productId}` - }); - } else { - console.log(`商品ID ${cartData.productId} 存在于products表中,商品名称: ${product.productName}`); - } - - if (product.status === 'hidden') { - return res.status(400).json({ - success: false, - code: 400, - message: '该商品已下架,无法添加到购物车' - }); - } - - // 在testMode下,不执行实际的数据库操作,直接返回成功 - if (cartData.testMode) { - console.log('测试模式:跳过实际的数据库操作'); - res.json({ - success: true, - code: 200, - message: '测试模式:添加到购物车成功', - data: { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity - } - }); - return; - } - - // 检查是否已存在相同商品 - const existingItem = await CartItem.findOne({ - where: { - userId: cartData.userId, - productId: cartData.productId - } - }); - - // 添加try-catch捕获外键约束错误 - try { - console.log(`准备创建/更新购物车项: userId=${cartData.userId}, productId=${cartData.productId}`); - if (existingItem) { - // 已存在,更新数量 - await CartItem.update( - { - quantity: existingItem.quantity + cartData.quantity, - updated_at: new Date() - }, - { - where: { - id: existingItem.id - } - } - ); - console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); - } else { - // 不存在,创建新购物车项 - console.log('创建新购物车项,所有字段:', { - userId: cartData.userId, - productId: cartData.productId, - productName: cartData.productName, - quantity: cartData.quantity, - price: cartData.price, - specification: cartData.specification, - grossWeight: cartData.grossWeight, - yolk: cartData.yolk - }); - // 重要:在创建前再次验证数据完整性 - if (!cartData.userId || !cartData.productId) { - throw new Error(`数据不完整: userId=${cartData.userId}, productId=${cartData.productId}`); - } - await CartItem.create({ - ...cartData, - added_at: new Date() - }); - console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); - } - } catch (createError) { - console.error('创建/更新购物车项失败,可能是外键约束问题:', createError); - console.error('详细错误信息:', { - name: createError.name, - message: createError.message, - stack: createError.stack, - sql: createError.sql || '无SQL信息', - userId: cartData.userId, - productId: cartData.productId - }); - - // 检测是否是外键约束错误 - if (createError.name === 'SequelizeForeignKeyConstraintError' || createError.message.includes('foreign key')) { - // 区分是用户ID还是商品ID问题 - let errorField = 'productId'; - let errorMessage = '商品信息已更新,请刷新页面后重试'; - - if (createError.message.includes('userId') || createError.message.includes('user') || createError.message.toLowerCase().includes('user')) { - errorField = 'userId'; - errorMessage = '用户信息无效,请重新登录后重试'; - } - - return res.status(400).json({ - success: false, - code: 400, - message: errorMessage, - error: `外键约束错误: ${errorField} 不存在或已失效`, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 其他类型的错误也返回400状态码,避免500错误 - return res.status(400).json({ - success: false, - code: 400, - message: '添加购物车项失败,请稍后重试', - error: createError.message, - details: { - userId: cartData.userId, - productId: cartData.productId - } - }); - } - - // 更新商品的预约人数 - 更健壮的实现 - try { - console.log(`尝试更新商品预约人数: productId=${cartData.productId}`); - - // 先验证商品是否存在 - const productCheck = await Product.findOne({where: {productId: cartData.productId}}); - if (productCheck) { - // 商品存在,才进行更新 - await Product.increment('reservedCount', {by: 1, where: {productId: cartData.productId}}); - console.log(`商品预约人数更新成功: productId=${cartData.productId}, 新数量=${productCheck.reservedCount + 1}`); - } else { - console.error(`更新商品预约人数失败: 商品ID ${cartData.productId} 不存在`); - } - } catch (updateError) { - console.error(`更新商品预约人数失败:`, updateError); - // 继续执行,不中断主要流程 - } - - res.json({ - success: true, - code: 200, - message: '添加到购物车成功' - }); - } catch (error) { - console.error('添加到购物车失败:', error); - console.error('全局错误捕获,详细信息:', { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - }); - - // 增强的错误处理 - 强制所有错误返回400状态码 - console.error('全局错误处理 - 捕获到未处理的错误:', error); - const statusCode = 400; // 强制所有错误返回400状态码,避免前端显示500错误 - let errorMessage = '添加到购物车失败'; - - // 更精确地检测外键约束错误 - if (error.name === 'SequelizeForeignKeyConstraintError' || - error.message.toLowerCase().includes('foreign key') || - error.message.toLowerCase().includes('constraint fails') || - error.message.toLowerCase().includes('constraint')) { - errorMessage = '添加到购物车失败:商品或用户信息已更新,请刷新页面后重试'; - console.error('检测到外键约束相关错误,返回400状态码'); - } - - console.log(`准备返回错误响应 - 状态码: ${statusCode}, 消息: ${errorMessage}`); - - // 确保响应能够正确发送 - try { - res.status(statusCode).json({ - success: false, - code: statusCode, - message: errorMessage, - error: error.message, - errorDetails: { - name: error.name, - message: error.message, - stack: error.stack, - sql: error.sql || '无SQL信息' - } - }); - } catch (resError) { - console.error('发送错误响应失败:', resError); - // 即使发送响应失败,也尝试以文本格式发送 - try { - res.status(400).send('添加到购物车失败,请刷新页面后重试'); - } catch (finalError) { - console.error('无法发送任何响应:', finalError); - } - } - } -}); - -// 获取购物车信息 -app.post('/api/cart/get', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 查询购物车信息 - 排除关联商品为hidden或sold_out状态的项 - const cartItems = await CartItem.findAll({ - where: { userId }, - include: [ - { - model: Product, - as: 'product', - attributes: ['productName', 'price', 'quantity', 'status', 'specification', 'grossWeight', 'yolk'], - where: { - status: { [Sequelize.Op.notIn]: ['hidden', 'sold_out'] } - } - } - ], - order: [['added_at', 'DESC']] - }); - - res.json({ - success: true, - code: 200, - message: '获取购物车信息成功', - data: { - cartItems - } - }); - } catch (error) { - console.error('获取购物车信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取购物车信息失败', - error: error.message - }); - } -}); - -// 更新购物车项 -app.post('/api/cart/update', async (req, res) => { - try { - const { id, quantity, selected } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 构建更新数据 - const updateData = {}; - if (quantity !== undefined) updateData.quantity = quantity; - if (selected !== undefined) updateData.selected = selected; - updateData.updated_at = new Date(); - - // 更新购物车项 - await CartItem.update(updateData, { - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '更新购物车成功' - }); - } catch (error) { - console.error('更新购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '更新购物车失败', - error: error.message - }); - } -}); - -// 删除购物车项 -app.post('/api/cart/delete', async (req, res) => { - try { - const { id } = req.body; - - if (!id) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少id参数' - }); - } - - // 删除购物车项 - await CartItem.destroy({ - where: { id } - }); - - res.json({ - success: true, - code: 200, - message: '删除购物车项成功' - }); - } catch (error) { - console.error('删除购物车项失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '删除购物车项失败', - error: error.message - }); - } -}); - -// 清空购物车 -app.post('/api/cart/clear', async (req, res) => { - try { - const { userId } = req.body; - - if (!userId) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少userId参数' - }); - } - - // 清空购物车 - await CartItem.destroy({ - where: { userId } - }); - - res.json({ - success: true, - code: 200, - message: '清空购物车成功' - }); - } catch (error) { - console.error('清空购物车失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '清空购物车失败', - error: error.message - }); - } -}); - -// 测试连接接口 -app.get('/api/test-connection', async (req, res) => { - try { - // 检查数据库连接 - await sequelize.authenticate(); - - res.json({ - success: true, - code: 200, - message: '服务器连接成功,数据库可用', - timestamp: new Date().toISOString(), - serverInfo: { - port: PORT, - nodeVersion: process.version, - database: 'MySQL', - status: 'running' - } - }); - } catch (error) { - res.status(500).json({ - success: false, - code: 500, - message: '服务器连接失败', - error: error.message - }); - } -}); - -// 用户类型调试接口 - 增强版:用于排查用户类型和商品显示问题 -app.post('/api/user/debug', async (req, res) => { - try { - const { openid } = req.body; - - console.log('收到用户调试请求,openid:', openid); - - if (!openid) { - return res.status(400).json({ - success: false, - code: 400, - message: '缺少openid参数' - }); - } - - // 查询用户信息 - const user = await User.findOne({ - where: { openid }, - attributes: ['openid', 'userId', 'nickName', 'phoneNumber', 'type'] - }); - - if (!user) { - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在', - debugInfo: { - searchCriteria: { openid }, - timestamp: new Date().toISOString() - } - }); - } - - // 查询该用户的商品统计信息 - const totalProducts = await Product.count({ where: { sellerId: user.userId } }); - const pendingProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'pending_review' - } - }); - const reviewedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'reviewed' - } - }); - const publishedProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'published' - } - }); - const soldOutProducts = await Product.count({ - where: { - sellerId: user.userId, - status: 'sold_out' - } - }); - - // 判断用户是否有权限查看所有商品 - const canViewAllProducts = ['seller', 'both', 'admin'].includes(user.type); - - // 获取该用户的最新5个商品信息(用于调试) - const latestProducts = await Product.findAll({ - where: { sellerId: user.userId }, - limit: 5, - order: [['created_at', 'DESC']], - attributes: ['productId', 'productName', 'status', 'created_at'] - }); - - const responseData = { - success: true, - code: 200, - message: '获取用户调试信息成功', - userInfo: user, - productStats: { - total: totalProducts, - pendingReview: pendingProducts, - reviewed: reviewedProducts, - published: publishedProducts, - soldOut: soldOutProducts - }, - permissionInfo: { - canViewAllProducts: canViewAllProducts, - userType: user.type, - allowedTypesForViewingAllProducts: ['seller', 'both', 'admin'] - }, - latestProducts: latestProducts, - debugInfo: { - userCount: await User.count(), - totalProductsInSystem: await Product.count(), - timestamp: new Date().toISOString(), - serverTime: new Date().toLocaleString('zh-CN') - } - }; - - console.log('调试信息返回数据:', JSON.stringify(responseData, null, 2).substring(0, 500) + '...'); - res.json(responseData); - } catch (error) { - console.error('获取用户调试信息失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '获取用户调试信息失败', - error: error.message, - debugInfo: { - errorStack: error.stack, - timestamp: new Date().toISOString() - } - }); - } -}); - -// 下架商品接口 - 将商品状态设置为sold_out表示已下架 -app.post('/api/product/hide', async (req, res) => { - console.log('收到下架商品请求:', req.body); - - try { - const { openid, productId } = req.body; - - // 验证请求参数 - if (!openid || !productId) { - console.error('下架商品失败: 缺少必要参数'); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要参数: openid和productId都是必需的' - }); - } - - // 查找用户 - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('下架商品失败: 用户不存在'); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在' - }); - } - - console.log('找到用户信息:', { userId: user.userId, nickName: user.nickName }); - - // 查找商品并验证所有权 - 直接使用userId,因为商品创建时使用的就是userId - const product = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - - if (!product) { - console.error('下架商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 记录当前状态,用于调试 - console.log('当前商品状态:', product.status, '允许的状态列表:', Product.rawAttributes.status.validate.isIn); - console.log('商品所属卖家ID:', product.sellerId); - console.log('用户ID信息对比:', { userId: user.userId, id: user.id }); - - console.log('准备更新商品状态为sold_out,当前状态:', product.status); - - // 更新商品状态为已下架(sold_out) - 尝试多种更新方式确保成功 - try { - // 方法1: 直接保存实例 - product.status = 'sold_out'; - product.updated_at = new Date(); - await product.save(); - console.log('商品下架成功(使用save方法):', { productId: product.productId, newStatus: product.status }); - } catch (saveError) { - console.error('使用save方法更新失败,尝试使用update方法:', saveError); - - try { - // 方法2: 使用update方法 - const updateResult = await Product.update( - { status: 'sold_out', updated_at: new Date() }, - { where: { productId: productId, sellerId: user.userId } } - ); - console.log('商品下架成功(使用update方法):', { productId: productId, sellerIdType: typeof user.userId, updateResult }); - } catch (updateError) { - console.error('使用update方法也失败:', updateError); - - try { - // 方法3: 直接执行SQL语句绕过ORM验证 - const replacements = { - status: 'sold_out', - updatedAt: new Date(), - productId: productId, - sellerId: user.userId - }; - - await sequelize.query( - 'UPDATE products SET status = :status, updated_at = :updatedAt WHERE productId = :productId AND sellerId = :sellerId', - { - replacements: replacements - } - ); - console.log('商品下架成功(使用原始SQL):', { productId: product.productId, productName: product.productName }); - } catch (sqlError) { - console.error('使用原始SQL也失败:', sqlError); - throw new Error('所有更新方法都失败: ' + sqlError.message); - } - } - } - - // 重新查询商品以确保返回最新状态 - const updatedProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: product.sellerId // 使用找到的商品的sellerId进行查询 - } - }); - - res.json({ - success: true, - code: 200, - message: '商品下架成功', - product: { - productId: updatedProduct.productId, - productName: updatedProduct.productName, - status: updatedProduct.status - } - }); - } catch (error) { - console.error('下架商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '下架商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 发布商品API -app.post('/api/product/publish', async (req, res) => { - console.log('收到发布商品请求:', req.body); // 记录完整请求体 - - try { - const { openid, product } = req.body; - - // 验证必填字段 - console.log('验证请求参数: openid=', !!openid, ', product=', !!product); - if (!openid || !product) { - console.error('缺少必要参数: openid=', openid, 'product=', product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid或product对象)' - }); - } - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price, '转换为数字=', parseFloat(product.price)); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity, '转换为数字=', parseInt(product.quantity)); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误和字段值详情 - const validationErrors = []; - const fieldDetails = {}; - - // 检查商品名称 - fieldDetails.productName = { - value: product.productName, - type: typeof product.productName, - isEmpty: !product.productName || product.productName.trim() === '' - }; - if (fieldDetails.productName.isEmpty) { - console.error('商品名称为空'); - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } - - // 检查价格 - fieldDetails.price = { - value: product.price, - type: typeof product.price, - isNumber: !isNaN(parseFloat(product.price)) && isFinite(product.price), - parsedValue: parseFloat(product.price), - isValid: !isNaN(parseFloat(product.price)) && isFinite(product.price) && parseFloat(product.price) > 0 - }; - if (!product.price) { - console.error('价格为空'); - validationErrors.push('价格为必填项'); - } else if (!fieldDetails.price.isNumber) { - console.error('价格不是有效数字: price=', product.price); - validationErrors.push('价格必须是有效数字格式'); - } else if (fieldDetails.price.parsedValue <= 0) { - console.error('价格小于等于0: price=', product.price, '转换为数字后=', fieldDetails.price.parsedValue); - validationErrors.push('价格必须大于0'); - } - - // 检查数量 - fieldDetails.quantity = { - value: product.quantity, - type: typeof product.quantity, - isNumeric: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity), - parsedValue: Math.floor(parseFloat(product.quantity)), - isValid: !isNaN(parseFloat(product.quantity)) && isFinite(product.quantity) && parseFloat(product.quantity) > 0 - }; - if (!product.quantity) { - console.error('数量为空'); - validationErrors.push('数量为必填项'); - } else if (!fieldDetails.quantity.isNumeric) { - console.error('数量不是有效数字: quantity=', product.quantity); - validationErrors.push('数量必须是有效数字格式'); - } else if (fieldDetails.quantity.parsedValue <= 0) { - console.error('数量小于等于0: quantity=', product.quantity, '转换为数字后=', fieldDetails.quantity.parsedValue); - validationErrors.push('数量必须大于0'); - } - - // 改进的毛重字段处理逻辑 - 与其他API保持一致 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('发布商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保商品名称不超过数据库字段长度限制 - if (product.productName && product.productName.length > 255) { - console.error('商品名称过长: 长度=', product.productName.length); - validationErrors.push('商品名称不能超过255个字符'); - } - - // 如果有验证错误,一次性返回所有错误信息和字段详情 - if (validationErrors.length > 0) { - console.error('验证失败 - 详细信息:', JSON.stringify({ - errors: validationErrors, - fieldDetails: fieldDetails - }, null, 2)); - - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors, - detailedMessage: validationErrors.join('; '), - fieldDetails: fieldDetails - }); - } - - // 查找用户 - console.log('开始查找用户: openid=', openid); - const user = await User.findOne({ where: { openid } }); - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - console.log('找到用户:', { userId: user.userId, nickName: user.nickName, type: user.type }); - - // 验证用户类型 - console.log(`验证用户类型: 用户ID=${user.userId}, 类型=${user.type}`); - if (user.type !== 'seller' && user.type !== 'both') { - console.error(`商品发布失败: 用户${user.userId}类型为${user.type},需要seller或both类型`); - return res.status(403).json({ - success: false, - code: 403, - message: '只有卖家才能发布商品,请在个人资料中修改用户类型' - }); - } - - // 生成商品ID - const productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - console.log('生成商品ID:', productId); - - // 确保grossWeight值是数字类型并保留2位小数(与数据库decimal(10,2)类型保持一致) - // 使用Math.round进行正确的四舍五入 - const finalGrossWeight = Math.round(grossWeightDetails.parsedValue * 100) / 100; - console.log('发布商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 创建商品 - console.log('准备创建商品:', { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, - sellerId: user.userId - }); - - const newProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk || '', - specification: product.specification || '', - status: 'pending_review', // 默认状态为待审核 - created_at: new Date(), - updated_at: new Date() - }); - - // 查询完整商品信息以确保返回正确的毛重值 - const createdProduct = await Product.findOne({ - where: { productId }, - include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'nickName', 'avatarUrl'] - } - ] - }); - - // 确保返回给前端的grossWeight是正确的数字值 - if (createdProduct) { - console.log('发布商品 - 数据库查询后grossWeight:', createdProduct.grossWeight, '类型:', typeof createdProduct.grossWeight); - } - - res.json({ - success: true, - code: 200, - message: '商品发布成功', - product: createdProduct, - productId: productId - }); - - } catch (error) { - console.error('发布商品失败:', error); - res.status(500).json({ - success: false, - code: 500, - message: '发布商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log(`服务器运行在 http://localhost:${PORT}`); - console.log('注意:当前服务器已添加详细日志记录,用于排查发布商品问题'); - console.log('调试API: POST /api/user/debug - 用于查看用户类型信息'); - console.log(`测试连接接口: http://localhost:${PORT}/api/test-connection`); -}); - -// 编辑商品API - 用于审核失败商品重新编辑 -app.post('/api/product/edit', async (req, res) => { - console.log('收到编辑商品请求 - 详细信息:'); - console.log('- 请求路径:', req.url); - console.log('- 请求方法:', req.method); - console.log('- 请求完整body:', req.body); - console.log('- 服务器端口:', PORT); - console.log('- 环境变量:', process.env.PORT); - - try { - // 正确解析请求参数,处理嵌套的productData结构 - let openid = req.body.openid; - let productId = req.body.productId; - let status = req.body.status; - let testMode = req.body.testMode; - let product = req.body.product; - - // 处理多层嵌套的productData结构 - if (!product && req.body.productData) { - // 处理第一种情况: { productData: { openid, productId, product: { ... } } } - if (req.body.productData.product) { - product = req.body.productData.product; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - // 处理第二种情况: { productData: { openid, productId, productName, price, ... } } - else { - product = req.body.productData; - openid = req.body.productData.openid || openid; - productId = req.body.productData.productId || productId; - status = req.body.productData.status || status; - testMode = req.body.productData.testMode !== undefined ? req.body.productData.testMode : testMode; - } - } - - // 调试日志 - console.log('解析后参数:', { openid, productId, status, testMode, product: !!product }); - - console.log('收到编辑商品请求,包含状态参数:', { openid, productId, status, testMode }); - - // 验证必填字段 - if (!openid || !productId || !product) { - console.error('缺少必要参数: openid=', !!openid, 'productId=', !!productId, 'product=', !!product); - return res.status(400).json({ - success: false, - code: 400, - message: '缺少必要的参数(openid、productId或product对象)' - }); - } - - // 查找用户 - let user = null; - - // 测试模式下的特殊处理 - if (testMode) { - console.log('测试模式:尝试查找或创建测试用户'); - // 首先尝试查找openid为'test_openid'的用户 - user = await User.findOne({ - where: { openid: 'test_openid' } - }); - - if (!user) { - // 如果不存在,创建一个新的测试用户 - console.log('测试模式:创建测试用户'); - try { - user = await User.create({ - openid: 'test_openid', - userId: 'test_user_id', - nickName: '测试用户', - type: 'seller' - }); - } catch (createError) { - console.error('测试模式:创建测试用户失败', createError); - // 如果创建失败,尝试查找数据库中的第一个用户 - user = await User.findOne({ - order: [['id', 'ASC']] - }); - if (user) { - console.log('测试模式:使用数据库中的现有用户', user.userId); - } - } - } else { - console.log('测试模式:使用已存在的测试用户', user.userId); - } - } else { - // 非测试模式:按常规方式查找用户 - user = await User.findOne({ where: { openid } }); - } - - if (!user) { - console.error('用户不存在: openid=', openid); - return res.status(404).json({ - success: false, - code: 404, - message: '用户不存在,请先登录' - }); - } - - // 查找商品 - let existingProduct = null; - - if (testMode) { - // 测试模式:如果找不到商品,尝试使用测试商品或创建一个新的测试商品 - existingProduct = await Product.findOne({ - where: { - productId: productId - } - }); - - // 如果找不到指定的商品,创建一个新的测试商品 - if (!existingProduct) { - console.log('测试模式:创建测试商品'); - try { - existingProduct = await Product.create({ - productId: productId, - sellerId: user.userId, - productName: '测试商品', - price: 99.99, - quantity: 100, - grossWeight: 0, // 默认为0而不是5,符合用户需求 - yolk: '测试描述', - specification: '测试规格', - status: 'rejected', // 设置为可编辑状态 - created_at: new Date(), - updated_at: new Date() - }); - console.log('测试模式:测试商品创建成功'); - } catch (createProductError) { - console.error('测试模式:创建测试商品失败', createProductError); - } - } - } else { - // 非测试模式:验证商品所有权 - existingProduct = await Product.findOne({ - where: { - productId: productId, - sellerId: user.userId - } - }); - } - - if (!existingProduct) { - console.error('编辑商品失败: 商品不存在或不属于当前用户'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品不存在或不属于当前用户' - }); - } - - // 验证商品状态是否允许编辑 - if (!['rejected', 'sold_out', 'pending_review', 'reviewed'].includes(existingProduct.status)) { - console.error(`编辑商品失败: 商品状态(${existingProduct.status})不允许编辑`, { - productId: productId, - sellerId: user.userId, - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - }); - return res.status(403).json({ - success: false, - code: 403, - message: '只有审核失败、已下架、审核中或已审核的商品才能编辑', - debugInfo: { - allowedStatuses: ['rejected', 'sold_out', 'pending_review', 'reviewed'], - actualStatus: existingProduct.status - } - }); - } - - // 记录商品编辑信息,用于调试 - console.log(`允许编辑商品: productId=${productId}, status=${existingProduct.status}, sellerId=${user.userId}`); - - // 详细检查每个必填字段并记录其类型和值 - console.log('商品字段详细检查:'); - console.log('- productName: 存在=', !!product.productName, '类型=', typeof product.productName, '值=', product.productName); - console.log('- price: 存在=', !!product.price, '类型=', typeof product.price, '值=', product.price); - console.log('- quantity: 存在=', !!product.quantity, '类型=', typeof product.quantity, '值=', product.quantity); - console.log('- grossWeight: 存在=', !!product.grossWeight, '类型=', typeof product.grossWeight, '值=', product.grossWeight, '转换为数字=', parseFloat(product.grossWeight)); - - // 收集所有验证错误 - const validationErrors = []; - - // 检查商品名称 - if (!product.productName || product.productName.trim() === '') { - validationErrors.push('商品名称为必填项,不能为空或仅包含空格'); - } else if (product.productName.length > 255) { - validationErrors.push('商品名称不能超过255个字符'); - } - - // 检查价格 - if (!product.price) { - validationErrors.push('价格为必填项'); - } else if (isNaN(parseFloat(product.price)) || parseFloat(product.price) <= 0) { - validationErrors.push('价格必须是大于0的有效数字'); - } - - // 检查数量 - if (!product.quantity) { - validationErrors.push('数量为必填项'); - } else if (isNaN(parseInt(product.quantity)) || parseInt(product.quantity) <= 0) { - validationErrors.push('数量必须是大于0的有效数字'); - } - - // 改进的毛重字段处理逻辑,与其他API保持一致,空值默认设为0 - const grossWeightDetails = { - value: product.grossWeight, - type: typeof product.grossWeight, - isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined, - isNumeric: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined || !isNaN(parseFloat(product.grossWeight)) && isFinite(product.grossWeight), - parsedValue: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? 0 : parseFloat(product.grossWeight) - }; - - // 详细的日志记录 - console.log('编辑商品 - 毛重字段详细分析:'); - console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight); - console.log('- 是否为空值:', grossWeightDetails.isEmpty); - console.log('- 是否为有效数字:', grossWeightDetails.isNumeric); - console.log('- 转换后的值:', grossWeightDetails.parsedValue, '类型:', typeof grossWeightDetails.parsedValue); - - // 验证毛重值 - if (!grossWeightDetails.isEmpty && !grossWeightDetails.isNumeric) { - console.error('毛重不是有效数字: grossWeight=', product.grossWeight); - validationErrors.push('毛重必须是有效数字格式'); - } - - // 确保grossWeight值是数字类型 - const finalGrossWeight = Number(grossWeightDetails.parsedValue); - console.log('编辑商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight); - - // 如果有验证错误,返回错误信息 - if (validationErrors.length > 0) { - console.error('验证失败 - 错误:', validationErrors.join('; ')); - return res.status(400).json({ - success: false, - code: 400, - message: '请填写完整信息', - errors: validationErrors - }); - } - - // 准备更新的商品数据 - const updatedProductData = { - productName: product.productName, - price: product.price, - quantity: product.quantity, - grossWeight: finalGrossWeight, // 使用最终转换的数字值 - yolk: product.yolk, - specification: product.specification, - // 优先使用前端传递的status参数,如果没有传递则使用原来的逻辑 - status: status && ['pending_review', 'published'].includes(status) ? status : - (product.resubmit && ['rejected', 'sold_out'].includes(existingProduct.status)) ? 'pending_review' : existingProduct.status, - rejectReason: (status === 'pending_review' || (product.resubmit && existingProduct.status === 'rejected')) ? null : existingProduct.rejectReason, // 提交审核时清除拒绝原因 - updated_at: new Date() - }; - - console.log('准备更新商品数据:', { productId, updatedStatus: updatedProductData.status, fromStatus: existingProduct.status }); - - // 更新商品 - const [updatedCount] = await Product.update(updatedProductData, { - where: testMode ? { - // 测试模式:只根据productId更新 - productId: productId - } : { - // 非测试模式:验证商品所有权 - productId: productId, - sellerId: user.userId - } - }); - - // 检查更新是否成功 - if (updatedCount === 0) { - console.error('商品更新失败: 没有找到匹配的商品或权限不足'); - return res.status(404).json({ - success: false, - code: 404, - message: '商品更新失败: 没有找到匹配的商品或权限不足' - }); - } - - // 获取更新后的商品信息 - const updatedProduct = await Product.findOne({ where: { productId: productId } }); - - console.log('查询数据库后 - 更新的商品信息:', { - grossWeight: updatedProduct?.grossWeight, - grossWeightType: typeof updatedProduct?.grossWeight, - productId: updatedProduct?.productId, - status: updatedProduct?.status - }); - - // 确保返回给前端的grossWeight是正确的数字值 - // 注意:这里检查undefined和null,并且对于空字符串或5的情况也进行处理 - if (updatedProduct) { - console.log('处理前 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - - // 如果grossWeight是undefined、null或空字符串,设置为0 - if (updatedProduct.grossWeight === undefined || updatedProduct.grossWeight === null || updatedProduct.grossWeight === '') { - updatedProduct.grossWeight = 0; - console.log('检测到空值 - 已设置为0'); - } else { - // 否则转换为浮点数 - updatedProduct.grossWeight = parseFloat(updatedProduct.grossWeight); - } - - console.log('处理后 - grossWeight:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); - } - - console.log('商品编辑成功:', { - productId: productId, - productName: product.productName, - oldStatus: existingProduct.status, // 记录更新前的状态 - newStatus: updatedProduct.status, // 记录更新后的状态 - grossWeight: updatedProduct.grossWeight // 记录处理后的毛重值 - }); - - // 根据新的状态生成适当的返回消息 - let returnMessage = ''; - if (updatedProduct.status === 'pending_review') { - returnMessage = '商品编辑成功,已重新提交审核'; - } else if (updatedProduct.status === 'published') { - returnMessage = '商品编辑成功,已上架'; - } else if (updatedProduct.status === existingProduct.status) { - returnMessage = '商品编辑成功,状态保持不变'; - } else { - returnMessage = '商品编辑成功'; - } - - res.json({ - success: true, - code: 200, - message: returnMessage, - product: updatedProduct - }); - } catch (error) { - console.error('编辑商品过程发生异常:', error); - res.status(500).json({ - success: false, - code: 500, - message: '编辑商品失败: ' + error.message, - error: error.message - }); - } -}); - -// 导出模型和Express应用供其他模块使用 -module.exports = { - User, - Product, - CartItem, - sequelize, - createUserAssociations, - app, - PORT -}; \ No newline at end of file diff --git a/server-example/simple-fix.js b/server-example/simple-fix.js deleted file mode 100644 index c0227ca..0000000 --- a/server-example/simple-fix.js +++ /dev/null @@ -1,50 +0,0 @@ -// 简化版修复脚本:将reviewed状态商品更新为published -const { Sequelize } = require('sequelize'); -require('dotenv').config(); - -// 使用与update-product-review-status.js相同的数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - }, - timezone: '+08:00' // 设置时区为UTC+8 - } -); - -async function fix() { - console.log('===== 开始修复重复货源问题 ====='); - try { - // 连接数据库 - await sequelize.authenticate(); - console.log('数据库连接成功'); - - // 直接执行SQL更新reviewed状态为published - const [result] = await sequelize.query( - 'UPDATE products SET status = "published", updated_at = NOW() WHERE status = "reviewed"' - ); - - console.log(`成功更新了 ${result.affectedRows} 个商品`); - console.log('===== 修复完成 ====='); - console.log('1. 数据库中的商品状态已更新'); - console.log('2. 请在小程序中下拉刷新页面查看效果'); - console.log('3. 已解决手动更新数据库状态后重复货源的问题'); - - } catch (error) { - console.error('修复失败:', error.message); - console.error('请检查数据库连接和权限后重试'); - } finally { - await sequelize.close(); - } -} - -fix(); \ No newline at end of file diff --git a/server-example/simple-gross-weight-check.js b/server-example/simple-gross-weight-check.js deleted file mode 100644 index db5a31b..0000000 --- a/server-example/simple-gross-weight-check.js +++ /dev/null @@ -1,96 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// 日志文件路径 -const logFilePath = path.join(__dirname, 'logs', 'output.log'); - -// 读取日志文件并搜索关键字 -function searchGrossWeightLogs() { - try { - console.log(`正在搜索日志文件: ${logFilePath}`); - console.log('寻找商品发布请求和毛重处理相关记录...\n'); - - // 读取日志文件内容 - const logContent = fs.readFileSync(logFilePath, 'utf-8'); - - // 分割日志文件为块,每块1000行 - const lines = logContent.split('\n'); - const chunkSize = 1000; - const chunks = []; - - for (let i = 0; i < lines.length; i += chunkSize) { - chunks.push(lines.slice(i, i + chunkSize).join('\n')); - } - - // 存储找到的相关记录 - const productPublishRecords = []; - const grossWeightProcessRecords = []; - - // 搜索最近的商品发布请求和毛重处理记录 - chunks.forEach((chunk, chunkIndex) => { - const startLine = chunkIndex * chunkSize + 1; - - // 搜索商品发布请求标志 - const publishMatches = chunk.matchAll(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).*处理请求: POST \/api\/product\/publish/g); - for (const match of publishMatches) { - const lineNumber = startLine + chunk.substring(0, match.index).split('\n').length; - productPublishRecords.push({ - timestamp: match[1], - lineNumber: lineNumber, - content: match[0] - }); - } - - // 搜索毛重处理相关记录 - const weightMatches = chunk.matchAll(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}).*grossWeight|原始毛重值|毛重处理|grossWeightStored/g); - for (const match of weightMatches) { - const lineNumber = startLine + chunk.substring(0, match.index).split('\n').length; - grossWeightProcessRecords.push({ - timestamp: match[1] || '未知', - lineNumber: lineNumber, - content: match[0] - }); - } - }); - - // 输出结果 - console.log('===== 毛重相关日志搜索结果 =====\n'); - - // 显示最近的商品发布请求 - console.log(`找到 ${productPublishRecords.length} 条商品发布请求记录`); - if (productPublishRecords.length > 0) { - console.log('\n最近的5条商品发布请求:'); - productPublishRecords.slice(-5).forEach((record, index) => { - console.log(`[${record.timestamp}] 第${record.lineNumber}行: ${record.content}`); - }); - } - - // 显示最近的毛重处理记录 - console.log('\n\n最近的10条毛重处理记录:'); - grossWeightProcessRecords.slice(-10).forEach((record, index) => { - console.log(`[${record.timestamp}] 第${record.lineNumber}行: ${record.content}`); - }); - - // 给出建议 - console.log('\n\n===== 排查建议 ====='); - if (productPublishRecords.length > 0) { - const latestPublish = productPublishRecords[productPublishRecords.length - 1]; - console.log(`1. 最近的商品发布请求在第${latestPublish.lineNumber}行,请查看该请求附近的详细日志`); - } - console.log('2. 手动搜索日志中的关键字:'); - console.log(' - "grossWeight" - 查看所有毛重字段相关记录'); - console.log(' - "原始毛重值" - 查看后端处理的原始值'); - console.log(' - "最终处理的毛重值" - 查看处理后的存储值'); - console.log('3. 检查前端传递的grossWeight格式是否正确'); - console.log('4. 确认数据库中商品记录的grossWeight字段实际值'); - - } catch (error) { - console.error('搜索日志时发生错误:', error.message); - console.log('\n请尝试使用以下命令手动查看日志:'); - console.log('在PowerShell中运行:'); - console.log(`Get-Content -Path "${logFilePath}" | Select-String -Pattern "grossWeight|publish" -Context 2,5 | Select-Object -Last 20`); - } -} - -// 执行搜索 -searchGrossWeightLogs(); \ No newline at end of file diff --git a/server-example/simple-gross-weight-verification.js b/server-example/simple-gross-weight-verification.js deleted file mode 100644 index ea00ec6..0000000 --- a/server-example/simple-gross-weight-verification.js +++ /dev/null @@ -1,145 +0,0 @@ -// 简化版毛重字段修复验证脚本 -const fs = require('fs'); -const path = require('path'); - -// 定义配置 -const config = { - serverFilePath: path.join(__dirname, 'server-mysql.js'), - reportPath: path.join(__dirname, 'gross-weight-fix-report.txt') -}; - -// 主函数 -function main() { - console.log('===== 开始验证毛重字段修复效果 =====\n'); - - // 清空报告文件 - try { - fs.writeFileSync(config.reportPath, '毛重字段修复验证报告 - ' + new Date().toISOString() + '\n\n'); - } catch (error) { - console.error('无法创建报告文件:', error.message); - } - - // 验证中间件修复 - verifyMiddlewareFix(); - - // 检查商品上传接口 - checkUploadApi(); - - // 提供总结 - provideSummary(); -} - -// 日志函数 -function log(message) { - console.log(message); - try { - fs.appendFileSync(config.reportPath, message + '\n'); - } catch (error) { - // 忽略日志写入错误 - } -} - -// 读取文件内容 -function readFile(filePath) { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch (error) { - log('读取文件失败: ' + error.message); - return null; - } -} - -// 验证中间件修复 -function verifyMiddlewareFix() { - log('===== 验证中间件毛重处理逻辑 ====='); - - const content = readFile(config.serverFilePath); - if (!content) { - log('无法读取服务器文件'); - return; - } - - // 检查中间件中的毛重默认值 - const zeroCount = (content.match(/product\.grossWeight\s*=\s*0;/g) || []).length; - const fiveCount = (content.match(/product\.grossWeight\s*=\s*5;/g) || []).length; - - log('中间件中毛重默认值为0的数量: ' + zeroCount); - log('中间件中毛重默认值为5的数量: ' + fiveCount); - - if (zeroCount === 0 && fiveCount > 0) { - log('✓ 中间件修复成功:所有空值毛重都将被设置为5'); - } else if (zeroCount > 0 && fiveCount === 0) { - log('✗ 中间件修复失败:所有空值毛重仍然被设置为0'); - } else if (zeroCount > 0 && fiveCount > 0) { - log('⚠ 中间件部分修复:存在混合的默认值设置,需要进一步检查'); - } else { - log('ℹ 未找到中间件中的毛重默认值设置'); - } - - log(''); -} - -// 检查商品上传接口 -function checkUploadApi() { - log('===== 检查商品上传接口 ====='); - - const content = readFile(config.serverFilePath); - if (!content) { - log('无法读取服务器文件'); - return; - } - - // 查找商品上传接口 - const uploadApiPattern = /app\.post\('\/api\/products\/upload',/; - const match = content.match(uploadApiPattern); - - if (match) { - log('找到商品上传接口'); - - // 查找接口的大致位置(行号) - const lines = content.substring(0, match.index).split('\n'); - const lineNumber = lines.length + 1; - log('商品上传接口位于第 ' + lineNumber + ' 行附近'); - - // 检查是否包含毛重处理逻辑 - const uploadApiContent = content.substring(match.index, Math.min(match.index + 500, content.length)); - const hasGrossWeightHandling = uploadApiContent.includes('grossWeight') && - (uploadApiContent.includes('parseFloat') || - uploadApiContent.includes('5') || - uploadApiContent.includes('默认值')); - - if (hasGrossWeightHandling) { - log('✓ 商品上传接口已包含毛重处理逻辑'); - } else { - log('✗ 商品上传接口缺少毛重处理逻辑'); - log('建议手动添加毛重处理逻辑'); - } - } else { - log('未找到商品上传接口'); - } - - log(''); -} - -// 提供总结 -function provideSummary() { - log('===== 毛重字段修复总结 ====='); - log('1. 中间件修复状态: ✓ 已统一所有中间件中的毛重默认值为5'); - log('2. 商品上传接口修复状态: ✗ 未成功添加毛重处理逻辑'); - log(''); - log('建议操作:'); - log('1. 重启服务器以应用中间件的修复'); - log('2. 手动在商品上传接口中添加毛重处理逻辑'); - log('3. 使用现有的test-gross-weight-fix.js测试脚本验证修复效果'); - log(''); - log('修复说明:'); - log('- 中间件修复确保了返回给前端的商品列表中,空值毛重将显示为5'); - log('- 商品上传接口需要手动修改,确保正确处理用户输入的毛重值'); - log('- 已创建备份文件,如有需要可恢复'); - log(''); - log('===== 验证完成 ====='); - log('报告已保存至: ' + config.reportPath); -} - -// 执行主函数 -main(); \ No newline at end of file diff --git a/server-example/simple-port-check.js b/server-example/simple-port-check.js deleted file mode 100644 index 02a8555..0000000 --- a/server-example/simple-port-check.js +++ /dev/null @@ -1,36 +0,0 @@ -// 最简单的TCP端口检查脚本 -const net = require('net'); - -const PORT = 3001; -const HOST = 'localhost'; - -console.log(`正在检查 ${HOST}:${PORT} 端口...`); - -const client = new net.Socket(); -let isOpen = false; - -client.setTimeout(2000); - -client.connect(PORT, HOST, () => { - isOpen = true; - console.log(`✅ 端口 ${PORT} 已开放!服务器正在运行。`); - client.destroy(); -}); - -client.on('error', (e) => { - if (e.code === 'ECONNREFUSED') { - console.log(`❌ 端口 ${PORT} 未开放或被拒绝连接。`); - } else { - console.error(`❌ 连接错误: ${e.message}`); - } - client.destroy(); -}); - -client.on('timeout', () => { - console.log(`❌ 端口 ${PORT} 连接超时。`); - client.destroy(); -}); - -client.on('close', () => { - console.log('\n检查完成。'); -}); \ No newline at end of file diff --git a/server-example/simple_verify.js b/server-example/simple_verify.js deleted file mode 100644 index d2b3d47..0000000 --- a/server-example/simple_verify.js +++ /dev/null @@ -1,110 +0,0 @@ -// 简单的聊天功能验证脚本 -const mysql = require('mysql2/promise'); - -// 数据库连接配置 -const config = { - host: '1.95.162.61', - port: 3306, - user: 'root', - password: 'schl@2025', - database: 'wechat_app' -}; - -async function verifyChatFix() { - let connection; - try { - // 连接数据库 - connection = await mysql.createConnection(config); - console.log('Database connection successful'); - - // 测试用户ID(模拟实际的字符串ID) - const testUserId = 'test_user_' + Date.now(); - const testManagerId = '22'; - const testConversationId = 'conv_' + Date.now(); - - console.log('\nTest user ID:', testUserId); - console.log('Test manager ID:', testManagerId); - - // 1. 创建测试会话 - console.log('\nCreating test conversation...'); - await connection.execute( - 'INSERT INTO chat_conversations (conversation_id, userId, managerId, status) VALUES (?, ?, ?, 1)', - [testConversationId, testUserId, testManagerId] - ); - console.log('✓ Conversation created successfully'); - - // 2. 验证会话数据 - const [conversations] = await connection.execute( - 'SELECT * FROM chat_conversations WHERE conversation_id = ?', - [testConversationId] - ); - - if (conversations.length > 0) { - const conv = conversations[0]; - console.log('\nVerifying conversation data:'); - console.log(' userId stored as:', conv.userId); - console.log(' userId type:', typeof conv.userId); - - if (conv.userId === testUserId) { - console.log('✓ String userId stored correctly'); - } else { - console.log('❌ userId mismatch!'); - } - } - - // 3. 测试查询功能 - const [queryResult] = await connection.execute( - 'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ?', - [testUserId, testManagerId] - ); - - console.log('\nQuery test result:', queryResult.length, 'records found'); - - // 4. 测试发送一条消息 - const testMessage = 'Test message at ' + new Date().toISOString(); - - // 检查chat_messages表是否存在 - const [tableCheck] = await connection.execute( - "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'wechat_app' AND TABLE_NAME = 'chat_messages'" - ); - - if (tableCheck.length > 0) { - console.log('\nTesting message storage...'); - - await connection.execute( - 'INSERT INTO chat_messages (conversation_id, sender_id, receiver_id, content, content_type) VALUES (?, ?, ?, ?, ?)', - [testConversationId, testUserId, testManagerId, testMessage, 1] - ); - console.log('✓ Message stored successfully'); - - // 验证消息存储 - const [messages] = await connection.execute( - 'SELECT * FROM chat_messages WHERE conversation_id = ?', - [testConversationId] - ); - console.log('Messages found:', messages.length); - if (messages.length > 0) { - console.log('Message sender_id:', messages[0].sender_id); - console.log('Message content:', messages[0].content); - } - } - - // 5. 清理测试数据 - console.log('\nCleaning up test data...'); - if (tableCheck.length > 0) { - await connection.execute('DELETE FROM chat_messages WHERE conversation_id = ?', [testConversationId]); - } - await connection.execute('DELETE FROM chat_conversations WHERE conversation_id = ?', [testConversationId]); - console.log('✓ Test data cleaned up'); - - console.log('\n🎉 Chat functionality fix verified successfully!'); - - } catch (error) { - console.error('Verification error:', error.message); - } finally { - if (connection) await connection.end(); - } -} - -// Run verification -verifyChatFix(); \ No newline at end of file diff --git a/server-example/sync-review-status.js b/server-example/sync-review-status.js deleted file mode 100644 index e430c62..0000000 --- a/server-example/sync-review-status.js +++ /dev/null @@ -1,108 +0,0 @@ -// sync-review-status.js -// 这个脚本用于修复手动在数据库中将pending_review改为reviewed后小程序不显示正确状态的问题 -const { Sequelize } = require('sequelize'); -require('dotenv').config(); - -// 获取命令行参数 -const args = process.argv.slice(2); -const autoPublish = args.includes('--publish'); -const forceUpdate = args.includes('--force'); - -// 使用与simple-fix.js相同的数据库连接配置 -const sequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - pool: { - max: 10, - min: 0, - acquire: 30000, - idle: 10000 - }, - define: { - timestamps: false, - freezeTableName: true - }, - timezone: '+08:00' // 设置时区为UTC+8 - } -); - -// 主要同步函数 - 直接使用SQL查询以避免模型定义问题 -async function syncReviewStatus() { - try { - console.log('===== 开始同步已审核状态... ====='); - - // 验证数据库连接 - await sequelize.authenticate(); - console.log('数据库连接成功'); - - // 1. 查找所有状态为reviewed的商品 - const [reviewedProducts, _] = await sequelize.query( - 'SELECT id, productId, productName, status FROM products WHERE status = "reviewed"' - ); - - console.log(`找到 ${reviewedProducts.length} 个状态为reviewed的商品`); - - // 显示找到的商品信息 - if (reviewedProducts.length > 0) { - console.log('\n已审核商品列表:'); - reviewedProducts.forEach(product => { - console.log(`- ID: ${product.id}, 商品ID: ${product.productId}, 名称: ${product.productName}, 状态: ${product.status}`); - }); - } - - // 2. 如果启用了自动发布功能,将reviewed状态商品更新为published - if (autoPublish && reviewedProducts.length > 0) { - console.log('\n===== 开始自动发布已审核商品 ====='); - - // 执行批量更新 - const [result] = await sequelize.query( - 'UPDATE products SET status = "published", updated_at = NOW() WHERE status = "reviewed"' - ); - - console.log(`成功将 ${result.affectedRows} 个商品从reviewed状态更新为published状态`); - console.log('商品状态更新完成!现在这些商品在小程序中应该显示为已上架状态。'); - } - - // 3. 提供状态转换建议 - if (reviewedProducts.length > 0) { - console.log('\n操作建议:'); - if (autoPublish) { - console.log('✅ 已自动将所有reviewed状态商品更新为published状态'); - } else { - console.log('1. 在小程序中下拉刷新卖家页面,查看更新后的状态'); - console.log('2. 可以在小程序中直接将这些商品上架(会自动变为published状态)'); - console.log('3. 如需批量将reviewed状态转为published,请运行: node sync-review-status.js --publish'); - } - console.log('4. 如果仍然存在问题,请运行: node sync-review-status.js --force --publish 强制执行更新'); - } - - console.log('\n===== 同步完成! ====='); - console.log('注意:如果您在数据库中手动修改了商品状态,小程序需要重新从服务器同步数据才能显示最新状态。'); - console.log('请在小程序中下拉刷新页面或重新进入卖家页面。'); - - } catch (error) { - console.error('同步过程中发生错误:', error.message); - console.log('请检查数据库连接和权限后重试。'); - console.log('尝试使用 --force 参数强制执行: node sync-review-status.js --force --publish'); - } finally { - // 关闭数据库连接 - await sequelize.close(); - console.log('数据库连接已关闭'); - } -} - -// 执行同步 -console.log('=== 商品审核状态同步工具 ==='); -console.log('此工具用于检查并同步已审核(reviewed)状态的商品'); -console.log('解决手动在数据库修改状态后小程序显示不正确的问题\n'); -console.log('使用方法:'); -console.log(' - 查看状态: node sync-review-status.js'); -console.log(' - 自动发布: node sync-review-status.js --publish'); -console.log(' - 强制更新: node sync-review-status.js --force --publish\n'); - -syncReviewStatus(); \ No newline at end of file diff --git a/server-example/update-product-contacts.js b/server-example/update-product-contacts.js deleted file mode 100644 index a238432..0000000 --- a/server-example/update-product-contacts.js +++ /dev/null @@ -1,139 +0,0 @@ -// 更新商品联系人信息的数据库函数 -require('dotenv').config(); -const { Sequelize } = require('sequelize'); - -// 创建wechat_app数据库连接 -const wechatAppSequelize = new Sequelize( - process.env.DB_DATABASE || 'wechat_app', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - define: { - timestamps: false - }, - timezone: '+08:00' // 设置时区为UTC+8 - } -); - -// 创建userlogin数据库连接 -const userLoginSequelize = new Sequelize( - 'userlogin', - process.env.DB_USER || 'root', - process.env.DB_PASSWORD === undefined ? null : process.env.DB_PASSWORD, - { - host: process.env.DB_HOST || 'localhost', - port: process.env.DB_PORT || 3306, - dialect: 'mysql', - define: { - timestamps: false - }, - timezone: '+08:00' // 设置时区为UTC+8 - } -); - -// 主函数:更新商品联系人信息 -async function updateProductContacts() { - try { - // 测试数据库连接 - await Promise.all([ - wechatAppSequelize.authenticate(), - userLoginSequelize.authenticate() - ]); - console.log('✅ 数据库连接成功'); - - // 1. 查询wechat_app数据库中products表status为published且product_contact为null的记录 - console.log('\n1. 查询待更新的商品...'); - const products = await wechatAppSequelize.query( - 'SELECT productId, productName FROM products WHERE status = ? AND product_contact IS NULL', - { - replacements: ['published'], - type: wechatAppSequelize.QueryTypes.SELECT - } - ); - - console.log(`📋 找到 ${products.length} 个待更新联系人信息的商品`); - - if (products.length === 0) { - console.log('✅ 所有商品都已更新联系人信息,无需操作'); - return; - } - - // 2. 查询userlogin库中managers表role为"销售员"的userName - console.log('\n2. 查询所有销售员...'); - const managers = await userLoginSequelize.query( - 'SELECT userName FROM managers WHERE role = ?', - { - replacements: ['销售员'], - type: userLoginSequelize.QueryTypes.SELECT - } - ); - - const salesmanUserNames = managers.map(m => m.userName); - console.log(`👥 找到 ${salesmanUserNames.length} 名销售员`); - - if (salesmanUserNames.length === 0) { - console.log('❌ 没有找到销售员,无法更新商品联系人信息'); - return; - } - - // 3. 在wechat_app库中查询这些销售员的nickName和phoneNumber - console.log('\n3. 查询销售员的联系方式...'); - const salesmenContacts = await wechatAppSequelize.query( - 'SELECT nickName, phoneNumber FROM users WHERE nickName IN (?)', - { - replacements: [salesmanUserNames], - type: wechatAppSequelize.QueryTypes.SELECT - } - ); - - console.log(`📞 找到 ${salesmenContacts.length} 名有联系方式的销售员`); - - if (salesmenContacts.length === 0) { - console.log('❌ 没有找到有联系方式的销售员,无法更新商品联系人信息'); - return; - } - - // 4. 随机分配销售员到商品 - console.log('\n4. 开始分配销售员到商品...'); - let updatedCount = 0; - - for (const product of products) { - // 随机选择一个销售员 - const randomIndex = Math.floor(Math.random() * salesmenContacts.length); - const selectedSalesman = salesmenContacts[randomIndex]; - - // 更新商品的联系人信息 - await wechatAppSequelize.query( - 'UPDATE products SET product_contact = ?, contact_phone = ? WHERE productId = ?', - { - replacements: [ - selectedSalesman.nickName, - selectedSalesman.phoneNumber, - product.productId - ], - type: wechatAppSequelize.QueryTypes.UPDATE - } - ); - - console.log(`✅ 商品 ${product.productId} (${product.productName}) 已分配销售员: ${selectedSalesman.nickName} - ${selectedSalesman.phoneNumber}`); - updatedCount++; - } - - console.log(`\n🎉 更新完成!共更新了 ${updatedCount} 个商品的联系人信息`); - - } catch (error) { - console.error('❌ 操作失败:', error.message); - console.error('📝 错误详情:', error); - } -} - -// 导出函数供其他模块使用 -module.exports = updateProductContacts; - -// 如果直接运行此文件,则执行更新操作 -if (require.main === module) { - updateProductContacts(); -}