瑞吉外賣-第六天


課程內容

  • 用戶地址簿功能
  • 菜品展示
  • 購物車
  • 下單

1. 用戶地址簿功能

1.1 需求分析

地址簿,指的是移動端消費者用戶的地址信息,用戶登錄成功后可以維護自己的地址信息。同一個用戶可以有多個地址信息,但是只能有一個默認地址

image-20210812191332892 image-20210812191822693

對於地址簿管理,我們需要實現以下幾個功能:

  • 新增地址
  • 地址列表查詢
  • 設置默認地址
  • 編輯地址
  • 刪除地址

1.2 數據模型

用戶的地址信息會存儲在address_book表,即地址簿表中。具體表結構如下:

image-20210812192228678

這里面有一個字段is_default,實際上我們在設置默認地址時,只需要更新這個字段就可以了。

1.3 導入功能代碼

對於這一類的單表的增刪改查,我們已經寫過很多了,基本的開發思路都是一樣的,那么本小節的用戶地址簿管理的增刪改查功能,我們就不再一一實現了,基本的代碼我們都已經提供了,直接導入進來,做一個測試即可。

對於下面的地址管理的代碼,我們可以直接從資料拷貝,也可以直接從下面的講義中復制。

1). 實體類 AddressBook(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.entity

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * 地址簿
 */
@Data
public class AddressBook implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
	
    //用戶id
    private Long userId;
	
    //收貨人
    private String consignee;
	
    //手機號
    private String phone;
	
    //性別 0 女 1 男
    private String sex;
	
    //省級區划編號
    private String provinceCode;
	
    //省級名稱
    private String provinceName;
	
    //市級區划編號
    private String cityCode;
	
    //市級名稱
    private String cityName;
	
    //區級區划編號
    private String districtCode;
	
    //區級名稱
    private String districtName;
	
    //詳細地址
    private String detail;
	
    //標簽
    private String label;
    
    //是否默認 0 否 1是
    private Integer isDefault;
    
    //創建時間
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    //更新時間
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    //創建人
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    //修改人
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

    //是否刪除
    private Integer isDeleted;
}

2). Mapper接口 AddressBookMapper(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.AddressBook;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;

@Mapper
public interface AddressBookMapper extends BaseMapper<AddressBook> {
}

3). 業務層接口 AddressBookService(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.AddressBook;

public interface AddressBookService extends IService<AddressBook> {
}

4). 業務層實現類 AddressBookServiceImpl(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.mapper.AddressBookMapper;
import com.itheima.reggie.service.AddressBookService;
import org.springframework.stereotype.Service;

@Service
public class AddressBookServiceImpl extends ServiceImpl<AddressBookMapper, AddressBook> implements AddressBookService {
}

5). 控制層 AddressBookController(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.controller

controller主要開發的功能:

A. 新增地址邏輯說明:

  • 需要記錄當前是哪個用戶的地址(關聯當前登錄用戶)

B. 設置默認地址

  • 每個用戶可以有很多地址,但是默認地址只能有一個 ;

  • 先將該用戶所有地址的is_default更新為0 , 然后將當前的設置的默認地址的is_default設置為1

C. 根據ID查詢地址

D. 查詢默認地址

  • 根據當前登錄用戶ID 以及 is_default進行查詢,查詢當前登錄用戶is_default為1的地址信息

E. 查詢指定用戶的全部地址

  • 根據當前登錄用戶ID,查詢所有的地址列表

代碼實現如下:

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import com.itheima.reggie.entity.AddressBook;
import com.itheima.reggie.service.AddressBookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

/**
 * 地址簿管理
 */
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
    @Autowired
    private AddressBookService addressBookService;

    /**
     * 新增
     */
    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);
        addressBookService.save(addressBook);
        return R.success(addressBook);
    }

    /**
     * 設置默認地址
     */
    @PutMapping("default")
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
        log.info("addressBook:{}", addressBook);
        LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        wrapper.set(AddressBook::getIsDefault, 0);
        //SQL:update address_book set is_default = 0 where user_id = ?
        addressBookService.update(wrapper);

        addressBook.setIsDefault(1);
        //SQL:update address_book set is_default = 1 where id = ?
        addressBookService.updateById(addressBook);
        return R.success(addressBook);
    }

    /**
     * 根據id查詢地址
     */
    @GetMapping("/{id}")
    public R get(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        if (addressBook != null) {
            return R.success(addressBook);
        } else {
            return R.error("沒有找到該對象");
        }
    }

    /**
     * 查詢默認地址
     */
    @GetMapping("default")
    public R<AddressBook> getDefault() {
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        queryWrapper.eq(AddressBook::getIsDefault, 1);

        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = addressBookService.getOne(queryWrapper);

        if (null == addressBook) {
            return R.error("沒有找到該對象");
        } else {
            return R.success(addressBook);
        }
    }

    /**
     * 查詢指定用戶的全部地址
     */
    @GetMapping("/list")
    public R<List<AddressBook>> list(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);

        //條件構造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);

        //SQL:select * from address_book where user_id = ? order by update_time desc
        return R.success(addressBookService.list(queryWrapper));
    }
}

1.4 功能測試

代碼導入進來,並且去閱讀了一下地址管理各個功能的邏輯實現,接下來,我們就可以啟動項目,進行一個測試。測試過程中,通過debug斷點調試觀察服務端程序的執行過程,在瀏覽器中使用調試工具查看頁面和服務端的交互過程和請求響應數據。

1). 新增

填寫表單數據,點擊保存地址,查看網絡請求。

image-20210812201636567

測試完畢之后,檢查數據庫中的數據,是否正常插入。

image-20210812201845336

2). 列表查詢

當新增地址完成后,頁面會再次發送一個請求,來查詢該用戶的所有地址列表,在界面進行展示。

image-20210812202430677

image-20210812202534169

3). 設置默認

在地址列表頁面中,勾選 "設為默認地址" ,此時會發送PUT請求,來設置默認地址。

image-20210812202841250

測試完畢后,我們再次查看數據庫表中的數據:

image-20210812203123499

2. 菜品展示

2.1 需求分析

用戶登錄成功后跳轉到系統首頁,在首頁需要根據分類來展示菜品和套餐。如果菜品設置了口味信息,需要展示image-20210812205330291按鈕,否則顯示image-20210812205346846按鈕。

image-20210812210328249

2.2 前端頁面分析

在開發代碼之前,需要梳理一下前端頁面和服務端的交互過程:

1). 頁面(front/index.html)發送ajax請求,獲取分類數據(菜品分類和套餐分類)

image-20210812215624794

該功能在之前的業務開發中,我們都已經實現了。通過請求響應的數據,我們也可以看到數據是可以正確獲取到的。

image-20210812221107947

注意:首頁加載時,不僅發送請求獲取分類列表,還發送了一次ajax請求用於加載購物車數據,而這兩次請求必須全部成功,頁面才可以正常渲染,而當前購物車列表查詢功能還未實現(報404),所以列表目前並未渲染。此處可以將這次請求的地址暫時修改一下,從靜態json文件獲取數據,等后續開發購物車功能時再修改回來,如下:

image-20210812221835628

修改之后,我們再次測試:

image-20210812222713700

目前該部分的功能我們已經調通,左側的分類菜單,和右側的菜品信息我們都可以看到,后續我們只需要將購物車列表的數據改成調用服務端接口查詢即可。

2). 頁面發送ajax請求,獲取第一個分類下的菜品或者套餐

image-20210812224353891

A. 根據分類ID查詢套餐列表:

image-20210812224810551

B. 根據分類ID查詢菜品列表:

image-20210812224624459

異步請求,查詢分類對應的菜品列表,功能我們已經實現了,但是我們之前查詢的只是菜品的基本信息,不包含菜品的口味信息。所以在前端界面中,我們看不到選擇菜品分類的信息。

image-20210812231220115

經過上述的分析,我們可以看到,服務端我們主要提供兩個方法, 分別用來:

A. 根據分類ID查詢菜品列表(包含菜品口味列表), 具體請求信息如下:

請求 說明
請求方式 GET
請求路徑 /dish/list
請求參數 ?categoryId=1397844263642378242&status=1

該功能在服務端已經實現,我們需要修改此方法,在原有方法的基礎上增加查詢菜品的口味信息。

B. 根據分類ID查詢套餐列表, 具體請求信息如下:

請求 說明
請求方式 GET
請求路徑 /setmeal/list
請求參數 ?categoryId=1397844263642378242&status=1

該功能在服務端並未實現。

2.3 代碼開發

2.3.1 查詢菜品方法修改

由於之前我們實現的根據分類查詢菜品列表,僅僅查詢了菜品的基本信息,未查詢菜品口味信息,而移動端用戶在點餐時,是需要選擇口味信息的,所以我們需要對之前的代碼實現進行完善,那么如何完善呢?

我們需要修改DishController的list方法,原來此方法的返回值類型為:R<List >。為了滿足移動端對數據的要求(菜品基本信息和菜品對應的口味信息),現在需要將方法的返回值類型改為:R<List > ,因為在DishDto中封裝了菜品對應的口味信息:

image-20210812231825043

代碼邏輯:

A. 根據分類ID查詢,查詢目前正在啟售的菜品列表 (已實現)

B. 遍歷菜品列表,並查詢菜品的分類信息及菜品的口味列表

C. 組裝數據DishDto,並返回

代碼實現:

@GetMapping("/list")
    public R<List<DishDto>> list(Dish dish){
        //構造查詢條件
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(dish.getCategoryId() != null ,Dish::getCategoryId,dish.getCategoryId());
        //添加條件,查詢狀態為1(起售狀態)的菜品
        queryWrapper.eq(Dish::getStatus,1);
        //添加排序條件
        queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime);
		
        List<Dish> list = dishService.list(queryWrapper);
	
        List<DishDto> dishDtoList = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item,dishDto);

            Long categoryId = item.getCategoryId();//分類id
            //根據id查詢分類對象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }

            //當前菜品的id
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
            //SQL:select * from dish_flavor where dish_id = ?
            List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
            dishDto.setFlavors(dishFlavorList);
            
            return dishDto;
        }).collect(Collectors.toList());

        return R.success(dishDtoList);
    }

2.3.2 根據分類ID查詢套餐

在SetmealController中創建list方法,根據條件查詢套餐數據。

/**
 * 根據條件查詢套餐數據
 * @param setmeal
 * @return
 */
@GetMapping("/list")
public R<List<Setmeal>> list(Setmeal setmeal){
    LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId());
    queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus());
    queryWrapper.orderByDesc(Setmeal::getUpdateTime);

    List<Setmeal> list = setmealService.list(queryWrapper);
    return R.success(list);
}

2.4 功能測試

把菜品展示的功能代碼完善完成之后,我們重新啟動服務,來測試一個菜品展示的功能。測試過程中可以使用瀏覽器的監控工具查看頁面和服務端的數據交互細節。

image-20210813175554516

點擊分類,根據分類查詢菜品列表/套餐列表:

image-20210813175835304

3. 購物車

3.1 需求分析

移動端用戶可以將菜品或者套餐添加到購物車。對於菜品來說,如果設置了口味信息,則需要選擇規格后才能加入購物車;對於套餐來說,可以直接點擊image-20210813181916235將當前套餐加入購物車。在購物車中可以修改菜品和套餐的數量,也可以清空購物車。

image-20210813182828045

這里面我們需要實現的功能包括:

1). 添加購物車

2). 查詢購物車

3). 清空購物車

3.2 數據模型

用戶的購物車數據,也是需要保存在數據庫中的,購物車對應的數據表為shopping_cart表,具體表結構如下:

image-20210813183334933

說明:

  • 購物車數據是關聯用戶的,在表結構中,我們需要記錄,每一個用戶的購物車數據是哪些
  • 菜品列表展示出來的既有套餐,又有菜品,如果APP端選擇的是套餐,就保存套餐ID(setmeal_id),如果APP端選擇的是菜品,就保存菜品ID(dish_id)
  • 對同一個菜品/套餐,如果選擇多份不需要添加多條記錄,增加數量number即可

