From 55455af7dbfc137d88dbb0dcdd3c53d5d9a18dcc Mon Sep 17 00:00:00 2001 From: fbw <1171460872@qq.com> Date: Mon, 30 Mar 2026 21:59:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96okhttp=E5=92=8Ctest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cc-admin-master/pom.xml | 7 +- cc-admin-master/yudao-module-interphone/pom.xml | 56 +++ .../yudao/module/interphone/auth/Author.java | 10 + .../interphone/auth/DefaultLoginService.java | 97 ++++ .../interphone/auth/DefaultTokenManager.java | 57 +++ .../yudao/module/interphone/auth/LoginService.java | 6 + .../yudao/module/interphone/auth/TokenManager.java | 11 + .../interphone/config/HttpClientProperties.java | 89 ++++ .../interphone/config/OkHttpClientConfig.java | 36 ++ .../yudao/module/interphone/core/ApiClient.java | 116 +++++ .../yudao/module/interphone/core/ApiRequest.java | 91 ++++ .../yudao/module/interphone/core/ApiResponse.java | 37 ++ .../yudao/module/interphone/core/HttpMethod.java | 5 + .../module/interphone/core/QueryStringBuilder.java | 53 +++ .../module/interphone/demo/DemoRemoteClient.java | 28 ++ .../module/interphone/retry/RetryContext.java | 37 ++ .../module/interphone/retry/RetryDecision.java | 35 ++ .../module/interphone/retry/RetryHandler.java | 6 + .../interphone/retry/TokenExpiredRetryHandler.java | 39 ++ .../interphone/demo/DemoRemoteClientTest.java | 42 ++ .../yudao/module/interphone/demo/TestConfig.java | 16 + .../src/test/resources/application-unit-test.yaml | 504 +++++++++++++++++++++ cc-admin-master/yudao-server/pom.xml | 7 + .../src/main/resources/application-dev.yaml | 8 +- .../src/main/resources/application-localfbw.yaml | 264 +++++++++++ .../src/main/resources/application.yaml | 2 +- .../src/main/resources/logback-spring.xml | 76 ---- 27 files changed, 1654 insertions(+), 81 deletions(-) create mode 100644 cc-admin-master/yudao-module-interphone/pom.xml create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/Author.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultLoginService.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultTokenManager.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/LoginService.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/TokenManager.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/HttpClientProperties.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/OkHttpClientConfig.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiClient.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiRequest.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiResponse.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/HttpMethod.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/QueryStringBuilder.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClient.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryContext.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryDecision.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryHandler.java create mode 100644 cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/TokenExpiredRetryHandler.java create mode 100644 cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClientTest.java create mode 100644 cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/TestConfig.java create mode 100644 cc-admin-master/yudao-module-interphone/src/test/resources/application-unit-test.yaml create mode 100644 cc-admin-master/yudao-server/src/main/resources/application-localfbw.yaml delete mode 100644 cc-admin-master/yudao-server/src/main/resources/logback-spring.xml diff --git a/cc-admin-master/pom.xml b/cc-admin-master/pom.xml index 4fab5de..1f96819 100644 --- a/cc-admin-master/pom.xml +++ b/cc-admin-master/pom.xml @@ -15,10 +15,11 @@ yudao-module-system yudao-module-infra - yudao-module-bpm - yudao-module-report - yudao-module-hand + yudao-module-bpm + yudao-module-report + yudao-module-hand yudao-module-hand-mqtt + yudao-module-interphone ${project.artifactId} diff --git a/cc-admin-master/yudao-module-interphone/pom.xml b/cc-admin-master/yudao-module-interphone/pom.xml new file mode 100644 index 0000000..91698c1 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/pom.xml @@ -0,0 +1,56 @@ + + + 4.0.0 + + cn.iocoder.boot + cc-admin-master + ${revision} + + + yudao-module-interphone + jar + + + + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + + + + + cn.iocoder.boot + yudao-spring-boot-starter-security + + + + cn.iocoder.boot + yudao-spring-boot-starter-mybatis + + + com.baomidou + mybatis-plus-generator + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + + + + + cn.iocoder.boot + yudao-spring-boot-starter-test + test + + + + + \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/Author.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/Author.java new file mode 100644 index 0000000..53bef53 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/Author.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.interphone.auth; + +import lombok.Data; + +@Data +public class Author { + private String accessToken; + private String refreshToken; + private String openId; +} diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultLoginService.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultLoginService.java new file mode 100644 index 0000000..ffcaa18 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultLoginService.java @@ -0,0 +1,97 @@ +package cn.iocoder.yudao.module.interphone.auth; + +import cn.iocoder.yudao.module.interphone.config.HttpClientProperties; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Objects; + +@Service +public class DefaultLoginService implements LoginService { + + private final OkHttpClient okHttpClient; + private final HttpClientProperties properties; + + public DefaultLoginService(OkHttpClient okHttpClient, HttpClientProperties properties) { + this.okHttpClient = okHttpClient; + this.properties = properties; + } + + @Override + public Author loginAndGetToken() { + HttpUrl url = HttpUrl.parse(properties.getApiUrl() + "auth/skipImgVerify") + .newBuilder() + .addQueryParameter("username", properties.getUsername()) + .addQueryParameter("password", properties.getPassword()) + .addQueryParameter("user_type", properties.getUserType()) + .addQueryParameter("appId", properties.getAppId()) + .addQueryParameter("appSecret", properties.getAppSecret()) + .build(); + + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { + throw new RuntimeException("登录失败,http code=" + response.code()); + } + + String body = Objects.requireNonNull(response.body()).string(); + + // 这里简化处理,实际项目建议用 Jackson 解析 JSON + // 假设响应:{"code":0,"token":"abc123"} + String token = extractToken(body); + if (token == null || token.isBlank()) { + throw new RuntimeException("登录成功但未获取到 token"); + } + String openId = extraOpenId(body); + if (openId == null || openId.isBlank()) { + throw new RuntimeException("登录成功但未获取到 openId"); + } + Author author = new Author(); + author.setAccessToken(token); + author.setOpenId(openId); + return author; + } catch (IOException e) { + throw new RuntimeException("登录请求异常", e); + } + } + + private String extractToken(String body) { + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(body); + JsonNode dataNode = rootNode.get("data"); + if (dataNode != null) { + JsonNode accessTokenNode = dataNode.get("access_token"); + if (accessTokenNode != null) { + return accessTokenNode.asText(); + } + } + return null; + } catch (IOException e) { + throw new RuntimeException("解析 token 失败", e); + } + } + private String extraOpenId(String body) { + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = mapper.readTree(body); + JsonNode dataNode = rootNode.get("data"); + if (dataNode != null) { + JsonNode openIdNode = dataNode.get("openid"); + if (openIdNode != null) { + return openIdNode.asText(); + } + } + return null; + } catch (IOException e) { + throw new RuntimeException("解析 openId 失败", e); + } + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultTokenManager.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultTokenManager.java new file mode 100644 index 0000000..b80f083 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultTokenManager.java @@ -0,0 +1,57 @@ +package cn.iocoder.yudao.module.interphone.auth; + +import org.springframework.stereotype.Component; + +import java.util.concurrent.locks.ReentrantLock; + +@Component +public class DefaultTokenManager implements TokenManager { + + private final LoginService loginService; + private volatile Author author; + private final ReentrantLock refreshLock = new ReentrantLock(); + + public DefaultTokenManager(LoginService loginService) { + this.loginService = loginService; + } + + @Override + public Author getAuthor() { + if (author == null || author.getAccessToken().isBlank()) { + refreshToken(); + } + return author; + } + + @Override + public void refreshToken() { + refreshLock.lock(); + try { + if (author != null && !author.getRefreshToken().isBlank()) { + return; + } + this.author = loginService.loginAndGetToken(); + } finally { + refreshLock.unlock(); + } + } + + @Override + public void clearAuthor() { + refreshLock.lock(); + try { + this.author = null; + } finally { + refreshLock.unlock(); + } + } + + public void forceRefreshToken() { + refreshLock.lock(); + try { + this.author = loginService.loginAndGetToken(); + } finally { + refreshLock.unlock(); + } + } +} diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/LoginService.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/LoginService.java new file mode 100644 index 0000000..bd479cd --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/LoginService.java @@ -0,0 +1,6 @@ +package cn.iocoder.yudao.module.interphone.auth; + +public interface LoginService { + + Author loginAndGetToken(); +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/TokenManager.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/TokenManager.java new file mode 100644 index 0000000..9047d40 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/TokenManager.java @@ -0,0 +1,11 @@ +package cn.iocoder.yudao.module.interphone.auth; + +public interface TokenManager { + + Author getAuthor(); + + void refreshToken(); + + void clearAuthor(); + +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/HttpClientProperties.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/HttpClientProperties.java new file mode 100644 index 0000000..7e0e202 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/HttpClientProperties.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.interphone.config; + +import lombok.Data; +import okhttp3.internal.Internal; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.validation.annotation.Validated; + +/** + * 对讲机服务配置属性 + */ +@Data +@Validated +@ConfigurationProperties(prefix = "interphone") +public class HttpClientProperties { + + /** + * 对讲机服务用户名 + */ + private String username; + + /** + * 对讲机服务密码 + */ + private String password; + + /** + * 对讲机服务API地址 + */ + private String apiUrl; + + private String userType = "0"; + + private String appId = "97796fef376d41e0a7dda02720d0e3c9"; + + private String appSecret = "m597gv5h4hmdusce"; + + /** + * HTTP 客户端配置 + */ + private AppHttp http = new AppHttp(); + + @Data + public static class AppHttp { + /** + * 连接超时时间(秒) + */ + private long connectTimeoutSeconds = 5; + + /** + * 读取超时时间(秒) + */ + private long readTimeoutSeconds = 10; + + /** + * 写入超时时间(秒) + */ + private long writeTimeoutSeconds = 10; + + /** + * 调用超时时间(秒) + */ + private long callTimeoutSeconds = 15; + + /** + * 最大空闲连接数 + */ + private int maxIdleConnections = 50; + + /** + * 连接保持时间(分钟) + */ + private long keepAliveDurationMinutes = 5; + + /** + * 重试配置 + */ + private Retry retry = new Retry(); + + @Data + public static class Retry { + /** + * 最大重试次数 + */ + private int maxRetries = 2; + } + } + +} diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/OkHttpClientConfig.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/OkHttpClientConfig.java new file mode 100644 index 0000000..4ff78fb --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/OkHttpClientConfig.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.interphone.config; + +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.concurrent.TimeUnit; + +@Configuration +@EnableConfigurationProperties(HttpClientProperties.class) +public class OkHttpClientConfig { + + @Bean + public ConnectionPool connectionPool(HttpClientProperties properties) { + return new ConnectionPool( + properties.getHttp().getMaxIdleConnections(), + properties.getHttp().getKeepAliveDurationMinutes(), + TimeUnit.MINUTES + ); + } + + @Bean + public OkHttpClient okHttpClient(HttpClientProperties properties, + ConnectionPool connectionPool) { + return new OkHttpClient.Builder() + .connectTimeout(properties.getHttp().getConnectTimeoutSeconds(), TimeUnit.SECONDS) + .readTimeout(properties.getHttp().getReadTimeoutSeconds(), TimeUnit.SECONDS) + .writeTimeout(properties.getHttp().getWriteTimeoutSeconds(), TimeUnit.SECONDS) + .callTimeout(properties.getHttp().getCallTimeoutSeconds(), TimeUnit.SECONDS) + .connectionPool(connectionPool) + .retryOnConnectionFailure(false) + .build(); + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiClient.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiClient.java new file mode 100644 index 0000000..52c6602 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiClient.java @@ -0,0 +1,116 @@ +package cn.iocoder.yudao.module.interphone.core; + +import cn.iocoder.yudao.module.interphone.auth.Author; +import cn.iocoder.yudao.module.interphone.auth.TokenManager; +import cn.iocoder.yudao.module.interphone.config.HttpClientProperties; +import cn.iocoder.yudao.module.interphone.retry.RetryContext; +import cn.iocoder.yudao.module.interphone.retry.RetryDecision; +import cn.iocoder.yudao.module.interphone.retry.RetryHandler; +import okhttp3.*; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Component +public class ApiClient { + + private final OkHttpClient okHttpClient; + private final HttpClientProperties properties; + private final TokenManager tokenManager; + private final List retryHandlers; + + public ApiClient(OkHttpClient okHttpClient, + HttpClientProperties properties, + TokenManager tokenManager, + List retryHandlers) { + this.okHttpClient = okHttpClient; + this.properties = properties; + this.tokenManager = tokenManager; + this.retryHandlers = retryHandlers; + } + + public ApiResponse execute(ApiRequest apiRequest) { + int maxRetries = properties.getHttp().getRetry().getMaxRetries(); + int attempt = 0; + + while (true) { + attempt++; + ApiResponse response = null; + Exception exception = null; + + try { + response = doExecute(apiRequest); + + if (response.isSuccess()) { + return response; + } + + RetryDecision decision = decideRetry(apiRequest, response, null, attempt); + if (decision.shouldRetry() && attempt <= maxRetries) { + continue; + } + + return response; + } catch (Exception e) { + exception = e; + RetryDecision decision = decideRetry(apiRequest, null, e, attempt); + if (decision.shouldRetry() && attempt <= maxRetries) { + continue; + } + throw new RuntimeException("HTTP 请求失败,path=" + apiRequest.getPath(), e); + } + } + } + + private ApiResponse doExecute(ApiRequest apiRequest) throws IOException { + String url = QueryStringBuilder.build( + properties.getApiUrl(), + apiRequest.getPath(), + apiRequest.getQueryParams() + ); + + Request.Builder builder = new Request.Builder().url(url); + + if (apiRequest.isNeedToken()) { + Author author = tokenManager.getAuthor(); + builder.url(url + "?access_token=" + author.getAccessToken() + "&openid=" + author.getOpenId()); + } + + if (apiRequest.getHeaders() != null) { + for (Map.Entry entry : apiRequest.getHeaders().entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + } + + if (apiRequest.getMethod() == HttpMethod.GET) { + builder.get(); + } else if (apiRequest.getMethod() == HttpMethod.POST) { + RequestBody requestBody = RequestBody.create( + apiRequest.getJsonBody() == null ? "" : apiRequest.getJsonBody(), + MediaType.parse("application/json; charset=utf-8") + ); + builder.post(requestBody); + } else { + throw new RuntimeException("不支持的请求方法: " + apiRequest.getMethod()); + } + + try (Response response = okHttpClient.newCall(builder.build()).execute()) { + String body = response.body() == null ? null : Objects.requireNonNull(response.body()).string(); + return new ApiResponse(response.code(), body, response.headers().toMultimap()); + } + } + + private RetryDecision decideRetry(ApiRequest request, ApiResponse response, Exception exception, int attempt) { + RetryContext context = new RetryContext(request, response, exception, attempt); + for (RetryHandler retryHandler : retryHandlers) { + RetryDecision decision = retryHandler.decide(context); + if (decision.shouldRetry()) { + return decision; + } + } + return RetryDecision.noRetry(); + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiRequest.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiRequest.java new file mode 100644 index 0000000..75891d9 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiRequest.java @@ -0,0 +1,91 @@ +package cn.iocoder.yudao.module.interphone.core; + + +import java.util.HashMap; +import java.util.Map; + + +public class ApiRequest { + + private HttpMethod method; + private String path; + private Map queryParams = new HashMap<>(); + private Map headers = new HashMap<>(); + private String jsonBody; + private boolean needToken = true; + + + public HttpMethod getMethod() { + return method; + } + + public void setMethod(HttpMethod method) { + this.method = method; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Map getQueryParams() { + return queryParams; + } + + public void setQueryParams(Map queryParams) { + this.queryParams = queryParams; + } + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public String getJsonBody() { + return jsonBody; + } + + public void setJsonBody(String jsonBody) { + this.jsonBody = jsonBody; + } + + public boolean isNeedToken() { + return needToken; + } + + public void setNeedToken(boolean needToken) { + this.needToken = needToken; + } + + public static ApiRequest get(String path, Map queryParams) { + ApiRequest request = new ApiRequest(); + request.setMethod(HttpMethod.GET); + request.setPath(path); + if (queryParams != null) { + request.setQueryParams(queryParams); + } + return request; + } + + public static ApiRequest get(String path) { + ApiRequest request = new ApiRequest(); + request.setMethod(HttpMethod.GET); + request.setPath(path); + request.setQueryParams(null); + return request; + } + + public static ApiRequest postJson(String path, String jsonBody) { + ApiRequest request = new ApiRequest(); + request.setMethod(HttpMethod.POST); + request.setPath(path); + request.setJsonBody(jsonBody); + return request; + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiResponse.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiResponse.java new file mode 100644 index 0000000..90c9e88 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiResponse.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.interphone.core; + +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ApiResponse { + + private int code; + private String body; + private Map> headers; + + public ApiResponse(int code, String body, Map> headers) { + this.code = code; + this.body = body; + this.headers = headers; + } + + public int getCode() { + return code; + } + + public String getBody() { + return body; + } + + public Map> getHeaders() { + return headers; + } + + public boolean isSuccess() { + return code >= 200 && code < 300; + } + +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/HttpMethod.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/HttpMethod.java new file mode 100644 index 0000000..167d5eb --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/HttpMethod.java @@ -0,0 +1,5 @@ +package cn.iocoder.yudao.module.interphone.core; + +public enum HttpMethod { + GET, POST +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/QueryStringBuilder.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/QueryStringBuilder.java new file mode 100644 index 0000000..ed132fd --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/QueryStringBuilder.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.module.interphone.core; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.StringJoiner; + +@Data +public final class QueryStringBuilder { + + public static String build(String baseUrl, String path, Map params) { + StringBuilder url = new StringBuilder(); + url.append(trimRightSlash(baseUrl)); + if (!path.startsWith("/")) { + url.append("/"); + } + url.append(path); + + if (params != null && !params.isEmpty()) { + StringJoiner joiner = new StringJoiner("&"); + for (Map.Entry entry : params.entrySet()) { + if (entry.getValue() == null) { + continue; + } + joiner.add(encode(entry.getKey()) + "=" + encode(entry.getValue())); + } + String query = joiner.toString(); + if (!query.isEmpty()) { + url.append("?").append(query); + } + } + return url.toString(); + } + + private static String trimRightSlash(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + return text.endsWith("/") ? text.substring(0, text.length() - 1) : text; + } + + private static String encode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new IllegalStateException("URL encode failed", e); + } + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClient.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClient.java new file mode 100644 index 0000000..c014f87 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClient.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.interphone.demo; + + +import cn.iocoder.yudao.module.interphone.core.ApiClient; +import cn.iocoder.yudao.module.interphone.core.ApiRequest; +import cn.iocoder.yudao.module.interphone.core.ApiResponse; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Component +public class DemoRemoteClient { + + private final ApiClient apiClient; + + public DemoRemoteClient(ApiClient apiClient) { + this.apiClient = apiClient; + } + + public String getUserInfo() { + + ApiRequest request = ApiRequest.get("/profile/agent"); + ApiResponse response = apiClient.execute(request); + return response.getBody(); + } + +} diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryContext.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryContext.java new file mode 100644 index 0000000..87e086d --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryContext.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.interphone.retry; + +import cn.iocoder.yudao.module.interphone.core.ApiRequest; +import cn.iocoder.yudao.module.interphone.core.ApiResponse; + + +public class RetryContext { + + private final ApiRequest request; + private final ApiResponse response; + private final Exception exception; + private final int attempt; + + public RetryContext(ApiRequest request, ApiResponse response, Exception exception, int attempt) { + this.request = request; + this.response = response; + this.exception = exception; + this.attempt = attempt; + } + + public ApiRequest getRequest() { + return request; + } + + public ApiResponse getResponse() { + return response; + } + + public Exception getException() { + return exception; + } + + public int getAttempt() { + return attempt; + } + +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryDecision.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryDecision.java new file mode 100644 index 0000000..780067a --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryDecision.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.interphone.retry; + + + +public class RetryDecision { + + private final boolean shouldRetry; + private final boolean tokenRefreshed; + + private RetryDecision(boolean shouldRetry, boolean tokenRefreshed) { + this.shouldRetry = shouldRetry; + this.tokenRefreshed = tokenRefreshed; + } + + public static RetryDecision noRetry() { + return new RetryDecision(false, false); + } + + public static RetryDecision retry() { + return new RetryDecision(true, false); + } + + public static RetryDecision retryAfterTokenRefresh() { + return new RetryDecision(true, true); + } + + public boolean shouldRetry() { + return shouldRetry; + } + + public boolean isTokenRefreshed() { + return tokenRefreshed; + } + +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryHandler.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryHandler.java new file mode 100644 index 0000000..9f4ffda --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryHandler.java @@ -0,0 +1,6 @@ +package cn.iocoder.yudao.module.interphone.retry; + +public interface RetryHandler { + + RetryDecision decide(RetryContext context); +} diff --git a/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/TokenExpiredRetryHandler.java b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/TokenExpiredRetryHandler.java new file mode 100644 index 0000000..d85afbe --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/TokenExpiredRetryHandler.java @@ -0,0 +1,39 @@ +package cn.iocoder.yudao.module.interphone.retry; + +import cn.iocoder.yudao.module.interphone.auth.DefaultTokenManager; +import cn.iocoder.yudao.module.interphone.core.ApiResponse; +import org.springframework.stereotype.Component; + +@Component +public class TokenExpiredRetryHandler implements RetryHandler { + + private final DefaultTokenManager tokenManager; + + public TokenExpiredRetryHandler(DefaultTokenManager tokenManager) { + this.tokenManager = tokenManager; + } + + @Override + public RetryDecision decide(RetryContext context) { + ApiResponse response = context.getResponse(); + if (response == null) { + return RetryDecision.noRetry(); + } + + // 方式1:HTTP 状态码 401 / 403 + if (response.getCode() == 401 || response.getCode() == 403 || response.getCode() == 40102) { + tokenManager.forceRefreshToken(); + return RetryDecision.retryAfterTokenRefresh(); + } + + // 方式2:业务码判断 + String body = response.getBody(); + if (body != null && + (body.contains("\"code\":40101") || body.contains("token expired") || body.contains("登录过期"))) { + tokenManager.forceRefreshToken(); + return RetryDecision.retryAfterTokenRefresh(); + } + + return RetryDecision.noRetry(); + } +} \ No newline at end of file diff --git a/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClientTest.java b/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClientTest.java new file mode 100644 index 0000000..ad34bdd --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClientTest.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.interphone.demo; + +import cn.iocoder.yudao.framework.test.core.ut.BaseRedisUnitTest; +import cn.iocoder.yudao.module.interphone.auth.DefaultLoginService; +import cn.iocoder.yudao.module.interphone.auth.DefaultTokenManager; +import cn.iocoder.yudao.module.interphone.auth.LoginService; +import cn.iocoder.yudao.module.interphone.config.HttpClientProperties; +import cn.iocoder.yudao.module.interphone.config.OkHttpClientConfig; +import cn.iocoder.yudao.module.interphone.core.ApiClient; +import cn.iocoder.yudao.module.interphone.core.ApiRequest; +import cn.iocoder.yudao.module.interphone.core.ApiResponse; +import cn.iocoder.yudao.module.interphone.retry.TokenExpiredRetryHandler; +import jakarta.annotation.Resource; +import okhttp3.OkHttpClient; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + + +@Import({ + OkHttpClientConfig.class, + DefaultLoginService.class, + DefaultTokenManager.class, + ApiClient.class, + TokenExpiredRetryHandler.class, + DemoRemoteClient.class, + OkHttpClient.class +}) +public class DemoRemoteClientTest extends BaseRedisUnitTest { + + + @Resource + private ApiClient apiClient; + + @Test + public void testGetUserInfo_Success() { + ApiRequest request = ApiRequest.get("/profile/agent"); + ApiResponse response = apiClient.execute(request); + System.out.println(response); + } +} diff --git a/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/TestConfig.java b/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/TestConfig.java new file mode 100644 index 0000000..345132c --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/TestConfig.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.interphone.demo; + +import cn.iocoder.yudao.module.interphone.auth.DefaultLoginService; +import cn.iocoder.yudao.module.interphone.auth.LoginService; +import cn.iocoder.yudao.module.interphone.config.HttpClientProperties; +import okhttp3.OkHttpClient; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class TestConfig { + @Bean + public LoginService loginService(OkHttpClient okHttpClient,HttpClientProperties httpClientProperties) { + return new DefaultLoginService(okHttpClient, httpClientProperties); + } +} diff --git a/cc-admin-master/yudao-module-interphone/src/test/resources/application-unit-test.yaml b/cc-admin-master/yudao-module-interphone/src/test/resources/application-unit-test.yaml new file mode 100644 index 0000000..ff20cb1 --- /dev/null +++ b/cc-admin-master/yudao-module-interphone/src/test/resources/application-unit-test.yaml @@ -0,0 +1,504 @@ +spring: + application: + name: gas_mobile + + main: + allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。 + allow-bean-definition-overriding: true + # Servlet 配置 + servlet: + # 文件上传相关配置项 + multipart: + max-file-size: 16MB # 单个文件大小 + max-request-size: 32MB # 设置总上传的文件大小 + + # Jackson 配置项 + jackson: + serialization: + write-dates-as-timestamps: true # 设置 Date 的格式,使用时间戳 + write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401 + write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳 + fail-on-empty-beans: false # 允许序列化无属性的 Bean + + # Cache 配置项 + cache: + type: REDIS + redis: + time-to-live: 1h # 设置过期时间为 1 小时 + +server: + servlet: + encoding: + enabled: true + charset: UTF-8 # 必须设置 UTF-8,避免 WebFlux 流式返回(AI 场景)会乱码问题 + force: true + +--- #################### 接口文档配置 #################### + +springdoc: + api-docs: + enabled: true + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui + default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档 + +knife4j: + enable: false # TODO 芋艿:需要关闭增强,具体原因见:https://github.com/xiaoymin/knife4j/issues/874 + setting: + language: zh_cn + +# 工作流 Flowable 配置 +flowable: + # 1. false: 默认值,Flowable 启动时,对比数据库表中保存的版本,如果不匹配。将抛出异常 + # 2. true: 启动时会对数据库中所有表进行更新操作,如果表存在,不做处理,反之,自动创建表 + # 3. create_drop: 启动时自动创建表,关闭时自动删除表 + # 4. drop_create: 启动时,删除旧表,再创建新表 + database-schema-update: true # 设置为 false,可通过 https://github.com/flowable/flowable-sql 初始化 + db-history-used: true # flowable6 默认 true 生成信息表,无需手动设置 + check-process-definitions: false # 设置为 false,禁用 /resources/processes 自动部署 BPMN XML 流程 + history-level: audit # full:保存历史数据的最高级别,可保存全部流程相关细节,包括流程流转各节点参数 + +# MyBatis Plus 的配置项 +mybatis-plus: + configuration: + map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。 + global-config: + db-config: + id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。 + # id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库 + # id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库 + # id-type: ASSIGN_ID # 分配 ID,默认使用雪花算法。注意,Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解 + logic-delete-value: 1 # 逻辑已删除值(默认为 1) + logic-not-delete-value: 0 # 逻辑未删除值(默认为 0) + banner: false # 关闭控制台的 Banner 打印 + type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject + encryptor: + password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成 + +mybatis-plus-join: + banner: false # 是否打印 mybatis plus join banner,默认true + sub-table-logic: true # 全局启用副表逻辑删除,默认true。关闭后关联查询不会加副表逻辑删除 + ms-cache: true # 拦截器MappedStatement缓存,默认 true + table-alias: t # 表别名(默认 t) + logic-del-type: on # 副表逻辑删除条件的位置,支持 WHERE、ON,默认 ON + +# Spring Data Redis 配置 +spring: + data: + redis: + repositories: + enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度 + +# VO 转换(数据翻译)相关 +easy-trans: + is-enable-global: true # 启用全局翻译(拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置,或通过 @IgnoreTrans 忽略某个接口 + +--- #################### 验证码相关配置 #################### + +aj: + captcha: + jigsaw: classpath:images/jigsaw # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径 + pic-click: classpath:images/pic-click # 滑动验证,底图路径,不配置将使用默认图片;以 classpath: 开头,取 resource 目录下路径 + cache-type: redis # 缓存 local/redis... + cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存 + timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行 + type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选 + water-mark: 1 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode + interference-options: 0 # 滑动干扰项(0/1/2) + req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false + req-get-lock-limit: 5 # 验证失败 5 次,get接口锁定 + req-get-lock-seconds: 10 # 验证失败后,锁定时间间隔 + req-get-minute-limit: 30 # get 接口一分钟内请求数限制 + req-check-minute-limit: 60 # check 接口一分钟内请求数限制 + req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制 + +--- #################### 消息队列相关 #################### + +# rocketmq 配置项,对应 RocketMQProperties 配置类 +rocketmq: + # Producer 配置项 + producer: + group: ${spring.application.name}_PRODUCER # 生产者分组 + +spring: + kafka: + producer: + acks: 1 + retries: 3 + batch-size: 16384 + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + properties: + linger.ms: 10 + buffer.memory: 33554432 + consumer: + enable-auto-commit: false + auto-offset-reset: earliest + max-poll-records: 1000 + fetch-max-wait: 3000 + + # 【修正3】改为 StringDeserializer + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + group-id: consumer-${spring.application.name} + + listener: + missing-topics-fatal: false + +--- #################### 芋道相关配置 #################### + +yudao: + info: + version: 1.0.0 + base-package: cn.iocoder.yudao + web: + admin-ui: + url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址 + xss: + enable: false + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 + security: + permit-all_urls: + - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + websocket: + enable: true # websocket的开关 + path: /infra/ws # 路径 + sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq + sender-rocketmq: + topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic + consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group + sender-rabbitmq: + exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange + queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue + sender-kafka: + topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic + consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group + swagger: + title: 芋道快速开发平台 + description: 提供管理后台、用户 App 的所有功能 + version: ${yudao.info.version} + url: ${yudao.web.admin-ui.url} + email: xingyu4j@vip.qq.com + license: MIT + license-url: https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE + codegen: + base-package: ${yudao.info.base-package} + db-schemas: ${spring.datasource.dynamic.datasource.master.name} + front-type: 20 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类 + vo-type: 10 # VO 的类型,参见 CodegenVOTypeEnum 枚举类 + delete-batch-enable: true # 是否生成批量删除接口 + unit-test-enable: false # 是否生成单元测试 + tenant: # 多租户相关配置项 + enable: true + ignore-urls: + - /jmreport/* # 积木报表,无法携带租户编号 + ignore-visit-urls: + - /admin-api/system/user/profile/** + - /admin-api/system/auth/** + ignore-tables: + ignore-caches: + - user_role_ids + - permission_menu_ids + - oauth_client + - notify_template + - mail_account + - mail_template + - sms_template + - iot:device + - iot:thing_model_list + sms-code: # 短信验证码相关的配置项 + expire-times: 10m + send-frequency: 1m + send-maximum-quantity-per-day: 10 + begin-code: 9999 # 这里配置 9999 的原因是,测试方便。 + end-code: 9999 # 这里配置 9999 的原因是,测试方便。 + trade: + order: + pay-expire-time: 2h # 支付的过期时间 + receive-expire-time: 14d # 收货的过期时间 + comment-expire-time: 7d # 评论的过期时间 + status-sync-to-wxa-enable: true # 是否同步订单状态到微信小程序 + express: + client: kd_100 + kd-niao: + api-key: cb022f1e-48f1-4c4a-a723-9001ac9676b8 + business-id: 1809751 + request-type: 1002 # 免费版 1002;付费版 8001 + kd100: + key: pLXUGAwK5305 + customer: E77DF18BE109F454A5CD319E44BF5177 + +debug: false +# 插件配置 TODO 芋艿:【IOT】需要处理下 +pf4j: + pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录 + +server: + port: 48081 + +--- #################### 数据库相关配置 #################### +spring: + autoconfigure: + # noinspection SpringBootApplicationYaml + exclude: + # - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境,不开启 Quartz 的自动配置 + # - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置 + # - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置 + # - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置 + - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建 + - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建 + # 数据源配置项 + datasource: + druid: # Druid 【监控】相关的全局配置 + web-stat-filter: + enabled: true + stat-view-servlet: + enabled: true + allow: # 设置白名单,不填则允许所有访问 + url-pattern: /druid/* + login-username: # 控制台管理用户名和密码 + login-password: + filter: + stat: + enabled: true + log-slow-sql: true # 慢 SQL 记录 + slow-sql-millis: 100 + merge-sql: true + wall: + config: + multi-statement-allow: true + dynamic: # 多数据源配置 + druid: # Druid 【连接池】相关的全局配置 + initial-size: 1 # 初始连接数 + min-idle: 1 # 最小连接池数量 + max-active: 20 # 最大连接池数量 + max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒 + time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒 + min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒 + max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒 + validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效 + test-while-idle: true + test-on-borrow: false + test-on-return: false + primary: master + datasource: + master: + url: jdbc:mysql://127.0.0.1:13307/hand_alarm_dev?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + username: root + password: Gsking164411 + driver-class-name: com.mysql.cj.jdbc.Driver # MySQL Connector/J 8.X 连接的示例 + tdengine: + url: jdbc:TAOS-RS://127.0.0.1:6042/hand_alarm + username: root + password: Gsking164411 + driver-class-name: com.taosdata.jdbc.rs.RestfulDriver # TDengine 连接的示例 + druid: + validation-query: SELECT 1 # TDengine 的验证 SQL + + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 + data: + redis: + host: 127.0.0.1 # 地址 + port: 6379 # 端口 + database: 5 # 数据库索引 + # password: dev # 密码,建议生产环境开启 + +--- #################### 定时任务相关配置 #################### + +# Quartz 配置项,对应 QuartzProperties 配置类 +spring: + quartz: + auto-startup: true # 本地开发环境,尽量不要开启 Job + scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName + job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存,可选 jdbc 使用数据库。 + wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时,是否等待定时任务执行完成。默认为 false ,建议设置为 true + properties: # 添加 Quartz Scheduler 附加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档 + org: + quartz: + # Scheduler 相关配置 + scheduler: + instanceName: schedulerName + instanceId: AUTO # 自动生成 instance ID + # JobStore 相关配置 + jobStore: + # JobStore 实现类。可见博客:https://blog.csdn.net/weixin_42458219/article/details/122247162 + class: org.springframework.scheduling.quartz.LocalDataSourceJobStore + isClustered: true # 是集群模式 + clusterCheckinInterval: 15000 # 集群检查频率,单位:毫秒。默认为 15000,即 15 秒 + misfireThreshold: 60000 # misfire 阀值,单位:毫秒。 + # 线程池相关配置 + threadPool: + threadCount: 25 # 线程池大小。默认为 10 。 + threadPriority: 5 # 线程优先级 + class: org.quartz.simpl.SimpleThreadPool # 线程池类型 + jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置 + initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。 + +--- #################### 消息队列相关 #################### + +# rocketmq 配置项,对应 RocketMQProperties 配置类 +rocketmq: + name-server: 127.0.0.1:9876 # RocketMQ Namesrv + +spring: + # RabbitMQ 配置项,对应 RabbitProperties 配置类 + rabbitmq: + host: 127.0.0.1 # RabbitMQ 服务的地址 + port: 5672 # RabbitMQ 服务的端口 + username: rabbit # RabbitMQ 服务的账号 + password: rabbit # RabbitMQ 服务的密码 + # Kafka 配置项,对应 KafkaProperties 配置类 + kafka: + bootstrap-servers: video.zdhlcn.com:9092 # 或者内网地址 172.21.16.6:9091,或者测试地址video.zdhlcn.com:9092,zdmq.zdhlcn.com:9092 + properties: + security.protocol: SASL_PLAINTEXT + sasl.mechanism: SCRAM-SHA-512 + sasl.jaas.config: 'org.apache.kafka.common.security.scram.ScramLoginModule required username="zdkafka" password="Zdhl@2025";' +##################### 服务保障相关配置 #################### + +# Lock4j 配置项 +lock4j: + acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒 + expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒 + +--- #################### 监控相关配置 #################### + +# Actuator 监控端点的配置项 +management: + endpoints: + web: + base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator + exposure: + include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。 + +# Spring Boot Admin 配置项 +spring: + boot: + admin: + # Spring Boot Admin Client 客户端的相关配置 + client: + url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址 + instance: + service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME] + # Spring Boot Admin Server 服务端的相关配置 + context-path: /admin # 配置 Spring + +# 日志文件配置 +logging: + file: + name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径 + level: + # 配置自己写的 MyBatis Mapper 打印日志 + cn.iocoder.yudao.module.bpm.dal.mysql: debug + cn.iocoder.yudao.module.infra.dal.mysql: debug + cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 配置 ApiErrorLogMapper 的日志级别为 info,避免和 GlobalExceptionHandler 重复打印 + cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info + cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info + cn.iocoder.yudao.module.pay.dal.mysql: debug + cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 PayNotifyTaskMapper 的日志级别为 info + cn.iocoder.yudao.module.system.dal.mysql: debug + cn.iocoder.yudao.module.mqtt: info + org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 芋艿:先禁用,Spring Boot 3.X 存在部分错误的 WARN 提示 + # 打开 dynamic-datasource 框架的 DEBUG 日志 + #com.baomidou.dynamic.datasource: DEBUG +debug: false + +--- #################### 微信公众号、小程序相关配置 #################### +wx: + mp: # 公众号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档 + app-id: wxf56b1542b9e85f8a # 测试号(Kongdy 提供的) + secret: 496379dcef1ba869e9234de8d598cfd3 + # 存储配置,解决 AccessToken 的跨节点的共享 + cp: + # 你的企业ID + corpId: ww6e1eee0a8ae45397 + agentId: 1000002 + corpSecret: ITbfuoZkmUifGoDL5ZB8SyuMzVM8VXZNkfZJzYn5sGo + + config-storage: + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 + key-prefix: wx # Redis Key 的前缀 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 + miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档 + appid: wxc4598c446f8a9cb3 # 测试号(Kongdy 提供的) + secret: 4a1a04e07f6a4a0751b39c3064a92c8b + config-storage: + type: RedisTemplate # 采用 RedisTemplate 操作 Redis,会自动从 Spring 中获取 + key-prefix: wa # Redis Key 的前缀 + http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台 + +--- #################### 芋道相关配置 #################### + +# 芋道配置项,设置当前项目所有自定义的配置 +yudao: + captcha: + enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试; + security: + mock-enable: true + access-log: # 访问日志的配置项 + enable: false + demo: false # 关闭演示模式 + wxa-code: + env-version: develop # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop" + wxa-subscribe-message: + miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”;体验版为 “trial”为;正式版为 “formal” + tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc + + + cache: + type: REDIS + prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE:: + timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 + +--- #################### iot相关配置 TODO 芋艿【IOT】:再瞅瞅 #################### +pf4j: + # pluginsDir: /tmp/ + pluginsDir: ../plugins + +mqtt: + enable: true + url: tcp://127.0.0.1:1883 + username: root + password: roomasd111 + client: + id: cc-admin-qg-dev + connectionTimeout: 10 + keepAliveInterval: 60 + cleanSession: true + subscribe: + # $share/hand_alarm/+/zds_up //MQTT 共享订阅,不是多台服务器的情况下不要开启 + topic: +/zds_up,+/zds_down + qos: 1,1 + default: + publishQos: 0 + offlineTime: 180 # 超过 180 秒无数据则判为数据超时 + pool: + coreSize: 10 + maxSize: 20 + queueSize: 100 + alarm: + rate-limit: 3000.0 # 报警 MQTT 推送限流速率(每秒最大请求数) + + +interphone: + username: zdhladmin + password: 123456a. + apiUrl: https://chat.zdhlcn.com:9443/api/v1/ + userType: 0 + appId: 97796fef376d41e0a7dda02720d0e3c9 + appSecret: m597gv5h4hmdusce + + http: + connect-timeout-seconds: 5 + read-timeout-seconds: 10 + write-timeout-seconds: 10 + call-timeout-seconds: 15 + max-idle-connections: 50 + keep-alive-duration-minutes: 5 + retry: + max-retries: 2 \ No newline at end of file diff --git a/cc-admin-master/yudao-server/pom.xml b/cc-admin-master/yudao-server/pom.xml index 37302f3..2defb97 100644 --- a/cc-admin-master/yudao-server/pom.xml +++ b/cc-admin-master/yudao-server/pom.xml @@ -56,6 +56,13 @@ yudao-module-hand-mqtt ${revision} + + + cn.iocoder.boot + yudao-module-interphone + ${revision} + + - - - - - - - -       - - - ${PATTERN_DEFAULT} - - - - - - - - - - ${PATTERN_DEFAULT} - - - - ${LOG_FILE} - - - ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} - - ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} - - ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} - - ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} - - ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30} - - - - - - 0 - - 256 - - - - - - - - ${PATTERN_DEFAULT} - - - - - - - - - - - - - - - - - - - - - -