瑞吉外卖业务开发

news2024/11/22 22:43:09

数据库

  • 我电脑上的数据库登录指令:mysql -uroot -p123456
  • 常用指令:show databases、user 数据库名、show tables。

创建项目

创建完项目后,要及时检查maven仓库的配置,jdk的配置,项目的编码,如下图。

 配置项目的pom依赖和aplication文件,在启动类中加入lombok的slf4j注解,就可以使用log.info()方法在控制台输出信息了,方便调试。

映射静态资源

  • 问题:如果静态资源直接放入resource目录之下,而不是放在static或者templates目录下面,则项目启动后,浏览器无法直接访问到静态资源。
  • 解决方法,编写WebMvcConfig配置文件,文件所在目录以及内容如下所示。
package com.example.reggie_take_out;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class ReggieTakeOutApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReggieTakeOutApplication.class, args);
        log.info("项目启动成功。。。");
    }

}

 
 

用户登录退出和拦截器功能的实现

用户登录功能

@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("退出成功");
    }

拦截器

拦截器和过滤器各自适用的场景

拦截器通常在业务处理层面进行操作,它们更接近业务逻辑,可以对请求进行细粒度的控制和处理。例如,权限验证是一个常见的业务处理需求,拦截器可以拦截请求并检查用户的权限,以确保只有具有访问权限的用户可以执行相应的操作。另外,日志记录也是拦截器常见的应用场景,通过拦截请求和响应,可以记录请求的细节和响应的结果,方便问题的排查和系统的监控。

过滤器则更多地关注于请求和响应的处理和过滤。它们通常在请求的前后进行操作,用于对请求和响应进行过滤、修改或转换。请求过滤是过滤器的常见应用场景,可以用于对请求进行预处理、验证和过滤,例如检查请求的来源、请求的参数等。同时,过滤器还可以对请求和响应的编码进行转换,以确保请求和响应的正确编码格式。

综上所述,拦截器和过滤器在不同的层面和目的上有所不同,拦截器更偏向于业务处理和控制,而过滤器更专注于对请求和响应的处理和过滤。

//注意在启动类中添加注解 @ServletComponentScan
@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;
        log.info("拦截到请求:{}",request.getRequestURI());

        // 1、获取本次请求的URI
        String requestURI = request.getRequestURI();

        //不需要检查的请求路径
        String[] urls = new String[]{
                "/employee/login",
                "/employee/logout",
                "/backend/**",
                "/front/**"
        };

        // 2、判断本次请求是否需要处理
        boolean check = check(urls, requestURI);

        // 3、如果不需要处理,则直接放行
        if(check == true){
            filterChain.doFilter(request, response);
            return;
        }

        // 4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){
            filterChain.doFilter(request, response);
            return;
        }

        // 5、如果未登录则返回未登录结果,通过输出流的方式向客户端页面响应数据
        response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
        return;
    }

    /**
     * 路径匹配,检查本次请求是否需要放行
     * @param urls
     * @param requestURI
     * @return
     */
    public boolean check(String[] urls, String requestURI){
        for(String url:urls){
            if(PATH_MATCHER.match(url, requestURI) == true ){
                return true;
            }
        }
        return false;
    }
}
由于dofilter的返回类型为void,所以不能通过return R.error("错误信息")向客户端返回信息,可使用response对象向客户端返回信息:
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));

新增员工功能

    @PostMapping
    public R<String> save(HttpServletRequest request, @RequestBody Employee employee){
        log.info("新增员工,员工信息:{}", employee.toString());

        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("新增员工成功");
    }

员工查询

    @GetMapping("/page")
    public R<Page> page(int page, int pageSize, String name){
        log.info("分页查询{} {} {}", page, pageSize, name);

        //构造分页构造器
        Page pageInfo = new Page(page, pageSize);

        //构造条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
        //添加过滤条件
        if(name != null){
            queryWrapper.like(Employee::getName, name);
        }
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        //执行查询
        employeeService.page(pageInfo, queryWrapper);
        return R.success(pageInfo);
    }

需要注意的是,如果没有配置 MyBatis Plus 的分页插件,意味着分页功能将不会启用。 那么调用 employeeService.page(pageInfo, queryWrapper) 方法时,将无法进行分页查询, 而是会返回所有符合条件的结果,而不是按照指定的分页参数进行分页查询。

启用和禁用员工

    /**
     * 根据id修改员工信息
     * @param employee
     * @return
     */
    @PutMapping
    public R<String> updata(HttpServletRequest request, @RequestBody Employee employee){
        log.info(employee.toString());
        long empid = (long)request.getSession().getAttribute("employee");
        employee.setUpdateUser(empid);
        employee.setUpdateTime(LocalDateTime.now());
        employeeService.updateById(employee);
        return R.success("员工信息修改成功");
    }

注意,ID为long类型,有19位,页面中js处理long型数字只能精确到前16位,所以最终通过ajax请求提交给服务端的时候id发生了变化。

解决办法是,将返回给客户端的数据转换为JSON格式,具体做法是在WebMvcConfig配置文件中添加扩展mvc框架的消息转换器,如下所示。

    /**
     * 扩展mvc框架的消息转换器
     * @param converters
     */
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器  底层使用Jackson将Java对象转为json
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到mvc框架的转换器集合中
        converters.add(0, messageConverter);
        super.extendMessageConverters(converters);
    }

公共字段的填充

  • 在实体类的相应字段上加入如下的注解。
    //在插入时生效
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    //在插入和更新时生效
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
  • 在MyMetaObjecthandler类中无法获得HttpSession对象,我们用TreadLocal来解决该问题,他是JDK中提供的一个类。

  •  如下为代码实现。
  • 首先构建基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
    package com.example.reggie_take_out.common;
    
    /**
     * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
     */
    public class BaseContext {
        private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        public static void setCurrentId(Long id){
            threadLocal.set(id);
        }
    
        public static Long getCurrenId(){
            return threadLocal.get();
        }
    }
    
  • 从filter中将用户id存入threadLocal提供的存储空间当中
        // 4、判断登录状态,如果已登录,则直接放行
        if(request.getSession().getAttribute("employee") != null){

            //将用户id存入threadLocal提供的存储空间当中
            BaseContext.setCurrentId((Long)request.getSession().getAttribute("employee"));

            filterChain.doFilter(request, response);
            return;
        }
  • 在MyMetaObjecthandler类中获取用户ID
        /**
         * 更新操作自动填充
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("公共字段自动填充[update]");
            log.info((metaObject.toString()));
            metaObject.setValue("updateTime", LocalDateTime.now());
            metaObject.setValue("updateUser", BaseContext.getCurrenId());
        }

 代码开发的结构

删除分类

删除分类时要注意,判断被删除的分类是否关联了菜品或者套餐,因此就不能在CategoryController直接使用categoryService.removeById( id )对分类进行删除。

在CategoryService接口中定义remove方法,实现关联删除的逻辑业务判断,在CategoryServiceImpl中具体的实现方法如下。

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {

    @Autowired
    private DishService dishService;

    @Autowired
    private SetmealService setmealService;

    /**
     * 根据id进行删除,删除之前要做判断
     * @param id
     */
    @Override
    public void remove(Long id) {
        //查询当前分类是否关联菜品,如果已经关联,抛出一个业务异常
        LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        dishLambdaQueryWrapper.eq(Dish::getCategoryId, id);
        int count1 = dishService.count(dishLambdaQueryWrapper);
        if(count1 >0) {
            throw new CustomException("当前分类下关联了菜品,不能删除");
        }


        //查询当前分类是否关联套餐,如果已经关联,抛出一个业务异常
        LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
        setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId, id);
        int count2 = setmealService.count(setmealLambdaQueryWrapper);
        if(count2 >0) {
            throw new CustomException("当前分类下关联了套餐,不能删除");

        }
        //正常删除业务
        super.removeById(id);
    }
}

自定义异常类的代码如下。

/**
 * 自定义业务异常类
 */
public class CustomException extends RuntimeException {
    public CustomException(String message){
        super(message);
    }
}

全局异常处理器的代码如下。

    /**
     * 异常处理方法
     * @return
     */
    @ExceptionHandler(CustomException.class)
    public R<String> exceptionHandler(CustomException ex){
        log.error(ex.getMessage());

        return R.error(ex.getMessage());
    }

文件上传

    /**
     * 文件上传
     * @param file
     * @return
     */
    @PostMapping("/upload")
    public R<String> upLoad(MultipartFile file){
        //file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件会被删除
        log.info(file.toString());

        //原始文件名
        String originalFilename = file.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));

        //使用UUID重新生成文件名,防止文件名称重复造成覆盖
        String filename = UUID.randomUUID().toString() + suffix;

        //创建一个目录对象
        File dir = new File(basePath);
        if(!dir.exists()){
            dir.mkdir();
        }

        //将临时文件转存到指定位置
        try {
            file.transferTo(new File(basePath+filename));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return R.success(filename);
    }

文件下载

 /**
     * 文件下载
     * @param name
     * @param response
     */
    @GetMapping("/download")
    public void download(String name, HttpServletResponse response){
        //输入流,通过输入流读取文件内容
        try {
            FileInputStream fileInputStream = new FileInputStream(basePath + name);

            //输出流,通过输出流将文件写回浏览器,在浏览器展示图片
            ServletOutputStream outputStream = response.getOutputStream();

            response.setContentType("/image/jpeg");

            //用于存储文件内容的缓冲区。
            int len = 0;
            byte[] bytes = new byte[1024];

            //输入流中读取文件内容,并将其写入输出流,直到文件的所有内容都被读取完毕。
            while( (len = fileInputStream.read(bytes)) != -1){
                //这行代码将缓冲区中的内容写入输出流,并通过flush()方法将数据刷新到浏览器。
                outputStream.write(bytes, 0, len);
                outputStream.flush();
            }

            //关闭资源
            outputStream.close();
            fileInputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

新增菜品

新增菜品,同时插入菜品对应的口味数据,需要操作两张表:dish,dish_flavor。

因此在DishService接口中自定义方法void saveWithFlavor(DishDto dishDto),用于新增菜品,同时插入菜品对应的口味数据。DishServiceImpl实现类中的代码如下。

@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {

    @Autowired
    private DishFlavorService dishFlavorService;

    /**
     * 新增菜品,同时保存相应的口味数据
     * @param dishDto
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDto dishDto) {
        //this.save(dishDto) 将调用 DishServiceImpl 类中的 save 方法,将 dishDto 对象保存到数据库中。
        this.save(dishDto);

        Long id = dishDto.getId();//菜品id

        //菜品口味
        List<DishFlavor> flavors = dishDto.getFlavors();

        //赋值菜品口味的相应id
        flavors = flavors.stream().map((item) -> {
            item.setDishId(id);
            return item;
        }).collect(Collectors.toList());

        dishFlavorService.saveBatch(flavors);
    }
}

注意,saveWithFlavor方法中操作了两张表,因此需要方法上加入@Transactional注解,并且在启动类中加上@EnableTransactionManagement 注解。

菜品展示

问题:页面展示需要分类的名称,但dish表中只有分类的id,因此总体思路是将dish表中的分类id取出来,用分类id在分类表中查询分类名称。

具体步骤:

  • 使用DTO(Data Transfer Object)数据传输对象,用于在不同层之间传输数据。DTO结构如下。
    @Data
    public class DishDto extends Dish {
    
        private List<DishFlavor> flavors = new ArrayList<>();
    
        private String categoryName;
    
        private Integer copies;
    }

    DTO继承自Dish类,并且DTO的categoryName属性可用于前端分类名称的展示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/628581.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据同步智能化!利用ETLCloud自动化流程实现钉钉OA系统数据自动同步至数仓

钉钉数据同步需求 钉钉是一款企业级通讯和协同办公应用软件&#xff0c;钉钉为企业提供包括聊天、通讯录、日程安排、考勤打卡、审批、通知公告、文件共享、会议等功能&#xff0c;很多企业都在使用钉钉。 很多情况下我们需要把钉钉的数据拉取到数据库中&#xff0c;然后再通…

案例分享 | 汽车连接器焊锡质量检测

连接器是电脑、电视、汽车等常见产品中不可缺少的部件&#xff0c;主要由接触件、绝缘体与金属壳体组成&#xff0c;用于传输电流或信号。 汽车连接器约占全球连接器市场四分之一的份额&#xff0c;随着未来汽车产业的持续膨胀&#xff0c;市场前景广阔。连接器产品的微型化、…

RPC通用爬虫

文章目录 RPC通用爬虫一、项目案例二、Rpc原理解析三、Rpc代码分享四、自我总结 RPC通用爬虫 一、项目案例 测试网址: aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL3NlcnZpY2UvYnVsbGV0aW5fbGlzdC5odG1sP3R5cGVhPWI2N2MzYjhhZGJkY2U3NGQ 二、Rpc原理解析 图解关系: ​ 主要包括,爬虫程…

Dockerfile应用的容器化

文章目录 Dockerfile应用的容器化应用的容器化——简介应用的容器化——详解单体应用容器化获取应用代码分析Dockerfile容器化当前应用/构建具体的镜像推送镜像到仓库运行应用程序测试总结 最佳实践利用构建缓存合并镜像 命令总结 Dockerfile应用的容器化 Docker 的核心思想是…

为什么网络安全缺口大,招聘却很少?

2023 年我国网络空间安全人才数量缺口超过了 140 万&#xff0c;就业人数却只有 10 多万&#xff0c;缺口高达了 93%。这里就有人会问了&#xff1a; 1、网络安全行业为什么这么缺人&#xff1f; 2、明明人才那么稀缺&#xff0c;为什么招聘时招安全的人员却没有那么多呢&am…

头部大模型公司进京赶考,向量数据库成为应考神器

日前&#xff0c;由品玩主办的「模型思辨」国内大模型产业生态研讨会在北京举办&#xff0c;Zilliz 与 360 集团、阿里巴巴、昆仑万维等来自大模型产业链的头部公司及投资机构参会&#xff0c;会上 Zilliz 创始人兼首席执行官星爵、360 集团创始人周鸿祎、昆仑万维创始人方汉等…

C++ 类设计的实践与理解

前言 C代码提供了足够的灵活性&#xff0c;因此对于大部分工程师来说都很难把握。本文介绍了写好C代码需要遵循的最佳实践方法&#xff0c;并在最后提供了一个工具可以帮助我们分析C代码的健壮度。 1. 尽可能尝试使用新的C标准 到2023年&#xff0c;C已经走过了40多个年头。新…

如何进行微服务测试?

微服务测试是一种特殊的测试类型&#xff0c;因为它涉及到多个独立的服务。以下是进行微服务测试的一般性步骤&#xff1a; 1. 确定系统架构 了解微服务架构对成功测试至关重要。确定每个微服务的职责、接口、依赖项和通信方式。了解这些信息可以帮助您更好地规划测试用例和测…

day09——线性回归

线性回归 一、什么是线性回归1&#xff0c;定义与公式2&#xff0c;线性回归的特征与目标的关系 二、线性回归的损失和优化原理1&#xff0c;损失函数2&#xff0c;优化算法 三、API四、实操&#xff1a;波士顿房价预测1&#xff0c;数据来源&#xff1a;scikit-learn2&#xf…

Acgis中实现栅格经纬度和行政区关联

写在前面 我是一个Acgis小白&#xff0c;写这篇博客是为了记录完成过程&#xff0c;如果有更高效的办法欢迎分享~ 我用的是Arcgis10.2。 需求描述 目前已有意大利的shp文件&#xff0c;希望将意大利划分成0.1*0.1经纬度的栅格&#xff0c;并且关联每个栅格中心点所属的省份信…

DBeaver复制数据库(数据库表结构以及内容)

一、 首先先建立一个数据库 &#xff08;已有请忽略此步骤&#xff09; &#xff08;名字 字符集等按需要自己填写&#xff09; 二、选择要复制的数据库 2.1右键选择 工具->转储数据库 2.2选择要导出的数据 2.3 选择要导出的路径 2.4 点击开始&#xff0c;等待导出完…

Revit中绘制弯曲的靠背栏杆和生成过梁

一、Revit中怎么绘制弯曲的靠背栏杆 栏杆通常我们见过位于在阳台处&#xff0c;但是在我们的古建筑中很常见到一种靠背栏杆&#xff0c;例如凉亭里面就很常见这种栏杆。那么如何绘制呢? 利用公制栏杆——支柱进行绘制 要运用放样工具进行绘制&#xff0c;设置一个工作平面&…

中国电子学会2023年05月份青少年软件编程Scratch图形化等级考试试卷四级真题(含答案)

2023-05 Scratch四级真题 分数&#xff1a;100 题数&#xff1a;24 测试时长&#xff1a;90min 一、单选题(共10题&#xff0c;共30分) 1. 下列积木运行后的结果是&#xff1f;&#xff08;B&#xff09;&#xff08;说明&#xff1a;逗号后面无空格&#xff09;&#xff…

mysql join 与 拆分成单表查询如何选择

参考以下文章&#xff0c;不错 数据库联表查询时&#xff0c;是直接使用join好还是分别查询到数据后自己处理较好&#xff1f; - 知乎 一&#xff0c;声明 1&#xff0c;数据量 首先场景是多个表数据量比较大&#xff0c;可能达到百万级 2&#xff0c;结论&#xff1a;最…

springboot项目启动指定对应环境的方法

1. 多环境准备 今天教大家一种多环境profile的写法&#xff0c;当然也可创建多个yml文件。如下所示&#xff1a; spring:application:name: cms-discovery-eureka-ha --- # 区分多环境 spring:profiles: peer1 server:port: 9092 eureka:in…

十、ELK安装ElastAlert 2插件飞书机器人告警(docker)

实现效果 1.创建相应挂载目录和文件 可任意位置&#xff0c;挂载对上就行&#xff0c;方便直接在宿主机修改配置。 /data/feishu-alert/config.yaml /data/feishu-alert/rules 2.编写config.yaml配置文件(/data/feishu-alert/config.yaml) #指定告警文件存放目录 rules_fo…

从C语言到C++_16(list的介绍和常用接口函数)

目录 1. list 介绍和简单使用 1.1 list介绍 1.2 list简单接口函数 1.3 push_back 和遍历 1.4 list常规接口函数使用 2. list 的其它接口函数 2.1 splice 接合 2.2 remove 删完一个值 2.3 sort和reverse 本章完。 list是个双向带头循环链表。 带头双向循环链表我们在…

21份软件测试全流程文档模板(标准版)

1、需求说明书 2、功能测试计划 3、功能测试用例 4、业务流程测试用例 5、系统安装配置说明书 6、阶段功能测试报告 7、性能测试计划 8、性能测试用例 9、性能测试报告 10、系统功能测试报告 11、需求变更说明书 12、用户建议说明书 13、验收测试报告 14、产品发布说明书 15、系…

redis下载安装

本文主要介绍如果在Centos7下安装Redis。 1.安装依赖 redis是由C语言开发&#xff0c;因此安装之前必须要确保服务器已经安装了gcc&#xff0c;可以通过如下命令查看机器是否安装&#xff1a; <span style"color:#000000"><span style"background-c…

Matlab论文插图绘制模板第101期—人口金字塔图

在之前的文章中&#xff0c;分享了Matlab双向柱状图的绘制模板&#xff1a; 进一步&#xff0c;再来分享一种特殊的双向柱状图&#xff1a;人口金字塔图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数据代码』已上传资源群中&#xff0c;加群的朋友请自…