[+] 结构调整

This commit is contained in:
acgist
2022-11-11 19:28:02 +08:00
parent e668670da8
commit 605e0fbbe7
46 changed files with 841 additions and 215 deletions

View File

@@ -10,15 +10,16 @@
|taoyao-nat|内网穿透|STUN/TURN|
|taoyao-boot|基础|启动模块|
|taoyao-live|直播|直播、连麦|
|taoyao-media|媒体|录制、视频美颜、AI识别、音频混音、变声|
|taoyao-test|测试|测试工具|
|taoyao-media|媒体|录制、视频美颜、AI识别、音频混音、变声、降噪|
|taoyao-signal|信令|信令服务|
|taoyao-server|服务|启动服务|
|taoyao-meeting|会议|会议模式、广播模式、单人对讲|
|taoyao-webrtc|WebRTC模块||
|taoyao-webrtc|WebRTC模块|WebRTC模块|
|taoyao-webrtc-jni|WebRTC JNI|WebRTC本地接口|
|taoyao-webrtc-sfu|WebRTC SFU架构实现||
|taoyao-webrtc-mcu|WebRTC MCU架构实现||
|taoyao-webrtc-mesh|WebRTC MESH架构实现||
|taoyao-webrtc-sfu|WebRTC SFU架构|SFU架构|
|taoyao-webrtc-mcu|WebRTC MCU架构|MCU架构|
|taoyao-webrtc-mesh|WebRTC MESH架构|MESH架构|
## STUN/TURN公共服务
@@ -30,46 +31,41 @@ stun:stun4.l.google.com:19302
stun:stun.stunprotocol.org:3478
```
## 终端
帐号(移动端|浏览器)
摄像头
### 功能
|功能|场景|描述|帐号|摄像头|
注册
注销
心跳
推流
拉流
邀请
踢出
绑定设备
解绑设备
进入会议:没有自动创建
关闭会议:
订阅
取消订阅
暂停推流
恢复推流
掉线重连
## 信令
### 信息
IP
MAC
信号
电量
通话状态
录制状态
|功能|描述|
|:--|:--|
|注册|终端注册(同步信息)|
|关闭|终端关闭(注销)|
|心跳|终端心跳|
|进入会议|没有会议自动创建|
|离开会议|离开会议|
|关闭会议|关闭会议(所有人员离开)|
|邀请终端|会议邀请终端|
|踢出终端|会议踢出终端|
|推流|控制终端推流|
|暂停推流|控制终端暂停推流|
|订阅(分流)|控制终端暂停推流|
|暂停订阅(分流)|控制终端暂停推流|
## 直播
终端推流到服务端,由服务端分流。
## 会议
### Mesh
流媒体点对点连接,不经过服务端。
### MCU
终端推流到服务端,由服务端分流并且混音。
### SFU
终端推流到服务端,由服务端分流没有混音。
## 证书
```

View File

@@ -38,6 +38,7 @@
<module>taoyao-nat</module>
<module>taoyao-boot</module>
<module>taoyao-live</module>
<module>taoyao-test</module>
<module>taoyao-media</module>
<module>taoyao-signal</module>
<module>taoyao-webrtc</module>
@@ -117,6 +118,11 @@
<artifactId>taoyao-live</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.acgist</groupId>
<artifactId>taoyao-test</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.acgist</groupId>
<artifactId>taoyao-media</artifactId>

View File

@@ -1,6 +1,7 @@
package com.acgist.taoyao.boot.config;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.Timer;
import java.util.TimerTask;
import java.util.stream.Collectors;
@@ -50,6 +51,7 @@ 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.interceptor.SlowInterceptor;
import com.acgist.taoyao.boot.model.MessageCode;
import com.acgist.taoyao.boot.service.IdService;
import com.acgist.taoyao.boot.service.impl.IdServiceImpl;
@@ -73,7 +75,7 @@ import lombok.extern.slf4j.Slf4j;
@Configuration
@EnableScheduling
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableConfigurationProperties({ TaoyaoProperties.class, WebrtcProperties.class, SecurityProperties.class })
@EnableConfigurationProperties({ IdProperties.class, TaoyaoProperties.class, WebrtcProperties.class, SecurityProperties.class })
public class BootAutoConfiguration {
@Value("${spring.application.name:taoyao}")
@@ -119,6 +121,12 @@ public class BootAutoConfiguration {
};
}
@Bean
@ConditionalOnMissingBean
public SlowInterceptor slowInterceptor() {
return new SlowInterceptor();
}
@Bean
@ConditionalOnMissingBean
public SecurityInterceptor securityInterceptor() {
@@ -139,15 +147,15 @@ public class BootAutoConfiguration {
@PostConstruct
public void init() {
final var runtime = Runtime.getRuntime();
final var runtimeMXBean = ManagementFactory.getRuntimeMXBean();
final Runtime runtime = Runtime.getRuntime();
final RuntimeMXBean 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("可用处理器数量:{}", runtime.availableProcessors());
log.info("Java版本{}", System.getProperty("java.version"));
log.info("Java主目录{}", System.getProperty("java.home"));
log.info("Java库目录{}", System.getProperty("java.library.path"));
@@ -184,8 +192,8 @@ public class BootAutoConfiguration {
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_3415, HttpMediaTypeNotSupportedException.class);
ErrorUtils.register(MessageCode.CODE_3406, HttpMediaTypeNotAcceptableException.class);
ErrorUtils.register(MessageCode.CODE_3405, HttpRequestMethodNotSupportedException.class);
ErrorUtils.register(MessageCode.CODE_3400, MissingServletRequestParameterException.class);

View File

@@ -0,0 +1,27 @@
package com.acgist.taoyao.boot.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Getter;
import lombok.Setter;
/**
* ID配置
*
* @author acgist
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "taoyao.id")
public class IdProperties {
/**
* 机器序号
*/
private Integer sn;
/**
* 最大序号
*/
private Integer maxIndex;
}

View File

@@ -1,5 +1,7 @@
package com.acgist.taoyao.boot.config;
import java.util.List;
import org.springdoc.core.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -9,8 +11,8 @@ 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.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityRequirement;
@@ -61,24 +63,33 @@ public class OpenApiAutoConfiguration {
public OpenAPI openAPI() {
return new OpenAPI()
.info(this.buildInfo())
.externalDocs(this.buildExternalDocumentation())
.addSecurityItem(this.buildSecurityRequirement())
.security(this.buildSecurity())
.components(this.buildComponents());
}
/**
* @return 文档基本信息
* @return 基本信息
*/
private Info buildInfo() {
return new Info()
.contact(this.buildContact())
.license(this.buildLicense())
.title(this.taoyaoProperties.getName())
.version(this.taoyaoProperties.getVersion())
.description(this.taoyaoProperties.getDescription())
.license(this.buildLicense());
.description(this.taoyaoProperties.getDescription());
}
/**
* @return 授权协议信息
* @return 联系方式
*/
private Contact buildContact() {
return new Contact()
.url(this.taoyaoProperties.getUrl())
.name(this.taoyaoProperties.getName());
}
/**
* @return 开源信息
*/
private License buildLicense() {
return new License()
@@ -86,21 +97,11 @@ public class OpenApiAutoConfiguration {
.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);
private List<SecurityRequirement> buildSecurity() {
return List.of(new SecurityRequirement().addList(SecurityProperties.BASIC));
}
/**

View File

@@ -36,5 +36,4 @@ public class TaoyaoProperties {
*/
private String description;
}

View File

@@ -1,11 +1,12 @@
package com.acgist.taoyao.boot.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
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 com.acgist.taoyao.boot.interceptor.InterceptorAdapter;
import lombok.extern.slf4j.Slf4j;
@@ -19,12 +20,17 @@ import lombok.extern.slf4j.Slf4j;
public class WebMvcConfigurerAutoConfiguration implements WebMvcConfigurer {
@Autowired
private SecurityInterceptor securityInterceptor;
private ApplicationContext context;
@Override
public void addInterceptors(InterceptorRegistry registry) {
log.info("加载拦截器securityInterceptor");
registry.addInterceptor(this.securityInterceptor).addPathPatterns("/**");
this.context.getBeansOfType(InterceptorAdapter.class).entrySet().stream()
.sorted((a, z) -> a.getValue().compareTo(z.getValue()))
.forEach(entry -> {
final InterceptorAdapter value = entry.getValue();
log.info("加载拦截器:{}-{}", entry.getKey(), value.name());
registry.addInterceptor(value).addPathPatterns(value.pathPattern());
});
}
}

View File

@@ -13,7 +13,7 @@ import lombok.Setter;
*/
@Getter
@Setter
@Schema(name = "WebRTC配置")
@Schema(title = "WebRTC配置", description = "WebRTC配置")
@ConfigurationProperties(prefix = "taoyao.webrtc")
public class WebrtcProperties {
@@ -42,15 +42,17 @@ public class WebrtcProperties {
/**
* 类型
*/
@Schema(name = "架构类型", description = "WebRTC架构类型")
@Schema(title = "架构类型", description = "WebRTC架构类型")
private Type type;
/**
* stun服务器
*/
@Schema(title = "stun服务器", description = "stun服务器")
private String[] stun;
/**
* turn服务器
*/
@Schema(title = "turn服务器", description = "turn服务器")
private String[] turn;
}

View File

@@ -0,0 +1,28 @@
package com.acgist.taoyao.boot.interceptor;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* 拦截器适配器
*
* @author acgist
*/
public abstract class InterceptorAdapter implements Ordered, HandlerInterceptor, Comparable<InterceptorAdapter> {
/**
* @return 名称
*/
public abstract String name();
/**
* @return 拦截地址
*/
public abstract String[] pathPattern();
@Override
public int compareTo(InterceptorAdapter o) {
return Integer.compare(this.getOrder(), o.getOrder());
}
}

View File

@@ -10,7 +10,6 @@ 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;
@@ -22,20 +21,29 @@ import lombok.extern.slf4j.Slf4j;
* @author acgist
*/
@Slf4j
public class SecurityInterceptor implements HandlerInterceptor {
/**
* 时间
*/
private ThreadLocal<Long> local = new ThreadLocal<>();
public class SecurityInterceptor extends InterceptorAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
public String name() {
return "安全拦截";
}
@Override
public String[] pathPattern() {
return new String[] { "/**" };
}
@Override
public int getOrder() {
return Integer.MIN_VALUE + 1;
}
@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());
@@ -85,14 +93,4 @@ public class SecurityInterceptor implements HandlerInterceptor {
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();
}
}

View File

@@ -0,0 +1,59 @@
package com.acgist.taoyao.boot.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import com.acgist.taoyao.boot.config.TaoyaoProperties;
import lombok.extern.slf4j.Slf4j;
/**
* 过慢请求统计拦截
*
* @author acgist
*/
@Slf4j
public class SlowInterceptor extends InterceptorAdapter {
/**
* 时间
*/
private ThreadLocal<Long> local = new ThreadLocal<>();
@Autowired
private TaoyaoProperties taoyaoProperties;
@Override
public String name() {
return "过慢请求统计拦截";
}
@Override
public String[] pathPattern() {
return new String[] { "/**" };
}
@Override
public int getOrder() {
return Integer.MIN_VALUE;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
this.local.set(System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) throws Exception {
final long duration;
final Long last = this.local.get();
if(last != null && (duration = System.currentTimeMillis() - last) > this.taoyaoProperties.getTimeout()) {
log.info("请求执行时间过慢:{}-{}", request.getRequestURI(), duration);
}
this.local.remove();
}
}

View File

@@ -2,6 +2,7 @@ package com.acgist.taoyao.boot.model;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
@@ -9,12 +10,13 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 信令头部
* 请求响应头部
*
* @author acgist
*/
@Getter
@Setter
@Schema( title = "请求响应头部", description = "请求响应头部")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -23,20 +25,24 @@ public class Header implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 信令版本
* 请求响应版本
*/
@Schema(title = "请求响应版本", description = "请求响应版本")
private String v;
/**
* 请求标识
* 请求响应标识
*/
@Schema(title = "请求响应标识", description = "请求响应标识")
private Long id;
/**
* 终端标识
*/
@Schema(title = "终端标识", description = "终端标识")
private String sn;
/**
* 协议标识
*/
@Schema(title = "协议标识", description = "协议标识")
private Integer pid;
}

View File

@@ -14,13 +14,13 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* 消息
* 请求响应消息
*
* @author acgist
*/
@Getter
@Setter
@Schema(name = "消息", description = "请求响应消息")
@Schema(title = "请求响应消息", description = "请求响应消息")
@Builder
@NoArgsConstructor
@AllArgsConstructor
@@ -31,22 +31,22 @@ public class Message implements Serializable {
/**
* 响应编码
*/
@Schema(name = "响应编码", description = "响应消息标识响应状态")
@Schema(title = "响应编码", description = "响应消息标识响应状态")
private String code;
/**
* 响应描述
*/
@Schema(name = "响应描述", description = "响应消息描述响应编码")
@Schema(title = "响应描述", description = "响应消息描述响应编码")
private String message;
/**
* 请求响应头部
*/
@Schema(name = "请求响应头部", description = "请求响应头部")
@Schema(title = "请求响应头部", description = "请求响应头部")
private Header header;
/**
* 请求响应主体
*/
@Schema(name = "请求响应主体", description = "请求响应主体")
@Schema(title = "请求响应主体", description = "请求响应主体")
private Object body;
/**

View File

@@ -5,10 +5,10 @@ import lombok.Getter;
/**
* 状态编码
*
* 1xxx=前置错误:前置校验错误(数据校验
* 2xxx=内部错误:服务内部错误
* 1xxx=前置错误:数据校验
* 2xxx=内部错误
* 3xxx=请求错误HTTP错误
* 9999=未知错误:没有适配异常
* 9999=未知错误
*
* @author acgist
*/
@@ -41,7 +41,7 @@ public enum MessageCode {
CODE_9999("9999", 500, "未知错误");
/**
* HTTP状态编码头部
* HTTP状态编码前缀
*/
public static final String HTTP_STATUS = "3";
@@ -54,7 +54,7 @@ public enum MessageCode {
*/
private final Integer status;
/**
* 状态信息
* 状态描述
*/
private final String message;
@@ -85,14 +85,7 @@ public enum MessageCode {
* @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));
return of(HTTP_STATUS + status);
}
}

View File

@@ -2,14 +2,16 @@ package com.acgist.taoyao.boot.model;
import java.util.Objects;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import lombok.Getter;
/**
* 状态编码异常
*
* @author acgist
*/
@Getter
public class MessageCodeException extends RuntimeException {
private static final long serialVersionUID = 1L;
@@ -20,104 +22,59 @@ public class MessageCodeException extends RuntimeException {
private final MessageCode code;
/**
* @param messages 错误消息
* @param message 错误消息
*
* @return 状态编码异常
*/
public static final MessageCodeException of(Object ... messages) {
return of(null, MessageCode.CODE_9999, messages);
public static final MessageCodeException of(String message) {
return of(null, null, message);
}
/**
* @param t 异常
* @param messages 错误消息
* @param message 错误消息
*
* @return 状态编码异常
*/
public static final MessageCodeException of(Throwable t, Object ... messages) {
return of(t, MessageCode.CODE_9999, messages);
public static final MessageCodeException of(Throwable t, String message) {
return of(t, null, message);
}
/**
* @param code 状态编码
* @param messages 错误消息
* @param message 错误消息
*
* @return 状态编码异常
*/
public static final MessageCodeException of(MessageCode code, Object ... messages) {
return of(null, code, messages);
public static final MessageCodeException of(MessageCode code, String message) {
return of(null, code, message);
}
/**
* @param t 异常
* @param code 状态编码
* @param messages 错误消息
* @param message 错误消息
*
* @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();
public static final MessageCodeException of(Throwable t, MessageCode code, String message) {
if(code == null) {
code = MessageCode.CODE_9999;
}
return new MessageCodeException(code, message, t);
}
/**
* @param code 状态编码
*/
public MessageCodeException(MessageCode code) {
this(code, code.getMessage());
if(StringUtils.isEmpty(message)) {
message = Objects.isNull(t) ? code.getMessage() : t.getMessage();
}
return new MessageCodeException(t, code, message);
}
/**
* @param t 异常
* @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) {
public MessageCodeException(Throwable t, MessageCode code, String message) {
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;
}
}
}

View File

@@ -5,6 +5,7 @@ import java.time.LocalDateTime;
import com.acgist.taoyao.boot.utils.JSONUtils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@@ -16,6 +17,7 @@ import lombok.Setter;
*/
@Getter
@Setter
@Schema(title = "模型", description = "模型")
@EqualsAndHashCode(callSuper = false, of = "id")
public abstract class Model implements Cloneable, Serializable {
@@ -24,14 +26,17 @@ public abstract class Model implements Cloneable, Serializable {
/**
* ID
*/
@Schema(title = "标识", description = "标识")
private Long id;
/**
* 创建时间
*/
@Schema(title = "创建时间", description = "创建时间")
private LocalDateTime createDate;
/**
* 修改时间
*/
@Schema(title = "修改时间", description = "修改时间")
private LocalDateTime modifyDate;
@Override

View File

@@ -1,7 +1,7 @@
package com.acgist.taoyao.boot.service;
/**
* ID
* ID生成器
*
* @author acgist
*/

View File

@@ -2,30 +2,25 @@ package com.acgist.taoyao.boot.service.impl;
import java.time.LocalDateTime;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.annotation.Autowired;
import com.acgist.taoyao.boot.config.IdProperties;
import com.acgist.taoyao.boot.service.IdService;
public class IdServiceImpl implements IdService {
/**
* 机器序号
*/
@Value("${taoyao.sn:0}")
private int sn = 9;
/**
* 当前索引
*/
private int index;
/**
* 最大索引
*/
private static final int MAX_INDEX = 999999;
@Autowired
private IdProperties idProperties;
@Override
public long id() {
synchronized (this) {
if (++this.index > MAX_INDEX) {
if (++this.index > this.idProperties.getMaxIndex()) {
this.index = 0;
}
}
@@ -38,7 +33,7 @@ public class IdServiceImpl implements IdService {
1000000000L * time.getMinute() +
10000000L * time.getSecond() +
// 机器序号一位
1000000L * this.sn +
1000000L * this.idProperties.getSn() +
// 每秒并发数量
this.index;
}

View File

@@ -125,7 +125,8 @@ public final class ErrorUtils {
请求参数:{}
请求方法:{}
错误信息:{}
""", path, query, method, message, globalError);
响应状态:{}
""", path, query, method, message, status, globalError);
} else {
log.warn("""
请求错误
@@ -133,8 +134,9 @@ public final class ErrorUtils {
请求参数:{}
请求方法:{}
错误信息:{}
响应状态:{}
原始信息:{}
""", path, query, method, message, globalError);
""", path, query, method, message, status, globalError);
}
return message;
}

View File

@@ -60,7 +60,7 @@ public final class JSONUtils {
try {
return MAPPER.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw MessageCodeException.of(e, "Java转JSON失败", object);
throw MessageCodeException.of(e, "Java转JSON失败" + object);
}
}
@@ -81,7 +81,7 @@ public final class JSONUtils {
return MAPPER.readValue(json, new TypeReference<T>() {
});
} catch (IOException e) {
throw MessageCodeException.of(e, "JSON转Java失败", json);
throw MessageCodeException.of(e, "JSON转Java失败" + json);
}
}
@@ -102,7 +102,7 @@ public final class JSONUtils {
try {
return MAPPER.readValue(json, clazz);
} catch (IOException e) {
throw MessageCodeException.of(e, "JSON转Java失败", json);
throw MessageCodeException.of(e, "JSON转Java失败" + json);
}
}
@@ -124,7 +124,7 @@ public final class JSONUtils {
return MAPPER.readValue(json, new TypeReference<Map<K, V>>() {
});
} catch (IOException e) {
throw MessageCodeException.of(e, "JSON转Map失败", json);
throw MessageCodeException.of(e, "JSON转Map失败" + json);
}
}
@@ -145,7 +145,7 @@ public final class JSONUtils {
return MAPPER.readValue(json, new TypeReference<List<T>>() {
});
} catch (IOException e) {
throw MessageCodeException.of(e, "JSON转List失败", json);
throw MessageCodeException.of(e, "JSON转List失败" + json);
}
}
@@ -169,7 +169,7 @@ public final class JSONUtils {
return MAPPER.readValue(json, new TypeReference<List<T>>() {
});
} catch (IOException e) {
throw MessageCodeException.of(e, "JSON转List失败", json);
throw MessageCodeException.of(e, "JSON转List失败" + json);
}
}

View File

@@ -50,6 +50,12 @@
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
</dependency>
<dependency>
<groupId>com.acgist</groupId>
<artifactId>taoyao-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,3 @@
taoyao:
security:
permit: /favicon.ico,/error

View File

@@ -0,0 +1,3 @@
taoyao:
security:
permit: /favicon.ico,/error

View File

@@ -1,11 +1,16 @@
server:
port: 8888
http2:
enabled: true
ssl:
key-alias: taoyao
key-store: classpath:taoyao.jks
key-store-password: 123456
key-password: 123456
tomcat:
thread:
max: 128
min-spare: 4
remoteip:
host-header: X-Forwarded-Host
port-header: X-Forwarded-Port
@@ -16,8 +21,6 @@ spring:
active: dev
application:
name: taoyao-server
jackson:
time-zone: GMT+8
servlet:
multipart:
max-file-size: 256MB
@@ -47,6 +50,9 @@ taoyao:
timeout: 5000
version: 1.0.0
description: WebRTC信令服务
id:
sn: 0
max-index: 999999
webrtc:
type: SFU
stun:
@@ -56,10 +62,12 @@ taoyao:
- stun:stun4.l.google.com:19302
- stun:stun.stunprotocol.org:3478
turn:
record:
storage: /data/record
security:
realm: taoyao
permit: /v3/api-docs/,/swagger-ui/,/error
username:
password:
permit: /v3/api-docs/,/swagger-ui/,/favicon.ico,/error
username: taoyao
password: taoyao
scheduled:
session: 0 * * * * ?

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,35 @@
package com.acgist.taoyao.boot.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import com.acgist.taoyao.main.TaoyaoApplication;
import com.acgist.taoyao.test.annotation.TaoyaoTest;
import com.acgist.taoyao.test.annotation.CostedTest;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@TaoyaoTest(classes = TaoyaoApplication.class)
//@SpringBootTest(classes = TaoyaoApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class IdServiceTest {
@Autowired
private IdService idService;
@Test
// @Timeout(value = 1000, unit = TimeUnit.MILLISECONDS)
// @Rollback()
// @RepeatedTest(10)
void testId() {
final long id = this.idService.id();
log.info("生成ID{}", id);
}
@Test
@CostedTest(count = 100000, thread = 10)
void testIdCosted() {
this.idService.id();
}
}

View File

@@ -27,12 +27,14 @@ public class RegisterListener extends ApplicationListenerAdapter<RegisterEvent>
@Override
public void onApplicationEvent(RegisterEvent event) {
final ClientSession session = event.getSession();
if(!session.authorized()) {
if (!session.authorized()) {
return;
}
final Message message = this.onlineProtocol.build();
message.setBody(Map.of("sn", session.sn()));
this.clientSessionManager.broadcast(message);
this.clientSessionManager.broadcast(session.sn(), message);
// TODOip等等
// TODO重新注册上来需要掉线重连
}
}

View File

@@ -0,0 +1,30 @@
package com.acgist.taoyao.signal.media;
/**
* 终端媒体操作
*
* @author acgist
*/
public interface ClientMediaHandler {
/**
* 打开
*/
void open(String id);
/**
* 暂停
*/
void pause();
/**
* 恢复
*/
void resume();
/**
* 关闭
*/
void close();
}

View File

@@ -0,0 +1,14 @@
package com.acgist.taoyao.signal.media;
/**
* 终端推流
*/
public class ClientMediaPublisher {
public void publish(String id) {
}
public void unpublish(String id) {
}
}

View File

@@ -0,0 +1,40 @@
package com.acgist.taoyao.signal.media;
/**
* 终端媒体订阅者(终端拉流)
*
* @author acgist
*/
public class ClientMediaSubscriber implements ClientMediaHandler {
public void subscribe(String id) {
}
public void unsubscribe(String id) {
}
@Override
public void open(String id) {
// TODO Auto-generated method stub
}
@Override
public void pause() {
// TODO Auto-generated method stub
}
@Override
public void resume() {
// TODO Auto-generated method stub
}
@Override
public void close() {
// TODO Auto-generated method stub
}
}

View File

@@ -0,0 +1,10 @@
package com.acgist.taoyao.signal.media.router;
/**
* 终端媒体流路由(推流->拉流)
*
* @author acgist
*/
public interface ClientMediaStreamRouter {
}

View File

@@ -0,0 +1,10 @@
package com.acgist.taoyao.signal.media.router;
import com.acgist.taoyao.signal.media.stream.ClientMediaStream;
public class ClientMediaStreamRouterAdapter {
ClientMediaStream source;
ClientMediaStream target;
}

View File

@@ -0,0 +1,89 @@
package com.acgist.taoyao.signal.media.stream;
/**
* 终端媒体流
*
* @author acgist
*/
public interface ClientMediaStream {
/**
* 终端媒体类型
*
* @author acgist
*/
public enum Type {
/**
* 音频
*/
AUDIO,
/**
* 视频
*/
VIDEO;
}
/**
* 终端媒体流状态
*
* @author acgist
*/
public enum Status {
/**
* 没有激活
*/
IDLE,
/**
* 已经激活
*/
BUSY,
/**
* 已经暂停
*/
PAUSE,
/**
* 已经关闭
*/
CLOSE;
}
/**
* @return 终端媒体流ID
*/
String id();
/**
* 打开终端媒体流
*/
void open();
/**
* 暂停终端媒体流
*/
void pause();
/**
* 恢复终端媒体流
*/
void resume();
/**
* 关闭终端媒体流
*/
void close();
/**
* @return 终端媒体流类型
*/
Type type();
/**
* @return 终端媒体流状态
*/
Status status();
}

View File

@@ -0,0 +1,19 @@
package com.acgist.taoyao.signal.media.stream;
/**
* 终端媒体流适配器
*
* @author acgist
*/
public abstract class ClientMediaStreamAdapter<T> implements ClientMediaStream {
/**
* 媒体标识
*/
private String id;
/**
* 真实流
*/
protected T stream;
}

View File

@@ -60,6 +60,7 @@ public class ProtocolManager {
* @param instance 会话实例
*/
public void execute(String message, AutoCloseable instance) {
log.debug("执行信令消息:{}", message);
if(StringUtils.isEmpty(message)) {
log.warn("消息为空:{}", message);
return;

View File

@@ -1,6 +1,8 @@
package com.acgist.taoyao.signal.session;
import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.signal.media.ClientMediaPublisher;
import com.acgist.taoyao.signal.media.ClientMediaSubscriber;
/**
* 会话
@@ -16,6 +18,21 @@ public interface ClientSession extends AutoCloseable {
*/
String sn();
/**
* @return 终端状态
*/
ClientSessionStatus status();
/**
* @return 终端媒体发布者
*/
ClientMediaPublisher publisher();
/**
* @return 终端媒体订阅者
*/
ClientMediaSubscriber subscriber();
/**
* 推送消息
*

View File

@@ -2,6 +2,9 @@ package com.acgist.taoyao.signal.session;
import org.apache.commons.lang3.StringUtils;
import com.acgist.taoyao.signal.media.ClientMediaPublisher;
import com.acgist.taoyao.signal.media.ClientMediaSubscriber;
/**
* 会话适配器
*
@@ -25,11 +28,26 @@ public abstract class ClientSessionAdapter<T extends AutoCloseable> implements C
* 是否授权
*/
protected boolean authorized;
/**
* 终端状态
*/
protected ClientSessionStatus status;
/**
* 终端媒体发布者
*/
protected ClientMediaPublisher publisher;
/**
* 终端媒体订阅者
*/
protected ClientMediaSubscriber subscriber;
protected ClientSessionAdapter(T instance) {
this.time = System.currentTimeMillis();
this.instance = instance;
this.authorized = false;
this.status = new ClientSessionStatus();
this.publisher = new ClientMediaPublisher();
this.subscriber = new ClientMediaSubscriber();
}
@Override
@@ -37,6 +55,21 @@ public abstract class ClientSessionAdapter<T extends AutoCloseable> implements C
return this.sn;
}
@Override
public ClientSessionStatus status() {
return this.status;
}
@Override
public ClientMediaPublisher publisher() {
return this.publisher;
}
@Override
public ClientMediaSubscriber subscriber() {
return this.subscriber;
}
@Override
public boolean timeout(long timeout) {
return !(this.authorized && System.currentTimeMillis() - this.time <= timeout);

View File

@@ -101,9 +101,8 @@ public class ClientSessionManager {
try {
if(session != null) {
session.close();
} else {
instance.close();
}
instance.close();
} catch (Exception e) {
log.error("关闭会话异常", e);
} finally {

View File

@@ -1,12 +1,36 @@
package com.acgist.taoyao.signal.session;
import lombok.Getter;
import lombok.Setter;
/**
* 终端状态
*
* @author acgist
*/
@Getter
@Setter
public class ClientSessionStatus {
/**
* 终端标识
*/
private String sn;
/**
* IP
*/
private String ip;
/**
* MAC
*/
private String mac;
/**
* 信号强度0~100
*/
private Integer signal = 0;
/**
* 电量0~100
*/
private Integer battery = 0;
}

View File

@@ -0,0 +1,36 @@
package com.acgist.taoyao.signal.session.socket;
import java.io.IOException;
import java.net.Socket;
import com.acgist.taoyao.boot.model.Message;
import com.acgist.taoyao.signal.session.ClientSessionAdapter;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
/**
* Socket会话
*
* @author acgist
*/
@Slf4j
@Getter
@Setter
public class SocketSession extends ClientSessionAdapter<Socket> {
public SocketSession(Socket instance) {
super(instance);
}
@Override
public void push(Message message) {
try {
this.instance.getOutputStream().write(message.toString().getBytes());
} catch (IOException e) {
log.error("Socket发送消息异常{}", message, e);
}
}
}

View File

@@ -1,7 +1,5 @@
package com.acgist.taoyao.signal.session.websocket;
import java.io.IOException;
import javax.websocket.Session;
import com.acgist.taoyao.boot.model.Message;
@@ -28,8 +26,12 @@ public class WebSocketSession extends ClientSessionAdapter<Session> {
@Override
public void push(Message message) {
try {
this.instance.getBasicRemote().sendText(message.toString());
} catch (IOException e) {
if(this.instance.isOpen()) {
this.instance.getBasicRemote().sendText(message.toString());
} else {
log.error("会话已经关闭:{}", this.instance);
}
} catch (Exception e) {
log.error("WebSocket发送消息异常{}", message, e);
}
}

View File

@@ -1,7 +1,5 @@
package com.acgist.taoyao.signal.session.websocket;
import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
@@ -70,8 +68,12 @@ public class WebSocketSignal {
*/
private void push(Session session, Message message) {
try {
session.getBasicRemote().sendText(message.toString());
} catch (IOException e) {
if(session.isOpen()) {
session.getBasicRemote().sendText(message.toString());
} else {
log.error("会话已经关闭:{}", session);
}
} catch (Exception e) {
log.error("推送消息异常:{}", message, e);
}
}

27
taoyao-test/pom.xml Normal file
View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.acgist</groupId>
<artifactId>taoyao</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>taoyao-test</artifactId>
<packaging>jar</packaging>
<name>taoyao-test</name>
<description>测试:测试工具</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,42 @@
package com.acgist.taoyao.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 多线程测试
*
* @author acgist
*/
@Target(ElementType.METHOD)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CostedTest {
/**
* @return 执行次数
*/
int count() default 1;
/**
* @return 线程数量
*/
int thread() default 1;
/**
* @return 超时时间
*/
long timeout() default 1000;
/**
* @return 超时时间单位
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

View File

@@ -0,0 +1,55 @@
package com.acgist.taoyao.test.annotation;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import lombok.extern.slf4j.Slf4j;
/**
* 多线程测试监听
*
* @author acgist
*/
@Slf4j
public class CostedTestTestExecutionListener implements TestExecutionListener {
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
final CostedTest costedTest = testContext.getTestMethod().getDeclaredAnnotation(CostedTest.class);
final int count = costedTest.count();
final int thread = costedTest.thread();
final long timeout = costedTest.timeout();
final TimeUnit timeUnit = costedTest.timeUnit();
final long aTime = System.currentTimeMillis();
if(thread == 1) {
for (int index = 0; index < count; index++) {
testContext.getTestMethod().invoke(testContext.getTestInstance());
}
} else {
final CountDownLatch countDownLatch = new CountDownLatch(count);
final ExecutorService executor = Executors.newFixedThreadPool(thread);
for (int index = 0; index < count; index++) {
executor.execute(() -> {
try {
testContext.getTestMethod().invoke(testContext.getTestInstance());
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
log.error("多线程测试异常", e);
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await(timeout, timeUnit);
}
final long zTime = System.currentTimeMillis();
final long costed = zTime - aTime;
log.info("多线程测试消耗时间:{}", costed);
}
}

View File

@@ -0,0 +1,31 @@
package com.acgist.taoyao.test.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.TestExecutionListeners.MergeMode;
/**
* 测试启动
*
* @author acgist
*/
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@TestExecutionListeners(listeners = CostedTestTestExecutionListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS)
public @interface TaoyaoTest {
@AliasFor(annotation = SpringBootTest.class)
Class<?>[] classes() default {};
}