最終shopping_cart表中存儲的數據示例:

image-20210815183440051

3.3 前端頁面分析

在開發代碼之前,需要梳理一下購物車操作時前端頁面和服務端的交互過程:

1). 點擊 "加入購物車" 或者 "+" 按鈕,頁面發送ajax請求,請求服務端,將菜品或者套餐添加到購物車

image-20210813185414102

image-20210813185731809

2). 點擊購物車圖標,頁面發送ajax請求,請求服務端查詢購物車中的菜品和套餐

此時,我們就需要將查詢購物車的代碼放開,不用再加載靜態的json數據了。

image-20210813190814766 image-20210813191237556

3). 點擊清空購物車按鈕,頁面發送ajax請求,請求服務端來執行清空購物車操作

image-20210813192012994

經過上述的分析,我們可以看到,對於購物車的功能,我們主要需要開發以下幾個功能,具體的請求信息如下:

1). 加入購物車

請求 說明
請求方式 POST
請求路徑 /shoppingCart/add
請求參數 json格式
菜品數據: 
{"amount":118,"dishFlavor":"不要蒜,微辣","dishId":"1397851099502260226","name":"全家福","image":"a53a4e6a-3b83-4044-87f9-9d49b30a8fdc.jpg"}

套餐數據: 
{"amount":38,"setmealId":"1423329486060957698","name":"營養超值工作餐","image":"9cd7a80a-da54-4f46-bf33-af3576514cec.jpg"}

2). 查詢購物車列表

請求 說明
請求方式 GET
請求路徑 /shoppingCart/list

3). 清空購物車功能

請求 說明
請求方式 DELETE
請求路徑 /shoppingCart/clean

3.4 准備工作

分析完畢購物車的業務需求和實現思路之后,在開發業務功能前,先將需要用到的類和接口基本結構創建好:

1). 實體類 ShoppingCart(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.entity

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

/**
 * 購物車
 */
@Data
public class ShoppingCart implements Serializable {
    private static final long serialVersionUID = 1L;
	
    private Long id;

    //名稱
    private String name;

    //用戶id
    private Long userId;

    //菜品id
    private Long dishId;

    //套餐id
    private Long setmealId;

    //口味
    private String dishFlavor;

    //數量
    private Integer number;

    //金額
    private BigDecimal amount;

    //圖片
    private String image;

    private LocalDateTime createTime;
}

2). Mapper接口 ShoppingCartMapper

所屬包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.ShoppingCart;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface ShoppingCartMapper extends BaseMapper<ShoppingCart> {
}

3). 業務層接口 ShoppingCartService

所屬包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.ShoppingCart;

public interface ShoppingCartService extends IService<ShoppingCart> {
}

4). 業務層實現類 ShoppingCartServiceImpl

所屬包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.ShoppingCart;
import com.itheima.reggie.mapper.ShoppingCartMapper;
import com.itheima.reggie.service.ShoppingCartService;
import org.springframework.stereotype.Service;

@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
}

5). 控制層 ShoppingCartController

所屬包: com.itheima.reggie.controller

import com.itheima.reggie.service.ShoppingCartService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 購物車
 */
@Slf4j
@RestController
@RequestMapping("/shoppingCart")
public class ShoppingCartController {
    @Autowired
    private ShoppingCartService shoppingCartService;
 
 }   

3.5 代碼開發

3.5.1 添加購物車

在ShoppingCartController中創建add方法,來完成添加購物車的邏輯實現,具體的邏輯如下:

A. 獲取當前登錄用戶,為購物車對象賦值

B. 根據當前登錄用戶ID 及 本次添加的菜品ID/套餐ID,查詢購物車數據是否存在

C. 如果已經存在,就在原來數量基礎上加1

D. 如果不存在,則添加到購物車,數量默認就是1

代碼實現如下:

