跳至主要內容

web模块构建

wangdx大约 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";
    }
}

上次编辑于: