You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5702 lines
199 KiB

3 months ago
// ECS服务器示例代码 - Node.js版 (MySQL版本)
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const { Sequelize, DataTypes, Model, Op } = require('sequelize');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const OssUploader = require('./oss-uploader');
require('dotenv').config();
// 创建Express应用
const app = express();
const PORT = process.env.PORT || 3003;
// 配置HTTP服务器连接限制
const http = require('http');
const server = http.createServer(app);
// 配置连接管理
server.maxConnections = 20; // 增加最大连接数限制
// 优化连接处理
app.use((req, res, next) => {
// 确保响应头包含正确的连接信息
res.setHeader('Connection', 'keep-alive');
res.setHeader('Keep-Alive', 'timeout=5, max=100');
next();
});
// 中间件
app.use(bodyParser.json());
// 创建临时文件夹用于存储上传的文件
const uploadTempDir = path.join(__dirname, 'temp-uploads');
if (!fs.existsSync(uploadTempDir)) {
fs.mkdirSync(uploadTempDir, { recursive: true });
}
// 配置multer中间件
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadTempDir);
},
filename: (req, file, cb) => {
// 生成唯一文件名
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const extname = path.extname(file.originalname).toLowerCase();
cb(null, file.fieldname + '-' + uniqueSuffix + extname);
}
});
// 为了解决URL重复问题,添加GET方法支持和URL修复处理
app.get('/api/wechat/getOpenid*', async (req, res) => {
// 无论URL格式如何,都返回正确的响应格式
res.json({
success: false,
code: 405,
message: '请使用POST方法访问此接口',
data: {}
});
});
// 添加全局中间件处理URL重复问题
app.use((req, res, next) => {
const url = req.url;
// 检测URL中是否有重复的模式
const repeatedPattern = /(\/api\/wechat\/getOpenid).*?(\1)/i;
if (repeatedPattern.test(url)) {
// 重定向到正确的URL
const correctedUrl = url.replace(repeatedPattern, '$1');
console.log(`检测到URL重复: ${url} -> 重定向到: ${correctedUrl}`);
res.redirect(307, correctedUrl); // 使用307保持原始请求方法
return;
}
next();
});
// 配置文件过滤函数,只允许上传图片
const fileFilter = (req, file, cb) => {
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'];
const extname = path.extname(file.originalname).toLowerCase();
if (allowedMimeTypes.includes(file.mimetype) && allowedExtensions.includes(extname)) {
cb(null, true);
} else {
cb(new Error('不支持的文件类型,仅支持JPG、PNG和GIF图片'), false);
}
};
// 创建multer实例
const upload = multer({
storage: storage,
limits: {
fileSize: 10 * 1024 * 1024 // 限制文件大小为10MB
},
fileFilter: fileFilter
});
// 添加请求日志中间件,捕获所有到达服务器的请求(必须放在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) {
// 处理商品数据的通用函数
const processProduct = (product) => {
if (product && typeof product === 'object') {
// 【关键修复】处理非数字字段的还原逻辑 - 支持毛重、价格和数量
// 还原非数字毛重值
if (product.grossWeight === 0.01 && product.isNonNumericGrossWeight && product.originalGrossWeight) {
product.grossWeight = String(product.originalGrossWeight);
console.log('中间件还原非数字毛重:', {
productId: product.productId,
original: product.originalGrossWeight,
final: product.grossWeight
});
}
// 正常处理:空值或其他值
else if (product.grossWeight === null || product.grossWeight === undefined || product.grossWeight === '') {
product.grossWeight = ''; // 空值设置为空字符串
} else {
product.grossWeight = String(product.grossWeight); // 确保是字符串类型
}
// 【新增】还原非数字价格值
if (product.price === 0.01 && product.isNonNumericPrice && product.originalPrice) {
product.price = String(product.originalPrice);
console.log('中间件还原非数字价格:', {
productId: product.productId,
original: product.originalPrice,
final: product.price
});
}
// 【新增】还原非数字数量值
if (product.quantity === 1 && product.isNonNumericQuantity && product.originalQuantity) {
product.quantity = String(product.originalQuantity);
console.log('中间件还原非数字数量:', {
productId: product.productId,
original: product.originalQuantity,
final: product.quantity
});
}
}
return product;
};
// 检查数据中是否包含商品列表
if (data && typeof data === 'object') {
// 处理/products/list接口的响应
if (data.products && Array.isArray(data.products)) {
data.products = data.products.map(processProduct);
}
// 处理/data字段中的商品列表
if (data.data && data.data.products && Array.isArray(data.data.products)) {
data.data.products = data.data.products.map(processProduct);
}
// 处理单个商品详情
if (data.data && data.data.product) {
data.data.product = processProduct(data.data.product);
}
// 处理直接的商品对象
if (data.product) {
data.product = processProduct(data.product);
}
}
// 调用原始的json方法
return originalJson.call(this, data);
};
next();
});
// 使用绝对路径加载环境变量(path模块已在文件顶部导入)
const envPath = path.resolve(__dirname, '.env');
console.log('正在从绝对路径加载.env文件:', envPath);
const dotenv = require('dotenv');
const result = dotenv.config({ path: envPath });
if (result.error) {
console.error('加载.env文件失败:', result.error.message);
} else {
console.log('.env文件加载成功');
console.log('解析的环境变量数量:', Object.keys(result.parsed || {}).length);
}
// 手动设置默认密码,确保密码被传递
if (!process.env.DB_PASSWORD || process.env.DB_PASSWORD === '') {
process.env.DB_PASSWORD = 'schl@2025';
console.log('已手动设置默认密码');
}
// 打印环境变量检查
console.log('环境变量检查:');
console.log('DB_HOST:', process.env.DB_HOST);
console.log('DB_PORT:', process.env.DB_PORT);
console.log('DB_DATABASE:', process.env.DB_DATABASE);
console.log('DB_USER:', process.env.DB_USER);
console.log('DB_PASSWORD长度:', process.env.DB_PASSWORD ? process.env.DB_PASSWORD.length : '0');
console.log('DB_PASSWORD值:', process.env.DB_PASSWORD ? '已设置(保密)' : '未设置');
// 从.env文件直接读取配置
const dbConfig = {
host: process.env.DB_HOST || '1.95.162.61',
port: process.env.DB_PORT || 3306,
database: process.env.DB_DATABASE || 'wechat_app',
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || ''
};
console.log('数据库连接配置:');
console.log(JSON.stringify(dbConfig, null, 2));
// MySQL数据库连接配置 - 确保密码正确传递
const sequelize = new Sequelize(
dbConfig.database,
dbConfig.user,
dbConfig.password, // 直接使用密码字符串,不做空值判断
{
host: dbConfig.host,
port: dbConfig.port,
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
},
logging: console.log,
define: {
timestamps: false
},
timezone: '+08:00' // 设置时区为UTC+8
}
);
// 微信小程序配置
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),
allowNull: true // 省份,可选
},
city: {
type: DataTypes.STRING(50),
allowNull: true // 城市,可选
},
district: {
type: DataTypes.STRING(255),
allowNull: true // 区域,可选
},
detailedaddress: {
type: DataTypes.STRING(255) // 详细地址
},
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 // 规格
},
// 入驻相关必填字段
collaborationid: {
type: DataTypes.TEXT,
allowNull: false // 合作商身份,必填
},
cooperation: {
type: DataTypes.STRING(255),
allowNull: false // 合作模式,必填
},
businesslicenseurl: {
type: DataTypes.TEXT,
allowNull: false // 营业执照,必填
},
proofurl: {
type: DataTypes.TEXT,
allowNull: true // 证明材料,可选
},
brandurl: {
type: DataTypes.TEXT // 品牌授权链文件
},
// 合作状态相关字段
partnerstatus: {
type: DataTypes.STRING(255) // 合作商状态
},
reasonforfailure: {
type: DataTypes.TEXT // 审核失败原因
},
agreement: {
type: DataTypes.TEXT // 合作商协议
},
reject_reason: {
type: DataTypes.TEXT // 拒绝理由
},
terminate_reason: {
type: DataTypes.TEXT // 终止合作理由
},
audit_time: {
type: DataTypes.DATE // 审核时间
},
followup: {
type: DataTypes.TEXT // 临时跟进
},
notice: {
type: DataTypes.STRING(255) // 通知提醒
},
// 时间字段
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
},
region: {
type: DataTypes.STRING(100),
},
price: {
type: DataTypes.STRING(10),
allowNull: false
},
quantity: {
type: DataTypes.INTEGER,
allowNull: false
},
grossWeight: {
type: DataTypes.STRING(100),
},
yolk: {
type: DataTypes.STRING(100),
},
specification: {
type: DataTypes.STRING(255),
},
// 联系人信息
product_contact: {
type: DataTypes.STRING(100),
allowNull: true,
comment: '联系人'
},
// 联系人电话信息
contact_phone: {
type: DataTypes.STRING(20),
allowNull: true,
comment: '联系人电话'
},
status: {
type: DataTypes.STRING(20),
defaultValue: 'pending_review',
validate: {
isIn: [['pending_review', 'reviewed', 'published', 'sold_out', 'rejected', 'hidden']]
}
},
rejectReason: {
type: DataTypes.TEXT
},
// 添加图片URL字段
imageUrls: {
type: DataTypes.TEXT,
get() {
const value = this.getDataValue('imageUrls');
return value ? JSON.parse(value) : [];
},
set(value) {
this.setDataValue('imageUrls', JSON.stringify(value));
}
},
// 新增预约相关字段
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
},
grossWeight: {
type: DataTypes.STRING(255)
},
yolk: {
type: DataTypes.STRING(100)
},
price: {
type: DataTypes.STRING(255)
},
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 {
// 重要修复:完全禁用外键约束创建和表结构修改
// 由于Product.sellerId(STRING)和User.id(INTEGER)类型不兼容,我们需要避免Sequelize尝试创建外键约束
// 使用alter: false和hooks: false来完全避免任何表结构修改操作
await sequelize.sync({
force: false, // 不强制重新创建表
alter: false, // 禁用alter操作,避免修改现有表结构
hooks: false, // 禁用所有钩子,防止任何表结构修改尝试
logging: true // 启用同步过程的日志,便于调试
});
console.log('数据库模型同步成功(已禁用外键约束创建)');
} catch (error) {
console.error('数据库模型同步失败:', error);
// 即使同步失败也继续运行,因为我们只需要API功能
console.log('数据库模型同步失败,但服务器继续运行,使用现有表结构');
// 增强的错误处理:如果是外键约束错误,提供更明确的信息
if (error.original && error.original.code === 'ER_FK_INCOMPATIBLE_COLUMNS') {
console.log('提示:外键约束不兼容错误已被忽略,这是预期行为,因为我们使用userId而非id进行关联');
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) {
return res.json({
success: false,
code: 400,
message: '缺少手机号信息',
data: {}
});
}
// 查找用户是否已存在
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,
notice: 'new', // 创建用户时固定设置notice为new
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
// POST方法实现
app.post('/api/wechat/getOpenid', async (req, res) => {
try {
const { code } = req.body;
if (!code) {
return res.json({
success: false,
code: 400,
message: '缺少必要参数code',
data: {}
});
}
// 获取openid和session_key
const wxData = await getSessionKey(code);
if (wxData.errcode) {
return res.json({
success: false,
code: 400,
message: `微信接口错误: ${wxData.errmsg}`,
data: {}
});
}
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: '微信用户', // 临时占位,等待用户授权
phoneNumber: '', // 使用空字符串代替临时手机号,后续由微信小程序拉取的真实手机号更新
type: userType, // 使用客户端传入的类型或默认买家身份
province: '', // 默认空字符串
city: '', // 默认空字符串
district: '', // 默认空字符串
proofurl: '', // 默认空字符串
collaborationid: '', // 默认空字符串
cooperation: '', // 默认空字符串
businesslicenseurl: '', // 默认空字符串
notice: 'new', // 创建用户时固定设置notice为new
created_at: new Date(),
updated_at: new Date()
});
// 为新创建的用户创建关联记录
const newUser = { userId, openid, nickName: '微信用户', phoneNumber: '' };
await createUserAssociations(newUser);
}
// 确保返回的data字段始终存在且不为空
res.json({
success: true,
code: 200,
message: '获取openid成功',
data: {
openid: openid || '',
userId: user ? user.userId : userId,
session_key: session_key || '',
unionid: unionid || ''
}
});
} catch (error) {
console.error('获取openid失败:', error);
// 错误情况下也确保返回data字段
res.status(500).json({
success: false,
code: 500,
message: '获取openid失败',
error: error.message,
data: {}
});
}
});
// 验证用户登录状态
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', '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 = 'shopping' } = req.body;
// 查找用户 - 如果提供了openid,则查找用户信息,否则允许匿名访问
let user = null;
if (openid) { // 只要提供了openid,就查找用户
user = await User.findOne({ where: { openid } });
// 注意:这里不再检查用户是否存在,允许openid无效的情况
if (!user) {
console.log('提供的openid无效,将以匿名方式访问');
}
}
console.log(`当前请求模式: ${viewMode},用户类型: ${user ? user.type : '测试模式'}`);
// 根据viewMode决定是否限制sellerId,无论是否为测试模式
console.log(`处理viewMode: ${viewMode},确保在shopping模式下不添加sellerId过滤`);
// 构建查询条件 - 根据viewMode决定是否包含sellerId
const where = {};
if (viewMode === 'seller') {
if (user) {
// 任何用户(包括测试模式)都只能查看自己的商品
console.log(`货源页面 - 用户 ${user.userId} 只能查看自己的商品,testMode: ${testMode}`);
where.sellerId = user.userId;
} else {
// 没有用户信息的情况
console.log('错误:没有用户信息,严格限制不返回任何商品');
where.sellerId = 'INVALID_USER_ID'; // 确保返回空结果
}
} else if (viewMode === 'shopping') {
// 购物页面:明确不设置sellerId,允许查看所有用户的商品
console.log('购物模式 - 不限制sellerId,允许查看所有用户的商品');
// 不添加任何sellerId过滤条件
}
// 其他模式:不限制sellerId
// 状态筛选 - 直接构建到where对象中,确保不会丢失
console.log(`当前用户类型: ${user ? user.type : '未知'},请求状态: ${status || '未指定'},测试模式: ${testMode}`);
// 初始化status筛选条件,确保总是有有效的状态过滤
let statusCondition = {};
// 如果有指定status参数,按参数筛选但同时排除hidden
if (status) {
console.log(`按状态筛选商品: status=${status},并排除hidden状态`);
if (status === 'all') {
// 特殊情况:请求所有商品但仍然排除hidden
statusCondition = { [Sequelize.Op.not]: 'hidden' };
} else if (Array.isArray(status)) {
// 如果status是数组,确保不包含hidden
const validStatuses = status.filter(s => s !== 'hidden');
if (validStatuses.length > 0) {
statusCondition = { [Sequelize.Op.in]: validStatuses };
} else {
statusCondition = { [Sequelize.Op.not]: 'hidden' };
}
} else {
// 单个状态值,确保不是hidden
if (status !== 'hidden') {
statusCondition = { [Sequelize.Op.eq]: status };
} else {
// 如果明确请求hidden状态,也返回空结果
statusCondition = { [Sequelize.Op.not]: 'hidden' };
}
}
} else {
// 没有指定status参数时 - 直接在where对象中设置状态筛选
if (user && (user.type === 'seller' || user.type === 'both') && viewMode === 'seller' && !testMode) {
// 卖家用户查看自己的商品列表
console.log(`卖家用户 ${user.userId} (类型:${user.type}) 查看自己的所有商品,但排除hidden状态`);
// 卖家可以查看自己的所有商品,但仍然排除hidden状态
statusCondition = { [Sequelize.Op.not]: 'hidden' };
} else {
// 未登录用户、买家用户或购物模式
console.log(`未登录用户、买家或购物模式,使用默认状态筛选: pending_review/reviewed/published`);
// 默认显示审核中、已审核和已发布的商品,排除hidden和sold_out状态
statusCondition = { [Sequelize.Op.in]: ['pending_review', 'reviewed', 'published'] };
}
}
// 确保设置有效的status查询条件
where.status = statusCondition;
console.log(`设置的status查询条件:`, JSON.stringify(statusCondition, null, 2));
console.log(`构建的完整查询条件:`, JSON.stringify(where, null, 2));
// 关键词搜索
if (keyword) {
where.productName = { [Sequelize.Op.like]: `%${keyword}%` };
}
// 计算偏移量
const offset = (page - 1) * pageSize;
// 查询商品列表 - 直接使用Product表中的reservedCount字段
const { count, rows: products } = await Product.findAndCountAll({
where,
include: [
{
model: User,
as: 'seller',
attributes: ['userId', 'nickName', 'avatarUrl']
}
],
attributes: {
include: [
'region' // 【新增】确保返回地区字段
]
},
order: [['created_at', 'DESC']],
limit: pageSize,
offset
});
// 添加详细日志,记录查询结果
console.log(`商品列表查询结果 - 商品数量: ${count}, 商品列表长度: ${products.length}`);
if (products.length > 0) {
console.log(`第一个商品数据:`, JSON.stringify(products[0], null, 2));
}
// 处理商品列表中的grossWeight字段,确保是数字类型,同时反序列化imageUrls
const processedProducts = products.map(product => {
const productJSON = product.toJSON();
// 确保created_at字段存在并转换为ISO字符串格式
if (!productJSON.created_at) {
console.log('商品缺少created_at字段,使用默认值');
productJSON.created_at = new Date().toISOString();
} else {
// 确保created_at是字符串格式
if (productJSON.created_at instanceof Date) {
productJSON.created_at = productJSON.created_at.toISOString();
} else if (typeof productJSON.created_at !== 'string') {
productJSON.created_at = new Date(productJSON.created_at).toISOString();
}
}
// 详细分析毛重字段
const grossWeightDetails = {
type: typeof productJSON.grossWeight,
isEmpty: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined,
isString: typeof productJSON.grossWeight === 'string',
value: productJSON.grossWeight === '' || productJSON.grossWeight === null || productJSON.grossWeight === undefined ? '' : String(productJSON.grossWeight)
};
// 确保grossWeight值是字符串类型
productJSON.grossWeight = String(grossWeightDetails.value);
// 确保reservedCount是数字类型,如果不存在则默认为0
productJSON.reservedCount = typeof productJSON.reservedCount === 'number' ? productJSON.reservedCount : 0;
// 重要修复:反序列化imageUrls字段,确保前端收到的是数组
if (productJSON.imageUrls && typeof productJSON.imageUrls === 'string') {
try {
console.log('【imageUrls修复】尝试反序列化JSON字符串:', productJSON.imageUrls);
// 增强修复:在反序列化前先清理可能有问题的JSON字符串
let cleanJsonStr = productJSON.imageUrls;
// 1. 移除多余的反斜杠
cleanJsonStr = cleanJsonStr.replace(/\\\\/g, '\\');
// 2. 移除可能导致JSON解析错误的字符(如反引号)
cleanJsonStr = cleanJsonStr.replace(/[`]/g, '');
// 3. 尝试反序列化清理后的字符串
const parsedImageUrls = JSON.parse(cleanJsonStr);
if (Array.isArray(parsedImageUrls)) {
// 4. 对数组中的每个URL应用清理函数
productJSON.imageUrls = parsedImageUrls.map(url => {
if (typeof url === 'string') {
// 移除URL中的反斜杠和特殊字符
return url.replace(/\\/g, '').replace(/[`]/g, '').trim();
}
return '';
}).filter(url => url && url.length > 0); // 过滤掉空URL
console.log('【imageUrls修复】反序列化成功,清理后得到数组长度:', productJSON.imageUrls.length);
} else {
console.warn('【imageUrls修复】反序列化结果不是数组,使用空数组');
productJSON.imageUrls = [];
}
} catch (error) {
console.error('【imageUrls修复】反序列化失败:', error);
// 简单处理:直接设置为空数组
productJSON.imageUrls = [];
}
} else if (!Array.isArray(productJSON.imageUrls)) {
console.warn('【imageUrls修复】imageUrls不是数组,使用空数组');
productJSON.imageUrls = [];
}
// 记录第一个商品的转换信息用于调试
if (products.indexOf(product) === 0) {
console.log('商品列表 - 第一个商品毛重字段处理:');
console.log('- 原始值:', grossWeightDetails.value, '类型:', grossWeightDetails.type);
console.log('- 转换后的值:', productJSON.grossWeight, '类型:', typeof productJSON.grossWeight);
console.log('- reservedCount值:', productJSON.reservedCount, '类型:', typeof productJSON.reservedCount);
}
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
});
}
});
// 上传商品 - 支持图片上传到阿里云OSS,修复支持多文件同时上传
app.post('/api/products/upload', upload.array('images', 10), async (req, res) => {
// 【全局保护】提前检测是否是仅图片上传的请求
const isAddImagesOnly = req.body.action === 'add_images_only' || req.body.isUpdate === 'true';
const existingProductId = req.body.productId;
if (isAddImagesOnly && existingProductId) {
console.log('【提前路由】检测到仅图片上传请求,直接处理');
return await handleAddImagesToExistingProduct(req, res, existingProductId, req.files || []);
}
let productData;
let uploadedFiles = req.files || []; // 修复:使用req.files而不是空数组
let imageUrls = [];
let product;
let tempFilesToClean = []; // 存储需要清理的临时文件路径
try {
// 【关键修复】首先检查是否是仅上传图片到已存在商品的请求
const isAddImagesOnly = req.body.action === 'add_images_only' || req.body.isUpdate === 'true';
const existingProductId = req.body.productId;
if (isAddImagesOnly && existingProductId) {
console.log('【图片更新模式】仅添加图片到已存在商品,商品ID:', existingProductId);
return await handleAddImagesToExistingProduct(req, res, existingProductId, uploadedFiles);
}
// 【关键修复】安全解析 productData - 修复 undefined 错误
try {
// 检查 productData 是否存在且不是字符串 'undefined'
if (req.body.productData && req.body.productData !== 'undefined' && req.body.productData !== undefined) {
productData = JSON.parse(req.body.productData);
console.log('成功解析 productData:', productData);
} else {
console.log('【关键修复】productData 不存在或为 undefined,使用 req.body');
productData = req.body;
// 对于仅图片上传的情况,需要特别处理
if (req.body.action === 'add_images_only' && req.body.productId) {
console.log('【图片上传模式】检测到仅图片上传请求,构建基础 productData');
productData = {
productId: req.body.productId,
sellerId: req.body.openid,
action: 'add_images_only'
};
}
}
} catch (e) {
console.error('解析 productData 失败,使用 req.body:', e.message);
productData = req.body;
}
// ========== 【新增】详细的地区字段调试信息 ==========
console.log('【地区字段调试】开始处理地区字段');
console.log('【地区字段调试】原始productData.region:', productData.region, '类型:', typeof productData.region);
console.log('【地区字段调试】原始请求体中的region字段:', req.body.region);
// 【新增】处理地区字段 - 增强版
if (productData.region) {
console.log('【地区字段调试】检测到地区字段:', productData.region, '类型:', typeof productData.region);
// 确保地区字段是字符串类型
if (typeof productData.region !== 'string') {
console.log('【地区字段调试】地区字段不是字符串,转换为字符串:', String(productData.region));
productData.region = String(productData.region);
}
console.log('【地区字段调试】处理后的地区字段:', productData.region, '类型:', typeof productData.region);
} else {
console.log('【地区字段调试】未检测到地区字段,设置为默认值或空');
productData.region = productData.region || ''; // 确保有默认值
console.log('【地区字段调试】设置默认值后的地区字段:', productData.region);
}
// 检查是否从其他来源传递了地区信息
if (req.body.region && !productData.region) {
console.log('【地区字段调试】从请求体中发现地区字段:', req.body.region);
productData.region = req.body.region;
}
console.log('【地区字段调试】最终确定的地区字段:', productData.region);
// ========== 地区字段调试结束 ==========
console.log('收到商品上传请求,处理后的 productData:', productData);
// 检查是否是简化上传模式(单步创建)
const isNewProduct = productData.isNewProduct === true;
console.log('是否为新商品创建:', isNewProduct);
// 改进的毛重字段处理逻辑,与编辑API保持一致
// 详细分析毛重字段
const grossWeightDetails = {
value: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined ? '' : String(productData.grossWeight),
type: typeof productData.grossWeight,
isEmpty: productData.grossWeight === '' || productData.grossWeight === null || productData.grossWeight === undefined,
isString: typeof productData.grossWeight === 'string'
};
// 详细的日志记录
console.log('上传商品 - 毛重字段详细分析:');
console.log('- 原始值:', productData.grossWeight, '类型:', typeof productData.grossWeight);
console.log('- 是否为空值:', grossWeightDetails.isEmpty);
console.log('- 是否为字符串类型:', grossWeightDetails.isString);
console.log('- 转换后的值:', grossWeightDetails.value, '类型:', typeof grossWeightDetails.value);
// 确保grossWeight值是字符串类型,直接使用处理后的值
productData.grossWeight = grossWeightDetails.value;
console.log('上传商品 - 最终存储的毛重值:', productData.grossWeight, '类型:', typeof productData.grossWeight);
// 验证必要字段
if (!productData.sellerId || !productData.productName || !productData.price || !productData.quantity) {
return res.status(400).json({
success: false,
code: 400,
message: '缺少必要的商品信息'
});
}
// 处理图片上传逻辑 - 使用批量上传确保多图片上传成功
try {
console.log('===== 图片处理逻辑 =====');
console.log('- 上传文件数量:', uploadedFiles.length);
console.log('- 是否有productData:', Boolean(productData));
// 首先处理所有上传的文件
if (uploadedFiles.length > 0) {
console.log('处理上传文件...');
// 创建已上传URL集合,用于避免重复
const uploadedFileUrls = new Set();
// 准备文件路径数组
const filePaths = uploadedFiles.map(file => file.path);
// 使用商品名称作为文件夹名,确保每个商品的图片独立存储
// 移除商品名称中的特殊字符,确保可以作为合法的文件夹名
const safeProductName = productData.productName
.replace(/[\/:*?"<>|]/g, '_') // 移除不合法的文件名字符
.substring(0, 50); // 限制长度
// 构建基础文件夹路径
const folderPath = `products/${safeProductName}`;
console.log(`准备批量上传到文件夹: ${folderPath}`);
// 创建自定义的文件上传函数,添加详细错误处理和连接测试
async function customUploadFile(filePath, folder) {
try {
console.log(`===== customUploadFile 开始 =====`);
console.log(`文件路径: ${filePath}`);
console.log(`目标文件夹: ${folder}`);
// 引入必要的模块
const path = require('path');
const fs = require('fs');
const { createHash } = require('crypto');
// 确保文件存在
const fileExists = await fs.promises.access(filePath).then(() => true).catch(() => false);
if (!fileExists) {
throw new Error(`文件不存在: ${filePath}`);
}
console.log('文件存在性检查通过');
// 获取文件信息
const stats = await fs.promises.stat(filePath);
console.log(`文件大小: ${stats.size} 字节, 创建时间: ${stats.birthtime}`);
// 获取文件扩展名
const extname = path.extname(filePath).toLowerCase();
if (!extname) {
throw new Error(`无法获取文件扩展名: ${filePath}`);
}
console.log(`文件扩展名: ${extname}`);
// 基于文件内容计算MD5哈希值,实现文件级去重
console.log('开始计算文件MD5哈希值...');
const hash = createHash('md5');
const stream = fs.createReadStream(filePath);
await new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => resolve());
});
const fileHash = hash.digest('hex');
console.log(`文件哈希值计算完成: ${fileHash}`);
// 构建OSS文件路径
const uniqueFilename = `${fileHash}${extname}`;
const ossFilePath = `${folder}/${uniqueFilename}`;
console.log(`准备上传到OSS路径: ${ossFilePath}`);
// 直接创建OSS客户端
console.log('正在直接创建OSS客户端...');
const OSS = require('ali-oss');
const ossConfig = require('./oss-config');
// 打印OSS配置(敏感信息隐藏)
console.log('OSS配置信息:');
console.log(`- region: ${ossConfig.region}`);
console.log(`- bucket: ${ossConfig.bucket}`);
console.log(`- accessKeyId: ${ossConfig.accessKeyId ? '已配置' : '未配置'}`);
console.log(`- accessKeySecret: ${ossConfig.accessKeySecret ? '已配置' : '未配置'}`);
// 使用配置创建OSS客户端
const ossClient = new OSS({
region: ossConfig.region,
accessKeyId: ossConfig.accessKeyId,
accessKeySecret: ossConfig.accessKeySecret,
bucket: ossConfig.bucket
});
console.log('OSS客户端创建成功');
// 测试OSS连接
console.log('正在测试OSS连接...');
try {
await ossClient.list({ max: 1 });
console.log('OSS连接测试成功');
} catch (connectionError) {
console.error('OSS连接测试失败:', connectionError.message);
throw new Error(`OSS连接失败,请检查配置和网络: ${connectionError.message}`);
}
// 上传文件,明确设置为公共读权限
console.log(`开始上传文件到OSS...`);
console.log(`上传参数: { filePath: ${ossFilePath}, localPath: ${filePath} }`);
// 添加超时控制
const uploadPromise = ossClient.put(ossFilePath, filePath, {
headers: {
'x-oss-object-acl': 'public-read'
},
acl: 'public-read'
});
// 设置30秒超时
const result = await Promise.race([
uploadPromise,
new Promise((_, reject) => setTimeout(() => reject(new Error('上传超时')), 30000))
]);
console.log(`文件上传成功!`);
console.log(`- OSS响应:`, JSON.stringify(result));
console.log(`- 返回URL: ${result.url}`);
// 验证URL
if (!result.url) {
throw new Error('上传成功但未返回有效URL');
}
console.log(`===== customUploadFile 成功完成 =====`);
return result.url;
} catch (error) {
console.error('文件上传失败:', error.message);
console.error('错误类型:', error.name);
console.error('错误详情:', error);
console.error('错误堆栈:', error.stack);
throw new Error(`文件上传到OSS失败: ${error.message}`);
}
}
// 改进的上传逻辑:使用逐个上传,添加更详细的日志和错误处理
console.log('开始逐个上传文件,数量:', filePaths.length);
let uploadResults = [];
for (let i = 0; i < filePaths.length; i++) {
console.log(`=== 开始处理文件 ${i + 1}/${filePaths.length} ===`);
console.log(`文件路径: ${filePaths[i]}`);
try {
// 检查文件是否存在并可访问
const fs = require('fs');
if (!fs.existsSync(filePaths[i])) {
throw new Error(`文件不存在或无法访问: ${filePaths[i]}`);
}
const stats = fs.statSync(filePaths[i]);
console.log(`文件大小: ${stats.size} 字节`);
console.log(`调用customUploadFile上传文件...`);
const uploadedUrl = await customUploadFile(filePaths[i], folderPath);
console.log(`文件 ${i + 1} 上传成功: ${uploadedUrl}`);
uploadResults.push({ fileIndex: i, success: true, url: uploadedUrl });
if (uploadedUrl && !uploadedFileUrls.has(uploadedUrl)) {
imageUrls.push(uploadedUrl);
uploadedFileUrls.add(uploadedUrl);
console.log(`已添加URL到结果数组,当前总数量: ${imageUrls.length}`);
} else if (uploadedFileUrls.has(uploadedUrl)) {
console.log(`文件 ${i + 1} 的URL已存在,跳过重复添加: ${uploadedUrl}`);
} else {
console.error(`文件 ${i + 1} 上传成功但返回的URL为空或无效`);
}
} catch (singleError) {
console.error(`文件 ${i + 1} 上传失败:`);
console.error(`错误信息:`, singleError.message);
console.error(`失败文件路径: ${filePaths[i]}`);
console.error(`错误堆栈:`, singleError.stack);
uploadResults.push({ fileIndex: i, success: false, error: singleError.message });
// 继续上传下一个文件,不中断整个流程
}
console.log(`=== 文件 ${i + 1}/${filePaths.length} 处理完成 ===\n`);
}
console.log(`文件上传处理完成,成功上传${imageUrls.length}/${filePaths.length}个文件`);
console.log(`上传详细结果:`, JSON.stringify(uploadResults, null, 2));
}
// 处理productData中的imageUrls,但需要避免重复添加
// 注意:我们只处理不在已上传文件URL中的图片
if (productData && productData.imageUrls && Array.isArray(productData.imageUrls)) {
console.log('处理productData中的imageUrls,避免重复');
// 创建已上传文件URL的集合,包含已经通过文件上传的URL
const uploadedFileUrls = new Set(imageUrls);
productData.imageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
const trimmedUrl = url.trim();
// 只有当这个URL还没有被添加时才添加它
if (!uploadedFileUrls.has(trimmedUrl)) {
imageUrls.push(trimmedUrl);
uploadedFileUrls.add(trimmedUrl);
}
}
});
}
console.log('最终收集到的图片URL数量:', imageUrls.length);
// 确保imageUrls是数组类型
if (!Array.isArray(imageUrls)) {
imageUrls = [];
console.log('警告: imageUrls不是数组,已重置为空数组');
}
} catch (uploadError) {
console.error('图片处理失败:', uploadError);
// 清理临时文件
cleanTempFiles(tempFilesToClean);
// 如果至少有一张图片上传成功,我们仍然可以继续创建商品
if (imageUrls.length > 0) {
console.log(`部分图片上传成功,共${imageUrls.length}张,继续创建商品`);
// 继续执行,不返回错误
} else {
// 如果所有图片都上传失败,才返回错误
return res.status(500).json({
success: false,
code: 500,
message: '图片上传失败',
error: uploadError.message
});
}
}
// 【关键修复】增强图片URL收集逻辑 - 从所有可能的来源收集图片URL
// 创建一个统一的URL集合,用于去重
const allImageUrlsSet = new Set(imageUrls);
// 【新增】检查是否是递归上传的一部分,并获取previousImageUrls
if (productData && productData.previousImageUrls && Array.isArray(productData.previousImageUrls)) {
console.log('【关键修复】检测到previousImageUrls,数量:', productData.previousImageUrls.length);
productData.previousImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allImageUrlsSet.add(url.trim());
console.log('【关键修复】从previousImageUrls添加URL:', url.trim());
}
});
}
// 1. 处理additionalImageUrls
if (req.body.additionalImageUrls) {
try {
let additionalUrls = [];
if (typeof req.body.additionalImageUrls === 'string') {
// 尝试解析JSON字符串
try {
additionalUrls = JSON.parse(req.body.additionalImageUrls);
} catch (jsonError) {
// 如果解析失败,检查是否是单个URL字符串
if (req.body.additionalImageUrls.trim() !== '') {
additionalUrls = [req.body.additionalImageUrls.trim()];
}
}
} else {
additionalUrls = req.body.additionalImageUrls;
}
if (Array.isArray(additionalUrls) && additionalUrls.length > 0) {
console.log('【关键修复】添加额外的图片URL,数量:', additionalUrls.length);
// 添加到统一集合
additionalUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allImageUrlsSet.add(url.trim());
}
});
}
} catch (error) {
console.error('处理additionalImageUrls时出错:', error);
}
}
// 2. 处理uploadedImageUrls
if (req.body.uploadedImageUrls) {
try {
let uploadedUrls = [];
if (typeof req.body.uploadedImageUrls === 'string') {
// 尝试解析JSON字符串
try {
uploadedUrls = JSON.parse(req.body.uploadedImageUrls);
} catch (jsonError) {
// 如果解析失败,检查是否是单个URL字符串
if (req.body.uploadedImageUrls.trim() !== '') {
uploadedUrls = [req.body.uploadedImageUrls.trim()];
}
}
} else {
uploadedUrls = req.body.uploadedImageUrls;
}
if (Array.isArray(uploadedUrls) && uploadedUrls.length > 0) {
console.log('【关键修复】检测到已上传的图片URLs,数量:', uploadedUrls.length);
// 添加到统一集合
uploadedUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allImageUrlsSet.add(url.trim());
}
});
}
} catch (error) {
console.error('处理uploadedImageUrls时出错:', error);
}
}
// 3. 处理allImageUrls
if (req.body.allImageUrls) {
try {
let allUrls = [];
if (typeof req.body.allImageUrls === 'string') {
try {
allUrls = JSON.parse(req.body.allImageUrls);
} catch (jsonError) {
if (req.body.allImageUrls.trim() !== '') {
allUrls = [req.body.allImageUrls.trim()];
}
}
} else {
allUrls = req.body.allImageUrls;
}
if (Array.isArray(allUrls) && allUrls.length > 0) {
console.log('【关键修复】处理allImageUrls,数量:', allUrls.length);
allUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allImageUrlsSet.add(url.trim());
}
});
}
} catch (error) {
console.error('处理allImageUrls时出错:', error);
}
}
// 4. 从productData中提取imageUrls
if (productData && (productData.imageUrls || productData.images || productData.allImageUrls)) {
const productImageUrls = [];
if (productData.imageUrls) {
if (Array.isArray(productData.imageUrls)) {
productImageUrls.push(...productData.imageUrls);
} else if (typeof productData.imageUrls === 'string') {
try {
const parsed = JSON.parse(productData.imageUrls);
if (Array.isArray(parsed)) {
productImageUrls.push(...parsed);
} else {
productImageUrls.push(productData.imageUrls);
}
} catch (e) {
productImageUrls.push(productData.imageUrls);
}
}
}
if (productData.images) {
if (Array.isArray(productData.images)) {
productImageUrls.push(...productData.images);
} else if (typeof productData.images === 'string') {
try {
const parsed = JSON.parse(productData.images);
if (Array.isArray(parsed)) {
productImageUrls.push(...parsed);
} else {
productImageUrls.push(productData.images);
}
} catch (e) {
productImageUrls.push(productData.images);
}
}
}
if (productData.allImageUrls) {
if (Array.isArray(productData.allImageUrls)) {
productImageUrls.push(...productData.allImageUrls);
} else if (typeof productData.allImageUrls === 'string') {
try {
const parsed = JSON.parse(productData.allImageUrls);
if (Array.isArray(parsed)) {
productImageUrls.push(...parsed);
} else {
productImageUrls.push(productData.allImageUrls);
}
} catch (e) {
productImageUrls.push(productData.allImageUrls);
}
}
}
if (productImageUrls.length > 0) {
console.log('【关键修复】从productData中提取图片URLs,数量:', productImageUrls.length);
productImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allImageUrlsSet.add(url.trim());
}
});
}
}
// 增强处理:添加清理和标准化函数,移除反引号、多余空格、多余反斜杠和所有可能导致JSON解析错误的字符
function cleanAndStandardizeUrl(url) {
if (!url || typeof url !== 'string') return '';
// 1. 移除所有反斜杠(防止JSON解析错误)
// 2. 移除反引号和多余空格
// 3. 确保URL格式正确
return url
.replace(/\\/g, '') // 移除所有反斜杠
.replace(/[`\s]/g, '') // 移除反引号和空格
.trim(); // 清理前后空白
}
// 将图片URL添加到商品数据中
productData.imageUrls = Array.from(allImageUrlsSet)
.map(cleanAndStandardizeUrl) // 清理每个URL
.filter(url => url && url.trim() !== '');
console.log('【调试5】添加到商品数据前imageUrls长度:', productData.imageUrls.length);
console.log('【调试6】添加到商品数据前imageUrls数据:', JSON.stringify(productData.imageUrls));
console.log('【调试7】第二层去重后productData.imageUrls长度:', productData.imageUrls.length);
console.log('【调试8】第二层去重后productData.imageUrls数据:', JSON.stringify(productData.imageUrls));
console.log('【调试8.1】去重差异检测:', imageUrls.length - productData.imageUrls.length, '个重复URL被移除');
console.log('商品数据中最终的图片URL数量:', productData.imageUrls.length);
console.log('商品数据中最终的图片URL列表:', productData.imageUrls);
// 检查sellerId是否为openid,如果是则查找对应的userId
let actualSellerId = productData.sellerId;
// 【测试模式】如果是测试环境或者明确指定了测试sellerId,跳过验证
const isTestMode = productData.sellerId === 'test_seller_openid' || process.env.NODE_ENV === 'test';
if (isTestMode) {
console.log('测试模式:跳过sellerId验证');
actualSellerId = 'test_user_id'; // 使用测试userId
} else {
// 如果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}`);
// 清理临时文件
cleanTempFiles(tempFilesToClean);
return res.status(401).json({
success: false,
code: 401,
message: '找不到对应的用户记录,请重新登录',
needRelogin: true // 添加重新登录标志,前端检测到这个标志时弹出登录窗口
});
}
}
}
// 【关键修复】增强递归上传支持 - 更可靠的会话ID和商品匹配
// 检查是否是递归上传的一部分,增加更多判断条件
const isSingleUpload = req.body.isSingleUpload === 'true';
const isRecursiveUpload = req.body.isRecursiveUpload === 'true';
const uploadIndex = parseInt(req.body.uploadIndex || req.body.currentImageIndex) || 0;
const totalImages = parseInt(req.body.totalImages || req.body.totalImageCount) || 1;
let hasMultipleImages = req.body.hasMultipleImages === 'true';
const totalImageCount = parseInt(req.body.totalImageCount || req.body.totalImages) || 1;
// 【关键修复】增加明确的多图片标记
const isMultiImageUpload = totalImageCount > 1 || req.body.hasMultipleImages === 'true';
console.log(`【递归上传信息】isSingleUpload=${isSingleUpload}, isRecursiveUpload=${isRecursiveUpload}`);
console.log(`【递归上传信息】uploadIndex=${uploadIndex}, totalImages=${totalImages}, totalImageCount=${totalImageCount}`);
console.log(`【递归上传信息】hasMultipleImages=${hasMultipleImages}, isMultiImageUpload=${isMultiImageUpload}`);
// 【重要修复】确保hasMultipleImages被正确识别
if (totalImageCount > 1) {
console.log('【重要】强制设置hasMultipleImages为true,因为总图片数量大于1');
hasMultipleImages = true;
}
// 【关键修复】增强的会话ID处理,优先使用前端预生成的会话ID
// 从多个来源获取会话ID,提高可靠性
let sessionId = req.body.sessionId || req.body.productId || req.body.uploadSessionId || productData.sessionId || productData.productId;
// 【重要修复】如果是多图上传,强制确保有会话ID
if (isMultiImageUpload && (!sessionId || !sessionId.startsWith('session_'))) {
// 生成新的会话ID
sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
console.log(`【会话ID修复】为多图上传生成新的会话ID: ${sessionId}`);
}
console.log(`【会话跟踪】最终使用的会话ID: ${sessionId || '无'}`);
// 如果是递归上传,尝试从数据库中查找匹配的临时ID或productId
let existingProduct = null;
let productId = null;
// 【关键修复】增强的商品查找逻辑,优先查找已存在的商品记录
if ((isSingleUpload || isRecursiveUpload || isMultiImageUpload) && sessionId) {
// 如果是递归上传且有会话ID,尝试查找已存在的商品
try {
console.log(`【商品查找】尝试查找已存在的商品记录,会话ID: ${sessionId}`);
// 【重要修复】同时匹配productId和sessionId字段
existingProduct = await Product.findOne({
where: {
[Op.or]: [
{ productId: sessionId },
{ sessionId: sessionId },
{ uploadSessionId: sessionId }
],
sellerId: actualSellerId
}
});
if (existingProduct) {
console.log(`【商品查找】找到已存在的商品记录,将更新而非创建新商品`);
productId = sessionId;
} else {
// 如果精确匹配失败,尝试查找该用户最近创建的商品(可能会话ID不匹配但需要关联)
console.log(`【商品查找】精确匹配失败,尝试查找该用户最近创建的商品`);
const recentProducts = await Product.findAll({
where: {
sellerId: actualSellerId,
// 查找最近5分钟内创建的商品
created_at: {
[Op.gt]: new Date(Date.now() - 5 * 60 * 1000)
}
},
order: [['created_at', 'DESC']],
limit: 3
});
if (recentProducts && recentProducts.length > 0) {
// 优先选择最近创建的商品
existingProduct = recentProducts[0];
productId = existingProduct.productId;
console.log(`【商品查找】找到用户最近创建的商品,productId: ${productId}`);
}
}
} catch (error) {
console.error(`【商品查找错误】查找已存在商品时出错:`, error);
}
}
// 如果没有找到已存在的商品或没有会话ID,生成新的商品ID
if (!productId) {
productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
console.log(`生成新的商品ID: ${productId}`);
}
// 创建商品,使用实际的sellerId,并确保有默认状态
// 再次确认图片URLs没有重复(第三层去重)- 添加URL指纹比对
console.log('【调试9】创建商品前productData.imageUrls数据:', JSON.stringify(productData.imageUrls));
console.log('【调试10】创建商品前productData.imageUrls长度:', productData.imageUrls.length);
// 【关键修复】重写图片URL合并逻辑,确保递归上传时正确累积所有图片
// 使用已声明的allImageUrlsSet来存储所有图片URL,自动去重
// 【重要】优先处理现有商品中的图片(递归上传的累积基础)
if (existingProduct && existingProduct.imageUrls) {
try {
let existingUrls = [];
if (typeof existingProduct.imageUrls === 'string') {
existingUrls = JSON.parse(existingProduct.imageUrls);
} else if (Array.isArray(existingProduct.imageUrls)) {
existingUrls = existingProduct.imageUrls;
}
if (Array.isArray(existingUrls)) {
existingUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】从现有商品记录获取已保存图片URLs,有效数量:', existingUrls.filter(Boolean).length);
}
} catch (e) {
console.error('解析现有商品图片URLs出错:', e);
}
}
console.log('【递归上传关键】现有商品图片URL数量:', allImageUrlsSet.size);
// 【关键修复】处理当前上传的图片文件
if (imageUrls && Array.isArray(imageUrls)) {
imageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】添加当前上传的图片URLs,数量:', imageUrls.length);
}
// 【关键修复】处理previousImageUrls(前端传递的已上传图片列表)
if (productData.previousImageUrls && Array.isArray(productData.previousImageUrls)) {
productData.previousImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】处理previousImageUrls,数量:', productData.previousImageUrls.length);
}
// 【关键修复】处理additionalImageUrls
if (req.body.additionalImageUrls) {
try {
const additionalUrls = JSON.parse(req.body.additionalImageUrls);
if (Array.isArray(additionalUrls)) {
additionalUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】处理additionalImageUrls,数量:', additionalUrls.length);
}
} catch (e) {
console.error('解析additionalImageUrls出错:', e);
}
}
// 【关键修复】处理uploadedImageUrls
if (req.body.uploadedImageUrls) {
try {
const uploadedUrls = JSON.parse(req.body.uploadedImageUrls);
if (Array.isArray(uploadedUrls)) {
uploadedUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】处理uploadedImageUrls,数量:', uploadedUrls.length);
}
} catch (e) {
console.error('解析uploadedImageUrls出错:', e);
}
}
// 【关键修复】处理productData中的imageUrls
if (productData.imageUrls && Array.isArray(productData.imageUrls)) {
productData.imageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim()) {
allImageUrlsSet.add(cleanAndStandardizeUrl(url));
}
});
console.log('【关键修复】处理productData.imageUrls,数量:', productData.imageUrls.length);
}
// 转换为数组
const combinedImageUrls = Array.from(allImageUrlsSet);
console.log('【关键修复】合并所有来源的图片URLs后,总数量:', combinedImageUrls.length);
console.log('【递归上传关键】当前累积的图片URLs:', JSON.stringify(combinedImageUrls));
// 【关键修复】由于已经使用Set去重,这里简化处理,只做清理和过滤
const finalImageUrls = combinedImageUrls
.filter(url => url && typeof url === 'string' && url.trim() !== ''); // 过滤空值
// 【关键修复】确保至少保存第一张图片,即使检测到重复
if (finalImageUrls.length === 0 && combinedImageUrls.length > 0) {
console.log('【重要警告】所有URL都被标记为重复,但至少保留第一张图片');
const firstValidUrl = combinedImageUrls
.map(cleanAndStandardizeUrl)
.find(url => url && url.trim() !== '');
if (firstValidUrl) {
finalImageUrls.push(firstValidUrl);
console.log('已保留第一张有效图片URL:', firstValidUrl);
}
}
console.log('【调试11】第三层去重后finalImageUrls长度:', finalImageUrls.length);
console.log('【调试12】第三层去重后finalImageUrls列表:', JSON.stringify(finalImageUrls));
console.log('【调试12.1】去重差异检测:', productData.imageUrls.length - finalImageUrls.length, '个重复URL被移除');
console.log('创建商品前最终去重后的图片URL数量:', finalImageUrls.length);
// 【关键修复】添加调试信息,确保finalImageUrls是正确的数组
console.log('【关键调试】finalImageUrls类型:', typeof finalImageUrls);
console.log('【关键调试】finalImageUrls是否为数组:', Array.isArray(finalImageUrls));
console.log('【关键调试】finalImageUrls长度:', finalImageUrls.length);
console.log('【关键调试】finalImageUrls内容:', JSON.stringify(finalImageUrls));
// 确保imageUrls在存储前正确序列化为JSON字符串
console.log('【关键修复】将imageUrls数组序列化为JSON字符串存储到数据库');
console.log('【递归上传关键】最终要存储的图片URL数量:', finalImageUrls.length);
console.log('【递归上传关键】最终要存储的图片URL列表:', JSON.stringify(finalImageUrls));
// 【重要修复】支持更新已存在的商品
let productToSave;
let isUpdate = false;
// ========== 【新增】创建商品前的地区字段详细调试 ==========
console.log('【地区字段调试】创建商品前 - 检查地区字段状态');
console.log('【地区字段调试】productData.region:', productData.region, '类型:', typeof productData.region);
console.log('【地区字段调试】existingProduct:', existingProduct ? '存在' : '不存在');
if (existingProduct) {
console.log('【地区字段调试】existingProduct.region:', existingProduct.region, '类型:', typeof existingProduct.region);
}
// ========== 地区字段调试结束 ==========
if (existingProduct) {
// 更新已存在的商品
isUpdate = true;
// 合并现有图片URL和新的图片URL
let existingImageUrls = [];
try {
if (existingProduct.imageUrls) {
const imageUrlsValue = existingProduct.imageUrls;
// 关键修复:防御性检查,避免解析 undefined
if (imageUrlsValue && imageUrlsValue !== 'undefined' && imageUrlsValue !== undefined) {
if (typeof imageUrlsValue === 'string') {
existingImageUrls = JSON.parse(imageUrlsValue);
} else if (Array.isArray(imageUrlsValue)) {
existingImageUrls = imageUrlsValue;
}
// 确保是数组
if (!Array.isArray(existingImageUrls)) {
existingImageUrls = existingImageUrls ? [existingImageUrls] : [];
}
}
}
} catch (e) {
console.error('解析现有商品图片URL失败:', e);
existingImageUrls = [];
}
// 额外的安全检查
if (!Array.isArray(existingImageUrls)) {
console.warn('existingImageUrls 不是数组,重置为空数组');
existingImageUrls = [];
}
// 【关键修复】增强的图片URL合并逻辑,确保保留所有图片
// 先创建一个Set包含所有来源的图片URL
const allUrlsSet = new Set();
// 添加现有商品的图片URL
existingImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allUrlsSet.add(url.trim());
}
});
// 添加当前上传的图片URL
finalImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allUrlsSet.add(url.trim());
}
});
// 【重要】尝试从请求参数中获取更多图片URL
const additionalUrlsSources = [
req.body.additionalImageUrls,
req.body.uploadedImageUrls,
req.body.allImageUrls,
productData.previousImageUrls,
productData.imageUrls,
productData.allImageUrls
];
additionalUrlsSources.forEach(source => {
if (source) {
try {
let urls = [];
if (typeof source === 'string') {
try {
urls = JSON.parse(source);
} catch (e) {
if (source.trim() !== '') {
urls = [source.trim()];
}
}
} else if (Array.isArray(source)) {
urls = source;
}
if (Array.isArray(urls)) {
urls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allUrlsSet.add(url.trim());
}
});
}
} catch (e) {
console.error('处理额外URL源时出错:', e);
}
}
});
const mergedImageUrls = Array.from(allUrlsSet);
console.log(`【会话合并】合并现有图片(${existingImageUrls.length})和新图片(${finalImageUrls.length}),总数: ${mergedImageUrls.length}`);
console.log(`【会话合并】合并后的图片列表:`, JSON.stringify(mergedImageUrls));
console.log(`【会话合并】会话ID: ${sessionId}`);
// ========== 【新增】更新商品时的地区字段调试 ==========
console.log('【地区字段调试】更新商品 - 准备合并地区字段');
console.log('【地区字段调试】productData.region:', productData.region);
console.log('【地区字段调试】existingProduct.region:', existingProduct.region);
const finalRegion = productData.region || existingProduct.region || '';
console.log('【地区字段调试】最终确定的地区字段:', finalRegion);
// ========== 地区字段调试结束 ==========
productToSave = {
...existingProduct.dataValues,
...productData,
imageUrls: JSON.stringify(mergedImageUrls),
allImageUrls: JSON.stringify(mergedImageUrls), // 额外保存一份,增强兼容性
sellerId: actualSellerId,
status: productData.status || existingProduct.status || 'pending_review',
region: finalRegion, // 使用调试确定的地区字段
updated_at: new Date(),
// 【重要修复】确保保存会话ID
sessionId: sessionId || existingProduct.dataValues.sessionId,
uploadSessionId: sessionId || existingProduct.dataValues.uploadSessionId,
// 标记多图片
hasMultipleImages: mergedImageUrls.length > 1,
totalImages: mergedImageUrls.length,
// 确保保留原始的创建时间
created_at: existingProduct.dataValues.created_at || new Date()
};
// ========== 【新增】保存前的地区字段验证 ==========
console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof productToSave.region);
// ========== 地区字段调试结束 ==========
console.log('【关键调试】即将更新的商品数据:', {
...productToSave,
imageUrls: 'JSON字符串长度: ' + productToSave.imageUrls.length,
sellerId: '已处理(隐藏具体ID)',
region: productToSave.region // 特别显示地区字段
});
} else {
// 创建新商品
// ========== 【新增】创建新商品时的地区字段调试 ==========
console.log('【地区字段调试】创建新商品 - 准备设置地区字段');
console.log('【地区字段调试】productData.region:', productData.region);
const finalRegion = productData.region || '';
console.log('【地区字段调试】最终确定的地区字段:', finalRegion);
// ========== 地区字段调试结束 ==========
productToSave = {
...productData,
imageUrls: JSON.stringify(finalImageUrls), // 关键修复:序列化为JSON字符串
allImageUrls: JSON.stringify(finalImageUrls), // 额外保存一份,增强兼容性
sellerId: actualSellerId, // 使用查找到的userId或原始openid
status: productData.status || 'pending_review', // 确保有默认状态为pending_review
region: finalRegion, // 使用调试确定的地区字段
productId,
// 【重要修复】确保保存会话ID
sessionId: sessionId,
uploadSessionId: sessionId,
// 标记多图片
hasMultipleImages: finalImageUrls.length > 1,
totalImages: finalImageUrls.length,
created_at: new Date(),
updated_at: new Date()
};
// ========== 【新增】保存前的地区字段验证 ==========
console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof productToSave.region);
// ========== 地区字段调试结束 ==========
// 记录要创建的商品数据(过滤敏感信息)
console.log('【关键调试】即将创建的商品数据:', {
...productToSave,
imageUrls: 'JSON字符串长度: ' + productToSave.imageUrls.length,
sellerId: '已处理(隐藏具体ID)',
region: productToSave.region // 特别显示地区字段
});
}
// 根据是否是更新操作执行不同的数据库操作
if (isUpdate) {
console.log(`【会话更新】执行商品更新,productId: ${productId}`);
// 确保imageUrls是正确的JSON字符串
if (!productToSave.imageUrls || typeof productToSave.imageUrls !== 'string') {
console.error('【严重错误】imageUrls不是字符串格式,重新序列化');
productToSave.imageUrls = JSON.stringify(finalImageUrls);
}
// ========== 【新增】数据库操作前的地区字段最终检查 ==========
console.log('【地区字段调试】数据库更新前最终检查 - region:', productToSave.region);
// ========== 地区字段调试结束 ==========
product = await Product.update(productToSave, {
where: {
productId: productId
}
});
// 查询更新后的商品完整信息
product = await Product.findOne({
where: {
productId: productId
}
});
console.log(`【会话更新】商品更新成功,productId: ${productId}`);
} else {
console.log(`【会话创建】执行商品创建,productId: ${productId}`);
// 确保imageUrls是正确的JSON字符串
if (!productToSave.imageUrls || typeof productToSave.imageUrls !== 'string') {
console.error('【严重错误】imageUrls不是字符串格式,重新序列化');
productToSave.imageUrls = JSON.stringify(finalImageUrls);
}
// ========== 【新增】数据库创建前的地区字段最终检查 ==========
console.log('【地区字段调试】数据库创建前最终检查 - region:', productToSave.region);
// ========== 地区字段调试结束 ==========
product = await Product.create(productToSave);
console.log(`【会话创建】商品创建成功,productId: ${productId}`);
}
// ========== 【新增】数据库操作后的地区字段验证 ==========
console.log('【地区字段调试】数据库操作后验证');
if (product) {
console.log('【地区字段调试】从数据库返回的商品地区字段:', product.region, '类型:', typeof product.region);
} else {
console.error('【地区字段调试】数据库操作后商品对象为空');
}
// ========== 地区字段调试结束 ==========
console.log('【成功】商品操作完成,productId:', product.productId);
// 【关键修复】确保返回给前端的响应包含完整的图片URL列表
// 从数据库中解析出图片URLs数组
// 使用let来允许重新赋值
let dbResponseImageUrls = [];
try {
// 【重要修复】首先尝试从数据库中获取最新的完整图片列表
if (product.imageUrls) {
if (typeof product.imageUrls === 'string') {
dbResponseImageUrls = JSON.parse(product.imageUrls);
if (!Array.isArray(dbResponseImageUrls)) {
dbResponseImageUrls = [dbResponseImageUrls];
}
} else if (Array.isArray(product.imageUrls)) {
dbResponseImageUrls = product.imageUrls;
}
console.log('【数据库读取】从数据库读取的图片URLs数量:', dbResponseImageUrls.length);
}
// 如果数据库中没有或者为空,使用我们收集的finalImageUrls
if (!dbResponseImageUrls || dbResponseImageUrls.length === 0) {
dbResponseImageUrls = finalImageUrls;
console.log('【备用方案】使用收集的finalImageUrls,数量:', dbResponseImageUrls.length);
}
// 【重要修复】确保去重和清理
const urlSet = new Set();
dbResponseImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
urlSet.add(url.trim());
}
});
dbResponseImageUrls = Array.from(urlSet);
// 确保数组格式正确
if (!Array.isArray(dbResponseImageUrls)) {
dbResponseImageUrls = [dbResponseImageUrls];
}
console.log('【最终响应】去重后返回给前端的图片URLs数量:', dbResponseImageUrls.length);
} catch (e) {
console.error('解析响应图片URLs出错:', e);
// 如果解析失败,使用我们收集的finalImageUrls
dbResponseImageUrls = finalImageUrls;
}
console.log('【关键修复】返回给前端的图片URL数量:', dbResponseImageUrls.length);
console.log('【关键修复】返回给前端的图片URL列表:', JSON.stringify(dbResponseImageUrls));
console.log('【递归上传关键】响应中包含的累积图片数量:', dbResponseImageUrls.length);
// 继续执行后续代码,确保返回完整的商品信息和图片URLs
// 【关键修复】确保返回完整的响应数据,包含所有图片URLs
// 准备响应数据,包含完整的图片URL列表
const customResponseData = {
success: true,
message: isUpdate ? '商品更新成功' : '商品创建成功',
code: 200,
// 【关键修复】直接返回完整的图片URL数组在多个位置,确保前端能正确获取
imageUrls: dbResponseImageUrls, // 顶层直接返回
allImageUrls: dbResponseImageUrls, // 增强兼容性
imageUrl: dbResponseImageUrls[0] || null, // 保持向后兼容
data: {
product: {
...product.toJSON(),
imageUrls: dbResponseImageUrls, // 确保在product对象中也包含完整URL列表
allImageUrls: dbResponseImageUrls,
imageUrl: dbResponseImageUrls[0] || null
},
imageUrls: dbResponseImageUrls, // 在data对象中也包含一份
allImageUrls: dbResponseImageUrls
},
product: {
...product.toJSON(),
imageUrls: dbResponseImageUrls, // 在顶层product对象中也包含
allImageUrls: dbResponseImageUrls,
imageUrl: dbResponseImageUrls[0] || null
},
// 【关键修复】添加会话和上传状态信息
uploadInfo: {
sessionId: sessionId,
productId: productId,
isMultiImageUpload: isMultiImageUpload,
isRecursiveUpload: isRecursiveUpload,
isFinalUpload: req.body.isFinalUpload === 'true',
currentIndex: uploadIndex,
totalImages: totalImages,
uploadedCount: dbResponseImageUrls.length,
hasMultipleImages: dbResponseImageUrls.length > 1
},
sessionId: productId, // 返回会话ID
productId: productId,
isRecursiveUpload: isRecursiveUpload,
uploadIndex: uploadIndex,
totalImages: totalImages,
hasMultipleImages: hasMultipleImages,
// 添加完整的调试信息
debugInfo: {
imageUrlsCount: dbResponseImageUrls.length,
isUpdate: isUpdate,
sessionInfo: {
sessionId: sessionId,
productId: productId
}
}
};
console.log('【响应准备】最终返回的响应数据结构:', {
success: customResponseData.success,
imageUrlsCount: customResponseData.imageUrls.length,
dataImageUrlsCount: customResponseData.data.imageUrls.length,
productImageUrlsCount: customResponseData.product.imageUrls.length
});
// 查询完整商品信息以确保返回正确的毛重值和图片URLs
product = await Product.findOne({
where: { productId },
include: [
{
model: User,
as: 'seller',
attributes: ['userId', 'nickName', 'avatarUrl']
}
]
});
// 【关键修复】在发送响应前,再次确认数据库中的图片URLs是否正确存储
if (product && product.imageUrls) {
let dbImageUrls;
try {
dbImageUrls = typeof product.imageUrls === 'string' ? JSON.parse(product.imageUrls) : product.imageUrls;
if (Array.isArray(dbImageUrls)) {
console.log(`【数据库验证】从数据库读取的图片URLs数量: ${dbImageUrls.length}`);
// 使用数据库中的最新URL列表更新响应
dbResponseImageUrls = dbImageUrls;
}
} catch (e) {
console.error('【数据库验证】解析数据库中的imageUrls失败:', e);
}
}
// 【增强的最终检查】确保返回给前端的图片URLs包含所有上传的图片
console.log('【最终检查】开始最终图片URLs检查');
// 【关键修复】确保URL列表格式正确且去重
let responseImageUrls = [];
try {
// 优先使用数据库响应的URL列表
responseImageUrls = dbResponseImageUrls;
if (!Array.isArray(responseImageUrls)) {
responseImageUrls = [responseImageUrls];
}
// 最后一次去重和清理
const finalUrlSet = new Set();
responseImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
finalUrlSet.add(url.trim());
}
});
responseImageUrls = Array.from(finalUrlSet);
console.log(`【最终响应】将返回给前端的图片URLs数量: ${responseImageUrls.length}`);
} catch (e) {
console.error('【最终响应】处理imageUrls失败:', e);
responseImageUrls = [];
}
// 更新customResponseData中的图片URL列表为最新的去重结果
customResponseData.imageUrls = responseImageUrls;
customResponseData.allImageUrls = responseImageUrls;
customResponseData.imageUrl = responseImageUrls[0] || null;
if (customResponseData.data) {
customResponseData.data.imageUrls = responseImageUrls;
customResponseData.data.allImageUrls = responseImageUrls;
if (customResponseData.data.product) {
customResponseData.data.product.imageUrls = responseImageUrls;
customResponseData.data.product.allImageUrls = responseImageUrls;
customResponseData.data.product.imageUrl = responseImageUrls[0] || null;
}
}
if (customResponseData.product) {
customResponseData.product.imageUrls = responseImageUrls;
customResponseData.product.allImageUrls = responseImageUrls;
customResponseData.product.imageUrl = responseImageUrls[0] || null;
}
customResponseData.uploadInfo.uploadedCount = responseImageUrls.length;
customResponseData.uploadInfo.hasMultipleImages = responseImageUrls.length > 1;
customResponseData.debugInfo.imageUrlsCount = responseImageUrls.length;
// ========== 【新增】响应前的地区字段最终验证 ==========
console.log('【地区字段调试】发送响应前最终验证');
if (customResponseData.product) {
console.log('【地区字段调试】响应中product.region:', customResponseData.product.region);
}
if (customResponseData.data && customResponseData.data.product) {
console.log('【地区字段调试】响应中data.product.region:', customResponseData.data.product.region);
}
// ========== 地区字段调试结束 ==========
console.log('【响应准备】最终返回的响应数据结构:', {
success: customResponseData.success,
imageUrlsCount: customResponseData.imageUrls.length,
dataImageUrlsCount: customResponseData.data?.imageUrls?.length,
productImageUrlsCount: customResponseData.product?.imageUrls?.length
});
// 发送最终增强的响应
res.status(200).json(customResponseData);
} catch (err) {
console.error('【错误】在添加商品时出错:', err);
res.status(500).json({
success: false,
message: '上传失败,请稍后重试',
code: 500,
error: err.message
});
}
});
// 【关键修复】在 handleAddImagesToExistingProduct 函数中加强图片合并逻辑
async function handleAddImagesToExistingProduct(req, res, existingProductId, uploadedFiles) {
let transaction;
try {
console.log('【图片更新模式】开始处理图片上传到已存在商品,商品ID:', existingProductId);
// 使用事务确保数据一致性
transaction = await sequelize.transaction();
// 查找现有商品并锁定行,防止并发问题
const existingProduct = await Product.findOne({
where: { productId: existingProductId },
lock: transaction.LOCK.UPDATE,
transaction
});
if (!existingProduct) {
await transaction.rollback();
return res.status(404).json({
success: false,
message: '商品不存在',
code: 404
});
}
console.log('【图片更新模式】找到现有商品:', existingProduct.productName);
// 【关键修复】重新解析现有图片URL,确保正确获取所有图片
let existingImageUrls = [];
try {
if (existingProduct.imageUrls) {
const imageUrlsData = existingProduct.imageUrls;
console.log('【图片解析】原始imageUrls数据:', imageUrlsData, '类型:', typeof imageUrlsData);
if (typeof imageUrlsData === 'string') {
existingImageUrls = JSON.parse(imageUrlsData);
console.log('【图片解析】解析后的数组:', existingImageUrls, '长度:', existingImageUrls.length);
} else if (Array.isArray(imageUrlsData)) {
existingImageUrls = imageUrlsData;
}
// 确保是数组
if (!Array.isArray(existingImageUrls)) {
console.warn('【图片解析】existingImageUrls不是数组,重置为空数组');
existingImageUrls = [];
}
}
} catch (e) {
console.error('【图片解析】解析现有商品图片URL失败:', e);
existingImageUrls = [];
}
console.log('【图片合并】现有图片URL数量:', existingImageUrls.length);
// 处理新图片上传
let newImageUrls = [];
if (uploadedFiles.length > 0) {
console.log('开始上传图片到已存在商品,数量:', uploadedFiles.length);
const safeProductName = (existingProduct.productName || 'product')
.replace(/[\/:*?"<>|]/g, '_')
.substring(0, 50);
const folderPath = `products/${safeProductName}`;
// 【关键修复】批量上传所有图片
const uploadPromises = uploadedFiles.map(async (file, index) => {
try {
console.log(`上传第${index + 1}/${uploadedFiles.length}张图片`);
// 使用 OssUploader 上传图片
const uploadedUrl = await OssUploader.uploadFile(file.path, folderPath);
if (uploadedUrl) {
console.log(`图片 ${index + 1} 上传成功:`, uploadedUrl);
// 上传成功后删除临时文件
try {
await fs.promises.unlink(file.path);
console.log(`已删除临时文件: ${file.path}`);
} catch (deleteError) {
console.warn(`删除临时文件失败: ${file.path}`, deleteError);
}
return uploadedUrl;
}
} catch (uploadError) {
console.error(`图片 ${index + 1} 上传失败:`, uploadError);
return null;
}
});
const uploadResults = await Promise.all(uploadPromises);
newImageUrls = uploadResults.filter(url => url !== null);
console.log(`成功上传 ${newImageUrls.length}/${uploadedFiles.length} 张新图片`);
}
// 【关键修复】合并图片URL(去重)
const allUrlsSet = new Set();
// 添加现有图片URL
existingImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allUrlsSet.add(url.trim());
console.log('【图片合并】添加现有URL:', url.trim());
}
});
// 添加新上传的图片URL
newImageUrls.forEach(url => {
if (url && typeof url === 'string' && url.trim() !== '') {
allUrlsSet.add(url.trim());
console.log('【图片合并】添加新URL:', url.trim());
}
});
const mergedImageUrls = Array.from(allUrlsSet);
console.log('【图片更新】最终合并后图片URL数量:', mergedImageUrls.length);
console.log('【图片更新】合并后的图片URL列表:', mergedImageUrls);
// 【关键修复】验证JSON序列化结果
const imageUrlsJson = JSON.stringify(mergedImageUrls);
console.log('【JSON验证】序列化后的JSON字符串:', imageUrlsJson);
console.log('【JSON验证】JSON字符串长度:', imageUrlsJson.length);
// 更新商品图片
await Product.update({
imageUrls: imageUrlsJson,
allImageUrls: imageUrlsJson,
updated_at: new Date(),
hasMultipleImages: mergedImageUrls.length > 1,
totalImages: mergedImageUrls.length
}, {
where: { productId: existingProductId },
transaction
});
// 提交事务
await transaction.commit();
// 返回成功响应
return res.status(200).json({
success: true,
message: `图片上传成功,共${newImageUrls.length}张新图片,总计${mergedImageUrls.length}张图片`,
code: 200,
imageUrls: mergedImageUrls,
allImageUrls: mergedImageUrls,
productId: existingProductId,
uploadedCount: newImageUrls.length,
totalCount: mergedImageUrls.length
});
} catch (error) {
console.error('【图片更新模式】处理图片上传时出错:', error);
if (transaction) {
await transaction.rollback();
}
return res.status(500).json({
success: false,
message: '图片上传失败',
code: 500,
error: error.message
});
}
}
// 其他路由...
// 辅助函数:清理临时文件
function cleanTempFiles(filePaths) {
if (!filePaths || filePaths.length === 0) {
return;
}
for (const filePath of filePaths) {
try {
if (filePath && fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
console.log('临时文件已清理:', filePath);
} else {
console.log('跳过清理不存在的文件:', filePath || 'undefined');
}
} catch (err) {
console.error('清理临时文件失败:', err);
}
}
}
// 获取商品详情
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状态商品,直接使用Product表中的reservedCount字段
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: '商品不存在'
});
}
// 对返回的商品数据进行处理
let updatedProduct = { ...product.toJSON() };
// 关键修复:将存储在数据库中的JSON字符串反序列化为JavaScript数组
if (updatedProduct.imageUrls && typeof updatedProduct.imageUrls === 'string') {
console.log('【关键修复】将数据库中的JSON字符串反序列化为JavaScript数组');
try {
updatedProduct.imageUrls = JSON.parse(updatedProduct.imageUrls);
console.log('反序列化后的imageUrls类型:', typeof updatedProduct.imageUrls);
} catch (parseError) {
console.error('反序列化imageUrls失败:', parseError);
// 如果解析失败,使用空数组确保前端不会崩溃
updatedProduct.imageUrls = [];
}
}
// 详细分析毛重字段
const grossWeightDetails = {
type: typeof updatedProduct.grossWeight,
isEmpty: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined,
isString: typeof updatedProduct.grossWeight === 'string',
value: updatedProduct.grossWeight === '' || updatedProduct.grossWeight === null || updatedProduct.grossWeight === undefined ? '' : String(updatedProduct.grossWeight),
isStoredSpecialValue: typeof updatedProduct.grossWeight === 'number' && updatedProduct.grossWeight === 0.01
};
// 详细的日志记录
console.log('商品详情 - 毛重字段详细分析:');
console.log('- 原始值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight);
console.log('- 是否为空值:', grossWeightDetails.isEmpty);
console.log('- 是否为字符串类型:', grossWeightDetails.isString);
console.log('- 是否为特殊存储值:', grossWeightDetails.isStoredSpecialValue);
console.log('- 转换后的值:', grossWeightDetails.value, '类型:', typeof grossWeightDetails.value);
// 从数据库读取时的特殊处理:如果是特殊值0.01,表示原始是非数字字符串
if (grossWeightDetails.isStoredSpecialValue && updatedProduct.originalGrossWeight) {
// 使用存储的原始非数字毛重字符串
updatedProduct.grossWeight = String(updatedProduct.originalGrossWeight);
console.log('检测到特殊存储值,还原原始非数字毛重:', updatedProduct.grossWeight);
} else {
// 确保grossWeight值是字符串类型
updatedProduct.grossWeight = String(grossWeightDetails.value);
}
// 确保reservedCount是数字类型,如果不存在则默认为0
updatedProduct.reservedCount = typeof updatedProduct.reservedCount === 'number' ? updatedProduct.reservedCount : 0;
console.log('商品详情 - 最终返回的毛重值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight);
console.log('商品详情 - 返回的预约人数:', updatedProduct.reservedCount, '类型:', typeof updatedProduct.reservedCount);
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/test-format', async (req, res) => {
try {
console.log('收到测试购物车请求 - 完整请求体:');
console.log(JSON.stringify(req.body, null, 2));
console.log('请求头:', req.headers);
// 从请求体中提取openid和商品数据(与实际路由相同的逻辑)
let openid, productData;
// 处理各种可能的数据结构
if (req.body.openid) {
openid = req.body.openid;
productData = req.body.product || req.body;
} else {
// 尝试从不同位置获取openid
openid = req.body.userInfo?.openId || req.body.userInfo?.openid || req.body.userId;
productData = req.body.product || req.body;
}
console.log('提取到的openid:', openid);
console.log('提取到的商品数据:', productData ? JSON.stringify(productData, null, 2) : '无');
// 获取商品ID
const productId = productData ? (productData.productId || productData.id) : null;
console.log('提取到的productId:', productId);
// 增强的数量字段检测和处理逻辑
const quantityFields = ['quantity', 'count', 'amount', 'num', 'qty', 'stock', 'countValue', 'quantityValue'];
console.log('所有可能的数量字段详细信息:');
// 初始化数量处理结果
let finalQuantity = 1;
let quantitySource = 'default';
let foundValidQuantity = false;
// 遍历并检测所有可能的数量字段
for (const field of quantityFields) {
if (productData && productData[field] !== undefined && productData[field] !== null && productData[field] !== '') {
const value = productData[field];
const type = typeof value;
console.log(` ${field}: ${value} 类型: ${type}`);
// 尝试转换为数字并验证
let numericValue;
if (type === 'string') {
// 清理字符串,移除非数字字符
const cleanStr = String(value).replace(/[^\d.]/g, '');
console.log(` 清理字符串后: "${cleanStr}"`);
numericValue = parseFloat(cleanStr);
} else {
numericValue = Number(value);
}
// 验证数字有效性
if (!isNaN(numericValue) && isFinite(numericValue) && numericValue > 0) {
// 标准化为整数
finalQuantity = Math.floor(numericValue);
quantitySource = field;
foundValidQuantity = true;
console.log(` ✓ 成功转换为有效数量: ${finalQuantity} (来自${field}字段)`);
break;
} else {
console.log(` ✗ 无法转换为有效数字`);
}
}
}
// 确保数量至少为1
finalQuantity = Math.max(1, finalQuantity);
console.log('数量处理结果汇总:');
console.log(` 最终处理的数量值: ${finalQuantity}`);
console.log(` 数量来源字段: ${quantitySource}`);
console.log(` 是否找到有效数量: ${foundValidQuantity}`);
// 构建详细的响应数据,包含处理结果
res.json({
success: true,
message: '测试请求成功接收',
receivedData: req.body,
processingResults: {
openid: openid,
productId: productId,
detectedQuantity: {
value: finalQuantity,
source: quantitySource,
isValid: foundValidQuantity
},
productDataStructure: productData ? Object.keys(productData) : []
},
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('测试路由出错:', error);
console.error('详细错误信息:', { name: error.name, message: error.message, stack: error.stack });
res.status(400).json({
success: false,
message: '测试路由处理失败',
error: error.message,
requestData: req.body,
timestamp: new Date().toISOString()
});
}
});
// 添加商品到购物车
app.post('/api/cart/add', async (req, res) => {
// 增加全局错误捕获,确保即使在try-catch外部的错误也能被处理
try {
console.log('收到添加到购物车请求 - 开始处理', req.url);
let cartData = req.body;
console.log('收到添加到购物车请求数据 - 完整请求体:');
console.log(JSON.stringify(req.body, null, 2));
console.log('请求头:', req.headers);
console.log('请求IP:', req.ip);
console.log('请求URL:', req.url);
console.log('请求方法:', req.method);
// 兼容客户端请求格式:客户端可能将数据封装在product对象中,并且使用openid而不是userId
if (cartData.product && !cartData.productId) {
// 从product对象中提取数据
const productData = cartData.product;
console.log('从product对象提取数据:', productData);
// 打印所有可能包含数量信息的字段
console.log('productData中可能的数量字段:');
console.log(' quantity:', productData.quantity, '类型:', typeof productData.quantity);
console.log(' count:', productData.count, '类型:', typeof productData.count);
console.log(' amount:', productData.amount, '类型:', typeof productData.amount);
console.log(' num:', productData.num, '类型:', typeof productData.num);
console.log(' quantityValue:', productData.quantityValue, '类型:', typeof productData.quantityValue);
console.log(' qty:', productData.qty, '类型:', typeof productData.qty);
console.log(' stock:', productData.stock, '类型:', typeof productData.stock);
console.log(' countValue:', productData.countValue, '类型:', typeof productData.countValue);
console.log(' product.quantity:', productData['product.quantity'], '类型:', typeof productData['product.quantity']);
console.log('客户端提供的openid:', cartData.openid);
// 使用openid作为userId
cartData = {
userId: cartData.openid || productData.userId,
productId: productData.productId || productData.id,
productName: productData.productName || productData.name,
// 优化的数量处理逻辑 - 确保获取有效数量并转换为数字
quantity: (() => {
let finalQuantity = 1; // 默认数量
let foundQuantity = false;
// 定义所有可能的数量字段名称,按优先级排序
const quantityFields = ['quantity', 'count', 'amount', 'num', 'qty', 'stock', 'countValue', 'quantityValue'];
// 遍历所有可能的数量字段,找到第一个有效值
for (const field of quantityFields) {
if (productData[field] !== undefined && productData[field] !== null && productData[field] !== '') {
console.log(`找到数量字段: ${field} = ${productData[field]} (类型: ${typeof productData[field]})`);
// 无论是什么类型,先尝试转换为数字
let numericValue;
if (typeof productData[field] === 'string') {
// 对于字符串,先移除所有非数字字符(保留小数点)
const cleanStr = String(productData[field]).replace(/[^\d.]/g, '');
console.log(`清理字符串后的数量: "${cleanStr}"`);
numericValue = parseFloat(cleanStr);
} else {
numericValue = Number(productData[field]);
}
// 验证是否是有效数字
if (!isNaN(numericValue) && isFinite(numericValue) && numericValue > 0) {
// 确保是整数
finalQuantity = Math.floor(numericValue);
console.log(`成功转换并标准化数量值: ${finalQuantity}`);
foundQuantity = true;
break;
} else {
console.log(`字段 ${field} 的值无法转换为有效数字: ${productData[field]}`);
}
}
}
// 如果遍历完所有字段仍未找到有效数量
if (!foundQuantity) {
console.log('未找到有效数量字段,使用默认值1');
}
// 最后确保数量至少为1
finalQuantity = Math.max(1, finalQuantity);
console.log(`最终确定的数量值: ${finalQuantity} (类型: ${typeof finalQuantity})`);
return finalQuantity;
})(),
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.quantity);
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(401).json({
success: false,
code: 401,
message: '找不到对应的用户记录,请重新登录',
error: `未找到用户记录: ${cartData.userId}`,
needRelogin: true // 添加重新登录标志
});
}
} 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(401).json({
success: false,
code: 401,
message: '找不到对应的用户记录,请重新登录',
error: `用户ID ${cartData.userId} 不存在`,
needRelogin: true // 添加重新登录标志
});
} 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('创建新购物车项,所有字段:');
console.log(' userId:', cartData.userId);
console.log(' productId:', cartData.productId);
console.log(' productName:', cartData.productName);
console.log(' 最终写入数据库的quantity值:', cartData.quantity, '类型:', typeof cartData.quantity);
console.log(' price:', cartData.price);
console.log(' specification:', cartData.specification);
console.log(' grossWeight:', cartData.grossWeight);
console.log(' 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
}
});
}
// 更新商品的预约人数 - 与selected状态同步的实现
try {
console.log(`尝试更新商品预约人数: productId=${cartData.productId}`);
// 检查用户类型,如果是seller则更新为both - 增强版
try {
console.log(`开始执行用户类型转换检查: userId=${cartData.userId}`);
const user = await User.findOne({ where: { userId: cartData.userId } });
if (!user) {
console.error(`用户类型转换失败: 未找到用户ID=${cartData.userId}`);
} else if (!user.type) {
console.error(`用户类型转换失败: 用户类型字段为空 - userId=${cartData.userId}`);
// 为安全起见,如果类型为空,设置为both
await User.update({ type: 'both' }, { where: { userId: cartData.userId } });
console.log(`已修复空类型字段: userId=${cartData.userId} 设置为 both`);
} else if (user.type === 'seller') {
console.log(`用户类型转换: userId=${cartData.userId}${user.type} 转为 both`);
const updateResult = await User.update({ type: 'both' }, { where: { userId: cartData.userId } });
console.log(`用户类型更新结果: 影响行数=${updateResult[0]}`);
// 验证更新是否成功
const updatedUser = await User.findOne({ where: { userId: cartData.userId } });
console.log(`用户类型更新验证: 更新后类型=${updatedUser.type}`);
} else {
console.log(`用户类型无需转换: userId=${cartData.userId} 类型=${user.type}`);
}
} catch (userUpdateError) {
console.error(`更新用户类型失败:`, userUpdateError);
console.error(`详细错误信息:`, { name: userUpdateError.name, message: userUpdateError.message, stack: userUpdateError.stack });
// 继续执行,不中断主要流程
}
// 只有当购物车项是选中状态时,才增加商品的预约人数
const isSelected = cartData.selected !== undefined ? cartData.selected : true; // 默认选中
if (isSelected) {
// 直接更新商品预约人数
await Product.increment('reservedCount', { by: 1, where: { productId: cartData.productId } });
// 更新后重新查询以获取实际的reservedCount值
const updatedProduct = await Product.findOne({ where: { productId: cartData.productId } });
if (updatedProduct) {
console.log(`商品预约人数更新成功: productId=${cartData.productId}, 新数量=${updatedProduct.reservedCount}`);
} else {
console.log(`商品预约人数更新成功,但无法获取更新后的值: productId=${cartData.productId}`);
}
} else {
console.log(`购物车项未选中,不更新商品预约人数: productId=${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'] }
},
// 修复连接条件,使用正确的 Sequelize 连接语法
on: {
productId: {
[Sequelize.Op.col]: 'CartItem.productId'
}
}
}
],
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;
updateData.updated_at = new Date();
// 如果selected状态发生变化,先获取当前购物车项信息
let cartItem = null;
if (selected !== undefined) {
cartItem = await CartItem.findOne({ where: { id } });
}
// 更新购物车项
if (selected !== undefined) updateData.selected = selected;
await CartItem.update(updateData, {
where: { id }
});
// 如果selected状态发生变化,同步更新商品的预约人数
if (selected !== undefined && cartItem) {
try {
const change = selected && !cartItem.selected ? 1 : (!selected && cartItem.selected ? -1 : 0);
if (change !== 0) {
console.log(`同步更新商品预约人数: productId=${cartItem.productId}, change=${change}`);
const product = await Product.findOne({ where: { productId: cartItem.productId } });
if (product) {
// 确保reservedCount不会变为负数
const newReservedCount = Math.max(0, (product.reservedCount || 0) + change);
await Product.update(
{ reservedCount: newReservedCount },
{ where: { productId: cartItem.productId } }
);
console.log(`商品预约人数更新成功: productId=${cartItem.productId}, 新数量=${newReservedCount}`);
}
}
} catch (syncError) {
console.error(`同步更新商品预约人数失败:`, syncError);
// 继续执行,不中断主要流程
}
}
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参数'
});
}
// 先获取要删除的购物车项信息,检查是否为选中状态
const cartItem = await CartItem.findOne({ where: { id } });
// 删除购物车项
await CartItem.destroy({
where: { id }
});
// 如果购物车项是选中状态,同步减少商品的预约人数
if (cartItem && cartItem.selected) {
try {
console.log(`删除选中的购物车项,同步减少商品预约人数: productId=${cartItem.productId}`);
const product = await Product.findOne({ where: { productId: cartItem.productId } });
if (product) {
// 确保reservedCount不会变为负数
const newReservedCount = Math.max(0, (product.reservedCount || 0) - 1);
await Product.update(
{ reservedCount: newReservedCount },
{ where: { productId: cartItem.productId } }
);
console.log(`商品预约人数更新成功: productId=${cartItem.productId}, 新数量=${newReservedCount}`);
}
} catch (syncError) {
console.error(`同步减少商品预约人数失败:`, syncError);
// 继续执行,不中断主要流程
}
}
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参数'
});
}
// 先获取用户所有被选中的购物车项
const selectedCartItems = await CartItem.findAll({
where: {
userId,
selected: true
}
});
// 清空购物车
await CartItem.destroy({
where: { userId }
});
// 如果有被选中的购物车项,同步减少对应商品的预约人数
if (selectedCartItems.length > 0) {
try {
console.log(`清空购物车,同步减少${selectedCartItems.length}个商品的预约人数`);
// 统计每个商品需要减少的预约人数
const productReservations = {};
selectedCartItems.forEach(item => {
productReservations[item.productId] = (productReservations[item.productId] || 0) + 1;
});
// 批量更新商品的预约人数
for (const [productId, count] of Object.entries(productReservations)) {
const product = await Product.findOne({ where: { productId } });
if (product) {
// 确保reservedCount不会变为负数
const newReservedCount = Math.max(0, (product.reservedCount || 0) - count);
await Product.update(
{ reservedCount: newReservedCount },
{ where: { productId } }
);
console.log(`商品预约人数更新成功: productId=${productId}, 减少=${count}, 新数量=${newReservedCount}`);
}
}
} catch (syncError) {
console.error(`同步减少商品预约人数失败:`, syncError);
// 继续执行,不中断主要流程
}
}
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
});
}
});
// OSS连接测试接口
app.get('/api/test-oss-connection', async (req, res) => {
try {
console.log('收到OSS连接测试请求');
const OssUploader = require('./oss-uploader');
const result = await OssUploader.testConnection();
res.status(result.success ? 200 : 500).json({
...result,
timestamp: new Date().toISOString(),
code: result.success ? 200 : 500
});
} catch (error) {
console.error('OSS连接测试接口错误:', error);
res.status(500).json({
success: false,
code: 500,
message: 'OSS连接测试接口执行失败',
error: error.message,
timestamp: new Date().toISOString()
});
}
});
// 用户类型调试接口 - 增强版:用于排查用户类型和商品显示问题
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,
isString: typeof product.price === 'string',
isValid: product.price !== null && product.price !== undefined && product.price !== ''
};
if (!product.price || product.price === '') {
console.error('价格为空');
validationErrors.push('价格为必填项');
} else {
// 确保价格是字符串类型
product.price = String(product.price);
}
// 检查数量
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');
}
// 改进的毛重字段处理逻辑 - 处理非数字字符串
const grossWeightDetails = {
type: typeof product.grossWeight,
isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined,
isString: typeof product.grossWeight === 'string',
value: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? '' : String(product.grossWeight),
isNonNumeric: typeof product.grossWeight === 'string' && product.grossWeight.trim() !== '' && isNaN(parseFloat(product.grossWeight.trim()))
};
// 详细的日志记录
console.log('发布商品 - 毛重字段详细分析:');
console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight);
console.log('- 是否为空值:', grossWeightDetails.isEmpty);
console.log('- 是否为字符串类型:', grossWeightDetails.isString);
console.log('- 是否为非数字字符串:', grossWeightDetails.isNonNumeric);
console.log('- 转换后的值:', grossWeightDetails.value, '类型:', typeof grossWeightDetails.value);
// 处理非数字毛重:添加特殊标记处理
if (grossWeightDetails.isEmpty) {
product.grossWeight = '';
} else if (grossWeightDetails.isNonNumeric) {
// 非数字字符串处理:使用特殊标记存储
// 在数字字段中存储0.01,并通过特殊属性标记为非数字
product.grossWeight = 0.01;
product.isNonNumericGrossWeight = true;
product.originalGrossWeight = String(grossWeightDetails.value);
console.log('检测到非数字毛重,使用特殊处理:', { original: grossWeightDetails.value, stored: product.grossWeight });
} else {
// 数字字符串或数字,直接存储
product.grossWeight = parseFloat(product.grossWeight) || 0;
}
// 确保商品名称不超过数据库字段长度限制
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(401).json({
success: false,
code: 401,
message: '找不到对应的用户记录,请重新登录',
needRelogin: true
});
}
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值是字符串类型
const finalGrossWeight = String(grossWeightDetails.value);
console.log('发布商品 - 最终存储的毛重值:', finalGrossWeight, '类型:', typeof finalGrossWeight);
// 创建商品
console.log('准备创建商品:', {
productName: product.productName,
price: product.price,
quantity: product.quantity,
grossWeight: finalGrossWeight,
sellerId: user.userId,
intendedStatus: 'pending_review' // 明确记录预期的状态
});
// 记录状态设置的详细日志
console.log('🛡️ 状态控制: 强制设置新商品状态为 pending_review');
console.log('🛡️ 状态控制: 忽略任何可能的自动发布逻辑');
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()
});
// 立即验证创建后的状态
console.log('✅ 商品创建后状态验证:', {
productId: productId,
actualStatus: newProduct.status,
expectedStatus: 'pending_review',
statusMatch: newProduct.status === 'pending_review'
});
// 查询完整商品信息以确保返回正确的毛重值和状态
const createdProduct = await Product.findOne({
where: { productId },
include: [
{
model: User,
as: 'seller',
attributes: ['userId', 'nickName', 'avatarUrl']
}
]
});
// 再次验证数据库中的状态
if (createdProduct) {
console.log('发布商品 - 数据库查询后grossWeight:', createdProduct.grossWeight, '类型:', typeof createdProduct.grossWeight);
console.log('✅ 数据库查询后状态验证:', {
productId: productId,
databaseStatus: createdProduct.status,
expectedStatus: 'pending_review',
statusMatch: createdProduct.status === 'pending_review'
});
// 安全检查:如果状态不是pending_review,强制更新回pending_review
if (createdProduct.status !== 'pending_review') {
console.log('⚠️ 警告: 发现状态不一致,强制更新回pending_review');
await Product.update(
{ status: 'pending_review', updated_at: new Date() },
{ where: { productId } }
);
// 更新后重新查询以确保状态正确
const updatedProduct = await Product.findOne({ where: { productId } });
console.log('✅ 强制更新后状态:', updatedProduct.status);
// 更新返回对象的状态
createdProduct.status = 'pending_review';
}
}
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
});
}
});
// 服务器启动由start-server.js负责
// 这里不再单独启动服务器,避免端口冲突
// 如需直接测试此文件,请使用:node test-fixed-sync.js
// 如需完整服务器测试,请使用:node full-server-test.js
// 编辑商品API - 用于审核失败商品重新编辑
app.post('/api/product/edit', async (req, res) => {
console.log('=== 编辑商品请求开始 ===');
console.log('【请求信息】收到编辑商品请求 - 详细信息:');
console.log('- 请求路径:', req.url);
console.log('- 请求方法:', req.method);
console.log('- 请求完整body:', JSON.stringify(req.body, null, 2));
console.log('- 服务器端口:', PORT);
try {
// 【关键修复】根据前端数据结构解析参数
let openid = req.body.openid;
let productId = req.body.productId;
let status = req.body.status;
let product = req.body.product;
console.log('【参数解析】前端发送的数据结构:', {
openid,
productId,
status,
product: !!product,
productKeys: product ? Object.keys(product) : '无product'
});
// ========== 【新增】编辑商品时的地区字段调试 ==========
console.log('【地区字段调试】编辑商品 - 开始处理');
console.log('【地区字段调试】请求体中的region字段:', req.body.region);
if (product) {
console.log('【地区字段调试】product对象中的region字段:', product.region, '类型:', typeof product.region);
}
// ========== 地区字段调试结束 ==========
// 验证必填字段
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对象)'
});
}
// 查找用户
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, openid: user.openid });
// 查找商品
console.log('【商品查找】查找商品 productId=', productId, 'sellerId=', user.userId);
const existingProduct = await Product.findOne({
where: {
productId: productId,
sellerId: user.userId
}
});
if (!existingProduct) {
console.error('【商品查找】编辑商品失败: 商品不存在或不属于当前用户');
return res.status(404).json({
success: false,
code: 404,
message: '商品不存在或不属于当前用户'
});
}
// ========== 【新增】编辑商品时的现有商品地区字段调试 ==========
console.log('【地区字段调试】找到现有商品:', {
productId: existingProduct.productId,
productName: existingProduct.productName,
existingRegion: existingProduct.region, // 特别显示现有地区字段
regionType: typeof existingProduct.region
});
// ========== 地区字段调试结束 ==========
console.log('【商品查找】找到商品:', {
productId: existingProduct.productId,
productName: existingProduct.productName,
status: existingProduct.status,
sellerId: existingProduct.sellerId
});
// 验证商品状态是否允许编辑
if (!['rejected', 'sold_out', 'pending_review', 'reviewed'].includes(existingProduct.status)) {
console.error('【状态验证】编辑商品失败: 商品状态不允许编辑', {
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('=== 开始图片处理 ===');
// 【关键修复】安全地获取和转换 existingImageUrls
let existingImageUrls = [];
console.log('【图片处理】开始处理商品图片...');
try {
// 从数据库获取现有图片URL
const rawImageUrls = existingProduct.imageUrls;
console.log('【图片处理】数据库中的原始imageUrls:', rawImageUrls, '类型:', typeof rawImageUrls);
if (Array.isArray(rawImageUrls)) {
// 如果已经是数组,直接使用
existingImageUrls = rawImageUrls;
console.log('【图片处理】existingImageUrls 已经是数组,长度:', existingImageUrls.length);
} else if (typeof rawImageUrls === 'string') {
// 如果是字符串,尝试解析JSON
console.log('【图片处理】existingImageUrls 是字符串,尝试解析JSON');
try {
const parsed = JSON.parse(rawImageUrls);
if (Array.isArray(parsed)) {
existingImageUrls = parsed;
console.log('【图片处理】成功解析JSON字符串为数组,长度:', existingImageUrls.length);
} else {
console.warn('【图片处理】解析后的JSON不是数组,使用空数组:', parsed);
existingImageUrls = [];
}
} catch (parseError) {
console.error('【图片处理】JSON解析失败,使用空数组:', parseError);
existingImageUrls = [];
}
} else if (rawImageUrls) {
// 如果是其他非空值,包装成数组
console.warn('【图片处理】非数组非字符串类型,包装成数组:', rawImageUrls);
existingImageUrls = [rawImageUrls].filter(Boolean);
} else {
// 空值情况
console.log('【图片处理】空值,使用空数组');
existingImageUrls = [];
}
} catch (error) {
console.error('【图片处理】获取existingImageUrls时发生错误:', error);
existingImageUrls = [];
}
// 【关键修复】确保 newImageUrls 是数组
let newImageUrls = [];
try {
// 【重要修复】检查前端是否传递了 imageUrls
if (product.imageUrls === undefined || product.imageUrls === null) {
console.log('【图片处理】前端未传递imageUrls字段,保留现有图片');
// 如果前端没有传递imageUrls,说明用户不想修改图片,保留现有图片
newImageUrls = [...existingImageUrls];
} else {
newImageUrls = Array.isArray(product.imageUrls) ? product.imageUrls : [];
}
console.log('【图片处理】新提交图片:', newImageUrls, '数量:', newImageUrls.length);
} catch (error) {
console.error('【图片处理】处理newImageUrls时发生错误:', error);
// 发生错误时,保留现有图片
newImageUrls = [...existingImageUrls];
}
console.log('【图片处理】最终确认 - 现有图片:', existingImageUrls, '类型:', typeof existingImageUrls, '是数组:', Array.isArray(existingImageUrls), '长度:', existingImageUrls.length);
console.log('【图片处理】最终确认 - 新提交图片:', newImageUrls, '类型:', typeof newImageUrls, '是数组:', Array.isArray(newImageUrls), '长度:', newImageUrls.length);
// 【关键修复】找出真正被删除的图片 - 只有当新图片数组不为空时才进行比较
let deletedImageUrls = [];
if (newImageUrls.length > 0) {
// 只有在新提交了图片时才删除图片
deletedImageUrls = existingImageUrls.filter(url => !newImageUrls.includes(url));
console.log('【图片处理】被删除的图片:', deletedImageUrls, '数量:', deletedImageUrls.length);
} else {
// 如果新图片数组为空,说明前端可能没有传递图片数据,不应该删除任何图片
console.log('【图片处理】新提交图片为空,不删除任何现有图片');
deletedImageUrls = [];
}
// 【关键修复】构建最终的图片URL数组
let finalImageUrls = [];
if (newImageUrls.length > 0) {
// 如果前端提交了新的图片,使用新的图片URL
finalImageUrls = [...new Set(newImageUrls)];
} else {
// 如果前端没有提交图片,保留现有图片
finalImageUrls = [...existingImageUrls];
console.log('【图片处理】使用现有图片,数量:', finalImageUrls.length);
}
console.log('【图片处理】最终图片URL:', finalImageUrls, '长度:', finalImageUrls.length);
// 只有在确实有图片被删除时才执行OSS删除操作
if (deletedImageUrls.length > 0) {
console.log('【OSS删除】开始删除被移除的图片...');
const deletePromises = [];
for (const deletedUrl of deletedImageUrls) {
try {
// 从完整的URL中提取OSS文件路径
const urlObj = new URL(deletedUrl);
let ossFilePath = urlObj.pathname;
// 移除开头的斜杠
if (ossFilePath.startsWith('/')) {
ossFilePath = ossFilePath.substring(1);
}
console.log('【OSS删除】准备删除文件:', ossFilePath, '原始URL:', deletedUrl);
// 异步执行删除,不阻塞主流程
const deletePromise = OssUploader.deleteFile(ossFilePath)
.then(() => {
console.log('【OSS删除】✅ 成功删除文件:', ossFilePath);
return { success: true, file: ossFilePath };
})
.catch(deleteError => {
// 【增强错误处理】区分权限错误和其他错误
if (deleteError.code === 'OSS_ACCESS_DENIED' ||
deleteError.message.includes('permission') ||
deleteError.originalError?.code === 'AccessDenied') {
console.error('【OSS删除】❌ 权限不足,无法删除文件:', ossFilePath);
console.error('【OSS删除】❌ 错误详情:', deleteError.message);
// 返回特殊标记,表示权限问题
return {
success: false,
file: ossFilePath,
error: deleteError.message,
permissionDenied: true // 标记为权限问题
};
} else {
console.error('【OSS删除】❌ 其他错误删除文件失败:', ossFilePath, '错误:', deleteError.message);
return {
success: false,
file: ossFilePath,
error: deleteError.message
};
}
});
deletePromises.push(deletePromise);
} catch (urlParseError) {
console.error('【OSS删除】解析URL失败:', deletedUrl, '错误:', urlParseError.message);
}
}
// 等待所有删除操作完成
if (deletePromises.length > 0) {
console.log('【OSS删除】等待删除操作完成,共', deletePromises.length, '个文件');
const deleteResults = await Promise.allSettled(deletePromises);
const successfulDeletes = deleteResults.filter(result =>
result.status === 'fulfilled' && result.value && result.value.success
).length;
const permissionDeniedDeletes = deleteResults.filter(result =>
result.status === 'fulfilled' && result.value && result.value.permissionDenied
).length;
const otherFailedDeletes = deleteResults.filter(result =>
result.status === 'fulfilled' && result.value && !result.value.success && !result.value.permissionDenied
).length;
console.log('【OSS删除】删除操作统计:');
console.log('【OSS删除】✅ 成功删除:', successfulDeletes, '个文件');
console.log('【OSS删除】❌ 权限不足:', permissionDeniedDeletes, '个文件');
console.log('【OSS删除】⚠️ 其他失败:', otherFailedDeletes, '个文件');
}
} else {
console.log('【OSS删除】没有需要删除的图片');
}
// 更新商品数据中的图片URL
product.imageUrls = finalImageUrls;
// ========== 【修复版】图片处理逻辑结束 ==========
// 详细检查每个必填字段并记录其类型和值
console.log('=== 开始字段验证 ===');
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));
// ========== 【新增】编辑商品时的地区字段详细检查 ==========
console.log('- region: 存在=', !!product.region, '类型=', typeof product.region, '值=', product.region);
// ========== 地区字段调试结束 ==========
// 收集所有验证错误
const validationErrors = [];
// 检查商品名称
if (!product.productName || product.productName.trim() === '') {
validationErrors.push('商品名称为必填项,不能为空或仅包含空格');
} else if (product.productName.length > 255) {
validationErrors.push('商品名称不能超过255个字符');
}
// 【关键修复】价格字段处理 - 采用与毛重类似的灵活处理方式
let finalPrice = product.price;
let isNonNumericPrice = false;
let originalPrice = null;
// 检查价格是否为空
if (!product.price) {
validationErrors.push('价格为必填项');
} else {
// 处理非数字价格值
if (typeof product.price === 'string' &&
isNaN(parseFloat(product.price)) &&
!isFinite(product.price)) {
// 标记为非数字价格,但保留原始值以支持中文输入
isNonNumericPrice = true;
originalPrice = product.price;
finalPrice = originalPrice; // 保留原始值以支持中文输入
console.log('【字段验证】编辑商品 - 发现非数字价格(支持中文):', originalPrice);
console.log('【字段验证】编辑商品 - 保留原始值:', {
isNonNumericPrice,
originalPrice,
finalPrice
});
}
}
// 【关键修复】数量字段处理 - 采用与毛重类似的灵活处理方式
let finalQuantity = product.quantity;
let isNonNumericQuantity = false;
let originalQuantity = null;
// 检查数量是否为空
if (!product.quantity) {
validationErrors.push('数量为必填项');
} else {
// 处理非数字数量值
if (typeof product.quantity === 'string' &&
isNaN(parseInt(product.quantity)) &&
!isFinite(product.quantity)) {
// 标记为非数字数量,但保留原始值以支持中文输入
isNonNumericQuantity = true;
originalQuantity = product.quantity;
finalQuantity = originalQuantity; // 保留原始值以支持中文输入
console.log('【字段验证】编辑商品 - 发现非数字数量(支持中文):', originalQuantity);
console.log('【字段验证】编辑商品 - 保留原始值:', {
isNonNumericQuantity,
originalQuantity,
finalQuantity
});
}
}
// 增强的毛重字段处理逻辑 - 与发布商品保持一致
const grossWeightDetails = {
type: typeof product.grossWeight,
isEmpty: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined,
isString: typeof product.grossWeight === 'string',
value: product.grossWeight === '' || product.grossWeight === null || product.grossWeight === undefined ? '' : String(product.grossWeight),
isNonNumeric: false // 新增字段:标记是否为非数字值
};
console.log('【字段验证】编辑商品 - 毛重字段详细分析:');
console.log('- 原始值:', product.grossWeight, '类型:', typeof product.grossWeight);
console.log('- 是否为空值:', grossWeightDetails.isEmpty);
console.log('- 是否为字符串类型:', grossWeightDetails.isString);
console.log('- 转换后的值:', grossWeightDetails.value, '类型:', typeof grossWeightDetails.value);
// 【关键修复】非数字毛重处理逻辑 - 与发布商品接口保持一致
let finalGrossWeight = grossWeightDetails.value;
let isNonNumericGrossWeight = false;
let originalGrossWeight = null;
// 处理非空非数字的毛重值 - 修改为保留原始值以支持中文输入
if (!grossWeightDetails.isEmpty &&
grossWeightDetails.isString &&
isNaN(parseFloat(grossWeightDetails.value)) &&
!isFinite(grossWeightDetails.value)) {
// 标记为非数字毛重,但保留原始值
isNonNumericGrossWeight = true;
originalGrossWeight = grossWeightDetails.value;
finalGrossWeight = originalGrossWeight; // 保留原始值以支持中文输入
grossWeightDetails.isNonNumeric = true;
console.log('【字段验证】编辑商品 - 发现非数字毛重(支持中文):', originalGrossWeight);
console.log('【字段验证】编辑商品 - 保留原始值:', {
isNonNumericGrossWeight,
originalGrossWeight,
finalGrossWeight
});
}
// 确保最终值是字符串类型
finalGrossWeight = String(finalGrossWeight);
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 finalStatus = status && status.trim() !== '' ? status : existingProduct.status;
// 如果是重新提交审核的情况
let isResubmit = false;
if (status === 'pending_review' && ['rejected', 'sold_out'].includes(existingProduct.status)) {
isResubmit = true;
console.log('【状态转换】检测到重新提交审核操作');
}
// ========== 【新增】编辑商品时的地区字段处理 ==========
console.log('【地区字段调试】准备更新商品数据 - 处理地区字段');
const finalRegion = product.region || existingProduct.region || '';
console.log('【地区字段调试】最终确定的地区字段:', finalRegion, '来源:',
product.region ? '新提交的数据' :
existingProduct.region ? '现有商品数据' : '默认空值');
// ========== 地区字段调试结束 ==========
// 增强数据同步逻辑
const updatedProductData = {
productName: product.productName,
// 【关键修复】修改价格字段处理,与毛重保持一致 - 非数字值保持字符串类型
price: isNonNumericPrice ? finalPrice : parseFloat(finalPrice),
// 【关键修复】修改数量字段处理,与毛重保持一致 - 非数字值保持字符串类型
quantity: isNonNumericQuantity ? finalQuantity : parseInt(finalQuantity, 10),
grossWeight: finalGrossWeight,
yolk: product.yolk,
specification: product.specification,
region: finalRegion, // 使用调试确定的地区字段
imageUrls: product.imageUrls, // 【重要】使用处理后的图片URL
status: finalStatus,
// 【关键修复】添加非数字毛重标记和原始值 - 与发布商品接口保持一致
isNonNumericGrossWeight: isNonNumericGrossWeight,
originalGrossWeight: originalGrossWeight,
// 【新增】添加非数字价格和数量标记和原始值
isNonNumericPrice: isNonNumericPrice,
originalPrice: originalPrice,
isNonNumericQuantity: isNonNumericQuantity,
originalQuantity: originalQuantity,
// 如果是重新提交审核,清除拒绝原因
rejectReason: isResubmit ? null : existingProduct.rejectReason,
updated_at: new Date()
};
// 【新增】更新前的最终数据验证
console.log('【数据更新】更新前最终数据验证:');
console.log('- 价格字段:', updatedProductData.price, '类型:', typeof updatedProductData.price, '是否非数字标记:', updatedProductData.isNonNumericPrice);
console.log('- 数量字段:', updatedProductData.quantity, '类型:', typeof updatedProductData.quantity, '是否非数字标记:', updatedProductData.isNonNumericQuantity);
console.log('- 毛重字段:', updatedProductData.grossWeight, '类型:', typeof updatedProductData.grossWeight, '是否非数字标记:', updatedProductData.isNonNumericGrossWeight);
// ========== 【新增】更新数据前的地区字段验证 ==========
console.log('【地区字段调试】更新数据中的region字段:', updatedProductData.region, '类型:', typeof updatedProductData.region);
// ========== 地区字段调试结束 ==========
console.log('【数据更新】准备更新商品数据:', {
productId,
oldStatus: existingProduct.status,
newStatus: updatedProductData.status,
isResubmit: isResubmit,
updatedFields: Object.keys(updatedProductData)
});
// 更新商品 - 使用最可靠的save方法
let updatedCount = 0;
try {
console.log('【数据更新】使用save方法更新商品数据');
// 直接更新现有商品实例的数据
for (const key in updatedProductData) {
if (updatedProductData.hasOwnProperty(key)) {
existingProduct[key] = updatedProductData[key];
}
}
// ========== 【新增】保存前的地区字段最终检查 ==========
console.log('【地区字段调试】保存前最终检查 - existingProduct.region:', existingProduct.region, '类型:', typeof existingProduct.region);
// ========== 地区字段调试结束 ==========
// 强制保存所有更改
await existingProduct.save({ validate: false });
updatedCount = 1;
console.log('【数据更新】使用save方法成功更新商品数据');
} catch (saveError) {
console.error('【数据更新】使用save方法更新商品数据失败:', saveError);
// 如果save方法失败,尝试使用update方法
try {
[updatedCount] = await Product.update(updatedProductData, {
where: {
productId: productId,
sellerId: user.userId
}
});
console.log('【数据更新】常规update方法执行结果,受影响行数:', updatedCount);
} catch (updateError) {
console.error('【数据更新】常规update方法更新商品数据失败:', updateError);
throw new Error('商品更新失败: ' + updateError.message);
}
}
// 检查更新是否成功
if (updatedCount === 0) {
console.error('【数据更新】商品更新失败: 没有找到匹配的商品或权限不足');
return res.status(404).json({
success: false,
code: 404,
message: '商品更新失败: 没有找到匹配的商品或权限不足'
});
}
// 获取更新后的商品信息
const updatedProduct = await Product.findOne({ where: { productId: productId } });
if (!updatedProduct) {
console.error('【数据更新】无法获取更新后的商品信息');
return res.status(500).json({
success: false,
code: 500,
message: '商品更新成功但无法获取更新后的信息'
});
}
// ========== 【新增】更新后的地区字段验证 ==========
console.log('【地区字段调试】更新后验证 - 从数据库读取的商品地区字段:', updatedProduct.region, '类型:', typeof updatedProduct.region);
// ========== 地区字段调试结束 ==========
console.log('【数据更新】商品编辑成功:', {
productId: productId,
productName: product.productName,
oldStatus: existingProduct.status,
newStatus: updatedProduct.status,
grossWeight: updatedProduct.grossWeight,
imageUrls: updatedProduct.imageUrls,
region: updatedProduct.region // 特别显示地区字段
});
// 根据新的状态生成适当的返回消息
let returnMessage = '';
if (updatedProduct.status === 'pending_review') {
returnMessage = isResubmit ? '商品编辑成功,已重新提交审核' : '商品编辑成功,等待审核';
} else if (updatedProduct.status === 'published') {
returnMessage = '商品编辑成功,已上架';
} else {
returnMessage = '商品编辑成功';
}
console.log('=== 编辑商品请求完成 ===');
res.json({
success: true,
code: 200,
message: returnMessage,
product: updatedProduct
});
} catch (error) {
console.error('【错误处理】编辑商品过程发生异常:', error);
console.error('【错误处理】错误堆栈:', error.stack);
res.status(500).json({
success: false,
code: 500,
message: '编辑商品失败: ' + error.message,
error: error.message
});
}
});
// ==================== 入驻相关API ====================
// 定义入驻申请数据模型
const SettlementApplication = sequelize.define('SettlementApplication', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
applicationId: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
userId: {
type: DataTypes.STRING,
allowNull: false
},
openid: {
type: DataTypes.STRING,
allowNull: false
},
identityType: {
type: DataTypes.ENUM('individual', 'enterprise'),
allowNull: false
},
cooperationMode: {
type: DataTypes.ENUM('supplier', 'buyer', 'both'),
allowNull: false
},
contactName: {
type: DataTypes.STRING,
allowNull: false
},
contactPhone: {
type: DataTypes.STRING,
allowNull: false
},
region: {
type: DataTypes.STRING,
allowNull: false
},
businessLicense: {
type: DataTypes.TEXT
},
animalQuarantine: {
type: DataTypes.TEXT
},
brandAuth: {
type: DataTypes.TEXT
},
status: {
type: DataTypes.ENUM('pending', 'approved', 'rejected', 'withdrawn'),
defaultValue: 'pending'
},
rejectReason: {
type: DataTypes.TEXT
},
submittedAt: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW
},
reviewedAt: {
type: DataTypes.DATE
}
}, {
tableName: 'settlement_applications',
timestamps: false
});
// 获取入驻状态
app.get('/api/settlement/status/:userId', async (req, res) => {
try {
const { userId } = req.params;
console.log('获取入驻状态请求:', { userId });
// 查找用户的入驻申请
const application = await SettlementApplication.findOne({
where: { userId },
order: [['submittedAt', 'DESC']]
});
if (!application) {
return res.json({
success: true,
code: 200,
data: {
hasApplication: false,
status: null,
message: '暂无入驻申请'
}
});
}
res.json({
success: true,
code: 200,
data: {
hasApplication: true,
applicationId: application.applicationId,
status: application.status,
identityType: application.identityType,
cooperationMode: application.cooperationMode,
submittedAt: application.submittedAt,
reviewedAt: application.reviewedAt,
rejectReason: application.rejectReason,
message: getStatusMessage(application.status)
}
});
} catch (error) {
console.error('获取入驻状态失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '获取入驻状态失败: ' + 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
});
}
});
// 上传入驻文件
app.post('/api/settlement/upload', upload.single('file'), async (req, res) => {
try {
const { openid, fileType } = req.body;
console.log('收到入驻文件上传请求:', { openid, fileType });
if (!openid || !fileType) {
return res.status(400).json({
success: false,
code: 400,
message: '参数不完整'
});
}
if (!req.file) {
return res.status(400).json({
success: false,
code: 400,
message: '未接收到文件'
});
}
// 上传文件到OSS - 使用静态方法调用
// 注意:OssUploader.uploadFile直接返回URL字符串,而不是包含url属性的对象
const fileUrl = await OssUploader.uploadFile(req.file.path, `settlement/${fileType}/${Date.now()}_${req.file.originalname}`);
// 删除临时文件
fs.unlinkSync(req.file.path);
// 确保返回的URL是干净的字符串,移除可能存在的反引号和空格
const cleanFileUrl = String(fileUrl).replace(/[` ]/g, '');
res.json({
success: true,
code: 200,
message: '文件上传成功',
data: {
fileUrl: cleanFileUrl,
fileType: fileType
}
});
} catch (error) {
console.error('入驻文件上传失败:', error);
// 清理临时文件
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
res.status(500).json({
success: false,
code: 500,
message: '文件上传失败: ' + error.message
});
}
});
// 撤回入驻申请
app.post('/api/settlement/withdraw', async (req, res) => {
try {
const { openid } = req.body;
console.log('撤回入驻申请请求:', { openid });
// 查找用户
const user = await User.findOne({ where: { openid } });
if (!user) {
return res.status(404).json({
success: false,
code: 404,
message: '用户不存在'
});
}
// 新的业务逻辑:只要partnerstatus为underreview,就表示有待审核的申请
// 不再依赖notice字段来确认申请ID
if (user.partnerstatus !== 'underreview') {
// 打印详细信息帮助调试
console.log('入驻申请检查失败:', {
partnerstatus: user.partnerstatus,
message: '用户未处于审核中状态'
});
return res.status(404).json({
success: false,
code: 404,
message: '未找到申请记录'
});
}
// 更新用户状态为未提交
await User.update({
notice: null, // 清除notice字段
partnerstatus: '' // 清空合作商状态
}, { where: { openid } });
res.json({
success: true,
code: 200,
message: '入驻申请已撤回'
});
} catch (error) {
console.error('撤回入驻申请失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '撤回入驻申请失败: ' + error.message
});
}
});
// 重新提交入驻申请
app.post('/api/settlement/resubmit/:applicationId', async (req, res) => {
try {
const { applicationId } = req.params;
const { openid } = req.body;
console.log('重新提交入驻申请请求:', { applicationId, openid });
// 查找用户
const user = await User.findOne({ where: { openid } });
if (!user) {
return res.status(404).json({
success: false,
code: 404,
message: '用户不存在'
});
}
// 查找申请
const application = await SettlementApplication.findOne({
where: {
applicationId,
userId: user.userId
}
});
if (!application) {
return res.status(404).json({
success: false,
code: 404,
message: '入驻申请不存在'
});
}
if (application.status !== 'rejected') {
return res.status(400).json({
success: false,
code: 400,
message: '只能重新提交被拒绝的申请'
});
}
// 更新状态为待审核
await application.update({
status: 'pending',
rejectReason: null,
reviewedAt: null
});
res.json({
success: true,
code: 200,
message: '入驻申请已重新提交,请等待审核'
});
} catch (error) {
console.error('重新提交入驻申请失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '重新提交入驻申请失败: ' + error.message
});
}
});
// 辅助函数:获取状态消息
function getStatusMessage(status) {
const statusMessages = {
'pending': '待审核',
'approved': '已通过',
'rejected': '已拒绝',
'withdrawn': '已撤回'
};
return statusMessages[status] || status;
}
// 导入并执行商品联系人更新函数
const updateProductContacts = require('./update-product-contacts');
// 添加API接口:更新商品联系人信息
app.post('/api/products/update-contacts', async (req, res) => {
try {
await updateProductContacts();
res.json({
success: true,
code: 200,
message: '商品联系人信息更新成功'
});
} catch (error) {
console.error('更新商品联系人信息失败:', error);
res.status(500).json({
success: false,
code: 500,
message: '更新商品联系人信息失败: ' + error.message
});
}
});
// 在服务器启动前执行商品联系人更新
updateProductContacts().then(() => {
console.log('\n📦 商品联系人信息更新完成!');
}).catch(error => {
console.error('\n❌ 商品联系人信息更新失败:', error.message);
}).finally(() => {
// 无论更新成功与否,都启动服务器
// 启动服务器监听 - 使用配置好的http server对象
// 监听0.0.0.0以允许通过所有网络接口访问(包括IPv4地址)
server.listen(PORT, '0.0.0.0', () => {
console.log(`\n🚀 服务器启动成功,监听端口 ${PORT}`);
console.log(`API 服务地址: http://localhost:${PORT}`);
console.log(`API 通过IP访问地址: http://192.168.0.98:${PORT}`);
console.log(`服务器最大连接数限制: ${server.maxConnections}`);
});
});
// 导出模型和Express应用供其他模块使用
module.exports = {
User,
Product,
CartItem,
sequelize,
createUserAssociations,
app,
PORT
};