修复启动

This commit is contained in:
ZuoZuo 2026-04-13 14:47:50 +08:00
parent 0e74f899f0
commit 3493a78341
35 changed files with 1360 additions and 263 deletions

View File

@ -25,22 +25,22 @@ import com.red.circle.other.inner.model.dto.sys.SysCountryCodeDTO;
import com.red.circle.other.inner.model.dto.user.account.UserAccountDTO;
import com.red.circle.tool.core.text.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@ -55,13 +55,13 @@ import static com.red.circle.tool.core.text.StringPool.SYMBOL_COMMA;
* @eo.groupName 认证服务
* @eo.path /auth
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class EndpointRestController {
private final ProcessToken processToken;
private final AppUserAccountClient appUserAccountClient;
@RestController
public class EndpointRestController {
private static final Logger log = LoggerFactory.getLogger(EndpointRestController.class);
private final ProcessToken processToken;
private final AppUserAccountClient appUserAccountClient;
private final RedCircleCredentialService redCircleCredentialService;
private final SysCountryCodeClient sysCountryCodeService;
private final EnumConfigClient enumConfigClient;
@ -70,8 +70,22 @@ public class EndpointRestController {
private final String APPCODE_CONFIG = "APPCODE ******";
private final String REQUEST_URL = "https://c2ba.api.huachen.cn/ip";
private final RedisService redisService;
private final RedisService redisService;
public EndpointRestController(ProcessToken processToken,
AppUserAccountClient appUserAccountClient,
RedCircleCredentialService redCircleCredentialService,
SysCountryCodeClient sysCountryCodeService,
EnumConfigClient enumConfigClient,
RedisService redisService) {
this.processToken = processToken;
this.appUserAccountClient = appUserAccountClient;
this.redCircleCredentialService = redCircleCredentialService;
this.sysCountryCodeService = sysCountryCodeService;
this.enumConfigClient = enumConfigClient;
this.redisService = redisService;
}
/**
* 检查token.
@ -300,23 +314,25 @@ public class EndpointRestController {
String result = EntityUtils.toString(response.getEntity());
log.info("请求地址:{},返回信息:{}", REQUEST_URL + "?ip=" + ip, result);
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.getInteger("ret").equals(200)) {
dataObject = jsonObject.getJSONObject("data");
redisService.setString(redisKey, dataObject.toJSONString());
}
} catch (Exception e) {
log.error("请求地址:{}, 异常信息:{}", REQUEST_URL + "?ip=" + ip, e.getMessage());
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject != null && Integer.valueOf(200).equals(jsonObject.getInteger("ret"))) {
dataObject = jsonObject.getJSONObject("data");
if (dataObject != null) {
redisService.setString(redisKey, dataObject.toJSONString());
}
}
} catch (Exception e) {
log.error("请求地址:{}, 异常信息:{}", REQUEST_URL + "?ip=" + ip, e.getMessage());
}
}
log.info("IP:{}, 国家信息:{}", ip, dataObject);
if (dataObject != null && "中国".equals(dataObject.getString("country"))) {
if (!Arrays.asList("香港", "澳门", "台湾").contains(dataObject.getString("region"))) {
ResponseAssert.isTrue(UserErrorCode.REGISTRATION_FAILED, false);
}
}
return dataObject.getString("country_id");
}
if (dataObject != null && "中国".equals(dataObject.getString("country"))) {
if (!Arrays.asList("香港", "澳门", "台湾").contains(dataObject.getString("region"))) {
ResponseAssert.isTrue(UserErrorCode.REGISTRATION_FAILED, false);
}
}
return dataObject != null ? dataObject.getString("country_id") : null;
}
public void checkZone(String reqZone) {
log.info("reqZone:{}", reqZone);

View File

@ -20,14 +20,14 @@ import com.red.circle.tool.core.text.StringPool;
import com.red.circle.tool.core.text.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
@ -45,50 +45,60 @@ import reactor.core.publisher.Mono;
*
* @author pengliang on 2023/4/18
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private static final Logger log = LoggerFactory.getLogger(AuthGlobalFilter.class);
private static final String ATYOU = "ATYOU";
private static final String LIKEI = "LIKEI";
private final ObjectMapper objectMapper;
private final GatewayProperties gatewayProperties;
private final AuthEndpointService authEndpointService;
private final PathMatcher pathMatcher = new AntPathMatcher();
private final ResultResponse<UserCredential> reqFailUserCredential = ResultResponse.failure(503,
"Request timed out",
"REQ_FAIL");
public AuthGlobalFilter(ObjectMapper objectMapper, GatewayProperties gatewayProperties,
AuthEndpointService authEndpointService) {
this.objectMapper = objectMapper;
this.gatewayProperties = gatewayProperties;
this.authEndpointService = authEndpointService;
}
private final ObjectMapper objectMapper;
private final GatewayProperties gatewayProperties;
private final AuthEndpointService authEndpointService;
private final PathMatcher pathMatcher = new AntPathMatcher();
private final ResultResponse<UserCredential> reqFailUserCredential = ResultResponse.failure(503,
"Request timed out",
"REQ_FAIL");
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long nowTime = DateUtils.now().getTime();
if (isIgnoreMatcherPath(exchange)) {
return returnDefaultExchange(exchange, chain, nowTime);
}
HttpHeaders reqHttpHeaders = exchange.getRequest().getHeaders();
ResponseAssert.notEmpty(FrameworkWebErrorCode.REQ_SYS_ORIGIN, reqHttpHeaders);
String authorization = getAuthorization(exchange);
if (checkAuthorizationNotPass(authorization)) {
return responseUnauthorized401(exchange);
}
checkRequestRequiredHeader(exchange);
exchange.getAttributes().put(ApiConstant.REQ_URL, ApiConstant.AUTH_SERVICE_API);
return authEndpointService.getTokenCredential(authorization)
.defaultIfEmpty(reqFailUserCredential)
.flatMap(response -> response.optionalBody().map(userCredential -> {
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> addHeaders(httpHeaders,
createReqInternal(nowTime, userCredential))).build();
exchange.getAttributes().put(ApiConstant.REQ_URL, request.getURI().getRawPath());
return chain.filter(exchange.mutate().request(request).build());
}
).orElseGet(() -> responseUnauthorized401(exchange))
);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
long nowTime = DateUtils.now().getTime();
ServerWebExchange normalizedExchange = normalizeReqSysOrigin(exchange);
if (isIgnoreMatcherPath(normalizedExchange)) {
return returnDefaultExchange(normalizedExchange, chain, nowTime);
}
HttpHeaders reqHttpHeaders = normalizedExchange.getRequest().getHeaders();
ResponseAssert.notEmpty(FrameworkWebErrorCode.REQ_SYS_ORIGIN, reqHttpHeaders);
String authorization = getAuthorization(normalizedExchange);
if (checkAuthorizationNotPass(authorization)) {
return responseUnauthorized401(normalizedExchange);
}
checkRequestRequiredHeader(normalizedExchange);
normalizedExchange.getAttributes().put(ApiConstant.REQ_URL, ApiConstant.AUTH_SERVICE_API);
return authEndpointService.getTokenCredential(authorization)
.defaultIfEmpty(reqFailUserCredential)
.flatMap(response -> response.optionalBody().map(userCredential -> {
ServerHttpRequest request = normalizedExchange.getRequest().mutate()
.headers(httpHeaders -> addHeaders(httpHeaders,
createReqInternal(nowTime, userCredential))).build();
normalizedExchange.getAttributes()
.put(ApiConstant.REQ_URL, request.getURI().getRawPath());
return chain.filter(normalizedExchange.mutate().request(request).build());
}
).orElseGet(() -> responseUnauthorized401(normalizedExchange))
);
}
private boolean checkAuthorizationNotPass(String authorization) {
try {
@ -167,9 +177,42 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
return reqInternal;
}
private void addHeaders(HttpHeaders httpHeaders, ReqInternal reqInternal) {
httpHeaders.add(RequestHeaderConstant.REQ_INNER_INTERNAL, reqInternal.toTransferVal());
}
private void addHeaders(HttpHeaders httpHeaders, ReqInternal reqInternal) {
httpHeaders.add(RequestHeaderConstant.REQ_INNER_INTERNAL, reqInternal.toTransferVal());
}
private ServerWebExchange normalizeReqSysOrigin(ServerWebExchange exchange) {
String rawReqSysOrigin = getHeaderFirstNotBankVal(exchange, RequestHeaderConstant.REQ_SYS_ORIGIN);
if (StringUtils.isBlank(rawReqSysOrigin)) {
return exchange;
}
ReqSysOrigin reqSysOrigin = ReqSysOrigin.parse(rawReqSysOrigin);
if (Objects.isNull(reqSysOrigin)) {
return exchange;
}
boolean changed = false;
if (Objects.equals(ATYOU, reqSysOrigin.getOrigin())) {
reqSysOrigin.setOrigin(LIKEI);
changed = true;
}
if (Objects.equals(ATYOU, reqSysOrigin.getOriginChild())) {
reqSysOrigin.setOriginChild(LIKEI);
changed = true;
}
if (!changed) {
return exchange;
}
String normalizedReqSysOrigin = "origin=" + reqSysOrigin.getOrigin()
+ ";originChild=" + reqSysOrigin.getOriginChild();
ServerHttpRequest request = exchange.getRequest().mutate()
.headers(httpHeaders -> httpHeaders.set(RequestHeaderConstant.REQ_SYS_ORIGIN,
normalizedReqSysOrigin))
.build();
return exchange.mutate().request(request).build();
}
@Override

View File

@ -45,8 +45,8 @@ logging:
---
spring:
cloud:
gateway:
httpclient:
response-timeout: PT30S
pool:
max-idle-time: 5000
gateway:
httpclient:
response-timeout: PT120S
pool:
max-idle-time: 5000

View File

@ -1,31 +1,94 @@
package com.red.circle.console.adapter.app.party3rd;//package com.sugartime.app.server.api.app.external;
import com.red.circle.external.inner.endpoint.oss.OssServiceClient;
import com.red.circle.framework.core.asserts.ResponseAssert;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 阿里云服务.
*/
package com.red.circle.console.adapter.app.party3rd;//package com.sugartime.app.server.api.app.external;
import com.red.circle.external.inner.endpoint.oss.OssServiceClient;
import com.red.circle.framework.core.asserts.ResponseAssert;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
/**
* 阿里云服务.
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/ali-yun", produces = MediaType.APPLICATION_JSON_VALUE)
public class AliYunRestController {
private final OssServiceClient ossServiceClient;
/**
* 阿里云sts.
*/
@GetMapping("/oss/sts")
public Object sts() {
return ResponseAssert.requiredSuccess(ossServiceClient.sts());
}
}
public class AliYunRestController {
private final OssServiceClient ossServiceClient;
@Value("${feign.external.url}")
private String externalBaseUrl;
/**
* 阿里云sts.
*/
@GetMapping("/oss/sts")
public Object sts() {
return ResponseAssert.requiredSuccess(ossServiceClient.sts());
}
@PostMapping(value = "/oss/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Map<String, String> upload(
@RequestParam("file") MultipartFile file,
@RequestParam(value = "dir", required = false) String dir,
@RequestParam(value = "filename", required = false) String filename) {
try {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpHeaders fileHeaders = new HttpHeaders();
if (file.getContentType() != null) {
fileHeaders.setContentType(MediaType.parseMediaType(file.getContentType()));
}
ByteArrayResource resource = new ByteArrayResource(file.getBytes()) {
@Override
public String getFilename() {
return Objects.toString(file.getOriginalFilename(), "upload.bin");
}
};
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", new HttpEntity<>(resource, fileHeaders));
String uploadUrl = externalBaseUrl.replaceAll("/+$", "")
+ "/oss/upload/general?dir=" + encode(dir)
+ "&filename=" + encode(filename);
ResponseEntity<Map> response = new RestTemplate().postForEntity(
uploadUrl,
new HttpEntity<>(body, requestHeaders),
Map.class);
Map<?, ?> responseBody = response.getBody();
if (responseBody == null || !Boolean.TRUE.equals(responseBody.get("status"))) {
String errorMsg = responseBody == null ? "upload failed"
: Objects.toString(responseBody.get("errorMsg"), "upload failed");
throw new IllegalStateException(errorMsg);
}
return Map.of("name", Objects.toString(responseBody.get("body"), ""));
} catch (Exception e) {
log.error("console upload proxy failed dir={} filename={}", dir, filename, e);
throw new IllegalStateException("upload failed", e);
}
}
private String encode(String value) {
return java.net.URLEncoder.encode(Objects.toString(value, ""), java.nio.charset.StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,49 @@
package com.red.circle.external.adapter.app.oss;
import com.red.circle.external.app.oss.LocalDiskStorageService;
import com.red.circle.framework.web.annotation.IgnoreResultResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.CacheControl;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.HandlerMapping;
import java.time.Duration;
/**
* 开发环境本地文件读取接口.
*/
@RestController
@RequestMapping("/oss/local")
public class LocalDiskFileRestController {
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final LocalDiskStorageService localDiskStorageService;
public LocalDiskFileRestController(LocalDiskStorageService localDiskStorageService) {
this.localDiskStorageService = localDiskStorageService;
}
@IgnoreResultResponse
@GetMapping("/**")
public ResponseEntity<?> file(HttpServletRequest request) {
LocalDiskStorageService.StoredFile file = localDiskStorageService.load(resolveKey(request));
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(file.contentType()))
.cacheControl(CacheControl.maxAge(Duration.ofDays(7)).cachePublic())
.contentLength(file.contentLength())
.body(file.resource());
}
private String resolveKey(HttpServletRequest request) {
String pathWithinMapping =
(String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String bestMatchPattern =
(String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
return pathMatcher.extractPathWithinPattern(bestMatchPattern, pathWithinMapping);
}
}

View File

@ -1,37 +1,52 @@
package com.red.circle.external.adapter.app.oss;
import com.red.circle.component.oss.OssService;
import com.red.circle.component.oss.aliyun.service.AliYunStsResponse;
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
import com.red.circle.external.response.OssErrorCode;
import com.red.circle.external.utils.FileUtils;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.framework.web.annotation.IgnoreResultResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
import com.red.circle.component.oss.OssService;
import com.red.circle.component.oss.aliyun.service.AliYunStsResponse;
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
import com.red.circle.external.app.oss.LocalDiskStorageService;
import com.red.circle.external.app.oss.TencentCosStorageService;
import com.red.circle.external.response.OssErrorCode;
import com.red.circle.external.utils.FileUtils;
import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.framework.web.annotation.IgnoreResultResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.UUID;
/**
* 阿里云,OSS存储.
*
* @author pengliang on 2020/9/7
* @eo.api-type http
* @eo.groupName 第三方服务.OSS存储
* @eo.path /external/oss
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/oss", produces = MediaType.APPLICATION_JSON_VALUE)
public class OssRestController {
private final AliYunStsService aliYunStsService;
private final OssService ossService;
* @eo.groupName 第三方服务.OSS存储
* @eo.path /external/oss
*/
@RestController
@RequestMapping(value = "/oss", produces = MediaType.APPLICATION_JSON_VALUE)
public class OssRestController {
private static final Logger log = LoggerFactory.getLogger(OssRestController.class);
private final AliYunStsService aliYunStsService;
private final OssService ossService;
private final LocalDiskStorageService localDiskStorageService;
private final TencentCosStorageService tencentCosStorageService;
public OssRestController(
AliYunStsService aliYunStsService,
OssService ossService,
LocalDiskStorageService localDiskStorageService,
TencentCosStorageService tencentCosStorageService) {
this.aliYunStsService = aliYunStsService;
this.ossService = ossService;
this.localDiskStorageService = localDiskStorageService;
this.tencentCosStorageService = tencentCosStorageService;
}
/**
* sts临时令牌.
@ -55,8 +70,8 @@ public class OssRestController {
* @eo.method Post
*/
@IgnoreResultResponse
@PostMapping("/upload")
public ResultResponse<String> upload(@RequestPart("file") MultipartFile file) {
@PostMapping("/upload")
public ResultResponse<String> upload(@RequestPart("file") MultipartFile file) {
try {
if (file.isEmpty()) {
return ResultResponse.failure(OssErrorCode.FILE_IS_NULL);
@ -69,17 +84,115 @@ public class OssRestController {
return ResultResponse.failure(OssErrorCode.FILE_TYPE_NOT_SUPPORTED);
}
log.warn(file.getOriginalFilename());
log.warn(file.getName());
log.warn(file.getOriginalFilename());
String fileName = "avatar/" +UUID.randomUUID() + FileUtils.getSuffix(file.getOriginalFilename());;
ossService.uploadInputStream(file.getInputStream(), fileName);
return ResultResponse.success(ossService.getAccessUrl(fileName));
log.warn(file.getName());
log.warn(file.getOriginalFilename());
String suffix = FileUtils.getSuffix(file.getOriginalFilename());
if (localDiskStorageService.isEnabled()) {
return ResultResponse.success(
localDiskStorageService.uploadAvatar(
file.getInputStream(),
file.getContentType(),
suffix));
}
if (tencentCosStorageService.isEnabled()) {
return ResultResponse.success(
tencentCosStorageService.uploadAvatar(
file.getInputStream(),
file.getSize(),
file.getContentType(),
suffix));
}
String fileName = "avatar/" + UUID.randomUUID() + suffix;
ossService.uploadInputStream(file.getInputStream(), fileName);
return ResultResponse.success(ossService.getAccessUrl(fileName));
} catch (Exception e) {
log.error(e.getMessage(), e);
return ResultResponse.failure(OssErrorCode.NETWORK_ANOMALY);
}
}
}
}
}
@IgnoreResultResponse
@PostMapping("/upload/general")
public ResultResponse<String> uploadGeneral(
@RequestPart("file") MultipartFile file,
@RequestParam(value = "dir", required = false) String dir,
@RequestParam(value = "filename", required = false) String filename) {
try {
if (file.isEmpty()) {
return ResultResponse.failure(OssErrorCode.FILE_IS_NULL);
}
if (file.getSize() > 10L * 1024 * 1024) {
return ResultResponse.failure(OssErrorCode.FILE_SIZE_GREATER_THAN_10M);
}
String storageKey = buildStorageKey(dir, filename, file.getOriginalFilename());
if (localDiskStorageService.isEnabled()) {
return ResultResponse.success(localDiskStorageService.upload(storageKey, file.getInputStream()));
}
if (tencentCosStorageService.isEnabled()) {
tencentCosStorageService.upload(
storageKey,
file.getInputStream(),
file.getSize(),
file.getContentType());
return ResultResponse.success(tencentCosStorageService.getAccessUrl(storageKey));
}
ossService.uploadInputStream(file.getInputStream(), storageKey);
return ResultResponse.success(ossService.getAccessUrl(storageKey));
} catch (Exception e) {
log.error("generic upload failed dir={} filename={}", dir, filename, e);
return ResultResponse.failure(OssErrorCode.NETWORK_ANOMALY);
}
}
private String buildStorageKey(String dir, String filename, String originalFilename) {
String normalizedDir = normalizeDirectory(dir);
String targetFilename = buildFilename(filename, originalFilename);
return StringUtils.hasText(normalizedDir) ? normalizedDir + "/" + targetFilename : targetFilename;
}
private String normalizeDirectory(String dir) {
if (!StringUtils.hasText(dir)) {
return "other";
}
String normalized = dir.trim().replace("\\", "/");
while (normalized.startsWith("/")) {
normalized = normalized.substring(1);
}
while (normalized.endsWith("/")) {
normalized = normalized.substring(0, normalized.length() - 1);
}
if (normalized.contains("..")) {
throw new IllegalArgumentException("invalid upload dir");
}
return normalized;
}
private String buildFilename(String filename, String originalFilename) {
String suffix = extractSuffix(originalFilename);
String target = StringUtils.hasText(filename) ? sanitizeFilename(filename.trim()) : UUID.randomUUID() + suffix;
if (!target.contains(".") && StringUtils.hasText(suffix)) {
target = target + suffix;
}
return target;
}
private String sanitizeFilename(String filename) {
String normalized = filename.replace("\\", "/");
int index = normalized.lastIndexOf('/');
String target = index >= 0 ? normalized.substring(index + 1) : normalized;
if (target.contains("..")) {
throw new IllegalArgumentException("invalid upload filename");
}
return target;
}
private String extractSuffix(String originalFilename) {
if (!StringUtils.hasText(originalFilename) || !originalFilename.contains(".")) {
return "";
}
return FileUtils.getSuffix(originalFilename);
}
}

View File

@ -7,18 +7,24 @@
<description>应用层,服务实现</description>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.red.circle</groupId>
<artifactId>external-client</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>com.red.circle</groupId>
<artifactId>external-client</artifactId>
</dependency>
<dependency>
<groupId>com.red.circle</groupId>
<artifactId>external-infrastructure</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>com.red.circle</groupId>
<artifactId>external-infrastructure</artifactId>
</dependency>
<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.265</version>
</dependency>
</dependencies>
<parent>
<artifactId>rc-service-external</artifactId>

View File

@ -0,0 +1,47 @@
package com.red.circle.external.app.oss;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 本地磁盘存储配置.
*/
@Component
@ConfigurationProperties(prefix = "red-circle.oss.local-disk")
public class LocalDiskStorageProperties {
private boolean enabled;
private String basePath = "/application/uploads";
private String accessUrl = "http://10.0.2.2:3000/oss/local";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBasePath() {
return basePath;
}
public void setBasePath(String basePath) {
this.basePath = basePath;
}
public String getAccessUrl() {
return accessUrl;
}
public void setAccessUrl(String accessUrl) {
this.accessUrl = accessUrl;
}
public boolean isConfigured() {
return enabled
&& StringUtils.hasText(basePath)
&& StringUtils.hasText(accessUrl);
}
}

View File

@ -0,0 +1,204 @@
package com.red.circle.external.app.oss;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.UUID;
/**
* 开发环境本地磁盘存储服务.
*/
@Service
public class LocalDiskStorageService {
private static final Logger log = LoggerFactory.getLogger(LocalDiskStorageService.class);
private static final String AVATAR_PREFIX = "avatar/";
private static final String EXTERNAL_LOCAL_PREFIX = "/external/oss/local/";
private static final String LOCAL_PREFIX = "/oss/local/";
private final LocalDiskStorageProperties properties;
public LocalDiskStorageService(LocalDiskStorageProperties properties) {
this.properties = properties;
}
public boolean isEnabled() {
return properties.isConfigured();
}
public String uploadAvatar(InputStream inputStream, String contentType, String suffix) {
String key = AVATAR_PREFIX + UUID.randomUUID() + normalizeSuffix(suffix);
writeFile(key, inputStream);
return buildAccessUrl(key);
}
public String upload(String key, InputStream inputStream) {
writeFile(key, inputStream);
return buildAccessUrl(key);
}
public StoredFile load(String keyOrUrl) {
String key = resolveKey(keyOrUrl);
if (!StringUtils.hasText(key)) {
throw new IllegalArgumentException("local disk key is empty");
}
Path path = resolveStoragePath(key);
if (!Files.exists(path) || !Files.isRegularFile(path)) {
throw new IllegalArgumentException("local disk file does not exist: " + key);
}
try {
Resource resource = new UrlResource(path.toUri());
String contentType = Files.probeContentType(path);
long contentLength = Files.size(path);
return new StoredFile(
resource,
StringUtils.hasText(contentType) ? contentType : "application/octet-stream",
contentLength);
} catch (IOException e) {
throw new IllegalStateException("load local file failed: " + key, e);
}
}
public boolean isManagedUrl(String source) {
if (!StringUtils.hasText(source)) {
return false;
}
if (!looksLikeUrl(source)) {
return false;
}
String path = URI.create(source).getPath();
return StringUtils.hasText(path)
&& (path.startsWith(EXTERNAL_LOCAL_PREFIX) || path.startsWith(LOCAL_PREFIX));
}
public String getAccessUrl(String keyOrUrl) {
if (!StringUtils.hasText(keyOrUrl)) {
return keyOrUrl;
}
if (looksLikeUrl(keyOrUrl)) {
return keyOrUrl;
}
return buildAccessUrl(keyOrUrl);
}
public void remove(String url) {
String key = resolveKey(url);
if (!StringUtils.hasText(key)) {
return;
}
try {
Files.deleteIfExists(resolveStoragePath(key));
} catch (IOException e) {
log.error("remove local file failed url={}", url, e);
throw new IllegalStateException("remove local file failed", e);
}
}
public void processFileSaveAs(String source, String target) {
if (!StringUtils.hasText(target)) {
return;
}
copy(source, target);
}
public String processImgSaveAsCompressZoom(String source, String target, int height) {
if (!StringUtils.hasText(target)) {
return processImgSaveAsCompressZoom(source, height);
}
copy(source, target);
return buildAccessUrl(target);
}
public String processImgSaveAsCompressZoom(String source, int height) {
return getAccessUrl(source);
}
private void writeFile(String key, InputStream inputStream) {
Path target = resolveStoragePath(key);
try {
Files.createDirectories(target.getParent());
Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
log.error("write local file failed key={}", key, e);
throw new IllegalStateException("write local file failed", e);
}
}
private void copy(String source, String target) {
String sourceKey = resolveKey(source);
if (!StringUtils.hasText(sourceKey)) {
return;
}
Path sourcePath = resolveStoragePath(sourceKey);
Path targetPath = resolveStoragePath(target);
try {
Files.createDirectories(targetPath.getParent());
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
log.error("copy local file failed source={} target={}", source, target, e);
throw new IllegalStateException("copy local file failed", e);
}
}
private Path resolveStoragePath(String key) {
Path basePath = Path.of(properties.getBasePath()).toAbsolutePath().normalize();
Path resolved = basePath.resolve(key).normalize();
if (!resolved.startsWith(basePath)) {
throw new IllegalArgumentException("invalid local disk key: " + key);
}
return resolved;
}
private String resolveKey(String keyOrUrl) {
if (!StringUtils.hasText(keyOrUrl)) {
return keyOrUrl;
}
if (!looksLikeUrl(keyOrUrl)) {
return normalizeKey(keyOrUrl);
}
String path = URI.create(keyOrUrl).getPath();
if (!StringUtils.hasText(path)) {
return null;
}
if (path.startsWith(EXTERNAL_LOCAL_PREFIX)) {
return normalizeKey(path.substring(EXTERNAL_LOCAL_PREFIX.length()));
}
if (path.startsWith(LOCAL_PREFIX)) {
return normalizeKey(path.substring(LOCAL_PREFIX.length()));
}
return null;
}
private String buildAccessUrl(String key) {
String accessUrl = properties.getAccessUrl();
return accessUrl.endsWith("/") ? accessUrl + key : accessUrl + "/" + key;
}
private String normalizeKey(String key) {
return key.startsWith("/") ? key.substring(1) : key;
}
private String normalizeSuffix(String suffix) {
if (!StringUtils.hasText(suffix)) {
return "";
}
return suffix.startsWith(".") ? suffix : "." + suffix;
}
private boolean looksLikeUrl(String value) {
return value.startsWith("http://") || value.startsWith("https://");
}
public record StoredFile(Resource resource, String contentType, long contentLength) {
}
}

View File

@ -0,0 +1,130 @@
package com.red.circle.external.app.oss;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
/**
* 腾讯云 COS 配置.
*/
@Component
@ConfigurationProperties(prefix = "red-circle.oss.tencent-cos")
public class TencentCosProperties {
private boolean enabled = true;
private String secretId = "IKID4FEAAyYv64nK1d2C6YnofMqCB0kLhq05";
private String secretKey = "UwFJAmAKFwQhjWGn58vUotkMktoumQq2";
private String bucketName = "app-release-1417798587";
private String region = "me-saudi-arabia";
private String accessUrl = "https://app-release-1417798587.cos.me-saudi-arabia.myqcloud.com";
private int connectionTimeoutMs = 15000;
private int connectionRequestTimeoutMs = 15000;
private int socketTimeoutMs = 120000;
private int requestTimeoutMs = 120000;
private int shutdownTimeoutMs = 10000;
private int maxErrorRetry = 0;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getSecretId() {
return secretId;
}
public void setSecretId(String secretId) {
this.secretId = secretId;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getAccessUrl() {
return accessUrl;
}
public void setAccessUrl(String accessUrl) {
this.accessUrl = accessUrl;
}
public int getConnectionTimeoutMs() {
return connectionTimeoutMs;
}
public void setConnectionTimeoutMs(int connectionTimeoutMs) {
this.connectionTimeoutMs = connectionTimeoutMs;
}
public int getConnectionRequestTimeoutMs() {
return connectionRequestTimeoutMs;
}
public void setConnectionRequestTimeoutMs(int connectionRequestTimeoutMs) {
this.connectionRequestTimeoutMs = connectionRequestTimeoutMs;
}
public int getSocketTimeoutMs() {
return socketTimeoutMs;
}
public void setSocketTimeoutMs(int socketTimeoutMs) {
this.socketTimeoutMs = socketTimeoutMs;
}
public int getRequestTimeoutMs() {
return requestTimeoutMs;
}
public void setRequestTimeoutMs(int requestTimeoutMs) {
this.requestTimeoutMs = requestTimeoutMs;
}
public int getShutdownTimeoutMs() {
return shutdownTimeoutMs;
}
public void setShutdownTimeoutMs(int shutdownTimeoutMs) {
this.shutdownTimeoutMs = shutdownTimeoutMs;
}
public int getMaxErrorRetry() {
return maxErrorRetry;
}
public void setMaxErrorRetry(int maxErrorRetry) {
this.maxErrorRetry = maxErrorRetry;
}
public boolean isConfigured() {
return enabled
&& StringUtils.hasText(secretId)
&& StringUtils.hasText(secretKey)
&& StringUtils.hasText(bucketName)
&& StringUtils.hasText(region);
}
}

View File

@ -0,0 +1,190 @@
package com.red.circle.external.app.oss;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.CopyObjectRequest;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.region.Region;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.UUID;
/**
* 腾讯云 COS 存储服务.
*/
@Service
public class TencentCosStorageService {
private static final String AVATAR_PREFIX = "avatar/";
private static final Logger log = LoggerFactory.getLogger(TencentCosStorageService.class);
private final TencentCosProperties properties;
public TencentCosStorageService(TencentCosProperties properties) {
this.properties = properties;
}
public boolean isEnabled() {
return properties.isConfigured();
}
public String uploadAvatar(InputStream inputStream, long contentLength, String contentType, String suffix) {
String key = AVATAR_PREFIX + UUID.randomUUID() + suffix;
upload(key, inputStream, contentLength, contentType);
return buildAccessUrl(key);
}
public void upload(String key, InputStream inputStream, long contentLength, String contentType) {
COSClient client = buildClient();
try {
byte[] data = inputStream.readAllBytes();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(data.length);
if (StringUtils.hasText(contentType)) {
metadata.setContentType(contentType);
}
client.putObject(
new PutObjectRequest(
properties.getBucketName(), key, new ByteArrayInputStream(data), metadata));
} catch (IOException e) {
log.error("tencent cos read upload stream failed key={}", key, e);
throw new IllegalStateException("read upload stream failed", e);
} catch (Exception e) {
log.error("tencent cos upload failed key={}", key, e);
throw e;
} finally {
client.shutdown();
}
}
public boolean isManagedUrl(String source) {
if (!isEnabled() || !StringUtils.hasText(source)) {
return false;
}
if (source.startsWith(getAccessUrlPrefix())) {
return true;
}
return source.contains(properties.getBucketName() + ".cos." + properties.getRegion() + ".myqcloud.com/");
}
public String getAccessUrl(String keyOrUrl) {
if (!StringUtils.hasText(keyOrUrl)) {
return keyOrUrl;
}
if (looksLikeUrl(keyOrUrl)) {
return keyOrUrl;
}
return buildAccessUrl(keyOrUrl);
}
public void remove(String url) {
String key = resolveKey(url);
if (!StringUtils.hasText(key)) {
return;
}
COSClient client = buildClient();
try {
client.deleteObject(properties.getBucketName(), key);
} catch (Exception e) {
log.error("tencent cos remove failed url={}", url, e);
throw e;
} finally {
client.shutdown();
}
}
public void processFileSaveAs(String source, String target) {
if (!StringUtils.hasText(target)) {
return;
}
copyObject(source, target);
}
public String processImgSaveAsCompressZoom(String source, String target, int height) {
if (!StringUtils.hasText(target)) {
return processImgSaveAsCompressZoom(source, height);
}
copyObject(source, target);
return buildAccessUrl(target);
}
public String processImgSaveAsCompressZoom(String source, int height) {
return getAccessUrl(source);
}
private void copyObject(String source, String target) {
String sourceKey = resolveKey(source);
if (!StringUtils.hasText(sourceKey)) {
return;
}
COSClient client = buildClient();
try {
CopyObjectRequest request =
new CopyObjectRequest(properties.getBucketName(), sourceKey, properties.getBucketName(), target);
client.copyObject(request);
} catch (Exception e) {
log.error("tencent cos copy failed source={} target={}", source, target, e);
throw e;
} finally {
client.shutdown();
}
}
private String resolveKey(String keyOrUrl) {
if (!StringUtils.hasText(keyOrUrl)) {
return keyOrUrl;
}
if (!looksLikeUrl(keyOrUrl)) {
return keyOrUrl;
}
if (!isManagedUrl(keyOrUrl)) {
return null;
}
String path = URI.create(keyOrUrl).getPath();
if (!StringUtils.hasText(path)) {
return null;
}
return path.startsWith("/") ? path.substring(1) : path;
}
private String buildAccessUrl(String key) {
return getAccessUrlPrefix() + key;
}
private String getAccessUrlPrefix() {
String accessUrl = StringUtils.hasText(properties.getAccessUrl())
? properties.getAccessUrl()
: "https://" + properties.getBucketName() + ".cos." + properties.getRegion() + ".myqcloud.com";
return accessUrl.endsWith("/") ? accessUrl : accessUrl + "/";
}
private boolean looksLikeUrl(String value) {
return value.startsWith("http://") || value.startsWith("https://");
}
private COSClient buildClient() {
COSCredentials credentials =
new BasicCOSCredentials(properties.getSecretId(), properties.getSecretKey());
ClientConfig clientConfig = new ClientConfig(new Region(properties.getRegion()));
clientConfig.setHttpProtocol(HttpProtocol.https);
clientConfig.setConnectionTimeout(properties.getConnectionTimeoutMs());
clientConfig.setConnectionRequestTimeout(properties.getConnectionRequestTimeoutMs());
clientConfig.setSocketTimeout(properties.getSocketTimeoutMs());
clientConfig.setRequestTimeOutEnable(true);
clientConfig.setRequestTimeout(properties.getRequestTimeoutMs());
clientConfig.setShutdownTimeout(properties.getShutdownTimeoutMs());
clientConfig.setMaxErrorRetry(properties.getMaxErrorRetry());
return new COSClient(credentials, clientConfig);
}
}

View File

@ -1,56 +1,103 @@
package com.red.circle.external.inner.service.oss.impl;
import com.red.circle.component.oss.OssService;
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
import com.red.circle.external.inner.service.oss.OssServiceClientService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.red.circle.component.oss.OssService;
import com.red.circle.component.oss.aliyun.service.AliYunStsService;
import com.red.circle.external.app.oss.LocalDiskStorageService;
import com.red.circle.external.app.oss.TencentCosStorageService;
import com.red.circle.external.inner.service.oss.OssServiceClientService;
import org.springframework.stereotype.Service;
/**
* oss 服务api.
*
* @author pengliang on 2023/10/15
*/
@Service
@RequiredArgsConstructor
public class OssServiceClientServiceImpl implements OssServiceClientService {
private final OssService ossService;
private final AliYunStsService aliYunStsService;
* oss 服务api.
*
* @author pengliang on 2023/10/15
*/
@Service
public class OssServiceClientServiceImpl implements OssServiceClientService {
private final OssService ossService;
private final AliYunStsService aliYunStsService;
private final LocalDiskStorageService localDiskStorageService;
private final TencentCosStorageService tencentCosStorageService;
public OssServiceClientServiceImpl(
OssService ossService,
AliYunStsService aliYunStsService,
LocalDiskStorageService localDiskStorageService,
TencentCosStorageService tencentCosStorageService) {
this.ossService = ossService;
this.aliYunStsService = aliYunStsService;
this.localDiskStorageService = localDiskStorageService;
this.tencentCosStorageService = tencentCosStorageService;
}
@Override
public Object sts() {
return aliYunStsService.getAcsResponse();
}
@Override
public void remove(String url) {
ossService.remove(url);
}
@Override
public void remove(String url) {
if (localDiskStorageService.isManagedUrl(url)) {
localDiskStorageService.remove(url);
return;
}
if (tencentCosStorageService.isManagedUrl(url)) {
tencentCosStorageService.remove(url);
return;
}
ossService.remove(url);
}
@Override
public String getAccessUrl(String key) {
return ossService.getAccessUrl(key);
}
@Override
public String getAccessUrl(String key) {
if (localDiskStorageService.isManagedUrl(key)) {
return key;
}
if (tencentCosStorageService.isManagedUrl(key)) {
return key;
}
return ossService.getAccessUrl(key);
}
@Override
public String getVideoCover(String key) {
return ossService.getVideoCover(key);
}
@Override
public void processFileSaveAs(String source, String target, String styleType) {
ossService.processFileSaveAs(source, target, styleType);
}
@Override
public void processFileSaveAs(String source, String target, String styleType) {
if (localDiskStorageService.isManagedUrl(source)) {
localDiskStorageService.processFileSaveAs(source, target);
return;
}
if (tencentCosStorageService.isManagedUrl(source)) {
tencentCosStorageService.processFileSaveAs(source, target);
return;
}
ossService.processFileSaveAs(source, target, styleType);
}
@Override
public String processImgSaveAsCompressZoom(String source, String target, int height) {
return ossService.processImgSaveAsCompressZoom(source, target, height);
}
@Override
public String processImgSaveAsCompressZoom(String source, String target, int height) {
if (localDiskStorageService.isManagedUrl(source)) {
return localDiskStorageService.processImgSaveAsCompressZoom(source, target, height);
}
if (tencentCosStorageService.isManagedUrl(source)) {
return tencentCosStorageService.processImgSaveAsCompressZoom(source, target, height);
}
return ossService.processImgSaveAsCompressZoom(source, target, height);
}
@Override
public String processImgSaveAsCompressZoom(String source, int height) {
return ossService.processImgSaveAsCompressZoom(source, height);
}
}
@Override
public String processImgSaveAsCompressZoom(String source, int height) {
if (localDiskStorageService.isManagedUrl(source)) {
return localDiskStorageService.processImgSaveAsCompressZoom(source, height);
}
if (tencentCosStorageService.isManagedUrl(source)) {
return tencentCosStorageService.processImgSaveAsCompressZoom(source, height);
}
return ossService.processImgSaveAsCompressZoom(source, height);
}
}

View File

@ -0,0 +1,181 @@
package com.red.circle;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.red.circle.component.redis.annotation.aspect.TaskCacheLockAspect;
import com.red.circle.component.redis.props.RedisConfigProperties;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.tool.core.text.StringUtils;
import io.lettuce.core.internal.HostAndPort;
import io.lettuce.core.resource.ClientResources;
import io.lettuce.core.resource.DnsResolvers;
import io.lettuce.core.resource.MappingSocketAddressResolver;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableConfigurationProperties({RedisConfigProperties.class, RedisProperties.class})
public class RedisServiceFallbackConfiguration {
@Bean
@Primary
public RedisTemplate<String, Object> redCircleRedisTemplate(
RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setValueSerializer(new GenericToStringSerializer<>(Object.class));
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(connectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public KeyGenerator simpleKeyGenerator() {
return (target, method, params) -> {
StringBuilder builder = new StringBuilder();
builder.append(target.getClass().getSimpleName());
builder.append(".");
builder.append(method.getName());
builder.append("[");
for (Object param : params) {
builder.append(param);
}
builder.append("]");
return builder.toString();
};
}
@Bean
@ConditionalOnMissingBean
public CacheManager cacheManager(
RedisConnectionFactory redisConnectionFactory,
RedisConfigProperties redisConfigProperties) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
getRedisCacheConfigurationWithTtl(1800),
getRedisCacheConfigurationMap(redisConfigProperties));
}
@Bean
@Primary
@ConditionalOnMissingBean
public RedisService redisService(
@Qualifier("redCircleRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
return new RedisService(redisTemplate);
}
@Bean
@ConditionalOnMissingBean
public TaskCacheLockAspect taskCacheLockAspect(
@Qualifier("redCircleRedisTemplate") RedisTemplate<String, Object> redisTemplate) {
return new TaskCacheLockAspect(redisTemplate.opsForValue());
}
@Bean
@ConditionalOnMissingBean
public LettuceConnectionFactory redisConnectionFactory(RedisProperties redisProperties) {
MappingSocketAddressResolver resolver =
MappingSocketAddressResolver.create(
DnsResolvers.UNRESOLVED,
port -> HostAndPort.of(redisProperties.getHost(), port.getPort()));
ClientResources clientResources =
ClientResources.builder().socketAddressResolver(resolver).build();
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder =
LettucePoolingClientConfiguration.builder()
.poolConfig(getPoolConfig(redisProperties.getLettuce().getPool()))
.clientResources(clientResources);
if (Objects.nonNull(redisProperties.getLettuce().getShutdownTimeout())) {
builder.shutdownTimeout(redisProperties.getLettuce().getShutdownTimeout());
}
if (Objects.nonNull(redisProperties.getTimeout())) {
builder.commandTimeout(redisProperties.getTimeout());
}
if (StringUtils.isNotBlank(redisProperties.getClientName())) {
builder.clientName(redisProperties.getClientName());
}
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setDatabase(redisProperties.getDatabase());
configuration.setHostName(redisProperties.getHost());
configuration.setPort(redisProperties.getPort());
configuration.setPassword(redisProperties.getPassword());
LettucePoolingClientConfiguration clientConfiguration =
redisProperties.getSsl().isEnabled()
? builder.useSsl().disablePeerVerification().build()
: builder.build();
return new LettuceConnectionFactory(configuration, clientConfiguration);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(
RedisConfigProperties redisConfigProperties) {
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(16);
redisConfigProperties
.getInitExpiry()
.forEach(
(cacheName, ttl) ->
configurationMap.put(cacheName, getRedisCacheConfigurationWithTtl(ttl)));
return configurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.PROPERTY);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
return configuration
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.entryTtl(Duration.ofSeconds(seconds.longValue()));
}
private GenericObjectPoolConfig<?> getPoolConfig(RedisProperties.Pool properties) {
GenericObjectPoolConfig<Object> poolConfig = new GenericObjectPoolConfig<>();
poolConfig.setMaxTotal(properties.getMaxActive());
poolConfig.setMaxIdle(properties.getMaxIdle());
poolConfig.setMinIdle(properties.getMinIdle());
if (properties.getTimeBetweenEvictionRuns() != null) {
poolConfig.setTimeBetweenEvictionRuns(properties.getTimeBetweenEvictionRuns());
}
if (properties.getMaxWait() != null) {
poolConfig.setMaxWait(properties.getMaxWait());
}
return poolConfig;
}
}

View File

@ -29,6 +29,6 @@ management:
health:
show-details: always
rtc:
appId: f424387a480e41088239416e10030034
certificate: c96d8494f80542bb83c843ad4045df03
rtc:
appId: ceb9e2620d454bca9725f7a7f11d4019
certificate: 1fe700671f1641a8b42a474d4ad990a7

View File

@ -32,9 +32,10 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* 直播麦克风服务.
@ -56,12 +57,15 @@ public class LiveMicrophoneServiceImpl implements LiveMicrophoneService {
private final RedisService redisService;
private final RoomUserBlacklistClient roomUserBlacklistClient;
private final String authKey = "Basic N2Q0ZTRiZDYyODUyNDc3ZGI4Yzk3OGU1NTVmMDRiOGI6ODdkMjcxMTg2NzgxNGY4YjgzZTNkOWIyZmRjOTc3OWQ=";
private final String killRoom = "https://api.sd-rtn.com/dev/v1/kicking-rule";
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
private final String appId = "";
@Value("${rtc.authKey}")
private String authKey;
@Value("${rtc.killRoom}")
private String killRoom;
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
@Value("${rtc.appId}")
private String appId;
private final UserProfileClient userProfileClient;

View File

@ -58,15 +58,16 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.red.circle.tool.core.thread.ThreadPoolManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import com.red.circle.tool.core.thread.ThreadPoolManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
* @author pengliang on 2023/12/12
@ -93,13 +94,15 @@ public class LiveMicrophoneGatewayImpl implements LiveMicrophoneGateway {
private final UserCpClient userCpClient;
private final LiveRoomCacheService liveRoomCacheService;
// private final RedissonClient redissonClient;
//临时处理,后续房子啊Nacos配置处理
private final String authKey = "Basic MmIwZGFkOWQ0ZjEzNGQ2NmI5MmI2ZmY2MzljNmEwMTc6ZWRlMTQ1NTRjNTU0NDY2ZmE4OWNiYWFhNGNmZTM3YjA=";
private final String killRoom = "https://api.sd-rtn.com/dev/v1/kicking-rule";
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
private final String appId = "";
@Value("${rtc.authKey}")
private String authKey;
@Value("${rtc.killRoom}")
private String killRoom;
private final String queryUserInfo = "https://api.sd-rtn.com/dev/v1/channel/user/";
@Value("${rtc.appId}")
private String appId;
@Override

View File

@ -55,11 +55,12 @@
<version>5.8.22</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<parent>
<artifactId>rc-service-other</artifactId>

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.tool.core.text.StringUtils;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.other.inner.enums.user.AuthTypeEnum;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotNull;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import java.io.Serial;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import java.io.Serial;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.tool.core.date.AgeUtils;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import lombok.Data;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotNull;

View File

@ -1,4 +1,4 @@
package com.red.circle.other.app.dto.cmd.user.user;;
package com.red.circle.other.app.dto.cmd.user.user;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotNull;