栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot实战-瑞吉外卖系统(1)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot实战-瑞吉外卖系统(1)

1.搭建数据库环境

创建数据库

导入sql文件

2.Maven项目搭建

导入依赖

pom.xml



  4.0.0
  
    org.springframework.boot
    spring-boot-starter-parent
    2.4.5
     
  
  org.example
  reggie
  1.0-SNAPSHOT

  
    8
    8
  

  

    
      org.springframework.boot
      spring-boot-starter
    

    
      org.springframework.boot
      spring-boot-starter-test
      test
    

    
      org.springframework.boot
      spring-boot-starter-web
      compile
    

    
      com.baomidou
      mybatis-plus-boot-starter
      3.4.2
    

    
      org.projectlombok
      lombok
      1.18.20
    

    
      com.alibaba
      fastjson
      1.2.76
    

    
      commons-lang
      commons-lang
      2.6
    

    
      mysql
      mysql-connector-java
      runtime
    

    
      com.alibaba
      druid-spring-boot-starter
      1.1.23
    

  

  
    
      
        org.springframework.boot
        spring-boot-maven-plugin
        2.4.5
      
    
  

配置application.yml

server:
  port: 8080
spring:
  application:
    name: reggie
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
      username: root
      password: 128730
mybatis-plus:
  configuration:
    #在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 主键生成策略
      id-type: ASSIGN_ID

ReggieApplication 项目启动测试

package com.reggie.takeout;

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

@Slf4j      //打印日志
@SpringBootApplication
public class ReggieApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class, args);
        log.info("项目启动成功...");
    }
}
3.静态资源导入

设置静态资源访问--映射路径 com.reggie.takeout.config

WebMvcConfig

package com.reggie.takeout.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("静态资源开始映射...");
        registry.addResourceHandler("/backend
@Data
public class Employee{

    private static final long serialVersionUID = 1L;

    private Long id;

    private String username;

    private String name;

    private String password;

    private String phone;

    private String sex;

    private String idNumber;

    private Integer status;

    @TableField(fill = FieldFill.INSERT)
    private String createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

}

mapper

package com.reggie.takeout.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.reggie.takeout.pojo.Employee;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface EmployeeMapper extends BaseMapper {
}

service和serviceImpl

package com.reggie.takeout.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.reggie.takeout.pojo.Employee;

public interface EmployeeService extends IService {
}
package com.reggie.takeout.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reggie.takeout.mapper.EmployeeMapper;
import com.reggie.takeout.pojo.Employee;
import com.reggie.takeout.service.EmployeeService;
import org.springframework.stereotype.Service;

@Service
public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {

}

controller

package com.reggie.takeout.conrtoller;

import com.reggie.takeout.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
    @Autowired
    private EmployeeService employeeService=null;

    @PostMapping("/login")
    public R login(HttpServletRequest request, @RequestBody Employee employee){
        //将页面提交的密码进行MD5加密
        String password = employee.getPassword();
        password= DigestUtils.md5DigestAsHex(password.getBytes());
        //根据用户名查数据库(查不到返回结果)
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername,employee.getUsername());
        Employee emp = employeeService.getOne(queryWrapper);
        //查不到用户名 返回失败
        if (emp==null){
            return R.error("登录失败");
        }
        //比对密码(密码错误返回结果)
        if (!emp.getPassword().equals(password)){
            return R.error("登录失败");
        }
        //查询员工状态,员工状态禁用下不可登录
        if (emp.getStatus()==0){
            return R.error("账户已锁定");
        }
        request.getSession().setAttribute("employee",emp.getId());
        return R.success(emp);
    }
}

 配置返回通用结果类

此类是个通用结果类,服务端响应的所有结果最终都会包装成此种类型返回给前端

package com.reggie.takeout.common;

import lombok.Data;
import java.util.HashMap;
import java.util.Map;



@Data
public class R {

    
    private Integer code;

    
    private String msg;

    
    private T data;

    
    private Map map = new HashMap();

    public static  R success(T object) {
        R r = new R();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static  R error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}
退出功能

点击退出 

 退出 移除session

    //退出
    @PostMapping("/logout")
    public R loginOut(HttpServletRequest request){
        request.getSession().removeAttribute("employee");
        return R.success("用户退出成功");
    }

分析

点击登录

 

退出之后 userInfo消失

登录过滤器
步骤:
1.创建自定义过滤器LoginCheckFilter 加入注解@WebFilter
2.在SpringBoot启动类加入注解@ServletComponentScan
3.完善过滤器的处理逻辑
    获取本次处理请求的URL
    判断本次请求是否需要处理
    若不需要,直接放行
    判断登陆状态,若已经登陆直接放行
    若未登陆则返回未登陆结果

LoginCheckFilter

package com.reggie.takeout.filter;

import com.alibaba.fastjson.JSON;
import com.reggie.takeout.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.AntPathMatcher;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "
    public boolean check(String urls[],String requestURL){
        for (String url:urls){
            boolean match = PATH_MATCHER.match(url, requestURL);
            if (match){
                return true;
            }
        }
        //如果遍历完 没有返回true 证明没有匹配上 返回false
        return false;
    }
}

前端 request.js通过匹配“NOTLOGIN”进行响应 进行页面跳转

5.新增员工
需求:添加员工信息到员工表 用户名username唯一
步骤:
1.页面发送ajax请求,将新增员工页面输入的数据以json的形式提交到服务器
2.服务端Controller接受数据并调用service,Service调用Mapper操作数据库,保存数据
    @PostMapping
    public R addEmployee(HttpServletRequest request,@RequestBody Employee employee){
        //设置默认密码 使用md5加密
        employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
        //设置创建时间 更新时间
        employee.setCreateTime(String.valueOf(LocalDateTime.now()));
        employee.setUpdateTime(String.valueOf(LocalDateTime.now()));
        //设置创建者 修改者
        Long userId = (Long) request.getSession().getAttribute("employee");
        employee.setCreateUser(userId);
        employee.setUpdateUser(userId);
        //调用service
        employeeService.save(employee);
        log.info("新增员工信息{}",employee.toString());
       return R.success("添加成功");
    }

全局异常捕获

由于用户名设置了主键唯一,若员工用户名相同就会报错,解决报错有两种方式。

        1.try catch捕获

        2.全局的异常捕获

第一种方式后续捕获类似异常需要大量的重复代码,所以使用第二种

GlobalExceptionHandler

package com.reggie.takeout.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.sql.SQLIntegrityConstraintViolationException;


@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R exceptionHandler(SQLIntegrityConstraintViolationException exception){
        log.info(exception.getMessage());
        //错误信息 Duplicate entry 'guwen' for key 'idx_username'
        if (exception.getMessage().contains("Duplicate entry ")){
            String[] split = exception.getMessage().split(" ");//以空格分隔开
            //Duplicate entry 'guwen' for key 'idx_username'对应
            //0          1       2     3   4        5
            String msg="用户"+split[2]+"已存在";
            return R.error(msg);
        }
        return R.error("未知错误");
    }
}

测试

 6.员工信息展示 分析

 

步骤:
1.页面发送Ajax请求,将分页查询参数(page,pagesize,name)提交到服务端
2.服务端Controller接收页面提交的数据并调用查询的数据
3.Service调用Mapper操作数据库,查询分页数据
4.Controller将查询到的分页数据响应到页面
5.页面接收到分页的数据并通过ElementUI的Table组件展示到页面上
配置Mybatis-plus分页插件
package com.reggie.takeout.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
controller层
    @GetMapping("/page")
    public R page(int page, int pageSize, String name){
        log.info("page={},pageSize={},name={}",page,pageSize,name);
        //1.构造分页构造器
        Page pageInfo = new Page(page, pageSize);
        //2.构造条件
        LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.hasText(name),Employee::getName,name);
        //3.添加排序 按照账户创建时间排序
        queryWrapper.orderByAsc(Employee::getCreateTime);
        //4.执行查询
        employeeService.page(pageInfo,queryWrapper);
        return R.success(pageInfo);
    }
启用禁用员工账户

管理员可以对普通用户进行账户启用和禁用,普通用户无相关权限,所以对普通用户不显示启用禁用按钮

页面初始化时获取账户用户名

created() {
          this.init()
          this.user = JSON.parse(localStorage.getItem('userInfo')).username
        },

判断登录账户是否是管理员 

            
              {{ scope.row.status == '1' ? '禁用' : '启用' }}
            

向后端传递json数据,将要启用禁用的员工id与状态传递给后端

          //状态修改
          statusHandle (row) {
            this.id = row.id
            this.status = row.status
            this.$confirm('确认调整该账号的状态?', '提示', {
              'confirmButtonText': '确定',
              'cancelButtonText': '取消',
              'type': 'warning'
              }).then(() => {
              enableOrDisableEmployee({ 'id': this.id, 'status': !this.status ? 1 : 0 }).then(res => {
                console.log('enableOrDisableEmployee',res)
                if (String(res.code) === '1') {
                  this.$message.success('账号状态更改成功!')
                  this.handleQuery()
                }
              }).catch(err => {
                this.$message.error('请求出错了:' + err)
              })
            })
          },
// 修改---启用禁用接口
function enableOrDisableEmployee (params) {
  return $axios({
    url: '/employee',
    method: 'put',
    data: { ...params }
  })
}

分析:

1. 页面发送ajax请求,将参数(id、status)提交到服务端
2. 服务端Controller接收页面提交的数据并调用Service更新数据
3. Service调用Mapper操作数据库

 Controller

    
    @PutMapping
    public R update(HttpServletRequest request,@RequestBody Employee employee){//if (String(res.code) === '1') 返回json
        //获取员工id
        Long empId = (Long) request.getSession().getAttribute("employee");
        //更新 修改者和修改时间
        employee.setUpdateUser(empId);
        employee.setUpdateTime(String.valueOf(LocalDateTime.now()));
        //执行修改
        employeeService.updateById(employee);
        return R.success("信息修改成功");
    }

问题:

 数据库中的id与前端传递的id不相同,是因为涉及一个JS的精度问题,JS识别Long类型只精确到16位,而ID有19位,导致ID精度丢失。

代码修复

步骤:
1.使用JacksonObjectMapper对JSON数据进行转换
2.在WebConfig配置类中扩展SringMVC的消息转换器,镜像Java对象到JSON数据的转换

 JacksonObjectMapper

package com.reggie.takeout.common;


import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.springframework.stereotype.Component;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;


@Component
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));

        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

将id更改为字符串,就不会损失精度,并且修改时间格式 

WebMvcConfig

    
    @Override
    protected void extendMessageConverters(List> converters) {
        log.info("扩展消息转换器...");
        //创建消息转换器
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        //设置对象转换器 底层使用Jackson将java对象转为json
        mappingJackson2HttpMessageConverter.setObjectMapper(new JacksonObjectMapper());
        //将上面的消息转换器对象追加到MVC框架的转换器集合中
        converters.add(0,mappingJackson2HttpMessageConverter);//添加索引,使优先级最高,不然还是会默认使用自带的转换器
    }
7.编辑员工信息

分析:

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

created() {
          this.id = requestUrlParam('id')
          this.actionType = this.id ? 'edit' : 'add'
          if (this.id) {
            this.init()
          }
        },
//获取url地址上面的参数
function requestUrlParam(argname){
  var url = location.href
  var arrStr = url.substring(url.indexOf("?")+1).split("&")
  for(var i =0;i
// 修改页面反查详情接口
function queryEmployeeById (id) {
  return $axios({
    url: `/employee/${id}`,
    method: 'get'
  })
}
员工信息回显

Controller

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

由于修改和添加都是共用了add.html页面,所以修改操作只需要根据id查找到员工信息然后回显到前端即可,保存功能复用了上面的update方法

8.公共字段自动填充

新增员工时需要设置时间,创建人,修改时间,修改人等字段,在编辑员工的时候也要设置这些字段,这些属于公共的字段可以使用MybatisPlus提供的公共字段填充功能,避免代码重复

步骤:
1.实体类的属性上加入@TableField注解,指定自动填充的策略
2.按照框架编写元数据对象处理器,同一字段赋值,实现MetaObjectHandler接口
3.使用ThreadLocal解决无法获取session中的员工ID的问题
    3.1编写BaseContext工具类,基于ThreadLocal封装的工具类
    3.2在LoginCheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
    3.3在MyMetaObjectHandler的方法中调用BaseContext获取登录用户的id

ThreadLocal

ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
 

我们可以在LogainCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值用户ID,然后使用MyMetaObjectHandler的updateFill方法来获取用户ID

实体类

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

MyMetaObjectHandler 

package com.reggie.takeout.common;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("公共字段insert自动填充");
        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自动填充");
        metaObject.setValue("updateTime",LocalDateTime.now());
        metaObject.setValue("updateUser",BaseContext.getCurrentId());
    }
}

BaseContext

package com.reggie.takeout.common;


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

CheckLoginFilter

        //判断登陆状态,若已经登陆直接放行 获取session
        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;
        }

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/999274.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号