外卖点餐系统

一、后台管理系统

1.员工管理

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee>{
}
public interface EmployeeService extends IService<Employee> {
}
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService{
}

①登录 

 @PostMapping("/login")
    public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee){

        //1、将页面提交的密码password进行md5加密处理
        String password = employee.getPassword();
        password = DigestUtils.md5DigestAsHex(password.getBytes());
        //2、根据页面提交的用户名username查询数据库
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
        //3、如果没有查询到则返回登录失败结果
        if(emp == null){
            return R.error("登录失败");
        }
        //4、密码比对,如果不一致则返回登录失败结果
        if(!emp.getPassword().equals(password)){
            return R.error("密码错误");
        }
        //5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果
        if(emp.getStatus() == 0){
            return R.error("账号已禁用");
        }
        //6、登录成功,将员工id存入Session并返回登录成功结果
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }

 ②退出

 @PostMapping("/logout")
    public R<String> logout(HttpServletRequest request){
        //清理Session中保存的当前登录员工的id
        request.getSession().removeAttribute("employee");
        return R.success("退出成功");
    }

③添加员工

  @PostMapping
    public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
        log.info("新增员工,员工信息:{}",employee.toString());
        //设置初始密码123456,需要进行md5加密处理
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        //employee.setCreateTime(LocalDateTime.now());
        //employee.setUpdateTime(LocalDateTime.now());
        //获得当前登录用户的id
        //Long empId = (Long) request.getSession().getAttribute("employee");
        //employee.setCreateUser(empId);
        //employee.setUpdateUser(empId);
        employeeService.save(employee);
        return R.success("新增员工成功");
    }

④修改员工信息

1)修改时要先根据id查询员工信息

 @GetMapping("/{id}")
    public R<Employee> getById(@PathVariable Long id){
        log.info("根据id查询员工信息...");
        Employee employee = employeeService.getById(id);
        if(employee != null){
            return R.success(employee);
        }
        return R.error("没有查询到对应员工信息");
    }

2)再执行更新操作

 @PutMapping
    public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
        log.info(employee.toString());
        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);
        //Long empId = (Long)request.getSession().getAttribute("employee");
        //employee.setUpdateTime(LocalDateTime.now());
        //employee.setUpdateUser(empId);
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

⑤分页

  @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

2.分类管理

public interface CategoryService extends IService<Category> {
    public void remove(Long id);

}
@Service
public class CategoryServiceImpl 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();
        if(count2 > 0){
            //已经关联套餐,抛出一个业务异常
            throw new CustomException("当前分类下关联了套餐,不能删除");
        }
        //正常删除分类
        super.removeById(id);
    }
   @DeleteMapping
    public R<String> delete(Long id){
        log.info("删除分类,id为:{}",id);
        //categoryService.removeById(id);
        categoryService.remove(id);
        return R.success("分类信息删除成功");
    }

②增加分类

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

 ③修改不需要重新写根据id查询数据是因为前端页面已经使用了v-model进行双向绑定,所以可以直接回显数据。

  @PutMapping
    public R<String> update(@RequestBody Category category){
        log.info("修改分类信息:{}",category);
        categoryService.updateById(category);
        return R.success("修改分类信息成功");
    }

④分页,因为没有搜索框,所以只需要两个参数就好,不需要添加过滤条件。

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

3.菜品管理

①新增菜品

1)业务层和实现类的方法:

public interface DishService extends IService<Dish> {
    //新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish、dish_flavor
    public void saveWithFlavor(DishDto dishDto);
    //根据id查询菜品信息和对应的口味信息
    public DishDto getByIdWithFlavor(Long id);
    //更新菜品信息,同时更新对应的口味信息
    public void updateWithFlavor(DishDto dishDto);
    //删除菜品,同时删除相关的口味信息
    public void  deleteWithFlavor(Long[] ids);
}

2)因为同时要操作两个表,所以要同时将菜品信息和菜品口味信息放入两个表,使用Transactional注解,完成事务操作。

  */
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //保存菜品的基本信息到菜品表dish
        this.save(dishDto);
        Long dishId = dishDto.getId();//菜品id
        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishId);
            return item;
        }).collect(Collectors.toList());
        //保存菜品口味数据到菜品口味表dish_flavor
        dishFlavorService.saveBatch(flavors);

    }
 @PostMapping//1
    public R<String> save(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.saveWithFlavor(dishDto);
        return R.success("新增菜品成功");
    }

② 点击修改菜品信息的时候需要回显菜品数据以及有关它自己的口味信息。

   public DishDto getByIdWithFlavor(Long id) {
        //查询菜品基本信息,从dish表查询
        Dish dish = this.getById(id);
        DishDto dishDto = new DishDto();
        BeanUtils.copyProperties(dish,dishDto);
        //查询当前菜品对应的口味信息,从dish_flavor表查询
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dish.getId());
        List<DishFlavor> flavors = dishFlavorService.list(queryWrapper);
        dishDto.setFlavors(flavors);
        return dishDto;
    }

controller层

@GetMapping("/{id}")//2
    public R<DishDto> get(@PathVariable Long id){
        DishDto dishDto = dishService.getByIdWithFlavor(id);
        return R.success(dishDto);
    }

 ③更新菜品表的菜品的基本信息以及更新口味表的有关该菜品的口味信息,因为操作两个表以及有很多操作,所以要增加事务注解。

@Override
    @Transactional
    public void updateWithFlavor(DishDto dishDto) {
        //更新dish表基本信息
        this.updateById(dishDto);
        //清理当前菜品对应口味数据---dish_flavor表的delete操作
        LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(DishFlavor::getDishId,dishDto.getId());
        dishFlavorService.remove(queryWrapper);
        //添加当前提交过来的口味数据---dish_flavor表的insert操作
        List<DishFlavor> flavors = dishDto.getFlavors();
        flavors = flavors.stream().map((item) -> {
            item.setDishId(dishDto.getId());
            return item;
        }).collect(Collectors.toList());
        dishFlavorService.saveBatch(flavors);
    }

