From 6bb3f5a45d05f9732d946c0f3a2b7c752c02c260 Mon Sep 17 00:00:00 2001 From: huangqizhen <15552608129@163.com> Date: Mon, 30 Jun 2025 15:40:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 35 ++- .../com/example/demo/Util/ExcelUploadUtil.java | 200 ++++++++++++++++ .../example/demo/Util/ExecutionContextUtil.java | 227 ++++++++++++++++++ .../com/example/demo/Util/FeiShuAlertUtil.java | 173 ++++++++++++++ .../java/com/example/demo/Util/RedisLockUtil.java | 37 +++ src/main/java/com/example/demo/Util/RedisUtil.java | 31 ++- .../java/com/example/demo/config/EnvConfig.java | 18 ++ .../java/com/example/demo/config/RedisConfig.java | 4 +- .../example/demo/controller/ExportController.java | 28 +++ .../demo/controller/GoldDetailController.java | 31 +++ .../demo/controller/PermissionController.java | 4 + .../com/example/demo/domain/DTO/GoldDetailDTO.java | 44 ++++ .../com/example/demo/domain/export/Goldmingxi.java | 28 +++ .../demo/domain/vo/AiEmotionExportRecordVO.java | 28 +++ .../example/demo/domain/vo/ExecutionContext.java | 25 ++ .../java/com/example/demo/domain/vo/ExportVo.java | 26 +++ .../com/example/demo/mapper/AiEmotionMapper.java | 25 ++ .../java/com/example/demo/mapper/ExportMapper.java | 19 ++ .../com/example/demo/mapper/GoldDetailMapper.java | 15 +- .../com/example/demo/service/AiEmotionService.java | 19 ++ .../example/demo/service/ExportExcelService.java | 16 ++ .../example/demo/service/GoldDetailService.java | 7 + .../example/demo/service/PermissionService.java | 1 + .../demo/serviceImpl/AiEmotionServiceImpl.java | 35 +++ .../demo/serviceImpl/ExportExcelServiceImpl.java | 253 +++++++++++++++++++++ .../demo/serviceImpl/GoldDetailServiceImpl.java | 69 +++++- .../demo/serviceImpl/PermissionServiceImpl.java | 5 + src/main/resources/application.yml | 14 +- src/main/resources/mapper/AiEmotionMapper.xml | 17 ++ src/main/resources/mapper/ExportMapper.xml | 18 ++ src/main/resources/mapper/GoldDetailMapper.xml | 15 +- src/main/resources/mapper/PermissionMapper.xml | 19 +- src/main/resources/mapper/UrlMapper.xml | 8 + 33 files changed, 1444 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/example/demo/Util/ExcelUploadUtil.java create mode 100644 src/main/java/com/example/demo/Util/ExecutionContextUtil.java create mode 100644 src/main/java/com/example/demo/Util/FeiShuAlertUtil.java create mode 100644 src/main/java/com/example/demo/Util/RedisLockUtil.java create mode 100644 src/main/java/com/example/demo/config/EnvConfig.java create mode 100644 src/main/java/com/example/demo/controller/ExportController.java create mode 100644 src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java create mode 100644 src/main/java/com/example/demo/domain/export/Goldmingxi.java create mode 100644 src/main/java/com/example/demo/domain/vo/AiEmotionExportRecordVO.java create mode 100644 src/main/java/com/example/demo/domain/vo/ExecutionContext.java create mode 100644 src/main/java/com/example/demo/domain/vo/ExportVo.java create mode 100644 src/main/java/com/example/demo/mapper/AiEmotionMapper.java create mode 100644 src/main/java/com/example/demo/mapper/ExportMapper.java create mode 100644 src/main/java/com/example/demo/service/AiEmotionService.java create mode 100644 src/main/java/com/example/demo/service/ExportExcelService.java create mode 100644 src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java create mode 100644 src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java create mode 100644 src/main/resources/mapper/AiEmotionMapper.xml create mode 100644 src/main/resources/mapper/ExportMapper.xml create mode 100644 src/main/resources/mapper/UrlMapper.xml diff --git a/pom.xml b/pom.xml index 216c74b..93ebbc3 100644 --- a/pom.xml +++ b/pom.xml @@ -31,15 +31,48 @@ + com.alibaba + fastjson + 1.2.83 + + + org.apache.commons + commons-lang3 + 3.12.0 + + + + org.apache.httpcomponents + httpclient + 4.5.14 + + + cn.hutool + hutool-all + 5.8.24 + + org.springframework.boot spring-boot-starter-web + org.springframework.boot + spring-boot-starter-validation + + org.mybatis.spring.boot mybatis-spring-boot-starter 3.0.4 - + + com.alibaba + easyexcel + 3.1.3 + + + com.fasterxml.jackson.core + jackson-databind + org.springframework.boot spring-boot-devtools diff --git a/src/main/java/com/example/demo/Util/ExcelUploadUtil.java b/src/main/java/com/example/demo/Util/ExcelUploadUtil.java new file mode 100644 index 0000000..77bf58f --- /dev/null +++ b/src/main/java/com/example/demo/Util/ExcelUploadUtil.java @@ -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 defaultHeaders; + private final Map 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 defaultHeaders, Map 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 customHeaders, + Map customParams) throws IOException, UploadException { + // 验证文件 + validateFile(excelFile); + + try { + // 准备请求 + HttpEntity> requestEntity = prepareRequest(excelFile, targetDir, customHeaders, customParams); + + // 执行上传 + ResponseEntity 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> prepareRequest(File file, String targetDir, + Map customHeaders, + Map customParams) { + // 设置请求头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + // 添加默认和自定义请求头 + defaultHeaders.forEach(headers::set); + customHeaders.forEach(headers::set); + + // 准备请求体 + MultiValueMap 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 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/Util/ExecutionContextUtil.java b/src/main/java/com/example/demo/Util/ExecutionContextUtil.java new file mode 100644 index 0000000..10bafa4 --- /dev/null +++ b/src/main/java/com/example/demo/Util/ExecutionContextUtil.java @@ -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 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 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 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"; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/Util/FeiShuAlertUtil.java b/src/main/java/com/example/demo/Util/FeiShuAlertUtil.java new file mode 100644 index 0000000..cf2a00e --- /dev/null +++ b/src/main/java/com/example/demo/Util/FeiShuAlertUtil.java @@ -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> contentList = new ArrayList<>(); + List 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 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> contentList = new ArrayList<>(); + List 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); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/Util/RedisLockUtil.java b/src/main/java/com/example/demo/Util/RedisLockUtil.java new file mode 100644 index 0000000..5ae5858 --- /dev/null +++ b/src/main/java/com/example/demo/Util/RedisLockUtil.java @@ -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); + } + } +} diff --git a/src/main/java/com/example/demo/Util/RedisUtil.java b/src/main/java/com/example/demo/Util/RedisUtil.java index 5431742..47e9e23 100644 --- a/src/main/java/com/example/demo/Util/RedisUtil.java +++ b/src/main/java/com/example/demo/Util/RedisUtil.java @@ -1,4 +1,3 @@ -/* package com.example.demo.Util; import jakarta.annotation.PostConstruct; @@ -35,24 +34,24 @@ public class RedisUtil { delayMessageExecutor = Executors.newFixedThreadPool(DELAY_THREAD_POOL_SIZE); } - */ + /** * 发送消息到队列 * @param queueName 队列名称 * @param message 消息内容 - *//* + */ public void sendMessage(String queueName, Object message) { redisTemplate.opsForList().rightPush(queueName, message); } - */ + /** * 阻塞获取消息(优化版,增加重试机制) * @param queueName 队列名称 * @param timeout 超时时间(秒) * @return 消息内容 - *//* + */ public Object blockingGetMessage(String queueName, long timeout) { // 分段获取,避免长时间阻塞 @@ -66,35 +65,35 @@ public class RedisUtil { return null; } - */ + /** * 非阻塞获取消息 * @param queueName 队列名称 * @return 消息内容 - *//* + */ public Object getMessage(String queueName) { return redisTemplate.opsForList().leftPop(queueName); } - */ + /** * 获取队列长度 * @param queueName 队列名称 * @return 队列长度 - *//* + */ public Long getQueueSize(String queueName) { return redisTemplate.opsForList().size(queueName); } - */ + /** * 发送延迟消息(优化版) * @param queueName 队列名称 * @param message 消息内容 * @param delay 延迟时间(秒) - *//* + */ public void sendDelayMessage(String queueName, Object message, long delay) { String delayQueueKey = getDelayQueueKey(queueName); @@ -116,10 +115,10 @@ public class RedisUtil { }); } - */ + /** * 启动延迟消息处理任务 - *//* + */ @Scheduled(fixedRate = DELAY_QUEUE_POLL_INTERVAL) public void processDelayMessages() { @@ -134,10 +133,10 @@ public class RedisUtil { } } - */ + /** * 处理单个延迟队列 - *//* + */ private void processSingleDelayQueue(String queueName) { String delayQueueKey = getDelayQueueKey(queueName); @@ -187,4 +186,4 @@ public class RedisUtil { } return serialized; } -}*/ +} diff --git a/src/main/java/com/example/demo/config/EnvConfig.java b/src/main/java/com/example/demo/config/EnvConfig.java new file mode 100644 index 0000000..1e7df48 --- /dev/null +++ b/src/main/java/com/example/demo/config/EnvConfig.java @@ -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"; +} diff --git a/src/main/java/com/example/demo/config/RedisConfig.java b/src/main/java/com/example/demo/config/RedisConfig.java index dfd5115..3cae685 100644 --- a/src/main/java/com/example/demo/config/RedisConfig.java +++ b/src/main/java/com/example/demo/config/RedisConfig.java @@ -1,4 +1,4 @@ -/* + package com.example.demo.config; import org.springframework.beans.factory.annotation.Autowired; @@ -45,4 +45,4 @@ public class RedisConfig { } -*/ + diff --git a/src/main/java/com/example/demo/controller/ExportController.java b/src/main/java/com/example/demo/controller/ExportController.java new file mode 100644 index 0000000..4302859 --- /dev/null +++ b/src/main/java/com/example/demo/controller/ExportController.java @@ -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; + } +} diff --git a/src/main/java/com/example/demo/controller/GoldDetailController.java b/src/main/java/com/example/demo/controller/GoldDetailController.java index 71e86a3..41ba378 100644 --- a/src/main/java/com/example/demo/controller/GoldDetailController.java +++ b/src/main/java/com/example/demo/controller/GoldDetailController.java @@ -1,15 +1,23 @@ package com.example.demo.controller; +import com.example.demo.Util.BusinessException; +import com.example.demo.Util.RedisLockUtil; +import com.example.demo.domain.DTO.GoldDetailDTO; import com.example.demo.domain.entity.User; import com.example.demo.domain.vo.GoldDetail; import com.example.demo.domain.vo.Page; import com.example.demo.domain.vo.Result; import com.example.demo.service.GoldDetailService; +import com.example.demo.serviceImpl.AiEmotionServiceImpl; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.ObjectUtils; import org.springframework.web.bind.annotation.*; +import java.util.UUID; + /** * @program: GOLD * @ClassName GoldDetailController @@ -26,6 +34,12 @@ import org.springframework.web.bind.annotation.*; @CrossOrigin public class GoldDetailController { private final GoldDetailService goldDetailService; + + @Autowired + private RedisLockUtil redisLockUtil; + @Autowired + private AiEmotionServiceImpl aiEmotionServiceImpl; + @PostMapping("/getGoldDetail") public Result getGoldDetail(@RequestBody Page page){ @@ -58,4 +72,21 @@ public class GoldDetailController { } return Result.success(goldDetailService.getGold(page.getPageNum(), page.getPageSize(), page.getUser())); } + @PostMapping("export") + public Result export(@Valid @RequestBody GoldDetailDTO dto) { + String lockKey = "export:lock:" + dto.getToken(); // 锁的 Key(可按用户/业务区分) + String requestId = UUID.randomUUID().toString(); // 请求 ID(防止误删锁) + long expireTime = 5000; // 锁过期时间(5秒)s + try { + // 尝试获取锁 + if (!redisLockUtil.tryLock(lockKey, requestId, expireTime)) { + throw new BusinessException("操作太频繁,请稍后重试"); + } + // 执行业务逻辑 + return GoldDetailService.addExportRecord(dto); + } finally { + // 释放锁 + redisLockUtil.unlock(lockKey, requestId); + } + } } diff --git a/src/main/java/com/example/demo/controller/PermissionController.java b/src/main/java/com/example/demo/controller/PermissionController.java index 964c986..9e043d4 100644 --- a/src/main/java/com/example/demo/controller/PermissionController.java +++ b/src/main/java/com/example/demo/controller/PermissionController.java @@ -62,5 +62,9 @@ public class PermissionController { public Result updateAdminRole(@RequestBody AdminRole adminrole){ return Result.success(permissionService.updateAdminRole(adminrole)); } + @PostMapping("/upadatePermission") + public Result upadatePermission(@RequestBody Admin admin) throws Exception { + return Result.success(permissionService.upadatePermission(admin)); + } } diff --git a/src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java b/src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java new file mode 100644 index 0000000..9aa1dd0 --- /dev/null +++ b/src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java @@ -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 + ); + } +} diff --git a/src/main/java/com/example/demo/domain/export/Goldmingxi.java b/src/main/java/com/example/demo/domain/export/Goldmingxi.java new file mode 100644 index 0000000..a5c0b08 --- /dev/null +++ b/src/main/java/com/example/demo/domain/export/Goldmingxi.java @@ -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; //提交人 +} diff --git a/src/main/java/com/example/demo/domain/vo/AiEmotionExportRecordVO.java b/src/main/java/com/example/demo/domain/vo/AiEmotionExportRecordVO.java new file mode 100644 index 0000000..8f4a5e8 --- /dev/null +++ b/src/main/java/com/example/demo/domain/vo/AiEmotionExportRecordVO.java @@ -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; +} diff --git a/src/main/java/com/example/demo/domain/vo/ExecutionContext.java b/src/main/java/com/example/demo/domain/vo/ExecutionContext.java new file mode 100644 index 0000000..76d3213 --- /dev/null +++ b/src/main/java/com/example/demo/domain/vo/ExecutionContext.java @@ -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(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/domain/vo/ExportVo.java b/src/main/java/com/example/demo/domain/vo/ExportVo.java new file mode 100644 index 0000000..3042db9 --- /dev/null +++ b/src/main/java/com/example/demo/domain/vo/ExportVo.java @@ -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; + +} diff --git a/src/main/java/com/example/demo/mapper/AiEmotionMapper.java b/src/main/java/com/example/demo/mapper/AiEmotionMapper.java new file mode 100644 index 0000000..52cd24b --- /dev/null +++ b/src/main/java/com/example/demo/mapper/AiEmotionMapper.java @@ -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); +} diff --git a/src/main/java/com/example/demo/mapper/ExportMapper.java b/src/main/java/com/example/demo/mapper/ExportMapper.java new file mode 100644 index 0000000..3d95954 --- /dev/null +++ b/src/main/java/com/example/demo/mapper/ExportMapper.java @@ -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); + +} diff --git a/src/main/java/com/example/demo/mapper/GoldDetailMapper.java b/src/main/java/com/example/demo/mapper/GoldDetailMapper.java index a6343f2..eaf1ee7 100644 --- a/src/main/java/com/example/demo/mapper/GoldDetailMapper.java +++ b/src/main/java/com/example/demo/mapper/GoldDetailMapper.java @@ -4,6 +4,7 @@ import com.example.demo.domain.entity.User; import com.example.demo.domain.vo.GoldDetail; import com.example.demo.domain.vo.Total; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -19,7 +20,19 @@ import java.util.List; public interface GoldDetailMapper { List getGoldDetail(GoldDetail goldDetail); Total getTotal(GoldDetail goldDetail); - List getGold(User user); Total GoldTotal(User user); + public static class ExportRecordIdHolder{ + private Long id; + } + void insertExportRecord( + @Param("recordId") ExportRecordIdHolder recordId, // 用于接收主键 + @Param("jwcode") Integer jwcode, + @Param("type") Integer type, + @Param("state") Integer state, + @Param("url") String url, + @Param("fileName") String fileName, + @Param("dataNum") Integer dataNum + ); + ); } diff --git a/src/main/java/com/example/demo/service/AiEmotionService.java b/src/main/java/com/example/demo/service/AiEmotionService.java new file mode 100644 index 0000000..919e782 --- /dev/null +++ b/src/main/java/com/example/demo/service/AiEmotionService.java @@ -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; + +} diff --git a/src/main/java/com/example/demo/service/ExportExcelService.java b/src/main/java/com/example/demo/service/ExportExcelService.java new file mode 100644 index 0000000..7db47e3 --- /dev/null +++ b/src/main/java/com/example/demo/service/ExportExcelService.java @@ -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; + +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/GoldDetailService.java b/src/main/java/com/example/demo/service/GoldDetailService.java index 2592acc..6106f9e 100644 --- a/src/main/java/com/example/demo/service/GoldDetailService.java +++ b/src/main/java/com/example/demo/service/GoldDetailService.java @@ -1,9 +1,14 @@ package com.example.demo.service; +import com.example.demo.domain.DTO.GoldDetailDTO; import com.example.demo.domain.entity.User; import com.example.demo.domain.vo.GoldDetail; +import com.example.demo.domain.vo.Result; import com.example.demo.domain.vo.Total; import com.github.pagehelper.PageInfo; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; /** * @program: GOLD @@ -19,4 +24,6 @@ public interface GoldDetailService { PageInfo getGold(Integer pageNum, Integer pageSize, User user); Total GoldTotal(User user); + //异步导出客户明细 + Result addExportRecord(GoldDetailDTO dto); } diff --git a/src/main/java/com/example/demo/service/PermissionService.java b/src/main/java/com/example/demo/service/PermissionService.java index 3cc4fe7..8d124bf 100644 --- a/src/main/java/com/example/demo/service/PermissionService.java +++ b/src/main/java/com/example/demo/service/PermissionService.java @@ -26,4 +26,5 @@ public interface PermissionService { List getRole(String token); Integer deleteAdmin(Integer id); Integer updateAdminRole(AdminRole adminRole); + Integer upadatePermission(Admin admin) throws Exception; } diff --git a/src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java new file mode 100644 index 0000000..e709ee4 --- /dev/null +++ b/src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java @@ -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); + } +} diff --git a/src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java new file mode 100644 index 0000000..5895109 --- /dev/null +++ b/src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java @@ -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 rawData = (Map) data; + Long total = (Long) rawData.get("total"); + List> list = (List>) 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java index adcc1c2..98bcb28 100644 --- a/src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java @@ -1,16 +1,24 @@ package com.example.demo.serviceImpl; +import com.example.demo.domain.DTO.GoldDetailDTO; import com.example.demo.domain.entity.User; import com.example.demo.domain.vo.GoldDetail; +import com.example.demo.domain.vo.Result; import com.example.demo.domain.vo.Total; import com.example.demo.mapper.GoldDetailMapper; import com.example.demo.service.GoldDetailService; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @program: GOLD @@ -25,24 +33,25 @@ public class GoldDetailServiceImpl implements GoldDetailService { @Autowired private GoldDetailMapper goldDetailMapper; + @Override public PageInfo getGoldDetail(Integer pageNum, Integer pageSize, GoldDetail goldDetail) { PageHelper.startPage(pageNum, pageSize); - List list= goldDetailMapper.getGoldDetail(goldDetail); + List list = goldDetailMapper.getGoldDetail(goldDetail); return new PageInfo<>(list); } @Override public Total getTotal(GoldDetail goldDetail) { - return goldDetailMapper.getTotal(goldDetail); + return goldDetailMapper.getTotal(goldDetail); } @Override public PageInfo getGold(Integer pageNum, Integer pageSize, User user) { PageHelper.startPage(pageNum, pageSize); - List list= goldDetailMapper.getGold(user); + List list = goldDetailMapper.getGold(user); return new PageInfo<>(list); } @@ -50,4 +59,58 @@ public class GoldDetailServiceImpl implements GoldDetailService { public Total GoldTotal(User user) { return goldDetailMapper.GoldTotal(user); } + + @Override + public Result addExportRecord(GoldDetailDTO dto) { + // 获取操作者 jwcode + + // 生成文件名 + String fileName = String.format("%s_%s_%s.xlsx", + "客户金币明细", + "操作人", + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); + System.out.println(fileName); + dto.setJwcode(123456); + dto.setUrl(""); + dto.setFileName(fileName); + dto.setDataNum(0); + try{ + // 调用方式 + GoldDetailMapper.ExportRecordIdHolder idHolder = new AiEmotionMapper.ExportRecordIdHolder(); + goldDetailMapper.insertExportRecord( + idHolder, // 用于接收主键 + dto.getJwcode(), + dto.getType(), + dto.getState(), + dto.getUrl(), + dto.getFileName(), + dto.getDataNum() + ); + // 获取主键 + Long recordId = idHolder.getId(); + // 2. 构造完整的 JSON 数据(包含所有请求参数) + Map exportData = new HashMap<>(); + exportData.put("recordId", recordId); + + // 手动构造请求数据(避免 toString() 只返回部分字段) + Map requestData = new HashMap<>(); + requestData.put("text", dto.getText()); + requestData.put("sort", dto.getSort()); + requestData.put("field", dto.getField()); + requestData.put("deptId", dto.getDeptid()); + exportData.put("requestData", requestData); + + // 3. 发送到 Redis 消息队列 + String jsonData = new ObjectMapper().writeValueAsString(exportData); + redisUtil.sendMessage("hwgold:queue:export_queue", jsonData); + }catch (Exception e){ + e.printStackTrace(); + throw new SystemException("导出数据异常,请稍后重试", e); + } + return Result.success(); + } + +} + } + } diff --git a/src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java index 83cdc06..d4b5cf5 100644 --- a/src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java @@ -111,5 +111,10 @@ public class PermissionServiceImpl implements PermissionService { return permissionMapper.updateAdminRole(adminRole); } + @Override + public Integer upadatePermission(Admin admin) throws Exception { + return permissionMapper.updatePermission(admin); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cbbfb21..bb5201b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,9 +4,9 @@ spring: fail-on-unknown-properties: false datasource: mysql1: - jdbc-url: jdbc:mysql://192.168.8.220:3306/hwgold?serverTimezone=Asia/Shanghai - username: hwgold - password: 123456 + jdbc-url: jdbc:mysql://18.143.76.3:3306/hwgoldc?serverTimezone=Asia/Shanghai + username: hwgoldc + password: zB48T55wCsHC8KPz driver-class-name: com.mysql.cj.jdbc.Driver hikari: pool-name: mysql1HikariCP @@ -62,10 +62,10 @@ spring: data: redis: database: 0 - host: 192.168.8.94 - port: 6379 - password: - timeout: 1000 + host: 18.143.76.3 + port: 10703 + password: Ngc0FYUTA6h3wC5J + lettuce: pool: max-active: 20 diff --git a/src/main/resources/mapper/AiEmotionMapper.xml b/src/main/resources/mapper/AiEmotionMapper.xml new file mode 100644 index 0000000..2824bba --- /dev/null +++ b/src/main/resources/mapper/AiEmotionMapper.xml @@ -0,0 +1,17 @@ + + + + + UPDATE admin_export_record + + state = #{state}, + url = #{url}, + reason = #{reason}, + data_num = #{dataNum}, + + WHERE id = #{recordId} + + + \ No newline at end of file diff --git a/src/main/resources/mapper/ExportMapper.xml b/src/main/resources/mapper/ExportMapper.xml new file mode 100644 index 0000000..8cfcb31 --- /dev/null +++ b/src/main/resources/mapper/ExportMapper.xml @@ -0,0 +1,18 @@ + + + + + UPDATE export + + state = #{state}, + url = #{url}, + reason = #{reason}, + data_num = #{dataNum}, + + WHERE id = #{recordId} + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/GoldDetailMapper.xml b/src/main/resources/mapper/GoldDetailMapper.xml index 02cfb51..8e0c357 100644 --- a/src/main/resources/mapper/GoldDetailMapper.xml +++ b/src/main/resources/mapper/GoldDetailMapper.xml @@ -1,6 +1,9 @@ + + insert into + select * from user - + and jwcode = #{jwcode} @@ -83,7 +86,7 @@ sum(current_permanent_gold) + sum(current_free_june + current_free_december) + sum(current_task_gold) as Goldtotal from `user` - + and jwcode = #{jwcode} diff --git a/src/main/resources/mapper/PermissionMapper.xml b/src/main/resources/mapper/PermissionMapper.xml index e16d781..5fbb905 100644 --- a/src/main/resources/mapper/PermissionMapper.xml +++ b/src/main/resources/mapper/PermissionMapper.xml @@ -12,20 +12,11 @@ update admin - - admin_name=#{name}, - - - market=#{market}, - - - postiton=#{postiton}, - - - roleId=#{role}, + + admin_status= #{adminStatus}, - where id= #{id} + where id=#{id} update admin_role @@ -49,7 +40,7 @@ select distinct market from admin - + \ No newline at end of file diff --git a/src/main/resources/mapper/UrlMapper.xml b/src/main/resources/mapper/UrlMapper.xml new file mode 100644 index 0000000..714c781 --- /dev/null +++ b/src/main/resources/mapper/UrlMapper.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file