租移动
This commit is contained in:
@@ -12,7 +12,7 @@ CREATE TABLE `sys_operation_log` (
|
|||||||
`class_method` VARCHAR(512) NOT NULL COMMENT '类名.方法名',
|
`class_method` VARCHAR(512) NOT NULL COMMENT '类名.方法名',
|
||||||
`request_params` TEXT COMMENT '请求参数(脱敏后)',
|
`request_params` TEXT COMMENT '请求参数(脱敏后)',
|
||||||
`response_data` MEDIUMBLOB COMMENT 'GZIP压缩后的响应JSON',
|
`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',
|
`cost_time` BIGINT NOT NULL COMMENT '耗时ms',
|
||||||
`user_agent` VARCHAR(512) DEFAULT NULL,
|
`user_agent` VARCHAR(512) DEFAULT NULL,
|
||||||
`error_msg` TEXT COMMENT '异常信息(截断2000)',
|
`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.SensitiveDataUtil;
|
||||||
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
|
import com.yunhai.bschclassmanage.utils.SyncLogRetryContext;
|
||||||
import com.yunhai.bschclassmanage.utils.UserContext;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
@@ -70,11 +75,11 @@ public class SysOperationLogAspect {
|
|||||||
Object result = null;
|
Object result = null;
|
||||||
try {
|
try {
|
||||||
result = point.proceed();
|
result = point.proceed();
|
||||||
entity.setStatusCode(200);
|
entity.setStatusCode(resolveSuccessCode(result));
|
||||||
fillResponse(entity, result, DEFAULT_MAX_RESPONSE_SIZE);
|
fillResponse(entity, result, DEFAULT_MAX_RESPONSE_SIZE);
|
||||||
return result;
|
return result;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
entity.setStatusCode(500);
|
entity.setStatusCode(resolveErrorCode(e));
|
||||||
entity.setErrorMsg(buildErrorMsg(e));
|
entity.setErrorMsg(buildErrorMsg(e));
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} 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() {
|
private HttpServletRequest getRequest() {
|
||||||
ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes a = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
return a != null ? a.getRequest() : null;
|
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
|
private byte[] responseData; // GZIP 压缩后的响应 JSON
|
||||||
|
|
||||||
@Column(name = "status_code", nullable = false)
|
@Column(name = "status_code", nullable = false)
|
||||||
private Integer statusCode; // 200 成功 / 500 异常
|
private Integer statusCode; // 业务状态码:0 成功 / 非 0 失败(全局返回对象 code)
|
||||||
|
|
||||||
@Column(name = "cost_time", nullable = false)
|
@Column(name = "cost_time", nullable = false)
|
||||||
private Long costTime; // ms
|
private Long costTime; // ms
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.yunhai.bschclassmanage.enums;
|
package com.yunhai.bschclassmanage.enums;
|
||||||
|
|
||||||
|
import com.yunhai.bschclassmanage.model.vo.SelectIntVO;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -80,4 +82,10 @@ public enum NodeType {
|
|||||||
|
|
||||||
return null;
|
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 操作。
|
* 重试:按记录的 classMethod + requestParams 反射重放原始 Controller 操作。
|
||||||
* 说明:
|
* 说明:
|
||||||
* - 仅允许 statusCode != 200(失败)的记录重试;
|
* - 仅允许 statusCode != 0(失败)的记录重试;
|
||||||
* - 仅支持入参为 JSON 数组形式(POST body / @RequestBody)。GET query 串无法可靠重建,直接拒绝;
|
* - 仅支持入参为 JSON 数组形式(POST body / @RequestBody)。GET query 串无法可靠重建,直接拒绝;
|
||||||
* - 经 Spring 代理调用,会再次走 SysOperationLogAspect 切面、生成一条新的审计日志;
|
* - 经 Spring 代理调用,会再次走 SysOperationLogAspect 切面、生成一条新的审计日志;
|
||||||
* - 重放前还原原操作人上下文(UserContext),使新审计日志记录正确的操作人;
|
* - 重放前还原原操作人上下文(UserContext),使新审计日志记录正确的操作人;
|
||||||
@@ -121,8 +121,8 @@ public class SysOperationLogServiceImpl implements ISysOperationLogService {
|
|||||||
public Object retry(Long id) {
|
public Object retry(Long id) {
|
||||||
SysOperationLog e = getById(id);
|
SysOperationLog e = getById(id);
|
||||||
if (e == null) throw new DException("审计日志不存在(id=" + id + ")");
|
if (e == null) throw new DException("审计日志不存在(id=" + id + ")");
|
||||||
if (e.getStatusCode() != null && e.getStatusCode() == 200) {
|
if (e.getStatusCode() != null && e.getStatusCode() == 0) {
|
||||||
throw new DException("该操作已成功(statusCode=200),无需重试");
|
throw new DException("该操作已成功(statusCode=0),无需重试");
|
||||||
}
|
}
|
||||||
// 多校隔离:不允许重试其他学校的操作日志
|
// 多校隔离:不允许重试其他学校的操作日志
|
||||||
if (e.getSchId() != null && UserContext.getSchId() != null
|
if (e.getSchId() != null && UserContext.getSchId() != null
|
||||||
|
|||||||
@@ -321,7 +321,7 @@
|
|||||||
<option>PUT</option><option>DELETE</option>
|
<option>PUT</option><option>DELETE</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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>
|
<div class="fi"><label>日志级别</label>
|
||||||
<select id="op_logLevel">
|
<select id="op_logLevel">
|
||||||
<option value="">全部</option>
|
<option value="">全部</option>
|
||||||
@@ -786,14 +786,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tb.innerHTML = rows.map(r => {
|
tb.innerHTML = rows.map(r => {
|
||||||
const ok = r.statusCode === 200;
|
const ok = r.statusCode === 0;
|
||||||
return '<tr>' +
|
return '<tr>' +
|
||||||
'<td class="mono">' + r.id + '</td>' +
|
'<td class="mono">' + r.id + '</td>' +
|
||||||
'<td>' + (OP_LEVEL[r.logLevel] || '<span class="tag gray">' + (r.logLevel == null ? '-' : r.logLevel) + '</span>') + '</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">' + escHtml(r.operationType || '-') + '</td>' +
|
||||||
'<td class="mono"><span class="cell-clip" title="' + escHtml(r.requestUri) + '">' + escHtml(r.requestUri) + '</span></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 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 class="mono">' + (r.costTime == null ? '-' : r.costTime + ' ms') + '</td>' +
|
||||||
'<td>' + escHtml(r.userName || (r.userId == null ? '-' : r.userId)) + '</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>' +
|
'<td>' + escHtml((r.schId != null && schoolMap[r.schId]) ? schoolMap[r.schId] : (r.schId == null ? '-' : r.schId)) + '</td>' +
|
||||||
|
|||||||
Reference in New Issue
Block a user