refactor(docs): 简化README结构,更新技术栈和项目结构描述
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
# 数据库配置
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_HOST=nj-cdb-3pwh2kz1.sql.tencentcdb.com
|
||||
DB_PORT=20784
|
||||
DB_USERNAME=jiebanke
|
||||
DB_PASSWORD=aiot741$12346
|
||||
DB_NAME=niumall
|
||||
|
||||
# JWT配置
|
||||
|
||||
71
backend/check_existing_tables.js
Normal file
71
backend/check_existing_tables.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 检查现有表结构
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const { Sequelize } = require('sequelize');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: false
|
||||
}
|
||||
);
|
||||
|
||||
async function checkTables() {
|
||||
try {
|
||||
console.log('🔍 检查现有表结构...\n');
|
||||
|
||||
// 获取所有表
|
||||
const [tables] = await sequelize.query("SHOW TABLES");
|
||||
console.log('📋 现有表列表:');
|
||||
tables.forEach(table => {
|
||||
const tableName = table[`Tables_in_${process.env.DB_NAME || 'niumall'}`];
|
||||
console.log(` - ${tableName}`);
|
||||
});
|
||||
|
||||
// 检查suppliers表结构
|
||||
console.log('\n🏭 suppliers表结构:');
|
||||
try {
|
||||
const [columns] = await sequelize.query("DESCRIBE suppliers");
|
||||
columns.forEach(col => {
|
||||
console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : ''} ${col.Key ? `(${col.Key})` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(' 表不存在或查询失败');
|
||||
}
|
||||
|
||||
// 检查users表结构
|
||||
console.log('\n👤 users表结构:');
|
||||
try {
|
||||
const [columns] = await sequelize.query("DESCRIBE users");
|
||||
columns.forEach(col => {
|
||||
console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : ''} ${col.Key ? `(${col.Key})` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(' 表不存在或查询失败');
|
||||
}
|
||||
|
||||
// 检查orders表结构
|
||||
console.log('\n📋 orders表结构:');
|
||||
try {
|
||||
const [columns] = await sequelize.query("DESCRIBE orders");
|
||||
columns.forEach(col => {
|
||||
console.log(` - ${col.Field}: ${col.Type} ${col.Null === 'NO' ? 'NOT NULL' : ''} ${col.Key ? `(${col.Key})` : ''}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(' 表不存在或查询失败');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 检查失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
}
|
||||
}
|
||||
|
||||
checkTables();
|
||||
@@ -5,7 +5,7 @@ module.exports = {
|
||||
development: {
|
||||
username: process.env.DB_USERNAME || 'jiebanke',
|
||||
password: process.env.DB_PASSWORD || 'aiot741$12346',
|
||||
database: process.env.DB_NAME || 'jbkdata',
|
||||
database: process.env.DB_NAME || 'niumall',
|
||||
host: process.env.DB_HOST || 'nj-cdb-3pwh2kz1.sql.tencentcdb.com',
|
||||
port: process.env.DB_PORT || 20784,
|
||||
dialect: 'mysql',
|
||||
|
||||
100
backend/create_admin_user.js
Normal file
100
backend/create_admin_user.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 创建管理员用户
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
|
||||
// 创建数据库连接
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: false
|
||||
}
|
||||
);
|
||||
|
||||
async function createAdminUser() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 检查是否已存在管理员用户
|
||||
const [existingUsers] = await sequelize.query(
|
||||
"SELECT * FROM users WHERE username = 'admin' OR user_type = 'admin'"
|
||||
);
|
||||
|
||||
if (existingUsers.length > 0) {
|
||||
console.log('⚠️ 管理员用户已存在:');
|
||||
existingUsers.forEach(user => {
|
||||
console.log(`- ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}, 类型: ${user.user_type}`);
|
||||
});
|
||||
|
||||
// 更新现有管理员用户的密码
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
await sequelize.query(
|
||||
"UPDATE users SET username = 'admin', password_hash = ?, user_type = 'admin', status = 'active' WHERE id = ?",
|
||||
{
|
||||
replacements: [hashedPassword, existingUsers[0].id]
|
||||
}
|
||||
);
|
||||
console.log('✅ 已更新现有用户为管理员,用户名: admin, 密码: admin123');
|
||||
} else {
|
||||
// 创建新的管理员用户
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
const uuid = uuidv4();
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO users (
|
||||
uuid, username, password_hash, openid, nickname, user_type, status,
|
||||
registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
{
|
||||
replacements: [
|
||||
uuid,
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'admin_' + uuid.substring(0, 8), // 为管理员生成一个唯一的openid
|
||||
'系统管理员',
|
||||
'admin',
|
||||
'active',
|
||||
'admin_create',
|
||||
0
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
console.log('✅ 管理员用户创建成功!');
|
||||
}
|
||||
|
||||
console.log('\n📋 管理员登录信息:');
|
||||
console.log('用户名: admin');
|
||||
console.log('密码: admin123');
|
||||
console.log('登录地址: http://localhost:3000/');
|
||||
|
||||
// 验证创建结果
|
||||
const [adminUsers] = await sequelize.query(
|
||||
"SELECT id, username, nickname, user_type, status FROM users WHERE user_type = 'admin'"
|
||||
);
|
||||
|
||||
console.log('\n当前管理员用户列表:');
|
||||
adminUsers.forEach(user => {
|
||||
console.log(`- ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}, 状态: ${user.status}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建管理员用户失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行创建脚本
|
||||
createAdminUser();
|
||||
676
backend/database_integrity_checker.js
Normal file
676
backend/database_integrity_checker.js
Normal file
@@ -0,0 +1,676 @@
|
||||
/**
|
||||
* 数据库结构完整性检查和自动修复工具
|
||||
*
|
||||
* 功能:
|
||||
* 1. 比对现有结构与设计文档规范
|
||||
* 2. 识别缺失的表、字段、约束或索引
|
||||
* 3. 生成并执行必要的DDL语句补全结构
|
||||
* 4. 验证数据完整性约束
|
||||
* 5. 对缺失的基础数据执行初始化插入
|
||||
*
|
||||
* @author NiuMall Team
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const { Sequelize, DataTypes, QueryTypes } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// 创建数据库连接
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: (msg) => console.log(`[SQL] ${msg}`)
|
||||
}
|
||||
);
|
||||
|
||||
// 操作日志记录
|
||||
const operationLogs = [];
|
||||
|
||||
function logOperation(operation, status, details = '') {
|
||||
const log = {
|
||||
timestamp: new Date().toISOString(),
|
||||
operation,
|
||||
status,
|
||||
details
|
||||
};
|
||||
operationLogs.push(log);
|
||||
console.log(`[${status}] ${operation}: ${details}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库表结构定义(基于设计文档)
|
||||
*/
|
||||
const expectedTables = {
|
||||
// 用户基础表
|
||||
users: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
uuid: 'VARCHAR(36) UNIQUE',
|
||||
username: 'VARCHAR(50) UNIQUE',
|
||||
password_hash: 'VARCHAR(255)',
|
||||
openid: 'VARCHAR(64)',
|
||||
unionid: 'VARCHAR(64)',
|
||||
nickname: 'VARCHAR(50) NOT NULL',
|
||||
real_name: 'VARCHAR(50)',
|
||||
avatar: 'VARCHAR(255)',
|
||||
gender: "ENUM('male','female','other')",
|
||||
birthday: 'DATETIME',
|
||||
phone: 'VARCHAR(20) UNIQUE',
|
||||
email: 'VARCHAR(100) UNIQUE',
|
||||
user_type: "ENUM('buyer','trader','supplier','driver','staff','admin') DEFAULT 'buyer'",
|
||||
company_name: 'VARCHAR(100)',
|
||||
company_address: 'VARCHAR(200)',
|
||||
business_license: 'VARCHAR(255)',
|
||||
id_card: 'VARCHAR(18)',
|
||||
status: "ENUM('active','inactive','suspended','pending_approval') DEFAULT 'pending_approval'",
|
||||
last_login_at: 'DATETIME',
|
||||
login_count: 'INT DEFAULT 0',
|
||||
registration_source: "ENUM('miniprogram','web','admin_create') DEFAULT 'miniprogram'",
|
||||
approval_notes: 'TEXT',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
||||
},
|
||||
indexes: [
|
||||
'INDEX idx_uuid (uuid)',
|
||||
'INDEX idx_username (username)',
|
||||
'INDEX idx_phone (phone)',
|
||||
'INDEX idx_email (email)',
|
||||
'INDEX idx_openid (openid)',
|
||||
'INDEX idx_user_type (user_type)',
|
||||
'INDEX idx_status (status)'
|
||||
]
|
||||
},
|
||||
|
||||
// 用户详情表
|
||||
user_profiles: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
user_id: 'BIGINT NOT NULL',
|
||||
company_name: 'VARCHAR(200)',
|
||||
business_license: 'VARCHAR(500)',
|
||||
license_number: 'VARCHAR(100)',
|
||||
legal_person: 'VARCHAR(100)',
|
||||
contact_address: 'TEXT',
|
||||
emergency_contact: 'VARCHAR(100)',
|
||||
emergency_phone: 'VARCHAR(20)',
|
||||
bank_account: 'VARCHAR(50)',
|
||||
bank_name: 'VARCHAR(200)',
|
||||
tax_number: 'VARCHAR(50)',
|
||||
qualification_docs: 'JSON',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
||||
},
|
||||
foreignKeys: [
|
||||
'FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE'
|
||||
],
|
||||
indexes: [
|
||||
'UNIQUE KEY uk_user_id (user_id)',
|
||||
'INDEX idx_company_name (company_name)',
|
||||
'INDEX idx_license_number (license_number)'
|
||||
]
|
||||
},
|
||||
|
||||
// 订单主表
|
||||
orders: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
order_no: 'VARCHAR(50) UNIQUE NOT NULL',
|
||||
client_id: 'BIGINT NOT NULL',
|
||||
trader_id: 'BIGINT',
|
||||
supplier_id: 'BIGINT',
|
||||
cattle_type: 'VARCHAR(50) NOT NULL',
|
||||
quantity: 'INT NOT NULL',
|
||||
weight_range: 'VARCHAR(50)',
|
||||
estimated_weight: 'DECIMAL(8,2)',
|
||||
actual_weight: 'DECIMAL(8,2)',
|
||||
unit_price: 'DECIMAL(10,2) NOT NULL',
|
||||
price_type: "ENUM('per_kg','per_head') DEFAULT 'per_kg'",
|
||||
total_amount: 'DECIMAL(12,2) NOT NULL',
|
||||
prepaid_amount: 'DECIMAL(12,2) DEFAULT 0',
|
||||
final_amount: 'DECIMAL(12,2)',
|
||||
pickup_address: 'TEXT',
|
||||
delivery_address: 'TEXT NOT NULL',
|
||||
pickup_time: 'DATETIME',
|
||||
delivery_time: 'DATETIME',
|
||||
actual_delivery_time: 'DATETIME',
|
||||
status: "ENUM('draft','pending','confirmed','preparing','loading','transporting','arrived','inspecting','accepted','completed','cancelled') DEFAULT 'draft'",
|
||||
cancel_reason: 'TEXT',
|
||||
special_requirements: 'TEXT',
|
||||
quality_standards: 'JSON',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
|
||||
confirmed_at: 'TIMESTAMP NULL',
|
||||
completed_at: 'TIMESTAMP NULL'
|
||||
},
|
||||
foreignKeys: [
|
||||
'FOREIGN KEY (client_id) REFERENCES users(id)',
|
||||
'FOREIGN KEY (trader_id) REFERENCES users(id)',
|
||||
'FOREIGN KEY (supplier_id) REFERENCES users(id)'
|
||||
],
|
||||
indexes: [
|
||||
'INDEX idx_order_no (order_no)',
|
||||
'INDEX idx_client_id (client_id)',
|
||||
'INDEX idx_trader_id (trader_id)',
|
||||
'INDEX idx_supplier_id (supplier_id)',
|
||||
'INDEX idx_status (status)',
|
||||
'INDEX idx_created_at (created_at)',
|
||||
'INDEX idx_delivery_time (delivery_time)',
|
||||
'INDEX idx_cattle_type (cattle_type)'
|
||||
]
|
||||
},
|
||||
|
||||
// 供应商表
|
||||
suppliers: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
name: 'VARCHAR(100) NOT NULL',
|
||||
code: 'VARCHAR(20) UNIQUE NOT NULL',
|
||||
contact: 'VARCHAR(50) NOT NULL',
|
||||
phone: 'VARCHAR(20) UNIQUE NOT NULL',
|
||||
email: 'VARCHAR(100)',
|
||||
address: 'VARCHAR(200) NOT NULL',
|
||||
region: 'VARCHAR(20) NOT NULL',
|
||||
business_license: 'VARCHAR(255)',
|
||||
animal_quarantine_certificate: 'VARCHAR(255)',
|
||||
qualification_level: "ENUM('A','B','C','D') DEFAULT 'C'",
|
||||
certifications: 'JSON',
|
||||
cattle_types: 'JSON',
|
||||
capacity: 'INT DEFAULT 0',
|
||||
rating: 'DECIMAL(3,2) DEFAULT 0.00',
|
||||
cooperation_start_date: 'DATE',
|
||||
status: "ENUM('active','inactive','suspended','blacklisted') DEFAULT 'active'",
|
||||
bank_account: 'VARCHAR(50)',
|
||||
bank_name: 'VARCHAR(100)',
|
||||
tax_number: 'VARCHAR(30)',
|
||||
notes: 'TEXT',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
||||
},
|
||||
indexes: [
|
||||
'INDEX idx_code (code)',
|
||||
'INDEX idx_phone (phone)',
|
||||
'INDEX idx_region (region)',
|
||||
'INDEX idx_qualification_level (qualification_level)',
|
||||
'INDEX idx_status (status)',
|
||||
'INDEX idx_rating (rating)'
|
||||
]
|
||||
},
|
||||
|
||||
// 运输任务表
|
||||
transport_tasks: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
task_no: 'VARCHAR(50) UNIQUE NOT NULL',
|
||||
order_id: 'BIGINT NOT NULL',
|
||||
driver_id: 'BIGINT NOT NULL',
|
||||
vehicle_no: 'VARCHAR(20) NOT NULL',
|
||||
vehicle_type: 'VARCHAR(50)',
|
||||
vehicle_capacity: 'DECIMAL(8,2)',
|
||||
driver_license: 'VARCHAR(50)',
|
||||
start_location: 'VARCHAR(200)',
|
||||
end_location: 'VARCHAR(200)',
|
||||
start_latitude: 'DECIMAL(10,6)',
|
||||
start_longitude: 'DECIMAL(10,6)',
|
||||
end_latitude: 'DECIMAL(10,6)',
|
||||
end_longitude: 'DECIMAL(10,6)',
|
||||
planned_distance: 'DECIMAL(8,2)',
|
||||
actual_distance: 'DECIMAL(8,2)',
|
||||
planned_start_time: 'DATETIME',
|
||||
actual_start_time: 'DATETIME',
|
||||
planned_end_time: 'DATETIME',
|
||||
actual_end_time: 'DATETIME',
|
||||
estimated_arrival_time: 'DATETIME',
|
||||
status: "ENUM('assigned','preparing','loading','started','transporting','arrived','unloading','completed','cancelled') DEFAULT 'assigned'",
|
||||
transport_fee: 'DECIMAL(10,2)',
|
||||
fuel_cost: 'DECIMAL(10,2)',
|
||||
toll_cost: 'DECIMAL(10,2)',
|
||||
other_cost: 'DECIMAL(10,2)',
|
||||
notes: 'TEXT',
|
||||
cancel_reason: 'TEXT',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
||||
},
|
||||
foreignKeys: [
|
||||
'FOREIGN KEY (order_id) REFERENCES orders(id)',
|
||||
'FOREIGN KEY (driver_id) REFERENCES users(id)'
|
||||
],
|
||||
indexes: [
|
||||
'INDEX idx_task_no (task_no)',
|
||||
'INDEX idx_order_id (order_id)',
|
||||
'INDEX idx_driver_id (driver_id)',
|
||||
'INDEX idx_vehicle_no (vehicle_no)',
|
||||
'INDEX idx_status (status)',
|
||||
'INDEX idx_planned_start_time (planned_start_time)',
|
||||
'INDEX idx_created_at (created_at)'
|
||||
]
|
||||
},
|
||||
|
||||
// 支付记录表
|
||||
payments: {
|
||||
columns: {
|
||||
id: 'BIGINT PRIMARY KEY AUTO_INCREMENT',
|
||||
payment_no: 'VARCHAR(50) UNIQUE NOT NULL',
|
||||
order_id: 'BIGINT NOT NULL',
|
||||
user_id: 'BIGINT NOT NULL',
|
||||
amount: 'DECIMAL(12,2) NOT NULL',
|
||||
paid_amount: 'DECIMAL(12,2)',
|
||||
currency: 'VARCHAR(10) DEFAULT "CNY"',
|
||||
payment_method: "ENUM('bank_transfer','alipay','wechat_pay','cash','check','other') NOT NULL",
|
||||
payment_channel: 'VARCHAR(100)',
|
||||
third_party_order_no: 'VARCHAR(100)',
|
||||
third_party_transaction_id: 'VARCHAR(100)',
|
||||
payer_bank_account: 'VARCHAR(50)',
|
||||
payer_bank_name: 'VARCHAR(200)',
|
||||
payee_bank_account: 'VARCHAR(50)',
|
||||
payee_bank_name: 'VARCHAR(200)',
|
||||
status: "ENUM('pending','processing','success','failed','cancelled','refunded') DEFAULT 'pending'",
|
||||
failure_reason: 'TEXT',
|
||||
payment_time: 'DATETIME',
|
||||
confirmed_time: 'DATETIME',
|
||||
notes: 'TEXT',
|
||||
receipt_url: 'VARCHAR(500)',
|
||||
created_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP',
|
||||
updated_at: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
||||
},
|
||||
foreignKeys: [
|
||||
'FOREIGN KEY (order_id) REFERENCES orders(id)',
|
||||
'FOREIGN KEY (user_id) REFERENCES users(id)'
|
||||
],
|
||||
indexes: [
|
||||
'INDEX idx_payment_no (payment_no)',
|
||||
'INDEX idx_order_id (order_id)',
|
||||
'INDEX idx_user_id (user_id)',
|
||||
'INDEX idx_status (status)',
|
||||
'INDEX idx_payment_method (payment_method)',
|
||||
'INDEX idx_payment_time (payment_time)',
|
||||
'INDEX idx_third_party_order_no (third_party_order_no)'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 检查表是否存在
|
||||
*/
|
||||
async function checkTableExists(tableName) {
|
||||
try {
|
||||
const [results] = await sequelize.query(
|
||||
`SELECT COUNT(*) as count FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`,
|
||||
{
|
||||
replacements: [process.env.DB_NAME || 'niumall', tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
return results[0].count > 0;
|
||||
} catch (error) {
|
||||
logOperation(`检查表 ${tableName}`, 'ERROR', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的列信息
|
||||
*/
|
||||
async function getTableColumns(tableName) {
|
||||
try {
|
||||
const [results] = await sequelize.query(
|
||||
`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||
ORDER BY ORDINAL_POSITION`,
|
||||
{
|
||||
replacements: [process.env.DB_NAME || 'niumall', tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
return results;
|
||||
} catch (error) {
|
||||
logOperation(`获取表 ${tableName} 列信息`, 'ERROR', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表的索引信息
|
||||
*/
|
||||
async function getTableIndexes(tableName) {
|
||||
try {
|
||||
const [results] = await sequelize.query(
|
||||
`SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, INDEX_TYPE
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
|
||||
ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||||
{
|
||||
replacements: [process.env.DB_NAME || 'niumall', tableName],
|
||||
type: QueryTypes.SELECT
|
||||
}
|
||||
);
|
||||
return results;
|
||||
} catch (error) {
|
||||
logOperation(`获取表 ${tableName} 索引信息`, 'ERROR', error.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建缺失的表
|
||||
*/
|
||||
async function createMissingTable(tableName, tableDefinition) {
|
||||
try {
|
||||
logOperation(`创建表 ${tableName}`, 'INFO', '开始创建表');
|
||||
|
||||
// 构建CREATE TABLE语句
|
||||
const columns = Object.entries(tableDefinition.columns)
|
||||
.map(([name, definition]) => `${name} ${definition}`)
|
||||
.join(',\n ');
|
||||
|
||||
const foreignKeys = tableDefinition.foreignKeys || [];
|
||||
const fkConstraints = foreignKeys.length > 0 ? ',\n ' + foreignKeys.join(',\n ') : '';
|
||||
|
||||
const createTableSQL = `
|
||||
CREATE TABLE ${tableName} (
|
||||
${columns}${fkConstraints}
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='${tableName}表'
|
||||
`;
|
||||
|
||||
await sequelize.query(createTableSQL);
|
||||
logOperation(`创建表 ${tableName}`, 'SUCCESS', '表创建成功');
|
||||
|
||||
// 创建索引
|
||||
if (tableDefinition.indexes) {
|
||||
for (const indexSQL of tableDefinition.indexes) {
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE ${tableName} ADD ${indexSQL}`);
|
||||
logOperation(`创建索引`, 'SUCCESS', `${tableName}: ${indexSQL}`);
|
||||
} catch (error) {
|
||||
logOperation(`创建索引`, 'WARNING', `${tableName}: ${indexSQL} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logOperation(`创建表 ${tableName}`, 'ERROR', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加缺失的列
|
||||
*/
|
||||
async function addMissingColumn(tableName, columnName, columnDefinition) {
|
||||
try {
|
||||
const alterSQL = `ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnDefinition}`;
|
||||
await sequelize.query(alterSQL);
|
||||
logOperation(`添加列`, 'SUCCESS', `${tableName}.${columnName}`);
|
||||
} catch (error) {
|
||||
if (error.message.includes('Duplicate column name')) {
|
||||
logOperation(`添加列`, 'WARNING', `${tableName}.${columnName} 已存在`);
|
||||
} else {
|
||||
logOperation(`添加列`, 'ERROR', `${tableName}.${columnName} - ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建缺失的索引
|
||||
*/
|
||||
async function createMissingIndex(tableName, indexSQL) {
|
||||
try {
|
||||
await sequelize.query(`ALTER TABLE ${tableName} ADD ${indexSQL}`);
|
||||
logOperation(`创建索引`, 'SUCCESS', `${tableName}: ${indexSQL}`);
|
||||
} catch (error) {
|
||||
if (error.message.includes('Duplicate key name') || error.message.includes('already exists')) {
|
||||
logOperation(`创建索引`, 'WARNING', `${tableName}: ${indexSQL} 已存在`);
|
||||
} else {
|
||||
logOperation(`创建索引`, 'ERROR', `${tableName}: ${indexSQL} - ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化基础数据
|
||||
*/
|
||||
async function initializeBaseData() {
|
||||
try {
|
||||
logOperation('初始化基础数据', 'INFO', '开始检查和创建基础数据');
|
||||
|
||||
// 检查是否存在管理员用户
|
||||
const [adminUsers] = await sequelize.query(
|
||||
"SELECT * FROM users WHERE user_type = 'admin' LIMIT 1"
|
||||
);
|
||||
|
||||
if (adminUsers.length === 0) {
|
||||
// 创建默认管理员用户
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
const uuid = uuidv4();
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO users (
|
||||
uuid, username, password_hash, openid, nickname, user_type, status,
|
||||
registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
{
|
||||
replacements: [
|
||||
uuid,
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'admin_' + uuid.substring(0, 8),
|
||||
'系统管理员',
|
||||
'admin',
|
||||
'active',
|
||||
'admin_create',
|
||||
0
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
logOperation('创建管理员用户', 'SUCCESS', '用户名: admin, 密码: admin123');
|
||||
} else {
|
||||
logOperation('检查管理员用户', 'INFO', '管理员用户已存在');
|
||||
}
|
||||
|
||||
// 检查系统配置表数据
|
||||
const configTableExists = await checkTableExists('system_configs');
|
||||
if (configTableExists) {
|
||||
const [configs] = await sequelize.query("SELECT COUNT(*) as count FROM system_configs");
|
||||
if (configs[0].count === 0) {
|
||||
// 插入默认系统配置
|
||||
const defaultConfigs = [
|
||||
['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'],
|
||||
['system.version', '1.0.0', 'string', 'system', '系统版本'],
|
||||
['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'],
|
||||
['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'],
|
||||
['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)']
|
||||
];
|
||||
|
||||
for (const [key, value, type, category, description] of defaultConfigs) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO system_configs (config_key, config_value, config_type, category, description, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
{ replacements: [key, value, type, category, description] }
|
||||
);
|
||||
}
|
||||
|
||||
logOperation('初始化系统配置', 'SUCCESS', `插入 ${defaultConfigs.length} 条配置记录`);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logOperation('初始化基础数据', 'ERROR', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据完整性
|
||||
*/
|
||||
async function validateDataIntegrity() {
|
||||
try {
|
||||
logOperation('验证数据完整性', 'INFO', '开始数据完整性检查');
|
||||
|
||||
const issues = [];
|
||||
|
||||
// 检查外键约束
|
||||
const [orphanedOrders] = await sequelize.query(`
|
||||
SELECT o.id, o.order_no, o.client_id
|
||||
FROM orders o
|
||||
LEFT JOIN users u ON o.client_id = u.id
|
||||
WHERE u.id IS NULL
|
||||
LIMIT 10
|
||||
`);
|
||||
|
||||
if (orphanedOrders.length > 0) {
|
||||
issues.push(`发现 ${orphanedOrders.length} 个订单的客户ID无效`);
|
||||
}
|
||||
|
||||
// 检查重复数据
|
||||
const [duplicateUsers] = await sequelize.query(`
|
||||
SELECT phone, COUNT(*) as count
|
||||
FROM users
|
||||
WHERE phone IS NOT NULL
|
||||
GROUP BY phone
|
||||
HAVING COUNT(*) > 1
|
||||
`);
|
||||
|
||||
if (duplicateUsers.length > 0) {
|
||||
issues.push(`发现 ${duplicateUsers.length} 个重复的手机号`);
|
||||
}
|
||||
|
||||
// 检查必填字段空值
|
||||
const [emptyNicknames] = await sequelize.query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM users
|
||||
WHERE nickname IS NULL OR nickname = ''
|
||||
`);
|
||||
|
||||
if (emptyNicknames[0].count > 0) {
|
||||
issues.push(`发现 ${emptyNicknames[0].count} 个用户昵称为空`);
|
||||
}
|
||||
|
||||
if (issues.length > 0) {
|
||||
logOperation('数据完整性检查', 'WARNING', `发现 ${issues.length} 个问题: ${issues.join('; ')}`);
|
||||
} else {
|
||||
logOperation('数据完整性检查', 'SUCCESS', '未发现数据完整性问题');
|
||||
}
|
||||
|
||||
return issues;
|
||||
|
||||
} catch (error) {
|
||||
logOperation('验证数据完整性', 'ERROR', error.message);
|
||||
return [`数据完整性检查失败: ${error.message}`];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主要的数据库检查和修复函数
|
||||
*/
|
||||
async function checkAndRepairDatabase() {
|
||||
try {
|
||||
console.log('\n🔍 ===== 数据库结构完整性检查开始 =====\n');
|
||||
|
||||
// 1. 测试数据库连接
|
||||
logOperation('数据库连接测试', 'INFO', '正在连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
logOperation('数据库连接测试', 'SUCCESS', '数据库连接成功');
|
||||
|
||||
// 2. 检查和创建缺失的表
|
||||
logOperation('表结构检查', 'INFO', '开始检查表结构...');
|
||||
|
||||
for (const [tableName, tableDefinition] of Object.entries(expectedTables)) {
|
||||
const exists = await checkTableExists(tableName);
|
||||
|
||||
if (!exists) {
|
||||
logOperation(`表检查`, 'WARNING', `表 ${tableName} 不存在,准备创建`);
|
||||
await createMissingTable(tableName, tableDefinition);
|
||||
} else {
|
||||
logOperation(`表检查`, 'SUCCESS', `表 ${tableName} 存在`);
|
||||
|
||||
// 检查列结构
|
||||
const existingColumns = await getTableColumns(tableName);
|
||||
const existingColumnNames = existingColumns.map(col => col.COLUMN_NAME);
|
||||
|
||||
for (const [columnName, columnDefinition] of Object.entries(tableDefinition.columns)) {
|
||||
if (!existingColumnNames.includes(columnName)) {
|
||||
logOperation(`列检查`, 'WARNING', `表 ${tableName} 缺少列 ${columnName}`);
|
||||
await addMissingColumn(tableName, columnName, columnDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查索引
|
||||
if (tableDefinition.indexes) {
|
||||
for (const indexSQL of tableDefinition.indexes) {
|
||||
await createMissingIndex(tableName, indexSQL);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 初始化基础数据
|
||||
await initializeBaseData();
|
||||
|
||||
// 4. 验证数据完整性
|
||||
const integrityIssues = await validateDataIntegrity();
|
||||
|
||||
// 5. 生成检查报告
|
||||
console.log('\n📊 ===== 数据库检查报告 =====\n');
|
||||
|
||||
const successCount = operationLogs.filter(log => log.status === 'SUCCESS').length;
|
||||
const warningCount = operationLogs.filter(log => log.status === 'WARNING').length;
|
||||
const errorCount = operationLogs.filter(log => log.status === 'ERROR').length;
|
||||
|
||||
console.log(`✅ 成功操作: ${successCount}`);
|
||||
console.log(`⚠️ 警告信息: ${warningCount}`);
|
||||
console.log(`❌ 错误信息: ${errorCount}`);
|
||||
|
||||
if (integrityIssues.length > 0) {
|
||||
console.log(`\n🔍 数据完整性问题: ${integrityIssues.length}`);
|
||||
integrityIssues.forEach((issue, index) => {
|
||||
console.log(` ${index + 1}. ${issue}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 6. 输出详细日志
|
||||
console.log('\n📝 ===== 详细操作日志 =====\n');
|
||||
operationLogs.forEach(log => {
|
||||
const icon = log.status === 'SUCCESS' ? '✅' :
|
||||
log.status === 'WARNING' ? '⚠️' :
|
||||
log.status === 'ERROR' ? '❌' : 'ℹ️';
|
||||
console.log(`${icon} [${log.timestamp}] ${log.operation}: ${log.details}`);
|
||||
});
|
||||
|
||||
console.log('\n🎉 ===== 数据库结构检查完成 =====\n');
|
||||
|
||||
// 7. 输出连接信息
|
||||
console.log('📋 系统信息:');
|
||||
console.log(`🌐 后端服务: http://localhost:4330`);
|
||||
console.log(`🎨 管理后台: http://localhost:3000`);
|
||||
console.log(`👤 管理员账户: admin / admin123`);
|
||||
console.log(`📚 API文档: http://localhost:4330/api-docs`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 数据库检查过程中发生严重错误:', error);
|
||||
logOperation('数据库检查', 'ERROR', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查和修复
|
||||
if (require.main === module) {
|
||||
checkAndRepairDatabase().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkAndRepairDatabase,
|
||||
expectedTables,
|
||||
operationLogs
|
||||
};
|
||||
92
backend/fix_user_table.js
Normal file
92
backend/fix_user_table.js
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 修复用户表结构 - 添加缺失的字段
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const { Sequelize, DataTypes } = require('sequelize');
|
||||
|
||||
// 创建数据库连接
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: console.log
|
||||
}
|
||||
);
|
||||
|
||||
async function checkColumnExists(tableName, columnName) {
|
||||
try {
|
||||
const [results] = await sequelize.query(
|
||||
`SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = '${process.env.DB_NAME || 'niumall'}' AND TABLE_NAME = '${tableName}' AND COLUMN_NAME = '${columnName}'`
|
||||
);
|
||||
return results.length > 0;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fixUserTable() {
|
||||
try {
|
||||
console.log('连接数据库...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
console.log('\n开始修复用户表结构...');
|
||||
|
||||
// 定义需要添加的字段
|
||||
const fieldsToAdd = [
|
||||
{ name: 'username', sql: 'ALTER TABLE users ADD COLUMN username VARCHAR(50) UNIQUE AFTER id' },
|
||||
{ name: 'password_hash', sql: 'ALTER TABLE users ADD COLUMN password_hash VARCHAR(255) AFTER username' },
|
||||
{ name: 'user_type', sql: "ALTER TABLE users ADD COLUMN user_type ENUM('buyer', 'trader', 'supplier', 'driver', 'staff', 'admin') DEFAULT 'buyer' AFTER password_hash" },
|
||||
{ name: 'real_name', sql: 'ALTER TABLE users ADD COLUMN real_name VARCHAR(50) AFTER nickname' },
|
||||
{ name: 'unionid', sql: 'ALTER TABLE users ADD COLUMN unionid VARCHAR(64) AFTER openid' },
|
||||
{ name: 'company_name', sql: 'ALTER TABLE users ADD COLUMN company_name VARCHAR(100) AFTER user_type' },
|
||||
{ name: 'company_address', sql: 'ALTER TABLE users ADD COLUMN company_address VARCHAR(200) AFTER company_name' },
|
||||
{ name: 'business_license', sql: 'ALTER TABLE users ADD COLUMN business_license VARCHAR(255) AFTER company_address' },
|
||||
{ name: 'id_card', sql: 'ALTER TABLE users ADD COLUMN id_card VARCHAR(18) AFTER business_license' },
|
||||
{ name: 'status', sql: "ALTER TABLE users ADD COLUMN status ENUM('active', 'inactive', 'suspended', 'pending_approval') DEFAULT 'pending_approval' AFTER id_card" },
|
||||
{ name: 'last_login_at', sql: 'ALTER TABLE users ADD COLUMN last_login_at DATETIME AFTER status' },
|
||||
{ name: 'login_count', sql: 'ALTER TABLE users ADD COLUMN login_count INT DEFAULT 0 AFTER last_login_at' },
|
||||
{ name: 'registration_source', sql: "ALTER TABLE users ADD COLUMN registration_source ENUM('miniprogram', 'web', 'admin_create') DEFAULT 'miniprogram' AFTER login_count" },
|
||||
{ name: 'approval_notes', sql: 'ALTER TABLE users ADD COLUMN approval_notes TEXT AFTER registration_source' }
|
||||
];
|
||||
|
||||
for (const field of fieldsToAdd) {
|
||||
try {
|
||||
const exists = await checkColumnExists('users', field.name);
|
||||
if (exists) {
|
||||
console.log(`⚠️ 字段 ${field.name} 已存在,跳过`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`添加字段: ${field.name}`);
|
||||
await sequelize.query(field.sql);
|
||||
console.log('✅ 成功');
|
||||
} catch (error) {
|
||||
console.log(`❌ 添加字段 ${field.name} 失败:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n✅ 用户表结构修复完成!');
|
||||
|
||||
// 检查表结构
|
||||
console.log('\n检查修复后的表结构...');
|
||||
const [results] = await sequelize.query('SHOW FULL COLUMNS FROM users');
|
||||
console.log('用户表字段列表:');
|
||||
results.forEach(column => {
|
||||
console.log(`- ${column.Field}: ${column.Type} ${column.Null === 'NO' ? 'NOT NULL' : 'NULL'} ${column.Key ? `(${column.Key})` : ''}`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 修复失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行修复脚本
|
||||
fixUserTable();
|
||||
630
backend/init_database_with_test_data.js
Normal file
630
backend/init_database_with_test_data.js
Normal file
@@ -0,0 +1,630 @@
|
||||
/**
|
||||
* 数据库初始化脚本 - 创建表和测试数据
|
||||
*
|
||||
* 功能:
|
||||
* 1. 创建所有必要的数据表
|
||||
* 2. 插入测试数据
|
||||
* 3. 验证数据完整性
|
||||
*
|
||||
* @author NiuMall Team
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const { Sequelize, QueryTypes } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// 创建数据库连接
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: (msg) => console.log(`[SQL] ${msg}`)
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 创建所有数据表的SQL语句
|
||||
*/
|
||||
const createTableSQLs = {
|
||||
// 用户表
|
||||
users: `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
|
||||
uuid VARCHAR(36) UNIQUE COMMENT '用户唯一标识符',
|
||||
username VARCHAR(50) UNIQUE COMMENT '用户名',
|
||||
password_hash VARCHAR(255) COMMENT '密码哈希值',
|
||||
openid VARCHAR(64) COMMENT '微信小程序OpenID',
|
||||
unionid VARCHAR(64) COMMENT '微信UnionID',
|
||||
nickname VARCHAR(50) NOT NULL COMMENT '用户昵称',
|
||||
real_name VARCHAR(50) COMMENT '真实姓名',
|
||||
avatar VARCHAR(255) COMMENT '头像URL',
|
||||
gender ENUM('male', 'female', 'other') COMMENT '性别',
|
||||
birthday DATETIME COMMENT '生日',
|
||||
phone VARCHAR(20) UNIQUE COMMENT '手机号码',
|
||||
email VARCHAR(100) UNIQUE COMMENT '邮箱地址',
|
||||
user_type ENUM('buyer', 'trader', 'supplier', 'driver', 'staff', 'admin') DEFAULT 'buyer' COMMENT '用户类型',
|
||||
company_name VARCHAR(100) COMMENT '公司名称',
|
||||
company_address VARCHAR(200) COMMENT '公司地址',
|
||||
business_license VARCHAR(255) COMMENT '营业执照文件路径',
|
||||
id_card VARCHAR(18) COMMENT '身份证号',
|
||||
status ENUM('active', 'inactive', 'suspended', 'pending_approval') DEFAULT 'pending_approval' COMMENT '用户状态',
|
||||
last_login_at DATETIME COMMENT '最后登录时间',
|
||||
login_count INT DEFAULT 0 COMMENT '登录次数',
|
||||
registration_source ENUM('miniprogram', 'web', 'admin_create') DEFAULT 'miniprogram' COMMENT '注册来源',
|
||||
approval_notes TEXT COMMENT '审核备注',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
INDEX idx_uuid (uuid),
|
||||
INDEX idx_username (username),
|
||||
INDEX idx_phone (phone),
|
||||
INDEX idx_email (email),
|
||||
INDEX idx_openid (openid),
|
||||
INDEX idx_user_type (user_type),
|
||||
INDEX idx_status (status)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户基础表'
|
||||
`,
|
||||
|
||||
// 供应商表
|
||||
suppliers: `
|
||||
CREATE TABLE IF NOT EXISTS suppliers (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '供应商ID',
|
||||
name VARCHAR(100) NOT NULL COMMENT '供应商名称',
|
||||
code VARCHAR(20) UNIQUE NOT NULL COMMENT '供应商编码',
|
||||
contact VARCHAR(50) NOT NULL COMMENT '联系人姓名',
|
||||
phone VARCHAR(20) UNIQUE NOT NULL COMMENT '联系电话',
|
||||
email VARCHAR(100) COMMENT '邮箱地址',
|
||||
address VARCHAR(200) NOT NULL COMMENT '详细地址',
|
||||
region VARCHAR(20) NOT NULL COMMENT '所属区域',
|
||||
business_license VARCHAR(255) COMMENT '营业执照文件路径',
|
||||
animal_quarantine_certificate VARCHAR(255) COMMENT '动物防疫条件合格证文件路径',
|
||||
qualification_level ENUM('A', 'B', 'C', 'D') DEFAULT 'C' COMMENT '资质等级',
|
||||
certifications JSON COMMENT '其他认证证书信息',
|
||||
cattle_types JSON COMMENT '可供应的牛只品种',
|
||||
capacity INT DEFAULT 0 COMMENT '月供应能力(头数)',
|
||||
rating DECIMAL(3,2) DEFAULT 0.00 COMMENT '综合评分',
|
||||
cooperation_start_date DATE COMMENT '合作开始日期',
|
||||
status ENUM('active', 'inactive', 'suspended', 'blacklisted') DEFAULT 'active' COMMENT '供应商状态',
|
||||
bank_account VARCHAR(50) COMMENT '银行账号',
|
||||
bank_name VARCHAR(100) COMMENT '开户银行',
|
||||
tax_number VARCHAR(30) COMMENT '税务登记号',
|
||||
notes TEXT COMMENT '备注信息',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
INDEX idx_code (code),
|
||||
INDEX idx_phone (phone),
|
||||
INDEX idx_region (region),
|
||||
INDEX idx_qualification_level (qualification_level),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_rating (rating)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商表'
|
||||
`,
|
||||
|
||||
// 订单表
|
||||
orders: `
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
|
||||
order_no VARCHAR(50) UNIQUE NOT NULL COMMENT '订单号',
|
||||
client_id BIGINT NOT NULL COMMENT '采购人ID',
|
||||
trader_id BIGINT COMMENT '贸易商ID',
|
||||
supplier_id BIGINT COMMENT '供应商ID',
|
||||
cattle_type VARCHAR(50) NOT NULL COMMENT '牛只品种',
|
||||
quantity INT NOT NULL COMMENT '数量(头)',
|
||||
weight_range VARCHAR(50) COMMENT '重量范围',
|
||||
estimated_weight DECIMAL(8,2) COMMENT '预估总重量(kg)',
|
||||
actual_weight DECIMAL(8,2) COMMENT '实际总重量(kg)',
|
||||
unit_price DECIMAL(10,2) NOT NULL COMMENT '单价(元/kg或元/头)',
|
||||
price_type ENUM('per_kg', 'per_head') DEFAULT 'per_kg' COMMENT '计价方式',
|
||||
total_amount DECIMAL(12,2) NOT NULL COMMENT '订单总金额',
|
||||
prepaid_amount DECIMAL(12,2) DEFAULT 0 COMMENT '预付金额',
|
||||
final_amount DECIMAL(12,2) COMMENT '最终结算金额',
|
||||
pickup_address TEXT COMMENT '取货地址',
|
||||
delivery_address TEXT NOT NULL COMMENT '交货地址',
|
||||
pickup_time DATETIME COMMENT '取货时间',
|
||||
delivery_time DATETIME COMMENT '要求交货时间',
|
||||
actual_delivery_time DATETIME COMMENT '实际交货时间',
|
||||
status ENUM('draft', 'pending', 'confirmed', 'preparing', 'loading', 'transporting', 'arrived', 'inspecting', 'accepted', 'completed', 'cancelled') DEFAULT 'draft' COMMENT '订单状态',
|
||||
cancel_reason TEXT COMMENT '取消原因',
|
||||
special_requirements TEXT COMMENT '特殊要求',
|
||||
quality_standards JSON COMMENT '质量标准JSON',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
confirmed_at TIMESTAMP NULL COMMENT '确认时间',
|
||||
completed_at TIMESTAMP NULL COMMENT '完成时间',
|
||||
|
||||
FOREIGN KEY (client_id) REFERENCES users(id),
|
||||
FOREIGN KEY (trader_id) REFERENCES users(id),
|
||||
FOREIGN KEY (supplier_id) REFERENCES suppliers(id),
|
||||
|
||||
INDEX idx_order_no (order_no),
|
||||
INDEX idx_client_id (client_id),
|
||||
INDEX idx_trader_id (trader_id),
|
||||
INDEX idx_supplier_id (supplier_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_delivery_time (delivery_time),
|
||||
INDEX idx_cattle_type (cattle_type)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单主表'
|
||||
`,
|
||||
|
||||
// 支付记录表
|
||||
payments: `
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '支付ID',
|
||||
payment_no VARCHAR(50) UNIQUE NOT NULL COMMENT '支付单号',
|
||||
order_id BIGINT NOT NULL COMMENT '订单ID',
|
||||
user_id BIGINT NOT NULL COMMENT '付款用户ID',
|
||||
amount DECIMAL(12,2) NOT NULL COMMENT '支付金额',
|
||||
paid_amount DECIMAL(12,2) COMMENT '实际支付金额',
|
||||
currency VARCHAR(10) DEFAULT 'CNY' COMMENT '货币类型',
|
||||
payment_method ENUM('bank_transfer', 'alipay', 'wechat_pay', 'cash', 'check', 'other') NOT NULL COMMENT '支付方式',
|
||||
payment_channel VARCHAR(100) COMMENT '支付渠道',
|
||||
third_party_order_no VARCHAR(100) COMMENT '第三方订单号',
|
||||
third_party_transaction_id VARCHAR(100) COMMENT '第三方交易ID',
|
||||
payer_bank_account VARCHAR(50) COMMENT '付款账户',
|
||||
payer_bank_name VARCHAR(200) COMMENT '付款银行',
|
||||
payee_bank_account VARCHAR(50) COMMENT '收款账户',
|
||||
payee_bank_name VARCHAR(200) COMMENT '收款银行',
|
||||
status ENUM('pending', 'processing', 'success', 'failed', 'cancelled', 'refunded') DEFAULT 'pending' COMMENT '支付状态',
|
||||
failure_reason TEXT COMMENT '失败原因',
|
||||
payment_time DATETIME COMMENT '支付时间',
|
||||
confirmed_time DATETIME COMMENT '确认时间',
|
||||
notes TEXT COMMENT '支付备注',
|
||||
receipt_url VARCHAR(500) COMMENT '支付凭证URL',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
|
||||
INDEX idx_payment_no (payment_no),
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_payment_method (payment_method),
|
||||
INDEX idx_payment_time (payment_time),
|
||||
INDEX idx_third_party_order_no (third_party_order_no)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付记录表'
|
||||
`,
|
||||
|
||||
// 运输任务表
|
||||
transport_tasks: `
|
||||
CREATE TABLE IF NOT EXISTS transport_tasks (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '任务ID',
|
||||
task_no VARCHAR(50) UNIQUE NOT NULL COMMENT '任务编号',
|
||||
order_id BIGINT NOT NULL COMMENT '订单ID',
|
||||
driver_id BIGINT NOT NULL COMMENT '司机ID',
|
||||
vehicle_no VARCHAR(20) NOT NULL COMMENT '车牌号',
|
||||
vehicle_type VARCHAR(50) COMMENT '车辆类型',
|
||||
vehicle_capacity DECIMAL(8,2) COMMENT '载重量(吨)',
|
||||
driver_license VARCHAR(50) COMMENT '驾驶证号',
|
||||
start_location VARCHAR(200) COMMENT '起始地点',
|
||||
end_location VARCHAR(200) COMMENT '目的地点',
|
||||
start_latitude DECIMAL(10,6) COMMENT '起始纬度',
|
||||
start_longitude DECIMAL(10,6) COMMENT '起始经度',
|
||||
end_latitude DECIMAL(10,6) COMMENT '目的纬度',
|
||||
end_longitude DECIMAL(10,6) COMMENT '目的经度',
|
||||
planned_distance DECIMAL(8,2) COMMENT '计划距离(公里)',
|
||||
actual_distance DECIMAL(8,2) COMMENT '实际距离(公里)',
|
||||
planned_start_time DATETIME COMMENT '计划开始时间',
|
||||
actual_start_time DATETIME COMMENT '实际开始时间',
|
||||
planned_end_time DATETIME COMMENT '计划结束时间',
|
||||
actual_end_time DATETIME COMMENT '实际结束时间',
|
||||
estimated_arrival_time DATETIME COMMENT '预计到达时间',
|
||||
status ENUM('assigned', 'preparing', 'loading', 'started', 'transporting', 'arrived', 'unloading', 'completed', 'cancelled') DEFAULT 'assigned' COMMENT '任务状态',
|
||||
transport_fee DECIMAL(10,2) COMMENT '运输费用',
|
||||
fuel_cost DECIMAL(10,2) COMMENT '燃油费用',
|
||||
toll_cost DECIMAL(10,2) COMMENT '过路费',
|
||||
other_cost DECIMAL(10,2) COMMENT '其他费用',
|
||||
notes TEXT COMMENT '备注',
|
||||
cancel_reason TEXT COMMENT '取消原因',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id),
|
||||
FOREIGN KEY (driver_id) REFERENCES users(id),
|
||||
|
||||
INDEX idx_task_no (task_no),
|
||||
INDEX idx_order_id (order_id),
|
||||
INDEX idx_driver_id (driver_id),
|
||||
INDEX idx_vehicle_no (vehicle_no),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_planned_start_time (planned_start_time),
|
||||
INDEX idx_created_at (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='运输任务表'
|
||||
`,
|
||||
|
||||
// 系统配置表
|
||||
system_configs: `
|
||||
CREATE TABLE IF NOT EXISTS system_configs (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '配置ID',
|
||||
config_key VARCHAR(100) UNIQUE NOT NULL COMMENT '配置键',
|
||||
config_value TEXT NOT NULL COMMENT '配置值',
|
||||
config_type ENUM('string', 'number', 'boolean', 'json', 'array') DEFAULT 'string' COMMENT '配置类型',
|
||||
category VARCHAR(50) NOT NULL COMMENT '配置分类',
|
||||
description TEXT COMMENT '配置描述',
|
||||
is_public BOOLEAN DEFAULT FALSE COMMENT '是否公开配置',
|
||||
is_editable BOOLEAN DEFAULT TRUE COMMENT '是否可编辑',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
|
||||
INDEX idx_config_key (config_key),
|
||||
INDEX idx_category (category),
|
||||
INDEX idx_is_public (is_public)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表'
|
||||
`
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建表
|
||||
*/
|
||||
async function createTables() {
|
||||
console.log('\n📋 开始创建数据表...');
|
||||
|
||||
for (const [tableName, sql] of Object.entries(createTableSQLs)) {
|
||||
try {
|
||||
console.log(`📝 创建表: ${tableName}`);
|
||||
await sequelize.query(sql);
|
||||
console.log(`✅ 表 ${tableName} 创建成功`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ 表 ${tableName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入测试数据
|
||||
*/
|
||||
async function insertTestData() {
|
||||
console.log('\n📊 开始插入测试数据...');
|
||||
|
||||
try {
|
||||
// 1. 插入管理员用户
|
||||
console.log('👤 创建管理员用户...');
|
||||
const adminPassword = await bcrypt.hash('admin123', 10);
|
||||
const adminUuid = uuidv4();
|
||||
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO users (
|
||||
uuid, username, password_hash, openid, nickname, real_name,
|
||||
user_type, status, registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
adminUuid, 'admin', adminPassword, 'admin_' + adminUuid.substring(0, 8),
|
||||
'系统管理员', '管理员', 'admin', 'active', 'admin_create', 0
|
||||
]
|
||||
});
|
||||
|
||||
// 2. 插入测试用户
|
||||
console.log('👥 创建测试用户...');
|
||||
const testUsers = [
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'buyer001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '采购商张三',
|
||||
real_name: '张三',
|
||||
phone: '13800138001',
|
||||
email: 'buyer001@example.com',
|
||||
user_type: 'buyer',
|
||||
company_name: '北京牛肉加工厂',
|
||||
company_address: '北京市朝阳区xxx街道'
|
||||
},
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'trader001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '贸易商李四',
|
||||
real_name: '李四',
|
||||
phone: '13800138002',
|
||||
email: 'trader001@example.com',
|
||||
user_type: 'trader',
|
||||
company_name: '上海牛只贸易有限公司',
|
||||
company_address: '上海市浦东新区xxx路'
|
||||
},
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'driver001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '司机王五',
|
||||
real_name: '王五',
|
||||
phone: '13800138003',
|
||||
email: 'driver001@example.com',
|
||||
user_type: 'driver',
|
||||
id_card: '110101199001011234'
|
||||
}
|
||||
];
|
||||
|
||||
for (const user of testUsers) {
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO users (
|
||||
uuid, username, password_hash, openid, nickname, real_name, phone, email,
|
||||
user_type, company_name, company_address, id_card, status,
|
||||
registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
user.uuid, user.username, user.password, user.username + '_openid',
|
||||
user.nickname, user.real_name, user.phone, user.email, user.user_type,
|
||||
user.company_name || null, user.company_address || null, user.id_card || null,
|
||||
'active', 'admin_create', 0
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 3. 插入供应商数据
|
||||
console.log('🏭 创建供应商数据...');
|
||||
const suppliers = [
|
||||
{
|
||||
name: '内蒙古草原牧业有限公司',
|
||||
code: 'SUP001',
|
||||
contact: '赵大牛',
|
||||
phone: '13900139001',
|
||||
email: 'sup001@example.com',
|
||||
address: '内蒙古呼和浩特市赛罕区草原路123号',
|
||||
region: '内蒙古',
|
||||
qualification_level: 'A',
|
||||
cattle_types: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']),
|
||||
capacity: 500,
|
||||
rating: 4.8,
|
||||
cooperation_start_date: '2023-01-01'
|
||||
},
|
||||
{
|
||||
name: '新疆天山畜牧合作社',
|
||||
code: 'SUP002',
|
||||
contact: '马小羊',
|
||||
phone: '13900139002',
|
||||
email: 'sup002@example.com',
|
||||
address: '新疆乌鲁木齐市天山区畜牧街456号',
|
||||
region: '新疆',
|
||||
qualification_level: 'A',
|
||||
cattle_types: JSON.stringify(['哈萨克牛', '新疆褐牛']),
|
||||
capacity: 300,
|
||||
rating: 4.5,
|
||||
cooperation_start_date: '2023-03-15'
|
||||
},
|
||||
{
|
||||
name: '山东鲁西黄牛养殖场',
|
||||
code: 'SUP003',
|
||||
contact: '孙大强',
|
||||
phone: '13900139003',
|
||||
email: 'sup003@example.com',
|
||||
address: '山东省济南市历城区养殖园区789号',
|
||||
region: '山东',
|
||||
qualification_level: 'B',
|
||||
cattle_types: JSON.stringify(['鲁西黄牛', '利木赞牛']),
|
||||
capacity: 200,
|
||||
rating: 4.2,
|
||||
cooperation_start_date: '2023-06-01'
|
||||
}
|
||||
];
|
||||
|
||||
for (const supplier of suppliers) {
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO suppliers (
|
||||
name, code, contact, phone, email, address, region, qualification_level,
|
||||
cattle_types, capacity, rating, cooperation_start_date, status, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
supplier.name, supplier.code, supplier.contact, supplier.phone, supplier.email,
|
||||
supplier.address, supplier.region, supplier.qualification_level, supplier.cattle_types,
|
||||
supplier.capacity, supplier.rating, supplier.cooperation_start_date, 'active'
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 插入订单数据
|
||||
console.log('📋 创建订单数据...');
|
||||
|
||||
// 获取用户ID
|
||||
const [buyers] = await sequelize.query("SELECT id FROM users WHERE user_type = 'buyer' LIMIT 1");
|
||||
const [traders] = await sequelize.query("SELECT id FROM users WHERE user_type = 'trader' LIMIT 1");
|
||||
const [supplierIds] = await sequelize.query("SELECT id FROM suppliers LIMIT 2");
|
||||
|
||||
if (buyers.length > 0 && supplierIds.length > 0) {
|
||||
const orders = [
|
||||
{
|
||||
order_no: 'ORD' + Date.now() + '001',
|
||||
client_id: buyers[0].id,
|
||||
trader_id: traders.length > 0 ? traders[0].id : null,
|
||||
supplier_id: supplierIds[0].id,
|
||||
cattle_type: '西门塔尔牛',
|
||||
quantity: 50,
|
||||
estimated_weight: 25000.00,
|
||||
unit_price: 32.50,
|
||||
price_type: 'per_kg',
|
||||
total_amount: 812500.00,
|
||||
prepaid_amount: 200000.00,
|
||||
delivery_address: '北京市朝阳区屠宰场',
|
||||
delivery_time: '2024-01-15 08:00:00',
|
||||
status: 'confirmed',
|
||||
special_requirements: '要求健康证明齐全',
|
||||
quality_standards: JSON.stringify({
|
||||
min_weight: 450,
|
||||
max_weight: 550,
|
||||
health_grade: 'A'
|
||||
})
|
||||
},
|
||||
{
|
||||
order_no: 'ORD' + Date.now() + '002',
|
||||
client_id: buyers[0].id,
|
||||
supplier_id: supplierIds.length > 1 ? supplierIds[1].id : supplierIds[0].id,
|
||||
cattle_type: '安格斯牛',
|
||||
quantity: 30,
|
||||
estimated_weight: 16500.00,
|
||||
unit_price: 35.00,
|
||||
price_type: 'per_kg',
|
||||
total_amount: 577500.00,
|
||||
prepaid_amount: 150000.00,
|
||||
delivery_address: '上海市浦东新区加工厂',
|
||||
delivery_time: '2024-01-20 10:00:00',
|
||||
status: 'pending',
|
||||
special_requirements: '需要冷链运输',
|
||||
quality_standards: JSON.stringify({
|
||||
min_weight: 500,
|
||||
max_weight: 600,
|
||||
health_grade: 'A'
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
for (const order of orders) {
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO orders (
|
||||
order_no, client_id, trader_id, supplier_id, cattle_type, quantity,
|
||||
estimated_weight, unit_price, price_type, total_amount, prepaid_amount,
|
||||
delivery_address, delivery_time, status, special_requirements, quality_standards,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
order.order_no, order.client_id, order.trader_id, order.supplier_id,
|
||||
order.cattle_type, order.quantity, order.estimated_weight, order.unit_price,
|
||||
order.price_type, order.total_amount, order.prepaid_amount, order.delivery_address,
|
||||
order.delivery_time, order.status, order.special_requirements, order.quality_standards
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 插入系统配置
|
||||
console.log('⚙️ 创建系统配置...');
|
||||
const configs = [
|
||||
['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'],
|
||||
['system.version', '1.0.0', 'string', 'system', '系统版本'],
|
||||
['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'],
|
||||
['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'],
|
||||
['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)'],
|
||||
['quality.min_score', '80', 'number', 'quality', '质量检验最低分数'],
|
||||
['notification.sms_enabled', 'true', 'boolean', 'notification', '是否启用短信通知'],
|
||||
['notification.email_enabled', 'true', 'boolean', 'notification', '是否启用邮件通知']
|
||||
];
|
||||
|
||||
for (const [key, value, type, category, description] of configs) {
|
||||
await sequelize.query(`
|
||||
INSERT IGNORE INTO system_configs (
|
||||
config_key, config_value, config_type, category, description,
|
||||
is_public, is_editable, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [key, value, type, category, description, true, true]
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ 测试数据插入完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 插入测试数据失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*/
|
||||
async function validateData() {
|
||||
console.log('\n🔍 验证数据完整性...');
|
||||
|
||||
try {
|
||||
// 统计各表数据量
|
||||
const tables = ['users', 'suppliers', 'orders', 'payments', 'transport_tasks', 'system_configs'];
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`);
|
||||
console.log(`📊 ${table}: ${result[0].count} 条记录`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ 表 ${table} 不存在或查询失败`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查管理员用户
|
||||
const [adminUsers] = await sequelize.query(
|
||||
"SELECT id, username, nickname, user_type FROM users WHERE user_type = 'admin'"
|
||||
);
|
||||
|
||||
console.log('\n👤 管理员用户:');
|
||||
adminUsers.forEach(user => {
|
||||
console.log(` - ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}`);
|
||||
});
|
||||
|
||||
// 检查供应商
|
||||
const [suppliers] = await sequelize.query(
|
||||
"SELECT id, name, code, region, qualification_level FROM suppliers LIMIT 5"
|
||||
);
|
||||
|
||||
console.log('\n🏭 供应商列表:');
|
||||
suppliers.forEach(supplier => {
|
||||
console.log(` - ${supplier.code}: ${supplier.name} (${supplier.region}, 等级${supplier.qualification_level})`);
|
||||
});
|
||||
|
||||
// 检查订单
|
||||
const [orders] = await sequelize.query(
|
||||
"SELECT id, order_no, cattle_type, quantity, total_amount, status FROM orders LIMIT 5"
|
||||
);
|
||||
|
||||
console.log('\n📋 订单列表:');
|
||||
orders.forEach(order => {
|
||||
console.log(` - ${order.order_no}: ${order.cattle_type} ${order.quantity}头, ¥${order.total_amount} (${order.status})`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据验证失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function initializeDatabase() {
|
||||
try {
|
||||
console.log('\n🚀 ===== 数据库初始化开始 =====');
|
||||
|
||||
// 1. 测试连接
|
||||
console.log('\n📡 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 创建表
|
||||
await createTables();
|
||||
|
||||
// 3. 插入测试数据
|
||||
await insertTestData();
|
||||
|
||||
// 4. 验证数据
|
||||
await validateData();
|
||||
|
||||
console.log('\n🎉 ===== 数据库初始化完成 =====');
|
||||
console.log('\n📋 系统信息:');
|
||||
console.log('🌐 后端服务: http://localhost:4330');
|
||||
console.log('🎨 管理后台: http://localhost:3000');
|
||||
console.log('👤 管理员账户: admin / admin123');
|
||||
console.log('📚 API文档: http://localhost:4330/api-docs');
|
||||
console.log('\n🔑 测试账户:');
|
||||
console.log(' 采购商: buyer001 / 123456');
|
||||
console.log(' 贸易商: trader001 / 123456');
|
||||
console.log(' 司机: driver001 / 123456');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 数据库初始化失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行初始化
|
||||
if (require.main === module) {
|
||||
initializeDatabase();
|
||||
}
|
||||
|
||||
module.exports = { initializeDatabase };
|
||||
415
backend/insert_compatible_test_data.js
Normal file
415
backend/insert_compatible_test_data.js
Normal file
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* 兼容现有表结构的测试数据插入脚本
|
||||
*/
|
||||
require('dotenv').config();
|
||||
const { Sequelize } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: (msg) => console.log(`[SQL] ${msg}`)
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 插入测试数据
|
||||
*/
|
||||
async function insertTestData() {
|
||||
console.log('\n📊 开始插入兼容的测试数据...');
|
||||
|
||||
try {
|
||||
// 1. 检查并插入管理员用户
|
||||
console.log('👤 检查管理员用户...');
|
||||
const [existingAdmin] = await sequelize.query(
|
||||
"SELECT id FROM users WHERE username = 'admin'"
|
||||
);
|
||||
|
||||
if (existingAdmin.length === 0) {
|
||||
console.log('创建管理员用户...');
|
||||
const adminPassword = await bcrypt.hash('admin123', 10);
|
||||
const adminUuid = uuidv4();
|
||||
|
||||
await sequelize.query(`
|
||||
INSERT INTO users (
|
||||
uuid, username, password_hash, openid, nickname, real_name,
|
||||
user_type, status, registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
adminUuid, 'admin', adminPassword, 'admin_' + adminUuid.substring(0, 8),
|
||||
'系统管理员', '管理员', 'admin', 'active', 'admin_create', 0
|
||||
]
|
||||
});
|
||||
console.log('✅ 管理员用户创建成功');
|
||||
} else {
|
||||
console.log('✅ 管理员用户已存在');
|
||||
}
|
||||
|
||||
// 2. 插入测试用户
|
||||
console.log('👥 创建测试用户...');
|
||||
const testUsers = [
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'buyer001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '采购商张三',
|
||||
real_name: '张三',
|
||||
phone: '13800138001',
|
||||
email: 'buyer001@example.com',
|
||||
user_type: 'buyer',
|
||||
company_name: '北京牛肉加工厂',
|
||||
company_address: '北京市朝阳区xxx街道'
|
||||
},
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'trader001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '贸易商李四',
|
||||
real_name: '李四',
|
||||
phone: '13800138002',
|
||||
email: 'trader001@example.com',
|
||||
user_type: 'trader',
|
||||
company_name: '上海牛只贸易有限公司',
|
||||
company_address: '上海市浦东新区xxx路'
|
||||
},
|
||||
{
|
||||
uuid: uuidv4(),
|
||||
username: 'driver001',
|
||||
password: await bcrypt.hash('123456', 10),
|
||||
nickname: '司机王五',
|
||||
real_name: '王五',
|
||||
phone: '13800138003',
|
||||
email: 'driver001@example.com',
|
||||
user_type: 'driver',
|
||||
id_card: '110101199001011234'
|
||||
}
|
||||
];
|
||||
|
||||
for (const user of testUsers) {
|
||||
// 检查用户是否已存在
|
||||
const [existing] = await sequelize.query(
|
||||
"SELECT id FROM users WHERE username = ?",
|
||||
{ replacements: [user.username] }
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
await sequelize.query(`
|
||||
INSERT INTO users (
|
||||
uuid, username, password_hash, openid, nickname, real_name, phone, email,
|
||||
user_type, company_name, company_address, id_card, status,
|
||||
registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
user.uuid, user.username, user.password, user.username + '_openid',
|
||||
user.nickname, user.real_name, user.phone, user.email, user.user_type,
|
||||
user.company_name || null, user.company_address || null, user.id_card || null,
|
||||
'active', 'admin_create', 0
|
||||
]
|
||||
});
|
||||
console.log(`✅ 用户 ${user.username} 创建成功`);
|
||||
} else {
|
||||
console.log(`✅ 用户 ${user.username} 已存在`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 插入供应商数据(使用现有表结构)
|
||||
console.log('🏭 创建供应商数据...');
|
||||
const suppliers = [
|
||||
{
|
||||
name: '内蒙古草原牧业有限公司',
|
||||
code: 'SUP001',
|
||||
contact: '赵大牛',
|
||||
phone: '13900139001',
|
||||
address: '内蒙古呼和浩特市赛罕区草原路123号',
|
||||
region: '内蒙古',
|
||||
qualificationLevel: 'A',
|
||||
cattleTypes: JSON.stringify(['西门塔尔牛', '安格斯牛', '夏洛莱牛']),
|
||||
capacity: 500,
|
||||
rating: 4.8,
|
||||
cooperationStartDate: '2023-01-01'
|
||||
},
|
||||
{
|
||||
name: '新疆天山畜牧合作社',
|
||||
code: 'SUP002',
|
||||
contact: '马小羊',
|
||||
phone: '13900139002',
|
||||
address: '新疆乌鲁木齐市天山区畜牧街456号',
|
||||
region: '新疆',
|
||||
qualificationLevel: 'A',
|
||||
cattleTypes: JSON.stringify(['哈萨克牛', '新疆褐牛']),
|
||||
capacity: 300,
|
||||
rating: 4.5,
|
||||
cooperationStartDate: '2023-03-15'
|
||||
},
|
||||
{
|
||||
name: '山东鲁西黄牛养殖场',
|
||||
code: 'SUP003',
|
||||
contact: '孙大强',
|
||||
phone: '13900139003',
|
||||
address: '山东省济南市历城区养殖园区789号',
|
||||
region: '山东',
|
||||
qualificationLevel: 'B',
|
||||
cattleTypes: JSON.stringify(['鲁西黄牛', '利木赞牛']),
|
||||
capacity: 200,
|
||||
rating: 4.2,
|
||||
cooperationStartDate: '2023-06-01'
|
||||
}
|
||||
];
|
||||
|
||||
for (const supplier of suppliers) {
|
||||
// 检查供应商是否已存在
|
||||
const [existing] = await sequelize.query(
|
||||
"SELECT id FROM suppliers WHERE code = ?",
|
||||
{ replacements: [supplier.code] }
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
await sequelize.query(`
|
||||
INSERT INTO suppliers (
|
||||
name, code, contact, phone, address, region, qualificationLevel,
|
||||
cattleTypes, capacity, rating, cooperationStartDate, status, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
supplier.name, supplier.code, supplier.contact, supplier.phone,
|
||||
supplier.address, supplier.region, supplier.qualificationLevel, supplier.cattleTypes,
|
||||
supplier.capacity, supplier.rating, supplier.cooperationStartDate, 'active'
|
||||
]
|
||||
});
|
||||
console.log(`✅ 供应商 ${supplier.code} 创建成功`);
|
||||
} else {
|
||||
console.log(`✅ 供应商 ${supplier.code} 已存在`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 插入订单数据(使用现有表结构)
|
||||
console.log('📋 创建订单数据...');
|
||||
|
||||
// 获取用户和供应商ID
|
||||
const [buyers] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'buyer' LIMIT 1");
|
||||
const [traders] = await sequelize.query("SELECT id, nickname FROM users WHERE user_type = 'trader' LIMIT 1");
|
||||
const [supplierList] = await sequelize.query("SELECT id, name FROM suppliers LIMIT 2");
|
||||
|
||||
if (buyers.length > 0 && supplierList.length > 0) {
|
||||
const orders = [
|
||||
{
|
||||
orderNo: 'ORD' + Date.now().toString().slice(-8) + '001',
|
||||
buyerId: buyers[0].id,
|
||||
buyerName: buyers[0].nickname,
|
||||
supplierId: supplierList[0].id,
|
||||
supplierName: supplierList[0].name,
|
||||
traderId: null,
|
||||
traderName: null,
|
||||
cattleBreed: '西门塔尔牛',
|
||||
cattleCount: 50,
|
||||
expectedWeight: 25000.00,
|
||||
unitPrice: 32.50,
|
||||
totalAmount: 812500.00,
|
||||
paidAmount: 200000.00,
|
||||
remainingAmount: 612500.00,
|
||||
deliveryAddress: '北京市朝阳区屠宰场',
|
||||
expectedDeliveryDate: '2024-01-15 08:00:00',
|
||||
status: 'confirmed',
|
||||
notes: '要求健康证明齐全,质量等级A级'
|
||||
},
|
||||
{
|
||||
orderNo: 'ORD' + Date.now().toString().slice(-8) + '002',
|
||||
buyerId: buyers[0].id,
|
||||
buyerName: buyers[0].nickname,
|
||||
supplierId: supplierList.length > 1 ? supplierList[1].id : supplierList[0].id,
|
||||
supplierName: supplierList.length > 1 ? supplierList[1].name : supplierList[0].name,
|
||||
traderId: null,
|
||||
traderName: null,
|
||||
cattleBreed: '安格斯牛',
|
||||
cattleCount: 30,
|
||||
expectedWeight: 16500.00,
|
||||
unitPrice: 35.00,
|
||||
totalAmount: 577500.00,
|
||||
paidAmount: 150000.00,
|
||||
remainingAmount: 427500.00,
|
||||
deliveryAddress: '上海市浦东新区加工厂',
|
||||
expectedDeliveryDate: '2024-01-20 10:00:00',
|
||||
status: 'pending',
|
||||
notes: '需要冷链运输,重量范围500-600kg'
|
||||
}
|
||||
];
|
||||
|
||||
for (const order of orders) {
|
||||
// 检查订单是否已存在
|
||||
const [existing] = await sequelize.query(
|
||||
"SELECT id FROM orders WHERE orderNo = ?",
|
||||
{ replacements: [order.orderNo] }
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
await sequelize.query(`
|
||||
INSERT INTO orders (
|
||||
orderNo, buyerId, buyerName, supplierId, supplierName, traderId, traderName,
|
||||
cattleBreed, cattleCount, expectedWeight, unitPrice, totalAmount, paidAmount,
|
||||
remainingAmount, deliveryAddress, expectedDeliveryDate, status, notes,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [
|
||||
order.orderNo, order.buyerId, order.buyerName, order.supplierId, order.supplierName,
|
||||
order.traderId, order.traderName, order.cattleBreed, order.cattleCount,
|
||||
order.expectedWeight, order.unitPrice, order.totalAmount, order.paidAmount,
|
||||
order.remainingAmount, order.deliveryAddress, order.expectedDeliveryDate,
|
||||
order.status, order.notes
|
||||
]
|
||||
});
|
||||
console.log(`✅ 订单 ${order.orderNo} 创建成功`);
|
||||
} else {
|
||||
console.log(`✅ 订单 ${order.orderNo} 已存在`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 插入系统配置
|
||||
console.log('⚙️ 创建系统配置...');
|
||||
const configs = [
|
||||
['system.name', '活牛采购智能数字化系统', 'string', 'system', '系统名称'],
|
||||
['system.version', '1.0.0', 'string', 'system', '系统版本'],
|
||||
['order.auto_confirm_hours', '24', 'number', 'order', '订单自动确认时间(小时)'],
|
||||
['payment.timeout_minutes', '30', 'number', 'payment', '支付超时时间(分钟)'],
|
||||
['transport.tracking_interval', '300', 'number', 'transport', '位置跟踪间隔(秒)'],
|
||||
['quality.min_score', '80', 'number', 'quality', '质量检验最低分数'],
|
||||
['notification.sms_enabled', 'true', 'boolean', 'notification', '是否启用短信通知'],
|
||||
['notification.email_enabled', 'true', 'boolean', 'notification', '是否启用邮件通知']
|
||||
];
|
||||
|
||||
for (const [key, value, type, category, description] of configs) {
|
||||
// 检查配置是否已存在
|
||||
const [existing] = await sequelize.query(
|
||||
"SELECT id FROM system_configs WHERE config_key = ?",
|
||||
{ replacements: [key] }
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
await sequelize.query(`
|
||||
INSERT INTO system_configs (
|
||||
config_key, config_value, config_type, category, description,
|
||||
is_public, is_editable, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
`, {
|
||||
replacements: [key, value, type, category, description, true, true]
|
||||
});
|
||||
console.log(`✅ 配置 ${key} 创建成功`);
|
||||
} else {
|
||||
console.log(`✅ 配置 ${key} 已存在`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ 测试数据插入完成');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 插入测试数据失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据
|
||||
*/
|
||||
async function validateData() {
|
||||
console.log('\n🔍 验证数据完整性...');
|
||||
|
||||
try {
|
||||
// 统计各表数据量
|
||||
const tables = ['users', 'suppliers', 'orders', 'payments', 'system_configs'];
|
||||
|
||||
for (const table of tables) {
|
||||
try {
|
||||
const [result] = await sequelize.query(`SELECT COUNT(*) as count FROM ${table}`);
|
||||
console.log(`📊 ${table}: ${result[0].count} 条记录`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ 表 ${table} 不存在或查询失败`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查管理员用户
|
||||
const [adminUsers] = await sequelize.query(
|
||||
"SELECT id, username, nickname, user_type FROM users WHERE user_type = 'admin'"
|
||||
);
|
||||
|
||||
console.log('\n👤 管理员用户:');
|
||||
adminUsers.forEach(user => {
|
||||
console.log(` - ID: ${user.id}, 用户名: ${user.username}, 昵称: ${user.nickname}`);
|
||||
});
|
||||
|
||||
// 检查供应商
|
||||
const [suppliers] = await sequelize.query(
|
||||
"SELECT id, name, code, region, qualificationLevel FROM suppliers LIMIT 5"
|
||||
);
|
||||
|
||||
console.log('\n🏭 供应商列表:');
|
||||
suppliers.forEach(supplier => {
|
||||
console.log(` - ${supplier.code}: ${supplier.name} (${supplier.region}, 等级${supplier.qualificationLevel})`);
|
||||
});
|
||||
|
||||
// 检查订单
|
||||
const [orders] = await sequelize.query(
|
||||
"SELECT id, orderNo, cattleBreed, cattleCount, totalAmount, status FROM orders LIMIT 5"
|
||||
);
|
||||
|
||||
console.log('\n📋 订单列表:');
|
||||
orders.forEach(order => {
|
||||
console.log(` - ${order.orderNo}: ${order.cattleBreed} ${order.cattleCount}头, ¥${order.totalAmount} (${order.status})`);
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 数据验证失败:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
console.log('\n🚀 ===== 兼容数据插入开始 =====');
|
||||
|
||||
// 1. 测试连接
|
||||
console.log('\n📡 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 插入测试数据
|
||||
await insertTestData();
|
||||
|
||||
// 3. 验证数据
|
||||
await validateData();
|
||||
|
||||
console.log('\n🎉 ===== 数据插入完成 =====');
|
||||
console.log('\n📋 系统信息:');
|
||||
console.log('🌐 后端服务: http://localhost:4330');
|
||||
console.log('🎨 管理后台: http://localhost:3000');
|
||||
console.log('👤 管理员账户: admin / admin123');
|
||||
console.log('📚 API文档: http://localhost:4330/api-docs');
|
||||
console.log('\n🔑 测试账户:');
|
||||
console.log(' 采购商: buyer001 / 123456');
|
||||
console.log(' 贸易商: trader001 / 123456');
|
||||
console.log(' 司机: driver001 / 123456');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 操作失败:', error);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { main };
|
||||
@@ -31,43 +31,40 @@
|
||||
"author": "NiuMall Team",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.1.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"express-validator": "^7.0.1",
|
||||
"helmet": "^7.1.0",
|
||||
"joi": "^17.11.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"moment": "^2.29.4",
|
||||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^3.6.5",
|
||||
"sequelize": "^6.35.2",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"uuid": "^9.0.1",
|
||||
"mysql2": "^3.6.5",
|
||||
"bcrypt": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"joi": "^17.11.0",
|
||||
"cors": "^2.8.5",
|
||||
"helmet": "^7.1.0",
|
||||
"express-rate-limit": "^7.1.5",
|
||||
"compression": "^1.7.4",
|
||||
"morgan": "^1.10.0",
|
||||
"winston": "^3.11.0",
|
||||
"winston-daily-rotate-file": "^4.7.1",
|
||||
"yamljs": "^0.3.0"
|
||||
"dotenv": "^16.3.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"swagger-jsdoc": "^6.2.8",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"express-validator": "^7.0.1",
|
||||
"moment": "^2.29.4",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.2",
|
||||
"jest": "^29.7.0",
|
||||
"supertest": "^6.3.3",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"prettier": "^3.1.0",
|
||||
"sequelize-cli": "^6.6.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"supertest": "^6.3.3"
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.1",
|
||||
"sequelize-cli": "^6.6.2"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
|
||||
242
backend/simple_db_checker.js
Normal file
242
backend/simple_db_checker.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/**
|
||||
* 简化版数据库结构检查和修复工具
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
const { Sequelize, QueryTypes } = require('sequelize');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
// 创建数据库连接
|
||||
const sequelize = new Sequelize(
|
||||
process.env.DB_NAME || 'niumall',
|
||||
process.env.DB_USERNAME || 'root',
|
||||
process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
{
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
logging: false
|
||||
}
|
||||
);
|
||||
|
||||
async function checkAndRepairDatabase() {
|
||||
try {
|
||||
console.log('\n🔍 ===== 数据库结构完整性检查 =====\n');
|
||||
|
||||
// 1. 测试数据库连接
|
||||
console.log('📡 测试数据库连接...');
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
|
||||
// 2. 检查现有表
|
||||
console.log('\n📋 检查现有表结构...');
|
||||
const tables = await sequelize.query(
|
||||
"SHOW TABLES",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const tableNames = tables.map(table => Object.values(table)[0]);
|
||||
console.log(`📊 发现 ${tableNames.length} 个表: ${tableNames.join(', ')}`);
|
||||
|
||||
// 3. 检查users表结构
|
||||
if (tableNames.includes('users')) {
|
||||
console.log('\n🔍 检查users表结构...');
|
||||
const columns = await sequelize.query(
|
||||
"SHOW COLUMNS FROM users",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
const columnNames = columns.map(col => col.Field);
|
||||
console.log(`📝 users表字段 (${columnNames.length}个): ${columnNames.join(', ')}`);
|
||||
|
||||
// 检查关键字段
|
||||
const requiredFields = ['username', 'password_hash', 'user_type', 'status'];
|
||||
const missingFields = requiredFields.filter(field => !columnNames.includes(field));
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
console.log(`⚠️ 缺少关键字段: ${missingFields.join(', ')}`);
|
||||
} else {
|
||||
console.log('✅ 关键字段完整');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 检查suppliers表
|
||||
if (!tableNames.includes('suppliers')) {
|
||||
console.log('\n⚠️ suppliers表不存在,创建中...');
|
||||
await sequelize.query(`
|
||||
CREATE TABLE suppliers (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
code VARCHAR(20) UNIQUE NOT NULL,
|
||||
contact VARCHAR(50) NOT NULL,
|
||||
phone VARCHAR(20) UNIQUE NOT NULL,
|
||||
email VARCHAR(100),
|
||||
address VARCHAR(200) NOT NULL,
|
||||
region VARCHAR(20) NOT NULL,
|
||||
business_license VARCHAR(255),
|
||||
animal_quarantine_certificate VARCHAR(255),
|
||||
qualification_level ENUM('A','B','C','D') DEFAULT 'C',
|
||||
certifications JSON,
|
||||
cattle_types JSON,
|
||||
capacity INT DEFAULT 0,
|
||||
rating DECIMAL(3,2) DEFAULT 0.00,
|
||||
cooperation_start_date DATE,
|
||||
status ENUM('active','inactive','suspended','blacklisted') DEFAULT 'active',
|
||||
bank_account VARCHAR(50),
|
||||
bank_name VARCHAR(100),
|
||||
tax_number VARCHAR(30),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
console.log('✅ suppliers表创建成功');
|
||||
} else {
|
||||
console.log('✅ suppliers表已存在');
|
||||
}
|
||||
|
||||
// 5. 检查orders表
|
||||
if (!tableNames.includes('orders')) {
|
||||
console.log('\n⚠️ orders表不存在,创建中...');
|
||||
await sequelize.query(`
|
||||
CREATE TABLE orders (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
orderNo VARCHAR(50) UNIQUE NOT NULL,
|
||||
buyerId BIGINT NOT NULL,
|
||||
buyerName VARCHAR(100) NOT NULL,
|
||||
supplierId BIGINT,
|
||||
supplierName VARCHAR(100),
|
||||
traderId BIGINT,
|
||||
traderName VARCHAR(100),
|
||||
cattleBreed VARCHAR(20) NOT NULL,
|
||||
cattleCount INT NOT NULL,
|
||||
expectedWeight DECIMAL(10,2) NOT NULL,
|
||||
actualWeight DECIMAL(10,2),
|
||||
unitPrice DECIMAL(10,2) NOT NULL,
|
||||
totalAmount DECIMAL(15,2) NOT NULL,
|
||||
paidAmount DECIMAL(15,2) DEFAULT 0,
|
||||
remainingAmount DECIMAL(15,2) DEFAULT 0,
|
||||
status ENUM('pending','confirmed','loading','shipping','delivered','completed','cancelled') DEFAULT 'pending',
|
||||
deliveryAddress VARCHAR(255) NOT NULL,
|
||||
expectedDeliveryDate DATETIME NOT NULL,
|
||||
actualDeliveryDate DATETIME,
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
console.log('✅ orders表创建成功');
|
||||
} else {
|
||||
console.log('✅ orders表已存在');
|
||||
}
|
||||
|
||||
// 6. 检查payments表
|
||||
if (!tableNames.includes('payments')) {
|
||||
console.log('\n⚠️ payments表不存在,创建中...');
|
||||
await sequelize.query(`
|
||||
CREATE TABLE payments (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
order_id BIGINT NOT NULL,
|
||||
user_id BIGINT NOT NULL,
|
||||
amount DECIMAL(15,2) NOT NULL,
|
||||
paid_amount DECIMAL(15,2),
|
||||
payment_type ENUM('wechat','alipay','bank') NOT NULL,
|
||||
payment_method ENUM('mini_program','app','web') NOT NULL,
|
||||
payment_no VARCHAR(50) UNIQUE NOT NULL,
|
||||
third_party_id VARCHAR(100),
|
||||
status ENUM('pending','paid','failed','refunded') DEFAULT 'pending',
|
||||
paid_time DATETIME,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||
`);
|
||||
console.log('✅ payments表创建成功');
|
||||
} else {
|
||||
console.log('✅ payments表已存在');
|
||||
}
|
||||
|
||||
// 7. 检查管理员用户
|
||||
console.log('\n👤 检查管理员用户...');
|
||||
const adminUsers = await sequelize.query(
|
||||
"SELECT * FROM users WHERE user_type = 'admin' OR username = 'admin'",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
|
||||
if (adminUsers.length === 0) {
|
||||
console.log('⚠️ 未找到管理员用户,创建中...');
|
||||
const hashedPassword = await bcrypt.hash('admin123', 10);
|
||||
const uuid = uuidv4();
|
||||
|
||||
await sequelize.query(
|
||||
`INSERT INTO users (
|
||||
uuid, username, password_hash, openid, nickname, user_type, status,
|
||||
registration_source, login_count, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`,
|
||||
{
|
||||
replacements: [
|
||||
uuid,
|
||||
'admin',
|
||||
hashedPassword,
|
||||
'admin_' + uuid.substring(0, 8),
|
||||
'系统管理员',
|
||||
'admin',
|
||||
'active',
|
||||
'admin_create',
|
||||
0
|
||||
]
|
||||
}
|
||||
);
|
||||
console.log('✅ 管理员用户创建成功');
|
||||
} else {
|
||||
console.log(`✅ 发现 ${adminUsers.length} 个管理员用户`);
|
||||
adminUsers.forEach(user => {
|
||||
console.log(` - ID: ${user.id}, 用户名: ${user.username || 'N/A'}, 昵称: ${user.nickname}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 8. 数据完整性检查
|
||||
console.log('\n🔍 数据完整性检查...');
|
||||
|
||||
// 检查用户表数据
|
||||
const userCount = await sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM users",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
console.log(`📊 用户总数: ${userCount[0].count}`);
|
||||
|
||||
// 检查订单表数据
|
||||
if (tableNames.includes('orders')) {
|
||||
const orderCount = await sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM orders",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
console.log(`📊 订单总数: ${orderCount[0].count}`);
|
||||
}
|
||||
|
||||
// 检查供应商表数据
|
||||
if (tableNames.includes('suppliers')) {
|
||||
const supplierCount = await sequelize.query(
|
||||
"SELECT COUNT(*) as count FROM suppliers",
|
||||
{ type: QueryTypes.SELECT }
|
||||
);
|
||||
console.log(`📊 供应商总数: ${supplierCount[0].count}`);
|
||||
}
|
||||
|
||||
console.log('\n🎉 ===== 数据库检查完成 =====');
|
||||
console.log('\n📋 系统信息:');
|
||||
console.log('🌐 后端服务: http://localhost:4330');
|
||||
console.log('🎨 管理后台: http://localhost:3000');
|
||||
console.log('👤 管理员账户: admin / admin123');
|
||||
console.log('📚 API文档: http://localhost:4330/api-docs');
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ 数据库检查失败:', error.message);
|
||||
} finally {
|
||||
await sequelize.close();
|
||||
console.log('\n🔌 数据库连接已关闭');
|
||||
}
|
||||
}
|
||||
|
||||
// 运行检查
|
||||
checkAndRepairDatabase();
|
||||
@@ -64,6 +64,7 @@ app.use(helmet({
|
||||
app.use(cors({
|
||||
origin: [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:3001',
|
||||
'http://localhost:5173',
|
||||
'https://wapi.nanniwan.com',
|
||||
'https://ad.nanniwan.com',
|
||||
|
||||
Reference in New Issue
Block a user