SpecialWeekSpecialWeek

苍穹外卖功能优化

本文导航按标题快速定位

缓存菜品

用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大。就会导致系统响应慢,用户体验差。

实现思路

通过Redis来缓存菜品数据,减少数据库查询操作。
alt text

缓存逻辑分析:

1.每个分类下的菜品保存一份缓存数据

2.数据库中菜品数据有变更时清理缓存数
alt text
key为分类的id,值为菜品信息。

代码实现

修改DishController中的根据分类id查询菜品的list方法。添加查询redis缓存的功能。

设计代码展开
@Autowired
   private RedisTemplate redisTemplate;
/**
    * 根据分类id查询菜品
    *
    * @param categoryId
    * @return
    */
   @GetMapping("/list")
   @ApiOperation("根据分类id查询菜品")
   public Result<List<DishVO>> list(Long categoryId) {

       //构造redis中的key,规则:dish_分类id
       String key = "dish_" + categoryId;

       //查询redis中是否存在菜品数据
       List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key);
       if(list != null && list.size() > 0){
           //如果存在,直接返回,无须查询数据库
           return Result.success(list);
       }
	////////////////////////////////////////////////////////
       Dish dish = new Dish();
       dish.setCategoryId(categoryId);
       dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品

       //如果不存在,查询数据库,将查询到的数据放入redis中
       list = dishService.listWithFlavor(dish);
       ////////////////////////////////////////////////////////
       //这里我们放进去的就是list类型虽然放进去的时候序列化成了String,但是我们在取用的时候还得用list所属类型来接收。
       redisTemplate.opsForValue().set(key, list);

       return Result.success(list);
   }

我们放进去的是list类型,虽然放进去的时候序列化成了String,但是我们在取用的时候还得用list所属类型来接收。

问题:不清理缓存就会导致数据库和页面内容不同步

在数据库进行增删改等操作时,数据库中数据会发生变化,但是Redis中缓存的数据未发生变化从而导致数据库和Redis中的缓存的数据不一致,故而在对数据库进行操作时,需要加入清理缓存逻辑来清理Redis中对应的未同步缓存数据

需要修改的方法:
  • 新增菜品
  • 修改菜品
  • 批量删除菜品
  • 起售,停售菜品

在DishController中添加删除缓存的方法

在这部分的代码中可能涉及到多张表的缓存删除操作,我们统一用删除所有缓存的方法,后续会优化。

@Autowired
   private RedisTemplate redisTemplate;
/**
    * 清理缓存数据
    * @param pattern
    */
   private void cleanCache(String pattern){
       Set keys = redisTemplate.keys(pattern);
       redisTemplate.delete(keys);
   }

(1)新增菜品

@PostMapping
@ApiOperation("新增菜品")
public Result save(@RequestBody DishDTO dishDTO) {
    log.info("新增菜品:{}", dishDTO);
    dishService.saveWithFlavor(dishDTO);
    //清理缓存数据
    String key = "dish_" + dishDTO.getCategoryId();
    cleanCache(key);
    return Result.success();
}

(2)批量删除菜品

@DeleteMapping
@ApiOperation("菜品批量删除")
public Result delete(@RequestParam List<Long> ids) {
    log.info("菜品批量删除:{}", ids);
    dishService.deleteBatch(ids);
    //将所有的菜品缓存数据清理掉,所有以dish_开头的key
    cleanCache("dish_*");
    return Result.success();
}

(3)修改菜品优化

@ApiOperation("修改菜品")
public Result update(@RequestBody DishDTO dishDTO) {
    log.info("修改菜品:{}", dishDTO);
    dishService.updateWithFlavor(dishDTO);
    //将所有的菜品缓存数据清理掉,所有以dish_开头的key
    cleanCache("dish_*");
    return Result.success();
}

(4)菜品起售停售

@PostMapping("/status/{status}")
@ApiOperation("菜品起售停售")
public Result<String> startOrStop(@PathVariable Integer status, Long id) {
    dishService.startOrStop(status, id);
    //将所有的菜品缓存数据清理掉,所有以dish_开头的key
    cleanCache("dish_*");
    return Result.success();
}

SpringCache

Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,操作类似于通过注解来实现事务管理

Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,例如:
  • EHCache
  • Caffeine
  • Redis
我们这里的缓存实现是Redis。

使用springCache需导入坐标

<dependency>
   <groupId>org. springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
   <version>2.7.3</version>
</dependency>

常用注解
@EnableCaching 开启缓存注解功能,通常加在启动类上

@Cacheable
在方法执行前先查询缓存中是否有数据,如果有数据,则直接返回缓存数据;如果没有缓存数据,调用方法并将方法返回值放到缓存中,通常加在方法上

@CachePut 将方法的返回值放到缓存中

@CacheEvict 将一条或多条数据从缓存中删除

使用Cache

  1. 导入cache mavean坐标
  2. 需保证项目中具有缓存实现的mavean坐标,如:Redis的mavean坐标
  3. 引导类上加@EnableCaching:(开启缓存注解功能)
(1)@CachePut注解

当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,用法如下:

/**
* CachePut:将方法返回值放入缓存
* value:缓存的名称,每个缓存名称下面可以有多个key
* key:缓存的key
*/
@PostMapping
   @CachePut(value = "userCache", key = "#user.id")//key的生成:userCache::1
   public User save(@RequestBody User user){
       userMapper.insert(user);
       return user;
   }

需要注意的是注解中key的值,他的命名需要和方法中的形参的名字一样,以此来动态的获取id数据,如上方示例user.id是取形参user的id属性的值。我们也可以通过获取方法返回值的属性值;如此方法在注解的key中赋值”#result.id”就可以获取方法返回值user的id属性。最终他会通过代理对象来计算key为 userCache::1。

其他的赋值方式

#user.id 使用user的id属性作为key

#result.id 以返回对象的id属性作为key

#p0.id 使用第一个参数p0的id属性作为key

#a0.id 使用第一个参数的id属性作为key

#root.args[0].id 使用第一个参数的id属性作为key

(2)@Cacheable注解

/**
* Cacheable:在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,	  *调用方法并将方法返回值放到缓存中
* value:缓存的名称,每个缓存名称下面可以有多个key
* key:缓存的key
*/
@GetMapping
   @Cacheable(cacheNames = "userCache",key="#id")
   public User getById(Long id){
       User user = userMapper.getById(id);
       return user;
   }

(3)@CacheEvict注解

@DeleteMapping
   @CacheEvict(cacheNames = "userCache",key = "#id")//删除某个key对应的缓存数据
   public void deleteById(Long id){
       userMapper.deleteById(id);
   }

@DeleteMapping("/delAll")
   @CacheEvict(cacheNames = "userCache",allEntries = true)//删除userCache下所有的缓存数据
   public void deleteAll(){
       userMapper.deleteAll();
   }

删除userCache下所有的缓存数据:cacheNames = “…” ,allEntries = true

缓存套餐

实现步骤

(1). 导入Spring Cache和Redis相关maven坐标

(2). 在启动类上加入@EnableCaching注解,开启缓存注解功能

(3). 在用户端接口SetmealController的 list 方法上加入@Cacheable注解

(4). 在管理端接口SetmealController的增删改类等方法上加入@CacheEvict注解

代码实现

1.用户端的SetmealController的list方法

@GetMapping("/list")
@ApiOperation("根据分类id查询套餐")
@Cacheable(cacheNames = "setmealCache",key = "#categoryId") //key: setmealCache::100
public Result<List<Setmeal>> list(Long categoryId) {
    Setmeal setmeal = new Setmeal();
    setmeal.setCategoryId(categoryId);
    setmeal.setStatus(StatusConstant.ENABLE);
 
    List<Setmeal> list = setmealService.list(setmeal);
    return Result.success(list);
}

2.在管理端接口SetmealController的 save、delete、update、startOrStop等方法上加入CacheEvict注解

设计代码展开
/**
    * 新增套餐
    *
    * @param setmealDTO
    * @return
    */
   @PostMapping
   @ApiOperation("新增套餐")
   @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")//key: setmealCache::100
   public Result save(@RequestBody SetmealDTO setmealDTO) {
       setmealService.saveWithDish(setmealDTO);
       return Result.success();
   }
/**
    * 批量删除套餐
    *
    * @param ids
    * @return
    */
   @DeleteMapping
   @ApiOperation("批量删除套餐")
   @CacheEvict(cacheNames = "setmealCache",allEntries = true)
   public Result delete(@RequestParam List<Long> ids) {
       setmealService.deleteBatch(ids);
       return Result.success();
   }
/**
    * 修改套餐
    *
    * @param setmealDTO
    * @return
    */
   @PutMapping
   @ApiOperation("修改套餐")
   @CacheEvict(cacheNames = "setmealCache",allEntries = true)
   public Result update(@RequestBody SetmealDTO setmealDTO) {
       setmealService.update(setmealDTO);
       return Result.success();
   }

   /**
    * 套餐起售停售
    *
    * @param status
    * @param id
    * @return
    */
   @PostMapping("/status/{status}")
   @ApiOperation("套餐起售停售")
   @CacheEvict(cacheNames = "setmealCache",allEntries = true)
   public Result startOrStop(@PathVariable Integer status, Long id) {
       setmealService.startOrStop(status, id);
       return Result.success();
   }

添加购物车

需求分析和设计

产品原型:

用户可以将菜品或者套餐添加到购物车。对于菜品来说,如果设置了口味信息,则需要选择规格后才能加入购物车;对于套餐来说,可以直接点击+将当前套餐加入购物车。在购物车中可以修改菜品和套餐的数量,也可以清空购物车。

接口设计
alt text
数据库表shopping_cart设计(购物车中的数据也需要存储在表中)。
alt text

说明:

  • 购物车数据是关联用户的,在表结构中,我们需要记录,每一个用户的购物车数据是哪些
  • 菜品列表展示出来的既有套餐,又有菜品,如果用户选择的是套餐,就保存套餐ID(setmeal_id),如果用户选择的是菜品,就保存菜品ID(dish_id)
  • 对同一个菜品/套餐,如果选择多份不需要添加多条记录,增加数量number即可

代码设计

1.ShoppingCartController

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "C端-购物车接口")
public class ShoppingCartController {
 
    @Autowired
    private ShoppingCartService shoppingCartService;

    @PostMapping("/add")
    @ApiOperation("添加购物车")
    public Result<String> add(@RequestBody ShoppingCartDTO shoppingCartDTO){
        log.info("添加购物车:{}", shoppingCartDTO);
        shoppingCartService.addShoppingCart(shoppingCartDTO);//后绪步骤实现
        return Result.success();
    }

2.ShoppinCartDTO

@Data
public class ShoppingCartDTO implements Serializable {

    private Long dishId;
    private Long setmealId;
    private String dishFlavor;

}

3.ShoppingCartService接口

public interface ShoppingCartService {

    void addShoppingCart(ShoppingCart shoppingCart);
}

4.ShoppingCartServiceImpl

当我们添加购物车的时候需要判断,添加的商品是否已经在购物车里了,如果已经存在就只需要执行修改操作将该菜品的数量加一,反之执行插入语句插入菜品。需要注意的是口味的不同的相同菜品也是不同的通过user_id来判断是谁的购物车。因为购物车可能存的是dish或者是setmeal,所以我们还需要判断一下添加的类型,可以判断传入的数据中的dishId是否为空。

需要执行的SQL语句

select * from shopping_cart where user_id = ? and setmeal_id = ?

select * from shopping_cart where user_id = ? and dish_id = ? and dish_flavor

设计代码展开
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        ShoppingCart shoppingCart = new ShoppingCart();
        //拷贝dishId,setmealId,dishFlavor
        BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
        //获取userid并封装
        shoppingCart.setUserId(BaseContext.getCurrentId());
        //判断当前商品是否在购物车中
        List<ShoppingCart> shoppingCartList = shoppingCartMapper.list(shoppingCart);

        if (shoppingCartList != null && shoppingCartList.size() == 1) {
            //如果已经存在,就更新数量,数量加1
            //get(0)是取出列表中的第一条数据,虽然我们用列表来接收,但我们只会在数据库中搜索到一个数据
            shoppingCart = shoppingCartList.get(0);
            shoppingCart.setNumber(shoppingCart.getNumber() + 1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        } else {
            //如果不存在,插入数据,数量就是1
            //判断当前添加到购物车的是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();
            if (dishId != null) {
                //添加到购物车的是菜品
                Dish dish = dishMapper.getById(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
            } else {
                //添加到购物车的是套餐
                Setmeal setmeal = setmealMapper.getById(shoppingCartDTO.getSetmealId());
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
            }
            //无论是dish还是setmeal都需要这个操作。
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartMapper.insert(shoppingCart);
        }
    }
}
若显示userid为null可能是user拦截器,WebMVCConfiguration拦截器或者是application.yml令牌配置类注入信息填写错误。

5.ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper {

    List<ShoppingCart> list(ShoppingCart shoppingCart);
 
    @Update("update shopping_cart set number = #{number} where id = #{id}")
    void updateNumberById(ShoppingCart shoppingCart);
 
    @Insert("insert into shopping_cart (name, user_id, dish_id, setmeal_id, dish_flavor, number, amount, image, create_time) " +
            " values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
    void insert(ShoppingCart shoppingCart);
 
}

6.ShoppingCartMapper.xml

<?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.ShoppingCartMapper">
    <select id="list" parameterType="ShoppingCart" resultType="ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="dishId != null">
                and dish_id = #{dishId}
            </if>
            <if test="setmealId != null">
                and setmeal_id = #{setmealId}
            </if>
            <if test="dishFlavor != null">
                and dish_flavor = #{dishFlavor}
            </if>
        </where>
        order by create_time desc
    </select>
</mapper>

查看购物车

需求设计

(1)接口设计
alt text

(2)代码开发

1.ShoppingCartController

/**
    * 查看购物车
    * @return
    */
   @GetMapping("/list")
   @ApiOperation("查看购物车")
   public Result<List<ShoppingCart>> list(){
       return Result.success(shoppingCartService.showShoppingCart());
   }

2.service

/**
    * 查看购物车根据用户id查就行。
    * @return
    */
   public List<ShoppingCart> showShoppingCart() {
       return shoppingCartMapper.list(ShoppingCart.
                                      builder().
                                      userId(BaseContext.getCurrentId()).
                                      build());
   }

清空购物车

需求设计

alt text

代码开发

1.ShoppingCartController

/**
    * 清空购物车商品
    * @return
    */
   @DeleteMapping("/clean")
   @ApiOperation("清空购物车商品")
   public Result<String> clean(){
       shoppingCartService.cleanShoppingCart();
       return Result.success();
   }

2.service

/**
    * 清空购物车商品
    */
   public void cleanShoppingCart() {
       shoppingCartMapper.deleteByUserId(BaseContext.getCurrentId());
   }

(3)Mapper

/**
    * 根据用户id删除购物车数据
    *
    * @param userId
    */
   @Delete("delete from shopping_cart where user_id = #{userId}")
   void deleteByUserId(Long userId);

伤处购物车中的一个商品

需求分析

alt text

代码开发

(1) ShoppingCartController

/**
     * 删除购物车中一个商品
     * @param shoppingCartDTO
     * @return
*/
@PostMapping("/sub")
@ApiOperation("删除购物车中一个商品")
public Result sub(@RequestBody ShoppingCartDTO shoppingCartDTO){
    log.info("删除购物车中一个商品,商品:{}", shoppingCartDTO);
    shoppingCartService.subShoppingCart(shoppingCartDTO);
    return Result.success();
}

(2) ShoppingCartServiceImpl

public void subShoppingCart(ShoppingCartDTO shoppingCartDTO) {
    ShoppingCart shoppingCart = new ShoppingCart();
    BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
    //设置查询条件,查询当前登录用户的购物车数据
    shoppingCart.setUserId(BaseContext.getCurrentId());
 
    List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
 
    if(list != null && list.size() > 0){
        shoppingCart = list.get(0);
 
        Integer number = shoppingCart.getNumber();
        if(number == 1){
            //当前商品在购物车中的份数为1,直接根据查询返回的主键删除当前记录
            shoppingCartMapper.deleteById(shoppingCart.getId());
        }else {
            //当前商品在购物车中的份数不为1,修改份数即可
            shoppingCart.setNumber(shoppingCart.getNumber() - 1);
            shoppingCartMapper.updateNumberById(shoppingCart);
        }
    }
}

(3) ShoppingCartMapper

@Delete("delete from shopping_cart where id = #{id}")
void deleteById(Long id);

用户下单与微信支付

导入地址簿功能代码

不是重点内容,简单的对单表增删改查操作。就是那个收货地址,有新增、修改、删除地址,添加标签等功能。
在用户下单的时候就会用到查询默认地址的接口。修改地址的时候也需要使用查询来回显数据。

用户下单

需求分析和设计

总金额(商品价格、打包盒、配送费)、商品、商品数量根据购物车来计算。

用户的名字、收货地址、手机号都可以在地址簿中获得。
alt text

接口设计
用户将菜品或者套餐加入购物车后,可以点击购物车中的 “去结算” 按钮,页面跳转到订单确认页面,点击 “去支付” 按钮则完成下单操作。
alt text
alt text

表设计

  1. 订单表(orders)
  2. 订单明细表(order_detail)

orders表和order_detail表时一对多的关系,一张orders表对应多张订单明细表。这里再说一下逻辑外键只是为了我们方便查询相关的表,在数据库中并没有设置成外键。
alt text

代码开发

(1)根据接口文档设计DTO和VO

(2)创建OrderController

在user端中创建,我们需要为这个bean起一个别名,因为我们在管理端,也要创建order接口。

@RestController("userOrderController")
@RequestMapping("/user/order")
@Slf4j
@Api(tags = "C端-订单接口")
public class OrderController {
 
    @Autowired
    private OrderService orderService;

    @PostMapping("/submit")
    @ApiOperation("用户下单")
    public Result<OrderSubmitVO> submit(@RequestBody OrdersSubmitDTO ordersSubmitDTO) {
        log.info("用户下单:{}", ordersSubmitDTO);
        OrderSubmitVO orderSubmitVO = orderService.submitOrder(ordersSubmitDTO);
        return Result.success(orderSubmitVO);
    } 
 
}

(3)OrderServiceImpl

设计代码展开
@Autowired
    private OrderMapper orderMapper;
    @Autowired
    private OrderDetailMapper orderDetailMapper;
    @Autowired
    private AddressBookMapper addressBookMapper;
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    //用户下单
    @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);
        //传入ShoppingCart对象,根据userId来查。
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);

        if(list.size()==0 || list==null){
            throw new ShoppingCartBusinessException(MessageConstant.SHOPPING_CART_IS_NULL);
        }
        //涉及到了两种表:一张订单表和多张明细表。
        Orders orders = new Orders();
        BeanUtils.copyProperties(ordersSubmitDTO,orders);
        //--补充Orders属性参数--
        orders.setOrderTime(LocalDateTime.now());
        orders.setPayStatus(Orders.UN_PAID);
        orders.setStatus(Orders.PENDING_PAYMENT);
        //System.currentTimeMillis()这是系统的时间戳,因为是Long类型的,但我们需要String所以强转。
        orders.setNumber(String.valueOf(System.currentTimeMillis()));
        orders.setPhone(addressBook.getPhone());
        orders.setConsignee(addressBook.getConsignee());
        orders.setUserId(userId);
        orders.setAddress(addressBook.getDetail());

        //--插入订单数据--
        orderMapper.insert(orders);//需要返回主键值

        //--向明细表插入n条数据--
        //获取明细数据
        List<OrderDetail> orderDetailList = new ArrayList<>();
        for(ShoppingCart cart:list){
            OrderDetail orderDetail = new OrderDetail();
            BeanUtils.copyProperties(cart,orderDetail);
            orderDetail.setOrderId(orders.getId());
            orderDetailList.add(orderDetail);
        }

        //像明细表中插入明细
        orderDetailMapper.insertBatch(orderDetailList);

        //--清空当前用户的购物车数据--
        shoppingCartMapper.deleteByUserId(userId);

        //--分装VO返回结果--
        OrderSubmitVO orderSubmitVO = OrderSubmitVO.builder()
                .id(orders.getId())
                .orderTime(orders.getOrderTime())
                .orderNumber(orders.getNumber())
                .orderAmount(orders.getAmount())
                .build();
        return orderSubmitVO;
    }
