From 4eb67e246edf7815570462104ecc4dc15a0a66ec Mon Sep 17 00:00:00 2001 From: KodouWZF <3092554823@qq.com> Date: Mon, 2 Nov 2026 20:40:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E4=BA=A4=E4=BB=98):=20=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E4=BE=A7=E4=B8=A4=E5=8F=B0=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=A6=BB?= =?UTF-8?q?=E7=BA=BF=E9=83=A8=E7=BD=B2=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 内网机/外网机各起一套栈,两台之间不开任何端口,文件只经光闸在 gate/out 与对端 gate/in 之间单向摆渡。一份 compose + 一份 config + 一份 nginx 模板,全部差异收在 .env 里。 - docker-compose.yml:5 服务只发布前端端口;app 容器 IP 锁死并由 compose 注入 -Dcode.snowflake.workid.=,绕开兜底 99 超雪花 5 位上限导致启动即崩的问题 - .env.iw / .env.ow:出厂配置与随机口令已填好,仅 MESHSAFE_SECRET 故意留占位(以 MeshSafe 侧白名单为准);按 maintainer 决定原样入库 - install.sh iw|ow:按角色建 .env -> 自检 -> sha256 校验 tar -> 架构 核对 -> docker load 后校验 tag -> 建目录/属主/SELinux 标签 -> 起栈 并逐个等健康。修掉 pipefail 下 `grep | head -1` 静默中断、架构字段 误读 manifest.json(实际在 Config blob)、die() 未定义先被调用 - export-images.sh:就地刷新 images/*.tar、sql、.env 的 tag 与 SHA256SUMS,再打 dist 交付盘;tar 与 SHA256SUMS 由 .gitignore 排除 - sql/01+02:从 backend/db 的 PG 全量与初始数据覆盖拷贝。三个 upgrade-*.sql 的 PG 段(x_upload_config、x_file_security)已在 init-table-postgres-full.sql 内,空库跑 01->02 即完整现状 - smoke_test.py:18 条断言的现场验收,凭证全部走环境变量 - 部署说明.md:拓扑、交付清单、出厂口令表、iw/ow 差异、验收清单、 13 条已知坑 镜像 tar 不进 git,装机包由 export-images.sh 出盘。 --- deploy/offline/.env.example | 89 + deploy/offline/.env.iw | 62 + deploy/offline/.env.ow | 61 + deploy/offline/.gitignore | 7 + deploy/offline/VERSION.txt | 22 + deploy/offline/config/application.yml | 97 + deploy/offline/docker-compose.yml | 189 ++ deploy/offline/nginx/default.conf.template | 39 + deploy/offline/scripts/export-images.sh | 110 ++ deploy/offline/scripts/install.sh | 205 ++ deploy/offline/scripts/smoke_test.py | 270 +++ deploy/offline/sql/01-init-table.sql | 2082 ++++++++++++++++++++ deploy/offline/sql/02-init-data.sql | 46 + deploy/offline/sql/README.md | 12 + deploy/offline/部署说明.md | 219 ++ 15 files changed, 3510 insertions(+) create mode 100644 deploy/offline/.env.example create mode 100644 deploy/offline/.env.iw create mode 100644 deploy/offline/.env.ow create mode 100644 deploy/offline/.gitignore create mode 100644 deploy/offline/VERSION.txt create mode 100644 deploy/offline/config/application.yml create mode 100644 deploy/offline/docker-compose.yml create mode 100644 deploy/offline/nginx/default.conf.template create mode 100644 deploy/offline/scripts/export-images.sh create mode 100644 deploy/offline/scripts/install.sh create mode 100644 deploy/offline/scripts/smoke_test.py create mode 100644 deploy/offline/sql/01-init-table.sql create mode 100644 deploy/offline/sql/02-init-data.sql create mode 100644 deploy/offline/sql/README.md create mode 100644 deploy/offline/部署说明.md diff --git a/deploy/offline/.env.example b/deploy/offline/.env.example new file mode 100644 index 0000000..f62c925 --- /dev/null +++ b/deploy/offline/.env.example @@ -0,0 +1,89 @@ +# ============================================================================= +# s-file 客户侧离线部署 —— 唯一需要现场修改的文件 +# +# 用法:cp .env.example .env 然后按本机角色填写,两台服务器各填一份 +# 内网机:SFILE_ROLE=iw SFILE_TRANSFER_LOCATION=1 SFILE_SITE_ROOT=/usr/share/nginx/inside +# 外网机:SFILE_ROLE=ow SFILE_TRANSFER_LOCATION=2 SFILE_SITE_ROOT=/usr/share/nginx/outside +# +# 凡是写着「必须改」的值,不改起不来或者起来即安全隐患 +# ============================================================================= + +# ---------- 本机角色(必须改) ---------- +# iw=内网机 / ow=外网机,只影响容器名前缀与 MQ 交换机命名,两侧不能填同一个 +SFILE_ROLE=iw +# 传输方向标记:内网机 1,外网机 2。写反了跨网摆渡直接不工作 +SFILE_TRANSFER_LOCATION=1 +# 前端站点根:内网机 inside,外网机 outside(同一个 web 镜像内含两套前端包) +SFILE_SITE_ROOT=/usr/share/nginx/inside + +# ---------- 镜像(来自交付 tar,现场 docker load,不会联网拉取) ---------- +APP_IMAGE=sfile-app:1.0.0 +WEB_IMAGE=sfile-web:1.0.0 +PG_IMAGE=postgres:18 +REDIS_IMAGE=redis:8 +MQ_IMAGE=rabbitmq:3.13-management-alpine + +# postgres 数据目录挂载点:18 及以后是 /var/lib/postgresql,15 及以前是 /var/lib/postgresql/data +# 用交付的 PG_IMAGE 就不用动;自行换库版本时按上面规则改这一行 +PG_DATA_DIR=/var/lib/postgresql + +# ---------- 对外访问 ---------- +# 只暴露前端一个端口;后端/数据库/Redis/MQ 全部只在容器网络内,不映射宿主机 +WEB_HOST=0.0.0.0 +WEB_PORT=80 + +# ---------- 口令与密钥(必须改) ---------- +POSTGRES_DB=s_file +POSTGRES_USER=sfile +POSTGRES_PASSWORD=CHANGE_ME_pg_password +MQ_USER=sfile +MQ_PASSWORD=CHANGE_ME_mq_password +# JWT 签名密钥,至少 32 字符,两侧不要相同,丢失即所有登录态失效 +JWT_SECRET=CHANGE_ME_jwt_secret_at_least_32_chars +# 新建用户的初始密码 +USER_DEFAULT_PASSWORD=CHANGE_ME_user_init_pwd + +# ---------- 发号器(workerId)与容器网段 ---------- +# 后端雪花 ID 的 workerId 按「app 容器 IP」取键;取不到会回退 99,而 99 超过 5 位上限, +# 应用会直接启动失败(worker Id can't be greater than 31)。所以这里必须锁 IP。 +# 下面三个值必须同一网段,一般不用改;只有和客户机器上已有 docker 网段冲突时才整段换掉 +APP_SUBNET=172.30.88.0/24 +APP_GATEWAY=172.30.88.1 +APP_NODE_IP=172.30.88.10 +# 发号器机器号,0-31。两台独立服务器各跑一套,取同一个值也没问题; +# 若同一台机器上并跑内网+外网两套栈,必须给不同值,否则生成的 ID 会撞 +APP_WORK_ID=1 + +# ---------- 主机数据目录 ---------- +# 应用上传文件、日志、数据库/Redis/MQ 持久化的根目录,需独立数据盘且预留足够空间 +SFILE_DATA_ROOT=/data/sfile +# 光闸摆渡目录:本机的「发出去」目录,由光闸设备单向搬运到对端 +GATE_SEND_DIR=/data/sfile/gate/out +# 光闸摆渡目录:对端经光闸投递进来的目录 +GATE_RECV_DIR=/data/sfile/gate/in + +# ---------- MeshSafe 文件安全检测 ---------- +# 没有部署 MeshSafe 时置 false,前端自动隐藏「安全检测」列,跨网摆渡不受影响 +MESHSAFE_ENABLED=true +# 同机部署:http://host.docker.internal:44380(需 Docker >= 20.10) +# 独立服务器/集群:http://:<端口> +MESHSAFE_BASE_URL=http://host.docker.internal:44380 +# 必须与 MeshSafe 侧 MESHSAFE_API_CLIENTS 白名单里的凭证一致(必须改) +MESHSAFE_CLIENT_ID=CHANGE_ME_client_id +MESHSAFE_SECRET=CHANGE_ME_client_secret +# 送检来源标识,两侧区分开便于审计追溯 +MESHSAFE_CHANNEL=sfile-iw +# 检测服务不可用时:true=放行并记录,false=阻断(等保从严建议 false) +MESHSAFE_FAIL_OPEN=true +MESHSAFE_TIMEOUT_SECONDS=120 +MESHSAFE_POLL_TIMEOUT_MINUTES=60 +# 拉取检测结论的轮询周期,默认每 10 秒 +MESHSAFE_POLL_CRON=0/10 * * * * ? + +# ---------- 资源与运行时 ---------- +TZ=Asia/Shanghai +# 按机器内存调整,堆外还需预留 1G 左右 +APP_JAVA_OPTS=-Xms1g -Xmx3g -Dfile.encoding=UTF-8 +REDIS_MAXMEMORY=512mb +# 跨网文件监听任务周期,默认每分钟扫一次摆渡目录 +FILE_TRANSFER_MONITOR_CRON=0 */1 * * * ? diff --git a/deploy/offline/.env.iw b/deploy/offline/.env.iw new file mode 100644 index 0000000..94bd4d5 --- /dev/null +++ b/deploy/offline/.env.iw @@ -0,0 +1,62 @@ +# ============================================================================= +# s-file 交付配置 —— 内网机(拷到内网服务器后 mv 成 .env) +# +# 装机后必须立刻改的两处:admin 登录口令、新用户初始密码 USER_DEFAULT_PASSWORD +# 下面的库/MQ 口令是出厂默认值,客户内网可用,但交付单要写清并建议现场轮换 +# ============================================================================= + +# ---------- 本机角色 ---------- +SFILE_ROLE=iw +SFILE_TRANSFER_LOCATION=1 +SFILE_SITE_ROOT=/usr/share/nginx/inside + +# ---------- 镜像(images/ 里的 tar docker load 后就是这些 tag) ---------- +APP_IMAGE=sfile-app:1.0.0 +WEB_IMAGE=sfile-web:1.0.0 +PG_IMAGE=postgres:18 +REDIS_IMAGE=redis:8 +MQ_IMAGE=rabbitmq:3.13-management-alpine +PG_DATA_DIR=/var/lib/postgresql + +# ---------- 对外访问:只暴露前端 ---------- +WEB_HOST=0.0.0.0 +WEB_PORT=80 + +# ---------- 口令与密钥 ---------- +POSTGRES_DB=s_file +POSTGRES_USER=sfile +POSTGRES_PASSWORD=Sfile-iw-Pg-N1tOz1MxgdaS +MQ_USER=sfile +MQ_PASSWORD=Sfile-iw-Mq-UTqVSnCDZ0LB +JWT_SECRET=86a6b015f3292eb3fdab298d3eced941da4276d47e24e924b82ab8adee397d70 +USER_DEFAULT_PASSWORD=Sfile-iw-Usr-FBLS-0fA + +# ---------- 发号器与容器网段(与外网机各用一段,同机并跑时才需要错开) ---------- +APP_SUBNET=172.30.88.0/24 +APP_GATEWAY=172.30.88.1 +APP_NODE_IP=172.30.88.10 +APP_WORK_ID=1 + +# ---------- 主机数据目录 ---------- +SFILE_DATA_ROOT=/data/sfile +GATE_SEND_DIR=/data/sfile/gate/out +GATE_RECV_DIR=/data/sfile/gate/in + +# ---------- MeshSafe 文件安全检测 ---------- +# 凭证必须与 MeshSafe 侧 MESHSAFE_API_CLIENTS 白名单一致,现场按 MeshSafe 交付单核对 +MESHSAFE_ENABLED=true +MESHSAFE_BASE_URL=http://host.docker.internal:44380 +MESHSAFE_CLIENT_ID=sfile-iw +MESHSAFE_SECRET=CHANGE_ME_meshsafe_secret +MESHSAFE_CHANNEL=sfile-iw +# 等保从严建议 false:检测服务不可用时阻断而不是放行 +MESHSAFE_FAIL_OPEN=true +MESHSAFE_TIMEOUT_SECONDS=120 +MESHSAFE_POLL_TIMEOUT_MINUTES=60 +MESHSAFE_POLL_CRON=0/10 * * * * ? + +# ---------- 资源与运行时 ---------- +TZ=Asia/Shanghai +APP_JAVA_OPTS=-Xms1g -Xmx3g -Dfile.encoding=UTF-8 +REDIS_MAXMEMORY=512mb +FILE_TRANSFER_MONITOR_CRON=0 */1 * * * ? diff --git a/deploy/offline/.env.ow b/deploy/offline/.env.ow new file mode 100644 index 0000000..e0fb0d1 --- /dev/null +++ b/deploy/offline/.env.ow @@ -0,0 +1,61 @@ +# ============================================================================= +# s-file 交付配置 —— 外网机(拷到外网服务器后 mv 成 .env) +# +# 与内网机只差:角色/传输方向/站点根/网段/机器号/目录名/口令/JWT +# 装机后必须立刻改的两处:admin 登录口令、新用户初始密码 USER_DEFAULT_PASSWORD +# ============================================================================= + +# ---------- 本机角色 ---------- +SFILE_ROLE=ow +SFILE_TRANSFER_LOCATION=2 +SFILE_SITE_ROOT=/usr/share/nginx/outside + +# ---------- 镜像(images/ 里的 tar docker load 后就是这些 tag) ---------- +APP_IMAGE=sfile-app:1.0.0 +WEB_IMAGE=sfile-web:1.0.0 +PG_IMAGE=postgres:18 +REDIS_IMAGE=redis:8 +MQ_IMAGE=rabbitmq:3.13-management-alpine +PG_DATA_DIR=/var/lib/postgresql + +# ---------- 对外访问:只暴露前端 ---------- +WEB_HOST=0.0.0.0 +WEB_PORT=80 + +# ---------- 口令与密钥(与内网机不同) ---------- +POSTGRES_DB=s_file +POSTGRES_USER=sfile +POSTGRES_PASSWORD=Sfile-ow-Pg-pT-673--i1rk +MQ_USER=sfile +MQ_PASSWORD=Sfile-ow-Mq-sfW5Cv-Nk-cA +JWT_SECRET=9eeeea031052a156bb278607f89dac3e45282f8a934fa0ba01c24facacde6988 +USER_DEFAULT_PASSWORD=Sfile-ow-Usr-WqDJ7PDS + +# ---------- 发号器与容器网段 ---------- +APP_SUBNET=172.30.98.0/24 +APP_GATEWAY=172.30.98.1 +APP_NODE_IP=172.30.98.10 +APP_WORK_ID=2 + +# ---------- 主机数据目录 ---------- +SFILE_DATA_ROOT=/data/sfile +GATE_SEND_DIR=/data/sfile/gate/out +GATE_RECV_DIR=/data/sfile/gate/in + +# ---------- MeshSafe 文件安全检测 ---------- +# 外网机一般也走一次检测(接收解包后送检);该侧不部署 MeshSafe 时置 MESHSAFE_ENABLED=false +MESHSAFE_ENABLED=true +MESHSAFE_BASE_URL=http://host.docker.internal:44380 +MESHSAFE_CLIENT_ID=sfile-ow +MESHSAFE_SECRET=CHANGE_ME_meshsafe_secret +MESHSAFE_CHANNEL=sfile-ow +MESHSAFE_FAIL_OPEN=true +MESHSAFE_TIMEOUT_SECONDS=120 +MESHSAFE_POLL_TIMEOUT_MINUTES=60 +MESHSAFE_POLL_CRON=0/10 * * * * ? + +# ---------- 资源与运行时 ---------- +TZ=Asia/Shanghai +APP_JAVA_OPTS=-Xms1g -Xmx3g -Dfile.encoding=UTF-8 +REDIS_MAXMEMORY=512mb +FILE_TRANSFER_MONITOR_CRON=0 */1 * * * ? diff --git a/deploy/offline/.gitignore b/deploy/offline/.gitignore new file mode 100644 index 0000000..5ce9c00 --- /dev/null +++ b/deploy/offline/.gitignore @@ -0,0 +1,7 @@ +# 交付包里的镜像 tar 是出盘产物(约 650MB),不进 git +images/*.tar +images/SHA256SUMS.txt +dist/ + +# 现场改出来的配置不进 git(.env.iw / .env.ow 是出厂默认,随包交付) +.env diff --git a/deploy/offline/VERSION.txt b/deploy/offline/VERSION.txt new file mode 100644 index 0000000..ee6f41f --- /dev/null +++ b/deploy/offline/VERSION.txt @@ -0,0 +1,22 @@ +s-file 离线交付包 1.0.0 +构建平台: linux/amd64 +导出时间: 2026-11-02 13:58:00 + +镜像清单(sha256 见 images/SHA256SUMS.txt): + postgres.tar 161M "RepoTags":["postgres:18"] + rabbitmq.tar 84M "RepoTags":["rabbitmq:3.13-management-alpine"] + redis.tar 53M "RepoTags":["redis:8"] + sfile-app.tar 313M "RepoTags":["sfile-app:1.0.0"] + sfile-web.tar 39M "RepoTags":["sfile-web:1.0.0"] + +SQL: sql/01-init-table.sql (93974 B) sql/02-init-data.sql (2791 B) + +本包所含修复: 弹窗内容被长文件名撑破右边框(DialogContent grid 隐式轨道 min-content 撑开), + 传输/分享确认框的被拦截文件清单由截断改为换行完整显示。 + +现场步骤(两台服务器各执行一次): + 内网机 tar -xzf sfile-offline-1.0.0-linux-amd64.tar.gz && cd sfile-offline-1.0.0-linux-amd64 + vi .env.iw # 至少改 MESHSAFE_SECRET,按需改口令/网段/目录 + ./scripts/install.sh iw + 外网机 同上,改成 ./scripts/install.sh ow + 验证 python3 scripts/smoke_test.py diff --git a/deploy/offline/config/application.yml b/deploy/offline/config/application.yml new file mode 100644 index 0000000..2790fa8 --- /dev/null +++ b/deploy/offline/config/application.yml @@ -0,0 +1,97 @@ +# ============================================================================= +# s-file 客户侧静态默认配置(两台机器共用同一份,现场一般不用改) +# +# 随角色/环境变化的取值一律由 docker-compose 的环境变量注入,本文件不放: +# 数据库/Redis/MQ 地址与口令、JWT 密钥、用户初始密码、传输方向与摆渡目录、 +# MeshSafe 开关与凭证、MQ 交换机命名 —— 全部在 .env 里填 +# 这样交付物里不存在任何写死的口令或密钥 +# ============================================================================= + +server: + port: 8080 + +spring: + application: + name: s-file + datasource: + driver-class-name: org.postgresql.Driver + type: com.alibaba.druid.pool.DruidDataSource + druid: + initial-size: 5 + min-idle: 5 + max-active: 50 + validation-query: SELECT 1 + test-while-idle: true + # 摆渡解包/送检会在多任务里并发取连接,压太低会出现等待 + max-wait: 10000 + redis: + timeout: 5000 + lettuce: + pool: + max-active: 16 + max-idle: 8 + servlet: + multipart: + max-file-size: 500MB + max-request-size: 500MB + main: + allow-bean-definition-overriding: true + +mybatis-plus: + mapper-locations: classpath*:com/senmesh/**/mapper/*.xml + +# 平台编码,需与 sql/02-init-data.sql 里 uc_platform.platform_code 一致 +system: + code: 1 + +security: + jwt: + enable: true + # secret 由环境变量 SECURITY_JWT_SECRET 注入,勿在此填写 + expiration: 86400000 + header: Authorization + ignoring: /error,/favicon.ico + login-page: /login + superior-only: false + # 单机自闭环部署:权限走本地库,不接外部用户中心 + use-local: true + allow-if-all-abstain-decisions: true + oauth2: + enable: false + not-logs: "" + anonymous: "" + +# 新用户初始密码由环境变量 USER_DEFAULT_PASSWORD 注入 +user: + default: + password: ${USER_DEFAULT_PASSWORD:} + +file: + upload-dir: /data/upload + storage: + type: local + transfer: + # location / send-dir / receive-dir / monitor.cron 均由环境变量注入 + # 解包时是否再做一次内容风险识别(依赖下面的 dify,未部署 dify 时保持 false) + unpack-risk-check: false + +# 内容风险识别(Dify)。未部署 Dify 时留空并保持 file.transfer.unpack-risk-check=false +dify: + risk: + rule: + api-key: "" + scan: + api-key: "" + +# MeshSafe 全段由环境变量注入(MESHSAFE_*),未部署时 MESHSAFE_ENABLED=false 即可, +# 前端会自动隐藏「安全检测」列,跨网摆渡不受影响 +meshsafe: + review-sync-to-meshsafe: true + +# 接口文档在生产关闭 +swagger: + enable: false + +logging: + level: + com.senmesh: info diff --git a/deploy/offline/docker-compose.yml b/deploy/offline/docker-compose.yml new file mode 100644 index 0000000..4f6eecf --- /dev/null +++ b/deploy/offline/docker-compose.yml @@ -0,0 +1,189 @@ +# ============================================================================= +# s-file 信创文件安全传输系统 —— 客户侧单台服务器部署(内网机 / 外网机通用同一份) +# +# 一台服务器一套本栈;两台机器靠光闸摆渡目录互通,彼此之间不开任何网络端口。 +# 所有随角色变化的取值都在同目录 .env 里,本文件现场不用改。 +# +# ./scripts/install.sh iw # 内网机(第一次会把 .env.iw 拷成 .env) +# ./scripts/install.sh ow # 外网机 +# +# 手动起也行:cp .env.iw .env && vi .env -> docker load -i images/*.tar -> docker compose up -d +# +# 只发布前端端口;后端 / PostgreSQL / Redis / RabbitMQ 仅在容器网络内可达。 +# ============================================================================= + +name: sfile-${SFILE_ROLE:?缺少 SFILE_ROLE(内网机 iw / 外网机 ow)} + +services: + web: + image: ${WEB_IMAGE:?缺少 WEB_IMAGE} + container_name: sfile-${SFILE_ROLE}-web + restart: unless-stopped + ports: + - "${WEB_HOST:-0.0.0.0}:${WEB_PORT:-80}:80" + environment: + TZ: ${TZ:-Asia/Shanghai} + # nginx 官方镜像的 /etc/nginx/templates/*.template 会按 NGINX_ENVSUBST_FILTER + # 过滤后 envsubst 生成 conf;限定只替换 SFILE_ 开头的变量,避免动到 $uri/$http_host + NGINX_ENVSUBST_FILTER: ^SFILE_ + SFILE_SITE_ROOT: ${SFILE_SITE_ROOT:?缺少 SFILE_SITE_ROOT} + SFILE_APP_UPSTREAM: app + volumes: + - ./nginx/default.conf.template:/etc/nginx/templates/default.conf.template:ro + depends_on: + app: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -sf -o /dev/null http://127.0.0.1:80/ || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 20s + networks: [sfile] + + app: + image: ${APP_IMAGE:?缺少 APP_IMAGE} + container_name: sfile-${SFILE_ROLE}-app + restart: unless-stopped + stop_grace_period: 30s + environment: + TZ: ${TZ:-Asia/Shanghai} + # 发号器 workerId 按「容器 IP」取键,取不到会回退 99 而 99 > 31 直接启动失败, + # 因此这里锁死 app 的容器 IP,并把同一个键写进 JVM 参数,两处永远一致 + JAVA_OPTS: "${APP_JAVA_OPTS:--Xms1g -Xmx3g -Dfile.encoding=UTF-8} -Dcode.snowflake.workid.${APP_NODE_IP:?缺少 APP_NODE_IP}=${APP_WORK_ID:?缺少 APP_WORK_ID}" + SPRING_CONFIG_ADDITIONAL_LOCATION: file:/config/application.yml + + SPRING_DATASOURCE_URL: jdbc:postgresql://pg:5432/${POSTGRES_DB:?缺少 POSTGRES_DB} + SPRING_DATASOURCE_USERNAME: ${POSTGRES_USER:?缺少 POSTGRES_USER} + SPRING_DATASOURCE_PASSWORD: ${POSTGRES_PASSWORD:?缺少 POSTGRES_PASSWORD} + + SPRING_REDIS_HOST: redis + SPRING_REDIS_PORT: 6379 + + SPRING_RABBITMQ_HOST: mq + SPRING_RABBITMQ_PORT: 5672 + SPRING_RABBITMQ_USERNAME: ${MQ_USER:?缺少 MQ_USER} + SPRING_RABBITMQ_PASSWORD: ${MQ_PASSWORD:?缺少 MQ_PASSWORD} + # 两侧交换机/广播 ID 必须不同,防止有人把两套栈并到一台机器时串消息 + RABBITMQ_CUSTOM_TOPIC_EXCHANGE_NAME: s-file-${SFILE_ROLE}-topic + RABBITMQ_CUSTOM_DELAYED_EXCHANGE_NAME: s-file-${SFILE_ROLE}-delayed + RABBITMQ_CUSTOM_BROADCAST_ID: s-file-${SFILE_ROLE}-broadcast + + SECURITY_JWT_SECRET: ${JWT_SECRET:?缺少 JWT_SECRET} + USER_DEFAULT_PASSWORD: ${USER_DEFAULT_PASSWORD:?缺少 USER_DEFAULT_PASSWORD} + + # 内网机 1 / 外网机 2,写反了摆渡方向就反了 + FILE_TRANSFER_LOCATION: "${SFILE_TRANSFER_LOCATION:?缺少 SFILE_TRANSFER_LOCATION}" + FILE_TRANSFER_SEND_DIR: /data/transfer/send + FILE_TRANSFER_RECEIVE_DIR: /data/transfer/receive + FILE_TRANSFER_MONITOR_CRON: "${FILE_TRANSFER_MONITOR_CRON:-0 */1 * * * ?}" + + MESHSAFE_ENABLED: ${MESHSAFE_ENABLED:-true} + MESHSAFE_BASE_URL: ${MESHSAFE_BASE_URL:-} + MESHSAFE_CLIENT_ID: ${MESHSAFE_CLIENT_ID:-} + MESHSAFE_SECRET: ${MESHSAFE_SECRET:-} + MESHSAFE_CHANNEL: ${MESHSAFE_CHANNEL:-sfile-${SFILE_ROLE}} + MESHSAFE_FAIL_OPEN: ${MESHSAFE_FAIL_OPEN:-true} + MESHSAFE_TIMEOUT_SECONDS: ${MESHSAFE_TIMEOUT_SECONDS:-120} + MESHSAFE_POLL_TIMEOUT_MINUTES: ${MESHSAFE_POLL_TIMEOUT_MINUTES:-60} + MESHSAFE_POLL_CRON: ${MESHSAFE_POLL_CRON:-0/10 * * * * ?} + volumes: + - ./config/application.yml:/config/application.yml:ro + - ${SFILE_DATA_ROOT:?缺少 SFILE_DATA_ROOT}/upload:/data/upload + - ${SFILE_DATA_ROOT}/logs:/app/logs + # 摆渡目录:容器内固定路径,宿主机侧由光闸设备负责单向搬运 + - ${GATE_SEND_DIR:?缺少 GATE_SEND_DIR}:/data/transfer/send + - ${GATE_RECV_DIR:?缺少 GATE_RECV_DIR}:/data/transfer/receive + # MeshSafe 与应用同机部署时靠它从容器内访问宿主机端口; + # Docker < 20.10 不支持 host-gateway,或 MeshSafe 在独立服务器时,删掉这两行即可 + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + pg: + condition: service_healthy + redis: + condition: service_healthy + mq: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -s --max-time 5 -o /dev/null http://127.0.0.1:8080/ || exit 1"] + interval: 30s + timeout: 8s + retries: 5 + # 首次建库要跑全量 DDL,冷启动给足时间 + start_period: 180s + networks: + sfile: + ipv4_address: ${APP_NODE_IP} + + pg: + image: ${PG_IMAGE:?缺少 PG_IMAGE} + container_name: sfile-${SFILE_ROLE}-pg + restart: unless-stopped + stop_grace_period: 60s + environment: + TZ: ${TZ:-Asia/Shanghai} + POSTGRES_DB: ${POSTGRES_DB:?缺少 POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER:?缺少 POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?缺少 POSTGRES_PASSWORD} + # 空库首次启动才执行 initdb.d,已有数据卷时会被忽略 + POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C" + volumes: + # postgres 18+ 数据目录挂载点是 /var/lib/postgresql,15- 是 .../data,见 .env 的 PG_DATA_DIR + - ${SFILE_DATA_ROOT}/pgdata:${PG_DATA_DIR:-/var/lib/postgresql} + - ./sql/01-init-table.sql:/docker-entrypoint-initdb.d/01-init-table.sql:ro + - ./sql/02-init-data.sql:/docker-entrypoint-initdb.d/02-init-data.sql:ro + healthcheck: + # 必须强制走 TCP(-h 127.0.0.1):首次 initdb 期间镜像会临时起一个只听 unix socket + # 的实例,此时用默认 socket 探测也会返回成功,导致 app 抢跑连库失败 + test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 60s + networks: [sfile] + + redis: + image: ${REDIS_IMAGE:?缺少 REDIS_IMAGE} + container_name: sfile-${SFILE_ROLE}-redis + restart: unless-stopped + command: ["redis-server", "--appendonly", "yes", "--maxmemory", "${REDIS_MAXMEMORY:-512mb}", "--maxmemory-policy", "allkeys-lru"] + environment: + TZ: ${TZ:-Asia/Shanghai} + volumes: + - ${SFILE_DATA_ROOT}/redis:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 3s + retries: 5 + networks: [sfile] + + mq: + image: ${MQ_IMAGE:?缺少 MQ_IMAGE} + container_name: sfile-${SFILE_ROLE}-mq + restart: unless-stopped + stop_grace_period: 30s + environment: + TZ: ${TZ:-Asia/Shanghai} + RABBITMQ_DEFAULT_USER: ${MQ_USER:?缺少 MQ_USER} + RABBITMQ_DEFAULT_PASS: ${MQ_PASSWORD:?缺少 MQ_PASSWORD} + volumes: + - ${SFILE_DATA_ROOT}/mq:/var/lib/rabbitmq + healthcheck: + test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"] + interval: 20s + timeout: 10s + retries: 10 + start_period: 60s + networks: [sfile] + +networks: + sfile: + driver: bridge + ipam: + config: + # 锁 app 容器 IP 是为了发号器 workerId 取键;subnet/网关/app IP 三个值必须同网段。 + # 若与客户机器上已有 docker 网段冲突,整段换掉(如 172.31.88.0/24)即可 + - subnet: ${APP_SUBNET:?缺少 APP_SUBNET} + gateway: ${APP_GATEWAY:?缺少 APP_GATEWAY} diff --git a/deploy/offline/nginx/default.conf.template b/deploy/offline/nginx/default.conf.template new file mode 100644 index 0000000..44257b0 --- /dev/null +++ b/deploy/offline/nginx/default.conf.template @@ -0,0 +1,39 @@ +server { + listen 80; + server_name _; + charset utf-8; + + # 内网机 = /usr/share/nginx/inside,外网机 = /usr/share/nginx/outside(见 .env) + root ${SFILE_SITE_ROOT}; + index index.html; + + # 与后端 multipart 上限(500MB)保持一致,改这里要同步改 application.yml + client_max_body_size 500m; + + # /api/xxx -> 后端 /xxx(去掉 /api 前缀,与前端构建时的 VITE_API_URL=/api 对应) + location /api/ { + proxy_pass http://${SFILE_APP_UPSTREAM}:8080/; + proxy_http_version 1.1; + # 必须保留端口($http_host):后端 getRootDomain 对「无端口的 IP/localhost」会抛 + # IllegalStateException,导致登录成功回调 500。改成 $host 就会复现这个坑 + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 600s; + proxy_send_timeout 600s; + # 大文件上传不在 nginx 侧缓冲,直接流给后端 + proxy_request_buffering off; + } + + # SPA:hash 路由,静态资源找不到时回落 index.html + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|mjs|css|woff2?|ttf|png|jpe?g|gif|svg|ico)$ { + expires 7d; + access_log off; + } +} diff --git a/deploy/offline/scripts/export-images.sh b/deploy/offline/scripts/export-images.sh new file mode 100644 index 0000000..4d53d68 --- /dev/null +++ b/deploy/offline/scripts/export-images.sh @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +# ============================================================================= +# 出交付盘:在我方构建机上执行,把 deploy/offline/ 刷新成一个可直接带走的完整交付包 +# +# ./scripts/export-images.sh 1.0.0 # 默认 linux/amd64 +# TARGET_PLATFORM=linux/arm64 ./scripts/export-images.sh 1.0.0 # 鲲鹏/飞腾 +# +# 刷新后的 deploy/offline/(现场照它装,不再依赖本仓库): +# ├── images/*.tar 五个镜像 + SHA256SUMS.txt +# ├── sql/01-init-table.sql 建表 02-init-data.sql 初始数据 +# ├── .env.iw / .env.ow 内网机 / 外网机出厂配置 +# └── docker-compose.yml config/ nginx/ scripts/ VERSION.txt +# 另外产出 deploy/offline/dist/sfile-offline-<版本>-<平台>.tar.gz(拷盘/刻录用) +# +# 前置:backend/target/s-file.jar 与 frontend/ 已构建好(见仓库根 README) +# ============================================================================= +set -euo pipefail + +VER="${1:?用法:export-images.sh <版本号>,如 1.0.0}" +PLATFORM="${TARGET_PLATFORM:-linux/amd64}" +PLATFORM_TAG="$(echo "$PLATFORM" | tr -d '/:')" + +HERE="$(cd "$(dirname "$0")/.." && pwd)" # deploy/offline +ROOT="$(cd "$HERE/../.." && pwd)" # 仓库根 +OUT="$HERE/dist/sfile-offline-$VER-$PLATFORM_TAG" + +APP_IMAGE="sfile-app:$VER" +WEB_IMAGE="sfile-web:$VER" +PG_IMAGE="postgres:18" # 验证过的是 18.3,同大版本即可 +REDIS_IMAGE="redis:8" # 验证过的是 8.6.3 +MQ_IMAGE="rabbitmq:3.13-management-alpine" # 验证过的是 3.13.7 + +say() { printf '\033[1;36m==> %s\033[0m\n' "$1"; } + +say "构建目标平台:$PLATFORM" +[ "$PLATFORM" = "linux/amd64" ] || say "非 amd64 需要 buildx/QEMU 模拟器,慢属正常" + +# ---------- 1. 构建业务镜像 ---------- +say "构建 $APP_IMAGE" +docker build --platform "$PLATFORM" -f "$ROOT/deploy/docker/Dockerfile.app" -t "$APP_IMAGE" "$ROOT/backend/target" + +say "构建 $WEB_IMAGE(内含内网+外网两套前端,耗时较长)" +docker build --platform "$PLATFORM" -f "$ROOT/deploy/docker/Dockerfile.web" -t "$WEB_IMAGE" "$ROOT/frontend" + +# ---------- 2. 基础镜像就位(本机没有才拉,避免交付时缺包) ---------- +for img in "$PG_IMAGE" "$REDIS_IMAGE" "$MQ_IMAGE"; do + docker image inspect "$img" >/dev/null 2>&1 || { + say "本机没有 $img,拉取 $PLATFORM 版本" + docker pull --platform "$PLATFORM" "$img" + } +done + +# ---------- 3. 镜像 tar 就地导出 ---------- +say "docker save 导出镜像到 $HERE/images/(约 700M,耐心等)" +mkdir -p "$HERE/images" +rm -f "$HERE"/images/*.tar "$HERE/images/SHA256SUMS.txt" +docker save -o "$HERE/images/sfile-app.tar" "$APP_IMAGE" +docker save -o "$HERE/images/sfile-web.tar" "$WEB_IMAGE" +docker save -o "$HERE/images/postgres.tar" "$PG_IMAGE" +docker save -o "$HERE/images/redis.tar" "$REDIS_IMAGE" +docker save -o "$HERE/images/rabbitmq.tar" "$MQ_IMAGE" +( cd "$HERE/images" && sha256sum *.tar > SHA256SUMS.txt ) + +# ---------- 4. SQL 与 .env 就地刷新 ---------- +# 建表脚本必须先于灌数据脚本执行,靠文件名前缀定序 +say "刷新 sql/(来自 backend/db/)" +mkdir -p "$HERE/sql" +cp "$ROOT/backend/db/init-table-postgres-full.sql" "$HERE/sql/01-init-table.sql" +cp "$ROOT/backend/db/init-data.sql" "$HERE/sql/02-init-data.sql" + +say "刷新 .env.iw / .env.ow 的镜像 tag 为 $VER" +for f in .env.iw .env.ow; do + [ -f "$HERE/$f" ] || { say "缺少 $HERE/$f,跳过"; continue; } + sed -i -E "s/^APP_IMAGE=.*/APP_IMAGE=sfile-app:$VER/; s/^WEB_IMAGE=.*/WEB_IMAGE=sfile-web:$VER/" "$HERE/$f" +done + +# ---------- 5. 组交付目录并打包 ---------- +say "组装 $OUT" +rm -rf "$OUT" +mkdir -p "$OUT/sql" +cp "$HERE/docker-compose.yml" "$HERE/.env.example" "$HERE/.env.iw" "$HERE/.env.ow" "$OUT/" +cp -r "$HERE/config" "$HERE/nginx" "$HERE/scripts" "$HERE/sql" "$HERE/images" "$OUT/" +cp "$HERE/.gitignore" "$OUT/" 2>/dev/null || true + +{ + echo "s-file 离线交付包 $VER" + echo "构建平台: $PLATFORM" + echo "导出时间: $(date '+%F %T')" + echo + echo "镜像清单(sha256 见 images/SHA256SUMS.txt):" + for t in "$OUT"/images/*.tar; do + TAGS="$(tar -xOf "$t" manifest.json | tr -d '\n' | { grep -oE '"RepoTags":\[[^]]*\]' || true; } | awk 'NR==1{print}')" + printf ' %-18s %-8s %s\n' "$(basename "$t")" "$(du -h "$t" | cut -f1)" "${TAGS:-?}" + done + echo + echo "SQL: sql/01-init-table.sql sql/02-init-data.sql" + echo + echo "现场步骤(两台服务器各执行一次):" + echo " 内网机 tar -xzf sfile-offline-$VER-$PLATFORM_TAG.tar.gz && cd sfile-offline-$VER-$PLATFORM_TAG" + echo " vi .env.iw # 至少改 MESHSAFE_SECRET,按需改口令/网段/目录" + echo " ./scripts/install.sh iw" + echo " 外网机 同上,改成 ./scripts/install.sh ow" + echo " 验证 python3 scripts/smoke_test.py" +} > "$OUT/VERSION.txt" + +say "打包交付盘" +mkdir -p "$HERE/dist" +tar -czf "$OUT.tar.gz" -C "$(dirname "$OUT")" "$(basename "$OUT")" +du -h "$OUT.tar.gz" | awk '{print " 交付盘: "$1" "$2}' +echo " 两台服务器各自解压同一份盘,装完只差 install.sh 的参数(iw / ow)" diff --git a/deploy/offline/scripts/install.sh b/deploy/offline/scripts/install.sh new file mode 100644 index 0000000..ee23078 --- /dev/null +++ b/deploy/offline/scripts/install.sh @@ -0,0 +1,205 @@ +#!/usr/bin/env bash +# ============================================================================= +# s-file 客户侧一键安装(在每台服务器上各执行一次) +# +# cd /opt/sfile && ./scripts/install.sh iw # 内网机 +# cd /opt/sfile && ./scripts/install.sh ow # 外网机 +# +# 首次执行会按角色把交付包里的 .env.iw / .env.ow 拷成 .env;之后改 .env 即可, +# 想回到交付默认值就删掉 .env 重跑。 +# +# 做五件事:按角色选配置 -> 环境自检 -> 载入镜像 tar -> 建数据目录 -> 起栈并等到健康 +# 幂等:重复执行只重建有差异的容器,不动已初始化好的数据库 +# ============================================================================= +set -euo pipefail + +HERE="$(cd "$(dirname "$0")/.." && pwd)" +cd "$HERE" + +ROLE_ARG="${1:-}" + +envget() { grep -E "^[[:space:]]*$1=" .env | tail -1 | cut -d= -f2- | tr -d '"' | sed 's/[[:space:]]*$//'; } + +step() { printf '\n\033[1;36m==> %s\033[0m\n' "$1"; } +die() { printf '\033[1;31m!!! %s\033[0m\n' "$1" >&2; exit 1; } + +case "$ROLE_ARG" in + ""|iw|ow) ;; + *) die "未知角色「$ROLE_ARG」,只支持 iw(内网机)或 ow(外网机)" ;; +esac + +# ---------- 0. 配置就位 ---------- +if [ ! -f .env ]; then + case "$ROLE_ARG" in + iw|ow) + [ -f ".env.$ROLE_ARG" ] || die "缺少 .env.$ROLE_ARG(交付包不完整)" + step "按角色初始化配置:cp .env.$ROLE_ARG .env" + cp ".env.$ROLE_ARG" .env + ;; + "") die "用法:./scripts/install.sh iw|ow(iw=内网机,ow=外网机)" ;; + esac +elif [ -n "$ROLE_ARG" ]; then + # 已有 .env 时不让角色参数悄悄覆盖现场改动 + CUR_ROLE="$(envget SFILE_ROLE)" + [ "$CUR_ROLE" = "$ROLE_ARG" ] \ + || die ".env 已存在且角色是 $CUR_ROLE,与参数 $ROLE_ARG 不符;确认要换角色请先 rm .env" +fi + +# ---------- 0. 前置自检 ---------- +step "检查 docker 与 compose" +command -v docker >/dev/null 2>&1 || die "未安装 docker,请先装 Docker Engine(信创系统用厂商源,版本号 >= 20.10)" +docker info >/dev/null 2>&1 || die "docker 守护进程不可用,检查服务是否启动、当前用户是否在 docker 组" +if docker compose version >/dev/null 2>&1; then + DC="docker compose" +elif command -v docker-compose >/dev/null 2>&1; then + DC="docker-compose" +else + die "缺少 compose:安装 docker compose v2 插件,或用 docker-compose 命令" +fi +echo " 使用:$DC" +DOCKER_VER="$(docker version --format '{{.Server.Version}}')" +echo " docker server:$DOCKER_VER" + +[ -f .env ] || die "缺少 .env —— 先执行:cp .env.example .env && vi .env" + +if grep -q "CHANGE_ME" .env; then + # MeshSafe 的 secret 只有真的启用检测时才必须填,否则客户没接 MeshSafe 也装不了机 + if [ "$(envget MESHSAFE_ENABLED)" = "true" ] && grep -qE "^MESHSAFE_SECRET=.*CHANGE_ME" .env; then + die "启用了 MeshSafe 但 MESHSAFE_SECRET 还是占位值:按 MeshSafe 交付单改成白名单里的 secret,或把 MESHSAFE_ENABLED 改成 false" + fi + rest="$(grep -n CHANGE_ME .env | grep -v '^.*MESHSAFE_SECRET=' | cut -d: -f2 | tr '\n' ' ')" + [ -z "$rest" ] || die ".env 里还有 CHANGE_ME 未替换:$rest" +fi + +step "检查交付物" +[ -d images ] || die "缺少 images/ 目录(镜像 tar 应与本脚本同级目录的 images/ 下)" +tar_count=$(find images -maxdepth 1 -name '*.tar' | wc -l) +[ "$tar_count" -eq 5 ] || die "images/ 下应有 5 个 .tar(app/web/pg/redis/mq),实际 $tar_count 个:$(ls images)" +for f in sql/01-init-table.sql sql/02-init-data.sql; do + [ -s "$f" ] || die "缺少 $f,参见 sql/README.md" +done +echo " 镜像 tar:5 个,SQL 脚本就位" + +# ---------- 1. 校验交付盘 + 镜像架构 ---------- +# 650M 的 tar 经 U 盘/内网 scp 拷坏是最常见的现场故障,坏包 load 到一半才报错更难查 +if [ -f images/SHA256SUMS.txt ] && [ "${SKIP_CHECKSUM:-0}" != "1" ]; then + step "校验镜像 tar 完整性(sha256,约半分钟)" + ( cd images && sha256sum -c SHA256SUMS.txt ) \ + || die "有 tar 与 SHA256SUMS.txt 不一致:交付盘拷坏了,重新拷一份;确认无碍可 SKIP_CHECKSUM=1 跳过" +fi + +step "核对镜像架构" +HOST_ARCH="$(uname -m)" +case "$HOST_ARCH" in + x86_64) EXPECT="amd64" ;; + aarch64|arm64) EXPECT="arm64" ;; + *) EXPECT="unknown" ;; +esac +echo " 本机:$HOST_ARCH (期望镜像架构 ${EXPECT})" + +# 架构在镜像的 config blob 里,不在 manifest.json —— manifest.json 只给 Config 的路径 +tar_arch() { + local t="$1" cfg + cfg="$(tar -xOf "$t" manifest.json 2>/dev/null | tr -d '\n' \ + | { grep -oE '"Config":"[^"]+"' || true; } | awk -F'"' 'NR==1{print $4}')" + [ -n "$cfg" ] || { echo ""; return 0; } + tar -xOf "$t" "$cfg" 2>/dev/null | tr -d '\n' \ + | { grep -oE '"architecture":"[a-z0-9-]+"' || true; } | awk -F'"' 'NR==1{print $4}' +} + +if [ "$EXPECT" != "unknown" ]; then + # app 和 pg 各查一个:一个是自己构建的,一个是基础镜像,架构错了必是出盘时选错平台 + for t in images/sfile-app.tar images/postgres.tar; do + [ -f "$t" ] || continue + # 不能用 `| head -1`:pipefail 下 head 提前关管道会让上游 grep 收到 SIGPIPE(141),脚本静默中断 + IMG_ARCH="$(tar_arch "$t")" + if [ -n "${IMG_ARCH:-}" ] && [ "$IMG_ARCH" != "$EXPECT" ]; then + die "$(basename "$t") 是 $IMG_ARCH,本机是 $EXPECT —— 别硬装,回来重出对应架构的包" + fi + printf ' %-18s %s\n' "$(basename "$t")" "${IMG_ARCH:-未识别,继续}" + done +fi + +# ---------- 2. 载入镜像 ---------- +step "docker load 载入离线镜像" +for t in images/*.tar; do + echo " <- $t" + docker load -i "$t" +done + +# compose 用的是 .env 里的 tag,缺一个就会去联网拉,离线机上直接卡死 +for img in "$(envget APP_IMAGE)" "$(envget WEB_IMAGE)" "$(envget PG_IMAGE)" \ + "$(envget REDIS_IMAGE)" "$(envget MQ_IMAGE)"; do + docker image inspect "$img" >/dev/null 2>&1 \ + || die "载入后仍看不到镜像 $img —— tar 与 .env 的 tag 不一致,别继续,回来看 images/ 和 .env" + echo " ✓ $img" +done + +# ---------- 3. 目录 ---------- +step "创建数据与摆渡目录" +DATA_ROOT="$(envget SFILE_DATA_ROOT)" +GATE_SEND="$(envget GATE_SEND_DIR)" +GATE_RECV="$(envget GATE_RECV_DIR)" +[ -n "$DATA_ROOT" ] || die ".env 里 SFILE_DATA_ROOT 为空" + +mkdir -p "$DATA_ROOT"/{upload,logs,pgdata,redis,mq} +mkdir -p "$GATE_SEND" "$GATE_RECV" + +# 数据盘挂载点没起来时直接建目录会落到根分区,先确认一下 +if ! mountpoint -q "$DATA_ROOT" 2>/dev/null; then + printf ' \033[1;33m提示:%s 不是独立挂载点,确认容量够(上传+库+日志)\033[0m\n' "$DATA_ROOT" +fi + +# pgdata 提前给到容器内 postgres 的 uid=999,避免镜像入口脚本 chown 失败; +# redis/mq 的官方入口脚本自己会改属主,这里只保证目录存在且可写 +chown -R 999:999 "$DATA_ROOT/pgdata" 2>/dev/null || true +chmod -R 750 "$DATA_ROOT" + +# SELinux(麒麟/统信多为 enforcing):无 :z 标记的绑定挂载需要放开标签,否则库起不来 +if command -v chcon >/dev/null 2>&1; then + chcon -Rt svirt_sandbox_file_t "$DATA_ROOT" "$GATE_SEND" "$GATE_RECV" 2>/dev/null \ + || echo " chcon 失败(未启用 SELinux 可忽略)" +fi +echo " $DATA_ROOT / $GATE_SEND / $GATE_RECV 就位" + +# ---------- 4. 起栈 ---------- +step "启动服务(首次建库约 1-3 分钟)" +$DC up -d + +step "等待健康检查" +ROLE="$(envget SFILE_ROLE)" +for svc in pg redis mq app web; do + name="sfile-${ROLE}-${svc}" + printf ' %-24s ' "$name" + for _ in $(seq 1 60); do + st="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)" + case "$st" in + healthy|running) printf '\033[32m%s\033[0m\n' "$st"; break ;; + missing) sleep 2 ;; + *) sleep 5 ;; + esac + done + [ "${st:-}" = "healthy" ] || [ "${st:-}" = "running" ] || { + printf '\033[31m超时(最后状态:%s)\033[0m\n' "${st:-unknown}" + $DC ps + echo " 看日志:docker logs --tail 200 ${name}" + exit 1 + } +done + +# ---------- 5. 出口信息 ---------- +IP="$(hostname -I 2>/dev/null | tr ' ' '\n' | { grep -E '^[0-9]+\.' || true; } | awk 'NR==1{print}')" +PORT="$(envget WEB_PORT)" +step "完成" +cat <}:${PORT:-80} + 角色: ${ROLE}(传输方向 location=$(envget SFILE_TRANSFER_LOCATION),站点 $(envget SFILE_SITE_ROOT)) + 摆渡: 发出 $(envget GATE_SEND_DIR) / 收到 $(envget GATE_RECV_DIR) + MeshSafe: $(envget MESHSAFE_ENABLED) -> $(envget MESHSAFE_BASE_URL) + + 常用命令: + $DC ps 看状态 + docker logs -f sfile-${ROLE}-app 看后端日志 + $DC restart app 改完 .env/config 后重启后端 +EOF +echo " 首次登录后立刻改掉 admin 的默认口令。" diff --git a/deploy/offline/scripts/smoke_test.py b/deploy/offline/scripts/smoke_test.py new file mode 100644 index 0000000..1e1d0a3 --- /dev/null +++ b/deploy/offline/scripts/smoke_test.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""s-file 交付验收冒烟测试:登录 -> 上传送检 -> 不通过拦截 -> 跨网摆渡 -> 外网解包再送检 -> 人工复核。 + +在能同时访问内网机与外网机前端的机器上运行(需要 python3 + requests)。 +全部取值走环境变量,脚本内不含任何口令: + + SFILE_IW_BASE=http://10.0.0.11/api SFILE_OW_BASE=http://10.0.0.12/api \\ + SFILE_ADMIN_USER=admin SFILE_ADMIN_PWD='<现场口令>' python3 smoke_test.py + +退出码:0 全通过,1 有失败项,2 参数缺失。 +""" +import json +import os +import sys +import time +import uuid + +try: + import requests +except ImportError: + print("缺少 requests:pip3 install requests") + sys.exit(2) + +IW = os.environ.get("SFILE_IW_BASE", "http://127.0.0.1:28180/api").rstrip("/") +OW = os.environ.get("SFILE_OW_BASE", "http://127.0.0.1:28280/api").rstrip("/") +USER = os.environ.get("SFILE_ADMIN_USER", "admin") +PWD = os.environ.get("SFILE_ADMIN_PWD", "") +# 光闸发出目录(内网机侧本地路径)。跨机验收时留空,只断言接口成功 +GATE_IW2OW = os.environ.get("SFILE_GATE_SEND_DIR", "") + +# EICAR 标准测试串;后端的 MeshSafe 结构引擎会识别「内嵌 EICAR」, +# 因此允许在尾部追加唯一标记(见 main 里的载荷说明) +EICAR = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" +STATUS = {"1": "检测中", "2": "检测通过", "3": "检测不通过", "4": "检测失败"} + +results = [] + + +def rec(name, ok, detail=""): + results.append((name, bool(ok), detail)) + print((" [OK] " if ok else " [FAIL] ") + name + (" " + detail if detail else ""), flush=True) + + +class Side: + def __init__(self, base, label): + self.base = base + self.label = label + self.s = requests.Session() + self.token = None + + def _hdr(self, csrf=False): + # 后端 MyLoginSuccessHandler 靠 x-requested-with 决定是否回 JSON,否则 302 跳转 + h = {"x-requested-with": "XMLHttpRequest"} + if self.token: + h["Authorization"] = "Bearer " + self.token + if csrf: + t = self.s.cookies.get("XSRF-TOKEN") + if t: + h["X-XSRF-TOKEN"] = t + return h + + def login(self, user, pwd): + r = self.s.post(self.base + "/login", + data={"username": user, "password": pwd, "userType": 1}, + headers=self._hdr(), timeout=30) + j = self._json(r) + if str(j.get("code")) != "200": + raise RuntimeError("登录失败 HTTP %s:%s" % (r.status_code, json.dumps(j, ensure_ascii=False)[:300])) + self.token = j["data"]["token"] + # 触发 XSRF-TOKEN cookie(POST/PUT 需要带 X-XSRF-TOKEN) + self.s.get(self.base + "/user/info", headers=self._hdr(), timeout=30) + return j + + def get(self, path, params=None): + r = self.s.get(self.base + path, params=params, headers=self._hdr(), timeout=60) + return r.status_code, self._json(r) + + def post(self, path, payload=None, files=None): + kw = {"files": files} if files is not None else {"json": payload if payload is not None else {}} + r = self.s.post(self.base + path, headers=self._hdr(csrf=True), timeout=180, **kw) + return r.status_code, self._json(r) + + @staticmethod + def _json(r): + try: + return r.json() + except Exception: + return {"_status": r.status_code, "_raw": r.text[:400]} + + +def ok(j): + return str(j.get("code")) == "200" + + +def msg(j): + return j.get("msg") or j.get("_raw") or json.dumps(j, ensure_ascii=False)[:200] + + +def page_find(side, name, root_type, parent_id=0): + """按文件名在指定列表里找记录。root_type:1 我的文件 / 2 内穿外 / 3 外传内""" + _, j = side.get("/user_file/page", + {"parentId": parent_id, "pageNo": 1, "pageSize": 50, "rootType": root_type}) + if not ok(j): + return None + for e in (j.get("data") or {}).get("entityList", []): + if e.get("fileName") == name: + return e + return None + + +def wait_scan(side, file_code, timeout=240): + """轮询安全检测结论;检测中(1)继续等,其余状态返回""" + deadline = time.time() + timeout + detail = None + while time.time() < deadline: + _, j = side.get("/file_security/detail", {"fileCode": file_code}) + if ok(j): + detail = j.get("data") + st = str((detail or {}).get("scanStatus") or "") + if st and st != "1": + return detail + time.sleep(3) + return detail + + +def main(): + if not PWD: + print("必须设置 SFILE_ADMIN_PWD") + return 2 + tag = uuid.uuid4().hex[:8] + clean_name = "sfile-smoke-clean-%s.txt" % tag + bad_name = "sfile-smoke-eicar-%s.txt" % tag + + iw, ow = Side(IW, "内网"), Side(OW, "外网") + print("\n目标:内网 %s / 外网 %s" % (IW, OW)) + + print("\n== 1. 登录两侧 ==") + for side in (iw, ow): + try: + side.login(USER, PWD) + rec("%s 登录" % side.label, True, "token %d 字符" % len(side.token)) + except Exception as e: + rec("%s 登录" % side.label, False, str(e)) + return 1 + _, j = iw.get("/file_security/enabled") + ms_on = ok(j) and j.get("data") is True + rec("MeshSafe 开关", True, "enabled=%s(false 时跳过 3/4/7 段)" % ms_on) + + print("\n== 2. 上传 + 建档 ==") + # fileCode 按内容寻址:同一份字节会复用上一条检测结论(含人工复核), + # 载荷必须每轮唯一,否则「不通过拦截」这类断言会被上一次的放行结论污染 + uploaded = {} + for name, content in ((clean_name, ("s-file smoke clean payload %s\n" % tag) * 80), + (bad_name, EICAR + "\n# smoke-tag %s\n" % tag)): + _, j = iw.post("/file/upload", files={"file": (name, content.encode(), "text/plain")}) + if not ok(j): + rec("上传 %s" % name, False, msg(j)) + return 1 + f = j["data"] + _, j2 = iw.post("/normal_user_file/create", + {"fileCode": f.get("fileCode"), "fileName": name, "fileType": "1", "parentId": 0}) + rec("上传并建档 %s" % name, ok(j2), msg(j2) if not ok(j2) else "fileCode=%s" % f.get("fileCode")) + if not ok(j2): + return 1 + uploaded[name] = f.get("fileCode") + + print("\n== 3. 检测结论 ==") + ids = {} + verdicts = {} + for name, code in uploaded.items(): + found = page_find(iw, name, "1") + if found: + ids[name] = found["id"] + d = wait_scan(iw, code) + st = str((d or {}).get("scanStatus") or "") + verdicts[name] = st + print(" %s -> %s | action=%s reason=%s" + % (name, STATUS.get(st, "未知"), (d or {}).get("action"), ((d or {}).get("reason") or "")[:80])) + if ms_on: + rec("干净文件检测通过", verdicts.get(clean_name) == "2", "状态=%s" % STATUS.get(verdicts.get(clean_name))) + rec("EICAR 检测不通过", verdicts.get(bad_name) == "3", "状态=%s" % STATUS.get(verdicts.get(bad_name))) + + print("\n== 4. 不通过文件禁止传输 ==") + if len(ids) != len(uploaded): + rec("内网「我的文件」可见上传文件", False, "只找到 %s" % list(ids)) + return 1 + if ms_on: + _, j = iw.get("/file_security/share_check", {"userFileId": ids[bad_name]}) + d = j.get("data") or {} + rec("share_check 命中不通过", ok(j) and d.get("blockedCount", 0) >= 1, + "blocked=%s scanning=%s" % (d.get("blockedCount"), d.get("scanningCount"))) + _, j = iw.post("/transfer_user_file/create", {"userFileId": ids[bad_name]}) + rec("不通过文件传输被拦截", not ok(j), msg(j)[:160]) + + print("\n== 5. 干净文件跨网摆渡 ==") + before = set(os.listdir(GATE_IW2OW)) if GATE_IW2OW and os.path.isdir(GATE_IW2OW) else None + _, j = iw.post("/transfer_user_file/create", {"userFileId": ids[clean_name]}) + rec("内网发起传输", ok(j), msg(j) if not ok(j) else "外发文件夹ID=%s" % j.get("data")) + if not ok(j): + return 1 + if before is None: + print(" 未设 SFILE_GATE_SEND_DIR(跨机验收常见),跳过摆渡包落盘检查") + else: + new_zip = None + for _ in range(30): + delta = set(os.listdir(GATE_IW2OW)) - before + if delta: + new_zip = sorted(delta)[0] + break + time.sleep(2) + rec("光闸发出目录出现摆渡包", new_zip is not None, new_zip or GATE_IW2OW) + + print("\n== 6. 外网解包 + 再送检(监听任务默认每分钟一次) ==") + # 接收侧落在「内穿外」列表(root_type=2),目录名带时间戳后缀,需进目录取子文件 + folder = None + deadline = time.time() + 300 + while time.time() < deadline and folder is None: + _, j = ow.get("/user_file/page", {"parentId": 0, "pageNo": 1, "pageSize": 50, "rootType": "2"}) + for e in (j.get("data") or {}).get("entityList", []): + if (e.get("fileName") or "").startswith(clean_name.rsplit(".", 1)[0]): + folder = e + break + if folder is None: + time.sleep(10) + rec("外网「内穿外」出现接收目录", folder is not None, + str(folder.get("fileName")) if folder else "超时未出现(检查光闸与 FILE_TRANSFER_LOCATION)") + if folder: + child = None + _, j = ow.get("/user_file/page", + {"parentId": folder["id"], "pageNo": 1, "pageSize": 50, "rootType": "2"}) + for e in (j.get("data") or {}).get("entityList", []): + if e.get("fileName") == clean_name: + child = e + break + rec("接收目录内可见文件", child is not None, str(child.get("fileCode")) if child else "目录为空") + if child: + d = wait_scan(ow, child["fileCode"], timeout=180) + st = str((d or {}).get("scanStatus") or "") + rec("外网解包后自动送检", (not ms_on) or st == "2", + "状态=%s reason=%s" % (STATUS.get(st, "未知"), ((d or {}).get("reason") or "")[:60])) + + print("\n== 7. 人工复核改判 ==") + if not ms_on: + print(" MeshSafe 未启用,跳过") + else: + _, j = iw.post("/file_security/review", + {"fileCode": uploaded[bad_name], "decision": "pass", + "comment": "交付验收复核放行 %s" % tag}) + rec("复核改判接口", ok(j), msg(j) if not ok(j) else "") + d = j.get("data") or {} + rec("复核后状态=通过", str(d.get("scanStatus")) == "2", + "状态=%s action=%s" % (STATUS.get(str(d.get("scanStatus"))), d.get("action"))) + _, j = iw.get("/file_security/detail", {"fileCode": uploaded[bad_name]}) + rec("复核说明落库", ok(j) and tag in ((j.get("data") or {}).get("reviewComment") or ""), + str((j.get("data") or {}).get("reviewComment"))[:80]) + _, j = iw.get("/file_security/share_check", {"userFileId": ids[bad_name]}) + d = j.get("data") or {} + rec("复核后不再拦截", ok(j) and d.get("blockedCount", 0) == 0, "blocked=%s" % d.get("blockedCount")) + + print("\n== 汇总 ==") + passed = sum(1 for _, o, _ in results if o) + for name, o, _ in results: + print(" %s %s" % ("OK " if o else "FAIL", name)) + print(" %d/%d 通过" % (passed, len(results))) + return 0 if passed == len(results) else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/deploy/offline/sql/01-init-table.sql b/deploy/offline/sql/01-init-table.sql new file mode 100644 index 0000000..c595562 --- /dev/null +++ b/deploy/offline/sql/01-init-table.sql @@ -0,0 +1,2082 @@ +-- PostgreSQL Database Initialization Script +-- Converted from MySQL with Column Comments + +-- ---------------------------- +-- Table structure for async_task_notice +-- ---------------------------- +DROP TABLE IF EXISTS async_task_notice; +CREATE TABLE async_task_notice ( + id BIGSERIAL PRIMARY KEY, + union_type VARCHAR(32) NOT NULL, + union_id BIGINT NOT NULL, + content VARCHAR(255) NOT NULL, + biz_status_str VARCHAR(32) DEFAULT NULL, + biz_param_data VARCHAR(512) DEFAULT NULL, + flow_status SMALLINT DEFAULT 1, + notice_status SMALLINT DEFAULT 1, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_create_by ON async_task_notice(create_by); +CREATE INDEX idx_union ON async_task_notice(union_type,union_id); +COMMENT ON TABLE async_task_notice IS '异步任务通知'; +COMMENT ON COLUMN async_task_notice.id IS '主键'; +COMMENT ON COLUMN async_task_notice.union_type IS '关联类型:common_import等'; +COMMENT ON COLUMN async_task_notice.union_id IS '关联id'; +COMMENT ON COLUMN async_task_notice.content IS '内容'; +COMMENT ON COLUMN async_task_notice.biz_status_str IS '业务状态'; +COMMENT ON COLUMN async_task_notice.biz_param_data IS '业务参数'; +COMMENT ON COLUMN async_task_notice.flow_status IS '流程状态:1开启,2结束'; +COMMENT ON COLUMN async_task_notice.notice_status IS '通知状态:1未读,2已读'; +COMMENT ON COLUMN async_task_notice.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN async_task_notice.create_by IS '创建人'; +COMMENT ON COLUMN async_task_notice.create_time IS '创建时间'; +COMMENT ON COLUMN async_task_notice.update_by IS '更新人'; +COMMENT ON COLUMN async_task_notice.update_time IS '更新时间'; +COMMENT ON COLUMN async_task_notice.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for notice_content_template +-- ---------------------------- +DROP TABLE IF EXISTS notice_content_template; +CREATE TABLE notice_content_template ( + id BIGSERIAL PRIMARY KEY, + template_id BIGINT DEFAULT NULL, + channel_type SMALLINT DEFAULT NULL, + title VARCHAR(64) DEFAULT NULL, + content_template TEXT, + enabled BOOLEAN DEFAULT TRUE, + link VARCHAR(128) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE notice_content_template IS '通知消息体模版'; +COMMENT ON COLUMN notice_content_template.id IS '主键'; +COMMENT ON COLUMN notice_content_template.template_id IS '通知模版id'; +COMMENT ON COLUMN notice_content_template.channel_type IS '消息通道类型:1站内信,2短信,3钉钉,4企业微信,5邮件(见notice_channel_type)'; +COMMENT ON COLUMN notice_content_template.title IS '标题'; +COMMENT ON COLUMN notice_content_template.content_template IS '内容模版'; +COMMENT ON COLUMN notice_content_template.enabled IS '是否启用'; +COMMENT ON COLUMN notice_content_template.link IS '链接'; +COMMENT ON COLUMN notice_content_template.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN notice_content_template.create_by IS '创建人'; +COMMENT ON COLUMN notice_content_template.create_time IS '创建时间'; +COMMENT ON COLUMN notice_content_template.update_by IS '更新人'; +COMMENT ON COLUMN notice_content_template.update_time IS '更新时间'; +COMMENT ON COLUMN notice_content_template.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for notice_receiver_setting +-- ---------------------------- +DROP TABLE IF EXISTS notice_receiver_setting; +CREATE TABLE notice_receiver_setting ( + id BIGSERIAL PRIMARY KEY, + template_id BIGINT DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + receiver_union_type SMALLINT DEFAULT NULL, + receiver_union_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE notice_receiver_setting IS '通知接收人设置'; +COMMENT ON COLUMN notice_receiver_setting.id IS '主键'; +COMMENT ON COLUMN notice_receiver_setting.template_id IS '通知模版id'; +COMMENT ON COLUMN notice_receiver_setting.org_id IS '机构id'; +COMMENT ON COLUMN notice_receiver_setting.receiver_union_type IS '接收人关联类型:1用户id,2角色,3标准角色(见notice_receiver_type)'; +COMMENT ON COLUMN notice_receiver_setting.receiver_union_id IS '接收人关联id:用户id,角色id,标准角色id'; +COMMENT ON COLUMN notice_receiver_setting.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN notice_receiver_setting.create_by IS '创建人'; +COMMENT ON COLUMN notice_receiver_setting.create_time IS '创建时间'; +COMMENT ON COLUMN notice_receiver_setting.update_by IS '更新人'; +COMMENT ON COLUMN notice_receiver_setting.update_time IS '更新时间'; +COMMENT ON COLUMN notice_receiver_setting.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for notice_send_record +-- ---------------------------- +DROP TABLE IF EXISTS notice_send_record; +CREATE TABLE notice_send_record ( + id BIGSERIAL PRIMARY KEY, + template_id BIGINT DEFAULT NULL, + sender_org_id BIGINT DEFAULT NULL, + receiver_org_id BIGINT DEFAULT NULL, + content_template_id BIGINT DEFAULT NULL, + content_param_data VARCHAR(512) DEFAULT NULL, + content TEXT, + appointed BOOLEAN DEFAULT NULL, + link VARCHAR(128) DEFAULT NULL, + content_template TEXT, + title VARCHAR(64) DEFAULT NULL, + channel_type SMALLINT DEFAULT NULL, + status SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE notice_send_record IS '通知发送记录(内容模版级)'; +COMMENT ON COLUMN notice_send_record.id IS '主键'; +COMMENT ON COLUMN notice_send_record.template_id IS '通知模版id'; +COMMENT ON COLUMN notice_send_record.sender_org_id IS '发送方机构id'; +COMMENT ON COLUMN notice_send_record.receiver_org_id IS '接收方机构id'; +COMMENT ON COLUMN notice_send_record.content_template_id IS '消息体模版id'; +COMMENT ON COLUMN notice_send_record.content_param_data IS '消息体参数'; +COMMENT ON COLUMN notice_send_record.content IS '消息体'; +COMMENT ON COLUMN notice_send_record.appointed IS '是否指定接收方(非预先设定)'; +COMMENT ON COLUMN notice_send_record.link IS '链接'; +COMMENT ON COLUMN notice_send_record.content_template IS '内容模版'; +COMMENT ON COLUMN notice_send_record.title IS '标题'; +COMMENT ON COLUMN notice_send_record.channel_type IS '消息通道类型:1站内信,2短信,3钉钉,4企业微信,5邮件(见notice_channel_type)'; +COMMENT ON COLUMN notice_send_record.status IS '状态:1待处理,2已处理 等(见notice_send_status)'; +COMMENT ON COLUMN notice_send_record.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN notice_send_record.create_by IS '创建人'; +COMMENT ON COLUMN notice_send_record.create_time IS '创建时间'; +COMMENT ON COLUMN notice_send_record.update_by IS '更新人'; +COMMENT ON COLUMN notice_send_record.update_time IS '更新时间'; +COMMENT ON COLUMN notice_send_record.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for notice_send_record_detail +-- ---------------------------- +DROP TABLE IF EXISTS notice_send_record_detail; +CREATE TABLE notice_send_record_detail ( + id BIGSERIAL PRIMARY KEY, + record_id BIGINT DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + receiver_account_no VARCHAR(32) DEFAULT NULL, + receiver_status SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE notice_send_record_detail IS '通知发送记录明细(接收人)'; +COMMENT ON COLUMN notice_send_record_detail.id IS '主键'; +COMMENT ON COLUMN notice_send_record_detail.record_id IS '发送记录id'; +COMMENT ON COLUMN notice_send_record_detail.user_id IS '接收人id'; +COMMENT ON COLUMN notice_send_record_detail.receiver_account_no IS '接收方帐号'; +COMMENT ON COLUMN notice_send_record_detail.receiver_status IS '接收状态:1待发送,2已发送,3发送失败,4接收已读(见notice_receiver_status)'; +COMMENT ON COLUMN notice_send_record_detail.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN notice_send_record_detail.create_by IS '创建人'; +COMMENT ON COLUMN notice_send_record_detail.create_time IS '创建时间'; +COMMENT ON COLUMN notice_send_record_detail.update_by IS '更新人'; +COMMENT ON COLUMN notice_send_record_detail.update_time IS '更新时间'; +COMMENT ON COLUMN notice_send_record_detail.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for notice_template +-- ---------------------------- +DROP TABLE IF EXISTS notice_template; +CREATE TABLE notice_template ( + id BIGSERIAL PRIMARY KEY, + template_code VARCHAR(32) DEFAULT NULL, + template_name VARCHAR(64) DEFAULT NULL, + module_type SMALLINT DEFAULT NULL, + biz_type SMALLINT DEFAULT NULL, + level SMALLINT DEFAULT NULL, + enabled BOOLEAN DEFAULT TRUE, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE notice_template IS '通知模版'; +COMMENT ON COLUMN notice_template.id IS '主键'; +COMMENT ON COLUMN notice_template.template_code IS '通知模版code'; +COMMENT ON COLUMN notice_template.template_name IS '通知模版名称'; +COMMENT ON COLUMN notice_template.module_type IS '模块分类:1业务,2监控/预警(见notice_module_type)'; +COMMENT ON COLUMN notice_template.biz_type IS '业务分类:'; +COMMENT ON COLUMN notice_template.level IS '级别:1平台,2机构(见notice_level)'; +COMMENT ON COLUMN notice_template.enabled IS '是否启用'; +COMMENT ON COLUMN notice_template.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN notice_template.create_by IS '创建人'; +COMMENT ON COLUMN notice_template.create_time IS '创建时间'; +COMMENT ON COLUMN notice_template.update_by IS '更新人'; +COMMENT ON COLUMN notice_template.update_time IS '更新时间'; +COMMENT ON COLUMN notice_template.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_action +-- ---------------------------- +DROP TABLE IF EXISTS uc_action; +CREATE TABLE uc_action ( + id BIGSERIAL PRIMARY KEY, + union_type VARCHAR(32) DEFAULT NULL, + union_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_action IS '操作记录'; +COMMENT ON COLUMN uc_action.id IS '主键'; +COMMENT ON COLUMN uc_action.union_type IS '关联类型'; +COMMENT ON COLUMN uc_action.union_id IS '关联id'; +COMMENT ON COLUMN uc_action.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_action.create_by IS '创建人'; +COMMENT ON COLUMN uc_action.create_time IS '创建时间'; +COMMENT ON COLUMN uc_action.update_by IS '更新人'; +COMMENT ON COLUMN uc_action.update_time IS '更新时间'; +COMMENT ON COLUMN uc_action.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_action_detail +-- ---------------------------- +DROP TABLE IF EXISTS uc_action_detail; +CREATE TABLE uc_action_detail ( + id BIGSERIAL PRIMARY KEY, + action_id BIGINT DEFAULT NULL, + field VARCHAR(32) DEFAULT NULL, + field_alias VARCHAR(32) DEFAULT NULL, + field_dict_code VARCHAR(32) DEFAULT NULL, + old_value VARCHAR(255) DEFAULT NULL, + new_value VARCHAR(255) DEFAULT NULL, + diff TEXT, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_action_detail IS '操作记录明细'; +COMMENT ON COLUMN uc_action_detail.id IS '主键'; +COMMENT ON COLUMN uc_action_detail.action_id IS '操作记录id'; +COMMENT ON COLUMN uc_action_detail.field IS '属性'; +COMMENT ON COLUMN uc_action_detail.field_alias IS '属性别名'; +COMMENT ON COLUMN uc_action_detail.field_dict_code IS '属性字典'; +COMMENT ON COLUMN uc_action_detail.old_value IS '旧值'; +COMMENT ON COLUMN uc_action_detail.new_value IS '新值'; +COMMENT ON COLUMN uc_action_detail.diff IS '差异'; +COMMENT ON COLUMN uc_action_detail.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_action_detail.create_by IS '创建人'; +COMMENT ON COLUMN uc_action_detail.create_time IS '创建时间'; +COMMENT ON COLUMN uc_action_detail.update_by IS '更新人'; +COMMENT ON COLUMN uc_action_detail.update_time IS '更新时间'; +COMMENT ON COLUMN uc_action_detail.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_auth_client +-- ---------------------------- +DROP TABLE IF EXISTS uc_auth_client; +CREATE TABLE uc_auth_client ( + id BIGSERIAL PRIMARY KEY, + auth_type_code SMALLINT DEFAULT NULL, + client_name VARCHAR(32) DEFAULT NULL, + param_data VARCHAR(256) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_auth_client IS '授权客户端'; +COMMENT ON COLUMN uc_auth_client.id IS '主键'; +COMMENT ON COLUMN uc_auth_client.auth_type_code IS '授权类型标识'; +COMMENT ON COLUMN uc_auth_client.client_name IS '授权应用名称'; +COMMENT ON COLUMN uc_auth_client.param_data IS '配置数据'; +COMMENT ON COLUMN uc_auth_client.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_auth_client.create_by IS '创建人'; +COMMENT ON COLUMN uc_auth_client.create_time IS '创建时间'; +COMMENT ON COLUMN uc_auth_client.update_by IS '更新人'; +COMMENT ON COLUMN uc_auth_client.update_time IS '更新时间'; +COMMENT ON COLUMN uc_auth_client.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_auth_detail +-- ---------------------------- +DROP TABLE IF EXISTS uc_auth_detail; +CREATE TABLE uc_auth_detail ( + id BIGSERIAL PRIMARY KEY, + auth_client_id BIGINT DEFAULT NULL, + auth_type_code SMALLINT DEFAULT NULL, + nickname VARCHAR(32) DEFAULT NULL, + avatar VARCHAR(256) DEFAULT NULL, + gender VARCHAR(2) DEFAULT NULL, + country VARCHAR(32) DEFAULT NULL, + province VARCHAR(32) DEFAULT NULL, + city VARCHAR(32) DEFAULT NULL, + open_id VARCHAR(32) DEFAULT NULL, + unique_id VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_auth_detail IS '授权明细表'; +COMMENT ON COLUMN uc_auth_detail.id IS '主键'; +COMMENT ON COLUMN uc_auth_detail.auth_client_id IS '授权客户端id'; +COMMENT ON COLUMN uc_auth_detail.auth_type_code IS '授权类型标识'; +COMMENT ON COLUMN uc_auth_detail.nickname IS '昵称'; +COMMENT ON COLUMN uc_auth_detail.avatar IS '头像'; +COMMENT ON COLUMN uc_auth_detail.gender IS '性别'; +COMMENT ON COLUMN uc_auth_detail.country IS '国家'; +COMMENT ON COLUMN uc_auth_detail.province IS '省份'; +COMMENT ON COLUMN uc_auth_detail.city IS '城市'; +COMMENT ON COLUMN uc_auth_detail.open_id IS 'open_id'; +COMMENT ON COLUMN uc_auth_detail.unique_id IS '授权唯一id'; +COMMENT ON COLUMN uc_auth_detail.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_auth_detail.create_by IS '创建人'; +COMMENT ON COLUMN uc_auth_detail.create_time IS '创建时间'; +COMMENT ON COLUMN uc_auth_detail.update_by IS '更新人'; +COMMENT ON COLUMN uc_auth_detail.update_time IS '更新时间'; +COMMENT ON COLUMN uc_auth_detail.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_auth_detail_entrypoint +-- ---------------------------- +DROP TABLE IF EXISTS uc_auth_detail_entrypoint; +CREATE TABLE uc_auth_detail_entrypoint ( + id BIGSERIAL PRIMARY KEY, + auth_detail_id BIGINT DEFAULT NULL, + union_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_union_id ON uc_auth_detail_entrypoint(union_id); +COMMENT ON TABLE uc_auth_detail_entrypoint IS '授权明细入口'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.id IS '主键'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.auth_detail_id IS '授权客户端id'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.union_id IS '关联id(小程序码id)'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.create_by IS '创建人'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.create_time IS '创建时间'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.update_by IS '更新人'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.update_time IS '更新时间'; +COMMENT ON COLUMN uc_auth_detail_entrypoint.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_auth_subscribe_detail +-- ---------------------------- +DROP TABLE IF EXISTS uc_auth_subscribe_detail; +CREATE TABLE uc_auth_subscribe_detail ( + id BIGSERIAL PRIMARY KEY, + auth_client_id BIGINT DEFAULT NULL, + open_id VARCHAR(32) DEFAULT NULL, + unique_id VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_auth_subscribe_detail IS '授权订阅明细表'; +COMMENT ON COLUMN uc_auth_subscribe_detail.id IS '主键'; +COMMENT ON COLUMN uc_auth_subscribe_detail.auth_client_id IS '授权客户端id'; +COMMENT ON COLUMN uc_auth_subscribe_detail.open_id IS 'open_id'; +COMMENT ON COLUMN uc_auth_subscribe_detail.unique_id IS '授权唯一id'; +COMMENT ON COLUMN uc_auth_subscribe_detail.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_auth_subscribe_detail.create_by IS '创建人'; +COMMENT ON COLUMN uc_auth_subscribe_detail.create_time IS '创建时间'; +COMMENT ON COLUMN uc_auth_subscribe_detail.update_by IS '更新人'; +COMMENT ON COLUMN uc_auth_subscribe_detail.update_time IS '更新时间'; +COMMENT ON COLUMN uc_auth_subscribe_detail.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_auth_type +-- ---------------------------- +DROP TABLE IF EXISTS uc_auth_type; +CREATE TABLE uc_auth_type ( + id BIGSERIAL PRIMARY KEY, + auth_main_type SMALLINT DEFAULT NULL, + auth_type_code SMALLINT DEFAULT NULL, + name VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_auth_type IS '授权类型'; +COMMENT ON COLUMN uc_auth_type.id IS '主键'; +COMMENT ON COLUMN uc_auth_type.auth_main_type IS '授权大类:1微信,2支付宝'; +COMMENT ON COLUMN uc_auth_type.auth_type_code IS '授权类型标识'; +COMMENT ON COLUMN uc_auth_type.name IS '名称'; +COMMENT ON COLUMN uc_auth_type.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_auth_type.create_by IS '创建人'; +COMMENT ON COLUMN uc_auth_type.create_time IS '创建时间'; +COMMENT ON COLUMN uc_auth_type.update_by IS '更新人'; +COMMENT ON COLUMN uc_auth_type.update_time IS '更新时间'; +COMMENT ON COLUMN uc_auth_type.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_domain +-- ---------------------------- +DROP TABLE IF EXISTS uc_domain; +CREATE TABLE uc_domain ( + id BIGSERIAL PRIMARY KEY, + platform_code SMALLINT DEFAULT NULL, + domain_name VARCHAR(32) DEFAULT NULL, + ip_port VARCHAR(32) DEFAULT NULL, + publiced BOOLEAN DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_domain IS '域名表'; +COMMENT ON COLUMN uc_domain.id IS '主键'; +COMMENT ON COLUMN uc_domain.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_domain.domain_name IS '域名'; +COMMENT ON COLUMN uc_domain.ip_port IS 'ip:port'; +COMMENT ON COLUMN uc_domain.publiced IS '是否公共'; +COMMENT ON COLUMN uc_domain.org_id IS '机构id'; +COMMENT ON COLUMN uc_domain.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_domain.create_by IS '创建人'; +COMMENT ON COLUMN uc_domain.create_time IS '创建时间'; +COMMENT ON COLUMN uc_domain.update_by IS '更新人'; +COMMENT ON COLUMN uc_domain.update_time IS '更新时间'; +COMMENT ON COLUMN uc_domain.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_domain_auth +-- ---------------------------- +DROP TABLE IF EXISTS uc_domain_auth; +CREATE TABLE uc_domain_auth ( + id BIGSERIAL PRIMARY KEY, + domain_id BIGINT DEFAULT NULL, + auth_type_code SMALLINT DEFAULT NULL, + auth_client_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_domain_auth IS '域名授权表'; +COMMENT ON COLUMN uc_domain_auth.id IS '主键'; +COMMENT ON COLUMN uc_domain_auth.domain_id IS '域名id'; +COMMENT ON COLUMN uc_domain_auth.auth_type_code IS '授权类型标识'; +COMMENT ON COLUMN uc_domain_auth.auth_client_id IS '授权客户端id'; +COMMENT ON COLUMN uc_domain_auth.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_domain_auth.create_by IS '创建人'; +COMMENT ON COLUMN uc_domain_auth.create_time IS '创建时间'; +COMMENT ON COLUMN uc_domain_auth.update_by IS '更新人'; +COMMENT ON COLUMN uc_domain_auth.update_time IS '更新时间'; +COMMENT ON COLUMN uc_domain_auth.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_domain_setting +-- ---------------------------- +DROP TABLE IF EXISTS uc_domain_setting; +CREATE TABLE uc_domain_setting ( + id BIGSERIAL PRIMARY KEY, + domain_id BIGINT DEFAULT NULL, + type SMALLINT DEFAULT NULL, + remark VARCHAR(32) DEFAULT '', + data TEXT, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_domain_setting IS '域名配置表'; +COMMENT ON COLUMN uc_domain_setting.id IS '主键'; +COMMENT ON COLUMN uc_domain_setting.domain_id IS '域名id'; +COMMENT ON COLUMN uc_domain_setting.type IS '类型'; +COMMENT ON COLUMN uc_domain_setting.remark IS '配置备注'; +COMMENT ON COLUMN uc_domain_setting.data IS '配置数据'; +COMMENT ON COLUMN uc_domain_setting.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_domain_setting.create_by IS '创建人'; +COMMENT ON COLUMN uc_domain_setting.create_time IS '创建时间'; +COMMENT ON COLUMN uc_domain_setting.update_by IS '更新人'; +COMMENT ON COLUMN uc_domain_setting.update_time IS '更新时间'; +COMMENT ON COLUMN uc_domain_setting.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_dynamic_table +-- ---------------------------- +DROP TABLE IF EXISTS uc_dynamic_table; +CREATE TABLE uc_dynamic_table ( + id BIGSERIAL PRIMARY KEY, + table_type VARCHAR(32) DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + column_data TEXT, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); + +-- ---------------------------- +-- Table structure for uc_faq +-- ---------------------------- +DROP TABLE IF EXISTS uc_faq; +CREATE TABLE uc_faq ( + id BIGSERIAL PRIMARY KEY, + faq_order INTEGER DEFAULT 0, + faq_type VARCHAR(2) DEFAULT NULL, + question VARCHAR(512) DEFAULT NULL, + answer TEXT, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_faq IS '常见问题表'; +COMMENT ON COLUMN uc_faq.id IS '主键'; +COMMENT ON COLUMN uc_faq.faq_order IS '排序'; +COMMENT ON COLUMN uc_faq.faq_type IS '类型'; +COMMENT ON COLUMN uc_faq.question IS '问题'; +COMMENT ON COLUMN uc_faq.answer IS '回答'; +COMMENT ON COLUMN uc_faq.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_faq.create_by IS '创建人'; +COMMENT ON COLUMN uc_faq.create_time IS '创建时间'; +COMMENT ON COLUMN uc_faq.update_by IS '更新人'; +COMMENT ON COLUMN uc_faq.update_time IS '更新时间'; +COMMENT ON COLUMN uc_faq.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_faq_file +-- ---------------------------- +DROP TABLE IF EXISTS uc_faq_file; +CREATE TABLE uc_faq_file ( + id BIGSERIAL PRIMARY KEY, + question_id BIGINT DEFAULT NULL, + file_name VARCHAR(256) DEFAULT NULL, + file_code VARCHAR(32) DEFAULT NULL, + file_suffix VARCHAR(32) DEFAULT NULL, + sort BIGINT DEFAULT NULL, + description VARCHAR(256) DEFAULT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_faq_file IS '常见问题附件关系表'; +COMMENT ON COLUMN uc_faq_file.id IS '主键'; +COMMENT ON COLUMN uc_faq_file.question_id IS '常见问题ID'; +COMMENT ON COLUMN uc_faq_file.file_name IS '文件名称'; +COMMENT ON COLUMN uc_faq_file.file_code IS '文件编码'; +COMMENT ON COLUMN uc_faq_file.file_suffix IS '文件后缀'; +COMMENT ON COLUMN uc_faq_file.sort IS '排序'; +COMMENT ON COLUMN uc_faq_file.description IS '文件描述'; +COMMENT ON COLUMN uc_faq_file.deleted IS '是否删除'; +COMMENT ON COLUMN uc_faq_file.create_by IS '创建人'; +COMMENT ON COLUMN uc_faq_file.create_time IS '创建时间'; +COMMENT ON COLUMN uc_faq_file.update_by IS '更新人'; +COMMENT ON COLUMN uc_faq_file.update_time IS '更新时间'; +COMMENT ON COLUMN uc_faq_file.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_feedback +-- ---------------------------- +DROP TABLE IF EXISTS uc_feedback; +CREATE TABLE uc_feedback ( + id BIGSERIAL PRIMARY KEY, + type VARCHAR(2) DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + phone VARCHAR(32) DEFAULT NULL, + content VARCHAR(255) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + source_type VARCHAR(2) DEFAULT NULL +); +COMMENT ON TABLE uc_feedback IS '反馈表'; +COMMENT ON COLUMN uc_feedback.id IS '主键'; +COMMENT ON COLUMN uc_feedback.type IS '反馈类型'; +COMMENT ON COLUMN uc_feedback.user_id IS '用户id'; +COMMENT ON COLUMN uc_feedback.phone IS '手机号'; +COMMENT ON COLUMN uc_feedback.content IS '反馈内容'; +COMMENT ON COLUMN uc_feedback.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_feedback.create_by IS '创建人'; +COMMENT ON COLUMN uc_feedback.create_time IS '创建时间'; +COMMENT ON COLUMN uc_feedback.update_by IS '更新人'; +COMMENT ON COLUMN uc_feedback.update_time IS '更新时间'; +COMMENT ON COLUMN uc_feedback.proxy_by IS '代理人'; +COMMENT ON COLUMN uc_feedback.source_type IS '来源'; + +-- ---------------------------- +-- Table structure for uc_import_main +-- ---------------------------- +DROP TABLE IF EXISTS uc_import_main; +CREATE TABLE uc_import_main ( + id BIGSERIAL PRIMARY KEY, + import_no VARCHAR(64) DEFAULT NULL, + type VARCHAR(32) DEFAULT NULL, + import_status SMALLINT DEFAULT NULL, + total_num INTEGER DEFAULT NULL, + success_num INTEGER DEFAULT NULL, + fail_msg VARCHAR(255) DEFAULT NULL, + finish_time TIMESTAMP DEFAULT NULL, + execute_type SMALLINT DEFAULT NULL, + extra_id BIGINT DEFAULT NULL, + file_code VARCHAR(32) DEFAULT NULL, + file_name VARCHAR(128) DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + remark VARCHAR(255) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_import_no ON uc_import_main(import_no); +COMMENT ON TABLE uc_import_main IS '通用导入批次表'; +COMMENT ON COLUMN uc_import_main.id IS '主键ID'; +COMMENT ON COLUMN uc_import_main.import_no IS '批次号'; +COMMENT ON COLUMN uc_import_main.type IS '导入类型'; +COMMENT ON COLUMN uc_import_main.import_status IS '导入状态:1新建,2解析失败,3校验中,4校验失败,5校验成功,6执行导入'; +COMMENT ON COLUMN uc_import_main.total_num IS '总数'; +COMMENT ON COLUMN uc_import_main.success_num IS '成功数'; +COMMENT ON COLUMN uc_import_main.fail_msg IS '失败原因'; +COMMENT ON COLUMN uc_import_main.finish_time IS '完成时间时间'; +COMMENT ON COLUMN uc_import_main.execute_type IS '执行类型:1.普通,2.指定外部id'; +COMMENT ON COLUMN uc_import_main.extra_id IS '外部生成id'; +COMMENT ON COLUMN uc_import_main.file_code IS '文件CODE'; +COMMENT ON COLUMN uc_import_main.file_name IS '文件名称'; +COMMENT ON COLUMN uc_import_main.org_id IS '机构ID'; +COMMENT ON COLUMN uc_import_main.remark IS '备注'; +COMMENT ON COLUMN uc_import_main.deleted IS '是否删除'; +COMMENT ON COLUMN uc_import_main.create_by IS '创建人'; +COMMENT ON COLUMN uc_import_main.create_time IS '创建时间'; +COMMENT ON COLUMN uc_import_main.update_by IS '修改人'; +COMMENT ON COLUMN uc_import_main.update_time IS '修改时间'; +COMMENT ON COLUMN uc_import_main.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_lite_code +-- ---------------------------- +DROP TABLE IF EXISTS uc_lite_code; +CREATE TABLE uc_lite_code ( + id BIGSERIAL PRIMARY KEY, + lite_code_config_id BIGINT DEFAULT NULL, + unique_code VARCHAR(32) DEFAULT NULL, + param_value VARCHAR(255) DEFAULT NULL, + query_value VARCHAR(255) DEFAULT NULL, + file_code VARCHAR(32) DEFAULT NULL, + url_scheme VARCHAR(64) DEFAULT NULL, + name VARCHAR(32) DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_lite_code IS '小程序码'; +COMMENT ON COLUMN uc_lite_code.id IS '主键'; +COMMENT ON COLUMN uc_lite_code.lite_code_config_id IS '小程序码配置id'; +COMMENT ON COLUMN uc_lite_code.unique_code IS '唯一码-uuid'; +COMMENT ON COLUMN uc_lite_code.param_value IS '参数值(按参数键升序排列)(格式:**&**)'; +COMMENT ON COLUMN uc_lite_code.query_value IS 'query参数值(格式:**=**&**=**)'; +COMMENT ON COLUMN uc_lite_code.file_code IS '小程序码文件编码'; +COMMENT ON COLUMN uc_lite_code.url_scheme IS '小程序scheme码'; +COMMENT ON COLUMN uc_lite_code.name IS '名称'; +COMMENT ON COLUMN uc_lite_code.user_id IS '用户id'; +COMMENT ON COLUMN uc_lite_code.org_id IS '机构id'; +COMMENT ON COLUMN uc_lite_code.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_lite_code.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_lite_code.create_by IS '创建人'; +COMMENT ON COLUMN uc_lite_code.create_time IS '创建时间'; +COMMENT ON COLUMN uc_lite_code.update_by IS '更新人'; +COMMENT ON COLUMN uc_lite_code.update_time IS '更新时间'; +COMMENT ON COLUMN uc_lite_code.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_lite_code_config +-- ---------------------------- +DROP TABLE IF EXISTS uc_lite_code_config; +CREATE TABLE uc_lite_code_config ( + id BIGSERIAL PRIMARY KEY, + auth_client_id BIGINT DEFAULT NULL, + biz_type SMALLINT DEFAULT NULL, + path VARCHAR(64) DEFAULT NULL, + param_key VARCHAR(64) DEFAULT NULL, + env_param_key VARCHAR(64) DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_lite_code_config IS '小程序码配置'; +COMMENT ON COLUMN uc_lite_code_config.id IS '主键'; +COMMENT ON COLUMN uc_lite_code_config.auth_client_id IS '授权客户端id'; +COMMENT ON COLUMN uc_lite_code_config.biz_type IS '小程序码业务类型'; +COMMENT ON COLUMN uc_lite_code_config.path IS '路径'; +COMMENT ON COLUMN uc_lite_code_config.param_key IS '参数键-逗号分隔'; +COMMENT ON COLUMN uc_lite_code_config.env_param_key IS '内置环境参数键-逗号分隔'; +COMMENT ON COLUMN uc_lite_code_config.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_lite_code_config.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_lite_code_config.create_by IS '创建人'; +COMMENT ON COLUMN uc_lite_code_config.create_time IS '创建时间'; +COMMENT ON COLUMN uc_lite_code_config.update_by IS '更新人'; +COMMENT ON COLUMN uc_lite_code_config.update_time IS '更新时间'; +COMMENT ON COLUMN uc_lite_code_config.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_org_relation +-- ---------------------------- +DROP TABLE IF EXISTS uc_org_relation; +CREATE TABLE uc_org_relation ( + id BIGSERIAL PRIMARY KEY, + org_id BIGINT DEFAULT NULL, + org_type SMALLINT DEFAULT NULL, + parent_id BIGINT DEFAULT NULL, + path VARCHAR(255) DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL, + platform_name VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_org_id ON uc_org_relation(org_id); +COMMENT ON TABLE uc_org_relation IS '机构关系表'; +COMMENT ON COLUMN uc_org_relation.id IS '主键'; +COMMENT ON COLUMN uc_org_relation.org_id IS '机构id'; +COMMENT ON COLUMN uc_org_relation.org_type IS '类型'; +COMMENT ON COLUMN uc_org_relation.parent_id IS '上级id'; +COMMENT ON COLUMN uc_org_relation.path IS '路径'; +COMMENT ON COLUMN uc_org_relation.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_org_relation.platform_name IS '平台名称'; +COMMENT ON COLUMN uc_org_relation.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_org_relation.create_by IS '创建人'; +COMMENT ON COLUMN uc_org_relation.create_time IS '创建时间'; +COMMENT ON COLUMN uc_org_relation.update_by IS '更新人'; +COMMENT ON COLUMN uc_org_relation.update_time IS '更新时间'; +COMMENT ON COLUMN uc_org_relation.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_org_user +-- ---------------------------- +DROP TABLE IF EXISTS uc_org_user; +CREATE TABLE uc_org_user ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + administrator BOOLEAN DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX INDEX12378548369700 ON uc_org_user(user_id); +CREATE INDEX INDEX12378670651400 ON uc_org_user(org_id); +COMMENT ON TABLE uc_org_user IS '机构用户表'; +COMMENT ON COLUMN uc_org_user.id IS '主键'; +COMMENT ON COLUMN uc_org_user.user_id IS '用户id'; +COMMENT ON COLUMN uc_org_user.org_id IS '机构id'; +COMMENT ON COLUMN uc_org_user.administrator IS '是否管理员'; +COMMENT ON COLUMN uc_org_user.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_org_user.create_by IS '创建人'; +COMMENT ON COLUMN uc_org_user.create_time IS '创建时间'; +COMMENT ON COLUMN uc_org_user.update_by IS '更新人'; +COMMENT ON COLUMN uc_org_user.update_time IS '更新时间'; +COMMENT ON COLUMN uc_org_user.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_platform +-- ---------------------------- +DROP TABLE IF EXISTS uc_platform; +CREATE TABLE uc_platform ( + id BIGSERIAL PRIMARY KEY, + platform_code SMALLINT DEFAULT NULL, + name VARCHAR(32) DEFAULT NULL, + full_name VARCHAR(32) DEFAULT NULL, + short_name VARCHAR(32) DEFAULT NULL, + business BOOLEAN DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_platform IS '系统应用表'; +COMMENT ON COLUMN uc_platform.id IS '主键'; +COMMENT ON COLUMN uc_platform.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_platform.name IS '名称'; +COMMENT ON COLUMN uc_platform.full_name IS '全称'; +COMMENT ON COLUMN uc_platform.short_name IS '简称'; +COMMENT ON COLUMN uc_platform.business IS '是否业务系统'; +COMMENT ON COLUMN uc_platform.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_platform.create_by IS '创建人'; +COMMENT ON COLUMN uc_platform.create_time IS '创建时间'; +COMMENT ON COLUMN uc_platform.update_by IS '更新人'; +COMMENT ON COLUMN uc_platform.update_time IS '更新时间'; +COMMENT ON COLUMN uc_platform.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_slick_param +-- ---------------------------- +DROP TABLE IF EXISTS uc_slick_param; +CREATE TABLE uc_slick_param ( + id BIGSERIAL PRIMARY KEY, + unique_code VARCHAR(32) DEFAULT NULL, + query_value VARCHAR(255) DEFAULT NULL, + param_data VARCHAR(255) DEFAULT NULL, + hash_code INTEGER DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_hash_code ON uc_slick_param(hash_code); +CREATE INDEX idx_unique_code ON uc_slick_param(unique_code); +COMMENT ON TABLE uc_slick_param IS '圆滑的参数'; +COMMENT ON COLUMN uc_slick_param.id IS '主键'; +COMMENT ON COLUMN uc_slick_param.unique_code IS '唯一码-uuid'; +COMMENT ON COLUMN uc_slick_param.query_value IS 'query升序值(格式:**=**&**=**)'; +COMMENT ON COLUMN uc_slick_param.param_data IS 'json'; +COMMENT ON COLUMN uc_slick_param.hash_code IS '哈希值'; +COMMENT ON COLUMN uc_slick_param.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_slick_param.create_by IS '创建人'; +COMMENT ON COLUMN uc_slick_param.create_time IS '创建时间'; +COMMENT ON COLUMN uc_slick_param.update_by IS '更新人'; +COMMENT ON COLUMN uc_slick_param.update_time IS '更新时间'; +COMMENT ON COLUMN uc_slick_param.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_user +-- ---------------------------- +DROP TABLE IF EXISTS uc_user; +CREATE TABLE uc_user ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(32) DEFAULT NULL, + password VARCHAR(128) DEFAULT NULL, + type SMALLINT DEFAULT NULL, + supeiror BOOLEAN DEFAULT NULL, + security_code VARCHAR(128) DEFAULT NULL, + nickname VARCHAR(32) DEFAULT NULL, + avatar VARCHAR(256) DEFAULT NULL, + phone VARCHAR(32) DEFAULT NULL, + phone_binding BOOLEAN DEFAULT NULL, + real_name VARCHAR(32) DEFAULT NULL, + card_no VARCHAR(18) DEFAULT NULL, + authenticated BOOLEAN DEFAULT NULL, + status SMALLINT DEFAULT NULL, + email VARCHAR(32) DEFAULT NULL, + address VARCHAR(32) DEFAULT NULL, + education SMALLINT DEFAULT NULL, + init_user_id BIGINT DEFAULT NULL, + enabled BOOLEAN DEFAULT NULL, + account_non_expired BOOLEAN DEFAULT NULL, + account_non_locked BOOLEAN DEFAULT NULL, + credentials_non_expired BOOLEAN DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + gender SMALLINT DEFAULT NULL, + birthday date DEFAULT NULL +); +CREATE INDEX idx_card_no ON uc_user(card_no); +CREATE INDEX idx_phone ON uc_user(phone); +CREATE INDEX idx_real_name ON uc_user(real_name); +COMMENT ON TABLE uc_user IS '用户表'; +COMMENT ON COLUMN uc_user.id IS '主键'; +COMMENT ON COLUMN uc_user.username IS '账号'; +COMMENT ON COLUMN uc_user.password IS '密码'; +COMMENT ON COLUMN uc_user.type IS '用户类型:1企业用户,2个人用户'; +COMMENT ON COLUMN uc_user.supeiror IS '是否超管'; +COMMENT ON COLUMN uc_user.security_code IS '安全码'; +COMMENT ON COLUMN uc_user.nickname IS '昵称'; +COMMENT ON COLUMN uc_user.avatar IS '头像'; +COMMENT ON COLUMN uc_user.phone IS '手机号码'; +COMMENT ON COLUMN uc_user.phone_binding IS '手机号绑定状态'; +COMMENT ON COLUMN uc_user.real_name IS '真实姓名'; +COMMENT ON COLUMN uc_user.card_no IS '身份证号'; +COMMENT ON COLUMN uc_user.authenticated IS '是否实名'; +COMMENT ON COLUMN uc_user.status IS '状态:1.待激活,2已激活,3已注销'; +COMMENT ON COLUMN uc_user.email IS '邮箱'; +COMMENT ON COLUMN uc_user.address IS '用户所在地'; +COMMENT ON COLUMN uc_user.education IS '1博士、2硕士、3本科、4大专、5高中、6中专/中技、7初中及以下、8不限'; +COMMENT ON COLUMN uc_user.init_user_id IS '初始(推荐)用户id'; +COMMENT ON COLUMN uc_user.enabled IS '是否启用'; +COMMENT ON COLUMN uc_user.account_non_expired IS '账号是否未过期'; +COMMENT ON COLUMN uc_user.account_non_locked IS '是否未锁定'; +COMMENT ON COLUMN uc_user.credentials_non_expired IS '凭据是否未过期'; +COMMENT ON COLUMN uc_user.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user.create_by IS '创建人'; +COMMENT ON COLUMN uc_user.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user.update_by IS '更新人'; +COMMENT ON COLUMN uc_user.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user.proxy_by IS '代理人'; +COMMENT ON COLUMN uc_user.gender IS '性别:1男2女'; +COMMENT ON COLUMN uc_user.birthday IS '生日'; + +-- ---------------------------- +-- Table structure for uc_user_auth +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_auth; +CREATE TABLE uc_user_auth ( + id BIGSERIAL PRIMARY KEY, + auth_main_type SMALLINT DEFAULT NULL, + unique_id VARCHAR(32) DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_unique_id ON uc_user_auth(unique_id); +COMMENT ON TABLE uc_user_auth IS '用户授权中间表'; +COMMENT ON COLUMN uc_user_auth.id IS '主键'; +COMMENT ON COLUMN uc_user_auth.auth_main_type IS '授权大类'; +COMMENT ON COLUMN uc_user_auth.unique_id IS '授权唯一id'; +COMMENT ON COLUMN uc_user_auth.user_id IS '用户id'; +COMMENT ON COLUMN uc_user_auth.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_auth.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_auth.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_auth.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_auth.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_auth.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_user_cancel +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_cancel; +CREATE TABLE uc_user_cancel ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + status SMALLINT DEFAULT 1, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + cancellation_type VARCHAR(2) DEFAULT NULL, + remark VARCHAR(255) DEFAULT NULL +); +COMMENT ON TABLE uc_user_cancel IS '用户申请注销表'; +COMMENT ON COLUMN uc_user_cancel.id IS '主键'; +COMMENT ON COLUMN uc_user_cancel.user_id IS '用户表ID'; +COMMENT ON COLUMN uc_user_cancel.status IS '申请状态 1:已申请 2:已注销'; +COMMENT ON COLUMN uc_user_cancel.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_cancel.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_cancel.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_cancel.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_cancel.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_cancel.proxy_by IS '代理人'; +COMMENT ON COLUMN uc_user_cancel.cancellation_type IS '注销原因类型 数据字典'; +COMMENT ON COLUMN uc_user_cancel.remark IS '备注'; + +-- ---------------------------- +-- Table structure for uc_user_config +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_config; +CREATE TABLE uc_user_config ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + config_code VARCHAR(64) DEFAULT NULL, + config_value VARCHAR(1024) DEFAULT NULL, + remark VARCHAR(128) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_user_config IS '用户配置表'; +COMMENT ON COLUMN uc_user_config.id IS '主键'; +COMMENT ON COLUMN uc_user_config.user_id IS '用户id'; +COMMENT ON COLUMN uc_user_config.config_code IS '编码'; +COMMENT ON COLUMN uc_user_config.config_value IS '配置值'; +COMMENT ON COLUMN uc_user_config.remark IS '备注'; +COMMENT ON COLUMN uc_user_config.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_config.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_config.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_config.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_config.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_config.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_user_extension +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_extension; +CREATE TABLE uc_user_extension ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + technique_tag VARCHAR(256) DEFAULT NULL, + residence_address VARCHAR(128) DEFAULT NULL, + file_code VARCHAR(255) DEFAULT NULL, + file_name VARCHAR(255) DEFAULT NULL, + covid_vaccine SMALLINT DEFAULT NULL, + wechat_qr_code VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_user_extension IS '用户扩展表'; +COMMENT ON COLUMN uc_user_extension.id IS '主键'; +COMMENT ON COLUMN uc_user_extension.user_id IS '用户id'; +COMMENT ON COLUMN uc_user_extension.technique_tag IS '技能标签'; +COMMENT ON COLUMN uc_user_extension.residence_address IS '常住地区'; +COMMENT ON COLUMN uc_user_extension.file_code IS '简历附件编码'; +COMMENT ON COLUMN uc_user_extension.file_name IS '简历附件名称'; +COMMENT ON COLUMN uc_user_extension.covid_vaccine IS '新冠疫苗:1.未接种,2.1针,3.2针,4.3针'; +COMMENT ON COLUMN uc_user_extension.wechat_qr_code IS '微信二维码'; +COMMENT ON COLUMN uc_user_extension.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_extension.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_extension.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_extension.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_extension.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_extension.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_user_face +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_face; +CREATE TABLE uc_user_face ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + type SMALLINT DEFAULT NULL, + file_code VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX INDEX12379510213700 ON uc_user_face(user_id); +COMMENT ON TABLE uc_user_face IS '用户活体信息表'; +COMMENT ON COLUMN uc_user_face.id IS 'ID'; +COMMENT ON COLUMN uc_user_face.user_id IS '用户ID'; +COMMENT ON COLUMN uc_user_face.type IS '类型 1:照片 2:视频'; +COMMENT ON COLUMN uc_user_face.file_code IS '文件编码'; +COMMENT ON COLUMN uc_user_face.deleted IS '是否删除'; +COMMENT ON COLUMN uc_user_face.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_face.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_face.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_face.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_face.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for uc_user_postal_info +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_postal_info; +CREATE TABLE uc_user_postal_info ( + id BIGSERIAL PRIMARY KEY, + defaulted BOOLEAN DEFAULT NULL, + name VARCHAR(50) DEFAULT NULL, + phone VARCHAR(50) DEFAULT NULL, + address_code VARCHAR(50) DEFAULT NULL, + address_name VARCHAR(50) DEFAULT NULL, + address_detail VARCHAR(100) DEFAULT NULL, + deleted BOOLEAN DEFAULT NULL, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE uc_user_postal_info IS '用户邮寄信息'; +COMMENT ON COLUMN uc_user_postal_info.id IS '主键'; +COMMENT ON COLUMN uc_user_postal_info.defaulted IS '是否默认'; +COMMENT ON COLUMN uc_user_postal_info.name IS '收货人'; +COMMENT ON COLUMN uc_user_postal_info.phone IS '手机号'; +COMMENT ON COLUMN uc_user_postal_info.address_code IS '地区编码'; +COMMENT ON COLUMN uc_user_postal_info.address_name IS '地址名称'; +COMMENT ON COLUMN uc_user_postal_info.address_detail IS '详细地址'; +COMMENT ON COLUMN uc_user_postal_info.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_postal_info.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_postal_info.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_postal_info.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_postal_info.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_postal_info.proxy_by IS '代理人'; + + +-- ---------------------------- +-- Table structure for uc_user_register +-- ---------------------------- +DROP TABLE IF EXISTS uc_user_register; +CREATE TABLE uc_user_register ( + id BIGSERIAL PRIMARY KEY, + platform_code SMALLINT DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + inviter_id BIGINT DEFAULT NULL, + inviter_type SMALLINT DEFAULT NULL, + inviter_org_id BIGINT DEFAULT NULL, + entrypoint VARCHAR(32) DEFAULT NULL, + union_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_user_id ON uc_user_register(user_id); +COMMENT ON TABLE uc_user_register IS '用户注册表'; +COMMENT ON COLUMN uc_user_register.id IS '主键'; +COMMENT ON COLUMN uc_user_register.platform_code IS '平台编码'; +COMMENT ON COLUMN uc_user_register.user_id IS '用户id'; +COMMENT ON COLUMN uc_user_register.inviter_id IS '邀请人id'; +COMMENT ON COLUMN uc_user_register.inviter_type IS '邀请人用户类型'; +COMMENT ON COLUMN uc_user_register.inviter_org_id IS '邀请人机构id'; +COMMENT ON COLUMN uc_user_register.entrypoint IS '入口点'; +COMMENT ON COLUMN uc_user_register.union_id IS '关联id(小程序码id)'; +COMMENT ON COLUMN uc_user_register.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN uc_user_register.create_by IS '创建人'; +COMMENT ON COLUMN uc_user_register.create_time IS '创建时间'; +COMMENT ON COLUMN uc_user_register.update_by IS '更新人'; +COMMENT ON COLUMN uc_user_register.update_time IS '更新时间'; +COMMENT ON COLUMN uc_user_register.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_chat +-- ---------------------------- +DROP TABLE IF EXISTS xs_chat; +CREATE TABLE xs_chat ( + id BIGSERIAL PRIMARY KEY, + org_id BIGINT DEFAULT NULL, + org_user_id BIGINT DEFAULT NULL, + person_user_id BIGINT DEFAULT NULL, + type VARCHAR(32) DEFAULT NULL, + status SMALLINT DEFAULT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + create_by BIGINT NOT NULL, + create_time TIMESTAMP NOT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_chat IS '聊天表'; +COMMENT ON COLUMN xs_chat.id IS '主键'; +COMMENT ON COLUMN xs_chat.org_id IS '机构id'; +COMMENT ON COLUMN xs_chat.org_user_id IS '机构用户id'; +COMMENT ON COLUMN xs_chat.person_user_id IS '个人用户id'; +COMMENT ON COLUMN xs_chat.type IS '类型(chat_type)'; +COMMENT ON COLUMN xs_chat.status IS '状态:1有效,2关闭'; +COMMENT ON COLUMN xs_chat.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN xs_chat.create_by IS '创建人'; +COMMENT ON COLUMN xs_chat.create_time IS '创建时间'; +COMMENT ON COLUMN xs_chat.update_by IS '更新人'; +COMMENT ON COLUMN xs_chat.update_time IS '更新时间'; +COMMENT ON COLUMN xs_chat.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_chat_msg +-- ---------------------------- +DROP TABLE IF EXISTS xs_chat_msg; +CREATE TABLE xs_chat_msg ( + id BIGSERIAL PRIMARY KEY, + chat_id BIGINT DEFAULT NULL, + sender_id BIGINT DEFAULT NULL, + msg_type VARCHAR(16) DEFAULT NULL, + content VARCHAR(1024) DEFAULT NULL, + msg_status SMALLINT DEFAULT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE, + create_by BIGINT NOT NULL, + create_time TIMESTAMP NOT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_chat_msg IS '聊天消息表'; +COMMENT ON COLUMN xs_chat_msg.id IS '主键'; +COMMENT ON COLUMN xs_chat_msg.chat_id IS '聊天id'; +COMMENT ON COLUMN xs_chat_msg.sender_id IS '发送用户id'; +COMMENT ON COLUMN xs_chat_msg.msg_type IS '消息类型(chat_msg_type):text等'; +COMMENT ON COLUMN xs_chat_msg.content IS '个人用户id'; +COMMENT ON COLUMN xs_chat_msg.msg_status IS '消息状态:1未读,2已读'; +COMMENT ON COLUMN xs_chat_msg.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN xs_chat_msg.create_by IS '创建人'; +COMMENT ON COLUMN xs_chat_msg.create_time IS '创建时间'; +COMMENT ON COLUMN xs_chat_msg.update_by IS '更新人'; +COMMENT ON COLUMN xs_chat_msg.update_time IS '更新时间'; +COMMENT ON COLUMN xs_chat_msg.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_code_setting +-- ---------------------------- +DROP TABLE IF EXISTS xs_code_setting; +CREATE TABLE xs_code_setting ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(64) DEFAULT NULL, + name VARCHAR(128) DEFAULT NULL, + remark VARCHAR(128) DEFAULT NULL, + type VARCHAR(2) DEFAULT NULL, + data_type VARCHAR(2) DEFAULT NULL, + data_format VARCHAR(128) DEFAULT NULL, + level VARCHAR(20) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_code_setting IS '编码表'; +COMMENT ON COLUMN xs_code_setting.id IS '主键ID'; +COMMENT ON COLUMN xs_code_setting.code IS '编码'; +COMMENT ON COLUMN xs_code_setting.name IS '名称'; +COMMENT ON COLUMN xs_code_setting.remark IS '备注'; +COMMENT ON COLUMN xs_code_setting.type IS '类型 模板编码、配置编码'; +COMMENT ON COLUMN xs_code_setting.data_type IS '数据类型'; +COMMENT ON COLUMN xs_code_setting.data_format IS '数据格式编号'; +COMMENT ON COLUMN xs_code_setting.level IS '级别 平台、渠道、定制机构、普通机构'; +COMMENT ON COLUMN xs_code_setting.deleted IS '是否删除'; +COMMENT ON COLUMN xs_code_setting.create_by IS '创建人'; +COMMENT ON COLUMN xs_code_setting.create_time IS '创建时间'; +COMMENT ON COLUMN xs_code_setting.update_by IS '修改人'; +COMMENT ON COLUMN xs_code_setting.update_time IS '修改时间'; +COMMENT ON COLUMN xs_code_setting.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_company_extend +-- ---------------------------- +DROP TABLE IF EXISTS xs_company_extend; +CREATE TABLE xs_company_extend ( + id BIGSERIAL PRIMARY KEY, + seller_id BIGINT DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX INDEX173651263650200 ON xs_company_extend(org_id); +COMMENT ON TABLE xs_company_extend IS '企业的扩展信息'; +COMMENT ON COLUMN xs_company_extend.id IS '主键ID'; +COMMENT ON COLUMN xs_company_extend.seller_id IS '销售'; +COMMENT ON COLUMN xs_company_extend.org_id IS '所属机构ID'; +COMMENT ON COLUMN xs_company_extend.deleted IS '是否删除'; +COMMENT ON COLUMN xs_company_extend.create_by IS '创建人'; +COMMENT ON COLUMN xs_company_extend.create_time IS '创建时间'; +COMMENT ON COLUMN xs_company_extend.update_by IS '修改人'; +COMMENT ON COLUMN xs_company_extend.update_time IS '修改时间'; +COMMENT ON COLUMN xs_company_extend.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_component +-- ---------------------------- +DROP TABLE IF EXISTS xs_component; +CREATE TABLE xs_component ( + id BIGSERIAL PRIMARY KEY, + url_id BIGINT DEFAULT NULL, + code VARCHAR(32) DEFAULT NULL, + name VARCHAR(64) DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_component IS '组件'; +COMMENT ON COLUMN xs_component.id IS '主键ID'; +COMMENT ON COLUMN xs_component.url_id IS '菜单id'; +COMMENT ON COLUMN xs_component.code IS '组件编码'; +COMMENT ON COLUMN xs_component.name IS '组件名称'; +COMMENT ON COLUMN xs_component.platform_code IS '平台编码'; +COMMENT ON COLUMN xs_component.deleted IS '是否删除'; +COMMENT ON COLUMN xs_component.create_by IS '创建人'; +COMMENT ON COLUMN xs_component.create_time IS '创建时间'; +COMMENT ON COLUMN xs_component.update_by IS '修改人'; +COMMENT ON COLUMN xs_component.update_time IS '修改时间'; +COMMENT ON COLUMN xs_component.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_config +-- ---------------------------- +DROP TABLE IF EXISTS xs_config; +CREATE TABLE xs_config ( + id BIGSERIAL PRIMARY KEY, + config_code VARCHAR(64) DEFAULT NULL, + config_value TEXT, + org_id BIGINT DEFAULT NULL, + remark VARCHAR(128) DEFAULT NULL, + enable BOOLEAN DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_config IS '配置表'; +COMMENT ON COLUMN xs_config.id IS '主键ID'; +COMMENT ON COLUMN xs_config.config_code IS '编码'; +COMMENT ON COLUMN xs_config.config_value IS '配置值'; +COMMENT ON COLUMN xs_config.org_id IS '机构'; +COMMENT ON COLUMN xs_config.remark IS '备注'; +COMMENT ON COLUMN xs_config.enable IS '是否启用'; +COMMENT ON COLUMN xs_config.deleted IS '是否删除'; +COMMENT ON COLUMN xs_config.create_by IS '创建人'; +COMMENT ON COLUMN xs_config.create_time IS '创建时间'; +COMMENT ON COLUMN xs_config.update_by IS '修改人'; +COMMENT ON COLUMN xs_config.update_time IS '修改时间'; +COMMENT ON COLUMN xs_config.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_dict +-- ---------------------------- +DROP TABLE IF EXISTS xs_dict; +CREATE TABLE xs_dict ( + id BIGSERIAL PRIMARY KEY, + dict_code VARCHAR(32) DEFAULT NULL, + dict_name VARCHAR(64) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_dict_code ON xs_dict(dict_code); +COMMENT ON TABLE xs_dict IS '数据字典主信息'; +COMMENT ON COLUMN xs_dict.id IS '主键ID'; +COMMENT ON COLUMN xs_dict.dict_code IS '字典编码'; +COMMENT ON COLUMN xs_dict.dict_name IS '字典名称'; +COMMENT ON COLUMN xs_dict.deleted IS '是否删除'; +COMMENT ON COLUMN xs_dict.create_by IS '创建人'; +COMMENT ON COLUMN xs_dict.create_time IS '创建时间'; +COMMENT ON COLUMN xs_dict.update_by IS '修改人'; +COMMENT ON COLUMN xs_dict.update_time IS '修改时间'; +COMMENT ON COLUMN xs_dict.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_dict_detail +-- ---------------------------- +DROP TABLE IF EXISTS xs_dict_detail; +CREATE TABLE xs_dict_detail ( + id BIGSERIAL PRIMARY KEY, + dict_id BIGINT DEFAULT NULL, + dict_detail_code VARCHAR(64) DEFAULT NULL, + dict_detail_name VARCHAR(128) DEFAULT NULL, + dict_order INTEGER DEFAULT NULL, + parent_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_dict_id ON xs_dict_detail(dict_id); +COMMENT ON TABLE xs_dict_detail IS '数据字典明细信息'; +COMMENT ON COLUMN xs_dict_detail.id IS '主键ID'; +COMMENT ON COLUMN xs_dict_detail.dict_id IS '字典ID'; +COMMENT ON COLUMN xs_dict_detail.dict_detail_code IS '子项编码'; +COMMENT ON COLUMN xs_dict_detail.dict_detail_name IS '子项名称'; +COMMENT ON COLUMN xs_dict_detail.dict_order IS '排序'; +COMMENT ON COLUMN xs_dict_detail.parent_id IS '父子项ID'; +COMMENT ON COLUMN xs_dict_detail.deleted IS '是否删除'; +COMMENT ON COLUMN xs_dict_detail.create_by IS '创建人'; +COMMENT ON COLUMN xs_dict_detail.create_time IS '创建时间'; +COMMENT ON COLUMN xs_dict_detail.update_by IS '修改人'; +COMMENT ON COLUMN xs_dict_detail.update_time IS '修改时间'; +COMMENT ON COLUMN xs_dict_detail.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_district +-- ---------------------------- +DROP TABLE IF EXISTS xs_district; +CREATE TABLE xs_district ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(32) DEFAULT NULL, + parent_id BIGINT DEFAULT NULL, + initial VARCHAR(32) DEFAULT NULL, + initials VARCHAR(32) DEFAULT NULL, + pinyin VARCHAR(32) DEFAULT NULL, + extra VARCHAR(32) DEFAULT NULL, + suffix VARCHAR(32) DEFAULT NULL, + code VARCHAR(32) DEFAULT NULL, + area_code VARCHAR(32) DEFAULT NULL, + district_order INTEGER DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + level INTEGER DEFAULT NULL +); +COMMENT ON TABLE xs_district IS '省份城市'; +COMMENT ON COLUMN xs_district.id IS '主键ID'; +COMMENT ON COLUMN xs_district.name IS '城市名称'; +COMMENT ON COLUMN xs_district.parent_id IS '上级城市ID'; +COMMENT ON COLUMN xs_district.initial IS '首字母'; +COMMENT ON COLUMN xs_district.initials IS '简拼'; +COMMENT ON COLUMN xs_district.pinyin IS '全拼'; +COMMENT ON COLUMN xs_district.extra IS '特别行政区'; +COMMENT ON COLUMN xs_district.suffix IS '后缀'; +COMMENT ON COLUMN xs_district.code IS '城市编码'; +COMMENT ON COLUMN xs_district.area_code IS '区域编码'; +COMMENT ON COLUMN xs_district.district_order IS '排序'; +COMMENT ON COLUMN xs_district.deleted IS '是否删除'; +COMMENT ON COLUMN xs_district.create_by IS '创建人'; +COMMENT ON COLUMN xs_district.create_time IS '创建时间'; +COMMENT ON COLUMN xs_district.update_by IS '修改人'; +COMMENT ON COLUMN xs_district.update_time IS '修改时间'; +COMMENT ON COLUMN xs_district.proxy_by IS '代理人'; +COMMENT ON COLUMN xs_district.level IS '层级'; + +-- ---------------------------- +-- Table structure for xs_file_record +-- ---------------------------- +DROP TABLE IF EXISTS xs_file_record; +CREATE TABLE xs_file_record ( + id BIGSERIAL PRIMARY KEY, + file_code VARCHAR(64) DEFAULT NULL, + file_path VARCHAR(512) DEFAULT NULL, + file_name VARCHAR(128) DEFAULT NULL, + file_size INTEGER DEFAULT NULL, + md5_code VARCHAR(64) DEFAULT NULL, + file_suffix VARCHAR(32) DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + storage_type VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_file_code ON xs_file_record(file_code); +COMMENT ON TABLE xs_file_record IS '附件信息'; +COMMENT ON COLUMN xs_file_record.id IS '主键ID'; +COMMENT ON COLUMN xs_file_record.file_code IS '文件编码'; +COMMENT ON COLUMN xs_file_record.file_path IS '文件路径'; +COMMENT ON COLUMN xs_file_record.file_name IS '文件名'; +COMMENT ON COLUMN xs_file_record.file_size IS '文件大小'; +COMMENT ON COLUMN xs_file_record.md5_code IS '文件MD5'; +COMMENT ON COLUMN xs_file_record.file_suffix IS '文件后缀'; +COMMENT ON COLUMN xs_file_record.org_id IS '所属机构'; +COMMENT ON COLUMN xs_file_record.storage_type IS '储存器类型'; +COMMENT ON COLUMN xs_file_record.deleted IS '是否删除'; +COMMENT ON COLUMN xs_file_record.create_by IS '创建人'; +COMMENT ON COLUMN xs_file_record.create_time IS '创建时间'; +COMMENT ON COLUMN xs_file_record.update_by IS '修改人'; +COMMENT ON COLUMN xs_file_record.update_time IS '修改时间'; +COMMENT ON COLUMN xs_file_record.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_org +-- ---------------------------- +DROP TABLE IF EXISTS xs_org; +CREATE TABLE xs_org ( + id BIGSERIAL PRIMARY KEY, + org_name VARCHAR(32) DEFAULT NULL, + org_code VARCHAR(64) DEFAULT NULL, + type VARCHAR(2) DEFAULT NULL, + address_code VARCHAR(16) DEFAULT NULL, + address_name VARCHAR(128) DEFAULT NULL, + description VARCHAR(1024) DEFAULT NULL, + quality BOOLEAN DEFAULT FALSE, + enable BOOLEAN DEFAULT NULL, + activated BOOLEAN DEFAULT FALSE, + parent_id BIGINT DEFAULT NULL, + credit_code VARCHAR(64) DEFAULT NULL, + trade VARCHAR(2) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + activation_code VARCHAR(32) DEFAULT NULL, + phone VARCHAR(32) DEFAULT NULL, + query_children BOOLEAN DEFAULT NULL, + org_logo VARCHAR(64) DEFAULT NULL, + registered_address VARCHAR(128) DEFAULT NULL, + company_size VARCHAR(32) DEFAULT NULL, + business_license VARCHAR(64) DEFAULT NULL, + recruit_position_type VARCHAR(2) NOT NULL DEFAULT 1 +); +COMMENT ON TABLE xs_org IS '机构'; +COMMENT ON COLUMN xs_org.id IS '主键ID'; +COMMENT ON COLUMN xs_org.org_name IS '机构名称'; +COMMENT ON COLUMN xs_org.org_code IS '机构编号'; +COMMENT ON COLUMN xs_org.type IS '机构类型(1.平台 2.渠道代理商 3.普通机构 4.定制机构 5.散户机构 6.产品机构)'; +COMMENT ON COLUMN xs_org.address_code IS '任务地点编码'; +COMMENT ON COLUMN xs_org.address_name IS '任务地点全称'; +COMMENT ON COLUMN xs_org.description IS '机构描述'; +COMMENT ON COLUMN xs_org.quality IS '是否优质企业'; +COMMENT ON COLUMN xs_org.enable IS '是否启用'; +COMMENT ON COLUMN xs_org.activated IS '是否激活'; +COMMENT ON COLUMN xs_org.parent_id IS '父机构ID'; +COMMENT ON COLUMN xs_org.credit_code IS '统一社会信用代码'; +COMMENT ON COLUMN xs_org.trade IS '所属行业'; +COMMENT ON COLUMN xs_org.deleted IS '是否删除'; +COMMENT ON COLUMN xs_org.create_by IS '创建人'; +COMMENT ON COLUMN xs_org.create_time IS '创建时间'; +COMMENT ON COLUMN xs_org.update_by IS '修改人'; +COMMENT ON COLUMN xs_org.update_time IS '修改时间'; +COMMENT ON COLUMN xs_org.proxy_by IS '代理人'; +COMMENT ON COLUMN xs_org.activation_code IS '机构账号激活码'; +COMMENT ON COLUMN xs_org.phone IS '手机号码'; +COMMENT ON COLUMN xs_org.query_children IS '是否能查看下级数据'; +COMMENT ON COLUMN xs_org.org_logo IS '企业logo'; +COMMENT ON COLUMN xs_org.registered_address IS '注册地址'; +COMMENT ON COLUMN xs_org.company_size IS '规模 数据字典'; +COMMENT ON COLUMN xs_org.business_license IS '营业执照'; +COMMENT ON COLUMN xs_org.recruit_position_type IS '招聘岗位类型'; + +-- ---------------------------- +-- Table structure for xs_org_user +-- ---------------------------- +DROP TABLE IF EXISTS xs_org_user; +CREATE TABLE xs_org_user ( + id BIGSERIAL PRIMARY KEY, + org_id BIGINT DEFAULT NULL, + user_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_org_user IS '机构用户'; +COMMENT ON COLUMN xs_org_user.id IS '主键ID'; +COMMENT ON COLUMN xs_org_user.org_id IS '机构id'; +COMMENT ON COLUMN xs_org_user.user_id IS '用户id'; +COMMENT ON COLUMN xs_org_user.deleted IS '是否删除'; +COMMENT ON COLUMN xs_org_user.create_by IS '创建人'; +COMMENT ON COLUMN xs_org_user.create_time IS '创建时间'; +COMMENT ON COLUMN xs_org_user.update_by IS '修改人'; +COMMENT ON COLUMN xs_org_user.update_time IS '修改时间'; +COMMENT ON COLUMN xs_org_user.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_permission +-- ---------------------------- +DROP TABLE IF EXISTS xs_permission; +CREATE TABLE xs_permission ( + id BIGSERIAL PRIMARY KEY, + role_id BIGINT DEFAULT NULL, + resource_type VARCHAR(2) DEFAULT NULL, + resource_id BIGINT DEFAULT NULL, + attribute VARCHAR(32) DEFAULT NULL, + org_id BIGINT DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_permission IS '权限表'; +COMMENT ON COLUMN xs_permission.id IS '主键ID'; +COMMENT ON COLUMN xs_permission.role_id IS '角色id'; +COMMENT ON COLUMN xs_permission.resource_type IS '资源类型:1菜单 2组件 3接口'; +COMMENT ON COLUMN xs_permission.resource_id IS '资源id'; +COMMENT ON COLUMN xs_permission.attribute IS '权限鉴别属性,默认格式:ROLE_{roleId},可以扩展'; +COMMENT ON COLUMN xs_permission.org_id IS '机构id'; +COMMENT ON COLUMN xs_permission.platform_code IS '平台编码 1.业务系统 2.运营中台 3.用户中心 4.支付系统 5.在线培训 6.转发器 7.风控 8.网关 9.发号器 10.基础框架'; +COMMENT ON COLUMN xs_permission.deleted IS '是否删除'; +COMMENT ON COLUMN xs_permission.create_by IS '创建人'; +COMMENT ON COLUMN xs_permission.create_time IS '创建时间'; +COMMENT ON COLUMN xs_permission.update_by IS '修改人'; +COMMENT ON COLUMN xs_permission.update_time IS '修改时间'; +COMMENT ON COLUMN xs_permission.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_permission_organization +-- ---------------------------- +DROP TABLE IF EXISTS xs_permission_organization; +CREATE TABLE xs_permission_organization ( + id BIGSERIAL PRIMARY KEY, + permission_package_id BIGINT DEFAULT NULL, + allocation_org_id BIGINT DEFAULT NULL, + remark VARCHAR(100) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_allocation_org_id ON xs_permission_organization(allocation_org_id); +CREATE INDEX idx_permission_package_id ON xs_permission_organization(permission_package_id); +COMMENT ON TABLE xs_permission_organization IS '权限包-机构关系表'; +COMMENT ON COLUMN xs_permission_organization.id IS '主键id'; +COMMENT ON COLUMN xs_permission_organization.permission_package_id IS '权限包id'; +COMMENT ON COLUMN xs_permission_organization.allocation_org_id IS '分配机构orgid'; +COMMENT ON COLUMN xs_permission_organization.remark IS '备注'; +COMMENT ON COLUMN xs_permission_organization.deleted IS '是否删除'; +COMMENT ON COLUMN xs_permission_organization.create_by IS '创建人'; +COMMENT ON COLUMN xs_permission_organization.create_time IS '创建时间'; +COMMENT ON COLUMN xs_permission_organization.update_by IS '修改人'; +COMMENT ON COLUMN xs_permission_organization.update_time IS '修改时间'; +COMMENT ON COLUMN xs_permission_organization.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_permission_package +-- ---------------------------- +DROP TABLE IF EXISTS xs_permission_package; +CREATE TABLE xs_permission_package ( + id BIGSERIAL PRIMARY KEY, + permission_package_name VARCHAR(64) DEFAULT NULL, + permission_package_type SMALLINT DEFAULT 1, + platform_code SMALLINT DEFAULT NULL, + status SMALLINT DEFAULT 1, + remark VARCHAR(100) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE xs_permission_package IS '标准权限包'; +COMMENT ON COLUMN xs_permission_package.id IS '主键id'; +COMMENT ON COLUMN xs_permission_package.permission_package_name IS '权限包名称'; +COMMENT ON COLUMN xs_permission_package.permission_package_type IS '类型 1.基础包 2.其他类型'; +COMMENT ON COLUMN xs_permission_package.platform_code IS '平台编码 1.业务系统 2.运营中台 3.用户中心 4.支付系统 5.在线培训 6.转发器 7.风控 8.网关 9.发号器 10.基础框架'; +COMMENT ON COLUMN xs_permission_package.status IS '状态 1.有效 2.失效'; +COMMENT ON COLUMN xs_permission_package.remark IS '备注'; +COMMENT ON COLUMN xs_permission_package.deleted IS '是否删除'; +COMMENT ON COLUMN xs_permission_package.create_by IS '创建人'; +COMMENT ON COLUMN xs_permission_package.create_time IS '创建时间'; +COMMENT ON COLUMN xs_permission_package.update_by IS '修改人'; +COMMENT ON COLUMN xs_permission_package.update_time IS '修改时间'; +COMMENT ON COLUMN xs_permission_package.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_permission_resources +-- ---------------------------- +DROP TABLE IF EXISTS xs_permission_resources; +CREATE TABLE xs_permission_resources ( + id BIGSERIAL PRIMARY KEY, + permission_package_id BIGINT DEFAULT NULL, + resource_id BIGINT DEFAULT NULL, + resource_type SMALLINT DEFAULT 1, + remark VARCHAR(100) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_resource_id ON xs_permission_resources(resource_id); +CREATE INDEX INDEX12372582404500 ON xs_permission_resources(permission_package_id); +COMMENT ON TABLE xs_permission_resources IS '权限包-资源关系表'; +COMMENT ON COLUMN xs_permission_resources.id IS '主键id'; +COMMENT ON COLUMN xs_permission_resources.permission_package_id IS '权限包id'; +COMMENT ON COLUMN xs_permission_resources.resource_id IS '资源id'; +COMMENT ON COLUMN xs_permission_resources.resource_type IS '类型 1.菜单 2.组件 3.接口'; +COMMENT ON COLUMN xs_permission_resources.remark IS '备注'; +COMMENT ON COLUMN xs_permission_resources.deleted IS '是否删除'; +COMMENT ON COLUMN xs_permission_resources.create_by IS '创建人'; +COMMENT ON COLUMN xs_permission_resources.create_time IS '创建时间'; +COMMENT ON COLUMN xs_permission_resources.update_by IS '修改人'; +COMMENT ON COLUMN xs_permission_resources.update_time IS '修改时间'; +COMMENT ON COLUMN xs_permission_resources.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_real_name_two_verification +-- ---------------------------- +DROP TABLE IF EXISTS xs_real_name_two_verification; +CREATE TABLE xs_real_name_two_verification ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + person_name VARCHAR(64) DEFAULT NULL, + identity_no VARCHAR(32) DEFAULT NULL, + status VARCHAR(2) DEFAULT 0, + authentication_source SMALLINT DEFAULT NULL, + err_msg VARCHAR(64) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); + +-- ---------------------------- +-- Table structure for xs_role +-- ---------------------------- +DROP TABLE IF EXISTS xs_role; +CREATE TABLE xs_role ( + id BIGSERIAL PRIMARY KEY, + org_id BIGINT DEFAULT NULL, + name VARCHAR(128) DEFAULT NULL, + type VARCHAR(2) DEFAULT 0, + enabled BOOLEAN NOT NULL DEFAULT FALSE, + description VARCHAR(128) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + standard_type VARCHAR(2) DEFAULT 1, + platform_code SMALLINT DEFAULT NULL +); +COMMENT ON TABLE xs_role IS '角色'; +COMMENT ON COLUMN xs_role.id IS '主键ID'; +COMMENT ON COLUMN xs_role.org_id IS '机构id'; +COMMENT ON COLUMN xs_role.name IS '名称'; +COMMENT ON COLUMN xs_role.type IS '类型:0业务角色、1系统角色-渠道代理商、2系统角色-普通机构'; +COMMENT ON COLUMN xs_role.enabled IS '是否启用'; +COMMENT ON COLUMN xs_role.description IS '描述'; +COMMENT ON COLUMN xs_role.deleted IS '是否删除'; +COMMENT ON COLUMN xs_role.create_by IS '创建人'; +COMMENT ON COLUMN xs_role.create_time IS '创建时间'; +COMMENT ON COLUMN xs_role.update_by IS '修改人'; +COMMENT ON COLUMN xs_role.update_time IS '修改时间'; +COMMENT ON COLUMN xs_role.proxy_by IS '代理人'; +COMMENT ON COLUMN xs_role.standard_type IS '标准角色类型:见role_standard_type'; +COMMENT ON COLUMN xs_role.platform_code IS '平台编码'; + +-- ---------------------------- +-- Table structure for xs_role_allocation +-- ---------------------------- +DROP TABLE IF EXISTS xs_role_allocation; +CREATE TABLE xs_role_allocation ( + id BIGSERIAL PRIMARY KEY, + role_id BIGINT DEFAULT NULL, + allocation_type VARCHAR(2) DEFAULT NULL, + allocation_id BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_allocation_id ON xs_role_allocation(allocation_id); +COMMENT ON TABLE xs_role_allocation IS '角色分配表'; +COMMENT ON COLUMN xs_role_allocation.id IS '主键ID'; +COMMENT ON COLUMN xs_role_allocation.role_id IS '角色id'; +COMMENT ON COLUMN xs_role_allocation.allocation_type IS '角色分配类型:1个人2部门3组'; +COMMENT ON COLUMN xs_role_allocation.allocation_id IS '角色具体分配类型对应的id'; +COMMENT ON COLUMN xs_role_allocation.deleted IS '是否删除'; +COMMENT ON COLUMN xs_role_allocation.create_by IS '创建人'; +COMMENT ON COLUMN xs_role_allocation.create_time IS '创建时间'; +COMMENT ON COLUMN xs_role_allocation.update_by IS '修改人'; +COMMENT ON COLUMN xs_role_allocation.update_time IS '修改时间'; +COMMENT ON COLUMN xs_role_allocation.proxy_by IS '代理人'; + +-- ---------------------------- +-- Table structure for xs_role_standard_type +-- ---------------------------- +DROP TABLE IF EXISTS xs_role_standard_type; +CREATE TABLE xs_role_standard_type ( + id BIGSERIAL PRIMARY KEY, + role_code VARCHAR(32) DEFAULT NULL, + name VARCHAR(64) DEFAULT NULL, + platform_code VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT NULL, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + type_order INTEGER DEFAULT NULL +); +COMMENT ON TABLE xs_role_standard_type IS '标准角色类型'; +COMMENT ON COLUMN xs_role_standard_type.id IS '主键'; +COMMENT ON COLUMN xs_role_standard_type.role_code IS '编码'; +COMMENT ON COLUMN xs_role_standard_type.name IS '名称'; +COMMENT ON COLUMN xs_role_standard_type.platform_code IS '平台编码'; +COMMENT ON COLUMN xs_role_standard_type.deleted IS '是否逻辑删除'; +COMMENT ON COLUMN xs_role_standard_type.create_by IS '创建人'; +COMMENT ON COLUMN xs_role_standard_type.create_time IS '创建时间'; +COMMENT ON COLUMN xs_role_standard_type.update_by IS '更新人'; +COMMENT ON COLUMN xs_role_standard_type.update_time IS '更新时间'; +COMMENT ON COLUMN xs_role_standard_type.proxy_by IS '代理人'; +COMMENT ON COLUMN xs_role_standard_type.type_order IS '排序'; + +-- ---------------------------- +-- Table structure for xs_url +-- ---------------------------- +DROP TABLE IF EXISTS xs_url; +CREATE TABLE xs_url ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(128) DEFAULT NULL, + link VARCHAR(256) DEFAULT NULL, + icon VARCHAR(32) DEFAULT NULL, + parent_id BIGINT DEFAULT NULL, + resource_order INTEGER DEFAULT NULL, + type SMALLINT DEFAULT NULL, + visible BOOLEAN DEFAULT NULL, + description VARCHAR(128) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + platform_code SMALLINT DEFAULT NULL +); +COMMENT ON TABLE xs_url IS '菜单地址'; +COMMENT ON COLUMN xs_url.id IS '主键ID'; +COMMENT ON COLUMN xs_url.name IS '名称'; +COMMENT ON COLUMN xs_url.link IS '链接'; +COMMENT ON COLUMN xs_url.icon IS 'icon'; +COMMENT ON COLUMN xs_url.parent_id IS '父级'; +COMMENT ON COLUMN xs_url.resource_order IS '排序'; +COMMENT ON COLUMN xs_url.type IS '资源类型 1:菜单 2:接口'; +COMMENT ON COLUMN xs_url.visible IS '是否可见'; +COMMENT ON COLUMN xs_url.description IS '描述'; +COMMENT ON COLUMN xs_url.deleted IS '是否删除'; +COMMENT ON COLUMN xs_url.create_by IS '创建人'; +COMMENT ON COLUMN xs_url.create_time IS '创建时间'; +COMMENT ON COLUMN xs_url.update_by IS '修改人'; +COMMENT ON COLUMN xs_url.update_time IS '修改时间'; +COMMENT ON COLUMN xs_url.proxy_by IS '代理人'; +COMMENT ON COLUMN xs_url.platform_code IS '平台编码'; + + + + +-- ---------------------------- +-- Table structure for x_file_category +-- ---------------------------- +DROP TABLE IF EXISTS x_file_category; +CREATE TABLE x_file_category ( + id BIGSERIAL PRIMARY KEY, + file_category VARCHAR(32) DEFAULT NULL, + file_suffix VARCHAR(32) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE x_file_category IS '文件种类'; +COMMENT ON COLUMN x_file_category.id IS '主键ID'; +COMMENT ON COLUMN x_file_category.file_category IS '文件种类'; +COMMENT ON COLUMN x_file_category.file_suffix IS '文件后缀'; +COMMENT ON COLUMN x_file_category.deleted IS '是否删除'; +COMMENT ON COLUMN x_file_category.create_by IS '创建人'; +COMMENT ON COLUMN x_file_category.create_time IS '创建时间'; +COMMENT ON COLUMN x_file_category.update_by IS '修改人'; +COMMENT ON COLUMN x_file_category.update_time IS '修改时间'; +COMMENT ON COLUMN x_file_category.proxy_by IS '代理人'; + + +-- ---------------------------- +-- Table structure for x_user_space +-- ---------------------------- +DROP TABLE IF EXISTS x_user_space; +CREATE TABLE x_user_space ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + total_size BIGINT DEFAULT NULL, + used_size BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE x_user_space IS '用户空间'; +COMMENT ON COLUMN x_user_space.id IS '主键ID'; +COMMENT ON COLUMN x_user_space.user_id IS '用户id'; +COMMENT ON COLUMN x_user_space.total_size IS '总容量'; +COMMENT ON COLUMN x_user_space.used_size IS '已使用容量'; +COMMENT ON COLUMN x_user_space.deleted IS '是否删除'; +COMMENT ON COLUMN x_user_space.create_by IS '创建人'; +COMMENT ON COLUMN x_user_space.create_time IS '创建时间'; +COMMENT ON COLUMN x_user_space.update_by IS '修改人'; +COMMENT ON COLUMN x_user_space.update_time IS '修改时间'; +COMMENT ON COLUMN x_user_space.proxy_by IS '代理人'; + + +-- ---------------------------- +-- Table structure for x_user_file +-- ---------------------------- +DROP TABLE IF EXISTS x_user_file; +CREATE TABLE x_user_file ( + id BIGSERIAL PRIMARY KEY, + user_id BIGINT DEFAULT NULL, + root_type VARCHAR(2) DEFAULT NULL, + file_type VARCHAR(32) DEFAULT NULL, + file_name VARCHAR(256) DEFAULT NULL, + parent_id BIGINT DEFAULT NULL, + file_code VARCHAR(32) DEFAULT NULL, + file_status VARCHAR(2) DEFAULT NULL, + path VARCHAR(255) DEFAULT NULL, + end_time TIMESTAMP DEFAULT NULL, + access_code VARCHAR(6) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +COMMENT ON TABLE x_user_file IS '用户文件'; +COMMENT ON COLUMN x_user_file.id IS '主键ID'; +COMMENT ON COLUMN x_user_file.user_id IS '用户id'; +COMMENT ON COLUMN x_user_file.root_type IS '根类型:1普通/2内穿外/3外传内/4我的分享'; +COMMENT ON COLUMN x_user_file.file_type IS '文件大类:1文件/2文件夹'; +COMMENT ON COLUMN x_user_file.file_name IS '文件名'; +COMMENT ON COLUMN x_user_file.parent_id IS '父id'; +COMMENT ON COLUMN x_user_file.file_code IS '文件编码'; +COMMENT ON COLUMN x_user_file.file_status IS '文件状态:1正常2失效11解析中12待审核13传输中'; +COMMENT ON COLUMN x_user_file.path IS '路径'; +COMMENT ON COLUMN x_user_file.end_time IS '有效期'; +COMMENT ON COLUMN x_user_file.access_code IS '访问码'; +COMMENT ON COLUMN x_user_file.deleted IS '是否删除'; +COMMENT ON COLUMN x_user_file.create_by IS '创建人'; +COMMENT ON COLUMN x_user_file.create_time IS '创建时间'; +COMMENT ON COLUMN x_user_file.update_by IS '修改人'; +COMMENT ON COLUMN x_user_file.update_time IS '修改时间'; +COMMENT ON COLUMN x_user_file.proxy_by IS '代理人'; + + +-- ---------------------------- +-- Table structure for x_file_risk +-- ---------------------------- +DROP TABLE IF EXISTS x_file_risk; +CREATE TABLE x_file_risk ( + id BIGSERIAL PRIMARY KEY, + file_code VARCHAR(32) DEFAULT NULL, + max_risk_level VARCHAR(2) DEFAULT NULL, + risk_json TEXT DEFAULT NULL, + dify_file_json TEXT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + CONSTRAINT uk_file_code UNIQUE (file_code) +); +COMMENT ON TABLE x_file_risk IS '文件风险'; +COMMENT ON COLUMN x_file_risk.id IS '主键ID'; +COMMENT ON COLUMN x_file_risk.file_code IS '文件编码'; +COMMENT ON COLUMN x_file_risk.max_risk_level IS '最高风险等级:1重度/2中度/3轻度'; +COMMENT ON COLUMN x_file_risk.risk_json IS '风险详情JSON'; +COMMENT ON COLUMN x_file_risk.dify_file_json IS 'Dify文件上传响应JSON'; +COMMENT ON COLUMN x_file_risk.deleted IS '是否删除'; +COMMENT ON COLUMN x_file_risk.create_by IS '创建人'; +COMMENT ON COLUMN x_file_risk.create_time IS '创建时间'; +COMMENT ON COLUMN x_file_risk.update_by IS '修改人'; +COMMENT ON COLUMN x_file_risk.update_time IS '修改时间'; +COMMENT ON COLUMN x_file_risk.proxy_by IS '代理人'; + +CREATE TABLE x_upload_config ( + id BIGSERIAL PRIMARY KEY, + org_id BIGINT DEFAULT NULL, + upload_enabled BOOLEAN DEFAULT TRUE, + allow_all_type BOOLEAN DEFAULT TRUE, + allowed_suffixes VARCHAR(1024) DEFAULT NULL, + max_file_size BIGINT DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL, + CONSTRAINT uk_upload_config_org_id UNIQUE (org_id) +); +COMMENT ON TABLE x_upload_config IS '机构上传配置'; +COMMENT ON COLUMN x_upload_config.id IS '主键ID'; +COMMENT ON COLUMN x_upload_config.org_id IS '机构id'; +COMMENT ON COLUMN x_upload_config.upload_enabled IS '是否允许上传'; +COMMENT ON COLUMN x_upload_config.allow_all_type IS '是否允许全部类型(false时按allowed_suffixes白名单校验)'; +COMMENT ON COLUMN x_upload_config.allowed_suffixes IS '允许的文件后缀(逗号分隔,小写),allow_all_type=false时生效'; +COMMENT ON COLUMN x_upload_config.max_file_size IS '单文件大小上限(MB),空或0不限制'; +COMMENT ON COLUMN x_upload_config.deleted IS '是否删除'; +COMMENT ON COLUMN x_upload_config.create_by IS '创建人'; +COMMENT ON COLUMN x_upload_config.create_time IS '创建时间'; +COMMENT ON COLUMN x_upload_config.update_by IS '修改人'; +COMMENT ON COLUMN x_upload_config.update_time IS '修改时间'; +COMMENT ON COLUMN x_upload_config.proxy_by IS '代理人'; + +CREATE TABLE x_file_security ( + id BIGSERIAL PRIMARY KEY, + file_code VARCHAR(32) DEFAULT NULL, + task_id VARCHAR(64) DEFAULT NULL, + scan_status VARCHAR(2) DEFAULT NULL, + action VARCHAR(32) DEFAULT NULL, + reason VARCHAR(1024) DEFAULT NULL, + result_json TEXT DEFAULT NULL, + review_comment VARCHAR(512) DEFAULT NULL, + deleted BOOLEAN DEFAULT FALSE, + create_by BIGINT DEFAULT NULL, + create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + update_by BIGINT DEFAULT NULL, + update_time TIMESTAMP DEFAULT NULL, + proxy_by BIGINT DEFAULT NULL +); +CREATE INDEX idx_file_security_file_code ON x_file_security(file_code); +CREATE INDEX idx_file_security_scan_status ON x_file_security(scan_status); +COMMENT ON TABLE x_file_security IS '文件安全检测记录'; +COMMENT ON COLUMN x_file_security.id IS '主键ID'; +COMMENT ON COLUMN x_file_security.file_code IS '文件编码'; +COMMENT ON COLUMN x_file_security.task_id IS 'MeshSafe检测任务ID'; +COMMENT ON COLUMN x_file_security.scan_status IS '检测状态:1检测中/2通过/3不通过/4检测失败'; +COMMENT ON COLUMN x_file_security.action IS 'MeshSafe处置动作:pass/manual_review/reject'; +COMMENT ON COLUMN x_file_security.reason IS '检测结论说明'; +COMMENT ON COLUMN x_file_security.result_json IS '检测完整结果JSON'; +COMMENT ON COLUMN x_file_security.review_comment IS '人工复核说明(人工改判时有值)'; +COMMENT ON COLUMN x_file_security.deleted IS '是否删除'; +COMMENT ON COLUMN x_file_security.create_by IS '创建人'; +COMMENT ON COLUMN x_file_security.create_time IS '创建时间'; +COMMENT ON COLUMN x_file_security.update_by IS '修改人'; +COMMENT ON COLUMN x_file_security.update_time IS '修改时间'; +COMMENT ON COLUMN x_file_security.proxy_by IS '代理人'; diff --git a/deploy/offline/sql/02-init-data.sql b/deploy/offline/sql/02-init-data.sql new file mode 100644 index 0000000..7e998c4 --- /dev/null +++ b/deploy/offline/sql/02-init-data.sql @@ -0,0 +1,46 @@ +-- 系统uc_platform +INSERT INTO uc_platform +(platform_code, name, full_name, short_name, business, deleted, create_time) +VALUES(1, '文档系统', 's-file', 'file', true, FALSE, now()); + +-- 平台机构/平台机构管理员 +INSERT INTO xs_org +(org_name, org_code, type, address_code, address_name, description, quality, enable, activated, parent_id, credit_code, trade, deleted, create_by, create_time, update_by, update_time, proxy_by, activation_code, phone, query_children, org_logo, registered_address, company_size, business_license, recruit_position_type) +VALUES('senmesh', 'O001', '1', '440232', NULL, NULL, false, TRUE, TRUE, NULL, '', NULL, FALSE, 0, now(), 1, now(), null, NULL, null, NULL, null, '1234', '6', null, '1'); + +INSERT INTO uc_user +(username, password, type, supeiror, security_code, nickname, avatar, phone, phone_binding, real_name, card_no, authenticated, status, email, address, education, init_user_id, enabled, account_non_expired, account_non_locked, credentials_non_expired, deleted, create_by, create_time, update_by, update_time, proxy_by, gender, birthday) +VALUES('admin', '$2a$10$0N3iaxx2sYv0hXvYzS4.6.yriL.DfjRTjED7Y4vkqxzbxi2dNpBou', 1, TRUE, '$2a$10$mGA1jdBzq8micEr02KiJ9OiYQz1fc/PSVG0hN0EYUuxhZp8EI1M2q', '平台管理员', '', '13888888888', TRUE, '平台管理员', NULL, FALSE, 2, null, NULL, NULL, NULL, TRUE, TRUE, TRUE, TRUE, FALSE, 0, now(), 1, NULL, NULL, NULL, NULL); +INSERT INTO uc_org_user +(user_id, org_id, administrator, deleted, create_by, create_time, update_by, update_time, proxy_by) +VALUES(1, 1, TRUE, FALSE, 0, now(), NULL, NULL, NULL); +-- 平台uc_org_relation +INSERT INTO uc_org_relation +(org_id, org_type, parent_id, path, platform_code, platform_name, deleted, create_by, create_time, update_by, update_time, proxy_by) +VALUES(1, 1, 0, '1', 1, NULL, FALSE, NULL, NULL, NULL, NULL, NULL); + +-- ---------------------------- +-- Records of x_file_category +-- ---------------------------- +INSERT INTO x_file_category (file_category, file_suffix, deleted, create_by, create_time) VALUES +('video', 'mp4', FALSE, 0, NOW()), +('video', 'mkv', FALSE, 0, NOW()), +('video', 'avi', FALSE, 0, NOW()), +('video', 'mov', FALSE, 0, NOW()), +('video', 'wmv', FALSE, 0, NOW()), +('video', 'flv', FALSE, 0, NOW()), +('audio', 'mp3', FALSE, 0, NOW()), +('audio', 'wav', FALSE, 0, NOW()), +('audio', 'flac', FALSE, 0, NOW()), +('audio', 'aac', FALSE, 0, NOW()), +('document', 'txt', FALSE, 0, NOW()), +('document', 'docx', FALSE, 0, NOW()), +('document', 'pdf', FALSE, 0, NOW()), +('document', 'epub', FALSE, 0, NOW()), +('other', 'zip', FALSE, 0, NOW()), +('other', 'rar', FALSE, 0, NOW()), +('other', '7z', FALSE, 0, NOW()), +('other', 'jpg', FALSE, 0, NOW()), +('other', 'png', FALSE, 0, NOW()), +('other', 'gif', FALSE, 0, NOW()), +('other', 'tiff', FALSE, 0, NOW()); diff --git a/deploy/offline/sql/README.md b/deploy/offline/sql/README.md new file mode 100644 index 0000000..c591477 --- /dev/null +++ b/deploy/offline/sql/README.md @@ -0,0 +1,12 @@ +建表与初始数据脚本已随包放好,文件名前缀决定执行顺序(先建表后灌数据): + +| 文件 | 来源 | 说明 | +|---|---|---| +| `01-init-table.sql` | `backend/db/init-table-postgres-full.sql` | 全量 DDL,含 `x_file_security` 安全检测表 | +| `02-init-data.sql` | `backend/db/init-data.sql` | admin 账号、机构、平台编码(`system.code=1` 要与此一致) | + +出盘时用 `scripts/export-images.sh` 从仓库重新覆盖这两份,别手改副本; +增量升级脚本(`backend/db/upgrade-*.sql`)不在这里,装好后按《部署说明.md》第 7 节人工执行。 + +只有 `POSTGRES_*` 指向的库为空时,PostgreSQL 官方镜像才会执行 `docker-entrypoint-initdb.d`; +数据目录已有数据时会跳过 —— 想重建就把 `$SFILE_DATA_ROOT/pgdata` 一起清掉再 `up`。 diff --git a/deploy/offline/部署说明.md b/deploy/offline/部署说明.md new file mode 100644 index 0000000..a598569 --- /dev/null +++ b/deploy/offline/部署说明.md @@ -0,0 +1,219 @@ +# s-file 客户侧离线部署(两台服务器:内网机 + 外网机) + +> 本目录是**交付用**部署;本机联调用的两套栈同起在一台机器上,见 [../部署说明.md](../部署说明.md)。 +> 客户现场没有网络、没有光闸模拟目录,一切靠 `docker load` 的镜像 tar + 真实光闸摆渡目录。 + +--- + +## 1. 拓扑 + +``` + 内网(客户办公网) 外网(外部报送网) + ┌────────────────────────┐ ┌────────────────────────┐ + │ 内网服务器 A │ │ 外网服务器 B │ + │ ┌──────────────────┐ │ 光闸/单向 │ ┌──────────────────┐ │ + │ │ sfile-iw-web :80 │◄─┼── 摆渡设备 ──►┼─ │ sfile-ow-web :80 │ │ + │ │ sfile-iw-app :8080│ │ (不开放网络) │ │ sfile-ow-app │ │ + │ │ pg/redis/mq(内部) │ │ │ │ pg/redis/mq(内部)│ + │ └──────────────────┘ │ │ └──────────────────┘ │ + │ /data/sfile/gate/out │─── 发 ────► │ /data/sfile/gate/in │ + │ /data/sfile/gate/in │◄─── 收 ──── │ /data/sfile/gate/out │ + └───────────┬────────────┘ └───────────┬────────────┘ + │ http://host.docker.internal: + ┌──────┴───────────┐ + │ MeshSafe 文件安全检测 │ 同机容器 / 独立服务器 / 集群均可,只要求 内网机能 HTTP 访问到 + └──────────────────┘ +``` + +- 两台服务器之间**不开任何 TCP 端口**,文件只经光闸在 `gate/out` ⇄ 对端 `gate/in` 之间单向搬运。 +- 每台机器一个独立 compose 栈,同一份 `docker-compose.yml`,差别全在 `.env`。 +- 摆渡目录由光闸设备挂载/同步进宿主机,应用只认容器内固定路径 `/data/transfer/send`、`/data/transfer/receive`。 + +--- + +## 2. 交付包内容 + +本目录本身**就是**交付包,`.env`、`sql`、`images/*.tar` 都已就位,整目录拷走即可装机(不需要现场再回仓库取任何东西)。 + +``` +deploy/offline/ +├── docker-compose.yml 一台机器的 5 个服务(web/app/pg/redis/mq),现场不改 +├── .env.iw 内网机出厂配置(已填好,含口令)← install.sh iw 会拷成 .env +├── .env.ow 外网机出厂配置(同上,口令与内网机不同) +├── .env.example 空白模板,只在客户环境差异大到出厂值不适用时才用 +├── VERSION.txt 本次出盘的版本/平台/时间/镜像清单 +├── config/application.yml 静态默认配置,里面不放任何口令 +├── nginx/default.conf.template 前端反代(内/外网共用,靠 SFILE_SITE_ROOT 选站点) +├── sql/01-init-table.sql 全量建表(92K,含 x_file_security) +├── sql/02-init-data.sql 初始数据(2.8K,admin、机构、平台编码) +├── images/*.tar 5 个镜像,共 650M(docker save 原始大小) +├── images/SHA256SUMS.txt 交付盘校验值,install.sh 开机先比对它 +└── scripts/ + ├── install.sh 现场一键安装(要带 iw / ow 参数) + ├── smoke_test.py 现场自动化验收(18 条断言) + └── export-images.sh 我方出盘脚本,现场用不到 +``` + +> 仓库里 `images/*.tar`、`SHA256SUMS.txt`、`dist/` 已被 `.gitignore` 排除——镜像 tar 不进气仓,只走交付盘。 + +镜像清单与已验证版本(tag 必须与 tar 里的完全一致,`install.sh` 会核对 sha256、架构与 tag 三者): + +| 镜像 | 交付 tag | tar 大小 | 本机验证版本 | 说明 | +|---|---|---|---|---| +| `sfile-app` | `1.0.0` | 313M | JDK8 运行时 eclipse-temurin:8-jre-jammy | 内含 s-file.jar | +| `sfile-web` | `1.0.0` | 39M | nginx:alpine | **内网包与外网包都在同一个镜像里** | +| `postgres` | `18` | 161M | 18.3 | | +| `redis` | `8` | 53M | 8.6.3 | | +| `rabbitmq` | `3.13-management-alpine` | 84M | 3.13.7 | | + +--- + +## 3. 现场要先定的 6 件事 + +| 项 | 变量 | 建议 | +|---|---|---| +| 服务器 CPU 架构 | — | `uname -m`:x86_64→amd64,aarch64(鲲鹏/飞腾)→arm64。**出盘前必须确认**,架构不符装不上 | +| 前端对外端口 | `WEB_PORT` | 出厂值 80;被占用改 8080/9080,同步改客户访问地址 | +| 数据盘根目录 | `SFILE_DATA_ROOT` | 独立挂载点,出厂值 `/data/sfile`。上传文件+库+日志都在这,按 500MB/文件上限估:`文件平均大小 × 日均份数 × 保留天数`,一般 ≥200G | +| 光闸摆渡目录 | `GATE_SEND_DIR` / `GATE_RECV_DIR` | 与光闸厂商确认挂载点,**发出/收到是两个不同目录**,两侧机器各自配自己的 | +| MeshSafe 地址与密钥 | `MESHSAFE_BASE_URL` / `MESHSAFE_SECRET` | 同机:`http://host.docker.internal:44380`;独立机:`http://:<端口>`;`MESHSAFE_SECRET` 出厂是占位值,**接了检测就必须按 MeshSafe 交付单改**,没接就 `MESHSAFE_ENABLED=false` | +| 各类口令 | `POSTGRES_PASSWORD` 等 | 出厂已给随机值可直接跑,正式交付按客户安全要求轮换(见 §5.1) | + +机器规格建议:8 核 / 16G 起(应用堆 `-Xmx` 3G,PG + MQ + Redis 还要约 2-3G,留足页缓存),系统盘 100G + 数据盘独立。 + +--- + +## 4. 出盘(我方,需要联网一次) + +```bash +# 前置:backend/target/s-file.jar 与 frontend/ 已构建 +cd deploy/offline +./scripts/export-images.sh 1.0.0 # amd64 +TARGET_PLATFORM=linux/arm64 ./scripts/export-images.sh 1.0.0 # 鲲鹏/飞腾 +``` + +脚本会把 `images/*.tar`、`sql/*.sql`、`.env.iw/.env.ow` 的镜像 tag、`SHA256SUMS.txt` 就地刷新, +再打一份 `dist/sfile-offline-<版本>-<平台>.tar.gz` 用于拷盘/刻录。带盘就带这个 gz。 + +--- + +## 5. 装机(客户侧,两台机器各执行一次) + +```bash +# 1) 解包(两台机器用同一个盘,解出来的目录一模一样) +tar -xzf sfile-offline-1.0.0-linux-amd64.tar.gz -C /opt/ +cd /opt/sfile-offline-1.0.0-linux-amd64 + +# 2) 只改必须改的:MeshSafe 密钥(不接检测就关掉开关) +vi .env.iw # 内网机;外网机改 .env.ow + +# 3) 一键装:按角色建 .env -> 自检 -> 校验 tar -> docker load -> 建目录 -> 起栈 -> 等健康 +./scripts/install.sh iw # 内网机 +./scripts/install.sh ow # 外网机 +``` + +`install.sh` 第一次执行会把 `.env.iw`/`.env.ow` 拷成 `.env`,之后只读 `.env`——现场改动不会被覆盖。 +想回到出厂值:`rm .env && ./scripts/install.sh iw`。 +参数与已有 `.env` 的角色不一致时直接拒绝,防止在内网机上起了外网栈。 + +装完会打印访问地址。首次登录后**立刻改掉 admin 的初始口令**。 + +### 5.1 出厂口令(交付单要抄一份,并建议现场轮换) + +| 变量 | 内网机出厂值 | 外网机出厂值 | 现场是否必须改 | +|---|---|---|---| +| `POSTGRES_PASSWORD` | `Sfile-iw-Pg-N1tOz1MxgdaS` | `Sfile-ow-Pg-pT-673--i1rk` | 建议(只在本机容器网络内生效,不对网暴露) | +| `MQ_PASSWORD` | `Sfile-iw-Mq-UTqVSnCDZ0LB` | `Sfile-ow-Mq-sfW5Cv-Nk-cA` | 建议(同上) | +| `JWT_SECRET` | `86a6b015…7d70`(64 位) | `9eeeea03…6988`(64 位) | 必须保密;泄露等于能伪造登录态,两侧已不同 | +| `USER_DEFAULT_PASSWORD` | `Sfile-iw-Usr-FBLS-0fA` | `Sfile-ow-Usr-WqDJ7PDS` | **必须改**:新建用户的初始密码,业务员要知道 | +| admin 登录口令 | 由 `backend/db/init-data.sql` 灌入 | 同左 | **必须改**:装完第一次登录就改 | +| `MESHSAFE_SECRET` | `CHANGE_ME_meshsafe_secret` | `CHANGE_ME_meshsafe_secret` | **故意留空**:以 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单为准,装机前替换 | + +> `install.sh` 只对**启用了 MeshSafe 且 secret 还是占位值**的情况卡住;`MESHSAFE_ENABLED=false` 时其余占位值不拦,方便先装后接检测。 + +### 5.2 内网机 / 外网机配置差异 + +`.env.iw` 与 `.env.ow` 已按下表分别填好,这里列出来只为让客户网络/安全评审能对上: + +| 变量 | 内网机 | 外网机 | +|---|---|---| +| `SFILE_ROLE` | `iw` | `ow` | +| `SFILE_TRANSFER_LOCATION` | `1` | `2` | +| `SFILE_SITE_ROOT` | `/usr/share/nginx/inside` | `/usr/share/nginx/outside` | +| `GATE_SEND_DIR` | `/data/sfile/gate/out`(光闸的**内→外**发出目录) | `/data/sfile/gate/out`(光闸的**外→内**发出目录) | +| `GATE_RECV_DIR` | `/data/sfile/gate/in`(光闸的**外→内**投递目录) | `/data/sfile/gate/in`(光闸的**内→外**投递目录) | +| `MESHSAFE_CHANNEL` / `MESHSAFE_CLIENT_ID` | `sfile-iw` | `sfile-ow` | +| `JWT_SECRET` / `POSTGRES_PASSWORD` / `MQ_PASSWORD` | 一套 | 另一套(已确认不同) | +| `WEB_PORT` | 80 | 80(两台机器各占自己的 80,不冲突) | +| `APP_SUBNET` / `APP_NODE_IP` / `APP_WORK_ID` | `172.30.88.0/24` / `.10` / `1` | `172.30.98.0/24` / `.10` / `2` | + +> 两侧网段/机器号故意错开:客户如果先把两台合并成一台机器做演示,不改也能同起。 +> `SFILE_TRANSFER_LOCATION` 写反是本方案最容易踩的坑:值对调后,两侧都以为自己是发送端,摆渡目录里的包永远不会被解包。 + +--- + +## 6. 验收清单(现场按这个走一遍) + +| # | 动作 | 期望 | +|---|---|---| +| 1 | 浏览器开 `http://<内网机IP>:<端口>` | 左上角有「内网」徽标,能登录 | +| 2 | 开 `http://<外网机IP>:<端口>` | 无「内网」徽标(设计上只有内网侧显示标识),能登录 | +| 3 | 内网上传一个正常文件 | 「安全检测」列几秒内从**检测中**变**检测通过** | +| 4 | 内网上传 EICAR 测试串 | 变**检测不通过**,理由含 `Eicar-Test-Signature` 或 `内嵌 EICAR` | +| 5 | 对该文件点分享/传输 | 被拒:`安全检测未通过,禁止分享与传输` | +| 6 | 内网把通过的文件「传输」到外网 | 内网 `GATE_SEND_DIR` 出现 `_.zip` | +| 7 | 等 1 分钟内网监听任务 | 外网「内穿外」目录出现 `<原名>_<时间戳>` 文件夹,文件在内 | +| 8 | 外网该文件的安全检测 | 自动送检并变**检测通过** | +| 9 | 内网对不通过文件做人工复核放行 | 状态变**检测通过**、标记**人工复核**,之后可传输 | +| 10 | `docker compose ps` 两台机各 5 个 healthy | 无 `Restarting`/`unhealthy` | + +第 3-9 步可用 `scripts/smoke_test.py` 自动跑(在有 python3 + requests 的机器上,指向客户 IP),现场没条件就手工点。 + +```bash +SFILE_IW_BASE=http://<内网机IP>:<端口>/api \ +SFILE_OW_BASE=http://<外网机IP>:<端口>/api \ +SFILE_ADMIN_USER=admin SFILE_ADMIN_PWD='<现场初始口令>' \ +python3 scripts/smoke_test.py +``` + +--- + +## 7. 运维 + +```bash +docker compose ps # 状态 +docker logs -f sfile-iw-app # 后端日志(含送检/摆渡全过程) +docker compose up -d app # 改完 .env 或 config 后重启后端 +docker compose down # 停止(不动数据) +docker compose down -v --remove-orphans # ⚠ 数据在 bind 目录里,不会丢;但别顺手执行 +``` + +**备份只需三样**:`$SFILE_DATA_ROOT/pgdata`(库)、`$SFILE_DATA_ROOT/upload`(文件本体)、`.env`(口令)。 +恢复:换机器后原路径放回 + `docker load` + `install.sh`。 + +数据库升级:新版本的增量 SQL 放 `backend/db/upgrade-*.sql`,人工执行 + +```bash +docker exec -i sfile-iw-pg psql -U sfile -d s_file < upgrade-20261101-meshsafe-file-security.sql +``` + +(`sql/01/02` 只在**空库首次启动**执行,已有数据卷会被忽略。) + +--- + +## 8. 已知坑与取舍 + +1. **占位值检查是有意的,别图省事先注释掉**:`install.sh` 只在「`MESHSAFE_ENABLED=true` 且 `MESHSAFE_SECRET` 还是占位值」时拒绝启动,其余 `CHANGE_ME` 也会拦。没接检测就老实写 `MESHSAFE_ENABLED=false`。 +2. **交付盘必须整目录拷**:`install.sh` 会核 5 个 tar 的 sha256、个数、以及 `.env` 里 5 个镜像 tag 是否都能 load 到。只拷脚本不拷 `images/`(tar 不进气仓,见 `.gitignore`)是最常见的现场翻车。 +3. **架构不符装不上**:`sfile-app`/`sfile-web` 是在构建机上 `docker build` 出来的,信创 ARM 机必须用 `TARGET_PLATFORM=linux/arm64` 重新出盘(`install.sh` 会拦你)。 +4. **PostgreSQL 18 的数据目录挂载点是 `/var/lib/postgresql`**(15 及以前是 `/var/lib/postgresql/data`)。自己换库版本时改 `.env` 的 `PG_DATA_DIR`,否则容器会报「检测到未挂载的数据」并反复重启。 +5. **nginx 反代必须透传带端口的 Host**(模板里已用 `$http_host`)。改成 `$host` 会让后端 `RequestUtils.getRootDomain` 对无端口地址抛 `IllegalStateException`,症状就是**登录 500**。 +6. **发号器 workerId 必须能取到,否则应用起不来。** 后端按 `code.snowflake.workid.` 取机器号,取不到时回退默认值 `99`,而 99 超出雪花 5 位上限,启动即 `IllegalArgumentException: worker Id can't be greater than 31`。本方案的做法是把 app 的容器 IP 锁死(`APP_NODE_IP`)并由 compose 自动写进 JVM 参数,`.env` 里 `APP_SUBNET / APP_GATEWAY / APP_NODE_IP` 三个值必须同网段。**只想改代码解决的话**,把 `CodeConfiguration#snowFlakeCodeGenerator` 的兜底 `99L` 改成一个合法值即可解除这层 IP 耦合——但那样同侧多实例会撞 ID,需自行权衡。 +7. **MeshSafe 凭证**:`MESHSAFE_CLIENT_ID`/`SECRET` 必须在 MeshSafe 侧 `MESHSAFE_API_CLIENTS` 白名单里存在,否则送检全部 401。`MESHSAFE_FAIL_OPEN=true` 时检测服务挂了不阻断业务(只记失败),等保从严请置 `false`。 +8. **`host.docker.internal` 需要 Docker ≥ 20.10**;MeshSafe 在独立服务器时不需要它,可把 compose 里那两行 `extra_hosts` 删掉。 +9. **PDF 盖章依赖 spire.pdf 是桩实现**:本机因拿不到商业包做了替代 jar,运行时调用会抛 `UnsupportedOperationException`。交付前若客户要电子签章功能,必须换回 `e-iceblue:spire.pdf:3.7.4` 正式包重新打 jar。 +10. **摆渡延迟固定为监听周期**(默认 1 分钟一次)。客户嫌慢就调 `FILE_TRANSFER_MONITOR_CRON`,不要改成秒级——它会整目录扫描。 +11. **两侧数据库完全独立**,同一份 `init-data.sql` 灌出来 admin 初始口令一致,交付时两侧都要改。 +12. **装完才发现角色装反了**(内网机上跑了 `install.sh ow`):`.env` 与参数角色不一致时 `install.sh` 直接拒绝,所以真发生一定是 `rm .env` 之后重装。compose 项目名跟着角色走,旧项目还在占用 `APP_SUBNET`,会报 `invalid pool request: Pool overlaps with other one on this address space`。先把旧项目拆掉再起: + `docker compose -p sfile-<旧角色> down` → `rm .env` → `./scripts/install.sh <正确角色>`。 +13. **检测结论按文件内容复用**:MeshSafe 的 `fileCode` 是内容寻址,同一份字节重复上传会直接沿用上次结论,不会重新扫。这是性能上的设计,但等保评审可能会问「改判是否会被复用绕过」,现场演示验收时请用**每次内容都不同**的测试文件。