在大趋势的流行下,每个项目发展到后期都会考虑到分布式或者微服务,如果让你自己管理一个子项目,自己搭建完框架后你是否考虑到一个问题,怎样让自己项目看起来不那么篓呢?这里记录一下,当一个新项目到手上后我们都能提前做好哪些事。
统一返回信息
首先定义好返回的包装类
@Data
public class CommonResponse extends HashMap<String, Object> {
private static final ObjectMapper OBJECT_MAPPER= new ObjectMapper();
private static final long serialVersionUID = 1L;
public static final String MSG_TAG = "msg";
public static final String DATA_TAG = "data";
/**
* 状态类型
*/
public enum Type {
/**
* 成功
*/
SUCCESS(200),
/**
* 警告
*/
WARN(301),
/**
* 失败
*/
DEFEATED(400),
/**
* 错误
*/
ERROR(500);
private final int value;
Type(int value) {this.value = value;}
public int value() {return this.value;}
}
/**
* 状态类型
*/
private CommonResponse.Type type;
/**
* 状态码
*/
private int code;
/**
* 返回内容
*/
private String msg;
/**
* 数据对象
*/
private Object data;
/**
* 初始化一个新创建的 UserResponse 对象,使其表示一个空消息。
*/
public CommonResponse() {
}
/**
* 初始化一个新创建的 UserResponse 对象
*
* @param type 状态类型
* @param msg 返回内容
*/
public CommonResponse(CommonResponse.Type type, String msg) {
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 UserResponse 对象
*
* @param type 状态类型
* @param msg 返回内容
* @param data 数据对象
*/
public CommonResponse(CommonResponse.Type type, String msg, Object data) {
super.put(MSG_TAG, msg);
if (data != null) {
super.put(DATA_TAG, data);
}
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static CommonResponse success(Object data) {
return CommonResponse.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static CommonResponse success(String msg, Object data) {
return new CommonResponse(CommonResponse.Type.SUCCESS, msg, data);
}
/**
* 返回失败消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 失败消息
*/
public static CommonResponse defeated(String msg, Object data) {
return new CommonResponse(Type.DEFEATED, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static CommonResponse warn(String msg, Object data) {
return new CommonResponse(Type.WARN, msg, data);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static CommonResponse error(String msg, Object data) {
return new CommonResponse(CommonResponse.Type.ERROR, msg, data);
}
@Override
public String toString() {
try {
return marshal(this);
} catch (Exception e) {
e.printStackTrace();
return "error during toString()";
}
}
public static String marshal(Object value) throws Exception
{
try
{
return OBJECT_WRITER.writeValueAsString(value);
}
catch (JsonGenerationException e)
{
throw new Exception(e);
}
catch (JsonMappingException e)
{
throw new Exception(e);
}
catch (IOException e)
{
throw new Exception(e);
}
}
}
接下来我们就创建一个全局拦截类来实现我们对所有接口的封装
@RestControllerAdvice
public class VsprintfConfig implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
// 判断是否包装
ImmutableList<String> typeNames = ImmutableList.of(
ProjectsController.class.getTypeName()
);
return typeNames.contains(methodParameter.getContainingClass().getTypeName());
//返回true代表全部拦截
// return true;
}
@Override
public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
//如需单独处理,在此判断
if (o instanceof Page) {
//自定义分页统一处理方法
return PageObject((Page<?>) o);
}
if (o instanceof CommonResponse) {
return o;
}
if (o instanceof String) {
return JSON.toJSONString(CommonResponse.success(o));
}
return CommonResponse.success(o);
}
}
统一异常
当然这里也可以配合统一返回信息功能,这里不再演示,有需要可自行更改
@Slf4j
@RestControllerAdvice
@Order(-1)
@ResponseBody
public class MyException {
@ExceptionHandler(IOException.class)
public ResponseEntity iOExceptionHandler(IOException e) {
// log.error("IO异常", e.getMessage());
log.error("IO异常", e);
return new ResponseEntity<>(CommonResponse.error("IO异常:" + e), HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 此方法可配合@Valid使用,不需要写BindingResult bindingResult,故此记录一下
*/
@ExceptionHandler(value = BindException.class)
public ResponseEntity bindExceptionErrorHandler(BindException e) {
// log.error("参数错误", e.getMessage());
log.error("参数错误", e);
BindingResult result = e.getBindingResult();
StringBuffer errorString = new StringBuffer();
if (result.hasErrors()) {
List<FieldError> fieldErrors = result.getFieldErrors();
int i = 1;
fieldErrors.forEach(error -> {
errorString.append(error.getDefaultMessage());
if(i<fieldErrors.size()){
errorString.append(",");
}
});
}
return new ResponseEntity<>(CommonResponse.error("参数错误:" + errorString), HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(IndexOutOfBoundsException.class)
public ResponseEntity indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
// log.error("数组越界异常", ex.getMessage());
log.error("数组越界异常", ex);
return new ResponseEntity<>(CommonResponse.error("数组越界异常:" + ex.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
这里只是简单介绍几种,如有需要,根据自己项目需求配置即可
单个参数拦截
简单说一下在项目中我们是如果单独拦截参数的,比如token。首先,我们定义一个接口,以注解形式实现
@Target({ElementType.PARAMETER})//参数级别
@Retention(RetentionPolicy.RUNTIME) //注解保留到运行阶段
public @interface ParamsNotNull {
}
@Slf4j
public class CheckParamsInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws NullPointerException {
if (!(handler instanceof HandlerMethod)) {
return true;
}
//拿到该方法上加了注解的参数名称
List<String> list = getParamsName((HandlerMethod) handler);
for (String s : list) {
//获取到参数名称并判断是否为空
String parameter = request.getParameter(s);
if (StringUtils.isEmpty(parameter)) {
throw new NullPointerException("token不能为空");
}
}
//如果拿到的对象为空,说明没有此注解,直接放行
return true;
}
/**
* 拿到在参数上加了该注解的参数名称
*/
private List getParamsName(HandlerMethod handlerMethod) {
Parameter[] parameters = handlerMethod.getMethod().getParameters();
List<String> list = new ArrayList<>();
for (Parameter parameter : parameters) {
if (parameter.isAnnotationPresent(ParamsNotNull.class)) {
list.add(parameter.getName());
}
}
return list;
}
}
接下来配置生效路径
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
CheckParamsInterceptor checkParamsInterceptor = new CheckParamsInterceptor();
@Override
public void addInterceptors(InterceptorRegistry registry) {
//如果除了接口的请求还有其他请求的话可以在所有的接口前面加个前缀区分开
registry.addInterceptor(checkParamsInterceptor).addPathPatterns("/**");
}
}
AOP注解验证拦截
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface verifUsers {
// String description() default "";
}
@Aspect
@Component
public class userAspect {
@Autowired
UserDao userDao;
private static Logger logger = LogManager.getLogger(userAspect.class.getName());
@Pointcut("@annotation(com.treeyee.verifUsers)")
public void controllerAspect() {
}
// 这里我们采用AOP注解的方式,当然,我们也可以配置全路径匹配
// @Before("execution(* com.treeyee.controller.*.*ById(..))")
// @Before("execution(* com.treeyee.controller.*.*(..))")
@Before("controllerAspect()")
public void test(JoinPoint joinPoint) throws MyException {
Map<String, Object> params = getNameAndValue(joinPoint);
for (Map.Entry<String, Object> entry : params.entrySet()) {
System.out.println("name: " + entry.getKey() + " value: " + entry.getValue());
//进行我们的业务处理
}
}
/**
* 获取参数Map集合
* @param joinPoint
* @return
*/
Map<String, Object> getNameAndValue(JoinPoint joinPoint) {
Map<String, Object> param = new HashMap<>();
Object[] paramValues = joinPoint.getArgs();
String[] paramNames = ((CodeSignature)joinPoint.getSignature()).getParameterNames();
for (int i = 0; i < paramNames.length; i++) {
param.put(paramNames[i], paramValues[i]);
}
return param;
}
}
本篇文章主要就是简单记录一下,如有需要以后再完善。