From 20e5114f2ed00073c64736ab4b1193f291bbbde0 Mon Sep 17 00:00:00 2001
From: acgist <289547414@qq.com>
Date: Thu, 10 Nov 2022 07:24:15 +0800
Subject: [PATCH] [*]
---
README.md | 18 +-
docs/bin/deploy.sh | 1 -
pom.xml | 121 +++++++---
taoyao-boot/pom.xml | 34 +++
.../boot/config/BootAutoConfiguration.java | 210 +++++++++++++++++
.../taoyao/boot/config/FormatStyle.java | 133 +++++++++++
.../boot/config/OpenApiAutoConfiguration.java | 125 ++++++++++
.../boot/config/SecurityProperties.java | 47 ++++
.../taoyao/boot/config/TaoyaoProperties.java | 43 ++++
.../WebMvcConfigurerAutoConfiguration.java | 30 +++
.../controller/TaoyaoControllerAdvice.java | 25 ++
.../controller/TaoyaoErrorController.java | 31 +++
.../boot/interceptor/SecurityInterceptor.java | 99 ++++++++
.../com/acgist/taoyao/boot/model/Message.java | 166 +++++++++++++
.../acgist/taoyao/boot/model/MessageCode.java | 98 ++++++++
.../boot/model/MessageCodeException.java | 123 ++++++++++
.../com/acgist/taoyao/boot/model/Model.java | 42 ++++
.../taoyao/boot/model/ModifyOptional.java | 52 ++++
.../acgist/taoyao/boot/service/IdService.java | 17 ++
.../boot/service/impl/IdServiceImpl.java | 5 +
.../acgist/taoyao/boot/utils/BeanUtils.java | 36 +++
.../acgist/taoyao/boot/utils/DateUtils.java | 184 +++++++++++++++
.../acgist/taoyao/boot/utils/ErrorUtils.java | 223 ++++++++++++++++++
.../taoyao/boot/utils/ExceptionUtils.java | 43 ++++
.../acgist/taoyao/boot/utils/FileUtils.java | 55 +++++
.../acgist/taoyao/boot/utils/JSONUtils.java | 213 +++++++++++++++++
.../acgist/taoyao/boot/utils/URLUtils.java | 70 ++++++
.../main/resources/META-INF/spring.factories | 4 +
taoyao-boot/src/main/resources/banner.txt | 10 +
.../src/main/resources/logback-spring.xml | 127 ++++++++++
taoyao-client/pom.xml | 30 +++
taoyao-live/pom.xml | 11 +-
taoyao-media/pom.xml | 4 +-
taoyao-meeting/pom.xml | 16 +-
.../meeting/controller/RoomController.java | 25 ++
{taoyao-model => taoyao-nat}/pom.xml | 6 +-
taoyao-server/pom.xml | 59 ++++-
.../com/acgist/taoyao/TaoyaoApplication.java | 4 +
.../src/main/resources/application-dev.yml | 0
.../main/resources/application-release.yml | 0
.../src/main/resources/application-test.yml | 0
.../src/main/resources/application.properties | 1 -
.../src/main/resources/application.yml | 31 +++
.../src/main/resources/static/client.html | 10 +
.../src/main/resources/static/index.html | 10 +
.../src/main/resources/static/room.html | 10 +
taoyao-server/src/main/resources/taoyao.jks | Bin 0 -> 2776 bytes
taoyao-signal/pom.xml | 14 +-
.../config/SignalAutoConfiguration.java | 37 +++
.../com/acgist/taoyao/signal/event/Event.java | 15 ++
.../taoyao/signal/event/EventAdapter.java | 10 +
.../signal/event/client/RegisterEvent.java | 10 +
.../acgist/taoyao/signal/message/Header.java | 38 +++
.../acgist/taoyao/signal/message/Message.java | 41 ++++
.../taoyao/signal/protocol/Protocol.java | 15 ++
.../signal/protocol/ProtocolAdapter.java | 5 +
.../signal/protocol/ProtocolManager.java | 20 ++
.../acgist/taoyao/signal/session/Session.java | 5 +
.../taoyao/signal/session/SessionAdapter.java | 5 +
.../taoyao/signal/session/SessionManager.java | 162 +++++++++++++
.../signal/session/socket/TaoyaoSocket.java | 10 +
.../session/websocket/SessionWrapper.java | 72 ++++++
.../session/websocket/TaoyaoWebSocket.java | 60 +++++
.../main/resources/META-INF/spring.factories | 2 +
taoyao-webrtc/README.md | 7 +
taoyao-webrtc/pom.xml | 6 +-
taoyao-webrtc/taoyao-webrtc-mcu/pom.xml | 9 +-
taoyao-webrtc/taoyao-webrtc-mesh/pom.xml | 2 +-
.../pom.xml | 15 +-
taoyao-webrtc/taoyao-webrtc-sfu/pom.xml | 9 +-
70 files changed, 3096 insertions(+), 75 deletions(-)
create mode 100644 taoyao-boot/pom.xml
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/BootAutoConfiguration.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/FormatStyle.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/OpenApiAutoConfiguration.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/SecurityProperties.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebMvcConfigurerAutoConfiguration.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoControllerAdvice.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoErrorController.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/interceptor/SecurityInterceptor.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCode.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Model.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/ModifyOptional.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/BeanUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/DateUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ExceptionUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/FileUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java
create mode 100644 taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/URLUtils.java
create mode 100644 taoyao-boot/src/main/resources/META-INF/spring.factories
create mode 100644 taoyao-boot/src/main/resources/banner.txt
create mode 100644 taoyao-boot/src/main/resources/logback-spring.xml
create mode 100644 taoyao-client/pom.xml
create mode 100644 taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/controller/RoomController.java
rename {taoyao-model => taoyao-nat}/pom.xml (72%)
create mode 100644 taoyao-server/src/main/resources/application-dev.yml
create mode 100644 taoyao-server/src/main/resources/application-release.yml
create mode 100644 taoyao-server/src/main/resources/application-test.yml
delete mode 100644 taoyao-server/src/main/resources/application.properties
create mode 100644 taoyao-server/src/main/resources/application.yml
create mode 100644 taoyao-server/src/main/resources/static/client.html
create mode 100644 taoyao-server/src/main/resources/static/index.html
create mode 100644 taoyao-server/src/main/resources/static/room.html
create mode 100644 taoyao-server/src/main/resources/taoyao.jks
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/config/SignalAutoConfiguration.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/Event.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/EventAdapter.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/RegisterEvent.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Header.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Message.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/Session.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionAdapter.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionManager.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/socket/TaoyaoSocket.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/SessionWrapper.java
create mode 100644 taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/TaoyaoWebSocket.java
create mode 100644 taoyao-signal/src/main/resources/META-INF/spring.factories
create mode 100644 taoyao-webrtc/README.md
rename taoyao-webrtc/{taoyao-webrtc-native => taoyao-webrtc-mix}/pom.xml (61%)
diff --git a/README.md b/README.md
index 370ddb8..b8a31ea 100644
--- a/README.md
+++ b/README.md
@@ -7,19 +7,19 @@
|模块|名称|描述|
|:--|:--|:--|
|taoyao|桃夭|桃之夭夭灼灼其华|
-|taoyao-nat|内网穿透|STUN/TURN暂不实现(公共服务或者搭建coturn服务)|
+|taoyao-nat|内网穿透|STUN/TURN暂不实现(请用公共服务或者搭建coturn服务)|
+|taoyao-boot|基础|启动模块|
|taoyao-live|直播|直播、连麦|
-|taoyao-model|模型|数据模型|
|taoyao-media|媒体|录制、视频(美颜、AI识别)、音频(混音、变声)|
-|taoyao-client|终端|帐号、摄像头|
+|taoyao-client|终端|帐号(移动端|浏览器)、摄像头|
|taoyao-signal|信令|信令服务|
|taoyao-server|服务|启动服务|
|taoyao-meeting|会议|会议模式、广播模式、单人对讲|
|taoyao-webrtc|WebRTC模块||
|taoyao-webrtc-sfu|WebRTC SFU架构实现||
|taoyao-webrtc-mcu|WebRTC MCU架构实现||
+|taoyao-webrtc-mix|WebRTC混合|MCU/SFU混合媒体服务|
|taoyao-webrtc-mesh|WebRTC MESH架构实现||
-|taoyao-webrtc-native|WebRTC底层实现|MCU/SFU底层媒体服务|
## STUN/TURN公共服务
@@ -33,7 +33,7 @@ stun:stun.stunprotocol.org:3478
## 终端
-帐号可以管理媒体,摄像头只能被动管理。
+帐号(移动端|浏览器)可以管理媒体,摄像头只能被动管理。
### 功能
@@ -68,4 +68,10 @@ MAC
## 会议
-##
\ No newline at end of file
+##
+
+## 证书
+
+```
+keytool -genkeypair -keyalg RSA -dname "CN=localhost, OU=acgist, O=taoyao, L=GZ, ST=GD, C=CN" -alias taoyao -validity 3650 -ext ku:c=dig,keyE -ext eku=serverAuth -ext SAN=dns:localhost,ip:127.0.0.1 -keystore taoyao.jks -keypass 123456 -storepass 123456
+```
\ No newline at end of file
diff --git a/docs/bin/deploy.sh b/docs/bin/deploy.sh
index 1163ef0..12d42a3 100644
--- a/docs/bin/deploy.sh
+++ b/docs/bin/deploy.sh
@@ -29,7 +29,6 @@ if [ ! -d "$base/../deploy/${project.artifactId}" ]; then
fi
# 拷贝文件
cp -rf ${project.basedir}/target/${project.artifactId}-${project.version}/* $base/../deploy/${project.artifactId}
-cp -rf ${project.basedir}/target/${project.artifactId}-${project.version}.jar $base/../deploy/${project.artifactId}
# 启动服务
cd $base/../deploy/${project.artifactId}
diff --git a/pom.xml b/pom.xml
index 140e14a..79fb815 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,15 +16,16 @@
pom
https://gitee.com/acgist/taoyao
- 桃夭
- 基于WebRTC实现信令服务,实现Mesh、MCU和SFU三种媒体通信架构,支持直播会议两种场景。
+ taoyao
+ 桃夭:基于WebRTC实现信令服务,实现Mesh、MCU和SFU三种媒体通信架构,支持直播会议两种场景。
2022
17
1.18.24
- 3.0.0
+ 1.0.32006
+ 1.6.12
1.5.3.Final
4.4
@@ -35,8 +36,8 @@
taoyao-nat
+ taoyao-boot
taoyao-live
- taoyao-model
taoyao-media
taoyao-client
taoyao-signal
@@ -46,6 +47,20 @@
+
+
+ org.mapstruct
+ mapstruct
+
+
+ org.mapstruct
+ mapstruct-processor
+
+
+ org.projectlombok
+ lombok
+
+
org.apache.commons
commons-lang3
@@ -54,6 +69,30 @@
org.apache.commons
commons-collections4
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
org.springframework.boot
@@ -71,12 +110,12 @@
com.acgist
- taoyao-live
+ taoyao-boot
${project.version}
com.acgist
- taoyao-model
+ taoyao-live
${project.version}
@@ -121,30 +160,19 @@
com.acgist
- taoyao-webrtc-mesh
+ taoyao-webrtc-mix
${project.version}
com.acgist
- taoyao-webrtc-native
+ taoyao-webrtc-mesh
${project.version}
-
+
- org.mapstruct
- mapstruct
- ${mapstruct.version}
-
-
- org.mapstruct
- mapstruct-processor
- ${mapstruct.version}
-
-
-
- org.projectlombok
- lombok
- ${lombok.version}
+ org.webrtc
+ google-webrtc
+ ${webrtc.version}
@@ -152,11 +180,25 @@
commons-collections4
${collections4.version}
-
+
- io.springfox
- springfox-boot-starter
- ${springfox.version}
+ org.mapstruct
+ mapstruct
+ ${mapstruct.version}
+ provided
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${mapstruct.version}
+ provided
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
@@ -200,10 +242,6 @@
org.apache.maven.plugins
maven-compiler-plugin
-
- org.apache.maven.plugins
- maven-jar-plugin
-
@@ -281,6 +319,15 @@
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ ${springdoc.version}
+
+
+
test
@@ -305,9 +352,9 @@
- io.springfox
- springfox-boot-starter
- ${springfox.version}
+ org.springdoc
+ springdoc-openapi-ui
+ ${springdoc.version}
provided
@@ -336,9 +383,9 @@
- io.springfox
- springfox-boot-starter
- ${springfox.version}
+ org.springdoc
+ springdoc-openapi-ui
+ ${springdoc.version}
provided
diff --git a/taoyao-boot/pom.xml b/taoyao-boot/pom.xml
new file mode 100644
index 0000000..c9fa638
--- /dev/null
+++ b/taoyao-boot/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ 4.0.0
+
+
+ com.acgist
+ taoyao
+ 1.0.0
+
+
+ taoyao-boot
+ jar
+
+ taoyao-boot
+ 基础:启动模型
+
+
+
+
+ org.springdoc
+ springdoc-openapi-ui
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ true
+
+
+
+
\ No newline at end of file
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/BootAutoConfiguration.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/BootAutoConfiguration.java
new file mode 100644
index 0000000..0e018be
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/BootAutoConfiguration.java
@@ -0,0 +1,210 @@
+package com.acgist.taoyao.boot.config;
+
+import java.lang.management.ManagementFactory;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.slf4j.ILoggerFactory;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.ConversionNotSupportedException;
+import org.springframework.beans.TypeMismatchException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
+import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.task.TaskExecutorBuilder;
+import org.springframework.boot.task.TaskSchedulerBuilder;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.task.TaskExecutor;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.scheduling.TaskScheduler;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.validation.BindException;
+import org.springframework.web.HttpMediaTypeNotAcceptableException;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingPathVariableException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.ServletRequestBindingException;
+import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
+import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import com.acgist.taoyao.boot.controller.TaoyaoControllerAdvice;
+import com.acgist.taoyao.boot.controller.TaoyaoErrorController;
+import com.acgist.taoyao.boot.interceptor.SecurityInterceptor;
+import com.acgist.taoyao.boot.model.MessageCode;
+import com.acgist.taoyao.boot.utils.ErrorUtils;
+import com.acgist.taoyao.boot.utils.FileUtils;
+import com.acgist.taoyao.boot.utils.JSONUtils;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import ch.qos.logback.classic.LoggerContext;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 全局配置
+ *
+ * @author acgist
+ */
+@Slf4j
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@Import({
+ TaskExecutionAutoConfiguration.class,
+ TaskSchedulingAutoConfiguration.class
+})
+@EnableAsync
+@Configuration
+@EnableScheduling
+@EnableAspectJAutoProxy(exposeProxy = true)
+@EnableConfigurationProperties({ TaoyaoProperties.class, SecurityProperties.class })
+public class BootAutoConfiguration {
+
+ @Value("${spring.application.name:taoyao}")
+ private String name;
+
+ @Autowired
+ private ApplicationContext context;
+
+ @Bean
+ @Primary
+ @ConditionalOnMissingBean
+ public ObjectMapper objectMapper() {
+ return JSONUtils.buildMapper();
+ }
+
+ @Bean
+ @Primary
+ @ConditionalOnMissingBean
+ public TaskExecutor taskExecutor(TaskExecutorBuilder builder) {
+ return builder.build();
+ }
+
+ @Bean
+ @Primary
+ @ConditionalOnMissingBean
+ public TaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
+ return builder.build();
+ }
+
+ @Bean
+ public CommandLineRunner successCommandLineRunner() {
+ return new CommandLineRunner() {
+ @Override
+ public void run(String ... args) throws Exception {
+ log.info("项目启动成功:{}", BootAutoConfiguration.this.name);
+ }
+ };
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public SecurityInterceptor securityInterceptor() {
+ return new SecurityInterceptor();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public TaoyaoErrorController taoyaoErrorController() {
+ return new TaoyaoErrorController();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public TaoyaoControllerAdvice taoyaoControllerAdvice() {
+ return new TaoyaoControllerAdvice();
+ }
+
+ @PostConstruct
+ public void init() {
+ final var runtime = Runtime.getRuntime();
+ final var runtimeMXBean = ManagementFactory.getRuntimeMXBean();
+ final String freeMemory = FileUtils.formatSize(runtime.freeMemory());
+ final String totalMemory = FileUtils.formatSize(runtime.totalMemory());
+ final String maxMemory = FileUtils.formatSize(runtime.maxMemory());
+ log.info("操作系统名称:{}", System.getProperty("os.name"));
+ log.info("操作系统架构:{}", System.getProperty("os.arch"));
+ log.info("操作系统版本:{}", System.getProperty("os.version"));
+ log.info("操作系统可用处理器数量:{}", runtime.availableProcessors());
+ log.info("Java版本:{}", System.getProperty("java.version"));
+ log.info("Java主目录:{}", System.getProperty("java.home"));
+ log.info("Java库目录:{}", System.getProperty("java.library.path"));
+ log.info("ClassPath:{}", System.getProperty("java.class.path"));
+ log.info("虚拟机名称:{}", System.getProperty("java.vm.name"));
+ log.info("虚拟机参数:{}", runtimeMXBean.getInputArguments().stream().collect(Collectors.joining(" ")));
+ log.info("虚拟机空闲内存:{}", freeMemory);
+ log.info("虚拟机已用内存:{}", totalMemory);
+ log.info("虚拟机最大内存:{}", maxMemory);
+ log.info("工作目录:{}", System.getProperty("user.dir"));
+ log.info("用户目录:{}", System.getProperty("user.home"));
+ log.info("临时目录:{}", System.getProperty("java.io.tmpdir"));
+ log.info("文件编码:{}", System.getProperty("file.encoding"));
+ this.context.getBeansOfType(TaskExecutor.class).forEach((k, v) -> {
+ log.info("系统任务线程池:{}-{}", k, v);
+ });
+ this.context.getBeansOfType(TaskScheduler.class).forEach((k, v) -> {
+ log.info("系统定时任务线程池:{}-{}", k, v);
+ });
+ this.registerException();
+ }
+
+ /**
+ * 异常注册
+ */
+ public void registerException() {
+ ErrorUtils.register(MessageCode.CODE_3400, BindException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, TypeMismatchException.class);
+ ErrorUtils.register(MessageCode.CODE_3404, NoHandlerFoundException.class);
+ ErrorUtils.register(MessageCode.CODE_3503, AsyncRequestTimeoutException.class);
+ ErrorUtils.register(MessageCode.CODE_3500, MissingPathVariableException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, ServletRequestBindingException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, HttpMessageNotReadableException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, MethodArgumentNotValidException.class);
+ ErrorUtils.register(MessageCode.CODE_3500, ConversionNotSupportedException.class);
+ ErrorUtils.register(MessageCode.CODE_3500, HttpMessageNotWritableException.class);
+ ErrorUtils.register(MessageCode.CODE_3415, HttpMediaTypeNotSupportedException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, MissingServletRequestPartException.class);
+ ErrorUtils.register(MessageCode.CODE_3406, HttpMediaTypeNotAcceptableException.class);
+ ErrorUtils.register(MessageCode.CODE_3405, HttpRequestMethodNotSupportedException.class);
+ ErrorUtils.register(MessageCode.CODE_3400, MissingServletRequestParameterException.class);
+ }
+
+ @PreDestroy
+ public void destroy() {
+ log.info("系统关闭:{}", this.name);
+ // 刷出日志缓存
+ final ILoggerFactory factory = LoggerFactory.getILoggerFactory();
+ if (factory instanceof LoggerContext context) {
+ context.stop();
+ }
+ // 定时强制关机
+ final Timer timer = new Timer(true);
+ timer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ // 强制关机:无效
+// System.exit(0);
+ // 强制关机
+ Runtime.getRuntime().halt(0);
+ }
+ }, 5000);
+ }
+
+}
\ No newline at end of file
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/FormatStyle.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/FormatStyle.java
new file mode 100644
index 0000000..4b1187c
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/FormatStyle.java
@@ -0,0 +1,133 @@
+package com.acgist.taoyao.boot.config;
+
+import java.time.format.DateTimeFormatter;
+
+import lombok.Getter;
+
+/**
+ * 格式
+ *
+ * @author acgist
+ */
+public interface FormatStyle {
+
+ /**
+ * 默认日期格式
+ */
+ public static final String YYYY_MM_DD_HH24_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+ /**
+ * 日期时间
+ *
+ * @author acgist
+ */
+ @Getter
+ public static enum DateTimeStyle {
+
+ // YYYY
+ YYYYMMDD_HH24_MM("yyyyMMdd HH:mm"),
+ YYYY_MM_DD_HH24_MM("yyyy-MM-dd HH:mm"),
+ YYYYMMDDHH24MMSS("yyyyMMddHHmmss"),
+ YYYYMMDDHH24MMSSSSS("yyyyMMddHHmmssSSS"),
+ YYYYMMDD_HH24_MM_SS("yyyyMMdd HH:mm:ss"),
+ YYYYMMDD_HH24_MM_SS_SSS("yyyyMMdd HH:mm:ss.SSS"),
+ YYYY_MM_DD_HH24_MM_SS("yyyy-MM-dd HH:mm:ss"),
+ YYYY_MM_DD_HH24_MM_SS_SSS("yyyy-MM-dd HH:mm:ss.SSS"),
+ // YY
+ YYMMDD_HH24_MM("yyMMdd HH:mm"),
+ YY_MM_DD_HH24_MM("yy-MM-dd HH:mm"),
+ YYMMDDHH24MMSS("yyMMddHHmmss"),
+ YYMMDDHH24MMSSSSS("yyMMddHHmmssSSS"),
+ YYMMDD_HH24_MM_SS("yyMMdd HH:mm:ss"),
+ YYMMDD_HH24_MM_SS_SSS("yyMMdd HH:mm:ss.SSS"),
+ YY_MM_DD_HH24_MM_SS("yy-MM-dd HH:mm:ss"),
+ YY_MM_DD_HH24_MM_SS_SSS("yy-MM-dd HH:mm:ss.SSS"),
+ // ISO
+ YY_MM_DD_HH24_MM_SS_ISO("yy-MM-dd'T'HH:mm:ss"),
+ YY_MM_DD_HH24_MM_SS_SSS_ISO("yy-MM-dd'T'HH:mm:ss.SSS"),
+ YYYY_MM_DD_HH24_MM_SS_ISO("yyyy-MM-dd'T'HH:mm:ss"),
+ YYYY_MM_DD_HH24_MM_SS_SSS_ISO("yyyy-MM-dd'T'HH:mm:ss.SSS"),
+ // UTC
+ YY_MM_DD_HH24_MM_SS_UTC("yy-MM-dd'T'HH:mm:ss'Z'"),
+ YY_MM_DD_HH24_MM_SS_SSS_UTC("yy-MM-dd'T'HH:mm:ss.SSS'Z'"),
+ YYYY_MM_DD_HH24_MM_SS_UTC("yyyy-MM-dd'T'HH:mm:ss'Z'"),
+ YYYY_MM_DD_HH24_MM_SS_SSS_UTC("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
+
+ /**
+ * 格式
+ */
+ private final String format;
+ /**
+ * 格式工具
+ */
+ private final DateTimeFormatter dateTimeFormatter;
+
+ private DateTimeStyle(String format) {
+ this.format = format;
+ this.dateTimeFormatter = DateTimeFormatter.ofPattern(format);
+ }
+
+ }
+
+ /**
+ * 时间
+ *
+ * @author acgist
+ */
+ @Getter
+ public static enum TimeStyle {
+
+ HH24("HH"),
+ HH24MM("HHmm"),
+ HH24_MM("HH:mm"),
+ HH24MMSS("HHmmss"),
+ HH24_MM_SS("HH:mm:ss"),
+ HH24MMSSSSS("HHmmssSSS"),
+ HH24_MM_SS_SSS("HH:mm:ss.SSS");
+
+ /**
+ * 格式
+ */
+ private final String format;
+ /**
+ * 格式工具
+ */
+ private final DateTimeFormatter dateTimeFormatter;
+
+ private TimeStyle(String format) {
+ this.format = format;
+ this.dateTimeFormatter = DateTimeFormatter.ofPattern(format);
+ }
+
+ }
+
+ /**
+ * 日期
+ *
+ * @author acgist
+ */
+ @Getter
+ public static enum DateStyle {
+
+ YYMMDD("yyMMdd"),
+ YYYYMMDD("yyyyMMdd"),
+ YY_MM_DD("yy-MM-dd"),
+ YYYY_MM_DD("yyyy-MM-dd");
+
+ /**
+ * 格式
+ */
+ private String format;
+ /**
+ * 格式工具
+ */
+ private final DateTimeFormatter dateTimeFormatter;
+
+ private DateStyle(String format) {
+ this.format = format;
+ this.dateTimeFormatter = DateTimeFormatter.ofPattern(format);
+ }
+
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/OpenApiAutoConfiguration.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/OpenApiAutoConfiguration.java
new file mode 100644
index 0000000..efe75a4
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/OpenApiAutoConfiguration.java
@@ -0,0 +1,125 @@
+package com.acgist.taoyao.boot.config;
+
+import org.springdoc.core.GroupedOpenApi;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.ExternalDocumentation;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.info.License;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+
+/**
+ * 文档配置
+ *
+ * @author acgist
+ */
+@Profile("dev")
+@Configuration
+@ConditionalOnClass(OpenAPI.class)
+public class OpenApiAutoConfiguration {
+
+ @Autowired
+ private TaoyaoProperties taoyaoProperties;
+
+ @Bean
+ public GroupedOpenApi liveApi() {
+ return GroupedOpenApi.builder()
+ .group("live")
+ .displayName("直播")
+ .packagesToScan("com.acgist.taoyao.live")
+ .build();
+ }
+
+ @Bean
+ public GroupedOpenApi meetingApi() {
+ return GroupedOpenApi.builder()
+ .group("meeting")
+ .displayName("会议")
+ .packagesToScan("com.acgist.taoyao.meeting")
+ .build();
+ }
+
+ @Bean
+ public GroupedOpenApi taoyaoApi() {
+ return GroupedOpenApi.builder()
+ .group("taoyao")
+ .displayName("桃夭")
+ .packagesToScan("com.acgist.taoyao")
+ .build();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean
+ public OpenAPI openAPI() {
+ return new OpenAPI()
+ .info(this.buildInfo())
+ .externalDocs(this.buildExternalDocumentation())
+ .addSecurityItem(this.buildSecurityRequirement())
+ .components(this.buildComponents());
+ }
+
+ /**
+ * @return 文档基本信息
+ */
+ private Info buildInfo() {
+ return new Info()
+ .title(this.taoyaoProperties.getName())
+ .version(this.taoyaoProperties.getVersion())
+ .description(this.taoyaoProperties.getDescription())
+ .license(this.buildLicense());
+ }
+
+ /**
+ * @return 授权协议信息
+ */
+ private License buildLicense() {
+ return new License()
+ .name("Apache 2.0")
+ .url("https://www.apache.org/licenses/LICENSE-2.0.html");
+ }
+
+ /**
+ * @return 外部文档信息
+ */
+ private ExternalDocumentation buildExternalDocumentation() {
+ return new ExternalDocumentation()
+ .description(this.taoyaoProperties.getDescription())
+ .url(this.taoyaoProperties.getUrl());
+ }
+
+ /**
+ * @return 授权
+ */
+ private SecurityRequirement buildSecurityRequirement() {
+ return new SecurityRequirement()
+ .addList(SecurityProperties.BASIC);
+ }
+
+ /**
+ * @return 授权
+ */
+ private Components buildComponents() {
+ return new Components()
+ .addSecuritySchemes(SecurityProperties.BASIC, this.buildSecurityScheme());
+ }
+
+ /**
+ * @return 授权
+ */
+ private SecurityScheme buildSecurityScheme() {
+ return new SecurityScheme()
+ .name(SecurityProperties.BASIC)
+ .scheme(SecurityProperties.BASIC)
+ .in(SecurityScheme.In.HEADER)
+ .type(SecurityScheme.Type.HTTP);
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/SecurityProperties.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/SecurityProperties.java
new file mode 100644
index 0000000..f402ca6
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/SecurityProperties.java
@@ -0,0 +1,47 @@
+package com.acgist.taoyao.boot.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 安全配置
+ *
+ * @author acgist
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "taoyao.security")
+public class SecurityProperties {
+
+ /**
+ * Basic认证
+ */
+ public static final String BASIC = "Basic";
+
+ /**
+ * 范围
+ */
+ private String realm;
+ /**
+ * 公共地址
+ */
+ private String[] permit;
+ /**
+ * 用户
+ */
+ private String username;
+ /**
+ * 密码
+ */
+ private String password;
+
+ /**
+ * @return 授权信息
+ */
+ public String getAuthorization() {
+ return this.username + ":" + this.password;
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java
new file mode 100644
index 0000000..6c59eb8
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/TaoyaoProperties.java
@@ -0,0 +1,43 @@
+package com.acgist.taoyao.boot.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 系统配置
+ *
+ * @author acgist
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "taoyao")
+public class TaoyaoProperties {
+
+ private TaoyaoProperties() {
+ }
+
+ /**
+ * 地址
+ */
+ private String url;
+ /**
+ * 名称
+ */
+ private String name;
+ /**
+ * 超时时间
+ */
+ private Long timeout;
+ /**
+ * 版本
+ */
+ private String version;
+ /**
+ * 描述
+ */
+ private String description;
+
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebMvcConfigurerAutoConfiguration.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebMvcConfigurerAutoConfiguration.java
new file mode 100644
index 0000000..df3dfbc
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/config/WebMvcConfigurerAutoConfiguration.java
@@ -0,0 +1,30 @@
+package com.acgist.taoyao.boot.config;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.acgist.taoyao.boot.interceptor.SecurityInterceptor;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * MVC配置
+ *
+ * @author acgist
+ */
+@Slf4j
+@Configuration
+public class WebMvcConfigurerAutoConfiguration implements WebMvcConfigurer {
+
+ @Autowired
+ private SecurityInterceptor securityInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ log.info("加载拦截器:securityInterceptor");
+ registry.addInterceptor(this.securityInterceptor).addPathPatterns("/**");
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoControllerAdvice.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoControllerAdvice.java
new file mode 100644
index 0000000..5ad7448
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoControllerAdvice.java
@@ -0,0 +1,25 @@
+package com.acgist.taoyao.boot.controller;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.acgist.taoyao.boot.model.Message;
+import com.acgist.taoyao.boot.utils.ErrorUtils;
+
+/**
+ * 统一异常处理
+ *
+ * @author acgist
+ */
+@RestControllerAdvice
+public class TaoyaoControllerAdvice {
+
+ @ExceptionHandler(Exception.class)
+ public Message exception(Exception e, HttpServletRequest request, HttpServletResponse response) {
+ return ErrorUtils.message(e, request, response);
+ }
+
+}
\ No newline at end of file
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoErrorController.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoErrorController.java
new file mode 100644
index 0000000..d78981e
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/controller/TaoyaoErrorController.java
@@ -0,0 +1,31 @@
+package com.acgist.taoyao.boot.controller;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.acgist.taoyao.boot.model.Message;
+import com.acgist.taoyao.boot.utils.ErrorUtils;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+/**
+ * 统一错误页面
+ *
+ * @author acgist
+ */
+@Tag(name = "统一错误页面", description = "全局统一错误页面")
+@RestController
+public class TaoyaoErrorController implements ErrorController {
+
+ @Operation(summary = "统一错误地址", description = "全局统一错误地址")
+ @RequestMapping(value = ErrorUtils.ERROR_PATH)
+ public Message index(HttpServletRequest request, HttpServletResponse response) {
+ return ErrorUtils.message(request, response);
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/interceptor/SecurityInterceptor.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/interceptor/SecurityInterceptor.java
new file mode 100644
index 0000000..bf12cae
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/interceptor/SecurityInterceptor.java
@@ -0,0 +1,99 @@
+package com.acgist.taoyao.boot.interceptor;
+
+import java.util.Base64;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import com.acgist.taoyao.boot.config.SecurityProperties;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 安全拦截
+ *
+ * @author acgist
+ */
+@Slf4j
+public class SecurityInterceptor implements HandlerInterceptor {
+
+ /**
+ * 时间
+ */
+ private ThreadLocal local = new ThreadLocal<>();
+
+ @Autowired
+ private SecurityProperties securityProperties;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ if(this.permit(request) || this.authorization(request)) {
+ this.local.set(System.currentTimeMillis());
+ return true;
+ }
+ response.setStatus(HttpStatus.UNAUTHORIZED.value());
+ response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic Realm=\"" + this.securityProperties.getRealm() + "\"");
+ return false;
+ }
+
+ /**
+ * @param request 请求
+ *
+ * @return 是否公共请求
+ */
+ private boolean permit(HttpServletRequest request) {
+ final String uri = request.getRequestURI();
+ if(ArrayUtils.isEmpty(this.securityProperties.getPermit())) {
+ return false;
+ }
+ for (String permit : this.securityProperties.getPermit()) {
+ if(uri.startsWith(permit)) {
+ log.debug("授权成功(公共请求):{}-{}", uri, permit);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param request 请求
+ *
+ * @return 是否授权成功
+ */
+ private boolean authorization(HttpServletRequest request) {
+ final String uri = request.getRequestURI();
+ String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
+ if(StringUtils.isEmpty(authorization)) {
+ return false;
+ }
+ final int index = authorization.indexOf(' ');
+ if(index < 0 || !authorization.substring(0, index).equalsIgnoreCase(SecurityProperties.BASIC)) {
+ return false;
+ }
+ authorization = authorization.substring(index).strip();
+ authorization = new String(Base64.getDecoder().decode(authorization));
+ if(!authorization.equals(this.securityProperties.getAuthorization())) {
+ return false;
+ }
+ log.debug("授权成功(Basic):{}-{}", uri, authorization);
+ return true;
+ }
+
+ @Override
+ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+ final long duration;
+ final Long last = this.local.get();
+ if(last != null && (duration = System.currentTimeMillis() - last) > 1000) {
+ log.info("执行时间过慢:{}-{}", request.getRequestURI(), duration);
+ }
+ this.local.remove();
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java
new file mode 100644
index 0000000..df5633d
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Message.java
@@ -0,0 +1,166 @@
+package com.acgist.taoyao.boot.model;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.acgist.taoyao.boot.utils.JSONUtils;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * 响应消息
+ *
+ * @author acgist
+ *
+ * @param 消息类型
+ */
+@Getter
+@Setter
+public class Message implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 响应编码
+ */
+ private String code;
+ /**
+ * 响应描述
+ */
+ private String message;
+ /**
+ * 消息内容
+ */
+ private T body;
+
+ /**
+ * 成功消息
+ *
+ * @param 消息类型
+ *
+ * @return 成功消息
+ */
+ public static final Message success() {
+ return success(null);
+ }
+
+ /**
+ * 成功消息
+ *
+ * @param 消息类型
+ *
+ * @param body 消息内容
+ *
+ * @return 成功消息
+ */
+ public static final Message success(T body) {
+ final Message message = new Message<>();
+ message.code = MessageCode.CODE_0000.getCode();
+ message.message = MessageCode.CODE_0000.getMessage();
+ message.body = body;
+ return message;
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @return 错误消息
+ */
+ public static final Message fail() {
+ return fail(MessageCode.CODE_9999);
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @param message 消息内容
+ *
+ * @return 错误消息
+ */
+ public static final Message fail(String message) {
+ return fail(MessageCode.CODE_9999, message);
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @param code 错误编码
+ *
+ * @return 错误消息
+ */
+ public static final Message fail(MessageCode code) {
+ return fail(code, null);
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @param code 错误编码
+ * @param message 错误描述
+ *
+ * @return 错误消息
+ */
+ public static final Message fail(MessageCode code, String message) {
+ final Message failMessage = new Message<>();
+ failMessage.code = code.getCode();
+ if (StringUtils.isEmpty(message)) {
+ failMessage.message = code.getMessage();
+ } else {
+ failMessage.message = message;
+ }
+ return failMessage;
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @param code 错误编码
+ * @param body 消息内容
+ *
+ * @return 错误消息
+ */
+ public static final Message fail(MessageCode code, T body) {
+ final Message message = new Message<>();
+ message.code = code.getCode();
+ message.message = code.getMessage();
+ message.body = body;
+ return message;
+ }
+
+ /**
+ * 错误消息
+ *
+ * @param 消息类型
+ *
+ * @param code 错误编码
+ * @param message 错误描述
+ * @param body 消息内容
+ *
+ * @return 错误消息
+ */
+ public static final Message fail(MessageCode code, String message, T body) {
+ final Message failMessage = new Message<>();
+ failMessage.code = code.getCode();
+ failMessage.message = message;
+ failMessage.body = body;
+ return failMessage;
+ }
+
+ @Override
+ public String toString() {
+ return JSONUtils.toJSON(this);
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCode.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCode.java
new file mode 100644
index 0000000..0e46da4
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCode.java
@@ -0,0 +1,98 @@
+package com.acgist.taoyao.boot.model;
+
+import lombok.Getter;
+
+/**
+ * 状态编码
+ *
+ * 1xxx=前置错误:前置校验错误(数据校验)
+ * 2xxx=内部错误:服务内部错误
+ * 3xxx=请求错误:HTTP错误
+ * 9999=未知错误:没有适配异常
+ *
+ * @author acgist
+ */
+@Getter
+public enum MessageCode {
+
+ // 成功
+ CODE_0000("0000", 200, "成功"),
+ // 1xxx
+ CODE_1000("1000", 404, "未知接口"),
+ CODE_1001("1001", 400, "上次请求没有完成"),
+ CODE_1002("1002", 400, "数据格式错误"),
+ CODE_1003("1003", 400, "验签失败"),
+ // 2xxx
+ CODE_2000("2000", 500, "服务错误"),
+ CODE_2001("2001", 504, "服务超时"),
+ // 3xxx
+ CODE_3400("3400", 400, "请求错误"),
+ CODE_3401("3401", 401, "没有授权"),
+ CODE_3403("3403", 403, "请求拒绝"),
+ CODE_3404("3404", 404, "资源失效"),
+ CODE_3405("3405", 405, "请求方法错误"),
+ CODE_3406("3406", 406, "请求不可接受"),
+ CODE_3415("3415", 415, "请求资源类型错误"),
+ CODE_3500("3500", 500, "系统异常"),
+ CODE_3502("3502", 502, "服务无效"),
+ CODE_3503("3503", 503, "服务正在维护"),
+ CODE_3504("3504", 504, "服务超时"),
+ // 9999
+ CODE_9999("9999", 500, "未知错误");
+
+ /**
+ * HTTP状态编码头部
+ */
+ public static final String HTTP_STATUS = "3";
+
+ /**
+ * 状态编码
+ */
+ private final String code;
+ /**
+ * 状态数值
+ */
+ private final Integer status;
+ /**
+ * 状态信息
+ */
+ private final String message;
+
+ private MessageCode(String code, Integer status, String message) {
+ this.code = code;
+ this.status = status;
+ this.message = message;
+ }
+
+ /**
+ * @param code 状态编码
+ *
+ * @return 状态编码
+ */
+ public static final MessageCode of(String code) {
+ final MessageCode[] values = MessageCode.values();
+ for (MessageCode value : values) {
+ if (value.code.equals(code)) {
+ return value;
+ }
+ }
+ return CODE_9999;
+ }
+
+ /**
+ * @param status HTTPStatus
+ *
+ * @return 状态编码
+ */
+ public static final MessageCode of(Integer status) {
+ final String code = HTTP_STATUS + status;
+ final MessageCode[] values = MessageCode.values();
+ for (MessageCode value : values) {
+ if (value.code.equals(code)) {
+ return value;
+ }
+ }
+ return of(String.valueOf(status));
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java
new file mode 100644
index 0000000..873c132
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/MessageCodeException.java
@@ -0,0 +1,123 @@
+package com.acgist.taoyao.boot.model;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 状态编码异常
+ *
+ * @author acgist
+ */
+public class MessageCodeException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 状态编码
+ */
+ private final MessageCode code;
+
+ /**
+ * @param messages 错误消息
+ *
+ * @return 状态编码异常
+ */
+ public static final MessageCodeException of(Object ... messages) {
+ return of(null, MessageCode.CODE_9999, messages);
+ }
+
+ /**
+ * @param t 异常
+ * @param messages 错误消息
+ *
+ * @return 状态编码异常
+ */
+ public static final MessageCodeException of(Throwable t, Object ... messages) {
+ return of(t, MessageCode.CODE_9999, messages);
+ }
+
+ /**
+ * @param code 状态编码
+ * @param messages 错误消息
+ *
+ * @return 状态编码异常
+ */
+ public static final MessageCodeException of(MessageCode code, Object ... messages) {
+ return of(null, code, messages);
+ }
+
+ /**
+ * @param t 异常
+ * @param code 状态编码
+ * @param messages 错误消息
+ *
+ * @return 状态编码异常
+ */
+ public static final MessageCodeException of(Throwable t, MessageCode code, Object ... messages) {
+ final String message;
+ if(ArrayUtils.isEmpty(messages)) {
+ message = Objects.isNull(t) ? code.getMessage() : t.getMessage();
+ } else {
+ // 拼接错误描述
+ final StringBuilder builder = new StringBuilder();
+ for (Object value : messages) {
+ builder.append(value);
+ }
+ message = builder.toString();
+ }
+ return new MessageCodeException(code, message, t);
+ }
+
+ /**
+ * @param code 状态编码
+ */
+ public MessageCodeException(MessageCode code) {
+ this(code, code.getMessage());
+ }
+
+ /**
+ * @param code 状态编码
+ * @param message 错误消息
+ */
+ public MessageCodeException(MessageCode code, String message) {
+ this(code, message, null);
+ }
+
+ /**
+ * @param code 状态编码
+ * @param t 异常
+ */
+ public MessageCodeException(MessageCode code, Throwable t) {
+ this(code, Objects.isNull(t) ? code.getMessage() : t.getMessage(), t);
+ }
+
+ /**
+ * @param code 状态编码
+ * @param message 错误消息
+ * @param t 异常
+ */
+ public MessageCodeException(MessageCode code, String message, Throwable t) {
+ super(message, t);
+ this.code = code;
+ }
+
+ /**
+ * @return 状态编码
+ */
+ public MessageCode getCode() {
+ return this.code;
+ }
+
+ @Override
+ public String getMessage() {
+ final String message = super.getMessage();
+ if (StringUtils.isEmpty(message)) {
+ return this.code.getMessage();
+ } else {
+ return message;
+ }
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Model.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Model.java
new file mode 100644
index 0000000..47f6b78
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/Model.java
@@ -0,0 +1,42 @@
+package com.acgist.taoyao.boot.model;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import com.acgist.taoyao.boot.utils.JSONUtils;
+
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * Model
+ *
+ * @author acgist
+ */
+@Getter
+@Setter
+@EqualsAndHashCode(callSuper = false, of = "id")
+public abstract class Model implements Cloneable, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * ID
+ */
+ private Long id;
+ /**
+ * 创建时间
+ */
+ private LocalDateTime createDate;
+ /**
+ * 修改时间
+ */
+ private LocalDateTime modifyDate;
+
+ @Override
+ public String toString() {
+ return JSONUtils.toJSON(this);
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/ModifyOptional.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/ModifyOptional.java
new file mode 100644
index 0000000..e422d90
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/model/ModifyOptional.java
@@ -0,0 +1,52 @@
+package com.acgist.taoyao.boot.model;
+
+import java.util.function.Supplier;
+
+/**
+ * 可修改的Optional
+ *
+ * @author acgist
+ */
+public final class ModifyOptional {
+
+ /**
+ * 值
+ */
+ private T t;
+ /**
+ * 生产者
+ */
+ private Supplier supplier;
+
+ private ModifyOptional(T t, Supplier supplier) {
+ this.t = t;
+ this.supplier = supplier;
+ }
+
+ public static final ModifyOptional of(T t) {
+ return new ModifyOptional<>(t, null);
+ }
+
+ public static final ModifyOptional of(Supplier supplier) {
+ return new ModifyOptional<>(null, supplier);
+ }
+
+ /**
+ * @return 值
+ */
+ public T get() {
+ return this.t;
+ }
+
+ /**
+ * @return 值
+ */
+ public T build() {
+ if(this.t != null) {
+ return this.t;
+ }
+ this.t = this.supplier.get();
+ return this.t;
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java
new file mode 100644
index 0000000..4979457
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/IdService.java
@@ -0,0 +1,17 @@
+package com.acgist.taoyao.boot.service;
+
+/**
+ * ID生成
+ *
+ * @author acgist
+ */
+public interface IdService {
+
+ /**
+ * 生成十八位的ID:YYMMddHHmmss + sn + xxxx
+ *
+ * @return ID
+ */
+ Long id();
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java
new file mode 100644
index 0000000..3a976f1
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/service/impl/IdServiceImpl.java
@@ -0,0 +1,5 @@
+package com.acgist.taoyao.boot.service.impl;
+
+public class IdServiceImpl {
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/BeanUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/BeanUtils.java
new file mode 100644
index 0000000..46b1f3d
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/BeanUtils.java
@@ -0,0 +1,36 @@
+package com.acgist.taoyao.boot.utils;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Bean工具
+ *
+ * @author acgist
+ */
+@Slf4j
+public final class BeanUtils {
+
+ private BeanUtils() {
+ }
+
+ /**
+ * @param 类型
+ *
+ * @param clazz 类型
+ *
+ * @return 实例
+ */
+ public static final T newInstance(Class clazz) {
+ Objects.requireNonNull(clazz, "无效类型");
+ try {
+ return clazz.getDeclaredConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
+ log.error("创建类型实例异常:{}", clazz, e);
+ }
+ return null;
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/DateUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/DateUtils.java
new file mode 100644
index 0000000..e610143
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/DateUtils.java
@@ -0,0 +1,184 @@
+package com.acgist.taoyao.boot.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.acgist.taoyao.boot.config.FormatStyle.DateStyle;
+import com.acgist.taoyao.boot.config.FormatStyle.DateTimeStyle;
+import com.acgist.taoyao.boot.config.FormatStyle.TimeStyle;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 日期工具
+ *
+ * @author acgist
+ */
+@Slf4j
+public final class DateUtils {
+
+ private DateUtils() {
+ }
+
+ /**
+ * 生成时间戳
+ *
+ * @return 时间戳
+ *
+ * @see #buildTime(LocalDateTime)
+ */
+ public static final String buildTime() {
+ return buildTime(LocalDateTime.now());
+ }
+
+ /**
+ * 生成时间戳
+ *
+ * @param localDateTime 时间
+ *
+ * @return 时间戳
+ */
+ public static final String buildTime(LocalDateTime localDateTime) {
+ if (Objects.isNull(localDateTime)) {
+ return buildTime();
+ }
+ return DateTimeStyle.YYYYMMDDHH24MMSS.getDateTimeFormatter().format(localDateTime);
+ }
+
+ /**
+ * 日期字符串转换日期
+ *
+ * @param value 日期字符串
+ * @param format 格式
+ *
+ * @return 日期
+ */
+ public static final Date parse(String value, String format) {
+ if(StringUtils.isEmpty(value) || StringUtils.isEmpty(format)) {
+ return null;
+ }
+ try {
+ final SimpleDateFormat formatter = new SimpleDateFormat(format);
+ return formatter.parse(value);
+ } catch (ParseException e) {
+ log.error("字符串转换日期异常:{}-{}", value, format, e);
+ }
+ return null;
+ }
+
+ /**
+ * 日期格式化字符串
+ *
+ * @param date 日期
+ * @param format 格式
+ *
+ * @return 日期字符串
+ */
+ public static final String format(Date date, String format) {
+ if(date == null || StringUtils.isEmpty(format)) {
+ return null;
+ }
+ final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
+ return simpleDateFormat.format(date);
+ }
+
+ /**
+ * 日期转化
+ *
+ * @param date Date
+ *
+ * @return LocalDate
+ */
+ public static final LocalDate toLocalDate(Date date) {
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ }
+
+ /**
+ * 日期转化
+ *
+ * @param date Date
+ *
+ * @return LocalDateTime
+ */
+ public static final LocalDateTime toLocalDateTime(Date date) {
+ return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+ }
+
+ /**
+ * 日期转化
+ *
+ * @param localDate LocalDate
+ *
+ * @return Date
+ */
+ public static final Date toDate(LocalDate localDate) {
+ return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+ }
+
+ /**
+ * 日期转化
+ *
+ * @param localDateTime LocalDateTime
+ *
+ * @return Date
+ */
+ public static final Date toDate(LocalDateTime localDateTime) {
+ return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+ }
+
+ /**
+ * 转换毫秒
+ *
+ * @param localDateTime LocalDateTime
+ *
+ * @return 毫秒
+ */
+ public static final long toMilli(LocalDateTime localDateTime) {
+ return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+ }
+
+ /**
+ * 格式化时间
+ *
+ * @param localTime LocalTime
+ * @param format 格式
+ *
+ * @return 时间字符串
+ */
+ public static String format(LocalTime localTime, TimeStyle format) {
+ return localTime != null && format != null ? format.getDateTimeFormatter().format(localTime) : null;
+ }
+
+ /**
+ * 格式化日期
+ *
+ * @param localDate LocalDate
+ * @param format 格式
+ *
+ * @return 日期字符串
+ */
+ public static String format(LocalDate localDate, DateStyle format) {
+ return localDate != null && format != null ? format.getDateTimeFormatter().format(localDate) : null;
+ }
+
+ /**
+ * 格式化日期
+ *
+ * @param localDateTime LocalDateTime
+ * @param format 格式
+ *
+ * @return 日期字符串
+ */
+ public static String format(LocalDateTime localDateTime, DateTimeStyle format) {
+ return localDateTime != null && format != null ? format.getDateTimeFormatter().format(localDateTime) : null;
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java
new file mode 100644
index 0000000..355972a
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ErrorUtils.java
@@ -0,0 +1,223 @@
+package com.acgist.taoyao.boot.utils;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.validation.BindException;
+import org.springframework.validation.ObjectError;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
+
+import com.acgist.taoyao.boot.model.Message;
+import com.acgist.taoyao.boot.model.MessageCode;
+import com.acgist.taoyao.boot.model.MessageCodeException;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 异常处理工具
+ *
+ * @author acgist
+ */
+@Slf4j
+public final class ErrorUtils {
+
+ /**
+ * 异常映射
+ */
+ private static final Map, MessageCode> CODE_MAPPING = new LinkedHashMap<>();
+
+ /**
+ * 错误地址
+ */
+ public static final String ERROR_PATH = "/error";
+ /**
+ * Servlet错误异常
+ */
+ public static final String EXCEPTION_SERVLET = "javax.servlet.error.exception";
+ /**
+ * Servlet错误编码
+ */
+ public static final String SERVLET_STATUS_CODE = "javax.servlet.error.status_code";
+ /**
+ * Servlet错误地址
+ */
+ public static final String SERVLET_REQUEST_URI = "javax.servlet.error.request_uri";
+ /**
+ * SpringBoot异常
+ */
+ public static final String EXCEPTION_SPRINGBOOT = "org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR";
+
+ private ErrorUtils() {
+ }
+
+ /**
+ * 注册异常(注意继承顺序)
+ *
+ * @param code 异常编码
+ * @param clazz 异常类型
+ */
+ public static final void register(MessageCode code, Class> clazz) {
+ log.info("注册异常映射:{}-{}", code, clazz);
+ CODE_MAPPING.put(clazz, code);
+ }
+
+ /**
+ * @param request 请求
+ * @param response 响应
+ *
+ * @return 错误信息
+ */
+ public static final Message message(HttpServletRequest request, HttpServletResponse response) {
+ return message(null, request, response);
+ }
+
+ /**
+ * @param t 异常
+ * @param request 请求
+ * @param response 响应
+ *
+ * @return 错误信息
+ */
+ public static final Message message(Throwable t, HttpServletRequest request, HttpServletResponse response) {
+ final Message message;
+ int status = globalStatus(request, response);
+ final Object globalError = t == null ? globalError(request) : t;
+ final Object rootError = ExceptionUtils.root(globalError);
+ if(rootError instanceof MessageCodeException) {
+ // 自定义的异常
+ final MessageCodeException messageCodeException = (MessageCodeException) rootError;
+ final MessageCode messageCode = messageCodeException.getCode();
+ status = messageCode.getStatus();
+ message = Message.fail(messageCode, messageCodeException.getMessage());
+ } else if(rootError instanceof Throwable) {
+ // 未知异常
+ final Throwable throwable = (Throwable) rootError;
+ final MessageCode messageCode = messageCode(status, throwable);
+ status = messageCode.getStatus();
+ message = Message.fail(messageCode, message(messageCode, throwable));
+ } else {
+ // 没有异常
+ final MessageCode messageCode = MessageCode.of(status);
+// status = messageCode.getStatus();
+ message = Message.fail(messageCode);
+ }
+ // 状态编码
+ if(status == HttpServletResponse.SC_OK) {
+ status = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+ }
+ response.setStatus(status);
+ final String path = Objects.toString(request.getAttribute(SERVLET_REQUEST_URI), request.getServletPath());
+ final String query = request.getQueryString();
+ final String method = request.getMethod();
+ if(globalError instanceof Throwable) {
+ log.error("""
+ 请求错误
+ 请求地址:{}
+ 请求参数:{}
+ 请求方法:{}
+ 错误信息:{}
+ """, path, query, method, message, globalError);
+ } else {
+ log.warn("""
+ 请求错误
+ 请求地址:{}
+ 请求参数:{}
+ 请求方法:{}
+ 错误信息:{}
+ 原始信息:{}
+ """, path, query, method, message, globalError);
+ }
+ return message;
+ }
+
+ /**
+ * @param request 请求
+ * @param response 响应
+ *
+ * @return 响应状态
+ */
+ public static final int globalStatus(HttpServletRequest request, HttpServletResponse response) {
+ final Object status = request.getAttribute(SERVLET_STATUS_CODE);
+ if(status instanceof Integer) {
+ return (Integer) status;
+ }
+ return response.getStatus();
+ }
+
+ /**
+ * @param request 请求
+ *
+ * @return 异常
+ */
+ public static final Object globalError(HttpServletRequest request) {
+ // Servlet错误异常
+ Object throwable = request.getAttribute(EXCEPTION_SERVLET);
+ if(throwable != null) {
+ return throwable;
+ }
+ // SpringBoot异常
+ throwable = request.getAttribute(EXCEPTION_SPRINGBOOT);
+ if(throwable != null) {
+ return throwable;
+ }
+ return throwable;
+ }
+
+ /**
+ * @param status 原始状态
+ * @param t 异常
+ *
+ * @return 响应状态
+ *
+ * @see ResponseEntityExceptionHandler
+ * @see DefaultHandlerExceptionResolver
+ */
+ public static final MessageCode messageCode(int status, Throwable t) {
+ return CODE_MAPPING.entrySet().stream()
+ .filter(entry -> {
+ final Class> clazz = t.getClass();
+ final Class> mappingClazz = entry.getKey();
+ return mappingClazz.equals(clazz) || mappingClazz.isAssignableFrom(clazz);
+ })
+ .map(Map.Entry::getValue)
+ .findFirst()
+ .orElse(MessageCode.of(status));
+ }
+
+ /**
+ * @param messageCode 错误编码
+ * @param t 异常
+ *
+ * @return 异常信息
+ */
+ public static final String message(MessageCode messageCode, Throwable t) {
+ // ValidationException
+ if(
+ t instanceof BindException ||
+ t instanceof MethodArgumentNotValidException
+ ) {
+ final BindException bindException = (BindException) t;
+ final List allErrors = bindException.getAllErrors();
+ return allErrors.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(","));
+ }
+ // 为了系统安全建议不要直接返回
+ final String message = t.getMessage();
+ if(messageCode == MessageCode.CODE_9999 && StringUtils.isNotEmpty(message)) {
+ return message;
+ }
+ if(StringUtils.isNotEmpty(message) && message.length() < 64) {
+ return message;
+ }
+ return messageCode.getMessage();
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ExceptionUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ExceptionUtils.java
new file mode 100644
index 0000000..69e4f9b
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/ExceptionUtils.java
@@ -0,0 +1,43 @@
+package com.acgist.taoyao.boot.utils;
+
+import com.acgist.taoyao.boot.model.MessageCodeException;
+
+/**
+ * 异常工具
+ *
+ * @author acgist
+ */
+public class ExceptionUtils {
+
+ /**
+ * @param t 异常
+ *
+ * @return 原始异常
+ *
+ * @see #root(Throwable)
+ */
+ public static final Object root(Object t) {
+ if(t instanceof Throwable) {
+ return ExceptionUtils.root((Throwable) t);
+ }
+ return t;
+ }
+
+ /**
+ * @param t 异常
+ *
+ * @return 原始异常
+ */
+ public static final Throwable root(Throwable t) {
+ Throwable cause = t;
+ do {
+ // 直接返回状态编码异常
+ if(cause instanceof MessageCodeException) {
+ return cause;
+ }
+ } while(cause != null && (cause = cause.getCause()) != null);
+ // 返回原始异常
+ return t;
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/FileUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/FileUtils.java
new file mode 100644
index 0000000..d5c66be
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/FileUtils.java
@@ -0,0 +1,55 @@
+package com.acgist.taoyao.boot.utils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * 文件工具
+ *
+ * @author acgist
+ */
+public final class FileUtils {
+
+ private FileUtils() {
+ }
+
+ /**
+ * 文件大小单位
+ */
+ private static final String[] UNITS = {"B", "KB", "MB", "GB", "TB", "PB"};
+
+ /**
+ * @param size 文件大小
+ *
+ * @return 文件大小
+ */
+ public static final String formatSize(Long size) {
+ return formatSize(size, "B");
+ }
+
+ /**
+ * @param size 文件大小
+ * @param unit 当前单位
+ *
+ * @return 文件大小
+ */
+ public static final String formatSize(Long size, String unit) {
+ if(size == null || size == 0L) {
+ return "0B";
+ }
+ int index = ArrayUtils.indexOf(UNITS, unit);
+ BigDecimal decimal = BigDecimal.valueOf(size);
+ final BigDecimal dataScale = BigDecimal.valueOf(1024);
+ while(decimal.compareTo(dataScale) >= 0) {
+ if(++index >= UNITS.length) {
+ index = UNITS.length - 1;
+ break;
+ }
+ decimal = decimal.divide(dataScale);
+ }
+ return decimal.setScale(2, RoundingMode.HALF_UP) + UNITS[index];
+ }
+
+}
diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java
new file mode 100644
index 0000000..3850417
--- /dev/null
+++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/JSONUtils.java
@@ -0,0 +1,213 @@
+package com.acgist.taoyao.boot.utils;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+
+import com.acgist.taoyao.boot.config.FormatStyle.DateStyle;
+import com.acgist.taoyao.boot.config.FormatStyle.DateTimeStyle;
+import com.acgist.taoyao.boot.config.FormatStyle.TimeStyle;
+import com.acgist.taoyao.boot.model.MessageCodeException;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.Module;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+
+/**
+ * JSON工具
+ *
+ * @author acgist
+ */
+public final class JSONUtils {
+
+ private JSONUtils() {
+ }
+
+ /**
+ * Mapper(线程安全)
+ */
+ private static final ObjectMapper MAPPER = buildMapper();
+
+ /**
+ * Java转JSON
+ *
+ * @param object Java
+ *
+ * @return JSON
+ */
+ public static final String toJSON(Object object) {
+ if (Objects.isNull(object)) {
+ return null;
+ }
+ try {
+ return MAPPER.writeValueAsString(object);
+ } catch (JsonProcessingException e) {
+ throw MessageCodeException.of(e, "Java转JSON失败:", object);
+ }
+ }
+
+ /**
+ * JSON转Java
+ *
+ * @param Java类型
+ *
+ * @param json JSON
+ *
+ * @return Java
+ */
+ public static final T toJava(String json) {
+ if (Objects.isNull(json)) {
+ return null;
+ }
+ try {
+ return MAPPER.readValue(json, new TypeReference() {
+ });
+ } catch (IOException e) {
+ throw MessageCodeException.of(e, "JSON转Java失败:", json);
+ }
+ }
+
+ /**
+ * JSON转Java
+ *
+ * @param Java类型
+ *
+ * @param json JSON
+ * @param clazz Java类型
+ *
+ * @return Java
+ */
+ public static final T toJava(String json, Class clazz) {
+ if (Objects.isNull(json) || Objects.isNull(clazz)) {
+ return null;
+ }
+ try {
+ return MAPPER.readValue(json, clazz);
+ } catch (IOException e) {
+ throw MessageCodeException.of(e, "JSON转Java失败:", json);
+ }
+ }
+
+ /**
+ * JSON转Map
+ *
+ * @param K类型
+ * @param V类型
+ *
+ * @param json JSON
+ *
+ * @return Map
+ */
+ public static final Map toMap(String json) {
+ if (Objects.isNull(json)) {
+ return Map.of();
+ }
+ try {
+ return MAPPER.readValue(json, new TypeReference