Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22ca6e0836 | ||
|
|
14b65e2a4a | ||
|
|
0f16e370ea | ||
|
|
fd031fa6b0 | ||
|
|
20383f4d2c | ||
|
|
1b021e2748 | ||
|
|
e69e634138 | ||
|
|
acea548670 | ||
|
|
8164e84b46 | ||
|
|
b083aa2cb2 | ||
|
|
6eea4ddfb8 | ||
|
|
391a837b80 | ||
|
|
acf41f6564 | ||
|
|
ab1f34d56b | ||
|
|
655385630b | ||
|
|
e21bd27813 | ||
|
|
82ccbda9c8 | ||
|
|
176592fbff | ||
|
|
3a3b0e7e79 | ||
|
|
68d9e3520a | ||
|
|
4249c7f3cd | ||
|
|
c2e7b8f12a | ||
|
|
dabb053b19 | ||
|
|
f0bd70ff6e | ||
|
|
1619e41c20 | ||
|
|
1aef90eff5 | ||
|
|
aa5cc483ec | ||
|
|
caec1d864c | ||
|
|
5e368f6c4e | ||
|
|
31b5d183da | ||
|
|
af9655c548 | ||
|
|
fffdd7e4c5 | ||
|
|
aa0724dc49 | ||
|
|
6c4d800c26 | ||
|
|
a94140fdb8 | ||
|
|
e8eaee0e77 | ||
|
|
e02dcc23da |
@@ -1,9 +0,0 @@
|
||||
# 开发环境 (dev):仅 vite dev 加载(--mode dev)。值可自由指本地或线上地址。
|
||||
# build 产物不读此文件(build 默认 production mode,只认 .env / .env.production,两者均不存在),
|
||||
# 线上环境差异全部由 projman 的 env-config-{目标}.json 承担。
|
||||
VITE_BASE_HOST=http://base.bestsch.com
|
||||
VITE_SSO_HOST=http://sso.yunhaixingtong.com
|
||||
VITE_SERVICE_HOST=http://localhost:8060
|
||||
VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk
|
||||
VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
@@ -0,0 +1,144 @@
|
||||
name: ci-userbase-service
|
||||
|
||||
# 后端标准化 CI:maven 构建 jar -> docker build -> 推 Nexus docker-hosted
|
||||
# 触发:push 到 main(仅 service 或本文件变更)/ 手动 Run workflow
|
||||
# 依赖仓库 Actions Secrets:NEXUS_USER / NEXUS_PASSWORD(ci-docker 账号,仅 docker-hosted 推送权)
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'service/**'
|
||||
- '.gitea/workflows/ci.yml'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'image tag (default = short sha)'
|
||||
required: false
|
||||
default: 'auto'
|
||||
deploy:
|
||||
description: 'deploy to 160 after build (auto / skip)'
|
||||
required: false
|
||||
default: 'auto'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
# docker:24 自带 docker CLI,复用宿主 docker daemon。
|
||||
# 注意:act_runner v0.6.1 默认已把宿主 docker.sock 挂入每个 job 容器,
|
||||
# 切勿在此再声明 volumes: docker.sock(会与默认挂载重复,报
|
||||
# "Duplicate mount point" 导致容器创建失败、全部 step cancelled)。
|
||||
container:
|
||||
image: docker:24
|
||||
steps:
|
||||
# docker:24 基于 alpine,默认无 git;先装 git 再手动 clone 本仓库
|
||||
- name: Install git (alpine)
|
||||
run: |
|
||||
# 统一走 aliyun 源:dl-cdn 时段性被拦会卡满 60s×2(2026-10-20 事故)
|
||||
sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories
|
||||
apk add --no-cache git
|
||||
|
||||
# 内网 Gitea 无法访问 github.com 拉取 actions/checkout,改为直接 clone 本仓库
|
||||
- name: Checkout (manual, internal Gitea)
|
||||
run: |
|
||||
git clone --depth 1 --branch main http://192.168.10.160:3000/${GITHUB_REPOSITORY}.git /tmp/src
|
||||
cp -a /tmp/src/. .
|
||||
rm -rf /tmp/src
|
||||
|
||||
- name: Compute image tag
|
||||
id: meta
|
||||
run: |
|
||||
if [ "${{ inputs.tag }}" = "auto" ] || [ -z "${{ inputs.tag }}" ]; then
|
||||
echo "tag=$(echo ${GITHUB_SHA} | cut -c1-7)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Build jar with Maven
|
||||
run: |
|
||||
# 注意:job 容器通过 docker.sock 调的是【宿主 docker daemon】,
|
||||
# 因此 docker run -v <path> 里的路径会被宿主按【宿主路径】解析;
|
||||
# 而 act 工作区是宿主上的 docker volume,容器内 $PWD 对宿主不可见,
|
||||
# 直接 -v $PWD:/build 会绑成空目录导致找不到 pom.xml。
|
||||
# 故改为:起 maven 容器 -> docker cp 源码入容器 -> exec 构建 -> cp 产物回工作区。
|
||||
# mavenbuild 加入 infra-infra-net,用容器名 nexus 直连 Nexus(8081 maven-public)。
|
||||
# 之前用 --add-host=nexus.yunhaixingtong.com:host-gateway 经 Caddy:80 反代,但该路径在
|
||||
# CI 容器内不可达,maven 回退到 /opt/docker/ubs-m2 里 08-05 的旧 yh-auth 缓存(缺
|
||||
# CasSsoAutoConfiguration),镜像启动报 serverName 崩溃。现改用 settings.xml mirror 把
|
||||
# pom 的 HTTP 仓库重定向到 http://nexus:8081/repository/maven-public/(已验证 -7 可达)。
|
||||
# 先清理上次失败残留的同名容器(set -e 下末尾的 rm 不会执行)
|
||||
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||
BUILD_CONTAINER="mavenbuild-${GITHUB_RUN_ID:-0}-$(date +%s)"
|
||||
echo "BUILD_CONTAINER=${BUILD_CONTAINER}"
|
||||
docker rm -f "$BUILD_CONTAINER" 2>/dev/null || true
|
||||
docker run -d --name "$BUILD_CONTAINER" \
|
||||
--network infra-infra-net \
|
||||
-v /opt/docker/ubs-m2:/root/.m2 \
|
||||
-w /build maven:3.6.3-jdk-8 sleep 3600
|
||||
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||
# 写 settings.xml:新增 profile 把 nexus:8081 maven-public 作为额外仓库(infra-infra-net 内容器名直连,已验证 -7 可达)。
|
||||
# 不用 mirror(external:http:*) 是因为它对 BOM import 解析未生效;profile 方式更可靠。
|
||||
# pom 原有的 nexus.yunhaixingtong.com(Caddy:80) 在 CI 容器内不可解析 → 快速失败,maven 转而用本仓库。
|
||||
docker exec "$BUILD_CONTAINER" sh -c "printf '%s' '<settings><profiles><profile><id>nexus-repo</id><repositories><repository><id>nexus-internal</id><url>http://nexus:8081/repository/maven-public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled><updatePolicy>always</updatePolicy></snapshots></repository></repositories></profile></profiles><activeProfiles><activeProfile>nexus-repo</activeProfile></activeProfiles></settings>' > /root/.m2/settings.xml"
|
||||
# 仅清旧 yh-auth 缓存(含 08-05 local install 的 maven-metadata-local.xml,避免本地安装优先于远端 -7);
|
||||
# BOM 等其它 yh.platform 模块保留缓存(Nexus 上它们仍是 08-05 的 -1,缓存与远端一致)。
|
||||
docker exec "$BUILD_CONTAINER" rm -rf /root/.m2/repository/com/yunhai/platform/yh-auth-spring-boot-starter
|
||||
docker exec "$BUILD_CONTAINER" sh -c 'cd /build/service && mvn -U -B -DskipTests clean package'
|
||||
mkdir -p "${PWD}/service/target"
|
||||
docker cp "$BUILD_CONTAINER":/build/service/target/userbase.jar "${PWD}/service/target/userbase.jar"
|
||||
docker rm -f "$BUILD_CONTAINER"
|
||||
|
||||
- name: Login to Nexus docker registry
|
||||
run: echo "${{ secrets.NEXUS_PASSWORD }}" | docker login nexus:8083 -u ${{ secrets.NEXUS_USER }} --password-stdin
|
||||
|
||||
- name: Build image
|
||||
run: docker build -t nexus:8083/docker-hosted/userbase-service:${{ steps.meta.outputs.tag }} -f service/docker/Dockerfile service
|
||||
|
||||
- name: Push image
|
||||
run: docker push nexus:8083/docker-hosted/userbase-service:${{ steps.meta.outputs.tag }}
|
||||
|
||||
- name: Report build
|
||||
run: echo "pushed nexus:8083/docker-hosted/userbase-service:${{ steps.meta.outputs.tag }}"
|
||||
|
||||
# —— 部署阶段(仅在 push 或手动 dispatch deploy=auto 时执行)——
|
||||
# 运行在 160 的 act_runner 上,复用宿主 docker daemon(docker.sock 由 act_runner 自动挂载),
|
||||
# 因此 docker pull / run 直接作用于 160 宿主,等价于在 160 上执行部署。
|
||||
- name: Deploy (pull + rollout with rollback)
|
||||
if: github.event_name == 'push' || inputs.deploy == 'auto'
|
||||
run: |
|
||||
set +e
|
||||
IMG=nexus:8083/docker-hosted/userbase-service:${{ steps.meta.outputs.tag }}
|
||||
echo "deploying $IMG"
|
||||
echo "${{ secrets.NEXUS_PASSWORD }}" | docker login nexus:8083 -u ${{ secrets.NEXUS_USER }} --password-stdin
|
||||
docker pull "$IMG"
|
||||
# 回滚锚:保留当前 userbase-service:latest(已知良好镜像),再额外打一个时间点标签
|
||||
RB=userbase-service:rb-$(date +%s)
|
||||
docker tag userbase-service:latest "$RB" 2>/dev/null || true
|
||||
docker stop userbase-service 2>/dev/null; docker rm -f userbase-service 2>/dev/null
|
||||
# 注意(SCA 2.2.4 坑): 命名空间必须用标准属性名 SPRING_CLOUD_NACOS_CONFIG_NAMESPACE,
|
||||
# 用 NACOS_NAMESPACE 这个 env 名不会被 bootstrap 的 ${NACOS_NAMESPACE:dev} 解析到(会落 public);
|
||||
# 凭据必须放顶层 SPRING_CLOUD_NACOS_USERNAME/PASSWORD, 放在 spring.cloud.nacos.config.* 下不生效(403)。
|
||||
docker run -d --name userbase-service --restart unless-stopped --network infra-infra-net -p 8060:8060 \
|
||||
-e NACOS_ADDR=nacos:8848 \
|
||||
-e SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=6196f0de-a28d-4894-8f10-e2982b57c6bc \
|
||||
-e SPRING_CLOUD_NACOS_USERNAME=yh-reader \
|
||||
-e SPRING_CLOUD_NACOS_PASSWORD=123456 \
|
||||
"$IMG"
|
||||
# 健康检查:Spring 起来后 /userbase/ 返回 404(根路径无映射控制器),000 表示未起来
|
||||
OK=0
|
||||
for i in $(seq 1 30); do
|
||||
sleep 5
|
||||
CODE=$(curl -s -o /dev/null -w '%{http_code}' http://localhost:8060/userbase/ 2>/dev/null || true)
|
||||
if [ "$CODE" != "000" ]; then OK=1; break; fi
|
||||
done
|
||||
if [ "$OK" = "0" ]; then
|
||||
echo "!! UNHEALTHY -> rollback to userbase-service:latest"
|
||||
docker stop userbase-service 2>/dev/null; docker rm -f userbase-service 2>/dev/null
|
||||
docker run -d --name userbase-service --restart unless-stopped --network infra-infra-net -p 8060:8060 userbase-service:latest
|
||||
echo "rolled back"; exit 7
|
||||
fi
|
||||
echo "DEPLOY OK: $IMG"
|
||||
docker tag "$IMG" userbase-service:latest
|
||||
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' | grep userbase || true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: deploy-userbase-front
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'front/**'
|
||||
- '.gitea/workflows/deploy-front.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
# node:20 自带 npm 与 git;通过已配置的 registry-mirrors 拉取
|
||||
# /data/www/userbase 与 docker.sock 由 act_runner 全局 options 统一挂载(见 act-runner config)
|
||||
container:
|
||||
image: node:20
|
||||
# 注意:本环境 act_runner v0.6.1 既不生效 container.options 的额外 --volume,
|
||||
# 也不生效 workflow 的 container.volumes(自定义卷被静默忽略),
|
||||
# 但它会默认把宿主 docker.sock 挂入每个 job 容器。
|
||||
# 因此部署改用 scripts/deploy-via-docker.js:通过该 docker.sock 启动一次性
|
||||
# helper 容器,把构建产物拷到宿主 /data/www/userbase/front。
|
||||
steps:
|
||||
# 内网 Gitea 无法访问 github.com 拉取 actions/checkout,改为直接 clone 本仓库
|
||||
- name: Checkout (manual, internal Gitea)
|
||||
run: |
|
||||
git clone --depth 1 --branch main http://192.168.10.160:3000/${GITHUB_REPOSITORY}.git /tmp/src
|
||||
cp -a /tmp/src/. .
|
||||
rm -rf /tmp/src
|
||||
|
||||
- name: Use npm mirror (npmmirror)
|
||||
working-directory: front
|
||||
run: npm config set registry https://registry.npmmirror.com
|
||||
|
||||
# node-sass(6.x) 无 Node20 预编译二进制,源码编译必失败;项目实际用 dart-sass(sass)。
|
||||
# 本地 lockfile 暂不便修改,这里在 runner 内把它从 lockfile 剔除后再 npm ci(不改仓库文件)。
|
||||
- name: Strip node-sass from lockfile (CI-only)
|
||||
run: |
|
||||
node -e "const fs=require('fs');const p='front/package-lock.json';const d=JSON.parse(fs.readFileSync(p,'utf8'));const pk=d.packages||{};for(const k of Object.keys(pk)){if(k==='node_modules/node-sass'||k.startsWith('node_modules/node-sass/'))delete pk[k];}if(d.dependencies&&d.dependencies.node-sass)delete d.dependencies.node-sass;fs.writeFileSync(p,JSON.stringify(d,null,2));"
|
||||
echo "node-sass stripped from lockfile"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: front
|
||||
run: npm ci --no-audit --no-fund
|
||||
|
||||
- name: Build (prod -> dist/prod)
|
||||
working-directory: front
|
||||
run: npm run build:prod
|
||||
|
||||
- name: Deploy to static dir (/data/www/userbase/front)
|
||||
# act_runner 不挂载自定义卷,改用宿主 docker.sock 启动一次性 helper 容器拷贝产物。
|
||||
run: node scripts/deploy-via-docker.js
|
||||
|
||||
- name: Done
|
||||
run: 'echo "deploy step finished; inspect host /data/www/userbase/_debug.txt for the full report"'
|
||||
@@ -1,99 +0,0 @@
|
||||
name: deploy-userbase-front
|
||||
|
||||
# 前端部署(方案 B:不打包镜像,直接部署静态文件 + 软链接版本控制)
|
||||
# 触发:push 到 main(已启用仓库 Actions,push 自动部署)/ 手动 Run workflow
|
||||
# 核心:vite build --mode prod -> dist/prod
|
||||
# -> 拷贝到 /data/www/userbase/releases/<version>/
|
||||
# -> 原子切换 /data/www/userbase/front 软链接指向新版本
|
||||
# -> 保留最近 5 个版本,更早自动清理(秒级回滚)
|
||||
# 不再构建/推送 nginx 镜像;Caddy 已改为 file_server(root=/srv/userbase=/data/www/userbase)。
|
||||
# 依赖:160 宿主的 docker daemon(runner 已挂 docker.sock),且 runner valid_volumes 已放行 /data/www/**。无需 NEXUS secret。
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
# docker:24 自带 docker CLI,复用宿主 docker daemon(act_runner v0.6.1 已挂载 docker.sock)。
|
||||
# 切勿再声明 volumes: docker.sock(会报 Duplicate mount point)。
|
||||
container:
|
||||
image: docker:24
|
||||
steps:
|
||||
- name: Install git (alpine)
|
||||
run: |
|
||||
# 统一走 aliyun 源:dl-cdn 时段性被拦会卡满 60s×2(2026-10-20 事故)
|
||||
sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories
|
||||
apk add --no-cache git
|
||||
|
||||
# 内网 Gitea 无法访问 github.com 拉取 actions/checkout,改为直接 clone 本仓库
|
||||
- name: Checkout (manual, internal Gitea)
|
||||
run: |
|
||||
git clone --depth 1 --branch main http://192.168.10.160:3000/${GITHUB_REPOSITORY}.git /tmp/src
|
||||
cp -a /tmp/src/. .
|
||||
rm -rf /tmp/src
|
||||
|
||||
# 构建 + 部署:在 node:20 容器里完成(源码经 docker cp 传入,并挂载宿主 /data/www/userbase)
|
||||
- name: Build & Deploy (symlink versioning)
|
||||
run: |
|
||||
set -e
|
||||
# 构建容器名必须全局唯一:runner 挂的是宿主 docker.sock,容器名在整个 daemon 上共享
|
||||
# 命名空间。固定名会在多仓/多条 workflow 并发时互相 docker rm -f 掉对方正在构建的
|
||||
# 容器,表现为无编译报错的 exit 137(SIGKILL,2026-10-21 实测复现)。
|
||||
# GITHUB_RUN_ID 实例级唯一,再拼时间戳兜底。
|
||||
BUILD_CONTAINER="frontbuild-${GITHUB_RUN_ID:-0}-$(date +%s)"
|
||||
echo "BUILD_CONTAINER=${BUILD_CONTAINER}"
|
||||
# 版本号:short sha + 时间戳(可改为 git tag 优先,见下)
|
||||
VERSION="$(echo ${GITHUB_SHA} | cut -c1-7)-$(date +%Y%m%d-%H%M)"
|
||||
echo "Deploying version: $VERSION"
|
||||
|
||||
docker rm -f "$BUILD_CONTAINER" 2>/dev/null || true
|
||||
docker run -d --name "$BUILD_CONTAINER" \
|
||||
-e VERSION="$VERSION" \
|
||||
-v /data/www/userbase:/data/www/userbase \
|
||||
-w /build node:20 sleep 3600
|
||||
|
||||
# 源码拷入构建容器(绕过 act 工作区非宿主路径的坑,与 service 的 mavenbuild 一致)
|
||||
docker cp "${PWD}/." "$BUILD_CONTAINER":/build/
|
||||
|
||||
docker exec "$BUILD_CONTAINER" sh -c '
|
||||
set -e
|
||||
cd /build
|
||||
# 去掉 Windows 专用 sass 二进制路径(Linux 容器内该路径无效,否则 build 报错)
|
||||
# .npmrc 被 gitignore、CI 克隆下来没有它,故仅在存在时才删(缺失不报错)
|
||||
if [ -f .npmrc ]; then sed -i "/SASS_BINARY_PATH/d" .npmrc; fi
|
||||
# 国内镜像源:内网直连 registry.npmjs.org 跨境极慢(实测 npm install 12 分 46 秒 / 754 包)
|
||||
npm config set registry https://registry.npmmirror.com
|
||||
npm ci
|
||||
npm run build:prod
|
||||
'
|
||||
|
||||
# 部署:首次把真实目录 front 迁入 releases/initial 并建软链;之后均为软链替换
|
||||
docker exec "$BUILD_CONTAINER" sh -c '
|
||||
set -e
|
||||
cd /build
|
||||
# ---- 首次初始化:front 当前是真实目录,无法直接 ln -sfn 覆盖 ----
|
||||
if [ ! -L /data/www/userbase/front ]; then
|
||||
mkdir -p /data/www/userbase/releases
|
||||
mv /data/www/userbase/front /data/www/userbase/releases/initial
|
||||
# 相对软链:Caddy 容器内 /data/www/userbase 挂在 /srv/userbase,
|
||||
# 绝对路径在容器内解析不到,必须用相对路径(相对 front 所在目录)
|
||||
ln -sfn releases/initial /data/www/userbase/front
|
||||
echo "INIT: front -> releases/initial (保留上线前状态用于回滚)"
|
||||
fi
|
||||
|
||||
mkdir -p /data/www/userbase/releases/$VERSION
|
||||
cp -r /build/dist/prod/. /data/www/userbase/releases/$VERSION/
|
||||
# 原子切换:front 此时已是软链接,ln -sfn 直接替换指向(必须用相对路径)
|
||||
ln -sfn releases/$VERSION /data/www/userbase/front
|
||||
echo "current symlink ->"; ls -la /data/www/userbase/front
|
||||
# 清理旧版本,保留最近 5 个(initial 也会被计入,最多保留 5 个含 initial)
|
||||
cd /data/www/userbase/releases
|
||||
ls -1t | tail -n +6 | while read d; do echo "rm old: $d"; rm -rf "$d"; done
|
||||
echo "remaining versions:"; ls -1t
|
||||
'
|
||||
|
||||
docker rm -f "$BUILD_CONTAINER"
|
||||
echo "DEPLOY OK: $VERSION"
|
||||
@@ -0,0 +1,27 @@
|
||||
name: diag-userbase
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dummy:
|
||||
description: 'noop'
|
||||
required: false
|
||||
default: 'x'
|
||||
jobs:
|
||||
diag:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker:24
|
||||
steps:
|
||||
- name: inspect running container config + embedded run-app.sh
|
||||
run: |
|
||||
echo "===== container env (NACOS?) ====="
|
||||
docker inspect userbase-service --format '{{json .Config.Env}}' 2>&1 || echo "NO CONTAINER"
|
||||
echo
|
||||
echo "===== -D patch present in RUNNING container's run-app.sh? ====="
|
||||
docker exec userbase-service grep -E 'Dyh.sso|client-host-url|server-url-prefix' /app/run-app.sh 2>&1 || echo "NO -D patch found in running container"
|
||||
echo
|
||||
echo "===== running image id ====="
|
||||
docker inspect userbase-service --format '{{.Image}}' 2>&1 || true
|
||||
echo
|
||||
echo "===== all userbase-service images (id / tag / age) ====="
|
||||
docker images --filter reference='*/userbase-service*' --format '{{.ID}} {{.Tag}} {{.CreatedSince}}' 2>&1
|
||||
@@ -0,0 +1,47 @@
|
||||
name: example-docker-push
|
||||
|
||||
# 示例:演示 act_runner 的 docker.sock 能力(构建并推送镜像到 Nexus docker-hosted)
|
||||
# 需要仓库 Settings -> Actions Secrets 配置:
|
||||
# NEXUS_USER = admin
|
||||
# NEXUS_PASSWORD = <nexus admin password>
|
||||
# 手动触发(Actions 页面 -> Run workflow),不会自动跑。
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'image tag to push'
|
||||
required: false
|
||||
default: 'demo'
|
||||
|
||||
jobs:
|
||||
docker-build-push:
|
||||
runs-on: ubuntu-latest
|
||||
# docker:24 自带 docker CLI,复用宿主 docker daemon
|
||||
# docker.sock 显式挂入 job 容器(act_runner 会校验 valid_volumes 白名单)
|
||||
container:
|
||||
image: docker:24
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
steps:
|
||||
# docker:24 基于 alpine,默认无 git;先装 git 再手动 clone 本仓库
|
||||
- name: Install git (alpine)
|
||||
run: |
|
||||
# 统一走 aliyun 源:dl-cdn 时段性被拦会卡满 60s×2(2026-10-20 事故)
|
||||
sed -i 's#dl-cdn.alpinelinux.org#mirrors.aliyun.com#g' /etc/apk/repositories
|
||||
apk add --no-cache git
|
||||
|
||||
# 内网 Gitea 无法访问 github.com 拉取 actions/checkout,改为直接 clone 本仓库
|
||||
- name: Checkout (manual, internal Gitea)
|
||||
run: |
|
||||
git clone --depth 1 --branch main http://192.168.10.160:3000/${GITHUB_REPOSITORY}.git /tmp/src
|
||||
cp -a /tmp/src/. .
|
||||
rm -rf /tmp/src
|
||||
|
||||
- name: Login to Nexus docker registry
|
||||
run: echo "${{ secrets.NEXUS_PASSWORD }}" | docker login nexus:8083 -u ${{ secrets.NEXUS_USER }}
|
||||
|
||||
- name: Build image (front nginx)
|
||||
run: docker build -t nexus:8083/docker-hosted/userbase-front:${{ inputs.tag }} -f front/docker/Dockerfile front
|
||||
|
||||
- name: Push image
|
||||
run: docker push nexus:8083/docker-hosted/userbase-front:${{ inputs.tag }}
|
||||
@@ -1,30 +1,32 @@
|
||||
# WorkBuddy local state
|
||||
# ---- dependencies ----
|
||||
node_modules/
|
||||
|
||||
# ---- build output (CI builds, do not commit) ----
|
||||
front/dist/
|
||||
front/dist_ra/
|
||||
|
||||
# ---- java build ----
|
||||
service/target/
|
||||
|
||||
# ---- local tooling / IDE ----
|
||||
.local-context/
|
||||
.workbuddy/
|
||||
|
||||
# Logs
|
||||
logs
|
||||
# ---- env secrets (keep *.example) ----
|
||||
.env
|
||||
!.env.example
|
||||
|
||||
# ---- logs ----
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
deploy/*_run*.log
|
||||
deploy/verify_run*.log
|
||||
deploy/push_run.log
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
yarn.lock
|
||||
.npmrc
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
# ---- OS ----
|
||||
Thumbs.db
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# ---- IDE ----
|
||||
.idea/
|
||||
# ---- vite temp cache ----
|
||||
*.timestamp-*.mjs
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// 纠正当前 api:gen 产物中命名空间的大小写错误
|
||||
// 映射依据:后端 service 下真实的 *Controller 类名 -> PascalCase 命名空间
|
||||
const fs = require('fs');
|
||||
const map = {
|
||||
Approle: 'AppRole',
|
||||
Appsettings: 'AppSettings',
|
||||
Groupauthz: 'GroupAuthz',
|
||||
Pushsubscription: 'PushSubscription',
|
||||
Signkeyinternal: 'SignKeyInternal',
|
||||
Sysgroup: 'SysGroup',
|
||||
Sysuser: 'SysUser',
|
||||
};
|
||||
const targets = ['src/api/apiDefinitions.ts', 'src/api/globals.d.ts'];
|
||||
for (const f of targets) {
|
||||
let s = fs.readFileSync(f, 'utf8');
|
||||
for (const [wrong, correct] of Object.entries(map)) {
|
||||
// 带引号 key:'Sysgroup. -> 'SysGroup.
|
||||
s = s.split(`'${wrong}.`).join(`'${correct}.`);
|
||||
// 裸引用(globals.d.ts 里的 interface/属性声明):Sysgroup: -> SysGroup: / Sysgroup. -> SysGroup.
|
||||
s = s.split(`${wrong}:`).join(`${correct}:`);
|
||||
s = s.split(`${wrong}.`).join(`${correct}.`);
|
||||
}
|
||||
fs.writeFileSync(f, s);
|
||||
console.log('fixed', f);
|
||||
}
|
||||
console.log('done');
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { GeneratorConfig } from "@alova/wormhole";
|
||||
|
||||
export default {
|
||||
generator: [
|
||||
{
|
||||
input: "http://localhost:8060/v3/api-docs",
|
||||
// 文档平台类型
|
||||
platform: "swagger",
|
||||
|
||||
// 生成代码的输出目录
|
||||
output: "src/api",
|
||||
// 响应数据的媒体类型
|
||||
responseMediaType: "application/json",
|
||||
// 请求体数据的媒体类型
|
||||
bodyMediaType: "application/json",
|
||||
// 生成的 API 版本
|
||||
version: 1,
|
||||
// 生成代码类型
|
||||
type: "typescript",
|
||||
// 全局 API 名称
|
||||
global: "Apis",
|
||||
splitImplementation: true,
|
||||
implementation: "inline",
|
||||
// API 处理函数
|
||||
handleApi: apiDescriptor => {
|
||||
// 过滤掉已废弃的 API
|
||||
if (apiDescriptor.deprecated) {
|
||||
return undefined;
|
||||
}
|
||||
// 优先使用后端显式声明的 operationId(稳定、可读、无重名后缀),
|
||||
// 仅在 operationId 缺失时回退到 summary。切勿用 summary 覆盖 operationId,
|
||||
// 否则中文/长文本 summary 会变成方法名导致运行时查不到。
|
||||
const newOpId = apiDescriptor.operationId ?? apiDescriptor.summary?.trim();
|
||||
const url = apiDescriptor.url.replace(/^\/{platform}\//, "/api/");
|
||||
// springdoc 默认把 Controller 类名转为 kebab tag(如 base-controller),
|
||||
// 而业务代码统一用 Pascal 无 Controller 的命名空间(如 Base)调用。
|
||||
// 这里剥掉 -controller 后缀并转为 PascalCase,使 wormhole 生成的
|
||||
// API 路径(Base.getUserInfo 等)与 Apis.Base.xxx 调用风格一致。
|
||||
// 由于转换结果是合法 JS 标识符,wormhole 会原样保留、不做 camelCase 改写。
|
||||
// springdoc v3 的 tag 有两种形态,都要能正确转为 PascalCase 且无 Controller 后缀:
|
||||
// - Pascal+Controller:SysGroupController -> SysGroup
|
||||
// - kebab+controller:sys-group-controller -> SysGroup
|
||||
// 关键:剥掉尾部 Controller 后,要在 camelCase 边界补空格再逐段首字母大写,
|
||||
// 否则 SysGroup -> Sysgroup(后面全被 toLowerCase 压成小写)会让运行时查不到。
|
||||
const toNamespace = (tag: string): string => {
|
||||
const stripped = tag.replace(/controller$/i, "");
|
||||
const withSpaces = stripped.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
||||
const parts = withSpaces.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
||||
return parts
|
||||
.map((p: string) => p.charAt(0).toUpperCase() + p.slice(1).toLowerCase())
|
||||
.join("");
|
||||
};
|
||||
if (Array.isArray(apiDescriptor.tags)) {
|
||||
apiDescriptor.tags = apiDescriptor.tags.map(toNamespace);
|
||||
}
|
||||
|
||||
return {
|
||||
...apiDescriptor,
|
||||
operationId: newOpId, // 关键:改掉它
|
||||
url,
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
} as GeneratorConfig;
|
||||
@@ -0,0 +1,20 @@
|
||||
# 本地构建推送 / 远端部署 用的环境变量示例
|
||||
# 用法:复制为 .env 或直接 export 后执行脚本
|
||||
|
||||
# ---- ACR 阿里云镜像仓库 ----
|
||||
ACR_REGISTRY=registry.cn-hangzhou.aliyuncs.com
|
||||
ACR_NAMESPACE=damaomi_docker
|
||||
ACR_REPO=yunhai
|
||||
ACR_USERNAME=your-acr-username
|
||||
ACR_PASSWORD=your-acr-password
|
||||
|
||||
# ---- 镜像 tag ----
|
||||
APP_VERSION=userbase-v1.0
|
||||
FRONT_VERSION=userbase-front-v1.0
|
||||
|
||||
# ---- 部署到远端 ----
|
||||
DEPLOY_HOST=192.168.10.157
|
||||
DEPLOY_PORT=22
|
||||
DEPLOY_USER=yunhai
|
||||
REMOTE_DIR=/opt/docker/userbase
|
||||
SSHPASS=your-ssh-password
|
||||
@@ -0,0 +1,6 @@
|
||||
docker run --rm --network infra-infra-net --entrypoint java registry.cn-hangzhou.aliyuncs.com/damaomi_docker/yunhai:userbase-v1.0 -jar /app/userbase.jar --spring.profiles.active=prod --debug > /tmp/ub_debug.log 2>&1
|
||||
echo "EXIT=$?"
|
||||
echo "=== conditions / binding matches ==="
|
||||
grep -nE "YhAuthAutoConfiguration|CasSsoConfiguration|Failed to bind|Binding to|YhSsoProperties|Exception starting filter|serverUrlPrefix|NoSuchBean" /tmp/ub_debug.log | head -60
|
||||
echo "=== tail (conditions report) ==="
|
||||
tail -50 /tmp/ub_debug.log
|
||||
@@ -0,0 +1 @@
|
||||
EOF
|
||||
@@ -0,0 +1,17 @@
|
||||
set -e
|
||||
echo "== wait for health (up to 180s) =="
|
||||
for i in $(seq 1 36); do
|
||||
st=$(docker inspect -f '{{.State.Health.Status}}' userbase-app 2>/dev/null || echo unknown)
|
||||
echo " [$i] health=$st"
|
||||
if [ "$st" = "healthy" ]; then break; fi
|
||||
sleep 5
|
||||
done
|
||||
echo "== container status =="
|
||||
docker ps --filter name=userbase --format "table {{.Names}}\t{{.Status}}"
|
||||
echo "== nacos reachability (host) =="
|
||||
curl -s -m 5 -o /dev/null -w "host nacos http=%{http_code}\n" "http://nacos.yunhaixingtong.com:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant=" || echo "host cannot reach nacos"
|
||||
echo "== nacos reachability (container) =="
|
||||
docker exec userbase-app sh -c "curl -s -m 5 -o /dev/null -w 'container nacos http=%{http_code}\n' 'http://nacos.yunhaixingtong.com:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant='" || echo "container cannot reach nacos"
|
||||
echo "== app logs: nacos / config / errors (tail) =="
|
||||
docker logs --tail 200 userbase-app 2>&1 | grep -iE "Loading nacos data|userbase.yaml|locate Nacos|NacosConfig|get config from nacos|ERROR|Exception|connect.*refused|UnknownHost|403|401|拒绝|超时|Started " | tail -40 || true
|
||||
echo "== done =="
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# 本地构建 UserBase 前后端镜像并推送到阿里云 ACR
|
||||
# 用法: ./build-push.sh [app_tag] [front_tag]
|
||||
# 前置环境变量: ACR_NAMESPACE, ACR_USERNAME, ACR_PASSWORD
|
||||
# 前置产物: service/target/userbase.jar、front/dist/prod/
|
||||
set -euo pipefail
|
||||
|
||||
APP_VERSION="${1:-${APP_VERSION:-userbase-v1.0}}"
|
||||
FRONT_VERSION="${2:-${FRONT_VERSION:-userbase-front-v1.0}}"
|
||||
REGISTRY="${ACR_REGISTRY:-registry.cn-hangzhou.aliyuncs.com}"
|
||||
NAMESPACE="${ACR_NAMESPACE:?请设置 ACR_NAMESPACE}"
|
||||
REPO="${ACR_REPO:-yunhai}"
|
||||
|
||||
APP_IMG="$REGISTRY/$NAMESPACE/$REPO:$APP_VERSION"
|
||||
FRONT_IMG="$REGISTRY/$NAMESPACE/$REPO:$FRONT_VERSION"
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
[ -f "$ROOT/service/target/userbase.jar" ] || { echo "缺少 service/target/userbase.jar,请先 mvn package"; exit 1; }
|
||||
[ -f "$ROOT/front/dist/prod/index.html" ] || { echo "缺少 front/dist/prod,请先 npm run build:prod"; exit 1; }
|
||||
|
||||
echo "== login $REGISTRY =="
|
||||
echo "${ACR_PASSWORD:?请设置 ACR_PASSWORD}" | docker login --username="${ACR_USERNAME:?请设置 ACR_USERNAME}" --password-stdin "$REGISTRY"
|
||||
|
||||
echo "== build $APP_IMG =="
|
||||
docker build -f "$ROOT/service/docker/Dockerfile" -t "$APP_IMG" "$ROOT/service"
|
||||
|
||||
echo "== build $FRONT_IMG =="
|
||||
docker build -f "$ROOT/front/docker/Dockerfile" -t "$FRONT_IMG" "$ROOT/front"
|
||||
|
||||
echo "== push =="
|
||||
docker push "$APP_IMG"
|
||||
docker push "$FRONT_IMG"
|
||||
|
||||
echo "PUSHED $APP_IMG"
|
||||
echo "PUSHED $FRONT_IMG"
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
MC=mysql8
|
||||
echo "MYSQL_CONTAINER=$MC"
|
||||
|
||||
docker exec userbase-app cat /app/config/application-prod.yml > /tmp/cfg.yml
|
||||
PW=$(awk '/username: root/{f=1} f&&/^ *password:/{sub(/^ *password: */,""); gsub(/\r/,""); print; exit}' /tmp/cfg.yml)
|
||||
echo "PW_LEN=${#PW}"
|
||||
|
||||
printf '[client]\nuser=root\npassword=%s\n' "$PW" > /tmp/my.cnf
|
||||
docker cp /tmp/my.cnf "$MC":/tmp/my.cnf
|
||||
|
||||
echo "== check sys_user.type column (via TCP 127.0.0.1) =="
|
||||
docker exec "$MC" sh -c "mysql --defaults-extra-file=/tmp/my.cnf -h 127.0.0.1 -N -e \"SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='bsch_userbase' AND TABLE_NAME='sys_user' AND COLUMN_NAME='type';\""
|
||||
echo "QUERY_EXIT=$?"
|
||||
|
||||
echo "== also confirm DB reachable & table exists =="
|
||||
docker exec "$MC" sh -c "mysql --defaults-extra-file=/tmp/my.cnf -h 127.0.0.1 -N -e \"SHOW TABLES FROM bsch_userbase LIKE 'sys_user';\""
|
||||
|
||||
docker exec "$MC" rm -f /tmp/my.cnf
|
||||
rm -f /tmp/my.cnf /tmp/cfg.yml
|
||||
echo "CLEANED"
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bash
|
||||
# 一键部署:上传编排文件 -> SSH 到服务器拉取镜像并启动
|
||||
# 用法: ./deploy.sh [app_tag] [front_tag]
|
||||
# 前置环境变量: DEPLOY_HOST, SSHPASS(可选 ACR_NAMESPACE / DEPLOY_USER / REMOTE_DIR)
|
||||
# 注:服务器需已 docker login registry.cn-hangzhou.aliyuncs.com
|
||||
set -euo pipefail
|
||||
|
||||
APP_VERSION="${1:-${APP_VERSION:-userbase-v1.0}}"
|
||||
FRONT_VERSION="${2:-${FRONT_VERSION:-userbase-front-v1.0}}"
|
||||
HOST="${DEPLOY_HOST:?请设置 DEPLOY_HOST}"
|
||||
PORT="${DEPLOY_PORT:-22}"
|
||||
USER="${DEPLOY_USER:-yunhai}"
|
||||
REMOTE_DIR="${REMOTE_DIR:-/opt/docker/userbase}"
|
||||
NAMESPACE="${ACR_NAMESPACE:-damaomi_docker}"
|
||||
REGISTRY="${ACR_REGISTRY:-registry.cn-hangzhou.aliyuncs.com}"
|
||||
PY="${PYTHON_BIN:-python3}"
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "== 1/2 上传 compose 到 $HOST:$REMOTE_DIR =="
|
||||
"$PY" upload.py "$HOST" "$PORT" "$USER" \
|
||||
docker-compose.userbase.yml "$REMOTE_DIR/docker-compose.userbase.yml"
|
||||
|
||||
echo "== 2/2 远端拉取镜像并启动 =="
|
||||
"$PY" remote_run.py "$HOST" "$PORT" "$USER" <<EOF
|
||||
set -e
|
||||
mkdir -p "$REMOTE_DIR/upload-data"
|
||||
cd "$REMOTE_DIR"
|
||||
cat > .env <<ENV
|
||||
ACR_REGISTRY=$REGISTRY
|
||||
ACR_NAMESPACE=$NAMESPACE
|
||||
APP_VERSION=$APP_VERSION
|
||||
FRONT_VERSION=$FRONT_VERSION
|
||||
ENV
|
||||
docker compose -f docker-compose.userbase.yml pull
|
||||
docker compose -f docker-compose.userbase.yml up -d
|
||||
docker image prune -f
|
||||
docker ps --filter name=userbase --format "table {{.Names}}\t{{.Status}}"
|
||||
echo DEPLOY_OK
|
||||
EOF
|
||||
@@ -0,0 +1,23 @@
|
||||
set -u
|
||||
CP=/opt/docker/infra/Caddyfile
|
||||
echo "===== 1. Caddyfile /userbase blocks (line numbers) ====="
|
||||
grep -n "userbase" "$CP"
|
||||
echo
|
||||
echo "===== 2. Full /userbase route region (lines 40-80) ====="
|
||||
sed -n '40,80p' "$CP"
|
||||
echo
|
||||
echo "===== 3. Caddy mounts in container ====="
|
||||
docker inspect caddy --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
|
||||
echo
|
||||
echo "===== 4. Running caddy admin config: does it contain 'userbase'? ====="
|
||||
docker exec caddy curl -s localhost:2019/config/ | grep -i "userbase" | head -n 20
|
||||
echo "--- end grep ---"
|
||||
echo
|
||||
echo "===== 5. Caddy container status ====="
|
||||
docker ps --filter name=caddy --format '{{.Names}} {{.Status}}'
|
||||
echo
|
||||
echo "===== 6. Direct test via 127.0.0.1 with Host header ====="
|
||||
for p in /userbase/ /userbase/index.html /userbase/static/js/ /userbase/api/x; do
|
||||
code=$(curl -s -o /tmp/ub_body -w "%{http_code}" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1$p")
|
||||
echo "$p -> $code | $(head -c 80 /tmp/ub_body | tr -d '\n')"
|
||||
done
|
||||
@@ -0,0 +1,24 @@
|
||||
set -u
|
||||
echo "===== 1. In-container view of /etc/caddy/Caddyfile: userbase count ====="
|
||||
docker exec caddy grep -c "userbase" /etc/caddy/Caddyfile
|
||||
echo
|
||||
echo "===== 2. Container start command ====="
|
||||
docker inspect caddy --format 'CMD={{.Config.Cmd}} ENTRY={{.Config.Entrypoint}}'
|
||||
echo
|
||||
echo "===== 3. Admin API reachable? top-level keys ====="
|
||||
docker exec caddy curl -s localhost:2019/
|
||||
echo
|
||||
echo "===== 4. Running HTTP servers + route matchers in config ====="
|
||||
docker exec caddy curl -s localhost:2019/config/ > /tmp/running.json
|
||||
echo "bytes: $(wc -c < /tmp/running.json)"
|
||||
echo "occurrences:"
|
||||
for s in "userbase" "Welcome to Base Platform" "handle_path" "reverse_proxy" "www.yunhaixingtong.com"; do
|
||||
echo " $s : $(grep -o "$s" /tmp/running.json | wc -l)"
|
||||
done
|
||||
echo
|
||||
echo "===== 5. Do a manual POST /load from inside container and show result ====="
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile 2>&1
|
||||
echo "exit=$?"
|
||||
sleep 1
|
||||
echo "--- after reload, recount ---"
|
||||
docker exec caddy curl -s localhost:2019/config/ | grep -c "userbase"
|
||||
@@ -0,0 +1,74 @@
|
||||
# UserBase 编排:后端(Spring Boot 8060) + 前端(nginx 80)
|
||||
# 两者都挂在 infra-infra-net,由 caddy 按服务名反代到 www.yunhaixingtong.com/userbase
|
||||
# 启动:cd /opt/docker/userbase && docker compose -f docker-compose.userbase.yml up -d
|
||||
services:
|
||||
userbase-app:
|
||||
image: ${ACR_REGISTRY:-registry.cn-hangzhou.aliyuncs.com}/${ACR_NAMESPACE:-damaomi_docker}/yunhai:${APP_VERSION:-userbase-v1.0}
|
||||
container_name: userbase-app
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- infra-infra-net
|
||||
# SSO 回调后容器内要做 CAS serviceValidate 回调 sso.yunhaixingtong.com,
|
||||
# 该虚拟主机由宿主机 caddy 按 vhost 路由,但容器内无 DNS 记录会 UnknownHostException。
|
||||
# host-gateway 指向宿主机网关(caddy 监听 :80/:443 全接口),无需硬编码 IP;
|
||||
# 旧版 Docker(<20.10)不支持 host-gateway,可改为宿主机桥接网关 IP,如 172.17.0.1。
|
||||
extra_hosts:
|
||||
- "sso.yunhaixingtong.com:host-gateway"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
# 配置中心:由 bootstrap.yml 中 spring.cloud.nacos.config 在 bootstrap 阶段自动拉取
|
||||
# 容器内通过 infra-infra-net 解析内网别名 nacos:8848(宿主机 shell 无法直接解析),
|
||||
# 公网域名 nacos.yunhaixingtong.com 仅用于控制台管理,生产容器走内网。
|
||||
NACOS_ADDR: nacos:8848
|
||||
# Nacos 已开启鉴权,config 客户端需携带账号(与旧 run-app.sh 的 nacos/nacos 一致)
|
||||
SPRING_CLOUD_NACOS_USERNAME: nacos
|
||||
SPRING_CLOUD_NACOS_PASSWORD: nacos
|
||||
SPRING_CLOUD_NACOS_CONFIG_USERNAME: nacos
|
||||
SPRING_CLOUD_NACOS_CONFIG_PASSWORD: nacos
|
||||
JAVA_OPTS: "-Xms512m -Xmx1024m"
|
||||
volumes:
|
||||
- ./upload-data:/data/upload
|
||||
expose:
|
||||
- "8060"
|
||||
healthcheck:
|
||||
# 只要能返回 HTTP 状态码就认为进程已起(业务接口普遍需要鉴权,不能用 -f)
|
||||
test: ["CMD-SHELL", "curl -s -o /dev/null -w '%{http_code}' http://localhost:8060/userbase/ | grep -qE '^[234]'"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 90s
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.5'
|
||||
memory: 1536M
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
userbase-front:
|
||||
image: ${ACR_REGISTRY:-registry.cn-hangzhou.aliyuncs.com}/${ACR_NAMESPACE:-damaomi_docker}/yunhai:${FRONT_VERSION:-userbase-front-v1.0}
|
||||
container_name: userbase-front
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- infra-infra-net
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
expose:
|
||||
- "80"
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
networks:
|
||||
infra-infra-net:
|
||||
external: true
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "== all containers (name + image) =="
|
||||
docker ps -a --format '{{.Names}}\t{{.Image}}'
|
||||
echo "== which containers are on infra-infra-net =="
|
||||
docker network inspect infra-infra-net --format '{{range .Containers}}{{.Name}} ({{.IPv4Address}}){{"\n"}}{{end}}' 2>/dev/null || echo "no such network"
|
||||
echo "== resolve mysql8 from userbase-app =="
|
||||
docker exec userbase-app getent hosts mysql8 2>/dev/null || echo "getent failed"
|
||||
@@ -0,0 +1,15 @@
|
||||
set -u
|
||||
echo "===== A. Reload caddy from inside the container (reads mounted /etc/caddy/Caddyfile) ====="
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||
echo "reload exit: $?"
|
||||
sleep 2
|
||||
echo
|
||||
echo "===== B. Verify running config now contains userbase ====="
|
||||
docker exec caddy curl -s localhost:2019/config/ | grep -c "userbase"
|
||||
echo "(count above; 0 means still missing)"
|
||||
echo
|
||||
echo "===== C. Test routes via 127.0.0.1 with Host header ====="
|
||||
for p in /userbase/ /userbase/index.html /userbase/static/js/ /userbase/api/x /userbase/login; do
|
||||
code=$(curl -s -o /tmp/ub_body -w "%{http_code}" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1$p")
|
||||
echo "$p -> $code | $(head -c 90 /tmp/ub_body | tr -d '\n')"
|
||||
done
|
||||
@@ -0,0 +1,19 @@
|
||||
set -u
|
||||
echo "===== host file (has userbase) ====="
|
||||
grep -c "userbase" /opt/docker/infra/Caddyfile
|
||||
echo "===== container file BEFORE restart (expect 0) ====="
|
||||
docker exec caddy grep -c "userbase" /etc/caddy/Caddyfile
|
||||
echo
|
||||
echo "===== restart caddy to re-bind the edited file ====="
|
||||
docker restart caddy
|
||||
echo "restart exit: $?"
|
||||
sleep 6
|
||||
echo
|
||||
echo "===== container file AFTER restart (expect >0) ====="
|
||||
docker exec caddy grep -c "userbase" /etc/caddy/Caddyfile
|
||||
echo
|
||||
echo "===== test routes ====="
|
||||
for p in /userbase/ /userbase/index.html /userbase/static/js/ /userbase/api/x /userbase/login; do
|
||||
code=$(curl -s -o /tmp/ub_body -w "%{http_code}" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1$p")
|
||||
echo "$p -> $code | $(head -c 100 /tmp/ub_body | tr -d '\n')"
|
||||
done
|
||||
@@ -0,0 +1,33 @@
|
||||
set -u
|
||||
CP=/opt/docker/infra/Caddyfile
|
||||
echo "===== before: line 70 ====="
|
||||
sed -n '70p' "$CP"
|
||||
echo "===== apply wildcard matcher (inode will change; restart re-binds) ====="
|
||||
sed -i 's#handle_path /userbase/ {#handle_path /userbase* {#' "$CP"
|
||||
echo "after:"; sed -n '70p' "$CP"
|
||||
echo
|
||||
echo "===== restart caddy to re-bind edited file ====="
|
||||
docker restart caddy
|
||||
echo "restart exit: $?"
|
||||
sleep 6
|
||||
echo "===== container sees fix? ====="
|
||||
docker exec caddy grep -n "handle_path /userbase" /etc/caddy/Caddyfile
|
||||
echo
|
||||
echo "===== extract a real JS asset from SPA and test it ====="
|
||||
html=$(curl -s -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/)
|
||||
echo "$html" | grep -oE 'src="[^"]+\.js"' | head -3
|
||||
asset=$(echo "$html" | grep -oE '/[A-Za-z0-9_./-]+\.js' | head -1)
|
||||
echo "first asset path: $asset"
|
||||
if [ -n "$asset" ]; then
|
||||
code=$(curl -s -o /dev/null -w "%{http_code}" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1/userbase${asset}")
|
||||
echo "/userbase${asset} -> $code"
|
||||
fi
|
||||
echo
|
||||
echo "===== route tests ====="
|
||||
for p in /userbase/ /userbase/index.html "/userbase${asset:-/static/js/}"; do
|
||||
code=$(curl -s -o /tmp/b -w "%{http_code}" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1$p")
|
||||
echo "$p -> $code | $(head -c 70 /tmp/b|tr -d '\n')"
|
||||
done
|
||||
echo
|
||||
echo "===== backend test ====="
|
||||
curl -s -o /dev/null -w "/userbase/api/x -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/api/x
|
||||
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# 探测内网 nacos:8848 是否可达、public 空间下 userbase.yaml 是否存在、是否需要鉴权
|
||||
echo "== resolve nacos =="
|
||||
getent hosts nacos || echo "cannot resolve nacos"
|
||||
echo "== fetch userbase.yaml (public, no auth) =="
|
||||
curl -s -m 8 -o /tmp/nc.txt -w "http=%{http_code}\n" "http://nacos:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant="
|
||||
echo "--- body (first 200 chars) ---"
|
||||
head -c 200 /tmp/nc.txt; echo
|
||||
if grep -q "unknown user" /tmp/nc.txt || grep -q "403" /tmp/nc.txt || [ "$(head -c 1 /tmp/nc.txt)" = "" ]; then
|
||||
echo "== needs auth, try login nacos/nacos =="
|
||||
TOKEN=$(curl -s -m 8 -X POST "http://nacos:8848/nacos/v1/auth/login?username=nacos&password=nacos" | sed -n 's/.*"accessToken":"\([^"]*\)".*/\1/p')
|
||||
echo "token_len=${#TOKEN}"
|
||||
if [ -n "$TOKEN" ]; then
|
||||
curl -s -m 8 -o /tmp/nc2.txt -w "http=%{http_code}\n" "http://nacos:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant=&accessToken=$TOKEN"
|
||||
echo "--- authed body (first 200 chars) ---"
|
||||
head -c 200 /tmp/nc2.txt; echo
|
||||
fi
|
||||
fi
|
||||
echo "== done =="
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
echo "== containers on host =="
|
||||
docker ps --format '{{.Names}}\t{{.Networks}}' | head -40
|
||||
echo "== infra-infra-net members =="
|
||||
docker network inspect infra-infra-net --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null
|
||||
echo "== resolve nacos from inside userbase-app =="
|
||||
docker exec userbase-app sh -c "getent hosts nacos || echo 'no nacos in container DNS'"
|
||||
echo "== curl nacos:8848 from inside userbase-app (no auth) =="
|
||||
docker exec userbase-app sh -c "curl -s -m 8 -o /tmp/nc.txt -w 'http=%{http_code}\n' 'http://nacos:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant=' ; head -c 200 /tmp/nc.txt; echo" || echo "curl failed"
|
||||
echo "== done =="
|
||||
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# 重新部署 userbase-app:拉取新镜像、重建、等待健康,并核对是否真正从 Nacos 拉到了配置
|
||||
echo "== login ACR =="
|
||||
echo '59qtJRqlT7' | docker login --username=15121037482 --password-stdin registry.cn-hangzhou.aliyuncs.com
|
||||
|
||||
cd /opt/docker/userbase
|
||||
echo "== pull userbase-app =="
|
||||
docker compose -f docker-compose.userbase.yml pull userbase-app
|
||||
echo "== up -d userbase-app =="
|
||||
docker compose -f docker-compose.userbase.yml up -d userbase-app
|
||||
|
||||
echo "== wait for health (up to 150s) =="
|
||||
for i in $(seq 1 30); do
|
||||
st=$(docker inspect -f '{{.State.Health.Status}}' userbase-app 2>/dev/null || echo unknown)
|
||||
echo " [$i] health=$st"
|
||||
if [ "$st" = "healthy" ]; then break; fi
|
||||
sleep 5
|
||||
done
|
||||
|
||||
echo "== nacos reachability (host) =="
|
||||
curl -s -m 5 -o /dev/null -w "host nacos http=%{http_code}\n" "http://nacos.yunhaixingtong.com:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant=" || echo "host cannot reach nacos"
|
||||
|
||||
echo "== nacos reachability (container) =="
|
||||
docker exec userbase-app sh -c "curl -s -m 5 -o /dev/null -w 'container nacos http=%{http_code}\n' 'http://nacos.yunhaixingtong.com:8848/nacos/v1/cs/configs?dataId=userbase.yaml&group=USERBASE&tenant='" || echo "container cannot reach nacos"
|
||||
|
||||
echo "== app logs: nacos / config / errors =="
|
||||
docker logs --tail 150 userbase-app 2>&1 | grep -iE "Loading nacos data|userbase.yaml|locate Nacos|NacosConfig|get config from nacos|ERROR|Exception|connect.*refused|UnknownHost|403|401|拒绝|超时" | tail -50 || true
|
||||
echo "== done =="
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
# 通过 SSH 在远端以 sudo 执行脚本(从 stdin 读取)
|
||||
# 用法: python3 remote_run.py <host> <port> <user> < script.sh
|
||||
# 依赖环境变量 SSHPASS
|
||||
import os, sys, paramiko
|
||||
|
||||
|
||||
def main():
|
||||
host = sys.argv[1]
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 22
|
||||
user = sys.argv[3] if len(sys.argv) > 3 else "root"
|
||||
pw = os.environ.get("SSHPASS")
|
||||
if not pw:
|
||||
sys.stderr.write("ERROR: 请先设置环境变量 SSHPASS\n")
|
||||
sys.exit(1)
|
||||
script = sys.stdin.read()
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(host, port, user, password=pw, timeout=30)
|
||||
stdin, stdout, stderr = c.exec_command("sudo -S bash -s", timeout=1800)
|
||||
stdin.write(pw + "\n")
|
||||
stdin.write(script)
|
||||
stdin.flush()
|
||||
stdin.channel.shutdown_write()
|
||||
sys.stdout.write(stdout.read().decode(errors="replace"))
|
||||
err = stderr.read().decode(errors="replace")
|
||||
if err.strip():
|
||||
sys.stderr.write("STDERR:\n" + err)
|
||||
code = stdout.channel.recv_exit_status()
|
||||
c.close()
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
# 线上冒烟:经 Caddy 访问 UserBase,确认 Nacos 配置生效、前后端可达
|
||||
echo "== SPA 首页 =="
|
||||
curl -s -m 8 -o /dev/null -w "/userbase/ -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/
|
||||
echo "== 前端静态资源 =="
|
||||
curl -s -m 8 -o /dev/null -w "/userbase/index.html -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/index.html
|
||||
echo "== 后端 API 可达性(CAS 重定向即代表到达后端) =="
|
||||
curl -s -m 8 -o /dev/null -w "/userbase/api/x -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/api/x
|
||||
echo "== 容器健康 =="
|
||||
docker inspect -f 'userbase-app health={{.State.Health.Status}}' userbase-app
|
||||
echo "== 确认应用真正连的是 nacos 下发的 mysql8 数据源(日志里看 datasource url) =="
|
||||
docker logs --tail 200 userbase-app 2>&1 | grep -iE "jdbc:mysql://mysql8|HikariPool|Started UserBaseApplication" | tail -5 || true
|
||||
echo "== done =="
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
# 通过 SFTP 上传文件到远端(参数: host port user local remote [local remote ...])
|
||||
# 依赖环境变量 SSHPASS
|
||||
import os, sys, paramiko
|
||||
|
||||
|
||||
def main():
|
||||
host = sys.argv[1]
|
||||
port = int(sys.argv[2]) if len(sys.argv) > 2 else 22
|
||||
user = sys.argv[3] if len(sys.argv) > 3 else "root"
|
||||
pw = os.environ.get("SSHPASS")
|
||||
if not pw:
|
||||
sys.stderr.write("ERROR: 请先设置环境变量 SSHPASS\n")
|
||||
sys.exit(1)
|
||||
t = paramiko.Transport((host, port))
|
||||
t.connect(username=user, password=pw)
|
||||
sftp = paramiko.SFTPClient.from_transport(t)
|
||||
args = sys.argv[4:]
|
||||
for i in range(0, len(args), 2):
|
||||
local, remote = args[i], args[i + 1]
|
||||
cur = ""
|
||||
for p in [p for p in os.path.dirname(remote).split("/") if p]:
|
||||
cur += "/" + p
|
||||
try:
|
||||
sftp.stat(cur)
|
||||
except IOError:
|
||||
try:
|
||||
sftp.mkdir(cur)
|
||||
except IOError:
|
||||
pass
|
||||
sftp.put(local, remote)
|
||||
print(f"PUT {local} -> {remote}")
|
||||
t.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,104 @@
|
||||
# ============================================================
|
||||
# 请在 Nacos 控制台新增此配置(public 命名空间)
|
||||
# Data ID : userbase.yaml
|
||||
# Group : USERBASE
|
||||
# Format : YAML
|
||||
# 说明:
|
||||
# - 本文件即应用自身配置。Nacos 解析规则为 ${spring.application.name}.${file-extension},
|
||||
# 因 bootstrap.yml 中 name=userbase、file-extension=yaml,故 Data ID 必须是 userbase.yaml。
|
||||
# - public 空间下的 common.properties 会作为共享配置优先加载(低优先级),
|
||||
# 本文件(高优先级)中的同名字段会覆盖 common.properties。
|
||||
# - 若 common.properties 已包含 datasource / redis 等公共项,下面这些行可按需删减;
|
||||
# 保留也无害(应用配置优先级更高,仍以本文件为准)。
|
||||
# - 生产启动时 profile=prod,Nacos 还会额外尝试 userbase-prod.yaml(可选,不存在也无妨)。
|
||||
# ============================================================
|
||||
|
||||
server:
|
||||
port: 8060
|
||||
servlet:
|
||||
# 对外经 caddy 暴露为 http://www.yunhaixingtong.com/userbase
|
||||
context-path: /userbase
|
||||
|
||||
spring:
|
||||
mvc:
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB
|
||||
max-request-size: 200MB
|
||||
jackson:
|
||||
time-zone: GMT+8
|
||||
date-format: yyyy-MM-dd HH:mm:ss
|
||||
|
||||
redis:
|
||||
host: redis-server
|
||||
port: 6379
|
||||
database: 0
|
||||
password: 'wv&WcDYAmLtqTIGM'
|
||||
timeout: 2000ms
|
||||
data:
|
||||
redis:
|
||||
repositories:
|
||||
enabled: false
|
||||
|
||||
datasource:
|
||||
url: jdbc:mysql://mysql8:3306/bsch_userbase?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: 'CVf^x$sW*b6CGHxh'
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
min-idle: 10
|
||||
max-active: 30
|
||||
|
||||
jpa:
|
||||
database: MySQL
|
||||
show-sql: false
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
properties:
|
||||
hibernate.order_by.default_null_ordering: last
|
||||
hibernate:
|
||||
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
|
||||
open-in-view: true
|
||||
|
||||
rabbitmq:
|
||||
host: rabbitmq
|
||||
port: 5672
|
||||
username: admin
|
||||
password: 'JdU29I#Q2E4yXoUl'
|
||||
listener:
|
||||
direct:
|
||||
auto-startup: false
|
||||
simple:
|
||||
auto-startup: false
|
||||
|
||||
# 单点登录 / 应用间签名鉴权(基础平台 yh-auth / yh-app-auth)
|
||||
yh:
|
||||
sso:
|
||||
server-url-prefix: http://sso.yunhaixingtong.com
|
||||
client-host-url: http://www.yunhaixingtong.com/userbase
|
||||
allow-headers: all
|
||||
allow-origins: all
|
||||
url-pattern: /api/*
|
||||
mobi-api-pattern: /mobiapi/*
|
||||
app-auth:
|
||||
enabled: true
|
||||
# 本应用自身身份(客户端签名用,若 UserBase 需调用其他带签名服务)
|
||||
app-key: 100616
|
||||
app-secret: 1febb9502d2744b6a235d3e4cfde4a2d
|
||||
# 受信应用(服务端验签用):appKey -> appSecret
|
||||
trusted-apps:
|
||||
100616: 1febb9502d2744b6a235d3e4cfde4a2d
|
||||
|
||||
ADMIN_TAG_ID: 290
|
||||
|
||||
# 上传文件落容器卷,compose 里挂到宿主机 ./upload-data
|
||||
FILE_PATH: /data/upload
|
||||
FILE_WEB_PATH: http://www.yunhaixingtong.com/userbase/upload/
|
||||
ZIYUAN_WEB_PATH_HTTP: http://base.bestsch.com/ziyuanku/file/
|
||||
UPLOAD_WEB_PATH_HTTP: http://www.yunhaixingtong.com/userbase/upload/file/
|
||||
ORC_SERVICE_URL: http://oa.bestsch.com/OrcService/api/Ocr/Upload
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
echo "== app startup DDL/schema hints =="
|
||||
docker logs userbase-app 2>&1 | grep -iE "alter table|update schema|create table|ddl|column|sys_user" | head -30 || echo "(no ddl lines captured)"
|
||||
|
||||
echo "== datasource config from container =="
|
||||
docker exec userbase-app cat /app/config/application-prod.yml 2>/dev/null | grep -iE "url:|username:|password:|jdbc" | sed -E 's/(password:).*/\1 ****/' || echo "(no config)"
|
||||
|
||||
echo "== mysql client available? =="
|
||||
which mysql || echo "NO_MYSQL_CLI"
|
||||
@@ -0,0 +1,19 @@
|
||||
set -u
|
||||
echo "===== 1. Main app JS chunk from SPA ====="
|
||||
html=$(curl -s -H "Host: www.yunhaixingtong.com" http://127.0.0.1/userbase/)
|
||||
appjs=$(echo "$html" | grep -oE '/static/js/[A-Za-z0-9_./-]+\.js' | head -1)
|
||||
echo "app js: $appjs"
|
||||
curl -s -o /dev/null -w " /userbase${appjs} -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" "http://127.0.0.1/userbase${appjs}"
|
||||
echo
|
||||
echo "===== 2. Ensure other sites not regressed (sso / b) ====="
|
||||
curl -s -o /dev/null -w " sso host / -> %{http_code}\n" -H "Host: sso.yunhaixingtong.com" http://127.0.0.1/
|
||||
curl -s -o /dev/null -w " b host / -> %{http_code}\n" -H "Host: www.yunhaixingtong.com" http://127.0.0.1/b/
|
||||
echo
|
||||
echo "===== 3. Caddy listening ports on host ====="
|
||||
ss -ltnp 2>/dev/null | grep -E ':80 |:443 ' | head
|
||||
echo
|
||||
echo "===== 4. Containers status ====="
|
||||
docker ps --format '{{.Names}} {{.Status}}' | grep -E 'caddy|userbase'
|
||||
echo
|
||||
echo "===== 5. userbase-front health (serve index directly) ====="
|
||||
docker exec userbase-front wget -qO- http://localhost/ 2>/dev/null | head -c 60; echo
|
||||
@@ -0,0 +1,7 @@
|
||||
# 正式环境 - base
|
||||
VITE_BASE_HOST=https://base.bestsch.com
|
||||
VITE_SSO_HOST=https://sso.bestsch.com
|
||||
VITE_SERVICE_HOST=https://base.bestsch.com/LearnCenter
|
||||
VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk
|
||||
VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
@@ -0,0 +1,7 @@
|
||||
# 开发环境 (dev)
|
||||
VITE_BASE_HOST=http://base.bestsch.com
|
||||
VITE_SSO_HOST=http://sso.bestsch.com
|
||||
VITE_SERVICE_HOST=http://localhost:8060
|
||||
VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk
|
||||
VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
@@ -0,0 +1,7 @@
|
||||
# 正式环境 - ld
|
||||
VITE_BASE_HOST=https://ld.bestsch.com
|
||||
VITE_SSO_HOST=https://sso.bestsch.com
|
||||
VITE_SERVICE_HOST=https://ld.bestsch.com/LearnCenter
|
||||
VITE_ZIYUANKU_HOST=https://ld.bestsch.com/BschZyk
|
||||
VITE_ZHUANXIANG_HOST=https://ld.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
@@ -0,0 +1,7 @@
|
||||
# 正式环境 - 云海星通(部署在 www.yunhaixingtong.com/userbase)
|
||||
VITE_BASE_HOST=http://www.yunhaixingtong.com
|
||||
VITE_SSO_HOST=http://sso.yunhaixingtong.com
|
||||
VITE_SERVICE_HOST=http://www.yunhaixingtong.com/userbase
|
||||
VITE_ZIYUANKU_HOST=https://base.bestsch.com/BschZyk
|
||||
VITE_ZHUANXIANG_HOST=https://base.bestsch.com/BschZncpd
|
||||
VITE_IS_MOCK=false
|
||||
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
yarn.lock
|
||||
.npmrc
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { GeneratorConfig } from "@alova/wormhole";
|
||||
|
||||
export default {
|
||||
generator: [
|
||||
{
|
||||
input: "http://localhost:8060/v2/api-docs",
|
||||
// 文档平台类型
|
||||
platform: "swagger",
|
||||
|
||||
// 生成代码的输出目录
|
||||
output: "src/api",
|
||||
// 响应数据的媒体类型
|
||||
responseMediaType: "application/json",
|
||||
// 请求体数据的媒体类型
|
||||
bodyMediaType: "application/json",
|
||||
// 生成的 API 版本
|
||||
version: 1,
|
||||
// 生成代码类型
|
||||
type: "typescript",
|
||||
// 全局 API 名称
|
||||
global: "Apis",
|
||||
splitImplementation: true,
|
||||
implementation: "inline",
|
||||
// API 处理函数
|
||||
handleApi: apiDescriptor => {
|
||||
// 过滤掉已废弃的 API
|
||||
if (apiDescriptor.deprecated) {
|
||||
return undefined;
|
||||
}
|
||||
const newOpId = apiDescriptor.summary?.trim() || apiDescriptor.operationId;
|
||||
const url = apiDescriptor.url.replace(/^\/{platform}\//, "/api/");
|
||||
|
||||
return {
|
||||
...apiDescriptor,
|
||||
operationId: newOpId, // 关键:改掉它
|
||||
url: apiDescriptor.url.replace(/^\/{platform}\//, "/api/"),
|
||||
};
|
||||
},
|
||||
},
|
||||
],
|
||||
} as GeneratorConfig;
|
||||
@@ -0,0 +1,6 @@
|
||||
//铃铛debug
|
||||
window.conf = {
|
||||
baseHost: "http://basedebug.bestsch.com",
|
||||
ssoHost: "http://ssodebug.bestsch.com",
|
||||
serviceHost: "http://basedebug.bestsch.com/QualityEvaluate",
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="../../favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%- title %></title>
|
||||
<link rel="stylesheet" href="./libs/ztree/zTreeStyle.css" />
|
||||
<script src="./libs/jquery.min.js"></script>
|
||||
<script src="./libs/lodash.min.js"></script>
|
||||
<script src="./libs/ztree/jquery.ztree.all.min.js"></script>
|
||||
<script src=" https://base.bestsch.com/BschHome/base/js/bschload.min.js?v=2025011001"></script>
|
||||
<%- injectScript %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -6,10 +6,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --mode dev",
|
||||
"build": "vite build",
|
||||
"build:prod": "vite build --mode prod",
|
||||
"build:ld": "vite build --mode ld",
|
||||
"build": "vite build --mode base",
|
||||
"build:base": "vite build --mode base",
|
||||
"build:ld": "vite build --mode ld",
|
||||
"build:prod": "vite build --mode prod",
|
||||
"preview": "vite preview",
|
||||
"api:gen": "alova gen -f"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -10,7 +10,6 @@ import Apis from '@/api/index'
|
||||
import { alovaInstance } from "@/utils/alova/alova-iview";
|
||||
import useCurrentInstance from "@/utils/hook/useCurrentInstance";
|
||||
import { UserRoleEnum } from "./types";
|
||||
import { isRouteAllowed } from "@/router";
|
||||
const store = useDefaultStore();
|
||||
const router = useRouter();
|
||||
const { $bus } = useCurrentInstance();
|
||||
@@ -54,9 +53,10 @@ const layoutInit = (info: any, modList: any[], appList: Array<any>, baseHost: st
|
||||
};
|
||||
|
||||
/**
|
||||
* 角色判定(降级兜底):GetUserScope 失败时按 GetUserInfo 的 user.type 推断——
|
||||
* type=1 → SuperAdmin,其余 → School。同时写入 currentRole 供下游
|
||||
* Utils.getCurrentRoleInfo 与请求头注入读取(保持兼容;正常路径以 GetUserScope 为准)。
|
||||
* 角色判定
|
||||
* 现基于 GetUserInfo 返回的 user.type 判定角色:
|
||||
* type=1 → SuperAdmin,type=2 → School(不再有 Teacher 角色)
|
||||
* 同时写入 currentRole 供下游 Utils.getCurrentRoleInfo 读取(保持兼容)
|
||||
*/
|
||||
const setCurrentRole = (user: any) => {
|
||||
const userId = user?.id ?? null;
|
||||
@@ -87,43 +87,12 @@ onMounted(async () => {
|
||||
try {
|
||||
var user: any = await alovaInstance.Request(Apis.Base.getUserInfo());
|
||||
|
||||
currentUser.value = user;
|
||||
|
||||
// 系统字典
|
||||
// 系统字典 & 模块列表(模块当前写死,后续接入接口)
|
||||
await store.fetchSysDict();
|
||||
|
||||
// 角色判定:优先 GetUserScope(后端单点判定四级访问级别,前端不重复实现规则),
|
||||
// 失败时降级走 setCurrentRole 的 type 推断
|
||||
setCurrentRole(user);
|
||||
try {
|
||||
const scope: any = await alovaInstance.Request(Apis.Base.getUserScope());
|
||||
if (scope && scope.level != null) {
|
||||
store.setUserRole(scope.level);
|
||||
store.setUserScope({
|
||||
bureauGroupId: scope.bureauGroupId ?? null,
|
||||
schoolGroupIds: scope.schoolGroupIds ?? null,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("获取访问范围失败,按用户类型降级判定角色", e);
|
||||
}
|
||||
|
||||
// NONE 级:整体「暂无权限」,不加载菜单(接口层已被后端 NONE 拦截器 403 双挡)
|
||||
store.setRoleLoaded(true);
|
||||
if (store.userRole === UserRoleEnum.None) {
|
||||
isNoAuthority.value = true;
|
||||
layoutInit(user, [], [], baseHost, ssoHost);
|
||||
isLogin.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 首次导航早于角色落定被守卫放行:此处复核当前路由(直接敲 URL 场景),越权重定向 /no-auth
|
||||
if (!isRouteAllowed(router.currentRoute.value, store.userRole)) {
|
||||
router.replace("/no-auth");
|
||||
}
|
||||
|
||||
// 模块列表(按当前级别过滤菜单权限矩阵)
|
||||
await store.fetchModules();
|
||||
|
||||
currentUser.value = user;
|
||||
setCurrentRole(user);
|
||||
layoutInit(user, store.modules, store.modules, baseHost, ssoHost);
|
||||
|
||||
// 登录后直接跳转到左侧菜单第一个可用入口
|
||||
@@ -2,12 +2,13 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* 用户中心接口文档 - version 0.0.1
|
||||
* 用户中心接口文档 - version 0.0.1-SNAPSHOT
|
||||
*
|
||||
* UserBase 用户中心 OpenAPI 文档(springdoc-openapi)
|
||||
* Demo project for Spring Boot
|
||||
*
|
||||
* OpenAPI version: 3.0.1
|
||||
* OpenAPI version: 3.0.0
|
||||
*
|
||||
* Contact:
|
||||
*
|
||||
* NOTE: This file is auto generated by the alova's vscode plugin.
|
||||
*
|
||||
@@ -16,104 +17,67 @@
|
||||
* **Do not edit the file manually.**
|
||||
*/
|
||||
export default {
|
||||
'SysUser.enable': ['POST', '/api/sysuser/Enable/{id}'],
|
||||
'SysUser.edit': ['POST', '/api/sysuser/Edit/{id}'],
|
||||
'SysUser.disable': ['POST', '/api/sysuser/Disable/{id}'],
|
||||
'SysUser.deleteFromGroup': ['POST', '/api/sysuser/DeleteFromGroup/{userId}/{groupId}'],
|
||||
'SysUser.changeSortNo': ['POST', '/api/sysuser/ChangeSortNo/{userId}'],
|
||||
'SysUser.changeGroups': ['POST', '/api/sysuser/ChangeGroups'],
|
||||
'SysUser.batchImport': ['POST', '/api/sysuser/BatchImport'],
|
||||
'SysUser.batchDelete': ['POST', '/api/sysuser/BatchDelete'],
|
||||
'SysUser.batchChangeStatus': ['POST', '/api/sysuser/BatchChangeStatus'],
|
||||
'SysUser.add': ['POST', '/api/sysuser/Add'],
|
||||
'SysGroup.sortChildren': ['POST', '/api/sysgroup/SortChildren'],
|
||||
'SysGroup.move': ['POST', '/api/sysgroup/Move'],
|
||||
'SysGroup.sysGroupEdit': ['POST', '/api/sysgroup/Edit/{id}'],
|
||||
'SysGroup.sysGroupRemove': ['POST', '/api/sysgroup/Delete/{id}'],
|
||||
'SysGroup.batchAdd': ['POST', '/api/sysgroup/BatchAdd'],
|
||||
'SysGroup.sysGroupAdd': ['POST', '/api/sysgroup/Add'],
|
||||
'PushSubscription.test': ['POST', '/api/push/Test'],
|
||||
'PushSubscription.retry': ['POST', '/api/push/Retry/{id}'],
|
||||
'GroupAuthz.setSchoolApp': ['POST', '/api/groupauthz/SetSchoolApp'],
|
||||
'GroupAuthz.setGroupModule': ['POST', '/api/groupauthz/SetGroupModule'],
|
||||
'GroupAuthz.saveModulePerms': ['POST', '/api/groupauthz/SaveModulePerms'],
|
||||
'GroupAuthz.assignApps': ['POST', '/api/groupauthz/AssignApps'],
|
||||
'Base.uploadFile': ['POST', '/api/base/UploadFile'],
|
||||
'AppRole.saveUserRoles': ['POST', '/api/approle/SaveUserRoles'],
|
||||
'AppRole.saveRoleUsers': ['POST', '/api/approle/SaveRoleUsers'],
|
||||
'AppRole.saveRoleModules': ['POST', '/api/approle/SaveRoleModules'],
|
||||
'AppRole.roleEdit': ['POST', '/api/approle/RoleEdit/{id}'],
|
||||
'AppRole.roleDelete': ['POST', '/api/approle/RoleDelete/{id}'],
|
||||
'AppRole.roleAdd': ['POST', '/api/approle/RoleAdd'],
|
||||
'AppRole.removeRoleUser': ['POST', '/api/approle/RemoveRoleUser'],
|
||||
'AppSettings.savePushSetting': ['POST', '/api/app/SavePushSetting/{id}'],
|
||||
'AppSettings.resetAppSecret': ['POST', '/api/app/ResetAppSecret/{id}'],
|
||||
'AppSettings.refreshSignKeys': ['POST', '/api/app/RefreshSignKeys'],
|
||||
'AppSettings.editType': ['POST', '/api/app/EditType/{id}'],
|
||||
'AppSettings.editModule': ['POST', '/api/app/EditModule/{id}'],
|
||||
'AppSettings.editApp': ['POST', '/api/app/EditApp/{id}'],
|
||||
'AppSettings.deleteType': ['POST', '/api/app/DeleteType/{id}'],
|
||||
'AppSettings.deleteModule': ['POST', '/api/app/DeleteModule/{id}'],
|
||||
'AppSettings.deleteApp': ['POST', '/api/app/DeleteApp/{id}'],
|
||||
'AppSettings.appTypeSort': ['POST', '/api/app/AppTypeSort'],
|
||||
'AppSettings.appSort': ['POST', '/api/app/AppSort'],
|
||||
'AppSettings.addType': ['POST', '/api/app/AddType'],
|
||||
'AppSettings.addModule': ['POST', '/api/app/AddModule'],
|
||||
'AppSettings.addApp': ['POST', '/api/app/AddApp'],
|
||||
'User.moveToGroup': ['POST', '/oapi/user/moveToGroup'],
|
||||
'User.edit_1': ['POST', '/oapi/user/edit/{id}'],
|
||||
'User.delete_': ['POST', '/oapi/user/delete/{id}/{groupId}'],
|
||||
'User.batchAdd_1': ['POST', '/oapi/user/batchAdd'],
|
||||
'User.add_1': ['POST', '/oapi/user/add'],
|
||||
'Group.edit_2': ['POST', '/oapi/group/edit/{id}'],
|
||||
'Group.delete_1': ['POST', '/oapi/group/delete/{id}'],
|
||||
'Group.batchAdd_2': ['POST', '/oapi/group/batchAdd'],
|
||||
'Group.add_2': ['POST', '/oapi/group/add'],
|
||||
'SysUser.list': ['GET', '/api/sysuser/List'],
|
||||
'SysUser.getUserGroups': ['GET', '/api/sysuser/Groups/{userId}'],
|
||||
'SysUser.userIdentity': ['GET', '/api/sysuser/UserIdentity'],
|
||||
'SysUser.export_': ['GET', '/api/sysuser/Export'],
|
||||
'SysUser.downloadTemplate': ['GET', '/api/sysuser/DownloadTemplate'],
|
||||
'SysGroup.sysGroupList': ['GET', '/api/sysgroup/List'],
|
||||
'SysGroup.getTree': ['GET', '/api/sysgroup/GetTree'],
|
||||
'SysGroup.getSchoolTree': ['GET', '/api/sysgroup/GetSchoolTree'],
|
||||
'PushSubscription.recordList': ['GET', '/api/push/RecordList'],
|
||||
'PushSubscription.download': ['GET', '/api/push/Download/{id}'],
|
||||
'SysOperationLog.operationLogList': ['GET', '/api/oplog/List'],
|
||||
'SysOperationLog.operationLogDownload': ['GET', '/api/oplog/Download/{id}'],
|
||||
'SysOapiLog.oapiLogList': ['GET', '/api/oapilog/List'],
|
||||
'SysOapiLog.oapiLogDownload': ['GET', '/api/oapilog/Download/{id}'],
|
||||
'GroupAuthz.userRoles': ['GET', '/api/groupauthz/UserRoles'],
|
||||
'GroupAuthz.userPermissionTree': ['GET', '/api/groupauthz/UserPermissionTree'],
|
||||
'GroupAuthz.userGroupTree': ['GET', '/api/groupauthz/UserGroupTree'],
|
||||
'GroupAuthz.searchUsers': ['GET', '/api/groupauthz/SearchUsers'],
|
||||
'GroupAuthz.hasModulePerm': ['GET', '/api/groupauthz/HasModulePerm'],
|
||||
'GroupAuthz.groupUsers': ['GET', '/api/groupauthz/GroupUsers'],
|
||||
'AppSettings.addModule': ['POST', '/api/app/AddModule'],
|
||||
'AppSettings.addType': ['POST', '/api/app/AddType'],
|
||||
'AppSettings.appAuthzList': ['GET', '/api/app/AppAuthzList'],
|
||||
'AppSettings.appList': ['GET', '/api/app/AppList'],
|
||||
'AppSettings.appSort': ['POST', '/api/app/AppSort'],
|
||||
'AppSettings.appSortList': ['GET', '/api/app/AppSortList'],
|
||||
'AppSettings.appTypeList': ['GET', '/api/app/AppTypeList'],
|
||||
'AppSettings.appTypeSort': ['POST', '/api/app/AppTypeSort'],
|
||||
'AppSettings.deleteApp': ['POST', '/api/app/DeleteApp/{id}'],
|
||||
'AppSettings.deleteModule': ['POST', '/api/app/DeleteModule/{id}'],
|
||||
'AppSettings.deleteType': ['POST', '/api/app/DeleteType/{id}'],
|
||||
'AppSettings.editApp': ['POST', '/api/app/EditApp/{id}'],
|
||||
'AppSettings.editModule': ['POST', '/api/app/EditModule/{id}'],
|
||||
'AppSettings.editType': ['POST', '/api/app/EditType/{id}'],
|
||||
'AppSettings.moduleList': ['GET', '/api/app/ModuleList'],
|
||||
'AppSettings.resetAppSecret': ['POST', '/api/app/ResetAppSecret/{id}'],
|
||||
'AppRole.removeRoleUser': ['POST', '/api/approle/RemoveRoleUser'],
|
||||
'AppRole.roleAdd': ['POST', '/api/approle/RoleAdd'],
|
||||
'AppRole.roleDelete': ['POST', '/api/approle/RoleDelete/{id}'],
|
||||
'AppRole.roleEdit': ['POST', '/api/approle/RoleEdit/{id}'],
|
||||
'AppRole.roleList': ['GET', '/api/approle/RoleList'],
|
||||
'AppRole.roleModuleTree': ['GET', '/api/approle/RoleModuleTree'],
|
||||
'AppRole.roleUsers': ['GET', '/api/approle/RoleUsers'],
|
||||
'AppRole.saveRoleModules': ['POST', '/api/approle/SaveRoleModules'],
|
||||
'AppRole.saveRoleUsers': ['POST', '/api/approle/SaveRoleUsers'],
|
||||
'AppRole.saveUserRoles': ['POST', '/api/approle/SaveUserRoles'],
|
||||
'Base.getSysDict': ['GET', '/api/base/GetSysDict'],
|
||||
'Base.getUserInfo': ['GET', '/api/base/GetUserInfo'],
|
||||
'Base.manualHashCacheDemo': ['GET', '/api/base/ManualHashCacheDemo'],
|
||||
'Base.RedirectTo': ['GET', '/api/base/RedirectTo/{id}'],
|
||||
'Base.uploadFile': ['POST', '/api/base/UploadFile'],
|
||||
'GroupAuthz.assignApps': ['POST', '/api/groupauthz/AssignApps'],
|
||||
'GroupAuthz.getSchoolAppId': ['GET', '/api/groupauthz/GetSchoolAppId'],
|
||||
'GroupAuthz.groupAppList': ['GET', '/api/groupauthz/GroupAppList'],
|
||||
'GroupAuthz.groupModuleTree': ['GET', '/api/groupauthz/GroupModuleTree'],
|
||||
'GroupAuthz.groupModuleTreeByCategory': ['GET', '/api/groupauthz/GroupModuleTreeByCategory'],
|
||||
'GroupAuthz.groupAppList': ['GET', '/api/groupauthz/GroupAppList'],
|
||||
'GroupAuthz.getSchoolAppId': ['GET', '/api/groupauthz/GetSchoolAppId'],
|
||||
'Base.RedirectTo': ['GET', '/api/base/RedirectTo/{id}'],
|
||||
'Base.manualHashCacheDemo': ['GET', '/api/base/ManualHashCacheDemo'],
|
||||
'Base.getUserInfo': ['GET', '/api/base/GetUserInfo'],
|
||||
'Base.getUserScope': ['GET', '/api/base/GetUserScope'],
|
||||
'Base.getSysDict': ['GET', '/api/base/GetSysDict'],
|
||||
'AppSettings.schoolList': ['GET', '/api/app/SchoolList'],
|
||||
'AppRole.roleUsers': ['GET', '/api/approle/RoleUsers'],
|
||||
'AppRole.roleModuleTree': ['GET', '/api/approle/RoleModuleTree'],
|
||||
'AppRole.roleList': ['GET', '/api/approle/RoleList'],
|
||||
'AppSettings.moduleList': ['GET', '/api/app/ModuleList'],
|
||||
'AppSettings.getPushSetting': ['GET', '/api/app/GetPushSetting/{id}'],
|
||||
'AppSettings.appTypeList': ['GET', '/api/app/AppTypeList'],
|
||||
'AppSettings.appSortList': ['GET', '/api/app/AppSortList'],
|
||||
'AppSettings.appList': ['GET', '/api/app/AppList'],
|
||||
'AppSettings.appAuthzList': ['GET', '/api/app/AppAuthzList'],
|
||||
'User.modules': ['GET', '/oapi/user/modules'],
|
||||
'User.byPhone': ['GET', '/oapi/user/byPhone'],
|
||||
'User.byLoginName': ['GET', '/oapi/user/byLoginName'],
|
||||
'User.byId': ['GET', '/oapi/user/byId'],
|
||||
'SignKeyInternal.signKey': ['GET', '/oapi/internal/sign-key/{appId}'],
|
||||
'Group.tree': ['GET', '/oapi/group/tree'],
|
||||
'Group.list_1': ['GET', '/oapi/group/list'],
|
||||
'Group.byId_1': ['GET', '/oapi/group/byId']
|
||||
'GroupAuthz.groupUsers': ['GET', '/api/groupauthz/GroupUsers'],
|
||||
'GroupAuthz.hasModulePerm': ['GET', '/api/groupauthz/HasModulePerm'],
|
||||
'GroupAuthz.saveModulePerms': ['POST', '/api/groupauthz/SaveModulePerms'],
|
||||
'GroupAuthz.searchUsers': ['GET', '/api/groupauthz/SearchUsers'],
|
||||
'GroupAuthz.setGroupModule': ['POST', '/api/groupauthz/SetGroupModule'],
|
||||
'GroupAuthz.setSchoolApp': ['POST', '/api/groupauthz/SetSchoolApp'],
|
||||
'GroupAuthz.userGroupTree': ['GET', '/api/groupauthz/UserGroupTree'],
|
||||
'GroupAuthz.userPermissionTree': ['GET', '/api/groupauthz/UserPermissionTree'],
|
||||
'GroupAuthz.userRoles': ['GET', '/api/groupauthz/UserRoles'],
|
||||
'OperationLog.list': ['GET', '/api/oplog/List'],
|
||||
'OperationLog.download': ['GET', '/api/oplog/Download/{id}'],
|
||||
'SysGroup.add': ['POST', '/api/sysgroup/Add'],
|
||||
'SysGroup.batchAdd': ['POST', '/api/sysgroup/BatchAdd'],
|
||||
'SysGroup.delete': ['POST', '/api/sysgroup/Delete/{id}'],
|
||||
'SysGroup.edit': ['POST', '/api/sysgroup/Edit/{id}'],
|
||||
'SysGroup.getSchoolTree': ['GET', '/api/sysgroup/GetSchoolTree'],
|
||||
'SysGroup.getTree': ['GET', '/api/sysgroup/GetTree'],
|
||||
'SysGroup.list': ['GET', '/api/sysgroup/List'],
|
||||
'SysGroup.move': ['POST', '/api/sysgroup/Move'],
|
||||
'SysGroup.sortChildren': ['POST', '/api/sysgroup/SortChildren'],
|
||||
'SysUser.add': ['POST', '/api/sysuser/Add'],
|
||||
'SysUser.delete': ['POST', '/api/sysuser/Delete/{id}'],
|
||||
'SysUser.disable': ['POST', '/api/sysuser/Disable/{id}'],
|
||||
'SysUser.edit': ['POST', '/api/sysuser/Edit/{id}'],
|
||||
'SysUser.enable': ['POST', '/api/sysuser/Enable/{id}'],
|
||||
'SysUser.list': ['GET', '/api/sysuser/List']
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* 用户中心接口文档 - version 0.0.1
|
||||
* 用户中心接口文档 - version 0.0.1-SNAPSHOT
|
||||
*
|
||||
* UserBase 用户中心 OpenAPI 文档(springdoc-openapi)
|
||||
* Demo project for Spring Boot
|
||||
*
|
||||
* OpenAPI version: 3.0.1
|
||||
* OpenAPI version: 3.0.0
|
||||
*
|
||||
* Contact:
|
||||
*
|
||||
* NOTE: This file is auto generated by the alova's vscode plugin.
|
||||
*
|
||||
@@ -10,7 +10,7 @@ let baseConfig: AxiosRequestConfig = {
|
||||
headers: {
|
||||
// "Content-Type": "application/x-www-form-urlencoded",
|
||||
"Content-Type": "application/json;charset=urf-8",
|
||||
yschapirequest: "1",
|
||||
bschapirequest: "1",
|
||||
"Access-Control-Allow-Headers": "Set-Cookie",
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
},
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BaseConf } from "@/types/Basics";
|
||||
|
||||
/**
|
||||
* 通过 Vite 环境变量管理配置,不再依赖 window.conf / config.js
|
||||
* 环境变量定义在项目根目录的 .env.dev / .env.base / .env.ld 中
|
||||
*/
|
||||
const env = import.meta.env;
|
||||
|
||||
let conf: BaseConf = {
|
||||
baseHost: env.VITE_BASE_HOST,
|
||||
ssoHost: env.VITE_SSO_HOST,
|
||||
serviceHost: env.VITE_SERVICE_HOST,
|
||||
relayConf: {
|
||||
ziyuankuHost: env.VITE_ZIYUANKU_HOST,
|
||||
zhuanxiangHost: env.VITE_ZHUANXIANG_HOST,
|
||||
},
|
||||
isMock: env.VITE_IS_MOCK === "true",
|
||||
isDebug: env.DEV,
|
||||
};
|
||||
|
||||
// HTTPS 自动升级:当前页面是 https 时,将所有 http:// 配置项升级为 https://
|
||||
var ishttps = "https:" == document.location.protocol ? true : false;
|
||||
if (ishttps) {
|
||||
Object.keys(conf).forEach(element => {
|
||||
if (typeof conf[element] === "string" && conf[element].indexOf("http://") > -1) {
|
||||
conf[element] = conf[element].replace("http://", "https://");
|
||||
}
|
||||
});
|
||||
// relayConf 内部也需要处理
|
||||
if (conf.relayConf) {
|
||||
Object.keys(conf.relayConf).forEach(key => {
|
||||
const val = (conf.relayConf as any)[key];
|
||||
if (typeof val === "string" && val.indexOf("http://") > -1) {
|
||||
(conf.relayConf as any)[key] = val.replace("http://", "https://");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default conf;
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { IModule } from "@/store";
|
||||
|
||||
/**
|
||||
* 左侧菜单数据(系统管理功能分组)
|
||||
* ----------------------------------------------------------------
|
||||
* 当前直接 Promise.resolve 返回写死数据;后续接入真实接口时,
|
||||
* 只需把 `return Promise.resolve(menu)` 替换为
|
||||
* `return alovaInstance.Request(Apis.xxx.getMenu())`
|
||||
* 即可无缝对接,调用方(store.fetchModules)无需任何改动。
|
||||
*
|
||||
* 分组结构:
|
||||
* 1. 组,用户管理 —— 顶层独立选项卡(单页)
|
||||
* 2. 应用管理 —— 顶层独立选项卡(= 应用列表主页 /app-manage/list);
|
||||
* 页内按钮跳转「应用类别管理」子页(/app-manage/category,不在菜单里,
|
||||
* 由 routes.ts 的 meta.parentId=1102 关联,面包屑「应用管理>应用类别管理」)
|
||||
* 3. 权限管理 —— 父目录:应用开通 / 组授权 / 权限一览
|
||||
* 4. 日志管理 —— 父目录:接口日志 / 系统日志
|
||||
*
|
||||
* 每个节点带唯一 id;子节点的 pid 由 layout.vue 的 flaten() 扁平化时
|
||||
* 依据父节点 id 自动推导,用于「高亮自身 + 面包屑展示父级」联动。
|
||||
* 菜单项不再渲染 icon。
|
||||
*/
|
||||
export function getMenuList(): Promise<IModule[]> {
|
||||
const menu: IModule[] = [
|
||||
// 1. 组,用户管理(顶层独立选项卡)
|
||||
{
|
||||
id: 1,
|
||||
modId: 1101,
|
||||
name: "组,用户管理",
|
||||
page: "/group-user",
|
||||
},
|
||||
// 2. 应用管理(顶层独立选项卡,= 应用列表主页;页内按钮跳转应用类别管理子页)
|
||||
{
|
||||
id: 2,
|
||||
modId: 1102,
|
||||
name: "应用管理",
|
||||
page: "/app-manage/list",
|
||||
},
|
||||
// 3. 权限管理(父目录)
|
||||
{
|
||||
id: 4,
|
||||
modId: 1108,
|
||||
name: "权限管理",
|
||||
page: "",
|
||||
childState: true,
|
||||
children: [
|
||||
{ id: 41, modId: 1103, name: "应用开通", page: "/app-open" },
|
||||
{ id: 42, modId: 1104, name: "组授权", page: "/group-auth" },
|
||||
{ id: 44, modId: 1110, name: "角色授权", page: "/role-authz/list" },
|
||||
{ id: 43, modId: 1105, name: "权限一览", page: "/permission-view" },
|
||||
],
|
||||
},
|
||||
// 4. 日志管理(父目录)
|
||||
{
|
||||
id: 5,
|
||||
modId: 1109,
|
||||
name: "日志管理",
|
||||
page: "",
|
||||
childState: true,
|
||||
children: [
|
||||
{ id: 51, modId: 1106, name: "接口日志", page: "/api-log" },
|
||||
{ id: 52, modId: 1107, name: "系统日志", page: "/sys-log" },
|
||||
{ id: 53, modId: 1111, name: "操作日志", page: "/oplog" },
|
||||
],
|
||||
},
|
||||
];
|
||||
return Promise.resolve(menu);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 914 B After Width: | Height: | Size: 914 B |
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 380 B |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 919 B After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 424 B After Width: | Height: | Size: 424 B |
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 740 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 795 B After Width: | Height: | Size: 795 B |
|
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 1014 B |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |