Java專案實戰——瑞吉外賣Day05
瑞吉外賣開發筆記 五
筆記內容為黑馬程式設計師視訊內容
套餐管理業務開發
新增套餐
需求分析
套餐就是菜品的集合。
後臺系統中可以管理套餐資訊,通過新增套餐功能來新增一個新的套餐,在新增套餐時需要選擇當前套餐所屬的套餐分類和包含的菜品,並且需要上傳套餐對應的圖片,在移動端會按照套餐分類來展示對應的套餐。
資料模型
新增套餐,其實就是將新增頁面錄入的套餐資訊插入到setmeal表,還需要向setmeal_dish表插入套餐和菜品關聯資料。所以在新增套餐時,涉及到兩個表:
-
setmeal 套餐表
-
setmeal_dish 套餐菜品關係表
程式碼開發-準備工作
在開發業務功能前,先將需要用到的類和介面基本結構建立好:
- 實體類SetmealDish(直接從課程資料中匯入即可,Setmeal實體前面+ 課程中已經匯入過了)
- DTO SetmealDto(直接從課程資料中匯入即可)
- Mapper介面SetmealDishMapper
- 業務層介面SetmealDishService
- 業務層實現類SetmealDishServicelmpl
- 控制層SetmealController
程式碼開發-梳理互動過程
在開發程式碼之前,需要梳理一下新增套餐時前端頁面和服務端的互動過程:
1、頁面(backend/ page/comboladd.html)傳送ajax請求,請求服務端獲取套餐分類資料並展示到下拉框中
2、頁面傳送ajax請求,請求服務端獲取菜品分類資料並展示到新增菜品視窗中
3、頁面傳送ajax請求,請求服務端,根據菜品分類查詢對應的菜品資料並展示到新增菜品視窗中
在DishController新增list方法
//根據條件查詢對應菜品資料 @GetMapping("/list") public R<List<Dish>> list(Dish dish){ //構造查詢條件 LambdaQueryWrapper<Dish> lambdaQueryWrapper=new LambdaQueryWrapper<>(); //新增條件,查詢狀態為1的(起售狀態) lambdaQueryWrapper.eq(Dish::getStatus,1); lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId()); //條件排序條件 lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list=dishService.list(lambdaQueryWrapper); return R.success(list); }
4、頁面傳送請求進行圖片上傳,請求服務端將圖片儲存到伺服器
5、頁面傳送請求進行圖片下載,將上傳的圖片進行回顯
6、點選儲存按鈕,傳送ajax請求,將套餐相關資料以json形式提交到服務端
在SetmealServiceImpl實現saveWithDish方法:新增套餐,同時要保持與菜品的關聯關係
@Service
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService{
@Autowired
private SetmealDishService setmealDishService;
//新增套餐,同時要保持與菜品的關聯關係
@Override
@Transactional
public void saveWithDish(SetmealDto setmealDto) {
//儲存套餐基本資訊,操作setmeal,執行insert操作
this.save(setmealDto);
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
setmealDishes.stream().map((item)->{
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
//儲存套餐和菜品的關聯資訊,操作setmeal_dish,執行insert操作
setmealDishService.saveBatch(setmealDishes);
}
}
在SetmealController新增save方法
@RestController
@RequestMapping("/setmeal")
@Slf4j
public class SetmealController {
@Autowired
private SetmealService setmealService;
@Autowired
private SetmealDishService setmealDishService;
@PostMapping
public R<String> save(@RequestBody SetmealDto setmealDto){
log.info("setmeal:{}",setmealDto);
setmealService.saveWithDish(setmealDto);
return R.success("新增套餐成功");
}
}
開發新增套餐功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這6次請求即可。
套餐分頁查詢
需求分析
系統中的套餐資料很多的時候,如果在一個頁面中全部展示出來會顯得比較亂,不便於檢視,所以一般的系統中都會以分頁的方式來展示列表資料。
程式碼開發-梳理互動過程
在開發程式碼之前,需要梳理一下套餐分頁查詢時前端頁面和服務端的互動過程:
1、頁面(backend/page/combo/list.html)傳送ajax請求,將分頁查詢引數(page、pageSize、name)提交到服務端,獲取分頁資料
2、頁面傳送請求,請求服務端進行圖片下載,用於頁面圖片展示
開發套餐資訊分頁查詢功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這2次請求即可。
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//構造分頁構造器
Page<Setmeal> pageInfo=new Page<>(page,pageSize);
Page<SetmealDto> pageDtoInfo=new Page<>();
//構造條件構造器
LambdaQueryWrapper<Setmeal> queryWrapper=new LambdaQueryWrapper<>();
//根據name進行模糊查詢
queryWrapper.like(!StringUtils.isEmpty(name),Setmeal::getName,name);
//新增排序條件,根據sort進行排序
queryWrapper.orderByDesc(Setmeal::getUpdateTime);
//進行分頁查詢
setmealService.page(pageInfo,queryWrapper);
//物件拷貝
BeanUtils.copyProperties(pageInfo,pageDtoInfo,"records");
List<Setmeal> records=pageInfo.getRecords();
List<SetmealDto> list= records.stream().map((item)->{
SetmealDto setmealDto=new SetmealDto();
BeanUtils.copyProperties(item,setmealDto);
Long categoryId = item.getCategoryId();
//根據id查分類物件
Category category = categoryService.getById(categoryId);
if(category!=null){
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
pageDtoInfo.setRecords(list);
return R.success(pageDtoInfo);
}
刪除、起售、停售套餐
需求分析
在套餐管理列表頁面點選刪除按鈕,可以刪除對應的套餐資訊。也可以通過複選框選擇多個套餐,點選批量刪除按鈕一次刪除多個套餐。注意,對於狀態為售賣中的套餐不能刪除,需要先停售,然後才能刪除。
程式碼實現
開發刪除套餐功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這2次請求即可。
觀察刪除單個套餐和批量刪除套餐的請求資訊可以發現,兩種請求的地址和請求方式都是相同的,不同的則是傳遞的id個數,所以在服務端可以提供一個方法來統一處理。
@DeleteMapping
public R<String> delete(String[] ids){
int index=0;
for(String id:ids) {
Setmeal setmeal = setmealService.getById(id);
if(setmeal.getStatus()!=1){
setmealService.removeById(id);
}else {
index++;
}
}
if (index>0&&index==ids.length){
return R.error("選中的套餐均為啟售狀態,不能刪除");
}else {
return R.success("刪除成功");
}
}
@PostMapping("/status/{status}")
public R<String> sale(@PathVariable int status,String[] ids){
for (String id:ids){
Setmeal setmeal = setmealService.getById(id);
setmeal.setStatus(status);
setmealService.updateById(setmeal);
}
return R.success("修改成功");
}
修改套餐
需求分析
在套餐管理列表頁面點選修改按鈕,跳轉到修改套餐頁面,在修改頁面回顯套餐相關資訊並進行修改,最後點選確定按鈕完成修改操作
程式碼開發-梳理互動過程
在開發程式碼之前,需要梳理一下修改套餐時前端頁面( add.html)和服務端的互動過程:
1、頁面傳送ajax請求,請求服務端獲取分類資料,用於套餐分類下拉框中資料展示
2、頁面傳送ajax請求,請求服務端,根據id查詢當前套餐資訊,用於套餐資訊回顯
- SetmealController處理Get請求
//根據Id查詢套餐資訊
@GetMapping("/{id}")
public R<SetmealDto> getById(@PathVariable Long id){
SetmealDto setmealDto=setmealService.getByIdWithDish(id);
return R.success(setmealDto);
}
- SetmealServiceImpl新增getByIdWithDish方法
@Override
public SetmealDto getByIdWithDish(Long id) {
//查詢套餐基本資訊
Setmeal setmeal = this.getById(id);
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(setmeal, setmealDto);
//查詢套餐菜品資訊
LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId,setmeal.getId());
List<SetmealDish> list = setmealDishService.list(queryWrapper);
setmealDto.setSetmealDishes(list);
return setmealDto;
}
3、頁面傳送請求,請求服務端進行圖片下載,用於頁圖片回顯
4、點選儲存按鈕,頁面傳送ajax請求,將修改後的菜品相關資料以json形式提交到服務端
- 在SetmealServiceImpl新增updateWithDish方法
@Override
public void updateWithDish(SetmealDto setmealDto) {
//更新setmeal表基本資訊
this.updateById(setmealDto);
//更新setmeal_dish表資訊delete操作
LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SetmealDish::getSetmealId, setmealDto.getId());
setmealDishService.remove(queryWrapper);
//更新setmeal_dish表資訊insert操作
List<SetmealDish> SetmealDishes = setmealDto.getSetmealDishes();
SetmealDishes = SetmealDishes.stream().map((item) -> {
item.setSetmealId(setmealDto.getId());
return item;
}).collect(Collectors.toList());
setmealDishService.saveBatch(SetmealDishes);
}
- 在SetmealController處理put請求
//修改套餐
@PutMapping
public R<String> update(@RequestBody SetmealDto setmealDto){
setmealService.updateWithDish(setmealDto);
return R.success("修改成功");
}
注意:開發修改套餐功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這4次請求即可。
手機驗證碼登入
簡訊傳送
簡訊服務介紹
目前市面上有很多第三方提供的簡訊服務,這些第三方簡訊服務會和各個運營商(移動、聯通、電信)對接,我們只需要註冊成為會員並且按照提供的開發文件進行呼叫就可以傳送簡訊。需要說明的是,這些簡訊服務一般都是收費服務。
常用簡訊服務:
- 阿里雲
- 華為雲
- 騰訊雲
- 京東
- 夢網
- 樂信
阿里雲簡訊服務-介紹
阿里雲簡訊服務(Short Message Service)是廣大企業客戶快速觸達手機使用者所優選使用的通訊能力。呼叫API或用群發助手,即可傳送驗證碼、通知類和營銷類簡訊;國內驗證簡訊秒級觸達,到達率最高可達99%;國際/港澳臺簡訊覆蓋200多個國家和地區,安全穩定,廣受出海企業選用。
應用場景:
- 驗證碼
- 簡訊通知
- 推廣簡訊
阿里雲簡訊服務-註冊賬號
阿里雲官網: https://www.aliyun.com/
點選官網首頁註冊按鈕。
阿里雲簡訊服務-設定簡訊簽名
註冊成功後,點選登入按鈕進行登入。登入後進入簡訊服務管理頁面,選擇國內訊息選單:
簡訊簽名是簡訊傳送者的署名,表示傳送方的身份。
阿里雲簡訊服務-設定簡訊模板
切換到【模板管理】標籤頁:
簡訊模板包含簡訊傳送內容、場景、變數資訊。
阿里雲簡訊服務-設定AccessKey
游標移動到使用者頭像上,在彈出的視窗中點選【AccessKey管理】∶
程式碼開發
使用阿里雲簡訊服務傳送簡訊,可以參照官方提供的文件即可。
具體開發步驟:
1、匯入maven座標
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.1.0</version>
</dependency>
2、呼叫API
public class SMSUtils {
/**
* 傳送簡訊
* @param signName 簽名
* @param templateCode 模板
* @param phoneNumbers 手機號
* @param param 引數
*/
public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", "");
IAcsClient client = new DefaultAcsClient(profile);
SendSmsRequest request = new SendSmsRequest();
request.setSysRegionId("cn-hangzhou");
request.setPhoneNumbers(phoneNumbers);
request.setSignName(signName);
request.setTemplateCode(templateCode);
request.setTemplateParam("{\"code\":\""+param+"\"}");
try {
SendSmsResponse response = client.getAcsResponse(request);
System.out.println("簡訊傳送成功");
}catch (ClientException e) {
e.printStackTrace();
}
}
}
手機驗證碼登入
需求分析
為了方便使用者登入,移動端通常都會提供通過手機驗證碼登入的功能。
手機驗證碼登入的優點:
- 方便快捷,無需註冊,直接登入
- 使用簡訊驗證碼作為登入憑證,無需記憶密碼
- 安全
登入流程:
輸入手機號>獲取驗證碼>輸入驗證碼>點選登入>登入成功
注意:通過手機驗證碼登入,手機號是區分不同使用者的標識。
資料模型
通過手機驗證碼登入時,涉及的表為user表,即使用者表。結構如下:
程式碼開發
在開發程式碼之前,需要梳理一下登入時前端頁面和服務端的互動過程:
1、在登入頁面(front/page/login.html)輸入手機號,點選【獲取驗證碼】按鈕,頁面傳送ajax請求,在服務端呼叫簡訊服務API給指定手機號傳送驗證碼簡訊
2、在登入頁面輸入驗證碼,點選【登入】按鈕,傳送ajax請求,在服務端處理登入請求
開發手機驗證碼登入功能,其實就是在服務端編寫程式碼去處理前端頁面傳送的這2次請求即可。
在開發業務功能前,先將需要用到的類和介面基本結構建立好:
- 實體類User(直接從課程資料中匯入即可)
- Mapper介面UserMapper
- 業務層介面UserService
- 業務層實現類UserServicelmpl
- 控制層UserController
- 工具類SMSutils、 ValidateCodeutils(直接從課程資料中匯入即可)
前面我們已經完成了LogincheckFilter過濾器的開發,此過濾器用於檢查使用者的登入狀態。我們在進行手機驗證碼登入時,傳送的請求需要在此過濾器處理時直接放行。
LoginCheckFilter過濾器新增
// 4-2、判斷登入狀態,如果已登入,則直接放行
if (request.getSession().getAttribute("user") != null) {
log.info("使用者已登入,使用者id為:{}", request.getSession().getAttribute("user"));
Long userId= (Long) request.getSession().getAttribute("user");
BaseContext.setCurrentId(userId);
filterChain.doFilter(request, response);
return;
}
由於資料中程式碼不全login.js自行新增
function sendMsgApi(data) {
return $axios({
'url':'/user/sendMsg',
'method':'post',
data
})
}
login.html
// this.form.code = (Math.random()*1000000).toFixed(0)
sendMsgApi({phone:this.form.phone})
UserController處理post請求(傳送驗證碼的請求)
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//獲取手機號
String phone=user.getPhone();
if(!StringUtils.isEmpty(phone)) {
//生成隨機的4位驗證碼
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code={}",code);
//呼叫阿里雲提供的簡訊服務API完成傳送簡訊
//SMSUtils.sendMessage("瑞吉外賣","",phone,code);
//需要將生成的驗證碼儲存到Session
session.setAttribute(phone,code);
return R.success("手機驗證碼簡訊傳送成功");
}
return R.error("手機簡訊傳送失敗");
}
由於前端頁面有部分程式碼缺失,建議拷貝資料中day05的front程式碼
在UserController編寫login處理post請求
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session) {
log.info("map:{}", map.toString());
//獲取手機號
String phone = map.get("phone").toString();
//獲取驗證碼
String code = map.get("code").toString();
//從Session中獲取儲存的驗證碼
Object codeInSession = session.getAttribute(phone);
//進行驗證碼比對(頁面提交的驗證碼和Session中儲存的驗證碼比對)
if (codeInSession != null && codeInSession.equals(code)) {
//如果能夠比對成功,說明登入成功
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);
if (user == null) {
//判斷當前手機號是否為新使用者,如果是新使用者則自動完成註冊
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("user",user.getId());
return R.success(user);
}
return R.error("登陸失敗");
}