课程内容

  • 导入地址薄功能代码
  • 用户下单
  • 订单支付

导入地址簿

业务功能

  • 查询地址列表
  • 新增地址
  • 修改地址
  • 查询地址
  • 删除地址
  • 设置默认地址
  • 查询默认地址

表设计

字段名 数据类型 说明 备注
id bigint 主键 自增
user_id bigint 用户id 逻辑外键
consignee varchar(50) 收货人
sex varchar(2) 性别
phone varchar(11) 手机号
province_code varchar(12) 省份编码
province_name varchar(32) 省份名称
city_code varchar(12) 城市编码
city_name varchar(32) 城市名称
district_code varchar(12) 区县编码
district_name varchar(32) 区县名称
detail varchar(200) 详细地址信息 具体到门牌号
label varchar(100) 标签 公司、家、学校
is_default tinyint(1) 是否默认地址 1是 0否

代码导入

  • 黑马已提供功能代码
  • 直接导入即可

用户下单

需求分析

  • 用户通过下单的方式通知商家
  • 下单后产生订单相关数据
  • 订单数据提供以下信息
    • 买的商品类型 数量
    • 订单总金额
    • 收货地址 用户信息

表设计

  • 用户下点业务对应的数据表分为订单表和订单明细表

    • 订单表: 主要存储订单的基本信息(订单号 状态 金额 用户信息)
    • 订单明细表: 主要存储订单详细信息(套餐 菜品)
  • orders订单表

字段名 数据类型 说明 备注
id bigint 主键 自增
number varchar(50) 订单号
status int 订单状态 1待付款 2待接单 3已接单 4派送中 5已完成 6已取消
user_id bigint 用户id 逻辑外键
address_book_id bigint 地址id 逻辑外键
order_time datetime 下单时间
checkout_time datetime 付款时间
pay_method int 支付方式 1微信支付 2支付宝支付
pay_status tinyint 支付状态 0未支付 1已支付 2退款
amount decimal(10,2) 订单金额
remark varchar(100) 备注信息
phone varchar(11) 手机号 冗余字段
address varchar(255) 详细地址信息 冗余字段
consignee varchar(32) 收货人 冗余字段
cancel_reason varchar(255) 订单取消原因
rejection_reason varchar(255) 拒单原因
cancel_time datetime 订单取消时间
estimated_delivery_time datetime 预计送达时间
delivery_status tinyint 配送状态 1立即送出 0选择具体时间
delivery_time datetime 送达时间
pack_amount int 打包费
tableware_number int 餐具数量
tableware_status tinyint 餐具数量状态 1按餐量提供 0选择具体数量
  • order_detail订单明细表
字段名 数据类型 说明 备注
id bigint 主键 自增
name varchar(32) 商品名称 冗余字段
image varchar(255) 商品图片路径 冗余字段
order_id bigint 订单id 逻辑外键
dish_id bigint 菜品id 逻辑外键
setmeal_id bigint 套餐id 逻辑外键
dish_flavor varchar(50) 菜品口味
number int 商品数量
amount decimal(10,2) 商品单价

代码开发

设计DTO

  • 根据用户下单解耦的参数设计DTO
  • 在pojo模块 OrderSubmitDTO已定义
package com.sky.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class OrdersSubmitDTO implements Serializable {
    //地址簿id
    private Long addressBookId;
    //付款方式
    private int payMethod;
    //备注
    private String remark;
    //预计送达时间
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime estimatedDeliveryTime;
    //配送状态  1立即送出  0选择具体时间
    private Integer deliveryStatus;
    //餐具数量
    private Integer tablewareNumber;
    //餐具数量状态  1按餐量提供  0选择具体数量
    private Integer tablewareStatus;
    //打包费
    private Integer packAmount;
    //总金额
    private BigDecimal amount;
}

设计VO

  • 在pojo模块中 OrderSubmitVO已定义
package com.sky.vo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSubmitVO implements Serializable {
    //订单id
    private Long id;
    //订单号
    private String orderNumber;
    //订单金额
    private BigDecimal orderAmount;
    //下单时间
    private LocalDateTime orderTime;
}

Controller层

  • 创建OrderController并提供用户下单方法
