diff --git a/backend/.env b/backend/.env index f91754f..62796ea 100644 --- a/backend/.env +++ b/backend/.env @@ -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配置 diff --git a/backend/check_existing_tables.js b/backend/check_existing_tables.js new file mode 100644 index 0000000..d4573e6 --- /dev/null +++ b/backend/check_existing_tables.js @@ -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(); \ No newline at end of file diff --git a/backend/config/database.js b/backend/config/database.js index c772377..19dc28e 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -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', diff --git a/backend/create_admin_user.js b/backend/create_admin_user.js new file mode 100644 index 0000000..e6a3c7d --- /dev/null +++ b/backend/create_admin_user.js @@ -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(); \ No newline at end of file diff --git a/backend/database_integrity_checker.js b/backend/database_integrity_checker.js new file mode 100644 index 0000000..a5579fd --- /dev/null +++ b/backend/database_integrity_checker.js @@ -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 +}; \ No newline at end of file diff --git a/backend/fix_user_table.js b/backend/fix_user_table.js new file mode 100644 index 0000000..8957982 --- /dev/null +++ b/backend/fix_user_table.js @@ -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(); \ No newline at end of file diff --git a/backend/init_database_with_test_data.js b/backend/init_database_with_test_data.js new file mode 100644 index 0000000..4339b02 --- /dev/null +++ b/backend/init_database_with_test_data.js @@ -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 }; \ No newline at end of file diff --git a/backend/insert_compatible_test_data.js b/backend/insert_compatible_test_data.js new file mode 100644 index 0000000..8deba0b --- /dev/null +++ b/backend/insert_compatible_test_data.js @@ -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 }; \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 5d013de..00e74aa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/simple_db_checker.js b/backend/simple_db_checker.js new file mode 100644 index 0000000..65c9556 --- /dev/null +++ b/backend/simple_db_checker.js @@ -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(); \ No newline at end of file diff --git a/backend/src/main.js b/backend/src/main.js index f65a1ed..7a139ba 100644 --- a/backend/src/main.js +++ b/backend/src/main.js @@ -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', diff --git a/scripts/database/README.md b/scripts/database/README.md new file mode 100644 index 0000000..8220913 --- /dev/null +++ b/scripts/database/README.md @@ -0,0 +1,196 @@ +# 数据库初始化脚本 + +本目录包含牛牛商城项目的数据库初始化脚本,用于创建数据库表结构和插入测试数据。 + +## 文件说明 + +### 1. `init_database.sql` +- **功能**: 创建所有必要的数据库表结构 +- **包含内容**: + - 用户表 (users) + - 供应商表 (suppliers) + - 司机表 (drivers) + - 车辆表 (vehicles) + - 订单表 (orders) + - 支付表 (payments) + - 运输表 (transports) + - 运输跟踪表 (transport_tracks) + - 质检记录表 (quality_records) + - 结算表 (settlements) + - 外键约束关系 + +### 2. `init_test_data.sql` +- **功能**: 插入测试数据 +- **包含内容**: + - 管理员、采购商、贸易商、质检员等用户数据 + - 5个供应商的基本信息 + - 5个司机和车辆的信息 + - 5个订单及相关的支付、运输、质检、结算记录 + - 运输跟踪轨迹数据 + +### 3. `run_init.sh` +- **功能**: 一键执行数据库初始化的Shell脚本 +- **特性**: + - 支持命令行参数配置数据库连接 + - 自动检测数据库连接 + - 可选择跳过表结构创建或测试数据插入 + - 彩色输出和进度提示 + - 执行完成后显示统计信息 + +## 使用方法 + +### 前置条件 + +1. **安装MySQL客户端** + ```bash + # macOS + brew install mysql-client + + # 或者安装完整的MySQL + brew install mysql + ``` + +2. **确保数据库服务可访问** + - 本地MySQL服务已启动 + - 或者远程数据库连接正常 + +### 执行初始化 + +#### 方法一:使用Shell脚本(推荐) + +```bash +# 进入脚本目录 +cd /Users/aiotagro/vue/niumall/scripts/database + +# 使用默认配置(localhost:3306) +./run_init.sh -u root -p your_password -d niumall + +# 使用远程数据库 +./run_init.sh \ + --host nj-cdb-3pwh2kz1.sql.tencentcdb.com \ + --port 20784 \ + --user jiebanke \ + --password 'aiot741$12346' \ + --database niumall + +# 只创建表结构,跳过测试数据 +./run_init.sh -u root -p password --skip-data + +# 只插入测试数据,跳过表结构创建 +./run_init.sh -u root -p password --skip-structure +``` + +#### 方法二:手动执行SQL文件 + +```bash +# 1. 创建表结构 +mysql -h host -P port -u username -p database_name < init_database.sql + +# 2. 插入测试数据 +mysql -h host -P port -u username -p database_name < init_test_data.sql +``` + +### 脚本参数说明 + +| 参数 | 简写 | 说明 | 默认值 | +|------|------|------|--------| +| `--host` | `-h` | 数据库主机地址 | localhost | +| `--port` | `-P` | 数据库端口 | 3306 | +| `--user` | `-u` | 数据库用户名 | root | +| `--password` | `-p` | 数据库密码 | 无 | +| `--database` | `-d` | 数据库名称 | niumall | +| `--skip-structure` | 无 | 跳过表结构创建 | false | +| `--skip-data` | 无 | 跳过测试数据插入 | false | +| `--help` | 无 | 显示帮助信息 | - | + +## 测试数据说明 + +### 用户数据 +- **管理员**: admin/123456 +- **采购商**: buyer001/123456, buyer002/123456 +- **贸易商**: trader001/123456 +- **质检员**: staff001/123456, staff002/123456 + +### 业务数据 +- **供应商**: 5个不同地区的供应商(内蒙古、山东、河南、新疆、黑龙江) +- **订单**: 5个订单,涵盖不同的牛种类型和交易状态 +- **运输**: 包含运输中和已安排的运输任务 +- **质检**: 装车前和到货质检记录 +- **结算**: 预付款和尾款结算记录 + +## 数据库表关系 + +``` +users (用户表) +├── orders.buyerId (买方) +├── orders.traderId (贸易商) +├── payments.user_id (支付用户) +├── quality_records.inspector_id (质检员) +└── settlements.approver_id (审批人) + +suppliers (供应商表) +└── orders.supplierId (供应商) + +drivers (司机表) +├── vehicles.driver_id (车辆司机) +├── transports.driver_id (运输司机) +└── transport_tracks.driver_id (跟踪司机) + +vehicles (车辆表) +├── drivers.current_vehicle_id (司机当前车辆) +└── transports.vehicle_id (运输车辆) + +orders (订单表) +├── payments.order_id (订单支付) +├── transports.order_id (订单运输) +├── transport_tracks.order_id (运输跟踪) +├── quality_records.order_id (质检记录) +└── settlements.order_id (结算记录) + +transports (运输表) +└── transport_tracks.transport_id (运输跟踪) +``` + +## 注意事项 + +1. **权限要求**: 执行脚本的数据库用户需要有CREATE、DROP、INSERT、SELECT权限 +2. **数据清理**: 脚本会删除已存在的表,请确保备份重要数据 +3. **字符集**: 所有表使用utf8mb4字符集,支持emoji和特殊字符 +4. **外键约束**: 表之间设置了外键约束,删除数据时需注意依赖关系 +5. **测试环境**: 建议在测试环境中先验证脚本的正确性 + +## 故障排除 + +### 1. MySQL客户端未安装 +```bash +# macOS安装MySQL客户端 +brew install mysql-client + +# 添加到PATH(如果需要) +echo 'export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +### 2. 数据库连接失败 +- 检查数据库服务是否启动 +- 验证主机地址、端口、用户名、密码是否正确 +- 确认网络连接和防火墙设置 + +### 3. 权限不足 +```sql +-- 为用户授予必要权限 +GRANT CREATE, DROP, INSERT, SELECT, UPDATE, DELETE ON niumall.* TO 'username'@'%'; +FLUSH PRIVILEGES; +``` + +### 4. 外键约束错误 +- 确保按照正确顺序创建表 +- 检查外键引用的表和字段是否存在 +- 验证数据类型是否匹配 + +## 更新日志 + +- **2024-01-21**: 初始版本,包含完整的表结构和测试数据 +- 支持10个核心业务表 +- 提供一键初始化脚本 +- 包含丰富的测试数据用于开发调试 \ No newline at end of file diff --git a/scripts/database/init_database.sql b/scripts/database/init_database.sql new file mode 100644 index 0000000..4e4b81d --- /dev/null +++ b/scripts/database/init_database.sql @@ -0,0 +1,380 @@ +-- ===================================================== +-- 牛牛商城数据库初始化脚本 +-- 创建时间: 2024-01-21 +-- 描述: 创建所有必要的数据表和初始化数据 +-- ===================================================== + +-- 设置字符集和时区 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; +SET time_zone = '+08:00'; + +-- ===================================================== +-- 1. 用户表 (users) +-- ===================================================== +DROP TABLE IF EXISTS `users`; +CREATE TABLE `users` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `uuid` varchar(36) DEFAULT NULL COMMENT '用户唯一标识符', + `username` varchar(50) DEFAULT NULL COMMENT '用户名', + `password_hash` varchar(255) DEFAULT NULL COMMENT '密码哈希值', + `openid` varchar(64) DEFAULT NULL COMMENT '微信小程序OpenID', + `unionid` varchar(64) DEFAULT NULL COMMENT '微信UnionID', + `nickname` varchar(50) NOT NULL COMMENT '用户昵称', + `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', + `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', + `gender` enum('male','female','other') DEFAULT NULL COMMENT '性别', + `birthday` date DEFAULT NULL COMMENT '生日', + `phone` varchar(20) DEFAULT NULL COMMENT '手机号码', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱地址', + `user_type` enum('buyer','trader','supplier','driver','staff','admin') NOT NULL DEFAULT 'buyer' COMMENT '用户类型', + `company_name` varchar(100) DEFAULT NULL COMMENT '公司名称', + `company_address` varchar(200) DEFAULT NULL COMMENT '公司地址', + `business_license` varchar(255) DEFAULT NULL COMMENT '营业执照文件路径', + `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号', + `status` enum('active','inactive','suspended','pending_approval') NOT NULL DEFAULT 'pending_approval' COMMENT '用户状态', + `last_login_at` datetime DEFAULT NULL COMMENT '最后登录时间', + `login_count` int(11) NOT NULL DEFAULT '0' COMMENT '登录次数', + `registration_source` enum('miniprogram','web','admin_create') NOT NULL DEFAULT 'miniprogram' COMMENT '注册来源', + `approval_notes` text COMMENT '审核备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + UNIQUE KEY `username` (`username`), + UNIQUE KEY `phone` (`phone`), + UNIQUE KEY `email` (`email`), + KEY `idx_openid` (`openid`), + KEY `idx_user_type` (`user_type`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +-- ===================================================== +-- 2. 供应商表 (suppliers) +-- ===================================================== +DROP TABLE IF EXISTS `suppliers`; +CREATE TABLE `suppliers` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '供应商ID', + `name` varchar(100) NOT NULL COMMENT '供应商名称', + `code` varchar(20) NOT NULL COMMENT '供应商编码', + `contact` varchar(50) NOT NULL COMMENT '联系人姓名', + `phone` varchar(20) NOT NULL COMMENT '联系电话', + `email` varchar(100) DEFAULT NULL COMMENT '邮箱地址', + `address` varchar(200) NOT NULL COMMENT '详细地址', + `region` varchar(20) NOT NULL COMMENT '所属区域', + `business_license` varchar(255) DEFAULT NULL COMMENT '营业执照文件路径', + `animal_quarantine_certificate` varchar(255) DEFAULT NULL COMMENT '动物防疫条件合格证文件路径', + `qualification_level` enum('A','B','C','D') NOT NULL DEFAULT 'C' COMMENT '资质等级', + `certifications` json DEFAULT NULL COMMENT '认证证书(JSON格式)', + `cattle_types` json DEFAULT NULL COMMENT '可供应牛种类型(JSON格式)', + `capacity` int(11) DEFAULT NULL COMMENT '供应能力(头/月)', + `rating` decimal(3,2) DEFAULT '0.00' COMMENT '供应商评级(0-5分)', + `cooperation_start_date` date DEFAULT NULL COMMENT '合作开始日期', + `status` enum('active','inactive','suspended') NOT NULL DEFAULT 'active' COMMENT '状态', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `code` (`code`), + UNIQUE KEY `phone` (`phone`), + KEY `idx_region` (`region`), + KEY `idx_status` (`status`), + KEY `idx_qualification_level` (`qualification_level`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商表'; + +-- ===================================================== +-- 3. 司机表 (drivers) +-- ===================================================== +DROP TABLE IF EXISTS `drivers`; +CREATE TABLE `drivers` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '司机ID', + `name` varchar(50) NOT NULL COMMENT '司机姓名', + `phone` varchar(20) NOT NULL COMMENT '联系电话', + `id_card` varchar(18) NOT NULL COMMENT '身份证号', + `driver_license` varchar(20) NOT NULL COMMENT '驾驶证号', + `license_type` enum('A1','A2','B1','B2','C1','C2') NOT NULL COMMENT '驾驶证类型', + `license_expiry_date` date NOT NULL COMMENT '驾驶证到期日期', + `qualification_certificate` varchar(255) DEFAULT NULL COMMENT '从业资格证文件路径', + `qualification_expiry_date` date DEFAULT NULL COMMENT '从业资格证到期日期', + `emergency_contact` varchar(50) DEFAULT NULL COMMENT '紧急联系人', + `emergency_phone` varchar(20) DEFAULT NULL COMMENT '紧急联系电话', + `current_vehicle_id` bigint(20) DEFAULT NULL COMMENT '当前使用车辆ID', + `employment_type` enum('full_time','part_time','contract') NOT NULL DEFAULT 'full_time' COMMENT '雇佣类型', + `hire_date` date DEFAULT NULL COMMENT '入职日期', + `salary` decimal(10,2) DEFAULT NULL COMMENT '薪资', + `performance_rating` decimal(3,2) DEFAULT '0.00' COMMENT '绩效评分(0-5分)', + `status` enum('active','inactive','on_leave','suspended') NOT NULL DEFAULT 'active' COMMENT '状态', + `notes` text COMMENT '备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `phone` (`phone`), + UNIQUE KEY `id_card` (`id_card`), + UNIQUE KEY `driver_license` (`driver_license`), + KEY `idx_status` (`status`), + KEY `idx_license_type` (`license_type`), + KEY `idx_current_vehicle_id` (`current_vehicle_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='司机表'; + +-- ===================================================== +-- 4. 车辆表 (vehicles) +-- ===================================================== +DROP TABLE IF EXISTS `vehicles`; +CREATE TABLE `vehicles` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '车辆ID', + `license_plate` varchar(20) NOT NULL COMMENT '车牌号', + `vehicle_type` varchar(50) NOT NULL COMMENT '车辆类型', + `capacity` int(11) NOT NULL COMMENT '载重能力(公斤)', + `driver_id` bigint(20) NOT NULL COMMENT '司机ID', + `status` enum('available','in_use','maintenance','retired') NOT NULL DEFAULT 'available' COMMENT '车辆状态', + `last_maintenance_date` date DEFAULT NULL COMMENT '上次维护日期', + `next_maintenance_date` date DEFAULT NULL COMMENT '下次维护日期', + `insurance_expiry_date` date DEFAULT NULL COMMENT '保险到期日期', + `registration_expiry_date` date DEFAULT NULL COMMENT '注册到期日期', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `license_plate` (`license_plate`), + KEY `idx_driver_id` (`driver_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆表'; + +-- ===================================================== +-- 5. 订单表 (orders) +-- ===================================================== +DROP TABLE IF EXISTS `orders`; +CREATE TABLE `orders` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID', + `orderNo` varchar(50) NOT NULL COMMENT '订单编号', + `buyerId` bigint(20) NOT NULL COMMENT '买方ID', + `buyerName` varchar(100) NOT NULL COMMENT '买方名称', + `supplierId` bigint(20) NOT NULL COMMENT '供应商ID', + `supplierName` varchar(100) NOT NULL COMMENT '供应商名称', + `traderId` bigint(20) DEFAULT NULL COMMENT '贸易商ID', + `traderName` varchar(100) DEFAULT NULL COMMENT '贸易商名称', + `cattleBreed` varchar(20) NOT NULL COMMENT '牛的品种', + `cattleCount` int(11) NOT NULL COMMENT '牛的数量', + `expectedWeight` decimal(10,2) NOT NULL COMMENT '预计总重量', + `actualWeight` decimal(10,2) DEFAULT NULL COMMENT '实际总重量', + `unitPrice` decimal(10,2) NOT NULL COMMENT '单价(元/公斤)', + `totalAmount` decimal(15,2) NOT NULL COMMENT '订单总金额', + `deliveryAddress` varchar(200) NOT NULL COMMENT '交货地址', + `deliveryDate` date NOT NULL COMMENT '交货日期', + `paymentMethod` enum('cash','bank_transfer','online_payment') NOT NULL COMMENT '支付方式', + `paymentStatus` enum('unpaid','partial_paid','paid','refunded') NOT NULL DEFAULT 'unpaid' COMMENT '支付状态', + `orderStatus` enum('pending','confirmed','in_production','shipped','delivered','completed','cancelled') NOT NULL DEFAULT 'pending' COMMENT '订单状态', + `notes` text COMMENT '订单备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `orderNo` (`orderNo`), + KEY `idx_buyerId` (`buyerId`), + KEY `idx_supplierId` (`supplierId`), + KEY `idx_traderId` (`traderId`), + KEY `idx_orderStatus` (`orderStatus`), + KEY `idx_paymentStatus` (`paymentStatus`), + KEY `idx_deliveryDate` (`deliveryDate`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; + +-- ===================================================== +-- 6. 支付表 (payments) +-- ===================================================== +DROP TABLE IF EXISTS `payments`; +CREATE TABLE `payments` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '支付ID', + `order_id` bigint(20) NOT NULL COMMENT '订单ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `amount` decimal(15,2) NOT NULL COMMENT '支付金额', + `paid_amount` decimal(15,2) DEFAULT NULL COMMENT '实际支付金额', + `payment_type` enum('wechat','alipay','bank') NOT NULL COMMENT '支付类型', + `payment_method` enum('mini_program','app','web') NOT NULL COMMENT '支付方式', + `payment_no` varchar(50) NOT NULL COMMENT '支付单号', + `third_party_id` varchar(100) DEFAULT NULL COMMENT '第三方支付ID', + `status` enum('pending','paid','failed','refunded') NOT NULL DEFAULT 'pending' COMMENT '支付状态', + `paid_time` datetime DEFAULT NULL COMMENT '支付时间', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `payment_no` (`payment_no`), + KEY `idx_order_id` (`order_id`), + KEY `idx_user_id` (`user_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付表'; + +-- ===================================================== +-- 7. 运输表 (transports) +-- ===================================================== +DROP TABLE IF EXISTS `transports`; +CREATE TABLE `transports` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '运输ID', + `order_id` bigint(20) NOT NULL COMMENT '关联订单ID', + `driver_id` bigint(20) NOT NULL COMMENT '司机ID', + `vehicle_id` bigint(20) NOT NULL COMMENT '车辆ID', + `start_location` varchar(255) NOT NULL COMMENT '起始地点', + `end_location` varchar(255) NOT NULL COMMENT '目的地', + `scheduled_start_time` datetime NOT NULL COMMENT '计划开始时间', + `actual_start_time` datetime DEFAULT NULL COMMENT '实际开始时间', + `scheduled_end_time` datetime NOT NULL COMMENT '计划结束时间', + `actual_end_time` datetime DEFAULT NULL COMMENT '实际结束时间', + `status` enum('scheduled','in_transit','completed','cancelled') NOT NULL DEFAULT 'scheduled' COMMENT '运输状态', + `estimated_arrival_time` datetime DEFAULT NULL COMMENT '预计到达时间', + `distance` decimal(8,2) DEFAULT NULL COMMENT '运输距离(公里)', + `fuel_cost` decimal(10,2) DEFAULT NULL COMMENT '燃油费用', + `toll_cost` decimal(10,2) DEFAULT NULL COMMENT '过路费', + `other_cost` decimal(10,2) DEFAULT NULL COMMENT '其他费用', + `total_cost` decimal(10,2) DEFAULT NULL COMMENT '总费用', + `notes` text COMMENT '运输备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_driver_id` (`driver_id`), + KEY `idx_vehicle_id` (`vehicle_id`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运输表'; + +-- ===================================================== +-- 8. 运输跟踪表 (transport_tracks) +-- ===================================================== +DROP TABLE IF EXISTS `transport_tracks`; +CREATE TABLE `transport_tracks` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '跟踪记录ID', + `transport_id` bigint(20) NOT NULL COMMENT '运输ID', + `order_id` bigint(20) NOT NULL COMMENT '订单ID', + `driver_id` bigint(20) NOT NULL COMMENT '司机ID', + `latitude` decimal(10,8) NOT NULL COMMENT '纬度', + `longitude` decimal(11,8) NOT NULL COMMENT '经度', + `speed` decimal(5,2) DEFAULT NULL COMMENT '速度(km/h)', + `direction` decimal(5,2) DEFAULT NULL COMMENT '方向角度', + `cattle_status` varchar(20) DEFAULT NULL COMMENT '牛只状态', + `temperature` decimal(5,2) DEFAULT NULL COMMENT '温度(℃)', + `humidity` decimal(5,2) DEFAULT NULL COMMENT '湿度(%)', + `video_url` varchar(255) DEFAULT NULL COMMENT '视频URL', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_transport_id` (`transport_id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_driver_id` (`driver_id`), + KEY `idx_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运输跟踪表'; + +-- ===================================================== +-- 9. 质检记录表 (quality_records) +-- ===================================================== +DROP TABLE IF EXISTS `quality_records`; +CREATE TABLE `quality_records` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '质检记录ID', + `order_id` bigint(20) NOT NULL COMMENT '关联订单ID', + `inspector_id` bigint(20) NOT NULL COMMENT '检验员ID', + `inspection_type` enum('pre_loading','in_transit','arrival','final') NOT NULL COMMENT '检验类型', + `inspection_date` datetime NOT NULL COMMENT '检验日期', + `location` varchar(200) NOT NULL COMMENT '检验地点', + `cattle_count_expected` int(11) NOT NULL COMMENT '预期牛只数量', + `cattle_count_actual` int(11) NOT NULL COMMENT '实际牛只数量', + `weight_expected` decimal(10,2) DEFAULT NULL COMMENT '预期总重量(kg)', + `weight_actual` decimal(10,2) DEFAULT NULL COMMENT '实际总重量(kg)', + `health_status` enum('excellent','good','fair','poor') NOT NULL COMMENT '健康状况', + `breed_verification` tinyint(1) NOT NULL DEFAULT '1' COMMENT '品种验证是否通过', + `age_range_verification` tinyint(1) NOT NULL DEFAULT '1' COMMENT '年龄范围验证是否通过', + `quarantine_certificate` varchar(255) DEFAULT NULL COMMENT '检疫证明文件路径', + `health_certificate` varchar(255) DEFAULT NULL COMMENT '健康证明文件路径', + `photos` json DEFAULT NULL COMMENT '检验照片路径数组(JSON格式)', + `videos` json DEFAULT NULL COMMENT '检验视频路径数组(JSON格式)', + `quality_issues` json DEFAULT NULL COMMENT '质量问题记录(JSON格式)', + `overall_rating` decimal(3,2) NOT NULL DEFAULT '0.00' COMMENT '综合评分(0-5分)', + `pass_status` enum('passed','conditional_pass','failed') NOT NULL COMMENT '检验结果', + `rejection_reason` text COMMENT '拒收原因', + `inspector_notes` text COMMENT '检验员备注', + `buyer_confirmation` tinyint(1) NOT NULL DEFAULT '0' COMMENT '买方确认', + `buyer_notes` text COMMENT '买方备注', + `confirmation_time` datetime DEFAULT NULL COMMENT '确认时间', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_order_id` (`order_id`), + KEY `idx_inspector_id` (`inspector_id`), + KEY `idx_inspection_type` (`inspection_type`), + KEY `idx_inspection_date` (`inspection_date`), + KEY `idx_pass_status` (`pass_status`), + KEY `idx_buyer_confirmation` (`buyer_confirmation`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='质检记录表'; + +-- ===================================================== +-- 10. 结算表 (settlements) +-- ===================================================== +DROP TABLE IF EXISTS `settlements`; +CREATE TABLE `settlements` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '结算记录ID', + `order_id` bigint(20) NOT NULL COMMENT '关联订单ID', + `settlement_no` varchar(50) NOT NULL COMMENT '结算单号', + `settlement_type` enum('advance','final','full') NOT NULL COMMENT '结算类型', + `cattle_count` int(11) NOT NULL COMMENT '结算牛只数量', + `unit_price` decimal(10,2) NOT NULL COMMENT '单价(元/公斤)', + `total_weight` decimal(10,2) NOT NULL COMMENT '总重量(公斤)', + `gross_amount` decimal(15,2) NOT NULL COMMENT '应付总金额', + `deduction_amount` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '扣款金额', + `deduction_reason` text COMMENT '扣款原因', + `net_amount` decimal(15,2) NOT NULL COMMENT '实付金额', + `payment_method` enum('cash','bank_transfer','check') NOT NULL COMMENT '支付方式', + `payment_status` enum('pending','paid','overdue') NOT NULL DEFAULT 'pending' COMMENT '支付状态', + `payment_date` date DEFAULT NULL COMMENT '支付日期', + `invoice_required` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否需要发票', + `invoice_type` enum('ordinary','special') DEFAULT NULL COMMENT '发票类型', + `invoice_status` enum('not_issued','issued','received') DEFAULT NULL COMMENT '发票状态', + `invoice_number` varchar(50) DEFAULT NULL COMMENT '发票号码', + `approver_id` bigint(20) DEFAULT NULL COMMENT '审批人ID', + `approval_status` enum('pending','approved','rejected') NOT NULL DEFAULT 'pending' COMMENT '审批状态', + `approval_time` datetime DEFAULT NULL COMMENT '审批时间', + `approval_notes` text COMMENT '审批备注', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `settlement_no` (`settlement_no`), + KEY `idx_order_id` (`order_id`), + KEY `idx_settlement_type` (`settlement_type`), + KEY `idx_payment_status` (`payment_status`), + KEY `idx_approval_status` (`approval_status`), + KEY `idx_approver_id` (`approver_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='结算表'; + +-- ===================================================== +-- 外键约束 +-- ===================================================== +-- 订单表外键 +ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_buyer` FOREIGN KEY (`buyerId`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_supplier` FOREIGN KEY (`supplierId`) REFERENCES `suppliers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE `orders` ADD CONSTRAINT `fk_orders_trader` FOREIGN KEY (`traderId`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- 支付表外键 +ALTER TABLE `payments` ADD CONSTRAINT `fk_payments_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `payments` ADD CONSTRAINT `fk_payments_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- 车辆表外键 +ALTER TABLE `vehicles` ADD CONSTRAINT `fk_vehicles_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- 司机表外键 +ALTER TABLE `drivers` ADD CONSTRAINT `fk_drivers_vehicle` FOREIGN KEY (`current_vehicle_id`) REFERENCES `vehicles` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- 运输表外键 +ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE `transports` ADD CONSTRAINT `fk_transports_vehicle` FOREIGN KEY (`vehicle_id`) REFERENCES `vehicles` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- 运输跟踪表外键 +ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_transport` FOREIGN KEY (`transport_id`) REFERENCES `transports` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `transport_tracks` ADD CONSTRAINT `fk_transport_tracks_driver` FOREIGN KEY (`driver_id`) REFERENCES `drivers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- 质检记录表外键 +ALTER TABLE `quality_records` ADD CONSTRAINT `fk_quality_records_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `quality_records` ADD CONSTRAINT `fk_quality_records_inspector` FOREIGN KEY (`inspector_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- 结算表外键 +ALTER TABLE `settlements` ADD CONSTRAINT `fk_settlements_order` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE `settlements` ADD CONSTRAINT `fk_settlements_approver` FOREIGN KEY (`approver_id`) REFERENCES `users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- 恢复外键检查 +SET FOREIGN_KEY_CHECKS = 1; + +-- ===================================================== +-- 完成数据库表结构创建 +-- ===================================================== \ No newline at end of file diff --git a/scripts/database/init_test_data.sql b/scripts/database/init_test_data.sql new file mode 100644 index 0000000..c9c1713 --- /dev/null +++ b/scripts/database/init_test_data.sql @@ -0,0 +1,128 @@ +-- ===================================================== +-- 牛牛商城测试数据初始化脚本 +-- 创建时间: 2024-01-21 +-- 描述: 插入测试数据,用于开发和测试环境 +-- ===================================================== + +-- 设置字符集 +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ===================================================== +-- 1. 用户测试数据 +-- ===================================================== +INSERT INTO `users` (`id`, `uuid`, `username`, `password_hash`, `nickname`, `real_name`, `phone`, `email`, `user_type`, `company_name`, `status`, `registration_source`) VALUES +(1, 'admin-uuid-001', 'admin', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '系统管理员', '张三', '13800138001', 'admin@niumall.com', 'admin', '牛牛商城', 'active', 'admin_create'), +(2, 'buyer-uuid-001', 'buyer001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商李四', '李四', '13800138002', 'buyer001@example.com', 'buyer', '大华肉业有限公司', 'active', 'web'), +(3, 'buyer-uuid-002', 'buyer002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '采购商王五', '王五', '13800138003', 'buyer002@example.com', 'buyer', '鑫源食品集团', 'active', 'web'), +(4, 'trader-uuid-001', 'trader001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '贸易商赵六', '赵六', '13800138004', 'trader001@example.com', 'trader', '中原贸易公司', 'active', 'web'), +(5, 'staff-uuid-001', 'staff001', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小明', '陈明', '13800138005', 'staff001@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create'), +(6, 'staff-uuid-002', 'staff002', '$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeGGvse/rXhd0.UpFrF6wcE.iy/i', '质检员小红', '李红', '13800138006', 'staff002@niumall.com', 'staff', '牛牛商城', 'active', 'admin_create'); + +-- ===================================================== +-- 2. 供应商测试数据 +-- ===================================================== +INSERT INTO `suppliers` (`id`, `name`, `code`, `contact`, `phone`, `email`, `address`, `region`, `qualification_level`, `cattle_types`, `capacity`, `rating`, `cooperation_start_date`, `status`) VALUES +(1, '内蒙古草原牧业有限公司', 'SUP001', '张牧民', '13900139001', 'contact@nmgcy.com', '内蒙古呼和浩特市赛罕区草原路123号', '内蒙古', 'A', '["西门塔尔牛", "安格斯牛", "夏洛莱牛"]', 500, 4.8, '2023-01-15', 'active'), +(2, '山东鲁西黄牛养殖场', 'SUP002', '李养牛', '13900139002', 'contact@sdlx.com', '山东济宁市嘉祥县畜牧路456号', '山东', 'A', '["鲁西黄牛", "利木赞牛"]', 300, 4.6, '2023-03-20', 'active'), +(3, '河南豫南肉牛合作社', 'SUP003', '王合作', '13900139003', 'contact@hnyn.com', '河南南阳市宛城区牧业大道789号', '河南', 'B', '["西门塔尔牛", "夏洛莱牛"]', 200, 4.2, '2023-05-10', 'active'), +(4, '新疆天山牧业集团', 'SUP004', '马天山', '13900139004', 'contact@xjts.com', '新疆乌鲁木齐市天山区牧场路321号', '新疆', 'A', '["安格斯牛", "海福特牛"]', 400, 4.7, '2023-02-28', 'active'), +(5, '黑龙江北大荒牧业', 'SUP005', '刘北方', '13900139005', 'contact@hlbdh.com', '黑龙江哈尔滨市道里区牧业街654号', '黑龙江', 'B', '["西门塔尔牛", "利木赞牛"]', 250, 4.3, '2023-04-15', 'active'); + +-- ===================================================== +-- 3. 司机测试数据 +-- ===================================================== +INSERT INTO `drivers` (`id`, `name`, `phone`, `id_card`, `driver_license`, `license_type`, `license_expiry_date`, `emergency_contact`, `emergency_phone`, `employment_type`, `hire_date`, `salary`, `performance_rating`, `status`) VALUES +(1, '张运输', '13700137001', '110101198001011234', 'A2001234567890', 'A2', '2025-12-31', '张妻子', '13700137101', 'full_time', '2023-01-10', 8000.00, 4.5, 'active'), +(2, '李司机', '13700137002', '110101198002022345', 'A2001234567891', 'A2', '2026-06-30', '李父亲', '13700137102', 'full_time', '2023-02-15', 7500.00, 4.3, 'active'), +(3, '王师傅', '13700137003', '110101198003033456', 'A2001234567892', 'A2', '2025-09-30', '王儿子', '13700137103', 'full_time', '2023-03-20', 7800.00, 4.6, 'active'), +(4, '赵老板', '13700137004', '110101198004044567', 'A2001234567893', 'A2', '2026-03-31', '赵妻子', '13700137104', 'contract', '2023-04-10', 9000.00, 4.8, 'active'), +(5, '陈快递', '13700137005', '110101198005055678', 'A2001234567894', 'A2', '2025-11-30', '陈母亲', '13700137105', 'part_time', '2023-05-05', 6500.00, 4.1, 'active'); + +-- ===================================================== +-- 4. 车辆测试数据 +-- ===================================================== +INSERT INTO `vehicles` (`id`, `license_plate`, `vehicle_type`, `capacity`, `driver_id`, `status`, `last_maintenance_date`, `next_maintenance_date`, `insurance_expiry_date`, `registration_expiry_date`) VALUES +(1, '京A12345', '大型货车', 15000, 1, 'available', '2024-01-15', '2024-04-15', '2024-12-31', '2025-01-10'), +(2, '京B23456', '中型货车', 10000, 2, 'available', '2024-01-20', '2024-04-20', '2024-11-30', '2025-02-15'), +(3, '京C34567', '大型货车', 18000, 3, 'in_use', '2024-01-10', '2024-04-10', '2024-10-31', '2025-03-20'), +(4, '京D45678', '特大型货车', 25000, 4, 'available', '2024-01-25', '2024-04-25', '2025-01-31', '2025-04-10'), +(5, '京E56789', '中型货车', 12000, 5, 'maintenance', '2024-01-05', '2024-04-05', '2024-09-30', '2025-05-05'); + +-- ===================================================== +-- 5. 订单测试数据 +-- ===================================================== +INSERT INTO `orders` (`id`, `orderNo`, `buyerId`, `buyerName`, `supplierId`, `supplierName`, `traderId`, `traderName`, `cattleBreed`, `cattleCount`, `expectedWeight`, `actualWeight`, `unitPrice`, `totalAmount`, `deliveryAddress`, `deliveryDate`, `paymentMethod`, `paymentStatus`, `orderStatus`) VALUES +(1, 'ORD202401001', 2, '大华肉业有限公司', 1, '内蒙古草原牧业有限公司', 4, '中原贸易公司', '西门塔尔牛', 50, 25000.00, 24800.00, 28.50, 706800.00, '北京市朝阳区肉联厂路100号', '2024-02-15', 'bank_transfer', 'partial_paid', 'shipped'), +(2, 'ORD202401002', 3, '鑫源食品集团', 2, '山东鲁西黄牛养殖场', NULL, NULL, '鲁西黄牛', 30, 18000.00, NULL, 26.80, 482400.00, '上海市浦东新区食品工业园区200号', '2024-02-20', 'bank_transfer', 'unpaid', 'confirmed'), +(3, 'ORD202401003', 2, '大华肉业有限公司', 3, '河南豫南肉牛合作社', NULL, NULL, '夏洛莱牛', 40, 22000.00, NULL, 29.20, 642400.00, '北京市朝阳区肉联厂路100号', '2024-02-25', 'online_payment', 'unpaid', 'pending'), +(4, 'ORD202401004', 3, '鑫源食品集团', 4, '新疆天山牧业集团', 4, '中原贸易公司', '安格斯牛', 60, 32000.00, NULL, 32.00, 1024000.00, '上海市浦东新区食品工业园区200号', '2024-03-01', 'bank_transfer', 'unpaid', 'pending'), +(5, 'ORD202401005', 2, '大华肉业有限公司', 5, '黑龙江北大荒牧业', NULL, NULL, '西门塔尔牛', 35, 19500.00, NULL, 27.50, 536250.00, '北京市朝阳区肉联厂路100号', '2024-03-05', 'bank_transfer', 'unpaid', 'pending'); + +-- ===================================================== +-- 6. 支付测试数据 +-- ===================================================== +INSERT INTO `payments` (`id`, `order_id`, `user_id`, `amount`, `paid_amount`, `payment_type`, `payment_method`, `payment_no`, `third_party_id`, `status`, `paid_time`) VALUES +(1, 1, 2, 353400.00, 353400.00, 'bank', 'web', 'PAY202401001001', 'BANK20240115001', 'paid', '2024-01-15 14:30:00'), +(2, 1, 2, 353400.00, NULL, 'bank', 'web', 'PAY202401001002', NULL, 'pending', NULL), +(3, 2, 3, 241200.00, NULL, 'bank', 'web', 'PAY202401002001', NULL, 'pending', NULL), +(4, 2, 3, 241200.00, NULL, 'bank', 'web', 'PAY202401002002', NULL, 'pending', NULL); + +-- ===================================================== +-- 7. 运输测试数据 +-- ===================================================== +INSERT INTO `transports` (`id`, `order_id`, `driver_id`, `vehicle_id`, `start_location`, `end_location`, `scheduled_start_time`, `actual_start_time`, `scheduled_end_time`, `actual_end_time`, `status`, `estimated_arrival_time`, `distance`, `fuel_cost`, `toll_cost`, `other_cost`, `total_cost`) VALUES +(1, 1, 3, 3, '内蒙古呼和浩特市赛罕区草原路123号', '北京市朝阳区肉联厂路100号', '2024-02-10 08:00:00', '2024-02-10 08:30:00', '2024-02-12 18:00:00', NULL, 'in_transit', '2024-02-12 16:00:00', 450.5, 1800.00, 200.00, 100.00, 2100.00), +(2, 2, 2, 2, '山东济宁市嘉祥县畜牧路456号', '上海市浦东新区食品工业园区200号', '2024-02-18 06:00:00', NULL, '2024-02-20 20:00:00', NULL, 'scheduled', '2024-02-20 18:00:00', 680.2, 2400.00, 350.00, 150.00, 2900.00); + +-- ===================================================== +-- 8. 运输跟踪测试数据 +-- ===================================================== +INSERT INTO `transport_tracks` (`id`, `transport_id`, `order_id`, `driver_id`, `latitude`, `longitude`, `speed`, `direction`, `cattle_status`, `temperature`, `humidity`, `created_at`) VALUES +(1, 1, 1, 3, 40.8518, 111.7519, 65.5, 135.2, '正常', 2.5, 45.0, '2024-02-10 10:00:00'), +(2, 1, 1, 3, 40.9234, 111.6789, 68.2, 140.8, '正常', 3.2, 42.0, '2024-02-10 12:00:00'), +(3, 1, 1, 3, 41.0567, 111.5432, 62.8, 138.5, '正常', 4.1, 38.0, '2024-02-10 14:00:00'), +(4, 1, 1, 3, 41.2345, 111.3456, 70.1, 142.3, '正常', 5.8, 35.0, '2024-02-10 16:00:00'), +(5, 1, 1, 3, 41.4567, 111.1234, 66.7, 139.7, '正常', 6.5, 32.0, '2024-02-10 18:00:00'); + +-- ===================================================== +-- 9. 质检记录测试数据 +-- ===================================================== +INSERT INTO `quality_records` (`id`, `order_id`, `inspector_id`, `inspection_type`, `inspection_date`, `location`, `cattle_count_expected`, `cattle_count_actual`, `weight_expected`, `weight_actual`, `health_status`, `breed_verification`, `age_range_verification`, `overall_rating`, `pass_status`, `inspector_notes`, `buyer_confirmation`) VALUES +(1, 1, 5, 'pre_loading', '2024-02-10 07:00:00', '内蒙古呼和浩特市赛罕区草原路123号', 50, 50, 25000.00, 24800.00, 'excellent', 1, 1, 4.8, 'passed', '牛只健康状况良好,品种纯正,符合订单要求', 0), +(2, 1, 6, 'arrival', '2024-02-12 15:30:00', '北京市朝阳区肉联厂路100号', 50, 50, 24800.00, 24800.00, 'good', 1, 1, 4.5, 'passed', '运输过程中牛只状态稳定,无明显损失', 1); + +-- ===================================================== +-- 10. 结算测试数据 +-- ===================================================== +INSERT INTO `settlements` (`id`, `order_id`, `settlement_no`, `settlement_type`, `cattle_count`, `unit_price`, `total_weight`, `gross_amount`, `deduction_amount`, `deduction_reason`, `net_amount`, `payment_method`, `payment_status`, `payment_date`, `invoice_required`, `invoice_type`, `invoice_status`, `approver_id`, `approval_status`, `approval_time`) VALUES +(1, 1, 'SET202401001', 'advance', 50, 28.50, 24800.00, 706800.00, 0.00, NULL, 353400.00, 'bank_transfer', 'paid', '2024-01-15', 1, 'special', 'issued', 1, 'approved', '2024-01-14 16:00:00'), +(2, 1, 'SET202401002', 'final', 50, 28.50, 24800.00, 706800.00, 0.00, NULL, 353400.00, 'bank_transfer', 'pending', NULL, 1, 'special', 'not_issued', 1, 'pending', NULL); + +-- ===================================================== +-- 更新司机当前车辆关联 +-- ===================================================== +UPDATE `drivers` SET `current_vehicle_id` = 1 WHERE `id` = 1; +UPDATE `drivers` SET `current_vehicle_id` = 2 WHERE `id` = 2; +UPDATE `drivers` SET `current_vehicle_id` = 3 WHERE `id` = 3; +UPDATE `drivers` SET `current_vehicle_id` = 4 WHERE `id` = 4; +UPDATE `drivers` SET `current_vehicle_id` = 5 WHERE `id` = 5; + +-- 恢复外键检查 +SET FOREIGN_KEY_CHECKS = 1; + +-- ===================================================== +-- 完成测试数据插入 +-- ===================================================== +SELECT '测试数据插入完成!' as message; +SELECT + (SELECT COUNT(*) FROM users) as users_count, + (SELECT COUNT(*) FROM suppliers) as suppliers_count, + (SELECT COUNT(*) FROM drivers) as drivers_count, + (SELECT COUNT(*) FROM vehicles) as vehicles_count, + (SELECT COUNT(*) FROM orders) as orders_count, + (SELECT COUNT(*) FROM payments) as payments_count, + (SELECT COUNT(*) FROM transports) as transports_count, + (SELECT COUNT(*) FROM transport_tracks) as transport_tracks_count, + (SELECT COUNT(*) FROM quality_records) as quality_records_count, + (SELECT COUNT(*) FROM settlements) as settlements_count; \ No newline at end of file diff --git a/scripts/database/init_with_node.js b/scripts/database/init_with_node.js new file mode 100644 index 0000000..24b39b5 --- /dev/null +++ b/scripts/database/init_with_node.js @@ -0,0 +1,292 @@ +#!/usr/bin/env node + +/** + * 基于Node.js的数据库初始化脚本 + * 使用mysql2库连接数据库并执行SQL脚本 + */ + +const mysql = require('../../backend/node_modules/mysql2/promise'); +const fs = require('fs').promises; +const path = require('path'); + +// 颜色输出 +const colors = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + reset: '\x1b[0m' +}; + +// 默认配置 +const defaultConfig = { + host: 'localhost', + port: 3306, + user: 'root', + password: '', + database: 'niumall' +}; + +// 显示帮助信息 +function showHelp() { + console.log('数据库初始化脚本 (Node.js版本)'); + console.log(''); + console.log('用法: node init_with_node.js [选项]'); + console.log(''); + console.log('选项:'); + console.log(' --host HOST 数据库主机地址 (默认: localhost)'); + console.log(' --port PORT 数据库端口 (默认: 3306)'); + console.log(' --user USER 数据库用户名 (默认: root)'); + console.log(' --password PASS 数据库密码'); + console.log(' --database DB 数据库名称 (默认: niumall)'); + console.log(' --skip-structure 跳过表结构创建'); + console.log(' --skip-data 跳过测试数据插入'); + console.log(' --help 显示此帮助信息'); + console.log(''); + console.log('示例:'); + console.log(' node init_with_node.js --user root --password secret'); + console.log(' node init_with_node.js --host 192.168.1.100 --database test'); +} + +// 解析命令行参数 +function parseArgs() { + const args = process.argv.slice(2); + const config = { ...defaultConfig }; + let skipStructure = false; + let skipData = false; + + for (let i = 0; i < args.length; i++) { + switch (args[i]) { + case '--host': + config.host = args[++i]; + break; + case '--port': + config.port = parseInt(args[++i]); + break; + case '--user': + config.user = args[++i]; + break; + case '--password': + config.password = args[++i]; + break; + case '--database': + config.database = args[++i]; + break; + case '--skip-structure': + skipStructure = true; + break; + case '--skip-data': + skipData = true; + break; + case '--help': + showHelp(); + process.exit(0); + break; + default: + console.error(`未知参数: ${args[i]}`); + showHelp(); + process.exit(1); + } + } + + return { config, skipStructure, skipData }; +} + +// 测试数据库连接 +async function testConnection(config) { + console.log(`${colors.yellow}正在测试数据库连接...${colors.reset}`); + + try { + const connection = await mysql.createConnection({ + host: config.host, + port: config.port, + user: config.user, + password: config.password + }); + + await connection.execute('SELECT 1'); + await connection.end(); + + console.log(`${colors.green}✅ 数据库连接成功!${colors.reset}`); + return true; + } catch (error) { + console.error(`${colors.red}❌ 数据库连接失败!${colors.reset}`); + console.error(`错误信息: ${error.message}`); + return false; + } +} + +// 创建数据库 +async function createDatabase(config) { + console.log(`${colors.yellow}正在创建数据库 '${config.database}'...${colors.reset}`); + + try { + const connection = await mysql.createConnection({ + host: config.host, + port: config.port, + user: config.user, + password: config.password + }); + + await connection.execute(`CREATE DATABASE IF NOT EXISTS \`${config.database}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`); + await connection.end(); + + console.log(`${colors.green}✅ 数据库创建成功!${colors.reset}`); + return true; + } catch (error) { + console.error(`${colors.red}❌ 数据库创建失败!${colors.reset}`); + console.error(`错误信息: ${error.message}`); + return false; + } +} + +// 执行SQL文件 +async function executeSqlFile(config, filePath, description) { + console.log(`${colors.yellow}正在执行 ${description}...${colors.reset}`); + + try { + // 读取SQL文件 + const sqlContent = await fs.readFile(filePath, 'utf8'); + + // 连接到指定数据库 + const connection = await mysql.createConnection({ + host: config.host, + port: config.port, + user: config.user, + password: config.password, + database: config.database, + multipleStatements: true + }); + + // 分割SQL语句并执行 + const statements = sqlContent + .split(';') + .map(stmt => stmt.trim()) + .filter(stmt => stmt.length > 0 && !stmt.startsWith('--')); + + let successCount = 0; + let errorCount = 0; + + for (const statement of statements) { + try { + await connection.execute(statement); + successCount++; + } catch (error) { + errorCount++; + console.warn(`${colors.yellow}⚠️ SQL执行警告: ${error.message}${colors.reset}`); + console.warn(`SQL语句: ${statement.substring(0, 100)}...`); + } + } + + await connection.end(); + + console.log(`${colors.green}✅ ${description}完成!${colors.reset}`); + console.log(`成功执行: ${successCount} 条语句,警告: ${errorCount} 条`); + + return true; + } catch (error) { + console.error(`${colors.red}❌ ${description}失败!${colors.reset}`); + console.error(`错误信息: ${error.message}`); + return false; + } +} + +// 验证表结构 +async function verifyTables(config) { + console.log(`${colors.yellow}正在验证表结构...${colors.reset}`); + + try { + const connection = await mysql.createConnection({ + host: config.host, + port: config.port, + user: config.user, + password: config.password, + database: config.database + }); + + const [tables] = await connection.execute('SHOW TABLES'); + await connection.end(); + + const tableNames = tables.map(row => Object.values(row)[0]); + + console.log(`${colors.green}✅ 数据库验证完成!${colors.reset}`); + console.log(`创建的表 (${tableNames.length}个):`); + tableNames.forEach(name => console.log(` - ${name}`)); + + return tableNames.length > 0; + } catch (error) { + console.error(`${colors.red}❌ 表结构验证失败!${colors.reset}`); + console.error(`错误信息: ${error.message}`); + return false; + } +} + +// 主函数 +async function main() { + console.log(`${colors.blue}数据库初始化工具 (Node.js版本)${colors.reset}`); + console.log('=================================='); + console.log(''); + + const { config, skipStructure, skipData } = parseArgs(); + + // 显示配置信息 + console.log(`${colors.blue}=== 数据库初始化配置 ===${colors.reset}`); + console.log(`主机: ${config.host}`); + console.log(`端口: ${config.port}`); + console.log(`用户: ${config.user}`); + console.log(`数据库: ${config.database}`); + console.log(`跳过表结构: ${skipStructure}`); + console.log(`跳过测试数据: ${skipData}`); + console.log(''); + + let success = true; + + // 1. 测试数据库连接 + if (!await testConnection(config)) { + process.exit(1); + } + + // 2. 创建数据库 + if (!await createDatabase(config)) { + process.exit(1); + } + + // 3. 执行表结构创建 + if (!skipStructure) { + const structureFile = path.join(__dirname, 'init_database.sql'); + if (!await executeSqlFile(config, structureFile, '表结构创建')) { + success = false; + } + } + + // 4. 插入测试数据 + if (!skipData && success) { + const dataFile = path.join(__dirname, 'init_test_data.sql'); + if (!await executeSqlFile(config, dataFile, '测试数据插入')) { + success = false; + } + } + + // 5. 验证结果 + if (success) { + await verifyTables(config); + console.log(''); + console.log(`${colors.green}🎉 数据库初始化完成!${colors.reset}`); + process.exit(0); + } else { + console.log(''); + console.log(`${colors.red}💥 数据库初始化失败!${colors.reset}`); + process.exit(1); + } +} + +// 错误处理 +process.on('unhandledRejection', (error) => { + console.error(`${colors.red}未处理的错误:${colors.reset}`, error); + process.exit(1); +}); + +// 执行主函数 +if (require.main === module) { + main(); +} \ No newline at end of file diff --git a/scripts/database/run_init.sh b/scripts/database/run_init.sh new file mode 100755 index 0000000..df917af --- /dev/null +++ b/scripts/database/run_init.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +# ===================================================== +# 牛牛商城数据库初始化执行脚本 +# 创建时间: 2024-01-21 +# 描述: 一键执行数据库初始化和测试数据插入 +# ===================================================== + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 脚本目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# 默认数据库配置 +DEFAULT_HOST="localhost" +DEFAULT_PORT="3306" +DEFAULT_USER="root" +DEFAULT_DATABASE="niumall" + +# 显示帮助信息 +show_help() { + echo -e "${BLUE}牛牛商城数据库初始化脚本${NC}" + echo "" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -h, --host HOST 数据库主机地址 (默认: $DEFAULT_HOST)" + echo " -P, --port PORT 数据库端口 (默认: $DEFAULT_PORT)" + echo " -u, --user USER 数据库用户名 (默认: $DEFAULT_USER)" + echo " -p, --password PASS 数据库密码" + echo " -d, --database DB 数据库名称 (默认: $DEFAULT_DATABASE)" + echo " --skip-structure 跳过表结构创建" + echo " --skip-data 跳过测试数据插入" + echo " --help 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 -h localhost -u root -p password123 -d niumall" + echo " $0 --host nj-cdb-3pwh2kz1.sql.tencentcdb.com --port 20784 --user jiebanke --password 'aiot741\$12346'" + echo "" +} + +# 初始化变量 +HOST="$DEFAULT_HOST" +PORT="$DEFAULT_PORT" +USER="$DEFAULT_USER" +PASSWORD="" +DATABASE="$DEFAULT_DATABASE" +SKIP_STRUCTURE=false +SKIP_DATA=false + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + -h|--host) + HOST="$2" + shift 2 + ;; + -P|--port) + PORT="$2" + shift 2 + ;; + -u|--user) + USER="$2" + shift 2 + ;; + -p|--password) + PASSWORD="$2" + shift 2 + ;; + -d|--database) + DATABASE="$2" + shift 2 + ;; + --skip-structure) + SKIP_STRUCTURE=true + shift + ;; + --skip-data) + SKIP_DATA=true + shift + ;; + --help) + show_help + exit 0 + ;; + *) + echo -e "${RED}错误: 未知参数 $1${NC}" + show_help + exit 1 + ;; + esac +done + +# 如果没有提供密码,提示输入 +if [ -z "$PASSWORD" ]; then + echo -e "${YELLOW}请输入数据库密码:${NC}" + read -s PASSWORD +fi + +# 构建MySQL连接参数 +MYSQL_CMD="mysql -h$HOST -P$PORT -u$USER -p$PASSWORD" + +# 显示配置信息 +echo -e "${BLUE}=== 数据库初始化配置 ===${NC}" +echo -e "主机: ${GREEN}$HOST${NC}" +echo -e "端口: ${GREEN}$PORT${NC}" +echo -e "用户: ${GREEN}$USER${NC}" +echo -e "数据库: ${GREEN}$DATABASE${NC}" +echo -e "跳过表结构: ${GREEN}$SKIP_STRUCTURE${NC}" +echo -e "跳过测试数据: ${GREEN}$SKIP_DATA${NC}" +echo "" + +# 测试数据库连接 +echo -e "${YELLOW}正在测试数据库连接...${NC}" +if ! $MYSQL_CMD -e "SELECT 1;" > /dev/null 2>&1; then + echo -e "${RED}❌ 数据库连接失败!请检查连接参数。${NC}" + exit 1 +fi +echo -e "${GREEN}✅ 数据库连接成功!${NC}" + +# 检查数据库是否存在,如果不存在则创建 +echo -e "${YELLOW}正在检查数据库 '$DATABASE' 是否存在...${NC}" +DB_EXISTS=$($MYSQL_CMD -e "SHOW DATABASES LIKE '$DATABASE';" | grep -c "$DATABASE") +if [ $DB_EXISTS -eq 0 ]; then + echo -e "${YELLOW}数据库 '$DATABASE' 不存在,正在创建...${NC}" + if $MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS \`$DATABASE\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"; then + echo -e "${GREEN}✅ 数据库 '$DATABASE' 创建成功!${NC}" + else + echo -e "${RED}❌ 数据库 '$DATABASE' 创建失败!${NC}" + exit 1 + fi +else + echo -e "${GREEN}✅ 数据库 '$DATABASE' 已存在。${NC}" +fi + +# 执行表结构创建 +if [ "$SKIP_STRUCTURE" = false ]; then + echo -e "${YELLOW}正在创建数据库表结构...${NC}" + if $MYSQL_CMD $DATABASE < "$SCRIPT_DIR/init_database.sql"; then + echo -e "${GREEN}✅ 数据库表结构创建成功!${NC}" + else + echo -e "${RED}❌ 数据库表结构创建失败!${NC}" + exit 1 + fi +else + echo -e "${YELLOW}⏭️ 跳过表结构创建。${NC}" +fi + +# 执行测试数据插入 +if [ "$SKIP_DATA" = false ]; then + echo -e "${YELLOW}正在插入测试数据...${NC}" + if $MYSQL_CMD $DATABASE < "$SCRIPT_DIR/init_test_data.sql"; then + echo -e "${GREEN}✅ 测试数据插入成功!${NC}" + else + echo -e "${RED}❌ 测试数据插入失败!${NC}" + exit 1 + fi +else + echo -e "${YELLOW}⏭️ 跳过测试数据插入。${NC}" +fi + +# 显示完成信息 +echo "" +echo -e "${GREEN}🎉 数据库初始化完成!${NC}" +echo "" +echo -e "${BLUE}=== 初始化摘要 ===${NC}" + +# 显示表统计信息 +echo -e "${YELLOW}正在统计表记录数...${NC}" +$MYSQL_CMD $DATABASE -e " +SELECT + '用户表' as table_name, COUNT(*) as record_count FROM users +UNION ALL +SELECT + '供应商表' as table_name, COUNT(*) as record_count FROM suppliers +UNION ALL +SELECT + '司机表' as table_name, COUNT(*) as record_count FROM drivers +UNION ALL +SELECT + '车辆表' as table_name, COUNT(*) as record_count FROM vehicles +UNION ALL +SELECT + '订单表' as table_name, COUNT(*) as record_count FROM orders +UNION ALL +SELECT + '支付表' as table_name, COUNT(*) as record_count FROM payments +UNION ALL +SELECT + '运输表' as table_name, COUNT(*) as record_count FROM transports +UNION ALL +SELECT + '运输跟踪表' as table_name, COUNT(*) as record_count FROM transport_tracks +UNION ALL +SELECT + '质检记录表' as table_name, COUNT(*) as record_count FROM quality_records +UNION ALL +SELECT + '结算表' as table_name, COUNT(*) as record_count FROM settlements; +" + +echo "" +echo -e "${GREEN}数据库初始化脚本执行完毕!${NC}" +echo -e "${BLUE}您现在可以启动应用程序进行测试。${NC}" \ No newline at end of file diff --git a/scripts/database/test_connection.sh b/scripts/database/test_connection.sh new file mode 100755 index 0000000..c33c58e --- /dev/null +++ b/scripts/database/test_connection.sh @@ -0,0 +1,175 @@ +#!/bin/bash + +# 数据库连接测试脚本 +# 用于验证数据库连接配置是否正确 + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# 默认配置 +HOST="localhost" +PORT="3306" +USER="root" +PASSWORD="" +DATABASE="niumall" + +# 显示帮助信息 +show_help() { + echo "数据库连接测试脚本" + echo "" + echo "用法: $0 [选项]" + echo "" + echo "选项:" + echo " -h, --host HOST 数据库主机地址 (默认: localhost)" + echo " -P, --port PORT 数据库端口 (默认: 3306)" + echo " -u, --user USER 数据库用户名 (默认: root)" + echo " -p, --password PASS 数据库密码" + echo " -d, --database DB 数据库名称 (默认: niumall)" + echo " --help 显示此帮助信息" + echo "" + echo "示例:" + echo " $0 -u root -p password" + echo " $0 --host 192.168.1.100 --port 3306 -u admin -p secret" +} + +# 解析命令行参数 +while [[ $# -gt 0 ]]; do + case $1 in + -h|--host) + HOST="$2" + shift 2 + ;; + -P|--port) + PORT="$2" + shift 2 + ;; + -u|--user) + USER="$2" + shift 2 + ;; + -p|--password) + PASSWORD="$2" + shift 2 + ;; + -d|--database) + DATABASE="$2" + shift 2 + ;; + --help) + show_help + exit 0 + ;; + *) + echo "未知参数: $1" + show_help + exit 1 + ;; + esac +done + +# 检查MySQL客户端是否安装 +check_mysql_client() { + if ! command -v mysql &> /dev/null; then + echo -e "${RED}❌ MySQL客户端未安装!${NC}" + echo "" + echo "请安装MySQL客户端:" + echo " macOS: brew install mysql-client" + echo " Ubuntu: sudo apt-get install mysql-client" + echo " CentOS: sudo yum install mysql" + return 1 + fi + return 0 +} + +# 测试数据库连接 +test_connection() { + echo -e "${BLUE}=== 数据库连接测试 ===${NC}" + echo "主机: $HOST" + echo "端口: $PORT" + echo "用户: $USER" + echo "数据库: $DATABASE" + echo "" + + # 构建MySQL连接命令 + MYSQL_CMD="mysql -h$HOST -P$PORT -u$USER" + if [[ -n "$PASSWORD" ]]; then + MYSQL_CMD="$MYSQL_CMD -p$PASSWORD" + fi + + echo -e "${YELLOW}正在测试连接...${NC}" + + # 测试基本连接 + if echo "SELECT 1;" | $MYSQL_CMD 2>/dev/null | grep -q "1"; then + echo -e "${GREEN}✅ 数据库连接成功!${NC}" + else + echo -e "${RED}❌ 数据库连接失败!${NC}" + echo "" + echo "可能的原因:" + echo "1. 数据库服务未启动" + echo "2. 主机地址或端口错误" + echo "3. 用户名或密码错误" + echo "4. 网络连接问题" + echo "5. 防火墙阻止连接" + return 1 + fi + + # 测试数据库是否存在 + echo -e "${YELLOW}检查数据库是否存在...${NC}" + if echo "USE $DATABASE;" | $MYSQL_CMD 2>/dev/null; then + echo -e "${GREEN}✅ 数据库 '$DATABASE' 存在${NC}" + else + echo -e "${YELLOW}⚠️ 数据库 '$DATABASE' 不存在,需要创建${NC}" + fi + + # 检查用户权限 + echo -e "${YELLOW}检查用户权限...${NC}" + GRANTS=$(echo "SHOW GRANTS;" | $MYSQL_CMD 2>/dev/null) + if [[ $? -eq 0 ]]; then + echo -e "${GREEN}✅ 用户权限查询成功${NC}" + echo "用户权限:" + echo "$GRANTS" | grep -E "(GRANT|CREATE|DROP|INSERT|SELECT|UPDATE|DELETE)" | head -3 + else + echo -e "${RED}❌ 无法查询用户权限${NC}" + fi + + # 检查数据库版本 + echo -e "${YELLOW}检查数据库版本...${NC}" + VERSION=$(echo "SELECT VERSION();" | $MYSQL_CMD 2>/dev/null | tail -n +2) + if [[ -n "$VERSION" ]]; then + echo -e "${GREEN}✅ MySQL版本: $VERSION${NC}" + else + echo -e "${RED}❌ 无法获取数据库版本${NC}" + fi + + return 0 +} + +# 主函数 +main() { + echo -e "${BLUE}数据库连接测试工具${NC}" + echo "==========================" + echo "" + + # 检查MySQL客户端 + if ! check_mysql_client; then + exit 1 + fi + + # 测试连接 + if test_connection; then + echo "" + echo -e "${GREEN}🎉 连接测试完成!数据库配置正常。${NC}" + exit 0 + else + echo "" + echo -e "${RED}💥 连接测试失败!请检查配置。${NC}" + exit 1 + fi +} + +# 执行主函数 +main \ No newline at end of file