// 时间处理辅助函数 - 获取当前时间
// 由于Sequelize已设置时区为+08:00,不需要手动加8小时
function getCurrentTime ( ) {
return new Date ( ) ;
}
function getCurrentTimeISOString ( ) {
return getCurrentTime ( ) . toISOString ( ) ;
}
function getCurrentTimeTimestamp ( ) {
return getCurrentTime ( ) . getTime ( ) ;
}
// 为保持向后兼容,保留原函数名
const getBeijingTime = getCurrentTime ;
const getBeijingTimeISOString = getCurrentTimeISOString ;
const getBeijingTimeTimestamp = getCurrentTimeTimestamp ;
// 类型处理辅助函数 - 添加于修复聊天功能
function ensureStringId ( id ) {
return String ( id ) . trim ( ) ;
}
function validateUserId ( userId ) {
if ( ! userId || userId === 0 || userId === '0' ) {
throw new Error ( '无效的userId: 不能为空或为0' ) ;
}
if ( typeof userId !== 'string' ) {
console . warn ( '警告: userId应该是字符串类型,当前类型:' , typeof userId , '值:' , userId ) ;
return String ( userId ) . trim ( ) ;
}
return userId . trim ( ) ;
}
function validateManagerId ( managerId ) {
if ( ! managerId || managerId === 0 || managerId === '0' || managerId === 'user' ) {
throw new Error ( '无效的managerId: 不能为空、为0或为"user"' ) ;
}
// 只允许数字类型的客服ID
if ( ! /^\d+$/ . test ( String ( managerId ) ) ) {
throw new Error ( '无效的managerId: 必须是数字类型' ) ;
}
// 确保managerId也是字符串类型
return String ( managerId ) . trim ( ) ;
}
// 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' ) ;
const WebSocket = require ( 'ws' ) ;
require ( 'dotenv' ) . config ( ) ;
// 创建Express应用
const app = express ( ) ;
const PORT = process . env . PORT || 3003 ;
// 配置HTTP服务器连接限制
const http = require ( 'http' ) ;
const server = http . createServer ( app ) ;
// 创建WebSocket服务器
const wss = new WebSocket . Server ( { server } ) ;
// 连接管理器 - 存储所有活跃的WebSocket连接
const connections = new Map ( ) ;
// 用户在线状态管理器
const onlineUsers = new Map ( ) ; // 存储用户ID到连接的映射
const onlineManagers = new Map ( ) ; // 存储客服ID到连接的映射
// 配置连接管理
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 ( ) ) ;
// 添加CORS头,解决跨域问题
app . use ( ( req , res , next ) => {
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
res . setHeader ( 'Access-Control-Allow-Methods' , 'GET, POST, PUT, DELETE, OPTIONS' ) ;
res . setHeader ( 'Access-Control-Allow-Headers' , 'Content-Type, Authorization' ) ;
if ( req . method === 'OPTIONS' ) {
return res . sendStatus ( 200 ) ;
}
next ( ) ;
} ) ;
// 测试接口 - 用于验证请求是否到达后端
app . get ( '/api/test' , ( req , res ) => {
res . json ( {
success : true ,
message : '后端服务正常运行' ,
timestamp : new Date ( ) . toISOString ( ) ,
headers : req . headers
} ) ;
} ) ;
// 测试POST接口
app . post ( '/api/test/post' , ( req , res ) => {
console . log ( '===== 测试POST接口被调用 =====' ) ;
console . log ( '1. 收到请求体:' , JSON . stringify ( req . body , null , 2 ) ) ;
console . log ( '2. 请求头:' , req . headers ) ;
console . log ( '================================' ) ;
res . json ( {
success : true ,
message : 'POST请求成功接收' ,
receivedData : req . body ,
timestamp : new Date ( ) . toISOString ( )
} ) ;
} ) ;
// 创建临时文件夹用于存储上传的文件
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 ) => {
// 使用统一的时间处理函数获取当前时间
const beijingTime = getBeijingTime ( ) ;
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数据库连接配置 - 为不同数据源创建独立连接
// 1. wechat_app数据源连接
const wechatAppSequelize = new Sequelize (
'wechat_app' ,
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
}
) ;
// 2. userlogin数据源连接
const userLoginSequelize = new Sequelize (
'userlogin' ,
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
}
) ;
// 为保持兼容性,保留默认sequelize引用(指向wechat_app)
const sequelize = wechatAppSequelize ;
// 定义会话模型
const ChatConversation = sequelize . define ( 'ChatConversation' , {
id : {
type : DataTypes . INTEGER ,
primaryKey : true ,
autoIncrement : true
} ,
conversation_id : {
type : DataTypes . STRING ,
allowNull : false ,
unique : true
} ,
userId : {
type : DataTypes . STRING ,
allowNull : false
} ,
managerId : {
type : DataTypes . STRING ,
allowNull : false
} ,
last_message : {
type : DataTypes . TEXT
} ,
last_message_time : {
type : DataTypes . DATE
} ,
unread_count : {
type : DataTypes . INTEGER ,
defaultValue : 0
} ,
cs_unread_count : {
type : DataTypes . INTEGER ,
defaultValue : 0
} ,
status : {
type : DataTypes . INTEGER ,
defaultValue : 1
} ,
user_online : {
type : DataTypes . INTEGER ,
defaultValue : 0
} ,
cs_online : {
type : DataTypes . INTEGER ,
defaultValue : 0
} ,
created_at : {
type : DataTypes . DATE
} ,
updated_at : {
type : DataTypes . DATE
}
} , {
tableName : 'chat_conversations' ,
timestamps : false
} ) ;
// 定义消息模型
const ChatMessage = sequelize . define ( 'ChatMessage' , {
message_id : {
type : DataTypes . STRING ,
primaryKey : true
} ,
conversation_id : {
type : DataTypes . STRING ,
allowNull : false
} ,
sender_type : {
type : DataTypes . INTEGER ,
allowNull : false
} ,
sender_id : {
type : DataTypes . STRING ,
allowNull : false
} ,
receiver_id : {
type : DataTypes . STRING ,
allowNull : false
} ,
content_type : {
type : DataTypes . INTEGER ,
allowNull : false
} ,
content : {
type : DataTypes . TEXT ,
allowNull : false
} ,
file_url : {
type : DataTypes . STRING
} ,
file_size : {
type : DataTypes . INTEGER
} ,
duration : {
type : DataTypes . INTEGER
} ,
is_read : {
type : DataTypes . INTEGER ,
defaultValue : 0
} ,
status : {
type : DataTypes . INTEGER ,
defaultValue : 1
} ,
created_at : {
type : DataTypes . DATE ,
defaultValue : Sequelize . NOW
} ,
updated_at : {
type : DataTypes . DATE ,
defaultValue : Sequelize . NOW
}
} , {
tableName : 'chat_messages' ,
timestamps : false
} ) ;
// 微信小程序配置
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 ( ) ;
// 获取用户会话列表接口
app . get ( '/api/conversations/user/:userId' , async ( req , res ) => {
try {
const { userId } = req . params ;
console . log ( ` 获取用户 ${ userId } 的会话列表 ` ) ;
// 从数据库获取用户的所有会话
const conversations = await ChatConversation . findAll ( {
where : {
userId : userId
} ,
order : [ [ Sequelize . literal ( 'last_message_time' ) , 'DESC' ] ]
} ) ;
console . log ( ` 找到 ${ conversations . length } 个会话 ` ) ;
// 格式化响应数据
const formattedConversations = conversations . map ( conv => {
// 获取最后消息的时间
const lastMessageTime = conv . last_message_time ? new Date ( conv . last_message_time ) : null ;
return {
conversation_id : conv . conversation_id ,
user_id : conv . userId ,
manager_id : conv . managerId ,
last_message : conv . last_message || '' ,
last_message_time : lastMessageTime ? new Date ( lastMessageTime . getTime ( ) + 8 * 60 * 60 * 1000 ) . toISOString ( ) : null ,
unread_count : conv . unread_count || 0 ,
status : conv . status
} ;
} ) ;
res . json ( {
success : true ,
code : 200 ,
message : '获取会话列表成功' ,
data : formattedConversations
} ) ;
} catch ( error ) {
console . error ( '获取会话列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取会话列表失败' ,
error : error . message
} ) ;
}
} ) ;
// 定义数据模型
// 用户模型
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
} ,
name : {
type : DataTypes . STRING ( 255 ) ,
allowNull : true , // 昵称,可选
comment : '昵称'
} ,
avatarUrl : {
type : DataTypes . TEXT
} ,
nickName : {
type : DataTypes . STRING ( 100 ) ,
allowNull : false , // 数据库NOT NULL: 联系人
comment : '联系人'
} ,
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 : false , // 数据库NOT NULL: 省份
comment : '省份'
} ,
city : {
type : DataTypes . STRING ( 50 ) ,
allowNull : false , // 数据库NOT NULL: 城市
comment : '城市'
} ,
district : {
type : DataTypes . STRING ( 255 ) ,
allowNull : false , // 数据库NOT NULL: 区域
comment : '区域'
} ,
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 , // 数据库NOT NULL: 合作商身份
comment : '合作商身份'
} ,
cooperation : {
type : DataTypes . STRING ( 255 ) ,
allowNull : false , // 数据库NOT NULL: 合作模式
comment : '合作模式'
} ,
businesslicenseurl : {
type : DataTypes . TEXT ,
allowNull : false , // 数据库NOT NULL: 营业执照
comment : '营业执照'
} ,
proofurl : {
type : DataTypes . TEXT ,
allowNull : false , // 数据库NOT NULL: 证明材料
comment : '证明材料:鸡场--动物检疫合格证,贸易商--法人身份证'
} ,
brandurl : {
type : DataTypes . TEXT , // 品牌授权链文件(可为空)
comment : '品牌授权链文件'
} ,
// 合作状态相关字段
partnerstatus : {
type : DataTypes . STRING ( 255 ) // 合作商状态
} ,
// 授权区域字段 - 用于存储用户位置信息
authorized_region : {
type : DataTypes . TEXT // 存储用户位置信息的JSON字符串
} ,
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 : '已有几人想要'
} ,
// 新增查看次数字段
frequency : {
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
} ,
sourceType : {
type : DataTypes . STRING ( 50 ) ,
allowNull : true ,
comment : '货源类型'
} ,
supplyStatus : {
type : DataTypes . STRING ( 50 ) ,
allowNull : true ,
comment : '供应状态'
} ,
category : {
type : DataTypes . STRING ( 50 ) ,
allowNull : true ,
comment : '商品种类'
} ,
label : {
type : DataTypes . INTEGER ,
defaultValue : 0 ,
comment : '下架标识,1表示已下架'
} ,
description : {
type : DataTypes . TEXT ,
allowNull : true ,
comment : '货源描述'
} ,
// 产品日志字段,用于记录价格变更等信息
product_log : {
type : DataTypes . TEXT ,
allowNull : true ,
comment : '产品日志' ,
get ( ) {
const value = this . getDataValue ( 'product_log' ) ;
return value ? JSON . parse ( value ) : [ ] ;
} ,
set ( value ) {
// 检查value是否已经是JSON字符串,如果是则直接使用,否则序列化
if ( typeof value === 'string' && ( value . startsWith ( '[' ) || value . startsWith ( '{' ) ) ) {
// 已经是JSON字符串,直接保存
this . setDataValue ( 'product_log' , value ) ;
} else {
// 需要序列化
this . setDataValue ( 'product_log' , JSON . stringify ( value ) ) ;
}
}
}
} , {
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 Favorite extends Model { }
Favorite . init ( {
id : {
type : DataTypes . INTEGER ,
autoIncrement : true ,
primaryKey : true
} ,
user_phone : {
type : DataTypes . STRING ( 20 ) ,
allowNull : false
} ,
productId : {
type : DataTypes . STRING ( 100 ) ,
allowNull : false
} ,
date : {
type : DataTypes . DATE ,
defaultValue : Sequelize . NOW
}
} , {
sequelize ,
modelName : 'Favorite' ,
tableName : 'favorites' ,
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
} ,
name : {
type : DataTypes . STRING ( 100 ) ,
allowNull : false , // 联系人
comment : '联系人'
} ,
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
} ) ;
// 价格资源模型 - 用于估价功能(匹配实际数据库表结构)
class Resources extends Model { }
Resources . init ( {
id : {
type : DataTypes . INTEGER . UNSIGNED ,
autoIncrement : true ,
primaryKey : true
} ,
category : {
type : DataTypes . STRING ( 255 ) ,
allowNull : true ,
comment : '品种/分类'
} ,
region : {
type : DataTypes . STRING ( 255 ) ,
allowNull : true ,
comment : '地区'
} ,
price1 : {
type : DataTypes . STRING ( 255 ) ,
allowNull : true ,
comment : '商品价格上限'
} ,
price2 : {
type : DataTypes . STRING ( 255 ) ,
allowNull : true ,
comment : '商品价格下限'
} ,
time : {
type : DataTypes . DATEONLY ,
allowNull : false ,
comment : '价格日期'
}
} , {
sequelize ,
modelName : 'Resources' ,
tableName : 'resources' ,
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' // 级联更新
} ) ;
// 收藏与商品的关系 (收藏属于商品)
Favorite . belongsTo ( Product , {
foreignKey : 'productId' ,
targetKey : 'productId' , // 目标键,使用productId字段(STRING类型)而非默认的id字段(INTEGER类型)
as : 'Product' // 别名,与收藏列表API中使用的一致
} ) ;
// 商品与收藏的一对多关系 (商品可以被多个用户收藏)
Product . hasMany ( Favorite , {
foreignKey : 'productId' ,
as : 'favorites' , // 别名,表示商品的收藏列表
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确保无论如何都只保留一条记录
const currentTime = getBeijingTime ( ) ;
await sequelize . query (
` INSERT INTO contacts (userId, name, phoneNumber, created_at, updated_at)
VALUES ( ? , ? , ? , ? , ? )
ON DUPLICATE KEY UPDATE
name = VALUES ( name ) ,
phoneNumber = VALUES ( phoneNumber ) ,
updated_at = ? ` ,
{
replacements : [ user . userId , user . name || '默认联系人' , user . phoneNumber || '' , currentTime , currentTime , currentTime ] ,
transaction : transaction
}
) ;
console . log ( '联系人记录已处理(创建或更新):' , user . userId ) ;
// 2. 处理用户管理记录 - 使用相同策略
await sequelize . query (
` INSERT INTO usermanagements (userId, created_at, updated_at)
VALUES ( ? , ? , ? )
ON DUPLICATE KEY UPDATE
updated_at = ? ` ,
{
replacements : [ user . userId , currentTime , currentTime , currentTime ] ,
transaction : transaction
}
) ;
console . log ( '用户管理记录已处理(创建或更新):' , user . userId ) ;
} ) ;
console . log ( '用户关联记录处理成功:' , user . userId ) ;
return true ;
} catch ( error ) {
console . error ( '创建用户关联记录失败:' , error . message ) ;
return false ;
}
}
// API路由
// 专门用于更新用户位置信息的API端点
app . post ( '/api/user/update-location' , async ( req , res ) => {
try {
const { openid , latitude , longitude , address } = req . body ;
if ( ! openid ) {
return res . json ( { success : false , message : '缺少openid' } ) ;
}
if ( ! latitude || ! longitude ) {
return res . json ( { success : false , message : '缺少位置信息' } ) ;
}
const locationData = { latitude , longitude } ;
const locationJson = JSON . stringify ( locationData ) ;
console . log ( '更新用户位置信息:' , { openid , locationJson } ) ;
// 准备要更新的数据
const updateData = {
updated_at : getBeijingTime ( )
} ;
// 如果有地址信息,将地址存储到authorized_region字段
if ( address ) {
updateData . authorized_region = address ;
} else {
// 如果没有地址信息,才存储经纬度
updateData . authorized_region = locationJson ;
}
const updateResult = await User . update (
updateData ,
{ where : { openid } }
) ;
console . log ( '位置更新结果:' , updateResult ) ;
if ( updateResult [ 0 ] > 0 ) {
// 查询更新后的用户数据
const updatedUser = await User . findOne ( {
where : { openid } ,
attributes : [ 'authorized_region' , 'detailedaddress' ]
} ) ;
console . log ( '更新后的位置信息:' , updatedUser . authorized_region ) ;
console . log ( '更新后的地址信息:' , updatedUser . detailedaddress ) ;
return res . json ( {
success : true ,
message : '位置和地址信息更新成功' ,
data : {
authorized_region : updatedUser . authorized_region ,
address : updatedUser . detailedaddress
}
} ) ;
} else {
return res . json ( { success : false , message : '用户不存在或位置信息未更新' } ) ;
}
} catch ( error ) {
console . error ( '更新位置信息失败:' , error ) ;
res . json ( { success : false , message : '更新位置信息失败' , error : error . message } ) ;
}
} ) ;
// 上传用户信息
app . post ( '/api/user/upload' , async ( req , res ) => {
try {
const userData = req . body ;
console . log ( '收到用户信息上传请求:' , userData ) ;
// 使用微信小程序拉取唯一电话号码的插件,不再需要检查手机号冲突
// 检查用户是否已存在,如果是更新操作,不需要强制要求手机号
let existingUser = await User . findOne ( {
where : { openid : userData . openid }
} ) ;
// 只有在创建新用户时才强制要求手机号
if ( ! existingUser && ! userData . phoneNumber ) {
return res . json ( {
success : false ,
code : 400 ,
message : '创建新用户时必须提供手机号信息' ,
data : { }
} ) ;
}
// 查找用户是否已存在
let user = await User . findOne ( {
where : { openid : userData . openid }
} ) ;
// 详细日志记录收到的位置数据
console . log ( '处理位置数据前:' , {
authorized_region : userData . authorized_region ,
type : typeof userData . authorized_region ,
isObject : typeof userData . authorized_region === 'object' ,
hasLatLng : userData . authorized_region && ( userData . authorized_region . latitude || userData . authorized_region . longitude )
} ) ;
if ( user ) {
// 更新用户信息,确保authorized_region字段正确处理
const updateData = { ... userData , updated_at : getBeijingTime ( ) } ;
// 特别处理authorized_region字段,只有当它是对象时才转换为JSON字符串
if ( updateData . authorized_region && typeof updateData . authorized_region === 'object' ) {
updateData . authorized_region = JSON . stringify ( updateData . authorized_region ) ;
} else if ( updateData . authorized_region === null || updateData . authorized_region === undefined ) {
// 如果是null或undefined,设置为空字符串
updateData . authorized_region = '' ;
} else if ( typeof updateData . authorized_region === 'string' && updateData . authorized_region . trim ( ) === '' ) {
// 如果是空字符串,保持为空字符串
updateData . authorized_region = '' ;
} else if ( typeof updateData . authorized_region === 'string' ) {
// 如果已经是字符串,保持不变
console . log ( 'authorized_region已经是字符串,无需转换:' , updateData . authorized_region ) ;
}
console . log ( '更新用户数据:' , { authorized_region : updateData . authorized_region } ) ;
await User . update ( updateData , {
where : { openid : userData . openid }
} ) ;
user = await User . findOne ( { where : { openid : userData . openid } } ) ;
console . log ( '更新后用户数据:' , { authorized_region : user . authorized_region } ) ;
// 使用统一的关联记录创建函数
await createUserAssociations ( user ) ;
} else {
// 创建新用户,确保authorized_region字段正确处理
const createData = {
... userData ,
notice : 'new' , // 创建用户时固定设置notice为new
created_at : getBeijingTime ( ) ,
updated_at : getBeijingTime ( )
} ;
// 特别处理authorized_region字段,只有当它是对象时才转换为JSON字符串
if ( createData . authorized_region && typeof createData . authorized_region === 'object' ) {
createData . authorized_region = JSON . stringify ( createData . authorized_region ) ;
} else if ( createData . authorized_region === null || createData . authorized_region === undefined ) {
// 如果是null或undefined,设置为空字符串
createData . authorized_region = '' ;
} else if ( typeof createData . authorized_region === 'string' && createData . authorized_region . trim ( ) === '' ) {
// 如果是空字符串,保持为空字符串
createData . authorized_region = '' ;
} else if ( typeof createData . authorized_region === 'string' ) {
// 如果已经是字符串,保持不变
console . log ( 'authorized_region已经是字符串,无需转换:' , createData . authorized_region ) ;
}
console . log ( '创建用户数据:' , { authorized_region : createData . authorized_region } ) ;
user = await User . create ( createData ) ;
console . log ( '创建后用户数据:' , { authorized_region : user . authorized_region } ) ;
// 使用统一的关联记录创建函数
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 : getBeijingTime ( )
} ,
{
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 : getBeijingTime ( )
} ,
{
where : { openid }
}
) ;
} else {
// 创建新用户
// 支持从客户端传入type参数,如果没有则默认为buyer
const userType = req . body . type || 'buyer' ;
await User . create ( {
openid ,
userId ,
session_key ,
name : '微信用户' , // 临时占位,等待用户授权
nickName : '微信用户' , // 数据库NOT NULL字段
phoneNumber : '' , // 使用空字符串代替临时手机号,后续由微信小程序拉取的真实手机号更新
type : userType , // 使用客户端传入的类型或默认买家身份
province : '' , // 默认空字符串
city : '' , // 默认空字符串
district : '' , // 默认空字符串
proofurl : '' , // 默认空字符串
collaborationid : '' , // 默认空字符串
cooperation : '' , // 默认空字符串
businesslicenseurl : '' , // 默认空字符串
notice : 'new' , // 创建用户时固定设置notice为new
created_at : getBeijingTime ( ) ,
updated_at : getBeijingTime ( )
} ) ;
// 为新创建的用户创建关联记录
const newUser = { userId , openid , name : '微信用户' , 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' , 'name' , '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' , 'name' , '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 : getBeijingTime ( )
} ,
{
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 . get ( '/api/product/categories' , async ( req , res ) => {
try {
const { openid } = req . query ;
console . log ( '获取商品分类列表, openid:' , openid || '未提供' ) ;
// 使用 Sequelize 的 distinct 查询获取不重复的分类
const products = await Product . findAll ( {
attributes : [
[ Sequelize . col ( 'category' ) , 'category' ]
] ,
where : {
category : {
[ Sequelize . Op . ne ] : null ,
[ Sequelize . Op . ne ] : ''
}
} ,
group : [ 'category' ]
} ) ;
// 提取分类数组
const categories = products . map ( p => p . category ) . filter ( c => c ) ;
console . log ( '获取到的分类列表:' , categories ) ;
res . json ( {
success : true ,
categories : [ '全部' , ... categories ] // 添加"全部"选项
} ) ;
} catch ( error ) {
console . error ( '获取分类列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
message : '获取分类列表失败: ' + error . message
} ) ;
}
} ) ;
// 获取商品列表 - 优化版本确保状态筛选正确应用
app . post ( '/api/product/list' , async ( req , res ) => {
try {
const { openid , status , keyword , category , 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 [ Sequelize . Op . or ] = [
{ productName : { [ Sequelize . Op . like ] : ` % ${ keyword } % ` } } ,
{ specification : { [ Sequelize . Op . like ] : ` % ${ keyword } % ` } } ,
{ region : { [ Sequelize . Op . like ] : ` % ${ keyword } % ` } } ,
{ grossWeight : { [ Sequelize . Op . like ] : ` % ${ keyword } % ` } } ,
{ yolk : { [ Sequelize . Op . like ] : ` % ${ keyword } % ` } }
] ;
}
// 分类筛选
if ( category ) {
where . category = { [ Sequelize . Op . eq ] : category } ;
}
// 计算偏移量
const offset = ( page - 1 ) * pageSize ;
// 查询商品列表 - 直接使用Product表中的reservedCount字段
const { count , rows : products } = await Product . findAndCountAll ( {
where ,
include : [
{
model : User ,
as : 'seller' ,
attributes : [ 'userId' , 'name' , 'nickName' , 'avatarUrl' ]
}
] ,
attributes : [
'id' ,
'productId' ,
'sellerId' ,
'productName' ,
'price' ,
'costprice' ,
'quantity' ,
'grossWeight' ,
'yolk' ,
'specification' ,
'created_at' ,
'updated_at' ,
'imageUrls' ,
'status' ,
'region' ,
'sourceType' ,
'supplyStatus' ,
'category' ,
'producting' ,
'description' ,
'frequency'
] ,
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 = await Promise . all ( products . map ( async product => {
const productJSON = product . toJSON ( ) ;
// 确保created_at字段存在并转换为正确格式
if ( ! productJSON . created_at ) {
console . log ( '商品缺少created_at字段,使用默认值' ) ;
productJSON . created_at = getBeijingTimeISOString ( ) ;
} 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 ) ;
// 查询该商品的收藏人数 - 从favorites表中统计
const favoriteCount = await Favorite . count ( {
where : {
productId : productJSON . productId
}
} ) ;
// 使用查询到的收藏人数更新reservedCount字段
productJSON . reservedCount = favoriteCount ;
// 重要修复:反序列化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 = [ ] ;
}
// 确保seller对象的nickName字段正确返回
if ( productJSON . seller ) {
// 确保seller对象结构正确,处理nickName字段
console . log ( 'seller对象各字段值:' ) ;
console . log ( '- seller.userId:' , productJSON . seller . userId ) ;
console . log ( '- seller.nickName:' , productJSON . seller . nickName ) ;
console . log ( '- seller.name:' , productJSON . seller . name ) ;
// 按照用户要求,只使用users表里的nickName
const nickName = productJSON . seller . nickName || productJSON . seller . name || '未知' ;
console . log ( '最终确定的nickName:' , nickName ) ;
productJSON . seller = {
... productJSON . seller ,
// 只使用nickName字段
nickName : nickName ,
sellerNickName : nickName , // 确保sellerNickName字段存在
sellerName : nickName
} ;
} else {
// 如果没有seller对象,创建默认对象
productJSON . seller = {
nickName : '未知' ,
sellerNickName : '未知' ,
sellerName : '未知'
} ;
}
// 确保created_at字段存在
console . log ( 'productJSON.created_at:' , productJSON . created_at ) ;
if ( ! productJSON . created_at ) {
console . warn ( '商品缺少created_at字段,使用默认值' ) ;
productJSON . created_at = getBeijingTimeISOString ( ) ;
}
// 记录第一个商品的转换信息用于调试
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 ) ;
console . log ( '- seller信息:' , JSON . stringify ( productJSON . seller ) ) ;
}
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 ( getBeijingTimeTimestamp ( ) - 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 : getBeijingTime ( ) ,
// 【重要修复】确保保存会话ID
sessionId : sessionId || existingProduct . dataValues . sessionId ,
uploadSessionId : sessionId || existingProduct . dataValues . uploadSessionId ,
// 标记多图片
hasMultipleImages : mergedImageUrls . length > 1 ,
totalImages : mergedImageUrls . length ,
// 确保保留原始的创建时间
created_at : existingProduct . dataValues . created_at || getBeijingTime ( )
} ;
// ========== 【新增】保存前的地区字段验证 ==========
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 : getBeijingTime ( ) ,
updated_at : getBeijingTime ( )
} ;
// ========== 【新增】保存前的地区字段验证 ==========
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' , 'name' , '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
} ) ;
// 更新用户类型:如果字段为空则填入seller,如果为buyer则修改为both
// 只在创建新商品时执行,不更新商品时执行
if ( ! isUpdate ) {
try {
// 获取当前用户信息
const currentUser = await User . findOne ( { where : { userId : actualSellerId } } ) ;
if ( currentUser ) {
console . log ( '更新用户类型前 - 当前用户类型:' , currentUser . type , '用户ID:' , currentUser . userId ) ;
// 检查用户类型并根据需求更新
if ( ( currentUser . type === null || currentUser . type === undefined || currentUser . type === '' ) || currentUser . type === 'buyer' ) {
let newType = '' ;
if ( currentUser . type === 'buyer' ) {
newType = 'both' ;
} else {
newType = 'seller' ;
}
// 更新用户类型
await User . update (
{ type : newType } ,
{ where : { userId : actualSellerId } }
) ;
console . log ( '用户类型更新成功 - 用户ID:' , currentUser . userId , '旧类型:' , currentUser . type , '新类型:' , newType ) ;
} else {
console . log ( '不需要更新用户类型 - 用户ID:' , currentUser . userId , '当前类型:' , currentUser . type ) ;
}
}
} catch ( updateError ) {
console . error ( '更新用户类型失败:' , updateError ) ;
// 不影响商品发布结果,仅记录错误
}
}
// 发送最终增强的响应
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 : getBeijingTime ( ) ,
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
} ) ;
}
}
// 其他路由...
// 收藏相关API端点
// 添加收藏
app . post ( '/api/favorites/add' , async ( req , res ) => {
try {
const { user_phone , productId } = req . body ;
console . log ( '收到添加收藏请求:' , { user_phone , productId } ) ;
// 验证参数
if ( ! user_phone || ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少必要参数' ,
data : { user_phone , productId }
} ) ;
}
// 检查是否已存在收藏记录
const existingFavorite = await Favorite . findOne ( {
where : {
user_phone ,
productId
}
} ) ;
if ( existingFavorite ) {
return res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '已经收藏过该商品' ,
data : existingFavorite
} ) ;
}
// 创建新的收藏记录
const newFavorite = await Favorite . create ( {
user_phone ,
productId ,
date : req . body . date || getBeijingTime ( ) // 使用前端传递的时间或当前UTC+8时间
} ) ;
console . log ( '收藏添加成功:' , newFavorite ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '收藏添加成功' ,
data : newFavorite
} ) ;
} catch ( error ) {
console . error ( '添加收藏失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '添加收藏失败' ,
error : error . message
} ) ;
}
} ) ;
// 取消收藏
app . post ( '/api/favorites/remove' , async ( req , res ) => {
try {
const { user_phone , productId } = req . body ;
console . log ( '收到取消收藏请求:' , { user_phone , productId } ) ;
// 验证参数
if ( ! user_phone || ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少必要参数'
} ) ;
}
// 删除收藏记录
const result = await Favorite . destroy ( {
where : {
user_phone ,
productId
}
} ) ;
if ( result === 0 ) {
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '收藏记录不存在'
} ) ;
}
console . log ( '收藏取消成功:' , { user_phone , productId } ) ;
res . status ( 200 ) . 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/favorites/list' , async ( req , res ) => {
try {
const { user_phone } = req . body ;
console . log ( '收到获取收藏列表请求:' , { user_phone } ) ;
// 验证参数
if ( ! user_phone ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少必要参数'
} ) ;
}
// 获取收藏列表,并关联查询商品信息
const favorites = await Favorite . findAll ( {
where : { user_phone } ,
include : [
{
model : Product ,
as : 'Product' , // 与关联定义中的别名一致
attributes : [ 'productId' , 'productName' , 'price' , 'quantity' , 'grossWeight' , 'imageUrls' , 'created_at' , 'specification' , 'yolk' , 'sourceType' , 'supplyStatus' ] ,
required : false // 使用LEFT JOIN,即使商品不存在也会返回收藏记录
}
] ,
order : [ [ 'date' , 'DESC' ] ]
} ) ;
console . log ( '获取收藏列表成功,数量:' , favorites . length ) ;
// 处理图片URL,确保是数组格式(与商品详情API保持一致的处理逻辑)
const processedFavorites = favorites . map ( favorite => {
const favoriteJSON = favorite . toJSON ( ) ;
if ( favoriteJSON . Product ) {
// 关键修复:将存储在数据库中的JSON字符串反序列化为JavaScript数组
if ( favoriteJSON . Product . imageUrls ) {
if ( typeof favoriteJSON . Product . imageUrls === 'string' ) {
console . log ( '【关键修复】将数据库中的JSON字符串反序列化为JavaScript数组' ) ;
try {
favoriteJSON . Product . imageUrls = JSON . parse ( favoriteJSON . Product . imageUrls ) ;
console . log ( '反序列化后的imageUrls类型:' , typeof favoriteJSON . Product . imageUrls ) ;
} catch ( parseError ) {
console . error ( '反序列化imageUrls失败:' , parseError ) ;
// 如果解析失败,使用空数组确保前端不会崩溃
favoriteJSON . Product . imageUrls = [ ] ;
}
} else if ( ! Array . isArray ( favoriteJSON . Product . imageUrls ) ) {
// 如果不是数组类型,也转换为空数组
favoriteJSON . Product . imageUrls = [ ] ;
}
} else {
// 确保imageUrls始终是数组
favoriteJSON . Product . imageUrls = [ ] ;
}
}
return favoriteJSON ;
} ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '获取收藏列表成功' ,
data : {
favorites : processedFavorites ,
count : processedFavorites . length
}
} ) ;
} catch ( error ) {
console . error ( '获取收藏列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取收藏列表失败' ,
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/increment-frequency' , async ( req , res ) => {
try {
const { productId } = req . body ;
if ( ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少productId参数'
} ) ;
}
// 增加商品查看次数
const result = await Product . increment ( 'frequency' , {
where : {
productId ,
status : { [ Sequelize . Op . not ] : 'hidden' }
}
} ) ;
console . log ( 'Product.increment result:' , result ) ;
console . log ( 'Product.increment result[0]:' , result [ 0 ] ) ;
console . log ( 'Product.increment result type:' , typeof result ) ;
// 检查是否找到并更新了商品
let updatedCount = 0 ;
if ( Array . isArray ( result ) ) {
updatedCount = result [ 0 ] || 0 ;
} else if ( result && result [ Sequelize . Op . increment ] ) {
// 处理Sequelize 6+的返回格式
updatedCount = result [ Sequelize . Op . increment ] || 0 ;
}
console . log ( 'Updated count:' , updatedCount ) ;
if ( updatedCount === 0 ) {
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '商品不存在或已被隐藏'
} ) ;
}
// 查询更新后的商品信息
console . log ( '查询更新后的商品信息,productId:' , productId ) ;
const updatedProduct = await Product . findOne ( {
attributes : [ 'productId' , 'productName' , 'frequency' ] ,
where : { productId }
} ) ;
console . log ( '查询到的更新后商品信息:' , updatedProduct ) ;
if ( ! updatedProduct ) {
// 这种情况不应该发生,因为我们已经检查了increment操作是否成功
return res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '服务器错误,无法获取更新后的商品信息'
} ) ;
}
return res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '商品查看次数增加成功' ,
data : {
productId : updatedProduct . productId ,
productName : updatedProduct . productName ,
frequency : updatedProduct . frequency
}
} ) ;
} catch ( error ) {
console . error ( '增加商品查看次数失败:' , error ) ;
return res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '服务器错误,增加商品查看次数失败' ,
error : error . message
} ) ;
}
} ) ;
// 获取商品详情
app . post ( '/api/products/detail' , async ( req , res ) => {
try {
const { productId } = req . body ;
if ( ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少productId参数'
} ) ;
}
// 查询商品详情 - 排除hidden状态商品,直接使用Product表中的reservedCount字段
const product = await Product . findOne ( {
attributes : [ 'productId' , 'productName' , 'price' , 'quantity' , 'grossWeight' , 'imageUrls' , 'created_at' , 'specification' , 'yolk' , 'sourceType' , 'supplyStatus' , 'producting' , 'product_contact' , 'contact_phone' , 'region' , 'freshness' , 'costprice' , 'description' , 'frequency' , 'product_log' ] ,
where : {
productId ,
status : { [ Sequelize . Op . not ] : 'hidden' }
} ,
include : [
{
model : User ,
as : 'seller' ,
attributes : [ 'userId' , 'name' , 'avatarUrl' ]
}
]
} ) ;
if ( ! product ) {
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '商品不存在'
} ) ;
}
// 查询收藏人数 - 从favorites表中统计该商品的收藏数量
const favoriteCount = await Favorite . count ( {
where : {
productId : productId
}
} ) ;
// 对返回的商品数据进行处理
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 = [ ] ;
}
}
// 处理产品日志字段,确保返回数组格式
if ( updatedProduct . product_log ) {
console . log ( '【产品日志】原始product_log:' , updatedProduct . product_log , '类型:' , typeof updatedProduct . product_log ) ;
if ( typeof updatedProduct . product_log === 'string' ) {
try {
updatedProduct . product_log = JSON . parse ( updatedProduct . product_log ) ;
console . log ( '【产品日志】反序列化后的product_log:' , updatedProduct . product_log , '类型:' , typeof updatedProduct . product_log ) ;
// 确保是数组格式
if ( ! Array . isArray ( updatedProduct . product_log ) ) {
updatedProduct . product_log = [ updatedProduct . product_log ] ;
console . log ( '【产品日志】转换为数组:' , updatedProduct . product_log ) ;
}
} catch ( parseError ) {
console . error ( '【产品日志】反序列化失败:' , parseError ) ;
// 如果解析失败,将字符串作为单个日志条目
updatedProduct . product_log = [ updatedProduct . product_log ] ;
console . log ( '【产品日志】转换为单条日志:' , updatedProduct . product_log ) ;
}
} else if ( ! Array . isArray ( updatedProduct . product_log ) ) {
// 如果不是字符串也不是数组,转换为数组
updatedProduct . product_log = [ updatedProduct . product_log ] ;
console . log ( '【产品日志】转换为数组:' , updatedProduct . product_log ) ;
}
} else {
// 如果没有日志,返回空数组
updatedProduct . product_log = [ ] ;
console . log ( '【产品日志】没有日志,返回空数组' ) ;
}
// 详细分析毛重字段
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 ) ;
}
// 设置收藏人数 - 从favorites表统计得到
updatedProduct . reservedCount = favoriteCount ;
console . log ( '商品详情 - 最终返回的毛重值:' , updatedProduct . grossWeight , '类型:' , typeof updatedProduct . grossWeight ) ;
console . log ( '商品详情 - 返回的收藏人数:' , updatedProduct . reservedCount , '类型:' , typeof updatedProduct . reservedCount ) ;
console . log ( '商品详情 - producting字段:' , updatedProduct . producting , '类型:' , typeof updatedProduct . producting ) ;
console . log ( '商品详情 - description字段:' , updatedProduct . description , '类型:' , typeof updatedProduct . description ) ;
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 : getBeijingTime ( )
} ,
{
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 = getBeijingTime ( ) ;
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 : getBeijingTime ( ) } ,
{ 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 : getBeijingTime ( ) ,
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
} ) ;
}
} ) ;
// 下架商品 - 修改label字段为1
app . post ( '/api/products/unpublish' , async ( req , res ) => {
console . log ( '收到下架商品请求:' , req . body ) ;
try {
const { productId } = req . body ;
if ( ! productId ) {
console . error ( '下架商品失败: 缺少productId参数' ) ;
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少productId参数'
} ) ;
}
// 查找商品
const product = await Product . findOne ( {
where : { productId }
} ) ;
if ( ! product ) {
console . error ( '下架商品失败: 商品不存在' ) ;
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '商品不存在'
} ) ;
}
console . log ( '准备下架商品,productId:' , productId , ',当前label:' , product . label ) ;
// 直接使用商品实例更新label字段和status字段
product . label = 1 ;
product . status = 'sold_out' ;
product . updated_at = getBeijingTime ( ) ;
try {
await product . save ( ) ;
console . log ( '下架商品成功(使用save方法):' , { productId : product . productId , newLabel : product . label , newStatus : product . status } ) ;
} catch ( saveError ) {
console . error ( '使用save方法更新失败,尝试使用update方法:' , saveError ) ;
try {
const updateResult = await Product . update (
{ label : 1 , status : 'sold_out' , updated_at : getBeijingTime ( ) } ,
{ where : { productId } }
) ;
console . log ( '下架商品成功(使用update方法):' , { productId , updateResult } ) ;
} catch ( updateError ) {
console . error ( '使用update方法也失败:' , updateError ) ;
throw new Error ( '下架商品失败: ' + updateError . message ) ;
}
}
// 重新查询商品以确保返回最新状态
const updatedProduct = await Product . findOne ( {
where : { productId }
} ) ;
res . json ( {
success : true ,
code : 200 ,
message : '下架商品成功' ,
product : {
productId : updatedProduct . productId ,
label : updatedProduct . label
}
} ) ;
} 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 : getBeijingTimeISOString ( )
} ) ;
} 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 : getBeijingTimeISOString ( )
} ) ;
}
} ) ;
// 添加商品到购物车
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 ) {
// 添加详细的时间调试日志
const updateCurrentDate = getBeijingTime ( ) ;
const updateUtcTime = updateCurrentDate . toISOString ( ) ;
console . log ( '购物车更新 - 当前时间对象:' , updateCurrentDate ) ;
console . log ( '购物车更新 - 转换后UTC时间:' , updateUtcTime ) ;
console . log ( '购物车更新 - 时区设置:' , sequelize . options . timezone ) ;
// 已存在,更新数量
await CartItem . update (
{
quantity : existingItem . quantity + cartData . quantity ,
updated_at : getBeijingTimeISOString ( )
} ,
{
where : {
id : existingItem . id
}
}
) ;
console . log ( ` 更新购物车项成功: id= ${ existingItem . id } , 新数量= ${ existingItem . quantity + cartData . quantity } ` ) ;
// 同步更新用户表的updated_at为UTC时间
const userUtcTime = getBeijingTimeISOString ( ) ;
console . log ( '用户表更新 - UTC时间:' , userUtcTime ) ;
await User . update (
{ updated_at : userUtcTime } ,
{ where : { userId : cartData . userId } }
) ;
console . log ( ` 用户 ${ cartData . userId } 的updated_at已更新为UTC时间: ${ userUtcTime } ` ) ;
} else {
// 不存在,创建新购物车项
console . log ( '创建新购物车项,所有字段:' ) ;
// 添加详细的时间调试日志
const currentDate = getBeijingTime ( ) ;
const utcTime = getBeijingTimeISOString ( ) ;
console . log ( '购物车创建 - 当前时间对象:' , currentDate ) ;
console . log ( '购物车创建 - 转换后UTC时间:' , utcTime ) ;
console . log ( '购物车创建 - 时区设置:' , sequelize . options . timezone ) ;
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 : getBeijingTimeISOString ( )
} ) ;
console . log ( ` 创建购物车项成功: userId= ${ cartData . userId } , productId= ${ cartData . productId } ` ) ;
// 同步更新用户表的updated_at为UTC时间
const updateUserUtcTime = getBeijingTimeISOString ( )
console . log ( '用户表更新 - UTC时间:' , updateUserUtcTime ) ;
await User . update (
{ updated_at : updateUserUtcTime } ,
{ where : { userId : cartData . userId } }
) ;
console . log ( ` 用户 ${ cartData . userId } 的updated_at已更新为UTC时间: ${ updateUserUtcTime } ` ) ;
}
} 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 = getBeijingTime ( ) ;
// 如果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 : getBeijingTimeISOString ( ) ,
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 : getBeijingTimeISOString ( ) ,
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' , 'name' , 'phoneNumber' , 'type' ]
} ) ;
if ( ! user ) {
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '用户不存在' ,
debugInfo : {
searchCriteria : { openid } ,
timestamp : getBeijingTimeISOString ( )
}
} ) ;
}
// 查询该用户的商品统计信息
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 : getBeijingTime ( ) . 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 , name : user . name } ) ;
// 查找商品并验证所有权 - 直接使用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 = getBeijingTime ( ) ;
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 : getBeijingTime ( ) } ,
{ 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 : getBeijingTime ( ) ,
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 , name : user . name , 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 : getBeijingTime ( ) ,
updated_at : getBeijingTime ( )
} ) ;
// 立即验证创建后的状态
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' , 'name' , '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 : getBeijingTime ( ) } ,
{ 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'
} ) ;
// ========== 【新增】调试:打印productId的类型和值 ==========
console . log ( '【调试】productId类型:' , typeof productId , '值:' , productId ) ;
console . log ( '【调试】openid类型:' , typeof openid , '值:' , openid ) ;
// ========== 调试结束 ==========
// ========== 【新增】编辑商品时的地区字段调试 ==========
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 : getBeijingTime ( )
} ;
// 【新增】更新前的最终数据验证
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 {
// 【调试】详细记录API接收到的原始数据
console . log ( '===== 入驻API调试开始 =====' ) ;
console . log ( '1. 原始请求体:' , JSON . stringify ( req . body , null , 2 ) ) ;
console . log ( '2. 请求头:' , req . headers ) ;
console . log ( '3. 请求方法:' , req . method ) ;
console . log ( '4. 请求URL:' , req . url ) ;
const { openid ,
collaborationid ,
cooperation ,
company ,
phoneNumber ,
province ,
city ,
district ,
detailedaddress ,
businesslicenseurl ,
proofurl ,
brandurl
} = req . body ;
// 【调试】验证解构后的数据值
console . log ( '5. 解构后的数据验证:' ) ;
console . log ( ' - openid:' , openid , typeof openid ) ;
console . log ( ' - collaborationid:' , collaborationid , typeof collaborationid ) ;
console . log ( ' - cooperation:' , cooperation , typeof cooperation ) ;
console . log ( ' - company:' , company , typeof company ) ;
console . log ( ' - phoneNumber:' , phoneNumber , typeof phoneNumber ) ;
console . log ( ' - province:' , province , typeof province ) ;
console . log ( ' - city:' , city , typeof city ) ;
console . log ( ' - district:' , district , typeof district ) ;
console . log ( ' - detailedaddress:' , detailedaddress , typeof detailedaddress ) ;
console . log ( ' - businesslicenseurl:' , businesslicenseurl , typeof businesslicenseurl ) ;
console . log ( ' - proofurl:' , proofurl , typeof proofurl ) ;
console . log ( ' - brandurl:' , brandurl , typeof brandurl ) ;
// 验证必填字段
if ( ! openid || ! collaborationid || ! cooperation || ! company || ! phoneNumber || ! province || ! city || ! district ) {
console . error ( '6. 必填字段验证失败:' , {
openid : ! ! openid ,
collaborationid : ! ! collaborationid ,
cooperation : ! ! cooperation ,
company : ! ! company ,
phoneNumber : ! ! phoneNumber ,
province : ! ! province ,
city : ! ! city ,
district : ! ! district
} ) ;
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '请填写完整的申请信息'
} ) ;
}
console . log ( '6. 必填字段验证通过' ) ;
// 查找用户信息
const user = await User . findOne ( { where : { openid } } ) ;
console . log ( '7. 查找用户结果:' , user ? {
userId : user . userId ,
openid : user . openid ,
name : user . name ,
collaborationid : user . collaborationid ,
cooperation : user . cooperation ,
partnerstatus : user . partnerstatus
} : '未找到用户' ) ;
if ( ! user ) {
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '用户不存在'
} ) ;
}
// 检查用户是否已有入驻信息且状态为审核中
if ( user . collaborationid && user . partnerstatus === 'underreview' ) {
console . log ( '8. 用户已有待审核的入驻申请,拒绝重复提交' ) ;
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '您已有待审核的入驻申请,请勿重复提交'
} ) ;
}
console . log ( '8. 用户状态检查通过,允许提交' ) ;
// 更新用户表中的入驻信息
// 转换collaborationid为中文(使用明确的英文标识以避免混淆)
let collaborationidCN = collaborationid ;
const collaborationidMap = {
'chicken' : '鸡场' ,
'trader' : '贸易商' ,
'wholesale' : '贸易商'
} ;
if ( collaborationidMap . hasOwnProperty ( collaborationid ) ) {
collaborationidCN = collaborationidMap [ collaborationid ] ;
} else {
// 如果传入的不是预期的英文值,直接使用传入值(可能是中文)
collaborationidCN = collaborationid ;
}
console . log ( '9. collaborationid转换结果:' , {
原值 : collaborationid ,
转换后 : collaborationidCN ,
映射表 : collaborationidMap
} ) ;
// 转换cooperation为中文合作模式(使用明确的英文标识以避免混淆)
let cooperationCN = cooperation ;
const cooperationMap = {
'resource_delegation' : '资源委托' ,
'self_define_sales' : '自主定义销售' ,
'regional_exclusive' : '区域包场合作' ,
'other' : '其他' ,
'wholesale' : '资源委托' ,
'self_define' : '自主定义销售' ,
// 添加中文值映射(前端直接传递中文值)
'代销业务' : '代销业务' ,
'采销联盟合作' : '采销联盟合作' ,
'包场合作' : '包场合作'
} ;
if ( cooperationMap . hasOwnProperty ( cooperation ) ) {
cooperationCN = cooperationMap [ cooperation ] ;
} else {
// 如果传入的不是预期的英文值,直接使用传入值(可能是中文)
cooperationCN = cooperation ;
}
console . log ( '10. cooperation转换结果:' , {
原值 : cooperation ,
转换后 : cooperationCN ,
映射表 : cooperationMap
} ) ;
// 构建更新数据对象,确保字段名称与数据库表结构完全匹配
// 特别注意:数据库中以下字段为NOT NULL约束
const updateData = {
nickName : user . nickName || String ( company || '未知联系人' ) , // 数据库NOT NULL: 联系人
collaborationid : String ( collaborationidCN || '未选择' ) , // 数据库NOT NULL: 合作商身份
cooperation : String ( cooperationCN || '未选择' ) , // 数据库NOT NULL: 合作模式
company : String ( company || '' ) , // 公司名称
phoneNumber : String ( phoneNumber || '' ) , // 电话号码
province : String ( province || '' ) , // 数据库NOT NULL: 省份
city : String ( city || '' ) , // 数据库NOT NULL: 城市
district : String ( district || '' ) , // 数据库NOT NULL: 区域
detailedaddress : String ( detailedaddress || '' ) , // 详细地址
businesslicenseurl : String ( ! businesslicenseurl || businesslicenseurl . trim ( ) === '' ? '未上传' : businesslicenseurl ) , // 数据库NOT NULL: 营业执照
proofurl : String ( ! proofurl || proofurl . trim ( ) === '' ? '未上传' : proofurl ) , // 数据库NOT NULL: 证明材料
brandurl : String ( ! brandurl || brandurl . trim ( ) === '' ? '' : brandurl ) , // 品牌授权链文件(可为空)
partnerstatus : 'underreview' , // 合作商状态明确设置为审核中
updated_at : getBeijingTime ( )
} ;
console . log ( '11. 构建的updateData对象:' , JSON . stringify ( updateData , null , 2 ) ) ;
console . log ( '12. 准备执行数据库更新,openid:' , openid ) ;
// 【调试】在更新前记录当前数据库状态
const beforeUpdateUser = await User . findOne ( { where : { openid : openid } } ) ;
console . log ( '13. 更新前数据库状态:' , beforeUpdateUser ? {
userId : beforeUpdateUser . userId ,
openid : beforeUpdateUser . openid ,
collaborationid : beforeUpdateUser . collaborationid ,
cooperation : beforeUpdateUser . cooperation ,
company : beforeUpdateUser . company ,
phoneNumber : beforeUpdateUser . phoneNumber ,
province : beforeUpdateUser . province ,
city : beforeUpdateUser . city ,
district : beforeUpdateUser . district ,
detailedaddress : beforeUpdateUser . detailedaddress ,
businesslicenseurl : beforeUpdateUser . businesslicenseurl ,
proofurl : beforeUpdateUser . proofurl ,
brandurl : beforeUpdateUser . brandurl ,
partnerstatus : beforeUpdateUser . partnerstatus ,
updated_at : beforeUpdateUser . updated_at
} : '未找到用户记录' ) ;
// 使用Sequelize的update方法更新数据
const updateResult = await User . update ( updateData , {
where : { openid : openid }
} ) ;
console . log ( '14. Sequelize更新操作结果:' , {
affectedRows : updateResult [ 0 ] ,
affectedCount : updateResult [ 1 ] ,
成功 : updateResult [ 0 ] > 0
} ) ;
if ( updateResult [ 0 ] === 0 ) {
console . error ( '15. 更新失败,未找到匹配的用户记录或没有更新任何字段' ) ;
return res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '更新用户信息失败,未找到匹配的记录'
} ) ;
}
// 【调试】在更新后记录数据库状态
const afterUpdateUser = await User . findOne ( { where : { openid : openid } } ) ;
console . log ( '16. 更新后数据库状态:' , afterUpdateUser ? {
userId : afterUpdateUser . userId ,
openid : afterUpdateUser . openid ,
collaborationid : afterUpdateUser . collaborationid ,
cooperation : afterUpdateUser . cooperation ,
company : afterUpdateUser . company ,
phoneNumber : afterUpdateUser . phoneNumber ,
province : afterUpdateUser . province ,
city : afterUpdateUser . city ,
district : afterUpdateUser . district ,
detailedaddress : afterUpdateUser . detailedaddress ,
businesslicenseurl : afterUpdateUser . businesslicenseurl ,
proofurl : afterUpdateUser . proofurl ,
brandurl : afterUpdateUser . brandurl ,
partnerstatus : afterUpdateUser . partnerstatus ,
updated_at : afterUpdateUser . updated_at
} : '未找到用户记录' ) ;
// 【调试】对比更新前后的差异
if ( beforeUpdateUser && afterUpdateUser ) {
console . log ( '17. 更新前后数据对比:' ) ;
const changedFields = { } ;
Object . keys ( updateData ) . forEach ( field => {
const beforeValue = beforeUpdateUser [ field ] ;
const afterValue = afterUpdateUser [ field ] ;
if ( beforeValue !== afterValue ) {
changedFields [ field ] = {
更新前 : beforeValue ,
更新后 : afterValue
} ;
}
} ) ;
console . log ( '18. 实际发生变化的字段:' , changedFields ) ;
}
// 验证更新是否成功
const updatedUser = await User . findOne ( { where : { openid : openid } } ) ;
console . log ( '19. 验证更新后的用户状态:' , updatedUser ? updatedUser . partnerstatus : '未找到用户' ) ;
// 双重确认:如果状态仍不是underreview,再次更新
if ( updatedUser && updatedUser . partnerstatus !== 'underreview' ) {
console . warn ( '20. 检测到状态未更新正确,执行二次更新,当前状态:' , updatedUser . partnerstatus ) ;
await User . update ( {
partnerstatus : 'underreview'
} , {
where : { openid : openid }
} ) ;
// 【调试】二次更新后的状态
const finalUpdateUser = await User . findOne ( { where : { openid : openid } } ) ;
console . log ( '21. 二次更新后的用户状态:' , finalUpdateUser ? finalUpdateUser . partnerstatus : '未找到用户' ) ;
}
console . log ( '22. 用户入驻信息更新成功,用户ID:' , user . userId ) ;
console . log ( '===== 入驻API调试结束 =====' ) ;
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
} ) ;
// 将用户的partnerstatus设置为Null
await user . update ( {
partnerstatus : null ,
reasonforfailure : 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' ) ;
const { time } = require ( 'console' ) ;
// 添加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
} ) ;
}
} ) ;
// 添加API接口:快速更新商品价格和联系人信息(不验证sellerId)
app . post ( '/api/products/quick-update' , async ( req , res ) => {
console . log ( '【快速更新】收到请求:' , req . body ) ;
try {
const { productId , price , product_contact , contact_phone } = req . body ;
if ( ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少productId参数'
} ) ;
}
// 查找商品
const product = await Product . findOne ( {
where : { productId }
} ) ;
if ( ! product ) {
console . error ( '【快速更新】商品不存在:' , productId ) ;
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '商品不存在'
} ) ;
}
console . log ( '【快速更新】找到商品:' , product . productName ) ;
// 只更新指定的字段
const updateFields = {
updated_at : getBeijingTime ( )
} ;
if ( price !== undefined ) {
updateFields . price = price ;
console . log ( '【快速更新】更新价格:' , price ) ;
}
if ( product_contact !== undefined ) {
updateFields . product_contact = product_contact ;
console . log ( '【快速更新】更新联系人:' , product_contact ) ;
}
if ( contact_phone !== undefined ) {
updateFields . contact_phone = contact_phone ;
console . log ( '【快速更新】更新联系电话:' , contact_phone ) ;
}
// 执行更新
await Product . update ( updateFields , {
where : { productId }
} ) ;
console . log ( '【快速更新】商品更新成功:' , productId ) ;
res . json ( {
success : true ,
code : 200 ,
message : '更新成功' ,
data : {
productId ,
price : updateFields . price ,
product_contact : updateFields . product_contact ,
contact_phone : updateFields . contact_phone
}
} ) ;
} catch ( error ) {
console . error ( '【快速更新】更新商品失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '更新商品失败: ' + error . message
} ) ;
}
} ) ;
// 添加API接口:更新产品日志
app . post ( '/api/products/update-log' , async ( req , res ) => {
console . log ( '【更新产品日志】收到请求:' , req . body ) ;
try {
const { productId , product_log } = req . body ;
if ( ! productId ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少productId参数'
} ) ;
}
// 查找商品
const product = await Product . findOne ( {
where : { productId }
} ) ;
if ( ! product ) {
console . error ( '【更新产品日志】商品不存在:' , productId ) ;
return res . status ( 404 ) . json ( {
success : false ,
code : 404 ,
message : '商品不存在'
} ) ;
}
console . log ( '【更新产品日志】找到商品:' , product . productName ) ;
// 更新产品日志
await Product . update ( {
product_log : product_log ,
updated_at : getBeijingTime ( )
} , {
where : { productId }
} ) ;
console . log ( '【更新产品日志】商品日志更新成功:' , productId ) ;
res . json ( {
success : true ,
code : 200 ,
message : '日志更新成功' ,
data : {
productId
}
} ) ;
} catch ( error ) {
console . error ( '【更新产品日志】更新商品日志失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '更新商品日志失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取用户会话列表
app . get ( '/api/conversations/user/:userId' , async ( req , res ) => {
try {
const userId = req . params . userId ;
const conversations = await getUserConversations ( userId ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : conversations
} ) ;
} catch ( error ) {
console . error ( '获取用户会话列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取会话列表失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取客服会话列表
app . get ( '/api/conversations/manager/:managerId' , async ( req , res ) => {
try {
const managerId = req . params . managerId ;
const conversations = await getManagerConversations ( managerId ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : conversations
} ) ;
} catch ( error ) {
console . error ( '获取客服会话列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取会话列表失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取会话历史消息
app . get ( '/api/conversations/:conversationId/messages' , async ( req , res ) => {
try {
const conversationId = req . params . conversationId ;
const page = parseInt ( req . query . page ) || 1 ;
const limit = parseInt ( req . query . limit ) || 50 ;
const offset = ( page - 1 ) * limit ;
const [ messages ] = await sequelize . query (
` SELECT * FROM chat_messages
WHERE conversation_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ? ` ,
{ replacements : [ conversationId , limit , offset ] }
) ;
// 反转顺序,使最早的消息在前
messages . reverse ( ) ;
// 获取消息总数
const [ [ totalCount ] ] = await sequelize . query (
'SELECT COUNT(*) as count FROM chat_messages WHERE conversation_id = ?' ,
{ replacements : [ conversationId ] }
) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : {
messages ,
pagination : {
page ,
limit ,
total : totalCount . count ,
totalPages : Math . ceil ( totalCount . count / limit )
}
}
} ) ;
} catch ( error ) {
console . error ( '获取历史消息失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取历史消息失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取聊天记录(兼容客户端接口)
app . post ( '/api/chat/messages' , async ( req , res ) => {
try {
const { chat_id , user_phone , before , limit = 50 } = req . body ;
console . log ( '收到聊天记录请求:' , { chat_id , user_phone , before , limit } ) ;
let query = ` SELECT * FROM chat_messages
WHERE ( sender_phone = ? AND receiver_phone = ? )
OR ( sender_phone = ? AND receiver_phone = ? ) ` ;
let replacements = [ user_phone , chat_id , chat_id , user_phone ] ;
// 如果有before参数,添加时间过滤
if ( before ) {
query += ' AND created_at < ?' ;
replacements . push ( before ) ;
}
// 按时间排序,最新的消息在前
query += ' ORDER BY created_at DESC LIMIT ?' ;
replacements . push ( parseInt ( limit ) ) ;
console . log ( '执行SQL:' , query ) ;
console . log ( '替换参数:' , replacements ) ;
const [ messages ] = await sequelize . query ( query , { replacements } ) ;
// 反转顺序,使最早的消息在前
messages . reverse ( ) ;
console . log ( '查询到的消息数量:' , messages . length ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : messages
} ) ;
} catch ( error ) {
console . error ( '获取聊天记录失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取聊天记录失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 发送聊天消息
app . post ( '/api/chat/send' , async ( req , res ) => {
try {
const { sender_phone , receiver_phone , content } = req . body ;
console . log ( '收到发送消息请求:' , { sender_phone , receiver_phone , content } ) ;
// 验证必填字段
if ( ! sender_phone || ! receiver_phone || ! content ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '发送者电话、接收者电话和消息内容不能为空'
} ) ;
}
// 插入消息到数据库
const query = ` INSERT INTO chat_messages (sender_phone, receiver_phone, content, created_at)
VALUES ( ? , ? , ? , NOW ( ) ) ` ;
const replacements = [ sender_phone , receiver_phone , content ] ;
console . log ( '执行SQL:' , query ) ;
console . log ( '替换参数:' , replacements ) ;
const [ result ] = await sequelize . query ( query , { replacements } ) ;
console . log ( '消息发送成功,插入结果:' , result ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '消息发送成功' ,
data : {
id : result . insertId ,
sender_phone ,
receiver_phone ,
content ,
created_at : new Date ( ) . toISOString ( )
}
} ) ;
} catch ( error ) {
console . error ( '发送消息失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '发送消息失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 标记消息已读
app . post ( '/api/conversations/:conversationId/read' , async ( req , res ) => {
try {
const conversationId = req . params . conversationId ;
const { userId , managerId , type } = req . body ;
const now = getBeijingTime ( ) ;
let updateField ;
if ( type === 'user' ) {
// 用户标记客服消息为已读
updateField = 'unread_count' ;
await sequelize . query (
'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2' ,
{ replacements : [ now , conversationId ] }
) ;
} else if ( type === 'manager' ) {
// 客服标记用户消息为已读
updateField = 'cs_unread_count' ;
await sequelize . query (
'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1' ,
{ replacements : [ now , conversationId ] }
) ;
} else {
throw new Error ( '无效的类型' ) ;
}
// 重置未读计数
await sequelize . query (
` UPDATE chat_conversations SET ${ updateField } = 0 WHERE conversation_id = ? ` ,
{ replacements : [ conversationId ] }
) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '已标记为已读'
} ) ;
} catch ( error ) {
console . error ( '标记已读失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '标记已读失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取在线统计信息
app . get ( '/api/online-stats' , async ( req , res ) => {
try {
const stats = getOnlineStats ( ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : stats
} ) ;
} catch ( error ) {
console . error ( '获取在线统计失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取在线统计失败: ' + error . message
} ) ;
}
} ) ;
// REST API接口 - 获取客服列表 - 支持按角色类型查询(销售员/采购员)
app . get ( '/api/managers' , async ( req , res ) => {
try {
// 获取请求参数中的角色类型(seller=销售员,buyer=采购员)
const { type } = req . query ;
console . log ( '获取客服列表请求,类型:' , type ) ;
// 根据角色类型确定查询条件
let whereClause = '' ;
let replacements = [ ] ;
if ( type === 'buyer' ) {
// 如果类型为buyer,查询所有与采购相关的职位
whereClause = 'WHERE (projectName = ? OR projectName = ? OR projectName = ?) AND phoneNumber IS NOT NULL AND phoneNumber != ""' ;
replacements = [ '采购' , '采购员' , '采购主管' ] ;
} else {
// 默认查询所有与销售相关的职位
whereClause = 'WHERE (projectName = ? OR projectName = ?) AND phoneNumber IS NOT NULL AND phoneNumber != ""' ;
replacements = [ '销售' , '销售员' ] ;
}
// 查询userlogin数据库中的personnel表,获取指定工位且有电话号码的用户
// 根据表结构选择所有需要的字段,包括新增的字段
const [ personnelData ] = await sequelize . query (
` SELECT id, managerId, managercompany, managerdepartment, organization, projectName, name, alias, phoneNumber, avatarUrl, responsible_area, label, egg_section, information FROM userlogin.personnel ${ whereClause } ORDER BY id ASC ` ,
{ replacements : replacements }
) ;
// 查询chat_online_status表获取客服在线状态(添加错误处理)
let onlineStatusData = [ ] ;
try {
const [ statusResults ] = await sequelize . query (
'SELECT userId, is_online FROM chat_online_status WHERE type = 2' , // type=2表示客服
{ type : sequelize . QueryTypes . SELECT }
) ;
onlineStatusData = statusResults || [ ] ;
} catch ( statusError ) {
console . warn ( '获取在线状态失败(可能是表不存在):' , statusError . message ) ;
// 表不存在或查询失败时,使用空数组
onlineStatusData = [ ] ;
}
// 创建在线状态映射
const onlineStatusMap = { } ;
// 检查onlineStatusData是否存在,防止undefined调用forEach
if ( Array . isArray ( onlineStatusData ) ) {
onlineStatusData . forEach ( status => {
onlineStatusMap [ status . userId ] = status . is_online === 1 ;
} ) ;
}
// 将获取的数据映射为前端需要的格式,添加online状态(综合考虑内存中的onlineManagers和数据库中的状态)
const isManagerOnline = ( id , managerId ) => {
// 转换ID为字符串以便正确比较
const stringId = String ( id ) ;
const stringManagerId = managerId ? String ( managerId ) : null ;
console . log ( ` 检查客服在线状态: id= ${ id } ( ${ stringId } ), managerId= ${ managerId } ( ${ stringManagerId } ) ` ) ;
// 首先从内存中的onlineManagers检查(实时性更好)
if ( onlineManagers && typeof onlineManagers . has === 'function' ) {
// 检查id或managerId是否在onlineManagers中
if ( onlineManagers . has ( stringId ) || ( stringManagerId && onlineManagers . has ( stringManagerId ) ) ) {
console . log ( ` 客服在线(内存检查): id= ${ id } ` ) ;
return true ;
}
}
// 其次从数据库查询结果检查
const dbStatus = onlineStatusMap [ stringId ] || ( stringManagerId && onlineStatusMap [ stringManagerId ] ) || false ;
console . log ( ` 客服在线状态(数据库): id= ${ id } , status= ${ dbStatus } ` ) ;
return dbStatus ;
} ;
const managers = personnelData . map ( ( person , index ) => ( {
id : person . id ,
managerId : person . managerId || ` PM ${ String ( index + 1 ) . padStart ( 3 , '0' ) } ` ,
managercompany : person . managercompany || '未知公司' ,
managerdepartment : person . managerdepartment || '采购部' ,
organization : person . organization || '采购组' ,
projectName : person . projectName || '采购员' ,
name : person . name || '未知' ,
alias : person . alias || person . name || '未知' ,
phoneNumber : person . phoneNumber || '' ,
avatar : person . avatarUrl || '' , // 使用表中的avatarUrl字段
avatarUrl : person . avatarUrl || '' , // 兼容前端的avatarUrl字段
egg_section : person . egg_section || '' , // 添加鸡蛋分字段
responsible_area : person . responsible_area || '' , // 添加负责区域字段
label : person . label || '' , // 添加标签字段
information : person . information || '' , // 添加个人信息字段
online : isManagerOnline ( person . id , person . managerId ) // 综合检查在线状态
} ) ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
data : managers
} ) ;
} catch ( error ) {
console . error ( '获取客服列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取客服列表失败: ' + error . message
} ) ;
}
} ) ;
// 认证处理函数
async function handleAuth ( ws , data ) {
// 详细日志记录原始认证数据
console . log ( '📱 收到认证请求:' , JSON . stringify ( data ) ) ;
// 优化认证数据提取,支持多种格式
// 1. 直接从data中提取(最简单的格式)
let managerId = data . managerId ;
let userId = data . userId ;
let type = data . type ;
let userType = data . userType ; // 明确提取userType字段
// 2. 如果没有找到,尝试从data.data中提取
if ( ! managerId && ! userId && data . data ) {
managerId = data . data . managerId ;
userId = data . data . userId ;
type = data . data . type ;
userType = data . data . userType || data . data . type ; // 从data.data中提取userType
console . log ( '🔄 从data.data中提取认证信息' ) ;
}
// 3. 兼容之前的payload逻辑
const payload = data . data || data ;
if ( ! managerId ) managerId = payload . managerId ;
if ( ! userId ) userId = payload . userId ;
if ( ! type ) type = payload . type ;
if ( ! userType ) userType = payload . userType || payload . type || 'unknown' ; // 确保userType有值
// 字符串化ID以确保类型一致性
if ( userId ) userId = String ( userId ) . trim ( ) ;
if ( managerId ) managerId = String ( managerId ) . trim ( ) ;
console . log ( '🔍 最终提取的认证信息:' , {
managerId ,
userId ,
type ,
userType ,
hasManagerId : ! ! managerId ,
hasUserId : ! ! userId ,
hasType : ! ! type ,
hasUserType : ! ! userType
} ) ;
// 确定最终的用户类型 - userType优先级高于type
const finalUserType = String ( userType ) . toLowerCase ( ) ;
const deviceInfo = payload . deviceInfo || { } ;
const connection = connections . get ( ws . connectionId ) ;
if ( ! connection ) {
ws . send ( JSON . stringify ( {
type : 'auth_error' ,
message : '连接已断开'
} ) ) ;
return ;
}
console . log ( ` 🔍 最终用户类型判断: finalUserType= ${ finalUserType } ` ) ;
// 验证用户或客服身份 - 使用finalUserType进行判断,不再重复定义userType
if ( ( finalUserType === 'user' || finalUserType . includes ( 'customer' ) ) && userId ) {
// 改进的用户认证逻辑,支持字符串ID并增加容错性
try {
// 首先尝试数据库验证
let userExists = false ;
try {
const [ existingUsers ] = await sequelize . query (
'SELECT userId FROM users WHERE userId = ? LIMIT 1' ,
{ replacements : [ userId ] }
) ;
userExists = existingUsers && existingUsers . length > 0 ;
if ( userExists ) {
console . log ( ` ✅ 用户ID验证成功: userId= ${ userId } 存在于数据库中 ` ) ;
} else {
console . log ( ` ℹ️ 用户ID在数据库中不存在: userId= ${ userId } ,但仍然允许连接 ` ) ;
}
} catch ( dbError ) {
console . warn ( ` ⚠️ 用户数据库验证失败,但继续处理认证: ${ dbError . message } ` ) ;
}
// 查询用户是否在personnel表中存在,以确定managerId
try {
const [ personnelData ] = await sequelize . query (
'SELECT id FROM userlogin.personnel WHERE userId = ? LIMIT 1' ,
{ replacements : [ userId ] }
) ;
if ( personnelData && personnelData . length > 0 ) {
console . log ( ` ✅ 用户在personnel表中存在,managerId= ${ personnelData [ 0 ] . id } ` ) ;
} else {
console . log ( ` ℹ️ 用户不在personnel表中,为普通用户 ` ) ;
}
} catch ( personnelError ) {
console . warn ( ` ⚠️ Personnel表查询失败,但继续处理认证: ${ personnelError . message } ` ) ;
}
} catch ( error ) {
console . error ( '❌ 用户验证过程中发生严重错误:' , error ) ;
// 即使出错也尝试继续,只记录错误不中断认证
}
// 继续设置连接信息,确保使用字符串ID
connection . userId = userId ;
connection . isUser = true ;
connection . userType = 'user' ;
onlineUsers . set ( userId , ws ) ;
// 尝试更新在线状态,但不中断认证流程
try {
await updateUserOnlineStatus ( userId , 1 ) ;
await updateChatOnlineStatus ( userId , 1 , ws . connectionId , deviceInfo ) ;
} catch ( statusError ) {
console . warn ( ` ⚠️ 更新在线状态失败,但认证继续: ${ statusError . message } ` ) ;
}
// 发送认证成功消息
ws . send ( JSON . stringify ( {
type : 'auth_success' ,
payload : { userId , type : 'user' }
} ) ) ;
console . log ( ` ✅ 用户认证成功: userId= ${ userId } , userType= ${ finalUserType } (已标准化为'user') ` ) ;
} else if ( finalUserType === 'manager' || finalUserType . includes ( 'customer_service' ) ) {
// 客服认证逻辑改进,增加容错性
let stringManagerId ;
if ( managerId ) {
stringManagerId = String ( managerId ) . trim ( ) ;
} else if ( userId ) {
// 如果没有提供managerId但提供了userId,尝试使用userId作为managerId
stringManagerId = String ( userId ) . trim ( ) ;
console . log ( ` ⚠️ 客服认证使用userId作为managerId: ${ stringManagerId } ` ) ;
} else {
// 缺少必要的managerId或userId
ws . send ( JSON . stringify ( {
type : 'auth_error' ,
message : '客服认证失败:缺少必要的managerId或userId'
} ) ) ;
return ;
}
// 检查managerId是否在personnel表中存在,增加容错机制
try {
let managerExists = false ;
try {
const [ existingManagers ] = await sequelize . query (
'SELECT id FROM userlogin.personnel WHERE id = ? LIMIT 1' ,
{ replacements : [ stringManagerId ] }
) ;
managerExists = existingManagers && existingManagers . length > 0 ;
if ( ! managerExists ) {
console . warn ( ` ⚠️ 客服ID在personnel表中不存在: managerId= ${ stringManagerId } ` ) ;
// 不再直接拒绝,而是允许继续连接但记录警告
}
} catch ( dbError ) {
console . warn ( ` ⚠️ 客服数据库验证失败,但继续处理认证: ${ dbError . message } ` ) ;
}
} catch ( error ) {
console . error ( '❌ 客服验证过程中发生严重错误:' , error ) ;
// 即使出错也尝试继续,只记录错误不中断认证
}
connection . managerId = stringManagerId ;
connection . isManager = true ;
connection . userType = 'manager' ; // 添加userType字段确保与其他函数兼容性
// 检查并记录添加前的状态
console . log ( ` 📝 添加客服前onlineManagers状态: has( ${ stringManagerId } )= ${ onlineManagers . has ( stringManagerId ) } ` ) ;
onlineManagers . set ( stringManagerId , ws ) ;
await updateManagerOnlineStatus ( stringManagerId , 1 ) ;
// 更新chat_online_status表
await updateChatOnlineStatus ( stringManagerId , 2 , ws . connectionId , deviceInfo ) ;
// 发送认证成功消息 - 使用字符串化的managerId确保一致
ws . send ( JSON . stringify ( {
type : 'auth_success' ,
payload : { managerId : stringManagerId , type : 'manager' }
} ) ) ;
console . log ( ` ✅ 客服认证成功: managerId= ${ stringManagerId } , userType= ${ finalUserType } (已标准化为'manager') ` ) ;
// 添加onlineManagers内容检查日志
console . log ( ` 客服 ${ stringManagerId } 已连接,onlineManagers键: ${ Array . from ( onlineManagers . keys ( ) ) . join ( ', ' ) } ` ) ;
console . log ( ` onlineManagers.has('22') = ${ onlineManagers . has ( '22' ) } ` ) ;
} else {
// 无效的认证信息
ws . send ( JSON . stringify ( {
type : 'auth_error' ,
message : '无效的认证信息'
} ) ) ;
}
}
// 更新chat_online_status表
async function updateChatOnlineStatus ( userId , type , socketId , deviceInfo ) {
try {
const now = getBeijingTime ( ) ;
// 使用INSERT ... ON DUPLICATE KEY UPDATE语法确保只更新或插入一条记录
await sequelize . query (
` INSERT INTO chat_online_status
( userId , type , socket_id , is_online , last_heartbeat , device_info , created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? )
ON DUPLICATE KEY UPDATE
socket_id = VALUES ( socket_id ) ,
is_online = VALUES ( is_online ) ,
last_heartbeat = VALUES ( last_heartbeat ) ,
device_info = VALUES ( device_info ) ,
updated_at = VALUES ( updated_at ) ` ,
{
replacements : [
userId ,
type , // 1:普通用户 2:客服
socketId ,
1 , // 在线状态
now ,
JSON . stringify ( deviceInfo ) ,
now ,
now
]
}
) ;
console . log ( ` 更新chat_online_status成功: userId= ${ userId } , type= ${ type } , socketId= ${ socketId } ` ) ;
} catch ( error ) {
console . error ( '更新chat_online_status失败:' , error ) ;
}
}
// 更新用户在线状态
async function updateUserOnlineStatus ( userId , status ) {
try {
// 更新chat_conversations表中用户的在线状态
await sequelize . query (
'UPDATE chat_conversations SET user_online = ? WHERE userId = ?' ,
{ replacements : [ status , userId ] }
) ;
// 通知相关客服用户状态变化
const conversations = await sequelize . query (
'SELECT DISTINCT managerId FROM chat_conversations WHERE userId = ?' ,
{ replacements : [ userId ] }
) ;
conversations [ 0 ] . forEach ( conv => {
const managerWs = onlineManagers . get ( conv . managerId ) ;
if ( managerWs ) {
managerWs . send ( JSON . stringify ( {
type : 'user_status_change' ,
payload : { userId , online : status === 1 }
} ) ) ;
}
} ) ;
} catch ( error ) {
console . error ( '更新用户在线状态失败:' , error ) ;
}
}
// 更新客服在线状态
async function updateManagerOnlineStatus ( managerId , status ) {
try {
// 更新chat_conversations表中客服的在线状态
await sequelize . query (
'UPDATE chat_conversations SET cs_online = ? WHERE managerId = ?' ,
{ replacements : [ status , managerId ] }
) ;
// 同步更新客服表中的在线状态 - 注释掉因为online字段不存在
// await sequelize.query(
// 'UPDATE userlogin.personnel SET online = ? WHERE id = ?',
// { replacements: [status, managerId] }
// );
// 检查并更新用户表中的type字段,确保客服用户类型为manager
// 使用独立的数据源连接进行跨库操作
try {
// 1. 使用userLoginSequelize查询personnel表获取电话号码
console . log ( ` 正在查询客服信息: managerId= ${ managerId } ` ) ;
const personnelResult = await userLoginSequelize . query (
'SELECT phoneNumber FROM personnel WHERE id = ? OR managerId = ?' ,
{ replacements : [ managerId , managerId ] , type : userLoginSequelize . QueryTypes . SELECT }
) ;
if ( personnelResult && personnelResult . length > 0 ) {
const phoneNumber = personnelResult [ 0 ] . phoneNumber ;
console . log ( ` 找到客服电话号码: ${ phoneNumber } ` ) ;
if ( phoneNumber ) {
// 2. 使用wechatAppSequelize更新users表中的type字段
console . log ( ` 准备更新用户类型: 手机号= ${ phoneNumber } ` ) ;
const updateResult = await wechatAppSequelize . query (
'UPDATE users SET type = ? WHERE phoneNumber = ? AND type = ?' ,
{ replacements : [ 'manager' , phoneNumber , 'customer' ] }
) ;
const affectedRows = updateResult [ 1 ] . affectedRows ;
if ( affectedRows > 0 ) {
console . log ( ` ✓ 成功更新用户类型: 客服ID= ${ managerId } , 手机号= ${ phoneNumber } , 用户类型从customer更新为manager ` ) ;
} else {
console . log ( ` ✓ 用户类型无需更新: 客服ID= ${ managerId } , 手机号= ${ phoneNumber } , 可能已经是manager类型 ` ) ;
}
}
} else {
console . log ( ` 未找到客服信息: managerId= ${ managerId } ` ) ;
}
} catch ( err ) {
console . error ( ` ❌ 更新用户类型失败: ${ err . message } ` ) ;
console . error ( ` 错误详情: ` , err ) ;
}
// 通知相关用户客服状态变化
const conversations = await sequelize . query (
'SELECT DISTINCT userId FROM chat_conversations WHERE managerId = ?' ,
{ replacements : [ managerId ] }
) ;
conversations [ 0 ] . forEach ( conv => {
const userWs = onlineUsers . get ( conv . userId ) ;
if ( userWs ) {
userWs . send ( JSON . stringify ( {
type : 'manager_status_change' ,
payload : { managerId , online : status === 1 }
} ) ) ;
}
} ) ;
// 通知其他客服状态变化
onlineManagers . forEach ( ( ws , id ) => {
if ( id !== managerId ) {
ws . send ( JSON . stringify ( {
type : 'manager_status_change' ,
payload : { managerId , online : status === 1 }
} ) ) ;
}
} ) ;
} catch ( error ) {
console . error ( '更新客服在线状态失败:' , error ) ;
}
}
// 添加定时检查连接状态的函数
function startConnectionMonitoring ( ) {
// 每30秒检查一次连接状态
setInterval ( async ( ) => {
try {
// 检查所有连接的活跃状态
const now = Date . now ( ) ;
connections . forEach ( ( connection , connectionId ) => {
const { ws , lastActive = now } = connection ;
// 如果超过60秒没有活动,关闭连接
if ( now - lastActive > 60000 ) {
console . log ( ` 关闭超时连接: ${ connectionId } ` ) ;
try {
ws . close ( ) ;
} catch ( e ) {
console . error ( '关闭连接失败:' , e ) ;
}
}
} ) ;
// 发送广播心跳给所有在线用户和客服
onlineUsers . forEach ( ws => {
try {
ws . send ( JSON . stringify ( { type : 'heartbeat' } ) ) ;
} catch ( e ) {
console . error ( '发送心跳失败给用户:' , e ) ;
}
} ) ;
onlineManagers . forEach ( ws => {
try {
ws . send ( JSON . stringify ( { type : 'heartbeat' } ) ) ;
} catch ( e ) {
console . error ( '发送心跳失败给客服:' , e ) ;
}
} ) ;
} catch ( error ) {
console . error ( '连接监控错误:' , error ) ;
}
} , 30000 ) ;
}
// 更新连接的最后活动时间
function updateConnectionActivity ( connectionId ) {
const connection = connections . get ( connectionId ) ;
if ( connection ) {
connection . lastActive = Date . now ( ) ;
}
}
// 获取在线统计信息
function getOnlineStats ( ) {
return {
totalConnections : connections . size ,
onlineUsers : onlineUsers . size ,
onlineManagers : onlineManagers . size ,
activeConnections : Array . from ( connections . entries ( ) ) . filter ( ( [ _ , conn ] ) => {
return ( conn . isUser && conn . userId ) || ( conn . isManager && conn . managerId ) ;
} ) . length
} ;
}
// 会话管理函数
// 创建或获取现有会话
async function createOrGetConversation ( userId , managerId ) {
// 修复: 确保ID类型一致
// 关键修复:明确区分userId和managerId,确保userId是普通用户ID,managerId是客服ID
let finalUserId = validateUserId ( userId ) ;
let finalManagerId = validateManagerId ( managerId ) ;
// 关键修复:验证managerId不是无效值(如"user")
if ( finalManagerId === 'user' || finalManagerId === '0' || ! finalManagerId ) {
console . error ( '严重错误: 尝试使用无效的managerId创建会话:' , managerId ) ;
throw new Error ( '无效的客服ID' ) ;
}
// 关键修复:验证userId不是无效值
if ( ! finalUserId || finalUserId === '0' ) {
console . error ( '严重错误: 尝试使用无效的userId创建会话:' , userId ) ;
throw new Error ( '无效的用户ID' ) ;
}
// 关键修复:确保userId和managerId不会被错误交换
// 如果userId是数字而managerId不是数字,说明参数顺序可能错误
if ( /^\d+$/ . test ( finalUserId ) && ! /^\d+$/ . test ( finalManagerId ) ) {
console . error ( '严重错误: 检测到userId和managerId可能被错误交换,userId:' , finalUserId , 'managerId:' , finalManagerId ) ;
throw new Error ( '无效的用户ID或客服ID,可能存在参数顺序错误' ) ;
}
// 设置重试参数,防止竞态条件导致的重复创建
let attempts = 0 ;
const maxAttempts = 3 ;
while ( attempts < maxAttempts ) {
try {
// 尝试查找已存在的会话 - 双向检查,避免重复创建
const [ existingConversations ] = await sequelize . query (
'SELECT * FROM chat_conversations WHERE (userId = ? AND managerId = ?) OR (userId = ? AND managerId = ?) LIMIT 1' ,
{ replacements : [ finalUserId , finalManagerId , finalManagerId , finalUserId ] }
) ;
if ( existingConversations && existingConversations . length > 0 ) {
const conversation = existingConversations [ 0 ] ;
// 如果会话已结束,重新激活
if ( conversation . status !== 1 ) {
await sequelize . query (
'UPDATE chat_conversations SET status = 1 WHERE conversation_id = ?' ,
{ replacements : [ conversation . conversation_id ] }
) ;
conversation . status = 1 ;
}
return conversation ;
}
// 创建新会话
const conversationId = crypto . randomUUID ( ) ;
const now = getBeijingTime ( ) ;
await sequelize . query (
` INSERT INTO chat_conversations
( conversation_id , userId , managerId , status , user_online , cs_online , created_at , updated_at )
VALUES ( ? , ? , ? , 1 , ? , ? , ? , ? ) ` ,
{
replacements : [
conversationId ,
finalUserId ,
finalManagerId ,
onlineUsers . has ( finalUserId ) ? 1 : 0 ,
onlineManagers . has ( finalManagerId ) ? 1 : 0 ,
now ,
now
]
}
) ;
// 返回新创建的会话
return {
conversation_id : conversationId ,
userId : finalUserId ,
managerId : finalManagerId ,
status : 1 ,
user_online : onlineUsers . has ( finalUserId ) ? 1 : 0 ,
cs_online : onlineManagers . has ( finalManagerId ) ? 1 : 0 ,
created_at : now ,
updated_at : now
} ;
} catch ( error ) {
console . error ( ` 创建或获取会话失败 (尝试 ${ attempts + 1 } / ${ maxAttempts } ): ` , error ) ;
attempts ++ ;
// 如果不是最后一次尝试,短暂延迟后重试
if ( attempts < maxAttempts ) {
await new Promise ( resolve => setTimeout ( resolve , 100 * attempts ) ) ;
} else {
// 最后一次尝试也失败了,抛出错误
throw error ;
}
}
}
// 所有尝试都失败后,再次查询以获取可能已存在的会话
const [ finalConversations ] = await sequelize . query (
'SELECT * FROM chat_conversations WHERE userId = ? AND managerId = ? LIMIT 1' ,
{ replacements : [ finalUserId , finalManagerId ] }
) ;
if ( finalConversations && finalConversations . length > 0 ) {
return finalConversations [ 0 ] ;
}
throw new Error ( '无法创建或获取会话,所有尝试均失败' ) ;
}
// 获取用户的所有会话
async function getUserConversations ( userId ) {
try {
// 第一步:获取所有会话
const [ conversations ] = await sequelize . query (
` SELECT * FROM chat_conversations
WHERE userId = ?
ORDER BY last_message_time DESC , created_at DESC ` ,
{ replacements : [ userId ] }
) ;
// 第二步:对于每个会话,单独获取用户和客服信息
for ( let i = 0 ; i < conversations . length ; i ++ ) {
const conversation = conversations [ i ] ;
// 获取用户信息
const [ users ] = await sequelize . query (
'SELECT nickName, avatarUrl FROM users WHERE userId = ?' ,
{ replacements : [ conversation . userId ] }
) ;
if ( users && users . length > 0 ) {
conversation . userNickName = users [ 0 ] . nickName ;
conversation . userAvatar = users [ 0 ] . avatarUrl ;
}
// 获取客服信息
try {
const [ personnel ] = await sequelize . query (
'SELECT name FROM userlogin.personnel WHERE id = ?' ,
{ replacements : [ conversation . managerId ] }
) ;
if ( personnel && personnel . length > 0 ) {
conversation . managerName = personnel [ 0 ] . name ;
} else {
// 客服信息不存在时,使用默认名称
conversation . managerName = ` 客服 ${ conversation . managerId } ` ;
}
} catch ( error ) {
console . warn ( '获取客服信息失败,使用默认名称:' , error . message ) ;
// 客服信息获取失败时,使用默认名称
conversation . managerName = ` 客服 ${ conversation . managerId } ` ;
}
}
return conversations ;
} catch ( error ) {
console . error ( '获取用户会话失败:' , error ) ;
throw error ;
}
}
// 获取客服的所有会话
async function getManagerConversations ( managerId ) {
try {
// 分两步查询,避免跨数据库表连接时的排序规则问题
// 第一步:获取所有会话
const [ conversations ] = await sequelize . query (
` SELECT * FROM chat_conversations WHERE managerId = ?
ORDER BY last_message_time DESC , created_at DESC ` ,
{ replacements : [ managerId ] }
) ;
// 第二步:对于每个会话,单独获取用户和客服信息
for ( let i = 0 ; i < conversations . length ; i ++ ) {
const conversation = conversations [ i ] ;
// 获取用户信息
const [ users ] = await sequelize . query (
'SELECT nickName, avatarUrl FROM users WHERE userId = ?' ,
{ replacements : [ conversation . userId ] }
) ;
if ( users && users . length > 0 ) {
conversation . userNickName = users [ 0 ] . nickName ;
conversation . userAvatar = users [ 0 ] . avatarUrl ;
}
// 获取客服信息
const [ personnel ] = await sequelize . query (
'SELECT name FROM userlogin.personnel WHERE id = ?' ,
{ replacements : [ conversation . managerId ] }
) ;
if ( personnel && personnel . length > 0 ) {
conversation . managerName = personnel [ 0 ] . name ;
}
}
return conversations ;
} catch ( error ) {
console . error ( '获取客服会话失败:' , error ) ;
throw error ;
}
}
// 消息处理函数
// 处理聊天消息
async function handleChatMessage ( ws , payload ) {
console . log ( '===== 开始处理聊天消息 =====' ) ;
console . log ( '收到聊天消息 - 原始payload:' , payload ) ;
console . log ( '连接信息 - connectionId:' , ws . connectionId ) ;
// 添加详细的连接信息日志
const connection = connections . get ( ws . connectionId ) ;
console . log ( '连接详情 - isUser:' , connection ? . isUser , 'isManager:' , connection ? . isManager ) ;
// 基本参数验证
if ( ! payload ) {
console . error ( '错误: payload为空' ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '消息数据不完整,缺少必要字段'
} ) ) ;
return ;
}
if ( ! payload . content ) {
console . error ( '错误: payload.content为空' ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '消息内容不能为空'
} ) ) ;
return ;
}
// 打印解构前的payload字段
console . log ( 'Payload字段检查:' ) ;
console . log ( '- conversationId存在:' , 'conversationId' in payload ) ;
console . log ( '- content存在:' , 'content' in payload ) ;
console . log ( '- contentType存在:' , 'contentType' in payload ) ;
console . log ( '- messageId存在:' , 'messageId' in payload ) ;
const { conversationId , content , contentType = 1 , fileUrl , fileSize , duration } = payload ;
console . log ( '解构后的值:' ) ;
console . log ( '- conversationId:' , conversationId ) ;
console . log ( '- content:' , content ? . substring ( 0 , 20 ) + '...' ) ;
console . log ( '- contentType:' , contentType ) ;
if ( ! connection ) {
console . error ( '错误: 连接不存在' ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '连接已失效'
} ) ) ;
return ;
}
try {
// 确定发送者和接收者信息
let senderId , receiverId , senderType ;
let conversation ;
if ( connection . isUser ) {
// 用户发送消息给客服
senderId = validateUserId ( connection . userId ) ;
senderType = 1 ;
// 关键验证:确保senderId有效
if ( ! senderId || senderId === 0 || senderId === '0' ) {
console . error ( '严重错误: 用户连接的userId无效:' , { userId : senderId } ) ;
throw new Error ( '用户认证信息不完整,无法发送消息' ) ;
}
console . log ( '处理用户消息:' , { userId : senderId , conversationId , hasManagerId : ! ! payload . managerId } ) ;
// 如果没有提供会话ID,则查找或创建会话
if ( ! conversationId ) {
if ( ! payload . managerId ) {
throw new Error ( '未指定客服ID' ) ;
}
receiverId = validateManagerId ( payload . managerId ) ;
// 确保senderId有效且不是测试ID
if ( ! senderId || senderId . includes ( 'test_' ) ) {
console . error ( '错误: 尝试使用无效或测试用户ID创建会话:' , senderId ) ;
throw new Error ( '用户认证信息无效,无法创建会话' ) ;
}
console . log ( '创建新会话:' , { userId : senderId , managerId : receiverId } ) ;
conversation = await createOrGetConversation ( senderId , receiverId ) ;
// 验证创建的会话信息
console . log ( '创建的会话详情:' , conversation ) ;
if ( ! conversation || ! conversation . conversation_id ) {
console . error ( '错误: 创建会话失败或返回无效的会话信息' ) ;
throw new Error ( '创建会话失败' ) ;
}
// 强制设置正确的userId,确保不会出现0或空值
conversation . userId = senderId ;
conversation . conversation_id = conversation . conversation_id || conversation . conversationId ;
// 立即验证并修复会话中的用户ID
if ( conversation . userId !== senderId ) {
console . warn ( '警告: 会话创建后userId不匹配,立即修复' ) ;
await sequelize . query (
'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?' ,
{ replacements : [ senderId , conversation . conversation_id ] }
) ;
conversation . userId = senderId ;
}
// 验证并修复数据库中的会话userId
if ( conversation . userId !== senderId ) {
console . warn ( '警告: 数据库返回的userId与连接的userId不匹配,正在修复' ) ;
await sequelize . query (
'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?' ,
{ replacements : [ senderId , conversation . conversation_id ] }
) ;
console . log ( ` 已修复会话用户ID: conversationId= ${ conversation . conversation_id } , 设置为 ${ senderId } ` ) ;
}
} else {
// 获取会话信息以确定接收者
console . log ( '查询现有会话:' , { conversationId } ) ;
// 检查是否是临时会话ID
if ( conversationId && conversationId . startsWith ( 'temp_' ) ) {
console . log ( '检测到临时会话ID,需要创建真实会话:' , conversationId ) ;
// 从临时会话ID中提取信息
// 支持前端临时会话ID格式: temp_[currentUserId]_[targetId]_[timestamp]
const tempIdParts = conversationId . split ( '_' ) ;
if ( tempIdParts . length >= 4 ) {
// 根据连接类型确定正确的userId和managerId
let tempUserId , tempManagerId ;
if ( connection . isUser ) {
// 用户连接: currentUserId是用户ID,targetId是客服ID
tempUserId = tempIdParts [ 1 ] ;
tempManagerId = tempIdParts [ 2 ] ;
} else if ( connection . isManager ) {
// 客服连接: currentUserId是客服ID,targetId是用户ID
tempManagerId = tempIdParts [ 1 ] ;
tempUserId = tempIdParts [ 2 ] ;
} else {
// 默认情况,尝试判断哪个是用户ID哪个是客服ID
if ( tempIdParts [ 1 ] . includes ( 'user_' ) ) {
tempUserId = tempIdParts [ 1 ] ;
tempManagerId = tempIdParts [ 2 ] ;
} else {
tempUserId = tempIdParts [ 2 ] ;
tempManagerId = tempIdParts [ 1 ] ;
}
}
console . log ( '从临时ID提取信息:' , { tempManagerId , tempUserId } ) ;
// 创建或获取真实会话
conversation = await createOrGetConversation ( tempUserId , tempManagerId ) ;
console . log ( '创建的真实会话:' , conversation ) ;
receiverId = tempManagerId ;
} else {
console . error ( '无法解析临时会话ID:' , conversationId ) ;
throw new Error ( '无效的临时会话ID格式' ) ;
}
} else {
// 正常查询现有会话
const [ conversations ] = await sequelize . query (
'SELECT * FROM chat_conversations WHERE conversation_id = ?' ,
{ replacements : [ conversationId ] }
) ;
if ( ! conversations || conversations . length === 0 ) {
console . warn ( '会话不存在,尝试从userId和managerId创建' ) ;
// 尝试从payload中获取managerId
if ( payload . managerId ) {
receiverId = validateManagerId ( payload . managerId ) ;
console . log ( '尝试使用payload中的managerId创建会话:' , { userId : senderId , managerId : receiverId } ) ;
conversation = await createOrGetConversation ( senderId , receiverId ) ;
} else {
throw new Error ( '会话不存在且无法确定客服ID' ) ;
}
} else {
conversation = conversations [ 0 ] ;
console . log ( '查询到的会话详情:' , conversation ) ;
// 关键修复:确保receiverId是有效的客服ID,不是"user"或其他无效值
receiverId = conversation . managerId ;
if ( receiverId === 'user' ) {
console . error ( '错误: 会话的managerId是"user",使用payload中的managerId替代' ) ;
if ( payload . managerId ) {
receiverId = validateManagerId ( payload . managerId ) ;
} else {
throw new Error ( '会话的managerId无效,且payload中没有提供有效的managerId' ) ;
}
}
}
}
// 验证会话的userId是否与当前用户匹配,不匹配则修复
// 关键修复:只有当会话的userId不是当前用户ID,并且会话的managerId不是当前用户ID时,才更新会话的userId
// 避免将用户ID和客服ID错误地交换
if ( conversation . userId !== senderId && conversation . managerId !== senderId ) {
console . error ( ` 错误: 会话userId( ${ conversation . userId } )与当前用户ID( ${ senderId } )不匹配 ` ) ;
// 更新会话的userId为当前用户ID
await sequelize . query (
'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?' ,
{ replacements : [ senderId , conversationId ] }
) ;
conversation . userId = senderId ;
console . log ( ` 已修复会话用户ID: conversationId= ${ conversationId } , 设置为 ${ senderId } ` ) ;
}
}
} else if ( connection . isManager ) {
// 客服发送消息给用户
senderId = validateManagerId ( connection . managerId ) ;
senderType = 2 ;
console . log ( '处理客服消息 - 详细信息:' ) ;
console . log ( '- managerId:' , senderId ) ;
console . log ( '- conversationId:' , conversationId ) ;
// 检查conversationId是否有效
if ( ! conversationId ) {
console . error ( '错误: 客服消息缺少conversationId' ) ;
throw new Error ( '消息数据不完整,缺少必要字段' ) ;
}
// 获取会话信息以确定接收者
console . log ( '查询会话信息:' , conversationId ) ;
const [ conversations ] = await sequelize . query (
'SELECT * FROM chat_conversations WHERE conversation_id = ?' ,
{ replacements : [ conversationId ] }
) ;
if ( ! conversations || conversations . length === 0 ) {
throw new Error ( '会话不存在' ) ;
}
conversation = conversations [ 0 ] ;
receiverId = conversation . userId ;
// 检查receiverId是否有效
console . log ( '从会话获取的receiverId:' , receiverId ) ;
// 修复方案:如果receiverId无效,我们需要查找正确的用户ID
if ( ! receiverId || receiverId === 0 || receiverId === '0' ) {
console . error ( '错误: 会话中的用户ID无效(0或为空),正在尝试修复' ) ;
// 查找该会话中的所有消息,尝试从用户发送的消息中获取正确的用户ID
const [ messages ] = await sequelize . query (
'SELECT sender_id FROM chat_messages WHERE conversation_id = ? AND sender_type = 1 LIMIT 1' ,
{ replacements : [ conversationId ] }
) ;
if ( messages && messages . length > 0 && messages [ 0 ] . sender_id && messages [ 0 ] . sender_id !== 0 ) {
// 找到正确的用户ID,更新会话信息
const correctUserId = messages [ 0 ] . sender_id ;
receiverId = correctUserId ;
// 更新数据库中的会话信息,修复userId为空的问题
await sequelize . query (
'UPDATE chat_conversations SET userId = ? WHERE conversation_id = ?' ,
{ replacements : [ correctUserId , conversationId ] }
) ;
console . log ( ` ✅ 成功修复会话用户ID: conversationId= ${ conversationId } , 从0更新为 ${ correctUserId } ` ) ;
} else {
// 如果找不到正确的用户ID,则抛出错误,不允许存储无效消息
console . error ( '❌ 无法找到有效的用户ID,消息发送失败' ) ;
throw new Error ( '会话用户信息不完整,无法发送消息' ) ;
}
}
// 确保会话对象中的userId也是正确的
conversation . userId = receiverId ;
} else {
throw new Error ( '未认证的连接' ) ;
}
// 确保会话存在
if ( ! conversation ) {
console . error ( '错误: 会话对象不存在' ) ;
throw new Error ( '会话信息无效' ) ;
}
// 获取会话ID,处理字段名差异
const convId = conversation . conversation_id || conversation . conversationId ;
if ( ! convId ) {
console . error ( '错误: 会话缺少有效的ID' , conversation ) ;
throw new Error ( '会话信息无效' ) ;
}
// 直接使用传入的senderId,确保始终有效
console . log ( '会话中的用户ID:' , senderId ) ;
if ( ! senderId || senderId === 0 || senderId === '0' ) {
console . error ( '错误: 用户ID无效' ) ;
throw new Error ( '用户信息不完整' ) ;
}
// 统一会话信息格式,强制使用正确的字段名
// 关键修复:保持原始会话的userId和managerId不变,只统一字段名
conversation = {
conversation_id : convId ,
userId : conversation . userId ,
managerId : conversation . managerId ,
... conversation
} ;
// 生成消息ID和时间戳
const messageId = payload . messageId || crypto . randomUUID ( ) ; // 允许前端提供messageId
const now = getBeijingTime ( ) ;
console . log ( '准备存储消息:' , {
messageId ,
conversationId : conversation . conversation_id ,
senderType ,
senderId ,
receiverId
} ) ;
try {
// 关键修复:确保storeMessage被正确调用
const storeResult = await storeMessage ( {
messageId ,
conversationId : conversation . conversation_id ,
senderType ,
senderId ,
receiverId ,
contentType ,
content ,
fileUrl ,
fileSize ,
duration ,
createdAt : now
} ) ;
console . log ( '✅ 消息存储成功:' , storeResult ) ;
console . log ( '开始更新会话信息...' ) ;
} catch ( storeError ) {
console . error ( '❌ 消息存储失败:' , storeError . message ) ;
throw storeError ; // 重新抛出错误,确保上层捕获
}
// 更新会话最后消息
await updateConversationLastMessage ( conversation . conversation_id , content , now ) ;
// 更新未读计数
if ( connection . isUser ) {
await updateUnreadCount ( conversation . conversation_id , 'cs_unread_count' , 1 ) ;
console . log ( '更新客服未读数:' , { conversationId : conversation . conversation_id } ) ;
} else {
await updateUnreadCount ( conversation . conversation_id , 'unread_count' , 1 ) ;
console . log ( '更新用户未读数:' , { conversationId : conversation . conversation_id } ) ;
}
// 构造消息对象
const messageData = {
messageId ,
conversationId : conversation . conversation_id ,
senderType ,
senderId ,
receiverId ,
contentType ,
content ,
fileUrl ,
fileSize ,
duration ,
isRead : 0 ,
status : 1 ,
createdAt : now
} ;
// 发送消息给接收者
let receiverWs ;
if ( senderType === 1 ) {
// 用户发送给客服
receiverWs = onlineManagers . get ( receiverId ) ;
console . log ( ` 尝试转发消息给客服 ${ receiverId } ,客服是否在线: ` , ! ! receiverWs ) ;
} else {
// 客服发送给用户
receiverWs = onlineUsers . get ( receiverId ) ;
console . log ( ` 尝试转发消息给用户 ${ receiverId } ,用户是否在线: ` , ! ! receiverWs ) ;
}
// 处理特殊情况:当发送者和接收者是同一个人(既是用户又是客服)
if ( ! receiverWs && senderId == receiverId ) {
if ( senderType === 1 ) {
// 用户发送消息给自己的客服身份
receiverWs = onlineManagers . get ( senderId ) ;
} else {
// 客服发送消息给自己的用户身份
receiverWs = onlineUsers . get ( senderId ) ;
}
console . log ( '处理同一会话内消息转发:' , { senderId , hasReceiverWs : ! ! receiverWs } ) ;
}
if ( receiverWs ) {
try {
receiverWs . send ( JSON . stringify ( {
type : 'new_message' ,
payload : messageData
} ) ) ;
console . log ( '消息转发成功' ) ;
} catch ( sendError ) {
console . error ( '转发消息失败:' , sendError ) ;
// 转发失败不影响消息存储,只记录错误
}
} else {
console . log ( '接收者不在线,消息已存储但未实时推送' ) ;
}
// 发送确认给发送者
ws . send ( JSON . stringify ( {
type : 'message_sent' ,
payload : {
messageId ,
status : 'success' ,
conversationId : conversation . conversation_id
}
} ) ) ;
console . log ( '消息处理完成:' , { messageId , status : 'success' } ) ;
} catch ( error ) {
console . error ( '处理聊天消息失败:' , {
error : error . message ,
stack : error . stack ,
payload : { conversationId , content : content ? content . substring ( 0 , 50 ) + '...' : '无内容' }
} ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '消息发送失败: ' + error . message
} ) ) ;
}
}
// 存储消息到数据库
async function storeMessage ( messageData ) {
const { messageId , conversationId , senderType , senderId , receiverId ,
contentType , content , fileUrl , fileSize , duration , createdAt } = messageData ;
// 参数验证
if ( ! messageId || ! conversationId || ! senderType || ! senderId || ! receiverId || ! content ) {
throw new Error ( '消息数据不完整,缺少必要字段' ) ;
}
// 确保所有ID都是字符串类型,并添加额外的验证
const stringSenderId = validateUserId ( senderId ) ;
const stringReceiverId = String ( receiverId ) . trim ( ) ;
const stringConversationId = String ( conversationId ) . trim ( ) ;
const stringMessageId = String ( messageId ) . trim ( ) ;
// 验证senderId不是测试ID或无效ID
if ( stringSenderId && stringSenderId . includes ( 'test_' ) ) {
console . warn ( '警告: 检测到使用测试ID发送消息:' , stringSenderId ) ;
// 不阻止消息发送,但记录警告
}
// 确保senderId不为空或0
if ( ! stringSenderId || stringSenderId === '0' || stringSenderId === 'null' || stringSenderId === 'undefined' ) {
throw new Error ( '无效的发送者ID' ) ;
}
try {
console . log ( '开始存储消息到数据库:' , {
messageId : stringMessageId ,
conversationId : stringConversationId ,
senderType : senderType === 1 ? '用户' : '客服' ,
senderId : stringSenderId ,
receiverId : stringReceiverId ,
contentType
} ) ;
const result = await sequelize . query (
` INSERT INTO chat_messages
( message_id , conversation_id , sender_type , sender_id , receiver_id ,
content_type , content , file_url , file_size , duration , is_read , status ,
created_at , updated_at )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , 0 , 1 , ? , ? ) ` ,
{
replacements : [
stringMessageId ,
stringConversationId ,
senderType ,
stringSenderId ,
stringReceiverId ,
contentType || 1 , // 默认文本消息
content ,
fileUrl || null ,
fileSize || null ,
duration || null ,
createdAt || getBeijingTime ( ) ,
createdAt || getBeijingTime ( )
]
}
) ;
// 记录影响行数,确认插入成功
const affectedRows = result [ 1 ] && result [ 1 ] . affectedRows ? result [ 1 ] . affectedRows : 0 ;
console . log ( ` 消息存储成功: messageId= ${ messageId } , 影响行数= ${ affectedRows } ` ) ;
return { success : true , messageId , affectedRows } ;
} catch ( error ) {
console . error ( '存储消息到数据库失败:' , {
error : error . message ,
stack : error . stack ,
messageData : {
messageId ,
conversationId ,
senderType ,
senderId ,
receiverId
}
} ) ;
throw new Error ( ` 消息存储失败: ${ error . message } ` ) ;
}
}
// 更新会话最后消息
async function updateConversationLastMessage ( conversationId , lastMessage , timestamp ) {
try {
// 关键修复:在更新最后消息前,先检查会话是否存在
const [ conversations ] = await sequelize . query (
'SELECT * FROM chat_conversations WHERE conversation_id = ? LIMIT 1' ,
{ replacements : [ conversationId ] }
) ;
if ( conversations && conversations . length > 0 ) {
// 只有当会话存在时,才更新最后消息
await sequelize . query (
'UPDATE chat_conversations SET last_message = ?, last_message_time = ?, updated_at = ? WHERE conversation_id = ?' ,
{ replacements : [ lastMessage , timestamp , timestamp , conversationId ] }
) ;
console . log ( ` 更新会话最后消息成功: conversationId= ${ conversationId } , lastMessage= ${ lastMessage } ` ) ;
} else {
// 如果会话不存在,不创建新会话,只记录警告
console . warn ( ` 警告: 尝试更新不存在的会话最后消息: conversationId= ${ conversationId } ` ) ;
}
} catch ( error ) {
console . error ( '更新会话最后消息失败:' , error ) ;
// 不抛出错误,避免影响消息存储
}
}
// 更新未读计数
async function updateUnreadCount ( conversationId , countField , increment ) {
try {
await sequelize . query (
` UPDATE chat_conversations
SET $ { countField } = $ { countField } + ? , updated_at = ?
WHERE conversation_id = ? ` ,
{ replacements : [ increment , getBeijingTime ( ) , conversationId ] }
) ;
} catch ( error ) {
console . error ( '更新未读计数失败:' , error ) ;
throw error ;
}
}
// 处理未读消息标记
async function handleMarkRead ( ws , payload ) {
console . log ( '收到标记已读请求:' , { payload , connectionId : ws . connectionId } ) ;
const { conversationId , messageIds } = payload ;
const connection = connections . get ( ws . connectionId ) ;
if ( ! connection ) {
console . error ( '连接不存在,无法标记已读' ) ;
return ;
}
if ( ! conversationId ) {
console . error ( '未提供会话ID' ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '未提供会话ID'
} ) ) ;
return ;
}
try {
const now = getBeijingTime ( ) ;
let countField ;
let updateQuery ;
let updateParams ;
if ( connection . isUser || connection . userType === 'user' ) {
// 用户标记客服消息为已读
countField = 'unread_count' ;
console . log ( '用户标记消息已读:' , { conversationId , userId : connection . userId } ) ;
if ( messageIds && Array . isArray ( messageIds ) && messageIds . length > 0 ) {
// 标记特定消息为已读
updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2 AND message_id IN (?)' ;
updateParams = [ now , conversationId , messageIds ] ;
} else {
// 标记所有消息为已读
updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 2' ;
updateParams = [ now , conversationId ] ;
}
} else if ( connection . isManager || connection . userType === 'manager' ) {
// 客服标记用户消息为已读
countField = 'cs_unread_count' ;
console . log ( '客服标记消息已读:' , { conversationId , managerId : connection . managerId } ) ;
if ( messageIds && Array . isArray ( messageIds ) && messageIds . length > 0 ) {
// 标记特定消息为已读
updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1 AND message_id IN (?)' ;
updateParams = [ now , conversationId , messageIds ] ;
} else {
// 标记所有消息为已读
updateQuery = 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND sender_type = 1' ;
updateParams = [ now , conversationId ] ;
}
} else {
throw new Error ( '未知的连接类型' ) ;
}
// 执行消息已读更新
await sequelize . query ( updateQuery , { replacements : updateParams } ) ;
// 重置未读计数
await sequelize . query (
` UPDATE chat_conversations SET ${ countField } = 0 WHERE conversation_id = ? ` ,
{ replacements : [ conversationId ] }
) ;
console . log ( '消息已读状态更新成功:' , { conversationId , countField } ) ;
// 发送确认
ws . send ( JSON . stringify ( {
type : 'marked_read' ,
payload : { conversationId , messageIds }
} ) ) ;
// 通知对方已读状态(可选)
// 这里可以根据需要添加向对方发送已读状态通知的逻辑
} catch ( error ) {
console . error ( '标记消息已读失败:' , {
error : error . message ,
stack : error . stack ,
conversationId
} ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '标记已读失败: ' + error . message
} ) ) ;
}
}
// 处理会话相关消息
async function handleSessionMessage ( ws , data ) {
// 兼容不同格式的消息数据
// 关键修复:同时支持type和action字段
const action = data . action || data . type || ( data . data && data . data . action ) || ( data . payload && data . payload . action ) || 'list' ; // 默认action为'list'
const conversationId = data . conversationId || ( data . data && data . data . conversationId ) || ( data . payload && data . payload . conversationId ) ;
const connection = connections . get ( ws . connectionId ) ;
if ( ! connection ) {
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '未认证的连接'
} ) ) ;
return ;
}
try {
switch ( action ) {
case 'get_conversations' :
case 'list' :
// 获取会话列表,支持'list'和'get_conversations'两种操作
console . log ( '获取会话列表请求:' , { action , isUser : connection . isUser || connection . userType === 'user' , isManager : connection . isManager || connection . userType === 'manager' } ) ;
let conversations ;
try {
if ( connection . isUser || connection . userType === 'user' ) {
const userId = connection . userId || ( connection . userType === 'user' && connection . userId ) ;
if ( ! userId ) {
throw new Error ( '用户ID不存在' ) ;
}
conversations = await getUserConversations ( userId ) ;
console . log ( '用户会话列表获取成功:' , { userId , conversationCount : conversations . length } ) ;
} else if ( connection . isManager || connection . userType === 'manager' ) {
const managerId = connection . managerId || ( connection . userType === 'manager' && connection . managerId ) ;
if ( ! managerId ) {
throw new Error ( '客服ID不存在' ) ;
}
conversations = await getManagerConversations ( managerId ) ;
console . log ( '客服会话列表获取成功:' , { managerId , conversationCount : conversations . length } ) ;
} else {
throw new Error ( '未知的连接类型' ) ;
}
// 为每个会话更新在线状态
if ( conversations && conversations . length > 0 ) {
const updatedConversations = await Promise . all ( conversations . map ( async ( conv ) => {
// 检查用户是否在线
const userOnline = onlineUsers . has ( conv . userId ) ? 1 : 0 ;
// 检查客服是否在线
const csOnline = onlineManagers . has ( conv . managerId ) ? 1 : 0 ;
// 如果在线状态有变化,更新数据库
if ( conv . user_online !== userOnline || conv . cs_online !== csOnline ) {
await sequelize . query (
'UPDATE chat_conversations SET user_online = ?, cs_online = ? WHERE conversation_id = ?' ,
{ replacements : [ userOnline , csOnline , conv . conversation_id ] }
) ;
conv . user_online = userOnline ;
conv . cs_online = csOnline ;
}
return conv ;
} ) ) ;
conversations = updatedConversations ;
}
// 支持两种响应格式,确保兼容性
if ( action === 'list' ) {
// 兼容测试脚本的响应格式
ws . send ( JSON . stringify ( {
type : 'session_list' ,
data : conversations || [ ]
} ) ) ;
} else {
// 原有响应格式
ws . send ( JSON . stringify ( {
type : 'conversations_list' ,
payload : { conversations : conversations || [ ] }
} ) ) ;
}
console . log ( '会话列表推送成功:' , { action , responseType : action === 'list' ? 'session_list' : 'conversations_list' } ) ;
} catch ( error ) {
console . error ( '获取会话列表失败:' , { error : error . message , action } ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '获取会话列表失败: ' + error . message
} ) ) ;
}
break ;
case 'get_messages' :
// 获取会话历史消息
if ( ! conversationId ) {
throw new Error ( '未指定会话ID' ) ;
}
const page = parseInt ( data . page || ( data . data && data . data . page ) || ( data . payload && data . payload . page ) ) || 1 ;
const limit = parseInt ( data . limit || ( data . data && data . data . limit ) || ( data . payload && data . payload . limit ) ) || 50 ;
const offset = ( page - 1 ) * limit ;
console . log ( '获取会话消息:' , { conversationId , page , limit , offset } ) ;
try {
// 查询消息
const [ messages ] = await sequelize . query (
` SELECT * FROM chat_messages
WHERE conversation_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ? ` ,
{ replacements : [ conversationId , limit , offset ] }
) ;
// 反转顺序,使最早的消息在前
messages . reverse ( ) ;
// 获取消息总数
const [ [ totalCount ] ] = await sequelize . query (
'SELECT COUNT(*) as count FROM chat_messages WHERE conversation_id = ?' ,
{ replacements : [ conversationId ] }
) ;
ws . send ( JSON . stringify ( {
type : 'messages_list' ,
payload : {
messages ,
conversationId ,
pagination : {
page ,
limit ,
total : totalCount . count ,
totalPages : Math . ceil ( totalCount . count / limit )
}
}
} ) ) ;
console . log ( '消息获取成功:' , { conversationId , messageCount : messages . length } ) ;
// 如果是客服查看消息,自动将未读消息标记为已读
if ( connection . isManager ) {
const readTime = getBeijingTime ( ) ;
await sequelize . query (
'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND is_read = 0 AND sender_type = 1' ,
{ replacements : [ readTime , conversationId ] }
) ;
// 更新会话未读数
await sequelize . query (
'UPDATE chat_conversations SET cs_unread_count = 0 WHERE conversation_id = ?' ,
{ replacements : [ conversationId ] }
) ;
console . log ( '客服查看后更新未读状态:' , { conversationId } ) ;
}
} catch ( error ) {
console . error ( '获取消息失败:' , { conversationId , error : error . message } ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : '获取消息失败: ' + error . message
} ) ) ;
}
break ;
case 'close_conversation' :
// 关闭会话
if ( ! conversationId ) {
throw new Error ( '未指定会话ID' ) ;
}
const status = connection . isUser ? 3 : 2 ;
await sequelize . query (
'UPDATE chat_conversations SET status = ? WHERE conversation_id = ?' ,
{ replacements : [ status , conversationId ] }
) ;
ws . send ( JSON . stringify ( {
type : 'conversation_closed' ,
payload : { conversationId }
} ) ) ;
break ;
}
} catch ( error ) {
console . error ( '处理会话消息失败:' , error ) ;
ws . send ( JSON . stringify ( {
type : 'error' ,
message : error . message
} ) ) ;
}
}
// 新增:获取聊天列表数据接口 - 根据用户手机号查询
app . post ( '/api/chat/list' , async ( req , res ) => {
try {
const { user_phone } = req . body ;
console . log ( '获取聊天列表 - user_phone:' , user_phone ) ;
if ( ! user_phone ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '用户手机号不能为空'
} ) ;
}
// 从wechat_app数据库的chat_list表中查询用户的聊天记录
const chatList = await sequelize . query (
'SELECT * FROM chat_list WHERE user_phone = ?' ,
{
replacements : [ user_phone ] ,
type : Sequelize . QueryTypes . SELECT
}
) ;
console . log ( '找到聊天记录数量:' , chatList . length ) ;
// 如果没有聊天记录,返回空数组
if ( chatList . length === 0 ) {
return res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '暂无聊天记录' ,
data : [ ]
} ) ;
}
// 返回聊天列表数据
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '获取聊天列表成功' ,
data : chatList
} ) ;
} catch ( error ) {
console . error ( '获取聊天列表失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取聊天列表失败: ' + error . message
} ) ;
}
} ) ;
// 新增:删除聊天记录
app . post ( '/api/chat/delete' , async ( req , res ) => {
try {
const { user_phone , manager_phone } = req . body ;
console . log ( '删除聊天记录 - user_phone:' , user_phone , 'manager_phone:' , manager_phone ) ;
if ( ! user_phone || ! manager_phone ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '用户手机号和业务员手机号不能为空'
} ) ;
}
// 删除chat_list表中的记录
const deleteResult = await sequelize . query (
'DELETE FROM chat_list WHERE user_phone = ? AND manager_phone = ?' ,
{ replacements : [ user_phone , manager_phone ] , type : sequelize . QueryTypes . DELETE }
) ;
console . log ( '删除聊天记录结果:' , deleteResult ) ;
return res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '删除成功'
} ) ;
} catch ( error ) {
console . error ( '删除聊天记录时出错:' , error ) ;
return res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '删除失败: ' + error . message
} ) ;
}
} ) ;
// 新增:添加聊天记录到chat_list表
app . post ( '/api/chat/add' , async ( req , res ) => {
try {
const { user_phone , manager_phone } = req . body ;
console . log ( '添加聊天记录 - user_phone:' , user_phone , 'manager_phone:' , manager_phone ) ;
if ( ! user_phone || ! manager_phone ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '用户手机号和业务员手机号不能为空'
} ) ;
}
// 检查chat_list表是否存在,如果不存在则创建
try {
await sequelize . query (
` CREATE TABLE IF NOT EXISTS chat_list (
id INT AUTO_INCREMENT PRIMARY KEY ,
user_phone VARCHAR ( 20 ) NOT NULL ,
manager_phone VARCHAR ( 20 ) NOT NULL ,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ,
INDEX idx_user_phone ( user_phone ) ,
INDEX idx_manager_phone ( manager_phone ) ,
UNIQUE KEY idx_user_manager ( user_phone , manager_phone )
) ENGINE = InnoDB ; `
) ;
console . log ( '✅ chat_list表检查/创建成功' ) ;
} catch ( createError ) {
console . warn ( '创建chat_list表时出错(可能已存在):' , createError . message ) ;
}
// 如果表已存在但没有唯一索引,尝试添加
try {
await sequelize . query (
'ALTER TABLE chat_list ADD CONSTRAINT IF NOT EXISTS idx_user_manager UNIQUE (user_phone, manager_phone)'
) ;
console . log ( '✅ 尝试添加唯一索引成功' ) ;
} catch ( indexError ) {
console . warn ( '添加唯一索引失败(可能已存在):' , indexError . message ) ;
}
// 先查询是否已存在两条记录
const [ existingRecords ] = await sequelize . query (
` SELECT user_phone, manager_phone FROM chat_list WHERE
( user_phone = ? AND manager_phone = ? ) OR
( user_phone = ? AND manager_phone = ? ) ` ,
{ replacements : [ user_phone , manager_phone , manager_phone , user_phone ] }
) ;
// 统计现有记录
const hasRecord1 = existingRecords . some ( record =>
record . user_phone === user_phone && record . manager_phone === manager_phone
) ;
const hasRecord2 = existingRecords . some ( record =>
record . user_phone === manager_phone && record . manager_phone === user_phone
) ;
console . log ( '记录检查结果 - 记录1存在:' , hasRecord1 , '记录2存在:' , hasRecord2 ) ;
// 只插入不存在的记录
let insertedCount = 0 ;
if ( ! hasRecord1 ) {
await sequelize . query (
'INSERT INTO chat_list (user_phone, manager_phone) VALUES (?, ?)' ,
{ replacements : [ user_phone , manager_phone ] }
) ;
insertedCount ++ ;
console . log ( '✅ 插入记录1成功: user_phone -> manager_phone' ) ;
} else {
console . log ( 'ℹ️ 记录1已存在: user_phone -> manager_phone' ) ;
}
if ( ! hasRecord2 ) {
await sequelize . query (
'INSERT INTO chat_list (user_phone, manager_phone) VALUES (?, ?)' ,
{ replacements : [ manager_phone , user_phone ] }
) ;
insertedCount ++ ;
console . log ( '✅ 插入记录2成功: manager_phone -> user_phone' ) ;
} else {
console . log ( 'ℹ️ 记录2已存在: manager_phone -> user_phone' ) ;
}
console . log ( ` ✅ 聊天记录处理完成,新插入 ${ insertedCount } 条记录 ` ) ;
res . status ( 200 ) . json ( {
success : true ,
message : '聊天记录添加成功'
} ) ;
} catch ( error ) {
console . error ( '添加聊天记录失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '添加聊天记录失败: ' + error . message
} ) ;
}
} ) ;
// 新增:获取业务员信息接口 - 根据手机号查询
app . post ( '/api/personnel/get' , async ( req , res ) => {
try {
const { phone } = req . body ;
console . log ( '获取业务员信息 - phone:' , phone ) ;
if ( ! phone ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '手机号不能为空'
} ) ;
}
// 从userlogin数据库的personnel表中查询业务员信息
const personnel = await userLoginSequelize . query (
'SELECT * FROM personnel WHERE phoneNumber = ?' ,
{
replacements : [ phone ] ,
type : Sequelize . QueryTypes . SELECT
}
) ;
console . log ( '找到业务员信息数量:' , personnel . length ) ;
if ( personnel . length === 0 ) {
return res . status ( 200 ) . json ( {
success : false ,
code : 404 ,
message : '未找到该手机号对应的业务员信息'
} ) ;
}
// 返回所有匹配的业务员信息
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '获取业务员信息成功' ,
data : personnel
} ) ;
} catch ( error ) {
console . error ( '获取业务员信息失败:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
code : 500 ,
message : '获取业务员信息失败: ' + error . message
} ) ;
}
} ) ;
// 估价API端点 - 基于resources表的历史价格数据
app . post ( '/api/evaluate/price' , async ( req , res ) => {
try {
// 获取原始请求体,并检查编码问题
console . log ( '原始请求体:' , JSON . stringify ( req . body ) ) ;
// 提取参数并进行编码检查
const { month , day , region , breed } = req . body ;
// 记录详细的参数信息
console . log ( '请求参数详细信息:' , {
month : {
value : month ,
type : typeof month ,
isEmpty : ! month ,
isValid : ! ! month
} ,
day : {
value : day ,
type : typeof day ,
isEmpty : ! day ,
isValid : ! ! day
} ,
region : {
value : region ,
type : typeof region ,
isEmpty : ! region ,
isValid : ! ! region ,
isEncoded : /[?%]/ . test ( region || '' )
} ,
breed : {
value : breed ,
type : typeof breed ,
isEmpty : ! breed ,
isValid : ! ! breed ,
isEncoded : /[?%]/ . test ( breed || '' )
}
} ) ;
// 检查参数有效性
if ( ! month || ! day || ! region || ! breed ) {
return res . status ( 400 ) . json ( {
success : false ,
code : 400 ,
message : '缺少必要参数:month, day, region, breed'
} ) ;
}
// 构建查询日期
const currentYear = new Date ( ) . getFullYear ( ) ;
const targetDate = new Date ( currentYear , parseInt ( month ) - 1 , parseInt ( day ) ) ;
const previousYear = currentYear - 1 ;
const twoYearsAgo = currentYear - 2 ;
// 格式化日期为YYYY-MM-DD
const formatDate = ( date ) => {
// 确保日期格式正确
const year = date . getFullYear ( ) ;
const month = String ( date . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
const day = String ( date . getDate ( ) ) . padStart ( 2 , '0' ) ;
return ` ${ year } - ${ month } - ${ day } ` ;
} ;
// 构建查询日期列表
const queryDates = [
formatDate ( new Date ( previousYear , parseInt ( month ) - 1 , parseInt ( day ) ) ) ,
formatDate ( new Date ( twoYearsAgo , parseInt ( month ) - 1 , parseInt ( day ) ) ) ,
formatDate ( new Date ( currentYear , parseInt ( month ) - 1 , parseInt ( day ) - 1 ) )
] ;
console . log ( '查询日期列表:' , queryDates ) ;
console . log ( '查询参数:' , {
region : region ,
category : breed ,
timeIn : queryDates
} ) ;
// 查询历史价格数据(使用time字段和category字段)
const historicalData = await Resources . findAll ( {
where : {
region : region ,
category : breed , // 使用category字段映射breed参数
time : {
[ Op . in ] : queryDates
}
} ,
order : [ [ 'time' , 'DESC' ] ]
} ) ;
console . log ( '查询到的历史数据数量:' , historicalData . length ) ;
// 如果找到了数据,输出详细数据用于调试
if ( historicalData . length > 0 ) {
console . log ( '历史数据详情:' , historicalData . map ( item => ( {
id : item . id ,
category : item . category ,
region : item . region ,
time : item . time ,
price1 : item . price1 ,
price2 : item . price2
} ) ) ) ;
}
// 如果没有足够的历史数据,返回默认值
if ( historicalData . length === 0 ) {
return res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '数据不足,使用默认估价' ,
data : {
estimatedPrice : 4.50 ,
priceRange : '4.05 - 4.95' ,
confidence : '75%' ,
trend : 'stable' ,
changePercent : 0 ,
dataSource : 'default' ,
explanation : '由于历史数据不足,使用默认价格估算'
}
} ) ;
}
// 处理历史价格数据(使用price1和price2字段)
const priceData = historicalData . map ( item => {
// 计算平均价格作为实际价格
const price1 = parseFloat ( item . price1 ) || 0 ;
const price2 = parseFloat ( item . price2 ) || 0 ;
const actualPrice = ( price1 + price2 ) / 2 || 4.5 ; // 如果没有价格数据,使用默认价格4.5
return {
date : item . time ,
price : actualPrice ,
price1 : price1 ,
price2 : price2 ,
year : new Date ( item . time ) . getFullYear ( )
} ;
} ) ;
// 计算价格趋势
const latestPrice = priceData [ 0 ] ? . price || 4.5 ;
const previousYearPrice = priceData . find ( p => p . year === previousYear ) ? . price || latestPrice ;
const twoYearsAgoPrice = priceData . find ( p => p . year === twoYearsAgo ) ? . price || latestPrice ;
const previousDayPrice = priceData . find ( p => p . year === currentYear ) ? . price || latestPrice ;
// 计算涨幅(与前一天比较)
const priceChange = latestPrice - previousDayPrice ;
const changePercent = previousDayPrice > 0 ? ( priceChange / previousDayPrice ) * 100 : 0 ;
// 判断趋势
let trend = 'stable' ;
if ( changePercent > 0.2 ) {
trend = 'rising' ;
} else if ( changePercent < - 0.2 ) {
trend = 'falling' ;
}
// 计算预估价格(基于历史数据和趋势)
let estimatedPrice = latestPrice ;
if ( changePercent > 0.2 ) {
// 涨幅超过0.2%,小幅上涨
estimatedPrice = latestPrice * ( 1 + Math . min ( changePercent / 100 , 0.02 ) ) ;
} else if ( changePercent < - 0.2 ) {
// 跌幅超过0.2%,小幅下跌
estimatedPrice = latestPrice * ( 1 + Math . max ( changePercent / 100 , - 0.02 ) ) ;
}
// 确保价格合理范围
estimatedPrice = Math . max ( estimatedPrice , 3.0 ) ; // 最低3元/斤
estimatedPrice = Math . min ( estimatedPrice , 8.0 ) ; // 最高8元/斤
// 计算价格区间
const priceRangeMin = ( estimatedPrice * 0.9 ) . toFixed ( 2 ) ;
const priceRangeMax = ( estimatedPrice * 1.1 ) . toFixed ( 2 ) ;
// 计算置信度
let confidence = '80%' ;
if ( historicalData . length >= 3 ) {
confidence = '90%' ;
} else if ( historicalData . length >= 2 ) {
confidence = '85%' ;
}
// 构建响应数据
const responseData = {
estimatedPrice : estimatedPrice . toFixed ( 2 ) ,
priceRange : ` ${ priceRangeMin } - ${ priceRangeMax } ` ,
confidence : confidence ,
trend : trend ,
changePercent : changePercent . toFixed ( 2 ) ,
dataSource : 'historical' ,
explanation : ` 基于 ${ historicalData . length } 条历史数据,结合当前趋势进行估算 ` ,
historicalPrices : priceData ,
comparison : {
vsPreviousDay : {
price : previousDayPrice . toFixed ( 2 ) ,
change : priceChange . toFixed ( 2 ) ,
changePercent : changePercent . toFixed ( 2 ) + '%'
} ,
vsLastYear : {
price : previousYearPrice . toFixed ( 2 ) ,
change : ( latestPrice - previousYearPrice ) . toFixed ( 2 ) ,
changePercent : ( ( latestPrice - previousYearPrice ) / previousYearPrice * 100 ) . toFixed ( 2 ) + '%'
}
}
} ;
console . log ( '估价结果:' , responseData ) ;
res . status ( 200 ) . json ( {
success : true ,
code : 200 ,
message : '估价成功' ,
data : responseData
} ) ;
} catch ( error ) {
console . error ( '估价API错误:' , 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地址)
// 启动连接监控
startConnectionMonitoring ( ) ;
console . log ( '连接监控服务已启动' ) ;
// 调试API - 查看resources表结构
app . get ( '/api/debug/resources-table' , async ( req , res ) => {
try {
// 查询表结构
const [ tableStructure ] = await sequelize . query ( 'DESCRIBE resources' ) ;
console . log ( 'Resources表结构:' , tableStructure ) ;
// 查询示例数据
const sampleData = await sequelize . query ( 'SELECT * FROM resources LIMIT 5' ) ;
console . log ( 'Resources示例数据:' , sampleData [ 0 ] ) ;
res . json ( {
success : true ,
tableStructure : tableStructure ,
sampleData : sampleData [ 0 ]
} ) ;
} catch ( error ) {
console . error ( '调试API错误:' , error ) ;
res . status ( 500 ) . json ( {
success : false ,
message : error . message
} ) ;
}
} ) ;
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 ( ` WebSocket 服务地址: ws://localhost: ${ PORT } ` ) ;
console . log ( ` 服务器最大连接数限制: ${ server . maxConnections } ` ) ;
console . log ( ` WebSocket 服务器已启动,等待连接... ` ) ;
} ) ;
} ) ;
// 导出模型和Express应用供其他模块使用
module . exports = {
User ,
Product ,
CartItem ,
Resources ,
sequelize ,
createUserAssociations ,
app ,
PORT
} ;