web模块构建
大约 5 分钟
数据序列化和反序列配置
package com.yix.common.utils.json;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.math.BigInteger;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
/**
* @author wangdx
*/
public class JacksonUtils {
private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_PATTERN = "yyyy-MM-dd";
private static final String TIME_PATTERN = "HH:mm:ss";
public static final Jackson2ObjectMapperBuilderCustomizer CUSTOMIZER = jackson2ObjectMapperBuilderCustomizer();
public static final ObjectMapper MAPPER = getObjectMapper();
/**
* 构建 Jackson 自定义配置
*
* @return
*/
public static Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.serializationInclusion(JsonInclude.Include.NON_NULL) //null不序列化
.failOnEmptyBeans(false) // 序列化时,对象为 null,是否抛异常
.failOnUnknownProperties(false) // 反序列化时,json 中包含 pojo 不存在属性时,是否抛异常
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 禁止将 java.util.Date、Calendar 序列化为数字(时间戳)
.dateFormat(new SimpleDateFormat(DATETIME_PATTERN)) // 设置 java.util.Date, Calendar 序列化、反序列化的格式
.timeZone(TimeZone.getTimeZone("GMT+8")); // 设置 java.util.Date, Calendar 序列化、反序列化的时区 国际化不同需要更改
// Jackson 序列化 long类型为String,解决后端返回的Long类型在前端精度丢失的问题
builder.serializerByType(BigInteger.class, ToStringSerializer.instance);
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
// 配置 Jackson 序列化 LocalDateTime、LocalDate、LocalTime 时使用的格式
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATETIME_PATTERN)));
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ofPattern(TIME_PATTERN)));
// 配置 Jackson 反序列化 LocalDateTime、LocalDate、LocalTime 时使用的格式
builder.deserializerByType(LocalDateTime.class
, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATETIME_PATTERN)));
builder.deserializerByType(LocalDate.class
, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DATE_PATTERN)));
builder.deserializerByType(LocalTime.class
, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(TIME_PATTERN)));
};
}
/**
* 根据 Jackson2ObjectMapperBuilderCustomizer 构建 ObjectMapper
*
* @return ObjectMapper
*/
public static ObjectMapper getObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
jackson2ObjectMapperBuilderCustomizer().customize(builder);
return builder.build();
}
public static String toJsonString(Object object) throws JsonProcessingException {
return MAPPER.writeValueAsString(object);
}
public static byte[] toJsonByte(Object object) throws JsonProcessingException {
return MAPPER.writeValueAsBytes(object);
}
}
package com.yix.common.utils.json;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* @author wangdx
*/
@Slf4j
public class JsonUtils {
public static String toJsonString(Object object) throws JsonProcessingException {
return JacksonUtils.MAPPER.writeValueAsString(object);
}
public static byte[] toJsonByte(Object object) throws JsonProcessingException {
return JacksonUtils.MAPPER.writeValueAsBytes(object);
}
public static String toJsonPrettyString(Object object) throws JsonProcessingException {
return JacksonUtils.MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(object);
}
public static <T> T parseObject(String text, Class<T> clazz) throws JsonProcessingException {
if (StrUtil.isEmpty(text)) {
return null;
}
return JacksonUtils.MAPPER.readValue(text, clazz);
}
public static <T> List<T> parseArray(String text, Class<T> clazz) throws JsonProcessingException {
if (StrUtil.isEmpty(text)) {
return new ArrayList<>();
}
return JacksonUtils.MAPPER.readValue(text, JacksonUtils.MAPPER.getTypeFactory().constructCollectionType(List.class, clazz));
}
public static JsonNode parseTree(String text) throws JsonProcessingException {
return JacksonUtils.MAPPER.readTree(text);
}
public static JsonNode parseTree(byte[] text) throws IOException {
return JacksonUtils.MAPPER.readTree(text);
}
public static boolean isJson(String text) {
return JSONUtil.isTypeJSON(text);
}
}
package com.yix.framework.config.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yix.common.utils.json.JacksonUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 自定义jacson配置 实现JacksonUtil工具类 JacksonUtil.MAPPER
*
* @author wangdx
*/
@Configuration
public class JacksonConfig {
@Bean
@ConditionalOnMissingBean(ObjectMapper.class)
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return JacksonUtils.CUSTOMIZER;
}
}
异常处理
全局异常处理
package com.yix.framework.web.exception;
import com.yix.common.exception.ServiceException;
import com.yix.common.utils.StringUtils;
import com.yix.common.web.dto.AjaxResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import java.io.Serializable;
import java.util.List;
/**
* 全局异常处理器
*
* @author wangdx
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@Schema(name = "EXCEPTION_BUSS", description = "业务异常")
public static final String EXCEPTION_BUSS = "exception.buss";
public static final String REQUET_PARAME_MISSING = "requet.parame.missing";
public static final String REQUET_PARAME_TYPE = "requet.parame.type";
public static final String REQUET_METHOD_ERROR = "requet.method.error";
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Object handleBindException(HttpRequestMethodNotSupportedException e
, HttpServletRequest request) {
log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod());
return AjaxResult.errorI18n(REQUET_METHOD_ERROR, new Object[]{e.getMethod()});
}
/**
* JSR303验证标准依赖
*/
@ExceptionHandler(BindException.class)
public Object handleBindException(BindException e
, HttpServletRequest request) {
log.error("请求地址'{}',发生业务异常.", request.getRequestURI(), e);
BindingResult bindingResult = e.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
for (ObjectError objectError : bindingResult.getAllErrors()) {
errorMsg.append(objectError.getDefaultMessage()).append(";");
}
return AjaxResult.error(errorMsg.delete(errorMsg.length() - 1, errorMsg.length()).toString());
}
/**
* 处理 SpringMVC 请求参数缺失 例如说,接口上设置了 @RequestParam(value="xx",required=true) 参数,结果并未传递 xx 参数
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public Object handleMissParam(MissingServletRequestParameterException e
, HttpServletRequest request) {
log.error("请求地址'{}',发生业务异常.", request.getRequestURI(), e);
return AjaxResult.errorI18n(REQUET_PARAME_MISSING, new Object[]{e.getParameterName()});
}
/**
* 处理 SpringMVC 请求参数类型错误 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public Object handleTypeParam(MethodArgumentTypeMismatchException e
, HttpServletRequest request) {
log.error("请求地址'{}',发生业务异常.", request.getRequestURI(), e);
return AjaxResult.errorI18n(REQUET_PARAME_TYPE, new Object[]{e.getParameter().getParameterName(), e.getParameter().getParameterType()});
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public Object handleServiceException(ServiceException e, HttpServletRequest request) {
log.error("请求地址'{}',发生业务异常.", request.getRequestURI(), e);
Integer code = e.getCode();
return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public Object handleException(Exception e, HttpServletRequest request) {
log.error("请求地址'{}',发生系统异常.", request.getRequestURI(), e);
return AjaxResult.error(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public Object handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return AjaxResult.error(e.getMessage());
}
//处理所有异常,主要是提供给 Filter 使用 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
public Object handleThrowable(HttpServletRequest request, Throwable ex) {
if (ex instanceof ServiceException) {
return handleServiceException((ServiceException) ex, request);
}
if (ex instanceof RuntimeException) {
return handleRuntimeException((RuntimeException) ex, request);
}
return handleException((Exception) ex, request);
}
}
错误页
package com.yix.common.web.action;
import com.yix.common.utils.message.MessageUtils;
import com.yix.common.web.dto.AjaxResult;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.MappingJackson2JsonView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 错误页处理
*
* @author wangdx
*/
public class ErrorController extends BasicErrorController {
@Schema(name = "SERVER_NOT_FOUND", description = "资源,服务未找到")
private static final String SERVER_NOT_FOUND = "server.not.found";
public ErrorController(ServerProperties serverProperties) {
super(new DefaultErrorAttributes(), serverProperties.getError());
}
/**
* 覆盖默认的JSON响应
*/
@Override
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = getStatus(request);
AjaxResult ajaxResult = AjaxResult.errorI18n(com.yix.common.constant.HttpStatus.NOT_FOUND
, SERVER_NOT_FOUND, null);
ajaxResult.put("time", System.currentTimeMillis());
// ajaxResult.put("referer", request.getHeader("Referer"));
// ajaxResult.put("path", request.getRequestURI());
return new ResponseEntity<Map<String, Object>>(ajaxResult, status);
}
/**
* 覆盖默认的HTML响应
*/
@Override
@ResponseBody
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
ModelAndView mav = new ModelAndView(new MappingJackson2JsonView());
mav.addObject(AjaxResult.CODE_TAG, com.yix.common.constant.HttpStatus.NOT_FOUND);
mav.addObject(AjaxResult.MSG_TAG, MessageUtils.message(SERVER_NOT_FOUND, null));
mav.addObject("time", System.currentTimeMillis());
// mav.addObject("referer", request.getHeader("Referer"));
// mav.addObject("path", request.getRequestURI());
return mav;
}
}
package com.yix.framework.web.exception;
import com.yix.common.web.action.ErrorController;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 全局basicerror处理
*
* @author wangdx
*/
@AutoConfigureBefore(ErrorMvcAutoConfiguration.class)
@Configuration
public class ErrorConfiguration {
//注入自定义的basicerr
@Bean(name = "errorController")
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public ErrorController errorController(ServerProperties serverProperties) {
return new ErrorController(serverProperties);
}
}
数据校验
1.
messageVal.name=名称不可为空!
2.
@Data
public class MessageVal {
@NotNull(message = "{messageVal.name}")
private String name;
private Integer age;
@NotNull(message = "邮箱错误")
private String email;
}
3.
@RequestMapping("valid")
@ResponseBody
public Object valid(@Valid MessageVal messageVal, @RequestParam(value = "pageSize", required = true) int pageSize, @RequestParam(value = "pageCur", required = true) int pageCur) {
Map<String, Object> result = new HashMap<>();
result.put("messageVal", messageVal);
return result;
}
knife4j 集成
基础环境搭建
knife4j:
enable: true
# 开启生产环境屏蔽
production: false
setting:
enable-home-custom: true
home-custom-path: classpath:markdown/home.md
enable-footer: true
enable-footer-custom: true
footer-custom-content: wang License|COpyfdasf 2081
basic:
enable: true
username: yix
password: yix
@Data
@Tag(name = "MessageVal", description = "消息测试类")
public class MessageVal {
@NotNull(message = "{messageVal.name}")
@Schema(name = "name", description = "项目")
private String name;
private Integer age;
@NotNull(message = "邮箱错误")
private String email;
}
package com.yix.examples.action;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.yix.examples.dto.MessageVal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* knife4j
*
* @author wangdx
*/
@RestController
@RequestMapping("/openapi/*")
@ApiSupport(author = "xujing", order = 15)
@Tag(name = "用户管理", description = "用户数据增删改查")
public class OpenapiAction {
@GetMapping("tag")
@ApiOperationSupport(author = "wangdx") //开发作者信息
@Operation(
summary = "根据ID,查询用户",
parameters = {
@Parameter(name = "id", required = true, in = ParameterIn.PATH)
},
responses = {
@ApiResponse(responseCode = "200", description = "成功", content = @Content(mediaType = "application/json", schema = @Schema(implementation = MessageVal.class))),
@ApiResponse(responseCode = "400", description = "错误", content = @Content(mediaType = "application/json"))
}
)
public Object tag(@Validated MessageVal messageVal) {
return "tad";
}
@GetMapping("author")
public Object author() {
return "tad";
}
}