From dbace286a63eba3d582ebcb45d0a3f03900307f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E9=A3=9E=E6=B4=8B?= <15778543+xufeiyang6017@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 10:39:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMySQL=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=97=B6=E9=97=B4=E5=B0=918=E5=B0=8F=E6=97=B6=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E7=A1=AE=E4=BF=9D=E7=99=BB=E5=BD=95=E5=92=8C?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E6=97=B6=E9=97=B4=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server-example/server-mysql.js | 115 +++++++++++++++++++-------------- test-time-backend.js | 39 +++++++++++ test-time-frontend.js | 34 ++++++++++ utils/api.js | 4 +- utils/time.js | 110 +++++++++++++++++++++++++++++++ 5 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 test-time-backend.js create mode 100644 test-time-frontend.js create mode 100644 utils/time.js diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index 589696f..83f1e30 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -1,3 +1,17 @@ +// 时间处理辅助函数 - 获取UTC+8时间 +function getBeijingTime() { + const now = new Date(); + return new Date(now.getTime() + 8 * 60 * 60 * 1000); // 手动加8小时 +} + +function getBeijingTimeISOString() { + return getBeijingTime().toISOString(); +} + +function getBeijingTimeTimestamp() { + return getBeijingTime().getTime(); +} + // 类型处理辅助函数 - 添加于修复聊天功能 function ensureStringId(id) { return String(id).trim(); @@ -293,7 +307,7 @@ const wechatAppSequelize = new Sequelize( define: { timestamps: false }, - timezone: '+00:00' // 设置时区为UTC + timezone: '+08:00' // 设置时区为UTC+8 } ); @@ -316,7 +330,7 @@ const userLoginSequelize = new Sequelize( define: { timestamps: false }, - timezone: '+00:00' // 设置时区为UTC + timezone: '+08:00' // 设置时区为UTC+8 } ); @@ -500,7 +514,7 @@ app.get('/api/conversations/user/:userId', async (req, res) => { user_id: conv.userId, manager_id: conv.managerId, last_message: conv.last_message || '', - last_message_time: lastMessageTime ? lastMessageTime.toISOString() : null, + last_message_time: lastMessageTime ? new Date(lastMessageTime.getTime() + 8 * 60 * 60 * 1000).toISOString() : null, unread_count: conv.unread_count || 0, status: conv.status }; @@ -1114,15 +1128,16 @@ async function createUserAssociations(user) { // 使用事务确保操作原子性 await sequelize.transaction(async (transaction) => { // 1. 处理联系人记录 - 使用INSERT ... ON DUPLICATE KEY UPDATE确保无论如何都只保留一条记录 + const currentTime = getBeijingTime(); await sequelize.query( `INSERT INTO contacts (userId, name, phoneNumber, created_at, updated_at) - VALUES (?, ?, ?, NOW(), NOW()) + VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name = VALUES(name), phoneNumber = VALUES(phoneNumber), - updated_at = NOW()`, + updated_at = ?`, { - replacements: [user.userId, user.name || '默认联系人', user.phoneNumber || ''], + replacements: [user.userId, user.name || '默认联系人', user.phoneNumber || '', currentTime, currentTime, currentTime], transaction: transaction } ); @@ -1131,11 +1146,11 @@ async function createUserAssociations(user) { // 2. 处理用户管理记录 - 使用相同策略 await sequelize.query( `INSERT INTO usermanagements (userId, created_at, updated_at) - VALUES (?, NOW(), NOW()) + VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE - updated_at = NOW()`, + updated_at = ?`, { - replacements: [user.userId], + replacements: [user.userId, currentTime, currentTime, currentTime], transaction: transaction } ); @@ -1179,7 +1194,7 @@ app.post('/api/user/upload', async (req, res) => { await User.update( { ...userData, - updated_at: new Date() + updated_at: getBeijingTime() }, { where: { openid: userData.openid } @@ -1194,8 +1209,8 @@ app.post('/api/user/upload', async (req, res) => { user = await User.create({ ...userData, notice: 'new', // 创建用户时固定设置notice为new - created_at: new Date(), - updated_at: new Date() + created_at: getBeijingTime(), + updated_at: getBeijingTime() }); // 使用统一的关联记录创建函数 @@ -1289,7 +1304,7 @@ app.post('/api/user/decodePhone', async (req, res) => { await User.update( { phoneNumber: phoneNumber, - updated_at: new Date() + updated_at: getBeijingTime() }, { where: { openid } @@ -1360,7 +1375,7 @@ app.post('/api/wechat/getOpenid', async (req, res) => { await User.update( { session_key: session_key, - updated_at: new Date() + updated_at: getBeijingTime() }, { where: { openid } @@ -1385,8 +1400,8 @@ app.post('/api/wechat/getOpenid', async (req, res) => { cooperation: '', // 默认空字符串 businesslicenseurl: '', // 默认空字符串 notice: 'new', // 创建用户时固定设置notice为new - created_at: new Date(), - updated_at: new Date() + created_at: getBeijingTime(), + updated_at: getBeijingTime() }); // 为新创建的用户创建关联记录 @@ -1547,7 +1562,7 @@ app.post('/api/user/update', async (req, res) => { await User.update( { ...updateData, - updated_at: new Date() + updated_at: getBeijingTime() }, { where: { openid } @@ -1711,13 +1726,13 @@ app.post('/api/product/list', async (req, res) => { // 确保created_at字段存在并转换为ISO字符串格式 if (!productJSON.created_at) { console.log('商品缺少created_at字段,使用默认值'); - productJSON.created_at = new Date().toISOString(); + productJSON.created_at = getBeijingTimeISOString(); } else { // 确保created_at是字符串格式 if (productJSON.created_at instanceof Date) { - productJSON.created_at = productJSON.created_at.toISOString(); + productJSON.created_at = new Date(productJSON.created_at.getTime() + 8 * 60 * 60 * 1000).toISOString(); } else if (typeof productJSON.created_at !== 'string') { - productJSON.created_at = new Date(productJSON.created_at).toISOString(); + productJSON.created_at = new Date(new Date(productJSON.created_at).getTime() + 8 * 60 * 60 * 1000).toISOString(); } } @@ -2473,7 +2488,7 @@ try { sellerId: actualSellerId, // 查找最近5分钟内创建的商品 created_at: { - [Op.gt]: new Date(Date.now() - 5 * 60 * 1000) + [Op.gt]: new Date(getBeijingTimeTimestamp() - 5 * 60 * 1000) } }, order: [['created_at', 'DESC']], @@ -2754,7 +2769,7 @@ try { sellerId: actualSellerId, status: productData.status || existingProduct.status || 'pending_review', region: finalRegion, // 使用调试确定的地区字段 - updated_at: new Date(), + updated_at: getBeijingTime(), // 【重要修复】确保保存会话ID sessionId: sessionId || existingProduct.dataValues.sessionId, uploadSessionId: sessionId || existingProduct.dataValues.uploadSessionId, @@ -2762,7 +2777,7 @@ try { hasMultipleImages: mergedImageUrls.length > 1, totalImages: mergedImageUrls.length, // 确保保留原始的创建时间 - created_at: existingProduct.dataValues.created_at || new Date() + created_at: existingProduct.dataValues.created_at || getBeijingTime() }; // ========== 【新增】保存前的地区字段验证 ========== @@ -2798,8 +2813,8 @@ try { // 标记多图片 hasMultipleImages: finalImageUrls.length > 1, totalImages: finalImageUrls.length, - created_at: new Date(), - updated_at: new Date() + created_at: getBeijingTime(), + updated_at: getBeijingTime() }; // ========== 【新增】保存前的地区字段验证 ========== @@ -3330,7 +3345,8 @@ app.post('/api/favorites/add', async (req, res) => { // 创建新的收藏记录 const newFavorite = await Favorite.create({ user_phone, - productId + productId, + date: req.body.date || getBeijingTime() // 使用前端传递的时间或当前UTC+8时间 }); console.log('收藏添加成功:', newFavorite); @@ -3715,7 +3731,7 @@ app.post('/api/products/delete', async (req, res) => { // 直接使用商品实例更新状态 product.status = 'hidden'; - product.updated_at = new Date(); + product.updated_at = getBeijingTime(); try { // 先尝试保存商品实例 @@ -3727,7 +3743,7 @@ app.post('/api/products/delete', async (req, res) => { // 如果保存失败,尝试使用update方法 try { const updateResult = await Product.update( - { status: 'hidden', updated_at: new Date() }, + { status: 'hidden', updated_at: getBeijingTime() }, { where: { productId } } ); console.log('删除商品成功(使用update方法):', { productId, updateResult }); @@ -3741,7 +3757,7 @@ app.post('/api/products/delete', async (req, res) => { { replacements: { status: 'hidden', - updatedAt: new Date(), + updatedAt: getBeijingTime(), productId: productId } } @@ -3876,7 +3892,7 @@ app.post('/api/cart/test-format', async (req, res) => { }, productDataStructure: productData ? Object.keys(productData) : [] }, - timestamp: new Date().toISOString() + timestamp: getBeijingTimeISOString() }); } catch (error) { console.error('测试路由出错:', error); @@ -3886,7 +3902,7 @@ app.post('/api/cart/test-format', async (req, res) => { message: '测试路由处理失败', error: error.message, requestData: req.body, - timestamp: new Date().toISOString() + timestamp: getBeijingTimeISOString() }); } }); @@ -4117,7 +4133,7 @@ app.post('/api/cart/add', async (req, res) => { console.log(`准备创建/更新购物车项: userId=${cartData.userId}, productId=${cartData.productId}`); if (existingItem) { // 添加详细的时间调试日志 - const updateCurrentDate = new Date(); + const updateCurrentDate = getBeijingTime(); const updateUtcTime = updateCurrentDate.toISOString(); console.log('购物车更新 - 当前时间对象:', updateCurrentDate); console.log('购物车更新 - 转换后UTC时间:', updateUtcTime); @@ -4126,7 +4142,7 @@ app.post('/api/cart/add', async (req, res) => { await CartItem.update( { quantity: existingItem.quantity + cartData.quantity, - updated_at: new Date().toISOString() + updated_at: getBeijingTimeISOString() }, { where: { @@ -4137,7 +4153,7 @@ app.post('/api/cart/add', async (req, res) => { console.log(`更新购物车项成功: id=${existingItem.id}, 新数量=${existingItem.quantity + cartData.quantity}`); // 同步更新用户表的updated_at为UTC时间 - const userUtcTime = new Date().toISOString(); + const userUtcTime = getBeijingTimeISOString(); console.log('用户表更新 - UTC时间:', userUtcTime); await User.update( { updated_at: userUtcTime }, @@ -4148,8 +4164,8 @@ app.post('/api/cart/add', async (req, res) => { // 不存在,创建新购物车项 console.log('创建新购物车项,所有字段:'); // 添加详细的时间调试日志 - const currentDate = new Date(); - const utcTime = currentDate.toISOString(); + const currentDate = getBeijingTime(); + const utcTime = getBeijingTimeISOString(); console.log('购物车创建 - 当前时间对象:', currentDate); console.log('购物车创建 - 转换后UTC时间:', utcTime); console.log('购物车创建 - 时区设置:', sequelize.options.timezone); @@ -4167,12 +4183,12 @@ app.post('/api/cart/add', async (req, res) => { } await CartItem.create({ ...cartData, - added_at: new Date().toISOString() + added_at: getBeijingTimeISOString() }); console.log(`创建购物车项成功: userId=${cartData.userId}, productId=${cartData.productId}`); // 同步更新用户表的updated_at为UTC时间 - const updateUserUtcTime = new Date().toISOString(); + const updateUserUtcTime = getBeijingTimeISOString() console.log('用户表更新 - UTC时间:', updateUserUtcTime); await User.update( { updated_at: updateUserUtcTime }, @@ -4407,7 +4423,7 @@ app.post('/api/cart/update', async (req, res) => { // 构建更新数据 const updateData = {}; if (quantity !== undefined) updateData.quantity = quantity; - updateData.updated_at = new Date(); + updateData.updated_at = getBeijingTime(); // 如果selected状态发生变化,先获取当前购物车项信息 let cartItem = null; @@ -4599,7 +4615,7 @@ app.get('/api/test-connection', async (req, res) => { success: true, code: 200, message: '服务器连接成功,数据库可用', - timestamp: new Date().toISOString(), + timestamp: getBeijingTimeISOString(), serverInfo: { port: PORT, nodeVersion: process.version, @@ -4626,7 +4642,7 @@ app.get('/api/test-oss-connection', async (req, res) => { res.status(result.success ? 200 : 500).json({ ...result, - timestamp: new Date().toISOString(), + timestamp: getBeijingTimeISOString(), code: result.success ? 200 : 500 }); } catch (error) { @@ -4669,7 +4685,7 @@ app.post('/api/user/debug', async (req, res) => { message: '用户不存在', debugInfo: { searchCriteria: { openid }, - timestamp: new Date().toISOString() + timestamp: getBeijingTimeISOString() } }); } @@ -4734,7 +4750,7 @@ app.post('/api/user/debug', async (req, res) => { userCount: await User.count(), totalProductsInSystem: await Product.count(), timestamp: new Date().toISOString(), - serverTime: new Date().toLocaleString('zh-CN') + serverTime: getBeijingTime().toLocaleString('zh-CN') } }; @@ -4814,7 +4830,7 @@ app.post('/api/product/hide', async (req, res) => { try { // 方法1: 直接保存实例 product.status = 'sold_out'; - product.updated_at = new Date(); + product.updated_at = getBeijingTime(); await product.save(); console.log('商品下架成功(使用save方法):', { productId: product.productId, newStatus: product.status }); } catch (saveError) { @@ -4823,7 +4839,7 @@ app.post('/api/product/hide', async (req, res) => { try { // 方法2: 使用update方法 const updateResult = await Product.update( - { status: 'sold_out', updated_at: new Date() }, + { status: 'sold_out', updated_at: getBeijingTime() }, { where: { productId: productId, sellerId: user.userId } } ); console.log('商品下架成功(使用update方法):', { productId: productId, sellerIdType: typeof user.userId, updateResult }); @@ -4834,7 +4850,7 @@ app.post('/api/product/hide', async (req, res) => { // 方法3: 直接执行SQL语句绕过ORM验证 const replacements = { status: 'sold_out', - updatedAt: new Date(), + updatedAt: getBeijingTime(), productId: productId, sellerId: user.userId }; @@ -5108,7 +5124,7 @@ app.post('/api/product/publish', async (req, res) => { if (createdProduct.status !== 'pending_review') { console.log('⚠️ 警告: 发现状态不一致,强制更新回pending_review'); await Product.update( - { status: 'pending_review', updated_at: new Date() }, + { status: 'pending_review', updated_at: getBeijingTime() }, { where: { productId } } ); @@ -8083,9 +8099,10 @@ async function handleSessionMessage(ws, data) { // 如果是客服查看消息,自动将未读消息标记为已读 if (connection.isManager) { + const readTime = getBeijingTime(); await sequelize.query( - 'UPDATE chat_messages SET is_read = 1, read_time = NOW() WHERE conversation_id = ? AND is_read = 0 AND sender_type = 1', - { replacements: [conversationId] } + 'UPDATE chat_messages SET is_read = 1, read_time = ? WHERE conversation_id = ? AND is_read = 0 AND sender_type = 1', + { replacements: [readTime, conversationId] } ); // 更新会话未读数 diff --git a/test-time-backend.js b/test-time-backend.js new file mode 100644 index 0000000..9b7cc69 --- /dev/null +++ b/test-time-backend.js @@ -0,0 +1,39 @@ +// 后端时间处理函数测试脚本 + +// 复制后端的时间处理函数 +function getBeijingTime() { + const now = new Date(); + return new Date(now.getTime() + 8 * 60 * 60 * 1000); // 手动加8小时 +} + +function getBeijingTimeISOString() { + return getBeijingTime().toISOString(); +} + +function getBeijingTimeTimestamp() { + return getBeijingTime().getTime(); +} + +console.log('=== 后端时间处理函数测试 ==='); + +// 测试当前时间 +const now = Date.now(); +console.log('当前时间戳 (UTC):', now); +console.log('当前时间 (UTC):', new Date(now).toISOString()); + +// 测试getBeijingTime +const beijingTime = getBeijingTime(); +console.log('UTC+8 Date对象:', beijingTime); +console.log('UTC+8 ISO字符串:', beijingTime.toISOString()); + +// 测试getBeijingTimeISOString +const beijingTimeISOString = getBeijingTimeISOString(); +console.log('getBeijingTimeISOString():', beijingTimeISOString); + +// 测试getBeijingTimeTimestamp +const beijingTimeTimestamp = getBeijingTimeTimestamp(); +console.log('UTC+8时间戳 (手动加8小时):', beijingTimeTimestamp); +console.log('时间差 (毫秒):', beijingTimeTimestamp - now); +console.log('时间差 (小时):', (beijingTimeTimestamp - now) / (1000 * 60 * 60)); + +console.log('=== 测试完成 ==='); diff --git a/test-time-frontend.js b/test-time-frontend.js new file mode 100644 index 0000000..6241b91 --- /dev/null +++ b/test-time-frontend.js @@ -0,0 +1,34 @@ +// 前端时间处理工具测试脚本 +const timeUtils = require('./utils/time.js'); + +console.log('=== 前端时间处理工具测试 ==='); + +// 测试当前时间 +const now = Date.now(); +console.log('当前时间戳 (UTC):', now); +console.log('当前时间 (UTC):', new Date(now).toISOString()); + +// 测试getUtc8Timestamp +const utc8Timestamp = timeUtils.getUtc8Timestamp(); +console.log('UTC+8时间戳 (手动加8小时):', utc8Timestamp); +console.log('时间差 (毫秒):', utc8Timestamp - now); +console.log('时间差 (小时):', (utc8Timestamp - now) / (1000 * 60 * 60)); + +// 测试getUtc8Date +const utc8Date = timeUtils.getUtc8Date(); +console.log('UTC+8 Date对象:', utc8Date); +console.log('UTC+8 ISO字符串:', utc8Date.toISOString()); + +// 测试toUtc8ISOString +const utc8ISOString = timeUtils.toUtc8ISOString(); +console.log('toUtc8ISOString():', utc8ISOString); + +// 测试toUtc8String +const utc8String = timeUtils.toUtc8String(); +console.log('toUtc8String():', utc8String); + +// 测试toBeijingTimeISOString (别名) +const beijingTimeISOString = timeUtils.toBeijingTimeISOString(); +console.log('toBeijingTimeISOString():', beijingTimeISOString); + +console.log('=== 测试完成 ==='); diff --git a/utils/api.js b/utils/api.js index caad4fd..7f6fe0c 100644 --- a/utils/api.js +++ b/utils/api.js @@ -2699,6 +2699,8 @@ module.exports = { addFavorite: function (productId) { console.log('API.addFavorite - productId:', productId); return new Promise((resolve, reject) => { + // 导入时间工具函数 + const timeUtils = require('./time.js'); const userId = wx.getStorageSync('userId'); // 获取用户信息,包含手机号 @@ -2729,7 +2731,7 @@ module.exports = { const favoriteData = { user_phone: userPhone, productId: productId, - date: new Date().toISOString() // 当前时间 + date: timeUtils.toBeijingTimeISOString() // 当前时间 }; console.log('添加收藏请求数据:', favoriteData); diff --git a/utils/time.js b/utils/time.js new file mode 100644 index 0000000..1b1ef0f --- /dev/null +++ b/utils/time.js @@ -0,0 +1,110 @@ +// UTC+8时间处理工具函数 + +// 固定时间戳(北京时间:2025-12-19 17:10:35.186) +const FIXED_UTC8_TIME = 1766135435186; +let isFixedTimeMode = false; // 默认关闭固定时间模式 + +/** + * 获取UTC+8时间戳 + * @param {Date|number} date - 可选,日期对象或时间戳 + * @returns {number} UTC+8时间戳 + */ +function getUtc8Timestamp(date) { + let baseTimestamp; + if (isFixedTimeMode) { + baseTimestamp = FIXED_UTC8_TIME; + } else if (!date) { + baseTimestamp = Date.now(); + } else if (typeof date === 'number') { + baseTimestamp = date; + } else { + baseTimestamp = date.getTime(); + } + return baseTimestamp + 8 * 60 * 60 * 1000; // 手动加8小时 +} + +/** + * 获取UTC+8 Date对象 + * @param {number} timestamp - 可选,时间戳 + * @returns {Date} UTC+8 Date对象 + */ +function getUtc8Date(timestamp) { + const ts = isFixedTimeMode ? FIXED_UTC8_TIME : (timestamp || Date.now()); + const date = new Date(ts + 8 * 60 * 60 * 1000); // 手动加8小时 + return date; +} + +/** + * 将时间转换为UTC+8的ISO字符串(带时区信息) + * @param {Date|number} date - 可选,日期对象或时间戳 + * @returns {string} ISO格式的UTC+8时间字符串 + */ +function toUtc8ISOString(date) { + const timestamp = getUtc8Timestamp(date); + const utcDate = new Date(timestamp); + + const year = utcDate.getUTCFullYear(); + const month = String(utcDate.getUTCMonth() + 1).padStart(2, '0'); + const day = String(utcDate.getUTCDate()).padStart(2, '0'); + const hours = String(utcDate.getUTCHours()).padStart(2, '0'); + const minutes = String(utcDate.getUTCMinutes()).padStart(2, '0'); + const seconds = String(utcDate.getUTCSeconds()).padStart(2, '0'); + const milliseconds = String(utcDate.getUTCMilliseconds()).padStart(3, '0'); + + return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${milliseconds}+08:00`; +} + +/** + * 将时间转换为UTC+8的本地字符串 + * @param {Date|number} date - 可选,日期对象或时间戳 + * @param {Object} options - 可选,格式化选项 + * @returns {string} 本地化的UTC+8时间字符串 + */ +function toUtc8String(date, options = {}) { + const defaultOptions = { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + // 移除timeZone设置,因为getUtc8Timestamp已经返回UTC+8时间戳 + }; + + const timestamp = getUtc8Timestamp(date); + const utc8Date = new Date(timestamp); + + return utc8Date.toLocaleString('zh-CN', { ...defaultOptions, ...options }); +} + +/** + * 设置固定时间模式 + * @param {boolean} mode - true启用,false禁用 + */ +function setFixedTimeMode(mode) { + isFixedTimeMode = mode; +} + +/** + * 获取固定时间模式状态 + * @returns {boolean} 当前固定时间模式状态 + */ +function getFixedTimeMode() { + return isFixedTimeMode; +} + +// 导出函数 +module.exports = { + getUtc8Timestamp, + getUtc8Date, + toUtc8ISOString, + toUtc8String, + setFixedTimeMode, + getFixedTimeMode, + // 向后兼容别名 + getBeijingTimeTimestamp: getUtc8Timestamp, + getBeijingTimeDate: getUtc8Date, + toBeijingTimeISOString: toUtc8ISOString, + toBeijingTimeString: toUtc8String +}; \ No newline at end of file