控制层

    @PutMapping//3
    public R<String> update(@RequestBody DishDto dishDto){
        log.info(dishDto.toString());
        dishService.updateWithFlavor(dishDto);
        return R.success("修改菜品成功");
    }

④删除菜品,同时删除删除该菜品是否与套餐有关联以及同时删除相关菜品口味。

    //删除菜品,同时删除删除该菜品是否与套餐有关联以及同时删除相关菜品口味
    @Override
    public void deleteWithFlavor(Long[] ids) {
        for (int i = 0; i < ids.length; i++) {
            Long dishId=ids[i];
            //条件构造器去查询该菜品的相关口味信息
            // 以及与套餐是否相连
            LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
            queryWrapper.eq(SetmealDish::getDishId,dishId);
            int count=setmealDishService.count(queryWrapper);
            if (count>0){
                throw new CustomException("该菜品已绑定相关套餐,请先下架相关套餐");
            }
            LambdaQueryWrapper<DishFlavor> queryWrapper1=new LambdaQueryWrapper<>();
            queryWrapper1.eq(DishFlavor::getDishId,dishId);
            //如果有的话,就提示不能删除
            dishFlavorService.remove(queryWrapper1);
            //删掉该菜品
            super.removeById(dishId);
        }
    }
 @DeleteMapping
   public R<String> deletes(Long[] ids){
        dishService.deleteWithFlavor(ids);
        return R.success("删除成功");
    }

 ⑤修改菜品起售/停售状态。【批量起售和停售还是有点bug】

  @PostMapping("/status/{status}")
    public R<String> updateStatusById(@PathVariable Integer status, Long[] ids) {
        // 增加日志验证是否接收到前端参数。
        log.info("根据id修改菜品的状态:{},id为:{}", status, ids);
        // 通过id查询数据库。修改id为ids数组中的数据的菜品状态status为前端页面提交的status。
        for (int i = 0; i < ids.length; i++) {
            Long id=ids[i];
            //根据id得到每个dish菜品。
            Dish dish = dishService.getById(id);
            dish.setStatus(status);
            dishService.updateById(dish);
        }
        return R.success("修改菜品状态成功");
    }

 ⑥分页

因为菜品信息表里只存储了菜品的信息和category_id,并没有存储它所属的分类的名称,所以要根据category_id查找到菜品分类名称,才能显示在页面上。

@GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        //构造分页构造器对象
        Page<Dish> pageInfo = new Page<>(page,pageSize);
        Page<DishDto> dishDtoPage = new Page<>();
        //条件构造器
        LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件
        queryWrapper.like(name != null,Dish::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Dish::getUpdateTime);
        //执行分页查询
        dishService.page(pageInfo,queryWrapper);
        //对象拷贝
        BeanUtils.copyProperties(pageInfo,dishDtoPage,"records");
        List<Dish> records = pageInfo.getRecords();
        List<DishDto> list = records.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item,dishDto);
            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            return dishDto;
        }).collect(Collectors.toList());
        dishDtoPage.setRecords(list);
        return R.success(dishDtoPage);
    }

4.套餐管理

业务层涉及方法

public interface SetmealService extends IService<Setmeal> {
    /**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     * @param setmealDto
     */
    public void saveWithDish(SetmealDto setmealDto);

    /**
     * 删除套餐,同时需要删除套餐和菜品的关联数据
     * @param ids
     */
    public void removeWithDish(List<Long> ids);
    //查询套餐
    public SetmealDto selectById(Long id);
    //修改套餐
    public void updateStemeal(SetmealDto setmealDto);
    //点击套餐查看菜品详情
    public List<DishDto> checkSemeal(Long id);
}

①新增套餐

/**
     * 新增套餐,同时需要保存套餐和菜品的关联关系
     *
     * @param setmealDto
     */
    @Transactional//因为要操作两个表
    public void saveWithDish(SetmealDto setmealDto) {
        //保存套餐的基本信息,操作setmeal,执行insert操作
        this.save(setmealDto);
        List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes();
        setmealDishes.stream().map((item) -> {
            //把setmealDishe这些菜品跟套餐id进行绑定
            item.setSetmealId(setmealDto.getId());
            return item;
        }).collect(Collectors.toList());
        //保存套餐和菜品的关联信息,操作setmeal_dish,执行insert操作
        setmealDishService.saveBatch(setmealDishes);//然后把得到的套餐的有关菜品保存即可
    }

控制层调用

   @PostMapping
    public R<String> save(@RequestBody SetmealDto setmealDto){
        log.info("套餐信息:{}",setmealDto);
        setmealService.saveWithDish(setmealDto);
        return R.success("新增套餐成功");
    }

②删除套餐,以及套餐-菜品表里的菜品信息 

 @Transactional
    public void removeWithDish(List<Long> ids) {
        //select count(*) from setmeal where id in (1,2,3) and status = 1
        //查询套餐状态,确定是否可用删除
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.in(Setmeal::getId, ids);//具体的值是ids
        queryWrapper.eq(Setmeal::getStatus, 1);
        int count = this.count(queryWrapper);
        if (count > 0) {
            //如果不能删除,抛出一个业务异常
            throw new CustomException("套餐正在售卖中,不能删除");
        }
        //如果可以删除,先删除套餐表中的数据---setmeal
        this.removeByIds(ids);
        //delete from setmeal_dish where setmeal_id in (1,2,3)
        LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.in(SetmealDish::getSetmealId, ids);//这是传过来的套餐id
        //删除关系表中的数据----setmeal_dish
        setmealDishService.remove(lambdaQueryWrapper);
    }

 控制层调用

 @DeleteMapping//为啥会出现批量不选也能删除啊啊啊啊啊啊啊啊啊啊啊啊啊啊!
    public R<String> deletes( Long[] ids){
        log.info("删除分类,id为:{}",ids);
        //挨个删除
        for (int j=0;j<ids.length;j++) {
            Long id = ids[j];
            Setmeal setmeal = setmealService.getById(id);
            if (setmeal.getStatus() == 1) {
                return R.error("该商品还在售卖,不能删除");
            }
            setmealService.removeById(setmeal);
        }return R.success("删除成功");
    }

