手记

需要掌握的技术知识点

目录:


  • lambda表达式

  • 表单校验

  • 单元测试的书写

  • 抽象复用性强的代码

  • 配置自动装载

  • AOP进行页面登录验证

  • 数据类型序列化

  • JSON与对象的转换

  • 控制器异常处理




lambda表达式


样例:


List<CartDTO> cartDTOList = orderDTO.getOrderDetailList()

        .stream().map(e -> new CartDTO(e.getProductId(), e.getProductQuantity()))

        .collect(Collectors.toList());


使用场景


> 需要对两个不同的数据类(List)进行转换。

> 比如这个例子,需要将OrderDetailList 转成 CartDTOList

> 更多的用法,可以参考这篇博客:https://www.cnblogs.com/aoeiuv/p/5911692.html


表单校验


样例:


@PostMapping("/create")

public ResultVO<Map<String, String>> create(@Valid OrderForm orderForm,

                                            BindingResult bindingResult)

{}


package com.product.sell.sell.form;


import lombok.Data;

import org.hibernate.validator.constraints.NotEmpty;


@Data

public class OrderForm {

    @NotEmpty(message="姓名必填")

    private String name;


    @NotEmpty(message = "手机号必填")

    private String phone;


    @NotEmpty(message = "地址必填")

    private String address;


    @NotEmpty(message = "openid必填")

    private String openid;


    @NotEmpty(message = "购物车不能为空")

    private String items;

}


一般需要对参数进行验证的,都是保存的post接口

由于数据量比较大,直接定义一个对象来对传递的表单进行存储。

然后在FORM类里,通过注解的形式进行校验。

控制器层通过


if (bindingResult.hasErrors()) {

    log.error("【创建订单】参数不正确:orderForm={}", orderForm);

    throw new SellException(ResultEnum.PARAM_ERR.getCode(),

            bindingResult.getFieldError().getDefaultMessage());

}


来获取到校验的结果


单元测试的书写


单元测试中需要加上这两个注解。


@RunWith(SpringRunner.class)

@SpringBootTest

public class OrderServiceImplTest {}


具体的单元测试:


@Test

public void findOne() {

    String orderId = "154494950704674973";

    OrderDTO result = orderService.findOne(orderId);

    log.info("查询单个订单", result);

    Assert.assertNotNull(result);

}


抽象复用性强的代码逻辑


场景:需要通过一个status值,获取到对应的中文解析。(1-》激活 0-》冻结)

这种一般用在后台,需要对数字进行解析。常规做法就是写 if 判断一下。

但是会存在非常多的重复性代码。

比较优秀的做法是,通过两个参数,直接获取到相对应的中文描述。


两个参数分别是 Integer status, Class<T> enumClass


public class EnumUtil {

    // T 这里有可能是任何一种类型的枚举类

    // T extends codeenum 这个接口 保证了他一定实现了接口里的方法

    // 这就是写 CodeEnum,并让你的枚举类实现它的原因所在

    public static <T extends CodeEnum> T getByCode(Integer code, Class<T> enumClass)

    {

        for (T each : enumClass.getEnumConstants()) {

            if (code.equals(each.getCode())) {

                return each;

            }

        }


        return null;

    }

}


这里Class 为啥要加一个 T呢?具体的解释,可以参考这篇博客: https://www.cnblogs.com/zhima-hu/p/7352555.html


通俗的讲,就是指任何一个类。


但是代码里用到了一个方法:


each.getCode()


如何保证传入的类一定有这个方法呢?


<T extends CodeEnum>


public interface CodeEnum {

    // 因为本次项目中code全部都是 Integer

    // 如果项目比较大,可以定义为 泛型

    Integer getCode();

}


这样就保证了作为参数传入的类,必定实现了 getCode 这个方法,因此可以放心的使用。


然后我们在具体的数据类中,写一个方法:



@Data

//@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)

// 上面这个废弃了。

// 如果结果中有null,则不去掉这个属性

// 还可以在yml里做全局配置.jackson ---

//@JsonInclude(JsonInclude.Include.NON_NULL)

public class OrderDTO {

    ...


    @JsonIgnore

    public OrderStatus getOrderStatusEnum()

    {

        return EnumUtil.getByCode(orderStatus, OrderStatus.class);

    }


    @JsonIgnore

    public PayStatus getPayStatusEnum()

    {

        return EnumUtil.getByCode(payStatus, PayStatus.class);

    }

}


lombok插件


引入依赖


<dependency>

    <groupId>org.projectlombok</groupId>

    <artifactId>lombok</artifactId>

</dependency>


即可使用注解的方式,免去getter setter的代码噩梦

记得要需要给 Idea 安装一个插件


配置自动装载


这次涉及到微信的开发,里面有些Jar包需要进行初始化配置(appID,secretID等)

这些配置内容,不写在控制器层,也不写在service层。而是直接将其装配成 bean。



@Component

public class WechatPayConfig {

    @Autowired

    private WechatAccountPayConfig wechatAccountPayConfig;


    @Bean

    public BestPayServiceImpl bestPayService()

    {

        BestPayServiceImpl bestPayService = new BestPayServiceImpl();

        bestPayService.setWxPayH5Config(wxPayH5Config());


        return bestPayService;

    }


    @Bean

    public WxPayH5Config wxPayH5Config()

    {

        WxPayH5Config wxPayH5Config = new WxPayH5Config();

        wxPayH5Config.setAppId(wechatAccountPayConfig.getMpAppId());

        wxPayH5Config.setAppSecret(wechatAccountPayConfig.getMpAppSecret());

        wxPayH5Config.setKeyPath(wechatAccountPayConfig.getKeyPath());

        wxPayH5Config.setMchId(wechatAccountPayConfig.getMchId());

        wxPayH5Config.setMchKey(wechatAccountPayConfig.getMchKey());

        wxPayH5Config.setNotifyUrl(wechatAccountPayConfig.getNotifyUrl());


        return wxPayH5Config;

    }

}


@Data

@Component

@ConfigurationProperties(prefix = "wechatpay")

public class WechatAccountPayConfig {

    private String mpAppId;


    private String mpAppSecret;

    // 微信商户的一些配置信息

    private String mchId;


    private String mchKey;


    private String keyPath;


    private String notifyUrl;

}


AOP进行页面登录验证


跟Laravel的middleware很类似。

通过一个pointcut,来定义需要进行切面验证的接口进行定义

通过几个方法(doBefore 和 doAfter)来定义进入这个方法之前(之后)需要做的逻辑

这边逻辑还有点不对。

登录这块儿的代码,需要重新写下。


public class HttpAspact {


    private final static Logger logger = LoggerFactory.getLogger(HttpAspact.class);


    @Pointcut("execution(public * com.product.sell.sell.controller.BuyerOrderController.list(..))")

    public void log(){}


    @Before("log()")

    public void doBefore(JoinPoint joinPoint)

    {

        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();

        HttpServletRequest request = attributes.getRequest();

        // url

        logger.info("url={}", request.getRequestURL());


        // method

        logger.info("method={}", request.getMethod());


        // IP

        logger.info("ip={}", request.getRemoteAddr());


        // class 方法

        logger.info("class method = {}", joinPoint.getSignature().getDeclaringTypeName() +

                "." +

                 joinPoint.getSignature().getName());


        // 参数

        logger.info("args={}", joinPoint.getArgs());

    }


    @After("log()")

    public void doAfter()

    {


    }



数据类型序列化 6-12


一般像ctime,mtime这种类型的字段,DAO里定义成 Date 类型的。

但是可能前端需要的格式不是这个类型,而是一个String类型的时间戳,如何进行转换呢?


@JsonSerialize(using = DateToLongSerializer.class)

private Date updateTime;


这里用到了  JsonSerialize 注解。


public class DateToLongSerializer extends JsonSerializer<Date> {

    @Override

    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {

        jsonGenerator.writeNumber(date.getTime() / 1000);

    }

}


JSON与对象的转换 6-11


代码里定义了一个 List 的值,但是前端表单传过来的是一个json字符串。


需要对这个字符串进行转换


public static OrderDTO convert(OrderForm orderForm)

{

    Gson gson = new Gson();

    OrderDTO orderDTO = new OrderDTO();

    List<OrderDetail> orderDetailList = new ArrayList<>();

    try {

        orderDetailList = gson.fromJson(orderForm.getItems(),

                new TypeToken<List<OrderDetail>>(){}.getType());

    } catch (Exception e) {

        log.error("【对象转化出错】,string={}", orderForm.getItems());

        throw new SellException(ResultEnum.PARAM_ERR);

    }

    orderDTO.setOrderDetailList(orderDetailList);


    return orderDTO;

}



控制器异常处理


在程序抛出异常时,接口不能返回500错误,而是要返回异常信息。格式不能乱


@ControllerAdvice

public class ExceptionHandle {


    @ExceptionHandler(value = SellException.class)

    @ResponseBody

    public ResultVO handle(Exception e)

    {

        if (e instanceof SellException) {

            SellException sellException = (SellException) e;

            return ResultVOUtil.error(sellException.getCode(), sellException.getMessage());

        }


        return ResultVOUtil.error(-1, e.getMessage());

    }

}


0人推荐
随时随地看视频
慕课网APP