更新完善页面
This commit is contained in:
+101
-9
@@ -16,44 +16,109 @@ const config = {
|
||||
password: requireEnv('DB_PASSWORD', ''),
|
||||
database: requireEnv('DB_NAME', 'miniprogram'),
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
connectionLimit: parseInt(process.env.DB_POOL_LIMIT) || 10,
|
||||
queueLimit: 0
|
||||
}
|
||||
|
||||
const SLOW_QUERY_MS = parseInt(process.env.SLOW_QUERY_MS) || 500
|
||||
|
||||
const pool = mysql.createPool(config)
|
||||
|
||||
const queryStats = {
|
||||
total: 0,
|
||||
slow: 0,
|
||||
errors: 0,
|
||||
totalDurationMs: 0,
|
||||
lastSlow: []
|
||||
}
|
||||
|
||||
function recordSlow(sql, durationMs, params) {
|
||||
queryStats.slow += 1
|
||||
const entry = {
|
||||
sql: sql.replace(/\s+/g, ' ').slice(0, 200),
|
||||
durationMs: Math.round(durationMs),
|
||||
paramCount: Array.isArray(params) ? params.length : 0,
|
||||
at: new Date().toISOString()
|
||||
}
|
||||
queryStats.lastSlow.unshift(entry)
|
||||
if (queryStats.lastSlow.length > 20) queryStats.lastSlow.pop()
|
||||
console.warn('[SLOW QUERY]', entry.durationMs + 'ms', entry.sql)
|
||||
}
|
||||
|
||||
async function query(sql, params = []) {
|
||||
const start = Date.now()
|
||||
const connection = await pool.getConnection()
|
||||
try {
|
||||
const [rows, fields] = await connection.execute(sql, params)
|
||||
const [rows] = await connection.execute(sql, params)
|
||||
const duration = Date.now() - start
|
||||
queryStats.total += 1
|
||||
queryStats.totalDurationMs += duration
|
||||
if (duration >= SLOW_QUERY_MS) recordSlow(sql, duration, params)
|
||||
return rows
|
||||
} catch (err) {
|
||||
queryStats.errors += 1
|
||||
throw err
|
||||
} finally {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
|
||||
function getPoolMetrics() {
|
||||
const p = pool.pool || pool
|
||||
return {
|
||||
connectionLimit: config.connectionLimit,
|
||||
allConnections: (p._allConnections && p._allConnections.length) || 0,
|
||||
freeConnections: (p._freeConnections && p._freeConnections.length) || 0,
|
||||
acquiringConnections: (p._acquiringConnections && p._acquiringConnections.length) || 0,
|
||||
queue: (p._connectionQueue && p._connectionQueue.length) || 0
|
||||
}
|
||||
}
|
||||
|
||||
function getQueryStats() {
|
||||
const avg = queryStats.total > 0 ? Math.round(queryStats.totalDurationMs / queryStats.total) : 0
|
||||
return {
|
||||
total: queryStats.total,
|
||||
slow: queryStats.slow,
|
||||
errors: queryStats.errors,
|
||||
avgDurationMs: avg,
|
||||
lastSlow: queryStats.lastSlow.slice(0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
function resetQueryStats() {
|
||||
queryStats.total = 0
|
||||
queryStats.slow = 0
|
||||
queryStats.errors = 0
|
||||
queryStats.totalDurationMs = 0
|
||||
queryStats.lastSlow = []
|
||||
}
|
||||
|
||||
async function initDatabase() {
|
||||
try {
|
||||
const connection = await mysql.createConnection({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
multipleStatements: true
|
||||
password: config.password
|
||||
})
|
||||
|
||||
|
||||
await connection.query(`CREATE DATABASE IF NOT EXISTS ${config.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`)
|
||||
await connection.query(`USE ${config.database}`)
|
||||
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const schema = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8')
|
||||
const statements = schema.split(';').filter(s => s.trim())
|
||||
|
||||
|
||||
for (const statement of statements) {
|
||||
await connection.query(statement)
|
||||
try {
|
||||
await connection.query(statement)
|
||||
} catch (e) {
|
||||
if (/Duplicate column name|already exists|exists/i.test(e.message)) continue
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await connection.end()
|
||||
console.log('Database initialized successfully')
|
||||
} catch (error) {
|
||||
@@ -77,10 +142,37 @@ async function transaction(callback) {
|
||||
}
|
||||
}
|
||||
|
||||
let healthCheckTimer = null
|
||||
|
||||
function startHealthCheck(intervalMs = 30000) {
|
||||
if (healthCheckTimer) clearInterval(healthCheckTimer)
|
||||
healthCheckTimer = setInterval(async () => {
|
||||
try {
|
||||
const connection = await pool.getConnection()
|
||||
await connection.ping()
|
||||
connection.release()
|
||||
} catch (err) {
|
||||
console.error('Database health check failed:', err.message)
|
||||
}
|
||||
}, intervalMs)
|
||||
}
|
||||
|
||||
function stopHealthCheck() {
|
||||
if (healthCheckTimer) {
|
||||
clearInterval(healthCheckTimer)
|
||||
healthCheckTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
pool,
|
||||
query,
|
||||
transaction,
|
||||
initDatabase,
|
||||
startHealthCheck,
|
||||
stopHealthCheck,
|
||||
getPoolMetrics,
|
||||
getQueryStats,
|
||||
resetQueryStats,
|
||||
config
|
||||
}
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
// 服务端域名配置 - 从环境变量读取,默认 IP
|
||||
const BASE_URL = process.env.BASE_URL || 'http://110.42.255.239:3006'
|
||||
// 服务端域名配置 - 从环境变量读取,默认正式域名
|
||||
const BASE_URL = process.env.BASE_URL || 'https://donghy.top'
|
||||
|
||||
const DOMAIN_CONFIG = {
|
||||
BASE_URL: BASE_URL,
|
||||
|
||||
+121
-2
@@ -40,7 +40,7 @@ CREATE TABLE IF NOT EXISTS `users` (
|
||||
`name` varchar(100) DEFAULT NULL COMMENT '用户名',
|
||||
`avatar` varchar(255) DEFAULT NULL COMMENT '头像',
|
||||
`points` int(11) DEFAULT 0 COMMENT '积分',
|
||||
`role` tinyint(1) DEFAULT 0 COMMENT '角色 0-普通用户 1-店员',
|
||||
`role` tinyint(1) DEFAULT 0 COMMENT '角色 0-普通用户 1-店员 2-管理员',
|
||||
`status` tinyint(1) DEFAULT 1 COMMENT '状态 1-正常 0-禁用',
|
||||
`openid` varchar(100) DEFAULT NULL COMMENT '微信openid',
|
||||
`token` varchar(100) DEFAULT NULL COMMENT '登录token',
|
||||
@@ -196,6 +196,14 @@ ALTER TABLE `goods` ADD INDEX IF NOT EXISTS `idx_goods_is_hot` (`is_hot`);
|
||||
ALTER TABLE `goods` ADD INDEX IF NOT EXISTS `idx_goods_is_new` (`is_new`);
|
||||
ALTER TABLE `goods` ADD INDEX IF NOT EXISTS `idx_goods_name` (`name`(100));
|
||||
|
||||
-- 商品表新增上下架字段
|
||||
ALTER TABLE `goods` ADD COLUMN IF NOT EXISTS `is_on_sale` tinyint(1) DEFAULT 1 COMMENT '是否上架 1-上架 0-下架';
|
||||
ALTER TABLE `goods` ADD COLUMN IF NOT EXISTS `cost_price` decimal(10,2) DEFAULT 0.00 COMMENT '进价';
|
||||
|
||||
-- 商品表全文索引:关键词搜索加速 (MySQL 5.7+/8.0)
|
||||
-- 注意:FULLTEXT 需 MyISAM 或 InnoDB 5.6+;若不支持会被忽略
|
||||
ALTER TABLE `goods` ADD FULLTEXT INDEX IF NOT EXISTS `ft_goods_name_desc` (`name`, `description`);
|
||||
|
||||
-- 分类表索引:按排序字段排序
|
||||
ALTER TABLE `categories` ADD INDEX IF NOT EXISTS `idx_categories_sort_order` (`sort_order`);
|
||||
|
||||
@@ -204,4 +212,115 @@ ALTER TABLE `points_logs` ADD INDEX IF NOT EXISTS `idx_points_logs_type` (`type`
|
||||
|
||||
-- 采购单表索引:按状态筛选、按时间排序
|
||||
ALTER TABLE `purchases` ADD INDEX IF NOT EXISTS `idx_purchases_status` (`status`);
|
||||
ALTER TABLE `purchases` ADD INDEX IF NOT EXISTS `idx_purchases_created_at` (`created_at`);
|
||||
ALTER TABLE `purchases` ADD INDEX IF NOT EXISTS `idx_purchases_created_at` (`created_at`);
|
||||
|
||||
-- 购物车表
|
||||
CREATE TABLE IF NOT EXISTS `carts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`goods_id` int(11) NOT NULL COMMENT '商品ID',
|
||||
`quantity` int(11) NOT NULL DEFAULT 1 COMMENT '数量',
|
||||
`weight` decimal(10,2) DEFAULT NULL COMMENT '称重(kg)',
|
||||
`selected` tinyint(1) DEFAULT 1 COMMENT '是否选中',
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `user_goods` (`user_id`, `goods_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `goods_id` (`goods_id`),
|
||||
CONSTRAINT `carts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `carts_ibfk_2` FOREIGN KEY (`goods_id`) REFERENCES `goods` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='购物车表';
|
||||
|
||||
-- 购物车表索引
|
||||
ALTER TABLE `carts` ADD INDEX IF NOT EXISTS `idx_carts_user_id` (`user_id`);
|
||||
ALTER TABLE `carts` ADD INDEX IF NOT EXISTS `idx_carts_selected` (`selected`);
|
||||
|
||||
-- 退款表
|
||||
CREATE TABLE IF NOT EXISTS `refunds` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`order_id` varchar(50) NOT NULL COMMENT '订单ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`type` tinyint(1) DEFAULT 1 COMMENT '退款类型 1-仅退款 2-退货退款',
|
||||
`reason` varchar(500) NOT NULL COMMENT '退款原因',
|
||||
`amount` decimal(10,2) NOT NULL COMMENT '退款金额',
|
||||
`status` tinyint(1) DEFAULT 0 COMMENT '状态 0-待处理 1-已通过 2-已拒绝',
|
||||
`admin_remark` varchar(500) DEFAULT NULL COMMENT '管理员备注',
|
||||
`processed_at` datetime DEFAULT NULL COMMENT '处理时间',
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `order_id` (`order_id`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `status` (`status`),
|
||||
CONSTRAINT `refunds_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
|
||||
CONSTRAINT `refunds_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='退款申请表';
|
||||
|
||||
-- 退款表索引
|
||||
ALTER TABLE `refunds` ADD INDEX IF NOT EXISTS `idx_refunds_status` (`status`);
|
||||
ALTER TABLE `refunds` ADD INDEX IF NOT EXISTS `idx_refunds_created_at` (`created_at`);
|
||||
|
||||
-- 首页分类配置表
|
||||
CREATE TABLE IF NOT EXISTS `home_categories` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`category_id` int(11) NOT NULL COMMENT '分类ID',
|
||||
`sort_order` int(11) DEFAULT 0 COMMENT '排序顺序',
|
||||
`is_enabled` tinyint(1) DEFAULT 1 COMMENT '是否启用',
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `category_id` (`category_id`),
|
||||
KEY `is_enabled` (`is_enabled`),
|
||||
KEY `sort_order` (`sort_order`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='首页分类配置表';
|
||||
|
||||
-- 首页分类默认数据
|
||||
INSERT INTO `home_categories` (`category_id`, `sort_order`, `is_enabled`) VALUES
|
||||
(1, 1, 1),
|
||||
(10, 2, 1),
|
||||
(42, 3, 1),
|
||||
(49, 4, 1),
|
||||
(37, 5, 1),
|
||||
(13, 6, 1),
|
||||
(2, 7, 1),
|
||||
(5, 8, 1);
|
||||
|
||||
-- 库存调整流水表 (P1-D2)
|
||||
CREATE TABLE IF NOT EXISTS `stock_logs` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`goods_id` int(11) NOT NULL COMMENT '商品ID',
|
||||
`change_qty` int(11) NOT NULL COMMENT '变更量 (正=入库/负=出库)',
|
||||
`before_qty` int(11) NOT NULL COMMENT '变更前库存',
|
||||
`after_qty` int(11) NOT NULL COMMENT '变更后库存',
|
||||
`reason` varchar(50) NOT NULL COMMENT '原因: purchase/sale/manual/refund',
|
||||
`ref_id` varchar(64) DEFAULT NULL COMMENT '关联单据ID(采购单/订单号)',
|
||||
`operator_id` int(11) DEFAULT NULL COMMENT '操作员ID',
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `goods_id` (`goods_id`),
|
||||
KEY `reason` (`reason`),
|
||||
KEY `created_at` (`created_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='库存流水表';
|
||||
|
||||
-- token 黑名单 (服务端 logout 强制失效)
|
||||
CREATE TABLE IF NOT EXISTS `token_blacklist` (
|
||||
`jti` varchar(64) NOT NULL COMMENT 'JWT ID',
|
||||
`user_id` int(11) NOT NULL COMMENT '用户ID',
|
||||
`expired_at` datetime NOT NULL COMMENT '原 token 过期时间',
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`jti`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `expired_at` (`expired_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='JWT 黑名单';
|
||||
|
||||
-- AI 调用限流与缓存
|
||||
CREATE TABLE IF NOT EXISTS `ai_cache` (
|
||||
`key_hash` char(64) NOT NULL COMMENT '请求 hash',
|
||||
`response` text NOT NULL COMMENT '响应体',
|
||||
`hits` int(11) DEFAULT 1,
|
||||
`expires_at` datetime NOT NULL,
|
||||
`created_at` timestamp DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`key_hash`),
|
||||
KEY `expires_at` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI 响应缓存';
|
||||
Reference in New Issue
Block a user