/**
* 添加購物車
* @param shoppingCart
* @return
*/
@PostMapping("/add")
public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
    log.info("購物車數據:{}",shoppingCart);

    //設置用戶id,指定當前是哪個用戶的購物車數據
    Long currentId = BaseContext.getCurrentId();
    shoppingCart.setUserId(currentId);

    Long dishId = shoppingCart.getDishId();

    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,currentId);

    if(dishId != null){
        //添加到購物車的是菜品
        queryWrapper.eq(ShoppingCart::getDishId,dishId);
    }else{
        //添加到購物車的是套餐
        queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
    }

    //查詢當前菜品或者套餐是否在購物車中
    //SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
    ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);

    if(cartServiceOne != null){
        //如果已經存在,就在原來數量基礎上加一
        Integer number = cartServiceOne.getNumber();
        cartServiceOne.setNumber(number + 1);
        shoppingCartService.updateById(cartServiceOne);
    }else{
        //如果不存在,則添加到購物車,數量默認就是一
        shoppingCart.setNumber(1);
        shoppingCart.setCreateTime(LocalDateTime.now());
        shoppingCartService.save(shoppingCart);
        cartServiceOne = shoppingCart;
    }
    return R.success(cartServiceOne);
}

3.5.2 查詢購物車

在ShoppingCartController中創建list方法,根據當前登錄用戶ID查詢購物車列表,並對查詢的結果進行創建時間的倒序排序。

代碼實現如下:

/**
* 查看購物車
* @return
*/
@GetMapping("/list")
public R<List<ShoppingCart>> list(){
    log.info("查看購物車...");
	
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
    queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
	
    List<ShoppingCart> list = shoppingCartService.list(queryWrapper);

    return R.success(list);
}

3.5.3 清空購物車

在ShoppingCartController中創建clean方法,在方法中獲取當前登錄用戶,根據登錄用戶ID,刪除購物車數據。

代碼實現如下:

/**
* 清空購物車
* @return
*/
@DeleteMapping("/clean")
public R<String> clean(){
    //SQL:delete from shopping_cart where user_id = ?
    LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());

    shoppingCartService.remove(queryWrapper);
    return R.success("清空購物車成功");
}

3.6 功能測試

按照前面分析的操作流程進行測試,測試功能以及數據庫中的數據是否是否正常。

1). 添加購物車

當添加的是菜品信息,而這個用戶的購物車中當前並沒有這個菜品時,添加一條數據,數量為1。

image-20210814070930745

檢查數據庫數據,由於是菜品保存的是dish_id。

image-20210814071113382

這時在頁面上,我們可以繼續點擊+號,在購物車中增加該菜品,此時,應該是對現有的購物車菜品數量加1,而不應該插入新的記錄。

image-20210814071613706

檢查數據庫數據:

image-20210814071707767

如果添加的是套餐,該套餐在當前用戶的購物車中並不存在,則添加一條數據,數量為1。

image-20210814071742125

檢查數據庫數據:

image-20210814071850689

2). 查看購物車

點擊頁面下面的購物車邊欄,查看購物車數據列表是否正常展示。

image-20210814071956804

3). 清空購物車

在購物車列表展示頁中點擊"清空", 查看購物車是否被清空。

image-20210814072159568

並檢查數據庫中的數據,可以看到數據已經被刪除。

4. 下單

4.1 需求分析

移動端用戶將菜品或者套餐加入購物車后,可以點擊購物車中的 "去結算" 按鈕,頁面跳轉到訂單確認頁面,點擊 "去支付" 按鈕則完成下單操作。

image-20210814072533469

這里,我們需要說明一下,這里並不會去開發支付功能,因為不論是支付寶的支付,還是微信支付,都是需要企業資質的,而我們大家在測試的時候,是沒有辦法提供企業資質的,所以這一部分支付功能我們就不去實現了。

4.2 數據模型

用戶下單業務對應的數據表為orders表和order_detail表(一對多關系,一個訂單關聯多個訂單明細):

表名 含義 說明
orders 訂單表 主要存儲訂單的基本信息(如: 訂單號、狀態、金額、支付方式、下單用戶、收件地址等)
order_detail 訂單明細表 主要存儲訂單詳情信息(如: 該訂單關聯的套餐及菜品的信息)