@RestController("userOrder")  
@RequestMapping("user/order")  
@Api(tags = "用户端订单相关接口")  
@Slf4j  
public class OrderController {  
  
    @Autowired  
    private OrderService orderService;  
  
    @ApiOperation("用户下单")  
    @PostMapping("/submit")  
    public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO){  
        log.info("用户下单:{}",ordersSubmitDTO);  
        OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);  
        return Result.success(orderSubmitVO);  
    }  
  
}

Service层

  • 创建OrderService接口 声明用户下单方法
public interface OrderService {  
  
    //用户下单  
    OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);  
}
  • 在实现类中实现
@Service  
public class OrderServiceImpl implements OrderService {  
  
    @Autowired  
    private OrderMapper orderMapper;  
  
    @Autowired  
    private OrderDetailMapper orderDetailMapper;  
  
    //检查地址是否有误  
    @Autowired  
    private AddressBookMapper addressBookMapper;  
  
    //检查购物车是否为空  
    @Autowired  
    private ShoppingCartMapper shoppingCartMapper;  
  
    @Override  
    //用户下单  
    public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {  
  
        //处理各种业务异常  
        AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());  
        if (addressBook == null) {  
            throw new RuntimeException(MessageConstant.ADDRESS_BOOK_IS_NULL);  
        }  
        Long userId = BaseContext.getCurrentId();  
  
        ShoppingCart shoppingCart = new ShoppingCart();  
        shoppingCart.setUserId(userId);  
        List<ShoppingCart> shoppingCartlist = shoppingCartMapper.list(shoppingCart);  
  
        if (shoppingCartlist == null || shoppingCartlist.size() == 0) {  
            throw new RuntimeException(MessageConstant.SHOPPING_CART_IS_NULL);  
        }  
  
        //向订单表插入一条数据  
        Orders orders = new Orders();  
        BeanUtils.copyProperties(ordersSubmitDTO,orders);  
        orders.setUserId(userId);  
        orders.setPayStatus(Orders.UN_PAID);  
        orders.setStatus(Orders.PENDING_PAYMENT);  
        orders.setNumber(String.valueOf(System.currentTimeMillis()));  
        orders.setPhone(addressBook.getPhone());  
        orders.setConsignee(addressBook.getConsignee());  
        orders.setUserId(userId);  
  
        orderMapper.insert(orders);  
  
        List<OrderDetail> orderDetailList = new ArrayList<>();  
        //向订单明细表插入多条数据  
        for(ShoppingCart cart : shoppingCartlist){  
            OrderDetail orderDetail = new OrderDetail();  
            BeanUtils.copyProperties(cart,orderDetail);  
            orderDetail.setOrderId(orders.getId());  
        }  
        orderDetailMapper.insertBatch(orderDetailList);  
  
        //清空当前用户购物车数据  
        shoppingCartMapper.deleteById(userId);  
  
        //返回订单相关信息  
        OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()  
                .id(orders.getId())  
                .orderTime(orders.getOrderTime())  
                .orderNumber(orders.getNumber())  
                .orderAmount(orders.getAmount())  
                .build();  
        return orderSubmitVO;  
    }

Mapper层

  • 先创建OrderMapper接口
@Mapper  
public interface OrderMapper {  
  
    //插入订单数据  
    void insert(Orders orders);  
}
  • 对应的xml文件
<mapper namespace="com.sky.mapper.OrderMapper">  
  
    <insert id="insert" parameterType="Orders" useGeneratedKeys="true" keyProperty="id">  
        insert into orders  
        (number, status, user_id, address_book_id, order_time, checkout_time, pay_method, pay_status, amount, remark,        phone, address, consignee, estimated_delivery_time, delivery_status, pack_amount, tableware_number,        tableware_status)        values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},        #{payStatus}, #{amount}, #{remark}, #{phone}, #{address}, #{consignee},        #{estimatedDeliveryTime}, #{deliveryStatus}, #{packAmount}, #{tablewareNumber}, #{tablewareStatus})    </insert>  
</mapper>
  • OrderDetailMapper接口
@Mapper  
public interface OrderDetailMapper {  
  
    //批量插入订单数据  
    void insertBatch(List<OrderDetail> orderDetails);  
}
  • 对应的xml
