33 changed files with 1444 additions and 50 deletions
-
35pom.xml
-
200src/main/java/com/example/demo/Util/ExcelUploadUtil.java
-
227src/main/java/com/example/demo/Util/ExecutionContextUtil.java
-
173src/main/java/com/example/demo/Util/FeiShuAlertUtil.java
-
37src/main/java/com/example/demo/Util/RedisLockUtil.java
-
31src/main/java/com/example/demo/Util/RedisUtil.java
-
18src/main/java/com/example/demo/config/EnvConfig.java
-
4src/main/java/com/example/demo/config/RedisConfig.java
-
28src/main/java/com/example/demo/controller/ExportController.java
-
31src/main/java/com/example/demo/controller/GoldDetailController.java
-
4src/main/java/com/example/demo/controller/PermissionController.java
-
44src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java
-
28src/main/java/com/example/demo/domain/export/Goldmingxi.java
-
28src/main/java/com/example/demo/domain/vo/AiEmotionExportRecordVO.java
-
25src/main/java/com/example/demo/domain/vo/ExecutionContext.java
-
26src/main/java/com/example/demo/domain/vo/ExportVo.java
-
25src/main/java/com/example/demo/mapper/AiEmotionMapper.java
-
19src/main/java/com/example/demo/mapper/ExportMapper.java
-
15src/main/java/com/example/demo/mapper/GoldDetailMapper.java
-
19src/main/java/com/example/demo/service/AiEmotionService.java
-
16src/main/java/com/example/demo/service/ExportExcelService.java
-
7src/main/java/com/example/demo/service/GoldDetailService.java
-
1src/main/java/com/example/demo/service/PermissionService.java
-
35src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java
-
253src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java
-
69src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java
-
5src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java
-
14src/main/resources/application.yml
-
17src/main/resources/mapper/AiEmotionMapper.xml
-
18src/main/resources/mapper/ExportMapper.xml
-
15src/main/resources/mapper/GoldDetailMapper.xml
-
17src/main/resources/mapper/PermissionMapper.xml
-
8src/main/resources/mapper/UrlMapper.xml
@ -0,0 +1,200 @@ |
|||||
|
package com.example.demo.Util; |
||||
|
|
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.core.io.FileSystemResource; |
||||
|
import org.springframework.http.*; |
||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory; |
||||
|
import org.springframework.http.converter.StringHttpMessageConverter; |
||||
|
import org.springframework.util.LinkedMultiValueMap; |
||||
|
import org.springframework.util.MultiValueMap; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.io.IOException; |
||||
|
import java.nio.charset.Charset; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* Excel文件上传工具类 |
||||
|
*/ |
||||
|
public class ExcelUploadUtil { |
||||
|
private static final Logger logger = LoggerFactory.getLogger(ExcelUploadUtil.class); |
||||
|
|
||||
|
// 默认配置 |
||||
|
private static final int DEFAULT_CONNECT_TIMEOUT = 30000; // 30秒 |
||||
|
private static final int DEFAULT_READ_TIMEOUT = 60000; // 60秒 |
||||
|
|
||||
|
private final RestTemplate restTemplate; |
||||
|
private final String uploadUrl; |
||||
|
private final Map<String, String> defaultHeaders; |
||||
|
private final Map<String, String> defaultParams; |
||||
|
|
||||
|
/** |
||||
|
* 构造方法 |
||||
|
* |
||||
|
* @param uploadUrl 上传接口URL |
||||
|
*/ |
||||
|
public ExcelUploadUtil(String uploadUrl) { |
||||
|
this(uploadUrl, new HashMap<>(), new HashMap<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 构造方法 |
||||
|
* |
||||
|
* @param uploadUrl 上传接口URL |
||||
|
* @param defaultHeaders 默认请求头 |
||||
|
* @param defaultParams 默认请求参数 |
||||
|
*/ |
||||
|
public ExcelUploadUtil(String uploadUrl, Map<String, String> defaultHeaders, Map<String, String> defaultParams) { |
||||
|
this.uploadUrl = uploadUrl; |
||||
|
this.defaultHeaders = new HashMap<>(defaultHeaders); |
||||
|
this.defaultParams = new HashMap<>(defaultParams); |
||||
|
this.restTemplate = createRestTemplate(DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建RestTemplate (Spring Boot 1.x 兼容版本) |
||||
|
*/ |
||||
|
private RestTemplate createRestTemplate(int connectTimeout, int readTimeout) { |
||||
|
RestTemplate restTemplate = new RestTemplate(); |
||||
|
|
||||
|
// 添加字符串消息转换器 (Spring 1.x 使用Charset而不是StandardCharsets) |
||||
|
restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(Charset.forName("UTF-8"))); |
||||
|
|
||||
|
// 设置超时 (Spring 1.x 方式) |
||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); |
||||
|
factory.setConnectTimeout(connectTimeout); |
||||
|
factory.setReadTimeout(readTimeout); |
||||
|
restTemplate.setRequestFactory(factory); |
||||
|
|
||||
|
return restTemplate; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 上传Excel文件 |
||||
|
* |
||||
|
* @param excelFile Excel文件 |
||||
|
* @param targetDir 目标目录 |
||||
|
* @return 上传结果 |
||||
|
* @throws IOException 文件操作异常 |
||||
|
* @throws UploadException 上传异常 |
||||
|
*/ |
||||
|
public String uploadExcel(File excelFile, String targetDir) throws IOException, UploadException { |
||||
|
return uploadExcel(excelFile, targetDir, new HashMap<>(), new HashMap<>()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 上传Excel文件(带自定义参数) |
||||
|
* |
||||
|
* @param excelFile Excel文件 |
||||
|
* @param targetDir 目标目录 |
||||
|
* @param customHeaders 自定义请求头 |
||||
|
* @param customParams 自定义请求参数 |
||||
|
* @return 上传结果 |
||||
|
* @throws IOException 文件操作异常 |
||||
|
* @throws UploadException 上传异常 |
||||
|
*/ |
||||
|
public String uploadExcel(File excelFile, String targetDir, |
||||
|
Map<String, String> customHeaders, |
||||
|
Map<String, String> customParams) throws IOException, UploadException { |
||||
|
// 验证文件 |
||||
|
validateFile(excelFile); |
||||
|
|
||||
|
try { |
||||
|
// 准备请求 |
||||
|
HttpEntity<MultiValueMap<String, Object>> requestEntity = prepareRequest(excelFile, targetDir, customHeaders, customParams); |
||||
|
|
||||
|
// 执行上传 |
||||
|
ResponseEntity<String> response = restTemplate.exchange( |
||||
|
uploadUrl, |
||||
|
HttpMethod.POST, |
||||
|
requestEntity, |
||||
|
String.class |
||||
|
); |
||||
|
|
||||
|
// 处理响应 |
||||
|
return handleResponse(response, excelFile.getName()); |
||||
|
} catch (Exception e) { |
||||
|
logger.error("Excel文件上传失败: {}", excelFile.getAbsolutePath(), e); |
||||
|
throw new UploadException("文件上传失败: " + e.getMessage(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 验证文件 |
||||
|
*/ |
||||
|
private void validateFile(File file) throws IOException { |
||||
|
if (file == null) { |
||||
|
throw new IOException("文件不能为null"); |
||||
|
} |
||||
|
if (!file.exists()) { |
||||
|
throw new IOException("文件不存在: " + file.getAbsolutePath()); |
||||
|
} |
||||
|
if (!file.isFile()) { |
||||
|
throw new IOException("不是有效的文件: " + file.getAbsolutePath()); |
||||
|
} |
||||
|
if (file.length() == 0) { |
||||
|
throw new IOException("文件内容为空: " + file.getAbsolutePath()); |
||||
|
} |
||||
|
if (!file.getName().toLowerCase().endsWith(".xlsx") && |
||||
|
!file.getName().toLowerCase().endsWith(".xls")) { |
||||
|
throw new IOException("仅支持Excel文件(.xlsx, .xls)"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 准备请求 |
||||
|
*/ |
||||
|
private HttpEntity<MultiValueMap<String, Object>> prepareRequest(File file, String targetDir, |
||||
|
Map<String, String> customHeaders, |
||||
|
Map<String, String> customParams) { |
||||
|
// 设置请求头 |
||||
|
HttpHeaders headers = new HttpHeaders(); |
||||
|
headers.setContentType(MediaType.MULTIPART_FORM_DATA); |
||||
|
|
||||
|
// 添加默认和自定义请求头 |
||||
|
defaultHeaders.forEach(headers::set); |
||||
|
customHeaders.forEach(headers::set); |
||||
|
|
||||
|
// 准备请求体 |
||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); |
||||
|
body.add("file", new FileSystemResource(file)); |
||||
|
body.add("dir", targetDir); |
||||
|
|
||||
|
// 添加默认和自定义参数 |
||||
|
defaultParams.forEach(body::add); |
||||
|
customParams.forEach(body::add); |
||||
|
|
||||
|
return new HttpEntity<>(body, headers); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理响应 |
||||
|
*/ |
||||
|
private String handleResponse(ResponseEntity<String> response, String filename) throws UploadException { |
||||
|
if (response.getStatusCode() == HttpStatus.OK) { |
||||
|
logger.info("文件上传成功: {}", filename); |
||||
|
return response.getBody(); |
||||
|
} else { |
||||
|
String errorMsg = String.format("上传接口返回错误状态码: %d, 响应: %s", |
||||
|
response.getStatusCodeValue(), response.getBody()); |
||||
|
logger.error(errorMsg); |
||||
|
throw new UploadException(errorMsg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 自定义上传异常 |
||||
|
*/ |
||||
|
public static class UploadException extends Exception { |
||||
|
public UploadException(String message) { |
||||
|
super(message); |
||||
|
} |
||||
|
|
||||
|
public UploadException(String message, Throwable cause) { |
||||
|
super(message, cause); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,227 @@ |
|||||
|
package com.example.demo.Util; |
||||
|
|
||||
|
|
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import jakarta.servlet.http.HttpServletRequest; |
||||
|
import org.springframework.web.context.request.RequestContextHolder; |
||||
|
import org.springframework.web.context.request.ServletRequestAttributes; |
||||
|
|
||||
|
|
||||
|
import java.io.BufferedReader; |
||||
|
import java.io.File; |
||||
|
import java.io.IOException; |
||||
|
import java.lang.management.ManagementFactory; |
||||
|
import java.util.*; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
public class ExecutionContextUtil { |
||||
|
|
||||
|
/** |
||||
|
* 获取当前执行环境信息 |
||||
|
* @param request 如果是Web请求,传入HttpServletRequest |
||||
|
* @return 执行环境信息对象 |
||||
|
*/ |
||||
|
/** |
||||
|
* 从Spring上下文获取当前HttpServletRequest |
||||
|
*/ |
||||
|
public static ExecutionContext getExecutionContext() { |
||||
|
ExecutionContext context = new ExecutionContext(); |
||||
|
context.setExecutionTime(new Date()); |
||||
|
|
||||
|
HttpServletRequest request = getCurrentHttpRequest(); |
||||
|
|
||||
|
if (isWebEnvironment(request)) { |
||||
|
// Web API 环境 |
||||
|
context.setExecutionType("API"); |
||||
|
context.setApiUrl(getRealRequestUrl(request)); |
||||
|
context.setRequestParams(getRequestParams(request)); |
||||
|
context.setToken(getRequestToken(request)); |
||||
|
context.setMethod(request.getMethod()); |
||||
|
} else { |
||||
|
// 脚本环境 |
||||
|
context.setExecutionType("SCRIPT"); |
||||
|
context.setScriptFile(getMainClassFile()); |
||||
|
} |
||||
|
|
||||
|
return context; |
||||
|
} |
||||
|
|
||||
|
private static HttpServletRequest getCurrentHttpRequest() { |
||||
|
try { |
||||
|
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); |
||||
|
} catch (IllegalStateException e) { |
||||
|
// 不在Web请求上下文中 |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static boolean isWebEnvironment(HttpServletRequest request) { |
||||
|
return request != null; |
||||
|
} |
||||
|
|
||||
|
private static String getRealRequestUrl(HttpServletRequest request) { |
||||
|
// 1. 获取协议(优先从代理头获取) |
||||
|
String protocol = getHeaderWithFallback(request, |
||||
|
Arrays.asList("X-Forwarded-Proto", "X-Forwarded-Protocol"), |
||||
|
request.getScheme() |
||||
|
); |
||||
|
|
||||
|
// 2. 获取真实域名(优先从代理头获取原始域名) |
||||
|
String domain = getHeaderWithFallback(request, |
||||
|
Arrays.asList( |
||||
|
"X-Original-Host", // 一些代理服务器设置的原始终端 |
||||
|
"X-Real-Host", // 另一个可能的原始主机头 |
||||
|
"X-Forwarded-Host", // 转发的主机头 |
||||
|
"Host" // 最后回退到常规主机头 |
||||
|
), |
||||
|
request.getServerName() |
||||
|
); |
||||
|
|
||||
|
// 3. 获取端口(智能处理默认端口) |
||||
|
Integer port = getRealPort(request, protocol); |
||||
|
|
||||
|
// 4. 获取原始路径(包括QueryString) |
||||
|
String path = getOriginalUri(request); |
||||
|
|
||||
|
// 组装完整URL |
||||
|
return String.format("%s://%s:%s%s", |
||||
|
protocol, |
||||
|
domain, |
||||
|
port, |
||||
|
path |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// 辅助方法:带fallback的header获取 |
||||
|
// 方法1:保持强类型(推荐) |
||||
|
private static String getHeaderWithFallback( |
||||
|
HttpServletRequest request, |
||||
|
List<String> headerNames, // 明确要求String列表 |
||||
|
String defaultValue |
||||
|
) { |
||||
|
return headerNames.stream() |
||||
|
.map(request::getHeader) |
||||
|
.filter(Objects::nonNull) |
||||
|
.findFirst() |
||||
|
.orElse(defaultValue); |
||||
|
} |
||||
|
|
||||
|
// 获取真实端口(处理代理情况) |
||||
|
private static int getRealPort(HttpServletRequest request, String protocol) { |
||||
|
// 优先从代理头获取 |
||||
|
String forwardedPort = request.getHeader("X-Forwarded-Port"); |
||||
|
if (forwardedPort != null) { |
||||
|
return Integer.parseInt(forwardedPort); |
||||
|
} |
||||
|
|
||||
|
// 其次从请求获取 |
||||
|
int port = request.getServerPort(); |
||||
|
|
||||
|
// 处理反向代理场景 |
||||
|
if (port == 80 && "https".equals(protocol)) { |
||||
|
return 443; |
||||
|
} |
||||
|
if (port == 443 && "http".equals(protocol)) { |
||||
|
return 80; |
||||
|
} |
||||
|
return port; |
||||
|
} |
||||
|
|
||||
|
// 获取原始URI(包含QueryString) |
||||
|
private static String getOriginalUri(HttpServletRequest request) { |
||||
|
// 优先从代理头获取原始URI |
||||
|
String originalUri = request.getHeader("X-Original-URI"); |
||||
|
if (originalUri != null) { |
||||
|
return originalUri; |
||||
|
} |
||||
|
|
||||
|
// 默认从request获取 |
||||
|
String queryString = request.getQueryString(); |
||||
|
return request.getRequestURI() + |
||||
|
(queryString != null ? "?" + queryString : ""); |
||||
|
} |
||||
|
|
||||
|
private static String getRequestParams(HttpServletRequest request) { |
||||
|
try { |
||||
|
// 1. 优先读取Query String(无需缓存) |
||||
|
String queryString = request.getQueryString(); |
||||
|
if (queryString != null) return queryString; |
||||
|
|
||||
|
// 2. 检查表单参数(GET/POST都适用) |
||||
|
Map<String, String[]> params = request.getParameterMap(); |
||||
|
if (!params.isEmpty()) return formatParams(params); |
||||
|
|
||||
|
// 3. 只有明确是JSON请求时才尝试读取body |
||||
|
if (isJsonRequest(request)) { |
||||
|
return readJsonBodyOnDemand(request); |
||||
|
} |
||||
|
|
||||
|
return "{}"; |
||||
|
} catch (Exception e) { |
||||
|
return "{\"error\":\"failed to read params\"}"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static String readJsonBodyOnDemand(HttpServletRequest request) throws IOException { |
||||
|
// 关键点:直接读取原始InputStream(不缓存) |
||||
|
try (BufferedReader reader = request.getReader()) { |
||||
|
String body = reader.lines().collect(Collectors.joining()); |
||||
|
return body.isEmpty() ? "{}" : body; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static boolean isJsonRequest(HttpServletRequest request) { |
||||
|
String contentType = request.getContentType(); |
||||
|
return contentType != null && contentType.contains("application/json"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static String formatParams(Map<String, String[]> params) { |
||||
|
// 优化后的参数格式化方法 |
||||
|
return params.entrySet().stream() |
||||
|
.map(entry -> { |
||||
|
String key = escapeJson(entry.getKey()); |
||||
|
String[] values = entry.getValue(); |
||||
|
if (values.length == 1) { |
||||
|
return "\"" + key + "\":\"" + escapeJson(values[0]) + "\""; |
||||
|
} |
||||
|
return "\"" + key + "\":[" + |
||||
|
Arrays.stream(values) |
||||
|
.map(v -> "\"" + escapeJson(v) + "\"") |
||||
|
.collect(Collectors.joining(",")) + |
||||
|
"]"; |
||||
|
}) |
||||
|
.collect(Collectors.joining(",", "{", "}")); |
||||
|
} |
||||
|
|
||||
|
private static String escapeJson(String raw) { |
||||
|
return raw.replace("\\", "\\\\") |
||||
|
.replace("\"", "\\\"") |
||||
|
.replace("\n", "\\n"); |
||||
|
} |
||||
|
|
||||
|
private static String getRequestToken(HttpServletRequest request) { |
||||
|
String token = request.getHeader("Authorization"); |
||||
|
if (token == null) { |
||||
|
token = request.getHeader("token"); |
||||
|
} |
||||
|
return token; |
||||
|
} |
||||
|
|
||||
|
private static String getMainClassFile() { |
||||
|
try { |
||||
|
// 获取主类名 |
||||
|
String mainClass = ManagementFactory.getRuntimeMXBean().getSystemProperties().get("sun.java.command"); |
||||
|
if (mainClass != null) { |
||||
|
// 简单处理,提取主类名 |
||||
|
String className = mainClass.split(" ")[0]; |
||||
|
// 转换为文件路径 |
||||
|
return className.replace('.', File.separatorChar) + ".java"; |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
return "UnknownScript"; |
||||
|
} |
||||
|
} |
@ -0,0 +1,173 @@ |
|||||
|
package com.example.demo.Util; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.config.EnvConfig; |
||||
|
import org.apache.http.HttpResponse; |
||||
|
import org.apache.http.HttpStatus; |
||||
|
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.stereotype.Component; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.*; |
||||
|
|
||||
|
/** |
||||
|
* 飞书报警信息发送工具类 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class FeiShuAlertUtil { |
||||
|
|
||||
|
private static final String FEISHU_WEBHOOK_URL_PROD = "https://open.feishu.cn/open-apis/bot/v2/hook/1a515b19-b64f-46b7-9486-35842b9539fe"; |
||||
|
private static final String FEISHU_WEBHOOK_URL_TEST = "https://open.feishu.cn/open-apis/bot/v2/hook/384c78aa-8df1-498b-9c47-04e890ed9877"; |
||||
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
||||
|
|
||||
|
static { |
||||
|
DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送报警信息到飞书群(紧凑单行格式) |
||||
|
* |
||||
|
* @param context 请求接口 |
||||
|
* @param errorFile 错误文件 |
||||
|
* @param errorLine 错误行数 |
||||
|
* @param errorMsg 错误信息 |
||||
|
* @param params 脚本执行时需要手动传参,如果时API请求则无需传,自动获取参数 |
||||
|
* @return 是否发送成功 |
||||
|
*/ |
||||
|
public static boolean sendAlertMessage(ExecutionContext context, |
||||
|
String errorFile, int errorLine, |
||||
|
String errorMsg, String params) { |
||||
|
JSONObject message = new JSONObject(); |
||||
|
message.put("msg_type", "post"); |
||||
|
|
||||
|
JSONObject content = new JSONObject(); |
||||
|
JSONObject post = new JSONObject(); |
||||
|
JSONObject zhCn = new JSONObject(); |
||||
|
String title = "⚠️ 系统异常报警 ⚠️"; |
||||
|
zhCn.put("title", title); |
||||
|
|
||||
|
List<List<JSONObject>> contentList = new ArrayList<>(); |
||||
|
List<JSONObject> elements = new ArrayList<>(); |
||||
|
|
||||
|
StringBuilder contentBuilder = new StringBuilder(); |
||||
|
contentBuilder.append("------------------------------\n\n"); |
||||
|
|
||||
|
if ("API".equals(context.getExecutionType())) { |
||||
|
contentBuilder.append("**执行类型**: API请求\n\n"); |
||||
|
contentBuilder.append("**请求接口**: ").append(context.getApiUrl()).append("\n\n"); |
||||
|
contentBuilder.append("**请求参数**: ").append(params).append("\n\n"); |
||||
|
contentBuilder.append("**请求方法**: ").append(context.getMethod()).append("\n\n"); |
||||
|
if (context.getToken() != null) { |
||||
|
contentBuilder.append("**请求Token**: ") |
||||
|
.append(context.getToken().length() > 10 ? |
||||
|
context.getToken().substring(0, 10) + "..." : |
||||
|
context.getToken()) |
||||
|
.append("\n\n"); |
||||
|
} |
||||
|
} else { |
||||
|
contentBuilder.append("**执行类型**: 脚本执行\n\n"); |
||||
|
contentBuilder.append("**脚本文件**: ").append(context.getScriptFile()).append("\n\n"); |
||||
|
contentBuilder.append("**请求参数**: ").append(params).append("\n\n"); |
||||
|
} |
||||
|
|
||||
|
contentBuilder.append("**错误位置**: ").append(errorFile).append(":").append(errorLine).append("\n\n"); |
||||
|
contentBuilder.append("**错误信息**: ").append(errorMsg).append("\n\n"); |
||||
|
contentBuilder.append("**执行时间**: ").append(formatDate(context.getExecutionTime())).append("\n\n"); |
||||
|
contentBuilder.append("**报警时间**: ").append(formatDate(new Date())).append("\n\n"); |
||||
|
contentBuilder.append("------------------------------"); |
||||
|
|
||||
|
addContentElement(elements, "text", contentBuilder.toString()); |
||||
|
|
||||
|
contentList.add(elements); |
||||
|
zhCn.put("content", contentList); |
||||
|
post.put("zh_cn", zhCn); |
||||
|
content.put("post", post); |
||||
|
message.put("content", content); |
||||
|
|
||||
|
return sendMessage(message); |
||||
|
} |
||||
|
|
||||
|
private static String formatDate(Date date) { |
||||
|
// 实现日期格式化方法 |
||||
|
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date); |
||||
|
} |
||||
|
|
||||
|
private static void addContentElement(List<JSONObject> elements, String tag, String text) { |
||||
|
JSONObject element = new JSONObject(); |
||||
|
element.put("tag", tag); |
||||
|
element.put("text", text); |
||||
|
elements.add(element); |
||||
|
} |
||||
|
|
||||
|
private static boolean sendMessage(JSONObject message) { |
||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { |
||||
|
String FEISHU_WEBHOOK_URL; |
||||
|
String environment = EnvConfig.ENV; |
||||
|
System.out.println("当前环境变量:" + environment); |
||||
|
if (Objects.equals(environment, "unknown") || environment.equals("dev")) { |
||||
|
FEISHU_WEBHOOK_URL = FEISHU_WEBHOOK_URL_TEST; |
||||
|
} else { |
||||
|
FEISHU_WEBHOOK_URL = FEISHU_WEBHOOK_URL_PROD; |
||||
|
} |
||||
|
HttpPost httpPost = new HttpPost(FEISHU_WEBHOOK_URL); |
||||
|
httpPost.addHeader("Content-Type", "application/json; charset=utf-8"); |
||||
|
|
||||
|
StringEntity entity = new StringEntity(message.toJSONString(), "UTF-8"); |
||||
|
httpPost.setEntity(entity); |
||||
|
|
||||
|
HttpResponse response = httpClient.execute(httpPost); |
||||
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { |
||||
|
String result = EntityUtils.toString(response.getEntity()); |
||||
|
JSONObject obj = JSON.parseObject(result); |
||||
|
return obj.getInteger("code") == 0; |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
System.out.println("发送飞书异常" + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 发送普通信息到飞书群 |
||||
|
* |
||||
|
* @param title 消息标题 |
||||
|
* @param content 消息内容 |
||||
|
* @return 是否发送成功 |
||||
|
*/ |
||||
|
public static boolean sendNormalMessage(String title, String content) { |
||||
|
JSONObject message = new JSONObject(); |
||||
|
message.put("msg_type", "post"); |
||||
|
|
||||
|
JSONObject messageContent = new JSONObject(); |
||||
|
JSONObject post = new JSONObject(); |
||||
|
JSONObject zhCn = new JSONObject(); |
||||
|
zhCn.put("title", title); |
||||
|
|
||||
|
List<List<JSONObject>> contentList = new ArrayList<>(); |
||||
|
List<JSONObject> elements = new ArrayList<>(); |
||||
|
|
||||
|
StringBuilder contentBuilder = new StringBuilder(); |
||||
|
contentBuilder.append("------------------------------\n\n"); |
||||
|
contentBuilder.append(content).append("\n\n"); |
||||
|
contentBuilder.append("**发送时间**: ").append(formatDate(new Date())).append("\n\n"); |
||||
|
contentBuilder.append("------------------------------"); |
||||
|
|
||||
|
addContentElement(elements, "text", contentBuilder.toString()); |
||||
|
|
||||
|
contentList.add(elements); |
||||
|
zhCn.put("content", contentList); |
||||
|
post.put("zh_cn", zhCn); |
||||
|
messageContent.put("post", post); |
||||
|
message.put("content", messageContent); |
||||
|
|
||||
|
return sendMessage(message); |
||||
|
} |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
package com.example.demo.Util; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.StringRedisTemplate; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Component |
||||
|
public class RedisLockUtil { |
||||
|
|
||||
|
@Autowired |
||||
|
private StringRedisTemplate redisTemplate; |
||||
|
|
||||
|
/** |
||||
|
* 尝试获取分布式锁 |
||||
|
* @param lockKey 锁的 Key |
||||
|
* @param requestId 请求 ID(可用 UUID) |
||||
|
* @param expireTime 锁的过期时间(毫秒) |
||||
|
* @return 是否获取成功 |
||||
|
*/ |
||||
|
public boolean tryLock(String lockKey, String requestId, long expireTime) { |
||||
|
return redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 释放分布式锁 |
||||
|
* @param lockKey 锁的 Key |
||||
|
* @param requestId 请求 ID |
||||
|
*/ |
||||
|
public void unlock(String lockKey, String requestId) { |
||||
|
String currentValue = redisTemplate.opsForValue().get(lockKey); |
||||
|
if (requestId.equals(currentValue)) { |
||||
|
redisTemplate.delete(lockKey); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package com.example.demo.config; |
||||
|
|
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName EnvConfig |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 13:55 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Configuration |
||||
|
public class EnvConfig { |
||||
|
public static final String ENV = "dev"; |
||||
|
public static final String ENV_PROD = "prod"; |
||||
|
public static final String ENV_TEST = "test"; |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.example.demo.controller; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Export; |
||||
|
import com.example.demo.domain.vo.Result; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportController |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-28 15:22 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@RestController |
||||
|
@RequestMapping("/export") |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
@CrossOrigin |
||||
|
public class ExportController { |
||||
|
@PostMapping("/export") |
||||
|
public Result export(@RequestBody Export export){ |
||||
|
return null; |
||||
|
} |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AiEmotionExportDTO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-30 15:06 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
public class GoldDetailDTO { |
||||
|
private String token; |
||||
|
private String url = ""; |
||||
|
private String fileName = ""; |
||||
|
private Integer sort = 0; |
||||
|
private String field = ""; |
||||
|
private Integer jwcode; |
||||
|
private Integer type = 0; //类型 |
||||
|
private Integer state = 0; //状态 |
||||
|
private String text = ""; //关键词搜索 |
||||
|
private Integer dataNum = 0; |
||||
|
private String deptid = ""; |
||||
|
|
||||
|
@NotNull(message = "page不能为空") |
||||
|
private Integer page = 1; |
||||
|
@NotNull(message = "pageSize不能为空") |
||||
|
private Integer pageSize = 20; |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return String.format( |
||||
|
"AiEmotionExport(jwcode=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
jwcode, type, state, dataNum |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.example.demo.domain.export; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName goldmingxi |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 17:37 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class Goldmingxi { |
||||
|
private String name; // 名称 |
||||
|
private Integer jwcode; // 精网号 |
||||
|
private String market; // 所属地区 |
||||
|
private String payPlatform; // 支付平台 |
||||
|
private Integer type; // 类型 |
||||
|
private Integer sumGold; // 总金币 |
||||
|
private Integer permentGold; //永久金币 |
||||
|
private Integer freeJune; // 免费金币六月到期 |
||||
|
private Integer freeDecember; // 免费金币七月到期 |
||||
|
private Integer taskGold; // 任务金币 |
||||
|
private String adminName; //提交人 |
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.example.demo.domain.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AiEmotionExportRecordVO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 14:59 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class AiEmotionExportRecordVO { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
private String token; |
||||
|
private Long id; |
||||
|
private Long jwcode; |
||||
|
private String fileName; |
||||
|
private String url; |
||||
|
private Integer state; |
||||
|
private Date createTime; |
||||
|
private Date updateTime; |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
package com.example.demo.domain.vo; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
public class ExecutionContext { |
||||
|
// getters 和 setters |
||||
|
private String executionType; // "API" 或 "SCRIPT" |
||||
|
private String apiUrl; |
||||
|
private String requestParams; |
||||
|
private String token; |
||||
|
private String scriptFile; |
||||
|
private Date executionTime; |
||||
|
private String method; |
||||
|
|
||||
|
// 构造方法 |
||||
|
public ExecutionContext() { |
||||
|
this.executionTime = new Date(); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.example.demo.domain.vo; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportVo |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 17:24 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
|
||||
|
public class ExportVo { |
||||
|
private String token; |
||||
|
private Long id; |
||||
|
private String fileName; |
||||
|
private String url; |
||||
|
private Integer state; |
||||
|
|
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
package com.example.demo.mapper; |
||||
|
|
||||
|
import com.example.demo.domain.vo.AiEmotionExportRecordVO; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
import org.apache.ibatis.annotations.Param; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AiEmotionMapper |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 16:48 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Mapper |
||||
|
public interface AiEmotionMapper { |
||||
|
void updateStatus( |
||||
|
@Param("recordId") Long recordId, |
||||
|
@Param("state") Integer state, |
||||
|
@Param("url") String url, |
||||
|
@Param("reason") String reason, |
||||
|
@Param("dataNum") Integer dataNum |
||||
|
); |
||||
|
AiEmotionExportRecordVO getRecordById(Long recordId); |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package com.example.demo.mapper; |
||||
|
|
||||
|
import com.example.demo.domain.vo.ExportVo; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportMapper |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 17:28 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Mapper |
||||
|
public interface ExportMapper { |
||||
|
ExportVo getExportData(Integer id); |
||||
|
ExportVo updateExportData(Long recordId, Integer state, String url, String reason, Integer dataNum); |
||||
|
|
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package com.example.demo.service; |
||||
|
|
||||
|
import com.example.demo.domain.vo.AiEmotionExportRecordVO; |
||||
|
import com.example.demo.domain.vo.ExportVo; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AiEmotionService |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 14:27 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
public interface AiEmotionService { |
||||
|
|
||||
|
ExportVo updateStatus(Long recordId, int i, String s, String s1, int i1); |
||||
|
AiEmotionExportRecordVO getRecordById(Long id) throws Exception; |
||||
|
|
||||
|
} |
@ -0,0 +1,16 @@ |
|||||
|
package com.example.demo.service; |
||||
|
|
||||
|
import com.example.demo.domain.vo.AiEmotionExportRecordVO; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName exportService |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-28 15:39 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
public interface ExportExcelService { |
||||
|
Exception handleExcelExportData(String message) throws Exception; |
||||
|
|
||||
|
} |
@ -0,0 +1,35 @@ |
|||||
|
package com.example.demo.serviceImpl; |
||||
|
|
||||
|
import com.example.demo.domain.vo.AiEmotionExportRecordVO; |
||||
|
import com.example.demo.domain.vo.ExportVo; |
||||
|
import com.example.demo.mapper.AiEmotionMapper; |
||||
|
import com.example.demo.mapper.ExportMapper; |
||||
|
import com.example.demo.service.AiEmotionService; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AiEmotionServiceImpl |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 14:46 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Service |
||||
|
public class AiEmotionServiceImpl implements AiEmotionService { |
||||
|
@Autowired |
||||
|
private ExportMapper exportMapper; |
||||
|
@Autowired |
||||
|
private AiEmotionMapper aiEmotionMapper; |
||||
|
|
||||
|
@Override |
||||
|
public ExportVo updateStatus(Long recordId, int i, String s, String s1, int i1) { |
||||
|
return exportMapper.updateExportData(recordId, i, s, s1, i1); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public AiEmotionExportRecordVO getRecordById(Long id) throws Exception { |
||||
|
return aiEmotionMapper.getRecordById(id); |
||||
|
} |
||||
|
} |
@ -0,0 +1,253 @@ |
|||||
|
package com.example.demo.serviceImpl; |
||||
|
|
||||
|
import cn.hutool.log.AbstractLog; |
||||
|
import com.alibaba.excel.EasyExcel; |
||||
|
import com.alibaba.excel.ExcelWriter; |
||||
|
import com.alibaba.excel.write.metadata.WriteSheet; |
||||
|
import com.example.demo.Util.ExcelUploadUtil; |
||||
|
import com.example.demo.controller.GoldDetailController; |
||||
|
import com.example.demo.domain.export.Goldmingxi; |
||||
|
import com.example.demo.domain.vo.*; |
||||
|
|
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.example.demo.service.AiEmotionService; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
import java.io.*; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
|
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class ExportExcelServiceImpl implements ExportExcelService { |
||||
|
|
||||
|
private static final ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
|
||||
|
//注入AiEmotionService |
||||
|
@Autowired |
||||
|
private AiEmotionService aiEmotionService; |
||||
|
//注入GoldDetailController |
||||
|
@Autowired |
||||
|
private GoldDetailController goldDetailController; |
||||
|
|
||||
|
// 每页查询的数据量 |
||||
|
private static final int PAGE_SIZE = 1000; |
||||
|
|
||||
|
|
||||
|
@Transactional |
||||
|
@Override |
||||
|
public Exception handleExcelExportData(String message) throws Exception { |
||||
|
System.out.println("明细导出excel数据开始执行:" + message); |
||||
|
long startTime = System.currentTimeMillis(); |
||||
|
Long recordId = null; |
||||
|
String fileName = null; |
||||
|
File tempFile = null; |
||||
|
OutputStream outputStream = null; |
||||
|
ExcelWriter excelWriter = null; |
||||
|
|
||||
|
try { |
||||
|
// 1. 解析JSON任务 |
||||
|
JsonNode rootNode = objectMapper.readTree(message); |
||||
|
// 2. 获取基本参数 |
||||
|
recordId = rootNode.path("recordId").asLong(); |
||||
|
JsonNode requestDataNode = rootNode.path("requestData"); |
||||
|
// 3. 验证导出记录 |
||||
|
AiEmotionExportRecordVO record = validateExportRecord(recordId); |
||||
|
if (record == null) return null; |
||||
|
//4. 更新状态为处理中 |
||||
|
aiEmotionService.updateStatus(recordId, 1, "", "", 0); |
||||
|
// 5. 准备Excel文件 |
||||
|
fileName = record.getFileName(); |
||||
|
// 初始化临时文件(保存到本地临时目录) |
||||
|
tempFile = File.createTempFile("export_", ".xlsx"); |
||||
|
outputStream = new FileOutputStream(tempFile); // 使用文件输出流 |
||||
|
// 从JSON中提取单个值 |
||||
|
String text = requestDataNode.has("text") ? requestDataNode.get("text").asText() : null; |
||||
|
Integer sort = requestDataNode.has("sort") ? requestDataNode.get("sort").asInt() : null; |
||||
|
String field = requestDataNode.has("field") ? requestDataNode.get("field").asText() : null; |
||||
|
String deptId = requestDataNode.has("deptId") ? requestDataNode.get("deptId").asText() : null; |
||||
|
|
||||
|
try { |
||||
|
// 6. 初始化Excel写入器(指向本地文件流) |
||||
|
excelWriter = initExcelWriter(outputStream, "user"); |
||||
|
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build(); |
||||
|
// 7. 分页查询并写入数据 |
||||
|
Page page = new Page(); |
||||
|
page.setPageNum(1); |
||||
|
page.setPageSize(1000); |
||||
|
Integer totalCount = 0; |
||||
|
boolean hasMore = true; |
||||
|
while (hasMore) { |
||||
|
Result pageResult = goldDetailController.getGoldDetail(page); |
||||
|
Integer code = pageResult.getCode(); |
||||
|
Object data = pageResult.getData(); |
||||
|
if (code == 200) { |
||||
|
Map<String, Object> rawData = (Map<String, Object>) data; |
||||
|
Long total = (Long) rawData.get("total"); |
||||
|
List<Map<String, Object>> list = (List<Map<String, Object>>) rawData.get("list"); |
||||
|
// 检查是否还有数据 |
||||
|
if (list == null || list.isEmpty()) { |
||||
|
hasMore = false; |
||||
|
} else { |
||||
|
// 写入数据(注意:finish()应在所有数据写入后调用) |
||||
|
excelWriter.write(list, writeSheet); |
||||
|
page.setPageNum(page.getPageNum() + 1); |
||||
|
totalCount += list.size(); |
||||
|
log.info("导出进度 recordId: {}, 已处理: {}条", recordId, totalCount); |
||||
|
// 检查是否还有更多数据 |
||||
|
hasMore = totalCount < total; |
||||
|
} |
||||
|
} else { |
||||
|
hasMore = false; |
||||
|
log.error("获取数据失败,状态码: {}", code); |
||||
|
} |
||||
|
} |
||||
|
// 7. 完成Excel写入(所有数据写入后关闭写入器) |
||||
|
if (excelWriter != null) { |
||||
|
excelWriter.finish(); |
||||
|
} |
||||
|
if (outputStream != null) { |
||||
|
outputStream.flush(); // 确保所有数据写入 |
||||
|
outputStream.close(); // 关闭文件流 |
||||
|
} |
||||
|
// 检查文件是否存在且不为空 |
||||
|
if (tempFile != null && tempFile.exists() && tempFile.length() > 0) { |
||||
|
// 8. 上传到OSS(读取本地临时文件) |
||||
|
// 获取接口的基础 URL |
||||
|
String uploadUrl = "http://39.101.133.168:8828/hljw/api/aws/upload"; |
||||
|
try { |
||||
|
// 1. 创建上传工具实例 |
||||
|
ExcelUploadUtil uploadUtil = new ExcelUploadUtil(uploadUrl); |
||||
|
|
||||
|
// 2. 准备要上传的文件 |
||||
|
File excelFile = new File(tempFile.toURI()); |
||||
|
try { |
||||
|
// 3. 执行上传 |
||||
|
String result = uploadUtil.uploadExcel(excelFile, "export/excel/"); |
||||
|
// 1. 解析JSON任务 |
||||
|
JsonNode uploadResult = objectMapper.readTree(result); |
||||
|
long code = uploadResult.path("code").asLong(); |
||||
|
String url = String.valueOf(uploadResult.path("data")); |
||||
|
url = url.replace("\"", ""); |
||||
|
if (code == 1) { |
||||
|
// 3. 验证导出记录decodecode |
||||
|
aiEmotionService.updateStatus(recordId, 2, url, "", totalCount); |
||||
|
} else { |
||||
|
//更新失败 |
||||
|
aiEmotionService.updateStatus(recordId, 3, "", url, 0); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
//更新失败 |
||||
|
aiEmotionService.updateStatus(recordId, 3, "", StringUtils.substring(e.getMessage(), 0, 500), 0); |
||||
|
throw new Exception("文件上传云端失败1", e); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("上传文件失败 recordId: {}, 文件名: {}", recordId, fileName, e); |
||||
|
//更新状态为失败 |
||||
|
if (recordId != null) { |
||||
|
aiEmotionService.updateStatus(recordId, 3, "", StringUtils.substring(e.getMessage(), 0, 500), 0); |
||||
|
} |
||||
|
throw new Exception("文件上传云端失败2", e); |
||||
|
} |
||||
|
} else { |
||||
|
throw new Exception("导出的Excel文件不存在或为空"); |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
System.out.println("导出异常" + e.getMessage()); |
||||
|
log.error("导出任务处理失败 recordId: {}", recordId, e); |
||||
|
// 更新状态为失败 |
||||
|
if (recordId != null) { |
||||
|
aiEmotionService.updateStatus(recordId, 3, "", StringUtils.substring(e.getMessage(), 0, 500), 0); |
||||
|
} |
||||
|
throw new Exception("导出异常", e); |
||||
|
} finally { |
||||
|
// 确保资源被关闭 |
||||
|
try { |
||||
|
if (excelWriter != null) { |
||||
|
excelWriter.finish(); |
||||
|
} |
||||
|
if (outputStream != null) { |
||||
|
outputStream.close(); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("关闭资源失败", e); |
||||
|
throw new Exception("excel文件关闭资源失败", e); |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("导出任务处理失败 recordId: {}", recordId, e); |
||||
|
// 更新状态为失败 |
||||
|
if (recordId != null) { |
||||
|
aiEmotionService.updateStatus(recordId, 3, "", StringUtils.substring(e.getMessage(), 0, 500), 0); |
||||
|
} |
||||
|
System.out.println("<导出失败>" + e.getMessage()); |
||||
|
throw new Exception("导出任务处理失败", e); |
||||
|
} finally { |
||||
|
// 清理临时文件 |
||||
|
if (tempFile != null && tempFile.exists()) { |
||||
|
try { |
||||
|
if (tempFile.delete()) { |
||||
|
log.info("临时文件已删除: {}", tempFile.getAbsolutePath()); |
||||
|
} else { |
||||
|
log.warn("无法删除临时文件: {}", tempFile.getAbsolutePath()); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("删除临时文件失败", e.getMessage()); |
||||
|
throw new Exception("删除临时文件失败", e); |
||||
|
} |
||||
|
} |
||||
|
long endTime = System.currentTimeMillis(); |
||||
|
log.info("导出任务完成,耗时: {}毫秒", (endTime - startTime)); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 验证导出记录 |
||||
|
*/ |
||||
|
private AiEmotionExportRecordVO validateExportRecord(Long recordId) throws Exception { |
||||
|
AiEmotionExportRecordVO record = aiEmotionService.getRecordById(recordId); |
||||
|
AbstractLog log = null; |
||||
|
if (record == null) { |
||||
|
log.error("导出记录不存在 recordId: {}", recordId); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
// 检查是否已经处理过 |
||||
|
if (record.getState() != 0) { |
||||
|
log.warn("导出记录已处理 recordId: {}, status: {}", recordId, record.getState()); |
||||
|
return null; |
||||
|
} |
||||
|
return record; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 初始化excel文件 |
||||
|
* @param os |
||||
|
* @param exportType |
||||
|
* @return |
||||
|
*/ |
||||
|
private ExcelWriter initExcelWriter(OutputStream os, String exportType) { |
||||
|
switch (exportType) { |
||||
|
case "user": |
||||
|
return EasyExcel.write(os, Goldmingxi.class) |
||||
|
.inMemory(Boolean.TRUE) |
||||
|
.build(); |
||||
|
default: |
||||
|
throw new IllegalArgumentException("不支持的导出类型: " + exportType); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.example.demo.mapper.AiEmotionMapper"> |
||||
|
<update id="updateStatus"> |
||||
|
UPDATE admin_export_record |
||||
|
<set> |
||||
|
<if test="state != null">state = #{state},</if> |
||||
|
<if test="url != null and url != ''">url = #{url},</if> |
||||
|
<if test="reason != null and reason != ''">reason = #{reason},</if> |
||||
|
<if test="dataNum != null and dataNum != ''">data_num = #{dataNum},</if> |
||||
|
</set> |
||||
|
WHERE id = #{recordId} |
||||
|
</update> |
||||
|
<select id="getRecordById" resultType="com.example.demo.domain.vo.AiEmotionExportRecordVO"> |
||||
|
SELECT id, file_name, state, url FROM admin_export_record WHERE id = #{recordId} |
||||
|
</select> |
||||
|
</mapper> |
@ -0,0 +1,18 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.example.demo.mapper.ExportMapper"> |
||||
|
<update id="updateExportData"> |
||||
|
UPDATE export |
||||
|
<set> |
||||
|
<if test="state != null">state = #{state},</if> |
||||
|
<if test="url != null and url != ''">url = #{url},</if> |
||||
|
<if test="reason != null and reason != ''">reason = #{reason},</if> |
||||
|
<if test="dataNum != null and dataNum != ''">data_num = #{dataNum},</if> |
||||
|
</set> |
||||
|
WHERE id = #{recordId} |
||||
|
</update> |
||||
|
|
||||
|
<select id="getExportData" resultType="com.example.demo.domain.vo.ExportVo"> |
||||
|
select id,file_name,url,state from export where id=#{recordId} |
||||
|
</select> |
||||
|
</mapper> |
@ -0,0 +1,8 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.example.demo.mapper.UrlMapper"> |
||||
|
|
||||
|
<select id="selectBaseUrl" resultType="java.lang.String"> |
||||
|
select value from env where `key` = #{key} |
||||
|
</select> |
||||
|
</mapper> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue