商城后端模板

This commit is contained in:
dzl
2025-09-24 10:02:03 +08:00
commit 658ff89c6a
93 changed files with 55153 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
# 表合并说明文档
## 概述
本文档说明如何使用 `merge_tables.js` 脚本将 `order_allocations` 表数据合并到 `transfers` 表中,以实现数据库结构优化。
## 背景
原系统中,`order_allocations` 表和 `transfers` 表存在功能重叠,为了简化数据库结构和提高查询效率,我们决定将 `order_allocations` 表的数据合并到 `transfers` 表中,并通过 `source_type` 字段区分不同来源的转账记录。
## 合并策略
1.`transfers` 表添加必要的字段,包括 `source_type``matching_order_id``cycle_number`
2.`order_allocations` 表中的数据迁移到 `transfers`
3. 更新相关的外键引用
## 使用方法
### 前置条件
1. 确保已对数据库进行备份
2. 确保系统处于维护状态,没有用户正在使用
### 执行步骤
1. 进入项目根目录
2. 执行以下命令运行合并脚本:
```bash
node scripts/merge_tables.js
```
3. 脚本执行完成后,检查控制台输出,确认迁移是否成功
### 验证步骤
1. 检查 `transfers` 表中是否包含所有 `order_allocations` 表的数据
2. 验证系统功能是否正常,特别是与匹配订单相关的功能
3. 确认无误后,可以考虑删除 `order_allocations` 表(可选)
## 回滚方案
如果合并过程中出现问题,或者合并后系统功能异常,可以通过以下步骤回滚:
1. 使用之前的数据库备份恢复数据
2. 如果没有备份,可以手动将 `transfers` 表中 `source_type='allocation'` 的记录删除,并重新运行原有的系统
## 注意事项
1. 合并过程会自动处理已有关联的记录,避免重复迁移
2. 合并脚本使用事务进行操作,确保数据一致性
3. 建议在测试环境验证成功后再在生产环境执行
## 技术支持
如有问题,请联系技术支持团队。

View File

@@ -0,0 +1,144 @@
const fs = require('fs');
const path = require('path');
const mysql = require('mysql2/promise');
require('dotenv').config();
// 数据库配置
const dbConfig = {
host: process.env.DB_HOST || '114.55.111.44',
user: process.env.DB_USER || 'maov2',
password: process.env.DB_PASSWORD || '5fYhw8z6T62b7heS',
database: process.env.DB_NAME || 'maov2',
};
// 初始化数据库连接
async function initDB() {
try {
const connection = await mysql.createConnection(dbConfig);
console.log('数据库连接成功');
return connection;
} catch (error) {
console.error('数据库连接失败:', error);
throw error;
}
}
// 递归解析省市区数据
function parseRegionData(regions, parentCode = null, level = 1) {
const result = [];
let sortOrder = 1;
for (const region of regions) {
// 添加当前区域
result.push({
code: region.code,
name: region.name,
parent_code: parentCode,
level: level,
sort_order: sortOrder++
});
// 递归处理子区域
if (region.children && region.children.length > 0) {
const childrenData = parseRegionData(region.children, region.code, level + 1);
result.push(...childrenData);
}
}
return result;
}
// 导入省市区数据
async function importChinaRegions() {
let connection;
try {
// 读取 JSON 数据文件
const jsonFilePath = path.join(__dirname, 'pca-code.json');
const jsonData = fs.readFileSync(jsonFilePath, 'utf8');
const regionsData = JSON.parse(jsonData);
console.log('成功读取省市区数据文件');
// 解析数据
const parsedData = parseRegionData(regionsData);
console.log(`解析完成,共 ${parsedData.length} 条记录`);
// 连接数据库
connection = await initDB();
// 清空现有数据
await connection.execute('DELETE FROM china_regions');
console.log('已清空现有数据');
// 批量插入数据
const batchSize = 100;
let insertedCount = 0;
for (let i = 0; i < parsedData.length; i += batchSize) {
const batch = parsedData.slice(i, i + batchSize);
const values = batch.map(item => [
item.code,
item.name,
item.parent_code,
item.level,
item.sort_order
]);
const placeholders = values.map(() => '(?, ?, ?, ?, ?)').join(', ');
const flatValues = values.flat();
await connection.execute(
`INSERT INTO china_regions (code, name, parent_code, level, sort_order) VALUES ${placeholders}`,
flatValues
);
insertedCount += batch.length;
console.log(`已插入 ${insertedCount}/${parsedData.length} 条记录`);
}
// 统计导入结果
const [provinceResult] = await connection.execute(
'SELECT COUNT(*) as count FROM china_regions WHERE level = 1'
);
const [cityResult] = await connection.execute(
'SELECT COUNT(*) as count FROM china_regions WHERE level = 2'
);
const [districtResult] = await connection.execute(
'SELECT COUNT(*) as count FROM china_regions WHERE level = 3'
);
const [totalResult] = await connection.execute(
'SELECT COUNT(*) as count FROM china_regions'
);
console.log('\n=== 导入完成 ===');
console.log(`省份数量: ${provinceResult[0].count}`);
console.log(`城市数量: ${cityResult[0].count}`);
console.log(`区县数量: ${districtResult[0].count}`);
console.log(`总记录数: ${totalResult[0].count}`);
} catch (error) {
console.error('导入失败:', error);
throw error;
} finally {
if (connection) {
await connection.end();
console.log('数据库连接已关闭');
}
}
}
// 如果直接运行此脚本
if (require.main === module) {
importChinaRegions()
.then(() => {
console.log('省市区数据导入成功!');
process.exit(0);
})
.catch((error) => {
console.error('导入过程中发生错误:', error);
process.exit(1);
});
}
module.exports = { importChinaRegions };

