This commit is contained in:
dzc committed 2026-09-29 19:55:58 +08:00
commit da167a46ef
5 files changed
+218

No files matched your search

@@ -0,0 +1,34 @@
package com.bsd.pjcloud.admin.api.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
@Data
@TableName("seata_tx_probe")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "Seata事务探测")
public class SeataTxProbeEntity extends Model<SeataTxProbeEntity> {
@TableId(type = IdType.ASSIGN_ID)
@Schema(description = "主键")
private Long id;
@Schema(description = "本次探测标记")
private String mark;
@Schema(description = "commit / rollback")
private String caseType;
@Schema(description = "备注")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}
@@ -0,0 +1,100 @@
package com.bsd.pjcloud.admin.controller;
import com.bsd.pjcloud.admin.service.SeataTxProbeService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 后台一键探测 Seata:提交应落库,回滚应不落库。
*/
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/seataProbe")
@Tag(description = "seataProbe", name = "Seata事务探测")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SeataTxProbeController {
private final SeataTxProbeService seataTxProbeService;
@Operation(summary = "Seata提交探测")
@SysLog("Seata提交探测")
@PostMapping("/commit")
@PreAuthorize("@pms.hasPermission('admin_seata_probe')")
public R<Map<String, Object>> commit() {
try {
Map<String, Object> started = seataTxProbeService.testCommit();
String mark = String.valueOf(started.get("mark"));
long cnt = seataTxProbeService.countByMark(mark);
Map<String, Object> body = new HashMap<>(started);
body.put("rowCount", cnt);
body.put("pass", cnt == 1);
body.put("message", cnt == 1
? "提交成功:已连上 TC,数据已落库(seata_tx_probe)"
: "提交异常:事务看似结束但库中无记录,请查 Seata/undo_log/数据源代理");
return cnt == 1 ? R.ok(body) : R.failed(body, String.valueOf(body.get("message")));
}
catch (Exception e) {
log.error("[seataProbe/commit] 失败", e);
return R.failed("提交探测失败:" + e.getMessage());
}
}
@Operation(summary = "Seata回滚探测")
@SysLog("Seata回滚探测")
@PostMapping("/rollback")
@PreAuthorize("@pms.hasPermission('admin_seata_probe')")
public R<Map<String, Object>> rollback() {
String mark = null;
String xid = null;
try {
seataTxProbeService.testRollbackExpectThrow();
return R.failed("回滚探测异常:应抛错触发回滚,但方法正常返回");
}
catch (IllegalStateException e) {
String msg = e.getMessage();
if (msg != null && msg.startsWith("SEATA_PROBE_ROLLBACK:")) {
String payload = msg.substring("SEATA_PROBE_ROLLBACK:".length());
int p = payload.indexOf('|');
if (p >= 0) {
mark = payload.substring(0, p);
xid = payload.substring(p + 1);
}
else {
mark = payload;
}
}
}
catch (Exception e) {
log.error("[seataProbe/rollback] 非预期异常", e);
return R.failed("回滚探测失败:" + e.getMessage());
}
if (mark == null) {
return R.failed("回滚探测失败:未拿到 mark");
}
long cnt = seataTxProbeService.countByMark(mark);
Map<String, Object> body = new HashMap<>(6);
body.put("mark", mark);
body.put("xid", xid);
body.put("caseType", "rollback");
body.put("rowCount", cnt);
body.put("pass", cnt == 0);
body.put("message", cnt == 0
? "回滚成功:已连上 TC,插入被全局回滚,库中无残留"
: "回滚失败:库中仍有记录,全局事务可能未生效(检查数据源代理/Seata)");
return cnt == 0 ? R.ok(body) : R.failed(body, String.valueOf(body.get("message")));
}
}
@@ -0,0 +1,9 @@
package com.bsd.pjcloud.admin.mapper;
import com.bsd.pjcloud.admin.api.entity.SeataTxProbeEntity;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SeataTxProbeMapper extends PjcloudBaseMapper<SeataTxProbeEntity> {
}
@@ -0,0 +1,18 @@
package com.bsd.pjcloud.admin.service;
import java.util.Map;
/**
* Seata 全局事务探测:验证 TC 连通与 AT 提交/回滚。
*/
public interface SeataTxProbeService {
Map<String, Object> testCommit();
/**
* 故意抛错触发全局回滚;调用方捕获异常后查库校验。
*/
Map<String, Object> testRollbackExpectThrow();
long countByMark(String mark);
}
@@ -0,0 +1,57 @@
package com.bsd.pjcloud.admin.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bsd.pjcloud.admin.api.entity.SeataTxProbeEntity;
import com.bsd.pjcloud.admin.mapper.SeataTxProbeMapper;
import com.bsd.pjcloud.admin.service.SeataTxProbeService;
import lombok.RequiredArgsConstructor;
import org.apache.seata.core.context.RootContext;
import org.apache.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Service
@RequiredArgsConstructor
public class SeataTxProbeServiceImpl implements SeataTxProbeService {
private final SeataTxProbeMapper seataTxProbeMapper;
@Override
@GlobalTransactional(name = "seata-probe-commit", rollbackFor = Exception.class)
public Map<String, Object> testCommit() {
String mark = "C-" + UUID.randomUUID().toString().replace("-", "");
insertRow(mark, "commit", "commit probe");
Map<String, Object> out = new HashMap<>(4);
out.put("mark", mark);
out.put("xid", RootContext.getXID());
out.put("caseType", "commit");
return out;
}
@Override
@GlobalTransactional(name = "seata-probe-rollback", rollbackFor = Exception.class)
public Map<String, Object> testRollbackExpectThrow() {
String mark = "R-" + UUID.randomUUID().toString().replace("-", "");
insertRow(mark, "rollback", "rollback probe before throw");
throw new IllegalStateException("SEATA_PROBE_ROLLBACK:" + mark + "|" + RootContext.getXID());
}
@Override
public long countByMark(String mark) {
return seataTxProbeMapper.selectCount(Wrappers.<SeataTxProbeEntity>lambdaQuery()
.eq(SeataTxProbeEntity::getMark, mark));
}
private void insertRow(String mark, String caseType, String remark) {
SeataTxProbeEntity row = new SeataTxProbeEntity();
row.setMark(mark);
row.setCaseType(caseType);
row.setRemark(remark);
row.setCreateTime(LocalDateTime.now());
seataTxProbeMapper.insert(row);
}
}