liuyu
5 天以前 aed0e43ff4eb2cdb5fa81b6b9b16d8d2b723c02e
feat: 新增 jnpf-tms 业务微服务骨架

按 eln/dms 五层结构新增 TMS 独立服务(端口 30017,路由 /api/tms/**),并完成 discovery/router、compose、启停脚本与根 pom 接入,便于后续编写 TMS 业务。

Co-authored-by: Cursor <cursoragent@cursor.com>
15个文件已添加
13个文件已修改
707 ■■■■■ 已修改文件
README.md 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/host-override/discovery.yaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/shared/discovery.yaml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/shared/logger.yaml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
config/shared/router.yaml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker-compose.override.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker-compose.testenv.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker-compose.yml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docker/delivery/assemble-offline-package.sh 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
dockerfile/tms/Dockerfile 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-api/pom.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-api/src/main/java/jnpf/utils/TmsFeignName.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-biz/pom.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-biz/src/main/java/jnpf/tmsMapper/package-info.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-biz/src/main/java/jnpf/tmsService/package-info.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-controller/pom.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-controller/src/main/java/jnpf/tmsController/PingController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-entity/pom.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-entity/src/main/java/jnpf/tmsEntity/package-info.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-server/pom.xml 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-server/src/main/java/jnpf/JnpfTmsApplication.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-server/src/main/resources/application.yml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/jnpf-tms-server/src/main/resources/logback-spring.xml 300 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
jnpf-tms/pom.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
start-all.ps1 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
start-all.sh 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
stop-all.ps1 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -139,13 +139,14 @@
### 服务拓扑
| 服务 | 端口 | 职责 |
|---|---|---|
|---|---|------------------------------------------------------|
| `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-tms` | 30017 | TMS 考试培训管理                                 |
| **`jnpf-lims`** | **30019** | **LIMS 实验室信息管理,主要业务模块** |
| `jnpf-flow-engine` | 31000 | Flowable 7.0.1 流程引擎。**独立工程,不在根 pom**,用独立数据库 `jnpf_flow` |
@@ -175,6 +176,7 @@
|---|---|
| `/api/lims/`、`/api/extend/`(旧前缀,同指) | jnpf-lims |
| `/api/dms/` | jnpf-dms |
| `/api/tms/` | jnpf-tms |
| `/api/eln/` | jnpf-eln |
| `/api/biz/` | jnpf-biz-common(如 `/api/biz/onlyoffice/xxx`) |
| `/api/audit/` | jnpf-biz-common(别名保留) |
@@ -184,7 +186,7 @@
### 业务模块分层
以 `jnpf-lims` 为例,其他业务模块(dms/eln)结构相同:
以 `jnpf-lims` 为例,其他业务模块(dms/eln/tms)结构相同:
```
jnpf-lims/
config/host-override/discovery.yaml
@@ -48,5 +48,7 @@
              - uri: http://127.0.0.1:30015
            jnpf-dms:
              - uri: http://127.0.0.1:30016
            jnpf-tms:
              - uri: http://127.0.0.1:30017
            jnpf-lims:
              - uri: http://127.0.0.1:30019
config/shared/discovery.yaml
@@ -53,5 +53,7 @@
              - uri: http://cx-biz-common:30015
            jnpf-dms:
              - uri: http://cx-dms:30016
            jnpf-tms:
              - uri: http://cx-tms:30017
            jnpf-lims:
              - uri: http://cx-lims:30019
config/shared/logger.yaml
@@ -12,6 +12,7 @@
    # 新增服务时如果它在 log.level 里没有 INFO 条目,就往这里补一行。
    jnpf.JnpfGatewayApplication: INFO
    jnpf.JnpfLimsApplication: INFO
    jnpf.JnpfTmsApplication: INFO
    # 解除注释后Druid连接池打印SQL语句
    druid.sql.Statement: debug
#    druid.sql.DataSource: debug
config/shared/router.yaml
@@ -63,6 +63,13 @@
            - Path=/api/dms/**
          filters:
            - StripPrefix=2
        # TMS 业务服务
        - id: jnpf-tms
          uri: lb://jnpf-tms
          predicates:
            - Path=/api/tms/**
          filters:
            - StripPrefix=2
        # 报表服务
        - id: jnpf-datareport
          uri: lb://jnpf-datareport
docker-compose.override.yml
@@ -125,6 +125,10 @@
    <<: *no-local-data-plane
    ports: ["127.0.0.1:30016:30016"]
    volumes: *config-shared-volume
  cx-tms:
    <<: *no-local-data-plane
    ports: ["127.0.0.1:30017:30017"]
    volumes: *config-shared-volume
  # cx-visualdata 随平台聚合停用,保留以便回滚
  # cx-visualdata:
  #   <<: *no-local-data-plane
docker-compose.testenv.yml
@@ -70,4 +70,5 @@
  cx-eln: *no-local-pg
  cx-biz-common: *no-local-pg
  cx-dms: *no-local-pg
  cx-tms: *no-local-pg
  cx-scheduletask: *no-local-pg
docker-compose.yml
@@ -554,6 +554,15 @@
      <<: *hc-30000
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/30016"]
  cx-tms:
    <<: *cx
    container_name: cx-tms
    image: cx/tms:${CX_VERSION:-1.0.0}
    build: { context: ., dockerfile: dockerfile/tms/Dockerfile, args: { BASE_IMAGE: "${BASE_IMAGE:-bellsoft/liberica-openjre-rocky:21}" } }
    healthcheck:
      <<: *hc-30000
      test: ["CMD-SHELL", "exec 3<>/dev/tcp/127.0.0.1/30017"]
  # ── cx-visualdata 已并入 cx-platform(2026-08-11),整段注释保留以便回滚 ──
  # cx-visualdata:
  #   <<: *cx
docker/delivery/assemble-offline-package.sh
@@ -69,6 +69,7 @@
  jnpf-biz-common/jnpf-biz-common-server/target
  jnpf-lims/jnpf-lims-server/target
  jnpf-dms/jnpf-dms-server/target
  jnpf-tms/jnpf-tms-server/target
)
for d in "${BIZ_JARS[@]}"; do
  ls "$d"/*.jar >/dev/null 2>&1 || die "缺业务 jar:${d}/*.jar —— 先 make jars"
dockerfile/tms/Dockerfile
New file
@@ -0,0 +1,19 @@
# 基础镜像:本地默认 Docker Hub 多架构(arm64 原生,L024);交付构建注入 SWR amd64 单架构
ARG BASE_IMAGE=bellsoft/liberica-openjre-rocky:21
FROM ${BASE_IMAGE}
LABEL maintainer=cx-team
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /data/jnpfsoft/javaApi/jnpf-tms
COPY jnpf-tms/jnpf-tms-server/target/*.jar ./jnpf-tms.jar
COPY config/shared /config/shared
ENV JNPF_CONFIG_DIR=/config
EXPOSE 30017
ENTRYPOINT ["/bin/sh","-c","java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.math=ALL-UNNAMED --add-opens=java.base/sun.net.util=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/jdk.internal.module=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED -Dfile.encoding=utf8 -Djava.security.egd=file:/dev/./urandom -jar jnpf-tms.jar"]
jnpf-tms/jnpf-tms-api/pom.xml
New file
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-tms</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms-api</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-common-feign</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-tms-entity</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
jnpf-tms/jnpf-tms-api/src/main/java/jnpf/utils/TmsFeignName.java
New file
@@ -0,0 +1,11 @@
package jnpf.utils;
/**
 * TMS 服务 Feign 名称常量,供其它服务引用。
 */
public class TmsFeignName {
    public static final String TMS_SERVER_NAME = "jnpf-tms";
    private TmsFeignName() {
    }
}
jnpf-tms/jnpf-tms-biz/pom.xml
New file
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-tms</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms-biz</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-cloud-base</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-common-database</artifactId>
        </dependency>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-tms-entity</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-tms-api</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
jnpf-tms/jnpf-tms-biz/src/main/java/jnpf/tmsMapper/package-info.java
New file
@@ -0,0 +1,4 @@
/**
 * TMS MyBatis Mapper 包(由 {@code @MapperScan("jnpf.tmsMapper")} 扫描)。
 */
package jnpf.tmsMapper;
jnpf-tms/jnpf-tms-biz/src/main/java/jnpf/tmsService/package-info.java
New file
@@ -0,0 +1,4 @@
/**
 * TMS 业务服务包。
 */
package jnpf.tmsService;
jnpf-tms/jnpf-tms-controller/pom.xml
New file
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-tms</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms-controller</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-common-springaop</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-tms-biz</artifactId>
            <version>${project.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>
jnpf-tms/jnpf-tms-controller/src/main/java/jnpf/tmsController/PingController.java
New file
@@ -0,0 +1,18 @@
package jnpf.tmsController;
import jnpf.base.ActionResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 链路验收探针:网关 /api/tms/ping(StripPrefix=2)→ 本服务 /ping。
 * 放行配置见 application.yml(sa-token / 租户 / 内部鉴权三处)。
 */
@RestController
public class PingController {
    @GetMapping("/ping")
    public ActionResult<String> ping() {
        return ActionResult.success("pong");
    }
}
jnpf-tms/jnpf-tms-entity/pom.xml
New file
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-tms</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms-entity</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-common-core</artifactId>
        </dependency>
    </dependencies>
</project>
jnpf-tms/jnpf-tms-entity/src/main/java/jnpf/tmsEntity/package-info.java
New file
@@ -0,0 +1,4 @@
/**
 * TMS 实体 / DTO / VO 包。
 */
package jnpf.tmsEntity;
jnpf-tms/jnpf-tms-server/pom.xml
New file
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-tms</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms-server</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.jnpf</groupId>
            <artifactId>jnpf-tms-controller</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    <profiles>
        <profile>
            <id>default-package</id>
            <activation>
                <jdk>[,]</jdk>
            </activation>
            <build>
                <finalName>jnpf-tms-${project.version}</finalName>
                <plugins>
                    <plugin>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-maven-plugin</artifactId>
                        <configuration>
                            <mainClass>jnpf.JnpfTmsApplication</mainClass>
                            <layout>ZIP</layout>
                        </configuration>
                        <executions>
                            <execution>
                                <goals>
                                    <goal>repackage</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
        <profile>
            <id>external-package</id>
            <build>
                <finalName>jnpf-tms-${project.version}</finalName>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-dependency-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>copy-dependencies</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>copy-dependencies</goal>
                                </goals>
                                <configuration>
                                    <outputDirectory>${external-lib-out-path}</outputDirectory>
                                    <overWriteReleases>true</overWriteReleases>
                                    <overWriteSnapshots>true</overWriteSnapshots>
                                    <overWriteIfNewer>true</overWriteIfNewer>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <configuration>
                            <archive>
                                <manifest>
                                    <mainClass>jnpf.JnpfTmsApplication</mainClass>
                                    <addClasspath>true</addClasspath>
                                    <classpathPrefix>${external-lib-start-path}</classpathPrefix>
                                </manifest>
                                <manifestEntries>
                                    <Class-Path>.</Class-Path>
                                </manifestEntries>
                            </archive>
                        </configuration>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
</project>
jnpf-tms/jnpf-tms-server/src/main/java/jnpf/JnpfTmsApplication.java
New file
@@ -0,0 +1,18 @@
package jnpf;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@Slf4j
@SpringBootApplication
@EnableFeignClients
@MapperScan(value = "jnpf.tmsMapper")
public class JnpfTmsApplication {
    public static void main(String[] args) {
        SpringApplication.run(JnpfTmsApplication.class, args);
        JnpfTmsApplication.log.info("TMS启动成功");
    }
}
jnpf-tms/jnpf-tms-server/src/main/resources/application.yml
New file
@@ -0,0 +1,58 @@
spring:
  application:
    name: jnpf-tms
  mvc:
    servlet:
      load-on-startup: 0
  main:
    allow-bean-definition-overriding: true
    allow-circular-references: true
  config:
    # 配置来源 = 仓库 config/(镜像内 /config,经 JNPF_CONFIG_DIR 指向)。
    import:
      - file:${JNPF_CONFIG_DIR:config}/shared/datasource.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/resources.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/system-config.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/frame-config.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/logger.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/mq.yaml
      - file:${JNPF_CONFIG_DIR:config}/shared/discovery.yaml
      - optional:file:${JNPF_CONFIG_DIR:config}/host-override/discovery.yaml
# 应用服务器
server:
  tomcat:
    uri-encoding: UTF-8
  port: 30017
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
    logfile:
      access: unrestricted
# ping 探针放行
jnpf:
  security:
    ignore:
      urls:
        - /ping
        - /api/tms/ping
        - /dev-api/tms/ping
  tenant:
    ignore-urls:
      - /ping
      - /api/tms/ping
      - /dev-api/tms/ping
sa-token:
  filter:
    exclude-paths:
      - /ping
      - /api/tms/ping
      - /dev-api/tms/ping
jnpf-tms/jnpf-tms-server/src/main/resources/logback-spring.xml
New file
@@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--日志格式应用spring boot默认的格式,也可以自己更改-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <property name="FILE_LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] [%logger{50}] [%M] [%line] - %msg%n" />
    <springProperty scope="context" name="SERVICE_NAME" source="spring.application.name" defaultValue="jnpf"/>
    <!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
    <springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="log/${SERVICE_NAME}"/>
    <!-- 全局日志等级 -->
    <springProperty scope="context" name="LOG_LEVEL_ROOT" source="log.level.root" defaultValue="INFO"/>
    <!-- 服务自定义等级 如需自定义服务日志等级 修改下方的【自定义服务名】与nacos上的log.level.自定义服务名=等级 -->
    <springProperty scope="context" name="LOG_LEVEL" source="log.level.jnpf-tms" defaultValue="${LOG_LEVEL_ROOT}"/>
    <!-- 日志记录器,日期滚动记录,level为 ERROR 日志 -->
    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/log_error.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM-dd,aux}/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志最大的历史7天-->
            <maxHistory>7</maxHistory>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录error级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>error</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 日志记录器,日期滚动记录,level为 INFO 日志 -->
    <appender name="FILE_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/log_info.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/info/%d{yyyy-MM-dd,aux}/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志最大的历史7天-->
            <maxHistory>7</maxHistory>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录info级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>info</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 日志记录器,日期滚动记录,level为 WARN 日志 -->
    <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/log_warn.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/warn/%d{yyyy-MM-dd,aux}/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志最大的历史7天-->
            <maxHistory>7</maxHistory>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录warn级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warn</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 日志记录器,日期滚动记录,level为 DEBUG 日志 -->
    <appender name="FILE_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/log_debug.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/debug/%d{yyyy-MM-dd,aux}/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志最大的历史7天-->
            <maxHistory>7</maxHistory>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
        <!-- 此日志文件只记录debug级别的 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 日志记录器,日期滚动记录,所有日志 -->
    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 正在记录的日志文件的路径及文件名 -->
        <file>${LOG_PATH}/log_total.log</file>
        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
            <fileNamePattern>${LOG_PATH}/total/%d{yyyy-MM-dd,aux}/log-total-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!--日志最大的历史7天-->
            <maxHistory>7</maxHistory>
            <!-- 除按日志记录之外,还配置了日志文件不能超过2M,若超过2M,日志文件会以索引0开始,
            命名日志文件,例如log-error-2013-12-21.0.log -->
            <maxFileSize>10MB</maxFileSize>
        </rollingPolicy>
        <!-- 追加方式记录日志 -->
        <append>true</append>
        <!-- 日志文件的格式 -->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${FILE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>
    <!-- 异步输出 DEBUG -->
    <appender name="ASYNC_FILE_DEBUG" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="FILE_DEBUG"/>
    </appender>
    <!-- 异步输出 INFO -->
    <appender name="ASYNC_FILE_INFO" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="FILE_INFO"/>
    </appender>
    <!-- 异步输出 WARN -->
    <appender name="ASYNC_FILE_WARN" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="FILE_WARN"/>
    </appender>
    <!-- 异步输出 ERROR -->
    <appender name="ASYNC_FILE_ERROR" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="FILE_ERROR"/>
    </appender>
    <!-- 异步输出 ALL -->
    <appender name="ASYNC_FILE_ALL" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="FILE_ALL"/>
    </appender>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>utf-8</charset>
        </encoder>
    </appender>
    <!-- 异步输出 控制台 -->
    <appender name="ASYNC_STDOUT" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>256</queueSize>
        <appender-ref ref="STDOUT"/>
    </appender>
    <!--<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        &lt;!&ndash; 必填:目标:LogStash的 IP:Port &ndash;&gt;
        <destination>192.168.0.50:50000</destination>
        &lt;!&ndash; 可选:保持程序存活时间 &ndash;&gt;
        <keepAliveDuration>5 minutes</keepAliveDuration>
        &lt;!&ndash; 可选:重连延迟时长 &ndash;&gt;
        <reconnectionDelay>10 second</reconnectionDelay>
        &lt;!&ndash; 可选:等待策略 &ndash;&gt;
        <waitStrategyType>sleeping</waitStrategyType>
        &lt;!&ndash;  ============ encoder必须配置,有多种可选 ============= &ndash;&gt;
        &lt;!&ndash; 编码器二:LoggingEventCompositeJsonEncoder &ndash;&gt;
        <encoder charset="UTF-8" class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
            <providers>
                &lt;!&ndash; 时间戳:时区 &ndash;&gt;
                <timestamp>
                    <timeZone>UTC</timeZone>
                </timestamp>
                &lt;!&ndash; 模式 &ndash;&gt;
                <pattern>
                    <pattern>
                        {
                        "severity": "%level",
                        "service": "${SERVICE_NAME:-}",
                        "trace": "%X{X-B3-TraceId:-}",
                        "span": "%X{X-B3-SpanId:-}",
                        "exportable": "%X{X-Span-Export:-}",
                        "pid": "${PID:-}",
                        "thread": "%thread",
                        "class": "%logger{40}",
                        "msg": "%message"
                        &lt;!&ndash;"idx_pre": "elk-original-third-access",&ndash;&gt;
                        &lt;!&ndash;"json": "#asJson{%message}"  这个asJson可以把对应的字符串作为json对象取出来,这样es可以对json里面的字段索引了&ndash;&gt;
                        }
                    </pattern>
                </pattern>
            </providers>
        </encoder>
    </appender>-->
    <!--<appender name="skywalking-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>-->
    <root level="${LOG_LEVEL}">
        <appender-ref ref="ASYNC_STDOUT"/>
        <appender-ref ref="ASYNC_FILE_ERROR"/>
        <appender-ref ref="ASYNC_FILE_INFO"/>
        <appender-ref ref="ASYNC_FILE_WARN"/>
        <appender-ref ref="ASYNC_FILE_DEBUG"/>
        <appender-ref ref="ASYNC_FILE_ALL"/>
        <!--<appender-ref ref="LOGSTASH"/>-->
        <!--<appender-ref ref="skywalking-log"/>-->
    </root>
</configuration>
jnpf-tms/pom.xml
New file
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jnpf-java-cloud</artifactId>
        <groupId>com.jnpf</groupId>
        <version>6.1.0-RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>jnpf-tms</artifactId>
    <packaging>pom</packaging>
    <modules>
        <module>jnpf-tms-api</module>
        <module>jnpf-tms-entity</module>
        <module>jnpf-tms-biz</module>
        <module>jnpf-tms-controller</module>
        <module>jnpf-tms-server</module>
    </modules>
</project>
pom.xml
@@ -27,6 +27,7 @@
        <module>jnpf-lims</module>
        <module>jnpf-dms</module>
        <module>jnpf-eln</module>
        <module>jnpf-tms</module>
        <!-- jnpf-audit 已于 2026-08-11 整体搬入 jnpf-biz-common(6 模块压到 3 个,
             jnpf-audit-server / cx-audit 退役),不再是顶层模块 -->
        <module>jnpf-biz-common</module>
start-all.ps1
@@ -57,6 +57,7 @@
    [PSCustomObject]@{ Name = 'jnpf-eln';        Jar = "jnpf-eln/jnpf-eln-server/target/jnpf-eln-$($Script:Version).jar";                        Port = 30013 }
    [PSCustomObject]@{ Name = 'jnpf-lims';       Jar = "jnpf-lims/jnpf-lims-server/target/jnpf-lims-$($Script:Version).jar";                   Port = 30019 }
    [PSCustomObject]@{ Name = 'jnpf-dms';        Jar = "jnpf-dms/jnpf-dms-server/target/jnpf-dms-$($Script:Version).jar";                       Port = 30016 }
    [PSCustomObject]@{ Name = 'jnpf-tms';        Jar = "jnpf-tms/jnpf-tms-server/target/jnpf-tms-$($Script:Version).jar";                       Port = 30017 }
)
$Script:TargetServices = @()
@@ -778,7 +779,7 @@
    switch ($Name) {
        'jnpf-platform'   { return 1 }
        'jnpf-biz-common' { return 2 }
        { $_ -in 'jnpf-lims', 'jnpf-dms' } { return 3 }
        { $_ -in 'jnpf-lims', 'jnpf-dms', 'jnpf-tms' } { return 3 }
        default           { return 4 }
    }
}
@@ -883,7 +884,7 @@
  --build, -b    启动前先编译项目
服务名(可选,省略 = 全部):
  jnpf-gateway  jnpf-platform  jnpf-biz-common  jnpf-eln  jnpf-lims  jnpf-dms
  jnpf-gateway  jnpf-platform  jnpf-biz-common  jnpf-eln  jnpf-lims  jnpf-dms  jnpf-tms
  注:jnpf-biz-common(30015) 含审计能力。
      只起 jnpf-lims 而不起它,审计事件会投递失败并落 spool。
start-all.sh
@@ -153,6 +153,7 @@
  "jnpf-eln         jnpf-eln/jnpf-eln-server/target/jnpf-eln-${VERSION}.jar                    30013"
  "jnpf-lims        jnpf-lims/jnpf-lims-server/target/jnpf-lims-${VERSION}.jar               30019"
  "jnpf-dms         jnpf-dms/jnpf-dms-server/target/jnpf-dms-${VERSION}.jar                   30016"
  "jnpf-tms         jnpf-tms/jnpf-tms-server/target/jnpf-tms-${VERSION}.jar                   30017"
)
# 补丁 #8:宿主栈直连坐标(覆盖 config/shared 里的 compose 默认值)
@@ -497,7 +498,7 @@
  --build, -b    启动前先编译项目
服务名(可选,省略 = 全部):
  jnpf-gateway  jnpf-platform  jnpf-biz-common  jnpf-lims  jnpf-dms
  jnpf-gateway  jnpf-platform  jnpf-biz-common  jnpf-lims  jnpf-dms  jnpf-tms
  注:jnpf-biz-common(30015) 含审计能力(2026-08-11 由原 jnpf-audit:30014 搬入)。
      只起 jnpf-lims 而不起它,审计事件会投递失败并落 spool。
@@ -629,7 +630,7 @@
  case "$1" in
    jnpf-platform)    echo 1 ;;
    jnpf-biz-common)  echo 2 ;;
    jnpf-lims|jnpf-dms) echo 3 ;;
    jnpf-lims|jnpf-dms|jnpf-tms) echo 3 ;;
    *)                echo 4 ;;
  esac
}
stop-all.ps1
@@ -37,6 +37,7 @@
    [PSCustomObject]@{ Name = 'jnpf-eln';        Jar = 'jnpf-eln-6.1.0-RELEASE.jar';        Port = 30013 }
    [PSCustomObject]@{ Name = 'jnpf-lims';       Jar = 'jnpf-lims-6.1.0-RELEASE.jar';       Port = 30019 }
    [PSCustomObject]@{ Name = 'jnpf-dms';        Jar = 'jnpf-dms-6.1.0-RELEASE.jar';        Port = 30016 }
    [PSCustomObject]@{ Name = 'jnpf-tms';        Jar = 'jnpf-tms-6.1.0-RELEASE.jar';        Port = 30017 }
)
function Write-ColorLine {
@@ -72,7 +73,7 @@
    Write-Host ''
    Write-Host '省略服务名时停止全部服务;指定服务名时只停止对应服务:'
    Write-Host '  jnpf-gateway  jnpf-platform  jnpf-biz-common'
    Write-Host '  jnpf-eln      jnpf-lims      jnpf-dms'
    Write-Host '  jnpf-eln      jnpf-lims      jnpf-dms      jnpf-tms'
    Write-Host ''
    Write-Host '示例:'
    Write-Host "  .\$scriptName"