套餐管理业务开发
新增套餐
需求分析
套餐就是菜品的集合。
后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
数据模型
新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:
-
setmeal_dish表
-
setmeal表
代码开发
准备工作
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
- 实体类SetmealDish(直接从课程资料中导入即可,Setmeal实体前面+ 课程中已经导入过了)
- DTO SetmealDto(直接从课程资料中导入即可)
- Mapper接口SetmealDishMapper
- 业务层接口SetmealDishService
- 业务层实现类SetmealDishServicelmpl
- 控制层SetmealController
梳理流程
在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程:
- 页面(backend/ page/comboladd.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中
- 页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中
- 页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中
/**
* 根据条件查询对应的数据
* @param dish
* @return
*/
@GetMapping("/list")
public R<List<Dish>> list(Dish dish){
QueryWrapper<Dish> queryWrapper = new QueryWrapper<Dish>();
queryWrapper.eq(dish.getCategoryId() != null, "category_id", dish.getCategoryId());
// 添加条件,status为1的就是起售
queryWrapper.eq("status", 1);
queryWrapper.orderByAsc("sort").orderByDesc("update_time");
List<Dish> dishList = dishService.list(queryWrapper);
return R.success(dishList);
}
- 页面发送请求进行图片上传,请求服务端将图片保存到服务器
- 页面发送请求进行图片下载,将上传的图片进行回显
- 点击保存按钮,发送ajax请求,将套餐相关数据以json形式提交到服务端
在SetmealServiceImpl实现saveWithDish方法:新增套餐,同时要保持与菜品的关联关系
@Autowired
private SetmealDishService setmealDishService;
/**
* 新增套餐,同时及保存套餐和菜品的关系
* @param setmealDto
*/
@Override
@Transactional
public void insertWithDish(SetmealDto setmealDto) {
// 保存套餐的基本信息,setmeal表中--insert
this.save(setmealDto);
// 保存套餐中的菜品信息,setmealDish表中--insert
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
Iterator<SetmealDish> iterator = setmealDishes.iterator();
while (iterator.hasNext()){
SetmealDish next = iterator.next();
next.setSetmealId(setmealDto.getId());
}
setmealDishService.saveBatch(setmealDishes);
}
再SetmealController中添加save方法
/**
* 新增套餐
* @param setmealDto
* @return
*/
@PostMapping
public R<String> insert(@RequestBody SetmealDto setmealDto){
log.info("setmealDto:{}", setmealDto.toString());
setmealService.insertWithDish(setmealDto);
return R.success("新增套餐成功");
}
开发新增套餐功能,其实就是在服务端编写代码去处理前端页面发送的这6次请求即可。
套餐分页查询
需求分析
系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
梳理过程
在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程:
- 页面(backend/page/combo/list.html)发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端,获取分页数据
- 页面发送请求,请求服务端进行图片下载,用于页面图片展示
开发套餐信息分页查询功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
/**
* 套餐分类查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> page(int page, int pageSize, String name){
Page<Setmeal> pageInfo = new Page<>(page, pageSize);
Page<SetmealDto> setmealDtoPage = new Page<>();
QueryWrapper<Setmeal> queryWrapper = new QueryWrapper<Setmeal>();
queryWrapper.like(name != null, "name", name);
queryWrapper.orderByDesc("update_time");
setmealService.page(pageInfo, queryWrapper);
BeanUtils.copyProperties(pageInfo, setmealDtoPage, "records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> setmealDtoList = new ArrayList<>();
Iterator<Setmeal> iterator = records.iterator();
while (iterator.hasNext()){
Setmeal setmeal = iterator.next();
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(setmeal, setmealDto);
Long categoryId = setmeal.getCategoryId();
log.info("categoryId:{}", categoryId);
Category category = categoryService.getById(categoryId);
if (category != null){
setmealDto.setCategoryName(category.getName());
setmealDtoList.add(setmealDto);
}
}
setmealDtoPage.setRecords(setmealDtoList);
return R.success(setmealDtoPage);
}
删除,起售,停售套餐
需求分析
在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。
代码实现
开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。观察删除单个套餐和批量删除套餐的请求信息可以发现,两种请求的地址和请求方式都是相同的,不同的则是传递的id个数,所以在服务端可以提供一个方法来统一处理。
注意事项:
- 当删除套餐的时候要从setmeal中删除,同时还要删除setmeal_dish表中与该套餐相对应的菜品信息,应当注意如果这个套餐是在起售中(status=1)则不能删除,所以需要重新写一函数:deleteLIst
- 在SetmealService中创建方法:
// 删除套餐,同时删除套餐信息和对应关联的菜品信息
public void deleteWithDish(List<Long> ids);
- 在SetmealServiceImpl中实现方法:
/**
* 删除套餐,同时删除套餐信息和对应关联的菜品信息
* @param ids
*/
@Override
@Transactional
public void deleteWithDish(List<Long> ids) {
// 查询套餐状态,看是否可以删除
QueryWrapper<Setmeal> queryWrapper = new QueryWrapper<Setmeal>();
queryWrapper.in("id", ids);
queryWrapper.eq("status", 1);
int count = this.count(queryWrapper);
// 如果不能删除就报一个错误
if (count > 0){
throw new CustomException("商品正在销售中,不能删除");
}
// 如果可以删除,先删除套餐表中的数据
this.removeByIds(ids);
// 再删除关联表setmeal_dish中的数据
QueryWrapper<SetmealDish> dishQueryWrapper = new QueryWrapper<SetmealDish>();
dishQueryWrapper.in("setmeal_id", ids);
setmealDishService.remove(dishQueryWrapper);
}
- SetmealController中调用方法:
/**
* 批量删除
* @param ids
* @return
*/
@DeleteMapping
public R<String> deleteList(@RequestParam("ids") List<Long> ids) {
setmealService.deleteWithDish(ids);
return R.success("删除成功");
}
起售/停售
/**
* 批量起售/禁售套餐
* @param status
* @param ids
* @return
*/
@PostMapping("/status/{status}")
public R<String> saleList(@PathVariable("status") int status, String[] ids){
for (String id : ids){
Setmeal setmeal = setmealService.getById(id);
setmeal.setStatus(status);
setmealService.updateById(setmeal);
}
return R.success("操作成功");
}
修改套餐
需求分析
在套餐管理列表页面点击修改按钮,跳转到修改套餐页面,在修改页面回显套餐相关信息并进行修改,最后点击确定按钮完成修改操作
梳理流程
在开发代码之前,需要梳理一下修改套餐时前端页面( add.html)和服务端的交互过程:
- 页面发送ajax请求,请求服务端获取分类数据,用于套餐分类下拉框中数据展示
- 页面发送ajax请求,请求服务端,根据id查询当前套餐信息,用于套餐信息回显
- SetmealController处理Get请求(用于回显)
/**
* 查询套餐信息
* @param id
* @return
*/
@GetMapping("/{id}")
public R<SetmealDto> getById(@PathVariable("id") Long id){
log.info("id:{}", id);
SetmealDto setmealDto = setmealService.getByIdWithDish(id);
return R.success(setmealDto);
}
- SetmealServiceImpl中添加getByIdWithDish方法
/**
* 查询套餐,同时查询相对应的菜品信息
* @param id
*/
@Override
public SetmealDto getByIdWithDish(Long id) {
Setmeal setmeal = this.getById(id);
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(setmeal, setmealDto);
QueryWrapper<SetmealDish> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("setmeal_id", setmeal.getId());
List<SetmealDish> dishList = setmealDishService.list(queryWrapper);
setmealDto.setSetmealDishes(dishList);
return setmealDto;
}
- 页面发送请求,请求服务端进行图片下载,用于页图片回显
- 点击保存按钮,页面发送ajax请求,将修改后的菜品相关数据以json形式提交到服务端
- 在SetmealServiceImpl添加updateWithDish方法
/**
* 更新套餐,同时更新相对应的菜品信息
* @param setmealDto
*/
@Override
public void updateWithDish(SetmealDto setmealDto) {
// 更新setmeal表
this.updateById(setmealDto);
// 清楚套餐中所对应的菜品信息,setmeal_dish表
QueryWrapper<SetmealDish> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("setmeal_id", setmealDto.getId());
setmealDishService.remove(queryWrapper);
// 添加当前提交过来的信息
List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
Iterator<SetmealDish> iterator = setmealDishes.iterator();
while (iterator.hasNext()){
SetmealDish setmealDish = iterator.next();
setmealDish.setSetmealId(setmealDto.getId());
}
setmealDishService.saveBatch(setmealDishes);
}
- 在SetmealController处理put请求
/**
* 更新套餐信息
* @param setmealDto
* @return
*/
@PutMapping
public R<String> update(@RequestBody SetmealDto setmealDto){
log.info("setmealDto:{}", setmealDto.toString());
setmealService.updateWithDish(setmealDto);
return R.success("修改成功");
}
**注意:**开发修改套餐功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。
手机验证码登录
短信发送
短信服务介绍
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。
常用的短信服务:
- 阿里云
- 华为云
- 腾讯云
- 京东
- 梦网
- 乐信
阿里云短信服务-介绍
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。
应用场景:
- 验证码
- 短信通知
- 推广短信
阿里云短信服务-注册账号
阿里云官网:https://www.aliyun.com/
阿里云短信服务-设置短信签名
注册成功后,点击登录按钮进行登录。登录后进入短信服务管理页面,选择国内消息菜单:
短信签名是短信发送者的署名,表示发送方的身份。
阿里云短信服务-设置短信模板
切换到【模板管理】标签页:
短信模板包含短信发送内容、场景、变量信息。
代码开发
使用阿里云短信服务发送短信,可以参照官方提供的文档即可。
具体开发步骤:
- 导入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>
- 调用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表,即用户表。结构如下:
开发流程
在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:
- 在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信
- 在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求
开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。
在开发业务功能前,先将需要用到的类和接口基本结构创建好:
- 实体类User(直接从课程资料中获取)
- Mapper接口UserMapper
- 业务层接口UserService
- 控制层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中添加(在路径/front/api/login.js下)
function sendMsgApi(data) {
return $axios({
'url': '/user/sendMsg',
'method': 'post',
data
})
}
- 注释掉front下的login.html的66行,也就是生成随机验证码的那一行,然后加上两行,
const res = sendMsgApi({phone:this.form.phone})
sessionStorage.setItem(“code”,res) - 然后在btnlogin()这个函数中添加,大概在75行
const res = await loginApi({phone:this.form.phone,code:this.form.code}) - 如果还是不行就重启idea或者是在浏览器中按ctrl+F5清楚缓存
UserController处理post请求(发送验证码的请求)
/**
* 发送手机短信验证码
* @param user
* @return
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
log.info("user:{}", user.toString());
// 获取手机号
String phone = user.getPhone();
if (StringUtils.isNotBlank(phone)){
// 生成4位随机号码
String code = ValidateCodeUtils.generateValidateCode(4).toString();
log.info("code:{}", code);
// 调用阿里云提供的短信服务发送短信
//SMSUtils.sendMessage("", "", phone, code);
// 需要将生成的验证码保存到Session中
session.setAttribute(phone, code);
return R.success("手机短信验证码发送成功");
}
return R.error("短信发送失败");
}
在UserController编写login处理post请求
/**
* 移动端用户登录
* @param map
* @param session
* @return
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
// 获取手机号
String phone = map.get("phone").toString();
// 获取验证码
String code = map.get("code").toString();
// 从Session中获取验证码
String codeInSession = (String)session.getAttribute(phone);
// 将输入的验证码与Session中的进行比对
if (code.equals(codeInSession) && codeInSession != null){
// 如果比对成功,就登录成功
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone", 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("登录失败");
}
效果展示