具體的表結構如下:

A. orders 訂單表

image-20210814095559935

B. order_detail

image-20210814073544977

數據示例:

image-20210815224918077

用戶提交訂單時,需要往訂單表orders中插入一條記錄,並且需要往order_detail中插入一條或多條記錄。

4.3 前端頁面分析

在開發代碼之前,需要梳理一下用戶下單操作時前端頁面和服務端的交互過程:

1). 在購物車中點擊image-20210814073907767按鈕,頁面跳轉到訂單確認頁面

image-20210814075105094

頁面跳轉前端已經完成,我們無需操作。

2). 在訂單確認頁面,發送ajax請求,請求服務端獲取當前登錄用戶的默認地址

image-20210814075454329

該功能在用戶地址簿管理功能開發時,已經實現(導入),我們無需操作。

3). 在訂單確認頁面,發送ajax請求,請求服務端獲取當前登錄用戶的購物車數據

image-20210814075635880

該功能已經實現,我們無需操作。

4). 在訂單確認頁面點擊image-20210814075722616按鈕,發送ajax請求,請求服務端完成下單操作

image-20210814080254623

經過上述的分析,我們看到前三步的功能我們都已經實現了,我們主要需要實現最后一步的下單功能,該功能具體的請求信息如下:

請求 說明
請求方式 POST
請求路徑 /order/submit
請求參數

4.4 准備工作

在開發業務功能前,先將需要用到的類和接口基本結構創建好:

1). 實體類 Orders、OrderDetail(直接從課程資料中導入即可)

所屬包: com.itheima.reggie.entity

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

/**
 * 訂單
 */
@Data
public class Orders implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;
    //訂單號
    private String number;
    //訂單狀態 1待付款,2待派送,3已派送,4已完成,5已取消
    private Integer status;

    //下單用戶id
    private Long userId;

    //地址id
    private Long addressBookId;

    //下單時間
    private LocalDateTime orderTime;

    //結賬時間
    private LocalDateTime checkoutTime;

    //支付方式 1微信,2支付寶
    private Integer payMethod;

    //實收金額
    private BigDecimal amount;

    //備注
    private String remark;

    //用戶名
    private String userName;

    //手機號
    private String phone;

    //地址
    private String address;

    //收貨人
    private String consignee;
}
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;

/**
 * 訂單明細
 */
@Data
public class OrderDetail implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long id;

    //名稱
    private String name;

    //訂單id
    private Long orderId;

    //菜品id
    private Long dishId;

    //套餐id
    private Long setmealId;

    //口味
    private String dishFlavor;

    //數量
    private Integer number;

    //金額
    private BigDecimal amount;

    //圖片
    private String image;
}

2). Mapper接口 OrderMapper、OrderDetailMapper

所屬包: com.itheima.reggie.mapper

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Orders;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderMapper extends BaseMapper<Orders> {
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.OrderDetail;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OrderDetailMapper extends BaseMapper<OrderDetail> {
}

3). 業務層接口 OrderService、OrderDetailService

所屬包: com.itheima.reggie.service

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Orders;

public interface OrderService extends IService<Orders> {
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.OrderDetail;

public interface OrderDetailService extends IService<OrderDetail> {
}

4). 業務層實現類 OrderServiceImpl、OrderDetailServiceImpl

所屬包: com.itheima.reggie.service.impl

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.*;
import com.itheima.reggie.mapper.OrderMapper;
import com.itheima.reggie.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService {
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.OrderDetail;
import com.itheima.reggie.mapper.OrderDetailMapper;
import com.itheima.reggie.service.OrderDetailService;
import org.springframework.stereotype.Service;

@Service
public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {
}

5). 控制層 OrderController、OrderDetailController

所屬包: com.itheima.reggie.controller

import com.itheima.reggie.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 訂單
 */
@Slf4j
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;
}
import com.itheima.reggie.service.OrderDetailService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 訂單明細
 */
@Slf4j
@RestController
@RequestMapping("/orderDetail")
public class OrderDetailController {

