1. 程式人生 > 其它 >Java專案實戰——瑞吉外賣Day03

Java專案實戰——瑞吉外賣Day03

瑞吉外賣開發筆記 三

筆記內容為黑馬程式設計師視訊內容

分類管理業務開發

公共欄位自動填充

問題分析

前面我們已經完成了後臺系統的員工管理功能開發,在新增員工時需要設定建立時間、建立人、修改時間、修改人等欄位,在編輯員工時需要設定修改時間和修改人等欄位。這些欄位屬於公共欄位,也就是很多表中都有這些欄位,如下:

能不能對於這些公共欄位在某個地方統一處理,來簡化開發呢?答案就是使用Mybatis Plus提供的公共欄位自動填充功能。

程式碼實現

Mybatis Plus公共欄位自動填充,也就是在插入或者更新的時候為指定欄位賦予指定的值,使用它的好處就是可以統一對這些欄位進行處理,避免了重複程式碼。

實現步驟:

1、在實體類的屬性上加入@TableField註解,指定自動填充的策略

@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;

2、按照框架要求編寫元資料物件處理器,在此類中統一為公共欄位賦值,此類需要實現MetaObjectHandler介面

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入時自動填充
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共欄位自動填充【insert】。。。");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser",new Long(1));
        metaObject.setValue("updateUser",new Long(1));
    }
    //更新時自動填充
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共欄位自動填充【update】。。。");
        log.info(metaObject.toString());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",new Long(1));
    }
}

功能完善

前面我們已經完成了公共欄位自動填充功能的程式碼開發,但是還有一個問題沒有解決,就是我們在自動填充createUser和updateUser時設定的使用者id是固定值,現在我們需要改造成動態獲取當前登入使用者的id。

有的同學可能想到,使用者登入成功後我們將使用者id存入了HttpSession中,現在我從HttpSession中獲取不就行了?

注意,我們在MyMetaObjectHandler類中是不能獲得HttpSession物件的,所以我們需要通過其他方式來獲取登入使用者id。

可以使用ThreadLocal來解決此問題,它是JDK中提供的一個類。

在學習ThreadLocal之前,我們需要先確認一個事情,就是客戶端傳送的每次http請求,對應的在服務端都會分配一個新的執行緒來處理,在處理過程中涉及到下面類中的方法都屬於相同的一個執行緒:

1、LoginCheckFilter的doFilter方法

2、EmployeeContraller的update方法

3、MyMetaObjectHandler的updateFill方法

可以在上面的三個方法中分別加入下面程式碼(獲取當前執行緒id):

long id = Thread.currentThread().getId() ;
log.info("執行緒id:{}" ,id);

執行編輯員工功能進行驗證,通過觀察控制檯輸出可以發現,一次請求對應的執行緒id是相同的:

什麼是ThreadLocal?

ThreadLocal並不是一個Thread,而是Thread的區域性變數。當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
ThreadLocal為每個執行緒提供單獨一份儲存空間,具有執行緒隔離的效果,只有在執行緒內才能獲取到對應的值,執行緒外則不能訪問。

ThreadLocal常用方法:

  • public void set(T value) 設定當前執行緒區域性變數的值
  • public T get() 返回當前執行緒所對應的執行緒區域性變數的值

我們可以在LoginCheckFilter的doFilter方法中獲取當前登入使用者id,並呼叫ThreadLocal的set方法來設定當前執行緒的執行緒區域性變數的值(使用者id),然後在MyMetaObjectHandler的updateFill方法中呼叫ThreadLocal的get方法來獲得當前執行緒所對應的執行緒區域性變數的值(使用者id)。

實現步驟:

1、編寫BaseContext工具類,基於ThreadLocal封裝的工具類

/**
 * 基於ThreadLocal封裝的工具類,用於儲存和獲取當前登入使用者的id
 */
public class BaseContext {
    private static ThreadLocal<Long> threadLocal=new ThreadLocal<>();

    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }

    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

2、在LogincheckFilter的doFilter方法中呼叫BaseContext來設定當前登入使用者的id

if (request.getSession().getAttribute("employee") != null) {
    log.info("使用者已登入,使用者id為:{}", request.getSession().getAttribute("employee"));

    Long empId= (Long) request.getSession().getAttribute("employee");

    BaseContext.setCurrentId(empId);

    filterChain.doFilter(request, response);
    return;
}

3、在MyMeta0bjectHandler的方法中呼叫BaseContext獲取登入使用者的id

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入時自動填充
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共欄位自動填充【insert】。。。");
        log.info(metaObject.toString());
        metaObject.setValue("createTime", LocalDateTime.now());
        metaObject.setValue("updateTime", LocalDateTime.now());
        metaObject.setValue("createUser",BaseContext.getCurrentId());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
    //更新時自動填充
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共欄位自動填充【update】。。。");
        log.info(metaObject.toString());

        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}

新增分類

需求分析

後臺系統中可以管理分類資訊,分類包括兩種型別,分別是菜品分類和套餐分類。當我們在後臺系統中新增菜品時需要選擇一個菜品分類,當我們在後臺系統中新增一個套餐時需要選擇一個套餐分類,在移動端也會按照菜品分類和套餐分類來展示對應的菜品和套餐。


資料模型

新增分類,其實就是將我們新增視窗錄入的分類資料插入到category表,表結構如下:

程式碼開發

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

  • 實體類Category(直接從課程資料中匯入即可)
@Data
public class Category implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //型別 1 菜品分類 2 套餐分類
    private Integer type;


    //分類名稱
    private String name;


    //順序
    private Integer sort;


    //建立時間
    @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;

}
  • Mapper介面CategoryMapper
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
  • 業務層介面CategoryService
public interface CategoryService extends IService<Category> {
}
  • 業務層實現類CategoryServicelmpl
@Service
public class CategoryServicelmpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
  • 控制層CategoryController
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
}

在開發程式碼之前,需要梳理一下整個程式的執行過程:

1、頁面(backend/page/category/list.html)傳送ajax請求,將新增分類視窗輸入的資料以json形式提交到服務端

2、服務端Controller接收頁面提交的資料並呼叫Service將資料進行儲存

3、Service呼叫Mapper操作資料庫,儲存資料

可以看到新增菜品分類和新增套餐分類請求的服務端地址和提交的json資料結構相同,所以服務端只需要提供一個方法統一處理即可


//新增分類
@PostMapping
public R<String> save(@RequestBody Category category){
    log.info("category:{}",category);
    categoryService.save(category);
    return R.success("新增分類成功");
}

分類資訊分頁查詢

需求分析

系統中的分類很多的時候,如果在一個頁面中全部展示出來會顯得比較亂,不便於檢視,所以一般的系統中都會以分頁的方式來展示列表資料。

程式碼開發

在開發程式碼之前,需要梳理一下整個程式的執行過程:

1、頁面傳送ajax請求,將分頁查詢引數(page.pageSize)提交到服務端

2、服務端Controller接收頁面提交的資料並呼叫Service查詢資料

3、Service呼叫Mapper操作資料庫,查詢分頁資料

4、Controller將查詢到的分頁資料響應給頁面

5、頁面接收到分頁資料並通過ElementUI的Table元件展示到頁面上

@GetMapping("/page")
public R<Page> page(int page, int pageSize) {
    //構造分頁構造器
    Page<Category> pageInfo=new Page<>(page,pageSize);
    //構造條件構造器
    LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
    //新增排序條件,根據sort進行排序
    queryWrapper.orderByAsc(Category::getSort);
    //進行分頁查詢
    categoryService.page(pageInfo,queryWrapper);

    return R.success(pageInfo);
}

注意:要把Category中的private Integer isDeleted;註釋掉才能查詢到資料

刪除分類

