租移动
This commit is contained in:
@@ -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;
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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>' +
|
||||
|
||||
Reference in New Issue
Block a user