③修改套餐先回显数据:

  //修改套餐信息并回显数据
    // 1.现根据id查询到该套餐信息
    @GetMapping("/{id}")
    public R<SetmealDto> CheckMeal(@PathVariable Long id){
    SetmealDto setmealDto=setmealService.selectById(id);
        //添加判断,看是否有传id值过来
        return R.success(setmealDto);
    }

再调用实现类里的方法去更新,清空以前的套餐里所包含的菜品信息,再将新拿到的批量保存。

  @Override
    @Transactional
    public void updateStemeal(SetmealDto setmealDto) {
        //1.先更新套餐表信息
        this.updateById(setmealDto);
        //2.清理相关菜品信息
             //2.1写sql,先构造条件构造器
        LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,setmealDto.getId());
             //2.2拿到套餐id,进行删除
        setmealDishService.remove(queryWrapper);
        //3.拿到提交的数据,进行添加操作
             //3.1重新拿到套餐dto里的菜品数据列表
        List<SetmealDish> setmealDishList=setmealDto.getSetmealDishes();
            //3.2将列表封装成一个数组字段
        setmealDishList=setmealDishList.stream().map((item)->{
            //3.3将这个数组的数据与当前套餐id锁死
            item.setSetmealId(setmealDto.getId());
                return item;
        }).collect(Collectors.toList());
            //3.4批量保存
        setmealDishService.saveBatch(setmealDishList);
    }

 控制层调用

 //2.将修改的信息封装成新的对象,保存到数据库中
    @PutMapping
    //不需要返回一个对象,只需要返回一个布尔值,所以返回值是R<String>
    public R<String> saveMeal(@RequestBody SetmealDto setmealDto){
        setmealService.updateStemeal(setmealDto);
        return  R.success("修改成功");
    }

 ④修改套餐状态,这个的话单独修改是都没有问题的,知识批量修改那几个按钮依稀有点问题。

 /*
    修改套餐状态
     */
    @PostMapping("/status/{status}")
    public R<String> updatestemeal(@PathVariable Integer status,Long[] ids){
        //先拿到每一个套餐,再逐一修改
        for (int i = 0; i < ids.length; i++) {
            Long id=ids[i];
            Setmeal setmeal=setmealService.getById(id);
            //先设置单个状态属性
            setmeal.setStatus(status);
            //再修改整个对象信息
            setmealService.updateById(setmeal);
        }return R.success("修改状态成功");
    }

⑤分页

  @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        //分页构造器对象
        Page<Setmeal> pageInfo = new Page<>(page,pageSize);
        Page<SetmealDto> dtoPage = new Page<>();//因为Page有categoryName这个属性,但是要拷贝
        LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
        //添加查询条件,根据name进行like模糊查询
        queryWrapper.like(name != null,Setmeal::getName,name);
        //添加排序条件,根据更新时间降序排列
        queryWrapper.orderByDesc(Setmeal::getUpdateTime);
        setmealService.page(pageInfo,queryWrapper);
        //return R.success(pageInfo);
        //如果直接返回的话,“套餐分类“这个字段显示不出来
       
        //对象拷贝
        //(原对象,拷到的目标对象,忽略records是因为page。class里的records的泛型不一样
        BeanUtils.copyProperties(pageInfo,dtoPage,"records");
        //自己重新拿到不同泛型的records
        List<Setmeal> records = pageInfo.getRecords();
        //item是遍历出来的每一个套餐实体
        List<SetmealDto> list = records.stream().map((item) -> {
            SetmealDto setmealDto = new SetmealDto();
            //对象拷贝
            BeanUtils.copyProperties(item,setmealDto);
            //分类id
            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());//收集到所有数据之后转成list集合
        dtoPage.setRecords(list);
        return R.success(dtoPage);
    }

5.订单管理

mapper层

@Mapper
public interface OrderMapper extends BaseMapper<Orders> {
}

service层

public interface OrderService extends IService<Orders> {
}

 实现层

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Orders> implements OrderService { }

①分页 

  @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String number, String beginTime, String endTime) {
        //分页构造器
        Page<Orders> pageinfo = new Page<>(page, pageSize);//只能有这两参数
        //条件构造器
        LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
        //添加过滤条件
        queryWrapper.like(number != null, Orders::getNumber, number)
                .gt(StringUtils.isNotEmpty(beginTime), Orders::getOrderTime, beginTime)
                .lt(StringUtils.isNotEmpty(endTime), Orders::getOrderTime, endTime);
        //添加排序条件,根据sort进行排序
        queryWrapper.orderByAsc(Orders::getOrderTime);
        //分页查询
        orderService.page(pageinfo, queryWrapper);
        return R.success(pageinfo);
    }

②修改订单状态

 因为前端的传参列表长这样,所以参数传递类型为

使用PUT请求,但是请求负载传的是一个map集合,所以可以使用这种方式接收


    @PutMapping
    public R<String> UpdateStatus(@RequestBody Map<String, String> map) {
        String id = map.get("id");
        Long orderId = Long.parseLong(id);
        Integer status = Integer.parseInt(map.get("status"));
        //拿到值之后开始做判断
        if (orderId == null || status == null) {
            return R.error("传入信息不合法");
        }
        //重建一个对象,将信息修改
        Orders orders = orderService.getById(orderId);
        orders.setStatus(status);
        //修改之后执行修改语句
        orderService.updateById(orders);
        return R.success("订单状态修改成功");
    }

③查看订单详情

这个应该是跟之前一样在前端已经设置好了回显。

 二、小程序用户端

1.短信验证

直接使用阿里云的api即可,导入坐标,调用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();
		}
	}
}

直接使用黑马的随机发送验证码的工具类:

package com.itheima.reggie.utils;

import java.util.Random;

/**
 * 随机生成验证码工具类
 */
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;
    }
}

2.用户登录 、退出

①用户登录

在登录之前记得在过滤器那里把需要放行的地方再添加一些需要放行的路径:

1ba637e9cc92437c9a758efeebdcef59.png

 还要继续把用户端已经登录的用户信息存储到session中才能不跳回登录页面。

ec1b8d267879493895079bd68262049b.png

 登录请求接口代码编写:

  @PostMapping("/login")
    public R<User> login(@RequestBody Map map, HttpSession session){
        log.info(map.toString());//因为实体类里边无code这个属性
        //获取手机号
        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);
            }
            //记得存userid存进session
            session.setAttribute("user",user.getId());
            return R.success(user);
        }
        return R.error("登录失败");
    }

②用户退出

 @PostMapping("/loginout")
    public R<String> logout(HttpServletRequest request){
        request.getSession().removeAttribute("user");
        return R.success("退出成功");
    }

2.店铺主页有关功能

①回显菜品分类信息

侧边栏需要显示所有菜品信息:

8990608542884f1889579d2a05a15e46.png

 接口代码编写:

    @GetMapping("/list")//用户端左侧栏显示的
    public R<List<Category>> list(Category category){
        //条件构造器
        LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
        //添加条件
        queryWrapper.eq(category.getType() != null,Category::getType,category.getType());
        //添加排序条件
        queryWrapper.orderByAsc(Category::getSort).orderByDesc(Category::getUpdateTime);
        List<Category> list = categoryService.list(queryWrapper);
        return R.success(list);
    }

②点击某一个分类可以查看该分类下的所有菜品

a08aaad281384a5bbf13ef066302297f.png

因为不仅仅需要看菜品的基本信息,还要看它的口味啥的,所以代码需要考虑的很多。其实根据分类id去查询分类名称那几行可以省略,对页面显示没有影响。

 @GetMapping("/list")//用户端的显示菜品分类的请求
    public R<List<DishDto>> list(Dish dish){
        //构造查询条件,首先是根据菜品分类id查找
        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);
        //查到所有的基本Dish列表,但是因为dish实体里边并没有口味信息,所以需要用到DishDto
        List<Dish> list = dishService.list(queryWrapper);
        List<DishDto> dishDtoList = list.stream().map((item) -> {
            DishDto dishDto = new DishDto();
            BeanUtils.copyProperties(item,dishDto);
            Long categoryId = item.getCategoryId();//分类id
            //根据id查询分类对象
            Category category = categoryService.getById(categoryId);
            if(category != null){
                String categoryName = category.getName();
                dishDto.setCategoryName(categoryName);
            }
            //当前菜品的id,需要根据此id去查找相关口味信息
            Long dishId = item.getId();
            LambdaQueryWrapper<DishFlavor> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(DishFlavor::getDishId,dishId);
            //SQL:select * from dish_flavor where dish_id = ?
            List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper);
            //记得查出来之后与之绑定
            dishDto.setFlavors(dishFlavorList);
            return dishDto;
        }).collect(Collectors.toList());
        return R.success(dishDtoList);
    }

③查看套餐详细信息

c572df1194da48e095f1bfd3feb22bdc.png

  @Override
    public List<DishDto> checkSemeal(Long id) {
        //1.根据id去查询相关的菜品
        LambdaQueryWrapper<SetmealDish> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(SetmealDish::getSetmealId,id);
        //2.得到一个只带菜品id和菜品名称的小菜品列表
        List<SetmealDish> list=setmealDishService.list(queryWrapper);
        //3.要把图片啥的都显示出来,所以要返回菜品dto(因为是把菜品的名称、图片、介绍啥的都展示出来)
        //3.1把list里面的关于菜品里的id+名字这两个基本信息先转换成列表,再拷贝出来
        List<DishDto> dishDtoList=list.stream().map((setmealDish)->{//这是一个逐个逐个copy的操作,属于一个循环
            //括号里面是转成一个个的对象,而不是列表
            DishDto dishDto=new DishDto();
            //3.2拷贝一小部分,dishid和名字
            BeanUtils.copyProperties(setmealDish,dishDto);
            //3.3获取dishid,方便从dish表里边查询图片等详细信息
            Long dishId=setmealDish.getDishId();
            Dish dish=dishService.getById(dishId);
            BeanUtils.copyProperties(dish,dishDto);
            //3.4得到一个带图片等详情信息的dishdto
            return dishDto;
        }).collect(Collectors.toList());//循环操作
        //  4.最后返回得到所以菜品dto的列表
        return dishDtoList;
    }

记得是返回一个列表给前端,不然前端收不到信息。 

  //用户端点击图片查看套餐详情
    @GetMapping("/dish/{id}")
    public  R<List<DishDto>> checkSetmeal(@PathVariable Long id){
        //调用一个新的方法//记得定义返回的东西,不然前端收不到yyy
        //而且你返回给前端的东西是List<DishDto>
        List<DishDto> dishDtoList=setmealService.checkSemeal(id);
        return R.success(dishDtoList);
    }

3.购物车有关功能

①添加到购物车

注意事项:记得查看对应用户;要分清除是添加的是普通菜品还是套餐;要注意该菜品/套餐是否已经存在在购物车里,如在+1,不在则save(添加一个新的,记得默认数量是1;

实现类

@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
    @Override
    public void clean() {
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
    
        this.remove(queryWrapper);
    }
}

控制层

  @PostMapping("/add")
    public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){
        log.info("购物车数据:{}",shoppingCart);
        //1.设置用户id,指定当前是哪个用户的购物车数据
        Long currentId = BaseContext.getCurrentId();
        shoppingCart.setUserId(currentId);
        Long dishId = shoppingCart.getDishId();
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,currentId);//条件,具体的值
        if(dishId != null){
            //添加到购物车的是菜品
            queryWrapper.eq(ShoppingCart::getDishId,dishId);
        }else{
            //添加到购物车的是套餐
            queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId());
        }
        //查询当前菜品或者套餐是否在购物车中
        //SQL:select * from shopping_cart where user_id = ? and dish_id/setmeal_id = ?
        ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper);
        if(cartServiceOne != null){
            //如果已经存在,就在原来数量基础上加一
            Integer number = cartServiceOne.getNumber();
            cartServiceOne.setNumber(number + 1);
            //记得保存已经更新的对象(老是忘记)
            shoppingCartService.updateById(cartServiceOne);
        }else{
            //如果不存在,则添加到购物车,数量默认就是一
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            //然后也要记得save这个对象
            shoppingCartService.save(shoppingCart);
            cartServiceOne = shoppingCart;
        }
        //判断好了做好了相应的增加数量/新加入到购物车记得return
        return R.success(cartServiceOne);
    }

 ②减少购物车中的菜品

注意事项:也是要操作正确的用户;记得数量的操作使之不能为负数;查看是普通菜品还是套餐才能正确从不同的表里删除正确的数据;重新设置number值后,记得更新数据库或者直接删除哇。

    @PostMapping("/sub")
    @Transactional
    public R<ShoppingCart> sub(@RequestBody ShoppingCart shoppingCart){
        //1.先获取用户,查看该用户购物车情况
        
        Long userId=BaseContext.getCurrentId();
        LambdaQueryWrapper<ShoppingCart> queryWrapper1 = new LambdaQueryWrapper<>();
        //2.查看菜品id+套餐id//单独减去某一种商品的数量,所以只需要获取一个id就好
        Long dishId = shoppingCart.getDishId();
        Long stemealId=shoppingCart.getSetmealId();
        //3.判断是普通菜品还是套餐//并对这个菜品的数量进行操作
       if (dishId!=null){
           //3.1根据id查到这条数据
           //先要根据这个条件查出这条数据
           //3.2根据前面的条件构造器根据用户id+菜品id查询得到一个完整的购物车数据
           queryWrapper1.eq(ShoppingCart::getDishId,dishId);
           queryWrapper1.eq(ShoppingCart::getUserId,userId);
           //查到这条数据
           shoppingCart=shoppingCartService.getOne(queryWrapper1);
           //3.3获取购物车中该菜品的数量,并将数据减1
           int num1=shoppingCart.getNumber()-1;
           //3.4重新设置这条购物车数据的信息,将数量-1
           shoppingCart.setNumber(num1);
           //3.5根据数量判断是要更新数据库还是直接删除该条数据库记录
           //if (num1>0){
         
           if (shoppingCart.getNumber()>0){
               //更新数据库
               shoppingCartService.updateById(shoppingCart);
           } else  {//数量小于等于0,就从数据库中删除
               shoppingCartService.removeById(shoppingCart.getId());
           }
      
           return R.success(shoppingCart);
       }
        if (stemealId != null)
        {
           LambdaQueryWrapper<ShoppingCart> queryWrapper2=new LambdaQueryWrapper<>();
           queryWrapper2.eq(ShoppingCart::getSetmealId,stemealId);
           queryWrapper2.eq(ShoppingCart::getUserId,userId);
           shoppingCart=shoppingCartService.getOne(queryWrapper2);
           shoppingCart.setNumber((shoppingCart.getNumber()-1));
            if (shoppingCart.getNumber()>0){
                shoppingCartService.updateById(shoppingCart);
            } else{
                shoppingCartService.removeById(shoppingCart.getId());
            } return R.success(shoppingCart);
            }
            return R.error("操作异常");
    }

③清空购物车

 实现类


@Service
public class ShoppingCartServiceImpl extends ServiceImpl<ShoppingCartMapper, ShoppingCart> implements ShoppingCartService {
    @Override
    public void clean() {
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId());
     
        this.remove(queryWrapper);
    }
}

控制器

    @DeleteMapping("/clean")
    public R<String> clean(){
        shoppingCartService.clean();
        return R.success("清空购物车成功");
    }

 ④查看购物车

注意一下返回的对象哦

 @GetMapping("/list")
    public R<List<ShoppingCart>> list(){
        log.info("查看购物车...");
        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ShoppingCart::getUserId,BaseContext.getCurrentId());
        queryWrapper.orderByAsc(ShoppingCart::getCreateTime);
        List<ShoppingCart> list = shoppingCartService.list(queryWrapper);
        return R.success(list);
    }

4.地址模块

①新增地址

    @PostMapping
    public R<AddressBook> save(@RequestBody AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);
        addressBookService.save(addressBook);
        return R.success(addressBook);
    }

② 设置默认地址

 @PutMapping("default")
    public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
        log.info("addressBook:{}", addressBook);
        LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        wrapper.set(AddressBook::getIsDefault, 0);
        //SQL:update address_book set is_default = 0 where user_id = ?
        addressBookService.update(wrapper);
        addressBook.setIsDefault(1);
        //SQL:update address_book set is_default = 1 where id = ?
        addressBookService.updateById(addressBook);
        return R.success(addressBook);
    }

③修改地址

传统两件套:先查后改

  @GetMapping("/{id}")
    public R get(@PathVariable Long id) {
        AddressBook addressBook = addressBookService.getById(id);
        if (addressBook != null) {
            return R.success(addressBook);
        } else {
            return R.error("没有找到该对象");
        }
    }
    @PutMapping
    public R<String> update(@RequestBody AddressBook addressBook){
        //1.获取相关用户id,确保地址簿和用户对应
        Long userId=BaseContext.getCurrentId();
        //2.设置该地址的用户id
        addressBook.setUserId(userId);
        //3.更新操作
        addressBookService.updateById(addressBook);
        return R.success("修改地址成功");
    }

④删除地址

请求长这样:

579979e3033f459c9bd948e104cee833.png

 所以传参要注意一下,不能是id只能是ids,因为地址id是唯一的,所以其实可以不用userid。

  @DeleteMapping
    public R<String> deleteAdd(Long ids){
        //1.获取当前用户id
        Long userId=BaseContext.getCurrentId();
        //2.删除
        addressBookService.removeById(ids);
        return  R.success("删除成功");
    }

⑤查询指定用户的地址

   @GetMapping("/list")
    public R<List<AddressBook>> list(AddressBook addressBook) {
        addressBook.setUserId(BaseContext.getCurrentId());
        log.info("addressBook:{}", addressBook);
        //条件构造器
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
        queryWrapper.orderByDesc(AddressBook::getUpdateTime);
        //SQL:select * from address_book where user_id = ? order by update_time desc
        return R.success(addressBookService.list(queryWrapper));
    }

 ⑥查询默认地址(下单的时候要用)

  @GetMapping("default")
    public R<AddressBook> getDefault() {
        LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
        queryWrapper.eq(AddressBook::getIsDefault, 1);
        //SQL:select * from address_book where user_id = ? and is_default = 1
        AddressBook addressBook = addressBookService.getOne(queryWrapper);
        if (null == addressBook) {
            return R.error("没有找到该对象");
        } else {
            return R.success(addressBook);
        }
    }

 5.订单模块

①用户下单

@Transactional
    public void submit(Orders orders) {
        //获得当前用户id
        Long userId = BaseContext.getCurrentId();
        //查询当前用户的购物车数据
        LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ShoppingCart::getUserId,userId);
        List<ShoppingCart> shoppingCarts = shoppingCartService.list(wrapper);
        if(shoppingCarts == null || shoppingCarts.size() == 0){
            throw new CustomException("购物车为空,不能下单");
        }
        //查询用户数据
        User user = userService.getById(userId);
        //查询地址数据
        Long addressBookId = orders.getAddressBookId();//现在还没拿到,后续步骤采才去拿
        AddressBook addressBook = addressBookService.getById(addressBookId);
        if(addressBook == null){
            throw new CustomException("用户地址信息有误,不能下单");
        }
        long orderId = IdWorker.getId();
        AtomicInteger amount = new AtomicInteger(0);
        //拿到每一道菜品的信息
        List<OrderDetail> orderDetails = shoppingCarts.stream().map((item) -> {
            OrderDetail orderDetail = new OrderDetail();
            orderDetail.setOrderId(orderId);
            orderDetail.setNumber(item.getNumber());
            orderDetail.setDishFlavor(item.getDishFlavor());
            orderDetail.setDishId(item.getDishId());
            orderDetail.setSetmealId(item.getSetmealId());
            orderDetail.setName(item.getName());
            orderDetail.setImage(item.getImage());
            orderDetail.setAmount(item.getAmount());
            //数量*金额
            amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());
            return orderDetail;
        }).collect(Collectors.toList());
        //拿到所有的本单里的购物车里的信息之后,设置这个订单的信息
        orders.setId(orderId);
        orders.setOrderTime(LocalDateTime.now());
        orders.setCheckoutTime(LocalDateTime.now());
        orders.setStatus(2);
        orders.setAmount(new BigDecimal(amount.get()));//总金额
        orders.setUserId(userId);
        orders.setNumber(String.valueOf(orderId));//就是订单编号跟orderId(主键)一模一样
        orders.setUserName(user.getName());
        orders.setConsignee(addressBook.getConsignee());
        orders.setPhone(addressBook.getPhone());
        orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())
                + (addressBook.getCityName() == null ? "" : addressBook.getCityName())
                + (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())
                + (addressBook.getDetail() == null ? "" : addressBook.getDetail()));
        //向订单表插入数据,一条数据
        this.save(orders);
        //向订单明细表插入数据,多条数据
        orderDetailService.saveBatch(orderDetails);
        //清空购物车数据
        shoppingCartService.remove(wrapper);
    }
 @PostMapping("/submit")
    public R<String> submit(@RequestBody Orders orders) {
        log.info("订单数据:{}", orders);
        orderService.submit(orders);
        return R.success("下单成功");
    }

② 用户查看自己的历史订单以及展示详情

实体类OrderDto

@Data
public class OrderDto extends Orders  {
    private List<OrderDetail> orderDetails;
}

 实现类先封装一个根据订单id查找详情的方法

  //重写获取订单详情的方法
    @Override
    public List<OrderDetail> getOrderDetailListByOrderId(Long orderId) {
        LambdaQueryWrapper<OrderDetail> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderDetail::getOrderId,orderId);
        List<OrderDetail> orderDetailsList=orderDetailService.list(queryWrapper);
        return orderDetailsList;
    }

