初次提交

This commit is contained in:
2025-08-26 10:06:23 +08:00
commit a1944a573e
58 changed files with 19131 additions and 0 deletions

17
config/config.js Normal file
View 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
View 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
View 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
View 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
View 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
};

View 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 '银行账号(兼容旧版本)';