zl程序教程

您现在的位置是:首页 >  系统

当前栏目

校园外卖点餐系统——Day05【套餐管理业务开发】

系统开发 管理 业务 校园 day05
2023-09-11 14:20:19 时间

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

一、新增套餐

1. 需求分析

套餐就是菜品的集合。

后台系统中可以管理套餐信息,通过新增套餐功能来添加一个新的套餐,在添加套餐时需要选择当前套餐所属的套餐分类和包含的菜品,并且需要上传套餐对应的图片,在移动端会按照套餐分类来展示对应的套餐。
在这里插入图片描述
在这里插入图片描述


2. 数据模型

新增套餐,其实就是将新增页面录入的套餐信息插入到setmeal表,还需要向setmeal_dish表插入套餐和菜品关联数据。所以在新增套餐时,涉及到两个表:

  • setmeal【套餐表】
    在这里插入图片描述
  • setmeal_dish【套餐菜品关系表】
    在这里插入图片描述

3. 代码开发-准备工作

在开发业务代码功能之前,先将需要用的类和接口基本结构创建好:

  • 实体类SetmealDish(Setmeal实体前面已经导入过了)
    @Data
    public class SetmealDish implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private Long id;
    
    
        //套餐id
        private Long setmealId;
    
    
        //菜品id
        private Long dishId;
    
    
        //菜品名称 (冗余字段)
        private String name;
    
        //菜品原价
        private BigDecimal price;
    
        //份数
        private Integer copies;
    
    
        //排序
        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;
    }
    
  • DTO SetmealDto
    @Data
    public class SetmealDto extends Setmeal {
    
        private List<SetmealDish> setmealDishes;
    
        private String categoryName;
    }
    
    
  • Mapper接口SetmealDishMapper
    @Mapper
    public interface SetmealDishMapper extends BaseMapper<SetmealDish> {
    }
    
    
  • 业务层接口SetmealDishService
    public interface SetmealDishService extends IService<SetmealDish> {
    }
    
  • 业务层实现类SetmealDishServicelmpl
    @Service
    @Slf4j
    public class SetmealDishServiceImpl extends ServiceImpl<SetmealDishMapper,SetmealDish> implements SetmealDishService {
    }
    
  • 控制层SetmealController
    @Slf4j
    @RestController
    @RequestMapping("/setmeal")
    public class SetmealController {
    
    }
    

4. 代码开发-梳理交互过程

在开发代码之前,需要梳理一下新增套餐时前端页面和服务端的交互过程:

1、页面(backend/ page/comboladd.html)发送ajax请求,请求服务端获取套餐分类数据并展示到下拉框中。
在这里插入图片描述

2、页面发送ajax请求,请求服务端获取菜品分类数据并展示到添加菜品窗口中。

3、页面发送ajax请求,请求服务端,根据菜品分类查询对应的菜品数据并展示到添加菜品窗口中。

在DishController添加list方法:

 //根据条件查询对应的菜品数据
    @GetMapping("/list")
    public R<List<Dish>> 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);

        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方法

@Slf4j
@RestController
@RequestMapping("/setmeal")
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. 需求分析

系统中的套餐数据很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。

2. 代码开发

在开发代码之前,需要梳理一下套餐分页查询时前端页面和服务端的交互过程:

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);
    }

在这里插入图片描述


三、删除、起售、停售套餐

1. 需求分析

在套餐管理列表页面点击删除按钮,可以删除对应的套餐信息。也可以通过复选框选择多个套餐,点击批量删除按钮一次删除多个套餐。注意,对于状态为售卖中的套餐不能删除,需要先停售,然后才能删除。

2. 代码实现

开发删除套餐功能,其实就是在服务端编写代码去处理前端页面发送的这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("修改成功");
    }

四、修改套餐

1. 需求分析

在套餐管理列表页面点击修改按钮,跳转到修改套餐页面,在修改页面回显套餐相关信息并进行修改,最后点击确定按钮完成修改操作。

2. 代码开发

在开发代码之前,需要梳理一下修改套餐时前端页面( 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形式提交到服务端。

  • 在SetmealController处理put请求

    //修改套餐
    @PutMapping
    public R<String> update(@RequestBody SetmealDto setmealDto){
        setmealService.updateWithDish(setmealDto);
        return R.success("修改成功");
    }
    
    
  • 在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);
        }
    

注意: 开发修改套餐功能,其实就是在服务端编写代码去处理前端页面发送的这4次请求即可。


五、手机验证码登录

1. 短信发送

短信服务介绍
目前市面上有很多第三方提供的短信服务,这些第三方短信服务会和各个运营商(移动、联通、电信)对接,我们只需要注册成为会员并且按照提供的开发文档进行调用就可以发送短信。需要说明的是,这些短信服务一般都是收费服务。