    @Autowired
    private OrderDetailService orderDetailService;
}

4.5 代碼開發

在OrderController中創建submit方法,處理用戶下單的邏輯 :

/**
 * 用戶下單
 * @param orders
 * @return
 */
@PostMapping("/submit")
public R<String> submit(@RequestBody Orders orders){
    log.info("訂單數據:{}",orders);
    orderService.submit(orders);
    return R.success("下單成功");
}

由於下單的邏輯相對復雜,我們可以在OrderService中定義submit方法,來處理下單的具體邏輯:

/**
* 用戶下單
* @param orders
*/
public void submit(Orders orders);

然后在OrderServiceImpl中完成下單功能的具體實現,下單功能的具體邏輯如下:

A. 獲得當前用戶id, 查詢當前用戶的購物車數據

B. 根據當前登錄用戶id, 查詢用戶數據

C. 根據地址ID, 查詢地址數據

D. 組裝訂單明細數據, 批量保存訂單明細

E. 組裝訂單數據, 批量保存訂單數據

F. 刪除當前用戶的購物車列表數據

具體代碼實現如下:

@Autowired
private ShoppingCartService shoppingCartService;

@Autowired
private UserService userService;

@Autowired
private AddressBookService addressBookService;

@Autowired
private OrderDetailService orderDetailService;

/**
* 用戶下單
* @param orders
*/
@Transactional
public void submit(Orders orders) {
    //獲得當前用戶id
    Long userId = BaseContext.getCurrentId();

    //查詢當前用戶的購物車數據
    LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(ShoppingCart::getUserId,userId);
    List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);

    if(shoppingCarts == null || shoppingCarts.size() == 0){
    	throw new CustomException("購物車為空,不能下單");
    }

    //查詢用戶數據
    User user = userService.getById(userId);

    //查詢地址數據
    Long addressBookId = orders.getAddressBookId();
    AddressBook addressBook = addressBookService.getById(addressBookId);
    if(addressBook == null){
    	throw new CustomException("用戶地址信息有誤,不能下單");
    }

    long orderId = IdWorker.getId();//訂單號

    AtomicInteger amount = new AtomicInteger(0);

    //組裝訂單明細信息
    List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setOrderId(orderId);
        orderDetail.setNumber(item.getNumber());
        orderDetail.setDishFlavor(item.getDishFlavor());
        orderDetail.setDishId(item.getDishId());
        orderDetail.setSetmealId(item.getSetmealId());
        orderDetail.setName(item.getName());
        orderDetail.setImage(item.getImage());
        orderDetail.setAmount(item.getAmount());
        amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
        return orderDetail;
    }).collect(Collectors.toList());

    //組裝訂單數據
    orders.setId(orderId);
    orders.setOrderTime(LocalDateTime.now());
    orders.setCheckoutTime(LocalDateTime.now());
    orders.setStatus(2);
    orders.setAmount(new BigDecimal(amount.get()));//總金額
    orders.setUserId(userId);
    orders.setNumber(String.valueOf(orderId));
    orders.setUserName(user.getName());
    orders.setConsignee(addressBook.getConsignee());
    orders.setPhone(addressBook.getPhone());
    orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
        + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
        + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
        + (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
    //向訂單表插入數據,一條數據
    this.save(orders);

    //向訂單明細表插入數據,多條數據
    orderDetailService.saveBatch(orderDetails);

    //清空購物車數據
    shoppingCartService.remove(wrapper);
}

備注:

​ 上述邏輯處理中,計算購物車商品的總金額時,為保證我們每一次執行的累加計算是一個原子操作,我們這里用到了JDK中提供的一個原子類 AtomicInteger

4.6 功能測試

代碼編寫完成,我們重新啟動服務,按照前面分析的操作流程進行測試,查看數據是否正常即可。在測試過程中,我們可以通過debug的形式來跟蹤代碼的正常執行。

image-20210814084822573

檢查數據庫數據

訂單表插入一條記錄:

image-20210814084925524

訂單明細表插入四條記錄():

image-20210814085019401

同時,購物車的數據被刪除:

image-20210814085058814


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM