Browse Source

初始化okhttp和test

master
fbw 1 week ago
parent
commit
55455af7db
  1. 7
      cc-admin-master/pom.xml
  2. 56
      cc-admin-master/yudao-module-interphone/pom.xml
  3. 10
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/Author.java
  4. 97
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultLoginService.java
  5. 57
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/DefaultTokenManager.java
  6. 6
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/LoginService.java
  7. 11
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/auth/TokenManager.java
  8. 89
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/HttpClientProperties.java
  9. 36
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/config/OkHttpClientConfig.java
  10. 116
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiClient.java
  11. 91
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiRequest.java
  12. 37
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/ApiResponse.java
  13. 5
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/HttpMethod.java
  14. 53
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/core/QueryStringBuilder.java
  15. 28
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClient.java
  16. 37
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryContext.java
  17. 35
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryDecision.java
  18. 6
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/RetryHandler.java
  19. 39
      cc-admin-master/yudao-module-interphone/src/main/java/cn/iocoder/yudao/module/interphone/retry/TokenExpiredRetryHandler.java
  20. 42
      cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/DemoRemoteClientTest.java
  21. 16
      cc-admin-master/yudao-module-interphone/src/test/java/cn/iocoder/yudao/module/interphone/demo/TestConfig.java
  22. 504
      cc-admin-master/yudao-module-interphone/src/test/resources/application-unit-test.yaml
  23. 7
      cc-admin-master/yudao-server/pom.xml
  24. 8
      cc-admin-master/yudao-server/src/main/resources/application-dev.yaml
  25. 264
      cc-admin-master/yudao-server/src/main/resources/application-localfbw.yaml
  26. 2
      cc-admin-master/yudao-server/src/main/resources/application.yaml
  27. 76
      cc-admin-master/yudao-server/src/main/resources/logback-spring.xml

7
cc-admin-master/pom.xml

@ -15,10 +15,11 @@
<!-- 各种 module 拓展 -->
<module>yudao-module-system</module>
<module>yudao-module-infra</module>
<module>yudao-module-bpm</module>
<module>yudao-module-report</module>
<module>yudao-module-hand</module>
<module>yudao-module-bpm</module>
<module>yudao-module-report</module>
<module>yudao-module-hand</module>
<module>yudao-module-hand-mqtt</module>
<module>yudao-module-interphone</module>
</modules>
<name>${project.artifactId}</name>

56
cc-admin-master/yudao-module-interphone/pom.xml

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>cc-admin-master</artifactId>
<version>${revision}</version>
</parent>
<artifactId>yudao-module-interphone</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器,使用它解析表结构 -->
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!--外部请求-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

10
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;
}

97
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);
}
}
}

57
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();
}
}
}

6
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();
}

11
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();
}

89
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;
}
}
}

36
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();
}
}

116
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<RetryHandler> retryHandlers;
public ApiClient(OkHttpClient okHttpClient,
HttpClientProperties properties,
TokenManager tokenManager,
List<RetryHandler> 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<String, String> 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();
}
}

91
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<String, String> queryParams = new HashMap<>();
private Map<String, String> 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<String, String> getQueryParams() {
return queryParams;
}
public void setQueryParams(Map<String, String> queryParams) {
this.queryParams = queryParams;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> 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<String, String> 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;
}
}

37
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<String, List<String>> headers;
public ApiResponse(int code, String body, Map<String, List<String>> headers) {
this.code = code;
this.body = body;
this.headers = headers;
}
public int getCode() {
return code;
}
public String getBody() {
return body;
}
public Map<String, List<String>> getHeaders() {
return headers;
}
public boolean isSuccess() {
return code >= 200 && code < 300;
}
}

5
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
}

53
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<String, String> 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<String, String> 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);
}
}
}

28
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();
}
}

37
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;
}
}

35
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;
}
}

6
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);
}

39
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();
}
}

42
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);
}
}

16
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);
}
}

504
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

7
cc-admin-master/yudao-server/pom.xml

@ -56,6 +56,13 @@
<artifactId>yudao-module-hand-mqtt</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-interphone</artifactId>
<version>${revision}</version>
</dependency>
<!-- &lt;!&ndash; IoT 物联网相关模块。默认注释,保证编译速度 &ndash;&gt;
<dependency>
<groupId>cn.iocoder.boot</groupId>

8
cc-admin-master/yudao-server/src/main/resources/application-dev.yaml

@ -242,4 +242,10 @@ mqtt:
maxSize: 20
queueSize: 100
alarm:
rate-limit: 3000.0 # 报警 MQTT 推送限流速率(每秒最大请求数)
rate-limit: 3000.0 # 报警 MQTT 推送限流速率(每秒最大请求数)
interphone:
username: zdhladmin
password: 123456a.
apiUrl: https://chat.zdhlcn.com:9443/api/v1/

264
cc-admin-master/yudao-server/src/main/resources/application-localfbw.yaml

@ -0,0 +1,264 @@
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

2
cc-admin-master/yudao-server/src/main/resources/application.yaml

@ -2,7 +2,7 @@ spring:
application:
name: gas_mobile
profiles:
active: dev
active: localfbw
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。

76
cc-admin-master/yudao-server/src/main/resources/logback-spring.xml

@ -1,76 +0,0 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 变量 yudao.info.base-package,基础业务包 -->
<springProperty scope="context" name="yudao.info.base-package" source="yudao.info.base-package"/>
<!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level:级别从左显示 5 个字符宽度,%msg:日志消息,%n是换行符 -->
<property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- 控制台 Appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 文件 Appender -->
<!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
<!-- 日志文件名 -->
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动后的日志文件名 -->
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<!-- 日志文件,到达多少容量,进行滚动 -->
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<!-- 日志文件的总大小,0 表示不限制 -->
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<!-- 日志文件的保留天数 -->
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步写入日志,提升性能 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
<queueSize>256</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- SkyWalking GRPC 日志收集,实现日志中心。注意:SkyWalking 8.4.0 版本开始支持 -->
<appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 本地环境 -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
</root>
</springProfile>
<!-- 其它环境 -->
<springProfile name="dev,test,stage,prod,default">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="GRPC"/>
</root>
</springProfile>
</configuration>
Loading…
Cancel
Save