feat(qms): 新增QMS质量管理系统模块

- 创建QMS管理后台应用模块(nflg-qms-admin)
- 添加序列号生成服务支持重试机制
- 集成分布式追踪和Loki日志收集
- 配置Nacos服务发现和SSO单点登录
- 添加部署测试工具类用于SIT环境发布
- 生成TSID唯一标识工具类
- 创建数据库操作仓库模块(nflg-wms-repository)
- 更新父级项目配置添加QMS模块支持
- 修改WMS管理后台名称标识区分
This commit is contained in:
曹鹏飞 2026-04-08 11:09:36 +08:00
parent 4b9c5b540c
commit d4dcc3c5dd
17 changed files with 947 additions and 25 deletions

View File

@ -0,0 +1 @@
method.return[#response]=groovy: "com.nflg.wms.common.pojo.ApiResult<" + helper.resolveLink(it.doc("response")) +">"

277
nflg-qms-admin/pom.xml Normal file
View File

@ -0,0 +1,277 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.nflg</groupId>
<artifactId>nflg-wms</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>nflg-qms-admin</artifactId>
<name>服务-qms管理后台</name>
<description>管理后台服务</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.nflg</groupId>
<artifactId>nflg-wms-common</artifactId>
</dependency>
<dependency>
<groupId>com.nflg</groupId>
<artifactId>nflg-wms-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.github.biezhi</groupId>
<artifactId>TinyPinyin</artifactId>
<version>2.0.3.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-client</artifactId>
<exclusions>
<exclusion>
<artifactId>checker-qual</artifactId>
<groupId>org.checkerframework</groupId>
</exclusion>
</exclusions>
</dependency>
<!--生成pdf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.13.3</version>
<exclusions>
<exclusion>
<artifactId>bcprov-jdk14</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>commons-io</artifactId>
<groupId>commons-io</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.6</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
<exclusions>
<exclusion>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- <dependency>-->
<!-- <groupId>cn.idev.excel</groupId>-->
<!-- <artifactId>fastexcel</artifactId>-->
<!-- <version>1.3.0</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.52.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId>
<version>2.27.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-tsid</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-external-libs</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<!-- 指定源目录项目根目录下的lib -->
<resources>
<resource>
<directory>${project.basedir}/lib</directory>
<filtering>false</filtering>
</resource>
</resources>
<!-- 指定目标目录(与依赖库相同的目录) -->
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<!-- 自动创建目标目录 -->
<overwrite>true</overwrite>
</configuration>
</execution>
</executions>
</plugin>
<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>${project.build.directory}/lib</outputDirectory>
<excludeGroupIds>${project.groupId}</excludeGroupIds>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.nflg.qms.admin.AdminApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>${project.groupId}:*</include>
</includes>
</artifactSet>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,27 @@
package com.nflg.qms.admin;
import cn.dev33.satoken.sso.SaSsoManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableScheduling;
@Slf4j
@SpringBootApplication
@ComponentScan(basePackages = {"com.nflg.qms.admin","com.nflg.wms.starter","com.nflg.wms.repository"})
@EnableDiscoveryClient
@EnableScheduling
@EnableRetry
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
log.info("【QMS】服务已启动");
log.info("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------");
log.info("配置信息:" + SaSsoManager.getClientConfig());
}
}

View File

