初次提交
This commit is contained in:
17
config/config.js
Normal file
17
config/config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const mysql = require('mysql2')
|
||||
|
||||
const sql = {
|
||||
createConnection() {
|
||||
return mysql.createPool({
|
||||
connectionLimit: 10,
|
||||
host: '114.55.111.44',
|
||||
user: 'test_mao',
|
||||
password: 'nK2mPbWriBp25BRd',
|
||||
database: 'test_mao',
|
||||
charset: 'utf8mb4',
|
||||
multipleStatements: true
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
module.exports = sql
|
||||
70
config/constants.js
Normal file
70
config/constants.js
Normal file
@@ -0,0 +1,70 @@
|
||||
// 系统常量配置
|
||||
module.exports = {
|
||||
// 转账类型
|
||||
TRANSFER_TYPES: {
|
||||
USER_TO_USER: 'user_to_user',
|
||||
SYSTEM_TO_USER: 'system_to_user',
|
||||
USER_TO_SYSTEM: 'user_to_system'
|
||||
},
|
||||
|
||||
// 转账状态
|
||||
TRANSFER_STATUS: {
|
||||
PENDING: 'pending',
|
||||
CONFIRMED: 'confirmed',
|
||||
RECEIVED: 'received',
|
||||
REJECTED: 'rejected',
|
||||
CANCELLED: 'cancelled',
|
||||
NOT_RECEIVED: 'not_received',
|
||||
FAILED: 'failed'
|
||||
},
|
||||
|
||||
// 用户角色
|
||||
USER_ROLES: {
|
||||
ADMIN: 'admin',
|
||||
USER: 'user'
|
||||
},
|
||||
|
||||
// 订单状态
|
||||
ORDER_STATUS: {
|
||||
PENDING: 'pending',
|
||||
PAID: 'paid',
|
||||
SHIPPED: 'shipped',
|
||||
DELIVERED: 'delivered',
|
||||
CANCELLED: 'cancelled'
|
||||
},
|
||||
|
||||
// 错误代码
|
||||
ERROR_CODES: {
|
||||
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
||||
AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR',
|
||||
AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR',
|
||||
NOT_FOUND: 'NOT_FOUND',
|
||||
DUPLICATE_ENTRY: 'DUPLICATE_ENTRY',
|
||||
DATABASE_ERROR: 'DATABASE_ERROR',
|
||||
INTERNAL_ERROR: 'INTERNAL_ERROR'
|
||||
},
|
||||
|
||||
// HTTP状态码
|
||||
HTTP_STATUS: {
|
||||
OK: 200,
|
||||
CREATED: 201,
|
||||
BAD_REQUEST: 400,
|
||||
UNAUTHORIZED: 401,
|
||||
FORBIDDEN: 403,
|
||||
NOT_FOUND: 404,
|
||||
CONFLICT: 409,
|
||||
INTERNAL_SERVER_ERROR: 500
|
||||
},
|
||||
|
||||
// 分页默认值
|
||||
PAGINATION: {
|
||||
DEFAULT_PAGE: 1,
|
||||
DEFAULT_LIMIT: 10,
|
||||
MAX_LIMIT: 100
|
||||
},
|
||||
|
||||
// JWT配置
|
||||
JWT: {
|
||||
EXPIRES_IN: '24h'
|
||||
}
|
||||
};
|
||||
790
config/database-init.js
Normal file
790
config/database-init.js
Normal file
@@ -0,0 +1,790 @@
|
||||
const mysql = require('mysql2/promise');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { initDB, getDB, dbConfig } = require('../database');
|
||||
|
||||
/**
|
||||
* 数据库初始化函数
|
||||
* 创建所有必要的表结构和初始数据
|
||||
*/
|
||||
async function initDatabase() {
|
||||
try {
|
||||
// 首先创建数据库(如果不存在)
|
||||
const tempConnection = await mysql.createConnection({
|
||||
host: dbConfig.host,
|
||||
user: dbConfig.user,
|
||||
password: dbConfig.password
|
||||
});
|
||||
|
||||
// 创建数据库
|
||||
await tempConnection.execute(`CREATE DATABASE IF NOT EXISTS ${dbConfig.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
||||
await tempConnection.end();
|
||||
|
||||
// 初始化数据库连接池
|
||||
await initDB();
|
||||
console.log('数据库连接池初始化成功');
|
||||
|
||||
// 创建所有表
|
||||
await createTables();
|
||||
|
||||
// 添加字段(处理表结构升级)
|
||||
await addMissingFields();
|
||||
|
||||
// 创建默认数据
|
||||
await createDefaultData();
|
||||
|
||||
console.log('数据库初始化完成');
|
||||
} catch (error) {
|
||||
console.error('数据库初始化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建所有数据库表
|
||||
*/
|
||||
async function createTables() {
|
||||
// 用户表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
phone VARCHAR(20) UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('user', 'admin') DEFAULT 'user',
|
||||
avatar VARCHAR(255),
|
||||
points INT DEFAULT 0,
|
||||
balance DECIMAL(10,2) DEFAULT 0.00,
|
||||
real_name VARCHAR(100),
|
||||
id_card VARCHAR(18),
|
||||
wechat_qr VARCHAR(255),
|
||||
alipay_qr VARCHAR(255),
|
||||
bank_card VARCHAR(30),
|
||||
unionpay_qr VARCHAR(255),
|
||||
business_license VARCHAR(500),
|
||||
id_card_front VARCHAR(500),
|
||||
id_card_back VARCHAR(500),
|
||||
audit_status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
|
||||
audit_note TEXT,
|
||||
audited_by INT,
|
||||
audited_at TIMESTAMP NULL,
|
||||
city VARCHAR(50),
|
||||
district_id INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (audited_by) REFERENCES users(id),
|
||||
FOREIGN KEY (district_id) REFERENCES zhejiang_regions(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 商品表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
price INT NOT NULL,
|
||||
points_price INT NOT NULL,
|
||||
original_price INT,
|
||||
stock INT DEFAULT 0,
|
||||
sales INT DEFAULT 0,
|
||||
rating DECIMAL(3,2) DEFAULT 5.00,
|
||||
category VARCHAR(100),
|
||||
image_url VARCHAR(500),
|
||||
images JSON,
|
||||
details TEXT,
|
||||
status ENUM('active', 'inactive') DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// 订单表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
order_no VARCHAR(50) UNIQUE NOT NULL,
|
||||
total_amount INT NOT NULL,
|
||||
total_points INT NOT NULL,
|
||||
status ENUM('pending', 'paid', 'shipped', 'delivered', 'cancelled') DEFAULT 'pending',
|
||||
address JSON,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建转账记录表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS transfers (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
from_user_id INT NULL,
|
||||
to_user_id INT NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user',
|
||||
status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received') DEFAULT 'pending',
|
||||
voucher_url VARCHAR(500),
|
||||
description TEXT,
|
||||
batch_id VARCHAR(50),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建转账确认表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS transfer_confirmations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
transfer_id INT NOT NULL,
|
||||
confirmer_id INT NOT NULL,
|
||||
action ENUM('confirm', 'reject') NOT NULL,
|
||||
note TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (confirmer_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// 订单商品表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS order_items (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
order_id INT NOT NULL,
|
||||
product_id INT NOT NULL,
|
||||
quantity INT NOT NULL,
|
||||
price INT NOT NULL,
|
||||
points INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id),
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 积分记录表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS points_history (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
type ENUM('earn', 'spend') NOT NULL,
|
||||
amount INT NOT NULL,
|
||||
description VARCHAR(255),
|
||||
order_id INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 管理员操作日志表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS admin_operation_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
admin_id INT NOT NULL,
|
||||
operation_type VARCHAR(50) NOT NULL,
|
||||
target_type VARCHAR(50) NOT NULL,
|
||||
target_id INT NOT NULL,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (admin_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// 商品评价表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS product_reviews (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
product_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
order_id INT NOT NULL,
|
||||
rating INT NOT NULL,
|
||||
comment TEXT,
|
||||
images JSON,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
||||
FOREIGN KEY (order_id) REFERENCES orders(id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 匹配订单表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS matching_orders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
initiator_id INT NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
status ENUM('pending', 'matching', 'completed', 'cancelled') DEFAULT 'pending',
|
||||
cycle_count INT DEFAULT 0,
|
||||
max_cycles INT DEFAULT 3,
|
||||
matching_type ENUM('small', 'large', 'system_reverse') DEFAULT 'small',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (initiator_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// 匹配订单分配表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS order_allocations (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
matching_order_id INT NOT NULL,
|
||||
from_user_id INT NOT NULL,
|
||||
to_user_id INT NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
cycle_number INT NOT NULL,
|
||||
status ENUM('pending', 'confirmed', 'rejected', 'completed') DEFAULT 'pending',
|
||||
transfer_id INT,
|
||||
outbound_date DATE,
|
||||
return_date DATE,
|
||||
can_return_after TIMESTAMP,
|
||||
confirmed_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (from_user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (to_user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (transfer_id) REFERENCES transfers(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// 用户匹配池表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS user_matching_pool (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
available_amount DECIMAL(10,2) DEFAULT 0.00,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
last_matched_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_user (user_id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 匹配记录表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS matching_records (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
matching_order_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
action ENUM('join', 'confirm', 'reject', 'complete') NOT NULL,
|
||||
amount DECIMAL(10,2),
|
||||
note TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (matching_order_id) REFERENCES matching_orders(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建系统设置表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS system_settings (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
setting_key VARCHAR(100) NOT NULL UNIQUE,
|
||||
setting_value TEXT,
|
||||
description VARCHAR(255),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建激活码表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS registration_codes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
code VARCHAR(10) UNIQUE NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
is_used BOOLEAN DEFAULT FALSE,
|
||||
used_at TIMESTAMP NULL,
|
||||
created_by_admin_id INT,
|
||||
used_by_user_id INT,
|
||||
agent_id INT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by_admin_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (used_by_user_id) REFERENCES users(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建浙江省区域表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS zhejiang_regions (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
city_name VARCHAR(50) NOT NULL,
|
||||
district_name VARCHAR(50) NOT NULL,
|
||||
region_code VARCHAR(20) UNIQUE NOT NULL,
|
||||
is_available BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY unique_region (city_name, district_name)
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建区域代理表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS regional_agents (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
region_id INT NOT NULL,
|
||||
agent_code VARCHAR(20) UNIQUE NOT NULL,
|
||||
status ENUM('pending', 'active', 'suspended', 'terminated') DEFAULT 'pending',
|
||||
commission_rate DECIMAL(5,4) DEFAULT 0.1000,
|
||||
total_earnings DECIMAL(10,2) DEFAULT 0.00,
|
||||
recruited_merchants INT DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (region_id) REFERENCES zhejiang_regions(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY unique_agent_region (user_id, region_id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建代理商户关系表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS agent_merchants (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
agent_id INT NOT NULL,
|
||||
merchant_id INT NOT NULL,
|
||||
registration_code_id INT,
|
||||
matching_count INT DEFAULT 0,
|
||||
commission_earned DECIMAL(10,2) DEFAULT 0.00,
|
||||
is_qualified BOOLEAN DEFAULT FALSE,
|
||||
qualified_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (registration_code_id) REFERENCES registration_codes(id) ON DELETE SET NULL,
|
||||
UNIQUE KEY unique_agent_merchant (agent_id, merchant_id)
|
||||
)
|
||||
`);
|
||||
|
||||
// 创建代理佣金记录表
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS agent_commission_records (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
agent_id INT NOT NULL,
|
||||
merchant_id INT NOT NULL,
|
||||
order_id INT,
|
||||
commission_amount DECIMAL(10,2) NOT NULL,
|
||||
commission_type ENUM('registration', 'matching') DEFAULT 'matching',
|
||||
description TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (merchant_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (order_id) REFERENCES matching_orders(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加缺失的字段(处理数据库升级)
|
||||
*/
|
||||
async function addMissingFields() {
|
||||
// 为现有的matching_orders表添加字段
|
||||
const matchingOrderFields = [
|
||||
{ name: 'matching_type', sql: 'ALTER TABLE matching_orders ADD COLUMN matching_type ENUM(\'small\', \'large\') DEFAULT \'small\'' },
|
||||
{ name: 'is_system_reverse', sql: 'ALTER TABLE matching_orders ADD COLUMN is_system_reverse BOOLEAN DEFAULT FALSE' }
|
||||
];
|
||||
|
||||
for (const field of matchingOrderFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有的users表添加字段
|
||||
const userFields = [
|
||||
{ name: 'balance', sql: 'ALTER TABLE users ADD COLUMN balance DECIMAL(10,2) DEFAULT 0.00' },
|
||||
{ name: 'is_system_account', sql: 'ALTER TABLE users ADD COLUMN is_system_account BOOLEAN DEFAULT FALSE' },
|
||||
{ name: 'points', sql: 'ALTER TABLE users ADD COLUMN points INT DEFAULT 0' },
|
||||
{ name: 'avatar', sql: 'ALTER TABLE users ADD COLUMN avatar VARCHAR(255)' },
|
||||
{ name: 'real_name', sql: 'ALTER TABLE users ADD COLUMN real_name VARCHAR(100)' },
|
||||
{ name: 'id_card', sql: 'ALTER TABLE users ADD COLUMN id_card VARCHAR(18)' },
|
||||
{ name: 'wechat_qr', sql: 'ALTER TABLE users ADD COLUMN wechat_qr VARCHAR(255)' },
|
||||
{ name: 'alipay_qr', sql: 'ALTER TABLE users ADD COLUMN alipay_qr VARCHAR(255)' },
|
||||
{ name: 'bank_card', sql: 'ALTER TABLE users ADD COLUMN bank_card VARCHAR(30)' },
|
||||
{ name: 'unionpay_qr', sql: 'ALTER TABLE users ADD COLUMN unionpay_qr VARCHAR(255)' },
|
||||
{ name: 'phone', sql: 'ALTER TABLE users ADD COLUMN phone VARCHAR(20) UNIQUE' },
|
||||
{ name: 'completed_withdrawals', sql: 'ALTER TABLE users ADD COLUMN completed_withdrawals INT DEFAULT 0' },
|
||||
{ name: 'business_license', sql: 'ALTER TABLE users ADD COLUMN business_license VARCHAR(500)' },
|
||||
{ name: 'id_card_front', sql: 'ALTER TABLE users ADD COLUMN id_card_front VARCHAR(500)' },
|
||||
{ name: 'id_card_back', sql: 'ALTER TABLE users ADD COLUMN id_card_back VARCHAR(500)' },
|
||||
{ name: 'audit_status', sql: 'ALTER TABLE users ADD COLUMN audit_status ENUM(\'pending\', \'approved\', \'rejected\') DEFAULT \'pending\'' },
|
||||
{ name: 'audit_note', sql: 'ALTER TABLE users ADD COLUMN audit_note TEXT' },
|
||||
{ name: 'audited_by', sql: 'ALTER TABLE users ADD COLUMN audited_by INT' },
|
||||
{ name: 'audited_at', sql: 'ALTER TABLE users ADD COLUMN audited_at TIMESTAMP NULL' },
|
||||
{ name: 'city', sql: 'ALTER TABLE users ADD COLUMN city VARCHAR(50)' },
|
||||
{ name: 'district_id', sql: 'ALTER TABLE users ADD COLUMN district_id INT' }
|
||||
];
|
||||
|
||||
for (const field of userFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加用户表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有的products表添加字段
|
||||
const productFields = [
|
||||
{ name: 'points_price', sql: 'ALTER TABLE products ADD COLUMN points_price INT NOT NULL DEFAULT 0' },
|
||||
{ name: 'image_url', sql: 'ALTER TABLE products ADD COLUMN image_url VARCHAR(500)' },
|
||||
{ name: 'details', sql: 'ALTER TABLE products ADD COLUMN details TEXT' }
|
||||
];
|
||||
|
||||
for (const field of productFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加商品表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有的transfers表添加字段
|
||||
const transferFields = [
|
||||
{ name: 'is_overdue', sql: 'ALTER TABLE transfers ADD COLUMN is_overdue BOOLEAN DEFAULT FALSE' },
|
||||
{ name: 'is_bad_debt', sql: 'ALTER TABLE transfers ADD COLUMN is_bad_debt BOOLEAN DEFAULT FALSE' },
|
||||
{ name: 'confirmed_at', sql: 'ALTER TABLE transfers ADD COLUMN confirmed_at TIMESTAMP NULL' },
|
||||
{ name: 'deadline_at', sql: 'ALTER TABLE transfers ADD COLUMN deadline_at TIMESTAMP NULL' },
|
||||
{ name: 'admin_note', sql: 'ALTER TABLE transfers ADD COLUMN admin_note TEXT' },
|
||||
{ name: 'admin_modified_at', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_at TIMESTAMP NULL' },
|
||||
{ name: 'admin_modified_by', sql: 'ALTER TABLE transfers ADD COLUMN admin_modified_by INT' }
|
||||
];
|
||||
|
||||
for (const field of transferFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加转账表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修改transfers表的字段类型
|
||||
try {
|
||||
await getDB().execute(`
|
||||
ALTER TABLE transfers
|
||||
MODIFY COLUMN status ENUM('pending', 'confirmed', 'rejected', 'received', 'not_received', 'cancelled') DEFAULT 'pending'
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log('修改transfers状态字段错误:', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await getDB().execute(`
|
||||
ALTER TABLE transfers
|
||||
MODIFY COLUMN from_user_id INT NULL
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log('修改transfers from_user_id字段错误:', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
await getDB().execute(`
|
||||
ALTER TABLE transfers
|
||||
MODIFY COLUMN transfer_type ENUM('initial', 'return', 'user_to_user', 'system_to_user', 'user_to_system') DEFAULT 'user_to_user'
|
||||
`);
|
||||
} catch (error) {
|
||||
console.log('修改transfers transfer_type字段错误:', error.message);
|
||||
}
|
||||
|
||||
// 为现有的order_allocations表添加字段
|
||||
const allocationFields = [
|
||||
{ name: 'confirmed_at', sql: 'ALTER TABLE order_allocations ADD COLUMN confirmed_at TIMESTAMP NULL' },
|
||||
{ name: 'outbound_date', sql: 'ALTER TABLE order_allocations ADD COLUMN outbound_date DATE' },
|
||||
{ name: 'return_date', sql: 'ALTER TABLE order_allocations ADD COLUMN return_date DATE' },
|
||||
{ name: 'can_return_after', sql: 'ALTER TABLE order_allocations ADD COLUMN can_return_after TIMESTAMP' }
|
||||
];
|
||||
|
||||
for (const field of allocationFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加分配表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有的regional_agents表添加字段
|
||||
const agentFields = [
|
||||
{ name: 'approved_at', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_at TIMESTAMP NULL' },
|
||||
{ name: 'approved_by_admin_id', sql: 'ALTER TABLE regional_agents ADD COLUMN approved_by_admin_id INT' },
|
||||
// 提现相关字段
|
||||
{ name: 'withdrawn_amount', sql: 'ALTER TABLE regional_agents ADD COLUMN withdrawn_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT "已提现金额"' },
|
||||
{ name: 'pending_withdrawal', sql: 'ALTER TABLE regional_agents ADD COLUMN pending_withdrawal DECIMAL(10,2) DEFAULT 0.00 COMMENT "待审核提现金额"' },
|
||||
{ name: 'payment_type', sql: 'ALTER TABLE regional_agents ADD COLUMN payment_type ENUM("bank", "wechat", "alipay", "unionpay") DEFAULT "bank" COMMENT "收款方式类型"' },
|
||||
{ name: 'bank_name', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_name VARCHAR(100) COMMENT "银行名称"' },
|
||||
{ name: 'account_number', sql: 'ALTER TABLE regional_agents ADD COLUMN account_number VARCHAR(50) COMMENT "账号/银行账号"' },
|
||||
{ name: 'account_holder', sql: 'ALTER TABLE regional_agents ADD COLUMN account_holder VARCHAR(100) COMMENT "持有人姓名"' },
|
||||
{ name: 'qr_code_url', sql: 'ALTER TABLE regional_agents ADD COLUMN qr_code_url VARCHAR(255) COMMENT "收款码图片URL"' },
|
||||
{ name: 'bank_account', sql: 'ALTER TABLE regional_agents ADD COLUMN bank_account VARCHAR(50) COMMENT "银行账号(兼容旧版本)"' }
|
||||
];
|
||||
|
||||
for (const field of agentFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加代理表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为现有的registration_codes表添加字段
|
||||
const registrationCodeFields = [
|
||||
{ name: 'agent_id', sql: 'ALTER TABLE registration_codes ADD COLUMN agent_id INT NULL' }
|
||||
];
|
||||
|
||||
for (const field of registrationCodeFields) {
|
||||
try {
|
||||
await getDB().execute(field.sql);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate column name')) {
|
||||
console.log(`添加激活码表${field.name}字段错误:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为registration_codes表的agent_id字段添加外键约束
|
||||
try {
|
||||
await getDB().execute(`
|
||||
ALTER TABLE registration_codes
|
||||
ADD CONSTRAINT fk_registration_codes_agent_id
|
||||
FOREIGN KEY (agent_id) REFERENCES users(id) ON DELETE SET NULL
|
||||
`);
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Duplicate foreign key constraint name')) {
|
||||
console.log('添加激活码表agent_id外键约束错误:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 注意:MySQL不支持带WHERE条件的唯一索引
|
||||
// 区域激活代理的唯一性通过应用层验证来确保
|
||||
// 每个区域只能有一个激活状态的代理,这在代理申请、审核和启用时都会进行验证
|
||||
|
||||
// 创建代理提现记录表
|
||||
try {
|
||||
await getDB().execute(`
|
||||
CREATE TABLE IF NOT EXISTS agent_withdrawals (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
agent_id INT NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型',
|
||||
bank_name VARCHAR(100) COMMENT '银行名称',
|
||||
account_number VARCHAR(50) COMMENT '账号/银行账号',
|
||||
account_holder VARCHAR(100) COMMENT '持有人姓名',
|
||||
qr_code_url VARCHAR(255) COMMENT '收款码图片URL',
|
||||
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending',
|
||||
apply_note TEXT,
|
||||
admin_note TEXT,
|
||||
processed_by INT NULL,
|
||||
processed_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)',
|
||||
FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL
|
||||
)
|
||||
`);
|
||||
console.log('代理提现记录表创建成功');
|
||||
} catch (error) {
|
||||
if (!error.message.includes('Table') || !error.message.includes('already exists')) {
|
||||
console.log('创建代理提现记录表错误:', error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认数据
|
||||
*/
|
||||
async function createDefaultData() {
|
||||
// 创建默认管理员账号
|
||||
const defaultPassword = await bcrypt.hash('admin123', 10);
|
||||
|
||||
try {
|
||||
await getDB().execute(`
|
||||
INSERT IGNORE INTO users (username, phone, password, role)
|
||||
VALUES ('admin', '13800000000', ?, 'admin')
|
||||
`, [defaultPassword]);
|
||||
console.log('默认管理员账号已创建: 用户名: admin, 密码: admin123');
|
||||
} catch (error) {
|
||||
console.log('默认管理员账号已存在或创建失败:', error.message);
|
||||
}
|
||||
|
||||
// 创建多个系统公户账号
|
||||
const publicPassword = await bcrypt.hash('public123', 10);
|
||||
const systemAccounts = [
|
||||
{ username: 'merchant_001', phone: '13800000001', real_name: '优选商户' },
|
||||
{ username: 'merchant_002', phone: '13800000002', real_name: '品质商家' },
|
||||
{ username: 'merchant_003', phone: '13800000003', real_name: '信誉商户' },
|
||||
{ username: 'merchant_004', phone: '13800000004', real_name: '金牌商家' },
|
||||
{ username: 'merchant_005', phone: '13800000005', real_name: '钻石商户' }
|
||||
];
|
||||
|
||||
for (const account of systemAccounts) {
|
||||
try {
|
||||
const [result] = await getDB().execute(`
|
||||
INSERT IGNORE INTO users (username, phone, password, role, real_name, is_system_account)
|
||||
VALUES (?, ?, ?, 'user', ?, TRUE)
|
||||
`, [account.username, account.phone, publicPassword, account.real_name]);
|
||||
|
||||
if (result.affectedRows > 0) {
|
||||
// 为新创建的系统账户设置初始余额
|
||||
await getDB().execute(`
|
||||
UPDATE users SET balance = 0.00 WHERE id = ?
|
||||
`, [result.insertId]);
|
||||
console.log(`系统账户已创建: ${account.real_name} (${account.username})`);
|
||||
} else {
|
||||
// 确保现有系统账户的is_system_account字段正确设置
|
||||
await getDB().execute(`
|
||||
UPDATE users SET is_system_account = TRUE WHERE username = ?
|
||||
`, [account.username]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`系统账户${account.username}已存在或创建失败:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化浙江省区域数据
|
||||
await initializeZhejiangRegions();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化浙江省区域数据
|
||||
*/
|
||||
async function initializeZhejiangRegions() {
|
||||
const zhejiangRegions = [
|
||||
// 杭州市
|
||||
{ city: '杭州市', district: '上城区', code: 'HZ_SC' },
|
||||
{ city: '杭州市', district: '拱墅区', code: 'HZ_GS' },
|
||||
{ city: '杭州市', district: '西湖区', code: 'HZ_XH' },
|
||||
{ city: '杭州市', district: '滨江区', code: 'HZ_BJ' },
|
||||
{ city: '杭州市', district: '萧山区', code: 'HZ_XS' },
|
||||
{ city: '杭州市', district: '余杭区', code: 'HZ_YH' },
|
||||
{ city: '杭州市', district: '临平区', code: 'HZ_LP' },
|
||||
{ city: '杭州市', district: '钱塘区', code: 'HZ_QT' },
|
||||
{ city: '杭州市', district: '富阳区', code: 'HZ_FY' },
|
||||
{ city: '杭州市', district: '临安区', code: 'HZ_LA' },
|
||||
{ city: '杭州市', district: '桐庐县', code: 'HZ_TL' },
|
||||
{ city: '杭州市', district: '淳安县', code: 'HZ_CA' },
|
||||
{ city: '杭州市', district: '建德市', code: 'HZ_JD' },
|
||||
// 宁波市
|
||||
{ city: '宁波市', district: '海曙区', code: 'NB_HS' },
|
||||
{ city: '宁波市', district: '江北区', code: 'NB_JB' },
|
||||
{ city: '宁波市', district: '北仑区', code: 'NB_BL' },
|
||||
{ city: '宁波市', district: '镇海区', code: 'NB_ZH' },
|
||||
{ city: '宁波市', district: '鄞州区', code: 'NB_YZ' },
|
||||
{ city: '宁波市', district: '奉化区', code: 'NB_FH' },
|
||||
{ city: '宁波市', district: '象山县', code: 'NB_XS' },
|
||||
{ city: '宁波市', district: '宁海县', code: 'NB_NH' },
|
||||
{ city: '宁波市', district: '余姚市', code: 'NB_YY' },
|
||||
{ city: '宁波市', district: '慈溪市', code: 'NB_CX' },
|
||||
// 温州市
|
||||
{ city: '温州市', district: '鹿城区', code: 'WZ_LC' },
|
||||
{ city: '温州市', district: '龙湾区', code: 'WZ_LW' },
|
||||
{ city: '温州市', district: '瓯海区', code: 'WZ_OH' },
|
||||
{ city: '温州市', district: '洞头区', code: 'WZ_DT' },
|
||||
{ city: '温州市', district: '永嘉县', code: 'WZ_YJ' },
|
||||
{ city: '温州市', district: '平阳县', code: 'WZ_PY' },
|
||||
{ city: '温州市', district: '苍南县', code: 'WZ_CN' },
|
||||
{ city: '温州市', district: '文成县', code: 'WZ_WC' },
|
||||
{ city: '温州市', district: '泰顺县', code: 'WZ_TS' },
|
||||
{ city: '温州市', district: '瑞安市', code: 'WZ_RA' },
|
||||
{ city: '温州市', district: '乐清市', code: 'WZ_LQ' },
|
||||
// 嘉兴市
|
||||
{ city: '嘉兴市', district: '南湖区', code: 'JX_NH' },
|
||||
{ city: '嘉兴市', district: '秀洲区', code: 'JX_XZ' },
|
||||
{ city: '嘉兴市', district: '嘉善县', code: 'JX_JS' },
|
||||
{ city: '嘉兴市', district: '海盐县', code: 'JX_HY' },
|
||||
{ city: '嘉兴市', district: '海宁市', code: 'JX_HN' },
|
||||
{ city: '嘉兴市', district: '平湖市', code: 'JX_PH' },
|
||||
{ city: '嘉兴市', district: '桐乡市', code: 'JX_TX' },
|
||||
// 湖州市
|
||||
{ city: '湖州市', district: '吴兴区', code: 'HuZ_WX' },
|
||||
{ city: '湖州市', district: '南浔区', code: 'HuZ_NX' },
|
||||
{ city: '湖州市', district: '德清县', code: 'HuZ_DQ' },
|
||||
{ city: '湖州市', district: '长兴县', code: 'HuZ_CX' },
|
||||
{ city: '湖州市', district: '安吉县', code: 'HuZ_AJ' },
|
||||
// 绍兴市
|
||||
{ city: '绍兴市', district: '越城区', code: 'SX_YC' },
|
||||
{ city: '绍兴市', district: '柯桥区', code: 'SX_KQ' },
|
||||
{ city: '绍兴市', district: '上虞区', code: 'SX_SY' },
|
||||
{ city: '绍兴市', district: '新昌县', code: 'SX_XC' },
|
||||
{ city: '绍兴市', district: '诸暨市', code: 'SX_ZJ' },
|
||||
{ city: '绍兴市', district: '嵊州市', code: 'SX_SZ' },
|
||||
// 金华市
|
||||
{ city: '金华市', district: '婺城区', code: 'JH_WC' },
|
||||
{ city: '金华市', district: '金东区', code: 'JH_JD' },
|
||||
{ city: '金华市', district: '武义县', code: 'JH_WY' },
|
||||
{ city: '金华市', district: '浦江县', code: 'JH_PJ' },
|
||||
{ city: '金华市', district: '磐安县', code: 'JH_PA' },
|
||||
{ city: '金华市', district: '兰溪市', code: 'JH_LX' },
|
||||
{ city: '金华市', district: '义乌市', code: 'JH_YW' },
|
||||
{ city: '金华市', district: '东阳市', code: 'JH_DY' },
|
||||
{ city: '金华市', district: '永康市', code: 'JH_YK' },
|
||||
// 衢州市
|
||||
{ city: '衢州市', district: '柯城区', code: 'QZ_KC' },
|
||||
{ city: '衢州市', district: '衢江区', code: 'QZ_QJ' },
|
||||
{ city: '衢州市', district: '常山县', code: 'QZ_CS' },
|
||||
{ city: '衢州市', district: '开化县', code: 'QZ_KH' },
|
||||
{ city: '衢州市', district: '龙游县', code: 'QZ_LY' },
|
||||
{ city: '衢州市', district: '江山市', code: 'QZ_JS' },
|
||||
// 舟山市
|
||||
{ city: '舟山市', district: '定海区', code: 'ZS_DH' },
|
||||
{ city: '舟山市', district: '普陀区', code: 'ZS_PT' },
|
||||
{ city: '舟山市', district: '岱山县', code: 'ZS_DS' },
|
||||
{ city: '舟山市', district: '嵊泗县', code: 'ZS_SS' },
|
||||
// 台州市
|
||||
{ city: '台州市', district: '椒江区', code: 'TZ_JJ' },
|
||||
{ city: '台州市', district: '黄岩区', code: 'TZ_HY' },
|
||||
{ city: '台州市', district: '路桥区', code: 'TZ_LQ' },
|
||||
{ city: '台州市', district: '三门县', code: 'TZ_SM' },
|
||||
{ city: '台州市', district: '天台县', code: 'TZ_TT' },
|
||||
{ city: '台州市', district: '仙居县', code: 'TZ_XJ' },
|
||||
{ city: '台州市', district: '温岭市', code: 'TZ_WL' },
|
||||
{ city: '台州市', district: '临海市', code: 'TZ_LH' },
|
||||
{ city: '台州市', district: '玉环市', code: 'TZ_YH' },
|
||||
// 丽水市
|
||||
{ city: '丽水市', district: '莲都区', code: 'LS_LD' },
|
||||
{ city: '丽水市', district: '青田县', code: 'LS_QT' },
|
||||
{ city: '丽水市', district: '缙云县', code: 'LS_JY' },
|
||||
{ city: '丽水市', district: '遂昌县', code: 'LS_SC' },
|
||||
{ city: '丽水市', district: '松阳县', code: 'LS_SY' },
|
||||
{ city: '丽水市', district: '云和县', code: 'LS_YH' },
|
||||
{ city: '丽水市', district: '庆元县', code: 'LS_QY' },
|
||||
{ city: '丽水市', district: '景宁县', code: 'LS_JN' },
|
||||
{ city: '丽水市', district: '龙泉市', code: 'LS_LQ' }
|
||||
];
|
||||
|
||||
// 批量插入区域数据
|
||||
for (const region of zhejiangRegions) {
|
||||
try {
|
||||
await getDB().execute(
|
||||
'INSERT IGNORE INTO zhejiang_regions (city_name, district_name, region_code) VALUES (?, ?, ?)',
|
||||
[region.city, region.district, region.code]
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`插入区域数据失败: ${region.city} ${region.district}`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initDatabase,
|
||||
createTables,
|
||||
addMissingFields,
|
||||
createDefaultData,
|
||||
initializeZhejiangRegions
|
||||
};
|
||||
363
config/dbv2.js
Normal file
363
config/dbv2.js
Normal file
@@ -0,0 +1,363 @@
|
||||
class QueryBuilder {
|
||||
constructor() {
|
||||
this.conditions = {};
|
||||
this.limit = null;
|
||||
this.offset = null;
|
||||
this.groupBy = null;
|
||||
}
|
||||
|
||||
where(condition, ...params) {
|
||||
this.conditions[condition] = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
setLimit(limit) {
|
||||
this.limit = limit;
|
||||
return this;
|
||||
}
|
||||
|
||||
setOffset(offset) {
|
||||
this.offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
setGroupBy(groupBy) {
|
||||
this.groupBy = groupBy;
|
||||
return this;
|
||||
}
|
||||
|
||||
sqdata(sql, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
global.sqlReq.query(sql, params, (err, result) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getParams() {
|
||||
return Object.values(this.conditions).flat();
|
||||
}
|
||||
|
||||
buildConditions() {
|
||||
return Object.keys(this.conditions).map(condition => `${condition}`).join(' AND ');
|
||||
}
|
||||
}
|
||||
|
||||
class SelectBuilder extends QueryBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.selectFields = [];
|
||||
this.tables = [];
|
||||
this.orderByField = '';
|
||||
this.orderByDirection = 'ASC';
|
||||
this.subQueries = []; // 用于存储子查询
|
||||
this.unions = []; // 存储UNION查询
|
||||
}
|
||||
// 添加UNION查询
|
||||
union(queryBuilder, type = 'UNION') {
|
||||
this.unions.push({queryBuilder, type});
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加UNION ALL查询
|
||||
unionAll(queryBuilder) {
|
||||
this.union(queryBuilder, 'UNION ALL');
|
||||
return this;
|
||||
}
|
||||
|
||||
// 构建主查询部分(不含ORDER BY/LIMIT/OFFSET)
|
||||
buildMainQuery() {
|
||||
const subQuerySQL = this.subQueries.map(({alias, subQuery}) => `(${subQuery}) AS ${alias}`);
|
||||
const selectClause = this.selectFields.concat(subQuerySQL).join(', ');
|
||||
|
||||
let sql = `SELECT ${selectClause}
|
||||
FROM ${this.tables.join(' ')}`;
|
||||
|
||||
const conditionClauses = this.buildConditions();
|
||||
if (conditionClauses) {
|
||||
sql += ` WHERE ${conditionClauses}`;
|
||||
}
|
||||
|
||||
if (this.groupBy) {
|
||||
sql += ` GROUP BY ${this.groupBy}`;
|
||||
}
|
||||
|
||||
const params = this.getParams();
|
||||
return {sql, params};
|
||||
}
|
||||
|
||||
// 供UNION查询调用的构建方法
|
||||
buildForUnion() {
|
||||
return this.buildMainQuery();
|
||||
}
|
||||
|
||||
select(fields) {
|
||||
this.selectFields = fields.split(',').map(field => field.trim());
|
||||
return this;
|
||||
}
|
||||
|
||||
// 添加子查询
|
||||
addSubQuery(alias, subQuery) {
|
||||
this.subQueries.push({alias, subQuery});
|
||||
return this;
|
||||
}
|
||||
|
||||
whereLike(fields, keyword) {
|
||||
const likeConditions = fields.map(field => `${field} LIKE ?`).join(' OR ');
|
||||
this.conditions[likeConditions] = fields.map(() => `%${keyword}%`);
|
||||
return this;
|
||||
}
|
||||
|
||||
from(table) {
|
||||
this.tables.push(table);
|
||||
return this;
|
||||
}
|
||||
|
||||
leftJoin(table, condition) {
|
||||
this.tables.push(`LEFT JOIN ${table} ON ${condition}`);
|
||||
return this;
|
||||
}
|
||||
|
||||
orderBy(field, direction = 'ASC') {
|
||||
this.orderByField = field;
|
||||
this.orderByDirection = direction.toUpperCase();
|
||||
return this;
|
||||
}
|
||||
|
||||
paginate(page, pageSize) {
|
||||
if (page <= 0 || pageSize <= 0) {
|
||||
throw new Error('分页参数必须大于0');
|
||||
}
|
||||
this.limit = pageSize;
|
||||
this.offset = (page - 1) * pageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
async chidBuild() {
|
||||
|
||||
let sql = `SELECT ${this.selectFields.join(', ')}
|
||||
FROM ${this.tables.join(' ')}`;
|
||||
let conditionClauses = this.buildConditions();
|
||||
if (conditionClauses) {
|
||||
sql += ` WHERE ${conditionClauses}`;
|
||||
}
|
||||
if (this.orderByField) {
|
||||
sql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`;
|
||||
}
|
||||
if (this.limit !== null) {
|
||||
sql += ` LIMIT ${this.limit}`;
|
||||
}
|
||||
if (this.offset !== null) {
|
||||
sql += ` OFFSET ${this.offset}`;
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
|
||||
async build() {
|
||||
const main = this.buildMainQuery();
|
||||
let fullSql = `(${main.sql})`;
|
||||
const allParams = [...main.params];
|
||||
|
||||
// 处理UNION部分
|
||||
for (const union of this.unions) {
|
||||
const unionBuilder = union.queryBuilder;
|
||||
if (!(unionBuilder instanceof SelectBuilder)) {
|
||||
throw new Error('UNION query must be a SelectBuilder instance');
|
||||
}
|
||||
const unionResult = unionBuilder.buildForUnion();
|
||||
fullSql += ` ${union.type} (${unionResult.sql})`;
|
||||
allParams.push(...unionResult.params);
|
||||
}
|
||||
|
||||
// 添加ORDER BY、LIMIT、OFFSET
|
||||
if (this.orderByField) {
|
||||
fullSql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`;
|
||||
}
|
||||
if (this.limit !== null) {
|
||||
fullSql += ` LIMIT ${this.limit}`;
|
||||
}
|
||||
if (this.offset !== null) {
|
||||
fullSql += ` OFFSET ${this.offset}`;
|
||||
}
|
||||
console.log(fullSql,allParams);
|
||||
return await this.sqdata(fullSql, allParams);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UpdateBuilder extends QueryBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.table = '';
|
||||
this.updateFields = {};
|
||||
}
|
||||
|
||||
update(table) {
|
||||
this.table = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
set(field, value) {
|
||||
if (value && value.increment && typeof value === 'object' ) {
|
||||
this.updateFields[field] = {increment: value.increment};
|
||||
} else {
|
||||
this.updateFields[field] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
async build() {
|
||||
let sql = `UPDATE ${this.table}
|
||||
SET `;
|
||||
let updateClauses = Object.keys(this.updateFields).map(field => {
|
||||
const value = this.updateFields[field];
|
||||
if (value && value.increment && typeof value === 'object' ) {
|
||||
return `${field} = ${field} + ?`;
|
||||
}
|
||||
return `${field} = ?`;
|
||||
}).join(', ');
|
||||
|
||||
sql += updateClauses;
|
||||
|
||||
let conditionClauses = this.buildConditions();
|
||||
if (conditionClauses) {
|
||||
sql += ` WHERE ${conditionClauses}`;
|
||||
}
|
||||
// 处理参数,确保自增字段也传入增量值
|
||||
const params = [
|
||||
...Object.values(this.updateFields).map(value =>
|
||||
(value && value.increment && typeof value === 'object' ) ? value.increment : value
|
||||
),
|
||||
...this.getParams()
|
||||
];
|
||||
return await this.sqdata(sql, params);
|
||||
}
|
||||
}
|
||||
|
||||
class InsertBuilder extends QueryBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.table = '';
|
||||
this.insertValues = [];
|
||||
this.updateValues = {};
|
||||
}
|
||||
|
||||
insertInto(table) {
|
||||
this.table = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 仍然保留单条记录的插入
|
||||
values(values) {
|
||||
if (Array.isArray(values)) {
|
||||
this.insertValues = values;
|
||||
} else {
|
||||
this.insertValues = [values]; // 将单条记录包装成数组
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// 新增方法,支持一次插入多条记录
|
||||
valuesMultiple(records) {
|
||||
if (!Array.isArray(records) || records.length === 0) {
|
||||
throw new Error('Values must be a non-empty array');
|
||||
}
|
||||
|
||||
// 确保每一条记录都是对象
|
||||
records.forEach(record => {
|
||||
if (typeof record !== 'object') {
|
||||
throw new Error('Each record must be an object');
|
||||
}
|
||||
});
|
||||
|
||||
this.insertValues = records;
|
||||
return this;
|
||||
}
|
||||
|
||||
// 新增 upsert 方法,支持更新或插入
|
||||
upsert(values, updateFields) {
|
||||
// values: 要插入的记录
|
||||
// updateFields: 如果记录存在时,需要更新的字段
|
||||
if (!Array.isArray(values) || values.length === 0) {
|
||||
throw new Error('Values must be a non-empty array');
|
||||
}
|
||||
|
||||
// 检查每条记录是否是对象
|
||||
values.forEach(record => {
|
||||
if (typeof record !== 'object') {
|
||||
throw new Error('Each record must be an object');
|
||||
}
|
||||
});
|
||||
|
||||
this.insertValues = values;
|
||||
this.updateValues = updateFields || {};
|
||||
return this;
|
||||
}
|
||||
|
||||
async build() {
|
||||
if (this.insertValues.length === 0) {
|
||||
throw new Error("No values to insert");
|
||||
}
|
||||
|
||||
// 获取表单列名,假设所有记录有相同的字段
|
||||
const columns = Object.keys(this.insertValues[0]);
|
||||
|
||||
// 构建 VALUES 子句,支持批量插入
|
||||
const valuePlaceholders = this.insertValues.map(() =>
|
||||
`(${columns.map(() => '?').join(', ')})`
|
||||
).join(', ');
|
||||
|
||||
// 展平所有的插入值
|
||||
const params = this.insertValues.flatMap(record =>
|
||||
columns.map(column => record[column])
|
||||
);
|
||||
|
||||
// 如果有 updateFields,构建 ON DUPLICATE KEY UPDATE 子句
|
||||
let updateClause = '';
|
||||
if (Object.keys(this.updateValues).length > 0) {
|
||||
updateClause = ' ON DUPLICATE KEY UPDATE ' +
|
||||
Object.keys(this.updateValues).map(field => {
|
||||
return `${field} = VALUES(${field})`;
|
||||
}).join(', ');
|
||||
}
|
||||
|
||||
// 生成 SQL 语句
|
||||
const sql = `INSERT INTO ${this.table} (${columns.join(', ')})
|
||||
VALUES ${valuePlaceholders} ${updateClause}`;
|
||||
// 执行查询
|
||||
return await this.sqdata(sql, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DeleteBuilder extends QueryBuilder {
|
||||
constructor() {
|
||||
super();
|
||||
this.table = '';
|
||||
}
|
||||
|
||||
deleteFrom(table) {
|
||||
this.table = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
async build() {
|
||||
let sql = `DELETE
|
||||
FROM ${this.table}`;
|
||||
let conditionClauses = this.buildConditions();
|
||||
if (conditionClauses) {
|
||||
sql += ` WHERE ${conditionClauses}`;
|
||||
}
|
||||
return await this.sqdata(sql, this.getParams());
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SelectBuilder,
|
||||
UpdateBuilder,
|
||||
InsertBuilder,
|
||||
DeleteBuilder,
|
||||
};
|
||||
73
config/logger.js
Normal file
73
config/logger.js
Normal file
@@ -0,0 +1,73 @@
|
||||
const winston = require('winston');
|
||||
const path = require('path');
|
||||
|
||||
// 创建日志目录
|
||||
const logDir = path.join(__dirname, '../logs');
|
||||
|
||||
// 日志格式配置
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss'
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
// 控制台日志格式
|
||||
const consoleFormat = winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss'
|
||||
}),
|
||||
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
||||
return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
|
||||
})
|
||||
);
|
||||
|
||||
// 创建logger实例
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: logFormat,
|
||||
defaultMeta: { service: 'integrated-system' },
|
||||
transports: [
|
||||
// 错误日志文件
|
||||
new winston.transports.File({
|
||||
filename: path.join(logDir, 'error.log'),
|
||||
level: 'error',
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
}),
|
||||
// 所有日志文件
|
||||
new winston.transports.File({
|
||||
filename: path.join(logDir, 'combined.log'),
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// 开发环境添加控制台输出
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: consoleFormat
|
||||
}));
|
||||
}
|
||||
|
||||
// 审计日志记录器
|
||||
const auditLogger = winston.createLogger({
|
||||
level: 'info',
|
||||
format: logFormat,
|
||||
defaultMeta: { service: 'audit' },
|
||||
transports: [
|
||||
new winston.transports.File({
|
||||
filename: path.join(logDir, 'audit.log'),
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 10
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
logger,
|
||||
auditLogger
|
||||
};
|
||||
34
config/withdrawal-init.sql
Normal file
34
config/withdrawal-init.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- 创建代理提现记录表
|
||||
CREATE TABLE IF NOT EXISTS agent_withdrawals (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
agent_id INT NOT NULL,
|
||||
amount DECIMAL(10,2) NOT NULL,
|
||||
payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型',
|
||||
bank_name VARCHAR(100) COMMENT '银行名称',
|
||||
account_number VARCHAR(50) COMMENT '账号/银行账号',
|
||||
account_holder VARCHAR(100) COMMENT '持有人姓名',
|
||||
qr_code_url VARCHAR(255) COMMENT '收款码图片URL',
|
||||
status ENUM('pending', 'approved', 'rejected', 'completed') DEFAULT 'pending',
|
||||
apply_note TEXT,
|
||||
admin_note TEXT,
|
||||
processed_by INT NULL,
|
||||
processed_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (agent_id) REFERENCES regional_agents(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (processed_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||
-- 兼容旧字段
|
||||
bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)'
|
||||
);
|
||||
|
||||
-- 为regional_agents表添加提现相关字段
|
||||
ALTER TABLE regional_agents ADD COLUMN withdrawn_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '已提现金额';
|
||||
ALTER TABLE regional_agents ADD COLUMN pending_withdrawal DECIMAL(10,2) DEFAULT 0.00 COMMENT '待审核提现金额';
|
||||
ALTER TABLE regional_agents ADD COLUMN payment_type ENUM('bank', 'wechat', 'alipay', 'unionpay') DEFAULT 'bank' COMMENT '收款方式类型';
|
||||
ALTER TABLE regional_agents ADD COLUMN bank_name VARCHAR(100) COMMENT '银行名称';
|
||||
ALTER TABLE regional_agents ADD COLUMN account_number VARCHAR(50) COMMENT '账号/银行账号';
|
||||
ALTER TABLE regional_agents ADD COLUMN account_holder VARCHAR(100) COMMENT '持有人姓名';
|
||||
ALTER TABLE regional_agents ADD COLUMN qr_code_url VARCHAR(255) COMMENT '收款码图片URL';
|
||||
|
||||
-- 兼容旧字段(可选,用于数据迁移)
|
||||
ALTER TABLE regional_agents ADD COLUMN bank_account VARCHAR(50) COMMENT '银行账号(兼容旧版本)';
|
||||
Reference in New Issue
Block a user