租移动

This commit is contained in:
2026-08-21 09:47:45 +08:00
parent d923649a8d
commit c0dd74318c
9 changed files with 146 additions and 10 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ CREATE TABLE `sys_operation_log` (
`class_method` VARCHAR(512) NOT NULL COMMENT '类名.方法名',
`request_params` TEXT COMMENT '请求参数(脱敏后)',
`response_data` MEDIUMBLOB COMMENT 'GZIP压缩后的响应JSON',
`status_code` INT NOT NULL COMMENT '200成功 500异常',
`status_code` INT NOT NULL COMMENT '业务状态码 0成功 非0失败',
`cost_time` BIGINT NOT NULL COMMENT '耗时ms',
`user_agent` VARCHAR(512) DEFAULT NULL,
`error_msg` TEXT COMMENT '异常信息(截断2000)',
@@ -9,6 +9,11 @@ import com.yunhai.bschclassmanage.utils.OtherUtil;
import com.yunhai.bschclassmanage.utils.SensitiveDataUtil;
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.platform.apistd.ApiResponse;
import com.yunhai.platform.utils.exception.DException;
import com.yunhai.platform.utils.exception.ErrorCode;
import com.yunhai.platform.utils.exception.IErrorCode;
import com.yunhai.platform.utils.http.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.ProceedingJoinPoint;
@@ -70,11 +75,11 @@ public class SysOperationLogAspect {
Object result = null;
try {
result = point.proceed();
entity.setStatusCode(200);
entity.setStatusCode(resolveSuccessCode(result));
fillResponse(entity, result, DEFAULT_MAX_RESPONSE_SIZE);
return result;
} catch (Throwable e) {
entity.setStatusCode(500);
entity.setStatusCode(resolveErrorCode(e));
entity.setErrorMsg(buildErrorMsg(e));
throw e;
} finally {
@@ -144,6 +149,35 @@ public class SysOperationLogAspect {
}
}
/**
* 成功路径:提取全局返回对象的业务状态码。
* - Controller 直接返回 ApiResponse / ApiResult 时取其 code;
* - 其余(普通对象 / void / null)由平台 ApiResponseAdvice 统一包装为 success,code = SUCCESS(0)。
*/
private int resolveSuccessCode(Object result) {
if (result instanceof ApiResponse) {
return ((ApiResponse<?>) result).getCode();
}
if (result instanceof ApiResult) {
return ((ApiResult<?>) result).getCode();
}
return ErrorCode.SUCCESS.getCode();
}
/**
* 异常路径:从业务异常提取错误码,兜底 INTERNAL_SERVER_ERROR。
* DException.getCode() 返回 IErrorCode(可能为 null),需二次取 int code。
*/
private int resolveErrorCode(Throwable e) {
if (e instanceof DException) {
IErrorCode ec = ((DException) e).getCode();
if (ec != null) {
return ec.getCode();
}
}
return ErrorCode.INTERNAL_SERVER_ERROR.getCode();
}
private HttpServletRequest getRequest() {
ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return a != null ? a.getRequest() : null;
@@ -0,0 +1,48 @@
package com.yunhai.bschclassmanage.controller.api;
import com.yunhai.bschclassmanage.config.Config;
import com.yunhai.bschclassmanage.enums.NodeType;
import com.yunhai.bschclassmanage.model.dto.SendMoveUserGroupDTO;
import com.yunhai.bschclassmanage.model.dto.SendUserGroupDTO;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import com.yunhai.bschclassmanage.service.ISendUserBaseService;
import com.yunhai.bschclassmanage.utils.UserContext;
import com.yunhai.platform.openapi.client.YschBaseOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;
@RestController
@RequestMapping(Config.API_PATH+"/usergroup/")
@CrossOrigin(originPatterns = {"*"}, allowCredentials = "true")
public class SchUserGroupController {
@Autowired
private ISendUserBaseService sendUserBaseService;
@GetMapping("getNodeType")
public List<SelectIntVO> getNodeType() {
return NodeType.select();
}
@GetMapping("addGroup")
public void addGroup(@Valid @RequestBody SendUserGroupDTO groupdto) {
sendUserBaseService.addGroup(groupdto.getName(),groupdto.getParentId(),groupdto.getSort(),NodeType.getById(groupdto.getType()),UserContext.getSchId());
}
@GetMapping("editGroup")
public void editGroup(@Valid @RequestBody SendUserGroupDTO groupdto) {
sendUserBaseService.editGroup(groupdto.getId(),groupdto.getName(),groupdto.getParentId(),groupdto.getSort(),NodeType.getById(groupdto.getType()),UserContext.getSchId());
}
@GetMapping("deleteGroup")
public void deleteGroup(@RequestParam Integer id) {
sendUserBaseService.deleteGroup(id,UserContext.getSchId());
}
}
@@ -60,7 +60,7 @@ public class SysOperationLog implements Serializable {
private byte[] responseData; // GZIP 压缩后的响应 JSON
@Column(name = "status_code", nullable = false)
private Integer statusCode; // 200 成功 / 500 异常
private Integer statusCode; // 业务状态码:0 成功 / 非 0 失败(全局返回对象 code)
@Column(name = "cost_time", nullable = false)
private Long costTime; // ms
@@ -1,5 +1,7 @@
package com.yunhai.bschclassmanage.enums;
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
@@ -80,4 +82,10 @@ public enum NodeType {
return null;
}
public static List<SelectIntVO> select() {
List<SelectIntVO> list= CourseType.list().stream()
.map(type -> new SelectIntVO(type.getId(), type.getName()))
.collect(Collectors.toList());
return new ArrayList<>(list);
}
}
@@ -0,0 +1,17 @@
package com.yunhai.bschclassmanage.model.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 添加组员请求DTO
*/
@Data
public class SendMoveUserGroupDTO {
@NotNull(message = "用户称不能为空")
private Integer userId;
@NotNull(message = "请选择移动组")
private Integer groupId;
}
@@ -0,0 +1,29 @@
package com.yunhai.bschclassmanage.model.dto;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 添加组员请求DTO
*/
@Data
public class SendUserGroupDTO {
private Integer id;
@NotNull(message = "组名称不能为空")
private String name;
@NotNull(message = "请选择父节点")
private Integer parentId;
@NotNull(message = "教师ID列表不能为空")
private List<Integer> teaIds;
@NotNull(message = "请选择组类型")
private Integer type;
private Integer sort;
}
@@ -110,7 +110,7 @@ public class SysOperationLogServiceImpl implements ISysOperationLogService {
/**
* 重试:按记录的 classMethod + requestParams 反射重放原始 Controller 操作。
* 说明:
* - 仅允许 statusCode != 200(失败)的记录重试;
* - 仅允许 statusCode != 0(失败)的记录重试;
* - 仅支持入参为 JSON 数组形式(POST body / @RequestBody)。GET query 串无法可靠重建,直接拒绝;
* - 经 Spring 代理调用,会再次走 SysOperationLogAspect 切面、生成一条新的审计日志;
* - 重放前还原原操作人上下文(UserContext),使新审计日志记录正确的操作人;
@@ -121,8 +121,8 @@ public class SysOperationLogServiceImpl implements ISysOperationLogService {
public Object retry(Long id) {
SysOperationLog e = getById(id);
if (e == null) throw new DException("审计日志不存在(id=" + id + ")");
if (e.getStatusCode() != null && e.getStatusCode() == 200) {
throw new DException("该操作已成功(statusCode=200),无需重试");
if (e.getStatusCode() != null && e.getStatusCode() == 0) {
throw new DException("该操作已成功(statusCode=0),无需重试");
}
// 多校隔离:不允许重试其他学校的操作日志
if (e.getSchId() != null && UserContext.getSchId() != null
+3 -3
View File
@@ -321,7 +321,7 @@
<option>PUT</option><option>DELETE</option>
</select>
</div>
<div class="fi"><label>状态码</label><input type="number" id="op_statusCode" placeholder="如 200 / 500"/></div>
<div class="fi"><label>状态码</label><input type="number" id="op_statusCode" placeholder="如 0 / 非0"/></div>
<div class="fi"><label>日志级别</label>
<select id="op_logLevel">
<option value="">全部</option>
@@ -786,14 +786,14 @@
return;
}
tb.innerHTML = rows.map(r => {
const ok = r.statusCode === 200;
const ok = r.statusCode === 0;
return '<tr>' +
'<td class="mono">' + r.id + '</td>' +
'<td>' + (OP_LEVEL[r.logLevel] || '<span class="tag gray">' + (r.logLevel == null ? '-' : r.logLevel) + '</span>') + '</td>' +
'<td class="mono">' + escHtml(r.operationType || '-') + '</td>' +
'<td class="mono"><span class="cell-clip" title="' + escHtml(r.requestUri) + '">' + escHtml(r.requestUri) + '</span></td>' +
'<td class="mono">' + escHtml(r.requestMethod || '-') + '</td>' +
'<td>' + (ok ? '<span class="tag green">200</span>' : '<span class="tag red">' + escHtml(r.statusCode) + '</span>') + '</td>' +
'<td>' + (ok ? '<span class="tag green">0</span>' : '<span class="tag red">' + escHtml(r.statusCode) + '</span>') + '</td>' +
'<td class="mono">' + (r.costTime == null ? '-' : r.costTime + ' ms') + '</td>' +
'<td>' + escHtml(r.userName || (r.userId == null ? '-' : r.userId)) + '</td>' +
'<td>' + escHtml((r.schId != null && schoolMap[r.schId]) ? schoolMap[r.schId] : (r.schId == null ? '-' : r.schId)) + '</td>' +