@ -0,0 +1,48 @@
package com.nflg.qms.admin.service;
import cn.hutool.core.util.StrUtil;
import com.nflg.wms.repository.entity.BasdeSerialNumber;
import com.nflg.wms.repository.service.IBasdeSerialNumberService;
import jakarta.annotation.Resource;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@Component
public class BasdeSerialNumberControllerService {
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final int MAX_RETRY = 5; // 最大重试次数
@Resource
private IBasdeSerialNumberService basdeSerialNumberService;
@Retryable(
maxAttempts = 5, // 最大重试次数包括第一次调用
backoff = @Backoff(delay = 1000) // 重试间隔1秒
)
public String generateSerialNumber(Integer businessType) {
String currentDate = LocalDate.now().format(DATE_FORMATTER);
BasdeSerialNumber serialNumber = basdeSerialNumberService.lambdaQuery()
.eq(BasdeSerialNumber::getBusinessType, businessType)
.one();
if (Objects.isNull(serialNumber)) {
return null;
}
int nextSerial = 0;
if (StrUtil.equals(currentDate, serialNumber.getCurrentDateStr())) {
nextSerial = serialNumber.getMaxSerial();
}
nextSerial = nextSerial + 1;
serialNumber.setMaxSerial(nextSerial);
serialNumber.setCurrentDateStr(currentDate);
basdeSerialNumberService.updateById(serialNumber);
// 格式化为4位数字不足补零
return serialNumber.getBusinessPrefixNumber() + currentDate + String.format("%04d", nextSerial);
}
}

View File

@ -0,0 +1,14 @@
package com.nflg.qms.admin.util;
import io.hypersistence.tsid.TSID;
public class KeyUtil {
public static String next(){
return TSID.Factory.getTsid().toString();
}
public static String nextBase62(){
return TSID.Factory.getTsid().encode(62);
}
}

View File

@ -0,0 +1,30 @@
logging:
loki:
url: http://192.168.163.83:3100/loki/api/v1/push
level:
root: info
com:
nflg: trace
alibaba:
cloud:
nacos: debug
org:
springframework: debug
# sa-token配置
sa-token:
# SSO-相关配置
sso-client:
# SSO-Server 用户中心地址
server-url: http://sa-sso-server.com:9000
# LDAP
#spring:
# ldap:
# urls:
# - ldap://192.168.0.2:389
# base: dc=nflg
# username: commpub@nflg
# password: Nflg2019#
management:
otlp:
tracing:
endpoint: http://192.168.163.83:4318/v1/traces

View File

@ -0,0 +1,30 @@
logging:
loki:
url: http://192.168.163.83:3100/loki/api/v1/push
level:
root: info
com:
nflg: debug
alibaba:
cloud:
nacos: info
org:
springframework: info
# sa-token配置
sa-token:
# SSO-相关配置
sso-client:
# SSO-Server 用户中心地址
server-url: http://sa-sso-server.com:9000
# LDAP
#spring:
# ldap:
# urls:
# - ldap://192.168.0.2:389
# base: dc=nflg
# username: commpub@nflg
# password: Nflg2019#
management:
otlp:
tracing:
endpoint: http://192.168.163.83:4318/v1/traces

View File

@ -0,0 +1,30 @@
logging:
loki:
url: http://192.168.163.83:3100/loki/api/v1/push
level:
root: info
com:
nflg: trace
alibaba:
cloud:
nacos: debug
org:
springframework: debug
# sa-token配置
sa-token:
# SSO-相关配置
sso-client:
# SSO-Server 用户中心地址
server-url: http://sa-sso-server.com:9000
# LDAP
#spring:
# ldap:
# urls:
# - ldap://192.168.0.2:389
# base: dc=nflg
# username: commpub@nflg
# password: Nflg2019#
management:
otlp:
tracing:
endpoint: http://192.168.163.83:4318/v1/traces

View File

@ -0,0 +1,48 @@
server:
port: 8105
tomcat:
max-http-form-post-size: 200MB
spring:
main:
allow-bean-definition-overriding: true
application:
name: qms-local
profiles:
active: dev
config:
import: nacos:shared.properties?group=${spring.profiles.active}&refreshEnabled=true
cloud:
nacos:
config:
server-addr: ${nacos.server-addr:192.168.163.83:8848}
namespace: wms
group: ${spring.profiles.active}
discovery:
server-addr: ${nacos.server-addr:192.168.163.83:8848}
namespace: wms
group: ${spring.profiles.active}
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
logging:
level:
root: info
management:
endpoints:
web:
exposure:
include: '*'
enabled-by-default: on
endpoint:
health:
show-details: always
health:
db:
enabled: true
redis:
enabled: true
tracing:
enabled: true
sampling:
probability: 1.0

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 引入Spring Boot默认的基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<Property name="logDir" value="./logs" />
<springProperty scope="context" name="LOKI_URL" source="logging.loki.url" defaultValue=""/>
<springProperty scope="context" name="appName" source="spring.application.name" defaultValue="admin"/>
<springProperty scope="context" name="profile" source="spring.profiles.active" defaultValue="dev"/>
<define name="HOSTIP" class="com.nflg.wms.starter.definer.HostIpDefiner"/>
<!-- 控制台输出配置 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件日志配置 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logDir}/nflg-qms-admin.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 按天滚动日志并按大小拆分 -->
<fileNamePattern>${logDir}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 单个日志文件最大大小 -->
<maxFileSize>10MB</maxFileSize>
<!-- 日志保留天数 -->
<maxHistory>7</maxHistory>
<!-- 所有日志文件的总大小上限 -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<appender name="POWERJOB_WORKER_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logDir}/powerjob-worker.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logDir}/powerjob-worker.%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>7</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<append>true</append>
</appender>
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
<http>
<url>${LOKI_URL}</url>
</http>
<labels>
service_name = qms
app = ${appName}
profile = ${profile}
host = ${HOSTIP}
</labels>
<structuredMetadata>
traceId = %X{traceId}
level = %level
thread = %thread
class = %logger
</structuredMetadata>
<batch>
<staticLabels>true</staticLabels>
<maxItems>5</maxItems>
<timeoutMs>10000</timeoutMs>
</batch>
<message>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} - %msg %ex</pattern>
</message>
<verbose>false</verbose>
</appender>
<appender name="ASYNC_LOKI" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="LOKI" />
<!-- 队列大小 -->
<queueSize>1024</queueSize>
<!-- 丢弃策略 -->
<discardingThreshold>0</discardingThreshold>
<!-- 关键:即使队列满也不阻塞 -->
<neverBlock>true</neverBlock>
<includeCallerData>false</includeCallerData>
</appender>
<logger name="com.alibaba.nacos" level="warn" additivity="false">
<!-- <appender-ref ref="FILE"/>-->
<appender-ref ref="ASYNC_LOKI"/>
</logger>
<logger name="tech.powerjob" level="INFO" additivity="false">
<!-- <appender-ref ref="POWERJOB_WORKER_APPENDER" />-->
<appender-ref ref="ASYNC_LOKI"/>
</logger>
<!-- 默认配置 -->
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
<!-- <appender-ref ref="FILE"/>-->
<appender-ref ref="ASYNC_LOKI"/>
</root>
</configuration>

View File

@ -0,0 +1,332 @@
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.jupiter.api.Test;
import java.io.*;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class DeploySitTest {
private static final String serviceName = "admin";
private static final String localPath = System.getProperty("user.dir") + "//target//";
private static final String remotePath = "/mnt/app/qms/" + serviceName + "/";
private static final String jarName = "nflg-qms-" + serviceName + "-1.0.0-SNAPSHOT.jar";
@Test
public void DeployToSit() throws Exception {
SSHUtil sshUtil = new SSHUtil();
sshUtil.connect("192.168.163.84", 22, "root", "CMP2025nf");
//处理主jar包
handleFile(sshUtil, localPath + jarName, remotePath + jarName);
//处理字体目录
// handleDir(sshUtil, localPath, remotePath, "fonts");
//处理lib目录
// handleDir(sshUtil, localPath, remotePath, "lib");
//执行脚本启动服务
sshUtil.exec("cd " + remotePath + " && ./restart.sh");
sshUtil.disconnect();
}
private void handleDir(SSHUtil sshUtil, String localPath, String remotePath, String dirName) throws Exception {
printInfo("处理目录:" + dirName);
List<Path> files = getFileList(localPath + "lib");
for (Path file : files) {
handleFile(sshUtil, file.toString(), remotePath + "lib/" + file.getFileName().toString());
}
printInfo("处理目录完成");
}
public List<Path> getFileList(String folderPath) throws IOException {
Path path = Paths.get(folderPath);
if (Files.exists(path) && Files.isDirectory(path)) {
List<Path> fileList = new ArrayList<>();
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
for (Path entry : stream) {
fileList.add(entry);
}
}
return fileList;
}
return null;
}
private void handleFile(SSHUtil sshUtil, String localPath, String remotePath) throws Exception {
BasicFileAttributes localFileAttr = Files.readAttributes(Paths.get(localPath), BasicFileAttributes.class);
printInfo("处理文件:{},大小:{}", localPath, getSize(localFileAttr.size()));
if (sshUtil.fileExists(remotePath)) {
if (!StrUtil.equals(getRemoteFileMD5(sshUtil, remotePath), getLocalFileMD5(localPath), true)) {
printError("文件不一致,开始上传");
sshUtil.uploadFile(localPath, remotePath);
} else {
printInfo("文件一致");
}
} else {
printError("文件不存在,开始上传");
sshUtil.uploadFile(localPath, remotePath);
}
printInfo("处理完成");
}
private String getRemoteFileMD5(SSHUtil sshUtil, String remotePath) throws Exception {
String md5 = StrUtil.subPre(sshUtil.execWithReturn("md5sum " + remotePath), 32);
printInfo("远程文件MD5为" + md5);
return md5;
}
private String getLocalFileMD5(String localPath) throws Exception {
String md5 = DigestUtils.md5Hex(new FileInputStream(localPath));
printInfo("本地文件MD5为" + md5);
return md5;
}
private String getSize(long size) {
long s = 1024;
if (size < s) {
return size + "B";
}
s = s * 1024;
if (size < s) {
return String.format("%.2f", size / 1.00 / 1024) + "KB";
}
s = s * 1024;
if (size < s) {
return String.format("%.2f", size / 1.00 / 1024 / 1024) + "MB";
}
s = s * 1024;
return String.format("%.2f", size / 1.00 / 1024 / 1024 / 1024) + "GB";
}
private static class SSHUtil {
private Session session;
private ChannelSftp channelSftp;
/**
* 建立SSH连接
*/
public void connect(String host, int port, String userName, String password) throws JSchException {
printInfo("开始连接服务器,ip:{},port:{},userName:{},password:{}", host, port, userName, password);
JSch jsch = new JSch();
session = jsch.getSession(userName, host, port);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
printInfo("连接成功");
Channel channel = session.openChannel("sftp");
channel.connect();
channelSftp = (ChannelSftp) channel;
}
public String execWithReturn(String command) throws Exception {
printInfo("开始执行命令:{}", command);
if (session == null || !session.isConnected()) {
throw new IllegalStateException("SSH未连接");
}
ChannelExec channel = null;
BufferedReader reader = null;
try {
// 创建执行命令的通道
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
// 获取输入流以读取命令输出
InputStream in = channel.getInputStream();
InputStream err = channel.getExtInputStream();
channel.connect();
reader = new BufferedReader(new InputStreamReader(in));
String data = reader.readLine();
// log.info("执行命令成功,返回数据:" + data);
BufferedReader errReader = new BufferedReader(new InputStreamReader(err));
String errData = errReader.readLine();
// log.info("执行命令失败,返回数据:" + errData);
if (StrUtil.isBlank(errData)) {
return data;
} else {
errReader.close();
throw new Exception("执行命令失败:" + errData);
}
} finally {
if (channel != null) {
channel.disconnect();
}
if (reader != null) {
try {
reader.close();
} catch (Exception ignored) {
}
}
printInfo("执行命令完毕");
}
}
/**
* 执行远程命令并返回输出
*/
public void exec(String command) {
printInfo("开始执行命令:{}", command);
if (session == null || !session.isConnected()) {
throw new IllegalStateException("SSH未连接");
}
ChannelExec channel = null;
SshResultCallback callback = new SshResultCallback() {
@Override
public void onOutput(String output) {
printInfo(false, output);
}
@Override
public void onErrorOutput(String errorOutput) {
printError(false, errorOutput);
}
@Override
public void onCompleted(int exitStatus) {
printInfo("输出完毕,退出状态:" + exitStatus);
}
@Override
public void onError(Exception e) {
printError(false, e.getMessage());
}
};
try {
// 创建执行命令的通道
channel = (ChannelExec) session.openChannel("exec");
channel.setCommand(command);
// 获取输入流以读取命令输出
InputStream in = channel.getInputStream();
InputStream err = channel.getExtInputStream();
channel.connect();
// 读取命令输出
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
BufferedReader errReader = new BufferedReader(new InputStreamReader(err));
String line;
while ((line = reader.readLine()) != null) {
callback.onOutput(line);
}
// 读取错误输出
String errLine;
while ((errLine = errReader.readLine()) != null) {
callback.onErrorOutput(errLine);
}
// 获取退出状态
int exitStatus = channel.getExitStatus();
callback.onCompleted(exitStatus);
} catch (JSchException | IOException e) {
callback.onError(e);
} finally {
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
printInfo("执行命令完毕");
}
}
/**
* 检查远程文件是否存在
*/
public boolean fileExists(String remotePath) throws SftpException {
try {
channelSftp.stat(remotePath);
return true;
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
return false;
}
throw e;
}
}
/**
* 上传文件
*/
public void uploadFile(String localPath, String remotePath) throws SftpException {
printInfo("开始上传本地文件{}到远程{}", localPath, remotePath);
channelSftp.put(localPath, remotePath);
printInfo("上传完成");
}
/**
* 关闭连接
*/
public void disconnect() {
if (channelSftp != null) {
channelSftp.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}
public interface SshResultCallback {
void onOutput(String output);
void onErrorOutput(String errorOutput);
void onCompleted(int exitStatus);
void onError(Exception e);
}
private static void printError(String msg) {
if (StrUtil.isNotBlank(msg)) {
System.out.println(red + msg + reset);
}
}
private static void printError(boolean addTime, String msg) {
if (addTime) {
System.out.println(red + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " " + msg + reset);
} else {
System.out.println(red + msg + reset);
}
}
private static void printInfo(String msg) {
printInfo(true, msg);
}
private static void printInfo(boolean addTime, String msg) {
if (addTime) {
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " " + msg);
} else {
System.out.println(msg);
}
}
private static void printInfo(String format, Object... args) {
printInfo(true, format, args);
}
private static void printInfo(boolean addTime, String format, Object... args) {
if (addTime) {
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " " + StrUtil.format(format, args));
} else {
System.out.println(StrUtil.format(format, args));
}
}
public static final String red = "\u001B[31m";
public static final String reset = "\u001B[0m";
}

View File

@ -8,7 +8,7 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>nflg-wms-admin</artifactId>
<name>服务-管理后台</name>
<name>服务-wms管理后台</name>
<description>管理后台服务</description>
<packaging>jar</packaging>
<dependencies>

View File

@ -19,7 +19,7 @@ public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
log.info("服务已启动");
log.info("【WMS】服务已启动");
log.info("---------------------- Sa-Token SSO 模式二 Client 端启动成功 ----------------------");
log.info("配置信息:" + SaSsoManager.getClientConfig());
}

View File

@ -8,7 +8,6 @@
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>nflg-wms-repository</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>模块-db</name>
<description>封装数据库操作</description>
<packaging>jar</packaging>

View File

@ -1,21 +0,0 @@
package com.nflg.wms.shipment.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
@Lazy
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(3_000);
factory.setReadTimeout(10_000);
return new RestTemplate(factory);
}
}

View File

@ -1,4 +1,4 @@
package com.nflg.wms.admin.config;
package com.nflg.wms.starter.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

View File

@ -24,6 +24,7 @@
<module>nflg-wms-scheduled</module>
<module>nflg-wms-srm-receive</module>
<module>nflg-wms-shipment</module>
<module>nflg-qms-admin</module>
</modules>
<properties>
<java.version>17</java.version>