1、创建数据库
create database ruijiDB;
2、执行sql脚本
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jU037pHh-1659354030972)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_11-30-16.png)]
3、数据表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GvSBlKXB-1659354030974)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220510123310731-63671494.png)]
1、导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.2version>
<relativePath/>
parent>
<groupId>com.fangroupId>
<artifactId>ruiji_take_outartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>ruiji_take_outname>
<description>ruiji_take_outdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.2version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.20version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.76version>
dependency>
<dependency>
<groupId>commons-langgroupId>
<artifactId>commons-langartifactId>
<version>2.6version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.23version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
2、导入配置文件
server:
port: 8080
spring:
application:
#应用名称 可选
name: ruiji_take_out
# 数据源配置
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ruijiDB?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
# mybatis—plus 配置
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
3、配置Application启动类
import lombok.extern.java.Log;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@Slf4j //lombok提供的日志注解
@SpringBootApplication
@MapperScan("com.fan.ruiji_take_out.mapper") //配置接口扫描
public class RuijiTakeOutApplication {
public static void main(String[] args) {
SpringApplication.run(RuijiTakeOutApplication.class, args);
log.info("项目启动成功!");
}
}
启动项目,访问http://localhost:8080/backend/index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUTRm94j-1659354030975)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_11-37-07.png)]
访问成功!
4、使用mybatis-x插件从数据库导入实体类、mapper接口、service接口和实现类、mapper XML文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lzdIJqag-1659354030976)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_11-40-22.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tcgezt4w-1659354030976)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_11-40-57.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U17YFOKE-1659354030977)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_11-41-06.png)]
5、配置前端静态资源路径
创建一个config包,在包下创建WebMvcConfig类,并配置静态资源映射关系
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @title: WebMvcConfug
* @Author lfan
* @Date: 2022/7/30 9:22
* @Version 1.0
*/
@Slf4j
@Configuration
public class WebMvcConfug extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始静态资源映射");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class Result<T> {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
private Map map = new HashMap(); //动态数据
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
public Result<T> add(String key, Object value) {
this.map.put(key, value);
return this;
}
}
员工登录处理逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y9YKJKvj-1659354030978)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220510123427122-1922929796.png)]
package com.fan.ruiji_take_out.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.api.R;
import com.fan.ruiji_take_out.common.Result;
import com.fan.ruiji_take_out.entity.Employee;
import com.fan.ruiji_take_out.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
/**
* @title: EmployeeController
* @Author lfan
* @Date: 2022/7/30 9:57
* @Version 1.0
*/
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
/**
* 员工登录
*
* @param request 用于登录成功保存数据
* @param employee
* @return
*/
@PostMapping("/login")
public Result<Employee> login(HttpServletRequest request, @RequestBody Employee employee) {
//1、将页面提交的密码进行md5加密处理
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
//2、根据页面提交的用户名来查数据库
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", employee.getUsername());
Employee emp = employeeService.getOne(queryWrapper);
//3、如果没有查询到则返回失败结果
if (emp == null) {
return Result.error("登录失败");
}
//4、比对密码,如果不一致则返回失败结果
if (!emp.getPassword().equals(password)) {
return Result.error("登录失败");
}
//5、查看员工状态,如果已禁用状态,则返回员工已禁用结果
if (emp.getStatus() == 0) {
return Result.error("账号已禁用!");
}
//6、登录成功,将用户id存入Session并返回成功结果
request.getSession().setAttribute("employee", emp.getId());
return Result.success(emp);
}
}
/**
* 员工退出
*
* @param request
* @return
*/
@PostMapping("/logout")
public Result<String> logout(HttpServletRequest request) {
//清除session中保存的员工id
request.getSession().removeAttribute("employee");
return Result.success("退出成功");
}
@GetMapping("/page")
过滤器具体的处理逻辑如下:
1、获取本次请求的URI
2、判断本次请求是否需要处理
3、如果不需要处理,则直接放行
4、判断登录状态,如果已登录,则直接放行
5、如果未登录则返回未登录结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RvOGHMVN-1659354030978)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151434661-434791688.png)]
在filter包下创建LoginCheckFilter类
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.api.R;
import com.fan.ruiji_take_out.common.Result;
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;
/**
* @title: LoginCheckFilter
* @Author lfan
* @Date: 2022/7/30 13:58
* @Version 1.0
* 检查用户是否完成登录
*/
@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();
// 定义不需要处理的请求路径
String[] urls=new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
// 2、判断本次请求是否需要处理
boolean check = check(urls, requestURI);
// 3、如果不需要处理,则直接放行
if (check){
log.info("本次请求{}不需要处理",requestURI);
filterChain.doFilter(request,response);
return;
}
// 4、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
// 5、如果未登录则返回未登录结果,通过输出流向客户端页面响应数据
response.getWriter().write(JSON.toJSONString(Result.error("NOTLOGIN")));
}
//路径匹配,检查本次请求是否需要放行
public boolean check(String[] urls, String requestURI){
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match){
return match;
}
}
return false;
}
}
public static final AntPathMatcher PATH_MATCHER=new AntPathMatcher();
该对象可以用来匹配通配符 比如 “/backend/**” 和 “/backend/index.html”
未登录的返回结果是根据前端的响应拦截器来设置的 ==> data.msg === ‘NOTLOGIN’
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PeTJJjwa-1659354030979)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_14-41-18.png)]
后台系统中可以管理员工信息,通过新增员工来添加后台系统用户。点击 添加员工 按钮跳转到新增页面,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UHAi00cA-1659354030980)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_15-38-37.png)]
新增员工,其实就是将我们新增页面录入的员工数据插入到employee表。需要注意,employee表中对username字段加入了唯一约束,因为username是员工的登录账号,必须是唯一的
分析下该功能的执行流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGiN8mED-1659354030980)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151450410-1062740598.png)]
代码编写
/**
* 新增员工
* @param request
* @param employee
* @return
*/
@PostMapping
public Result<String> save(HttpServletRequest request,@RequestBody Employee employee){
log.info("新增员工,员工信息:{}",employee.toString());
//设置初始密码 需要进行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 Result.success("新增员工成功");
}
要注意的问题是 由于员工 username 在数据库中设置了unique 唯一约束
所以在添加员工信息时,如果添加了相同的员工 会抛出异常
java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'heniang' for key 'idx_username'
此时需要进行异常处理 有两种方法
1、在Controller方法中加入try.catch进行异常捕获
2、使用异常处理器进行全局异常捕获(推荐)
第一种需要在每个controller中进行捕获,不推荐
第二种只需要创建一个额外的类进行捕获
在common包下创建GlobalExceptionHanler类 用来处理全局异常
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.sql.SQLIntegrityConstraintViolationException;
/**
* @title: GlobalExceptionHandler
* @Author lfan
* @Date: 2022/7/30 15:26
* @Version 1.0
* 全局异常处理器
*/
@ControllerAdvice(annotations = {RestController.class, Controller.class}) //指定要拦截的类 根据类上的注解来指定
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 异常处理方法
* @param ex
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class) //指定要捕获的具体异常类
public Result<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.info(ex.getMessage());
if (ex.getMessage().contains("Duplicate entry")){//该错误违反唯一约束
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return Result.error(msg);
}
return Result.error("未知错误");
}
}
系统中的员工很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4kKO3b6-1659354030981)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151459085-440937171.png)]
在开发代码之前,需要梳理一下整个程序的执行过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5DJIENjw-1659354030982)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-01-38.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F5RHjCl6-1659354030982)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-02-06.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxLFyOFe-1659354030983)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-01-47.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VguitK6F-1659354030983)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-02-14.png)]
代码开发
配置MP的分页插件
在config包下创建MybatisPlusConfig类
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;
/**
* @title: MybatisPlusConfig
* @Author lfan
* @Date: 2022/7/30 16:07
* @Version 1.0
* 设置mybatis-plus的配置
*/
@Configuration
public class MybatisPlusConfig {
/**
* 配置MP的分页插件
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
分页查询员工
/**
* 分页查询员工
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public Result<Page> page(int page, int pageSize, String name){
log.info("page = {}, pageSize = {}, name = {}",page,pageSize,name);
//创建分页对象
Page pageInfo = new Page(page, pageSize);
//创建wrapper对象
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
//添加过滤条件
queryWrapper.like(!StringUtils.isEmpty(name),"name",name);
//添加排序条件
queryWrapper.orderByDesc("create_time");
//执行查询
pageInfo = employeeService.page(pageInfo,queryWrapper);
return Result.success(pageInfo);
}
功能展示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j1ZOAL2h-1659354030984)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-27-51.png)]
在员工管理列表页面,可以对某个员工账号进行启用或者禁用操作。账号禁用的员工不能登录系统,启用后的员工可以正常登录。
需要注意,只有管理员(admin用户)可以对其他普通用户进行启用、禁用操作,所以普通用户登录系统后启用、禁用按钮不显示。
页面中是怎么做到只有管理员admin能够看到启用、禁用按钮的?
只有管理员登录才能显示启用和禁用按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UbLRBYiz-1659354030984)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151516295-2052598661 (1)].png)
流程分析
1、页面发送ajax请求,将参数(id、 status)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service更新数据
3、Service调用Mapper操作数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BeTckPGJ-1659354030985)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-33-22.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IdoLKoRY-1659354030986)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-07-30_16-33-30.png)]
页面中的ajax请求是如何发送的?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1LMcYWO-1659354030986)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151529589-250427209.png)]
代码开发
根据id修改员工信息
/**
* 修改员工信息
* @param request
* @param employee
* @return
*/
@PutMapping
public Result<String> update(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 Result.success("员工信息修改成功");
}
测试过程中没有报错,但是功能并没有实现,查看数据库中的数据也没有变化。观察控制台输出的SQL:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hujrl97D-1659354030987)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151540882-307959154.png)]
SQL执行的结果是更新的数据行数为0,仔细观察id的值,和数据库中对应记录的id值并不相同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q8cf0lNz-1659354030988)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220511151551563-64038564.png)]
分页查询时服务端响应给页面的数据中id的值为19位数字,类型为long
页面中js处理long型数字只能精确到前16位,所以最终通过ajax请求提交给服务端的时候id就改变了
前面我们已经发现了问题的原因,即js对long型数据进行处理时丢失精度,导致提交的id和数据库中的id不一致。
如何解决这个问题?
我们可以在服务端给页面响应json数据时进行处理,将long型数据统一转为String字符串。
具体实现代码:
在common包下导入JacksonObjectMapper类
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 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;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
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);
}
}
然后在 WebMvcConfig类中扩展mvc的消息转换器
此消息转换器可以将 Java对象转为json数据
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中 放到索引为0的位置(最前面) 可以优先执行自己设置的转换器
converters.add(0,messageConverter);
}
最后测试一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OfhtekOx-1659354030990)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_10-02-11.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w9Y7riFe-1659354030991)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_10-02-41.png)]
禁用成功
执行流程
1、点击编辑按钮时,页面跳转到add.html,并在url中携带参数[员工id]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZkFtXoX-1659354030992)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_10-26-02.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5vuNeo1-1659354030992)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_10-28-07.png)]
2、在add.html页面获取url中的参数[员工id]
3、发送ajax请求,请求服务端,同时提交员工id参数
4、服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
/**
* 根据id获取用户信息
* @param id
* @return
*/
@GetMapping("/{id}")
public Result<Employee> getById(@PathVariable String id){
log.info("根据id获取对象");
Employee emp = employeeService.getById(id);
if (emp != null){
return Result.success(emp);
}
else {
return Result.error("没有查询到该用户信息");
}
}
5、页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
6、点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
7、服务端接收员工信息,并进行处理,完成后给页面响应
8、页面接收到服务端响应信息后进行相应处理
注意:add.html页面为公共页面,新增员工和编辑员工都是在此页面操作,所以该代码部分与之前添加员工代码对应,不需要重写。
/**
* 修改员工信息(修改员工状态,编辑员工)
* @param request
* @param employee
* @return
*/
@PutMapping
public Result<String> update(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 Result.success("员工信息修改成功");
}
修改员工状态,编辑员工进入的都是update方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FsOLGn3E-1659354030993)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_10-37-27.png)]
前面我们已经完成了后台系统的员工管理功能开发,在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段。这些字段属于公共字段,也就是很多表中都有这些字段,如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZijtGZTN-1659354030994)(D:\学习笔记\黑马瑞吉外卖\images\Snipaste_2022-08-01_15-32-41.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNeXsj5Z-1659354030995)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160148850-1381544179.png)]
可以使用MP的公共字段填充功能来简化开发
Mybatis Plus公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码。
实现步骤:
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接口
在common包下创建MyMetaObjectHandler类
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;
/**
* @title: MyMetaObjectHandler
* @Author lfan
* @Date: 2022/8/1 15:25
* @Version 1.0
*/
@Slf4j
@Component
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", new Long(1));
metaObject.setValue("updateUser", new Long(1));
}
//更新时自动填充
@Override
public void updateFill(MetaObject metaObject) {
log.info("公共字段自动填充【update】。。。");
log.info(metaObject.toString());
metaObject.setValue("updateTime", LocalDateTime.now());
//此处为临时设置数据
metaObject.setValue("updateUser", new Long(1));
}
}
前面我们已经完成了公共字段自动填充功能的代码开发,但是还有一个问题没有解决,就是我们在自动填充createUser和updateUser时设置的用户id是固定值,
现在我们需要改造成动态获取当前登录用户的id。
用户登录成功后我们将用户id存入了HttpSession中,现在我从HttpSession中获取不就行了?
注意,我们在MyMetaObjectHandler类中是不能获得HttpSession对象的,所以我们需要通过其他方式来获取登录用户id。
可以使用ThreadLocal来解决此问题,它是JDK中提供的一个类。
在学习ThreadLocal之前,我们需要先确认一个事情,就是客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理
过程中涉及到下面类中的方法都属于相同的一个线程:
1、LoginCheckFilter的doFilter方法
2、EmployeeContraller的update方法
3、MyMetaObjectHandler的updateFill方法
可以在上面的三个方法中分别加入下面代码(获取当前线程id):
long id = Thread.currentThread().getId() ;
log.info("线程id:{}" ,id);
执行编辑员工功能进行验证,通过观察控制台输出可以发现,一次请求对应的线程id是相同的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SkqgFa4M-1659354030996)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160210276-655169945.png)]
什么是ThreadLocal?
ThreadLocal并不是一个Thread,而是Thread的局部变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
ThreadLocal常用方法:
我们可以在LoginCheckFilter的doFilter方法中获取当前登录用户id,并调用ThreadLocal的set方法来设置当前线程的线程局部变量的值(用户id),然后在MyMetaObjectHandler的updateFill方法中调用ThreadLocal的get方法来获得当前线程所对应的线程局部变量的值(用户id)。
实现步骤:
1、编写BaseContext工具类,基于ThreadLocal封装的工具类
/**
* @title: BaseContext
* @Author lfan
* @Date: 2022/8/1 15:50
* @Version 1.0
* 基于ThreadLocal封装的工具类,用于保存和获取当前登录用户的id
*/
public class BaseContext {
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id){
threadLocal.set(id);
}
public static Long getCurrentId(){
return threadLocal.get();
}
}
2、在LogincheckFilter的doFilter方法中调用BaseContext来设置当前登录用户的id
if(request.getSession().getAttribute("employee") != null){
log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
//将当前用户id作为局部变量存放到当前线程中
Long empId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(empId);
filterChain.doFilter(request,response);
return;
}
3、在MyMeta0bjectHandler的方法中调用BaseContext获取登录用户的id
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;
/**
* @title: MyMetaObjectHandler
* @Author lfan
* @Date: 2022/8/1 15:25
* @Version 1.0
*/
@Slf4j
@Component
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());
}
}
·后台系统中可以管理分类信息,分类包括两种类型,分别是菜品分类和套餐分类。当我们在后台系统中添加菜品时需要选择一个菜品分类,当我们在后台系统中添加一个套餐时需要选择一个套餐分类,在移动端也会按照菜品分类和套餐分类来展示对应的菜品和套餐。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZavG8D9-1659354030997)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160234249-1136711537.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pPoQ04Z4-1659354030997)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160238251-250847397.png)]
数据模型
新增分类,其实就是将我们新增窗口录入的分类数据插入到category表,表结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGe1smIs-1659354030998)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160245889-1847592747.png)]
代码开发
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面(backend/page/category/list.html)发送ajax请求,将新增分类窗口输入的数据以json形式提交到服务端
2、服务端Controller接收页面提交的数据并调用Service将数据进行保存
3、Service调用Mapper操作数据库,保存数据
可以看到新增菜品分类和新增套餐分类请求的服务端地址和提交的json数据结构相同,所以服务端只需要提供一个方法统一处理即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9SfYMcRV-1659354030999)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160259222-1818691866.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8TbSoAx3-1659354030999)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160302285-1976698036.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AvDIZuyX-1659354031000)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160307681-1150668982.png)]
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fan.ruiji_take_out.common.Result;
import com.fan.ruiji_take_out.entity.Category;
import com.fan.ruiji_take_out.entity.Employee;
import com.fan.ruiji_take_out.service.CategoryService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
/**
* @title: CategoryController
* @Author lfan
* @Date: 2022/8/1 16:20
* @Version 1.0
*/
@Slf4j
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 新增分类
* @param category
* @return
*/
@PostMapping
public Result<String> save(@RequestBody Category category){
log.info("category :{}",category);
categoryService.save(category);
return Result.success("新增分类成功");
}
系统中的分类很多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般的系统中都会以分页的方式来展示列表数据。
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将分页查询参数(page.pageSize)提交到服务端
2、服务端Controller接收页面提交的数据并调用Service查询数据
3、Service调用Mapper操作数据库,查询分页数据
4、Controller将查询到的分页数据响应给页面
5、页面接收到分页数据并通过ElementUI的Table组件展示到页面上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mPE0zcny-1659354031000)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160317974-925436217.png)]
/**
* 分页查询分类
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public Result<Page> page(int page, int pageSize){
log.info("page = {}, pageSize = {}",page, pageSize);
//创建分页对象
Page pageInfo = new Page(page, pageSize);
//创建wrapper对象
QueryWrapper<Category> queryWrapper = new QueryWrapper<>();
//添加排序条件
queryWrapper.orderByDesc("sort");
pageInfo = categoryService.page(pageInfo,queryWrapper);
return Result.success(pageInfo);
}
在分类管理列表页面,可以对某个分类进行删除操作。需要注意的是当分类关联了菜品或者套餐时,此分类不允许删除。
在开发代码之前,需要梳理一下整个程序的执行过程:
1、页面发送ajax请求,将参数(id)提交到服务端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c2U5xF8D-1659354031001)(D:\学习笔记\黑马瑞吉外卖\images\2592691-20220512160327156-1521388521.png)]
2、服务端Controller接收页面提交的数据并调用Service删除数据
3、Service调用Mapper操作数据库
public interface CategoryService extends IService<Category> {
public void remove(Long id);
}
@Service
public class CategoryServicelmpl 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(setmealLambdaQueryWrapper);
if(count2>0){
//已经关联套餐,抛出业务异常
throw new CustomException("已经关联套餐,不能删除");
}
//正常删除分类
super.removeById(id);
}
}
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
//进行异常处理方法
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
/**
* 删除分类
* @param ids
* @return
*/
@DeleteMapping
public Result<String> delete(Long ids){
log.info("要删除的id为 :{}",ids);
categoryService.remove(ids);
return Result.success("分类信息删除成功");
}
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作
/**
* 修改分类
* @param category
* @return
*/
@PutMapping
public Result<String> update(@RequestBody Category category){
log.info("该分类信息为 :{}",category);
categoryService.updateById(category);
return Result.success("分类修改成功");
}
ambdaQueryWrapper=new LambdaQueryWrapper<>();
//添加查询条件,根据分类id进行查询
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
int count2 = setmealService.count(setmealLambdaQueryWrapper);
if(count2>0){
//已经关联套餐,抛出业务异常
throw new CustomException("已经关联套餐,不能删除");
}
//正常删除分类
super.removeById(id);
}
}
- 定义异常类CustomException
```scala
public class CustomException extends RuntimeException{
public CustomException(String message){
super(message);
}
}
//进行异常处理方法
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandler(CustomException ex){
log.error(ex.getMessage());
return R.error(ex.getMessage());
/**
* 删除分类
* @param ids
* @return
*/
@DeleteMapping
public Result<String> delete(Long ids){
log.info("要删除的id为 :{}",ids);
categoryService.remove(ids);
return Result.success("分类信息删除成功");
}
在分类管理列表页面点击修改按钮,弹出修改窗口,在修改窗口回显分类信息并进行修改,最后点击确定按钮完成修改操作
/**
* 修改分类
* @param category
* @return
*/
@PutMapping
public Result<String> update(@RequestBody Category category){
log.info("该分类信息为 :{}",category);
categoryService.updateById(category);
return Result.success("分类修改成功");
}