控制层

  //用户端查询自己订单及订单详情
    @GetMapping("/userPage")
    public R<Page> page(int page, int pageSize) {
        log.info("page = {},pageSize = {}", page, pageSize);
        //构造分页构造器
        Page<Orders> pageInfo = new Page<>(page, pageSize);
        Page<OrderDto> pageDto= new Page<>(page,pageSize);
        //一、先查询订单列表
        // 构造条件构造器,先获取用户id,根据用户id查询所有订单
        LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Orders::getUserId, BaseContext.getCurrentId());//获取id的时候有点懵
        //按时间排序
        queryWrapper.orderByDesc(Orders::getOrderTime);
        //返回订单列表分页信息
        orderService.page(pageInfo, queryWrapper);
        //只是订单列表哦,还没详情信息
        System.out.println(pageInfo);
       //二、查询订单详情
       
        //LambdaQueryWrapper<OrderDetail> queryWrapper2=new LambdaQueryWrapper<>();
        //获取属性,放进新建对象
        List<Orders> records=pageInfo.getRecords();
        //转换成list
        //循环拿出所有数据,再最后封装
        List<OrderDto> orderDtoList=records.stream().map((item)->{
            //需要用到dishDto
            OrderDto orderDto= new OrderDto();
            //此时的orderDto对象里面orderDetails属性还是空 下面准备为它赋值
            Long orderId=item.getId();
            List<OrderDetail> orderDetailList=orderService.getOrderDetailListByOrderId(orderId);
            //先将基本信息复制,再设置详情属性
            BeanUtils.copyProperties(item,orderDto);
            orderDto.setOrderDetails(orderDetailList);
            return  orderDto;
                }).collect(Collectors.toList());
        BeanUtils.copyProperties(pageInfo,pageDto,"records");
        pageDto.setRecords(orderDtoList);
        return R.success(pageDto);
    }

③再来一单

5f83dfba88f44b5d939d4684178e8810.png

 传递参数:

32787c7d7b964065923bf2c6e5dcedae.png

 所以在控制层中要注意参数的写法

    //再来一单功能,是把这些菜品给到购物车对象而不是生成新的订单
    // 把购物车清除
    //json格式传过来一个id,所以要拿到这个id去查找订单详情,再把这些信息放入新对象中,重新生成购物车对象即可
    @PostMapping("/again")
    public R<String> Again(@RequestBody Map<String,String> map){
        //返回值是R<String>
        //1.拿到该订单id
        String id=map.get("id");
        Long OrderId=Long.parseLong(id);
        //2.清空购物车//这个方法里是根据当前用户的id去清空的
        shoppingCartService.clean();
        Long userId=BaseContext.getCurrentId();
        //根据订单编号获取菜品信息放到购物车里去
        List<ShoppingCart> shoppingCartList=orderService.getOrderDetailListByOrderId(OrderId).
                stream().map((item)->{
        //把从order表中和order_details表中获取到的数据赋值给这个购物车对象
                ShoppingCart shoppingCart=new ShoppingCart();
                shoppingCart.setUserId(userId);
                shoppingCart.setImage(item.getImage());
            
                Long dishId = item.getDishId();
                Long setmealId = item.getSetmealId();//可以省略
                //我觉得我这个也是对的,先观望一下
                    if (item.getDishId()==null){
                        shoppingCart.setSetmealId(item.getSetmealId());
                    }else {
                        shoppingCart.setDishId(item.getDishId());
                    }
                shoppingCart.setDishId(dishId);
                shoppingCart.setName(item.getName());
                shoppingCart.setNumber(item.getNumber());
                shoppingCart.setAmount(item.getAmount());
                shoppingCart.setDishFlavor(item.getDishFlavor());
         
                shoppingCart.setCreateTime(LocalDateTime.now());
            
                return shoppingCart;//return的是新建的对象
                }).collect(Collectors.toList());
     shoppingCartService.save(shoppingCart);

        shoppingCartService.saveBatch(shoppingCartList);
        return R.success("操作成功");
    }

 三、一些工具类和其他

1.ThreadLocal类

public class BaseContext {
    private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    /**
     * 设置值
     * @param id
     */
    public static void setCurrentId(Long id){
        threadLocal.set(id);
    }
    /**
     * 获取值
     * @return
     */
    public static Long getCurrentId(){
        return threadLocal.get();
    }
}

2.过滤器

1.主要步骤:

3349907c5ca0471ab449c700987c144d.png

 2.写一个类实现Filter接口,并指明过滤器的名称和拦截路径,再编写处理细节。

//指定过滤器的名称filterName = "loginCheckFilter",拦截的路径urlPatterns = "/*"
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
    //路径匹配器,支持通配符
    public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
    @Override//这是重写过滤的方法
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //1、获取本次请求的URI//所以提前要强转
        String requestURI = request.getRequestURI();// /backend/index.html
        log.info("拦截到请求:{}",requestURI);
        //定义不需要处理的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**",
                "/common/**",
                "/user/sendMsg",
                "/user/login"
        };
        //2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);
        //3、如果不需要处理,则直接放行
        if(check){
            log.info("本次请求{}不需要处理",requestURI);
            //这是接口方法里提供的放行方法
            filterChain.doFilter(request,response);
            return;
        }
        //4-1、判断员工登录状态,如果已登录,则直接放行
        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;
        }
        //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;
        }
        log.info("用户未登录");
        //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;

    }

匹配路径的check方法:

  public boolean check(String[] urls,String requestURI){
        for (String url : urls) {
            boolean match = PATH_MATCHER.match(url, requestURI);
            if(match){
                return true;
            }
        }
        return false;
    }

3.编写全局异常处理器 

 1)先创建类,再在类的上面说明要拦截哪些控制器,再编写业务逻辑。

 

