外卖项目

项目介绍:

本项目,瑞吉外卖是专门为餐饮企业,餐厅,饭店定制的一款软件产品,包括系统管理,后台和移动端应用两部分,其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品,套餐订单等进行管理,维护。移动端应用主要提供给消费者使用,可以在线浏览菜品,添加购物车,下单等等。
本项目分为三期进行开发。
第一期主要实现基本需求,其中移动端应用通过h5实现。用,用户可以通过手机端浏览器访问。
第二期主要针对移动端应用进行改进,使用微信小程序实现用户使用起来更加方便。
第三期主要针对系统进行优化升级,提高系统的访问性能。

技术选型

用户层:
H5,vue.js,ElementUI,微信小程序
网关层:
Nginx
应用层:
springboot,springmvc,SpringSession,Spring,Swagger,lombok
数据层:
Mysql,Mybatis,Mybatis Plus,Redis
工具:
git,maven,junit

功能架构:

手机号登录,微信登录,地址管理,历史订单,产品规格,购物车,下单,菜品浏览

分类管理,菜品管理,套餐管理,菜品口味管理,员工登录,员工退出,员工管理,订单管理

开发环境的搭建:

1.首先对数据库进行搭建(省略)
2.使用maven进行项目搭建:修改项目的编码,maven仓库的配置,jdk配置等等

  • pom文件:继承spring-boot-starter-parent 版本 2.4.5 配置项目依赖的maven坐标
  • application.yml文件 配置数据库信息 配置Mybatispuls (自动映射实体或属性,去掉下划线驼峰命名,设置主键增长规则)
  • 创建启动类:加上@SpringBootApplication注解 再加上@SLF4j注解 方便调试(lombok提供)

后台开发

处理逻辑如下:
1.将页面提交的密码password进行md5加密
2.根据页面提交的用户名 username查询数据库
3.如果没有查到返回登录失败结果
4.密码比对,如果不一致返回登录失败结果
5.账号密码正确,进行员工状态的查看,如果为已禁用状态,则返回员工已禁用结果
6.登录成功,将员工id存入Session并返回成功结果。
代码:

 @PostMapping("/login")
    public R login(HttpServletRequest request, @RequestBody Employee employee) {
//        1.将页面提交的密码password进行md5加密
        String password = employee.getPassword();
        DigestUtils.md5DigestAsHex(password.getBytes());

//        2.根据页面提交的用户名 username查询数据库
        LambdaQueryWrapper 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);
    }

解决用户不登录也能访问管理页,使用过滤器或拦截器,如果用户完成登录那么继续访问,如果没有则跳转到登录页面。
实现步骤
1.创建自定义过滤器LoginCheckFilter
2.在启动类上加入注解@ServletComponentScan
3.完善过滤器的处理逻辑

过滤器具体的处理逻辑:

(1)获取本次请求的URI

(2)判断本次请求是否需要处理

(3)如果不需要处理直接放行

(4)判断登录状态,如果已登录,直接放行

(5)如果未登录怎返回未登录的结果

用户点击页面中退出按钮,发送post请求,请求地址为/employee/logout,后台只需在controller中创建对应的处理方法即可。
处理逻辑:
1.清理Session中的用户id
2.返回处理结果。

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

执行过程:

  • 点击按钮添加员工输入信息点击保存,发送post请求,请求地址为/employee,参数格式为Json
  • 页面发送ajax请求,将输入的数据已Json的形式提交到服务端
  • 服务端Controller接收页面提交的数据并调用Service将数据进行保存
  • Service调用Mapper操作数据库,保存数据
    *代码:
 @PostMapping
    public R 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.发送ajax请求,将分页查询参数(page、pageSize、name)提交到服务端
2.服务端controller接受页面提交的数据并调用Service查询数据
3.service调用Mapper操作数据库,查询分页数据
4.controller将查询到的数据转成json响应给页面
5.页面接受到分页数据并通过ElementUI的table组件展示到页面上

 @GetMapping("/page")
    public R page(int page,int pageSize,String name) {
        log.info("page = {} ,pageSize = {} ,name = {}", page, pageSize, name);

        // 构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        // 构造条件构造器
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
        // 添加过滤条件
        queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
        //添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);

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

代码开发:
1.点击编辑按钮,页面跳转到add.html,并在url中携带参数(员工id)
2.在add.html页面获取url中的参数
3.发送ajax请求,请求服务端,同时提交员工id参数
4.服务端接受请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
5.页面接受服务端响应的json数据,通过VUE的数据绑定进行员工信息的回显
6.点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7.服务端接受员工信息,并进行处理,完成后给页面响应
8.页面接收端服务端响应信息后进行相应的处理

 @GetMapping("/{id}")
    public R getById(@PathVariable Long id) {
        log.info("根据id查询员工信息。。。");
        Employee employee = employeeService.getById(id);

        return R.success(employee);
    }
@PutMapping
    public R update(HttpServletRequest request,@RequestBody Employee employee) {
        log.info(employee.toString());

        employee.setUpdateTime(LocalDateTime.now());
        employee.setUpdateUser((Long) request.getSession().getAttribute("employee"));
        employeeService.updateById(employee);

        return R.success("员工信息修改成功~");
    }

在开发中发现添加员工时,员工需要设置创建时间,创建人,修改时间,修改人等字段,而编辑员工信息时需要设置修改时间和修改人,这些属于公共字段,很多表中都有这些字段,所以我们能不能在某个地方统一处理,来简化开发呢? 答案就是: 使用mybatisplus提供的公共字段自动填充功能。

实现步骤:
1.在实体类的属性上加入@TableField注解,指定自动填充的策略
2.按照框架要求编写元数据对象处理器,在此类中统一为公共字段赋值,此类需要实现MetaObjectHandler接口

代码实现:部分一(在实体类中加入注解,指定自动填充的策略)

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

代码实现:部分二(实现MetaObjectHandler接口重写两个方法)

/**
 * 自定义元数据处理器
 */
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @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());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        // log.info("公共字段自动填充【update】、、、");
        // log.info(metaObject.toString());
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}

发生问题:我们在使用元数据处理器的时候无法通过使用request得到当前登录者的id,无法添加相应的修改人和添加人的参数,(已知客户端发送的每次http请求,对应在服务端都会分配一个新的线程来处理,这个线程处理的过程中会涉及到登录拦截器,EmployeeController的update方法,MyMetaObjectHandler中insert/update Fill方法,所以他们能够共享一个变量副本)解决方法使用线程ThreadLocal类他有一个静态内部类ThreadLocalMap,通过键值对的方式存储,key为当前线程的ThreadLocal对象,value值存对应线程的变量副本,

代码实现: 部分三(创建工具类,并书写静态方法啊 )


/**
 * 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id 作用范围是一个线程之内
 */
public class BaseContext {
    private static ThreadLocal threadLocal = new ThreadLocal<>();

    public static void setCurrentId(Long id) {
        threadLocal.set(id);
    }

    public static Long getCurrentId() {
        return threadLocal.get();
    }
}

代码实现:部分四(在登录完成检查成功后,使用request.getSession().getAttribute(“employee”); 获取登录者的id)

  // 通过session对象获取当前的用户id,然后通过线程共享给其他的类
            Long empId = (Long) request.getSession().getAttribute("employee");
            BaseContext.setCurrentId(empId);

代码实现:部分五(通过方法BaseContext.getCurrentId()获取当前登录者的id并传入参数(应用于部分二))

  /**
     * 新增分类
     * @param category
     * @return
     */
    @PostMapping
    public R save(@RequestBody Category category) {
        // 控制台打印日志:
        //log.info("category: {}",category);

        categoryService.save(category);
        return R.success("新增成功~");
    }
/**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    @GetMapping("/page")
    public R page(int page, int pageSize) {
        log.info("page = {} ,pageSize = {} ,name = {}", page, pageSize);

        // 构造分页构造器
        Page pageInfo = new Page(page,pageSize);
        // 构造条件构造器
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
        //添加排序条件
        queryWrapper.orderByDesc(Category::getSort);

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

删除分类时因为有菜品分类或者套餐分类,所以分类下有分支的话不能删除,所以我们需要在删除之前进行判断,如果关联了菜品或者套餐将抛出异常,异常为我们自己定义的CustomException继承RuntimeException,在CustomException将异常向上抛,最后在全局异常进行处理。如果两者没有关联,那么则进行正常删除。

  @Override
    public void remove(Long ids) {
        LambdaQueryWrapper dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件
        dishLambdaQueryWrapper.eq(Dish::getCategoryId, ids);
        int count1 = dishService.count(dishLambdaQueryWrapper);
        // 查询当前分类是否关联了菜品,如果已经关联了,抛出一个业务异常
        if (count1 > 0) {
            //已经关联了菜品,抛出一个业务异常
            throw new CustomException("当前分类下关联了菜品,不能删除!!!");
        }
        //查询当前分类是否关联了套餐,如果已经关联了,抛出一个业务异常
        LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Setmeal::getCategoryId, ids);
        int count2 = setmealService.count(lambdaQueryWrapper);
        if (count2 > 0) {
            //已经关联了套餐,抛出一个业务异常
            throw new CustomException("当前分类下关联了套餐,不能删除!!!");
        }

        //正常删除分类
        super.removeById(ids);
    }

文件上传时,对页面的Form表单有如下要求:
method= “post” 采用post方式提交数据
enctype=”multipart/form-data” 采用multipart格式上传文件
type=”file” 使用input的file控件上传

Original: https://www.cnblogs.com/zhangyouren/p/16609608.html
Author: Haziy
Title: 外卖项目

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/622601/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 删除MySQL数据用户

    mysql删除用户的方法: 1、使用”drop user 用户名;”命令删除; 2、使用”delete from user where user…

    数据库 2023年6月14日
    082
  • python中组合数据的操作

    2022-09-26 组合数据类型: 拷贝: deep(深拷贝) shallow(浅拷贝) 区别:例如,文件中有一个指针指向另一块存储空间,如果是深拷贝则将指向的那一块文件内容也全…

    数据库 2023年6月14日
    087
  • 设计 | ClickHouse 分布式表实现数据同步

    作者:吴帆 青云数据库团队成员主要负责维护 MySQL 及 ClickHouse 产品开发,擅长故障分析,性能优化。 在多副本分布式 ClickHouse 集群中,通常需要使用 D…

    数据库 2023年5月24日
    0100
  • MySQL查询性能优化七种武器之链路追踪

    MySQL优化器可以生成Explain执行计划,我们可以通过执行计划查看是否使用了索引,使用了哪种索引? 但我们并不确切地知道为什么使用这个索引。 [En] But we don&…

    数据库 2023年5月24日
    075
  • gh-ost使用问题记录

    因为 pt-osc 对数据库性能影响较大,且容易造成死锁问题,目前我们在线更改表结构都使用 gh-ost 工具进行修改,这里记录一下使用 gh-ost 过程中的问题,以作记录;首先…

    数据库 2023年6月9日
    081
  • my2sql工具之快速入门

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。 GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。 my2sql工具之快速入门 1….

    数据库 2023年5月24日
    095
  • MySQL高可用架构搭建实战

    前言 对于 MySQL 数据库作为各个业务系统的存储介质,在系统中承担着非常重要的职责,如果数据库崩了,那么对于读和写数据库的操作都会受到影响。如果不能迅速恢复,对业务的影响是非常…

    数据库 2023年5月24日
    0111
  • 01-MySQL主从复制

    问题导入 在之前项目的基础功能实现中,后台管理和移动端在进行数据访问的时候,都是直接操作数据库MySQL。此时的系统有且仅有一台MySQL服务器,则可能会出现如下问题 ①、读和写所…

    数据库 2023年5月24日
    075
  • leetcode 669. Trim a Binary Search Tree 修剪二叉搜索树 (简单)

    一、题目大意 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变…

    数据库 2023年6月16日
    088
  • 适用于顺序磁盘访问的1分钟法则

    预备知识梳理 本文中设定 block size 与 page size 大小相等。 什么是 Block 文章的开始先解释一下,磁盘的数据读写是以扇区 (sector) 为单位的,而…

    数据库 2023年6月14日
    099
  • 实现线程的两种方式

    实现Runnable接口如果当前类 不仅要继承其他类( 非Thread类), 还要实现多线程,那么 只能通过当前类实现 Runnable接口来 创建Thread类对象。 实现Run…

    数据库 2023年6月16日
    0116
  • CPU 是如何与内存交互的

    这篇文章主要整理了一下计算机中的内存结构,以及 CPU 是如何读写内存中的数据的,如何维护 CPU 缓存中的数据一致性。什么是虚拟内存,以及它存在的必要性。如有不对请多多指教。 概…

    数据库 2023年6月14日
    095
  • 关于CATALINA_HOME 和 CATALINA_BASE 的区别

    以下内容从官方复制出来的* 这些是一些重要的tomcat目录: 在整个文档中,都引用了以下两个属性: 默认情况下,CATALINA_HOME和CATALINA_BASE指向同一目录…

    数据库 2023年6月11日
    095
  • MySQL的插入性能优化

    MySQL的插入性能优化 修改系统变量的方法 一、通过编辑ini配置文件进行修改; 二、通过输入sql命令进行修改; 查询和修改系统变量; 如果要修改全局变量, 必须要显示指定&#…

    数据库 2023年6月14日
    091
  • 量子物理

    今天刷了YouTube的量子物理了解到了量子物理的发展史从微观到相对论从原子核到量子纠缠何其快哉 物理学:经典物理,量子物理。经典物理:万有引力量子物理:相对论 Original:…

    数据库 2023年6月11日
    080
  • 「萌新指南」SOA vs. 微服务:What’s the Difference?

    实话实说,在我还没有实习之前,我是连 SOA 是啥都不知道的,只听说过微服务,毕竟微服务实在太火了,想不知道都难,我觉得实习的时候肯定也是微服务,进组之后发现是 SOA 架构,当时…

    数据库 2023年6月6日
    0109
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球