news 2026/2/25 2:53:01

苍穹外卖日记 | Day8 用户下单、订单支付

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
苍穹外卖日记 | Day8 用户下单、订单支付

一、回顾与概览

第七天我们使用SpringCache进行缓存商品的操作,并将购物车这个核心模块编写完成。今天需要完成用户下单和订单支付两个核心模块,前者注重理解Service层逻辑,后者理解调用微信支付的时序图。

二、用户下单

1.前置地址簿功能导入

(1)产品原型和接口文档

通过分析接口文档和产品原型可以大致清楚地址簿功能的组成,查询用户所有地址、新增地址、修改地址、设置默认地址(包含取消功能)、查询默认地址、删除地址。上述功能接口写下来与之前的并没有太大区别因此直接导入,作为用户下单的前置功能模块。

(2)Controller层

package com.sky.controller.user; import com.sky.context.BaseContext; import com.sky.entity.AddressBook; import com.sky.result.Result; import com.sky.service.AddressBookService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/user/addressBook") @Api(tags = "C端地址簿接口") public class AddressBookController { @Autowired private AddressBookService addressBookService; /** * 查询当前登录用户的所有地址信息 * * @return */ @GetMapping("/list") @ApiOperation("查询当前登录用户的所有地址信息") public Result<List<AddressBook>> list() { AddressBook addressBook = new AddressBook(); addressBook.setUserId(BaseContext.getCurrentId()); List<AddressBook> list = addressBookService.list(addressBook); return Result.success(list); } /** * 新增地址 * * @param addressBook * @return */ @PostMapping @ApiOperation("新增地址") public Result save(@RequestBody AddressBook addressBook) { addressBookService.save(addressBook); return Result.success(); } @GetMapping("/{id}") @ApiOperation("根据id查询地址") public Result<AddressBook> getById(@PathVariable Long id) { AddressBook addressBook = addressBookService.getById(id); return Result.success(addressBook); } /** * 根据id修改地址 * * @param addressBook * @return */ @PutMapping @ApiOperation("根据id修改地址") public Result update(@RequestBody AddressBook addressBook) { addressBookService.update(addressBook); return Result.success(); } /** * 设置默认地址 * * @param addressBook * @return */ @PutMapping("/default") @ApiOperation("设置默认地址") public Result setDefault(@RequestBody AddressBook addressBook) { addressBookService.setDefault(addressBook); return Result.success(); } /** * 根据id删除地址 * * @param id * @return */ @DeleteMapping @ApiOperation("根据id删除地址") public Result deleteById(Long id) { addressBookService.deleteById(id); return Result.success(); } /** * 查询默认地址 */ @GetMapping("default") @ApiOperation("查询默认地址") public Result<AddressBook> getDefault() { //SQL:select * from address_book where user_id = ? and is_default = 1 AddressBook addressBook = new AddressBook(); addressBook.setIsDefault(1); addressBook.setUserId(BaseContext.getCurrentId()); List<AddressBook> list = addressBookService.list(addressBook); if (list != null && list.size() == 1) { return Result.success(list.get(0)); } return Result.error("没有查询到默认地址"); } }

(3)Service层

package com.sky.service.impl; import com.sky.context.BaseContext; import com.sky.entity.AddressBook; import com.sky.mapper.AddressBookMapper; import com.sky.service.AddressBookService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @Slf4j public class AddressBookServiceImpl implements AddressBookService { @Autowired private AddressBookMapper addressBookMapper; /** * 条件查询 * * @param addressBook * @return */ public List<AddressBook> list(AddressBook addressBook) { return addressBookMapper.list(addressBook); } /** * 新增地址 * * @param addressBook */ public void save(AddressBook addressBook) { addressBook.setUserId(BaseContext.getCurrentId()); addressBook.setIsDefault(0); addressBookMapper.insert(addressBook); } /** * 根据id查询 * * @param id * @return */ public AddressBook getById(Long id) { AddressBook addressBook = addressBookMapper.getById(id); return addressBook; } /** * 根据id修改地址 * * @param addressBook */ public void update(AddressBook addressBook) { addressBookMapper.update(addressBook); } /** * 设置默认地址 * * @param addressBook */ @Transactional public void setDefault(AddressBook addressBook) { // 补充取消当前地址为默认地址:如果当前地址为默认地址1,那么就设置为0 AddressBook addressBook1 = addressBookMapper.getById(addressBook.getId()); if (addressBook1.getIsDefault() == 1) { addressBook.setIsDefault(0); addressBookMapper.update(addressBook); return; } //1、将当前用户的所有地址修改为非默认地址 update address_book set is_default = ? where user_id = ? addressBook.setIsDefault(0); addressBook.setUserId(BaseContext.getCurrentId()); addressBookMapper.updateIsDefaultByUserId(addressBook); //2、将当前地址改为默认地址 update address_book set is_default = ? where id = ? addressBook.setIsDefault(1); addressBookMapper.update(addressBook); } /** * 根据id删除地址 * * @param id */ public void deleteById(Long id) { addressBookMapper.deleteById(id); } }

(4)Mapper层

package com.sky.mapper; import com.sky.entity.AddressBook; import org.apache.ibatis.annotations.*; import java.util.List; @Mapper public interface AddressBookMapper { /** * 条件查询 * @param addressBook * @return */ List<AddressBook> list(AddressBook addressBook); /** * 新增 * @param addressBook */ @Insert("insert into address_book" + " (user_id, consignee, phone, sex, province_code, province_name, city_code, city_name, district_code," + " district_name, detail, label, is_default)" + " values (#{userId}, #{consignee}, #{phone}, #{sex}, #{provinceCode}, #{provinceName}, #{cityCode}, #{cityName}," + " #{districtCode}, #{districtName}, #{detail}, #{label}, #{isDefault})") void insert(AddressBook addressBook); /** * 根据id查询 * @param id * @return */ @Select("select * from address_book where id = #{id}") AddressBook getById(Long id); /** * 根据id修改 * @param addressBook */ void update(AddressBook addressBook); /** * 根据 用户id修改 是否默认地址 * @param addressBook */ @Update("update address_book set is_default = #{isDefault} where user_id = #{userId}") void updateIsDefaultByUserId(AddressBook addressBook); /** * 根据id删除地址 * @param id */ @Delete("delete from address_book where id = #{id}") void deleteById(Long id); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.sky.mapper.AddressBookMapper"> <select id="list" parameterType="AddressBook" resultType="AddressBook"> select * from address_book <where> <if test="userId != null"> and user_id = #{userId} </if> <if test="phone != null"> and phone = #{phone} </if> <if test="isDefault != null"> and is_default = #{isDefault} </if> </where> </select> <update id="update" parameterType="addressBook"> update address_book <set> <if test="cityCode != null"> city_code = #{cityCode}, </if> <if test="cityName != null"> city_name = #{cityName}, </if> <if test="consignee != null"> consignee = #{consignee}, </if> <if test="districtCode != null"> district_code = #{districtCode}, </if> <if test="districtName != null"> district_name = #{districtName}, </if> <if test="provinceCode != null"> province_code = #{provinceCode}, </if> <if test="provinceName != null"> province_name = #{provinceName}, </if> <if test="sex != null"> sex = #{sex}, </if> <if test="phone != null"> phone = #{phone}, </if> <if test="detail != null"> detail = #{detail}, </if> <if test="label != null"> label = #{label}, </if> <if test="isDefault != null"> is_default = #{isDefault}, </if> </set> where id = #{id} </update> </mapper>

2.需求分析与接口文档

接口文档(对应前端请求,响应数据即小程序下单流程界面第二张展示的数据):

涉及orders和order_detail两张表(两张表是一对多关系):

前端请求(点击去支付调用后端接口):

产品原型(小程序下单流程界面):

基于我们的最终目的是把用户订单所提供的信息插入到orders和order_detail两张表中,又因为请求参数中所传的参数远远达不到表中字段要求,因此我们需要进行补充字段后进行插入。

3.Controller层

直接调用Service层,最终返回OrderSubmitVO,传参json格式加上@RequestBody注解。

4.Service层

(1)构造订单数据

首先先处理Orders表的插入,那先new一个Orders类,然后调用BeanUtils进行属性拷贝。接着就是逐个set缺失字段了。订单编号使用当前系统时间保证唯一性(单线程,全部由数字组成,包含字母也可以考虑使用UUID),订单状态设置为常量待付款,用户Id从ThreadLocal中获取,下单时间设置为当前时间,支付状态设置为常量未支付,手机地址、详细地址、收货人这三个字段在AddressBook表中,请求参数中传了AddressBookId,因此可以调用AddressBookMapper根据Id查地址,然后判断如果地址为空抛出自定义异常即可。下单人字段在User表中,可以通过ThreadLocal中的userId查User表获取到用户名称,查出来的user也需要判断如果为空抛出自定义异常用户未登录(userId不存在,即ThreadLocal中没有存入,拦截器未拦截)。最后调用OrdersMapper的insert方法,传入补充好字段的Orders对象插入表中即可。

(2)构造订单明细数据

之前已经分析过orders表和order_detail表两张表是一对多关系,因此先构造一个List,里面存储多个OrderDetail对象,另外分析order_detail表和shopping_cart表可以发现两张表字段基本上是一样的,因此只需要根据userId查询该用户购物车数据即可,然后把购物车里面的数据对应插入到order_detail表中即可,这也符合购物时把购物车商品清空移动到订单上的业务逻辑。对于查出来的List<ShoppingCart>也需要判断如果为空抛出自定义异常。然后就可以遍历这个List,每次遍历都new一个OrderDeatil对象,把ShoppingCart属性拷贝到OrderDeatil中,但需要注意id属性不能拷贝,避免出现重复id字段,BeanUtils的copyProperties的第三个参数ignoreProperties可以设置忽略字段。此外由于orders表和order_detail表两张表是一对多关系,每行订单明细数据还要管理订单id,之后才可以把封装好的对象add进List中,最后调用orderDetailMapper的insertBatch方法批量插入List表中的数据。

(3)清空购物车

成功下单后购物车里面的数据就被清空了,直接调用shoppingCartMapper的delete方法即可,传参userId,只清空该用户的购物车数据。

(4)返回OrderSubmitVO对象

观察小程序下单流程第二张图片,包含了订单ID,订单编号,订单总额,下单时间四个字段,OrderSubmitVO就是包含了这四个字段,需要将对应属性都补充后返回给前端。

5.Mapper层

AddressBookMapper的getById方法查询地址表

UserMapper的selectById方法查询用户表

ShoppingCartMapper的select方法查询购物车

OrdersMapper的insert方法插入订单数据

OrdersDetailMapper的insertBatch方法插入订单明细数据

ShoppingCartMapper的delete方法清空购物车

三、订单支付

1.微信小程序支付时序图

2.业务逻辑修改

点击确认支付就会自动调用开发者服务器(后端)的payment接口。

这一步是在Service层,主要是开发者服务器调用第三方微信下单接口的接口文档,最终返回prepay_id给开发者服务器作为预支付交易会话标识,后端则需要对该标识进一步加密,最终返回给小程序端支付参数OrderPaymentVO。但是因为在项目中无法获取有效的mchid等请求参数,所以没法返回有效的OrderPaymentVO,真正实现调用微信支付,所以需要修改Service层逻辑,直接在payment方法中调用paySuccess方法修改数据库中Orders表的订单状态,参数订单编号在ordersPaymentDTO里面存在,返回空的OrderPaymentVO保证程序正常走完即可。

由于后端无法返回正确的支付参数OrderPaymentVO,所以小程序端也无法调用微信支付,因此把相应代码注释,直接跳转支付成功页面即可。

3.实现效果

点击支付后显示成功下单,数据库中订单状态改为待接单。

四、总结

今天主要完成了用户下单和订单支付两个功能模块,感觉有了之前购物车模块编写的经验,用户下单写起来也不是很复杂,订单支付这里也是调用一些第三方接口类似操作,不过因为配置信息问题没法真正实现微信支付调用,只是进行了个模拟,所以导入的PayNotifyController这些东西都没用上,里面的一些加密逻辑也不怎么了解,新学了个cpolar技术也没用上,这玩意可以让第三方接口服务重定向到开发者服务器上可以用虚拟的网站地址而不是localhost又定向到自己服务器,可以在开发阶段调试使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/19 21:56:54

生成式AI革新多模态信息检索

利用生成式AI进行多模态信息检索 过去十年的大部分时间里&#xff0c;机器学习严重依赖于嵌入的概念&#xff1a;模型学习将输入数据转换为向量&#xff0c;使得向量空间内的几何关系具有语义含义。例如&#xff0c;在表示空间中嵌入相近的词可能具有相似的含义。嵌入概念意味…

作者头像 李华
网站建设 2026/2/24 2:08:13

互联网大厂Java面试:从分布式事务到微服务优化的技术场景解读

互联网大厂Java面试&#xff1a;从分布式事务到微服务优化的技术场景解读 场景与角色 在互联网大厂的会议室里&#xff0c;严肃的面试官李云龙正对水货程序员谢宝庆进行技术面试。第一轮提问&#xff1a;分布式事务的基本概念与实现 李云龙&#xff1a;谢宝庆&#xff0c;简单说…

作者头像 李华
网站建设 2026/2/24 4:19:09

【DVMBiLSTM诊断网络】基于离散韦格纳分布DWVD结合多尺度卷积神经网络和双向长短期记忆网络的故障诊断研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。 &#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书…

作者头像 李华
网站建设 2026/2/21 19:06:56

学长亲荐 10个AI论文网站测评:研究生毕业论文写作必备工具推荐

在当前学术研究日益数字化的背景下&#xff0c;研究生群体面临着从选题构思到论文撰写、格式调整等多环节的挑战。尤其是在AI工具广泛应用的今天&#xff0c;如何选择一款高效、专业且符合学术规范的写作辅助工具&#xff0c;成为众多学生关注的焦点。为此&#xff0c;我们基于…

作者头像 李华