From 67d2198e2dcd68879eef11219693381bc6f84986 Mon Sep 17 00:00:00 2001 From: iqudoo Date: Sun, 7 Jun 2026 19:16:04 +0800 Subject: [PATCH] http proxy --- .../controller/HttpProxyController.java | 966 ++++++++++++++++++ .../ApiSystemAsyncTaskInfoCancelAction.java | 2 +- .../ApiSystemAsyncTaskInfoClearAction.java | 35 + .../ApiSystemAsyncTaskInfoDeleteAction.java | 3 +- .../SystemAdminInfoAuthServiceImpl.java | 140 +-- .../SystemAdminInfoAuthTransactional.java | 167 +++ .../SystemAsyncTaskInfoTransactional.java | 22 +- .../SystemAsyncTaskInfoClearParam.java | 10 + .../service/ISystemAsyncTaskInfoService.java | 4 +- .../scheduling/LoopScheduling.java | 2 + .../setting/SystemGlobalHttpProxyConfig.java | 23 + 11 files changed, 1229 insertions(+), 145 deletions(-) create mode 100644 src/main/java/com/iqudoo/platform/application/controller/HttpProxyController.java create mode 100644 src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoClearAction.java create mode 100644 src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAdminInfoAuthTransactional.java create mode 100644 src/main/java/com/iqudoo/platform/application/setting/SystemGlobalHttpProxyConfig.java diff --git a/src/main/java/com/iqudoo/platform/application/controller/HttpProxyController.java b/src/main/java/com/iqudoo/platform/application/controller/HttpProxyController.java new file mode 100644 index 0000000..d377107 --- /dev/null +++ b/src/main/java/com/iqudoo/platform/application/controller/HttpProxyController.java @@ -0,0 +1,966 @@ +package com.iqudoo.platform.application.controller; + +import com.alibaba.excel.util.StringUtils; +import com.iqudoo.framework.tape.Tape; +import com.iqudoo.framework.tape.modules.IModuleSetting; +import com.iqudoo.platform.application.setting.SystemGlobalHttpProxyConfig; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.*; +import org.springframework.http.client.reactive.ClientHttpRequest; +import org.springframework.http.codec.ClientCodecConfigurer; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.reactive.function.BodyInserter; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +import javax.servlet.http.HttpServletRequest; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 通用请求代理控制器 + */ +@SuppressWarnings({"DuplicatedCode", "Convert2Lambda", "deprecation"}) +@RestController +public class HttpProxyController { + + private final WebClient webClient; + // 静态资源后缀匹配正则(包含JSON) + private static final Pattern STATIC_RESOURCE_PATTERN = Pattern.compile("\\.(css|js|png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|svg|json)(\\?.*)?$", Pattern.CASE_INSENSITIVE); + // HTML内容识别正则(强制识别,忽略Content-Type) + private static final Pattern HTML_CONTENT_PATTERN = Pattern.compile("^\\s*() { + @Override + public void accept(ClientCodecConfigurer clientCodecConfigurer) { + clientCodecConfigurer.defaultCodecs().maxInMemorySize(50 * 1024 * 1024); // 50MB + } + }) + .build(); + } + + /** + * 通用代理接口(基于目录路由) + */ + @RequestMapping(value = {"/domain-proxy/", "/domain-proxy/{proxyDir:.*}/**"}, method = { + RequestMethod.GET, + RequestMethod.POST, + RequestMethod.PUT, + RequestMethod.DELETE, + RequestMethod.PATCH + }) + public Mono> domainProxy( + @PathVariable(required = false) String proxyDir, + @RequestBody(required = false) Object body, + HttpServletRequest servletRequest) { + try { + SystemGlobalHttpProxyConfig httpProxyConfig = Tape.getService(IModuleSetting.class) + .getSetting(SystemGlobalHttpProxyConfig.class); + HttpHeaders headers = new HttpHeaders(); + String requestUri = servletRequest.getRequestURI(); + String proxyPath = requestUri.replaceFirst("^/domain-proxy(/[^/]+)?", ""); + if (proxyPath.startsWith("//")) { + proxyPath = proxyPath.substring(1); + } + // 匹配路由配置 + String proxyTargetUrl = null; + String proxyDomainUrl = null; + List replaceList = new ArrayList<>(); + if (StringUtils.isNotBlank(httpProxyConfig.getDomainProxyRouterMap())) { + String[] domainProxyConfig = httpProxyConfig.getDomainProxyRouterMap().split("\n"); + for (String domainProxy : domainProxyConfig) { + try { + Map folderMap = parseKeyValue(domainProxy); + if (folderMap.containsKey("key") && Objects.equals(folderMap.get("key"), proxyDir)) { + String[] routers = folderMap.get("value").trim().split("#"); + if (routers.length > 1) { + replaceList.addAll(Arrays.asList(routers).subList(1, routers.length)); + } + proxyDomainUrl = routers[0]; + if (proxyDomainUrl.endsWith("/")) { + proxyDomainUrl = proxyDomainUrl.substring(0, proxyDomainUrl.length() - 1); + } + proxyTargetUrl = proxyDomainUrl + (proxyPath.isEmpty() ? "" + : (proxyPath.startsWith("/") ? proxyPath : "/" + proxyPath)); + break; + } + } catch (Throwable ignored) { + } + } + } + // 校验目标地址 + if (StringUtils.isBlank(proxyTargetUrl)) { + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":404,\"msg\": \"Not found " + proxyDir + " proxy router\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, headers, HttpStatus.NOT_FOUND)); + } + // 拼接查询参数 + String queryString = servletRequest.getQueryString(); + if (StringUtils.isNotBlank(queryString)) { + proxyTargetUrl += (proxyTargetUrl.contains("?") ? "&" : "?") + queryString; + } + // 构建请求头 + copyRequestHeaders(servletRequest, headers); + // 处理请求方法和请求体 + HttpMethod method = HttpMethod.valueOf(servletRequest.getMethod()); + BodyInserter bodyInserter = buildBodyInserter(body, servletRequest); + // 发送代理请求并处理响应 + String finalProxyDomainUrl = proxyDomainUrl; + String finalProxyTargetUrl = proxyTargetUrl; + return webClient.method(method) + .uri(URI.create(proxyTargetUrl)) + .headers(new Consumer() { + @Override + public void accept(HttpHeaders httpHeaders) { + httpHeaders.addAll(headers); + } + }) + .body(bodyInserter) + .exchange() + .flatMap(new Function>>() { + @Override + public Mono> apply(ClientResponse clientResponse) { + try { + // 先判断是否为二进制请求,避免不必要的收集 + String requestUriLower = requestUri.toLowerCase(); + boolean isLikelyBinary = true; + try { + isLikelyBinary = isLikelyBinaryRequest(requestUriLower, clientResponse); + } catch (Exception e) { + // 判断失败时,默认按文本处理 + } + + if (isLikelyBinary) { + // 二进制请求:收集数据后返回 Resource + return clientResponse.bodyToFlux(DataBuffer.class) + .collectList() + .flatMap(new Function, Mono>>() { + @Override + public Mono> apply(List dataBuffers) { + try { + byte[] responseBytes = mergeDataBuffers(dataBuffers); + + // 构建响应头 + HttpHeaders responseHeaders = new HttpHeaders(); + HttpHeaders clientHeaders = clientResponse.headers().asHttpHeaders(); + + // 先检查原始响应头的 Content-Type + MediaType originalContentType = clientHeaders.getContentType(); + boolean hasBinaryContentType = isBinaryContentType(originalContentType); + + // 复制响应头,但排除可能冲突的 + clientHeaders.forEach(new HttpHeadersBiConsumer(responseHeaders)); + + // 处理 Content-Type:既然进入了二进制分支,说明应该是二进制内容 + if (hasBinaryContentType) { + // 如果原始响应头已经是二进制类型,使用它 + responseHeaders.setContentType(originalContentType); + } else { + // 如果原始响应头不是二进制类型(比如 text/html),尝试从 URI 自动检测 + MediaType contentType = getContentTypeFromUri(requestUriLower); + if (contentType != null) { + // 从 URI 检测到了类型,设置它 + responseHeaders.setContentType(contentType); + } + // 如果检测不到,不设置 content-type(已经在上面的复制时排除了) + } + + // 设置 Content-Length + responseHeaders.setContentLength(responseBytes.length); + + // 使用 ByteArrayResource 包装 + Resource resource = new ByteArrayResource(responseBytes) { + @Override + public String getFilename() { + String filename = requestUriLower; + int lastSlash = filename.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < filename.length() - 1) { + filename = filename.substring(lastSlash + 1); + } + int queryIndex = filename.indexOf('?'); + if (queryIndex > 0) { + filename = filename.substring(0, queryIndex); + } + return filename.isEmpty() ? "binary" : filename; + } + }; + + return Mono.just(new ResponseEntity<>(resource, responseHeaders, clientResponse.statusCode())); + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理二进制响应失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }); + } else { + // 文本请求:收集后处理 + return clientResponse.bodyToFlux(DataBuffer.class) + .collectList() + .flatMap(new Function, Mono>>() { + @Override + public Mono> apply(List dataBuffers) { + try { + byte[] responseBytes = mergeDataBuffers(dataBuffers); + return handleResponseContent(responseBytes, clientResponse, finalProxyDomainUrl, proxyDir, finalProxyTargetUrl, replaceList); + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理响应内容失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }); + } + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理响应失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }) + // 异常处理 + .onErrorResume(WebClientResponseException.class, new Function>>() { + @Override + public Mono> apply(WebClientResponseException e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = String.format("{\"code\":%d,\"msg\": \"Proxy request failure: %s\"}", + e.getRawStatusCode(), e.getMessage()); + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.valueOf(e.getRawStatusCode()))); + } + }) + .onErrorResume(new Function>>() { + @Override + public Mono> apply(Throwable e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"Proxy request failure: " + + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + }); + } catch (Throwable e) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"Internal server error: " + + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, headers, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + + /** + * 通用代理接口(直接指定目标URL) + */ + @RequestMapping(value = "/http-proxy", method = { + RequestMethod.GET, + RequestMethod.POST, + RequestMethod.PUT, + RequestMethod.DELETE, + RequestMethod.PATCH + }) + public Mono> httpProxy( + @RequestParam(required = false) String targetUrl, + @RequestBody(required = false) Object body, + HttpServletRequest servletRequest) { + String proxyTargetUrl = encodeUrlQueryParams(targetUrl); + if (StringUtils.isBlank(proxyTargetUrl)) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":400,\"msg\": \"目标地址不能为空\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, headers, HttpStatus.BAD_REQUEST)); + } + HttpHeaders headers = new HttpHeaders(); + copyRequestHeaders(servletRequest, headers); + HttpMethod method = HttpMethod.valueOf(servletRequest.getMethod()); + BodyInserter bodyInserter = buildBodyInserter(body, servletRequest); + return webClient.method(method) + .uri(URI.create(proxyTargetUrl)) + .headers(new Consumer() { + @Override + public void accept(HttpHeaders httpHeaders) { + httpHeaders.addAll(headers); + } + }) + .body(bodyInserter) + .exchange() + .flatMap(new Function>>() { + @Override + public Mono> apply(ClientResponse clientResponse) { + try { + // 先判断是否为二进制请求,避免不必要的收集 + String targetUrlLower = proxyTargetUrl.toLowerCase(); + boolean isLikelyBinary = false; + try { + isLikelyBinary = isLikelyBinaryRequest(targetUrlLower, clientResponse); + } catch (Exception e) { + // 判断失败时,默认按文本处理 + } + + if (isLikelyBinary) { + // 二进制请求:收集数据后返回 Resource + return clientResponse.bodyToFlux(DataBuffer.class) + .collectList() + .flatMap(new Function, Mono>>() { + @Override + public Mono> apply(List dataBuffers) { + try { + byte[] responseBytes = mergeDataBuffers(dataBuffers); + + // 构建响应头 + HttpHeaders responseHeaders = new HttpHeaders(); + HttpHeaders clientHeaders = clientResponse.headers().asHttpHeaders(); + + // 先检查原始响应头的 Content-Type + MediaType originalContentType = clientHeaders.getContentType(); + boolean hasBinaryContentType = isBinaryContentType(originalContentType); + + // 复制响应头,但排除可能冲突的 + clientHeaders.forEach(new HttpHeadersBiConsumer(responseHeaders)); + + // 处理 Content-Type:既然进入了二进制分支,说明应该是二进制内容 + if (hasBinaryContentType) { + // 如果原始响应头已经是二进制类型,使用它 + responseHeaders.setContentType(originalContentType); + } else { + // 如果原始响应头不是二进制类型(比如 text/html),尝试从 URI 自动检测 + MediaType contentType = getContentTypeFromUri(targetUrlLower); + if (contentType != null) { + // 从 URI 检测到了类型,设置它 + responseHeaders.setContentType(contentType); + } + // 如果检测不到,不设置 content-type(已经在上面的复制时排除了) + } + + // 设置 Content-Length + responseHeaders.setContentLength(responseBytes.length); + + // 使用 ByteArrayResource 包装 + Resource resource = new ByteArrayResource(responseBytes) { + @Override + public String getFilename() { + String filename = targetUrlLower; + int lastSlash = filename.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < filename.length() - 1) { + filename = filename.substring(lastSlash + 1); + } + int queryIndex = filename.indexOf('?'); + if (queryIndex > 0) { + filename = filename.substring(0, queryIndex); + } + return filename.isEmpty() ? "binary" : filename; + } + }; + + return Mono.just(new ResponseEntity<>(resource, responseHeaders, clientResponse.statusCode())); + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理二进制响应失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }); + } else { + // 文本请求:收集后处理 + return clientResponse.bodyToFlux(DataBuffer.class) + .collectList() + .flatMap(new Function, Mono>>() { + @Override + public Mono> apply(List dataBuffers) { + try { + byte[] responseBytes = mergeDataBuffers(dataBuffers); + return handleResponseContent(responseBytes, clientResponse, null, null, proxyTargetUrl, new ArrayList<>()); + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理响应内容失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }); + } + } catch (Exception e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"处理响应失败: " + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + }) + .onErrorResume(WebClientResponseException.class, new Function>>() { + @Override + public Mono> apply(WebClientResponseException e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = String.format("{\"code\":%d,\"msg\": \"Proxy request failure: %s\"}", + e.getRawStatusCode(), e.getMessage()); + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.valueOf(e.getRawStatusCode()))); + } + }) + .onErrorResume(new Function>>() { + @Override + public Mono> apply(Throwable e) { + HttpHeaders errorHeaders = new HttpHeaders(); + errorHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + String errorMsg = "{\"code\":500,\"msg\": \"Proxy request failure: " + + e.getMessage().replace("\"", "\\\"") + "\"}"; + return Mono.just(new ResponseEntity<>(errorMsg, errorHeaders, HttpStatus.INTERNAL_SERVER_ERROR)); + } + }); + } + + /** + * 核心修复:精准域名替换 + 保护HTML标签 + 统一响应格式 + */ + private Mono> handleResponseContent(byte[] responseBytes, ClientResponse response, + String domainUrl, String proxyDir, String proxyTargetUrl, List replaceList) { + HttpHeaders responseHeaders = new HttpHeaders(); + HttpHeaders clientHeaders = response.headers().asHttpHeaders(); + HttpStatus statusCode = response.statusCode(); + + if (responseBytes == null || responseBytes.length == 0) { + return Mono.just(new ResponseEntity<>(responseHeaders, statusCode)); + } + // 复制响应头,但排除可能冲突的 + clientHeaders.forEach(new HttpHeadersBiConsumer(responseHeaders)); + // ========== 首先判断是否为二进制内容 ========== + // 检查响应的 Content-Type + // 先检查原始响应头的 Content-Type + MediaType contentType = clientHeaders.getContentType(); + if (contentType == null) { + contentType = detectFileMediaType(responseBytes); + } + boolean isBinaryByContentType = isBinaryContentType(contentType); + // 检查文件扩展名是否为二进制类型 + boolean isBinaryByExtension = STATIC_RESOURCE_PATTERN.matcher(proxyTargetUrl).find() && + !TEXT_RESOURCE_PATTERN.matcher(proxyTargetUrl).find(); + // 如果是二进制内容,直接返回,不进行字符串转换 + // 判断条件:1. 有二进制 content-type 2. 有二进制扩展名且内容是二进制 3. 内容是二进制且没有 content-type(避免误判) + if (isBinaryByContentType || isBinaryByExtension || contentType == null) { + // 确保 Content-Type 正确设置 + if (contentType == null || !isBinaryByContentType) { + // 根据文件扩展名设置 Content-Type + String lowerUri = proxyTargetUrl.toLowerCase(); + if (lowerUri.endsWith(".png")) { + responseHeaders.setContentType(MediaType.IMAGE_PNG); + } else if (lowerUri.endsWith(".jpg") || lowerUri.endsWith(".jpeg")) { + responseHeaders.setContentType(MediaType.IMAGE_JPEG); + } else if (lowerUri.endsWith(".gif")) { + responseHeaders.setContentType(MediaType.IMAGE_GIF); + } else if (lowerUri.endsWith(".ico")) { + responseHeaders.setContentType(new MediaType("image", "x-icon")); + } else if (lowerUri.endsWith(".woff")) { + responseHeaders.setContentType(new MediaType("font", "woff")); + } else if (lowerUri.endsWith(".woff2")) { + responseHeaders.setContentType(new MediaType("font", "woff2")); + } else if (lowerUri.endsWith(".ttf")) { + responseHeaders.setContentType(new MediaType("font", "ttf")); + } else if (lowerUri.endsWith(".eot")) { + responseHeaders.setContentType(new MediaType("application", "vnd.ms-fontobject")); + } else if (lowerUri.endsWith(".svg")) { + responseHeaders.setContentType(new MediaType("image", "svg+xml")); + } else if (lowerUri.endsWith(".webp")) { + responseHeaders.setContentType(new MediaType("image", "webp")); + } else if (lowerUri.endsWith(".bmp")) { + responseHeaders.setContentType(new MediaType("image", "bmp")); + } + } + if (contentType != null) { + responseHeaders.setContentType(contentType); + } + + // 设置 Content-Length + responseHeaders.setContentLength(responseBytes.length); + + // 使用 ByteArrayResource 包装字节数组,Spring 可以正确序列化 + Resource resource = new ByteArrayResource(responseBytes) { + @Override + public String getFilename() { + // 从 URI 中提取文件名 + String filename = proxyTargetUrl; + int lastSlash = filename.lastIndexOf('/'); + if (lastSlash >= 0 && lastSlash < filename.length() - 1) { + filename = filename.substring(lastSlash + 1); + } + int queryIndex = filename.indexOf('?'); + if (queryIndex > 0) { + filename = filename.substring(0, queryIndex); + } + return filename.isEmpty() ? "binary" : filename; + } + }; + + // 返回 Resource,Spring 会自动处理二进制数据 + return Mono.just(new ResponseEntity<>(resource, responseHeaders, statusCode)); + } + + // ========== 文本内容处理 ========== + // 只有确定是文本内容时才转换为字符串 + String responseStr = new String(responseBytes, StandardCharsets.UTF_8); + String processedContent = responseStr; + + // ========== 精准域名替换(仅对文本内容) ========== + if (StringUtils.isNotBlank(domainUrl) && StringUtils.isNotBlank(proxyDir)) { + boolean needReplace = TEXT_RESOURCE_PATTERN.matcher(proxyTargetUrl).find() || + HTML_CONTENT_PATTERN.matcher(responseStr).find() || + responseStr.trim().startsWith("{") || responseStr.trim().startsWith("["); + // 仅对文本类资源进行替换 + + if (needReplace) { + processedContent = replaceDomainPrecisely(processedContent, domainUrl, proxyDir, replaceList); + } + } + + // ========== 文本响应类型处理 ========== + // 1. HTML类型(最高优先级,确保标签完整) + if (HTML_CONTENT_PATTERN.matcher(responseStr).find()) { + responseHeaders.setContentType(new MediaType(MediaType.TEXT_HTML, StandardCharsets.UTF_8)); + responseHeaders.remove(HttpHeaders.CONTENT_LENGTH); + return Mono.just(new ResponseEntity<>(processedContent, responseHeaders, statusCode)); + } + // 2. JSON类型 + else if (responseStr.trim().startsWith("{") || responseStr.trim().startsWith("[")) { + responseHeaders.setContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8)); + responseHeaders.remove(HttpHeaders.CONTENT_LENGTH); + return Mono.just(new ResponseEntity<>(processedContent, responseHeaders, statusCode)); + } + // 3. 文本类型(CSS/JS/TXT等) + else { + // 设置文本类型的content-type + setTextContentTypeWithCharset(responseHeaders, proxyTargetUrl, contentType); + responseHeaders.remove(HttpHeaders.CONTENT_LENGTH); + return Mono.just(new ResponseEntity<>(processedContent, responseHeaders, statusCode)); + } + } + + @SuppressWarnings("ClassCanBeRecord") + private static class HttpHeadersBiConsumer implements BiConsumer> { + + private final HttpHeaders responseHeaders; + + public HttpHeadersBiConsumer(HttpHeaders responseHeaders) { + this.responseHeaders = responseHeaders; + } + + @Override + public void accept(String key, List values) { + String lowerKey = key.toLowerCase(); + // 过滤目标接口返回的 CORS 相关头(避免和全局配置重复) + if (lowerKey.startsWith("access-control-") || lowerKey.startsWith("vary")) { + return; // 跳过,不复制这些头 + } + // 排除这些响应头 + if (!"content-length".equals(lowerKey) + && !"transfer-encoding".equals(lowerKey) + && !"content-encoding".equals(lowerKey) + && !"content-type".equals(lowerKey)) { + responseHeaders.put(key, values); + } + } + } + + /** + * 精准域名替换 + */ + private String replaceDomainPrecisely(String content, String originalDomain, String proxyDir, List replaceList) { + if (StringUtils.isBlank(content) || StringUtils.isBlank(originalDomain) || StringUtils.isBlank(proxyDir)) { + return content; + } + String proxyPath = "/domain-proxy/" + proxyDir; + String escapedDomain = Pattern.quote(originalDomain); + for (String str : replaceList) { + content = content.replaceAll(str, proxyPath + str); + } + return content.replaceAll(escapedDomain, proxyPath); + } + + /** + * 低版本兼容:设置文本类型Content-Type(带UTF-8编码) + */ + private void setTextContentTypeWithCharset(HttpHeaders headers, String requestUri, MediaType contentType) { + MediaType mediaType; + if (requestUri.endsWith(".css")) { + mediaType = new MediaType(MediaType.valueOf("text/css"), StandardCharsets.UTF_8); + } else if (requestUri.endsWith(".js")) { + mediaType = new MediaType(MediaType.valueOf("application/javascript"), StandardCharsets.UTF_8); + } else if (requestUri.endsWith(".json")) { + mediaType = new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8); + } else if (requestUri.endsWith(".xml")) { + mediaType = new MediaType(MediaType.APPLICATION_XML, StandardCharsets.UTF_8); + } else { + mediaType = contentType; + } + headers.setContentType(mediaType); + } + + /** + * 合并DataBuffer列表为字节数组 + */ + private byte[] mergeDataBuffers(List dataBuffers) { + if (dataBuffers.isEmpty()) { + return new byte[0]; + } + + int totalLength = 0; + for (DataBuffer buffer : dataBuffers) { + totalLength += buffer.readableByteCount(); + } + + byte[] result = new byte[totalLength]; + int offset = 0; + for (DataBuffer buffer : dataBuffers) { + int length = buffer.readableByteCount(); + buffer.read(result, offset, length); + offset += length; + DataBufferUtils.release(buffer); + } + return result; + } + + /** + * 快速判断是否为二进制请求(基于 URI 和响应头) + */ + private boolean isLikelyBinaryRequest(String requestUriLower, ClientResponse clientResponse) { + try { + // 检查文件扩展名 + if (STATIC_RESOURCE_PATTERN.matcher(requestUriLower).find() && + !TEXT_RESOURCE_PATTERN.matcher(requestUriLower).find()) { + return true; + } + + // 检查响应的 Content-Type + HttpHeaders headers = clientResponse.headers().asHttpHeaders(); + MediaType contentType = headers.getContentType(); + if (isBinaryContentType(contentType)) { + return true; + } + } catch (Exception e) { + // 忽略异常,返回 false + } + + return false; + } + + /** + * 判断 Content-Type 是否为二进制类型 + */ + private boolean isBinaryContentType(MediaType contentType) { + if (contentType == null) return false; + String type = contentType.getType().toLowerCase(); + String subtype = contentType.getSubtype().toLowerCase(); + return type.equals("image") || type.equals("font") || + (type.equals("application") && (subtype.contains("octet-stream") || + subtype.contains("pdf") || subtype.contains("zip") || + subtype.contains("rar") || subtype.contains("x-"))); + } + + /** + * 根据 URI 获取 Content-Type + */ + private MediaType getContentTypeFromUri(String lowerUri) { + if (lowerUri.endsWith(".png")) { + return MediaType.IMAGE_PNG; + } else if (lowerUri.endsWith(".jpg") || lowerUri.endsWith(".jpeg")) { + return MediaType.IMAGE_JPEG; + } else if (lowerUri.endsWith(".gif")) { + return MediaType.IMAGE_GIF; + } else if (lowerUri.endsWith(".ico")) { + return new MediaType("image", "x-icon"); + } else if (lowerUri.endsWith(".woff")) { + return new MediaType("font", "woff"); + } else if (lowerUri.endsWith(".woff2")) { + return new MediaType("font", "woff2"); + } else if (lowerUri.endsWith(".ttf")) { + return new MediaType("font", "ttf"); + } else if (lowerUri.endsWith(".eot")) { + return new MediaType("application", "vnd.ms-fontobject"); + } else if (lowerUri.endsWith(".svg")) { + return new MediaType("image", "svg+xml"); + } else if (lowerUri.endsWith(".webp")) { + return new MediaType("image", "webp"); + } else if (lowerUri.endsWith(".bmp")) { + return new MediaType("image", "bmp"); + } + return null; + } + + /** + * 复制请求头 + */ + private void copyRequestHeaders(HttpServletRequest request, HttpHeaders targetHeaders) { + Enumeration headerNames = request.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + if (!"host".equalsIgnoreCase(headerName) + && !"content-length".equalsIgnoreCase(headerName) + && !"connection".equalsIgnoreCase(headerName) + && !"transfer-encoding".equalsIgnoreCase(headerName)) { + Enumeration headerValues = request.getHeaders(headerName); + while (headerValues.hasMoreElements()) { + targetHeaders.add(headerName, headerValues.nextElement()); + } + } + } + // 强制添加 Origin 头(若前端未传,避免代理请求丢失) + String origin = request.getHeader("Origin"); + if (StringUtils.isNotBlank(origin) && !targetHeaders.containsKey("Origin")) { + targetHeaders.add("Origin", origin); + } + } + + /** + * 构建请求体 + */ + private BodyInserter buildBodyInserter(Object body, HttpServletRequest request) { + if (body == null) { + return BodyInserters.empty(); + } + + String contentType = request.getContentType(); + if (StringUtils.isBlank(contentType)) { + return BodyInserters.fromValue(body); + } + + if (contentType.contains(MediaType.MULTIPART_FORM_DATA_VALUE)) { + return BodyInserters.fromValue(body); + } else if (contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { + return BodyInserters.fromValue(body); + } else if (contentType.contains(MediaType.APPLICATION_OCTET_STREAM_VALUE)) { + if (body instanceof byte[]) { + return BodyInserters.fromValue((byte[]) body); + } else { + return BodyInserters.fromValue(body.toString().getBytes(StandardCharsets.UTF_8)); + } + } else { + return BodyInserters.fromValue(body); + } + } + + /** + * 解析路由配置的键值对 + */ + public static Map parseKeyValue(String input) { + Map result = new HashMap<>(); + if (StringUtils.isBlank(input)) { + return result; + } + + Pattern pattern = Pattern.compile("\\[(.*?)](.*)"); + Matcher matcher = pattern.matcher(input.trim()); + if (matcher.find()) { + result.put("key", matcher.group(1).trim()); + result.put("value", matcher.group(2).trim()); + } + return result; + } + + + // 魔数 -> MediaType 映射表(覆盖常见文件格式的标准MIME类型) + private static final Map MAGIC_NUMBER_MEDIA_TYPE_MAP = new HashMap<>(); + + static { + // ========== 图片格式 ========== + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}, MediaType.IMAGE_JPEG); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x89, (byte) 0x50, (byte) 0x4E, (byte) 0x47, (byte) 0x0D, (byte) 0x0A, (byte) 0x1A, (byte) 0x0A}, MediaType.IMAGE_PNG); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38}, MediaType.IMAGE_GIF); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x42, (byte) 0x4D}, MediaType.valueOf("image/bmp")); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x52, (byte) 0x49, (byte) 0x46, (byte) 0x46, -1, -1, -1, -1, (byte) 0x57, (byte) 0x45, (byte) 0x42, (byte) 0x50}, MediaType.valueOf("image/webp")); + // ========== 文档格式 ========== + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x25, (byte) 0x50, (byte) 0x44, (byte) 0x46}, MediaType.APPLICATION_PDF); + // MS Office(doc/xls/ppt) + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0, (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1}, MediaType.valueOf("application/msword")); + // Office Open XML(docx/xlsx/pptx) + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x50, (byte) 0x4B, (byte) 0x03, (byte) 0x04}, MediaType.valueOf("application/vnd.openxmlformats-officedocument.wordprocessingml.document")); + // ========== 压缩包格式 ========== + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x1F, (byte) 0x8B}, MediaType.valueOf("application/gzip")); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x52, (byte) 0x61, (byte) 0x72, (byte) 0x21, (byte) 0x1A, (byte) 0x07}, MediaType.valueOf("application/x-rar-compressed")); + // ========== 音频格式 ========== + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x49, (byte) 0x44, (byte) 0x33}, MediaType.valueOf("audio/mpeg")); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0xFF, (byte) 0xFB}, MediaType.valueOf("audio/mpeg")); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x4F, (byte) 0x67, (byte) 0x67, (byte) 0x53}, MediaType.valueOf("audio/ogg")); + // ========== 视频格式 ========== + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x18, (byte) 0x66, (byte) 0x74, (byte) 0x79, (byte) 0x70}, MediaType.valueOf("video/mp4")); + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x41, (byte) 0x56, (byte) 0x49, (byte) 0x20}, MediaType.valueOf("video/x-msvideo")); // AVI + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0x52, (byte) 0x49, (byte) 0x46, (byte) 0x46, -1, -1, -1, -1, (byte) 0x41, (byte) 0x56, (byte) 0x49, (byte) 0x20}, MediaType.valueOf("video/x-msvideo")); + // ========== 文本格式 ========== + // UTF-8 BOM + MAGIC_NUMBER_MEDIA_TYPE_MAP.put(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}, MediaType.TEXT_PLAIN); + } + + /** + * 从二进制byte数组检测文件内容类型,返回标准MediaType对象 + * + * @param data 二进制字节数组(建议至少包含前16字节) + * @return 匹配的MediaType,无法识别返回 MediaType.APPLICATION_OCTET_STREAM(二进制流) + */ + public static MediaType detectFileMediaType(byte[] data) { + // 空数据返回二进制流 + if (data == null || data.length == 0) { + return MediaType.APPLICATION_OCTET_STREAM; + } + + // 遍历魔数映射表匹配 + for (Map.Entry entry : MAGIC_NUMBER_MEDIA_TYPE_MAP.entrySet()) { + byte[] magicBytes = entry.getKey(); + MediaType mediaType = entry.getValue(); + + // 数据长度不足当前魔数长度,跳过 + if (data.length < magicBytes.length) { + continue; + } + + // 提取数据开头的对应长度字节 + byte[] dataSlice = Arrays.copyOfRange(data, 0, magicBytes.length); + + // 匹配魔数(处理-1表示任意字节的情况) + boolean isMatch = true; + for (int i = 0; i < magicBytes.length; i++) { + byte magicByte = magicBytes[i]; + byte dataByte = dataSlice[i]; + if (magicByte != -1 && magicByte != dataByte) { + isMatch = false; + break; + } + } + + if (isMatch) { + return mediaType; + } + } + + // 额外检测纯文本(非二进制) + if (isPlainText(data)) { + return MediaType.TEXT_PLAIN; + } + + // 未识别的二进制流 + return MediaType.APPLICATION_OCTET_STREAM; + } + + /** + * 辅助方法:判断是否为纯文本(简单检测) + * + * @param data 字节数组 + * @return 是否为纯文本 + */ + private static boolean isPlainText(byte[] data) { + int printableCount = 0; + // 最多检查前1024字节,避免大文件性能问题 + int checkLength = Math.min(data.length, 1024); + for (int i = 0; i < checkLength; i++) { + byte b = data[i]; + // 可打印ASCII字符(32-126)、换行(10)、回车(13)、制表符(9) + if ((b >= 32 && b <= 126) || b == 10 || b == 13 || b == 9) { + printableCount++; + } + } + // 可打印字符占比超过90%判定为纯文本 + return (double) printableCount / checkLength > 0.9; + } + + /** + * 对URL中的QueryString参数值进行URL编码(UTF-8) + * + * @param originalUrl 原始URL(包含中文等特殊字符的QueryString) + * @return 编码后的完整URL + */ + public static String encodeUrlQueryParams(String originalUrl) { + try { + // 空值校验 + if (originalUrl == null || originalUrl.isEmpty()) { + return originalUrl; + } + + // 解析URL为URI对象,方便拆分各部分 + URI uri = new URI(originalUrl); + String scheme = uri.getScheme(); // http/https等 + String host = uri.getHost(); // 域名 + int port = uri.getPort(); // 端口 + String path = uri.getPath(); // 路径 + String query = uri.getQuery(); // QueryString部分 + String fragment = uri.getFragment(); // 锚点部分(#后面的内容) + + // 如果没有QueryString,直接返回原URL + if (query == null || query.isEmpty()) { + return originalUrl; + } + + // 解析并编码QueryString参数 + String encodedQuery = encodeQueryParams(query); + + // 重新构建URI + URI encodedUri = new URI( + scheme, + null, // userInfo + host, + port, + path, + encodedQuery, + fragment + ); + return encodedUri.toString(); + } catch (Throwable throwable) { + return originalUrl; + } + } + + /** + * 解析并编码QueryString参数(保留参数名,仅编码参数值) + * + * @param queryString 原始QueryString(如:text=你好,世界&name=test) + * @return 编码后的QueryString + */ + private static String encodeQueryParams(String queryString) { + try { + Map paramMap = new LinkedHashMap<>(); + String[] params = queryString.split("&"); + // 拆分参数名和参数值 + for (String param : params) { + String[] keyValue = param.split("=", 2); // 只拆分第一个=,避免值中包含=的情况 + String key = keyValue[0]; + String value = keyValue.length > 1 ? keyValue[1] : ""; + // 对参数值进行URL编码(RFC 3986标准,替换空格为%20而非+) + String encodedValue = URLEncoder.encode(value, StandardCharsets.UTF_8) + .replace("+", "%20") // 替换URLEncoder默认的+为%20 + .replace("*", "%2A") + .replace("%7E", "~"); + paramMap.put(key, encodedValue); + } + // 重新拼接编码后的参数 + StringBuilder encodedQuery = new StringBuilder(); + for (Map.Entry entry : paramMap.entrySet()) { + if (!encodedQuery.isEmpty()) { + encodedQuery.append("&"); + } + encodedQuery.append(entry.getKey()) + .append("=") + .append(entry.getValue()); + } + return encodedQuery.toString(); + } catch (Throwable throwable) { + return queryString; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoCancelAction.java b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoCancelAction.java index d470788..9652a1c 100644 --- a/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoCancelAction.java +++ b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoCancelAction.java @@ -20,7 +20,7 @@ import javax.annotation.Resource; ) @ApiDocActionConfiguration( response = { - SystemAsyncTaskInfoDetailResponse.class + Void.class } ) public class ApiSystemAsyncTaskInfoCancelAction extends ProtoActionHandler { diff --git a/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoClearAction.java b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoClearAction.java new file mode 100644 index 0000000..685b28b --- /dev/null +++ b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoClearAction.java @@ -0,0 +1,35 @@ +package com.iqudoo.platform.application.domain.actions.apiSystemAsyncTaskInfo; + +import com.iqudoo.framework.tape.base.action.annotation.ActionConfig; +import com.iqudoo.framework.tape.base.action.annotation.ActionMapping; +import com.iqudoo.framework.tape.base.action.handler.ProtoActionHandler; +import com.iqudoo.framework.tape.base.docs.annotation.ApiDocActionConfiguration; +import com.iqudoo.framework.tape.modules.proto.Proto; +import com.iqudoo.platform.application.constants.BizConstants; +import com.iqudoo.platform.application.facade.request.SystemAsyncTaskInfoClearParam; +import com.iqudoo.platform.application.facade.service.ISystemAsyncTaskInfoService; + +import javax.annotation.Resource; + +@ActionMapping(action = "api.system.async.task.info.clear") +@ActionConfig( + group = { + BizConstants.AUTH_GROUP_SYS_ADMIN + } +) +@ApiDocActionConfiguration( + response = { + Void.class + } +) +public class ApiSystemAsyncTaskInfoClearAction extends ProtoActionHandler { + + @Resource + private ISystemAsyncTaskInfoService systemAsyncTaskInfoService; + + @Override + public Proto onHandleRequest(SystemAsyncTaskInfoClearParam param) throws Throwable { + return systemAsyncTaskInfoService.doClear(param); + } + +} diff --git a/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoDeleteAction.java b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoDeleteAction.java index 831dab7..61f3078 100644 --- a/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoDeleteAction.java +++ b/src/main/java/com/iqudoo/platform/application/domain/actions/apiSystemAsyncTaskInfo/ApiSystemAsyncTaskInfoDeleteAction.java @@ -7,7 +7,6 @@ import com.iqudoo.framework.tape.base.docs.annotation.ApiDocActionConfiguration; import com.iqudoo.framework.tape.modules.proto.Proto; import com.iqudoo.platform.application.constants.BizConstants; import com.iqudoo.platform.application.facade.request.SystemAsyncTaskInfoDeleteParam; -import com.iqudoo.platform.application.facade.response.SystemAsyncTaskInfoDetailResponse; import com.iqudoo.platform.application.facade.service.ISystemAsyncTaskInfoService; import javax.annotation.Resource; @@ -20,7 +19,7 @@ import javax.annotation.Resource; ) @ApiDocActionConfiguration( response = { - SystemAsyncTaskInfoDetailResponse.class + Void.class } ) public class ApiSystemAsyncTaskInfoDeleteAction extends ProtoActionHandler { diff --git a/src/main/java/com/iqudoo/platform/application/domain/service/SystemAdminInfoAuthServiceImpl.java b/src/main/java/com/iqudoo/platform/application/domain/service/SystemAdminInfoAuthServiceImpl.java index a6a4276..f57f440 100644 --- a/src/main/java/com/iqudoo/platform/application/domain/service/SystemAdminInfoAuthServiceImpl.java +++ b/src/main/java/com/iqudoo/platform/application/domain/service/SystemAdminInfoAuthServiceImpl.java @@ -1,125 +1,31 @@ package com.iqudoo.platform.application.domain.service; -import com.iqudoo.framework.tape.Tape; -import com.iqudoo.framework.tape.base.action.bean.ActionAuthInfo; -import com.iqudoo.framework.tape.base.action.bean.ActionState; -import com.iqudoo.framework.tape.base.action.bean.ActionTokenInfo; -import com.iqudoo.framework.tape.modules.IModuleSession; -import com.iqudoo.framework.tape.modules.asserts.AssertUtil; import com.iqudoo.framework.tape.modules.proto.Proto; import com.iqudoo.framework.tape.modules.proto.ProtoLoader; import com.iqudoo.framework.tape.modules.proto.ProtoUtil; -import com.iqudoo.framework.tape.modules.session.RefreshSessionParam; -import com.iqudoo.framework.tape.modules.utils.DigestUtil; -import com.iqudoo.framework.tape.modules.utils.RandomUtil; -import com.iqudoo.platform.application.constants.ErrorConstants; -import com.iqudoo.platform.application.database.model.SystemAdminInfo; -import com.iqudoo.platform.application.database.model.SystemAdminInfoExample; -import com.iqudoo.platform.application.facade.enums.AuthTokenGroupEnum; -import com.iqudoo.platform.application.facade.enums.SystemAdminInfoOptStatusEnum; -import com.iqudoo.platform.application.facade.enums.SystemAdminInfoOptSuperEnum; -import com.iqudoo.platform.application.facade.repository.ISystemAdminInfoRepository; +import com.iqudoo.platform.application.domain.transactionals.SystemAdminInfoAuthTransactional; import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthEditPasswordParam; import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthEditProfileParam; import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthLoginParam; -import com.iqudoo.platform.application.facade.request.SystemAdminInfoCreateParam; import com.iqudoo.platform.application.facade.response.SystemAdminInfoAuthLoginResponse; -import com.iqudoo.platform.application.facade.response.SystemAdminInfoDetailResponse; import com.iqudoo.platform.application.facade.service.ISystemAdminInfoAuthService; -import com.iqudoo.platform.application.facade.service.ISystemAdminInfoService; import org.springframework.stereotype.Service; import javax.annotation.Resource; -import java.util.Objects; @Service @SuppressWarnings("DuplicatedCode") public class SystemAdminInfoAuthServiceImpl implements ISystemAdminInfoAuthService { - private final String initAdminNickname = "超级管理员"; - private final String initAdminUsername = "admin"; - private final String initAdminPassword = "admin"; @Resource - private ISystemAdminInfoRepository systemAdminInfoRepository; - @Resource - private ISystemAdminInfoService systemAdminInfoService; + private SystemAdminInfoAuthTransactional systemAdminInfoAuthTransactional; @Override public Proto doLogin(SystemAdminInfoAuthLoginParam param) throws Throwable { return ProtoUtil.load(param, new ProtoLoader() { @Override public SystemAdminInfoAuthLoginResponse onLoad(SystemAdminInfoAuthLoginParam loadParam) throws Throwable { - AssertUtil.oneNotInvalid(loadParam.getUsername(), "账号不能为空"); - AssertUtil.oneNotInvalid(loadParam.getPassword(), "密码不能为空"); - SystemAdminInfoExample systemAdminInfoExample = new SystemAdminInfoExample(); - systemAdminInfoExample.createCriteria() - .andOptSuperEqualTo(SystemAdminInfoOptSuperEnum.SUPER.getValue()); - long superCount = systemAdminInfoRepository.count(systemAdminInfoExample); - if (superCount <= 0) { - if (Objects.equals(loadParam.getUsername(), initAdminUsername) - && (Objects.equals(loadParam.getPassword(), - initAdminPassword) || Objects.equals(loadParam.getPassword(), - DigestUtil.md5DigestAsHex(initAdminPassword)))) { - SystemAdminInfoCreateParam createParam = new SystemAdminInfoCreateParam(); - createParam.setNickname(initAdminNickname); - createParam.setUsername(initAdminUsername); - createParam.setPassword(initAdminPassword); - createParam.setOptStatus(SystemAdminInfoOptStatusEnum.ENABLE.getValue()); - createParam.setOptSuper(SystemAdminInfoOptSuperEnum.SUPER.getValue()); - Proto systemAdminInfoDetailResponseProto - = systemAdminInfoService.doCreate(createParam); - if (!systemAdminInfoDetailResponseProto.isSuccess()) { - throw systemAdminInfoDetailResponseProto.getErr(); - } - } - } - SystemAdminInfoExample whereDoExample = new SystemAdminInfoExample(); - whereDoExample.createCriteria() - .andUsernameEqualTo(loadParam.getUsername()); - SystemAdminInfo aDo = systemAdminInfoRepository - .findOne(whereDoExample); - if (aDo == null) { - throw ErrorConstants.AUTH_USER_NOT_EXIST; - } - // check password - String passwordHash = loadParam.getPassword(); - String passwordEncode = DigestUtil.md5DigestAsHex(aDo.getPwdSlot() - + passwordHash); - if (!aDo.getPwdMd5().equals(passwordEncode)) { - throw ErrorConstants.AUTH_USER_PASSWORD_ERROR; - } - // check status - if (aDo.getOptStatus() == SystemAdminInfoOptStatusEnum.DISABLE.getValue()) { - throw ErrorConstants.AUTH_USER_NOT_ENABLE; - } - String userGroup = AuthTokenGroupEnum.SYS_ADMIN.getValue(); - String originPlatform = loadParam.getRequestPlatform(); - String originDomain = loadParam.getClientDomain(); - String originIp = loadParam.getClientIpAddress(); - RefreshSessionParam refreshSessionParam = new RefreshSessionParam(); - refreshSessionParam.setUserGuid(aDo.getGuid()); - refreshSessionParam.setUserGroup(userGroup); - refreshSessionParam.setOriginPlatform(originPlatform); - refreshSessionParam.setOriginDomain(originDomain); - refreshSessionParam.setOriginIp(originIp); - refreshSessionParam.setRefresh(false); - String curSessionKey = Tape.getService(IModuleSession.class) - .refreshSession(refreshSessionParam) + aDo.getPwdSlot(); - ActionAuthInfo actionAuthInfo = new ActionAuthInfo(); - actionAuthInfo.setOriginPlatform(originPlatform); - actionAuthInfo.setUserGroup(userGroup); - actionAuthInfo.setUserGuid(aDo.getGuid()); - actionAuthInfo.setSessionKey(curSessionKey); - // login success - ActionTokenInfo actionTokenInfo = actionAuthInfo.createToken(); - SystemAdminInfoAuthLoginResponse systemAdminLoginResponse = new SystemAdminInfoAuthLoginResponse(); - systemAdminLoginResponse.setToken(actionTokenInfo.getToken()); - systemAdminLoginResponse.setRefreshToken(actionTokenInfo.getRefreshToken()); - systemAdminLoginResponse.setExpiresIn(actionTokenInfo.getExpiresIn()); - systemAdminLoginResponse.setRefreshExpiresIn(actionTokenInfo.getRefreshExpiresIn()); - // set user info - loadParam.getActionRequest().setAuthInfo(actionAuthInfo); - return systemAdminLoginResponse; + return systemAdminInfoAuthTransactional.handleLogin(loadParam); } }); @@ -130,32 +36,7 @@ public class SystemAdminInfoAuthServiceImpl implements ISystemAdminInfoAuthServi return ProtoUtil.load(param, new ProtoLoader() { @Override public Void onLoad(SystemAdminInfoAuthEditPasswordParam loadParam) throws Throwable { - AssertUtil.oneNotInvalid(loadParam.getGuid(), "GUID不能为空"); - AssertUtil.oneNotInvalid(loadParam.getPassword(), "密码不能为空"); - AssertUtil.oneNotInvalid(loadParam.getNewPassword(), "新密码不能为空"); - SystemAdminInfo systemAdminInfo = systemAdminInfoRepository.findValidById(loadParam.getGuid()); - if (systemAdminInfo == null) { - throw ErrorConstants.AUTH_USER_NOT_EXIST; - } - // check old password - String passwordHash = loadParam.getPassword(); - String newPasswordHash = loadParam.getNewPassword(); - String passwordEncode = DigestUtil.md5DigestAsHex(systemAdminInfo.getPwdSlot() - + passwordHash); - if (!systemAdminInfo.getPwdMd5().equals(passwordEncode)) { - throw ErrorConstants.AUTH_USER_PASSWORD_ERROR; - } - if (passwordHash.equals(newPasswordHash)) { - throw ErrorConstants.AUTH_USER_NEW_PASSWORD_ERROR; - } - String pwdSlot = RandomUtil.getRandomString(6); - systemAdminInfo.setPwdMd5(DigestUtil.md5DigestAsHex(pwdSlot - + loadParam.getNewPassword())); - systemAdminInfo.setPwdSlot(pwdSlot); - int update = systemAdminInfoRepository.update(systemAdminInfo); - if (update == 0) { - throw ActionState.getRequestToFrequentError(); - } + systemAdminInfoAuthTransactional.handleEditPassword(loadParam); return null; } @@ -167,18 +48,7 @@ public class SystemAdminInfoAuthServiceImpl implements ISystemAdminInfoAuthServi return ProtoUtil.load(param, new ProtoLoader() { @Override public Void onLoad(SystemAdminInfoAuthEditProfileParam loadParam) throws Throwable { - AssertUtil.oneNotInvalid(loadParam.getGuid(), "GUID不能为空"); - SystemAdminInfo systemAdminInfo = systemAdminInfoRepository.findValidById(loadParam.getGuid()); - if (systemAdminInfo == null) { - throw new ActionState("账号不存在"); - } - if (loadParam.getNickname() != null && !loadParam.getNickname().isEmpty()) { - systemAdminInfo.setNickname(loadParam.getNickname()); - } - int update = systemAdminInfoRepository.update(systemAdminInfo); - if (update == 0) { - throw ActionState.getRequestToFrequentError(); - } + systemAdminInfoAuthTransactional.handleEditProfile(loadParam); return null; } diff --git a/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAdminInfoAuthTransactional.java b/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAdminInfoAuthTransactional.java new file mode 100644 index 0000000..e334859 --- /dev/null +++ b/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAdminInfoAuthTransactional.java @@ -0,0 +1,167 @@ +package com.iqudoo.platform.application.domain.transactionals; + +import com.iqudoo.framework.tape.Tape; +import com.iqudoo.framework.tape.base.action.bean.ActionAuthInfo; +import com.iqudoo.framework.tape.base.action.bean.ActionState; +import com.iqudoo.framework.tape.base.action.bean.ActionTokenInfo; +import com.iqudoo.framework.tape.modules.IModuleSession; +import com.iqudoo.framework.tape.modules.asserts.AssertUtil; +import com.iqudoo.framework.tape.modules.proto.Proto; +import com.iqudoo.framework.tape.modules.session.RefreshSessionParam; +import com.iqudoo.framework.tape.modules.utils.DigestUtil; +import com.iqudoo.framework.tape.modules.utils.RandomUtil; +import com.iqudoo.platform.application.constants.ErrorConstants; +import com.iqudoo.platform.application.database.model.SystemAdminInfo; +import com.iqudoo.platform.application.database.model.SystemAdminInfoExample; +import com.iqudoo.platform.application.facade.enums.AuthTokenGroupEnum; +import com.iqudoo.platform.application.facade.enums.SystemAdminInfoOptStatusEnum; +import com.iqudoo.platform.application.facade.enums.SystemAdminInfoOptSuperEnum; +import com.iqudoo.platform.application.facade.repository.ISystemAdminInfoRepository; +import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthEditPasswordParam; +import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthEditProfileParam; +import com.iqudoo.platform.application.facade.request.SystemAdminInfoAuthLoginParam; +import com.iqudoo.platform.application.facade.request.SystemAdminInfoCreateParam; +import com.iqudoo.platform.application.facade.response.SystemAdminInfoAuthLoginResponse; +import com.iqudoo.platform.application.facade.response.SystemAdminInfoDetailResponse; +import com.iqudoo.platform.application.facade.service.ISystemAdminInfoService; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Objects; + +@SuppressWarnings({"DuplicatedCode", "FieldCanBeLocal"}) +@Component +public class SystemAdminInfoAuthTransactional { + + private final String initAdminNickname = "超级管理员"; + private final String initAdminUsername = "admin"; + private final String initAdminPassword = "admin"; + + @Resource + private ISystemAdminInfoRepository systemAdminInfoRepository; + @Resource + private ISystemAdminInfoService systemAdminInfoService; + + @Transactional(rollbackFor = Throwable.class) + public SystemAdminInfoAuthLoginResponse handleLogin(SystemAdminInfoAuthLoginParam loadParam) throws Throwable { + AssertUtil.oneNotInvalid(loadParam.getUsername(), "账号不能为空"); + AssertUtil.oneNotInvalid(loadParam.getPassword(), "密码不能为空"); + SystemAdminInfoExample systemAdminInfoExample = new SystemAdminInfoExample(); + systemAdminInfoExample.createCriteria() + .andOptSuperEqualTo(SystemAdminInfoOptSuperEnum.SUPER.getValue()); + long superCount = systemAdminInfoRepository.count(systemAdminInfoExample); + if (superCount <= 0) { + if (Objects.equals(loadParam.getUsername(), initAdminUsername) + && (Objects.equals(loadParam.getPassword(), + initAdminPassword) || Objects.equals(loadParam.getPassword(), + DigestUtil.md5DigestAsHex(initAdminPassword)))) { + SystemAdminInfoCreateParam createParam = new SystemAdminInfoCreateParam(); + createParam.setNickname(initAdminNickname); + createParam.setUsername(initAdminUsername); + createParam.setPassword(initAdminPassword); + createParam.setOptStatus(SystemAdminInfoOptStatusEnum.ENABLE.getValue()); + createParam.setOptSuper(SystemAdminInfoOptSuperEnum.SUPER.getValue()); + Proto systemAdminInfoDetailResponseProto + = systemAdminInfoService.doCreate(createParam); + if (!systemAdminInfoDetailResponseProto.isSuccess()) { + throw systemAdminInfoDetailResponseProto.getErr(); + } + } + } + SystemAdminInfoExample whereDoExample = new SystemAdminInfoExample(); + whereDoExample.createCriteria() + .andUsernameEqualTo(loadParam.getUsername()); + SystemAdminInfo aDo = systemAdminInfoRepository + .findOne(whereDoExample); + if (aDo == null) { + throw ErrorConstants.AUTH_USER_NOT_EXIST; + } + // check password + String passwordHash = loadParam.getPassword(); + String passwordEncode = DigestUtil.md5DigestAsHex(aDo.getPwdSlot() + + passwordHash); + if (!aDo.getPwdMd5().equals(passwordEncode)) { + throw ErrorConstants.AUTH_USER_PASSWORD_ERROR; + } + // check status + if (aDo.getOptStatus() == SystemAdminInfoOptStatusEnum.DISABLE.getValue()) { + throw ErrorConstants.AUTH_USER_NOT_ENABLE; + } + String userGroup = AuthTokenGroupEnum.SYS_ADMIN.getValue(); + String originPlatform = loadParam.getRequestPlatform(); + String originDomain = loadParam.getClientDomain(); + String originIp = loadParam.getClientIpAddress(); + RefreshSessionParam refreshSessionParam = new RefreshSessionParam(); + refreshSessionParam.setUserGuid(aDo.getGuid()); + refreshSessionParam.setUserGroup(userGroup); + refreshSessionParam.setOriginPlatform(originPlatform); + refreshSessionParam.setOriginDomain(originDomain); + refreshSessionParam.setOriginIp(originIp); + refreshSessionParam.setRefresh(false); + String curSessionKey = Tape.getService(IModuleSession.class) + .refreshSession(refreshSessionParam) + aDo.getPwdSlot(); + ActionAuthInfo actionAuthInfo = new ActionAuthInfo(); + actionAuthInfo.setOriginPlatform(originPlatform); + actionAuthInfo.setUserGroup(userGroup); + actionAuthInfo.setUserGuid(aDo.getGuid()); + actionAuthInfo.setSessionKey(curSessionKey); + // login success + ActionTokenInfo actionTokenInfo = actionAuthInfo.createToken(); + SystemAdminInfoAuthLoginResponse systemAdminLoginResponse = new SystemAdminInfoAuthLoginResponse(); + systemAdminLoginResponse.setToken(actionTokenInfo.getToken()); + systemAdminLoginResponse.setRefreshToken(actionTokenInfo.getRefreshToken()); + systemAdminLoginResponse.setExpiresIn(actionTokenInfo.getExpiresIn()); + systemAdminLoginResponse.setRefreshExpiresIn(actionTokenInfo.getRefreshExpiresIn()); + // set user info + loadParam.getActionRequest().setAuthInfo(actionAuthInfo); + return systemAdminLoginResponse; + } + + @Transactional(rollbackFor = Throwable.class) + public void handleEditProfile(SystemAdminInfoAuthEditProfileParam loadParam) throws Throwable { + AssertUtil.oneNotInvalid(loadParam.getGuid(), "GUID不能为空"); + SystemAdminInfo systemAdminInfo = systemAdminInfoRepository.findValidById(loadParam.getGuid()); + if (systemAdminInfo == null) { + throw new ActionState("账号不存在"); + } + if (loadParam.getNickname() != null && !loadParam.getNickname().isEmpty()) { + systemAdminInfo.setNickname(loadParam.getNickname()); + } + int update = systemAdminInfoRepository.update(systemAdminInfo); + if (update == 0) { + throw ActionState.getRequestToFrequentError(); + } + } + + @Transactional(rollbackFor = Throwable.class) + public void handleEditPassword(SystemAdminInfoAuthEditPasswordParam loadParam) throws Throwable { + AssertUtil.oneNotInvalid(loadParam.getGuid(), "GUID不能为空"); + AssertUtil.oneNotInvalid(loadParam.getPassword(), "密码不能为空"); + AssertUtil.oneNotInvalid(loadParam.getNewPassword(), "新密码不能为空"); + SystemAdminInfo systemAdminInfo = systemAdminInfoRepository.findValidById(loadParam.getGuid()); + if (systemAdminInfo == null) { + throw ErrorConstants.AUTH_USER_NOT_EXIST; + } + // check old password + String passwordHash = loadParam.getPassword(); + String newPasswordHash = loadParam.getNewPassword(); + String passwordEncode = DigestUtil.md5DigestAsHex(systemAdminInfo.getPwdSlot() + + passwordHash); + if (!systemAdminInfo.getPwdMd5().equals(passwordEncode)) { + throw ErrorConstants.AUTH_USER_PASSWORD_ERROR; + } + if (passwordHash.equals(newPasswordHash)) { + throw ErrorConstants.AUTH_USER_NEW_PASSWORD_ERROR; + } + String pwdSlot = RandomUtil.getRandomString(6); + systemAdminInfo.setPwdMd5(DigestUtil.md5DigestAsHex(pwdSlot + + loadParam.getNewPassword())); + systemAdminInfo.setPwdSlot(pwdSlot); + int update = systemAdminInfoRepository.update(systemAdminInfo); + if (update == 0) { + throw ActionState.getRequestToFrequentError(); + } + } + +} diff --git a/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAsyncTaskInfoTransactional.java b/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAsyncTaskInfoTransactional.java index 4372c39..e91ff80 100644 --- a/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAsyncTaskInfoTransactional.java +++ b/src/main/java/com/iqudoo/platform/application/domain/transactionals/SystemAsyncTaskInfoTransactional.java @@ -176,6 +176,10 @@ public class SystemAsyncTaskInfoTransactional { && !Objects.equals(loadParam.getBizId(), systemAsyncTaskInfo.getBizId())) { throw new ActionState("异步任务业务编号不匹配"); } + int update = systemAsyncTaskInfoRepository.deleteById(systemAsyncTaskInfo.getGuid(), true); + if (update == 0) { + throw ActionState.getRequestToFrequentError(); + } SystemAsyncTaskInfoCreateParam systemAsyncTaskInfoCreateParam = new SystemAsyncTaskInfoCreateParam(); systemAsyncTaskInfoCreateParam.setTransId(systemAsyncTaskInfo.getTransId()); @@ -213,10 +217,18 @@ public class SystemAsyncTaskInfoTransactional { // 大于24小时的已完成的异步任务,定时清除记录 SystemAsyncTaskInfoExample clearTaskExample = new SystemAsyncTaskInfoExample(); - clearTaskExample.createCriteria() - .andCreateTimeLessThanOrEqualTo(new Date(new Date().getTime() - - 24 * 60 * 60 * 1000L)) - .andOptStatusEqualTo(SystemAsyncTaskInfoOptStatusEnum.FULFILLED.getValue()); + SystemAsyncTaskInfoExample.Criteria criteria + = clearTaskExample.createCriteria(); + if (loadParam.getBizType() != null && !loadParam.getBizType().isEmpty()) { + criteria.andBizTypeEqualTo(loadParam.getBizType()); + } + if (loadParam.getBizId() != null && !loadParam.getBizId().isEmpty()) { + criteria.andBizIdEqualTo(loadParam.getBizId()); + } + if (loadParam.getClearTime() > 0) { + criteria.andCreateTimeLessThanOrEqualTo(new Date(loadParam.getClearTime())); + } + criteria.andOptStatusEqualTo(SystemAsyncTaskInfoOptStatusEnum.FULFILLED.getValue()); long count = systemAsyncTaskInfoRepository.count(clearTaskExample); if (count > 0) { systemAsyncTaskInfoRepository.deleteAll(clearTaskExample, true); @@ -395,4 +407,4 @@ public class SystemAsyncTaskInfoTransactional { }); } -} +} \ No newline at end of file diff --git a/src/main/java/com/iqudoo/platform/application/facade/request/SystemAsyncTaskInfoClearParam.java b/src/main/java/com/iqudoo/platform/application/facade/request/SystemAsyncTaskInfoClearParam.java index 4e1d32c..fa69371 100644 --- a/src/main/java/com/iqudoo/platform/application/facade/request/SystemAsyncTaskInfoClearParam.java +++ b/src/main/java/com/iqudoo/platform/application/facade/request/SystemAsyncTaskInfoClearParam.java @@ -1,5 +1,6 @@ package com.iqudoo.platform.application.facade.request; +import com.iqudoo.framework.tape.base.docs.annotation.ApiDocFieldConfiguration; import com.iqudoo.platform.application.facade.base.BaseParam; import lombok.Data; import lombok.EqualsAndHashCode; @@ -8,4 +9,13 @@ import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = false) public class SystemAsyncTaskInfoClearParam extends BaseParam { + @ApiDocFieldConfiguration(desc = "截止时间") + private long clearTime; + + @ApiDocFieldConfiguration(desc = "业务类型") + private String bizType; + + @ApiDocFieldConfiguration(desc = "业务编号") + private String bizId; + } diff --git a/src/main/java/com/iqudoo/platform/application/facade/service/ISystemAsyncTaskInfoService.java b/src/main/java/com/iqudoo/platform/application/facade/service/ISystemAsyncTaskInfoService.java index 2171edf..bec2342 100644 --- a/src/main/java/com/iqudoo/platform/application/facade/service/ISystemAsyncTaskInfoService.java +++ b/src/main/java/com/iqudoo/platform/application/facade/service/ISystemAsyncTaskInfoService.java @@ -22,10 +22,10 @@ public interface ISystemAsyncTaskInfoService { Proto doDelete(SystemAsyncTaskInfoDeleteParam param) throws Throwable; - Proto doReset(SystemAsyncTaskInfoResetParam param) throws Throwable; - Proto doTick(SystemAsyncTaskInfoTickParam param) throws Throwable; + Proto doReset(SystemAsyncTaskInfoResetParam param) throws Throwable; + Proto doSchedule(SystemAsyncTaskInfoScheduleParam param) throws Throwable; Proto doClear(SystemAsyncTaskInfoClearParam param) throws Throwable; diff --git a/src/main/java/com/iqudoo/platform/application/scheduling/LoopScheduling.java b/src/main/java/com/iqudoo/platform/application/scheduling/LoopScheduling.java index caf4e62..57ff933 100644 --- a/src/main/java/com/iqudoo/platform/application/scheduling/LoopScheduling.java +++ b/src/main/java/com/iqudoo/platform/application/scheduling/LoopScheduling.java @@ -9,6 +9,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.Date; @Component public class LoopScheduling { @@ -52,6 +53,7 @@ public class LoopScheduling { } SystemAsyncTaskInfoClearParam systemAsyncTaskInfoClearParam = new SystemAsyncTaskInfoClearParam(); systemAsyncTaskInfoClearParam.setThrowThrowable(true); + systemAsyncTaskInfoClearParam.setClearTime(new Date().getTime() - 24 * 60 * 60 * 1000L); Proto clearProto = systemAsyncTaskInfoService.doClear(systemAsyncTaskInfoClearParam); if (!clearProto.isSuccess()) { LOGGER.error("检查清理异步任务失败", clearProto.getErr()); diff --git a/src/main/java/com/iqudoo/platform/application/setting/SystemGlobalHttpProxyConfig.java b/src/main/java/com/iqudoo/platform/application/setting/SystemGlobalHttpProxyConfig.java new file mode 100644 index 0000000..3e0baf2 --- /dev/null +++ b/src/main/java/com/iqudoo/platform/application/setting/SystemGlobalHttpProxyConfig.java @@ -0,0 +1,23 @@ +package com.iqudoo.platform.application.setting; + +import com.iqudoo.framework.tape.modules.IModuleSetting; +import com.iqudoo.framework.tape.modules.annotation.SettingConfiguration; +import com.iqudoo.framework.tape.modules.annotation.SettingField; +import com.iqudoo.platform.application.constants.BizConstants; +import lombok.Data; + +@Data +@SettingConfiguration(group = BizConstants.SETTING_GROUP_SYSTEM, + key = "system_global_http_proxy_config", + title = "网络代理配置", + desc = "资源代理,适用于非同源资源代理为同源资源,解决跨域问题", + sort = 999998) +public class SystemGlobalHttpProxyConfig { + + @SettingField(label = "域名代理路由", + tips = "域名代理配置,多域名时换行,格式如下:[访问路径]代理域名,对应{代理域名}{资源PATH}的请求路径为{serverUrl}/domain-proxy/{访问路径}{资源PATH}", + type = IModuleSetting.SettingFieldType.TYPE_TEXTAREA, + rows = 6) + private String domainProxyRouterMap = ""; + +}