spring boot 存储操作日志(mysql)
记录聊天app, 操作日志存储实现
操作日志实体类
SQL(table 数据表)
create table if not exists `br_operate_log`
(
`operate_type` smallint unsigned null comment '操作人类别',
`business_type` smallint unsigned null comment '操作功能',
`method` varchar(16) null comment '请求方法',
`topic` varchar(32) null comment '主题',
`summary` varchar(120) null comment '概况',
`notes` varchar(255) null comment '备注',
`url` varchar(255) null comment '请求url',
`params` varchar(514) null comment '请求参数',
`response` varchar(2048) null comment '请求响应体',
`is_success` boolean null comment '成功处理',
`id` bigint(20) unsigned primary key,
`create_at` datetime null comment '创建时间',
`create_by` bigint unsigned null comment '创建人',
`create_ip` varchar(64) null comment '创建ip',
`create_address` varchar(64) null comment '创建ip地址 "国家|区域|省|市|宽带"',
`update_at` datetime null comment '最后更新时间',
`update_by` bigint unsigned null comment '最后更新人',
`update_ip` varchar(64) null comment '最后更新ip',
`update_address` varchar(64) null comment '最后更新ip地址 "国家|区域|省|市|宽带"',
`remark` varchar(256) null comment '备注',
`version` int default 0 null comment '乐观锁',
`is_deleted` boolean default false comment '逻辑删除 0 未删除 `null` 删除',
`is_status` boolean default true comment '是否启用 0 关闭 1 开启'
) auto_increment 100 comment '日志记录表';
BaseeEntity(基础实体类)
package com.bluereba.br_pojo.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* @author tuuuuuun
* @since 2022/10/10 0:12
*/
@Data
@ApiModel(value = "基础实体类", description = "")
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty("创建时间")
@TableField(value = "create_at", fill = FieldFill.INSERT)
private LocalDateTime createAt;
@ApiModelProperty("创建人")
@TableField(value = "create_by", fill = FieldFill.INSERT)
private Long createBy;
@ApiModelProperty("创建ip")
@TableField(value = "create_ip", fill = FieldFill.INSERT)
private String createIp;
@ApiModelProperty("创建ip地址 "国家|区域|省|市|宽带"")
@TableField(value = "create_address", fill = FieldFill.INSERT)
private String createAddress;
@ApiModelProperty("最后更新时间")
@TableField(value = "update_at", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateAt;
@ApiModelProperty("最后更新人")
@TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE)
private Long updateBy;
@ApiModelProperty("最后更新ip")
@TableField(value = "update_ip", fill = FieldFill.INSERT_UPDATE)
private String updateIp;
@ApiModelProperty("最后更新ip地址 "国家|区域|省|市|宽带"")
@TableField(value = "update_address", fill = FieldFill.INSERT_UPDATE)
private String updateAddress;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@ApiModelProperty("乐观锁")
@TableField("version")
@Version
private Integer version;
@ApiModelProperty("逻辑删除 0 未删除 `null` 删除")
@TableField("is_deleted")
@TableLogic
private Boolean isDeleted;
@ApiModelProperty("是否启用 0 关闭 1 开启")
@TableField("is_status")
private Boolean isStatus;
}
OperateLogEntity(日志实体类)
package com.bluereba.br_pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.bluereba.br_pojo.entity.type.BusinessType;
import com.bluereba.br_pojo.entity.type.OperatorType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
/**
* <p>
* 日志记录表
* </p>
*
* @author tuuuuuun
* @since 2022-10-19 02:09:15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("br_operate_log")
@EqualsAndHashCode(callSuper = true)
@ApiModel(value = "日志实体", description = "记录操作日志")
public class OperateLogEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* <span style="color: #33c481;font-size: 1.2em">操作参数<spans/>
*/
@TableField("operate_type")
@ApiModelProperty(value = "操作人类别", example = "", notes = "")
private OperatorType operateType;
@TableField("business_type")
@ApiModelProperty(value = "操作功能", example = "", notes = "")
private BusinessType businessType;
@TableField("is_success")
@ApiModelProperty(value = "成功处理", example = "", notes = "")
private Boolean isSuccess;
@TableField("topic")
@ApiModelProperty(value = "操作模块", example = "", notes = "")
private String topic;
@TableField("summary")
@ApiModelProperty(value = "操作概括", example = "", notes = "")
private String summary;
@TableField("notes")
@ApiModelProperty(value = "操作说明", example = "", notes = "")
private String notes;
/**
* <span style="color: #33c481;font-size: 1.2em">请求参数<spans/>
*/
@TableField("url")
@ApiModelProperty(value = "请求url", example = "", notes = "")
private String url;
@TableField("method")
@ApiModelProperty(value = "请求方法", example = "", notes = "")
private String method;
@TableField("params")
@ApiModelProperty(value = "请求参数", example = "", notes = "")
private String params;
/**
* <span style="color: #33c481;font-size: 1.2em">响应参数<spans/>
*/
@ApiModelProperty(value = "响应实体", example = "", notes = "")
private String response;
}
MybatisPlus
Config
@MapperScan("com.bluereba.br_dao.mapper") 指定 mapper 包路径,插件无所谓。也可以直接放到Spring Boot 启动类上
package com.bluereba.br_stomp.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* MybatisPlus 配置类
*
* @author tuuuuuun
* @since 2022/7/15 15:13
*/
@Configuration
@MapperScan("com.bluereba.br_dao.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// TODO.1 分页器
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// TODO.2 乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// TODO.3 阻断全表更新、删除的操作
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
Mapper
BaseMapper 提供了许多单表操作的基础方法
package com.bluereba.br_dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bluereba.br_pojo.entity.OperateLogEntity;
/**
* <p>
* 日志记录表 Mapper 接口
* </p>
*
* @author tuuuuuun
* @since 2022-10-19 02:09:15
*/
public interface OperateLogMapper extends BaseMapper<OperateLogEntity> {}
Service
IService 定义了基础的业务方法,需要实现
package com.bluereba.br_service.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bluereba.br_pojo.entity.OperateLogEntity;
/**
* <p>
* 日志记录表 服务类
* </p>
*
* @author tuuuuuun
* @since 2022-10-19 02:09:15
*/
public interface OperateLogService extends IService<OperateLogEntity> {}
ServiceImpl
ServiceImpl 实现了基础的业务方法
package com.bluereba.br_service.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bluereba.br_dao.mapper.OperateLogMapper;
import com.bluereba.br_pojo.entity.OperateLogEntity;
import com.bluereba.br_service.service.OperateLogService;
import org.springframework.stereotype.Service;
/**
* <p>
* 日志记录表 服务实现类
* </p>
*
* @author tuuuuuun
* @since 2022-10-19 02:09:15
*/
@Service
public class OperateLogServiceImpl extends ServiceImpl<OperateLogMapper, OperateLogEntity> implements OperateLogService {}
Aop
OperatorType 枚举 (操作人类型)
@EnumValue 指定数据库存储的字段
@JsonValue 响应前端的字段
package com.bluereba.br_pojo.entity.type;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author tuuuuuun
* @since 2022/10/18 6:36
*/
@Getter
@AllArgsConstructor
public enum OperatorType {
/**
* 操作人类型
*/
USER(1, "user"),
GROUP(2, "group"),
SYSTEM(3, "system");
@EnumValue
private final Integer id;
@JsonValue
private final String type;
}
BusinessType (操作动作 类型)
@EnumValue 指定数据库存储的字段
@JsonValue 响应前端的字段
package com.bluereba.br_pojo.entity.type;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author tuuuuuun
* @since 2022/10/18 6:36
*/
@Getter
@AllArgsConstructor
public enum BusinessType {
/**
* 操作动作
*/
INSERT(1, "新增操作"),
LOGIN(2, "登录操作"),
LOGOUT(3, "登出操作"),
UPDATE(4, "编辑操作"),
DELETE(5, "删除操作"),
FORCE(6, "强退"),
IMPORT(7, "导入"),
OTHER(8, "其他"),
GENERATE(9, "生成代码"),
CLEAN(10, "清空数据"),
GRANT(11, "授权");
@EnumValue
private final Integer id;
@JsonValue
private final String type;
}
操作日志注解
package com.bluereba.br_operate_log;
import com.bluereba.br_pojo.entity.type.BusinessType;
import com.bluereba.br_pojo.entity.type.OperatorType;
import java.lang.annotation.*;
/**
* @author tuuuuuun
* @since 2022/10/18 6:32
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperateLog {
// 操作模块
String topic() default "";
// 操作概括
String summary() default "";
// 操作说明
String notes() default "";
// 操作功能(OTHER其他、INSERT新增、UPDATE修改、DELETE删除、GRANT授权、EXPORT导出、IMPORT导入、FORCE强退、GENCODE生成代码、CLEAN清空数据)
BusinessType businessType() default BusinessType.OTHER;
// 操作人类型(OTHER其他、MANAGE后台用户、MOBILE手机端用户)
OperatorType operatorType() default OperatorType.USER;
// 保存请求的参数
boolean request() default true;
// 保存响应的参数
boolean response() default true;
// 保存详情数据 {{@code ApiEntity<?> }} save ? data
boolean detail() default false;
}
OperateHelper(构建日志实体类辅助类)
- 响应参数
- 请求参数
- 操作参数
ApiEntity(响应实体类)
package com.bluereba.br_common.api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author tuuuuuun
* @since 2022/7/15 16:05
*/
@Data
@ApiModel("响应对象")
@AllArgsConstructor
@NoArgsConstructor
public class ApiEntity<T> {
@ApiModelProperty(value = "状态码")
Integer status;
@ApiModelProperty(value = "消息")
String message;
@ApiModelProperty(value = "数据")
T data;
@ApiModelProperty(value = "时间")
LocalDateTime timestamp;
public ApiEntity(Integer status, T data) {
this.status = status;
this.data = data;
}
public ApiEntity(Integer status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
public ApiEntity(Integer status, String message) {
this.status = status;
this.message = message;
}
}
package com.bluereba.br_operate_log.utils;
import com.bluereba.br_common.api.ApiEntity;
import com.bluereba.br_operate_log.OperateLog;
import com.bluereba.br_pojo.entity.OperateLogEntity;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
/**
* 日志实体构建 辅助类
*
* @author tuuuuuun
* @since 2022/10/18 20:15
*/
@AllArgsConstructor
@NoArgsConstructor
@Component
public class OperateHelper {
private static ObjectMapper objectMapper;
private OperateLogEntity.OperateLogEntityBuilder builder = null;
private JoinPoint joinPoint = null;
private ApiEntity<?> result = null;
public static OperateHelper builder(@NonNull JoinPoint joinPoint,
@Nullable ApiEntity<?> result
) {
return new OperateHelper(OperateLogEntity.builder(), joinPoint, result);
}
/**
* 获取 {{@code ApiOperation}} 注解
*
* @return ApiOperation annotation
*/
public Optional<ApiOperation> getApiOperationAnnotation() {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
ApiOperation operation = signature.getMethod().getAnnotation(ApiOperation.class);
return Optional.ofNullable(operation);
}
/**
* 获取 {{@code Api}} 注解
*
* @return Api annotation
*/
public Optional<Api> getApiAnnotation() {
Api api = joinPoint.getTarget().getClass()
.getAnnotation(Api.class);
return Optional.ofNullable(api);
}
/**
* 获取 {{@code OperateLog}} 注解
*
* @return OperateLog annotation
*/
public Optional<OperateLog> getOperateLogAnnotation() {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
OperateLog operateLog = signature.getMethod().getAnnotation(OperateLog.class);
return Optional.ofNullable(operateLog);
}
/**
* 存储请求数据
*
* @return 结果
*/
public boolean saveRequestData() {
Optional<OperateLog> operateLog = getOperateLogAnnotation();
if (operateLog.isPresent()) {
OperateLog operate = operateLog.get();
// 存储 请求参数
return operate.response();
}
return false;
}
/**
* 存储响应数据
*
* @return 结果
*/
public boolean saveResponseData() {
Optional<OperateLog> operateLog = getOperateLogAnnotation();
if (operateLog.isPresent()) {
OperateLog operate = operateLog.get();
// 存储 响应参数
return operate.response();
}
return false;
}
@Autowired
public void setObjectMapper(ObjectMapper objectMapper) {
OperateHelper.objectMapper = objectMapper;
}
/**
* <span style="color: #33c481;font-size: 1.2em">操作参数<spans/>
*/
public OperateHelper operate() {
getOperateLogAnnotation().ifPresent(annotation -> {
// 操作功能
builder.businessType(annotation.businessType());
// 操作人类型
builder.operateType(annotation.operatorType());
// 日志主题
builder.topic(annotation.topic());
// 日志概括
builder.summary(annotation.summary());
// 日志说明
builder.notes(annotation.notes());
});
// 覆盖操作
getApiAnnotation().ifPresent(annotation -> {
String topic = StringUtils.join(annotation.tags(), "-");
if (StringUtils.isNotBlank(topic)) {
// 日志主题
builder.topic(topic);
}
});
// 覆盖操作
getApiOperationAnnotation().ifPresent(annotation -> {
// 日志概括
if (StringUtils.isNotBlank(annotation.value())) {
builder.summary(annotation.value());
}
// 日志说明
if (StringUtils.isNotBlank(annotation.notes())) {
builder.notes(annotation.notes());
}
});
return this;
}
/**
* <span style="color: #33c481;font-size: 1.2em">请求参数<spans/>
*/
public OperateHelper request() throws Exception {
if (!saveRequestData()) {
return this;
}
ServletRequestAttributes requestAttributes = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
HttpServletRequest request = requestAttributes.getRequest();
Object[] requestParam = joinPoint.getArgs();
String method = request.getMethod();
String url = request.getRequestURI();
String requestParams = objectMapper.writeValueAsString(requestParam);
builder
.params(requestParams)
.method(method)
.url(url);
}
return this;
}
/**
* <span style="color: #33c481;font-size: 1.2em">响应参数<spans/>
*/
public OperateHelper response() throws JsonProcessingException {
if (!saveResponseData()) {
return this;
}
// 响应实体
Boolean saveDetail = getOperateLogAnnotation()
.map(OperateLog::detail)
.orElse(false);
String result;
if (!saveDetail) {
// copy result, 直接修改默认的 result,会影响返回给前端的数据
ApiEntity<String> copyEntity = new ApiEntity<>();
BeanUtils.copyProperties(this.result, copyEntity);
copyEntity.setData("*");
result = objectMapper.writeValueAsString(copyEntity);
} else {
result = objectMapper.writeValueAsString(this.result);
}
builder.response(result);
return this;
}
public OperateHelper success() {
builder.isSuccess(true);
return this;
}
public OperateHelper fail() {
builder.isSuccess(false);
return this;
}
public OperateLogEntity build() {
return builder.build();
}
}
Mybatis 自动填充
- 填充基础实体类字段(用户参数,创建、更新参数)
PrincipalContextHolder
获取 Spring Security 上下文用户配置
注: UserDetailsEntity(实现了 UserDetails)
package com.bluereba.br_service.utils;
import com.bluereba.br_pojo.entity.security.UserDetailsEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.Optional;
/**
* @author tuuuuuun
* @since 2022/10/16 2:28
*/
public class PrincipalContextHolder {
/**
* 获取 当前用户 ID
*
* @return 用户 id
*/
public static Long getUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetailsEntity) {
UserDetailsEntity userDetails = (UserDetailsEntity) authentication.getPrincipal();
return userDetails.getUid();
}
}
return null;
}
public static Optional<Long> getUserIdOpt() {
return Optional.ofNullable(getUserId());
}
}
MyMetaObjectHandler
当插入实体时,会调用 insertFill 方法
当更新实体时,会调用 updateFill 方法
注: 只有构建了实体对象才会调用方法
package com.bluereba.br_web.handler;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.bluereba.br_common.utils.AddressTemplate;
import com.bluereba.br_service.utils.PrincipalContextHolder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
/**
* mybatis 自动填充, 可能会有填充问题
*
* @author tuuuuuun
* @since 2022/7/15 17:54
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MyMetaObjectHandler implements MetaObjectHandler {
private final HttpServletRequest httpServletRequest;
private final Searcher searcher;
private final AddressTemplate addressTemplate;
@Override
public void insertFill(MetaObject metaObject) {
PrincipalContextHolder.getUserIdOpt().ifPresent(uid -> {
// 填充创建人,更新人
this.strictInsertFill(metaObject, "createBy", Long.class, uid);
this.strictInsertFill(metaObject, "updateBy", Long.class, uid);
});
// 填充创建时间 和 更新时间
this.strictInsertFill(metaObject, "createAt", LocalDateTime::now,
LocalDateTime.class);
this.strictInsertFill(metaObject, "updateAt", LocalDateTime::now,
LocalDateTime.class);
// 填充 ip 已经 ip address
try {
// 糊涂包 工具类, 获取实际请求ip (包括nginx代理)
String ip = ServletUtil.getClientIP(httpServletRequest);
// ip2region 包, 离线将 ip 转 地址("国家|区域|省|市|宽带")
String address = searcher.search(ip);
this.strictInsertFill(metaObject, "createIp", String.class, ip);
this.strictInsertFill(metaObject, "createAddress", String.class, address);
this.strictInsertFill(metaObject, "updateIp", String.class, ip);
this.strictInsertFill(metaObject, "updateAddress", String.class, address);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Override
public void updateFill(MetaObject metaObject) {
PrincipalContextHolder.getUserIdOpt().ifPresent(uid -> {
// 填充创建人,更新人
this.strictUpdateFill(metaObject, "updateBy", Long.class, uid);
});
// 填充 ip 已经 ip address
try {
String ip = ServletUtil.getClientIP(httpServletRequest);
String address = searcher.search(ip);
this.strictUpdateFill(metaObject, "updateIp", String.class, ip);
this.strictUpdateFill(metaObject, "updateAddress", String.class, address);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// 填充更新时间
/*this.strictUpdateFill(metaObject, "updateAt", LocalDateTime::now,
LocalDateTime.class);*/
this.setFieldValByName("updateAt", LocalDateTime.now(), metaObject);
}
}
Aspect
package com.bluereba.br_operate_log.aspect;
import com.bluereba.br_common.api.ApiEntity;
import com.bluereba.br_operate_log.utils.OperateHelper;
import com.bluereba.br_pojo.entity.OperateLogEntity;
import com.bluereba.br_service.service.OperateLogService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @author tuuuuuun
* @since 2022/3/2 20:43
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperateLogAspect {
private final OperateLogService operateLogService;
/**
* 通过注解类标识 控制操作 (OperateLog 注解标识)
*/
@Pointcut("@annotation(com.bluereba.br_operate_log.OperateLog)")
public void operateLog() {
}
/**
* 方法成功执行
*/
@Async
@SneakyThrows
@AfterReturning(value = "operateLog()", returning = "result")
public void afterReturnOperate(JoinPoint joinPoint, ApiEntity<?> result) {
OperateLogEntity operateEntity = OperateHelper.builder(joinPoint, result)
.request()
.response()
.operate()
.success()
.build();
// 由 mybatis plus 提供 向数据库中插入一条数据
operateLogService.save(operateEntity);
}
/**
* 方法执行中异常
*/
@Async
@SneakyThrows
@AfterThrowing(pointcut = "operateLog()", throwing = "e")
public void afterThrowingOperate(JoinPoint joinPoint, Throwable e) {
OperateLogEntity operateEntity = OperateHelper.builder(joinPoint, null)
.request()
.operate()
.fail()
.build();
// 由 mybatis plus 提供 向数据库中插入一条数据
operateLogService.save(operateEntity);
}
}
处理 RestController 返回的 ApiEntity 实体
- 当消息为空 或 消息为默认消息 将替换为 swagger注解 {{@ApiOperation value}} 值
package com.bluereba.br_common.handler;
import com.bluereba.br_common.api.ApiEntity;
import com.bluereba.br_common.api.type.base.ApiEnum;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.time.LocalDateTime;
/**
* @author tuuuuuun
* @since 2022/10/17 21:38
*/
@RestControllerAdvice
public class ResponseBodyHandler implements ResponseBodyAdvice<ApiEntity<?>> {
@Override
public boolean supports(@NonNull MethodParameter returnType,
@NonNull Class<? extends HttpMessageConverter<?>> converterType) {
// 只处理 {{@code ApiEntity }} 对象
Class<?> c = returnType.getParameterType();
return c == ApiEntity.class;
}
@Override
public ApiEntity<?> beforeBodyWrite(@Nullable ApiEntity<?> body,
@NonNull MethodParameter returnType,
@NonNull MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
@NonNull ServerHttpRequest request,
@NonNull ServerHttpResponse response) {
if (body != null) {
// 获取 {{@code ApiOperation}} 注解
ApiOperation apiOperation = returnType.getAnnotatedElement()
.getAnnotation(ApiOperation.class);
if (apiOperation != null) {
// {{@code ApiOperation}} 注解 @value
String message = apiOperation.value();
// 响应对象 消息
String bodyMessage = body.getMessage();
// 默认成功消息
String successMessage = ApiEnum.OK.getMessage();
// 当消息为空 或 消息为默认消息 将替换为 swagger注解{{@ApiOperation value}}
if (StringUtils.isEmpty(bodyMessage) || successMessage.equals(bodyMessage)) {
body.setMessage(message);
}
}
body.setTimestamp(LocalDateTime.now());
}
return body;
}
}
使用
UserController
success 静态方法是对 ApiEnity 进行的封装
package com.bluereba.br_stomp.controller;
import com.bluereba.br_common.api.ApiEntity;
import com.bluereba.br_operate_log.OperateLog;
import com.bluereba.br_pojo.dto.UserLoginDto;
import com.bluereba.br_pojo.entity.UserEntity;
import com.bluereba.br_pojo.entity.type.BusinessType;
import com.bluereba.br_pojo.vo.UserRegisterVo;
import com.bluereba.br_service.service.UserService;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import static com.bluereba.br_common.api.ApiRes.success;
/**
* @author tuuuuuun
* @since 2022/10/9 19:15
*/
@Api(tags = "用户模块") // Api.tags 将成为 操作日志的主题(topic)
@RestController
@RequestMapping("user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/login")
@ApiOperation(value = "用户登陆") // ApiOperation.value 将成为 操作日志的操作概况(summary)
@OperateLog(businessType = BusinessType.LOGIN)
public ApiEntity<UserLoginDto> login(@RequestBody UserRegisterVo vo) {
UserLoginDto dto = userService.signUp(vo);
return success(dto);
}
@DeleteMapping("/logout")
@ApiOperation(value = "用户退出")
@OperateLog(businessType = BusinessType.LOGOUT)
public ApiEntity<?> logout() {
userService.logout();
return success();
}
@GetMapping("/info")
@ApiOperation(value = "用户信息")
public ApiEntity<UserEntity> info() {
UserEntity info = userService.info();
return success(info);
}
}
脱敏
UserRegisterVo
package com.bluereba.br_pojo.vo;
import com.bluereba.br_pojo.json.ChineseNameSerialize;
import com.bluereba.br_pojo.json.PasswordSerialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
/**
* 登录视图
*
* @author tuuuuuun
* @since 2022/10/10 0:00
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "登陆vo", description = "")
public class UserRegisterVo implements Serializable {
@NotNull(message = "登陆用户名不能为空")
@ApiModelProperty(value = "用户名", example = "", notes = "")
@JsonSerialize(using = ChineseNameSerialize.class)
private String username;
@NotNull(message = "用户密码不能为空")
@ApiModelProperty(value = "用户密码", example = "", notes = "")
@JsonSerialize(using = PasswordSerialize.class)
private String password;
@ApiModelProperty(value = "记住我", example = "false",
notes = "true, false, 1, 0, True, False")
private Boolean readme = false;
}
ChineseNameSerialize(中文名序列化)
package com.bluereba.br_pojo.json;
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* @author tuuuuuun
* @since 2022/10/18 23:00
*/
public class ChineseNameSerialize extends JsonSerializer<String> {
@Override
public void serialize(String v, JsonGenerator g, SerializerProvider s)
throws IOException {
// DesensitizedUtil 糊涂包下的脱敏工具类,
// chineseName 方法是对首字符后的字符串串进行 * 替换
g.writeString(DesensitizedUtil.chineseName(v));
}
}
PasswordSerialize(密码序列化)
package com.bluereba.br_pojo.json;
import cn.hutool.core.util.DesensitizedUtil;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* @author tuuuuuun
* @since 2022/10/18 23:00
*/
public class PasswordSerialize extends JsonSerializer<String> {
@Override
public void serialize(String v, JsonGenerator g, SerializerProvider s)
throws IOException {
// DesensitizedUtil 糊涂包下的脱敏工具类,
// password 方法是对全字符串进行 * 替换
g.writeString(DesensitizedUtil.password(v));
}
}