diff --git a/index.js b/index.js index 11dec0e..3d7641a 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ async function syncData() { try { // 1. 连接数据库 - await databaseService.connect(); + const connection = await databaseService.connect(); // 2. 查询所有需要同步的数据 console.log('正在查询数据库数据...'); @@ -22,7 +22,7 @@ async function syncData() { // 3. 批量提交数据到简道云 console.log('开始向简道云提交数据...'); - const results = await jiandaoyunService.batchSubmitData(syncData); + const results = await jiandaoyunService.batchSubmitData(syncData, connection); // 4. 更新同步状态 console.log('\n更新同步状态...'); diff --git a/src/config/config.js b/src/config/config.js index 83da491..5bd0494 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -17,30 +17,34 @@ module.exports = { fields: { id: 'id', userId: 'userId', - company: 'company', - name: 'nickName', phoneNumber: 'phoneNumber', type: 'type', - city: 'city' + authorizedRegion: 'authorized_region', + nickName: 'nickName' } }, - cartItems: { - name: 'cart_items', + userManagements: { + name: 'usermanagements', fields: { userId: 'userId', - productName: 'productName', - specification: 'specification', - quantity: 'quantity', - yolk: 'yolk' + userName: 'userName' + } + }, + favorites: { + name: 'favorites', + fields: { + userPhone: 'user_phone', + productId: 'productId' } }, products: { name: 'products', fields: { - sellerId: 'sellerId', + productId: 'productId', productName: 'productName', specification: 'specification', quantity: 'quantity', + grossWeight: 'grossWeight', yolk: 'yolk' } } @@ -57,23 +61,16 @@ module.exports = { // 字段映射关系 fieldMapping: { 'userId': '_widget_1765164283327', - 'company': '_widget_1765164283326', 'nickName': '_widget_1765164283341', 'phoneNumber': '_widget_1765164283342', 'type': '_widget_1765171392031', - 'city': '_widget_1765164283330', - // cart_items表映射 (buyer相关) - 'productName-buyer': '_widget_1765164283332', - 'specification-buyer': '_widget_1765164283336', - 'quantity-buyer': '_widget_1765164283337', - 'grossWeight-buyer': '_widget_1765164283338', - 'yolk-buyer': '_widget_1765164283339', - // products表映射 (sell相关) - 'productName-sell': '_widget_1765178808518', - 'specification-sell': '_widget_1765178808519', - 'quantity-sell': '_widget_1765178808520', - 'grossWeight-sell': '_widget_1765178808521', - 'yolk-sell': '_widget_1765178808522' + 'authorizedRegion': '_widget_1765164283330', + 'userName': '_widget_1766021289472', + 'productName': '_widget_1765164283332', + 'specification': '_widget_1765164283336', + 'quantity': '_widget_1765164283337', + 'grossWeight': '_widget_1765164283338', + 'yolk': '_widget_1765164283339' }, // 同步配置 diff --git a/src/services/databaseService.js b/src/services/databaseService.js index 6e6efeb..d6b760c 100644 --- a/src/services/databaseService.js +++ b/src/services/databaseService.js @@ -18,6 +18,7 @@ class DatabaseService { database: config.db.database }); console.log('数据库连接成功'); + return this.connection; } catch (error) { console.error('数据库连接失败:', error.message); throw error; @@ -38,56 +39,86 @@ class DatabaseService { // 根据配置决定是查询所有数据还是仅未同步数据 let usersQuery = `SELECT ${config.tables.users.fields.id}, ${config.tables.users.fields.userId}, - ${config.tables.users.fields.company}, - ${config.tables.users.fields.name}, ${config.tables.users.fields.phoneNumber}, ${config.tables.users.fields.type}, - ${config.tables.users.fields.city} + ${config.tables.users.fields.authorizedRegion}, + ${config.tables.users.fields.nickName}, + jiandaoyun_record_id FROM ${config.tables.users.name}`; - // 如果是增量同步,只查询未同步的数据 + // 如果是增量同步,查询未同步的数据以及有收藏产品的用户 if (config.sync.incremental) { - usersQuery += ` WHERE ${config.sync.statusField} = ${config.sync.unsyncedValue}`; - console.log('启用增量同步模式,只查询未同步的数据'); + usersQuery += ` WHERE ${config.sync.statusField} = ${config.sync.unsyncedValue} + OR EXISTS (SELECT 1 FROM ${config.tables.favorites.name} + WHERE ${config.tables.favorites.fields.userPhone} = ${config.tables.users.fields.phoneNumber})`; + console.log('启用增量同步模式,查询未同步数据及有收藏产品的用户'); } else { console.log('启用全量同步模式,查询所有数据'); } - console.log('执行查询:', usersQuery); + // 添加DISTINCT确保只返回唯一的用户数据 + usersQuery = usersQuery.replace('SELECT', 'SELECT DISTINCT'); + + console.log('执行用户查询:', usersQuery); const [users] = await this.connection.execute(usersQuery); console.log(`查询结果: 共找到 ${users.length} 条需要同步的用户数据`); const syncData = []; - // 为每个用户查询对应的cart_items和products数据 + // 为每个用户查询关联数据 for (const user of users) { const userId = user[config.tables.users.fields.userId]; + const phoneNumber = user[config.tables.users.fields.phoneNumber]; + const jiandaoyunRecordId = user.jiandaoyun_record_id; - // 查询cart_items表(buyer数据) - const [cartItems] = await this.connection.execute( - `SELECT ${config.tables.cartItems.fields.productName}, - ${config.tables.cartItems.fields.specification}, - ${config.tables.cartItems.fields.quantity}, - ${config.tables.cartItems.fields.yolk} - FROM ${config.tables.cartItems.name} - WHERE ${config.tables.cartItems.fields.userId} = ?`, + // 查询负责人信息(usermanagements表) + const [userManagements] = await this.connection.execute( + `SELECT ${config.tables.userManagements.fields.userName} + FROM ${config.tables.userManagements.name} + WHERE ${config.tables.userManagements.fields.userId} = ?`, [userId] ); - // 查询products表(sell数据) - const [products] = await this.connection.execute( - `SELECT ${config.tables.products.fields.productName}, - ${config.tables.products.fields.specification}, - ${config.tables.products.fields.quantity}, - ${config.tables.products.fields.yolk} - FROM ${config.tables.products.name} - WHERE ${config.tables.products.fields.sellerId} = ?`, - [userId] + // 查询用户收藏的产品(favorites表) + const [favorites] = await this.connection.execute( + `SELECT ${config.tables.favorites.fields.productId} + FROM ${config.tables.favorites.name} + WHERE ${config.tables.favorites.fields.userPhone} = ?`, + [phoneNumber] ); + // 查询产品详情(products表) + let products = []; + if (favorites.length > 0) { + const productIds = favorites.map(fav => fav[config.tables.favorites.fields.productId]); + // 构建动态占位符字符串,用于处理IN查询的数组参数 + const placeholders = productIds.map(() => '?').join(','); + const [productsResult] = await this.connection.execute( + `SELECT ${config.tables.products.fields.productName}, + ${config.tables.products.fields.specification}, + ${config.tables.products.fields.quantity}, + ${config.tables.products.fields.grossWeight}, + ${config.tables.products.fields.yolk} + FROM ${config.tables.products.name} + WHERE ${config.tables.products.fields.productId} IN (${placeholders})`, + productIds + ); + products = productsResult; + + // 如果用户有收藏商品,无论当前同步状态如何,都将其重置为未同步状态 + // 这样可以确保有新收藏商品的用户数据会被重新同步到简道云 + await this.connection.execute( + `UPDATE ${config.tables.users.name} + SET ${config.sync.statusField} = ? + WHERE ${config.tables.users.fields.userId} = ?`, + [config.sync.unsyncedValue, userId] + ); + console.log(`用户 ${userId} 有收藏商品,同步状态已重置为未同步`); + } + syncData.push({ user, - cartItems, + userManagement: userManagements[0] || {}, products, userId: userId // 保存用户ID,用于同步后更新状态 }); diff --git a/src/services/jiandaoyunService.js b/src/services/jiandaoyunService.js index 00f0755..eea7457 100644 --- a/src/services/jiandaoyunService.js +++ b/src/services/jiandaoyunService.js @@ -66,46 +66,29 @@ class JiandaoyunService { // 使用简道云API v1的正确值格式,使用value字段来包装值 jiandaoyunData[mapping.userId] = { value: user.userId || '' }; - jiandaoyunData[mapping.company] = { value: user.company || '' }; - jiandaoyunData[mapping.nickName] = { value: user.name || '' }; + jiandaoyunData[mapping.nickName] = { value: user.nickName || '' }; jiandaoyunData[mapping.phoneNumber] = { value: user.phoneNumber || '' }; jiandaoyunData[mapping.type] = { value: user.type || '' }; - jiandaoyunData[mapping.city] = { value: user.city || '' }; - - // 转换cart_items数据(buyer) - const cartItems = databaseData.cartItems; - console.log('购物车数据:', JSON.stringify(cartItems, null, 2)); - - if (cartItems.length > 0) { - const firstCartItem = cartItems[0]; - jiandaoyunData[mapping['productName-buyer']] = { value: firstCartItem.productName || '' }; - jiandaoyunData[mapping['specification-buyer']] = { value: firstCartItem.specification || '' }; - jiandaoyunData[mapping['quantity-buyer']] = { value: firstCartItem.quantity || '' }; - // 计算毛重:数量 * 规格中的克数 - const specification = firstCartItem.specification || ''; - const weightPerUnit = parseInt(specification.match(/(\d+)克/)?.[1] || '0'); - const quantity = parseInt(firstCartItem.quantity || '0'); - const grossWeight = weightPerUnit * quantity; - jiandaoyunData[mapping['grossWeight-buyer']] = { value: grossWeight.toString() }; - jiandaoyunData[mapping['yolk-buyer']] = { value: firstCartItem.yolk || '' }; + jiandaoyunData[mapping.authorizedRegion] = { value: user.authorizedRegion || '' }; + + // 转换负责人信息 + const userManagement = databaseData.userManagement; + console.log('负责人数据:', JSON.stringify(userManagement, null, 2)); + if (userManagement) { + jiandaoyunData[mapping.userName] = { value: userManagement.userName || '' }; } - // 转换products数据(sell) + // 转换产品数据 const products = databaseData.products; console.log('产品数据:', JSON.stringify(products, null, 2)); if (products.length > 0) { const firstProduct = products[0]; - jiandaoyunData[mapping['productName-sell']] = { value: firstProduct.productName || '' }; - jiandaoyunData[mapping['specification-sell']] = { value: firstProduct.specification || '' }; - jiandaoyunData[mapping['quantity-sell']] = { value: firstProduct.quantity || '' }; - // 计算毛重:数量 * 规格中的克数 - const specification = firstProduct.specification || ''; - const weightPerUnit = parseInt(specification.match(/(\d+)克/)?.[1] || '0'); - const quantity = parseInt(firstProduct.quantity || '0'); - const grossWeight = weightPerUnit * quantity; - jiandaoyunData[mapping['grossWeight-sell']] = { value: grossWeight.toString() }; - jiandaoyunData[mapping['yolk-sell']] = { value: firstProduct.yolk || '' }; + jiandaoyunData[mapping.productName] = { value: firstProduct.productName || '' }; + jiandaoyunData[mapping.specification] = { value: firstProduct.specification || '' }; + jiandaoyunData[mapping.quantity] = { value: firstProduct.quantity || '' }; + jiandaoyunData[mapping.grossWeight] = { value: firstProduct.grossWeight || '' }; + jiandaoyunData[mapping.yolk] = { value: firstProduct.yolk || '' }; } console.log('转换后的数据:', JSON.stringify(jiandaoyunData, null, 2)); @@ -117,6 +100,9 @@ class JiandaoyunService { async isPhoneNumberExists(phoneNumber) { try { const mapping = config.fieldMapping; + console.log(`检查电话号码 ${phoneNumber} 是否存在于简道云表单中...`); + console.log(`使用的字段映射: ${mapping.phoneNumber}`); + const url = `${this.baseUrl}/api/v1/app/${this.appId}/entry/${this.entryId}/data_list`; const headers = { 'Content-Type': 'application/json', @@ -136,13 +122,21 @@ class JiandaoyunService { } ] }, - page_size: 1 // 只需要知道是否存在,不需要返回所有结果 + page_size: 10 // 返回更多结果以便调试 }; + console.log('发送的查询请求:', JSON.stringify(payload, null, 2)); + const response = await axios.post(url, payload, { headers }); + console.log('查询响应状态:', response.status); + console.log('查询响应数据:', JSON.stringify(response.data, null, 2)); + // 如果返回的数据数量大于0,则表示该电话号码已存在 - return response.data.data.length > 0; + const exists = response.data.data.length > 0; + console.log(`电话号码 ${phoneNumber} 存在: ${exists}`); + + return exists; } catch (error) { console.error('查询电话号码是否存在失败:', error.message); if (error.response) { @@ -155,42 +149,63 @@ class JiandaoyunService { } // 批量提交数据到简道云 - async batchSubmitData(dataList) { + async batchSubmitData(dataList, connection) { const results = []; - for (const data of dataList) { + for (const item of dataList) { try { - // 检查电话号码是否已存在 - const phoneNumber = data.user.phoneNumber; - const exists = await this.isPhoneNumberExists(phoneNumber); + // 检查数据库中是否已有简道云记录ID + let recordId = null; + if (item.user && item.user.jiandaoyun_record_id) { + recordId = item.user.jiandaoyun_record_id; + console.log(`从数据库获取到简道云记录ID: ${recordId}`); + } + + if (recordId) { + console.log(`使用记录ID ${recordId} 更新数据`); + + // 转换数据格式 + const jiandaoyunData = this.transformDataToJiandaoyunFormat(item); - if (exists) { - console.log(`电话号码 ${phoneNumber} 已存在于简道云表单中,跳过同步`); + // 更新数据 + const result = await this.updateDataInForm(recordId, jiandaoyunData); results.push({ success: true, - skipped: true, - message: `电话号码 ${phoneNumber} 已存在,跳过同步`, - originalData: data + updated: true, + data: result, + originalData: item }); + console.log('数据更新成功:', result); continue; } // 转换数据格式 - const jiandaoyunData = this.transformDataToJiandaoyunFormat(data); + const jiandaoyunData = this.transformDataToJiandaoyunFormat(item); // 提交数据 const result = await this.submitDataToForm(jiandaoyunData); + + // 保存返回的记录ID到数据库 + if (result.data && result.data._id && connection && item.user && item.user.userId) { + const newRecordId = result.data._id; + await connection.execute( + `UPDATE ${config.tables.users.name} SET jiandaoyun_record_id = ? WHERE userId = ?`, + [newRecordId, item.user.userId] + ); + console.log(`已将简道云记录ID ${newRecordId} 保存到数据库`); + } + results.push({ success: true, data: result, - originalData: data + originalData: item }); console.log('数据提交成功:', result); } catch (error) { results.push({ success: false, error: error.message, - originalData: data + originalData: item }); console.error('数据提交失败:', error.message); } @@ -199,6 +214,69 @@ class JiandaoyunService { return results; } + // 根据电话号码获取简道云记录ID + async getRecordIdByPhoneNumber(phoneNumber) { + try { + const mapping = config.fieldMapping; + const url = `${this.baseUrl}/api/v1/app/${this.appId}/entry/${this.entryId}/data_list`; + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }; + + // 构建查询条件:电话号码字段等于指定值 + const payload = { + filter: { + rel: 'and', + cond: [ + { + field: mapping.phoneNumber, + type: 'text', + method: 'eq', + value: phoneNumber + } + ] + }, + page_size: 1 // 只需要一条记录即可 + }; + + const response = await axios.post(url, payload, { headers }); + + // 如果找到记录,返回第一条记录的_id + if (response.data.data.length > 0) { + return response.data.data[0]._id; + } + return null; + } catch (error) { + console.error('获取记录ID失败:', error.message); + return null; + } + } + + // 更新简道云表单中的数据 + async updateDataInForm(recordId, data) { + try { + const url = `${this.baseUrl}/api/v1/app/${this.appId}/entry/${this.entryId}/data_update`; + const headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }; + + // 构建更新请求体:记录ID和要更新的数据 + const payload = { + entry_id: this.entryId, + data_id: recordId, + data: data + }; + + const response = await axios.post(url, payload, { headers }); + return response.data; + } catch (error) { + console.error('更新数据失败:', error.message); + throw error; + } + } + // 测试简道云API连接 async testApiConnection() { try {