<mapper namespace="com.sky.mapper.OrderDetailMapper">

    <insert id="insertBatch" parameterType="list">
        insert into order_detail
        (name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)
        values
        <foreach collection="orderDetails" item="od" separator=",">
            (#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},
             #{od.number},#{od.amount},#{od.image})
        </foreach>
    </insert>

</mapper>

订单支付

  • 实现微信支付需要注册微信商户号
  • 个人不具备这种资质
  • 了解微信支付的流程
  • 能过阅读微信官方提供的接口文档
  • 能够和第三方支付平台对接起来即可
  • 代码固定可复用

微信支付介绍

  • 微信支付包括多款产品
  • 本项目选择小程序支付
  • 接入流程
    • 提交资料
    • 签署协议
    • 绑定场景
  • 时序图
    • 1.进入小程序下单
      • 2.下单
      • 3.返回订单号
      • 4.申请微信支付
        • 5.调用微信下单接口
        • 6.返回预支付交易表示
        • 7.将组合数据再次签名
      • 8.返回支付参数
    • 9.用户确认支付
      • 10.调起微信支付
      • 11.返回支付结果
    • 12.显示支付结过
      • 13.推送支付结果
      • 14.更新订单状态
    • 注:不同缩进分别对应
    • 微信用户
      • 微信小程序
        • 商户系统
          • 微信后台
  • 微信支付相关接口
    • JSAPI下单:
      • 商户系统调用该接口在微信支付服务后台生成预支付交易单
      • 对应时序图的第五步
    • 微信小程序调起支付
      • 通过JSAPI下单接口获取到发起支付的必要参数prepayid
      • 然后通过微信支付提供的小程序方法调起小程序支付
      • 对应时序图第十步

微信支付准备工作

数据安全问题

  • 完成微信支付有两个关键的步骤
  • 第一个需要在商户系统中调用后台的一个下单接口 就是生成预支付交易单
  • 第二个就是支付成功之后微信后台会给推送消息
  • 需要获得微信支付平台文件 商户私钥文件

如何调用到商户系统

  • 微信后台会调用到商户系统推送支付的结过
  • 调用本质上是http请求
  • 目前我们电脑使用局域网ip 微信后台无法调用
  • 使用内网穿透方案解决

cpolar软件

  • 下载地址:https://dashboard.cpolar.com/get-started
  • 跟随页面提示注册账号
  • 赋值自己的authtoken
  • 在安装界面执行cmd打开命令行窗口
  • 执行coplar.exe authtoken +自己的token
  • 再次执行cpolar.exe http +你的服务端口号
  • 即可获取临时公网域名
  • 使用临时域名访问swagger

代码导入

  • 直接导入黑马提供的支付代码即可
  • 配置相关文件
  • application-dev.yml
sky:
  wechat:
    appid: 自己的
    secret: 自己的
    mchid : 1561414331
    mchSerialNo: 4B3B3DC35414AD50B1B755BAF8DE9CC7CF407606
    privateKeyFilePath: D:\apiclient_key.pem
    apiV3Key: CZBK51236435wxpay435434323FFDuv3
    weChatPayCertFilePath: D:\wechatpay_166D96F876F45C7D07CE98952A96EC980368ACFC.pem
    notifyUrl: https://www.weixin.qq.com/wxpay/pay.php
    refundNotifyUrl: https://www.weixin.qq.com/wxpay/pay.php
  • application.yml
sky:
  wechat:
    appid: ${sky.wechat.appid}
    secret: ${sky.wechat.secret}
    mchid : ${sky.wechat.mchid}
    mchSerialNo: ${sky.wechat.mchSerialNo}
    privateKeyFilePath: ${sky.wechat.privateKeyFilePath}
    apiV3Key: ${sky.wechat.apiV3Key}
    weChatPayCertFilePath: ${sky.wechat.weChatPayCertFilePath}
    notifyUrl: ${sky.wechat.notifyUrl}
    refundNotifyUrl: ${sky.wechat.refundNotifyUrl}
  • 实际上我们没有黑马的证书文件
  • 也无法自己申请
  • 这部分代码实际上不能运行
  • 阅读理解即可

想温柔的对待这个世界