这里的Number是订单号。Consignee是收货人。在黑马的课程里少了orders.setAddress(addressBook.getDetail());这一句记得加上。

(4)OrderMapper

@Mapper
public interface OrderMapper {
    void insert(Orders orders);
}

(5)OrderMapper.xml

<?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" 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>

(6)OrderDetailMapper

@Mapper
public interface OrderDetailMapper {
    void insertBatch(List<OrderDetail> orderDetailList);
}

(7)OrderDetailMapper.xml

<?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" parameterType="list">
        insert into order_detail
        (name, order_id, dish_id, setmeal_id, dish_flavor, number, amount, image)
        values
        <foreach collection="orderDetailList" item="od" separator=",">
            (#{od.name},#{od.orderId},#{od.dishId},#{od.setmealId},#{od.dishFlavor},
            #{od.number},#{od.amount},#{od.image})
        </foreach>
    </insert>
</mapper>

订单支付

要实现微信支付就需要注册微信支付的一个商户号,这个商户号是必须要有一家企业并且有正规的营业执照。只有具备了这些资质之后,才可以去注册商户号,才能开通支付权限

目前个人学习不能实现微信支付并且这部分的代码比较固定可以复用,所以这一节我们只需要知道微信支付的功能。方便与企业对接。
alt text

支付功能实现

alt text
商户系统就是我们的后端系统。

微信支付相关接口

JSAPI下单:商户系统调用该接口在微信支付服务后台生成预支付交易单(对应时序图的第5步),并没有完成支付,相当于向微信备案。
alt text
微信小程序调起支付:通过JSAPI下单接口获取到发起支付的必要参数prepay_id,然后使用微信支付提供的小程序方法调起小程序支付(对应时序图的第10步)
alt text

用户确认支付小程序直接与微信后台交互,所以在时序图的第十四步要告诉后台更新订单状态。

第十三步推送支付结果:是微信后台访问商户系统但是我们是使用的本地地址微信后台访问不到。我们需要一个公网地址一边访问。有一个方法内网穿透,就是获得一个临时的IP地址。

跳过微信支付

由于微信支付需要商家认证才能开通,个人开发者只能模拟支付的流程,完成后续项目的学习

(1)在小程序开发者工具中修改

修改前:

alt text

修改后:

alt text

(2)导入黑马提供资料的微信支付部分代码。(导入红框中的代码)
alt text

(3)修改OrderServiceImpl中payment方法
alt text

(4)在OrderServiceImpl中paySuccess方法里检查是否实现这两个方法,有则完成;无则回看视频Day08第17集中此部分方法实现
alt text

(5)调试显示支付成功(注:由于前端支付成功提示信息被注释,小程序则不显示支付成功的提示信息)

课程:黑马。感谢CSDN博主Purse Wind

转:苍穹外卖功能优化2