开发问题记录
需求描述:甲方要求能看到用户的行为记录,需要给现有的所有业务添加操作日志记录,记录当前用户做的是什么业务,使用的什么功能,操作数据的id,业务执行是否成功以及操作时间。(数据会定期删除,并且用户量不是很大,记录字段也不长,不会有数据量过大的问题)
1. 使用SpringBoot拦截器intercepter
解决思路:因为使用的Dubbo调用(这里其实是个坑,思路从一开始就错了),所以不能使用过滤器,选择使用拦截器进行逻辑编写,因为controller层的每个类和方法都添加swagger的注解,所以可以通过反射获取到执行的业务和功能、用户和操作的数据id可以通过请求参数进行获取、业务是否成功可以通过返回的结果封装类进行获取、操作时间可以通过数据库自动生成。业务流程大概如下。
思路应该可以说是很清晰的,但是实践的时候才发现还是自己太天真。
遇到的问题:
HttpServletRequest
的流只能获取一次,也就是说如果我在拦截器中获取了,那么在业务层获取到的参数就为空。HttpServeletResponse
只能使用一种流,不能同时存在两种流(getWrite
,getOutputStream
两个方法互斥),而在其他的地方已经使用了getOutputStream
了,也就意味着我也只能使用getOutputStream
,但是我似乎没办法通过这个流获取到结果对象。。- 其实最后我才发现,我们的服务是
Dubbo
调用,虽然在调用接口时拦截器确实会被执行,但是HttpServletRequest
,HttpServeletResponse
中根本就没有我要的参数和结果。。。。不过这一次错误思路也让我对拦截器有了新的理解。
2. 使用Dubbo过滤器
解决思路:其实大体和用拦截器解决的思路相同,只是说变成了Dubbo过滤而已。
遇到的问题:
- 我似乎没有办法通过invoker获取到调用的类,也就没有办法获取类上的注解。
- 不知道什么原因,Dubbo过滤器未生效,直到最后也没有解决。
3. 最终解决方案AOP
这是最终解决方案,其实三个方法的思路都是一致的,只是采用的手段不一样。
解决思路:定义一个切面,在controller层的所有返回类型为ResponseBase(结果类型)的方法的前后执行逻辑。
代码实现(感觉代码写的不是很好,后续继续优化,如果有好的建议或解决方案欢迎指导):
@Aspect
@Component
public class UserOperationLogAspect {
private final String sql = "INSERT INTO LOG_USER_OPERATION (USER_NAME, TOKEN, BUSINESS_NAME, OPERATION_NAME, RESPONSE_STSTUS) VALUES (?, ?, ?, ?, ?)";
// controller层
@Around("execution(antu.microservice.ResponseBase antu.jsydsp.provider..*.*(..))")
public Object addOperationLog(ProceedingJoinPoint pjp) throws Throwable {
UserOperationLog operationLog = new UserOperationLog();
// 目标对象Class
Class<?> aClass = pjp.getTarget().getClass();
Object target = aClass;
// 方法参数
Object[] args = pjp.getArgs();
// 方法标识
MethodSignature signature = (MethodSignature)pjp.getSignature();
// 方法
Method method = aClass.getMethod(signature.getName(), signature.getParameterTypes());
if (aClass.isAnnotationPresent(Api.class)){
Api api = aClass.getAnnotation(Api.class);
operationLog.setBusinessName(Strings.isNotBlank(api.value()) ? api.value() : api.description());
}
if (method.isAnnotationPresent(ApiOperation.class)){
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
operationLog.setOperationName(apiOperation.value());
}
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
for (Annotation annotation : parameterAnnotations[i]) {
if (annotation instanceof ApiParam){
ApiParam apiParam = (ApiParam) annotation;
if ("token".equals(apiParam.value())){
operationLog.setToken(args[i].toString());
}
if ("IID".equals(apiParam.value()) || "iid".equals(apiParam.value())){
operationLog.setIid(args[i].toString());
}
}
}
}
Object responseBase = pjp.proceed();
try{
ResponseBase response = (ResponseBase) responseBase;
operationLog.setStatus(response.getStatus());
}catch (ClassCastException e){
System.out.println("当前方法非controller: " + signature.getName());
}
// 存库
Database db = new Database();
db.executeUpdate(sql, operationLog.getUserName(), operationLog.getToken(), operationLog.getBusinessName(), operationLog.getOperationName(), operationLog.getStatus());
return responseBase;
}
}