🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:苍穹外卖项目实战
🌠 首发时间:2024年5月20日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾
导入地址簿功能代码
需求分析和设计
产品原型:
业务功能:
- 查询地址列表
- 新增地址
- 修改地址
- 删除地址
- 设置默认地址
- 查询默认地址
接口设计:
-
新增地址
-
查询当前登录用户的所有地址信息
-
查询默认地址
-
根据 id 修改地址
-
根据 id 删除地址
-
根据 id 查询地址
-
设置默认地址
数据库设计(address_book表):
代码导入
导入资料中的地址簿模块功能代码:
功能测试
可以通过如下方式进行测试:
- 查看控制台 sql 和数据库中的数据变化
- Swagger 接口文档测试
- 前后端联调
启动服务,来到小程序端,点击进入个人中心:
点击地址管理:
一开始是没有地址的,你可以添加地址:
用户下单
需求分析和设计
用户下单业务说明:
-
在电商系统中,用户是通过下单的方式通知商家,用户已经购买了商品,需要商家进行备货和发货
-
用户下单后会产生订单相关数据,订单数据需要能够体现如下信息:
用户点餐业务流程:
接口设计(分析):
数据库设计:
-
订单表 orders
-
订单明细表 order_detail
-
订单表和订单明细表的关系:一对多
代码开发
根据用户下单接口的参数设计 DTO:
根据用户下单接口的返回结果设计 VO:
创建 OrderController 并提供用户下单方法:
import com.sky.dto.OrdersSubmitDTO;
import com.sky.result.Result;
import com.sky.service.OrderService;
import com.sky.vo.OrderSubmitVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "C端订单接口")
public class OrderController {
@Autowired
private OrderService orderService;
/**
* 用户下单
*
* @param ordersSubmitDTO
* @return
*/
@PostMapping("/submit")
@ApiOperation("用户下单")
public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
log.info("用户下单:{}", ordersSubmitDTO);
OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
return Result.success(orderSubmitVO);
}
}
创建 OrderService 接口,并声明用户下单方法:
import com.sky.dto.OrdersSubmitDTO;
import com.sky.vo.OrderSubmitVO;
public interface OrderService {
/**
* 用户下单
*
* @param ordersSubmitDTO
* @return
*/
OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO);
}
创建 OrderServiceImpl 实现 OrderService 接口:
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderDetailMapper orderDetailMapper;
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private AddressBookMapper addressBookMapper;
/**
* 用户下单
*
* @param ordersSubmitDTO
* @return
*/
@Transactional
public OrderSubmitVO submitOrder(OrdersSubmitDTO ordersSubmitDTO) {
//异常情况处理——收货地址为空
AddressBook addressBook = addressBookMapper.getById(ordersSubmitDTO.getAddressBookId());
if (addressBook == null) {
throw new AddressBookBusinessException(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 ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
}
//构造订单数据
Orders order = new Orders();
BeanUtils.copyProperties(ordersSubmitDTO, order); //拷贝对象属性
order.setPhone(addressBook.getPhone()); //手机号
order.setAddress(addressBook.getDetail()); //详细地址
order.setConsignee(addressBook.getConsignee()); //收货人
order.setNumber(String.valueOf(System.currentTimeMillis())); //订单号, 为当前时间戳
order.setUserId(userId);
order.setStatus(Orders.PENDING_PAYMENT); //订单状态为待付款
order.setPayStatus(Orders.UN_PAID); //支付状态为未支付
order.setOrderTime(LocalDateTime.now()); //下单时间
//向订单表插入1条数据
orderMapper.insert(order);
//订单明细数据
List<OrderDetail> orderDetailList = new ArrayList<>();
shoppingCartList.forEach(cart -> {
OrderDetail orderDetail = new OrderDetail();
BeanUtils.copyProperties(cart, orderDetail);
orderDetail.setOrderId(order.getId());
orderDetailList.add(orderDetail);
});
//向订单明细表插入n条数据
orderDetailMapper.insertBatch(orderDetailList);
//下单完, 需要清理购物车中的数据
shoppingCartMapper.deleteByUserId(userId);
//封装返回结果
OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
.id(order.getId())
.orderNumber(order.getNumber())
.orderAmount(order.getAmount())
.orderTime(order.getOrderTime())
.build();
return orderSubmitVO;
}
}
创建 OrderMapper 接口和对应的 xml 映射文件:
import com.sky.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
/**
* 插入订单数据
*
* @param order
*/
void insert(Orders order);
}
<?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.OrderMapper">
<!-- 插入订单数据-->
<insert id="insert" 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, user_name, consignee, cancel_reason, rejection_reason, cancel_time,
estimated_delivery_time, delivery_status, delivery_time, pack_amount, tableware_number,
tableware_status)
values (#{number}, #{status}, #{userId}, #{addressBookId}, #{orderTime}, #{checkoutTime}, #{payMethod},
#{payStatus}, #{amount},
#{remark}, #{phone}, #{address}, #{userName}, #{consignee}, #{cancelReason}, #{rejectionReason},
#{cancelTime},
#{estimatedDeliveryTime}, #{deliveryStatus}, #{deliveryTime}, #{packAmount}, #{tablewareNumber},
#{tablewareStatus})
</insert>
</mapper>
创建 OrderDetailMapper 接口和对应的 xml 映射文件:
import com.sky.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface OrderDetailMapper {
/**
* 批量插入订单明细数据
*
* @param orderDetailList
*/
void insertBatch(List<OrderDetail> orderDetailList);
}
<?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.OrderDetailMapper">
<!-- 批量插入订单明细数据-->
<insert id="insertBatch">
insert into order_detail (name, image, order_id, dish_id, setmeal_id, dish_flavor, number, amount)
values
<foreach collection="orderDetailList" item="orderDetail" separator=",">
(#{orderDetail.name}, #{orderDetail.image}, #{orderDetail.orderId}, #{orderDetail.dishId},
#{orderDetail.setmealId}, #{orderDetail.dishFlavor}, #{orderDetail.number}, #{orderDetail.amount})
</foreach>
</insert>
</mapper>
功能测试
可以通过如下方式进行测试:
- 查看控制台 sql
- 前后端联调
- 查看数据库中数据变化
随便添加商品,点击购物车去结算:
点击去支付,可以跳转到支付界面:
订单支付
微信支付介绍
微信支付产品
参考:https://pay.weixin.qq.com/static/product/product_index.shtml
微信支付接入流程
微信小程序支付时序图
-
JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单
-
微信小程序调起支付:通过 JSAPI 下单接口获取到发起支付的必要参数
prepay_id
,然后使用微信支付提供的小程序方法调起小程序支付
微信支付准备工作
获取微信支付平台证书、商户私钥文件:
这两个文件需要根据微信支付介入流程去申请微信支付商户号才能获取,个人开发建议直接放弃。
至于微信后台是如何调用到我们的商户系统,将支付结果推送给我们后端?
我们需要获取一个临时公网域名,这样微信后台就能访问到。
获取临时域名:支付成功后微信服务通过该域名回调我们的程序
这里我们需要使用到一个工具 cpolar,官网 https://www.cpolar.com/。
注册一个账号,来到这个页面:
下载后,解压安装即可。
复制你账号的 authtoken:
在 cpolar 的目录下进入命令行窗口,执行如下命令:
启动你的后端项目,在命令行窗口中执行下列命令,即可获取一个临时域名:
在命令行窗口右键选择标记,再选中生成的临时域名,右键即可复制,然后我们来到浏览器在域名后面加上 doc.html 一样可以访问我们的接口文档:
代码导入
微信支付相关配置:
导入微信支付功能代码:
PayNotifyController 这个文件需要在 controller 下新建一个包 notify 来存放,但其实不导入也没什么问题,因为我们实现不了完整的微信支付功能,这个类自然也没什么用。
UserMapper 少了一个 getById 的方法,自己写一下就行,很简单。
由于个人小程序无法实现微信支付功能,所以我们只能跳过微信支付环节,具体步骤如下:
首先来到小程序代码部分,将这一部分代码注释掉:
ps:右键选择格式化文档可以格式化代码。
然后,替换成下列代码:
//跳过微信支付环节,直接提示支付成功,用户点击后即可跳转到下单成功界面
wx.showModal({
title: '提示',
content: '支付成功',
success: function () {
uni.redirectTo({
url: '/pages/success/index?orderId=' + _this.orderId
});
}
})
将 OrderServiceImpl 的 payment 方法替换成下面这个方法:
/**
* 订单支付
*
* @param ordersPaymentDTO
* @return
*/
public OrderPaymentVO payment(OrdersPaymentDTO ordersPaymentDTO) throws Exception {
//跳过微信支付, 直接进行支付成功后的操作
paySuccess(ordersPaymentDTO.getOrderNumber());
return null;
}
功能测试
启动服务,来到小程序下单,进入支付界面:
会直接提示支付成功:
点击确定,会跳转到下单成功的界面: