feat(distribution): 独立分销后台账号体系、多租户隔离与主站后台分销管理GUI

- 独立账号鉴权:分销后台支持独立登录页面、Bearer+Cookie 双模鉴权与租户资料接口
- 多租户隔离:分销商成员强制锁定自身站点数据范围,防止横向越权;平台超管享有全局视界
- 一键开通分销商:平台端支持一键录入站点、管理员账号、分配角色并原子化初始化佣金规则
- 主后台分销分组:在主站后台增加「分销管理」导航分组,提供站点管理、规则配置、订单监控与结算管理 GUI
- 门禁与联调:补充完整单测,全量代码门禁 check.sh 100% 通过,dev.sh 支持前后端与分销后台一键同启
This commit is contained in:
yml2213
2026-09-07 15:45:53 +08:00
parent 0c37c7e659
commit 550b42eb22
28 changed files with 3192 additions and 117 deletions
+124 -31
View File
@@ -10,16 +10,23 @@ FRONTEND_PACKAGE_JSON="${FRONTEND_DIR}/package.json"
FRONTEND_PACKAGE_LOCK="${FRONTEND_DIR}/package-lock.json"
FRONTEND_NODE_MODULES="${FRONTEND_DIR}/node_modules"
FRONTEND_NODE_MODULES_LOCK="${FRONTEND_NODE_MODULES}/.package-lock.json"
DISTRIBUTION_DIR="${ROOT_DIR}/distribution-admin"
DISTRIBUTION_PACKAGE_JSON="${DISTRIBUTION_DIR}/package.json"
DISTRIBUTION_PACKAGE_LOCK="${DISTRIBUTION_DIR}/package-lock.json"
DISTRIBUTION_NODE_MODULES="${DISTRIBUTION_DIR}/node_modules"
DISTRIBUTION_NODE_MODULES_LOCK="${DISTRIBUTION_NODE_MODULES}/.package-lock.json"
MIGRATIONS_DIR="${ROOT_DIR}/backend/migrations"
MYSQL_DATABASE="${DEV_MYSQL_DATABASE:-hfb_sys}"
BACKEND_PORT="${DEV_BACKEND_PORT:-18088}"
FRONTEND_PORT="${DEV_FRONTEND_PORT:-5189}"
DISTRIBUTION_PORT="${DEV_DISTRIBUTION_PORT:-5190}"
MYSQL_PORT="${DEV_MYSQL_PORT:-13306}"
REDIS_PORT="${DEV_REDIS_PORT:-16379}"
MINIO_API_PORT="${DEV_MINIO_API_PORT:-19090}"
MINIO_CONSOLE_PORT="${DEV_MINIO_CONSOLE_PORT:-19091}"
BACKEND_HEALTH_URL="${DEV_BACKEND_HEALTH_URL:-http://127.0.0.1:${BACKEND_PORT}/health}"
FRONTEND_HEALTH_URL="${DEV_FRONTEND_HEALTH_URL:-http://127.0.0.1:${FRONTEND_PORT}/}"
DISTRIBUTION_HEALTH_URL="${DEV_DISTRIBUTION_HEALTH_URL:-http://127.0.0.1:${DISTRIBUTION_PORT}/}"
HTTP_READY_TIMEOUT="${DEV_HTTP_READY_TIMEOUT:-120}"
AUTO_KILL_PORTS="${DEV_AUTO_KILL_PORTS:-1}"
FORCE_NPM_CI="${DEV_FORCE_NPM_CI:-0}"
@@ -28,6 +35,7 @@ GOOSE_BIN="${GOOSE_BIN:-}"
BACKEND_PID=""
FRONTEND_PID=""
DISTRIBUTION_PID=""
CLEANED_UP=0
# 颜色定义
@@ -65,41 +73,46 @@ show_help() {
用法: ./scripts/dev.sh [选项]
选项:
--reset-db 重置数据库(删除并重建)
--no-migrate 不执行数据库迁移
--no-frontend 不启动前端
--no-backend 不启动后端
-h, --help 显示帮助信息
--reset-db 重置数据库(删除并重建)
--no-migrate 不执行数据库迁移
--no-frontend 不启动主站前端
--no-backend 不启动后端
--no-distribution 不启动分销后台
-h, --help 显示帮助信息
环境变量:
DEV_RESET_DB=1 等同于 --reset-db
DEV_RESET_DB=1 等同于 --reset-db
DEV_MYSQL_DATABASE=hfb_sys
开发数据库名
开发数据库名
DEV_BACKEND_PORT=18088
后端启动端口
后端启动端口
DEV_FRONTEND_PORT=5189
前端启动端口
主站前端启动端口
DEV_DISTRIBUTION_PORT=5190
分销后台启动端口
DEV_MYSQL_PORT=13306
MySQL 宿主机映射端口
MySQL 宿主机映射端口
DEV_REDIS_PORT=16379
Redis 宿主机映射端口
Redis 宿主机映射端口
DEV_MINIO_API_PORT=19090
MinIO API 宿主机映射端口
MinIO API 宿主机映射端口
DEV_MINIO_CONSOLE_PORT=19091
MinIO 控制台宿主机映射端口
MinIO 控制台宿主机映射端口
DEV_BACKEND_HEALTH_URL=http://127.0.0.1:18088/health
后端健康检查地址
后端健康检查地址
DEV_FRONTEND_HEALTH_URL=http://127.0.0.1:5189/
前端健康检查地址
主站前端健康检查地址
DEV_DISTRIBUTION_HEALTH_URL=http://127.0.0.1:5190/
分销后台健康检查地址
DEV_HTTP_READY_TIMEOUT=120
HTTP 服务就绪等待秒数
HTTP 服务就绪等待秒数
DEV_AUTO_KILL_PORTS=1
前后端端口被占用时自动停止占用的 Docker 容器或本机进程
前后端端口被占用时自动停止占用的 Docker 容器或本机进程
DEV_FORCE_NPM_CI=0
强制重新执行前端 npm ci
强制重新执行前端 npm ci
示例:
./scripts/dev.sh # 正常启动
./scripts/dev.sh # 正常启动(后端 + 主前后台 + 分销前台)
./scripts/dev.sh --reset-db # 重置数据库后启动(推荐迁移后使用)
EOF
exit 0
@@ -110,6 +123,7 @@ RESET_DB="${DEV_RESET_DB:-0}"
NO_MIGRATE=0
NO_FRONTEND=0
NO_BACKEND=0
NO_DISTRIBUTION=0
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -129,6 +143,10 @@ while [[ $# -gt 0 ]]; do
NO_BACKEND=1
shift
;;
--no-distribution)
NO_DISTRIBUTION=1
shift
;;
-h|--help)
show_help
;;
@@ -145,11 +163,14 @@ cleanup() {
fi
CLEANED_UP=1
if [[ -z "${FRONTEND_PID}" && -z "${BACKEND_PID}" ]]; then
if [[ -z "${FRONTEND_PID}" && -z "${BACKEND_PID}" && -z "${DISTRIBUTION_PID}" ]]; then
return
fi
log "正在停止前后端开发进程..."
if [[ -n "${DISTRIBUTION_PID}" ]] && kill -0 "${DISTRIBUTION_PID}" >/dev/null 2>&1; then
kill "${DISTRIBUTION_PID}" >/dev/null 2>&1 || true
fi
if [[ -n "${FRONTEND_PID}" ]] && kill -0 "${FRONTEND_PID}" >/dev/null 2>&1; then
kill "${FRONTEND_PID}" >/dev/null 2>&1 || true
fi
@@ -540,6 +561,64 @@ start_frontend() {
FRONTEND_PID="$!"
}
distribution_dependencies_need_ci() {
if [[ "${FORCE_NPM_CI}" == "1" ]]; then
return 0
fi
if [[ ! -d "${DISTRIBUTION_NODE_MODULES}" ]]; then
return 0
fi
if [[ ! -f "${DISTRIBUTION_NODE_MODULES_LOCK}" ]]; then
return 0
fi
if [[ "${DISTRIBUTION_PACKAGE_JSON}" -nt "${DISTRIBUTION_NODE_MODULES_LOCK}" || "${DISTRIBUTION_PACKAGE_LOCK}" -nt "${DISTRIBUTION_NODE_MODULES_LOCK}" ]]; then
return 0
fi
(
cd "${DISTRIBUTION_DIR}"
npm ls --depth=0 >/dev/null 2>&1
) || return 0
return 1
}
ensure_distribution_dependencies() {
if [[ ! -f "${DISTRIBUTION_PACKAGE_LOCK}" ]]; then
log_error "缺少 distribution-admin/package-lock.json,无法执行 npm ci"
exit 1
fi
if distribution_dependencies_need_ci; then
log "同步分销后台依赖(npm ci)..."
(
cd "${DISTRIBUTION_DIR}"
npm ci
)
log_success "分销后台依赖已同步"
else
log "分销后台依赖已同步,跳过 npm ci"
fi
}
start_distribution() {
ensure_distribution_dependencies
log "启动分销后台:http://localhost:${DISTRIBUTION_PORT}"
(
cd "${DISTRIBUTION_DIR}"
export PORT="${DISTRIBUTION_PORT}"
export DEV_DISTRIBUTION_PORT="${DISTRIBUTION_PORT}"
export DEV_BACKEND_PORT="${BACKEND_PORT}"
export VITE_API_PROXY_TARGET="${VITE_API_PROXY_TARGET:-http://127.0.0.1:${BACKEND_PORT}}"
npm run dev
) &
DISTRIBUTION_PID="$!"
}
watch_processes() {
while true; do
if [[ -n "${BACKEND_PID}" ]] && ! kill -0 "${BACKEND_PID}" >/dev/null 2>&1; then
@@ -549,7 +628,12 @@ watch_processes() {
fi
if [[ -n "${FRONTEND_PID}" ]] && ! kill -0 "${FRONTEND_PID}" >/dev/null 2>&1; then
wait "${FRONTEND_PID}" || true
printf "前端进程已退出\n" >&2
printf "主站前端进程已退出\n" >&2
exit 1
fi
if [[ -n "${DISTRIBUTION_PID}" ]] && ! kill -0 "${DISTRIBUTION_PID}" >/dev/null 2>&1; then
wait "${DISTRIBUTION_PID}" || true
printf "分销后台进程已退出\n" >&2
exit 1
fi
sleep 2
@@ -561,10 +645,10 @@ main() {
if [[ "${NO_BACKEND}" == "0" ]]; then
need_cmd go
fi
if [[ "${NO_FRONTEND}" == "0" ]]; then
if [[ "${NO_FRONTEND}" == "0" || "${NO_DISTRIBUTION}" == "0" ]]; then
need_cmd npm
fi
if [[ "${NO_BACKEND}" == "0" || "${NO_FRONTEND}" == "0" ]]; then
if [[ "${NO_BACKEND}" == "0" || "${NO_FRONTEND}" == "0" || "${NO_DISTRIBUTION}" == "0" ]]; then
need_cmd curl
fi
@@ -603,24 +687,33 @@ main() {
fi
if [[ "${NO_FRONTEND}" == "0" ]]; then
ensure_port_free "${FRONTEND_PORT}" "前端"
ensure_port_free "${FRONTEND_PORT}" "主站前端"
start_frontend
wait_http_ready "${FRONTEND_HEALTH_URL}" "前端" "${FRONTEND_PID}"
wait_http_ready "${FRONTEND_HEALTH_URL}" "主站前端" "${FRONTEND_PID}"
fi
if [[ -z "${BACKEND_PID}" && -z "${FRONTEND_PID}" ]]; then
log "仅启动 Docker 开发依赖,前后端未启动"
if [[ "${NO_DISTRIBUTION}" == "0" && -d "${DISTRIBUTION_DIR}" ]]; then
ensure_port_free "${DISTRIBUTION_PORT}" "分销后台"
start_distribution
wait_http_ready "${DISTRIBUTION_HEALTH_URL}" "分销后台" "${DISTRIBUTION_PID}"
fi
if [[ -z "${BACKEND_PID}" && -z "${FRONTEND_PID}" && -z "${DISTRIBUTION_PID}" ]]; then
log "仅启动 Docker 开发依赖,前后端与分销端未启动"
return 0
fi
log_success "开发环境已启动"
log_success "开发环境已就绪"
if [[ "${NO_FRONTEND}" == "0" ]]; then
local admin_base_path="${VITE_ADMIN_BASE_PATH:-/admin}"
log "前台:http://localhost:${FRONTEND_PORT}"
log "后台:http://localhost:${FRONTEND_PORT}${admin_base_path%/}/login"
log "前台地址:http://localhost:${FRONTEND_PORT}"
log "后台地址:http://localhost:${FRONTEND_PORT}${admin_base_path%/}/login"
fi
if [[ "${NO_DISTRIBUTION}" == "0" && -d "${DISTRIBUTION_DIR}" ]]; then
log "分销后台:http://localhost:${DISTRIBUTION_PORT}"
fi
log "按 Ctrl+C 停止前后端;Docker 依赖会保留运行"
log "按 Ctrl+C 停止所有服务;Docker 依赖会保留运行"
watch_processes
}