diff --git a/images/轮播图1.jpg b/images/轮播图1.jpg deleted file mode 100644 index 92bf382..0000000 Binary files a/images/轮播图1.jpg and /dev/null differ diff --git a/images/轮播图2.jpg b/images/轮播图2.jpg deleted file mode 100644 index c0971d6..0000000 Binary files a/images/轮播图2.jpg and /dev/null differ diff --git a/images/轮播图3.jpg b/images/轮播图3.jpg deleted file mode 100644 index 92bf382..0000000 Binary files a/images/轮播图3.jpg and /dev/null differ diff --git a/pages/profile/index.js b/pages/profile/index.js index 356dd30..75bdf3f 100644 --- a/pages/profile/index.js +++ b/pages/profile/index.js @@ -32,14 +32,14 @@ Page({ loadUserInfo() { console.log('开始加载用户信息') const app = getApp() - + // 从本地存储获取用户信息 const localUserInfo = wx.getStorageSync('userInfo') || {} if (app.globalData.userInfo) { this.setData({ userInfo: app.globalData.userInfo }) } else { app.globalData.userInfo = localUserInfo - this.setData({ + this.setData({ userInfo: localUserInfo, needPhoneAuth: !localUserInfo.phoneNumber }) @@ -53,7 +53,7 @@ Page({ if (userId && openid) { // 从服务器获取最新的用户信息,确保身份由数据库决定 this.refreshUserInfoFromServer(openid, userId) - + // 确保users存储结构存在 let users = wx.getStorageSync('users') if (!users) { @@ -111,7 +111,7 @@ Page({ // 始终根据当前用户类型显示对应的身份标签 if (userType && userType !== '') { let identityLabel = '身份:not_set' - + switch (userType) { case 'buyer': identityLabel = '身份:买家'; break case 'seller': identityLabel = '身份:卖家'; break @@ -129,29 +129,29 @@ Page({ // 从服务器刷新用户信息并同步身份数据 refreshUserInfoFromServer(openid, userId) { const API = require('../../utils/api.js') - + API.getUserInfo(openid).then(res => { console.log('从服务器获取用户信息成功:', res) - + if (res.success && res.data) { const serverUserInfo = res.data - + // 更新本地用户信息 const app = getApp() const updatedUserInfo = { ...app.globalData.userInfo, ...serverUserInfo } - + app.globalData.userInfo = updatedUserInfo wx.setStorageSync('userInfo', updatedUserInfo) this.setData({ userInfo: updatedUserInfo }) - + // 同步更新用户身份信息(当前身份由数据库决定) if (serverUserInfo.type) { this.syncUserTypeFromServer(userId, serverUserInfo.type) } - + console.log('用户信息已更新,昵称:', updatedUserInfo.name, '手机号:', updatedUserInfo.phoneNumber, '身份:', serverUserInfo.type) } }).catch(err => { @@ -159,26 +159,26 @@ Page({ // 如果getUserInfo失败,尝试使用validateUserLogin作为备选 API.validateUserLogin().then(res => { console.log('使用validateUserLogin获取用户信息成功:', res) - + if (res.success && res.data) { const serverUserInfo = res.data - + // 更新本地用户信息 const app = getApp() const updatedUserInfo = { ...app.globalData.userInfo, ...serverUserInfo } - + app.globalData.userInfo = updatedUserInfo wx.setStorageSync('userInfo', updatedUserInfo) this.setData({ userInfo: updatedUserInfo }) - + // 同步更新用户身份信息(当前身份由数据库决定) if (serverUserInfo.type) { this.syncUserTypeFromServer(userId, serverUserInfo.type) } - + console.log('用户信息已更新(备选方案):', updatedUserInfo) } }).catch(validateErr => { @@ -194,40 +194,40 @@ Page({ console.error('同步用户身份信息失败: 参数不完整') return } - + console.log('从服务器同步用户身份信息:', { userId, serverType }) - + // 更新本地存储的用户身份 let users = wx.getStorageSync('users') || {} if (!users[userId]) { users[userId] = {} } - + // 移除serverType中的customer(如果存在) let processedServerType = serverType.replace(/,?customer/g, '').replace(/^,|,$/g, '') - + // 构建新的用户类型 let newUserType = processedServerType - + // 只有当新构建的用户类型与本地不同时才更新 if (users[userId].type !== newUserType) { users[userId].type = newUserType wx.setStorageSync('users', users) - + // 更新全局用户类型 const app = getApp() app.globalData.userType = newUserType - + // 更新页面显示的用户类型 - this.setData({ - userType: this.formatUserType(newUserType) + this.setData({ + userType: this.formatUserType(newUserType) }) - + console.log('用户身份已从服务器同步并保留客服标识:', newUserType) } else { console.log('用户身份与服务器一致,无需更新:', newUserType) } - + // 更新用户标签,确保传入正确的参数 this.updateUserTags(userId, newUserType) }, @@ -272,16 +272,16 @@ Page({ // 使用API更新用户类型,确保与服务器同步 API.updateUserType(newType).then(res => { console.log('用户类型更新成功:', res) - + // 更新页面显示 const app = getApp() - this.setData({ - userType: this.formatUserType(app.globalData.userType) + this.setData({ + userType: this.formatUserType(app.globalData.userType) }) - + // 更新用户标签 this.updateUserTags(userId, app.globalData.userType) - + wx.showToast({ title: `Switched to ${typeName}`, icon: 'success', @@ -328,27 +328,27 @@ Page({ // 处理手机号授权结果 async onPhoneNumberResult(e) { console.log('手机号授权结果:', e) - + // 首先检查用户是否拒绝授权 if (e.detail.errMsg !== 'getPhoneNumber:ok') { console.log('用户拒绝授权手机号') - wx.showToast({ - title: '您已拒绝授权,操作已取消', - icon: 'none' + wx.showToast({ + title: '您已拒绝授权,操作已取消', + icon: 'none' }) // 直接返回,取消所有后续操作 return } - - wx.showLoading({ - title: '登录中...', - mask: true + + wx.showLoading({ + title: '登录中...', + mask: true }) - + try { // 引入API服务 const API = require('../../utils/api.js') - + // 1. 先执行微信登录获取code const loginRes = await new Promise((resolve, reject) => { wx.login({ @@ -356,28 +356,28 @@ Page({ fail: reject }) }) - + if (!loginRes.code) { throw new Error('获取登录code失败') } - + console.log('获取登录code成功:', loginRes.code) - + // 2. 使用code换取openid const openidRes = await API.getOpenid(loginRes.code) - + // 增强版响应处理逻辑,支持多种返回格式 let openid = null; let userId = null; let sessionKey = null; - + // 优先从data字段获取数据 if (openidRes && openidRes.data && typeof openidRes.data === 'object') { openid = openidRes.data.openid || openidRes.data.OpenID || null; userId = openidRes.data.userId || openidRes.data.userid || null; sessionKey = openidRes.data.session_key || openidRes.data.sessionKey || null; } - + // 如果data为空或不存在,尝试从响应对象直接获取 if (!openid && openidRes && typeof openidRes === 'object') { console.warn('服务器返回格式可能不符合预期,data字段为空或不存在,但尝试从根对象提取信息:', openidRes); @@ -385,20 +385,20 @@ Page({ userId = openidRes.userId || openidRes.userid || null; sessionKey = openidRes.session_key || openidRes.sessionKey || null; } - + // 检查服务器状态信息 const isSuccess = openidRes && (openidRes.success === true || openidRes.code === 200); - + if (!openid) { throw new Error('获取openid失败: ' + (openidRes && openidRes.message ? openidRes.message : '未知错误')) } - + // 存储openid和session_key wx.setStorageSync('openid', openid) if (sessionKey) { wx.setStorageSync('sessionKey', sessionKey) } - + // 确保始终使用从服务器获取的正式用户ID,不再生成临时ID if (userId) { wx.setStorageSync('userId', userId) @@ -413,19 +413,19 @@ Page({ console.warn('未找到有效的用户ID,请确保用户已授权登录'); } } - + console.log('获取openid成功并存储:', openid) - + // 3. 上传手机号加密数据到服务器解密 const phoneData = { ...e.detail, openid: openid, sessionKey: sessionKey || '' } - + console.log('准备上传手机号加密数据到服务器') const phoneRes = await API.uploadPhoneNumberData(phoneData) - + // 改进手机号解密结果的处理逻辑 if (!phoneRes || (!phoneRes.success && !phoneRes.phoneNumber)) { // 如果服务器返回格式不标准但包含手机号,也接受 @@ -433,18 +433,18 @@ Page({ throw new Error('获取手机号失败: ' + (phoneRes && phoneRes.message ? phoneRes.message : '未知错误')) } } - + // 检查是否有手机号冲突 const hasPhoneConflict = phoneRes.phoneNumberConflict || false const isNewPhone = phoneRes.isNewPhone || true const phoneNumber = phoneRes.phoneNumber || null - + console.log('手机号解密结果:', { phoneNumber: phoneNumber, hasPhoneConflict: hasPhoneConflict, isNewPhone: isNewPhone }) - + // 4. 获取用户微信名称和头像 let userProfile = null; try { @@ -460,7 +460,7 @@ Page({ console.warn('获取用户信息失败:', err); // 如果获取失败,使用默认值 } - + // 5. 创建用户信息 const app = getApp() const existingUserInfo = app.globalData.userInfo || wx.getStorageSync('userInfo') || {} @@ -475,36 +475,36 @@ Page({ language: (userProfile ? userProfile.userInfo.language : existingUserInfo.language) || 'zh_CN', phoneNumber: phoneNumber } - + // 6. 获取用户类型 const users = wx.getStorageSync('users') || {} let currentUserType = users[userId] && users[userId].type ? users[userId].type : '' - + // 如果没有用户类型,尝试从全局获取 if (!currentUserType) { currentUserType = app.globalData.userType || '' } - + // 7. 保存用户信息并等待上传完成 console.log('开始保存用户信息并上传到服务器...') await this.uploadUserInfoToServer(userInfo, userId, currentUserType) console.log('用户信息保存并上传完成') - + // 更新本地和全局用户信息 app.globalData.userInfo = userInfo wx.setStorageSync('userInfo', userInfo) - + // 更新页面状态 this.setData({ needPhoneAuth: false, userInfo: userInfo }) - + // 重新加载用户信息以更新UI this.loadUserInfo() - + wx.hideLoading() - + // 根据服务器返回的结果显示不同的提示 if (hasPhoneConflict) { wx.showModal({ @@ -538,11 +538,11 @@ Page({ } }); } - + } catch (error) { wx.hideLoading() console.error('登录过程中发生错误:', error) - + // 更具体的错误提示 let errorMsg = '登录失败,请重试' if (error.message.includes('网络')) { @@ -554,13 +554,13 @@ Page({ } else if (error.message.includes('手机号')) { errorMsg = '获取手机号失败,请重试' } - + wx.showToast({ title: errorMsg, icon: 'none', duration: 3000 }) - + // 清除可能已经保存的不完整信息 try { wx.removeStorageSync('openid') @@ -582,7 +582,7 @@ Page({ // 获取openid const openid = wx.getStorageSync('openid') - + // 验证必要参数 if (!userId || !openid) { const error = new Error('缺少必要的用户信息'); @@ -620,7 +620,7 @@ Page({ // 修改用户名称 onEditName() { const currentName = this.data.userInfo.name || '未登录'; - + wx.showModal({ title: '修改用户名称', editable: true, @@ -632,7 +632,7 @@ Page({ // 移除首尾空格并过滤多余空格 let newName = res.content.trim(); newName = newName.replace(/\s+/g, ' '); - + // 验证昵称不能为空 if (!newName) { wx.showToast({ @@ -642,7 +642,7 @@ Page({ }); return; } - + // 验证昵称长度 if (newName.length > 10) { wx.showToast({ @@ -652,7 +652,7 @@ Page({ }); return; } - + // 检查名称是否变化 if (newName === currentName) { wx.showToast({ @@ -662,13 +662,13 @@ Page({ }); return; } - + // 显示加载提示 wx.showLoading({ title: '正在更新...', mask: true }); - + // 更新用户信息 this.updateName(newName).finally(() => { // 无论成功失败,都隐藏加载提示 @@ -678,7 +678,7 @@ Page({ } }); }, - + // 检查位置授权状态 checkLocationAuth() { const that = this; @@ -753,7 +753,7 @@ Page({ // 获取用户当前位置 getUserLocation() { const that = this; - + // 先检查位置授权状态 wx.getSetting({ success(res) { @@ -767,27 +767,79 @@ Page({ const longitude = res.longitude; // 调用逆地理编码API获取详细地址 that.reverseGeocode(latitude, longitude); - + // 将位置信息存储到本地 wx.setStorageSync('userLocation', { latitude, longitude }); - + // 立即将位置数据上传到数据库users表的authorized_region字段 const locationData = { latitude: latitude, longitude: longitude }; - - // 构造上传的数据,包含authorized_region字段 - const userInfo = { + + // 构造上传的数据,包含authorized_region字段和必要的phoneNumber + let userInfo = { authorized_region: JSON.stringify(locationData) // 将位置数据转换为JSON字符串存储 }; - + + // 确保包含phoneNumber字段(服务器接口要求) + let phoneNumber = wx.getStorageSync('phoneNumber'); + if (!phoneNumber) { + // 尝试从用户信息中获取phoneNumber + const globalUserInfo = wx.getStorageSync('userInfo'); + phoneNumber = globalUserInfo?.phoneNumber; + } + + // 如果找到phoneNumber,添加到上传数据中 + if (phoneNumber) { + userInfo.phoneNumber = phoneNumber; + } else { + console.warn('位置上传警告: 未找到phoneNumber,可能导致上传失败'); + } + + // 检查openid是否存在 + let openid = wx.getStorageSync('openid'); + if (!openid) { + // 尝试从用户信息中获取openid + const globalUserInfo = wx.getStorageSync('userInfo'); + openid = globalUserInfo?.openid; + } + + // 确保openid存在 + if (!openid) { + console.error('位置上传失败: 未找到openid'); + wx.showToast({ + title: '位置上传失败,请先登录', + icon: 'none', + duration: 2000 + }); + return; + } + + console.log('位置上传前检查openid:', openid); + console.log('准备上传的位置数据:', userInfo); + // 调用API上传位置数据 const api = require('../../utils/api.js'); - api.uploadUserInfo(userInfo).then(res => { + api.uploadUserInfo({ + ...userInfo, + openid: openid // 明确传递openid + }).then(res => { console.log('位置数据上传成功:', res); + // 显示上传成功提示 + wx.showToast({ + title: '位置更新成功', + icon: 'success', + duration: 1500 + }); }).catch(err => { console.error('位置数据上传失败:', err); + // 显示上传失败提示 + wx.showToast({ + title: '位置上传失败', + icon: 'none', + duration: 2000 + }); }); }, fail() { @@ -845,20 +897,20 @@ Page({ ...this.data.userInfo, name: newName }; - + // 保存到本地存储和全局状态 app.globalData.userInfo = updatedUserInfo; wx.setStorageSync('userInfo', updatedUserInfo); - + // 更新页面显示 this.setData({ userInfo: updatedUserInfo }); - + // 更新服务器信息 const userId = wx.getStorageSync('userId'); const currentUserType = this.data.userType; - + // 如果有用户ID,则上传到服务器 if (userId) { // 使用Promise链处理上传 diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index 72c607b..7bab83c 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -122,7 +122,7 @@ app.get('/api/wechat/getOpenid*', async (req, res) => { // 添加全局中间件处理URL重复问题 app.use((req, res, next) => { const url = req.url; - + // 检测URL中是否有重复的模式 const repeatedPattern = /(\/api\/wechat\/getOpenid).*?(\1)/i; if (repeatedPattern.test(url)) { @@ -132,7 +132,7 @@ app.use((req, res, next) => { res.redirect(307, correctedUrl); // 使用307保持原始请求方法 return; } - + next(); }); @@ -181,40 +181,40 @@ app.use((req, res, next) => { 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 + 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 + 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 + console.log('中间件还原非数字数量:', { + productId: product.productId, + original: product.originalQuantity, + final: product.quantity }); } } @@ -232,12 +232,12 @@ app.use((req, res, next) => { 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); @@ -497,7 +497,7 @@ app.get('/api/conversations/user/:userId', async (req, res) => { try { const { userId } = req.params; console.log(`获取用户 ${userId} 的会话列表`); - + // 从数据库获取用户的所有会话 const conversations = await ChatConversation.findAll({ where: { @@ -505,14 +505,14 @@ app.get('/api/conversations/user/:userId', async (req, res) => { }, 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, @@ -523,7 +523,7 @@ app.get('/api/conversations/user/:userId', async (req, res) => { status: conv.status }; }); - + res.json({ success: true, code: 200, @@ -644,6 +644,10 @@ User.init({ partnerstatus: { type: DataTypes.STRING(255) // 合作商状态 }, + // 授权区域字段 - 用于存储用户位置信息 + authorized_region: { + type: DataTypes.TEXT // 存储用户位置信息的JSON字符串 + }, reasonforfailure: { type: DataTypes.TEXT // 审核失败原因 }, @@ -723,7 +727,7 @@ Product.init({ specification: { type: DataTypes.STRING(255), }, - // 联系人信息 + // 联系人信息 product_contact: { type: DataTypes.STRING(100), allowNull: true, @@ -1188,12 +1192,17 @@ app.post('/api/user/upload', async (req, res) => { console.log('收到用户信息上传请求:', userData); // 使用微信小程序拉取唯一电话号码的插件,不再需要检查手机号冲突 - // 确保用户数据中包含手机号 - if (!userData.phoneNumber) { + // 检查用户是否已存在,如果是更新操作,不需要强制要求手机号 + let existingUser = await User.findOne({ + where: { openid: userData.openid } + }); + + // 只有在创建新用户时才强制要求手机号 + if (!existingUser && !userData.phoneNumber) { return res.json({ success: false, code: 400, - message: '缺少手机号信息', + message: '创建新用户时必须提供手机号信息', data: {} }); } @@ -1415,7 +1424,7 @@ app.post('/api/wechat/getOpenid', async (req, res) => { businesslicenseurl: '', // 默认空字符串 notice: 'new', // 创建用户时固定设置notice为new created_at: getBeijingTime(), - updated_at: getBeijingTime() + updated_at: getBeijingTime() }); // 为新创建的用户创建关联记录 @@ -1509,7 +1518,8 @@ app.post('/api/user/get', async (req, res) => { const user = await User.findOne({ where: { openid }, include: [ - { model: Contact, + { + model: Contact, as: 'contacts', attributes: ['id', 'name', 'phoneNumber', 'wechat', 'account', 'accountNumber', 'bank', 'address'] }, @@ -1628,10 +1638,10 @@ app.post('/api/product/list', async (req, res) => { // 根据viewMode决定是否限制sellerId,无论是否为测试模式 console.log(`处理viewMode: ${viewMode},确保在shopping模式下不添加sellerId过滤`); - + // 构建查询条件 - 根据viewMode决定是否包含sellerId const where = {}; - + if (viewMode === 'seller') { if (user) { // 任何用户(包括测试模式)都只能查看自己的商品 @@ -1654,7 +1664,7 @@ app.post('/api/product/list', async (req, res) => { // 初始化status筛选条件,确保总是有有效的状态过滤 let statusCondition = {}; - + // 如果有指定status参数,按参数筛选但同时排除hidden if (status) { console.log(`按状态筛选商品: status=${status},并排除hidden状态`); @@ -1692,7 +1702,7 @@ app.post('/api/product/list', async (req, res) => { statusCondition = { [Sequelize.Op.in]: ['pending_review', 'reviewed', 'published'] }; } } - + // 确保设置有效的status查询条件 where.status = statusCondition; console.log(`设置的status查询条件:`, JSON.stringify(statusCondition, null, 2)); @@ -1711,12 +1721,12 @@ app.post('/api/product/list', async (req, res) => { const { count, rows: products } = await Product.findAndCountAll({ where, include: [ - { - model: User, - as: 'seller', - attributes: ['userId', 'name', 'avatarUrl'] - } - ], + { + model: User, + as: 'seller', + attributes: ['userId', 'name', 'avatarUrl'] + } + ], attributes: { include: [ 'region', // 【新增】确保返回地区字段 @@ -1738,7 +1748,7 @@ app.post('/api/product/list', async (req, res) => { // 处理商品列表中的grossWeight字段,确保是数字类型,同时反序列化imageUrls const processedProducts = products.map(product => { const productJSON = product.toJSON(); - + // 确保created_at字段存在并转换为ISO字符串格式 if (!productJSON.created_at) { console.log('商品缺少created_at字段,使用默认值'); @@ -1853,98 +1863,98 @@ 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 = []; // 存储需要清理的临时文件路径 + 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'); + // 【关键修复】首先检查是否是仅上传图片到已存在商品的请求 + 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; - - // 对于仅图片上传的情况,需要特别处理 - 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); - } + // ========== 【新增】详细的地区字段调试信息 ========== + console.log('【地区字段调试】开始处理地区字段'); + console.log('【地区字段调试】原始productData.region:', productData.region, '类型:', typeof productData.region); + console.log('【地区字段调试】原始请求体中的region字段:', req.body.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' - }; + // 【新增】处理地区字段 - 增强版 + 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); @@ -1953,1206 +1963,1206 @@ try { // 确保grossWeight值是字符串类型,直接使用处理后的值 productData.grossWeight = grossWeightDetails.value; - console.log('上传商品 - 最终存储的毛重值:', productData.grossWeight, '类型:', typeof productData.grossWeight); + 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: '缺少必要的商品信息' - }); - } + // 验证必要字段 + 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)); + // 处理图片上传逻辑 - 使用批量上传确保多图片上传成功 + try { + console.log('===== 图片处理逻辑 ====='); + console.log('- 上传文件数量:', uploadedFiles.length); + console.log('- 是否有productData:', Boolean(productData)); - // 首先处理所有上传的文件 - if (uploadedFiles.length > 0) { - console.log('处理上传文件...'); + // 首先处理所有上传的文件 + if (uploadedFiles.length > 0) { + console.log('处理上传文件...'); - // 创建已上传URL集合,用于避免重复 - const uploadedFileUrls = new Set(); + // 创建已上传URL集合,用于避免重复 + const uploadedFileUrls = new Set(); - // 准备文件路径数组 - const filePaths = uploadedFiles.map(file => file.path); + // 准备文件路径数组 + const filePaths = uploadedFiles.map(file => file.path); - // 使用商品名称作为文件夹名,确保每个商品的图片独立存储 - // 移除商品名称中的特殊字符,确保可以作为合法的文件夹名 - const safeProductName = productData.productName - .replace(/[\/:*?"<>|]/g, '_') // 移除不合法的文件名字符 - .substring(0, 50); // 限制长度 + // 使用商品名称作为文件夹名,确保每个商品的图片独立存储 + // 移除商品名称中的特殊字符,确保可以作为合法的文件夹名 + const safeProductName = productData.productName + .replace(/[\/:*?"<>|]/g, '_') // 移除不合法的文件名字符 + .substring(0, 50); // 限制长度 - // 构建基础文件夹路径 - const folderPath = `products/${safeProductName}`; + // 构建基础文件夹路径 + const folderPath = `products/${safeProductName}`; - console.log(`准备批量上传到文件夹: ${folderPath}`); + 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('文件存在性检查通过'); + // 创建自定义的文件上传函数,添加详细错误处理和连接测试 + 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 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 - }); + // 获取文件扩展名 + 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客户端创建成功'); + 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}`); - } + // 测试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} }`); + // 上传文件,明确设置为公共读权限 + 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' - }); + // 添加超时控制 + 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)) - ]); + // 设置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}`); + console.log(`文件上传成功!`); + console.log(`- OSS响应:`, JSON.stringify(result)); + console.log(`- 返回URL: ${result.url}`); - // 验证URL - if (!result.url) { - throw new Error('上传成功但未返回有效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(`===== 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 = []; + // 改进的上传逻辑:使用逐个上传,添加更详细的日志和错误处理 + console.log('开始逐个上传文件,数量:', filePaths.length); + let uploadResults = []; - for (let i = 0; i < filePaths.length; i++) { - console.log(`=== 开始处理文件 ${i + 1}/${filePaths.length} ===`); - console.log(`文件路径: ${filePaths[i]}`); + 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]}`); - } + 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} 字节`); + const stats = fs.statSync(filePaths[i]); + console.log(`文件大小: ${stats.size} 字节`); - console.log(`调用customUploadFile上传文件...`); - const uploadedUrl = await customUploadFile(filePaths[i], folderPath); + console.log(`调用customUploadFile上传文件...`); + const uploadedUrl = await customUploadFile(filePaths[i], folderPath); - console.log(`文件 ${i + 1} 上传成功: ${uploadedUrl}`); - uploadResults.push({ fileIndex: i, success: true, url: uploadedUrl }); + 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为空或无效`); + 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 }); + // 继续上传下一个文件,不中断整个流程 } - } 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(`=== 文件 ${i + 1}/${filePaths.length} 处理完成 ===\n`); - } - console.log(`文件上传处理完成,成功上传${imageUrls.length}/${filePaths.length}个文件`); - console.log(`上传详细结果:`, JSON.stringify(uploadResults, null, 2)); - } + 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,避免重复'); + // 处理productData中的imageUrls,但需要避免重复添加 + // 注意:我们只处理不在已上传文件URL中的图片 + if (productData && productData.imageUrls && Array.isArray(productData.imageUrls)) { + console.log('处理productData中的imageUrls,避免重复'); - // 创建已上传文件URL的集合,包含已经通过文件上传的URL - const uploadedFileUrls = new Set(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); + 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不是数组,已重置为空数组'); + 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 + }); + } } - } catch (uploadError) { - console.error('图片处理失败:', uploadError); - // 清理临时文件 - cleanTempFiles(tempFilesToClean); + // 【关键修复】增强图片URL收集逻辑 - 从所有可能的来源收集图片URL + // 创建一个统一的URL集合,用于去重 + const allImageUrlsSet = new Set(imageUrls); - // 如果至少有一张图片上传成功,我们仍然可以继续创建商品 - if (imageUrls.length > 0) { - console.log(`部分图片上传成功,共${imageUrls.length}张,继续创建商品`); - // 继续执行,不返回错误 - } else { - // 如果所有图片都上传失败,才返回错误 - return res.status(500).json({ - success: false, - code: 500, - message: '图片上传失败', - error: uploadError.message + // 【新增】检查是否是递归上传的一部分,并获取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()); + } }); } - } - // 【关键修复】增强图片URL收集逻辑 - 从所有可能的来源收集图片URL - // 创建一个统一的URL集合,用于去重 - const allImageUrlsSet = new Set(imageUrls); + // 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; + } - // 【新增】检查是否是递归上传的一部分,并获取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()); + 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); } - }); - } + } - // 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()]; + // 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; } - } else { - additionalUrls = req.body.additionalImageUrls; + + 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); } + } - 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()); + // 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); } - } 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()]; + // 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); } } - } 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()); + 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); } - }); + } } - } 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()]; + 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); } } - } else { - allUrls = req.body.allImageUrls; } - if (Array.isArray(allUrls) && allUrls.length > 0) { - console.log('【关键修复】处理allImageUrls,数量:', allUrls.length); - allUrls.forEach(url => { + if (productImageUrls.length > 0) { + console.log('【关键修复】从productData中提取图片URLs,数量:', productImageUrls.length); + productImageUrls.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); - } - } + // 增强处理:添加清理和标准化函数,移除反引号、多余空格、多余反斜杠和所有可能导致JSON解析错误的字符 + function cleanAndStandardizeUrl(url) { + if (!url || typeof url !== 'string') return ''; + // 1. 移除所有反斜杠(防止JSON解析错误) + // 2. 移除反引号和多余空格 + // 3. 确保URL格式正确 + return url + .replace(/\\/g, '') // 移除所有反斜杠 + .replace(/[`\s]/g, '') // 移除反引号和空格 + .trim(); // 清理前后空白 } - 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); + // 将图片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 } - } catch (e) { - productImageUrls.push(productData.images); + }); + + 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 // 添加重新登录标志,前端检测到这个标志时弹出登录窗口 + }); } } } - 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); - } - } + // 【关键修复】增强递归上传支持 - 更可靠的会话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; } - if (productImageUrls.length > 0) { - console.log('【关键修复】从productData中提取图片URLs,数量:', productImageUrls.length); - productImageUrls.forEach(url => { - if (url && typeof url === 'string' && url.trim() !== '') { - allImageUrlsSet.add(url.trim()); - } - }); + // 【关键修复】增强的会话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}`); } - } - // 增强处理:添加清理和标准化函数,移除反引号、多余空格、多余反斜杠和所有可能导致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 - } - }); + console.log(`【会话跟踪】最终使用的会话ID: ${sessionId || '无'}`); - if (user && user.userId) { - console.log(`找到了对应的userId: ${user.userId}`); - actualSellerId = user.userId; - } else { - console.error(`未找到对应的用户记录,openid: ${productData.sellerId}`); - // 清理临时文件 - cleanTempFiles(tempFilesToClean); + // 如果是递归上传,尝试从数据库中查找匹配的临时ID或productId + let existingProduct = null; + let productId = null; - return res.status(401).json({ - success: false, - code: 401, - message: '找不到对应的用户记录,请重新登录', - needRelogin: true // 添加重新登录标志,前端检测到这个标志时弹出登录窗口 + // 【关键修复】增强的商品查找逻辑,优先查找已存在的商品记录 + 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和商品匹配 - // 检查是否是递归上传的一部分,增加更多判断条件 - 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 + if (!productId) { + productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + console.log(`生成新的商品ID: ${productId}`); + } - // 【关键修复】增强的会话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 + // 创建商品,使用实际的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 (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) + if (Array.isArray(existingUrls)) { + existingUrls.forEach(url => { + if (url && typeof url === 'string' && url.trim()) { + allImageUrlsSet.add(cleanAndStandardizeUrl(url)); } - }, - order: [['created_at', 'DESC']], - limit: 3 - }); - - if (recentProducts && recentProducts.length > 0) { - // 优先选择最近创建的商品 - existingProduct = recentProducts[0]; - productId = existingProduct.productId; - console.log(`【商品查找】找到用户最近创建的商品,productId: ${productId}`); + }); + console.log('【关键修复】从现有商品记录获取已保存图片URLs,有效数量:', existingUrls.filter(Boolean).length); } + } catch (e) { + console.error('解析现有商品图片URLs出错:', e); } - } catch (error) { - console.error(`【商品查找错误】查找已存在商品时出错:`, error); } - } - // 如果没有找到已存在的商品或没有会话ID,生成新的商品ID - if (!productId) { - productId = `product_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - console.log(`生成新的商品ID: ${productId}`); - } + console.log('【递归上传关键】现有商品图片URL数量:', allImageUrlsSet.size); - // 创建商品,使用实际的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 (imageUrls && Array.isArray(imageUrls)) { + imageUrls.forEach(url => { + if (url && typeof url === 'string' && url.trim()) { + allImageUrlsSet.add(cleanAndStandardizeUrl(url)); + } + }); + console.log('【关键修复】添加当前上传的图片URLs,数量:', imageUrls.length); + } - 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); + // 【关键修复】处理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); } - } - - 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); + + // 【关键修复】处理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); } - } 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); + + // 【关键修复】处理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); } - } 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); + + // 【关键修复】处理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); } - } - 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] : []; + 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 = []; } - } 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()); + + // 额外的安全检查 + if (!Array.isArray(existingImageUrls)) { + console.warn('existingImageUrls 不是数组,重置为空数组'); + existingImageUrls = []; } - }); - - // 【重要】尝试从请求参数中获取更多图片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()]; + // 【关键修复】增强的图片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; } - } 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()); - } - }); + + if (Array.isArray(urls)) { + urls.forEach(url => { + if (url && typeof url === 'string' && url.trim() !== '') { + allUrlsSet.add(url.trim()); + } + }); + } + } catch (e) { + console.error('处理额外URL源时出错:', e); } - } 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() - }; + 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); + // ========== 地区字段调试结束 ========== - // ========== 【新增】保存前的地区字段验证 ========== - console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof productToSave.region); - // ========== 地区字段调试结束 ========== + 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, - 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); - // ========== 地区字段调试结束 ========== + // ========== 【新增】保存前的地区字段验证 ========== + console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof productToSave.region); + // ========== 地区字段调试结束 ========== - 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, + 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); + // ========== 地区字段调试结束 ========== - // ========== 【新增】保存前的地区字段验证 ========== - console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof productToSave.region); - // ========== 地区字段调试结束 ========== + 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, - imageUrls: 'JSON字符串长度: ' + productToSave.imageUrls.length, - sellerId: '已处理(隐藏具体ID)', - region: productToSave.region // 特别显示地区字段 - }); - } + // ========== 【新增】保存前的地区字段验证 ========== + console.log('【地区字段调试】保存前验证 - productToSave.region:', productToSave.region, '类型:', typeof 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('【关键调试】即将创建的商品数据:', { + ...productToSave, + imageUrls: 'JSON字符串长度: ' + productToSave.imageUrls.length, + sellerId: '已处理(隐藏具体ID)', + region: productToSave.region // 特别显示地区字段 + }); } - // ========== 【新增】数据库操作前的地区字段最终检查 ========== - console.log('【地区字段调试】数据库更新前最终检查 - region:', productToSave.region); - // ========== 地区字段调试结束 ========== - - product = await Product.update(productToSave, { - where: { - productId: productId + // 根据是否是更新操作执行不同的数据库操作 + if (isUpdate) { + console.log(`【会话更新】执行商品更新,productId: ${productId}`); + // 确保imageUrls是正确的JSON字符串 + if (!productToSave.imageUrls || typeof productToSave.imageUrls !== 'string') { + console.error('【严重错误】imageUrls不是字符串格式,重新序列化'); + productToSave.imageUrls = JSON.stringify(finalImageUrls); } - }); - // 查询更新后的商品完整信息 - product = await Product.findOne({ - where: { - productId: productId + + // ========== 【新增】数据库操作前的地区字段最终检查 ========== + 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(`【会话更新】商品更新成功,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('【地区字段调试】数据库创建前最终检查 - region:', productToSave.region); + // ========== 【新增】数据库操作后的地区字段验证 ========== + console.log('【地区字段调试】数据库操作后验证'); + if (product) { + console.log('【地区字段调试】从数据库返回的商品地区字段:', product.region, '类型:', typeof product.region); + } else { + console.error('【地区字段调试】数据库操作后商品对象为空'); + } // ========== 地区字段调试结束 ========== - product = await Product.create(productToSave); - console.log(`【会话创建】商品创建成功,productId: ${productId}`); - } + console.log('【成功】商品操作完成,productId:', product.productId); - // ========== 【新增】数据库操作后的地区字段验证 ========== - console.log('【地区字段调试】数据库操作后验证'); - if (product) { - console.log('【地区字段调试】从数据库返回的商品地区字段:', product.region, '类型:', typeof product.region); - } else { - console.error('【地区字段调试】数据库操作后商品对象为空'); - } - // ========== 地区字段调试结束 ========== + // 【关键修复】确保返回给前端的响应包含完整的图片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('【成功】商品操作完成,productId:', product.productId); + console.log('【数据库读取】从数据库读取的图片URLs数量:', dbResponseImageUrls.length); + } - // 【关键修复】确保返回给前端的响应包含完整的图片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]; + // 如果数据库中没有或者为空,使用我们收集的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()); } - } else if (Array.isArray(product.imageUrls)) { - dbResponseImageUrls = product.imageUrls; + }); + dbResponseImageUrls = Array.from(urlSet); + + // 确保数组格式正确 + if (!Array.isArray(dbResponseImageUrls)) { + dbResponseImageUrls = [dbResponseImageUrls]; } - - console.log('【数据库读取】从数据库读取的图片URLs数量:', dbResponseImageUrls.length); - } - - // 如果数据库中没有或者为空,使用我们收集的finalImageUrls - if (!dbResponseImageUrls || dbResponseImageUrls.length === 0) { + + console.log('【最终响应】去重后返回给前端的图片URLs数量:', dbResponseImageUrls.length); + } catch (e) { + console.error('解析响应图片URLs出错:', e); + // 如果解析失败,使用我们收集的finalImageUrls 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: { + 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对象中也包含完整URL列表 + imageUrls: dbResponseImageUrls, // 在顶层product对象中也包含 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, + // 【关键修复】添加会话和上传状态信息 + 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, - isMultiImageUpload: isMultiImageUpload, isRecursiveUpload: isRecursiveUpload, - isFinalUpload: req.body.isFinalUpload === 'true', - currentIndex: uploadIndex, + uploadIndex: 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 + 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 - }); + 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 + 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检查'); - // 【关键修复】在发送响应前,再次确认数据库中的图片URLs是否正确存储 - if (product && product.imageUrls) { - let dbImageUrls; + // 【关键修复】确保URL列表格式正确且去重 + let responseImageUrls = []; try { - dbImageUrls = typeof product.imageUrls === 'string' ? JSON.parse(product.imageUrls) : product.imageUrls; - if (Array.isArray(dbImageUrls)) { - console.log(`【数据库验证】从数据库读取的图片URLs数量: ${dbImageUrls.length}`); - // 使用数据库中的最新URL列表更新响应 - dbResponseImageUrls = dbImageUrls; + // 优先使用数据库响应的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); + console.error('【最终响应】处理imageUrls失败:', e); + responseImageUrls = []; } - } - // 【增强的最终检查】确保返回给前端的图片URLs包含所有上传的图片 - console.log('【最终检查】开始最终图片URLs检查'); + // 更新customResponseData中的图片URL列表为最新的去重结果 + customResponseData.imageUrls = responseImageUrls; + customResponseData.allImageUrls = responseImageUrls; + customResponseData.imageUrl = responseImageUrls[0] || null; - // 【关键修复】确保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()); + 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; } - }); - 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; } - } - - 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 - }); + customResponseData.uploadInfo.uploadedCount = responseImageUrls.length; + customResponseData.uploadInfo.hasMultipleImages = responseImageUrls.length > 1; + customResponseData.debugInfo.imageUrlsCount = responseImageUrls.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'; + // ========== 【新增】响应前的地区字段最终验证 ========== + 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 { - newType = 'seller'; + console.log('不需要更新用户类型 - 用户ID:', currentUser.userId, '当前类型:', currentUser.type); } - - // 更新用户类型 - 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); + // 不影响商品发布结果,仅记录错误 } - } 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 - }); -} + // 发送最终增强的响应 + res.status(200).json(customResponseData); + } catch (err) { + console.error('【错误】在添加商品时出错:', err); + res.status(500).json({ + success: false, + message: '上传失败,请稍后重试', + code: 500, + error: err.message + }); + } }); // 【关键修复】在 handleAddImagesToExistingProduct 函数中加强图片合并逻辑 @@ -3160,7 +3170,7 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl let transaction; try { console.log('【图片更新模式】开始处理图片上传到已存在商品,商品ID:', existingProductId); - + // 使用事务确保数据一致性 transaction = await sequelize.transaction(); @@ -3188,14 +3198,14 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl 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不是数组,重置为空数组'); @@ -3213,24 +3223,24 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl 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); @@ -3238,7 +3248,7 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl } catch (deleteError) { console.warn(`删除临时文件失败: ${file.path}`, deleteError); } - + return uploadedUrl; } } catch (uploadError) { @@ -3254,7 +3264,7 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl // 【关键修复】合并图片URL(去重) const allUrlsSet = new Set(); - + // 添加现有图片URL existingImageUrls.forEach(url => { if (url && typeof url === 'string' && url.trim() !== '') { @@ -3262,7 +3272,7 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl console.log('【图片合并】添加现有URL:', url.trim()); } }); - + // 添加新上传的图片URL newImageUrls.forEach(url => { if (url && typeof url === 'string' && url.trim() !== '') { @@ -3328,9 +3338,9 @@ async function handleAddImagesToExistingProduct(req, res, existingProductId, upl 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({ @@ -3340,7 +3350,7 @@ app.post('/api/favorites/add', async (req, res) => { data: { user_phone, productId } }); } - + // 检查是否已存在收藏记录 const existingFavorite = await Favorite.findOne({ where: { @@ -3348,7 +3358,7 @@ app.post('/api/favorites/add', async (req, res) => { productId } }); - + if (existingFavorite) { return res.status(200).json({ success: true, @@ -3357,16 +3367,16 @@ app.post('/api/favorites/add', async (req, res) => { 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, @@ -3388,9 +3398,9 @@ app.post('/api/favorites/add', async (req, res) => { 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({ @@ -3399,7 +3409,7 @@ app.post('/api/favorites/remove', async (req, res) => { message: '缺少必要参数' }); } - + // 删除收藏记录 const result = await Favorite.destroy({ where: { @@ -3407,7 +3417,7 @@ app.post('/api/favorites/remove', async (req, res) => { productId } }); - + if (result === 0) { return res.status(404).json({ success: false, @@ -3415,9 +3425,9 @@ app.post('/api/favorites/remove', async (req, res) => { message: '收藏记录不存在' }); } - + console.log('收藏取消成功:', { user_phone, productId }); - + res.status(200).json({ success: true, code: 200, @@ -3438,9 +3448,9 @@ app.post('/api/favorites/remove', async (req, res) => { 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({ @@ -3449,7 +3459,7 @@ app.post('/api/favorites/list', async (req, res) => { message: '缺少必要参数' }); } - + // 获取收藏列表,并关联查询商品信息 const favorites = await Favorite.findAll({ where: { user_phone }, @@ -3462,9 +3472,9 @@ app.post('/api/favorites/list', async (req, res) => { ], order: [['date', 'DESC']] }); - + console.log('获取收藏列表成功,数量:', favorites.length); - + // 处理图片URL,确保是数组格式(与商品详情API保持一致的处理逻辑) const processedFavorites = favorites.map(favorite => { const favoriteJSON = favorite.toJSON(); @@ -3492,7 +3502,7 @@ app.post('/api/favorites/list', async (req, res) => { } return favoriteJSON; }); - + res.status(200).json({ success: true, code: 200, @@ -4168,7 +4178,7 @@ app.post('/api/cart/add', async (req, res) => { } ); console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); - + // 同步更新用户表的updated_at为UTC时间 const userUtcTime = getBeijingTimeISOString(); console.log('用户表更新 - UTC时间:', userUtcTime); @@ -4203,7 +4213,7 @@ app.post('/api/cart/add', async (req, res) => { added_at: getBeijingTimeISOString() }); console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); - + // 同步更新用户表的updated_at为UTC时间 const updateUserUtcTime = getBeijingTimeISOString() console.log('用户表更新 - UTC时间:', updateUserUtcTime); @@ -5005,7 +5015,7 @@ app.post('/api/product/publish', async (req, res) => { console.log('- 是否为字符串类型:', grossWeightDetails.isString); console.log('- 是否为非数字字符串:', grossWeightDetails.isNonNumeric); console.log('- 转换后的值:', grossWeightDetails.value, '类型:', typeof grossWeightDetails.value); - + // 处理非数字毛重:添加特殊标记处理 if (grossWeightDetails.isEmpty) { product.grossWeight = ''; @@ -5194,10 +5204,10 @@ app.post('/api/product/edit', async (req, res) => { let status = req.body.status; let product = req.body.product; - console.log('【参数解析】前端发送的数据结构:', { - openid, - productId, - status, + console.log('【参数解析】前端发送的数据结构:', { + openid, + productId, + status, product: !!product, productKeys: product ? Object.keys(product) : '无product' }); @@ -5212,10 +5222,10 @@ app.post('/api/product/edit', async (req, res) => { // 验证必填字段 if (!openid || !productId || !product) { - console.error('【参数验证】缺少必要参数:', { - openid: !!openid, - productId: !!productId, - product: !!product + console.error('【参数验证】缺少必要参数:', { + openid: !!openid, + productId: !!productId, + product: !!product }); return res.status(400).json({ success: false, @@ -5258,7 +5268,7 @@ app.post('/api/product/edit', async (req, res) => { } // ========== 【新增】编辑商品时的现有商品地区字段调试 ========== - console.log('【地区字段调试】找到现有商品:', { + console.log('【地区字段调试】找到现有商品:', { productId: existingProduct.productId, productName: existingProduct.productName, existingRegion: existingProduct.region, // 特别显示现有地区字段 @@ -5266,7 +5276,7 @@ app.post('/api/product/edit', async (req, res) => { }); // ========== 地区字段调试结束 ========== - console.log('【商品查找】找到商品:', { + console.log('【商品查找】找到商品:', { productId: existingProduct.productId, productName: existingProduct.productName, status: existingProduct.status, @@ -5305,7 +5315,7 @@ app.post('/api/product/edit', async (req, res) => { // 从数据库获取现有图片URL const rawImageUrls = existingProduct.imageUrls; console.log('【图片处理】数据库中的原始imageUrls:', rawImageUrls, '类型:', typeof rawImageUrls); - + if (Array.isArray(rawImageUrls)) { // 如果已经是数组,直接使用 existingImageUrls = rawImageUrls; @@ -5389,22 +5399,22 @@ app.post('/api/product/edit', async (req, res) => { // 只有在确实有图片被删除时才执行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(() => { @@ -5413,52 +5423,52 @@ app.post('/api/product/edit', async (req, res) => { }) .catch(deleteError => { // 【增强错误处理】区分权限错误和其他错误 - if (deleteError.code === 'OSS_ACCESS_DENIED' || - deleteError.message.includes('permission') || - deleteError.originalError?.code === 'AccessDenied') { + 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, + 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 + 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 => + + const successfulDeletes = deleteResults.filter(result => result.status === 'fulfilled' && result.value && result.value.success ).length; - - const permissionDeniedDeletes = deleteResults.filter(result => + + const permissionDeniedDeletes = deleteResults.filter(result => result.status === 'fulfilled' && result.value && result.value.permissionDenied ).length; - - const otherFailedDeletes = deleteResults.filter(result => + + 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, '个文件'); @@ -5498,21 +5508,21 @@ app.post('/api/product/edit', async (req, res) => { 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)) { - + 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, @@ -5526,21 +5536,21 @@ app.post('/api/product/edit', async (req, res) => { 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)) { - + 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, @@ -5571,17 +5581,17 @@ app.post('/api/product/edit', async (req, res) => { let originalGrossWeight = null; // 处理非空非数字的毛重值 - 修改为保留原始值以支持中文输入 - if (!grossWeightDetails.isEmpty && - grossWeightDetails.isString && - isNaN(parseFloat(grossWeightDetails.value)) && - !isFinite(grossWeightDetails.value)) { - + 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, @@ -5608,7 +5618,7 @@ app.post('/api/product/edit', async (req, res) => { // 准备更新的商品数据 // 【关键修复】根据前端数据结构确定最终状态 const finalStatus = status && status.trim() !== '' ? status : existingProduct.status; - + // 如果是重新提交审核的情况 let isResubmit = false; if (status === 'pending_review' && ['rejected', 'sold_out'].includes(existingProduct.status)) { @@ -5619,9 +5629,9 @@ app.post('/api/product/edit', async (req, res) => { // ========== 【新增】编辑商品时的地区字段处理 ========== console.log('【地区字段调试】准备更新商品数据 - 处理地区字段'); const finalRegion = product.region || existingProduct.region || ''; - console.log('【地区字段调试】最终确定的地区字段:', finalRegion, '来源:', - product.region ? '新提交的数据' : - existingProduct.region ? '现有商品数据' : '默认空值'); + console.log('【地区字段调试】最终确定的地区字段:', finalRegion, '来源:', + product.region ? '新提交的数据' : + existingProduct.region ? '现有商品数据' : '默认空值'); // ========== 地区字段调试结束 ========== // 增强数据同步逻辑 @@ -5649,7 +5659,7 @@ app.post('/api/product/edit', async (req, res) => { rejectReason: isResubmit ? null : existingProduct.rejectReason, updated_at: getBeijingTime() }; - + // 【新增】更新前的最终数据验证 console.log('【数据更新】更新前最终数据验证:'); console.log('- 价格字段:', updatedProductData.price, '类型:', typeof updatedProductData.price, '是否非数字标记:', updatedProductData.isNonNumericPrice); @@ -5660,9 +5670,9 @@ app.post('/api/product/edit', async (req, res) => { console.log('【地区字段调试】更新数据中的region字段:', updatedProductData.region, '类型:', typeof updatedProductData.region); // ========== 地区字段调试结束 ========== - console.log('【数据更新】准备更新商品数据:', { - productId, - oldStatus: existingProduct.status, + console.log('【数据更新】准备更新商品数据:', { + productId, + oldStatus: existingProduct.status, newStatus: updatedProductData.status, isResubmit: isResubmit, updatedFields: Object.keys(updatedProductData) @@ -5690,7 +5700,7 @@ app.post('/api/product/edit', async (req, res) => { console.log('【数据更新】使用save方法成功更新商品数据'); } catch (saveError) { console.error('【数据更新】使用save方法更新商品数据失败:', saveError); - + // 如果save方法失败,尝试使用update方法 try { [updatedCount] = await Product.update(updatedProductData, { @@ -5718,7 +5728,7 @@ app.post('/api/product/edit', async (req, res) => { // 获取更新后的商品信息 const updatedProduct = await Product.findOne({ where: { productId: productId } }); - + if (!updatedProduct) { console.error('【数据更新】无法获取更新后的商品信息'); return res.status(500).json({ @@ -5845,7 +5855,7 @@ const SettlementApplication = sequelize.define('SettlementApplication', { app.get('/api/settlement/status/:userId', async (req, res) => { try { const { userId } = req.params; - + console.log('获取入驻状态请求:', { userId }); // 查找用户的入驻申请 @@ -5895,17 +5905,17 @@ app.get('/api/settlement/status/:userId', async (req, res) => { app.post('/api/settlement/submit', async (req, res) => { try { const { openid, - collaborationid, - cooperation, - company, - phoneNumber, - province, - city, - district, - businesslicenseurl, - proofurl, - brandurl - } = req.body; + collaborationid, + cooperation, + company, + phoneNumber, + province, + city, + district, + businesslicenseurl, + proofurl, + brandurl + } = req.body; console.log('收到入驻申请:', req.body); @@ -5949,11 +5959,11 @@ app.post('/api/settlement/submit', async (req, res) => { else if (collaborationid === 'wholesale') { collaborationidCN = '贸易商'; } - + // 转换cooperation为中文合作模式(使用明确的英文标识以避免混淆) // 直接使用传入的中文合作模式,确保支持:资源委托、自主定义销售、区域包场合作、其他 let cooperationCN = cooperation; - + // 如果传入的是英文值,则进行映射 if (cooperation === 'resource_delegation') { cooperationCN = '资源委托'; @@ -5973,7 +5983,7 @@ app.post('/api/settlement/submit', async (req, res) => { cooperationCN = '自主定义销售'; } // 确保存储的是中文合作模式 - + // 执行更新操作 const updateResult = await User.update({ collaborationid: collaborationidCN, // 合作商身份(中文) @@ -5991,11 +6001,11 @@ app.post('/api/settlement/submit', async (req, res) => { }, { where: { userId: user.userId } }); - + // 验证更新是否成功 const updatedUser = await User.findOne({ where: { userId: user.userId } }); console.log('更新后的用户状态:', updatedUser.partnerstatus); - + // 双重确认:如果状态仍不是underreview,再次更新 if (updatedUser && updatedUser.partnerstatus !== 'underreview') { console.warn('检测到状态未更新正确,执行二次更新:', updatedUser.partnerstatus); @@ -6030,7 +6040,7 @@ app.post('/api/settlement/submit', async (req, res) => { app.post('/api/settlement/upload', upload.single('file'), async (req, res) => { try { const { openid, fileType } = req.body; - + console.log('收到入驻文件上传请求:', { openid, fileType }); if (!openid || !fileType) { @@ -6058,7 +6068,7 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => { // 确保返回的URL是干净的字符串,移除可能存在的反引号和空格 const cleanFileUrl = String(fileUrl).replace(/[` ]/g, ''); - + res.json({ success: true, code: 200, @@ -6070,7 +6080,7 @@ app.post('/api/settlement/upload', upload.single('file'), async (req, res) => { }); } catch (error) { console.error('入驻文件上传失败:', error); - + // 清理临时文件 if (req.file && fs.existsSync(req.file.path)) { fs.unlinkSync(req.file.path); @@ -6246,7 +6256,7 @@ 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, @@ -6267,7 +6277,7 @@ 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, @@ -6290,7 +6300,7 @@ app.get('/api/conversations/:conversationId/messages', async (req, res) => { 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 = ? @@ -6298,16 +6308,16 @@ app.get('/api/conversations/:conversationId/messages', async (req, res) => { 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, @@ -6335,34 +6345,34 @@ app.get('/api/conversations/:conversationId/messages', async (req, res) => { 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, @@ -6382,9 +6392,9 @@ app.post('/api/chat/messages', async (req, res) => { 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({ @@ -6393,19 +6403,19 @@ app.post('/api/chat/send', async (req, res) => { 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, @@ -6433,10 +6443,10 @@ 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'; @@ -6454,13 +6464,13 @@ app.post('/api/conversations/:conversationId/read', async (req, res) => { } 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, @@ -6500,11 +6510,11 @@ app.get('/api/managers', async (req, res) => { // 获取请求参数中的角色类型(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 != ""'; @@ -6514,14 +6524,14 @@ app.get('/api/managers', async (req, res) => { 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 { @@ -6535,7 +6545,7 @@ app.get('/api/managers', async (req, res) => { // 表不存在或查询失败时,使用空数组 onlineStatusData = []; } - + // 创建在线状态映射 const onlineStatusMap = {}; // 检查onlineStatusData是否存在,防止undefined调用forEach @@ -6544,15 +6554,15 @@ app.get('/api/managers', async (req, res) => { 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中 @@ -6561,13 +6571,13 @@ app.get('/api/managers', async (req, res) => { 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')}`, @@ -6586,7 +6596,7 @@ app.get('/api/managers', async (req, res) => { information: person.information || '', // 添加个人信息字段 online: isManagerOnline(person.id, person.managerId) // 综合检查在线状态 })); - + res.status(200).json({ success: true, code: 200, @@ -6601,21 +6611,21 @@ app.get('/api/managers', async (req, res) => { }); } }); - - + + // 认证处理函数 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; @@ -6624,18 +6634,18 @@ async function handleAuth(ws, data) { 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, @@ -6646,13 +6656,13 @@ async function handleAuth(ws, data) { 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', @@ -6660,9 +6670,9 @@ async function handleAuth(ws, data) { })); return; } - + console.log(`🔍 最终用户类型判断: finalUserType=${finalUserType}`); - + // 验证用户或客服身份 - 使用finalUserType进行判断,不再重复定义userType if ((finalUserType === 'user' || finalUserType.includes('customer')) && userId) { // 改进的用户认证逻辑,支持字符串ID并增加容错性 @@ -6675,7 +6685,7 @@ async function handleAuth(ws, data) { { replacements: [userId] } ); userExists = existingUsers && existingUsers.length > 0; - + if (userExists) { console.log(`✅ 用户ID验证成功: userId=${userId} 存在于数据库中`); } else { @@ -6684,14 +6694,14 @@ async function handleAuth(ws, data) { } 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 { @@ -6704,13 +6714,13 @@ async function handleAuth(ws, data) { console.error('❌ 用户验证过程中发生严重错误:', error); // 即使出错也尝试继续,只记录错误不中断认证 } - + // 继续设置连接信息,确保使用字符串ID connection.userId = userId; connection.isUser = true; connection.userType = 'user'; onlineUsers.set(userId, ws); - + // 尝试更新在线状态,但不中断认证流程 try { await updateUserOnlineStatus(userId, 1); @@ -6718,13 +6728,13 @@ async function handleAuth(ws, data) { } 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')) { // 客服认证逻辑改进,增加容错性 @@ -6743,7 +6753,7 @@ async function handleAuth(ws, data) { })); return; } - + // 检查managerId是否在personnel表中存在,增加容错机制 try { let managerExists = false; @@ -6752,9 +6762,9 @@ async function handleAuth(ws, data) { 'SELECT id FROM userlogin.personnel WHERE id = ? LIMIT 1', { replacements: [stringManagerId] } ); - + managerExists = existingManagers && existingManagers.length > 0; - + if (!managerExists) { console.warn(`⚠️ 客服ID在personnel表中不存在: managerId=${stringManagerId}`); // 不再直接拒绝,而是允许继续连接但记录警告 @@ -6762,32 +6772,32 @@ async function handleAuth(ws, data) { } 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')}`); @@ -6842,13 +6852,13 @@ async function updateUserOnlineStatus(userId, status) { '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) { @@ -6871,13 +6881,13 @@ async function updateManagerOnlineStatus(managerId, status) { '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 { @@ -6887,11 +6897,11 @@ async function updateManagerOnlineStatus(managerId, status) { '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}`); @@ -6899,7 +6909,7 @@ async function updateManagerOnlineStatus(managerId, status) { '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`); @@ -6914,13 +6924,13 @@ async function updateManagerOnlineStatus(managerId, status) { 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) { @@ -6930,7 +6940,7 @@ async function updateManagerOnlineStatus(managerId, status) { })); } }); - + // 通知其他客服状态变化 onlineManagers.forEach((ws, id) => { if (id !== managerId) { @@ -6954,7 +6964,7 @@ function startConnectionMonitoring() { const now = Date.now(); connections.forEach((connection, connectionId) => { const { ws, lastActive = now } = connection; - + // 如果超过60秒没有活动,关闭连接 if (now - lastActive > 60000) { console.log(`关闭超时连接: ${connectionId}`); @@ -6965,7 +6975,7 @@ function startConnectionMonitoring() { } } }); - + // 发送广播心跳给所有在线用户和客服 onlineUsers.forEach(ws => { try { @@ -6974,7 +6984,7 @@ function startConnectionMonitoring() { console.error('发送心跳失败给用户:', e); } }); - + onlineManagers.forEach(ws => { try { ws.send(JSON.stringify({ type: 'heartbeat' })); @@ -7015,30 +7025,30 @@ async function createOrGetConversation(userId, managerId) { // 关键修复:明确区分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 { // 尝试查找已存在的会话 - 双向检查,避免重复创建 @@ -7046,7 +7056,7 @@ async function createOrGetConversation(userId, managerId) { '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]; // 如果会话已结束,重新激活 @@ -7059,19 +7069,19 @@ async function createOrGetConversation(userId, managerId) { } 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, + conversationId, + finalUserId, finalManagerId, onlineUsers.has(finalUserId) ? 1 : 0, onlineManagers.has(finalManagerId) ? 1 : 0, @@ -7080,7 +7090,7 @@ async function createOrGetConversation(userId, managerId) { ] } ); - + // 返回新创建的会话 return { conversation_id: conversationId, @@ -7104,17 +7114,17 @@ async function createOrGetConversation(userId, managerId) { } } } - + // 所有尝试都失败后,再次查询以获取可能已存在的会话 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('无法创建或获取会话,所有尝试均失败'); } @@ -7128,11 +7138,11 @@ async function getUserConversations(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 = ?', @@ -7142,7 +7152,7 @@ async function getUserConversations(userId) { conversation.userNickName = users[0].nickName; conversation.userAvatar = users[0].avatarUrl; } - + // 获取客服信息 try { const [personnel] = await sequelize.query( @@ -7178,11 +7188,11 @@ async function getManagerConversations(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 = ?', @@ -7192,7 +7202,7 @@ async function getManagerConversations(managerId) { conversation.userNickName = users[0].nickName; conversation.userAvatar = users[0].avatarUrl; } - + // 获取客服信息 const [personnel] = await sequelize.query( 'SELECT name FROM userlogin.personnel WHERE id = ?', @@ -7215,11 +7225,11 @@ 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为空'); @@ -7229,7 +7239,7 @@ async function handleChatMessage(ws, payload) { })); return; } - + if (!payload.content) { console.error('错误: payload.content为空'); ws.send(JSON.stringify({ @@ -7238,21 +7248,21 @@ async function handleChatMessage(ws, payload) { })); 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({ @@ -7261,61 +7271,61 @@ async function handleChatMessage(ws, payload) { })); 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或空值 + 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; - 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不匹配,正在修复'); @@ -7328,18 +7338,18 @@ async function handleChatMessage(ws, payload) { } 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]; @@ -7358,9 +7368,9 @@ async function handleChatMessage(ws, payload) { tempManagerId = tempIdParts[1]; } } - + console.log('从临时ID提取信息:', { tempManagerId, tempUserId }); - + // 创建或获取真实会话 conversation = await createOrGetConversation(tempUserId, tempManagerId); console.log('创建的真实会话:', conversation); @@ -7377,7 +7387,7 @@ async function handleChatMessage(ws, payload) { ); if (!conversations || conversations.length === 0) { console.warn('会话不存在,尝试从userId和managerId创建'); - + // 尝试从payload中获取managerId if (payload.managerId) { receiverId = validateManagerId(payload.managerId); @@ -7401,7 +7411,7 @@ async function handleChatMessage(ws, payload) { } } } - + // 验证会话的userId是否与当前用户匹配,不匹配则修复 // 关键修复:只有当会话的userId不是当前用户ID,并且会话的managerId不是当前用户ID时,才更新会话的userId // 避免将用户ID和客服ID错误地交换 @@ -7420,87 +7430,87 @@ async function handleChatMessage(ws, payload) { // 客服发送消息给用户 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', + + 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 (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] } + 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] } ); - - console.log(`✅ 成功修复会话用户ID: conversationId=${conversationId}, 从0更新为${correctUserId}`); - } else { - // 如果找不到正确的用户ID,则抛出错误,不允许存储无效消息 - console.error('❌ 无法找到有效的用户ID,消息发送失败'); - throw new Error('会话用户信息不完整,无法发送消息'); + + 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; + + // 确保会话对象中的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 = { @@ -7509,11 +7519,11 @@ async function handleChatMessage(ws, payload) { managerId: conversation.managerId, ...conversation }; - + // 生成消息ID和时间戳 const messageId = payload.messageId || crypto.randomUUID(); // 允许前端提供messageId const now = getBeijingTime(); - + console.log('准备存储消息:', { messageId, conversationId: conversation.conversation_id, @@ -7521,7 +7531,7 @@ async function handleChatMessage(ws, payload) { senderId, receiverId }); - + try { // 关键修复:确保storeMessage被正确调用 const storeResult = await storeMessage({ @@ -7537,17 +7547,17 @@ async function handleChatMessage(ws, payload) { 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); @@ -7556,7 +7566,7 @@ async function handleChatMessage(ws, payload) { await updateUnreadCount(conversation.conversation_id, 'unread_count', 1); console.log('更新用户未读数:', { conversationId: conversation.conversation_id }); } - + // 构造消息对象 const messageData = { messageId, @@ -7573,7 +7583,7 @@ async function handleChatMessage(ws, payload) { status: 1, createdAt: now }; - + // 发送消息给接收者 let receiverWs; if (senderType === 1) { @@ -7585,7 +7595,7 @@ async function handleChatMessage(ws, payload) { receiverWs = onlineUsers.get(receiverId); console.log(`尝试转发消息给用户 ${receiverId},用户是否在线:`, !!receiverWs); } - + // 处理特殊情况:当发送者和接收者是同一个人(既是用户又是客服) if (!receiverWs && senderId == receiverId) { if (senderType === 1) { @@ -7597,7 +7607,7 @@ async function handleChatMessage(ws, payload) { } console.log('处理同一会话内消息转发:', { senderId, hasReceiverWs: !!receiverWs }); } - + if (receiverWs) { try { receiverWs.send(JSON.stringify({ @@ -7612,7 +7622,7 @@ async function handleChatMessage(ws, payload) { } else { console.log('接收者不在线,消息已存储但未实时推送'); } - + // 发送确认给发送者 ws.send(JSON.stringify({ type: 'message_sent', @@ -7622,9 +7632,9 @@ async function handleChatMessage(ws, payload) { conversationId: conversation.conversation_id } })); - + console.log('消息处理完成:', { messageId, status: 'success' }); - + } catch (error) { console.error('处理聊天消息失败:', { error: error.message, @@ -7640,31 +7650,31 @@ async function handleChatMessage(ws, payload) { // 存储消息到数据库 async function storeMessage(messageData) { - const { messageId, conversationId, senderType, senderId, receiverId, - contentType, content, fileUrl, fileSize, duration, createdAt } = 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, @@ -7674,35 +7684,35 @@ async function storeMessage(messageData) { 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, + stringMessageId, + stringConversationId, + senderType, + stringSenderId, stringReceiverId, contentType || 1, // 默认文本消息 - content, - fileUrl || null, - fileSize || null, + content, + fileUrl || null, + fileSize || null, duration || null, - createdAt || getBeijingTime(), + 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('存储消息到数据库失败:', { @@ -7728,7 +7738,7 @@ async function updateConversationLastMessage(conversationId, lastMessage, timest 'SELECT * FROM chat_conversations WHERE conversation_id = ? LIMIT 1', { replacements: [conversationId] } ); - + if (conversations && conversations.length > 0) { // 只有当会话存在时,才更新最后消息 await sequelize.query( @@ -7764,15 +7774,15 @@ async function updateUnreadCount(conversationId, countField, increment) { // 处理未读消息标记 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({ @@ -7781,18 +7791,18 @@ async function handleMarkRead(ws, payload) { })); 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 (?)'; @@ -7806,7 +7816,7 @@ async function handleMarkRead(ws, payload) { // 客服标记用户消息为已读 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 (?)'; @@ -7819,27 +7829,27 @@ async function handleMarkRead(ws, payload) { } 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, @@ -7860,7 +7870,7 @@ async function handleSessionMessage(ws, data) { 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', @@ -7868,7 +7878,7 @@ async function handleSessionMessage(ws, data) { })); return; } - + try { switch (action) { case 'get_conversations': @@ -7894,7 +7904,7 @@ async function handleSessionMessage(ws, data) { } else { throw new Error('未知的连接类型'); } - + // 为每个会话更新在线状态 if (conversations && conversations.length > 0) { const updatedConversations = await Promise.all(conversations.map(async (conv) => { @@ -7902,7 +7912,7 @@ async function handleSessionMessage(ws, data) { 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( @@ -7912,12 +7922,12 @@ async function handleSessionMessage(ws, data) { conv.user_online = userOnline; conv.cs_online = csOnline; } - + return conv; })); conversations = updatedConversations; } - + // 支持两种响应格式,确保兼容性 if (action === 'list') { // 兼容测试脚本的响应格式 @@ -7932,7 +7942,7 @@ async function handleSessionMessage(ws, data) { payload: { conversations: conversations || [] } })); } - + console.log('会话列表推送成功:', { action, responseType: action === 'list' ? 'session_list' : 'conversations_list' }); } catch (error) { console.error('获取会话列表失败:', { error: error.message, action }); @@ -7942,19 +7952,19 @@ async function handleSessionMessage(ws, data) { })); } 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( @@ -7964,16 +7974,16 @@ async function handleSessionMessage(ws, data) { 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: { @@ -7987,9 +7997,9 @@ async function handleSessionMessage(ws, data) { } } })); - + console.log('消息获取成功:', { conversationId, messageCount: messages.length }); - + // 如果是客服查看消息,自动将未读消息标记为已读 if (connection.isManager) { const readTime = getBeijingTime(); @@ -7997,13 +8007,13 @@ async function handleSessionMessage(ws, data) { '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) { @@ -8014,19 +8024,19 @@ async function handleSessionMessage(ws, data) { })); } 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 } @@ -8047,7 +8057,7 @@ 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, @@ -8055,7 +8065,7 @@ app.post('/api/chat/list', async (req, res) => { message: '用户手机号不能为空' }); } - + // 从wechat_app数据库的chat_list表中查询用户的聊天记录 const chatList = await sequelize.query( 'SELECT * FROM chat_list WHERE user_phone = ?', @@ -8064,9 +8074,9 @@ app.post('/api/chat/list', async (req, res) => { type: Sequelize.QueryTypes.SELECT } ); - + console.log('找到聊天记录数量:', chatList.length); - + // 如果没有聊天记录,返回空数组 if (chatList.length === 0) { return res.status(200).json({ @@ -8076,7 +8086,7 @@ app.post('/api/chat/list', async (req, res) => { data: [] }); } - + // 返回聊天列表数据 res.status(200).json({ success: true, @@ -8099,7 +8109,7 @@ 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, @@ -8107,15 +8117,15 @@ app.post('/api/chat/delete', async (req, res) => { 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, @@ -8136,7 +8146,7 @@ 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, @@ -8144,7 +8154,7 @@ app.post('/api/chat/add', async (req, res) => { message: '用户手机号和业务员手机号不能为空' }); } - + // 检查chat_list表是否存在,如果不存在则创建 try { await sequelize.query( @@ -8163,7 +8173,7 @@ app.post('/api/chat/add', async (req, res) => { } catch (createError) { console.warn('创建chat_list表时出错(可能已存在):', createError.message); } - + // 如果表已存在但没有唯一索引,尝试添加 try { await sequelize.query( @@ -8173,7 +8183,7 @@ app.post('/api/chat/add', async (req, res) => { } catch (indexError) { console.warn('添加唯一索引失败(可能已存在):', indexError.message); } - + // 先查询是否已存在两条记录 const [existingRecords] = await sequelize.query( `SELECT user_phone, manager_phone FROM chat_list WHERE @@ -8181,17 +8191,17 @@ app.post('/api/chat/add', async (req, res) => { (user_phone = ? AND manager_phone = ?)`, { replacements: [user_phone, manager_phone, manager_phone, user_phone] } ); - + // 统计现有记录 - const hasRecord1 = existingRecords.some(record => + const hasRecord1 = existingRecords.some(record => record.user_phone === user_phone && record.manager_phone === manager_phone ); - const hasRecord2 = existingRecords.some(record => + 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) { @@ -8204,7 +8214,7 @@ app.post('/api/chat/add', async (req, res) => { } else { console.log('ℹ️ 记录1已存在: user_phone -> manager_phone'); } - + if (!hasRecord2) { await sequelize.query( 'INSERT INTO chat_list (user_phone, manager_phone) VALUES (?, ?)', @@ -8215,9 +8225,9 @@ app.post('/api/chat/add', async (req, res) => { } else { console.log('ℹ️ 记录2已存在: manager_phone -> user_phone'); } - + console.log(`✅ 聊天记录处理完成,新插入 ${insertedCount} 条记录`); - + res.status(200).json({ success: true, message: '聊天记录添加成功' @@ -8237,7 +8247,7 @@ 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, @@ -8245,7 +8255,7 @@ app.post('/api/personnel/get', async (req, res) => { message: '手机号不能为空' }); } - + // 从userlogin数据库的personnel表中查询业务员信息 const personnel = await userLoginSequelize.query( 'SELECT * FROM personnel WHERE phoneNumber = ?', @@ -8254,9 +8264,9 @@ app.post('/api/personnel/get', async (req, res) => { type: Sequelize.QueryTypes.SELECT } ); - + console.log('找到业务员信息数量:', personnel.length); - + if (personnel.length === 0) { return res.status(200).json({ success: false, @@ -8264,7 +8274,7 @@ app.post('/api/personnel/get', async (req, res) => { message: '未找到该手机号对应的业务员信息' }); } - + // 返回业务员信息 res.status(200).json({ success: true, @@ -8292,10 +8302,10 @@ updateProductContacts().then(() => { // 启动服务器监听 - 使用配置好的http server对象 // 监听0.0.0.0以允许通过所有网络接口访问(包括IPv4地址) // 启动连接监控 -startConnectionMonitoring(); -console.log('连接监控服务已启动'); + startConnectionMonitoring(); + console.log('连接监控服务已启动'); -server.listen(PORT, '0.0.0.0', () => { + 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}`);