14625
scripts/pca-code.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,273 @@
const fs = require('fs');
const path = require('path');
/**
* 代码更新脚本:将所有 order_allocations 表引用更新为 transfers 表
*
* 更新策略:
* 1. 将 order_allocations 表名替换为 transfers
* 2. 更新相关的字段映射和查询逻辑
* 3. 添加必要的 WHERE 条件来过滤 allocation 类型的记录
*/
class CodeUpdater {
constructor() {
this.filesToUpdate = [
'services/matchingService.js',
'routes/matchingAdmin.js',
'routes/transfers.js',
'routes/matching.js'
];
// 字段映射关系
this.fieldMappings = {
// order_allocations 字段 -> transfers 字段
'matching_order_id': 'matching_order_id',
'from_user_id': 'from_user_id',
'to_user_id': 'to_user_id',
'amount': 'amount',
'cycle_number': 'cycle_number',
'status': 'status',
'transfer_id': 'id', // order_allocations.transfer_id 对应 transfers.id
'created_at': 'created_at',
'updated_at': 'updated_at',
'confirmed_at': 'confirmed_at',
'outbound_date': 'outbound_date',
'return_date': 'return_date',
'can_return_after': 'can_return_after'
};
}
/**
* 更新单个文件
* @param {string} filePath - 文件路径
*/
async updateFile(filePath) {
const fullPath = path.join(process.cwd(), filePath);
if (!fs.existsSync(fullPath)) {
console.log(`文件不存在: ${filePath}`);
return;
}
console.log(`正在更新文件: ${filePath}`);
let content = fs.readFileSync(fullPath, 'utf8');
let originalContent = content;
// 1. 替换表名
content = content.replace(/\border_allocations\b/g, 'transfers');
// 2. 添加 source_type 过滤条件
content = this.addSourceTypeFilters(content);
// 3. 更新 INSERT 语句
content = this.updateInsertStatements(content);
// 4. 更新特定的查询逻辑
content = this.updateSpecificQueries(content, filePath);
// 5. 更新注释
content = this.updateComments(content);
if (content !== originalContent) {
// 创建备份
fs.writeFileSync(fullPath + '.backup', originalContent);
// 写入更新后的内容
fs.writeFileSync(fullPath, content);
console.log(`✓ 已更新: ${filePath}`);
console.log(`✓ 备份已创建: ${filePath}.backup`);
} else {
console.log(`- 无需更新: ${filePath}`);
}
}
/**
* 添加 source_type 过滤条件
* @param {string} content - 文件内容
* @returns {string} 更新后的内容
*/
addSourceTypeFilters(content) {
// 在 FROM transfers 后添加 WHERE source_type = 'allocation' 条件
// 但要避免重复添加
// 匹配 FROM transfers 但不包含 source_type 的情况
content = content.replace(
/FROM transfers(?!.*source_type)([\s\S]*?)(?=WHERE|ORDER|GROUP|LIMIT|;|$)/gi,
(match, afterFrom) => {
// 如果已经有 WHERE 子句,添加 AND 条件
if (afterFrom.includes('WHERE')) {
return match.replace(/WHERE/, 'WHERE source_type = \'allocation\' AND');
} else {
// 如果没有 WHERE 子句,添加新的 WHERE 条件
const beforeNextClause = match.match(/(ORDER|GROUP|LIMIT|;|$)/);
if (beforeNextClause) {
return match.replace(beforeNextClause[0], ` WHERE source_type = 'allocation' ${beforeNextClause[0]}`);
} else {
return match + " WHERE source_type = 'allocation'";
}
}
}
);
return content;
}
/**
* 更新 INSERT 语句
* @param {string} content - 文件内容
* @returns {string} 更新后的内容
*/
updateInsertStatements(content) {
// 更新 INSERT INTO transfers 语句,添加必要的字段
content = content.replace(
/INSERT INTO transfers\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/gi,
(match, fields, values) => {
// 如果字段列表中没有 source_type添加它
if (!fields.includes('source_type')) {
const fieldList = fields.trim() + ', source_type';
const valueList = values.trim() + ', \'allocation\'';
return `INSERT INTO transfers (${fieldList}) VALUES (${valueList})`;
}
return match;
}
);
return content;
}
/**
* 更新特定的查询逻辑
* @param {string} content - 文件内容
* @param {string} filePath - 文件路径
* @returns {string} 更新后的内容
*/
updateSpecificQueries(content, filePath) {
// 根据不同文件进行特定更新
if (filePath.includes('matchingService.js')) {
// 更新 matchingService.js 中的特定逻辑
content = this.updateMatchingServiceQueries(content);
}
if (filePath.includes('matchingAdmin.js')) {
// 更新 matchingAdmin.js 中的特定逻辑
content = this.updateMatchingAdminQueries(content);
}
return content;
}
/**
* 更新 matchingService.js 中的查询
* @param {string} content - 文件内容
* @returns {string} 更新后的内容
*/
updateMatchingServiceQueries(content) {
// 更新确认分配的逻辑
content = content.replace(
/UPDATE transfers SET status = "confirmed", transfer_id = \?, confirmed_at = NOW\(\) WHERE id = \?/g,
'UPDATE transfers SET status = "confirmed", confirmed_at = NOW() WHERE id = ? AND source_type = \'allocation\''
);
// 更新获取分配记录的查询
content = content.replace(
/SELECT \* FROM transfers WHERE id = \? AND from_user_id = \?/g,
'SELECT * FROM transfers WHERE id = ? AND from_user_id = ? AND source_type = \'allocation\''
);
return content;
}
/**
* 更新 matchingAdmin.js 中的查询
* @param {string} content - 文件内容
* @returns {string} 更新后的内容
*/
updateMatchingAdminQueries(content) {
// 更新管理员查询逻辑,确保只查询 allocation 类型的记录
content = content.replace(
/FROM transfers oa/g,
'FROM transfers oa WHERE oa.source_type = \'allocation\''
);
return content;
}
/**
* 更新注释
* @param {string} content - 文件内容
* @returns {string} 更新后的内容
*/
updateComments(content) {
content = content.replace(/order_allocations/g, 'transfers (allocation type)');
content = content.replace(/订单分配/g, '转账分配');
content = content.replace(/分配表/g, '转账表(分配类型)');
return content;
}
/**
* 执行所有文件的更新
*/
async updateAllFiles() {
console.log('开始更新代码引用...');
console.log('=' .repeat(60));
for (const filePath of this.filesToUpdate) {
try {
await this.updateFile(filePath);
} catch (error) {
console.error(`更新文件 ${filePath} 失败:`, error.message);
}
}
console.log('\n' + '=' .repeat(60));
console.log('✓ 代码更新完成!');
console.log('\n注意事项:');
console.log('1. 所有原始文件已备份为 .backup 文件');
console.log('2. 请测试更新后的代码功能是否正常');
console.log('3. 如有问题,可以使用备份文件恢复');
console.log('4. 确认无误后可删除 .backup 文件');
}
/**
* 恢复所有备份文件
*/
async restoreBackups() {
console.log('开始恢复备份文件...');
for (const filePath of this.filesToUpdate) {
const fullPath = path.join(process.cwd(), filePath);
const backupPath = fullPath + '.backup';
if (fs.existsSync(backupPath)) {
fs.copyFileSync(backupPath, fullPath);
console.log(`✓ 已恢复: ${filePath}`);
}
}
console.log('✓ 备份恢复完成!');
}
}
async function main() {
const updater = new CodeUpdater();
const args = process.argv.slice(2);
if (args.includes('--restore')) {
await updater.restoreBackups();
} else {
await updater.updateAllFiles();
}
}
// 如果直接运行此脚本
if (require.main === module) {
main().catch(console.error);
}
module.exports = CodeUpdater;

