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>() { + }); + } catch (IOException e) { + throw MessageCodeException.of(e, "JSON转Map失败:", json); + } + } + + /** + * JSON转List + * + * @param 元素类型 + * + * @param json JSON + * + * @return List + */ + public static final List toList(String json) { + if (Objects.isNull(json)) { + return List.of(); + } + try { + return MAPPER.readValue(json, new TypeReference>() { + }); + } catch (IOException e) { + throw MessageCodeException.of(e, "JSON转List失败:", json); + } + } + + /** + * JSON转List + * + * @param 元素类型 + * + * @param json JSON + * @param clazz 类型 + * + * @return List + */ + public static final List toList(String json, Class clazz) { + if (Objects.isNull(json)) { + return List.of(); + } + try { +// final JavaType javaType = MAPPER.getTypeFactory().constructParametricType(ArrayList.class, clazz); +// return MAPPER.readValue(json, javaType); + return MAPPER.readValue(json, new TypeReference>() { + }); + } catch (IOException e) { + throw MessageCodeException.of(e, "JSON转List失败:", json); + } + } + + /** + * @return Mapper + */ + public static final ObjectMapper buildMapper() { + final ObjectMapper mapper = new ObjectMapper(); + return mapper + .setTimeZone(TimeZone.getDefault()) + .setDateFormat(new SimpleDateFormat(DateTimeStyle.YYYY_MM_DD_HH24_MM_SS.getFormat())) + .registerModules(buildCustomModule(), buildJavaTimeModule()) + .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .setSerializationInclusion(Include.NON_NULL); + } + + /** + * @return Java类型转换模块 + */ + private static final Module buildCustomModule() { + final SimpleModule customModule = new SimpleModule("CustomModule"); + customModule.addSerializer(Long.class, ToStringSerializer.instance); + return customModule; + } + + /** + * @return Java时间类型模块 + */ + private static final JavaTimeModule buildJavaTimeModule() { + final JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(TimeStyle.HH24_MM_SS.getDateTimeFormatter())); + javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateStyle.YYYY_MM_DD.getDateTimeFormatter())); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeStyle.YYYY_MM_DD_HH24_MM_SS.getDateTimeFormatter())); + javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(TimeStyle.HH24_MM_SS.getDateTimeFormatter())); + javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateStyle.YYYY_MM_DD.getDateTimeFormatter())); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeStyle.YYYY_MM_DD_HH24_MM_SS.getDateTimeFormatter())); + return javaTimeModule; + } + +} diff --git a/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/URLUtils.java b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/URLUtils.java new file mode 100644 index 0000000..ac2c169 --- /dev/null +++ b/taoyao-boot/src/main/java/com/acgist/taoyao/boot/utils/URLUtils.java @@ -0,0 +1,70 @@ +package com.acgist.taoyao.boot.utils; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * URL工具 + * + * @author acgist + */ +public final class URLUtils { + + private URLUtils() { + } + + /** + * URL编码 + * + * @param content 原始内容 + * + * @return 编码内容 + */ + public static final String encode(String content) { + if (StringUtils.isEmpty(content)) { + return content; + } + return URLEncoder + .encode(content, StandardCharsets.UTF_8) + // 空格编码变成加号导致加号解码变成空格 + .replace("+", "%20"); + } + + /** + * URL解码 + * + * @param content 编码内容 + * + * @return 原始内容 + */ + public static final String decode(String content) { + if (StringUtils.isEmpty(content)) { + return content; + } + return URLDecoder.decode(content, StandardCharsets.UTF_8); + } + + /** + * Map转为URL参数 + * + * @param map Map + * + * @return URL参数 + */ + public static final String toQuery(Map map) { + if (MapUtils.isEmpty(map)) { + return null; + } + return map.entrySet().stream() + .filter(entry -> StringUtils.isNotEmpty(entry.getKey()) || StringUtils.isNotEmpty(entry.getValue())) + .map(entry -> String.join("=", entry.getKey(), URLUtils.encode(entry.getValue()))) + .collect(Collectors.joining("&")); + } + +} diff --git a/taoyao-boot/src/main/resources/META-INF/spring.factories b/taoyao-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..746eede --- /dev/null +++ b/taoyao-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1,4 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.acgist.taoyao.boot.config.BootAutoConfiguration,\ +com.acgist.taoyao.boot.config.OpenApiAutoConfiguration,\ +com.acgist.taoyao.boot.config.WebMvcConfigurerAutoConfiguration diff --git a/taoyao-boot/src/main/resources/banner.txt b/taoyao-boot/src/main/resources/banner.txt new file mode 100644 index 0000000..5ca4fe5 --- /dev/null +++ b/taoyao-boot/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + ___ ___ ___ ___ ___ ___ + /\ \ /\ \ /\ \ /\__\ /\ \ /\ \ + \:\ \ /::\ \ /::\ \ |::L__L /::\ \ /::\ \ + /::\__\ /::\:\__\ /:/\:\__\ |:::\__\ /::\:\__\ /:/\:\__\ + /:/\/__/ \/\::/ / \:\/:/ / /:;;/__/ \/\::/ / \:\/:/ / + \/__/ /:/ / \::/ / \/__/ /:/ / \::/ / + \/__/ \/__/ \/__/ \/__/ + +:: Spring Boot : ${spring-boot.formatted-version} +:: ${spring.application.name} : https://gitee.com/acgist/taoyao diff --git a/taoyao-boot/src/main/resources/logback-spring.xml b/taoyao-boot/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..55e868c --- /dev/null +++ b/taoyao-boot/src/main/resources/logback-spring.xml @@ -0,0 +1,127 @@ + + + + + + ${system.name} + + + + + + + + + ${system.path}/${system.name}.debug.log + + ${system.history} + ${system.path}/%d{yyyy-MM, aux}/${system.name}.debug.%d{yyyy-MM-dd}.log.gz + + ${system.buffer} + false + + ${system.charset} + ${system.pattern} + + + DEBUG + + + + + ${system.queue} + true + 0 + + + + ${system.path}/${system.name}.info.log + + ${system.history} + ${system.path}/%d{yyyy-MM, aux}/${system.name}.info.%d{yyyy-MM-dd}.log.gz + + ${system.buffer} + false + + ${system.charset} + ${system.pattern} + + + INFO + + + + + ${system.queue} + true + 0 + + + + ${system.path}/${system.name}.error.log + + ${system.history} + ${system.path}/%d{yyyy-MM, aux}/${system.name}.error.%d{yyyy-MM-dd}.log.gz + + ${system.buffer} + false + + ${system.charset} + ${system.pattern} + + + ERROR + + + + + ${system.queue} + true + 0 + + + + + ${system.charset} + ${system.pattern} + + + INFO + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/taoyao-client/pom.xml b/taoyao-client/pom.xml new file mode 100644 index 0000000..1c71edc --- /dev/null +++ b/taoyao-client/pom.xml @@ -0,0 +1,30 @@ + + + + + 4.0.0 + + + com.acgist + taoyao + 1.0.0 + + + taoyao-client + jar + + taoyao-client + 终端:帐号(移动端|浏览器)、摄像头 + + + + com.acgist + taoyao-boot + + + org.springframework.boot + spring-boot-starter-web + + + + \ No newline at end of file diff --git a/taoyao-live/pom.xml b/taoyao-live/pom.xml index ed6b908..633c0cd 100644 --- a/taoyao-live/pom.xml +++ b/taoyao-live/pom.xml @@ -13,9 +13,14 @@ taoyao-live jar - 直播 - 直播、连麦 + taoyao-live + 直播:直播、连麦 - + + + org.springframework.boot + spring-boot-starter-web + + \ No newline at end of file diff --git a/taoyao-media/pom.xml b/taoyao-media/pom.xml index f400995..e67ef7c 100644 --- a/taoyao-media/pom.xml +++ b/taoyao-media/pom.xml @@ -13,8 +13,8 @@ taoyao-media jar - 媒体 - 录制、视频(美颜、AI识别)、音频(混音、变声) + taoyao-media + 媒体:录制、视频(美颜、AI识别)、音频(混音、变声) diff --git a/taoyao-meeting/pom.xml b/taoyao-meeting/pom.xml index f0b6741..6b454ab 100644 --- a/taoyao-meeting/pom.xml +++ b/taoyao-meeting/pom.xml @@ -13,9 +13,19 @@ taoyao-meeting jar - 会议 - 会议模式、广播模式、单人对讲 + taoyao-meeting + 会议:会议模式、广播模式、单人对讲 - + + + org.springdoc + springdoc-openapi-ui + true + + + org.springframework.boot + spring-boot-starter-web + + \ No newline at end of file diff --git a/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/controller/RoomController.java b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/controller/RoomController.java new file mode 100644 index 0000000..d313af9 --- /dev/null +++ b/taoyao-meeting/src/main/java/com/acgist/taoyao/meeting/controller/RoomController.java @@ -0,0 +1,25 @@ +package com.acgist.taoyao.meeting.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * 房间 + * + * @author acgist + */ +@Tag(name = "房间", description = "房间管理") +@RestController +@RequestMapping("/room") +public class RoomController { + + @Operation(summary = "进入房间", description = "进入房间,如果房间不存在时自动创建。") + @GetMapping("/enter") + public void enter(String roomId) { + } + +} diff --git a/taoyao-model/pom.xml b/taoyao-nat/pom.xml similarity index 72% rename from taoyao-model/pom.xml rename to taoyao-nat/pom.xml index fb25e96..f7d5045 100644 --- a/taoyao-model/pom.xml +++ b/taoyao-nat/pom.xml @@ -10,11 +10,11 @@ 1.0.0 - taoyao-model + taoyao-nat jar - 模型 - 数据模型 + taoyao-net + 内网穿透:STUN/TURN暂不实现(请用公共服务或者搭建coturn服务) diff --git a/taoyao-server/pom.xml b/taoyao-server/pom.xml index 0d1255b..78d2646 100644 --- a/taoyao-server/pom.xml +++ b/taoyao-server/pom.xml @@ -13,14 +13,65 @@ taoyao-server jar - 服务 - 启动服务 + taoyao-server + 服务:启动服务 + + + ${project.parent.basedir} + false + - org.springframework.boot - spring-boot-starter-web + com.acgist + taoyao-live + + + com.acgist + taoyao-client + + + com.acgist + taoyao-signal + + + com.acgist + taoyao-meeting + + + com.acgist + taoyao-webrtc-sfu + + + com.acgist + taoyao-webrtc-mcu + + + com.acgist + taoyao-webrtc-mesh + + + org.springdoc + springdoc-openapi-ui + + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.acgist.taoyao.TaoyaoApplication + true + ./ + + + + + + + \ No newline at end of file diff --git a/taoyao-server/src/main/java/com/acgist/taoyao/TaoyaoApplication.java b/taoyao-server/src/main/java/com/acgist/taoyao/TaoyaoApplication.java index 88743ef..1fb84bd 100644 --- a/taoyao-server/src/main/java/com/acgist/taoyao/TaoyaoApplication.java +++ b/taoyao-server/src/main/java/com/acgist/taoyao/TaoyaoApplication.java @@ -2,8 +2,12 @@ package com.acgist.taoyao; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; +@ComponentScan(basePackages = "com.acgist.taoyao") @SpringBootApplication +@EnableAspectJAutoProxy(exposeProxy = true) public class TaoyaoApplication { public static void main(String[] args) { diff --git a/taoyao-server/src/main/resources/application-dev.yml b/taoyao-server/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e69de29 diff --git a/taoyao-server/src/main/resources/application-release.yml b/taoyao-server/src/main/resources/application-release.yml new file mode 100644 index 0000000..e69de29 diff --git a/taoyao-server/src/main/resources/application-test.yml b/taoyao-server/src/main/resources/application-test.yml new file mode 100644 index 0000000..e69de29 diff --git a/taoyao-server/src/main/resources/application.properties b/taoyao-server/src/main/resources/application.properties deleted file mode 100644 index 8b13789..0000000 --- a/taoyao-server/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/taoyao-server/src/main/resources/application.yml b/taoyao-server/src/main/resources/application.yml new file mode 100644 index 0000000..80a3331 --- /dev/null +++ b/taoyao-server/src/main/resources/application.yml @@ -0,0 +1,31 @@ +server: + port: 8888 + ssl: + key-alias: taoyao + key-store: classpath:taoyao.jks + key-store-password: 123456 + key-password: 123456 +spring: + profiles: + active: dev + application: + name: taoyao-server + jackson: + time-zone: GMT+8 + servlet: + multipart: + max-file-size: 256MB + max-request-size: 256MB +taoyao: + url: https://gitee.com/acgist/taoyao + name: 桃夭 + timeout: 5000 + version: 1.0.0 + description: WebRTC信令服务 + security: + realm: taoyao + permit: /v3/api-docs/,/swagger-ui/,/error + username: + password: + scheduled: + session: 0 * * * * ? diff --git a/taoyao-server/src/main/resources/static/client.html b/taoyao-server/src/main/resources/static/client.html new file mode 100644 index 0000000..42676a7 --- /dev/null +++ b/taoyao-server/src/main/resources/static/client.html @@ -0,0 +1,10 @@ + + + + +终端 + + + + + \ No newline at end of file diff --git a/taoyao-server/src/main/resources/static/index.html b/taoyao-server/src/main/resources/static/index.html new file mode 100644 index 0000000..18bf524 --- /dev/null +++ b/taoyao-server/src/main/resources/static/index.html @@ -0,0 +1,10 @@ + + + + +桃夭 + + + + + \ No newline at end of file diff --git a/taoyao-server/src/main/resources/static/room.html b/taoyao-server/src/main/resources/static/room.html new file mode 100644 index 0000000..f9cfbfe --- /dev/null +++ b/taoyao-server/src/main/resources/static/room.html @@ -0,0 +1,10 @@ + + + + +房间 + + + + + \ No newline at end of file diff --git a/taoyao-server/src/main/resources/taoyao.jks b/taoyao-server/src/main/resources/taoyao.jks new file mode 100644 index 0000000000000000000000000000000000000000..e2381106e1c63e41681bf7deed3cfa827fdc0544 GIT binary patch literal 2776 zcma);X*d)L7sqGDjGgR57&BS2)R@5t*H}lk?7NZ1mTNa5ORi;XS(CVSW6c)RRAjxj zVkQlukdh_j5|v2yxBI;BQ_uV9eLtLYp7Z;kb3Xo`gCeoN1Ob^)B=#6)Hu+?u&KMn*=hKSe3NvrwW#1YxiB> zmGL^&cPDwL$?gurgq_-Nho&alva8T4qI1b+WUYw@&E0{o4HKE{myXW#>S;QJDQBrP z#FUxS3JRQvt|K|PFAoQe%g1R5W52;auN*B~w$?NwyJ}oVSF!=IK3Hiqv0jGCl|4|T z!J%X9e!R4TO z=s}4FzgcM%F|MsfqB6SHuD%l=lVd{%8$8MSP$s$_LrA5CFc+wQk4Mf#Bu@|DNN=lZ zTw{0cht#ehT(nr$;hZX78}yk7t$+i|(c$hX&EJnjMuprFN&ItZoy~KYFl?kIs@-Nb zm95x~4M?!r)u+=gIs1PHGn1`_NWOJbsVQVqeva~VkNVa-_=}6>{N$In1{%=zD=!|D zLTr#HAEUbWIfRHx;vE+yF1xE98eEASm-rS92?b!!0>pNdd0`ZrMCiG^d6N$5@6b~d zIT@L~jg~%G%ojqsywm|1`ROh2$eSUrk3Ksfeq5q5A>z*!&&%lNeZ6MZLKzdRS=gKM zbv1iR0a~1KyLz*V8yyl`_t2D+8HHpChTUJVkz<2INLTJ;iu~K!=v15rlEZ=Jtv~TymJsQyhn=P z%BqONboqFH{fn0pUJ=l##booOpqfq(fdK(#7zIP`Rw&cAulaIHyG;tLC z>0OApKSxt}0n0G>t%56m$p_*1=Tdlt&ZMxo?WFC#r-1%d>r>&5Bl8ye^F=Kj^0P(3 zadyhLQ~KcNk;j$hD1)YnRETeJ-G{)j*HvfPQx3~Ba9G_ks|70nACeTtR!;HIW5eodrlfX zSP;K7R>#!5Gp!x`T#Ch0<*FjVwF|3L<9*%fuR3il$r#&XtM0&xlI4iUaOs)#>i+5k zA)AEyS>luNDD}M3`NEBaIr)~zp{RIE_Ruh?N?q)LTAOKFSb+X+K8HfIklN0G+*oy5 z^^O}_2POK`F4+Xo+%Qf+D8LO61c>}6qY%GDL9_sv&(A=`@PFBKTfg>s{JP^8AYVCQI!H7lW>R|L|FP+tA`j+q z{KryakDQ6*caFtzBrsg)fLTCm-BadN*q^(V1!4f`#HR+ULcIkGRgbihD4L{?wJ2;Z zPGT9Fn81J7l63ttCoAz>bXvH^m{6ue%Qb-(yI0Yjqj8Yl}8n;eR*?a-cHVa%h>pw_0}I*ML|R?~-h!vC^%{Czn3Z(kldkDcxv zA17lr8e0H=O*1q?w0FvF*Lgy(be8I|sqpc(=9!HcRR;xupywSEeGd19m*z z3SMLn4vhLFN9j1=v@#RRTf=eZb06_`ed7^1cfsR>(idFZ8>`<2|HghV{X2!`oFsiDdb@ZoRH= z#e7j37(bNM?1^&UKNrXzCLQC$%(X5kEYjt{TU&f<;spQtz2mSzp34ECu&SWOF zgwNS)w0CxEy9IgF7`M$weAhT6v4DaPJy0$IuiI5mnbL0I7kpt=Qh&^#!!V;K1BClr zLg%s7DyLaIsf!)?482~1jOpCPDGpyT^C86bywIb4b1*s*A%8Ch2qQFp4&YkS_R3{s z@F`u!^ouAuK9rEf|G{SzqQq-rP8i9yos5xD=Pk@iHT9guOC;sMSy-4duU96{HO|pf1liBz7&<-HhUa@)|mx8atIM+F9ZsIFlJ?bJQQ?O&dWKul0CWP*nY;|W3 zmNGUd!ZiPceUnX~nyQ1m#PHLu#uU|b_7HnBw-OkK!?4qd`?mHo+biiqs(^CealOX6 z0=Q1lrQIvQjtxC?27;FOPlWQT9 zHPF(T`;$Z5`;5zGt)Zx9EA7O1)u!HQtHPz_b6Lr<(LH}8*brtY28NKDv!*hme%XHE zkJP27NBRzL31-ab19WTiv{hWH9nHI4-I4t+NHILh424E<{rvVoKnMT~Z*~^<9#T-e zuB+3DF&Jn^0yG{BKsBK;%h~}l7!KkVSim;@o?D1N^NnU0wT(3+5jRfxEbxy-{R=~D B=rjNT literal 0 HcmV?d00001 diff --git a/taoyao-signal/pom.xml b/taoyao-signal/pom.xml index c4ea483..49d3adb 100644 --- a/taoyao-signal/pom.xml +++ b/taoyao-signal/pom.xml @@ -13,11 +13,19 @@ taoyao-signal jar - 信令 - 信令服务 + taoyao-signal + 信令:信令服务 - + + com.acgist + taoyao-boot + + + org.springframework.boot + spring-boot-starter-web + + org.springframework.boot spring-boot-starter-websocket diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/config/SignalAutoConfiguration.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/config/SignalAutoConfiguration.java new file mode 100644 index 0000000..de89481 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/config/SignalAutoConfiguration.java @@ -0,0 +1,37 @@ +package com.acgist.taoyao.signal.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +import com.acgist.taoyao.signal.protocol.ProtocolManager; +import com.acgist.taoyao.signal.session.SessionManager; +import com.acgist.taoyao.signal.session.websocket.TaoyaoWebSocket; + +/** + * 信令配置 + * + * @author acgist + */ +@Configuration +public class SignalAutoConfiguration { + + @Autowired + private ProtocolManager eventManager; + @Autowired + private SessionManager sessionManager; + + @Autowired + public TaoyaoWebSocket taoyaoWebSocket() { + return new TaoyaoWebSocket(this.eventManager, this.sessionManager); + } + + @Bean + @ConditionalOnMissingBean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/Event.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/Event.java new file mode 100644 index 0000000..c831b01 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/Event.java @@ -0,0 +1,15 @@ +package com.acgist.taoyao.signal.event; + +import com.acgist.taoyao.signal.protocol.Protocol; + +/** + * 事件 + * 事件主要负责执行信令 + * + * @author acgist + * + * @see Protocol + */ +public interface Event { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/EventAdapter.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/EventAdapter.java new file mode 100644 index 0000000..b2630d4 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/EventAdapter.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.event; + +/** + * 注册事件 + * + * @author acgist + */ +public class EventAdapter { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/RegisterEvent.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/RegisterEvent.java new file mode 100644 index 0000000..9a2f7bd --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/event/client/RegisterEvent.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.event.client; + +/** + * 注册事件 + * + * @author acgist + */ +public class RegisterEvent { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Header.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Header.java new file mode 100644 index 0000000..930b3c5 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Header.java @@ -0,0 +1,38 @@ +package com.acgist.taoyao.signal.message; + +import java.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 信令头部 + * + * @author acgist + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Header implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 信令来源 + */ + private String sn; + /** + * 事件标识 + */ + private String event; + /** + * 信令版本 + */ + private String version; + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Message.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Message.java new file mode 100644 index 0000000..8012bac --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/message/Message.java @@ -0,0 +1,41 @@ +package com.acgist.taoyao.signal.message; + +import java.io.Serializable; + +import com.acgist.taoyao.boot.utils.JSONUtils; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 信令消息 + * + * @author acgist + */ +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Message implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 信令头部 + */ + private Header header; + /** + * 信令主体 + */ + private Object body; + + @Override + public String toString() { + return JSONUtils.toJSON(this); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java new file mode 100644 index 0000000..2792575 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/Protocol.java @@ -0,0 +1,15 @@ +package com.acgist.taoyao.signal.protocol; + +/** + * 信令协议 + * + * 1000~1999:系统信令(关机) + * 2000~2999:终端信令(注册、注销、终端列表) + * 3000~3999:直播信令 + * 4000~4999:会议信令 + * + * @author acgist + */ +public interface Protocol { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java new file mode 100644 index 0000000..1b862e5 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolAdapter.java @@ -0,0 +1,5 @@ +package com.acgist.taoyao.signal.protocol; + +public class ProtocolAdapter { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java new file mode 100644 index 0000000..baccd61 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java @@ -0,0 +1,20 @@ +package com.acgist.taoyao.signal.protocol; + +import javax.websocket.Session; + +import org.springframework.stereotype.Service; + +/** + * 协议管理 + * + * @author acgist + */ +@Service +public class ProtocolManager { + + public void execute(Session session, String message) { + // TODO Auto-generated method stub + + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/Session.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/Session.java new file mode 100644 index 0000000..4133704 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/Session.java @@ -0,0 +1,5 @@ +package com.acgist.taoyao.signal.session; + +public interface Session { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionAdapter.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionAdapter.java new file mode 100644 index 0000000..2f514a2 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionAdapter.java @@ -0,0 +1,5 @@ +package com.acgist.taoyao.signal.session; + +public class SessionAdapter { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionManager.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionManager.java new file mode 100644 index 0000000..1cc52bf --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/SessionManager.java @@ -0,0 +1,162 @@ +package com.acgist.taoyao.signal.session; + +import java.io.IOException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.websocket.Session; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.acgist.taoyao.boot.config.TaoyaoProperties; +import com.acgist.taoyao.signal.message.Message; +import com.acgist.taoyao.signal.session.websocket.SessionWrapper; + +import lombok.extern.slf4j.Slf4j; + +/** + * 会话管理 + * + * @author acgist + */ +@Slf4j +@Service +public class SessionManager { + + @Autowired + private TaoyaoProperties taoyaoProperties; + + /** + * 没有授权会话 + */ + private Map unauthorized = new ConcurrentHashMap<>(); + /** + * 授权会话列表 + */ + private List sessions = new CopyOnWriteArrayList<>(); + + @Scheduled(cron = "${taoyao.scheduled.session:0 * * * * ?}") + public void scheduled() { + this.closeTimeoutSession(); + } + + /** + * 存入没有授权会话,定时清除没有授权会话。 + * + * @param session 会话 + */ + public void open(Session session) { + this.unauthorized.put(session, System.currentTimeMillis()); + } + + /** + * @param session 会话 + * + * @return 会话包装器 + */ + public SessionWrapper getWrapper(Session session) { + return this.sessions.stream() + .filter(v -> v.matchSession(session)) + .findFirst() + .orElse(null); + } + + /** + * 认证会话 + * + * @param sn 终端标识 + * @param session 会话 + */ + public void authorized(String sn, Session session) { + this.unauthorized.remove(session); + final SessionWrapper wrapper = new SessionWrapper(); + wrapper.setSn(sn); + wrapper.setSession(session); + this.sessions.add(wrapper); + } + + /** + * 单播消息 + * + * @param to 接收终端 + * @param message 消息 + */ + public void unicast(String to, Message message) { + this.sessions.stream().filter(v -> v.matchSn(to)).forEach(v -> { + message.getHeader().setSn(v.getSn()); + message.getHeader().setVersion(this.taoyaoProperties.getVersion()); + v.send(message); + }); + } + + /** + * 广播消息 + * + * @param message 消息 + */ + public void broadcast(Message message) { + this.sessions.forEach(v -> { + message.getHeader().setSn(v.getSn()); + message.getHeader().setVersion(this.taoyaoProperties.getVersion()); + v.send(message); + }); + } + + /** + * 广播消息 + * + * @param from 发送终端 + * @param message 消息 + */ + public void broadcast(String from, Message message) { + this.sessions.stream().filter(v -> v.matchNoneSn(from)).forEach(v -> { + message.getHeader().setSn(v.getSn()); + message.getHeader().setVersion(this.taoyaoProperties.getVersion()); + v.send(message); + }); + } + + /** + * 关闭会话 + * + * @param session 会话 + */ + public void close(Session session) { + final SessionWrapper wrapper = this.getWrapper(session); + if(wrapper != null) { + // TODO:退出房间 + // TODO:退出帐号 + // 移除 + this.sessions.remove(wrapper); + } + try { + session.close(); + } catch (IOException e) { + log.error("关闭会话异常", e); + } + } + + /** + * 定时关闭超时会话 + */ + private void closeTimeoutSession() { + log.debug("定时关闭超时会话"); + final Iterator> iterator = this.unauthorized.entrySet().iterator(); + while(iterator.hasNext()) { + final Entry next = iterator.next(); + final Long last = next.getValue(); + final Session session = next.getKey(); + if(System.currentTimeMillis() - last > this.taoyaoProperties.getTimeout()) { + log.debug("关闭超时会话:{}", session); + this.close(session); + } + } + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/socket/TaoyaoSocket.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/socket/TaoyaoSocket.java new file mode 100644 index 0000000..10c2ec3 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/socket/TaoyaoSocket.java @@ -0,0 +1,10 @@ +package com.acgist.taoyao.signal.session.socket; + +/** + * Socket信令 + * + * @author acgist + */ +public class TaoyaoSocket { + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/SessionWrapper.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/SessionWrapper.java new file mode 100644 index 0000000..f347f3d --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/SessionWrapper.java @@ -0,0 +1,72 @@ +package com.acgist.taoyao.signal.session.websocket; + +import java.io.IOException; + +import javax.websocket.Session; + +import com.acgist.taoyao.signal.message.Message; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * 会话包装器 + * + * @author acgist + */ +@Slf4j +@Getter +@Setter +public class SessionWrapper { + + /** + * 终端帐号 + */ + private String sn; + /** + * 会话 + */ + private Session session; + + /** + * 发送消息 + * + * @param message 消息 + */ + public void send(Message message) { + try { + this.session.getBasicRemote().sendText(message.toString()); + } catch (IOException e) { + log.error("WebSocket发送消息异常:{}", message, e); + } + } + + /** + * @param sn 终端编号 + * + * @return 是否匹配成功 + */ + public boolean matchSn(String sn) { + return this.sn != null && this.sn.equals(sn); + } + + /** + * @param sn 终端编号 + * + * @return 是否匹配失败 + */ + public boolean matchNoneSn(String sn) { + return this.sn != null && !this.sn.equals(sn); + } + + /** + * @param session 会话 + * + * @return 是否匹配成功 + */ + public boolean matchSession(Session session) { + return this.session != null && this.session.equals(session); + } + +} diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/TaoyaoWebSocket.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/TaoyaoWebSocket.java new file mode 100644 index 0000000..ce2ace5 --- /dev/null +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/session/websocket/TaoyaoWebSocket.java @@ -0,0 +1,60 @@ +package com.acgist.taoyao.signal.session.websocket; + +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import com.acgist.taoyao.signal.protocol.ProtocolManager; +import com.acgist.taoyao.signal.session.SessionManager; + +import lombok.extern.slf4j.Slf4j; + +/** + * WebSocket信令 + * + * @author acgist + */ +@Slf4j +@ServerEndpoint(value = "/taoyao/websocket") +public class TaoyaoWebSocket { + + private ProtocolManager eventManager; + private SessionManager sessionManager; + + public TaoyaoWebSocket(ProtocolManager eventManager, SessionManager sessionManager) { + this.eventManager = eventManager; + this.sessionManager = sessionManager; + } + + @OnOpen + public void open(Session session) { + log.debug("会话连接:{}", session); + this.sessionManager.open(session); + } + + @OnMessage + public void message(Session session, String message) { + log.debug("会话消息:{}-{}", session, message); + try { + this.eventManager.execute(session, message); + } catch (Exception e) { + log.error("处理会话消息异常", e); + } + } + + @OnClose + public void close(Session session) { + log.debug("会话关闭:{}", session); + this.sessionManager.close(session); + } + + @OnError + public void error(Session session, Throwable e) { + log.error("会话异常:{}", session, e); + this.close(session); + } + +} diff --git a/taoyao-signal/src/main/resources/META-INF/spring.factories b/taoyao-signal/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..985c929 --- /dev/null +++ b/taoyao-signal/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.acgist.taoyao.signal.config.SignalAutoConfiguration diff --git a/taoyao-webrtc/README.md b/taoyao-webrtc/README.md new file mode 100644 index 0000000..ce1c112 --- /dev/null +++ b/taoyao-webrtc/README.md @@ -0,0 +1,7 @@ +# WebRTC + +## 安装 + +## 观察者 + +订阅发布 diff --git a/taoyao-webrtc/pom.xml b/taoyao-webrtc/pom.xml index 41f4857..a47ee55 100644 --- a/taoyao-webrtc/pom.xml +++ b/taoyao-webrtc/pom.xml @@ -13,14 +13,14 @@ taoyao-webrtc pom - WebRTC模块 + taoyao-webrtc WebRTC模块 taoyao-webrtc-sfu taoyao-webrtc-mcu + taoyao-webrtc-mix taoyao-webrtc-mesh - taoyao-webrtc-native - + \ No newline at end of file diff --git a/taoyao-webrtc/taoyao-webrtc-mcu/pom.xml b/taoyao-webrtc/taoyao-webrtc-mcu/pom.xml index 640d6f1..aa188ec 100644 --- a/taoyao-webrtc/taoyao-webrtc-mcu/pom.xml +++ b/taoyao-webrtc/taoyao-webrtc-mcu/pom.xml @@ -13,9 +13,14 @@ taoyao-webrtc-mcu jar - WebRTC MCU架构实现 + taoyao-webrtc-mcu WebRTC MCU架构实现 - + + + com.acgist + taoyao-webrtc-mix + + \ No newline at end of file diff --git a/taoyao-webrtc/taoyao-webrtc-mesh/pom.xml b/taoyao-webrtc/taoyao-webrtc-mesh/pom.xml index 0108376..b38ca4d 100644 --- a/taoyao-webrtc/taoyao-webrtc-mesh/pom.xml +++ b/taoyao-webrtc/taoyao-webrtc-mesh/pom.xml @@ -13,7 +13,7 @@ taoyao-webrtc-mesh jar - WebRTC MESH架构实现 + taoyao-webrtc-mesh WebRTC MESH架构实现 diff --git a/taoyao-webrtc/taoyao-webrtc-native/pom.xml b/taoyao-webrtc/taoyao-webrtc-mix/pom.xml similarity index 61% rename from taoyao-webrtc/taoyao-webrtc-native/pom.xml rename to taoyao-webrtc/taoyao-webrtc-mix/pom.xml index 662516c..fabcb15 100644 --- a/taoyao-webrtc/taoyao-webrtc-native/pom.xml +++ b/taoyao-webrtc/taoyao-webrtc-mix/pom.xml @@ -10,12 +10,19 @@ 1.0.0 - taoyao-webrtc-native + taoyao-webrtc-mix jar - WebRTC底层实现 - MCU/SFU底层媒体服务 + taoyao-webrtc-mix + MCU/SFU混合媒体服务 - + + + \ No newline at end of file diff --git a/taoyao-webrtc/taoyao-webrtc-sfu/pom.xml b/taoyao-webrtc/taoyao-webrtc-sfu/pom.xml index 81ec2cb..6976fa9 100644 --- a/taoyao-webrtc/taoyao-webrtc-sfu/pom.xml +++ b/taoyao-webrtc/taoyao-webrtc-sfu/pom.xml @@ -13,9 +13,14 @@ taoyao-webrtc-sfu jar - WebRTC SFU架构实现 + taoyao-webrtc-sfu WebRTC SFU架构实现 - + + + com.acgist + taoyao-webrtc-mix + + \ No newline at end of file