配置与部署
服务端的全部配置集中在 server/src/main/resources/application.yml,运行时环境变量可覆盖;部署形态有三种:Docker Compose 编排、裸机 systemd、以及开发调试编排。
配置项
环境变量(部署必读)
| 变量 | 说明 | 示例 |
|---|---|---|
DB_HOST / DB_PORT | MySQL 地址与端口 | db / 3306 |
DB_NAME | 库名 | sinomed |
DB_USERNAME / DB_PASSWORD | 数据库账号 | sinomed |
DB_URL / DB_DRIVER / DB_DIALECT | 整串覆盖数据源(演示环境换 SQLite 用) | jdbc:sqlite:/app/data/sinomed.db?journal_mode=WAL&busy_timeout=5000 / org.sqlite.JDBC / org.hibernate.community.dialect.SQLiteDialect |
DEMO_SEED | 演示种子数据开关(幂等,重复启动跳过) | true |
SERVER_PORT | 服务端口 | 2347 |
JAVA_OPTS | JVM 参数(Dockerfile 注入) | -Xms256m -Xmx2048m |
application.yml 关键配置
| 配置键 | 值 | 说明 |
|---|---|---|
spring.jpa.hibernate.ddl-auto | update | 自动建表 / 加列,无需手工 DDL |
spring.datasource.dbcp2.* | 心跳检测 | 连接校验(SELECT 1) |
application.security.jwt.secret-key | 内置默认值 | 生产必须替换,Base64 HMAC 密钥 |
application.security.jwt.expiration | 86400000 | access token 1 天 |
application.security.jwt.refresh-token.expiration | 604800000 | refresh token 7 天 |
server.servlet.session.timeout | 24h | 会话超时 |
springdoc / knife4j | 开启 | 接口文档 /doc.html,中文界面 |
logging.level.* | debug/trace | Hibernate SQL 与 Security 日志,生产建议调低 |
spring.servlet.multipart.max-file-size / max-request-size | 100MB | 素材上传体积上限 |
sinomed.ads.upload-dir | data/ads(ADS_UPLOAD_DIR 可覆盖) | 广告素材目录,/media/** 静态映射 |
sinomed.ads.url-prefix | 仅 @Value 默认值 /media(yml 未落键) | 广告媒体静态映射前缀,WebMvcConfig 与 playlist 下发 URL 共用 |
sinomed.print.templates-dir | data/printtemplates(PRINT_TEMPLATES_DIR 可覆盖) | 打印模板覆盖目录 |
sinomed.receipt.clinic-name | 仅 @Value 默认值 示例医馆(yml 未落键) | 结算小票机构名( 占位替换) |
sinomed.pay.qrcodes-dir | 仅 PayQrCodeController 的 @Value 默认值(yml 未落键) | 收款码图片目录 |
demo.seed | false(DEMO_SEED 可覆盖) | 演示数据幂等播种开关 |
sinomed.famous-doctor-surge-cap-percent | 20(FAMOUS_DOCTOR_SURGE_CAP_PERCENT 可覆盖) | 名医挂号费溢价上限百分比;溢价值单列展示,不做阶梯/时段差异,不做定价引擎 |
sinomed.phone-lookup.mode | off(PHONE_LOOKUP_MODE 可覆盖) | 手机查卡模式:off(默认,关闭)/ openid_only(openid 绑定查卡)/ sms_verified(手机号 + 验证码双因子);口径见 kiosk.md |
sinomed.wechat.app-id | 空(WECHAT_APP_ID 可覆盖) | 微信小程序 appid:openid_only 模式经 jscode2session 换 openid 用;未配置时 openid 换取 400 明确拒绝,不静默 |
sinomed.wechat.app-secret | 空(WECHAT_APP_SECRET 可覆盖) | 微信小程序 AppSecret:只作服务端 jscode2session 出网,不下发前端 |
sinomed.official-account.enabled | false(WX_OA_ENABLED 可覆盖) | 公众号模块总开关:缺省 false=回调 200 拒处理落审计不静默;激活需 WX_OA_TOKEN 与 WX_OA_CREDENTIAL_KEY |
sinomed.official-account.token | 空(WX_OA_TOKEN 可覆盖) | 公众号消息推送 Token(SHA1 验签用) |
sinomed.official-account.material-dir | data/wxmaterials(WX_OA_MATERIAL_DIR 可覆盖) | 图片消息素材落盘根目录(入库存相对路径);下载失败/超限只记 warn 不放大回调 |
sinomed.official-account.material-max-bytes | 10485760(WX_OA_MATERIAL_MAX_BYTES 可覆盖) | 单素材下载上限(字节),超限跳过不落台账 |
sinomed.official-account.credential-key | 空(WX_OA_CREDENTIAL_KEY 可覆盖) | wx_accounts.app_secret/aes_key 落库加密密钥(AES-GCM,凭据不入库明文);缺省空=凭据写入拒绝,激活时必配 |
sinomed.official-account.oauth-redirect-base | 空(WX_OA_OAUTH_REDIRECT_BASE 可覆盖) | 网页授权回跳 H5 基址(如 https://clinic.example.com,设计稿 §5.6);缺省空=网页授权拒启动,激活时必配 |
sinomed.official-account.wechat-api-base-url | https://api.weixin.qq.com(WX_OA_WECHAT_API_BASE_URL 可覆盖) | 微信 API 出网基址(sns oauth2 换 openid);测试/联调可指本地桩 |
server.port | ${SERVER_PORT:2347} | 部署可用 SERVER_PORT 覆盖;本地裸跑默认 2347 |
端口约定
| 端口 | 用途 |
|---|---|
| 2347 | 服务端(对外由 Nginx 反代 /api) |
| 3306 | MySQL |
| 80 / 443 | Nginx(管理端静态站) |
| 5008 | 远程调试(仅 dev 编排) |
构建产物
cd server
./mvnw -B package -DskipTests # 产物 target/sinomed-server.jarDockerfile 基于 eclipse-temurin:21-jre-alpine,把 jar 复制为 /app/server.jar,JAVA_OPTS 可注入 JVM 参数,日志目录 /app/logs。
演示编排(根目录 compose.yml,SQLite 一键起)
在线演示与本地体验用根目录 compose.yml:两个服务(server + web,web 的 nginx 把 /api 反代到 server)、SQLite 数据库文件落 sinomed-data 持久卷、DEMO_SEED=true 幂等播种虚构演示数据:
docker compose up -d
# 管理端 http://localhost:8080 (admin / 123)
# 接口文档 http://localhost:2347/doc.html空卷首启自动建表;admin/123 在首次以 admin 登录时自动创建(内置 bcrypt 密码);重复启动按账号/手机号/卡项名等自然键判重,不会产生重复数据。接回 MySQL 时替换 compose 里 DB_* 五个环境变量即可(构建层另有 MVN_DIST_MIRROR / MAVEN_MIRROR_URL 可选国内源参数,出境受限也能构建)。
方式一:Docker Compose(MySQL 整套)
server/docker-compose.yml 编排三个服务:MySQL(自动执行 init/init.sql 建库建账号)+ 服务端镜像 + Nginx(挂载 web/dist 静态站与 init/nginx_http.conf):
cd server
docker compose up -d注意:
server/docker-compose.yml的 server 服务已注入DB_HOST(与 db 同处默认网络,按服务名 db 解析)/DB_PORT/DB_NAME/DB_USERNAME/DB_PASSWORD/SERVER_PORT全套,直接up即可起。要改端口或数据库名照server/dev.yml的 environment 段覆盖;仓库根compose.yml是演示编排(注入全套 + SQLite)。
方式二:开发调试编排
server/dev.yml 与上者的区别:服务端从本地 Dockerfile 构建、注入 DB_* 环境变量、开放 5008 远程调试端口(另映射 3000:3000,供管理端 dev server 联调)、MySQL 数据目录与日志落在仓库相对路径:
cd server
docker compose -f dev.yml up -d方式三:裸机 + systemd
- 安装 MySQL 8,执行
server/init/init.sql建库; - 安装 JDK 21,放置
sinomed-server.jar(如/data/sinomed/); - Nginx 配置静态站并把
/api反代到127.0.0.1:2347(server/init/内含 http / https 示例配置,SPA 路由 fallback 到index.html); - 参照
server/init/sinomed.service安装 systemd 服务并自启:
sudo cp server/init/sinomed.service /lib/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now sinomed在线演示预案
- CI 出镜像:
Docker Images工作流(.github/workflows/docker.yml)在 push main 且server/、web/变更时构建并推送 ghcr.io,tag 为latest(main 滚动)与sha-<短提交>(可回溯、可回滚);首次发布后在 GitHub Package 设置把sinomed-server/sinomed-web改为 Public,演示机才能匿名拉取。 - 演示机:runner-Docker(内网
192.168.5.5),部署目录/data/deploy/sinomed;放一份仓库的compose.yml,IMAGE_TAG=sha-<短提交>固定版本拉起,回滚即把 tag 改回上一短提交号后up -d。 - 域名与隧道:
*.cuihairu.site走 Cloudflare Tunnel(域名待定,先占位sinomed.cuihairu.site),隧道指向演示机 web 服务的8080端口,无需公网 IP 与备案端口暴露。 - 数据重置:演示数据全部为种子脚本虚构内容,
docker compose down -v && docker compose up -d即回到全新演示态。
Nginx 转发关系
浏览器 → Nginx(80/443)
├── / → web/dist 静态资源(SPA fallback index.html)
└── /api/** → http://<server>:2347(应用接口 /api/v1/**,含 ads 下发与叫号拉取)仓库内 web/nginx.conf 与 server/init/nginx_http.conf 只有上述两个 location。平板/Kiosk 部署要点:tablet/dist、kiosk/dist 构建产物放 Nginx 可达路径后,需自行添加 location /tablet/、location /kiosk/(kiosk 构建内 base 为 /kiosk/);素材上传目录默认在服务端工作目录 data/ads,/media/** 当前由应用层静态映射兜底(WebMvcConfig),如需 Nginx 直托管再加 location /media/ 指到该目录(或用 ADS_UPLOAD_DIR 统一挪到独立盘后指向新路径)。上传体积已在 application.yml 放开到 100MB,Nginx 侧如有 client_max_body_size 需同步放开。
日志
应用日志输出到 logs/(容器内 /app/logs):info.log 按天与 100MB 滚动、error.log 按天与 10MB 滚动;另有登录(login.log)、签到(sign.log)等业务日志文件,级别由 log4j2-spring.xml 定义。
安全待加固清单(如实记录源码现状)
- JWT 签名密钥为源码内置默认值,生产必须更换;
admin / 123在首次以 admin 登录时自动创建,上线前先改密;- Compose 文件内含示例数据库口令,需替换;
- 方法级权限仅覆盖员工管理写接口(
@PreAuthorize("hasRole('ADMIN')")),其余接口登录即可用; - Caffeine 缓存约 50 分钟过期,卡项/诊疗单更新已加
@CacheEvict失效,仍存短窗口脏读。