This commit is contained in:
zlwu committed 2026-05-12 16:35:07 +08:00
1 parent d336fd0226
commit 2e9d020d50
3454 files changed
+878963 -319

No files matched your search

@@ -0,0 +1,17 @@
-- /api/web/admin/api-request-logs 列表性能索引补强
-- 典型查询:
-- WHERE is_del=0 [AND http_method=?] [AND status_code=?]
-- [AND create_at between ? and ?]
-- ORDER BY create_at DESC
-- 默认列表 + 时间倒序分页
ALTER TABLE sys_api_request_log
ADD INDEX idx_arl_isdel_createat (is_del, create_at);
-- 按方法过滤
ALTER TABLE sys_api_request_log
ADD INDEX idx_arl_isdel_method_createat (is_del, http_method, create_at);
-- 按状态码过滤
ALTER TABLE sys_api_request_log
ADD INDEX idx_arl_isdel_status_createat (is_del, status_code, create_at);
@@ -0,0 +1,12 @@
-- 给 /api/web/admin/products 列表(GET,前端加密传输时为 POST + X-HTTP-Method-Override: GET)做索引补强
-- 典型 SQL:WHERE is_del = 0 [AND category_id = ?] [AND (name LIKE ... OR description LIKE ...)] ORDER BY create_at DESC
-- LIKE '%keyword%' 无法有效走 name/description 索引;无关键词时下面索引可帮助 COUNT + 分页排序。
-- 执行前请先 SHOW INDEX FROM bas_product; 若索引已存在可跳过对应语句。
-- 1) 全量列表 / 仅启用筛选:is_del + 排序字段
ALTER TABLE bas_product
ADD INDEX idx_product_isdel_createat (is_del, create_at);
-- 2) 按分类筛选 + 排序(与 1 并存;分类列表走此索引更省扫描)
ALTER TABLE bas_product
ADD INDEX idx_product_isdel_category_createat (is_del, category_id, create_at);
@@ -0,0 +1,26 @@
-- /api/web/delivery-tasks 列表性能索引补强
-- 目标查询特征:
-- 1) WHERE is_del=0 ORDER BY create_at DESC(默认列表)
-- 2) WHERE is_del=0 AND region_id=? AND status=? ORDER BY create_at DESC
-- 3) WHERE is_del=0 AND deliver_id=? ORDER BY create_at DESC
-- 4) 列表中按 task_id 聚合明细数量(biz_delivery_task_item)
-- 1) 默认列表与时间倒序分页
ALTER TABLE biz_delivery_task
ADD INDEX idx_dt_isdel_createat (is_del, create_at);
-- 2) 区域 + 状态过滤场景
ALTER TABLE biz_delivery_task
ADD INDEX idx_dt_isdel_region_status_createat (is_del, region_id, status, create_at);
-- 3) 配送员维度过滤场景
ALTER TABLE biz_delivery_task
ADD INDEX idx_dt_isdel_deliver_createat (is_del, deliver_id, create_at);
-- 4) 供应商维度过滤场景(供应商端任务列表)
ALTER TABLE biz_delivery_task
ADD INDEX idx_dt_isdel_supplier_createat (is_del, supplier_id, create_at);
-- 5) 列表 itemCount 聚合:WHERE task_id IN (...) AND is_del=0 GROUP BY task_id
ALTER TABLE biz_delivery_task_item
ADD INDEX idx_dti_task_isdel (task_id, is_del);
@@ -0,0 +1,21 @@
-- 给 /api/web/admin/orders 列表接口做性能索引补强(幂等尽量实现)
-- 注意:不同 MySQL 版本对 IF NOT EXISTS 支持不同;若执行失败,请先 SHOW INDEX 再手动判断是否已存在。
-- 1) 覆盖最常见列表:WHERE is_del=0 ORDER BY create_at DESC
-- 目标:减少 Using filesort / 全表扫描风险
ALTER TABLE biz_order
ADD INDEX idx_order_isdel_createat (is_del, create_at);
-- 2) 常用筛选:区域 + 订单主状态 + 时间排序
ALTER TABLE biz_order
ADD INDEX idx_order_isdel_region_orderstatus_createat (is_del, region_id, order_status, create_at);
-- 3) 常用筛选:区域 + 创建时间范围(startDate/endDate)
ALTER TABLE biz_order
ADD INDEX idx_order_isdel_region_createat (is_del, region_id, create_at);
-- 4) 单号查询建议:
-- 当前实现是 order_no LIKE '%xxx%',该写法通常无法走索引。
-- 若业务可改为前缀匹配(LIKE 'xxx%')或精确匹配(=),将显著提升性能。
-- 订单号已有唯一索引 uk_order_no(可用于 '=' 或前缀匹配)。
@@ -0,0 +1,20 @@
-- /api/web/admin/price-versions 列表性能索引补强
-- 典型查询:
-- WHERE is_del=0 [AND region_id=?] [AND supplier_id=?] [AND status=?]
-- ORDER BY create_at DESC
-- 默认列表 + 时间倒序分页
ALTER TABLE biz_price_list
ADD INDEX idx_pl_isdel_createat (is_del, create_at);
-- 区域维度过滤
ALTER TABLE biz_price_list
ADD INDEX idx_pl_isdel_region_createat (is_del, region_id, create_at);
-- 供应商维度过滤
ALTER TABLE biz_price_list
ADD INDEX idx_pl_isdel_supplier_createat (is_del, supplier_id, create_at);
-- 状态维度过滤
ALTER TABLE biz_price_list
ADD INDEX idx_pl_isdel_status_createat (is_del, status, create_at);
@@ -0,0 +1,7 @@
-- 询价报价界面“区域”下拉/Tab:启用区域列表索引补强
-- 对应接口:GET /api/web/admin/regions/active
-- 查询:WHERE is_del=0 AND is_active=1 ORDER BY sort_order
ALTER TABLE sys_region
ADD INDEX idx_region_isdel_active_sort (is_del, is_active, sort_order);
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""清空订单及相关表(开发调试用)。用法:在 backend 目录执行 python scripts/clear_biz_orders.py"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from sqlalchemy import text
from app.core.database import engine
TABLES = [
"biz_order_status_log_his",
"biz_order_status_log",
"biz_order_item",
"biz_order_his",
"biz_order_adjust_his",
"biz_order_adjust",
"biz_wechat_pay",
"biz_refund_his",
"biz_refund",
"biz_settlement_his",
"biz_settlement",
"biz_invoice",
"biz_deliverer_review_his",
"biz_deliverer_review",
"biz_delivery_warning_his",
"biz_delivery_warning",
"biz_delivery_task_item",
"biz_delivery_task_his",
"biz_delivery_task",
"biz_order",
]
def main() -> None:
with engine.begin() as conn:
conn.execute(text("SET FOREIGN_KEY_CHECKS=0"))
for t in TABLES:
conn.execute(text(f"TRUNCATE TABLE `{t}`"))
conn.execute(text("SET FOREIGN_KEY_CHECKS=1"))
print("已清空订单及相关表:", ", ".join(TABLES))
if __name__ == "__main__":
main()
+195
View File
@@ -0,0 +1,195 @@
#!/usr/bin/env python3
"""批量替换数据库历史 URL 前缀(MinIO 域名迁移脚本)。
用法(在 backend 目录执行):
1) 先预览:
python scripts/migrate_minio_urls.py --dry-run
2) 正式执行:
python scripts/migrate_minio_urls.py
3) 自定义前缀:
python scripts/migrate_minio_urls.py \
--old-prefix "https://www.chengyantek.cn:20004" \
--new-prefix "https://www.chengyantek.cn"
"""
from __future__ import annotations
import argparse
import sys
from datetime import datetime
from pathlib import Path
from sqlalchemy import text
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.core.database import engine # noqa: E402
DEFAULT_OLD_PREFIX = "https://www.chengyantek.cn:20004"
DEFAULT_NEW_PREFIX = "https://www.chengyantek.cn"
TEXT_TYPES = (
"char",
"varchar",
"tinytext",
"text",
"mediumtext",
"longtext",
)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="批量替换数据库中的 URL 前缀")
parser.add_argument("--old-prefix", default=DEFAULT_OLD_PREFIX, help="旧 URL 前缀")
parser.add_argument("--new-prefix", default=DEFAULT_NEW_PREFIX, help="新 URL 前缀")
parser.add_argument(
"--dry-run",
action="store_true",
help="仅预览受影响记录数,不执行更新",
)
return parser.parse_args()
def quote_ident(name: str) -> str:
"""安全引用 MySQL 标识符。"""
return f"`{name.replace('`', '``')}`"
def get_text_columns(conn) -> list[tuple[str, str]]:
sql = text(
"""
SELECT table_name, column_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
AND data_type IN :types
ORDER BY table_name, ordinal_position
"""
)
rows = conn.execute(sql, {"types": TEXT_TYPES}).fetchall()
return [(r[0], r[1]) for r in rows]
def count_affected(conn, table_name: str, column_name: str, old_prefix: str) -> int:
sql = text(
f"""
SELECT COUNT(*) AS cnt
FROM {quote_ident(table_name)}
WHERE {quote_ident(column_name)} LIKE :pattern
"""
)
return int(conn.execute(sql, {"pattern": f"%{old_prefix}%"}).scalar() or 0)
def update_column(
conn,
table_name: str,
column_name: str,
old_prefix: str,
new_prefix: str,
) -> int:
sql = text(
f"""
UPDATE {quote_ident(table_name)}
SET {quote_ident(column_name)} = REPLACE({quote_ident(column_name)}, :old_prefix, :new_prefix)
WHERE {quote_ident(column_name)} LIKE :pattern
"""
)
result = conn.execute(
sql,
{
"old_prefix": old_prefix,
"new_prefix": new_prefix,
"pattern": f"%{old_prefix}%",
},
)
return int(result.rowcount or 0)
def make_backup_table_name(table_name: str, suffix: str) -> str:
"""生成备份表名,控制在 MySQL 64 字符限制内。"""
max_table_len = 64
reserve = len("_bak_") + len(suffix)
max_base_len = max_table_len - reserve
base = table_name[:max_base_len]
return f"{base}_bak_{suffix}"
def backup_table(conn, table_name: str, backup_table_name: str) -> None:
"""备份单张表(结构+数据)。"""
conn.execute(
text(
f"CREATE TABLE {quote_ident(backup_table_name)} LIKE {quote_ident(table_name)}"
)
)
conn.execute(
text(
f"INSERT INTO {quote_ident(backup_table_name)} SELECT * FROM {quote_ident(table_name)}"
)
)
def main() -> None:
args = parse_args()
old_prefix = args.old_prefix.strip().rstrip("/")
new_prefix = args.new_prefix.strip().rstrip("/")
dry_run = args.dry_run
if not old_prefix or not new_prefix:
raise ValueError("old-prefix/new-prefix 不能为空")
if old_prefix == new_prefix:
raise ValueError("old-prefix 与 new-prefix 不能相同")
print(f"数据库 URL 迁移开始,模式:{'预览(dry-run)' if dry_run else '正式执行'}")
print(f"旧前缀: {old_prefix}")
print(f"新前缀: {new_prefix}")
total_rows = 0
hit_columns = 0
affected_items: list[tuple[str, str, int]] = []
with engine.begin() as conn:
columns = get_text_columns(conn)
for table_name, column_name in columns:
affected = count_affected(conn, table_name, column_name, old_prefix)
if affected <= 0:
continue
affected_items.append((table_name, column_name, affected))
if not affected_items:
print("未发现需要替换的数据,已结束。")
return
if not dry_run:
backup_suffix = datetime.now().strftime("%Y%m%d%H%M%S")
backup_tables: dict[str, str] = {}
for table_name, _, _ in affected_items:
if table_name in backup_tables:
continue
backup_name = make_backup_table_name(table_name, backup_suffix)
backup_table(conn, table_name, backup_name)
backup_tables[table_name] = backup_name
print(f"[BACKUP] {table_name} -> {backup_name}")
print(f"备份完成:共备份 {len(backup_tables)} 张表。")
for table_name, column_name, affected in affected_items:
hit_columns += 1
if dry_run:
print(f"[DRY-RUN] {table_name}.{column_name} -> {affected} 行")
total_rows += affected
continue
updated = update_column(conn, table_name, column_name, old_prefix, new_prefix)
print(f"[UPDATED] {table_name}.{column_name} -> {updated} 行")
total_rows += updated
if dry_run:
print(f"预览完成:共 {hit_columns} 个字段命中,预计影响 {total_rows} 行。")
else:
print(f"执行完成:共更新 {hit_columns} 个字段,影响 {total_rows} 行。")
if __name__ == "__main__":
main()
@@ -0,0 +1,68 @@
-- 运营端权限改造:super_admin + 多区域 + 菜单 code 授权
-- 1) 角色迁移:admin -> super_admin
UPDATE sys_user
SET role = 'super_admin',
update_at = NOW()
WHERE is_del = 0
AND role = 'admin';
-- 2) 用户-区域权限表
CREATE TABLE IF NOT EXISTS sys_user_region_scope (
id VARCHAR(32) NOT NULL COMMENT '主键ID',
user_id VARCHAR(32) NOT NULL COMMENT '用户ID',
region_id VARCHAR(32) NOT NULL COMMENT '区域ID',
is_del TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
create_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人ID',
create_name VARCHAR(100) DEFAULT NULL COMMENT '创建人姓名',
update_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
update_by VARCHAR(32) DEFAULT NULL COMMENT '修改人ID',
PRIMARY KEY (id),
UNIQUE KEY uk_user_region_scope (user_id, region_id, is_del),
KEY idx_user_region_scope_user (user_id, is_del),
KEY idx_user_region_scope_region (region_id, is_del)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户区域权限';
-- 3) 用户-区域权限历史表(无索引、无 is_del)
CREATE TABLE IF NOT EXISTS sys_user_region_scope_his (
id VARCHAR(32) NOT NULL COMMENT '主键ID',
user_id VARCHAR(32) NOT NULL COMMENT '用户ID',
region_id VARCHAR(32) NOT NULL COMMENT '区域ID',
create_at DATETIME DEFAULT NULL COMMENT '创建时间',
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人ID',
create_name VARCHAR(100) DEFAULT NULL COMMENT '创建人姓名',
update_at DATETIME DEFAULT NULL COMMENT '修改时间',
update_by VARCHAR(32) DEFAULT NULL COMMENT '修改人ID',
his_create_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '历史记录创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户区域权限历史';
-- 4) 用户-菜单 code 权限表
CREATE TABLE IF NOT EXISTS sys_user_menu_scope (
id VARCHAR(32) NOT NULL COMMENT '主键ID',
user_id VARCHAR(32) NOT NULL COMMENT '用户ID',
menu_code VARCHAR(100) NOT NULL COMMENT '菜单编码',
is_del TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除',
create_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人ID',
create_name VARCHAR(100) DEFAULT NULL COMMENT '创建人姓名',
update_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
update_by VARCHAR(32) DEFAULT NULL COMMENT '修改人ID',
PRIMARY KEY (id),
UNIQUE KEY uk_user_menu_scope (user_id, menu_code, is_del),
KEY idx_user_menu_scope_user (user_id, is_del),
KEY idx_user_menu_scope_code (menu_code, is_del)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户菜单权限';
-- 5) 用户-菜单 code 权限历史表(无索引、无 is_del)
CREATE TABLE IF NOT EXISTS sys_user_menu_scope_his (
id VARCHAR(32) NOT NULL COMMENT '主键ID',
user_id VARCHAR(32) NOT NULL COMMENT '用户ID',
menu_code VARCHAR(100) NOT NULL COMMENT '菜单编码',
create_at DATETIME DEFAULT NULL COMMENT '创建时间',
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人ID',
create_name VARCHAR(100) DEFAULT NULL COMMENT '创建人姓名',
update_at DATETIME DEFAULT NULL COMMENT '修改时间',
update_by VARCHAR(32) DEFAULT NULL COMMENT '修改人ID',
his_create_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '历史记录创建时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户菜单权限历史';