支付商城项目亮点总结


这是我在最近学习并完结的一个商城项目,拖得时间比较长,也不断总结了一些亮点和开发技巧,为了提高自己的项目开发能力,所以记录下来

支付

微信的native支付和支付宝的网页支付

  1. 使用了一个第三方jar包best-pay-sdk,该SDK帮我们集成了微信支付、支付宝支付。主要的步骤是引入依赖,支付宝和微信的配置(appId,私钥,公钥,回调地址,异步通知地址),支付宝要开启沙箱支付,因为微信是需要企业资质才能进行native支付

        @Component
        public class BestPayConfig {
    
        @Autowired
        AlipayAccountConfig alipayAccountConfig;
    
        @Bean
        public BestPayService bestPayService(){
            //支付宝配置
    
            AliPayConfig aliPayConfig = new AliPayConfig();
            aliPayConfig.setAppId(alipayAccountConfig.getAppId());
            aliPayConfig.setPrivateKey(alipayAccountConfig.getPrivateKey());
            aliPayConfig.setAliPayPublicKey(alipayAccountConfig.getAliPayPublicKey());
            aliPayConfig.setReturnUrl(alipayAccountConfig.getReturnUrl());
            aliPayConfig.setNotifyUrl(alipayAccountConfig.getNotifyUrl());
            aliPayConfig.setSandbox(alipayAccountConfig.isSandbox());
    
            //支付类, 所有方法都在这个类里
            BestPayServiceImpl bestPayService = new BestPayServiceImpl();
            bestPayService.setAliPayConfig(aliPayConfig);
            return bestPayService;
        }
    }
    

    这里面的配置使用软配置,这是一个好习惯,然后在application.yml里面进行内容配置,例如

        /**
    * Created by RD
    */
    @Component
    @ConfigurationProperties(prefix = "alipay")
    @Data
    public class AlipayAccountConfig {
    
        private String appId;
    
        private String privateKey;
    
        private String aliPayPublicKey;
    
        private String returnUrl;
    
        private String notifyUrl;
    
        private boolean sandbox;
    
    }
    alipay:
        appId: 2021000117610811
        privateKey: xxxxx
        aliPayPublicKey: xxxxx
        returnUrl: http://127.0.0.1
        notifyUrl: http://67dg79.natappfree.cc/pay/notify
        sandbox: true

    当然,这里还用到了内网穿透的一部分知识,以后还用到的时候再进行总结

枚举类也是解决硬编码问题的一种常用方法

@Getter
public enum PayPlatformEnum {

    //1-支付宝,2-微信
    ALIPAY(1),

    WX(2),
    ;

    Integer code;

    PayPlatformEnum(Integer code) {
        this.code = code;
    }
}

商城

  1. 对于一些常用的常量,可以新建一个常量类

        public class MallConst {
        public static final String CURRENT_USER = "currentUser";
    
        public static final Integer ROOT_PARENT_ID = 0;
    }
  2. 对于需要返回给前端的数据,可以单独提出一个vo对象,这样原始的pojo只用来建表,同时,将需要返回给前端的数据可以封装成一个接受类vo对象,用于提示返回的状态,因为每一个单独的vo对象数据类型不一样,可以采用泛型程序设计,如下:
    1

    @Data
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    public class ResponseVo<T> {
        private Integer status;
    
        private String msg;
    
        private T data;
    
        public ResponseVo(Integer status, String msg) {
            this.status = status;
            this.msg = msg;
        }
        public ResponseVo(Integer status,T data){
            this.status =status;
            this.data=data;
        }
        public static <T> ResponseVo<T> successByMsg(String msg){
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),msg);
        }
        public static <T> ResponseVo<T> success(){
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
        }
        public static <T> ResponseVo<T> success(T data){
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),data);
        }
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum){
            return new ResponseVo<>(responseEnum.getCode(),responseEnum.getDesc());
        }
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum,String msg){
            return new ResponseVo<>(responseEnum.getCode(),msg);
        }
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum, BindingResult bindingResult){
            return new ResponseVo<>(responseEnum.getCode(),
                    Objects.requireNonNull(bindingResult.getFieldError()).getField()+" "+bindingResult.getFieldError().getDefaultMessage());
        }
    }
  3. 可以单独提出一个提交类,用于封装需要提交的参数,并可以用@Valid进行字段的校验,同时,对待参数校验可能发生的异常,可以采用统一异常处理(@ControllerAdvice),例如

    @ControllerAdvice
    public class RuntimeExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        @ResponseBody
        @ResponseStatus(HttpStatus.FORBIDDEN)
        public ResponseVo handle(RuntimeException e){
            return ResponseVo.error(ResponseEnum.ERROR,e.getMessage());
        }
    
        @ExceptionHandler(UserLoginException.class)
        @ResponseBody
        public ResponseVo userLoginHandle(){
            return ResponseVo.error(ResponseEnum.NEED_LOGIN);
        }
    
        @ExceptionHandler(MethodArgumentNotValidException.class)
        @ResponseBody
        public ResponseVo notValidExceptionHandle(MethodArgumentNotValidException e){
            var bindingResult = e.getBindingResult();
            return ResponseVo.error(ResponseEnum.PARAM_ERROR,
                    Objects.requireNonNull(bindingResult.getFieldError()).getField()+" "+bindingResult.getFieldError().getDefaultMessage());
        }
    }

    b

       @PostMapping("/user/login")
    public ResponseVo login(@Valid @RequestBody UserLoginForm userLoginForm,
                            HttpSession session) {
        val userResponseVo = userService.login(userLoginForm.getUsername(), userLoginForm.getPassword());
    
        session.setAttribute(MallConst.CURRENT_USER, userResponseVo.getData());
    
        return userResponseVo;
    }
    
  4. 可以使用BeanUtils.copyProperties对vo对象和pojo对象进行相同属性的赋值

    private ProductVo product2ProductVo(Product product) {
        var productVo = new ProductVo();
        BeanUtils.copyProperties(product, productVo);
        return productVo;
    }
  5. 对于用户的密码可以使用springboot自带的DigestUtils类下的md5进行加密

    user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes(StandardCharsets.UTF_8)));

RabbitMq的使用

创建队列,支付端发送消息,商城接受消息,像这种中间件都需要引入jar包,在application.yml中进行诸如地址密码用户名端口等参数配置

MySQL的使用技巧

对于sql的操作,尽量选择少操作数据库,对于需要操纵的数据,可以选择一次性查出来,然后再对数据进行筛选,这样效率会高一点,对于一些已经有了的数据,可以使用in表达式进行查询,避免for循环带来的查询效率损失

<select id="selectByOrderNoSet" resultMap="BaseResultMap">
  select
  <include refid="Base_Column_List" />
  from mall_order_item
  <where>
    <if test="!orderNoSet.isEmpty()">
      order_no in
      <foreach collection="orderNoSet" item = "item" index="index" open="(" separator="," close=")">
        #{item}
      </foreach>
    </if>
  </where>
</select>

提高数据库的查询效率还可以使用map集合,例如

Set<Integer> productIdSet = cartList.stream()
		.map(Cart::getProductId)
		.collect(Collectors.toSet());
List<Product> productList = productMapper.selectByProductIdSet(productIdSet);
Map<Integer, Product> map  = productList.stream()
		.collect(Collectors.toMap(Product::getId, product -> product));

List<OrderItem> orderItemList = new ArrayList<>();
Long orderNo = generateOrderNo();
for (Cart cart : cartList) {
	//根据productId查数据库
	Product product = map.get(cart.getProductId());
          ...
          ...

Redis的使用技巧

Redis是不支持事务回滚的,详情可以看Redis事务不支持回滚

Stream的使用技巧

对于程序中的集合操作使用是很频繁的,使用Stream可以大量减少代码,代码呈现也更加美观,stream的使用看这个就够了

@Override
public ResponseVo<Integer> sum(Integer uid) {
	Integer sum = listForCart(uid).stream()
			.map(Cart::getQuantity)
			.reduce(0, Integer::sum);
	return ResponseVo.success(sum);
}
List<CategoryVo> categoryVoList = categories.stream()
       .filter(e -> e.getParentId().equals(ROOT_PARENT_ID))
       .map(this::category2CategoryVo)
        .sorted(Comparator.comparing(CategoryVo::getSortOrder).reversed())
       .collect(Collectors.toList());

文章作者: RD
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 RD !
评论
评论
  目录