常用短信服务:

  • 阿里云
  • 华为云
  • 腾讯云
  • 京东
  • 梦网
  • 乐信

阿里云短信服务-介绍
阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力。调用API或用群发助手,即可发送验证码、通知类和营销类短信;国内验证短信秒级触达,到达率最高可达99%;国际/港澳台短信覆盖200多个国家和地区,安全稳定,广受出海企业选用。

应用场景:

  • 验证码
  • 短信通知
  • 推广短信

阿里云短信服务-注册账号
阿里云官网:https://www.aliyun.com/
点击官网首页注册按钮。

阿里云短信服务-设置短信签名
注册成功后,点击登录按钮进行登录。登录后进入短信服务管理页面,选择国内消息菜单:
在这里插入图片描述
短信签名是短信发送者的署名,表示发送方的身份。

阿里云短信服务-设置短信模板
切换到【模板管理】标签页:
在这里插入图片描述
短信模板包含短信发送内容、场景、变量信息。

阿里云短信服务-设置AccessKey
光标移动到用户头像上,在弹出的窗口中点击【AccessKey管理】∶
在这里插入图片描述


2. 代码开发

使用阿里云短信服务发送短信,可以参照官方提供的文档即可。
具体开发步骤:

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();
		}
	}
}

1. 需求分析

为了方便用户登录,移动端通常都会提供通过手机验证码登录的功能。

手机验证码登录的优点:

  • 方便快捷,无需注册,直接登录
  • 使用短信验证码作为登录凭证,无需记忆密码
  • 安全

登录流程:
输入手机号>获取验证码>输入验证码>点击登录>登录成功

注意:通过手机验证码登录,手机号是区分不同用户的标识。

2. 数据模型

通过手机验证码登录时,涉及的表为user表,即用户表。结构如下:
在这里插入图片描述

3. 代码开发

在开发代码之前,需要梳理一下登录时前端页面和服务端的交互过程:

1、在登录页面(front/page/login.html)输入手机号,点击【获取验证码】按钮,页面发送ajax请求,在服务端调用短信服务API给指定手机号发送验证码短信。

2、在登录页面输入验证码,点击【登录】按钮,发送ajax请求,在服务端处理登录请求。

开发手机验证码登录功能,其实就是在服务端编写代码去处理前端页面发送的这2次请求即可。

在开发业务功能前,先将需要用到的类和接口基本结构创建好:

  • 实体类User

    @Data
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private Long id;
    
    
        //姓名
        private String name;
    
    
        //手机号
        private String phone;
    
    
        //性别 0 女 1 男
        private String sex;
    
    
        //身份证号
        private String idNumber;
    
    
        //头像
        private String avatar;
    
    
        //状态 0:禁用,1:正常
        private Integer status;
    }
    
    
  • Mapper接口UserMapper

    @Mapper
    public interface UserMapper extends BaseMapper<User>{
    }
    
  • 业务层接口UserService

    public interface UserService extends IService<User> {
    }
    
  • 业务层实现类UserServicelmpl

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{
    }
    
    
  • 控制层UserController

    @Slf4j
    @RestController
    @RequestMapping("/user")
    public class UserController {
    }
    
  • 工具类SMSutils、 ValidateCodeutils

    /**
     * 短信发送工具类
     */
    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();
    		}
    	}
    
    }
    
    /**
     * 随机生成验证码工具类
     */
    public class ValidateCodeUtils {
        /**
         * 随机生成验证码
         * @param length 长度为4位或者6位
         * @return
         */
        public static Integer generateValidateCode(int length){
            Integer code =null;
            if(length == 4){
                code = new Random().nextInt(9999);//生成随机数,最大为9999
                if(code < 1000){
                    code = code + 1000;//保证随机数为4位数字
                }
            }else if(length == 6){
                code = new Random().nextInt(999999);//生成随机数,最大为999999
                if(code < 100000){
                    code = code + 100000;//保证随机数为6位数字
                }
            }else{
                throw new RuntimeException("只能生成4位或6位数字验证码");
            }
            return code;
        }
    
        /**
         * 随机生成指定长度字符串验证码
         * @param length 长度
         * @return
         */
        public static String generateValidateCode4String(int length){
            Random rdm = new Random();
            String hash1 = Integer.toHexString(rdm.nextInt());
            String capstr = hash1.substring(0, length);
            return capstr;
        }
    }
    

前面我们已经完成了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;
        }

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("手机短信发送失败");
}

在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("登陆失败");
}

效果展示
在这里插入图片描述

在这里插入图片描述


创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。