事先声明:本文参考了两位大神的博客进行操作,最后会附上两篇博客的地址;另外水平实在有限,难免出错,望各位包涵。本博客仅当笔记用以留存。
业务场景:之前记录操作人都是在service层调接口手动添加的操作人日志记录,有两个坏处:
一是有些流程需要事务比较大,添加操作人记录也需要在事务中进行,一旦操作人添加不成功,会造成整个事务回滚。
二是添加操作人动作与业务耦合在一起。
思路:因此考虑是否可以使用注解的方式,将注解添加到Controller层,这样每个Controller方法不必显式声明String operatorName参数,完成操作人记录的自动添加。可以使用HandlerInterceptorAdapter子类的postHandle方法完成所需操作。
有几点需求,如下:1、Controller方法接受参数有几种:普通的GET请求;contentType="application/x-www-form-urlencoded"的POST请求;服务端使用@RequestParam接受的json参数;服务端使用@RequestParam标记的Dto对象自动实现映射。很不幸,我们的项目中这几种请求都存在,在解决兼容这些请求类型的时候踩了N多坑。
2、所有controller方法都需要返回json供前端调用,如果请求正常返回0000,如果请求失败返回9999。如果返回9999说明请求异常了,不能记录操作人,只有返回结果json中的code为0000时才能记录操作人。
解决方案:可以自定义注解,然后通过拦截请求来实现获取请求参数并获取返回值来添加操作人记录。
1、首先创建自定义注解:OperatorRecord
package com.link2c.mif.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//操作类型的枚举,里面记录了各种操作类型。
import com.link2c.mif.facade.enumration.OperatorTypeEnum;
/**
*
* @Title OperatorRecord.java
* @Description 记录操作人 注解
* @author lingweimeng
* @date 2018年2月1日 上午11:22:51
* @version V1.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperatorRecord {
/**
* 操作类型
*/
OperatorTypeEnum operatorType();
/**
* 备注,注解remark为空,则默认取枚举中的desc
*/
String remark() default"";
}
自定义注解,然后通过该注解有两个参数,第一个是操作类型,第二个是备注。如果备注为空,则自动记录操作类型对应的中文描述,OperatorTypeEnum定义如下:
public enum OperatorTypeEnum {
SHOPTRADETIME_ADD("商户营业时间新增"),
SHOPTRADETIME_UPDATE("商户营业时间修改");
private final String desc;
private OperatorTypeEnum(String _desc) {
this.desc = _desc;
}
public String getDesc() {
return this.desc;
}
}
2、
RepeatedlyReadFilter,由于请求中包含@requestParam这种请求,该请求实际将参数的json放到了body中,由于HttpServletRequest的getInputStream()和getHeader只能被读一次,且在实际的controller方法已经完成了读取,因此在postHandle方法中就不能再读取body里面的内容了,因此需要对request进行包装,用以保存request中body内容的字符数组;另外由于HttpServletResponse未提供获取响应信息的方法,因此也需要对response进行包装,来获取响应信息。
package com.link2c.mif.core.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.link2c.mif.core.proxy.RepeatedlyReadRequestWrapper;
import com.link2c.mif.core.proxy.ResponseWrapper;
import com.yeepay.g3.utils.common.log.Logger;
import com.yeepay.g3.utils.common.log.LoggerFactory;
public class RepeatedlyReadFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(RepeatedlyReadFilter.class);
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request,ServletResponse response, FilterChain chain) throws IOException, ServletException {
logger.info("复制request.getInputStream流");
boolean flag = false;
try {
flag = request.getContentType().startsWith("application/json");
} catch (Exception e) {
}
ServletRequest requestWrapper = null;
ResponseWrapper responseWrappse = null;
if (request instanceof HttpServletRequest && flag) {
//包装request
requestWrapper = new RepeatedlyReadRequestWrapper((HttpServletRequest) request);
}
if(response instanceof HttpServletResponse) {
//包装response
responseWrappse = new ResponseWrapper((HttpServletResponse)response);
}
if (null == requestWrapper || !flag) {
chain.doFilter(request, responseWrappse);
} else {
chain.doFilter(requestWrapper, responseWrappse);
}
response.setContentLength(-1);
String content = new String(responseWrappse.getBytes());
response.getOutputStream().write(content.getBytes());
}
@Override
public void init(FilterConfig paramFilterConfig) throws ServletException {
}
}
这里面的flag参数是为了兼容
contentType="
application/x-www-form-urlencoded
"的POST请求的。操作中发现这种POST请求不能进行包装,如果包装,在controller方法中参数就丢失了。
3、RepeatedlyReadFilter需要在web.xml中进行配置拦截所有请求
<filter>
<filter-name>springFilter</filter-name>
<filter-class>com.link2c.mif.core.filter.RepeatedlyReadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4、
OperatorRecordAspect
package com.link2c.mif.core.aspectj;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.link2c.mif.core.annotation.OperatorRecord;
import com.link2c.mif.core.member.service.OperatorRecordService;
import com.link2c.mif.core.proxy.RepeatedlyReadRequestWrapper;
import com.link2c.mif.core.proxy.ResponseWrapper;
import com.link2c.mif.facade.enumration.OperatorTypeEnum;
import com.yeepay.g3.utils.common.CheckUtils;
import com.yeepay.g3.utils.common.json.JSONUtils;
import com.yeepay.g3.utils.common.log.Logger;
import com.yeepay.g3.utils.common.log.LoggerFactory;
/**
*
* @Title OperatorRecordAspect.java
* @Description 记录操作人
* @author lingweimeng
* @date 2018年2月1日 下午1:56:58
* @version V1.0
*/
public class OperatorRecordAspect extends HandlerInterceptorAdapter {
@Resource(name="OperatorRecordServiceImpl")
private OperatorRecordService OperatorRecordService;
private Logger logger = LoggerFactory.getLogger(OperatorRecordAspect.class);
@Override
public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request,HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
ResponseWrapper wrapperResponse = null;
if(response instanceof ResponseWrapper) {
wrapperResponse = (ResponseWrapper) response;
}
HandlerMethod hm;
try {
hm = (HandlerMethod)handler;
OperatorRecord or = hm.getMethodAnnotation(OperatorRecord.class);
if(!CheckUtils.isNull(or)) {
OperatorTypeEnum operatorTypeEnum = or.operatorType();
String remark = or.remark();
String operatorType = operatorTypeEnum.name();
if(CheckUtils.isEmpty(remark))
remark = operatorTypeEnum.getDesc();
String operatorName = null;
String shopId = null;
shopId = getParamFromRequest(request,null, "shopId");
operatorName = getParamFromRequest(request,null, "operatorName");
//处理返回值,只有返回值是0000时才记录日志
boolean responseFlag = false;
String responseData = getResponseJson(wrapperResponse);
if(!CheckUtils.isEmpty(responseData)) {
try {
Map<String, String> jsonMap = JSONUtils.jsonToMap(responseData, String.class, String.class);
if(!CheckUtils.isEmpty(jsonMap) && !CheckUtils.isEmpty(jsonMap.get("code")) && "0000".equals(jsonMap.get("code"))) {
responseFlag = true;
}
} catch (Exception e) {
}
}
if(!CheckUtils.isEmpty(shopId) && !CheckUtils.isEmpty(operatorName) && responseFlag) {
try {
OperatorRecordService.add(shopId, null, operatorType, operatorName, remark);
} catch (Exception e) {
logger.error("添加操作人记录异常",e);
}
}
}
} catch (Exception e1) {
}
super.postHandle(request, response, handler, modelAndView);
}
/**
*
* @Title GetResponse
* @Description 获取response返回值
* @param response
* @return
* {@link }
* @since 2018年2月2日 下午2:23:01
*/
private String getResponseJson(ResponseWrapper wrapperResponse) {
if(!CheckUtils.isEmpty(wrapperResponse)) {
byte[] bts = wrapperResponse.getBytes();
if(bts.length > 0) {
String result = new String(bts);
return result;
}
}
return null;
}
@Override
public void afterCompletion(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex)throws Exception {
super.afterCompletion(request, response, handler, ex);
}
private String getParamFromRequest(HttpServletRequest request,RepeatedlyReadRequestWrapper requestWrapper,String paramName) {
if(!CheckUtils.isEmpty(requestWrapper))
request = requestWrapper;
boolean contentTypeJson = request.getContentType().startsWith("application/json");
String param = null;
if(contentTypeJson) {
try {
String body = getBodyString(request);
Map<String, String> bodyMap = JSONUtils.jsonToMap(body, String.class, String.class);
if(!CheckUtils.isEmpty(bodyMap) && !CheckUtils.isEmpty(bodyMap.get(paramName)))
param = bodyMap.get(paramName);
} catch (Exception e) {
}
} else {
param = request.getParameter(paramName);
if(!CheckUtils.isEmpty(param))
return param;
Map<String, String[]> params = request.getParameterMap();
Set<String> paramsKey = params.keySet();
for(String key : paramsKey) {
String paramValue = request.getParameter(key);
try {
Map<String, String> paramValueMap = JSONUtils.jsonToMap(paramValue, String.class, String.class);
if(!CheckUtils.isEmpty(paramValueMap) && !CheckUtils.isEmpty(paramValueMap.get(paramName)))
return paramValueMap.get(paramName);
} catch (Exception e) {
}
}
}
return param;
}
/**
* 获取请求Body
*
* @param request
*
* @return
*/
public static String getBodyString(ServletRequest request) {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = cloneInputStream(request.getInputStream());
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
/**
* Description: 复制输入流</br>
*
* @param inputStream
*
* @return</br>
*/
public static InputStream cloneInputStream(ServletInputStream inputStream) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1) {
byteArrayOutputStream.write(buffer, 0, len);
}
byteArrayOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
return byteArrayInputStream;
}
@Override
public void afterConcurrentHandlingStarted(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
super.afterConcurrentHandlingStarted(request, response, handler);
}
}
里面的shopId是店铺id,肯定要记录哪个店铺的哪个员工操作了什么,所以shopId是不可少的,大家可以把它当成普通的业务信息。
OperatorRecordService.add(shopId, null, operatorType, operatorName, remark);
这句是实际的记录操作人的动作。
5、OperatorRecordAspect的配置。在spring-mvc.xml中配置
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.link2c.mif.core.aspectj.OperatorRecordAspect">
</bean>
</mvc:interceptor>
</mvc:interceptors>
6、测试controller
package com.link2c.mif.publish.shopone.controller.reserve;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.link2c.mif.core.annotation.OperatorRecord;
import com.link2c.mif.facade.enumration.OperatorTypeEnum;
import com.link2c.mif.facade.response.BaseResponse2;
import com.linktoc.member.facade.dto.GeneralMemberDto;
import com.yeepay.g3.utils.common.json.JSONUtils;
@Controller
@RequestMapping("/test")
public class TestOperatorRecordController {
@RequestMapping("test1")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD,remark="remark1")
@ResponseBody
public String testController(String shopId,String operatorName) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(shopId);
System.out.println(operatorName);
System.out.println("-----------");
System.out.println("===========");
return JSONUtils.toJsonString(baseResponse);
}
@RequestMapping(value = "test2",produces = "application/json; charset=utf-8")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD,remark="remark2")
@ResponseBody
public String testController2(@RequestBody String param,HttpServletRequest request) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(request.getParameter("shopId"));
System.out.println(param);
System.out.println("-----------");
System.out.println("===========");
return JSONUtils.toJsonString(baseResponse);
}
@RequestMapping("test3")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD)
@ResponseBody
public String testController3(@RequestBody GeneralMemberDto generalMemberDto) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(generalMemberDto.toString());
System.out.println("-----------");
System.out.println("===========");
return JSONUtils.toJsonString(baseResponse);
}
@RequestMapping("test4")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD)
@ResponseBody
public String testController(@RequestParam("param") String param) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(param);
System.out.println("-----------");
System.out.println("===========");
return JSONUtils.toJsonString(baseResponse);
}
@RequestMapping("test5")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD,remark="remark1")
@ResponseBody
public void testController5(String shopId,String operatorName) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(shopId);
System.out.println(operatorName);
throw new RuntimeException("11111");
}
@RequestMapping("test6")
@OperatorRecord(operatorType = OperatorTypeEnum.SHOPTRADETIME_ADD,remark="remark1")
@ResponseBody
public String testController6(String shopId,String operatorName) {
BaseResponse2<String> baseResponse = new BaseResponse2<>();
baseResponse.putSuccess();
System.out.println(shopId);
System.out.println(operatorName);
try {
throw new RuntimeException("11111");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("-----------");
System.out.println("===========");
return JSONUtils.toJsonString(baseResponse);
}
}
7、引用博文:
http://blog.csdn.net/qq_35843124/article/details/72770871
http://blog.csdn.net/qq_33206732/article/details/78623042
感谢两位。