引入spring及其他工具类依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<!-- 连接,版本你们自己选择 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.connector}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<version>${sqlserver-jtds}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.boot}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis.plus.boot}</version>
</dependency>
<!-- hutool工具类,几乎包含所以通用方法 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.3</version>
</dependency>
然后创建操作日志的表,表字段如下
CREATE TABLE `config_log_operating` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '操作日志表',
`dotable` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作的表名',
`title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '日志的标题,动作',
`do_id` int unsigned DEFAULT NULL COMMENT '操作的表id',
`summary` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '内容概括',
`operator` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作人',
`operator_id` int unsigned DEFAULT NULL COMMENT '操作人id',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '操作时的ip',
`create_date` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`api` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '调用的接口信息',
`api_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '接口请求类型',
`source` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT 'admin' COMMENT '来源,admin:管理后台;pc:网页端;wap:手机端;wx:微信小程序;userAdmin:用户后台',
`source_url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '请求接口前页面的浏览器地址',
`visit_time` int unsigned DEFAULT '0' COMMENT '切面执行时长,用于查看反射的效率',
PRIMARY KEY (`id`) USING BTREE,
KEY `config_log_operating_create_date_IDX` (`create_date`) USING BTREE,
KEY `config_log_operating_do_id_IDX` (`do_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=463 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='操作日志表,一般用于管理后台'
相关controller,service,mapper代码你们自己创建,这里就不发了。
然后添加操作日志代码使用到了字符串灵活sql语句的执行,详情请看我字符串sql语句执行封装,借用hutool工具类_影耀子的博客-优快云博客
这篇文章,你也可以改成你自己封装的字符串sql语句执行的工具类
然后自定义注解代码如下:
/**
* @author 影耀子(YingYew)
* @Description: 操作日志自定义注解
* @ClassName: AddHandleLog
* @date 2022/7/12 17:25
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AddHandleLog {
/**
* 日志的标题信息
* @return
*/
String title() default "";
/**
* 需要查询的sql语句,必须包含id,id即为操作的表的id
* @return
*/
String sql() default "";
/**
* sql所需参数,一般为id,非参数值,参数值是根据此属性从接口方法参数中获取
* @return
*/
String sqlParameter() default "id";
/**
* 日志的内容概述,如:联系人电话:${tel} | 找酒主题:${subject},有的联系
* @return
*/
String summary() default "";
/**
* 日志操作的表名
* @return
*/
String table() default "";
}
然后就是最重要的切面类中的代码,添加操作日志的实现就是通过切面中的环绕通知实现的
对了,环绕通知中有捕获异常,所以使用了记录异常日志信息的方法,详情请参阅我
使用aop切面记录程序接口异常日志信息,包含完整方法名,参数类型,参数名,参数值及接口信息,异常信息_影耀子的博客-优快云博客
这篇文章,如果你不需要记录异常日志信息的话,删掉catch中的添加异常日志的代码即可
还有一点需要注意:就是删除请求使用的是delete请求,如果删除的业务使用了其他请求的话,便不会添加操作日志,因为数据库中已经查不到这条信息了。
/**
* @author 影耀子(YingYew)
* @Description: 添加操作日志的切面类
* @ClassName: AddLogAop
* @date 2022/7/20 14:38
*/
@Component
@Aspect
public class AddLogAop {
// 操作日志的相关service
@Autowired(required = false)
private ILogOperatingService logOperatingService;
// 异常日志的相关service,不需要记录异常日志信息的话删掉这个service即可
@Autowired(required = false)
private IExceptionLogService exceptionLogService;
/**
* 只针对管理后台
*/
@Pointcut("execution(* com.lookvin.admin.*.controller.*.*(..))")
public void pointCutServer(){}
/**
* 环绕通知
* @param jp
*/
@Around("pointCutServer()")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
assert requestAttributes != null;
HttpServletRequest request = (HttpServletRequest) requestAttributes.
resolveReference(RequestAttributes.REFERENCE_REQUEST);
assert request != null;
MethodSignature methodSignature = (MethodSignature) jp.getSignature();
Method method = methodSignature.getMethod();
Object[] args = jp.getArgs(); // 获取访问的方法的参数
String[] argNames = {}; // 参数名
if (args != null && args.length > 0) {
argNames = methodSignature.getParameterNames(); // 获取参数名
}
Map<String,Object> parameters = new HashMap<>();
for (int i = 0; i < argNames.length; i++) {
parameters.put(argNames[i],args[i]);
}
List<LogOperating> logOperatings = new ArrayList<>();
if ("DELETE".equals(request.getMethod())){
logOperatings = addLogOperating(parameters, method, request, System.currentTimeMillis());
}
// System.out.println("执行前");
Object proceed = jp.proceed();
if (ObjectUtil.isNotEmpty(proceed) && proceed.getClass() == R.class){
R r = (R)proceed;
if (r.isSuccess()){
// 正常返回添加日志
if (!"DELETE".equals(request.getMethod())){
logOperatings = addLogOperating(parameters, method, request, System.currentTimeMillis());
}
if (logOperatings.size() > 0){
logOperatingService.saveBatch(logOperatings);
}
}
}
// System.out.println("执行后");
return proceed;
}
/**
* 添加操作日志
* @param parameters 方法参数,key为方法名,value为方法值
* @param method 方法
*/
private List<LogOperating> addLogOperating(Map<String,Object> parameters, Method method, HttpServletRequest request, Long starttime) {
AddHandleLog addHandleLog = method.getAnnotation(AddHandleLog.class);
if (addHandleLog == null){
return new ArrayList<>();
}
String[] split = addHandleLog.sqlParameter().split("\\.");// sql所需参数
String sql = addHandleLog.sql(); // 要执行的sql语句
Object sqlParameterVal = null; // sql所需参数值
if (split.length > 1){
Object parameter = parameters.get(split[0]); // 获取参数对象
for (int i = 1; i < split.length; i++) {
if (i==split.length-1){
sqlParameterVal = BeanUtil.getFieldValue(parameter,split[i]); // 根据字段名获取值
}else {
parameter = BeanUtil.getFieldValue(parameter,split[i]);
}
}
}else {
sqlParameterVal = parameters.get(split[0]);
}
if (ArrayUtil.isArray(sqlParameterVal)){
// 代表数组,将数组转为字符串
sqlParameterVal = ArrayUtil.join(sqlParameterVal,",");
}
if (sqlParameterVal != null && Collection.class.isAssignableFrom(sqlParameterVal.getClass())){
// 如果是集合,将集合转为字符串
sqlParameterVal = CollUtil.join((List<?>)sqlParameterVal, ",");
}
// 我是使用jwt加解密用户信息的,这个是在拦截器中将用户信息存进Attribute属性中的,你也可以根据自己业务去修改成你自己的记录用户信息的办法
Claims userClaims = (Claims) request.getAttribute("user_claims");
// 这是获取到此请求是从哪里来的,可以根据自己业务修改
String soure = (String) request.getAttribute("soure");
// 获取ip地址
String clientIP = ServletUtil.getClientIP(request);
List<Entity> query = HutoolDbUtil.query(sql, sqlParameterVal);
List<LogOperating> logOperatingList = new ArrayList<>();
String pattern = "(\\$\\{)(.*?)(})";
for (Entity entity : query) {
String summary = addHandleLog.summary(); // 内容摘要
Matcher summaryMatcher = Pattern.compile(pattern).matcher(summary);
while (summaryMatcher.find()){
String group = summaryMatcher.group(2);
summary = summary.replace("${"+group+"}",entity.getStr(group)==null?"null":entity.getStr(group));
}
String title = addHandleLog.title();
Matcher titleMatcher = Pattern.compile(pattern).matcher(title);
while (titleMatcher.find()){
String group = titleMatcher.group(2);
title = title.replace("${"+group+"}",entity.getStr(group)==null?"null":entity.getStr(group)); // 防止entity.getStr(group)为null空指针
}
LogOperating logOperating = new LogOperating()
.setDotable(addHandleLog.table())
.setDoId(entity.getInt("id"))
.setTitle(title)
.setSummary(summary)
.setOperator(userClaims.getSubject())
.setIp(clientIP)
.setApi(request.getRequestURL().toString())
.setSource(soure)
// 根据自己业务修改,此字段是记录发送此请求时浏览器当前地址
.setSourceUrl(request.getHeader("RefererUrl"))
.setOperatorId(Integer.valueOf(userClaims.getId()))
.setApiType(request.getMethod())
;
long endtime = System.currentTimeMillis();
logOperating.setVisitTime(Math.toIntExact(endtime - starttime));
logOperatingList.add(logOperating);
}
return logOperatingList;
}
}
最主要的代码我们都已经编写完了。最后就是如何使用它了,我这里给你们一个接口示例
@RestController
@RequestMapping("/admin/admin/role")
@Validated
public class RoleController extends BaseController {
interface RoleLog{
String SUMMARY = "角色名称:${name},角色所属用户类型:${typeStr}";
String SQL = "select id,name,case type when 0 then '管理员' else '用户角色' end as typeStr from admin_role where id = ?";
String TABLE = "admin_role"; // 操作的表
}
@Autowired
private IRoleService roleService;
/**
* 添加角色
* @param role 角色信息
* @return
*/
@PostMapping(value = "",name = "ROLE_ADD")
@AddHandleLog(title = "添加角色信息",table =RoleLog.TABLE,
sql = RoleLog.SQL,
summary = RoleLog.SUMMARY, sqlParameter = "role.id"
)
public R add(@RequestBody @Validated Role role){
boolean save = roleService.save(role);
if (save){
return R.SUCCESS();
}
return R.FAIL();
}
/**
* 修改角色信息
* @param id 角色id
* @param role 角色信息
* @return
*/
@PutMapping(value = "/{id}",name = "ROLE_UPDATE")
@AddHandleLog(title = "修改角色信息",table =RoleLog.TABLE,
sql = RoleLog.SQL,
summary = RoleLog.SUMMARY
)
public R update(@PathVariable(name = "id") Integer id,
@RequestBody @Validated Role role){
role.setId(id);
boolean b = roleService.updateById(role);
if (b){
return R.SUCCESS();
}
return R.FAIL();
}
/**
* 删除角色
* @param id 角色id
* @return
*/
@DeleteMapping(value = "/{id}",name = "ROLE_DELETE")
@AddHandleLog(title = "删除角色信息",table =RoleLog.TABLE,
sql = RoleLog.SQL,
summary = RoleLog.SUMMARY
)
public R delete(@PathVariable(name = "id") Integer id){
boolean b = roleService.removeById(id);
if (b){
return R.SUCCESS();
}
return R.FAIL();
}
/**
* 分配权限
* @param assignPermission 分配权限的参数对象
* @return
*/
@PutMapping(value = "/assignPermission",name = "ROLE_ASSIGN_PERMISSION")
@AddHandleLog(title = "分配权限",table = RoleLog.TABLE,
sql = "with with_permission_role as (" +
"select ar.id,ar.name,(case ar.`type` when 0 then '管理后台角色' else '普通用户角色' end) as typeStr,arp.permission_id " +
"from admin_role ar left join admin_role_permission arp " +
"on ar.id =arp.role_id where ar.id= ?" +
")select with_permission_role.id,with_permission_role.name as roleName,with_permission_role.typeStr ," +
"(case admin_permission.name is null when true then '取消了所有' else concat('分配了',admin_permission.name) end) as permissionName," +
"now(3) as dateTime from with_permission_role left join admin_permission " +
"on with_permission_role.permission_id = admin_permission.id",
summary = "为${typeStr}-${roleName}${permissionName}权限,分配时间:${dateTime}",
sqlParameter = "assignPermission.id"
)
public R assignPermission(@RequestBody @Validated RoleAssignPermission assignPermission){
Boolean b = roleService.assignRoles(assignPermission);
if (b){
return R.SUCCESS();
}
return R.FAIL();
}
}
最后给你们看下我记录的操作日志信息