瑞吉外卖 基于spring Boot+mybatis-plus(含其他功能)

一、瑞吉外卖项目介绍

1、项目背景介绍

本项目(瑞吉外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括系统管理后台和移动端应用两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的菜品、套餐、订单等进行管理维护。移动端应用主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单等

本项目供分为3期进行开发
第一期
实现基本需求,其中移动端应用通过H5实现,用户可以通过手机浏览器访问;
第二期
针对移动端应用进行改进,使用微信小程序实现,用户使用起来更加方便;
第三期
针对系统进行优化升级,提高系统的访问性能;

2、产品原型介绍

产品原型一款产品成型之前的一个简单框架,就是将页面的排版布局展现出现,使产品的初步构思有一个可视化的展示。通过原型展示,可以更加直观的了解项目的需求和提供的功能。

注意事项:产品原型主要用于展示项目的功能,并不是最终的页面效果

技术选型
在这里插入图片描述

3、功能架构

在这里插入图片描述

4、角色

后台系统管理员

  • 登录后台管理系统,拥有后台系统中的所有操作权限;

后台系统普通员工

  • 登录后台管理系统,对菜品、套餐订单等进行管理;

C端用户

  • 登录移动端应用,可以浏览菜品、添加购物车、设置地址、在线下单等;

二、开发环境搭建

项目架构
在这里插入图片描述

1、数据库

1.1 创建数据库reggie

1.2 导入db_reggie.sql并执行sql

数据表

表名 信息
employee 员工表
category 菜品和套餐分类表
dish 菜品表
setmeal 套餐表
setmeal_dish 套餐菜品关系表
dish_flavor 菜品口味关系表
user 用户表
address_book 地址簿表
shopping_cart 购物车表
orders 订单表
order_detail 订单明细表

2、构件Maven项目

2.1 新建Maven项目

在这里插入图片描述

2.2 导入jar包

druid jar包异常
在这里插入图片描述
解决:更换druid版本号

	<!--父依赖-->
    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.4.RELEASE</version>
    </parent>

    <dependencies>

        <!--spring boot启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--spring boot test-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--web启动依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--MP启动依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--fastJSON-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.1.23</version>
        </dependency>
        <!--commons-lang-->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.6</version>
            </plugin>
        </plugins>
    </build>

2.3 编写配置文件

创建application.yml文件

# 端口号
server:
  port: 8080

spring:
  application:
    # 应用名称,选择型配置
    name: reggie_take_out
  # 数据源
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&userSSL=false
      username: root
      password: 123456
# MP配置
mybatis-plus:
  configuration:
    # 数据库映射 驼峰命名 user_name -> userName
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      # 自动生成id
      id-type: assign_id

2.4 导入静态资源

静态资源映射

直接复制粘贴在resources路径下
在这里插入图片描述
设置静态资源映射

@Configuration
@Slf4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
   

    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
   
        log.info("开始进行静态资源映射");
        registry.addResourceHandler("/backend/**")
                .addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**")
                .addResourceLocations("classpath:/front/");
    }
}

2.5 编写启动类

@SpringBootApplication
@Slf4j
public class ReggieApplication {
   
    public static void main(String[] args) {
   
        log.info("项目启动成功!");
        SpringApplication.run(ReggieApplication.class, args);
    }
}

2.6 测试

在这里插入图片描述

三、后台系统开发

1、登录系统

需求分析
通过访问登录页面http://localhost:8080/backend/page/login/login.html,点击登录按钮时,页面会发送请求login以及提交的参数usernamepassword
在这里插入图片描述
在这里插入图片描述

1.1 用户登录

1、创建实体类

Employee

@Data
public class Employee implements Serializable {
   
    // 序列化id
    private static final long serialVersionUID=1L;
    // 主键
    private Long id;
    // 姓名
    private String name;
    // 用户名
    private String username;
    // 密码
    private String password;
    // 手机号
    private String phone;
    // 性别
    private String sex;
    // 身份证号
    private String idNumber;
    // 状态:0:禁用,1:正常
    private Integer status;
    // 创建时间
    private LocalDateTime createTime;
    // 更新时间
    private LocalDateTime updateTime;
    // 创建人
    private Long createUser;
    // 修改人
    private Long updateUser;

}
2、Dao层

EmployeeMapper

@Mapper
public interface EmployeeMapper extends BaseMapper<Employee> {
   
}
3、Service层

EmployeeService

public interface EmployeeService extends IService<Employee> {
   
}

EmployeeServiceImpl

@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements EmployeeService {
   
}
4、导入通用结果类

由于前端页面需要后端接口返回对应的信息,所以引入R这个通用结果类;
在这里插入图片描述

R

/**
 * 通用结果类
 * @param <T>
 */
@Data
public class R<T> {
   
    // 状态码
    private Integer code;
    // 错误信息
    private String msg;
    // 数据
    private T data;
    private Map map=new HashMap();

    /**
     * 成功时返回
     * @param object
     * @param <T>
     * @return
     */
    public static <T> R<T> success(T object){
   
        R<T> r=new R<>();
        r.data=object;
        r.code=1;
        return  r;
    }

    /**
     * 错误时返回
     * @param msg
     * @param <T>
     * @return
     */
    public static <T> R<T> error(String msg){
   
        R<T> r=new R<>();
        r.msg=msg;
        r.code=0;

        return r;
    }

    public R<T> add(String key,Object value){
   
        this.map.put(key,value);
        return this;
    }
}
5、Controller

EmployeeController

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
   

    @Autowired
    private EmployeeService employeeService;
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public R<String> login(@RequestBody Employee employee){
   
        log.info("employee->{}",employee);

        return null;
    }
}

编写具体实现时,我们需要测试前端数据,后端是否已经接收到;
1.在log.info("employee->{}",employee); 打上断点,运行程序;
2.输入http://localhost:8080/backend/page/login/login.html 进入登录界面点击登录,页面跳转到登录界面,如下所示。
在这里插入图片描述
3.前端登录的账号密码数据已经接收到,可以继续晚上登录方法。

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

EmployeeController

@RestController
@Slf4j
@RequestMapping("/employee")
public class EmployeeController {
   

    @Autowired
    private EmployeeService employeeService;
    @RequestMapping(value = "/login",method = RequestMethod.POST)
    public R<Employee> login(@RequestBody Employee employee, HttpSession session){
   
        log.info("employee->{}",employee);
        // 1.获取页面传递的密码并加密处理
        String password = employee.getPassword();
        password= DigestUtils.md5DigestAsHex(password.getBytes());
        // 2.根据页面提交的username查询数据
        // 2.1 创建条件构造器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        // 2.2 查询条件
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        // 2.3 查询结果
        Employee emp = employeeService.getOne(queryWrapper);

        // 3.如果没有查到就返回登陆失败
        if (emp==null){
   
            return R.error("登陆失败");
        }
        // 4.密码对比
        if (!emp.getPassword().equals(password)){
   
            return R.error("登陆失败");
        }
        // 5.查看员工状态是否可以直接登录  0:禁用 1:正常
        if (emp.getStatus() == 0) {
   
            return R.error("员工已禁用");
        }
        // 6.登陆成功,将员工id存入session
        session.setAttribute("employee",emp.getId());

        return R.success(emp);
    }
}

1.2 用户退出

员工登陆成功后,页面跳转到后台系统首页面index.html,此时会显示当前用户名,如果员工需要退出系统,直接点击右侧的退出按钮即可退出系统,退出系统后页面应转回登陆页面。
在这里插入图片描述
点击后发送logout请求
在这里插入图片描述
代码实现
只需要将当前session里的员工Id清除掉即可,清除后,自动返回index.html页面
在这里插入图片描述

/**
     * 用户退出
     * @return
     */
    @RequestMapping(value = "/logout",method = RequestMethod.POST)
    public R<String> logout(HttpServletRequest request){
   
        log.info("进入退出功能");
        request.getSession().removeAttribute("employee");

        return R.success("退出成功");
    }

1.3 登录功能完善

问题分析
前面的登录功能虽然已经开发完成,但还存在一个问题:

  • 用户不登陆也可以直接访问系统首页面

这种设计并不合理,我们希望看到的效果:

  • 只有登录成功后才可以访问系统中的页面,如果没有登录,则跳转到登录页面;

解决方式:拦截器

代码实现
1、创建自定义过滤器
2、在启动类上加入注解@ServletComponentScan
3、完善过滤器的处理逻辑

@ServletComponentScan注解解析:

  • Servlet可以直接通过@WebServlet注解自动注册
  • Filter可以直接通过@WebFilter注解自动注册
  • Listener可以直接通过@WebListener 注解自动注册

LoginCheckFilter

/**
 * 检查用户是否已经完成登录
 */
@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){
   
            log.info("本次请求不需要处理");
            filterChain.doFilter(request,response);
            return;
        }
        // 4、判断登陆状态,如果已登陆,则直接放行
        if (request.getSession().getAttribute("employee")!=null){
   
            log.info("用户已登录");
            filterChain.doFilter(request,response);
            return;
        }

        // 5、如果未登录则返回未登录结果
        log.info("用户未登录");
        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){
   
            boolean match = PATH_MATCHER.match(url, requestURI);
            if (match){
   
                return true;
            }
        }
        return false;
    }
}

2、员工管理

2.1 新增员工

需求分析
后台系统中可以管理员工信息,通过新增员工信息来添加系统用户。点击【添加员工】按钮跳转到新增页面。
在这里插入图片描述

代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将新增员工页面输入的数据以json的形式提交到服务端;
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存;
3、Service调用Mapper操作数据库,保存数据。

1.查看新增页面请求url
在这里插入图片描述
2.编写Controller

	@RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> add(@RequestBody Employee employee, HttpServletRequest request){
   
        log.info("employee=>{}",employee);
        return null;
    }

log.info("employee=>{}",employee);打上断点,debug运行程序,查看页面提交到后端的数据;

3.新增页面输入数据
在这里插入图片描述
4.查看页面提交的数据
在这里插入图片描述
5.完善Controller代码

	/**
     * 新增员工
     * @param employee
     * @param request
     * @return
     */
    @RequestMapping(value = "",method = RequestMethod.POST)
    public R<String> add(@RequestBody Employee employee, HttpServletRequest request){
   
        log.info("employee=>{}",employee);
        /*
            由于页面提交的属性有限,其他属性还需自己手动添加
            只有name username phone sex idNumber
         */

        // 1.设置初识密码123456(需要md5加密)
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        // 2.获取当前用户id
        Long employeeId = (Long) request.getSession().getAttribute("employee");
        // 3.设置创建时间
        employee.setCreateTime(LocalDateTime.now());
        employee.setUpdateTime(LocalDateTime.now());
        // 4.设置更新时间
        // 5.设置当前用户id
        employee.setCreateUser(employeeId);
        // 6.设置修改用户id
        employee.setUpdateUser(employeeId);

        // 添加用户 Duplicate entry 'zhangsan' for key 'idx_username' username重复会报错
        employeeService.save(employee);

        return R.success("添加成功");
    }

6.测试
由于表中账号字段设置唯一,test表中已经存在,所以报错 500:Duplicate entry 'test' for key 'idx_username' ,只需换一个测试数据,重新输入,后面会对报错进行统一处理。

在这里插入图片描述
再次输入数据提交,测试代码
在这里插入图片描述
7.数据库查看是否新增成功
在这里插入图片描述

全局异常处理
/**
 * 全局异常处理
 */
@Slf4j
@ResponseBody
@ControllerAdvice(annotations = {
   RestController.class, Controller.class}) // 捕捉异常的范围
public class GlobalExceptionHandler {
   

    /**
     * 异常处理方法
     * @param  exception : 违反数据库的唯一约束条件
     * @return
     */
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> exceptionHandler(SQLIntegrityConstraintViolationException exception){
   
        log.error(exception.getMessage());

        if (exception.getMessage().contains("Duplicate entry")){
   
            // Duplicate entry 'test' for key 'idx_username'
            String [] error=exception.getMessage().split(" ");
            // 'test'
            return R.error(error[2]+"重复了");
        }
        return R.error("失败了");
    }
}

功能测试
登陆后,添加一个一个已经存在账号名,看前端页面提示信息,以及看后台是否输出了报错日志;
在这里插入图片描述

2.2 分页查询员工信息

需求分析

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

在这里插入图片描述
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数page、pageSize、name提交到服务端;
2、服务端Controller接受页面提交的数据并调用Service查询数据;
3、Service调用Mappers操作数据库,查询分页数据;
4、Controller将查询到的分页数据响应给页面;
5、页面接收到分页数据并通过ElemenUI的Table组件展示到页面上。

1.查看页面请求
在这里插入图片描述
在这里插入图片描述

2.编写后端接口

	@RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page> page(int page,int pageSize,String name){
   
        log.info("page=>{},pageSize=>{},name=>{}",page,pageSize,name);

        return null;
    }

测试前端数据是否可以接收到
首次进入index.html页面
在这里插入图片描述
利用name进行过滤
在这里插入图片描述
3.完善Controller代码

	/**
     * 分页查询员工信息
     * @param page
     * @param pageSize
     * @param name
     * @return
     */
    @RequestMapping(value = "/page",method = RequestMethod.GET)
    public R<Page<Employee>> page(int page,int pageSize,String name){
   
        log.info("page=>{},pageSize=>{},name=>{}",page,pageSize,name);
        // 构造分页构造器
        Page<Employee> pageInfo = new Page<>(page, pageSize);

        // 构造条件查询器
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        // 添加过滤条件
        queryWrapper.like(!Strings.isEmpty(name),Employee::getName,name);
        // 添加排序条件
        queryWrapper.orderByDesc(Employee::getUpdateTime);

        // 执行查询
        pageInfo=employeeService.page(pageInfo, queryWrapper);

        return R.success(pageInfo);
    }

再次测试

首次查询
在这里插入图片描述
过滤查询
在这里插入图片描述

注意:无论怎么查询,始终共有0条数据

问题分析

  • 没有创建MybatisPlusInterceptor(MyBatisPlus分页拦截器)实例,导致total值一直为0。

解决方法

  • 创建MybatisPlusInterceptor实例。
/**
 * 配置mybatis-plus提供的分页插件拦截器
 */
@Configuration
public class MybatisPlusConfig {
   

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

2.3 启用/禁用员工账号

需求分析
在员工管理列表页面中,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录管理系统,启用后可以正常登录;
需要注意的是:只有管理员(admin)才可以对其他普通用户进行启用/禁用操作,所以普通用户登录系统后启用/禁用不显示
并且如果某个员工账号状态为正常,则按钮显示为禁用,如果员工账号状态为已禁用,则按钮显示为启用
在这里插入图片描述

流程分析
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数id、status提交到服务端;
2、服务端Controller接收页面提交的数据并调用Service更新数据;
3、Service调用Mapper操作数据库。

代码开发
页面上的展示,前端代码已经处理好了,我们只要处理后端即可。
在这里插入图片描述
在这里插入图片描述
1.查看前端代码的接口
在这里插入图片描述

页面携带了两个参数:

  • 当前用户id
  • 当前用户的状态
    在这里插入图片描述

注意:启用/禁用员工账号,本质就是一个更新操作,修改员工状态的方法

2.编写Controller

	@RequestMapping(value = "",method = RequestMethod.PUT)
	public R<String> status(@RequestBody Employee employee){
   

       log.info("员工状态信息=>{}",employee);

       return null;
   }

在这里插入图片描述
在这里插入图片描述

我们发现,当我们进行debug查询时,发现前端传过来的id和我们数据库中的id不一样
原因是:mybatis-plus 对id 使用了雪花算法,所以存入数据库中的id是19长度,但是前端的js只能保证数据的前16位数据的精度,对我们id后面的3位数据进行四舍五入,所以就出现了精度丢失;
就会出现前端传过来的id和数据库中的id不一致,就无法修改到数据库中的信息。

解决方法

自定义消息转换器

由于js对long类型的数据精度会丢失,那么我们就把数据进行转型,我们可以在服务端给页面响应的json格式数

  • 7
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值