记得上下班打卡 | git大法好,push需谨慎

Commit 730f7e01 authored by 姜秀龙's avatar 姜秀龙

Add requirement documents for the ticketing system and new lost and found feature

parent f1be4545
......@@ -5,14 +5,22 @@ import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.liquidnet.common.cache.redis.util.RedisDataSourceUtil;
import com.liquidnet.commons.lang.util.DateUtil;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.param.platformFskfsfs.FskfsfsAddressExcelVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderRefundEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderRefundPicVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.dto.vo.returns.KylinOrderRefundsVo;
import com.liquidnet.service.kylin.entity.KylinOrderRefunds;
import com.liquidnet.service.kylin.entity.KylinOrderRefundEntities;
import com.liquidnet.service.kylin.entity.KylinOrderRefundPic;
import com.liquidnet.service.kylin.entity.KylinOrderTickets;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundsMapper;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundsEntitiesMapper;
import com.liquidnet.service.kylin.mapper.KylinOrderRefundPicMapper;
import com.liquidnet.service.kylin.mapper.KylinOrderTicketsMapper;
import com.mysql.cj.jdbc.result.ResultSetImpl;
import io.swagger.annotations.Api;
......@@ -34,6 +42,9 @@ import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 临时数据处理
......@@ -56,6 +67,10 @@ public class JxlDataKylinOrderController {
@Autowired
private KylinOrderRefundsMapper kylinOrderRefundsMapper;
@Autowired
private KylinOrderRefundsEntitiesMapper kylinOrderRefundsEntitiesMapper;
@Autowired
private KylinOrderRefundPicMapper kylinOrderRefundPicMapper;
@Autowired
private KylinOrderTicketsMapper kylinOrderTicketsMapper;
@Autowired
private MongoTemplate mongoTemplate;
......@@ -649,4 +664,350 @@ public class JxlDataKylinOrderController {
}
}
@GetMapping("KO006")
@ApiOperation("修复退款 Mysql 数据-MQ 内存满")
@ApiImplicitParams({
@ApiImplicitParam(type = "query", dataType = "Boolean", name = "autoFix", value = "是否自动修复(true=修复,false=仅检查)", required = false)
})
public ResponseDto KO006(
@RequestParam(value = "autoFix", required = false, defaultValue = "false") Boolean autoFix
) {
try {
log.info("开始批量查询退款信息,自动修复模式: {}", autoFix);
// 第一步:查询所有需要检查的订单(所有支付成功或者退款中的订单)
log.info("第一步:查询所有需要检查的订单");
String findOrdersSql = "SELECT\n" +
" kot.order_tickets_id,\n" +
" kot.pay_code,\n" +
" kot.order_code,\n" +
" kts.status,\n" +
" kot.user_id\n" +
"FROM kylin_order_tickets AS kot\n" +
" JOIN dragon_orders AS do\n" +
" ON do.code = kot.pay_code\n" +
" JOIN kylin_order_ticket_status AS kts\n" +
" ON kts.order_id = kot.order_tickets_id\n" +
"WHERE 1 = 1\n" +
" AND (kts.status = 1 OR kts.status = 3)\n" +
// "AND kot.order_code = '582523623766241280872835556'\n" +
" AND kot.performance_title = '2025中山草莓音乐节'\n" +
" AND do.status = 1;";
Connection connection = DriverManager.getConnection(SQL_URL, SQL_USER, SQL_PWD);
PreparedStatement stmt = connection.prepareStatement(findOrdersSql);
ResultSetImpl rs = (ResultSetImpl) stmt.executeQuery();
int totalCount = 0;
int hasRedisNoMysqlCount = 0; // Redis有但MySQL没有的订单数
int fixedCount = 0; // 成功修复的订单数
int failCount = 0; // 修复失败的订单数
// 记录缺失数据的订单信息
java.util.List<String> missingDataList = new java.util.ArrayList<>();
java.util.List<String> fixedDataList = new java.util.ArrayList<>();
java.util.List<String> failedDataList = new java.util.ArrayList<>();
// 第二步:逐个检查订单
log.info("第二步:开始逐个检查订单");
while (rs.next()) {
totalCount++;
String orderTicketsId = rs.getString("order_tickets_id");
String orderCode = rs.getString("order_code");
String userId = rs.getString("user_id");
try {
log.info("正在检查订单 {} - orderCode: {}, orderTicketsId: {}", totalCount, orderCode, orderTicketsId);
// 第二步:从 Redis 获取退款记录(使用 key 拼接)
Object redisObj = redisDataSourceUtil.getRedisKylinUtil().get(KylinRedisConst.ORDER_REFUND_BY_ORDER_ID + orderTicketsId);
List<KylinOrderRefundsVo> redisRefundList = null;
if (redisObj != null) {
redisRefundList = (List<KylinOrderRefundsVo>) redisObj;
log.info("Redis中找到退款记录 - orderCode: {}, 记录数: {}", orderCode, redisRefundList.size());
}
// 第三步:从 MySQL 查询退款记录
String checkRefundSql = "SELECT COUNT(1) as refund_count FROM kylin_order_refunds WHERE order_tickets_id = ?";
PreparedStatement refundStmt = connection.prepareStatement(checkRefundSql);
refundStmt.setString(1, orderTicketsId);
ResultSetImpl refundRs = (ResultSetImpl) refundStmt.executeQuery();
int mysqlRefundCount = 0;
if (refundRs.next()) {
mysqlRefundCount = refundRs.getInt("refund_count");
}
log.info("MySQL中退款记录数 - orderCode: {}, 记录数: {}", orderCode, mysqlRefundCount);
// 第四步:对比 Redis 和 MySQL 的数据
if (redisRefundList != null && redisRefundList.size() > 0 && mysqlRefundCount == 0) {
hasRedisNoMysqlCount++;
// 构建详细的缺失数据信息
StringBuilder missingInfo = new StringBuilder();
missingInfo.append("\n========================================\n");
missingInfo.append(String.format("订单编号: %s\n", orderCode));
missingInfo.append(String.format("订单ID: %s\n", orderTicketsId));
missingInfo.append(String.format("用户ID: %s\n", userId));
missingInfo.append(String.format("Redis退款记录数: %d\n", redisRefundList.size()));
missingInfo.append("Redis退款详情:\n");
for (int i = 0; i < redisRefundList.size(); i++) {
KylinOrderRefundsVo refundVo = redisRefundList.get(i);
missingInfo.append(String.format(" [%d] 退款ID: %s\n", i + 1, refundVo.getOrderRefundsId()));
missingInfo.append(String.format(" 订单票ID: %s\n", refundVo.getOrderTicketsId()));
missingInfo.append(String.format(" 退款状态: %s\n", refundVo.getStatus()));
missingInfo.append(String.format(" 退款类型: %s\n", refundVo.getType()));
missingInfo.append(String.format(" 退款金额: %s\n", refundVo.getPrice()));
missingInfo.append(String.format(" 快递费: %s\n", refundVo.getPriceExpress()));
missingInfo.append(String.format(" 创建时间: %s\n", refundVo.getCreatedAt()));
if (refundVo.getOrderRefundEntitiesVoList() != null && !refundVo.getOrderRefundEntitiesVoList().isEmpty()) {
missingInfo.append(String.format(" 退款实体数: %d\n", refundVo.getOrderRefundEntitiesVoList().size()));
}
}
missingInfo.append("========================================\n");
String missingInfoStr = missingInfo.toString();
missingDataList.add(missingInfoStr);
log.warn("发现数据缺失!{}", missingInfoStr);
// 第五步:如果开启自动修复,则进行修复操作
if (autoFix) {
log.info("开始修复订单 - orderCode: {}, 需要修复 {} 条退款记录", orderCode, redisRefundList.size());
boolean allSuccess = true;
StringBuilder fixInfo = new StringBuilder();
fixInfo.append(String.format("订单编号: %s, 订单ID: %s\n", orderCode, orderTicketsId));
for (KylinOrderRefundsVo refundVo : redisRefundList) {
try {
// 插入 kylin_order_refunds 主表
KylinOrderRefunds kylinOrderRefunds = new KylinOrderRefunds();
kylinOrderRefunds.setOrderRefundsId(refundVo.getOrderRefundsId());
kylinOrderRefunds.setOrderTicketsId(refundVo.getOrderTicketsId());
kylinOrderRefunds.setOrderRefundCode(refundVo.getOrderRefundCode());
kylinOrderRefunds.setPrice(refundVo.getPrice());
kylinOrderRefunds.setPriceExpress(refundVo.getPriceExpress());
kylinOrderRefunds.setStatus(refundVo.getStatus());
kylinOrderRefunds.setType(refundVo.getType());
kylinOrderRefunds.setCreatedAt(refundVo.getCreatedAt());
kylinOrderRefunds.setApplicantId(refundVo.getApplicantId());
kylinOrderRefunds.setApplicantName(refundVo.getApplicantName());
kylinOrderRefunds.setApplicantAt(refundVo.getApplicantAt());
kylinOrderRefundsMapper.insert(kylinOrderRefunds);
log.info("成功插入退款主表 - orderRefundsId: {}", refundVo.getOrderRefundsId());
// 时间格式化器
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 从 MongoDB 查询退款实体列表
List<KylinOrderRefundEntitiesVo> mongoEntitiesList = mongoTemplate.find(
Query.query(Criteria.where("orderRefundsId").is(refundVo.getOrderRefundsId())),
KylinOrderRefundEntitiesVo.class,
KylinOrderRefundEntitiesVo.class.getSimpleName()
);
if (!mongoEntitiesList.isEmpty()) {
for (KylinOrderRefundEntitiesVo entityVo : mongoEntitiesList) {
KylinOrderRefundEntities entity = new KylinOrderRefundEntities();
entity.setOrderRefundsEntitiesId(entityVo.getOrderRefundsEntitiesId());
entity.setOrderRefundsId(entityVo.getOrderRefundsId());
entity.setOrderTicketEntitiesId(entityVo.getOrderTicketEntitiesId());
entity.setRefundPrice(entityVo.getRefundPrice());
// 安全解析时间
if (entityVo.getCreatedAt() != null && !entityVo.getCreatedAt().isEmpty()) {
try {
entity.setCreatedAt(LocalDateTime.parse(entityVo.getCreatedAt(), formatter));
} catch (Exception e) {
log.warn("解析 createdAt 失败: {}", entityVo.getCreatedAt());
}
}
kylinOrderRefundsEntitiesMapper.insert(entity);
log.info("成功插入退款实体 - orderRefundEntitiesId: {}", entity.getOrderTicketEntitiesId());
}
}
// 从 MongoDB 查询退款图片列表
List<KylinOrderRefundPicVo> mongoPicsList = mongoTemplate.find(
Query.query(Criteria.where("orderRefundsId").is(refundVo.getOrderRefundsId())),
KylinOrderRefundPicVo.class,
KylinOrderRefundPicVo.class.getSimpleName()
);
if (!mongoPicsList.isEmpty()) {
for (KylinOrderRefundPicVo picVo : mongoPicsList) {
KylinOrderRefundPic pic = new KylinOrderRefundPic();
pic.setRefundPicId(picVo.getRefundPicId());
pic.setOrderRefundsId(picVo.getOrderRefundsId());
pic.setPicUrl(picVo.getPicUrl());
// 安全解析时间
if (picVo.getCreatedAt() != null && !picVo.getCreatedAt().isEmpty()) {
try {
pic.setCreatedAt(LocalDateTime.parse(picVo.getCreatedAt(), formatter));
} catch (Exception e) {
log.warn("解析 createdAt 失败: {}", picVo.getCreatedAt());
}
}
kylinOrderRefundPicMapper.insert(pic);
log.info("成功插入退款图片 - orderRefundPicId: {}", pic.getRefundPicId());
}
}
// 更新订单状态表 kylin_order_ticket_status - 将状态设为 3(退款中)
String updateStatusSql = "UPDATE kylin_order_ticket_status SET status = 3, updated_at = ? WHERE order_id = ?";
PreparedStatement updateStatusStmt = connection.prepareStatement(updateStatusSql);
updateStatusStmt.setObject(1, LocalDateTime.now());
updateStatusStmt.setString(2, orderTicketsId);
int statusUpdated = updateStatusStmt.executeUpdate();
log.info("成功更新订单状态为退款中 - orderTicketsId: {}, 影响行数: {}", orderTicketsId, statusUpdated);
// 更新订单实体表 kylin_order_ticket_entities - 根据退款实体将支付状态设为 2(退款中)
if (!mongoEntitiesList.isEmpty()) {
for (KylinOrderRefundEntitiesVo entityVo : mongoEntitiesList) {
String updateEntitiesSql = "UPDATE kylin_order_ticket_entities SET is_payment = 2, updated_at = ? WHERE order_ticket_entities_id = ?";
PreparedStatement updateEntitiesStmt = connection.prepareStatement(updateEntitiesSql);
updateEntitiesStmt.setObject(1, LocalDateTime.now());
updateEntitiesStmt.setString(2, entityVo.getOrderTicketEntitiesId());
int entitiesUpdated = updateEntitiesStmt.executeUpdate();
log.info("成功更新订单实体为退款中 - orderTicketEntitiesId: {}, 影响行数: {}",
entityVo.getOrderTicketEntitiesId(), entitiesUpdated);
}
}
// // 更新 MongoDB 中的订单状态
// Document orderStatusUpdateDoc = new Document("$set", new Document()
// .append("status", 3)
// .append("updatedAt", DateUtil.getNowTime())
// );
// mongoTemplate.getCollection(KylinOrderTicketVo.class.getSimpleName()).updateOne(
// Query.query(Criteria.where("orderTicketsId").is(orderTicketsId)).getQueryObject(),
// orderStatusUpdateDoc
// );
// log.info("成功更新MongoDB订单状态为退款中 - orderTicketsId: {}", orderTicketsId);
//
// // 更新 MongoDB 中的订单实体状态
// if (!mongoEntitiesList.isEmpty()) {
// for (KylinOrderRefundEntitiesVo entityVo : mongoEntitiesList) {
// Document entitiesStatusUpdateDoc = new Document("$set", new Document()
// .append("isPayment", 2)
// .append("updatedAt", DateUtil.getNowTime())
// );
// mongoTemplate.getCollection(KylinOrderTicketEntitiesVo.class.getSimpleName()).updateOne(
// Query.query(Criteria.where("orderTicketEntitiesId").is(entityVo.getOrderTicketEntitiesId())).getQueryObject(),
// entitiesStatusUpdateDoc
// );
// log.info("成功更新MongoDB订单实体为退款中 - orderTicketEntitiesId: {}", entityVo.getOrderTicketEntitiesId());
// }
// }
fixInfo.append(String.format(" ✓ 退款ID: %s, 状态: %s, 金额: %s\n",
refundVo.getOrderRefundsId(), refundVo.getStatus(), refundVo.getPrice()));
} catch (Exception e) {
allSuccess = false;
fixInfo.append(String.format(" ✗ 退款ID: %s 修复失败: %s\n",
refundVo.getOrderRefundsId(), e.getMessage()));
log.error("修复退款记录失败 - orderRefundsId: {}, error: ", refundVo.getOrderRefundsId(), e);
}
}
if (allSuccess) {
fixedCount++;
fixedDataList.add("✓ " + fixInfo.toString());
log.info("订单修复成功 - orderCode: {}", orderCode);
} else {
failCount++;
failedDataList.add("✗ " + fixInfo.toString());
log.error("订单修复部分失败 - orderCode: {}", orderCode);
}
}
}
} catch (Exception e) {
log.error("检查订单失败 - orderCode: {}, error: ", orderCode, e);
if (autoFix) {
failCount++;
failedDataList.add(String.format("✗ 订单: %s, 错误: %s\n", orderCode, e.getMessage()));
}
}
}
connection.close();
// 第六步:生成汇总报告
StringBuilder summaryReport = new StringBuilder();
summaryReport.append("\n\n========================================\n");
if (autoFix) {
summaryReport.append(" 数据修复汇总报告\n");
} else {
summaryReport.append(" 数据检查汇总报告\n");
}
summaryReport.append("========================================\n");
summaryReport.append(String.format("检查订单总数: %d\n", totalCount));
summaryReport.append(String.format("Redis有但MySQL缺失的订单数: %d\n", hasRedisNoMysqlCount));
if (autoFix) {
summaryReport.append(String.format("成功修复: %d\n", fixedCount));
summaryReport.append(String.format("修复失败: %d\n", failCount));
}
summaryReport.append("========================================\n");
if (!missingDataList.isEmpty()) {
summaryReport.append("\n缺失数据详情:\n");
for (String missingInfo : missingDataList) {
summaryReport.append(missingInfo);
}
}
if (autoFix) {
if (!fixedDataList.isEmpty()) {
summaryReport.append("\n✓ 成功修复的订单:\n");
for (String info : fixedDataList) {
summaryReport.append(info).append("\n");
}
}
if (!failedDataList.isEmpty()) {
summaryReport.append("\n✗ 修复失败的订单:\n");
for (String info : failedDataList) {
summaryReport.append(info).append("\n");
}
}
} else {
if (hasRedisNoMysqlCount > 0) {
summaryReport.append("\n提示:如需自动修复,请添加参数 autoFix=true\n");
} else {
summaryReport.append("\n恭喜!没有发现数据缺失的订单。\n");
}
}
summaryReport.append("\n========================================\n");
String reportStr = summaryReport.toString();
log.info(reportStr);
if (autoFix) {
log.info("批量修复完成 - 总数: {}, 缺失: {}, 成功: {}, 失败: {}",
totalCount, hasRedisNoMysqlCount, fixedCount, failCount);
return ResponseDto.success(String.format("批量修复完成!\n总订单数: %d\n缺失数据订单: %d\n成功修复: %d\n修复失败: %d\n\n%s",
totalCount, hasRedisNoMysqlCount, fixedCount, failCount, reportStr));
} else {
log.info("批量检查完成 - 总数: {}, Redis有但MySQL缺失: {}", totalCount, hasRedisNoMysqlCount);
return ResponseDto.success(String.format("批量检查完成!\n总订单数: %d\nRedis有但MySQL缺失: %d\n\n%s",
totalCount, hasRedisNoMysqlCount, reportStr));
}
} catch (Exception e) {
log.error("批量检查订单退款数据失败, error: ", e);
return ResponseDto.failure("批量检查失败: " + e.getMessage());
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment