This commit is contained in:
董海洋
2026-06-05 17:12:06 +08:00
parent 43de11e45c
commit 1937500b24
3 changed files with 223 additions and 19 deletions
+70
View File
@@ -0,0 +1,70 @@
// 一次性迁移脚本:为 goods 表补充 goods_no、barcode、is_on_sale、cost_price、remark 等字段
// 用法:node add_goods_no_barcode_columns.js
const mysql = require('mysql2/promise')
require('dotenv').config()
const COLUMNS = [
{ name: 'cost_price', ddl: 'ALTER TABLE goods ADD COLUMN `cost_price` decimal(10,2) DEFAULT 0 COMMENT "成本价"' },
{ name: 'goods_no', ddl: 'ALTER TABLE goods ADD COLUMN `goods_no` varchar(64) DEFAULT "" COMMENT "商品货号"' },
{ name: 'barcode', ddl: 'ALTER TABLE goods ADD COLUMN `barcode` varchar(64) DEFAULT "" COMMENT "商品条码"' },
{ name: 'is_on_sale', ddl: 'ALTER TABLE goods ADD COLUMN `is_on_sale` tinyint(1) DEFAULT 1 COMMENT "是否上架 1-上架 0-下架"' },
{ name: 'remark', ddl: 'ALTER TABLE goods ADD COLUMN `remark` varchar(500) DEFAULT "" COMMENT "备注"' }
]
const INDEXES = [
{ name: 'idx_barcode', ddl: 'ALTER TABLE goods ADD INDEX `idx_barcode` (`barcode`)' },
{ name: 'idx_goods_no', ddl: 'ALTER TABLE goods ADD INDEX `idx_goods_no` (`goods_no`)' }
]
async function main() {
const config = {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '3306'),
user: process.env.DB_USER || 'root',
password: process.env.DB_PASSWORD || '',
database: process.env.DB_NAME || 'miniprogram'
}
const connection = await mysql.createConnection(config)
try {
const [columns] = await connection.execute('DESCRIBE goods')
const existing = new Set(columns.map(col => col.Field))
console.log('当前 goods 表字段:', [...existing].join(', '))
for (const col of COLUMNS) {
if (existing.has(col.name)) {
console.log(`- 字段 ${col.name} 已存在,跳过`)
continue
}
console.log(`- 添加字段 ${col.name} ...`)
await connection.execute(col.ddl)
}
const [indexes] = await connection.execute('SHOW INDEX FROM goods')
const existingIndexes = new Set(indexes.map(i => i.Key_name))
for (const idx of INDEXES) {
if (existingIndexes.has(idx.name)) {
console.log(`- 索引 ${idx.name} 已存在,跳过`)
continue
}
console.log(`- 添加索引 ${idx.name} ...`)
try {
await connection.execute(idx.ddl)
} catch (err) {
console.warn(` 索引 ${idx.name} 添加失败:${err.message}`)
}
}
console.log('完成 ✅')
} finally {
await connection.end()
}
}
main().catch(err => {
console.error('迁移失败:', err)
process.exit(1)
})
+65 -19
View File
@@ -10,13 +10,17 @@ CREATE TABLE IF NOT EXISTS `categories` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品分类表';
ALTER TABLE `categories` ADD COLUMN IF NOT EXISTS `color` varchar(20) DEFAULT '#1890ff' COMMENT '分类颜色';
-- 兼容 MySQL < 8.0.29:基于 information_schema 判断后再 ALTER
SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'categories' AND COLUMN_NAME = 'color');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `categories` ADD COLUMN `color` varchar(20) DEFAULT ''#1890ff'' COMMENT ''分类颜色''', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(200) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`original_price` decimal(10,2) DEFAULT NULL COMMENT '原价',
`cost_price` decimal(10,2) DEFAULT 0 COMMENT '成本价',
`unit` varchar(20) DEFAULT '' COMMENT '单位',
`category_id` int(11) DEFAULT NULL COMMENT '分类ID',
`images` text COMMENT '图片JSON',
@@ -25,11 +29,17 @@ CREATE TABLE IF NOT EXISTS `goods` (
`is_hot` tinyint(1) DEFAULT 0 COMMENT '是否热销',
`is_new` tinyint(1) DEFAULT 0 COMMENT '是否新品',
`pricing_type` tinyint(1) DEFAULT 1 COMMENT '定价类型 1-按件 2-按重量',
`goods_no` varchar(64) DEFAULT '' COMMENT '商品货号',
`barcode` varchar(64) DEFAULT '' COMMENT '商品条码',
`is_on_sale` tinyint(1) DEFAULT 1 COMMENT '是否上架 1-上架 0-下架',
`description` text COMMENT '商品描述',
`remark` varchar(500) DEFAULT '' 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 `idx_barcode` (`barcode`),
KEY `idx_goods_no` (`goods_no`),
CONSTRAINT `goods_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品表';
@@ -188,31 +198,55 @@ CREATE TABLE IF NOT EXISTS `points_goods` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='积分商品表';
-- 订单表索引:按状态筛选、按时间排序
ALTER TABLE `orders` ADD INDEX IF NOT EXISTS `idx_orders_status` (`status`);
ALTER TABLE `orders` ADD INDEX IF NOT EXISTS `idx_orders_created_at` (`created_at`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders' AND INDEX_NAME = 'idx_orders_status');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `orders` ADD INDEX `idx_orders_status` (`status`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'orders' AND INDEX_NAME = 'idx_orders_created_at');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `orders` ADD INDEX `idx_orders_created_at` (`created_at`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 商品表索引:热销/新品筛选、名称搜索
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));
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND INDEX_NAME = 'idx_goods_is_hot');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `goods` ADD INDEX `idx_goods_is_hot` (`is_hot`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND INDEX_NAME = 'idx_goods_is_new');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `goods` ADD INDEX `idx_goods_is_new` (`is_new`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND INDEX_NAME = 'idx_goods_name');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `goods` ADD INDEX `idx_goods_name` (`name`(100))', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 商品表新增上下架字段
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 '进价';
SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND COLUMN_NAME = 'is_on_sale');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `goods` ADD COLUMN `is_on_sale` tinyint(1) DEFAULT 1 COMMENT ''是否上架 1-上架 0-下架''', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @col_exists = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND COLUMN_NAME = 'cost_price');
SET @sql = IF(@col_exists = 0, 'ALTER TABLE `goods` ADD COLUMN `cost_price` decimal(10,2) DEFAULT 0.00 COMMENT ''进价''', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 商品表全文索引:关键词搜索加速 (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`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'goods' AND INDEX_NAME = 'ft_goods_name_desc');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `goods` ADD FULLTEXT INDEX `ft_goods_name_desc` (`name`, `description`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 分类表索引:按排序字段排序
ALTER TABLE `categories` ADD INDEX IF NOT EXISTS `idx_categories_sort_order` (`sort_order`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'categories' AND INDEX_NAME = 'idx_categories_sort_order');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `categories` ADD INDEX `idx_categories_sort_order` (`sort_order`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 积分记录表索引:按类型筛选
ALTER TABLE `points_logs` ADD INDEX IF NOT EXISTS `idx_points_logs_type` (`type`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'points_logs' AND INDEX_NAME = 'idx_points_logs_type');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `points_logs` ADD INDEX `idx_points_logs_type` (`type`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 采购单表索引:按状态筛选、按时间排序
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`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'purchases' AND INDEX_NAME = 'idx_purchases_status');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `purchases` ADD INDEX `idx_purchases_status` (`status`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'purchases' AND INDEX_NAME = 'idx_purchases_created_at');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `purchases` ADD INDEX `idx_purchases_created_at` (`created_at`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 购物车表
CREATE TABLE IF NOT EXISTS `carts` (
@@ -233,8 +267,12 @@ CREATE TABLE IF NOT EXISTS `carts` (
) 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`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'carts' AND INDEX_NAME = 'idx_carts_user_id');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `carts` ADD INDEX `idx_carts_user_id` (`user_id`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'carts' AND INDEX_NAME = 'idx_carts_selected');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `carts` ADD INDEX `idx_carts_selected` (`selected`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 退款表
CREATE TABLE IF NOT EXISTS `refunds` (
@@ -258,8 +296,12 @@ CREATE TABLE IF NOT EXISTS `refunds` (
) 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`);
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'refunds' AND INDEX_NAME = 'idx_refunds_status');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `refunds` ADD INDEX `idx_refunds_status` (`status`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @idx_exists = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'refunds' AND INDEX_NAME = 'idx_refunds_created_at');
SET @sql = IF(@idx_exists = 0, 'ALTER TABLE `refunds` ADD INDEX `idx_refunds_created_at` (`created_at`)', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- 首页分类配置表
CREATE TABLE IF NOT EXISTS `home_categories` (
@@ -326,6 +368,10 @@ CREATE TABLE IF NOT EXISTS `ai_cache` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI 响应缓存';
-- 迁移:order_items.order_id 从 int 改为 varchar(64),与 orders.id (varchar(50)) 保持一致
ALTER TABLE `order_items` DROP FOREIGN KEY IF EXISTS `order_items_ibfk_1`;
SET @fk_exists = (SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = 'order_items' AND CONSTRAINT_NAME = 'order_items_ibfk_1');
SET @sql = IF(@fk_exists > 0, 'ALTER TABLE `order_items` DROP FOREIGN KEY `order_items_ibfk_1`', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
ALTER TABLE `order_items` MODIFY COLUMN `order_id` varchar(64) NOT NULL COMMENT '订单ID';
ALTER TABLE `order_items` ADD CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE;
SET @fk_exists = (SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND TABLE_NAME = 'order_items' AND CONSTRAINT_NAME = 'order_items_ibfk_1');
SET @sql = IF(@fk_exists = 0, 'ALTER TABLE `order_items` ADD CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE', 'SELECT 1');
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+88
View File
@@ -0,0 +1,88 @@
// 执行 schema.sql 到远程数据库
// 用法:node run_schema.js
const mysql = require('mysql2/promise')
const fs = require('fs')
const path = require('path')
require('dotenv').config({ path: path.join(__dirname, '.env') })
const CONFIG = {
host: '110.42.255.239',
port: 3306,
user: 'root',
password: 'Wentian9588.',
database: 'miniprogram',
multipleStatements: true,
charset: 'utf8mb4'
}
const SCHEMA_PATH = path.join(__dirname, 'config', 'schema.sql')
function splitSql(sql) {
// 去掉 -- 注释行
const lines = sql.split('\n').map(l => l.replace(/--.*$/, '')).join('\n')
// 按 ; 切分(保留完整语句)
const stmts = []
let buf = ''
let inString = false
let stringChar = ''
for (let i = 0; i < lines.length; i++) {
const ch = lines[i]
if (inString) {
buf += ch
if (ch === stringChar && lines[i - 1] !== '\\') {
inString = false
}
continue
}
if (ch === "'" || ch === '"') {
inString = true
stringChar = ch
buf += ch
continue
}
if (ch === ';') {
const s = buf.trim()
if (s) stmts.push(s)
buf = ''
continue
}
buf += ch
}
const tail = buf.trim()
if (tail) stmts.push(tail)
return stmts
}
async function main() {
const sql = fs.readFileSync(SCHEMA_PATH, 'utf8')
const statements = splitSql(sql)
console.log(`解析到 ${statements.length} 条语句`)
const connection = await mysql.createConnection(CONFIG)
console.log(`已连接 ${CONFIG.host}:${CONFIG.port}/${CONFIG.database}`)
let ok = 0
let fail = 0
for (let i = 0; i < statements.length; i++) {
const stmt = statements[i]
const preview = stmt.replace(/\s+/g, ' ').slice(0, 80)
try {
await connection.query(stmt)
ok++
console.log(`[${i + 1}/${statements.length}] OK ${preview}`)
} catch (err) {
fail++
console.error(`[${i + 1}/${statements.length}] FAIL ${preview}`)
console.error(` ${err.code || ''} ${err.sqlMessage || err.message}`)
}
}
console.log(`\n完成 ✅ 成功 ${ok} / 失败 ${fail}`)
await connection.end()
}
main().catch(err => {
console.error('连接失败:', err.message)
process.exit(1)
})