public class  GlobalExceptionHandler {
    /**
     * 异常处理方法
     * @return
     */
    //处理器+要处理的异常类型
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());
        if(ex.getMessage().contains("Duplicate entry")){
            String[] split = ex.getMessage().split(" ");
            String msg = split[2] + "已存在";
            return R.error(msg);//这只是一种错误
        }//如不知道直接写未知异常即可
        return R.error("未知错误");
    }
  /**
     * 异常处理方法
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex){
        log.error(ex.getMessage());
        return R.error(ex.getMessage());
    }

4.分页 

1)先写一个MP分页插件的配置类,通过拦截器对象把分页插件创建出来

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

2)写对应controller,使用mp提供的Page类,然后构造分页构造器,条件构造器,添加过滤条件+排序条件,最后执行查询就好啦。

f4eec0e485cc46319b74f94d0d5bafcb.png

   @GetMapping("/page")
    public R<Page> page(int page,int pageSize,String name){
        log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
        //构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);
        //执行查询
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }

5.公共字段自动填充 

4fc27c8353f345118a1d23d5286ccec9.png

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接口

3f614ed8501942069f3d761698307d32.png

 3)重写插入时自动填充方法,设置填充哪些字段并设置值(包括获取id才可以重新设置更新用户);

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

4)更新时插入自动填充方法,设置填充哪些字段并设置值;

  @Override
    public void updateFill(MetaObject metaObject) {
        log.info("公共字段自动填充[update]...");
        log.info(metaObject.toString());
        long id = Thread.currentThread().getId();
        log.info("线程id为:{}",id);
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }

5.文件上传下载

1)前端页面已经提供了 ,使用它的upload组件,文件上传时,需要满足以下条件。

b773cc954de7412b99a5fe7f05365236.png

2) 文件下载有两种形式,一种是直接下载到指定磁盘目录,一种是将文件以流的形式写回浏览器的过程。本项目直接回显到浏览器。

3)上传文件//转储路径可以在配置文件那边定义

获取原始文件名,拿到后缀,随机生成新的文件名,创建目录对象,判断是否存在,不存在则创建目录,然后将文件就转存。

 @PostMapping("/upload")
    public R<String> upload(MultipartFile file){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会删除
        log.info(file.toString());
        //原始文件名
        String originalFilename = file.getOriginalFilename();//abc.jpg
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        //使用UUID重新生成文件名,防止文件名称重复造成文件覆盖
        String fileName = UUID.randomUUID().toString() + suffix;//dfsdfdfd.jpg
        //创建一个目录对象
        File dir = new File(basePath);
        //判断当前目录是否存在
        if(!dir.exists()){
            //目录不存在,需要创建
            dir.mkdirs();
        }
        try {
            //将临时文件转存到指定位置
            file.transferTo(new File(basePath + fileName));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return R.success(fileName);
    }

 4)文件下载

通过输入流读取文件内容;通过输出流写回浏览器,就可以展示图片了。

 @GetMapping("/download")
    public void download(String name, HttpServletResponse response){

        try {
            //输入流,通过输入流读取文件内容
            FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
            //输出流,通过输出流将文件写回浏览器
            ServletOutputStream outputStream = response.getOutputStream();
            //设置相应的文件类型
            response.setContentType("image/jpeg");
            int len = 0;
            byte[] bytes = new byte[1024];//定义一个数组
            while ((len = fileInputStream.read(bytes)) != -1){//将读到的信息都放在数组中
                outputStream.write(bytes,0,len);//将信息写出去
                outputStream.flush();
            }
            //关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
本系统主要功能如下所示: (1) 前台显示功能 用户主要是在前台进行操作,因此该功能是开发一种面向消费者的网上订餐信息系统。类似于网上购物,在这个系统上,客户可以使用网络,查询各种各样的餐饮,比如菜品信息,价格信息,评价信息,还可以在留言区进行留言等。用户通过各种餐饮信息的对比,足不出户就可以选择物美价廉有可口的饭菜。其功能分析如下。 ①注册功能:用户可以注册成为会员,根据消费情况可以变更会员级别,以会员身份登录可以享受会员价消费。 ②登录功能:用户登录系统首页,查看餐饮信息,订购餐饮,浏览网站公告信息等。 ③修改信息功能:注册用户可以对自己的账号及相关信息进行修改,查看等管理。 ④餐饮搜索功能:为用户提供餐饮搜索的功能,输入搜索关键字,即可查出相对应的餐饮信息。 ⑤订餐功能:用户选择餐饮放入订餐车,可以更改订餐车中物品数量,或者取消选择。 ⑥订单管理功能:用户确定订餐,填写送餐地址,提交之后系统生成订单,用户可以查看订单信息。 ⑦留言评价功能:用户可以在留言区进行餐饮的留言评价,还可以查看留言。 ⑧公告浏览功能:用户在前台显示页面可以对系统发布的公告进行浏览。 (2) 后台管理功能 根据餐饮业经营的实际情况来开发本系统的后台管理,通过对经营者具体经营 方式调查分析,可以看出管理员主系统要涉及到一些数据库的逻辑操作和程序应用逻辑操作。具体的功能归纳如下: ①管理员登录:管理员在本地登录,创建新的管理员。 ②餐饮分类管理:管理员对餐饮类别进行添加,更新,删除等管理。 ③餐饮管理:管理员对服务器上的餐饮信息进行添加,查询,修改和删除。比如更新餐饮的图片,价格,分类,描述等。 ④订单管理:管理员对订单进行管理。对网上收到的订单,对其信息进行确认,对其所需餐饮种类、数量进行核对,并及时将不能够提供的服务反馈给用户,并对发布信息进行修改存档。对订单进行查询,修改,删除操作。 ⑤留言管理:管理员对用户对相关食物做的评价信息进行查看、回复、删除等操作。 ⑥会员管理:以数据库的方式存储用户的基本信息、订餐信息、会员信息等,以备下次更好的服务,提高服务整体的人性化。根据会员的消费情况修改会员的级别,以便给老客户提供更多优惠,招来更多回头客。 ⑦公告管理:管理员对餐饮企业的优惠活动等信息及时发布、更新。 将以上的功能制作成网页以后,在其中分别互相建立连接,基本上可以完成网上订餐系统用户和管理者的要求
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值