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("分類修改成功");