Browse Source

修复简道云重复新增数据问题:使用记录ID跟踪数据

Integration
SwTt29 3 months ago
parent
commit
cab3ce2f23
  1. 4
      index.js
  2. 45
      src/config/config.js
  3. 83
      src/services/databaseService.js
  4. 170
      src/services/jiandaoyunService.js

4
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更新同步状态...');

45
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'
},
// 同步配置

83
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,用于同步后更新状态
});

170
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 {

Loading…
Cancel
Save