init: 信创文件安全传输系统内外网本地 Docker 部署

包含 s-file 后端、React 前端、MeshSafe 安全检测对接,以及内外网两套
compose/nginx/配置(仅前端 28180/28280 对外发布,其余端口不暴露)。

说明:deploy/config/application-iw.yml 与 application-ow.yml 按维护者
明确要求原样入库,其中含本机 Docker 用的库口令、RabbitMQ 口令与 JWT
密钥;上生产前需替换。
This commit is contained in:
KodouWZF
2026-11-02 10:59:03 +08:00
commit 47273ae3fd
2190 changed files with 305040 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
# s-file 内网侧(file.transfer.location=1)本地联调配置
# 仅本机 docker 部署使用,密码与 JWT secret 不要带到生产
server:
port: 8080
spring:
application:
name: s-file
datasource:
url: jdbc:postgresql://pg-iw:5432/s_file
driver-class-name: org.postgresql.Driver
username: sfile
password: sfile_iw_2026
druid:
initial-size: 2
min-idle: 2
max-active: 10
validation-query: SELECT 1
redis:
host: redis-iw
port: 6379
timeout: 5000
lettuce:
pool:
max-active: 8
max-idle: 4
rabbitmq:
host: mq-iw
port: 5672
username: sfile
password: sfile_mq_iw_2026
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
# 平台编码(init-data 中 uc_platform.platform_code=1)
system:
code: 1
# 发号器雪花 workerId:键为 compose 中固定的容器 IP
"[code.snowflake.workid.172.25.18.10]": 1
security:
jwt:
enable: true
secret: sfile-local-iw-secret-key-please-change-0123456789abcdef
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: Qaz123567!@#
file:
upload-dir: /data/upload
storage:
type: local
transfer:
# 内网侧
location: "1"
# send-dir 与外网侧 receive-dir 指向同一摆渡目录,反之亦然
send-dir: /data/transfer/send
receive-dir: /data/transfer/receive
unpack-risk-check: false
dify:
risk:
rule:
api-key: test-rule-key
scan:
api-key: test-scan-key
# MeshSafe 文件安全检测:复用本机已部署的 meshsafe-nginx(44380)
# 凭证需存在于 MeshSafe .env 的 MESHSAFE_API_CLIENTS 白名单中
meshsafe:
enabled: true
base-url: http://host.docker.internal:44380
client-id: test-client
secret: test-secret
channel: sfile-iw
timeout-seconds: 120
fail-open: true
poll-timeout-minutes: 60
review-sync-to-meshsafe: true
poll:
cron: "0/10 * * * * ?"
rabbitmq:
custom:
topic-exchange-name: s-file-iw-topic
delayed-exchange-name: s-file-iw-delayed
broadcast-id: s-file-iw-broadcast
swagger:
enable: false
logging:
level:
com.senmesh: info

View File

@@ -0,0 +1,115 @@
# s-file 外网侧(file.transfer.location=2)本地联调配置
# 仅本机 docker 部署使用,密码与 JWT secret 不要带到生产
server:
port: 8080
spring:
application:
name: s-file
datasource:
url: jdbc:postgresql://pg-ow:5432/s_file
driver-class-name: org.postgresql.Driver
username: sfile
password: sfile_ow_2026
druid:
initial-size: 2
min-idle: 2
max-active: 10
validation-query: SELECT 1
redis:
host: redis-ow
port: 6379
timeout: 5000
lettuce:
pool:
max-active: 8
max-idle: 4
rabbitmq:
host: mq-ow
port: 5672
username: sfile
password: sfile_mq_ow_2026
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
# 平台编码(init-data 中 uc_platform.platform_code=1)
system:
code: 1
# 发号器雪花 workerId:键为 compose 中固定的容器 IP;两侧 workerId 必须不同
"[code.snowflake.workid.172.25.28.10]": 2
security:
jwt:
enable: true
secret: sfile-local-ow-secret-key-please-change-0123456789abcdef
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: Qaz123567!@#
file:
upload-dir: /data/upload
storage:
type: local
transfer:
# 外网侧:与内网侧相反
location: "2"
# compose 里 send-dir 挂到 gate/ow2iw、receive-dir 挂到 gate/iw2ow
send-dir: /data/transfer/send
receive-dir: /data/transfer/receive
unpack-risk-check: false
dify:
risk:
rule:
api-key: test-rule-key
scan:
api-key: test-scan-key
# MeshSafe 文件安全检测:复用本机已部署的 meshsafe-nginx(44380)
# 凭证需存在于 MeshSafe .env 的 MESHSAFE_API_CLIENTS 白名单中
meshsafe:
enabled: true
base-url: http://host.docker.internal:44380
client-id: test-client
secret: test-secret
channel: sfile-ow
timeout-seconds: 120
fail-open: true
poll-timeout-minutes: 60
review-sync-to-meshsafe: true
poll:
cron: "0/10 * * * * ?"
rabbitmq:
custom:
topic-exchange-name: s-file-ow-topic
delayed-exchange-name: s-file-ow-delayed
broadcast-id: s-file-ow-broadcast
swagger:
enable: false
logging:
level:
com.senmesh: info

252
deploy/docker-compose.yml Normal file
View File

@@ -0,0 +1,252 @@
# =============================================================================
# s-file 信创文件安全传输系统 —— 本机 docker 部署(内网侧 + 外网侧两套)
#
# 启动:
# docker build -f deploy/docker/Dockerfile.app -t sfile-app:local backend/target
# docker build -f deploy/docker/Dockerfile.web -t sfile-web:local frontend
# cd deploy && docker compose up -d
#
# 端口:
# 内网前端 http://<本机IP>:28180 外网前端 http://<本机IP>:28280
# 其余(后端/postgres/redis/rabbitmq)只在容器网络内通信,不映射宿主机端口
# MeshSafe 复用本机已运行的 meshsafe-nginx:44380(两套后端都经 host.docker.internal 调用)
#
# 摆渡目录(模拟光闸):
# gate/iw2ow = 内网 send + 外网 receive gate/ow2iw = 外网 send + 内网 receive
# =============================================================================
name: sfile
services:
# ===================== 内网侧 =====================
web-iw:
image: sfile-web:local
container_name: sfile-iw-web
restart: unless-stopped
networks: [iw_net]
ports:
- "0.0.0.0:28180:80"
volumes:
- ./nginx/web-iw.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app-iw:
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
app-iw:
image: sfile-app:local
container_name: sfile-iw-app
restart: unless-stopped
networks:
iw_net:
ipv4_address: 172.25.18.10
environment:
TZ: Asia/Shanghai
SPRING_CONFIG_ADDITIONAL_LOCATION: file:/config/application.yml
CODE_SNOWFLAKE_WORKID_172_25_18_10: "1"
volumes:
- ./config/application-iw.yml:/config/application.yml:ro
- ${SFILE_DATA:-D:/zcodedata/sfile}/iw/upload:/data/upload
- ${SFILE_DATA:-D:/zcodedata/sfile}/iw/logs:/app/logs
- ${SFILE_DATA:-D:/zcodedata/sfile}/gate/iw2ow:/data/transfer/send
- ${SFILE_DATA:-D:/zcodedata/sfile}/gate/ow2iw:/data/transfer/receive
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
pg-iw:
condition: service_healthy
redis-iw:
condition: service_healthy
mq-iw:
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
start_period: 150s
pg-iw:
image: postgres:latest
container_name: sfile-iw-pg
restart: unless-stopped
networks: [iw_net]
environment:
TZ: Asia/Shanghai
POSTGRES_DB: s_file
POSTGRES_USER: sfile
POSTGRES_PASSWORD: sfile_iw_2026
volumes:
# postgres:latest 为 18.x,数据目录挂载点已改为 /var/lib/postgresql
- sfile-pgdata-iw:/var/lib/postgresql
- ../backend/db/init-table-postgres-full.sql:/docker-entrypoint-initdb.d/01-init-table.sql:ro
- ../backend/db/init-data.sql:/docker-entrypoint-initdb.d/02-init-data.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sfile -d s_file"]
interval: 10s
timeout: 5s
retries: 10
start_period: 40s
redis-iw:
image: redis:latest
container_name: sfile-iw-redis
restart: unless-stopped
networks: [iw_net]
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
volumes:
- sfile-redis-iw:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
mq-iw:
image: rabbitmq:3-management-alpine
container_name: sfile-iw-mq
restart: unless-stopped
networks: [iw_net]
environment:
TZ: Asia/Shanghai
RABBITMQ_DEFAULT_USER: sfile
RABBITMQ_DEFAULT_PASS: sfile_mq_iw_2026
volumes:
- sfile-mq-iw:/var/lib/rabbitmq
healthcheck:
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
interval: 20s
timeout: 10s
retries: 10
start_period: 40s
# ===================== 外网侧 =====================
web-ow:
image: sfile-web:local
container_name: sfile-ow-web
restart: unless-stopped
networks: [ow_net]
ports:
- "0.0.0.0:28280:80"
volumes:
- ./nginx/web-ow.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
app-ow:
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
app-ow:
image: sfile-app:local
container_name: sfile-ow-app
restart: unless-stopped
networks:
ow_net:
ipv4_address: 172.25.28.10
environment:
TZ: Asia/Shanghai
SPRING_CONFIG_ADDITIONAL_LOCATION: file:/config/application.yml
CODE_SNOWFLAKE_WORKID_172_25_28_10: "2"
volumes:
- ./config/application-ow.yml:/config/application.yml:ro
- ${SFILE_DATA:-D:/zcodedata/sfile}/ow/upload:/data/upload
- ${SFILE_DATA:-D:/zcodedata/sfile}/ow/logs:/app/logs
- ${SFILE_DATA:-D:/zcodedata/sfile}/gate/ow2iw:/data/transfer/send
- ${SFILE_DATA:-D:/zcodedata/sfile}/gate/iw2ow:/data/transfer/receive
extra_hosts:
- "host.docker.internal:host-gateway"
depends_on:
pg-ow:
condition: service_healthy
redis-ow:
condition: service_healthy
mq-ow:
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
start_period: 150s
pg-ow:
image: postgres:latest
container_name: sfile-ow-pg
restart: unless-stopped
networks: [ow_net]
environment:
TZ: Asia/Shanghai
POSTGRES_DB: s_file
POSTGRES_USER: sfile
POSTGRES_PASSWORD: sfile_ow_2026
volumes:
- sfile-pgdata-ow:/var/lib/postgresql
- ../backend/db/init-table-postgres-full.sql:/docker-entrypoint-initdb.d/01-init-table.sql:ro
- ../backend/db/init-data.sql:/docker-entrypoint-initdb.d/02-init-data.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sfile -d s_file"]
interval: 10s
timeout: 5s
retries: 10
start_period: 40s
redis-ow:
image: redis:latest
container_name: sfile-ow-redis
restart: unless-stopped
networks: [ow_net]
command: ["redis-server", "--appendonly", "yes", "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru"]
volumes:
- sfile-redis-ow:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
mq-ow:
image: rabbitmq:3-management-alpine
container_name: sfile-ow-mq
restart: unless-stopped
networks: [ow_net]
environment:
TZ: Asia/Shanghai
RABBITMQ_DEFAULT_USER: sfile
RABBITMQ_DEFAULT_PASS: sfile_mq_ow_2026
volumes:
- sfile-mq-ow:/var/lib/rabbitmq
healthcheck:
test: ["CMD-SHELL", "rabbitmq-diagnostics -q ping"]
interval: 20s
timeout: 10s
retries: 10
start_period: 40s
networks:
iw_net:
driver: bridge
ipam:
config:
- subnet: 172.25.18.0/24
gateway: 172.25.18.1
ow_net:
driver: bridge
ipam:
config:
- subnet: 172.25.28.0/24
gateway: 172.25.28.1
volumes:
sfile-pgdata-iw:
sfile-redis-iw:
sfile-mq-iw:
sfile-pgdata-ow:
sfile-redis-ow:
sfile-mq-ow:

View File

@@ -0,0 +1,15 @@
# s-file 后端运行镜像(构建上下文 = backend/target,内含 s-file.jar)
# 用 JVM 参数 -Xmx 控制内存,配置文件由容器挂载 /config/application.yml
FROM eclipse-temurin:8-jre-jammy
ENV TZ=Asia/Shanghai
ENV JAVA_OPTS="-Xms256m -Xmx1g -Dfile.encoding=UTF-8"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /app
COPY s-file.jar /app/s-file.jar
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app/s-file.jar"]

View File

@@ -0,0 +1,28 @@
# 前端镜像:一次构建同时产出内网包与外网包
# 构建上下文 = frontend/ (见 frontend/.dockerignore)
# 站点根:/usr/share/nginx/inside(内网)、/usr/share/nginx/outside(外网)
# 由两套 nginx 容器各挂一份 conf 选择站点根,镜像只读共用
FROM node:20 AS build
WORKDIR /src
COPY package.json ./
RUN npm config set registry https://registry.npmmirror.com \
&& npm install --no-audit --no-fund
COPY . .
# .env.production.local 优先级高于 .env.production,用它固化两套包的差异:
# VITE_API_URL=/api —— 走同域 /api,由 nginx 反代时去掉前缀
# VITE_IS_INSIDE —— true 内网包 / false 外网包
RUN set -eux; \
printf 'VITE_API_URL=/api\nVITE_IS_INSIDE=true\n' > .env.production.local; \
npx vite build --outDir dist/inside; \
printf 'VITE_API_URL=/api\nVITE_IS_INSIDE=false\n' > .env.production.local; \
npx vite build --outDir dist/outside
FROM nginx:alpine
COPY --from=build /src/dist/inside /usr/share/nginx/inside
COPY --from=build /src/dist/outside /usr/share/nginx/outside
EXPOSE 80

37
deploy/nginx/web-iw.conf Normal file
View File

@@ -0,0 +1,37 @@
server {
listen 80;
server_name _;
charset utf-8;
root /usr/share/nginx/inside;
index index.html;
# 与后端 multipart 上限(500MB)保持一致
client_max_body_size 500m;
# /api/xxx -> 后端 /xxx(去掉 /api 前缀,与 vite dev 代理行为一致)
location /api/ {
proxy_pass http://app-iw:8080/;
proxy_http_version 1.1;
# 必须保留端口($http_host):后端 getRootDomain 对无端口的 localhost/IP 会抛
# IllegalStateException 导致 /login 成功回调 500(带端口时按非法域名返回 null,Cookie 不带 domain)
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;
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;
}
}

37
deploy/nginx/web-ow.conf Normal file
View File

@@ -0,0 +1,37 @@
server {
listen 80;
server_name _;
charset utf-8;
root /usr/share/nginx/outside;
index index.html;
# 与后端 multipart 上限(500MB)保持一致
client_max_body_size 500m;
# /api/xxx -> 后端 /xxx(去掉 /api 前缀,与 vite dev 代理行为一致)
location /api/ {
proxy_pass http://app-ow:8080/;
proxy_http_version 1.1;
# 必须保留端口($http_host):后端 getRootDomain 对无端口的 localhost/IP 会抛
# IllegalStateException 导致 /login 成功回调 500(带端口时按非法域名返回 null,Cookie 不带 domain)
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;
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;
}
}

93
deploy/部署说明.md Normal file
View File

@@ -0,0 +1,93 @@
# s-file 本机 docker 部署说明(内网侧 + 外网侧两套)
> 环境:Windows + Docker Desktop 4.54,MeshSafe 复用本机已运行的 `meshsafe-nginx:44380`
> 编排文件:`deploy/docker-compose.yml`
## 1. 访问入口
| 侧 | 地址 | 前端包 | 后端 `file.transfer.location` |
|---|---|---|---|
| 内网 | http://本机IP:28180 | `VITE_IS_INSIDE=true` | `1` |
| 外网 | http://本机IP:28280 | `VITE_IS_INSIDE=false` | `2` |
只有这两个 80 端口映射到宿主机(`0.0.0.0`,便于局域网真机联调)。后端 8080、PostgreSQL、Redis、RabbitMQ 全部只在容器网络内通信,**不映射宿主机端口**;nginx 通过 `location /api/ → http://app-xx:8080/` 反代并去掉 `/api` 前缀。
## 2. 容器与网段
| 内网侧 | 外网侧 | 说明 |
|---|---|---|
| `sfile-iw-web` | `sfile-ow-web` | nginx:alpine(本机已有) |
| `sfile-iw-app` | `sfile-ow-app` | sfile-app:local(eclipse-temurin:8-jre-jammy + s-file.jar) |
| `sfile-iw-pg` | `sfile-ow-pg` | postgres:latest(本机已有,18.3) |
| `sfile-iw-redis` | `sfile-ow-redis` | redis:latest(本机已有) |
| `sfile-iw-mq` | `sfile-ow-mq` | rabbitmq:3-management-alpine(本机已有) |
| 网络 `sfile_iw_net` 172.25.18.0/24 | 网络 `sfile_ow_net` 172.25.28.0/24 | 两侧物理隔离,仅通过摆渡目录互通 |
app 容器固定 IP(`172.25.18.10` / `172.25.28.10`),对应雪花 workerId `1` / `2`,两侧必须不同;改网段时同步改 `deploy/config/application-*.yml` 里的 `[code.snowflake.workid.<ip>]` 与 compose 的 `CODE_SNOWFLAKE_WORKID_*` 环境变量。
## 3. 摆渡目录(模拟光闸)
宿主机根目录:`D:/zcodedata/sfile/`(可用 `.env` 的 `SFILE_DATA` 覆盖)
| 目录 | 内网侧挂载 | 外网侧挂载 |
|---|---|---|
| `gate/iw2ow` | `/data/transfer/send` | `/data/transfer/receive` |
| `gate/ow2iw` | `/data/transfer/receive` | `/data/transfer/send` |
| `iw/upload`、`ow/upload` | 各自 `/data/upload` | 网盘物理文件,两侧独立 |
内网「我的发送」写入 `gate/iw2ow`,外网侧 `TransferFileMonitorJob` 扫描自己的 receive 目录解包入库并触发 MeshSafe 送检,反方向同理。
## 4. MeshSafe 对接
`deploy/config/application-iw.yml` / `application-ow.yml`:
```yaml
meshsafe:
enabled: true
base-url: http://host.docker.internal:44380 # 本机 meshsafe-nginx
client-id: test-client # 需在 MeshSafe .env 的 MESHSAFE_API_CLIENTS 白名单内
secret: test-secret
fail-open: true # 本地联调优先可用性;安全优先部署改 false
```
compose 给 app 容器加了 `extra_hosts: host.docker.internal:host-gateway`。上线前把两侧 channel 区分(`sfile-iw` / `sfile-ow`)并保持 MeshSafe 策略 `engine_failure_mode: fail_closed`。
## 5. 构建与启动
```bash
# 1) 后端 jar(需 JDK8 + Maven,本机已装在 D:/dev/jdk8、D:/dev/maven-tmp)
cd backend
JAVA_HOME=D:/dev/jdk8/jdk8u504-b01 mvn -s D:/dev/maven-conf/settings.xml clean package -DskipTests
# 2) 镜像
docker build -f deploy/docker/Dockerfile.app -t sfile-app:local backend/target
docker build -f deploy/docker/Dockerfile.web -t sfile-web:local frontend # 一次产出内网+外网两个站点
# 3) 启动(首次会执行 backend/db 的 PG 初始化脚本建 62 张表)
cd deploy && docker compose up -d
```
常用运维:
```bash
docker compose ps # 全部 10 个容器应为 healthy
docker compose restart app-iw app-ow # 改配置后重启后端
docker compose logs -f app-iw
docker compose down # 停止(保留数据卷与摆渡目录)
```
## 6. 已知取舍
- **spire.pdf 3.7.4**:公共仓库无此版本,本机用按 `PdfUtils.java` 引用签名手写的桩 jar(`~/.m2/repository/e-iceblue/spire.pdf/3.7.4/`)保证编译打包通过;**PDF 盖章功能运行时不可用**,正式环境需替换公司私服的真实 jar。
- **前端 `VITE_API_URL=/api`**:`.env.production` 原值为空,容器构建时用 `.env.production.local` 固化为 `/api`,与 nginx 反代规则和 vite dev 行为保持一致;改构建口径需同步改 `Dockerfile.web`。
- **postgres:latest 为 18.x**:数据目录挂载点已从 `/var/lib/postgresql/data` 改为 `/var/lib/postgresql`,挂错路径容器会反复重启。
- **配置与口令仅用于本地联调**:JWT secret、数据库/RabbitMQ 密码为占位值,勿带到生产。
- 数据库卷为命名卷(`sfile_sfile-pgdata-iw` / `-ow`);重新初始化需删卷后 `docker compose up -d`。
## 7. 验收清单
1. 两套登录页可访问(28180 / 28280),登录后内网侧菜单为「我的接收 / 我的发送」、外网侧相反;
2. 上传文件后「安全检测」列出现「检测中」→ 数秒内变「检测通过/不通过」(`docker exec sfile-iw-pg psql -U sfile -d s_file -c 'select * from x_file_security order by id desc limit 5;'`);
3. 检测不通过的文件分享/跨网传输被拒;文件夹部分不通过时确认后可只传通过项;
4. 内网发送到外网接收全链路走通,接收端解包后自动送检;
5. 管理端人工复核改判后文件恢复可分享/可传输。