4 Commits
ba19635520
...
d884849dc4
Author | SHA1 | Message | Date |
---|---|---|---|
|
d884849dc4 |
Merge remote-tracking branch 'origin/milestone-20250702-金币重构一期' into milestone-20250702-金币重构一期
|
3 days ago |
|
dc95d625e6 |
可跑(不合并)
|
3 days ago |
|
e56cb9744f |
可跑(不合并)
|
4 days ago |
|
6bb3f5a45d |
权限
|
4 days ago |
52 changed files with 3517 additions and 347 deletions
-
35pom.xml
-
25src/main/java/com/example/demo/Export/ExportService.java
-
177src/main/java/com/example/demo/Export/ExportServiceImpl.java
-
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
-
43src/main/java/com/example/demo/controller/AdminController.java
-
99src/main/java/com/example/demo/controller/ExportController.java
-
50src/main/java/com/example/demo/controller/GoldDetailController.java
-
4src/main/java/com/example/demo/controller/PermissionController.java
-
42src/main/java/com/example/demo/domain/DTO/ConsumeDTO.java
-
44src/main/java/com/example/demo/domain/DTO/GoldDetailDTO.java
-
42src/main/java/com/example/demo/domain/DTO/GoldUserDTO.java
-
43src/main/java/com/example/demo/domain/DTO/RechargeDTO.java
-
42src/main/java/com/example/demo/domain/DTO/RefundDTO.java
-
66src/main/java/com/example/demo/domain/entity/Admin.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
-
13src/main/java/com/example/demo/exception/SystemException.java
-
25src/main/java/com/example/demo/mapper/AiEmotionMapper.java
-
23src/main/java/com/example/demo/mapper/ExportMapper.java
-
16src/main/java/com/example/demo/mapper/GoldDetailMapper.java
-
264src/main/java/com/example/demo/security/TokenFilter.java
-
43src/main/java/com/example/demo/security/UploadFilter.java
-
15src/main/java/com/example/demo/service/AdminService.java
-
19src/main/java/com/example/demo/service/AiEmotionService.java
-
22src/main/java/com/example/demo/service/ExportExcelService.java
-
9src/main/java/com/example/demo/service/GoldDetailService.java
-
1src/main/java/com/example/demo/service/PermissionService.java
-
84src/main/java/com/example/demo/service/listen/AiEmotionExportListener.java
-
81src/main/java/com/example/demo/service/listen/ConsumeListener.java
-
82src/main/java/com/example/demo/service/listen/GoldListener.java
-
81src/main/java/com/example/demo/service/listen/RechargeListener.java
-
81src/main/java/com/example/demo/service/listen/RefundListener.java
-
69src/main/java/com/example/demo/service/queue/AbstractMessageListener.java
-
64src/main/java/com/example/demo/serviceImpl/AdminServiceImpl.java
-
35src/main/java/com/example/demo/serviceImpl/AiEmotionServiceImpl.java
-
1102src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java
-
115src/main/java/com/example/demo/serviceImpl/GoldDetailServiceImpl.java
-
5src/main/java/com/example/demo/serviceImpl/PermissionServiceImpl.java
-
8src/main/resources/application.yml
-
17src/main/resources/mapper/AiEmotionMapper.xml
-
28src/main/resources/mapper/ExportMapper.xml
-
16src/main/resources/mapper/GoldDetailMapper.xml
-
17src/main/resources/mapper/PermissionMapper.xml
-
8src/main/resources/mapper/UrlMapper.xml
@ -0,0 +1,25 @@ |
|||||
|
package com.example.demo.Export; |
||||
|
|
||||
|
import com.example.demo.domain.DTO.ConsumeDTO; |
||||
|
import com.example.demo.domain.DTO.GoldDetailDTO; |
||||
|
import com.example.demo.domain.DTO.RechargeDTO; |
||||
|
import com.example.demo.domain.DTO.RefundDTO; |
||||
|
import com.example.demo.domain.vo.Result; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportService |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 16:06 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
public interface ExportService { |
||||
|
//充值导出 |
||||
|
Result addExportRecharge(RechargeDTO dto); |
||||
|
//退款导出 |
||||
|
Result addExportRefund(RefundDTO dto); |
||||
|
//消费导出 |
||||
|
Result addExportConsume(ConsumeDTO dto); |
||||
|
|
||||
|
} |
@ -0,0 +1,177 @@ |
|||||
|
package com.example.demo.Export; |
||||
|
|
||||
|
import com.example.demo.domain.DTO.ConsumeDTO; |
||||
|
import com.example.demo.domain.DTO.RechargeDTO; |
||||
|
import com.example.demo.domain.DTO.RefundDTO; |
||||
|
import com.example.demo.domain.vo.Result; |
||||
|
import com.example.demo.exception.SystemException; |
||||
|
import com.example.demo.mapper.GoldDetailMapper; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
import java.time.format.DateTimeFormatter; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportServiceImpl |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 16:11 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Service |
||||
|
public class ExportServiceImpl implements ExportService{ |
||||
|
@Autowired |
||||
|
private GoldDetailMapper goldDetailMapper; |
||||
|
@Autowired |
||||
|
private RedisUtil redisUtil; |
||||
|
|
||||
|
@Override |
||||
|
public Result addExportRecharge(RechargeDTO dto) { |
||||
|
// 生成文件名 |
||||
|
String fileName = String.format("%s_%s_%s.xlsx", |
||||
|
"充值明细", |
||||
|
"操作人", |
||||
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); |
||||
|
System.out.println(fileName); |
||||
|
dto.setAccount(123456); |
||||
|
dto.setUrl(""); |
||||
|
dto.setFileName(fileName); |
||||
|
dto.setDataNum(0); |
||||
|
try{ |
||||
|
// 调用方式 |
||||
|
GoldDetailMapper.ExportRecordIdHolder idHolder = new GoldDetailMapper.ExportRecordIdHolder(); |
||||
|
goldDetailMapper.insertExportRecord( |
||||
|
idHolder, // 用于接收主键 |
||||
|
dto.getAccount(), |
||||
|
dto.getType(), |
||||
|
dto.getState(), |
||||
|
dto.getUrl(), |
||||
|
dto.getFileName(), |
||||
|
dto.getDataNum() |
||||
|
); |
||||
|
// 获取主键 |
||||
|
Long recordId = idHolder.getId(); |
||||
|
// 2. 构造完整的 JSON 数据(包含所有请求参数) |
||||
|
Map<String, Object> exportData = new HashMap<>(); |
||||
|
exportData.put("recordId", recordId); |
||||
|
|
||||
|
// 手动构造请求数据(避免 toString() 只返回部分字段) |
||||
|
Map<String, Object> 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("recharge:queue:export_queue", jsonData); |
||||
|
}catch (Exception e){ |
||||
|
e.printStackTrace(); |
||||
|
throw new SystemException("导出数据异常,请稍后重试", e); |
||||
|
} |
||||
|
return Result.success(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Result addExportRefund(RefundDTO dto) { |
||||
|
// 生成文件名 |
||||
|
String fileName = String.format("%s_%s_%s.xlsx", |
||||
|
"退款明细", |
||||
|
"操作人", |
||||
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); |
||||
|
System.out.println(fileName); |
||||
|
dto.setAccount(123456); |
||||
|
dto.setUrl(""); |
||||
|
dto.setFileName(fileName); |
||||
|
dto.setDataNum(0); |
||||
|
try{ |
||||
|
// 调用方式 |
||||
|
GoldDetailMapper.ExportRecordIdHolder idHolder = new GoldDetailMapper.ExportRecordIdHolder(); |
||||
|
goldDetailMapper.insertExportRecord( |
||||
|
idHolder, // 用于接收主键 |
||||
|
dto.getAccount(), |
||||
|
dto.getType(), |
||||
|
dto.getState(), |
||||
|
dto.getUrl(), |
||||
|
dto.getFileName(), |
||||
|
dto.getDataNum() |
||||
|
); |
||||
|
// 获取主键 |
||||
|
Long recordId = idHolder.getId(); |
||||
|
// 2. 构造完整的 JSON 数据(包含所有请求参数) |
||||
|
Map<String, Object> exportData = new HashMap<>(); |
||||
|
exportData.put("recordId", recordId); |
||||
|
|
||||
|
// 手动构造请求数据(避免 toString() 只返回部分字段) |
||||
|
Map<String, Object> 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("refund:queue:export_queue", jsonData); |
||||
|
}catch (Exception e){ |
||||
|
e.printStackTrace(); |
||||
|
throw new SystemException("导出数据异常,请稍后重试", e); |
||||
|
} |
||||
|
return Result.success(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Result addExportConsume(ConsumeDTO dto) { |
||||
|
// 生成文件名 |
||||
|
String fileName = String.format("%s_%s_%s.xlsx", |
||||
|
"消费明细", |
||||
|
"操作人", |
||||
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))); |
||||
|
System.out.println(fileName); |
||||
|
dto.setAccount(123456); |
||||
|
dto.setUrl(""); |
||||
|
dto.setFileName(fileName); |
||||
|
dto.setDataNum(0); |
||||
|
try{ |
||||
|
// 调用方式 |
||||
|
GoldDetailMapper.ExportRecordIdHolder idHolder = new GoldDetailMapper.ExportRecordIdHolder(); |
||||
|
goldDetailMapper.insertExportRecord( |
||||
|
idHolder, // 用于接收主键 |
||||
|
dto.getAccount(), |
||||
|
dto.getType(), |
||||
|
dto.getState(), |
||||
|
dto.getUrl(), |
||||
|
dto.getFileName(), |
||||
|
dto.getDataNum() |
||||
|
); |
||||
|
// 获取主键 |
||||
|
Long recordId = idHolder.getId(); |
||||
|
// 2. 构造完整的 JSON 数据(包含所有请求参数) |
||||
|
Map<String, Object> exportData = new HashMap<>(); |
||||
|
exportData.put("recordId", recordId); |
||||
|
|
||||
|
// 手动构造请求数据(避免 toString() 只返回部分字段) |
||||
|
Map<String, Object> 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("refund:queue:export_queue", jsonData); |
||||
|
}catch (Exception e){ |
||||
|
e.printStackTrace(); |
||||
|
throw new SystemException("导出数据异常,请稍后重试", e); |
||||
|
} |
||||
|
return Result.success(); |
||||
|
} |
||||
|
} |
@ -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,43 @@ |
|||||
|
package com.example.demo.controller; |
||||
|
|
||||
|
import com.example.demo.Util.JWTUtil; |
||||
|
import com.example.demo.domain.entity.Admin; |
||||
|
import com.example.demo.domain.vo.Result; |
||||
|
import com.example.demo.service.AdminService; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName AdminController |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 10:39 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@RestController |
||||
|
@RequestMapping("/admin") |
||||
|
@RequiredArgsConstructor |
||||
|
@Slf4j |
||||
|
@CrossOrigin |
||||
|
public class AdminController { |
||||
|
@Autowired |
||||
|
private AdminService adminService; |
||||
|
@PostMapping("/login") |
||||
|
public Result login(@RequestBody Admin admin){ |
||||
|
|
||||
|
try { |
||||
|
admin = adminService.login(admin); |
||||
|
admin.setPassword(null); |
||||
|
System.out.println("达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达瓦达"); |
||||
|
return Result.success(admin); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
log.error(e.getMessage()); |
||||
|
return Result.error(e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
@ -0,0 +1,99 @@ |
|||||
|
package com.example.demo.controller; |
||||
|
|
||||
|
import com.example.demo.Util.BusinessException; |
||||
|
import com.example.demo.Util.RedisLockUtil; |
||||
|
import com.example.demo.domain.DTO.ConsumeDTO; |
||||
|
import com.example.demo.domain.DTO.RechargeDTO; |
||||
|
import com.example.demo.domain.DTO.RefundDTO; |
||||
|
import com.example.demo.domain.entity.Export; |
||||
|
import com.example.demo.domain.vo.Result; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.GoldDetailService; |
||||
|
import com.example.demo.Export.ExportService; |
||||
|
import jakarta.validation.Valid; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* @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 { |
||||
|
@Autowired |
||||
|
private ExportExcelService exportExcelService; |
||||
|
@Autowired |
||||
|
private RedisLockUtil redisLockUtil; |
||||
|
@Autowired |
||||
|
private GoldDetailService goldDetailService; |
||||
|
@Autowired |
||||
|
private ExportService exportService; |
||||
|
@PostMapping("/export") |
||||
|
public Result export(@RequestBody Export Export){ |
||||
|
return Result.success(exportExcelService.getExcel(Export)); |
||||
|
} |
||||
|
@PostMapping("/exportRecharge") |
||||
|
public Result export(@Valid @RequestBody RechargeDTO 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 exportService.addExportRecharge(dto); |
||||
|
} finally { |
||||
|
// 释放锁 |
||||
|
redisLockUtil.unlock(lockKey, requestId); |
||||
|
} |
||||
|
} |
||||
|
@PostMapping("/exportRefund") |
||||
|
public Result export(@Valid @RequestBody RefundDTO 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 exportService.addExportRefund(dto); |
||||
|
} finally { |
||||
|
// 释放锁 |
||||
|
redisLockUtil.unlock(lockKey, requestId); |
||||
|
} |
||||
|
} |
||||
|
@PostMapping("/exportConsume") |
||||
|
public Result export(@Valid @RequestBody ConsumeDTO 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 exportService.addExportConsume(dto); |
||||
|
} finally { |
||||
|
// 释放锁 |
||||
|
redisLockUtil.unlock(lockKey, requestId); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ConsumeDTO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 16:17 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class ConsumeDTO { |
||||
|
private String token; |
||||
|
private String url = ""; |
||||
|
private String fileName = ""; |
||||
|
private Integer sort = 0; |
||||
|
private String field = ""; |
||||
|
private Integer account; |
||||
|
private Integer type = 4; //类型 |
||||
|
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(account=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
account, type, state, dataNum |
||||
|
); |
||||
|
} |
||||
|
} |
@ -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 account; |
||||
|
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(account=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
account, type, state, dataNum |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName GoldDTO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-30 23:40 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class GoldUserDTO { |
||||
|
private String token; |
||||
|
private String url = ""; |
||||
|
private String fileName = ""; |
||||
|
private Integer sort = 0; |
||||
|
private String field = ""; |
||||
|
private Integer account; |
||||
|
private Integer type = 1; //类型 |
||||
|
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(account=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
account, type, state, dataNum |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName RechargeDTO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 16:15 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
|
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class RechargeDTO { |
||||
|
private String token; |
||||
|
private String url = ""; |
||||
|
private String fileName = ""; |
||||
|
private Integer sort = 0; |
||||
|
private String field = ""; |
||||
|
private Integer account; |
||||
|
private Integer type = 2; //类型 |
||||
|
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(account=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
account, type, state, dataNum |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package com.example.demo.domain.DTO; |
||||
|
|
||||
|
import jakarta.validation.constraints.NotNull; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName RefundDTO |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 16:16 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class RefundDTO { |
||||
|
private String token; |
||||
|
private String url = ""; |
||||
|
private String fileName = ""; |
||||
|
private Integer sort = 0; |
||||
|
private String field = ""; |
||||
|
private Integer account; |
||||
|
private Integer type = 3; //类型 |
||||
|
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(account=%d, type=%d, state=%d, dataNum=%d)", |
||||
|
account, 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,13 @@ |
|||||
|
package com.example.demo.exception; |
||||
|
|
||||
|
public class SystemException extends RuntimeException { |
||||
|
public SystemException(String message) { |
||||
|
super(message); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public SystemException(String message, Throwable cause) { |
||||
|
super(message, cause); |
||||
|
} |
||||
|
|
||||
|
} |
@ -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,23 @@ |
|||||
|
package com.example.demo.mapper; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Export; |
||||
|
import com.example.demo.domain.vo.ExportVo; |
||||
|
import org.apache.ibatis.annotations.Mapper; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName ExportMapper |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−06-29 17:28 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Mapper |
||||
|
public interface ExportMapper { |
||||
|
ExportVo getExportData(Integer id); |
||||
|
int updateExportData(Long recordId, Integer state, String url, String reason, Integer dataNum); |
||||
|
List<Export> getExportRecord(Integer account); |
||||
|
|
||||
|
} |
@ -1,43 +0,0 @@ |
|||||
package com.example.demo.security; |
|
||||
|
|
||||
import jakarta.servlet.FilterChain; |
|
||||
import jakarta.servlet.ServletException; |
|
||||
import jakarta.servlet.http.HttpServletRequest; |
|
||||
import jakarta.servlet.http.HttpServletResponse; |
|
||||
import org.springframework.stereotype.Component; |
|
||||
import org.springframework.web.filter.OncePerRequestFilter; |
|
||||
import org.springframework.web.multipart.MultipartResolver; |
|
||||
|
|
||||
import java.io.IOException; |
|
||||
|
|
||||
|
|
||||
@Component |
|
||||
public class UploadFilter extends OncePerRequestFilter { |
|
||||
|
|
||||
private final MultipartResolver multipartResolver; |
|
||||
|
|
||||
public UploadFilter(MultipartResolver multipartResolver) { |
|
||||
this.multipartResolver = multipartResolver; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
|
||||
throws ServletException, IOException { |
|
||||
|
|
||||
// 检查请求是否为上传请求,这里假设上传请求的路径以 "/upload" 开头 |
|
||||
boolean isUploadRequest = request.getRequestURI().startsWith("/upload"); |
|
||||
System.out.println(isUploadRequest); |
|
||||
System.out.println("MultipartResolver: " + multipartResolver); |
|
||||
if (isUploadRequest ) { |
|
||||
System.out.println("执行upload-------------------------------"); |
|
||||
// 如果是上传请求且Content-Type为multipart/form-data,直接将请求传递给下一个过滤器或目标资源 |
|
||||
filterChain.doFilter(request, response); |
|
||||
} else { |
|
||||
// 如果不是上传请求,执行一些自定义逻辑 |
|
||||
// 例如,可以在这里添加令牌验证或其他安全检查 |
|
||||
|
|
||||
// 继续执行过滤器链 |
|
||||
filterChain.doFilter(request, response); |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,15 @@ |
|||||
|
package com.example.demo.service; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Admin; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName adminService |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 10:40 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
public interface AdminService { |
||||
|
Admin login(Admin admin)throws Exception; |
||||
|
} |
@ -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 { |
||||
|
|
||||
|
int updateStatus(Long recordId, int i, String s, String s1, int i1); |
||||
|
AiEmotionExportRecordVO getRecordById(Long id) throws Exception; |
||||
|
|
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package com.example.demo.service; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Export; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @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; |
||||
|
Exception handleExcel(String message) throws Exception; |
||||
|
Exception rechargeExcel(String message) throws Exception; |
||||
|
Exception consumeExcel(String message) throws Exception; |
||||
|
Exception refundExcel(String message) throws Exception; |
||||
|
List<Export> getExcel(Export export); |
||||
|
} |
@ -0,0 +1,84 @@ |
|||||
|
package com.example.demo.service.listen; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
|
||||
|
|
||||
|
import com.example.demo.Util.ExecutionContextUtil; |
||||
|
import com.example.demo.Util.FeiShuAlertUtil; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.queue.AbstractMessageListener; |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import static java.lang.Thread.sleep; |
||||
|
|
||||
|
|
||||
|
@Component |
||||
|
public class AiEmotionExportListener extends AbstractMessageListener<String> { |
||||
|
|
||||
|
|
||||
|
//注入ExportExcelService |
||||
|
@Autowired |
||||
|
private ExportExcelService exportExcelService; |
||||
|
|
||||
|
@Autowired |
||||
|
public AiEmotionExportListener( |
||||
|
RedisUtil redisQueueUtil |
||||
|
|
||||
|
) { |
||||
|
super(redisQueueUtil, "hwgold:queue:export_queue"); |
||||
|
System.out.println("监听器已启动,队列: "); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleMessage(String message) { |
||||
|
if (StrUtil.isBlank(message)) { |
||||
|
System.err.println("redis消息队列数据为空" + message); |
||||
|
} |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
exportExcelService.handleExcelExportData(message); |
||||
|
} catch (Exception e) { |
||||
|
logError(e, message); |
||||
|
throw new RuntimeException("Failed to process HWGOLD export: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void logError(Exception e, String message) { |
||||
|
System.err.println("Export data listener exception: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
try { |
||||
|
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
||||
|
String cause = ""; |
||||
|
if (e.getCause() != null) { |
||||
|
cause = e.getCause().getMessage(); |
||||
|
} |
||||
|
FeiShuAlertUtil.sendAlertMessage( |
||||
|
context, |
||||
|
e.getStackTrace()[0].getFileName(), |
||||
|
e.getStackTrace()[0].getLineNumber(), |
||||
|
"HWGOLD Export Error: " + e.getMessage() + " 底层错误: " + cause , |
||||
|
"Failed message: " + message |
||||
|
); |
||||
|
} catch (Exception alertEx) { |
||||
|
System.err.println("Failed to send Feishu alert: " + alertEx.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleError(Exception e, String message) { |
||||
|
System.err.println("处理消息失败: " + message); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,81 @@ |
|||||
|
package com.example.demo.service.listen; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.example.demo.Export.ExportService; |
||||
|
import com.example.demo.Util.ExecutionContextUtil; |
||||
|
import com.example.demo.Util.FeiShuAlertUtil; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.queue.AbstractMessageListener; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName RechargeListener |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 15:46 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Component |
||||
|
public class ConsumeListener extends AbstractMessageListener<String> { |
||||
|
//注入ExportExcelService |
||||
|
@Autowired |
||||
|
private ExportExcelService exportService; |
||||
|
|
||||
|
@Autowired |
||||
|
public ConsumeListener( |
||||
|
RedisUtil redisQueueUtil |
||||
|
|
||||
|
) { |
||||
|
super(redisQueueUtil, "consume:queue:export_queue"); |
||||
|
System.out.println("监听器已启动,队列: "); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleMessage(String message) { |
||||
|
if (StrUtil.isBlank(message)) { |
||||
|
System.err.println("redis消息队列数据为空" + message); |
||||
|
} |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
exportService.consumeExcel(message); |
||||
|
} catch (Exception e) { |
||||
|
logError(e, message); |
||||
|
throw new RuntimeException("Failed to process HWGOLD export: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void logError(Exception e, String message) { |
||||
|
System.err.println("Export data listener exception: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
try { |
||||
|
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
||||
|
String cause = ""; |
||||
|
if (e.getCause() != null) { |
||||
|
cause = e.getCause().getMessage(); |
||||
|
} |
||||
|
FeiShuAlertUtil.sendAlertMessage( |
||||
|
context, |
||||
|
e.getStackTrace()[0].getFileName(), |
||||
|
e.getStackTrace()[0].getLineNumber(), |
||||
|
"HWGOLD Export Error: " + e.getMessage() + " 底层错误: " + cause , |
||||
|
"Failed message: " + message |
||||
|
); |
||||
|
} catch (Exception alertEx) { |
||||
|
System.err.println("Failed to send Feishu alert: " + alertEx.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleError(Exception e, String message) { |
||||
|
System.err.println("处理消息失败: " + message); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,82 @@ |
|||||
|
package com.example.demo.service.listen; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
|
||||
|
import com.example.demo.Util.ExecutionContextUtil; |
||||
|
import com.example.demo.Util.FeiShuAlertUtil; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.GoldDetailService; |
||||
|
import com.example.demo.service.queue.AbstractMessageListener; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName GoldListener |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 15:43 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Component |
||||
|
public class GoldListener extends AbstractMessageListener<String> { |
||||
|
//注入ExportExcelService |
||||
|
@Autowired |
||||
|
private ExportExcelService exportExcelService; |
||||
|
|
||||
|
@Autowired |
||||
|
public GoldListener( |
||||
|
RedisUtil redisQueueUtil |
||||
|
|
||||
|
) { |
||||
|
super(redisQueueUtil, "gold:queue:export_queue"); |
||||
|
System.out.println("监听器已启动,队列: "); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleMessage(String message) { |
||||
|
if (StrUtil.isBlank(message)) { |
||||
|
System.err.println("redis消息队列数据为空" + message); |
||||
|
} |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
exportExcelService.handleExcel(message); |
||||
|
} catch (Exception e) { |
||||
|
logError(e, message); |
||||
|
throw new RuntimeException("Failed to process HWGOLD export: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void logError(Exception e, String message) { |
||||
|
System.err.println("Export data listener exception: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
try { |
||||
|
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
||||
|
String cause = ""; |
||||
|
if (e.getCause() != null) { |
||||
|
cause = e.getCause().getMessage(); |
||||
|
} |
||||
|
FeiShuAlertUtil.sendAlertMessage( |
||||
|
context, |
||||
|
e.getStackTrace()[0].getFileName(), |
||||
|
e.getStackTrace()[0].getLineNumber(), |
||||
|
"HWGOLD Export Error: " + e.getMessage() + " 底层错误: " + cause , |
||||
|
"Failed message: " + message |
||||
|
); |
||||
|
} catch (Exception alertEx) { |
||||
|
System.err.println("Failed to send Feishu alert: " + alertEx.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleError(Exception e, String message) { |
||||
|
System.err.println("处理消息失败: " + message); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,81 @@ |
|||||
|
package com.example.demo.service.listen; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.example.demo.Export.ExportService; |
||||
|
import com.example.demo.Util.ExecutionContextUtil; |
||||
|
import com.example.demo.Util.FeiShuAlertUtil; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.queue.AbstractMessageListener; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName RechargeListener |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 15:46 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Component |
||||
|
public class RechargeListener extends AbstractMessageListener<String> { |
||||
|
//注入ExportExcelService |
||||
|
@Autowired |
||||
|
private ExportExcelService exportService; |
||||
|
|
||||
|
@Autowired |
||||
|
public RechargeListener( |
||||
|
RedisUtil redisQueueUtil |
||||
|
|
||||
|
) { |
||||
|
super(redisQueueUtil, "recharge:queue:export_queue"); |
||||
|
System.out.println("监听器已启动,队列: "); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleMessage(String message) { |
||||
|
if (StrUtil.isBlank(message)) { |
||||
|
System.err.println("redis消息队列数据为空" + message); |
||||
|
} |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
exportService.rechargeExcel(message); |
||||
|
} catch (Exception e) { |
||||
|
logError(e, message); |
||||
|
throw new RuntimeException("Failed to process HWGOLD export: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void logError(Exception e, String message) { |
||||
|
System.err.println("Export data listener exception: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
try { |
||||
|
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
||||
|
String cause = ""; |
||||
|
if (e.getCause() != null) { |
||||
|
cause = e.getCause().getMessage(); |
||||
|
} |
||||
|
FeiShuAlertUtil.sendAlertMessage( |
||||
|
context, |
||||
|
e.getStackTrace()[0].getFileName(), |
||||
|
e.getStackTrace()[0].getLineNumber(), |
||||
|
"HWGOLD Export Error: " + e.getMessage() + " 底层错误: " + cause , |
||||
|
"Failed message: " + message |
||||
|
); |
||||
|
} catch (Exception alertEx) { |
||||
|
System.err.println("Failed to send Feishu alert: " + alertEx.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleError(Exception e, String message) { |
||||
|
System.err.println("处理消息失败: " + message); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,81 @@ |
|||||
|
package com.example.demo.service.listen; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.example.demo.Export.ExportService; |
||||
|
import com.example.demo.Util.ExecutionContextUtil; |
||||
|
import com.example.demo.Util.FeiShuAlertUtil; |
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import com.example.demo.domain.vo.ExecutionContext; |
||||
|
import com.example.demo.service.ExportExcelService; |
||||
|
import com.example.demo.service.queue.AbstractMessageListener; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
/** |
||||
|
* @program: GOLD |
||||
|
* @ClassName RechargeListener |
||||
|
* @description: |
||||
|
* @author: huangqizhen |
||||
|
* @create: 2025−07-01 15:46 |
||||
|
* @Version 1.0 |
||||
|
**/ |
||||
|
@Component |
||||
|
public class RefundListener extends AbstractMessageListener<String> { |
||||
|
//注入ExportExcelService |
||||
|
@Autowired |
||||
|
private ExportExcelService exportExcelService; |
||||
|
|
||||
|
@Autowired |
||||
|
public RefundListener( |
||||
|
RedisUtil redisQueueUtil |
||||
|
|
||||
|
) { |
||||
|
super(redisQueueUtil, "refund:queue:export_queue"); |
||||
|
System.out.println("监听器已启动,队列: "); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleMessage(String message) { |
||||
|
if (StrUtil.isBlank(message)) { |
||||
|
System.err.println("redis消息队列数据为空" + message); |
||||
|
} |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
exportExcelService.refundExcel(message); |
||||
|
} catch (Exception e) { |
||||
|
logError(e, message); |
||||
|
throw new RuntimeException("Failed to process HWGOLD export: " + e.getMessage(), e); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
private void logError(Exception e, String message) { |
||||
|
System.err.println("Export data listener exception: " + e.getMessage()); |
||||
|
e.printStackTrace(); |
||||
|
try { |
||||
|
ExecutionContext context = ExecutionContextUtil.getExecutionContext(); |
||||
|
String cause = ""; |
||||
|
if (e.getCause() != null) { |
||||
|
cause = e.getCause().getMessage(); |
||||
|
} |
||||
|
FeiShuAlertUtil.sendAlertMessage( |
||||
|
context, |
||||
|
e.getStackTrace()[0].getFileName(), |
||||
|
e.getStackTrace()[0].getLineNumber(), |
||||
|
"HWGOLD Export Error: " + e.getMessage() + " 底层错误: " + cause , |
||||
|
"Failed message: " + message |
||||
|
); |
||||
|
} catch (Exception alertEx) { |
||||
|
System.err.println("Failed to send Feishu alert: " + alertEx.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void handleError(Exception e, String message) { |
||||
|
System.err.println("处理消息失败: " + message); |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
@ -0,0 +1,69 @@ |
|||||
|
package com.example.demo.service.queue; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.example.demo.Util.RedisUtil; |
||||
|
import jakarta.annotation.PostConstruct; |
||||
|
|
||||
|
|
||||
|
import java.util.concurrent.ExecutorService; |
||||
|
import java.util.concurrent.Executors; |
||||
|
|
||||
|
/** |
||||
|
* 抽象消息监听器 |
||||
|
*/ |
||||
|
public abstract class AbstractMessageListener<T> { |
||||
|
|
||||
|
private final ExecutorService executorService = Executors.newSingleThreadExecutor(); |
||||
|
protected final RedisUtil redisQueueUtil; |
||||
|
protected final String queueName; |
||||
|
|
||||
|
public AbstractMessageListener(RedisUtil redisQueueUtil, String queueName) { |
||||
|
this.redisQueueUtil = redisQueueUtil; |
||||
|
this.queueName = queueName; |
||||
|
} |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init() { |
||||
|
executorService.submit(this::listen); |
||||
|
} |
||||
|
|
||||
|
private void listen() { |
||||
|
System.out.println("消费者消费数据" + queueName + "<UNK>"); |
||||
|
while (!Thread.currentThread().isInterrupted()) { |
||||
|
try { |
||||
|
Object message = redisQueueUtil.blockingGetMessage(queueName, 1); |
||||
|
if (message != null) { |
||||
|
try { |
||||
|
handleMessage((T) message); |
||||
|
} catch (Exception e) { |
||||
|
handleError(e, (T) message); |
||||
|
} |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
System.err.println("监听队列异常: " + e.getMessage()); |
||||
|
try { |
||||
|
Thread.sleep(5000); |
||||
|
} catch (InterruptedException ex) { |
||||
|
Thread.currentThread().interrupt(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理消息 |
||||
|
* @param message 消息内容 |
||||
|
*/ |
||||
|
protected abstract void handleMessage(T message); |
||||
|
|
||||
|
/** |
||||
|
* 处理错误 |
||||
|
* @param e 异常 |
||||
|
* @param message 消息内容 |
||||
|
*/ |
||||
|
protected void handleError(Exception e, T message) { |
||||
|
System.err.println("处理消息异常: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,64 @@ |
|||||
|
package com.example.demo.serviceImpl; |
||||
|
|
||||
|
import com.example.demo.domain.entity.Admin; |
||||
|
import com.example.demo.mapper.AdminMapper; |
||||
|
import com.example.demo.service.AdminService; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.security.authentication.AuthenticationManager; |
||||
|
import org.springframework.security.authentication.BadCredentialsException; |
||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; |
||||
|
import org.springframework.security.core.Authentication; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
@Transactional |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class AdminServiceImpl implements AdminService { |
||||
|
|
||||
|
@Autowired |
||||
|
private AuthenticationManager authenticationManager; |
||||
|
@Autowired |
||||
|
private AdminMapper adminMapper; |
||||
|
|
||||
|
@Override |
||||
|
public Admin login(Admin admin) throws Exception { |
||||
|
try { |
||||
|
Admin admin1 = adminMapper.getAdmin(admin.getAccount()); |
||||
|
String[] machineIds = admin1.getMachineId().split(","); |
||||
|
|
||||
|
boolean flag = false; |
||||
|
for (String machineId : machineIds) { |
||||
|
if (admin.getMachineId() != null && admin.getMachineId().equals(machineId)) |
||||
|
flag = true; |
||||
|
} |
||||
|
if (!flag) { |
||||
|
throw new RuntimeException("你没有使用该机器的权限!"); |
||||
|
} |
||||
|
System.out.println(admin.getAccount()); |
||||
|
System.out.println(admin.getPassword()+"---------------------------"); |
||||
|
UsernamePasswordAuthenticationToken token = |
||||
|
new UsernamePasswordAuthenticationToken(admin.getAccount(),admin.getPassword()); |
||||
|
System.out.println( token+"---------------------------"); |
||||
|
// Authentication authentication = authenticationManager.authenticate(token); |
||||
|
// Admin loginAdmin = (Admin) authentication.getPrincipal(); |
||||
|
Admin loginAdmin = (Admin) authenticationManager.authenticate(token).getPrincipal(); |
||||
|
|
||||
|
return loginAdmin; |
||||
|
|
||||
|
}catch (NullPointerException e){ |
||||
|
throw new RuntimeException("无此精网号"); |
||||
|
}catch(BadCredentialsException exception){ |
||||
|
throw new BadCredentialsException("密码错误"); |
||||
|
}catch (Exception e){ |
||||
|
throw new RuntimeException("你没有使用该机器的权限!"); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -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 int 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); |
||||
|
} |
||||
|
} |
1102
src/main/java/com/example/demo/serviceImpl/ExportExcelServiceImpl.java
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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 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="getRecordById" resultType="com.example.demo.domain.vo.AiEmotionExportRecordVO"> |
||||
|
SELECT id, file_name, state, url FROM export WHERE id = #{recordId} |
||||
|
</select> |
||||
|
</mapper> |
@ -0,0 +1,28 @@ |
|||||
|
<?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> |
||||
|
<select id="getExportRecord" resultType="com.example.demo.domain.entity.Export"> |
||||
|
select * from export |
||||
|
<where> |
||||
|
<if test="account != null"> |
||||
|
and account = #{account} |
||||
|
</if> |
||||
|
</where> |
||||
|
</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