37
scripts/verify_data.js Normal file
View File

@@ -0,0 +1,37 @@
const { initDB, getDB } = require('../database');
async function verifyData() {
try {
await initDB();
// 检查省份数据
const [provinces] = await getDB().query('SELECT code, name FROM china_regions WHERE level = 1 ORDER BY code LIMIT 10');
console.log('省份数据样本:');
provinces.forEach(p => console.log(` ${p.code} - ${p.name}`));
// 检查城市数据
const [cities] = await getDB().query('SELECT code, name, parent_code FROM china_regions WHERE level = 2 ORDER BY code LIMIT 10');
console.log('\n城市数据样本:');
cities.forEach(c => console.log(` ${c.code} - ${c.name} (${c.parent_code})`));
// 检查区县数据
const [districts] = await getDB().query('SELECT code, name, parent_code FROM china_regions WHERE level = 3 ORDER BY code LIMIT 10');
console.log('\n区县数据样本:');
districts.forEach(d => console.log(` ${d.code} - ${d.name} (${d.parent_code})`));
// 统计各级别数量
const [stats] = await getDB().query('SELECT level, COUNT(*) as count FROM china_regions GROUP BY level ORDER BY level');
console.log('\n各级别统计:');
stats.forEach(row => {
const levelName = row.level === 1 ? '省份' : row.level === 2 ? '城市' : '区县';
console.log(` ${levelName}(level ${row.level}): ${row.count}`);
});
} catch (error) {
console.error('验证失败:', error);
} finally {
process.exit();
}
}
verifyData();

115
scripts/verify_merge.js Normal file
View File

@@ -0,0 +1,115 @@
const mysql = require('mysql2/promise');
const { dbConfig } = require('../config/config');
/**
* 验证表合并结果的脚本
* 检查 order_allocations 表和 transfers 表的数据一致性
*/
async function verifyMerge() {
console.log('开始验证表合并结果...');
console.log('=' .repeat(60));
let connection;
try {
// 创建数据库连接
connection = await mysql.createConnection({
host: dbConfig.host,
user: dbConfig.user,
password: dbConfig.password,
database: dbConfig.database
});
// 1. 检查 order_allocations 表中有多少条记录
const [allocationCount] = await connection.execute(
'SELECT COUNT(*) as count FROM order_allocations'
);
console.log(`order_allocations 表总记录数: ${allocationCount[0].count}`);
// 2. 检查 transfers 表中有多少条 allocation 类型的记录
const [transferCount] = await connection.execute(
'SELECT COUNT(*) as count FROM transfers WHERE source_type = \'allocation\''
);
console.log(`transfers 表中 allocation 类型记录数: ${transferCount[0].count}`);
// 3. 检查 order_allocations 表中有多少条记录没有关联的 transfer_id
const [unlinkedCount] = await connection.execute(
'SELECT COUNT(*) as count FROM order_allocations WHERE transfer_id IS NULL'
);
console.log(`order_allocations 表中无关联 transfer_id 的记录数: ${unlinkedCount[0].count}`);
// 4. 检查数据一致性 - 抽样检查
console.log('\n数据一致性检查抽样:');
const [sampleAllocations] = await connection.execute(
'SELECT * FROM order_allocations WHERE transfer_id IS NOT NULL LIMIT 5'
);
for (const allocation of sampleAllocations) {
const [transfer] = await connection.execute(
'SELECT * FROM transfers WHERE id = ?',
[allocation.transfer_id]
);
if (transfer.length === 0) {
console.log(` ✗ 错误: allocation_id=${allocation.id} 关联的 transfer_id=${allocation.transfer_id} 不存在`);
continue;
}
const transferRecord = transfer[0];
const isConsistent =
transferRecord.from_user_id == allocation.from_user_id &&
transferRecord.to_user_id == allocation.to_user_id &&
transferRecord.amount == allocation.amount &&
transferRecord.matching_order_id == allocation.matching_order_id &&
transferRecord.cycle_number == allocation.cycle_number;
if (isConsistent) {
console.log(` ✓ allocation_id=${allocation.id} 与 transfer_id=${allocation.transfer_id} 数据一致`);
} else {
console.log(` ✗ 错误: allocation_id=${allocation.id} 与 transfer_id=${allocation.transfer_id} 数据不一致`);
console.log(' allocation:', {
from_user_id: allocation.from_user_id,
to_user_id: allocation.to_user_id,
amount: allocation.amount,
matching_order_id: allocation.matching_order_id,
cycle_number: allocation.cycle_number
});
console.log(' transfer:', {
from_user_id: transferRecord.from_user_id,
to_user_id: transferRecord.to_user_id,
amount: transferRecord.amount,
matching_order_id: transferRecord.matching_order_id,
cycle_number: transferRecord.cycle_number
});
}
}
console.log('\n' + '=' .repeat(60));
// 总结
if (allocationCount[0].count === transferCount[0].count && unlinkedCount[0].count === 0) {
console.log('✓ 验证成功! 所有 order_allocations 记录都已正确迁移到 transfers 表');
} else {
console.log('⚠ 验证结果: 可能存在未完全迁移的数据');
console.log(` - order_allocations 总数: ${allocationCount[0].count}`);
console.log(` - transfers 中 allocation 类型数: ${transferCount[0].count}`);
console.log(` - 未关联记录数: ${unlinkedCount[0].count}`);
}
} catch (error) {
console.error('验证失败:', error);
throw error;
} finally {
// 关闭数据库连接
if (connection) {
await connection.end();
}
}
}
// 如果直接运行此脚本
if (require.main === module) {
verifyMerge().catch(console.error);
}
module.exports = verifyMerge;