diff --git a/xinelu-admin/src/main/resources/application.yml b/xinelu-admin/src/main/resources/application.yml
index 9f59fc2..3685ecc 100644
--- a/xinelu-admin/src/main/resources/application.yml
+++ b/xinelu-admin/src/main/resources/application.yml
@@ -169,3 +169,10 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 腾讯云音视频
+trtc:
+ sdkappid: 1400236771
+ sdksecretkey: 83ab78d1a8513af6626d58cc2bacd7b28bfb2af06515fa02b0204129ebb53422
+ secretid: AKIDOBpP2ICALat0wP4lcIiAMtZ7XgUJ5vMO
+ secretkey: zxjJhGcx75lECyweHgphKYefWCkBPSHt
diff --git a/xinelu-common/pom.xml b/xinelu-common/pom.xml
index 657082d..e68911b 100644
--- a/xinelu-common/pom.xml
+++ b/xinelu-common/pom.xml
@@ -17,6 +17,17 @@
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
org.springframework
@@ -135,6 +146,40 @@
org.projectlomboklombok
+
+
+ org.springframework
+ spring-websocket
+
+
+ com.squareup.okio
+ okio
+ 1.12.0
+
+
+ com.squareup.okhttp
+ okhttp
+ 2.7.5
+
+
+ com.google.code.gson
+ gson
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.4.7
+
+
+ org.apache.tomcat.embed
+ tomcat-embed-websocket
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/core/dto/MessageTemplate.java b/xinelu-common/src/main/java/com/xinelu/common/core/dto/MessageTemplate.java
new file mode 100644
index 0000000..f5a80de
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/core/dto/MessageTemplate.java
@@ -0,0 +1,55 @@
+package com.xinelu.common.core.dto;
+
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * shitianqi
+ * 2022-01-19 19:02
+ * 长连接消息模板
+ */
+@Data
+public class MessageTemplate {
+ /**
+ * 发送消息对象key值(消息来源)
+ */
+ private String fromKey;
+
+ /**
+ * 发送消息对象Name值(消息来源)
+ */
+ private String fromName;
+
+ /**
+ * 接收消息对象key值(消息去向)
+ */
+ private String toKey;
+
+ /**
+ * 接收消息对象name值(消息去向)
+ */
+ private String toName;
+
+ /**
+ * 消息内容(Json)
+ */
+ private String message;
+
+ /**
+ * 消息类型(标识在{@link com.xinelu.common.enums.MessageContentType}中)
+ */
+ private String msgType;
+
+ /**
+ * 发送时间
+ * */
+ private Date sendTime;
+
+ /**
+ * 问诊记录业务主键
+ */
+ private String recordId;
+
+ /** 问诊房间号 */
+ private String roomNo;
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/enums/MessageContentType.java b/xinelu-common/src/main/java/com/xinelu/common/enums/MessageContentType.java
new file mode 100644
index 0000000..17b2b60
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/enums/MessageContentType.java
@@ -0,0 +1,21 @@
+package com.xinelu.common.enums;
+
+import lombok.Getter;
+
+/**
+ * shitainqi
+ * 2022-01-18 21:01
+ * 长连接内容对象枚举列表(在此处增加长连接返回对象)
+ */
+@Getter
+public enum MessageContentType {
+
+ DEVICE,
+ NOTICE,
+ PUSH,
+ CHAT,
+ /** 会诊 */
+ CONSULTATION,
+ /** 问诊 */
+ VIDEO
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocket.java b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocket.java
new file mode 100644
index 0000000..01b3083
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocket.java
@@ -0,0 +1,74 @@
+package com.xinelu.common.socket;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+@ServerEndpoint(value = "/webSocket/{key}")
+public class WebSocket {
+
+ /**
+ * 连接建立成功调用的方法
+ */
+ @OnOpen
+ public void onOpen(@PathParam(value = "key") String key, Session session) throws IOException {
+ if (StringUtils.isNotBlank(key)) {
+ WebSocketUtils.add(key, session);
+ } else {
+ session.getAsyncRemote().sendText("来自服务器的消息:您未传递key值");
+ }
+ }
+
+ /**
+ * 连接关闭调用的方法
+ */
+ @OnClose
+ public void onClose(@PathParam("key") String key) throws IOException {
+ WebSocketUtils.remove(key);
+ }
+
+ /**
+ * 收到客户端消息后调用的方法
+ *
+ * @param message 客户端发送过来的消息
+ */
+ @OnMessage
+ public void onMessage(String message, Session fromSession) {
+ if (message.equals("ping")) {
+ return;
+ }
+ for (Map.Entry sessionEntry : WebSocketUtils.clients.entrySet()) {
+ // 获取自己
+ if (fromSession.getId().equals(sessionEntry.getValue().getId())) {
+ //打印消息
+ WebSocketUtils.receive(sessionEntry.getKey(), message);
+ }
+ }
+
+ }
+
+ @OnError
+ public void onError(Session session, Throwable error) throws IOException {
+ for (Map.Entry sessionEntry : WebSocketUtils.clients.entrySet()) {
+ // 获取自己
+ if (session.getId().equals(sessionEntry.getValue().getId())) {
+ //清除异常连接
+ WebSocketUtils.remove(sessionEntry.getKey());
+ }
+ }
+ log.error("发生错误");
+ error.printStackTrace();
+ }
+
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketConfig.java b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketConfig.java
new file mode 100644
index 0000000..aed392b
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketConfig.java
@@ -0,0 +1,17 @@
+package com.xinelu.common.socket;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+
+ /**
+ * 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
+ */
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketUtils.java b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketUtils.java
new file mode 100644
index 0000000..80dbf38
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/socket/WebSocketUtils.java
@@ -0,0 +1,102 @@
+package com.xinelu.common.socket;
+
+import com.alibaba.fastjson2.JSON;
+import com.xinelu.common.core.dto.MessageTemplate;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.websocket.Session;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * shitianqi
+ * 2022-01-18 19:17
+ * socket类和请求和方法
+ */
+@Slf4j
+public class WebSocketUtils {
+
+
+ //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
+ /**
+ * 记录当前在线连接数
+ */
+ public static AtomicInteger onlineCount = new AtomicInteger();
+
+ /**
+ * 存放所有在线的客户端
+ */
+ public static ConcurrentHashMap clients = new ConcurrentHashMap<>();
+
+ /**
+ * shitianqi
+ * 2022-01-18 19:47
+ * 上线用户
+ *
+ * @param key
+ * @param session
+ */
+ public static void add(String key, Session session) throws IOException {
+ remove(key);
+ clients.put(key, session);
+ onlineCount.incrementAndGet(); // 在线数加1
+ int count = onlineCount.get();
+ log.info("有新连接加入:{},当前在线人数为:{}", key, count > 0? count: 0);
+ }
+
+
+ /**
+ * shitianqi
+ * 2022-01-18 19:47
+ * 打印某在线用户接收消息内容和在线情况到日志
+ *
+ * @param key
+ * @param message
+ */
+ public static void receive(String key, String message) {
+ log.info("服务端收到客户端[{}]的消息:{}", key, message);
+// log.info("当前连接数 = " + clients.size());
+ }
+
+ /**
+ * shitianqi
+ * 2022-01-18 19:47
+ * 下线用户
+ *
+ * @param key
+ */
+ public static void remove(String key) throws IOException {
+ if (clients.containsKey(key)) {
+ clients.get(key).close();
+ clients.remove(key);
+ onlineCount.decrementAndGet(); // 在线数减1
+// log.info("有一连接关闭:{},当前在线人数为:{}", key, onlineCount.get());
+ }
+// log.info("当前连接数 = " + clients.size());
+ }
+
+ /**
+ * shitianqi
+ * 2022-01-18 19:47
+ * 发送消息(内容为对象json)
+ *
+ * @param key
+ * @param msg
+ * @return boolean 是否成功
+ */
+ public static boolean sendMessage(String key, MessageTemplate msg) {
+// log.info("当前连接数 = " + clients.size());
+ if (clients.get(key) == null) {
+ return false;
+ } else {
+ try {
+ log.info("发送消息:" + JSON.toJSONString(msg));
+ clients.get(key).getBasicRemote().sendText(JSON.toJSONString(msg));
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractClient.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractClient.java
new file mode 100644
index 0000000..ab12ccc
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractClient.java
@@ -0,0 +1,521 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+import com.squareup.okhttp.Headers;
+import com.squareup.okhttp.Headers.Builder;
+import com.squareup.okhttp.Response;
+import com.xinelu.common.utils.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.xinelu.common.utils.tencentcloudapi.common.http.HttpConnection;
+import com.xinelu.common.utils.tencentcloudapi.common.profile.ClientProfile;
+import com.xinelu.common.utils.tencentcloudapi.common.profile.HttpProfile;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Type;
+import java.net.URLEncoder;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.UUID;
+import javax.crypto.Mac;
+import javax.net.ssl.SSLContext;
+import javax.xml.bind.DatatypeConverter;
+
+/**
+ * 抽象client类
+ */
+abstract public class AbstractClient {
+
+ public static final int HTTP_RSP_OK = 200;
+ public static final String SDK_VERSION = "SDK_JAVA_3.0.89";
+
+
+ private Credential credential;
+ private ClientProfile profile;
+ private String endpoint;
+ private String region;
+ private String path;
+ private String sdkVersion;
+ private String apiVersion;
+ public Gson gson;
+
+ public AbstractClient(String endpoint, String version, Credential credential, String region) {
+ this(endpoint, version, credential, region, new ClientProfile());
+ }
+
+ public AbstractClient(String endpoint, String version, Credential credential, String region,
+ ClientProfile profile) {
+
+ this.credential = credential;
+ this.profile = profile;
+ this.endpoint = endpoint;
+ this.region = region;
+ this.path = "/";
+ this.sdkVersion = AbstractClient.SDK_VERSION;
+ this.apiVersion = version;
+ this.gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+ warmup();
+ }
+
+ /**
+ * 设置产品地域
+ *
+ * @param region
+ * 产品地域
+ */
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ /**
+ * 返回产品地域
+ *
+ * @return 地域名称
+ */
+ public String getRegion() {
+ return this.region;
+ }
+
+ /**
+ * 设置配置实例
+ *
+ * @param profile
+ * 配置实例
+ */
+ public void setClientProfile(ClientProfile profile) {
+ this.profile = profile;
+ }
+
+ /**
+ * 获取配置实例
+ *
+ * @return 配置实例
+ */
+ public ClientProfile getClientProfile() {
+ return this.profile;
+ }
+
+ /**
+ * 设置认证信息实例
+ *
+ * @param credential
+ * 认证信息实例
+ */
+ public void setCredential(Credential credential) {
+ this.credential = credential;
+ }
+
+ /**
+ * 获取认证信息实例
+ *
+ * @return 认证信息实例
+ */
+ public Credential getCredential() {
+ return this.credential;
+ }
+
+ /**
+ * Use post/json with tc3-hmac-sha256 signature to call any action. Ignore
+ * request method and signature method defined in profile.
+ *
+ * @param action
+ * Name of action to be called.
+ * @param jsonPayload
+ * Parameters of action serialized in json string format.
+ * @return Raw response from API if request succeeded, otherwise an exception
+ * will be raised instead of raw response
+ * @throws TencentCloudSDKException
+ */
+ public String call(String action, String jsonPayload) throws TencentCloudSDKException {
+ String endpoint = this.endpoint;
+ // in case user has reset endpoint after init this client
+ if (!(this.profile.getHttpProfile().getEndpoint() == null)) {
+ endpoint = this.profile.getHttpProfile().getEndpoint();
+ }
+ // always use post tc3-hmac-sha256 signature process
+ // okhttp always set charset even we don't specify it,
+ // to ensure signature be correct, we have to set it here as well.
+ String contentType = "application/json; charset=utf-8";
+ byte[] requestPayload = jsonPayload.getBytes();
+ String canonicalUri = "/";
+ String canonicalQueryString = "";
+ String canonicalHeaders = "content-type:" + contentType + "\nhost:" + endpoint + "\n";
+ String signedHeaders = "content-type;host";
+
+ String hashedRequestPayload = "";
+ if (this.profile.isUnsignedPayload()) {
+ hashedRequestPayload = Sign.sha256Hex("UNSIGNED-PAYLOAD".getBytes());
+ } else {
+ hashedRequestPayload = Sign.sha256Hex(requestPayload);
+ }
+ String canonicalRequest = HttpProfile.REQ_POST + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
+ + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
+
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
+ String service = endpoint.split("\\.")[0];
+ String credentialScope = date + "/" + service + "/" + "tc3_request";
+ String hashedCanonicalRequest = Sign.sha256Hex(canonicalRequest.getBytes());
+ String stringToSign = "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
+
+ String secretId = this.credential.getSecretId();
+ String secretKey = this.credential.getSecretKey();
+ byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(), date);
+ byte[] secretService = Sign.hmac256(secretDate, service);
+ byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
+ String signature = DatatypeConverter.printHexBinary(Sign.hmac256(secretSigning, stringToSign)).toLowerCase();
+ String authorization = "TC3-HMAC-SHA256 " + "Credential=" + secretId + "/" + credentialScope + ", "
+ + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
+
+ HttpConnection conn = new HttpConnection(this.profile.getHttpProfile().getConnTimeout(),
+ this.profile.getHttpProfile().getReadTimeout(), this.profile.getHttpProfile().getWriteTimeout());
+ String url = this.profile.getHttpProfile().getProtocol() + endpoint + this.path;
+ Builder hb = new Headers.Builder();
+ hb.add("Content-Type", contentType).add("Host", endpoint).add("Authorization", authorization)
+ .add("X-TC-Action", action).add("X-TC-Timestamp", timestamp).add("X-TC-Version", this.apiVersion)
+ .add("X-TC-Region", this.getRegion()).add("X-TC-RequestClient", SDK_VERSION);
+ String token = this.credential.getToken();
+ if (token != null && !token.isEmpty()) {
+ hb.add("X-TC-Token", token);
+ }
+ if (this.profile.isUnsignedPayload()) {
+ hb.add("X-TC-Content-SHA256", "UNSIGNED-PAYLOAD");
+ }
+
+ Headers headers = hb.build();
+ Response resp = conn.postRequest(url, requestPayload, headers);
+ if (resp.code() != AbstractClient.HTTP_RSP_OK) {
+ throw new TencentCloudSDKException(resp.code() + resp.message());
+ }
+ String respbody = null;
+ try {
+ respbody = resp.body().string();
+ } catch (IOException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ JsonResponseModel errResp = null;
+ try {
+ Type errType = new TypeToken>() {
+ }.getType();
+ errResp = gson.fromJson(respbody, errType);
+ } catch (JsonSyntaxException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ if (errResp.response.error != null) {
+ throw new TencentCloudSDKException(errResp.response.error.code + "-" + errResp.response.error.message,
+ errResp.response.requestId);
+ }
+ return respbody;
+ }
+
+ protected String internalRequest(AbstractModel request, String actionName) throws TencentCloudSDKException {
+ Response okRsp = null;
+ String endpoint = this.endpoint;
+ if (!(this.profile.getHttpProfile().getEndpoint() == null)) {
+ endpoint = this.profile.getHttpProfile().getEndpoint();
+ }
+ String [] binaryParams = request.getBinaryParams();
+ String sm = this.profile.getSignMethod();
+ String reqMethod = this.profile.getHttpProfile().getReqMethod();
+
+ // currently, customized params only can be supported via post json tc3-hmac-sha256
+ HashMap customizedParams = request.any();
+ if (customizedParams.size() > 0) {
+ if (binaryParams.length > 0) {
+ throw new TencentCloudSDKException("WrongUsage: Cannot post multipart with customized parameters.");
+ }
+ if (sm.equals(ClientProfile.SIGN_SHA1) || sm.equals(ClientProfile.SIGN_SHA256)) {
+ throw new TencentCloudSDKException("WrongUsage: Cannot use HmacSHA1 or HmacSHA256 with customized parameters.");
+ }
+ if (reqMethod.equals(HttpProfile.REQ_GET)) {
+ throw new TencentCloudSDKException("WrongUsage: Cannot use get method with customized parameters.");
+ }
+ }
+
+ if (binaryParams.length > 0 || sm.equals(ClientProfile.SIGN_TC3_256)) {
+ okRsp = doRequestWithTC3(endpoint, request, actionName);
+ } else if (sm.equals(ClientProfile.SIGN_SHA1) || sm.equals(ClientProfile.SIGN_SHA256)) {
+ okRsp = doRequest(endpoint, request, actionName);
+ } else {
+ throw new TencentCloudSDKException("Signature method " + sm + " is invalid or not supported yet.");
+ }
+
+ if (okRsp.code() != AbstractClient.HTTP_RSP_OK) {
+ throw new TencentCloudSDKException(okRsp.code() + okRsp.message());
+ }
+ String strResp = null;
+ try {
+ strResp = okRsp.body().string();
+ } catch (IOException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ JsonResponseModel errResp = null;
+ try {
+ Type errType = new TypeToken>() {
+ }.getType();
+ errResp = gson.fromJson(strResp, errType);
+ } catch (JsonSyntaxException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ if (errResp.response.error != null) {
+ throw new TencentCloudSDKException(errResp.response.error.code + "-" + errResp.response.error.message,
+ errResp.response.requestId);
+ }
+
+ return strResp;
+ }
+
+ private Response doRequest(String endpoint, AbstractModel request, String action) throws TencentCloudSDKException {
+ HashMap param = new HashMap();
+ request.toMap(param, "");
+ String strParam = this.formatRequestData(action, param);
+ HttpConnection conn = new HttpConnection(
+ this.profile.getHttpProfile().getConnTimeout(),
+ this.profile.getHttpProfile().getReadTimeout(),
+ this.profile.getHttpProfile().getWriteTimeout());
+ String reqMethod = this.profile.getHttpProfile().getReqMethod();
+ String url = this.profile.getHttpProfile().getProtocol() + endpoint + this.path;
+ if (reqMethod.equals(HttpProfile.REQ_GET)) {
+ return conn.getRequest(url + "?" + strParam);
+ } else if (reqMethod.equals(HttpProfile.REQ_POST)) {
+ return conn.postRequest(url, strParam);
+ } else {
+ throw new TencentCloudSDKException("Method only support (GET, POST)");
+ }
+ }
+
+ private Response doRequestWithTC3(String endpoint, AbstractModel request, String action) throws TencentCloudSDKException {
+ String httpRequestMethod = this.profile.getHttpProfile().getReqMethod();
+ if (httpRequestMethod == null) {
+ throw new TencentCloudSDKException("Request method should not be null, can only be GET or POST");
+ }
+ String contentType = "application/x-www-form-urlencoded";
+ byte [] requestPayload = "".getBytes();
+ HashMap params = new HashMap();
+ request.toMap(params, "");
+ String [] binaryParams = request.getBinaryParams();
+ if ( binaryParams.length > 0 ) {
+ httpRequestMethod = HttpProfile.REQ_POST;
+ String boundary = UUID.randomUUID().toString();
+ // okhttp always set charset even we don't specify it,
+ // to ensure signature be correct, we have to set it here as well.
+ contentType = "multipart/form-data; charset=utf-8" + "; boundary=" + boundary;
+ try {
+ requestPayload = getMultipartPayload(request, boundary);
+ } catch (Exception e) {
+ throw new TencentCloudSDKException("Failed to generate multipart. because: " + e);
+ }
+ } else if (httpRequestMethod.equals(HttpProfile.REQ_POST)) {
+ requestPayload = AbstractModel.toJsonString(request).getBytes();
+ // okhttp always set charset even we don't specify it,
+ // to ensure signature be correct, we have to set it here as well.
+ contentType = "application/json; charset=utf-8";
+ }
+ String canonicalUri = "/";
+ String canonicalQueryString = this.getCanonicalQueryString(params, httpRequestMethod);
+ String canonicalHeaders = "content-type:" + contentType + "\nhost:" + endpoint + "\n";
+ String signedHeaders = "content-type;host";
+
+ String hashedRequestPayload = "";
+ if (this.profile.isUnsignedPayload()) {
+ hashedRequestPayload = Sign.sha256Hex("UNSIGNED-PAYLOAD".getBytes());
+ } else {
+ hashedRequestPayload = Sign.sha256Hex(requestPayload);
+ }
+ String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
+ + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
+
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
+ String service = endpoint.split("\\.")[0];
+ String credentialScope = date + "/" + service + "/" + "tc3_request";
+ String hashedCanonicalRequest = Sign.sha256Hex(canonicalRequest.getBytes());
+ String stringToSign = "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
+
+ String secretId = this.credential.getSecretId();
+ String secretKey = this.credential.getSecretKey();
+ byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(), date);
+ byte[] secretService = Sign.hmac256(secretDate, service);
+ byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
+ String signature = DatatypeConverter.printHexBinary(Sign.hmac256(secretSigning, stringToSign)).toLowerCase();
+ String authorization = "TC3-HMAC-SHA256 " + "Credential=" + secretId + "/" + credentialScope + ", "
+ + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
+
+ HttpConnection conn = new HttpConnection(
+ this.profile.getHttpProfile().getConnTimeout(),
+ this.profile.getHttpProfile().getReadTimeout(),
+ this.profile.getHttpProfile().getWriteTimeout());
+ String url = this.profile.getHttpProfile().getProtocol() + endpoint + this.path;
+ Builder hb = new Headers.Builder();
+ hb.add("Content-Type", contentType)
+ .add("Host", endpoint)
+ .add("Authorization", authorization)
+ .add("X-TC-Action", action)
+ .add("X-TC-Timestamp", timestamp)
+ .add("X-TC-Version", this.apiVersion)
+ .add("X-TC-RequestClient", SDK_VERSION);
+ if (null != this.getRegion()) {
+ hb.add("X-TC-Region", this.getRegion());
+ }
+ String token = this.credential.getToken();
+ if (token != null && ! token.isEmpty()) {
+ hb.add("X-TC-Token", token);
+ }
+ if (this.profile.isUnsignedPayload()) {
+ hb.add("X-TC-Content-SHA256", "UNSIGNED-PAYLOAD");
+ }
+
+ Headers headers = hb.build();
+ if (httpRequestMethod.equals(HttpProfile.REQ_GET)) {
+ return conn.getRequest(url + "?" + canonicalQueryString, headers);
+ } else if (httpRequestMethod.equals(HttpProfile.REQ_POST)) {
+ return conn.postRequest(url, requestPayload, headers);
+ } else {
+ throw new TencentCloudSDKException("Method only support GET, POST");
+ }
+ }
+
+ private byte [] getMultipartPayload(AbstractModel request, String boundary) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ String [] binaryParams = request.getBinaryParams();
+ for (Map.Entry entry : request.getMultipartRequestParams().entrySet()) {
+ baos.write("--".getBytes());
+ baos.write(boundary.getBytes());
+ baos.write("\r\n".getBytes());
+ baos.write("Content-Disposition: form-data; name=\"".getBytes());
+ baos.write(entry.getKey().getBytes());
+ if (Arrays.asList(binaryParams).contains(entry.getKey())) {
+ baos.write("\"; filename=\"".getBytes());
+ baos.write(entry.getKey().getBytes());
+ baos.write("\"\r\n".getBytes());
+ } else {
+ baos.write("\"\r\n".getBytes());
+ }
+ baos.write("\r\n".getBytes());
+ baos.write(entry.getValue());
+ baos.write("\r\n".getBytes());
+ }
+ if (baos.size() != 0) {
+ baos.write("--".getBytes());
+ baos.write(boundary.getBytes());
+ baos.write("--\r\n".getBytes());
+ }
+ byte [] bytes = baos.toByteArray();
+ baos.close();
+ return bytes;
+ }
+
+ private String getCanonicalQueryString(HashMap params, String method) throws TencentCloudSDKException {
+ if ( method != null && method.equals(HttpProfile.REQ_POST)) {
+ return "";
+ }
+ StringBuilder queryString = new StringBuilder("");
+ for (Map.Entry entry : params.entrySet()) {
+ String v;
+ try {
+ v = URLEncoder.encode(entry.getValue(), "UTF8");
+ } catch (UnsupportedEncodingException e) {
+ throw new TencentCloudSDKException("UTF8 is not supported." + e.getMessage());
+ }
+ queryString.append("&")
+ .append(entry.getKey())
+ .append("=")
+ .append(v);
+ }
+ return queryString.toString().substring(1);
+ }
+
+ private String formatRequestData(String action, Map param) throws TencentCloudSDKException {
+ param.put("Action", action);
+ param.put("RequestClient", this.sdkVersion);
+ param.put("Nonce", String.valueOf(Math.abs(new Random().nextInt())));
+ param.put("Timestamp", String.valueOf(System.currentTimeMillis() / 1000));
+ param.put("Version", this.apiVersion);
+
+ if (this.credential.getSecretId() != null && (!this.credential.getSecretId().isEmpty())) {
+ param.put("SecretId", this.credential.getSecretId());
+ }
+
+ if (this.region != null && (!this.region.isEmpty())) {
+ param.put("Region", this.region);
+ }
+
+ if (this.profile.getSignMethod() != null && (!this.profile.getSignMethod().isEmpty())) {
+ param.put("SignatureMethod", this.profile.getSignMethod());
+ }
+
+ if (this.credential.getToken() != null && (!this.credential.getToken().isEmpty())) {
+ param.put("Token", this.credential.getToken());
+ }
+
+ String endpoint = this.endpoint;
+ if (!(this.profile.getHttpProfile().getEndpoint() == null)) {
+ endpoint = this.profile.getHttpProfile().getEndpoint();
+ }
+
+ String sigInParam = Sign.makeSignPlainText(new TreeMap(param),
+ this.profile.getHttpProfile().getReqMethod(), endpoint, this.path);
+ String sigOutParam = Sign.sign(this.credential.getSecretKey(), sigInParam, this.profile.getSignMethod());
+
+ String strParam = "";
+ try {
+ for (Map.Entry entry : param.entrySet()) {
+ strParam += (URLEncoder.encode(entry.getKey(), "utf-8") + "="
+ + URLEncoder.encode(entry.getValue(), "utf-8") + "&");
+ }
+ strParam += ("Signature=" + URLEncoder.encode(sigOutParam, "utf-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ return strParam;
+ }
+
+ /**
+ * warm up, try to avoid unnecessary cost in the first request
+ */
+ private void warmup() {
+ try {
+ // it happens in SDK signature process.
+ // first invoke costs around 250 ms.
+ Mac.getInstance("HmacSHA1");
+ Mac.getInstance("HmacSHA256");
+ // it happens inside okhttp, but I think any https framework/package will do the same.
+ // first invoke costs around 150 ms.
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+ sslContext.init(null, null, null);
+ } catch (Exception e) {
+ // ignore but print message to console
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractModel.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractModel.java
new file mode 100644
index 0000000..3a0c7d9
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/AbstractModel.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 抽象model类
+ */
+abstract public class AbstractModel {
+ // any stores customized parameters which are not documented.
+ // You should make sure it can be correctly serialized to json string.
+ private HashMap customizedParams = new HashMap();
+
+ protected abstract void toMap(HashMap map, String prefix);
+
+ /*
+ * valid only when it's a request object.
+ * some actions can only be posted in multipart format,
+ * this method is used to mark which parameters are binary type.
+ */
+ protected String [] getBinaryParams() {
+ return new String[0];
+ }
+
+ /*
+ * valid only when it's a multipart request object.
+ */
+ protected HashMap getMultipartRequestParams() {
+ return new HashMap();
+ }
+
+ protected void setParamSimple(HashMap map, String key, V value) {
+ if (value != null) {
+
+ key = key.substring(0, 1).toUpperCase() + key.substring(1);
+ key = key.replace("_", ".");
+ map.put(key, String.valueOf(value));
+ }
+ }
+
+ protected void setParamArraySimple(HashMap map, String prefix, V [] array) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ this.setParamSimple(map, prefix + i, array[i]);
+ }
+ }
+ }
+
+ protected void setParamObj(HashMap map, String prefix, V obj) {
+ if (obj != null) {
+ obj.toMap(map, prefix);
+ }
+ }
+
+ protected void setParamArrayObj(HashMap map, String prefix, V [] array) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ this.setParamObj(map, prefix + i + ".", array[i]);
+ }
+ }
+ }
+
+ /**
+ * 序列化函数,将对象数据转化为json格式的string
+ *
+ * @param obj
+ * 集成自AbstractModel的子类实例
+ * @return json格式的string
+ */
+ public static String toJsonString(O obj) {
+ return toJsonObject(obj).toString();
+ }
+
+ /**
+ * Recursively generate obj's JSON object. Even if obj.any() is empty, this
+ * recursive progress cannot be skipped because customized additional parameter
+ * might be hidden in lower data structure.
+ *
+ * @param obj
+ * @return
+ */
+ private static JsonObject toJsonObject(O obj) {
+ Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+ JsonObject joall = new JsonObject();
+ JsonObject joadd = gson.toJsonTree(obj.any()).getAsJsonObject();
+ for (Map.Entry entry : joadd.entrySet()) {
+ joall.add(entry.getKey(), entry.getValue());
+ }
+ // jopublic will override joadd if key conflict exists
+ JsonObject jopublic = gson.toJsonTree(obj).getAsJsonObject();
+ for (Map.Entry entry : jopublic.entrySet()) {
+ Object fo = null;
+ try {
+ Field f = obj.getClass().getDeclaredField(entry.getKey());
+ f.setAccessible(true);
+ fo = f.get(obj);
+ } catch (Exception e) {
+ // this should never happen
+ e.printStackTrace();
+ }
+ if (fo instanceof AbstractModel) {
+ joall.add(entry.getKey(), toJsonObject((AbstractModel)fo));
+ } else {
+ joall.add(entry.getKey(), entry.getValue());
+ }
+ }
+ return joall;
+ }
+
+ /**
+ * 序列化函数,根据传入的json格式的string实例化一个cls对象返回
+ *
+ * @param json
+ * json格式的string
+ * @param cls
+ * 与json匹配的类对象
+ * @return cls的实例
+ */
+ public static O fromJsonString(String json, Class cls) {
+ Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
+ return gson.fromJson(json, cls);
+ }
+
+ /**
+ * Set any key value pair to this model.
+ *
+ * @param key
+ * @param value
+ */
+ public void set(String key, Object value) {
+ this.customizedParams.put(key, value);
+ }
+
+ /**
+ * Get customized key value pairs from this model.
+ *
+ * @return
+ */
+ public HashMap any() {
+ return this.customizedParams;
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Credential.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Credential.java
new file mode 100644
index 0000000..7bdde2f
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Credential.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+/**
+ * 认证相关信息类
+ */
+public class Credential {
+
+ /**
+ * secretId,在控制台申请
+ */
+ private String secretId;
+
+ /**
+ * secretKey,在控制台申请
+ */
+ private String secretKey;
+
+ /**
+ * token
+ */
+ private String token;
+
+ /**
+ * @param secretId 在控制台申请
+ * @param secretKey 在控制台申请
+ */
+ public Credential(String secretId, String secretKey) {
+ this(secretId, secretKey, "");
+ }
+
+ /**
+ * @param secretId 在控制台申请
+ * @param secretKey 在控制台申请
+ * @param token
+ */
+ public Credential(String secretId, String secretKey, String token) {
+ this.secretId = secretId;
+ this.secretKey = secretKey;
+ this.token = token;
+ }
+
+ /**
+ * 设置secretId
+ * @param secretId
+ */
+ public void setSecretId(String secretId) {
+ this.secretId = secretId;
+ }
+
+ /**
+ * 设置secretKey
+ * @param secretKey
+ */
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ /**
+ * 设置token
+ * @param token
+ */
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ /**
+ * 获取secretId
+ * @return secretId
+ */
+ public String getSecretId() {
+ return this.secretId;
+ }
+
+ /**
+ * 获取secretKey
+ * @return secretKey
+ */
+ public String getSecretKey() {
+ return this.secretKey;
+ }
+
+ /**
+ * 获取token
+ * @return token
+ */
+ public String getToken() {
+ return this.token;
+ }
+
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseErrModel.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseErrModel.java
new file mode 100644
index 0000000..f5d03fe
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseErrModel.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * api 报错response类,用以格式化接收失败的http response
+ * @author Administrator
+ *
+ */
+public class JsonResponseErrModel {
+
+ @SerializedName("RequestId")
+ @Expose
+ public String requestId;
+
+ @SerializedName("Error")
+ @Expose
+ public ErrorInfo error;
+
+ class ErrorInfo {
+ @SerializedName("Code")
+ @Expose
+ public String code;
+
+ @Expose
+ @SerializedName("Message")
+ public String message;
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseModel.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseModel.java
new file mode 100644
index 0000000..c8eea20
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/JsonResponseModel.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * api response类,用以格式化接收http response
+ * @param 具体对应的response类
+ */
+public class JsonResponseModel {
+
+ @SerializedName("Response")
+ @Expose
+ public T response;
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Sign.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Sign.java
new file mode 100644
index 0000000..a346700
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/Sign.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common;
+
+import com.xinelu.common.utils.tencentcloudapi.common.exception.TencentCloudSDKException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.TreeMap;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import javax.xml.bind.DatatypeConverter;
+
+;
+
+/**
+ * 签名工具类
+ */
+public class Sign {
+ private final static Charset UTF8 = StandardCharsets.UTF_8;
+
+ /**
+ *
+ * @param sigStr
+ * @param secretKey
+ * @param sigMethod
+ * @return string sign string
+ * @throws TencentCloudSDKException
+ */
+ public static String sign(String secretKey, String sigStr, String sigMethod) throws TencentCloudSDKException
+ {
+ String sig = null;
+ try{
+ Mac mac = Mac.getInstance(sigMethod);
+ byte[] hash;
+ SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(UTF8), mac.getAlgorithm());
+
+ mac.init(secretKeySpec);
+ hash = mac.doFinal(sigStr.getBytes(UTF8));
+ sig = DatatypeConverter.printBase64Binary(hash);
+ } catch (Exception e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ return sig;
+ }
+
+ public static String makeSignPlainText(TreeMap requestParams, String reqMethod, String host, String path) {
+
+ String retStr = "";
+ retStr += reqMethod;
+ retStr += host;
+ retStr += path;
+ retStr += buildParamStr(requestParams, reqMethod);
+ return retStr;
+ }
+
+ protected static String buildParamStr(TreeMap requestParams, String requestMethod) {
+
+ String retStr = "";
+ for(String key: requestParams.keySet()) {
+ String value = requestParams.get(key).toString();
+ if (retStr.length() == 0) {
+ retStr += '?';
+ } else {
+ retStr += '&';
+ }
+ retStr += key.replace("_", ".") + '=' + value;
+
+ }
+ return retStr;
+ }
+
+ public static String sha256Hex(String s) throws TencentCloudSDKException {
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new TencentCloudSDKException("SHA-256 is not supported." + e.getMessage());
+ }
+ byte[] d = md.digest(s.getBytes(UTF8));
+ return DatatypeConverter.printHexBinary(d).toLowerCase();
+ }
+
+ public static String sha256Hex(byte[] b) throws TencentCloudSDKException {
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new TencentCloudSDKException("SHA-256 is not supported." + e.getMessage());
+ }
+ byte[] d = md.digest(b);
+ return DatatypeConverter.printHexBinary(d).toLowerCase();
+ }
+
+ public static byte[] hmac256(byte[] key, String msg) throws TencentCloudSDKException {
+ Mac mac;
+ try {
+ mac = Mac.getInstance("HmacSHA256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new TencentCloudSDKException("HmacSHA256 is not supported." + e.getMessage());
+ }
+ SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
+ try {
+ mac.init(secretKeySpec);
+ } catch (InvalidKeyException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ return mac.doFinal(msg.getBytes(UTF8));
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/exception/TencentCloudSDKException.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/exception/TencentCloudSDKException.java
new file mode 100644
index 0000000..a87ccdd
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/exception/TencentCloudSDKException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common.exception;
+
+/**
+ * 腾讯云api sdk异常类
+ */
+public class TencentCloudSDKException extends Exception {
+
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 请求Id,发起请求前的异常这个字段为空
+ */
+ private String requestId;
+
+ /**
+ * Error code, When API returns a failure, it must have an error code.
+ */
+ private String errorCode;
+
+ /**
+ * @param message 异常信息
+ */
+ public TencentCloudSDKException(String message) {
+ this(message, "");
+ }
+
+ /**
+ * @param message 异常信息
+ * @param requestId 请求id
+ */
+ public TencentCloudSDKException(String message, String requestId) {
+ super(message);
+ this.requestId = requestId;
+ }
+
+ public TencentCloudSDKException(String message, String requestId, String errorCode) {
+ super(message);
+ this.requestId = requestId;
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * 获取请求id
+ * @return requestId
+ */
+ public String getRequestId() {
+ return requestId;
+ }
+
+ /**
+ * Get error code
+ * @return A string represents error code
+ */
+// public String getErrorCode() {
+// return errorCode;
+// }
+
+ /**
+ * 格式化输出异常信息
+ * @return 异常信息
+ */
+ public String toString() {
+ return "[TencentCloudSDKException]" + "message:" + this.getMessage() + " requestId:" + this.getRequestId();
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/http/HttpConnection.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/http/HttpConnection.java
new file mode 100644
index 0000000..2ca6516
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/http/HttpConnection.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common.http;
+
+import com.squareup.okhttp.Headers;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.Response;
+import com.xinelu.common.utils.tencentcloudapi.common.exception.TencentCloudSDKException;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * http连接类
+ */
+public class HttpConnection {
+
+ private OkHttpClient client;
+
+ public HttpConnection(Integer connTimeout, Integer readTimeout, Integer writeTimeout) {
+ this.client = new OkHttpClient();
+ this.client.setConnectTimeout(connTimeout, TimeUnit.SECONDS);
+ this.client.setReadTimeout(readTimeout, TimeUnit.SECONDS);
+ this.client.setWriteTimeout(writeTimeout, TimeUnit.SECONDS);
+
+ }
+
+
+ public Response doRequest(Request request) throws TencentCloudSDKException {
+ Response response = null;
+ try {
+ response = this.client.newCall(request).execute();
+ } catch (IOException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+ return response;
+ }
+
+ public Response getRequest(String url) throws TencentCloudSDKException {
+ Request request = null;
+ try {
+ request = new Request.Builder()
+ .url(url)
+ .get()
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ return this.doRequest(request);
+ }
+
+ public Response getRequest(String url, Headers headers) throws TencentCloudSDKException {
+ Request request = null;
+ try {
+ request = new Request.Builder()
+ .url(url)
+ .headers(headers)
+ .get()
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ return this.doRequest(request);
+ }
+
+ public Response postRequest(String url, String body) throws TencentCloudSDKException {
+ MediaType contentType = MediaType.parse("application/x-www-form-urlencoded");
+ Request request = null;
+ try {
+ request = new Request.Builder()
+ .url(url)
+ .post(RequestBody.create(contentType, body))
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ return this.doRequest(request);
+ }
+
+ public Response postRequest(String url, String body, Headers headers) throws TencentCloudSDKException {
+ MediaType contentType = MediaType.parse(headers.get("Content-Type"));
+ Request request = null;
+ try {
+ request = new Request.Builder()
+ .url(url)
+ .post(RequestBody.create(contentType, body))
+ .headers(headers)
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ return this.doRequest(request);
+ }
+
+ public Response postRequest(String url, byte [] body, Headers headers) throws TencentCloudSDKException {
+ MediaType contentType = MediaType.parse(headers.get("Content-Type"));
+ Request request = null;
+ try {
+ request = new Request.Builder()
+ .url(url)
+ .post(RequestBody.create(contentType, body))
+ .headers(headers)
+ .build();
+ } catch (IllegalArgumentException e) {
+ throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
+ }
+
+ return this.doRequest(request);
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/ClientProfile.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/ClientProfile.java
new file mode 100644
index 0000000..c1038cd
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/ClientProfile.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common.profile;
+
+/**
+ * client选项类
+ * @author Administrator
+ *
+ */
+public class ClientProfile {
+
+ /**
+ * HmacSHA1签名方法
+ */
+ public static final String SIGN_SHA1 = "HmacSHA1";
+
+ /**
+ * HmacSHA256签名方法
+ */
+ public static final String SIGN_SHA256 = "HmacSHA256";
+
+ /**
+ * Signature Version 3
+ */
+ public static final String SIGN_TC3_256 = "TC3-HMAC-SHA256";
+
+ /**
+ * http相关选项,请参考HttpProfile
+ */
+ private HttpProfile httpProfile;
+
+ /**
+ * 签名方法
+ */
+ private String signMethod;
+
+ /**
+ * If payload is NOT involved in signing process, true means will ignore payload,
+ * default is false.
+ */
+ private boolean unsignedPayload;
+
+ /**
+ * @param signMethod 签名方法
+ * @param httpProfile HttpProfile实例
+ */
+ public ClientProfile(String signMethod, HttpProfile httpProfile) {
+ if (signMethod == null || signMethod.isEmpty()) {
+ signMethod = SIGN_TC3_256;
+ }
+ this.signMethod = signMethod;
+ this.httpProfile = httpProfile;
+ this.unsignedPayload = false;
+ }
+
+ public ClientProfile(String signMethod) {
+ this(signMethod, new HttpProfile());
+ }
+
+ public ClientProfile() {
+ this(ClientProfile.SIGN_TC3_256, new HttpProfile());
+ }
+
+ /**
+ * 设置签名方法
+ * @param signMethod
+ */
+ public void setSignMethod(String signMethod) {
+ this.signMethod = signMethod;
+ }
+
+ /**
+ * 设置http选项
+ * @param httpProfile 参考HttpProfile
+ */
+ public void setHttpProfile(HttpProfile httpProfile) {
+ this.httpProfile = httpProfile;
+ }
+
+ /**
+ * 获取签名方法
+ * @return 签名方法
+ */
+ public String getSignMethod() {
+ return this.signMethod;
+ }
+
+ /**
+ * 获取HttpProfile实例
+ * @return HttpProfile实例
+ */
+ public HttpProfile getHttpProfile() {
+ return this.httpProfile;
+ }
+
+ /**
+ * Set the flag of whether payload should be ignored.
+ * Only has effect when request method is POST.
+ * @param flag
+ */
+ public void setUnsignedPayload(boolean flag) {
+ this.unsignedPayload = flag;
+ }
+
+ /**
+ * Get the flag of whether payload is ignored.
+ * @return
+ */
+ public boolean isUnsignedPayload() {
+ return this.unsignedPayload;
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/HttpProfile.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/HttpProfile.java
new file mode 100644
index 0000000..e16ee58
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/common/profile/HttpProfile.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.xinelu.common.utils.tencentcloudapi.common.profile;
+
+/**
+ * http选项类
+ */
+public class HttpProfile {
+
+ /**
+ * https协议
+ */
+ public static final String REQ_HTTPS = "https://";
+
+ /**
+ * http协议
+ */
+ public static final String REQ_HTTP = "http://";
+
+ /**
+ * post请求
+ */
+ public static final String REQ_POST = "POST";
+
+ /**
+ * get请求
+ */
+ public static final String REQ_GET = "GET";
+
+ /**
+ * 时间单位,1分钟 60s
+ */
+ public static final int TM_MINUTE = 60;
+
+ /**
+ * 请求方法
+ */
+ private String reqMethod;
+
+ /**
+ * 请求域名
+ */
+ private String endpoint;
+
+ /**
+ * 请求协议
+ */
+ private String protocol;
+
+ /**
+ * 读超时时间(秒)
+ */
+ private int readTimeout;
+
+ /**
+ * 写超时时间(秒)
+ */
+ private int writeTimeout;
+
+ /**
+ * 连接超时时间(秒)
+ */
+ private int connTimeout;
+
+
+ public HttpProfile() {
+ this.reqMethod = HttpProfile.REQ_POST;
+ this.endpoint = null;
+ this.protocol = HttpProfile.REQ_HTTPS;
+ this.readTimeout = 0;
+ this.writeTimeout = 0;
+ this.connTimeout = HttpProfile.TM_MINUTE;
+ }
+
+ /**
+ * 设置请求方法
+ * @param reqMethod 请求方法
+ */
+ public void setReqMethod(String reqMethod) {
+ this.reqMethod = reqMethod;
+ }
+
+ /**
+ * 设置请求域名
+ * @param endpoint 域名(xx.[region.]tencentcloudapi.com)
+ */
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ /**
+ * 设置读超时时间
+ * @param readTimeout 读超时时间
+ */
+ public void setReadTimeout(int readTimeout) {
+ this.readTimeout = readTimeout;
+ }
+
+ /**
+ * 设置写超时时间
+ * @param writeTimeout 写超时时间
+ */
+ public void setWriteTimeout(int writeTimeout) {
+ this.writeTimeout = writeTimeout;
+ }
+
+ /**
+ * 设置连接超时时间
+ * @param connTimeout 连接超时时间
+ */
+ public void setConnTimeout(int connTimeout) {
+ this.connTimeout = connTimeout;
+ }
+
+ /**
+ * 设置请求协议
+ * @param protocol 请求协议(https:// http://)
+ */
+ public void setProtocol(String protocol) {
+ this.protocol = protocol;
+ }
+
+ /**
+ * 获取请求方法
+ * @return reqMethod
+ */
+ public String getReqMethod() {
+ return this.reqMethod;
+ }
+
+ /**
+ * 获取请求域名
+ * @return endpoint
+ */
+ public String getEndpoint() {
+ return this.endpoint;
+ }
+
+ /**
+ * 获取读超时时间
+ * @return readTimeout
+ */
+ public int getReadTimeout() {
+ return this.readTimeout;
+ }
+
+ /**
+ * 获取写超时时间
+ * @return writeTimeout
+ */
+ public int getWriteTimeout() {
+ return this.writeTimeout;
+ }
+
+ /**
+ * 获取连接超时时间
+ * @return connTimeout
+ */
+ public int getConnTimeout() {
+ return this.connTimeout;
+ }
+
+ /**
+ * 获取请求协议
+ * @return protocol
+ */
+ public String getProtocol() {
+ return this.protocol;
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/Base64URL.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/Base64URL.java
new file mode 100644
index 0000000..abb9433
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/Base64URL.java
@@ -0,0 +1,24 @@
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722;
+
+import java.util.Base64;
+
+public class Base64URL {
+ public static byte[] base64EncodeUrl(byte[] input) {
+ byte[] base64 = Base64.getEncoder().encode(input);
+ for (int i = 0; i < base64.length; ++i)
+ switch (base64[i]) {
+ case '+':
+ base64[i] = '*';
+ break;
+ case '/':
+ base64[i] = '-';
+ break;
+ case '=':
+ base64[i] = '_';
+ break;
+ default:
+ break;
+ }
+ return base64;
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TLSSigAPIv2.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TLSSigAPIv2.java
new file mode 100644
index 0000000..6a05466
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TLSSigAPIv2.java
@@ -0,0 +1,312 @@
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722;
+
+import com.alibaba.fastjson2.JSONObject;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.zip.Deflater;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+public class TLSSigAPIv2 {
+ final private long sdkappid;
+ final private String key;
+
+ public TLSSigAPIv2(long sdkappid, String key) {
+ this.sdkappid = sdkappid;
+ this.key = key;
+ }
+
+ /**
+ * 【功能说明】用于签发 TRTC 和 IM 服务中必须要使用的 UserSig 鉴权票据
+ *
+ * 【参数说明】
+ *
+ * @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+ * @param expire - UserSig 票据的过期时间,单位是秒,比如 86400 代表生成的 UserSig 票据在一天后就无法再使用了。
+ * @return usersig -生成的签名
+ */
+
+ /**
+ * Function: Used to issue UserSig that is required by the TRTC and IM services.
+ *
+ * Parameter description:
+ *
+ * @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+ * @param expire - UserSig expiration time, in seconds. For example, 86400 indicates that the generated UserSig will expire one day after being generated.
+ * @return usersig - Generated signature.
+ */
+ public String genUserSig(String userid, long expire) {
+ return genUserSig(userid, expire, null);
+ }
+
+ /**
+ * 【功能说明】
+ * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
+ * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
+ * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
+ * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
+ * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
+ *
+ * 【参数说明】
+ *
+ * @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+ * @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
+ * @param roomid - 房间号,用于指定该 userid 可以进入的房间号
+ * @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
+ * - 第 1 位:0000 0001 = 1,创建房间的权限
+ * - 第 2 位:0000 0010 = 2,加入房间的权限
+ * - 第 3 位:0000 0100 = 4,发送语音的权限
+ * - 第 4 位:0000 1000 = 8,接收语音的权限
+ * - 第 5 位:0001 0000 = 16,发送视频的权限
+ * - 第 6 位:0010 0000 = 32,接收视频的权限
+ * - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
+ * - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
+ * - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
+ * - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
+ * @return usersig - 生成带userbuf的签名
+ */
+
+ /**
+ * Function:
+ * Used to issue PrivateMapKey that is optional for room entry.
+ * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.
+ * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.
+ * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.
+ * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.
+ *
+ * Parameter description:
+ *
+ * @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+ * @param roomid - ID of the room to which the specified UserID can enter.
+ * @param expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.
+ * @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:
+ * - Bit 1: 0000 0001 = 1, permission for room creation
+ * - Bit 2: 0000 0010 = 2, permission for room entry
+ * - Bit 3: 0000 0100 = 4, permission for audio sending
+ * - Bit 4: 0000 1000 = 8, permission for audio receiving
+ * - Bit 5: 0001 0000 = 16, permission for video sending
+ * - Bit 6: 0010 0000 = 32, permission for video receiving
+ * - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)
+ * - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)
+ * - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.
+ * - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.
+ * @return usersig - Generate signature with userbuf
+ */
+ public String genPrivateMapKey(String userid, long expire, long roomid, long privilegeMap) {
+ byte[] userbuf = genUserBuf(userid, roomid, expire, privilegeMap, 0, ""); //生成userbuf
+ return genUserSig(userid, expire, userbuf);
+ }
+
+ /**
+ * 【功能说明】
+ * 用于签发 TRTC 进房参数中可选的 PrivateMapKey 权限票据。
+ * PrivateMapKey 需要跟 UserSig 一起使用,但 PrivateMapKey 比 UserSig 有更强的权限控制能力:
+ * - UserSig 只能控制某个 UserID 有无使用 TRTC 服务的权限,只要 UserSig 正确,其对应的 UserID 可以进出任意房间。
+ * - PrivateMapKey 则是将 UserID 的权限控制的更加严格,包括能不能进入某个房间,能不能在该房间里上行音视频等等。
+ * 如果要开启 PrivateMapKey 严格权限位校验,需要在【实时音视频控制台】/【应用管理】/【应用信息】中打开“启动权限密钥”开关。
+ *
+ * 【参数说明】
+ *
+ * @param userid - 用户id,限制长度为32字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。
+ * @param expire - PrivateMapKey 票据的过期时间,单位是秒,比如 86400 生成的 PrivateMapKey 票据在一天后就无法再使用了。
+ * @param roomstr - 字符串房间号,用于指定该 userid 可以进入的房间号
+ * @param privilegeMap - 权限位,使用了一个字节中的 8 个比特位,分别代表八个具体的功能权限开关:
+ * - 第 1 位:0000 0001 = 1,创建房间的权限
+ * - 第 2 位:0000 0010 = 2,加入房间的权限
+ * - 第 3 位:0000 0100 = 4,发送语音的权限
+ * - 第 4 位:0000 1000 = 8,接收语音的权限
+ * - 第 5 位:0001 0000 = 16,发送视频的权限
+ * - 第 6 位:0010 0000 = 32,接收视频的权限
+ * - 第 7 位:0100 0000 = 64,发送辅路(也就是屏幕分享)视频的权限
+ * - 第 8 位:1000 0000 = 200,接收辅路(也就是屏幕分享)视频的权限
+ * - privilegeMap == 1111 1111 == 255 代表该 userid 在该 roomid 房间内的所有功能权限。
+ * - privilegeMap == 0010 1010 == 42 代表该 userid 拥有加入房间和接收音视频数据的权限,但不具备其他权限。
+ * @return usersig - 生成带userbuf的签名
+ */
+
+ /**
+ * Function:
+ * Used to issue PrivateMapKey that is optional for room entry.
+ * PrivateMapKey must be used together with UserSig but with more powerful permission control capabilities.
+ * - UserSig can only control whether a UserID has permission to use the TRTC service. As long as the UserSig is correct, the user with the corresponding UserID can enter or leave any room.
+ * - PrivateMapKey specifies more stringent permissions for a UserID, including whether the UserID can be used to enter a specific room and perform audio/video upstreaming in the room.
+ * To enable stringent PrivateMapKey permission bit verification, you need to enable permission key in TRTC console > Application Management > Application Info.
+ *
+ * Parameter description:
+ *
+ *
+ * @param userid - User ID. The value can be up to 32 bytes in length and contain letters (a-z and A-Z), digits (0-9), underscores (_), and hyphens (-).
+ * @param roomid - ID of the room to which the specified UserID can enter.
+ * @param expire - PrivateMapKey expiration time, in seconds. For example, 86400 indicates that the generated PrivateMapKey will expire one day after being generated.
+ * @param privilegeMap - Permission bits. Eight bits in the same byte are used as the permission switches of eight specific features:
+ * - Bit 1: 0000 0001 = 1, permission for room creation
+ * - Bit 2: 0000 0010 = 2, permission for room entry
+ * - Bit 3: 0000 0100 = 4, permission for audio sending
+ * - Bit 4: 0000 1000 = 8, permission for audio receiving
+ * - Bit 5: 0001 0000 = 16, permission for video sending
+ * - Bit 6: 0010 0000 = 32, permission for video receiving
+ * - Bit 7: 0100 0000 = 64, permission for substream video sending (screen sharing)
+ * - Bit 8: 1000 0000 = 200, permission for substream video receiving (screen sharing)
+ * - privilegeMap == 1111 1111 == 255: Indicates that the UserID has all feature permissions of the room specified by roomid.
+ * - privilegeMap == 0010 1010 == 42: Indicates that the UserID has only the permissions to enter the room and receive audio/video data.
+ * @return usersig - Generate signature with userbuf
+ */
+ public String genPrivateMapKeyWithStringRoomID(String userid, long expire, String roomstr, long privilegeMap) {
+ byte[] userbuf = genUserBuf(userid, 0, expire, privilegeMap, 0, roomstr); //生成userbuf
+ return genUserSig(userid, expire, userbuf);
+ }
+
+ private String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf) {
+ String contentToBeSigned = "TLS.identifier:" + identifier + "\n"
+ + "TLS.sdkappid:" + sdkappid + "\n"
+ + "TLS.time:" + currTime + "\n"
+ + "TLS.expire:" + expire + "\n";
+ if (null != base64Userbuf) {
+ contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";
+ }
+ try {
+ byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);
+ Mac hmac = Mac.getInstance("HmacSHA256");
+ SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");
+ hmac.init(keySpec);
+ byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));
+ return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ return "";
+ }
+ }
+
+ private String genUserSig(String userid, long expire, byte[] userbuf) {
+
+ long currTime = System.currentTimeMillis() / 1000;
+
+ JSONObject sigDoc = new JSONObject();
+ sigDoc.put("TLS.ver", "2.0");
+ sigDoc.put("TLS.identifier", userid);
+ sigDoc.put("TLS.sdkappid", sdkappid);
+ sigDoc.put("TLS.expire", expire);
+ sigDoc.put("TLS.time", currTime);
+
+ String base64UserBuf = null;
+ if (null != userbuf) {
+ base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");
+ sigDoc.put("TLS.userbuf", base64UserBuf);
+ }
+ String sig = hmacsha256(userid, currTime, expire, base64UserBuf);
+ if (sig.length() == 0) {
+ return "";
+ }
+ sigDoc.put("TLS.sig", sig);
+ Deflater compressor = new Deflater();
+ compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));
+ compressor.finish();
+ byte[] compressedBytes = new byte[2048];
+ int compressedBytesLength = compressor.deflate(compressedBytes);
+ compressor.end();
+ return (new String(Base64URL.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,
+ 0, compressedBytesLength)))).replaceAll("\\s*", "");
+ }
+
+ public byte[] genUserBuf(String account, long dwAuthID, long dwExpTime,
+ long dwPrivilegeMap, long dwAccountType, String RoomStr) {
+ //视频校验位需要用到的字段,按照网络字节序放入buf中
+ /*
+ cVer unsigned char/1 版本号,填0
+ wAccountLen unsigned short /2 第三方自己的帐号长度
+ account wAccountLen 第三方自己的帐号字符
+ dwSdkAppid unsigned int/4 sdkappid
+ dwAuthID unsigned int/4 群组号码
+ dwExpTime unsigned int/4 过期时间 ,直接使用填入的值
+ dwPrivilegeMap unsigned int/4 权限位,主播0xff,观众0xab
+ dwAccountType unsigned int/4 第三方帐号类型
+ */
+
+ //The fields required for the video check digit are placed in buf according to the network byte order.
+ /*
+ cVer unsigned char/1 Version number, fill in 0
+ wAccountLen unsigned short /2 Third party's own account length
+ account wAccountLen Third party's own account characters
+ dwSdkAppid unsigned int/4 sdkappid
+ dwAuthID unsigned int/4 group number
+ dwExpTime unsigned int/4 Expiration time , use the filled value directly
+ dwPrivilegeMap unsigned int/4 Permission bits, host 0xff, audience 0xab
+ dwAccountType unsigned int/4 Third-party account type
+ */
+ int accountLength = account.length();
+ int roomStrLength = RoomStr.length();
+ int offset = 0;
+ int bufLength = 1 + 2 + accountLength + 20 ;
+ if (roomStrLength > 0) {
+ bufLength = bufLength + 2 + roomStrLength;
+ }
+ byte[] userbuf = new byte[bufLength];
+
+ //cVer
+ if (roomStrLength > 0) {
+ userbuf[offset++] = 1;
+ } else {
+ userbuf[offset++] = 0;
+ }
+
+ //wAccountLen
+ userbuf[offset++] = (byte) ((accountLength & 0xFF00) >> 8);
+ userbuf[offset++] = (byte) (accountLength & 0x00FF);
+
+ //account
+ for (; offset < 3 + accountLength; ++offset) {
+ userbuf[offset] = (byte) account.charAt(offset - 3);
+ }
+
+ //dwSdkAppid
+ userbuf[offset++] = (byte) ((sdkappid & 0xFF000000) >> 24);
+ userbuf[offset++] = (byte) ((sdkappid & 0x00FF0000) >> 16);
+ userbuf[offset++] = (byte) ((sdkappid & 0x0000FF00) >> 8);
+ userbuf[offset++] = (byte) (sdkappid & 0x000000FF);
+
+ //dwAuthId,房间号
+ //dwAuthId, room number
+ userbuf[offset++] = (byte) ((dwAuthID & 0xFF000000) >> 24);
+ userbuf[offset++] = (byte) ((dwAuthID & 0x00FF0000) >> 16);
+ userbuf[offset++] = (byte) ((dwAuthID & 0x0000FF00) >> 8);
+ userbuf[offset++] = (byte) (dwAuthID & 0x000000FF);
+
+ //expire,过期时间,当前时间 + 有效期(单位:秒)
+ //expire,Expiration time, current time + validity period (unit: seconds)
+ long currTime = System.currentTimeMillis() / 1000;
+ long expire = currTime + dwExpTime;
+ userbuf[offset++] = (byte) ((expire & 0xFF000000) >> 24);
+ userbuf[offset++] = (byte) ((expire & 0x00FF0000) >> 16);
+ userbuf[offset++] = (byte) ((expire & 0x0000FF00) >> 8);
+ userbuf[offset++] = (byte) (expire & 0x000000FF);
+
+ //dwPrivilegeMap,权限位
+ //dwPrivilegeMap,Permission bits
+ userbuf[offset++] = (byte) ((dwPrivilegeMap & 0xFF000000) >> 24);
+ userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x00FF0000) >> 16);
+ userbuf[offset++] = (byte) ((dwPrivilegeMap & 0x0000FF00) >> 8);
+ userbuf[offset++] = (byte) (dwPrivilegeMap & 0x000000FF);
+
+ //dwAccountType,账户类型
+ //dwAccountType,account type
+ userbuf[offset++] = (byte) ((dwAccountType & 0xFF000000) >> 24);
+ userbuf[offset++] = (byte) ((dwAccountType & 0x00FF0000) >> 16);
+ userbuf[offset++] = (byte) ((dwAccountType & 0x0000FF00) >> 8);
+ userbuf[offset++] = (byte) (dwAccountType & 0x000000FF);
+
+
+ if (roomStrLength > 0) {
+ //roomStrLen
+ userbuf[offset++] = (byte) ((roomStrLength & 0xFF00) >> 8);
+ userbuf[offset++] = (byte) (roomStrLength & 0x00FF);
+
+ //roomStr
+ for (; offset < bufLength; ++offset) {
+ userbuf[offset] = (byte) RoomStr.charAt(offset - (bufLength - roomStrLength));
+ }
+ }
+ return userbuf;
+ }
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TrtcClient.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TrtcClient.java
new file mode 100644
index 0000000..1e0ba8b
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/TrtcClient.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722;
+
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+import com.xinelu.common.utils.tencentcloudapi.common.AbstractClient;
+import com.xinelu.common.utils.tencentcloudapi.common.Credential;
+import com.xinelu.common.utils.tencentcloudapi.common.JsonResponseModel;
+import com.xinelu.common.utils.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.xinelu.common.utils.tencentcloudapi.common.profile.ClientProfile;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.DissolveRoomRequest;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.DissolveRoomResponse;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.KickOutUserRequest;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.KickOutUserResponse;
+import java.lang.reflect.Type;
+
+public class TrtcClient extends AbstractClient {
+ private static String endpoint = "trtc.tencentcloudapi.com";
+ private static String version = "2019-07-22";
+
+ /**
+ * 构造client
+ * @param credential 认证信息实例
+ * @param region 产品地域
+ */
+ public TrtcClient(Credential credential, String region) {
+ this(credential, region, new ClientProfile());
+ }
+
+ /**
+ * 构造client
+ * @param credential 认证信息实例
+ * @param region 产品地域
+ * @param profile 配置实例
+ */
+ public TrtcClient(Credential credential, String region, ClientProfile profile) {
+ super(TrtcClient.endpoint, TrtcClient.version, credential, region, profile);
+ }
+
+ /**
+ *接口说明:把房间所有用户从房间踢出,解散房间。支持 TRTC SDK 6.6及以上版本,包括Android、iOS、Windows 和 macOS。
+ * @param req DissolveRoomRequest
+ * @return DissolveRoomResponse
+ * @throws TencentCloudSDKException
+ */
+ public DissolveRoomResponse DissolveRoom(DissolveRoomRequest req) throws TencentCloudSDKException {
+ JsonResponseModel rsp = null;
+ try {
+ Type type = new TypeToken>() {
+ }.getType();
+ rsp = gson.fromJson(this.internalRequest(req, "DissolveRoom"), type);
+ } catch (JsonSyntaxException e) {
+ throw new TencentCloudSDKException(e.getMessage());
+ }
+ return rsp.response;
+ }
+
+ /**
+ *接口说明:将用户从房间踢出。支持 TRTC SDK 6.6及以上版本,包括Android、iOS、Windows 和 macOS。
+ * @param req KickOutUserRequest
+ * @return KickOutUserResponse
+ * @throws TencentCloudSDKException
+ */
+ public KickOutUserResponse KickOutUser(KickOutUserRequest req) throws TencentCloudSDKException{
+ JsonResponseModel rsp = null;
+ try {
+ Type type = new TypeToken>() {
+ }.getType();
+ rsp = gson.fromJson(this.internalRequest(req, "KickOutUser"), type);
+ } catch (JsonSyntaxException e) {
+ throw new TencentCloudSDKException(e.getMessage());
+ }
+ return rsp.response;
+ }
+
+}
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomRequest.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomRequest.java
new file mode 100644
index 0000000..5cd5139
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomRequest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.xinelu.common.utils.tencentcloudapi.common.AbstractModel;
+import java.util.HashMap;
+
+public class DissolveRoomRequest extends AbstractModel {
+
+ /**
+ * TRTC的SDKAppId。
+ */
+ @SerializedName("SdkAppId")
+ @Expose
+ private Long SdkAppId;
+
+ /**
+ * 房间号。
+ */
+ @SerializedName("RoomId")
+ @Expose
+ private Long RoomId;
+
+ /**
+ * 获取TRTC的SDKAppId。
+ * @return SdkAppId TRTC的SDKAppId。
+ */
+ public Long getSdkAppId() {
+ return this.SdkAppId;
+ }
+
+ /**
+ * 设置TRTC的SDKAppId。
+ * @param SdkAppId TRTC的SDKAppId。
+ */
+ public void setSdkAppId(Long SdkAppId) {
+ this.SdkAppId = SdkAppId;
+ }
+
+ /**
+ * 获取房间号。
+ * @return RoomId 房间号。
+ */
+ public Long getRoomId() {
+ return this.RoomId;
+ }
+
+ /**
+ * 设置房间号。
+ * @param RoomId 房间号。
+ */
+ public void setRoomId(Long RoomId) {
+ this.RoomId = RoomId;
+ }
+
+ /**
+ * 内部实现,用户禁止调用
+ */
+ public void toMap(HashMap map, String prefix) {
+ this.setParamSimple(map, prefix + "SdkAppId", this.SdkAppId);
+ this.setParamSimple(map, prefix + "RoomId", this.RoomId);
+
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomResponse.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomResponse.java
new file mode 100644
index 0000000..041b16d
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/DissolveRoomResponse.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.xinelu.common.utils.tencentcloudapi.common.AbstractModel;
+import java.util.HashMap;
+
+public class DissolveRoomResponse extends AbstractModel {
+
+ /**
+ * 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ @SerializedName("RequestId")
+ @Expose
+ private String RequestId;
+
+ /**
+ * 获取唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ * @return RequestId 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ public String getRequestId() {
+ return this.RequestId;
+ }
+
+ /**
+ * 设置唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ * @param RequestId 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ public void setRequestId(String RequestId) {
+ this.RequestId = RequestId;
+ }
+
+ /**
+ * 内部实现,用户禁止调用
+ */
+ public void toMap(HashMap map, String prefix) {
+ this.setParamSimple(map, prefix + "RequestId", this.RequestId);
+
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserRequest.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserRequest.java
new file mode 100644
index 0000000..6323444
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserRequest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.xinelu.common.utils.tencentcloudapi.common.AbstractModel;
+import java.util.HashMap;
+
+public class KickOutUserRequest extends AbstractModel {
+
+ /**
+ * TRTC的SDKAppId。
+ */
+ @SerializedName("SdkAppId")
+ @Expose
+ private Long SdkAppId;
+
+ /**
+ * 房间号。
+ */
+ @SerializedName("RoomId")
+ @Expose
+ private Long RoomId;
+
+ /**
+ * 要踢的用户列表,最多10个。
+ */
+ @SerializedName("UserIds")
+ @Expose
+ private String [] UserIds;
+
+ /**
+ * 获取TRTC的SDKAppId。
+ * @return SdkAppId TRTC的SDKAppId。
+ */
+ public Long getSdkAppId() {
+ return this.SdkAppId;
+ }
+
+ /**
+ * 设置TRTC的SDKAppId。
+ * @param SdkAppId TRTC的SDKAppId。
+ */
+ public void setSdkAppId(Long SdkAppId) {
+ this.SdkAppId = SdkAppId;
+ }
+
+ /**
+ * 获取房间号。
+ * @return RoomId 房间号。
+ */
+ public Long getRoomId() {
+ return this.RoomId;
+ }
+
+ /**
+ * 设置房间号。
+ * @param RoomId 房间号。
+ */
+ public void setRoomId(Long RoomId) {
+ this.RoomId = RoomId;
+ }
+
+ /**
+ * 获取要踢的用户列表,最多10个。
+ * @return UserIds 要踢的用户列表,最多10个。
+ */
+ public String [] getUserIds() {
+ return this.UserIds;
+ }
+
+ /**
+ * 设置要踢的用户列表,最多10个。
+ * @param UserIds 要踢的用户列表,最多10个。
+ */
+ public void setUserIds(String [] UserIds) {
+ this.UserIds = UserIds;
+ }
+
+ /**
+ * 内部实现,用户禁止调用
+ */
+ public void toMap(HashMap map, String prefix) {
+ this.setParamSimple(map, prefix + "SdkAppId", this.SdkAppId);
+ this.setParamSimple(map, prefix + "RoomId", this.RoomId);
+ this.setParamArraySimple(map, prefix + "UserIds.", this.UserIds);
+
+ }
+}
+
diff --git a/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserResponse.java b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserResponse.java
new file mode 100644
index 0000000..7aa59dc
--- /dev/null
+++ b/xinelu-common/src/main/java/com/xinelu/common/utils/tencentcloudapi/trtc/v20190722/models/KickOutUserResponse.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import com.xinelu.common.utils.tencentcloudapi.common.AbstractModel;
+import java.util.HashMap;
+
+public class KickOutUserResponse extends AbstractModel {
+
+ /**
+ * 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ @SerializedName("RequestId")
+ @Expose
+ private String RequestId;
+
+ /**
+ * 获取唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ * @return RequestId 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ public String getRequestId() {
+ return this.RequestId;
+ }
+
+ /**
+ * 设置唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ * @param RequestId 唯一请求 ID,每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
+ */
+ public void setRequestId(String RequestId) {
+ this.RequestId = RequestId;
+ }
+
+ /**
+ * 内部实现,用户禁止调用
+ */
+ public void toMap(HashMap map, String prefix) {
+ this.setParamSimple(map, prefix + "RequestId", this.RequestId);
+
+ }
+}
+
diff --git a/xinelu-framework/src/main/java/com/xinelu/framework/config/SecurityConfig.java b/xinelu-framework/src/main/java/com/xinelu/framework/config/SecurityConfig.java
index a0b9738..daea435 100644
--- a/xinelu-framework/src/main/java/com/xinelu/framework/config/SecurityConfig.java
+++ b/xinelu-framework/src/main/java/com/xinelu/framework/config/SecurityConfig.java
@@ -111,7 +111,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers("/login", "/register", "/captchaImage").anonymous()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
- .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/nurseApplet/**", "/nurseApp/**").permitAll()
+ .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**", "/nurseApplet/**", "/nurseApp/**", "/webSocket/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningproject/AppletScreeningProjectController.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningproject/AppletScreeningProjectController.java
new file mode 100644
index 0000000..93cd3ea
--- /dev/null
+++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningproject/AppletScreeningProjectController.java
@@ -0,0 +1,33 @@
+package com.xinelu.applet.controller.appletscreeningproject;
+
+import com.xinelu.common.core.controller.BaseController;
+import com.xinelu.common.core.domain.R;
+import com.xinelu.manage.domain.screeningproject.ScreeningProject;
+import com.xinelu.manage.service.screeningproject.IScreeningProjectService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: 项目控制器——小程序
+ * @author: haown
+ * @create: 2023-03-07 14:23
+ **/
+@RestController
+@RequestMapping("/nurseApplet/screening/project")
+@Api(tags = "项目控制器-小程序")
+public class AppletScreeningProjectController extends BaseController {
+ @Resource
+ private IScreeningProjectService projectService;
+
+ @ApiOperation("筛查项目列表")
+ @GetMapping("list")
+ public R> list(ScreeningProject project) {
+ List list = projectService.findList(project);
+ return R.ok(list);
+ }
+}
diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningrecord/AppletScreeningRecordController.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningrecord/AppletScreeningRecordController.java
new file mode 100644
index 0000000..33a1ff2
--- /dev/null
+++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/appletscreeningrecord/AppletScreeningRecordController.java
@@ -0,0 +1,131 @@
+package com.xinelu.applet.controller.appletscreeningrecord;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.xinelu.common.annotation.RepeatSubmit;
+import com.xinelu.common.constant.ScreeningProjectConstants;
+import com.xinelu.common.core.controller.BaseController;
+import com.xinelu.common.core.domain.R;
+import com.xinelu.common.core.page.TableDataInfo;
+import com.xinelu.common.utils.DateUtils;
+import com.xinelu.common.utils.StringUtils;
+import com.xinelu.manage.dto.screeningrecord.ScreeningApplyDTO;
+import com.xinelu.manage.dto.screeningrecord.ScreeningRecordDTO;
+import com.xinelu.manage.service.patientinfo.IPatientInfoService;
+import com.xinelu.manage.service.screeningrecord.IScreeningRecordService;
+import com.xinelu.manage.vo.screeningrecord.ScreeningRecordVo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import java.util.List;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @Author mengkuiliang
+ * @Description 筛查预约控制器
+ * @Date 2023-01-29 13:57
+ * @Param
+ * @return
+ **/
+@RestController
+@RequestMapping("/nurseApplet/screening/record")
+@Api(tags = "筛查预约控制器-小程序")
+public class AppletScreeningRecordController extends BaseController {
+
+ @Resource
+ private IScreeningRecordService screeningRecordService;
+
+ @Resource
+ private IPatientInfoService patientService;
+
+ @GetMapping("/record")
+ @ApiOperation(value = "获取筛查结果记录")
+ public TableDataInfo record(ScreeningRecordDTO query) {
+ startPage();
+ query.setScreeningType("1");
+ query.setScreeningStatus("4");
+ return getDataTable(screeningRecordService.record(query));
+ }
+
+ @GetMapping("/list")
+ @ApiOperation(value = "获取列表")
+ public TableDataInfo list(ScreeningRecordDTO query) {
+ startPage();
+ query.setScreeningType("1");
+ List list = screeningRecordService.list(query);
+ // 二维码返回
+ list.forEach(record -> {
+ StringUtils.isNotEmpty(record.getApplyBarcode());
+ // TODO 居民申请条码
+ //record.setApplyBarcode(patientService.getPicToBase64(record.getApplyBarcode()));
+ });
+ return getDataTable(list);
+ }
+
+ @PostMapping("/save")
+ @ApiOperation(value = "预约")
+ @RepeatSubmit(interval = 2000, message = "请求过于频繁")
+ public R save(@RequestBody ScreeningApplyDTO body) {
+ try {
+ if(!DateUtils.formatDate(body.getApplyStartTime(), "yyyy-MM-dd").equals(DateUtils.formatDate(body.getApplyEndTime(), "yyyy-MM-dd"))) {
+ return R.fail("预约日期请选择在同一天");
+ }
+ return R.ok(screeningRecordService.save(body));
+ } catch (Exception e) {
+ return R.fail(e.getMessage());
+ }
+ }
+
+ @GetMapping("/cancel/{screeningId}")
+ @ApiOperation(value = "取消预约")
+ public R cancel(@PathVariable String screeningId) {
+ // 判断是否已登记
+ ScreeningRecordVo screeningRecordVo = screeningRecordService.detail(screeningId);
+ if (StringUtils.equals(screeningRecordVo.getScreeningStatus(), "3") || StringUtils.equals(screeningRecordVo.getScreeningStatus(), "4")) {
+ return R.fail("该预约已登记,不能取消");
+ }
+ screeningRecordService.cancel(screeningId);
+ return R.ok();
+ }
+
+ @GetMapping("/detail/{screeningId}")
+ @ApiOperation(value = "获取预约详情")
+ public R detail(@PathVariable String screeningId) {
+ ScreeningRecordVo screeningRecordVo = screeningRecordService.detail(screeningId);
+ if (screeningRecordVo != null) {
+ // TODO 设置文件类型
+ //File fileInfo = sysFileService.getFile(screeningRecordVo.getAttachment());
+ //if (fileInfo != null) {
+ // screeningRecordVo.setFileType(fileInfo.getSuffix());
+ //}
+ if (!StringUtils.contains(screeningRecordVo.getProjectName(), ScreeningProjectConstants.ALZHEIMER)) {
+ screeningRecordService.getRecordDetail(screeningRecordVo);
+ }
+ }
+ return R.ok(screeningRecordVo);
+ }
+
+ @GetMapping("/getScreening/{registerId}")
+ @ApiOperation(value = "获取当前预约信息")
+ public R getScreeningByRegisterId(@PathVariable String registerId) {
+ return R.ok(screeningRecordService.getScreeningByRegisterId(registerId));
+ }
+
+ @GetMapping("/last/{registerId}/{projectId}")
+ @ApiOperation(value = "获取最新一次筛查结果")
+ public R last(@PathVariable String registerId, @PathVariable String projectId) {
+ ScreeningRecordVo screeningRecordVo = screeningRecordService.last(registerId, projectId);
+ return R.ok(screeningRecordVo);
+ }
+
+ @GetMapping("/getInfo/{assessRecordId}")
+ @ApiOperation(value = "推送筛查项目跳转查询推荐详情")
+ public R getInfo(@PathVariable("assessRecordId") String assessRecordId) {
+ JSONObject jsonObject = screeningRecordService.getInfo(assessRecordId);
+ return R.ok(jsonObject);
+ }
+}
diff --git a/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/videoconsultation/VideoConsultationController.java b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/videoconsultation/VideoConsultationController.java
new file mode 100644
index 0000000..2558bb3
--- /dev/null
+++ b/xinelu-nurse-applet/src/main/java/com/xinelu/applet/controller/videoconsultation/VideoConsultationController.java
@@ -0,0 +1,79 @@
+package com.xinelu.applet.controller.videoconsultation;
+
+import cn.hutool.core.util.RandomUtil;
+import com.xinelu.common.core.domain.R;
+import com.xinelu.common.core.dto.MessageTemplate;
+import com.xinelu.common.enums.MessageContentType;
+import com.xinelu.common.socket.WebSocketUtils;
+import com.xinelu.common.utils.tencentcloudapi.common.Credential;
+import com.xinelu.common.utils.tencentcloudapi.common.exception.TencentCloudSDKException;
+import com.xinelu.common.utils.tencentcloudapi.common.profile.ClientProfile;
+import com.xinelu.common.utils.tencentcloudapi.common.profile.HttpProfile;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.TLSSigAPIv2;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.TrtcClient;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.DissolveRoomRequest;
+import com.xinelu.common.utils.tencentcloudapi.trtc.v20190722.models.DissolveRoomResponse;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import java.util.Date;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author gaoyu
+ * @description 远程会诊控制器
+ * @date 2022-12-07 9:49
+ */
+@Api(tags = "远程会诊控制器")
+@RestController
+@RequestMapping("/nurseApplet/consultation")
+public class VideoConsultationController {
+
+ @Value("${trtc.sdkappid}")
+ private String sdkappid;
+ @Value("${trtc.secretid}")
+ private String secretid;
+ @Value("${trtc.secretkey}")
+ private String secretkey;
+ private final static String ENDPOINT = "trtc.tencentcloudapi.com";
+ private final static String REGION = "ap-beijing";
+
+ @ApiOperation("获取userSig")
+ @GetMapping("getUserSig/{userId}")
+ public R getUserSig(@PathVariable String userId) {
+ TLSSigAPIv2 sigAPIv2 = new TLSSigAPIv2(1600003294, "6b8b57a7eedb92b6646d1c81bd68681ab924e53b52069cd20b0f53c8e3801a18");
+ String userSig = sigAPIv2.genUserSig(userId, 36000);
+ return R.ok(userSig);
+ }
+
+ @ApiOperation("开始会诊")
+ @GetMapping("start/{applyId}")
+ public R start(@PathVariable String applyId) {
+ int roomId = RandomUtil.randomInt(1, 1000);
+ MessageTemplate msg = new MessageTemplate();
+ msg.setMessage(String.valueOf(roomId));
+ msg.setToKey(applyId);
+ msg.setMsgType(MessageContentType.CONSULTATION.name());
+ msg.setSendTime(new Date());
+ WebSocketUtils.sendMessage(applyId, msg);
+ return R.ok(String.valueOf(roomId));
+ }
+
+ @ApiOperation("解散房间")
+ @GetMapping("dissolveRoom/{roomId}")
+ public R> dissolveRoom(@PathVariable String roomId) throws TencentCloudSDKException {
+ Credential cred = new Credential(secretid, secretkey);
+ HttpProfile httpProfile = new HttpProfile();
+ httpProfile.setEndpoint(ENDPOINT);
+ ClientProfile clientProfile = new ClientProfile();
+ clientProfile.setHttpProfile(httpProfile);
+ TrtcClient client = new TrtcClient(cred, REGION, clientProfile);
+ String params = "{\"SdkAppId\":" + sdkappid + ",\"RoomId\":" + roomId + "}";
+ DissolveRoomRequest req = DissolveRoomRequest.fromJsonString(params, DissolveRoomRequest.class);
+ DissolveRoomResponse resp = client.DissolveRoom(req);
+ return R.ok(resp.getRequestId());
+ }
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/schedule/ScheduleController.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/schedule/ScheduleController.java
new file mode 100644
index 0000000..cdade48
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/schedule/ScheduleController.java
@@ -0,0 +1,18 @@
+package com.xinelu.manage.controller.schedule;
+
+import io.swagger.annotations.ApiModel;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @description: 排班班次控制器
+ * @author: haown
+ * @create: 2023-09-21 17:25
+ **/
+@ApiModel(value = "排班班次控制器")
+@RestController
+@RequestMapping("/system/schedule")
+public class ScheduleController {
+
+
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningproject/ScreeningProjectController.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningproject/ScreeningProjectController.java
index 2284c69..8b522b4 100644
--- a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningproject/ScreeningProjectController.java
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningproject/ScreeningProjectController.java
@@ -29,7 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
* @create: 2023-01-19 10:49
**/
@RestController
-@RequestMapping("/business/project")
+@RequestMapping("/system/screening/project")
@Api(tags = "项目控制器")
public class ScreeningProjectController extends BaseController {
@Resource
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningrecord/ScreeningRecordController.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningrecord/ScreeningRecordController.java
index e857b97..9a56d52 100644
--- a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningrecord/ScreeningRecordController.java
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/controller/screeningrecord/ScreeningRecordController.java
@@ -33,7 +33,7 @@ import org.springframework.web.bind.annotation.RestController;
* @return
**/
@RestController
-@RequestMapping("/business/screening")
+@RequestMapping("/system/screening/record")
@Api(tags = "筛查预约控制器")
public class ScreeningRecordController extends BaseController {
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplan/SchedulePlan.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplan/SchedulePlan.java
new file mode 100644
index 0000000..3905f9e
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplan/SchedulePlan.java
@@ -0,0 +1,85 @@
+package com.xinelu.manage.domain.scheduleplan;
+
+import com.xinelu.common.core.domain.BaseEntity;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 医生排班计划表
+ * @TableName schedule_plan
+ */
+@Data
+public class SchedulePlan extends BaseEntity {
+ /**
+ * 主键
+ */
+ private Long id;
+
+ /**
+ * 医院主键
+ */
+ private Long hospitalId;
+
+ /**
+ * 医院名称
+ */
+ private String hospitalName;
+
+ /**
+ * 所属部门id
+ */
+ private Long departmentId;
+
+ /**
+ * 所属部门名称
+ */
+ private String departmentName;
+
+ /**
+ * 班次主键
+ */
+ private Long scheduleId;
+
+ /**
+ * 班次名称
+ */
+ private String scheduleName;
+
+ /**
+ * 医生主键
+ */
+ private Long doctorId;
+
+ /**
+ * 医生姓名
+ */
+ private String doctorName;
+
+ /**
+ * 排班开始日期
+ */
+ private Date scheduleStartDate;
+
+ /**
+ * 排班结束日期
+ */
+ private Date scheduleEndDate;
+
+ /**
+ * 每人可预约分钟数
+ */
+ private Integer minutesPerPatient;
+
+ /**
+ * 每天可预约人数
+ */
+ private Integer patientsPerDay;
+
+ /**
+ * 出诊地点
+ */
+ private String scheduleAddress;
+
+ private static final long serialVersionUID = 1L;
+
+}
\ No newline at end of file
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplandetail/SchedulePlanDetail.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplandetail/SchedulePlanDetail.java
new file mode 100644
index 0000000..8896c2e
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/domain/scheduleplandetail/SchedulePlanDetail.java
@@ -0,0 +1,55 @@
+package com.xinelu.manage.domain.scheduleplandetail;
+
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 医生排班计划明细表
+ * @TableName schedule_plan_detail
+ */
+@Data
+public class SchedulePlanDetail implements Serializable {
+ /**
+ * 主键
+ */
+ private Long id;
+
+ /**
+ * 排班计划主键
+ */
+ private Long schedulePlanId;
+
+ /**
+ * 医生主键
+ */
+ private Long doctorId;
+
+ /**
+ * 医生姓名
+ */
+ private String doctorName;
+
+ /**
+ * 排班日期
+ */
+ private Date scheduleDate;
+
+ /**
+ * 开始时间
+ */
+ private Date scheduleStartTime;
+
+ /**
+ * 结束时间
+ */
+ private Date scheduleEndTime;
+
+ /**
+ * 预约状态(0:未预约,1:已预约)
+ */
+ private String applyState;
+
+ private static final long serialVersionUID = 1L;
+
+}
\ No newline at end of file
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplan/SchedulePlanMapper.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplan/SchedulePlanMapper.java
new file mode 100644
index 0000000..4b3b36d
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplan/SchedulePlanMapper.java
@@ -0,0 +1,25 @@
+package com.xinelu.manage.mapper.scheduleplan;
+
+import com.xinelu.manage.domain.scheduleplan.SchedulePlan;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan(医生排班计划表)】的数据库操作Mapper
+* @createDate 2023-09-21 17:17:23
+* @Entity com.xinelu.manage.domain.scheduleplan.SchedulePlan
+*/
+public interface SchedulePlanMapper {
+
+ int deleteByPrimaryKey(Long id);
+
+ int insert(SchedulePlan record);
+
+ int insertSelective(SchedulePlan record);
+
+ SchedulePlan selectByPrimaryKey(Long id);
+
+ int updateByPrimaryKeySelective(SchedulePlan record);
+
+ int updateByPrimaryKey(SchedulePlan record);
+
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplandetail/SchedulePlanDetailMapper.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplandetail/SchedulePlanDetailMapper.java
new file mode 100644
index 0000000..fcf3c37
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/mapper/scheduleplandetail/SchedulePlanDetailMapper.java
@@ -0,0 +1,25 @@
+package com.xinelu.manage.mapper.scheduleplandetail;
+
+import com.xinelu.manage.domain.scheduleplandetail.SchedulePlanDetail;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan_detail(医生排班计划明细表)】的数据库操作Mapper
+* @createDate 2023-09-21 17:22:22
+* @Entity com.xinelu.manage.domain.scheduleplandetail.SchedulePlanDetail
+*/
+public interface SchedulePlanDetailMapper {
+
+ int deleteByPrimaryKey(Long id);
+
+ int insert(SchedulePlanDetail record);
+
+ int insertSelective(SchedulePlanDetail record);
+
+ SchedulePlanDetail selectByPrimaryKey(Long id);
+
+ int updateByPrimaryKeySelective(SchedulePlanDetail record);
+
+ int updateByPrimaryKey(SchedulePlanDetail record);
+
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/SchedulePlanService.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/SchedulePlanService.java
new file mode 100644
index 0000000..ef4dec8
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/SchedulePlanService.java
@@ -0,0 +1,10 @@
+package com.xinelu.manage.service.scheduleplan;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan(医生排班计划表)】的数据库操作Service
+* @createDate 2023-09-21 17:16:40
+*/
+public interface SchedulePlanService {
+
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/impl/SchedulePlanServiceImpl.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/impl/SchedulePlanServiceImpl.java
new file mode 100644
index 0000000..9ef331f
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplan/impl/SchedulePlanServiceImpl.java
@@ -0,0 +1,18 @@
+package com.xinelu.manage.service.scheduleplan.impl;
+
+import com.xinelu.manage.service.scheduleplan.SchedulePlanService;
+import org.springframework.stereotype.Service;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan(医生排班计划表)】的数据库操作Service实现
+* @createDate 2023-09-21 17:16:40
+*/
+@Service
+public class SchedulePlanServiceImpl implements SchedulePlanService{
+
+}
+
+
+
+
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/SchedulePlanDetailService.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/SchedulePlanDetailService.java
new file mode 100644
index 0000000..3bf46b5
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/SchedulePlanDetailService.java
@@ -0,0 +1,10 @@
+package com.xinelu.manage.service.scheduleplandetail;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan_detail(医生排班计划明细表)】的数据库操作Service
+* @createDate 2023-09-21 17:21:38
+*/
+public interface SchedulePlanDetailService {
+
+}
diff --git a/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/impl/SchedulePlanDetailServiceImpl.java b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/impl/SchedulePlanDetailServiceImpl.java
new file mode 100644
index 0000000..f71e778
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/java/com/xinelu/manage/service/scheduleplandetail/impl/SchedulePlanDetailServiceImpl.java
@@ -0,0 +1,18 @@
+package com.xinelu.manage.service.scheduleplandetail.impl;
+
+import com.xinelu.manage.service.scheduleplandetail.SchedulePlanDetailService;
+import org.springframework.stereotype.Service;
+
+/**
+* @author haown
+* @description 针对表【schedule_plan_detail(医生排班计划明细表)】的数据库操作Service实现
+* @createDate 2023-09-21 17:21:38
+*/
+@Service
+public class SchedulePlanDetailServiceImpl implements SchedulePlanDetailService{
+
+}
+
+
+
+
diff --git a/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplan/SchedulePlanMapper.xml b/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplan/SchedulePlanMapper.xml
new file mode 100644
index 0000000..d2b9267
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplan/SchedulePlanMapper.xml
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,hospital_id,hospital_name,
+ department_id,department_name,schedule_id,
+ schedule_name,doctor_id,doctor_name,
+ schedule_start_date,schedule_end_date,minutes_per_patient,
+ patients_per_day,schedule_address,create_by,
+ create_time,update_by,update_time
+
+
+
+
+
+ delete from schedule_plan
+ where id = #{id,jdbcType=BIGINT}
+
+
+ insert into schedule_plan
+ ( id,hospital_id,hospital_name
+ ,department_id,department_name,schedule_id
+ ,schedule_name,doctor_id,doctor_name
+ ,schedule_start_date,schedule_end_date,minutes_per_patient
+ ,patients_per_day,schedule_address,create_by
+ ,create_time,update_by,update_time
+ )
+ values (#{id,jdbcType=BIGINT},#{hospitalId,jdbcType=BIGINT},#{hospitalName,jdbcType=VARCHAR}
+ ,#{departmentId,jdbcType=BIGINT},#{departmentName,jdbcType=VARCHAR},#{scheduleId,jdbcType=BIGINT}
+ ,#{scheduleName,jdbcType=VARCHAR},#{doctorId,jdbcType=BIGINT},#{doctorName,jdbcType=VARCHAR}
+ ,#{scheduleStartDate,jdbcType=DATE},#{scheduleEndDate,jdbcType=DATE},#{minutesPerPatient,jdbcType=INTEGER}
+ ,#{patientsPerDay,jdbcType=INTEGER},#{scheduleAddress,jdbcType=VARCHAR},#{createBy,jdbcType=VARCHAR}
+ ,#{createTime,jdbcType=TIMESTAMP},#{updateBy,jdbcType=VARCHAR},#{updateTime,jdbcType=TIMESTAMP}
+ )
+
+
+ insert into schedule_plan
+
+ id,
+ hospital_id,
+ hospital_name,
+ department_id,
+ department_name,
+ schedule_id,
+ schedule_name,
+ doctor_id,
+ doctor_name,
+ schedule_start_date,
+ schedule_end_date,
+ minutes_per_patient,
+ patients_per_day,
+ schedule_address,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+
+
+ #{id,jdbcType=BIGINT},
+ #{hospitalId,jdbcType=BIGINT},
+ #{hospitalName,jdbcType=VARCHAR},
+ #{departmentId,jdbcType=BIGINT},
+ #{departmentName,jdbcType=VARCHAR},
+ #{scheduleId,jdbcType=BIGINT},
+ #{scheduleName,jdbcType=VARCHAR},
+ #{doctorId,jdbcType=BIGINT},
+ #{doctorName,jdbcType=VARCHAR},
+ #{scheduleStartDate,jdbcType=DATE},
+ #{scheduleEndDate,jdbcType=DATE},
+ #{minutesPerPatient,jdbcType=INTEGER},
+ #{patientsPerDay,jdbcType=INTEGER},
+ #{scheduleAddress,jdbcType=VARCHAR},
+ #{createBy,jdbcType=VARCHAR},
+ #{createTime,jdbcType=TIMESTAMP},
+ #{updateBy,jdbcType=VARCHAR},
+ #{updateTime,jdbcType=TIMESTAMP},
+
+
+
+ update schedule_plan
+
+
+ hospital_id = #{hospitalId,jdbcType=BIGINT},
+
+
+ hospital_name = #{hospitalName,jdbcType=VARCHAR},
+
+
+ department_id = #{departmentId,jdbcType=BIGINT},
+
+
+ department_name = #{departmentName,jdbcType=VARCHAR},
+
+
+ schedule_id = #{scheduleId,jdbcType=BIGINT},
+
+
+ schedule_name = #{scheduleName,jdbcType=VARCHAR},
+
+
+ doctor_id = #{doctorId,jdbcType=BIGINT},
+
+
+ doctor_name = #{doctorName,jdbcType=VARCHAR},
+
+
+ schedule_start_date = #{scheduleStartDate,jdbcType=DATE},
+
+
+ schedule_end_date = #{scheduleEndDate,jdbcType=DATE},
+
+
+ minutes_per_patient = #{minutesPerPatient,jdbcType=INTEGER},
+
+
+ patients_per_day = #{patientsPerDay,jdbcType=INTEGER},
+
+
+ schedule_address = #{scheduleAddress,jdbcType=VARCHAR},
+
+
+ create_by = #{createBy,jdbcType=VARCHAR},
+
+
+ create_time = #{createTime,jdbcType=TIMESTAMP},
+
+
+ update_by = #{updateBy,jdbcType=VARCHAR},
+
+
+ update_time = #{updateTime,jdbcType=TIMESTAMP},
+
+
+ where id = #{id,jdbcType=BIGINT}
+
+
+ update schedule_plan
+ set
+ hospital_id = #{hospitalId,jdbcType=BIGINT},
+ hospital_name = #{hospitalName,jdbcType=VARCHAR},
+ department_id = #{departmentId,jdbcType=BIGINT},
+ department_name = #{departmentName,jdbcType=VARCHAR},
+ schedule_id = #{scheduleId,jdbcType=BIGINT},
+ schedule_name = #{scheduleName,jdbcType=VARCHAR},
+ doctor_id = #{doctorId,jdbcType=BIGINT},
+ doctor_name = #{doctorName,jdbcType=VARCHAR},
+ schedule_start_date = #{scheduleStartDate,jdbcType=DATE},
+ schedule_end_date = #{scheduleEndDate,jdbcType=DATE},
+ minutes_per_patient = #{minutesPerPatient,jdbcType=INTEGER},
+ patients_per_day = #{patientsPerDay,jdbcType=INTEGER},
+ schedule_address = #{scheduleAddress,jdbcType=VARCHAR},
+ create_by = #{createBy,jdbcType=VARCHAR},
+ create_time = #{createTime,jdbcType=TIMESTAMP},
+ update_by = #{updateBy,jdbcType=VARCHAR},
+ update_time = #{updateTime,jdbcType=TIMESTAMP}
+ where id = #{id,jdbcType=BIGINT}
+
+
diff --git a/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplandetail/SchedulePlanDetailMapper.xml b/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplandetail/SchedulePlanDetailMapper.xml
new file mode 100644
index 0000000..db19e6d
--- /dev/null
+++ b/xinelu-nurse-manage/src/main/resources/mapper/manage/scheduleplandetail/SchedulePlanDetailMapper.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,schedule_plan_id,doctor_id,
+ doctor_name,schedule_date,schedule_start_time,
+ schedule_end_time,apply_state
+
+
+
+
+
+ delete from schedule_plan_detail
+ where id = #{id,jdbcType=BIGINT}
+
+
+ insert into schedule_plan_detail
+ ( id,schedule_plan_id,doctor_id
+ ,doctor_name,schedule_date,schedule_start_time
+ ,schedule_end_time,apply_state)
+ values (#{id,jdbcType=BIGINT},#{schedulePlanId,jdbcType=BIGINT},#{doctorId,jdbcType=BIGINT}
+ ,#{doctorName,jdbcType=VARCHAR},#{scheduleDate,jdbcType=DATE},#{scheduleStartTime,jdbcType=TIME}
+ ,#{scheduleEndTime,jdbcType=TIME},#{applyState,jdbcType=VARCHAR})
+
+
+ insert into schedule_plan_detail
+
+ id,
+ schedule_plan_id,
+ doctor_id,
+ doctor_name,
+ schedule_date,
+ schedule_start_time,
+ schedule_end_time,
+ apply_state,
+
+
+ #{id,jdbcType=BIGINT},
+ #{schedulePlanId,jdbcType=BIGINT},
+ #{doctorId,jdbcType=BIGINT},
+ #{doctorName,jdbcType=VARCHAR},
+ #{scheduleDate,jdbcType=DATE},
+ #{scheduleStartTime,jdbcType=TIME},
+ #{scheduleEndTime,jdbcType=TIME},
+ #{applyState,jdbcType=VARCHAR},
+
+
+
+ update schedule_plan_detail
+
+
+ schedule_plan_id = #{schedulePlanId,jdbcType=BIGINT},
+
+
+ doctor_id = #{doctorId,jdbcType=BIGINT},
+
+
+ doctor_name = #{doctorName,jdbcType=VARCHAR},
+
+
+ schedule_date = #{scheduleDate,jdbcType=DATE},
+
+
+ schedule_start_time = #{scheduleStartTime,jdbcType=TIME},
+
+
+ schedule_end_time = #{scheduleEndTime,jdbcType=TIME},
+
+
+ apply_state = #{applyState,jdbcType=VARCHAR},
+
+
+ where id = #{id,jdbcType=BIGINT}
+
+
+ update schedule_plan_detail
+ set
+ schedule_plan_id = #{schedulePlanId,jdbcType=BIGINT},
+ doctor_id = #{doctorId,jdbcType=BIGINT},
+ doctor_name = #{doctorName,jdbcType=VARCHAR},
+ schedule_date = #{scheduleDate,jdbcType=DATE},
+ schedule_start_time = #{scheduleStartTime,jdbcType=TIME},
+ schedule_end_time = #{scheduleEndTime,jdbcType=TIME},
+ apply_state = #{applyState,jdbcType=VARCHAR}
+ where id = #{id,jdbcType=BIGINT}
+
+