需求分析

在分類管理列表頁面,可以對某個分類進行刪除操作。需要注意的是當分類關聯了菜品或者套餐時,此分類不允許刪除。

程式碼開發

在開發程式碼之前,需要梳理一下整個程式的執行過程:

1、頁面傳送ajax請求,將引數(id)提交到服務端

2、服務端Controller接收頁面提交的資料並呼叫Service刪除資料

3、Service呼叫Mapper操作資料庫

//根據id刪除分類
@DeleteMapping
public R<String> delete(Long ids){
    log.info("刪除分類,id為{}",ids);
    categoryService.removeById(ids);
    //程式碼完善之後categoryService.remove(ids);
    return R.success("分類資訊刪除成功");
}

程式碼完善

前面我們已經實現了根據id刪除分類的功能,但是並沒有檢查刪除的分類是否關聯了菜品或者套餐,所以我們需要進行功能完善。

要完善分類刪除功能,需要先準備基礎的類和介面:

1、實體類Dish和Setmeal (從課程資料中複製即可)

@Data
public class Dish implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //菜品名稱
    private String name;


    //菜品分類id
    private Long categoryId;


    //菜品價格
    private BigDecimal price;


    //商品碼
    private String code;


    //圖片
    private String image;


    //描述資訊
    private String description;


    //0 停售 1 起售
    private Integer status;


    //順序
    private Integer sort;


    @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;

}
@Data
public class Setmeal implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;


    //分類id
    private Long categoryId;


    //套餐名稱
    private String name;


    //套餐價格
    private BigDecimal price;


    //狀態 0:停用 1:啟用
    private Integer status;


    //編碼
    private String code;


    //描述資訊
    private String description;


    //圖片
    private String image;


    @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介面DishMapper和SetmealMapper

@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}
@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}

3、Service介面DishService和SetmealService

public interface DishService extends IService<Dish> {
}
public interface SetmealService extends IService<Setmeal> {
}

4、Service實現類DishServicelmpl和SetmealServicelmpl

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}
@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService{
}

關鍵程式碼

  • 在CategoryService新增remove方法
public interface CategoryService extends IService<Category> {
    public void remove(Long id);
}
  • 在CategoryServicelmpl實現remove方法
@Service
public class CategoryServicelmpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;

    @Autowired
    private SetmealService setmealService;

    @Override
    public void remove(Long id) {
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper=new LambdaQueryWrapper<>();
        //新增查詢條件,根據分類id進行查詢
        dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
        int count1 = dishService.count(dishLambdaQueryWrapper);

        //查詢當前分類是否關聯菜品,如果已經關聯,丟擲業務異常
        if(count1>0){
            //已經關聯菜品,丟擲業務異常
            throw new CustomException("已經關聯菜品,不能刪除");
        }

        //查詢當前分類是否關聯了套餐,如果已經關聯,丟擲業務異常
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper=new LambdaQueryWrapper<>();
        //新增查詢條件,根據分類id進行查詢
        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
        int count2 = setmealService.count(setmealLambdaQueryWrapper);

        if(count2>0){
            //已經關聯套餐,丟擲業務異常
            throw new CustomException("已經關聯套餐,不能刪除");
        }
        //正常刪除分類
        super.removeById(id);
    }
}
  • 定義異常類CustomException
public class CustomException extends RuntimeException{
    public CustomException(String message){
        super(message);
    }
}
  • 在全域性異常處理器GlobalExceptionHandler新增
//進行異常處理方法
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
    log.error(ex.getMessage());

    return R.error(ex.getMessage());
}

修改分類

需求分析

在分類管理列表頁面點選修改按鈕,彈出修改視窗,在修改視窗回顯分類資訊並進行修改,最後點選確定按鈕完成修改操作

程式碼實現

//修改分類
@PutMapping
public R<String> update(@RequestBody Category category){
    categoryService.updateById(category);
    return R.success("分類修改成功");