基于 Spring Cloud Alibaba 的低代码微服务平台(v6.1.0-RELEASE),业务侧包含 LIMS(实验室信息管理)、DMS(文档管理)等模块。
配套仓库:
bostal/
├── jnpf-java-cloud-v6x # 后端(本仓库)
└── jnpf-web-monorepo-framework-v6.1 # 前端(pnpm monorepo)
start-all.sh 是宿主机 java -jar 形态的批量启动脚本:Java 服务直接跑在 macOS 上,基础设施(PostgreSQL / Redis / 流程引擎 / OnlyOffice)跑在 Docker 里。相比全栈容器,改一行代码重启单个服务只要几十秒。
| 依赖 | 要求 | 说明 |
|---|---|---|
| JDK | 21(推荐)或 17 | 镜像基底是 bellsoft/liberica-openjre-rocky:21;流程引擎工程要求 21 |
| Maven | 3.6.3+ | 无 mvnw wrapper,需系统安装 |
| Docker Desktop | 任意近期版本 | 起基础设施容器 |
| PostgreSQL | 已建好的实例 | 库结构与初始数据不在本仓库内,由甲方提供。需要两个库:业务库 jnpf_init(角色 jnpf_app)、流程引擎库 jnpf_flow(角色 jnpf_flow_app) |
macOS 自带 bash 是 3.2,脚本已按 3.2 语法编写(无关联数组、空数组用 ${arr[@]+...} 展开),直接 ./start-all.sh 即可,不需要装 bash 5。
宿主进程要用容器名解析到本机,一次性写入 /etc/hosts:
127.0.0.1 cx-infra cx-redis cx-flow-engine
cp .env.example .env
至少确认这几项(.env.example 内每一节都有详细注释):
| 键 | 说明 |
|---|---|
CUSTOMER_DB_HOST / CUSTOMER_DB_PORT |
数据库坐标。业务库和流程引擎库都由这一组推导,**必然同源** |
JNPF_APP_DB_USER / JNPF_APP_DB_PASSWORD |
业务库凭据 |
JNPF_FLOW_DB_USER / JNPF_FLOW_DB_PASSWORD |
流程引擎库凭据 |
JNPF_REDIS_PASSWORD |
Redis requirepass,业务侧同值透传 |
ONLYOFFICE_JWT_SECRET |
必填,缺了 compose 起不来;只在需要文档在线编辑时才关心 |
COMPOSE_PROFILES |
用外部数据库时**注释掉这一行**(它控制是否启动栈内 PG 容器) |
两个坑:
JNPF_FLOW_DB_URL。它是整串覆盖的逃生口,会把流程引擎库和业务库拆成两个来源,症状是流程模板大面积「找不到流程模板」。cx-postgres 时,start-all.sh 会**自动翻译成 127.0.0.1:5433**(compose 只把栈内 PG 发布到 5433,避免和系统 PG 撞车)。指向外部实例(IP 坐标)时这个翻译不生效。启动时脚本会打印实际使用的坐标,可以据此确认连的是哪个库。./start-all.sh
前台运行,实时打印每个服务的就绪状态,Ctrl+C 统一停止。首次运行若缺 JAR 会自动编译对应模块。
./start-all.sh # 前台启动全部(缺 JAR 自动补编)
./start-all.sh --build # 先全量编译再启动
./start-all.sh restart # 停止 → 编译 → 启动
./start-all.sh stop # 停止全部(可在另一个终端执行)
./start-all.sh status # 查看运行状态
./start-all.sh help # 完整帮助
./start-all.sh jnpf-lims # 只启动 lims
./start-all.sh restart jnpf-lims # 只重启 lims(仅编译该模块,快很多)
./start-all.sh stop jnpf-lims # 只停 lims,不影响其他服务
./start-all.sh status jnpf-lims # 只看 lims
./start-all.sh restart jnpf-lims jnpf-platform # 多服务一起
日常开发最常用的是 ./start-all.sh restart jnpf-lims —— 它用 mvn -pl <模块> -am 只编译目标模块及其依赖,比全量 mvn package 快一个数量级。
脚本不是一股脑全拉起来的:
| 波次 | 服务 |
|---|---|
| 1 | jnpf-platform |
| 2 | jnpf-biz-common |
| 3 | jnpf-lims、jnpf-dms |
| 4 | 其余(当前是 jnpf-eln) |
闸门逻辑:读 vm.loadavg,负载 ≥ LOAD_GATE(默认 9)就等 20s 再放下一波。4 个业务 JVM 同时启动会瞬间打满 CPU,导致 Druid 建连超时。
可调环境变量:
| 变量 | 默认 | 说明 |
|---|---|---|
SERVICE_READY_TIMEOUT |
360 | 等待就绪的最长秒数 |
JNPF_SERVICE_MAX_HEAP |
384m | 单服务堆上限 |
JNPF_PLATFORM_MAX_HEAP |
1536m | jnpf-platform 例外档(九个模块同一个 JVM) |
LOAD_GATE |
9 | 分波负载闸门 |
用法:JNPF_SERVICE_MAX_HEAP=512m ./start-all.sh jnpf-lims
| 位置 | 内容 |
|---|---|
log/<服务名>/startup.log |
宿主栈的启动与运行日志(脚本重定向的 stdout/stderr) |
.pids/<服务名>.pid |
进程 PID,stop / status 靠它跨终端管理 |
常见状态含义:
[跳过] ... 已在运行 — PID 文件里的进程还活着[失败] ... 端口 X 已被未登记进程占用 — 端口上有进程但不是本脚本启动的,脚本不会去动它,需要自己 lsof -i :X 处理[未登记](status 输出)— 同上,端口在监听但没有对应 PID 文件[超时] — 进程活着但超时未监听端口,去 log/<服务名>/startup.log 看栈宿主栈下,Document Server 在容器里而后端在宿主上,容器内的 127.0.0.1 指向容器自己。脚本已自动导出三个地址变量走 host.docker.internal,正常情况下不用在 .env 里配;显式配了则以 .env 为准。
| 服务 | 端口 | 职责 |
|---|---|---|
jnpf-gateway |
30000 | API 网关,路由转发。基于 WebFlux,与 servlet 栈不能同进程,必须独立部署 |
jnpf-platform |
30002 | 平台聚合服务:下列九个平台模块合并在一个 JVM 内 |
jnpf-scheduletask |
30009 | 定时任务(xxl-job admin),未并入 platform |
jnpf-eln |
30013 | ELN 电子实验记录(当前为骨架) |
jnpf-biz-common |
30015 | 公共业务能力聚合:OnlyOffice、审计等 |
jnpf-dms |
30016 | 文档管理 |
jnpf-lims |
30019 | LIMS 实验室信息管理,主要业务模块 |
jnpf-flow-engine |
31000 | Flowable 7.0.1 流程引擎。**独立工程,不在根 pom**,用独立数据库 jnpf_flow |
jnpf-platform 内含的九个模块:
| 模块 | 原端口 | 职责 |
|---|---|---|
jnpf-oauth |
30001 | 认证授权(Sa-Token + JWT) |
jnpf-system |
30002 | 平台核心:字典、权限、数据接口;同时是 Dubbo LogProvider 的唯一 provider(20880) |
jnpf-visualdev |
30003 | 在线开发引擎(表单/列表设计器) |
jnpf-flowable |
30004 | 工作流**业务层**,注册名是 jnpf-workflow(真引擎是上面的 flow-engine) |
jnpf-file |
30005 | 文件管理 |
jnpf-message |
30008 | 消息中心 |
jnpf-permission |
30010 | 权限管理 |
jnpf-visualdata |
30011 | 数据可视化 |
jnpf-app |
30012 | 移动端服务 |
九个模块合并进一个 JVM 后内存占用从约 6.7GB 降到 1.07GB。**九个服务名全部保留为服务发现表里的别名**,指向 30002 —— 所以 60 多个
@FeignClient、网关的lb://路由、各类脚本都不需要改动,写代码时照旧按原服务名调用即可。九个*-server模块的源码也保留着,由各自父 pom 的microserviceprofile 门控,回滚不需要改代码。同理,审计能力已并入
jnpf-biz-common,但服务名jnpf-audit和路由前缀/api/audit/都刻意保留为别名,调用方零改动。
前端请求 → 网关(30000)→ 按前缀转发:
| 前缀 | 目标 |
|---|---|
/api/lims/、/api/extend/(旧前缀,同指) |
jnpf-lims |
/api/dms/ |
jnpf-dms |
/api/eln/ |
jnpf-eln |
/api/biz/ |
jnpf-biz-common(如 /api/biz/onlyoffice/xxx) |
/api/audit/ |
jnpf-biz-common(别名保留) |
/api/oauth/ /api/system/ /api/visualdev/ /api/workflow/ /api/file/ /api/message/ … |
jnpf-platform(经服务名别名解析) |
完整路由表在 config/shared/router.yaml。
以 jnpf-lims 为例,其他业务模块(dms/eln)结构相同:
jnpf-lims/
├── jnpf-lims-entity # 实体类、DTO、枚举
├── jnpf-lims-biz # Service + Mapper
├── jnpf-lims-controller # REST 控制器
├── jnpf-lims-api # Feign 远程调用接口
└── jnpf-lims-server # Spring Boot 启动入口 + 配置
包名统一用 lims 前缀:
| 层 | 路径 |
|---|---|
| Entity | jnpf-lims-entity/src/main/java/jnpf/limsEntity/ |
| Mapper | jnpf-lims-biz/src/main/java/jnpf/limsMapper/ |
| Service | jnpf-lims-biz/src/main/java/jnpf/limsService/ |
| Controller | jnpf-lims-controller/src/main/java/jnpf/limsController/ |
jnpf-common(parent POM)
└── jnpf-public/jnpf-cloud-base # 静态服务发现 + 核心工具
└── jnpf-public/jnpf-common-springaop # AOP + Sa-Token + Sentinel + Swagger
└── 各业务服务
@MapperScan 能扫描到jianyan_liushuihao、chanpin_mingcheng)f_ 开头:这是平台技术列的保留前缀(f_id / f_tenant_id / f_version / f_delete_mark / f_creator_time / f_flow_state 等),审计层正是靠这个前缀剔除技术列@TableName + BaseMapper<T>){code: 200, msg: "...", data: ...}mvn clean package -DskipTests # 全量(约 10 分钟)
mvn clean package -DskipTests -pl jnpf-lims/jnpf-lims-server -am # 只构建 lims
make jars # 打包全部业务 jar
构建 profile:default-package(fat JAR,默认)、external-package(依赖外置)。
没有配置中心。配置是仓库内的 YAML 文件 + 环境变量占位符,三层组合。
| 层 | 位置 | 作用 | 是否进镜像 |
|---|---|---|---|
| 共享配置 | config/shared/*.yaml |
单一事实源。所有环境共用同一份结构 | ✅ /config/shared |
| 宿主覆盖 | config/host-override/*.yaml |
宿主 java -jar 栈的差异覆盖 |
❌ 只存在于工作区 |
| 环境值 | 仓库根 .env |
真实的密码、坐标、域名 | ❌ 不入库 |
规则很简单:**config/shared/ 里只写结构和占位符,真实值一律走 .env**。
# config/shared/datasource.yaml 里长这样
datasource:
db-type: PostgreSQL
db-name: jnpf_init
host: ${CUSTOMER_DB_HOST:cx-postgres}
port: ${CUSTOMER_DB_PORT:5432}
username: ${JNPF_APP_DB_USER:jnpf_app}
password: ${JNPF_APP_DB_PASSWORD:jnpf_app_2026}
占位符的默认值 = 本机 compose 开发形态,所以 cp .env.example .env 之后基本可以直接跑。
config/shared/ 下按关注点分文件:datasource.yaml(数据源/Redis)、discovery.yaml(服务发现)、router.yaml(网关路由)、logger.yaml(日志)、resources.yaml(文件存储)、system-config.yaml(域名等)、jnpf-biz-common.yaml、jnpf-dms.yaml(模块专属)等。
.env 同时被两边读取:
docker compose 自动读仓库根 .env,注入容器环境start-all.sh 启动时逐行解析 .env 并 export差异只在 config/host-override/。目前只有一份 discovery.yaml —— 服务发现表里,容器栈解析到容器名(cx-platform:30002),宿主栈整表覆盖为 127.0.0.1:30002。这个文件在容器内不存在,Spring 的 optional: 导入落空即跳过,所以同一份 application.yml 能双栈通用。
.env 里同名键**后出现的覆盖先出现的**;启动前已在环境里的变量不会被 .env 覆盖,所以可以 FOO=bar ./start-all.sh 临时改值。
没有 Nacos。config/shared/discovery.yaml 是一张 SimpleDiscoveryClient 实例表,直接把服务名映射到 URI:
spring.cloud.discovery.client.simple.instances:
jnpf-lims:
- uri: http://cx-lims:30019
消费方是所有 @FeignClient 的 lb 解析、网关的 lb:// 路由、knife4j 的文档聚合。
⚠️ 新增服务要同步三处:
config/shared/discovery.yaml、config/host-override/discovery.yaml、docker-compose.yml。漏掉任何一处,对应形态下的调用会 502。
| 场景 | 操作 |
|---|---|
改 config/** 或 .env,宿主栈 |
./start-all.sh stop && ./start-all.sh(Druid 连接池在启动时定死,不热更新) |
改 config/**,容器栈 |
rebuild 镜像,或用 volume 挂载覆盖 /config/shared(开发用的 override 已经挂好) |
| 交付现场改结构级配置 | 挂载覆盖 /config/shared 即可,不必重建镜像 |
jnpf_init 和流程引擎库 jnpf_flow 由同一组坐标推导,**必须同源**JNPF_APP_CONN_LIMIT / JNPF_FLOW_CONN_LIMIT 分配,两者之和要小于实例 max_connections − 3,避免单个角色占满连接槽饿死另一个![]() |
README | 14 KB | 查看 | 编辑 | 原始文档 | blame | 历史 |
![]() |
config/README | 862 b | 查看 | 编辑 | 原始文档 | blame | 历史 |
![]() |
docker/delivery/INSTALL | 7 KB | 查看 | 编辑 | 原始文档 | blame | 历史 |
![]() |
docker/postgres/migration/README-杩佺Щ鎿嶄綔鎵嬪唽 | 14 KB | 查看 | 编辑 | 原始文档 | blame | 历史 |
![]() |
docker/postgres/tencent/README | 872 b | 查看 | 编辑 | 原始文档 | blame | 历史 |
![]() |
jnpf-flow-engine/README | 5 KB | 查看 | 编辑 | 原始文档 | blame | 历史 |