问题分析:前面的登陆存在一个问题,如果用户不进行登陆,直接访问系统的首页,照样可以正常访问,这种设计是不合理的,我们希望看到的效果是只有完成了登陆后才可以访问系统中的页面,如果没有登陆则跳转到登陆页面;
那么如何实现?
答案就是使用过滤器或者是拦截器,在拦截器或者是过滤器中判断用户是否已经完成了登陆,如果没有登陆则跳转到登陆页面;
代码实现:这里使用的是过滤器;
①创建自定义过滤器LongCheckFilter
package com.itheima.reggie.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
log.info("拦截到请求:{}",httpServletRequest.getRequestURI());
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
②在启动类加上注解@ServletComponentScan
然后先测试一下过滤器能不能生效,具体的逻辑等下再书写;发送请求,看后台能不能打印拦截的信息:
package com.itheima.reggie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
@Slf4j
@SpringBootApplication
@ServletComponentScan
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class,args);
log.info("项目启动成功...");
}
}
③完善过滤器的处理逻辑
过滤器具体的处理逻辑如下:
)
具体逻辑的代码实现:
package com.itheima.reggie.filter;
import com.alibaba.fastjson.JSON;
import com.itheima.reggie.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;
@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 {
//对请求和响应进行强转,我们需要的是带http的
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURL = request.getRequestURI();
//定义不需要处理的请求路径 比如静态资源(静态页面我们不需要拦截,因为此时的静态页面是没有数据的)
String[] urls = new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
//做调试用的
//log.info("拦截到请求:{}",requestURL);
//2、判断本次请求是否需要处理
boolean check = check(urls, requestURL);
//3、如果不需要处理,则直接放行
if(check){
//log.info("本次请求{}不需要处理",requestURL);
filterChain.doFilter(request,response);
return;
}
//4、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
//log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
return;
}
//log.info("用户未登录");
//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) {
//把浏览器发过来的请求和我们定义的不拦截的url做比较,匹配则放行
boolean match = PATH_MATCHER.match(url, requestURI);
if(match){
return true;
}
}
return false;
}
}
功能测试: 发起几个请求看看后台的输出,和能不能访问到资源里面的数据,和能不能跳转,注意,上面的后台日志代码已经被注释,需要在后台看到日志的话,需要把注释去掉;
数据模型:
新增员工,其实就是将我们的新增页面录入的员工数据插入到employee表;注意:employee表中对username字段加入了唯一的约束,因为username是员工的登陆账号,必须是唯一的!
employee表中的status字段默认设置为1,表示员工状态可以正常登陆;
代码开发:
在开发代码之前,需要梳理一下整个程序的执行过程:
/**
* 新增员工
* @param employee
* @return
*/
@PostMapping()//因为请求就是 /employee 在类上已经写了,所以咱俩不用再写了
public R<String> save(HttpServletRequest request,@RequestBody Employee employee){
//对新增的员工设置初始化密码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); //创建人的id,就是当前用户的id(在进行添加操作的id)
employee.setUpdateUser(empId);//最后的更新人是谁
//mybatis提供的新增方法
employeeService.save(employee);
return R.success("新增员工成功");
}
功能测试:登陆之后,点击添加,然后确认,然后去数据库看一下新增数据成功没,新增成功,那就表示代码可以执行; 注意:但是因为我们把username设置为唯一索引,所以下次再新增用户的时候,就会出现异常,这个异常是MySQL数据库抛出来的;
解决bug:
上面的程序不好,不好在 我写一个save 可以这么保存,但是未来越来越多的代码 都会面临这样的问题,那就会写很多的try
和catch
。所以,我现在利用全局异常捕获这个异常。
这个全局异常捕获写在common包下;
package com.itheima.reggie.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}) //表示拦截哪些类型的controller注解
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理SQLIntegrityConstraintViolationException异常的方法
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandle(SQLIntegrityConstraintViolationException exception){
log.error(exception.getMessage()); //报错记得打日志
if (exception.getMessage().contains("Duplicate entry")){
//获取已经存在的用户名,这里是从报错的异常信息中获取的
String[] split = exception.getMessage().split(" ");
String msg = split[2] + "这个用户名已经存在";
return R.error(msg);
}
return R.error("未知错误");
}
}
功能测试:登陆后,添加一个一个已经存在账号名,看前端页面提示的是什么信息,以及看后台是否输出了报错日志;
需求分析:系统中的员工比较多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般都系统中都会以分页的方式来展示列表数据。
流程分析:
在开发代码之前,需要梳理一下整个程序的执行过程:
Java代码:
//配置mybatis-plus的分页插件
package com.itheima.reggie.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;
/**
* 配置mybatis-plus提供的分页插件拦截器
*/
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
/**
* 员工信息分页
* @param page 当前页数
* @param pageSize 当前页最多存放数据条数,就是这一页查几条数据
* @param name 根据name查询员工的信息
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
//这里之所以是返回page对象(mybatis-plus的page对象),是因为前端需要这些分页的数据(比如当前页,总页数)
//在编写前先测试一下前端传过来的分页数据有没有被我们接受到
//log.info("page = {},pageSize = {},name = {}" ,page,pageSize,name);
//构造分页构造器 就是page对象
Page pageInfo = new Page(page,pageSize);
//构造条件构造器 就是动态的封装前端传过来的过滤条件 记得加泛型
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper();
//根据条件查询 注意这里的条件是不为空
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
//添加一个排序条件
queryWrapper.orderByDesc(Employee::getUpdateTime);
//执行查询 这里不用封装了mybatis-plus帮我们做好了
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
功能测试:
分页的三个时机:
①用户登录成功时,分页查询一次
②用户使用条件查询的时候分页一次
③跳转页面的时候分页查询一次
需求分析:
在员工管理列表页面中,可以对某个员工账号进行启用或者是禁用操作。账号禁用的员工不能登陆系统,启用后的员工可以正常登陆;
需要注意的是:只有管理员(admin用户)才可以对其他普通用户进行启用操作,禁用操作,所以普通用户登录系统后启用,禁用按钮不显示;
并且如果某个员工账号的状态为正常,则按钮显示为’‘禁用’,如果员工账号状态为已禁用,则按钮显示为“启用”。
普通员工登录系统后,启用,禁用按钮不显示;
代码开发:
注意:这里修改状态码要反着来,因为正常的用户你只能把它设置为禁用;已经禁用的账号你只能把它设置为正常
流程分析:
在开发代码之前,需要梳理一下整个程序的执行过程:
注意:启用,禁用的员工账号,本质上就是一个更新操作,也就是对status状态字段进行修改操作;
在controller中创建update方法,此方法是一个通用的修改员工信息的方法,因为status也是employee中的一个属性而已;这里使用了动态SQL的功能,根据具体的数据修改对应的字段信息;
/**
* 根据id修改员工信息
* @param employee
* @return
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
log.info(employee.toString());
Long empId = (Long)request.getSession().getAttribute("employee");
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(empId);
employeeService.updateById(employee);
return R.success("员工信息修改成功");
}
功能测试:测试的时候我们发现出现了问题,就是我们修改员工的状态,提示信息显示修改成功,但是我们去数据库查验证的时候,发现员工的状态码压根就没有变化,这是为什么呢?
仔细观察id后,我们会发现后台的SQL语句使用的id和数据库中的id是不一样的!
原因是:mybatis-plus
对id
使用了雪花算法,所以存入数据库中的id是19为长度,但是前端的js只能保证数据的前16位的数据的精度,对我们id后面三位数据进行了四舍五入,所以就出现了精度丢失;就会出现前度传过来的id和数据里面的id不匹配,就没办法正确的修改到我们想要的数据;
当然另一种解决bug的方法是:关闭mybatis-plus的雪花算法来处理ID,我们使用自增ID的策略来往数据库添加id就行;
代码bug修复:
思路:既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端(Java端)给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串;
代码实现步骤:
消息转换器
,在此消息转换器中使用提供的对象转换器
进行Java对象到json数据的转换步骤一:自定义消息转换类
package com.itheima.reggie.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 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 配置类中扩展spring mvc 的消息转换器,在此消息转换器中使用spring提供的对象转换器进行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框架的转换器集合中
//转换器是有优先级顺序的,这里我们把自己定义的消息转换器设置为第一优先级,所以会优先使用我们的转换器来进行相关数据进行转换,如果我们的转换器没有匹配到相应的数据来转换,那么就会去寻找第二个优先级的转换器,以此类推
converters.add(0,messageConverter);
}
然后启动程序,使用f12查看服务器响应到浏览器的用户id是不是变成了字符串,和数据库中是否相对应;
发现对应,即消息转换器配置成功;
然后再去测试 启用与禁用 员工账号这个功能,发现操作更新成功,并且数据库修改成功;
需求分析:
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
注意: add.html页 面为公共页面,新增员工和编辑员工都是在此页面操作
数据回显后端代码:其实主要逻辑在前端。。。。。
/**
* 根据前端传过来的员工id查询数据库进行数据会显给前端
* @param id
* @return
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
if (employee != null){
return R.success(employee) ;
}
return R.error("没有查询到该员工信息");
}
修改回显数据后,点击保存,会发送一个update的请求给后端,前面我们已经写了这个update的controller,所以只需要在前端跳转发请求就行;这样就实现了方法的复用,减少了代码量;
功能测试:自己测试编辑,看能不能数据回显,可不可以修改成功,修改后数据库的数据有没有跟着变化;
问题分析:
把相关的注解加在需要mybatis-plus自动帮我们填充的字段上面
@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;
然后设置一个处理类:在此类中为公共字段赋值,需要实现 接口;
package com.itheima.reggie.common;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import java.time.LocalDateTime;
/**
* @author LJM
* @create 2022/4/16
* 自定义元数据对象处理器
*/
@Slf4j
@Component //注意:这个要记得交给spring容器管理,不然这个功能就没发用。。。。
//那么怎么确定你要添加的功能是不是要交给容器管理呢?就是你直接写了一个工具类或者是功能类,需要对数据库的数据或者是数据库数据的结果产生影响的时候,你明明写了这样一个类,但是功能却没有生效,那么这个时候就要首先考虑是不是容器没有托管这个类
public class MyMetaObjecthandler implements MetaObjectHandler {
/**
* 插入操作,自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("createUser", new Long(1)); //这里的id是不能直接获取的,所以这里先写死,后面教你怎么动态获取员工id
metaObject.setValue("updateUser",new Long(1));
}
/**
* 更新操作,自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime",LocalDateTime.now());
metaObject.setValue("updateUser",new Long(1));
}
}
功能完善:
然后为了动态的获取员工的id,这里我们使用了threadLocal这个局部变量来获取和存储员工id;
创建一个工具类来设置和获取threadLocal中的员工id, 注意:要先把数据设置进threadLocal中,才能获取到
package com.itheima.reggie.common;
/**
* @author LJM
* @create 2022/4/16
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
*/
public class BaseContext {
//用来存储用户id
private static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置值
* @param id
*/
public static void setCurrentId(Long id){
threadLocal.set(id);
}
/**
* 获取值
* @return
*/
public static Long getCurrentId(){
return threadLocal.get();
}
}
在前面我们写的LongCheckFilter这个过滤器中,把这个地方的代码加上添加和保存id的代码
//4、判断登录状态,如果已登录,则直接放行
if(request.getSession().getAttribute("employee") != null){
//log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));
//把用户id存储到本地的threadLocal
Long emId = (Long) request.getSession().getAttribute("employee");
BaseContext.setCurrentId(emId);
filterChain.doFilter(request,response);
return;
}
把处理器中的静态id改为动态获取:
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
这里的ID之所以全为1,是因为操作添加员工这个功能的管理员为admin,它的id就是1;
需求分析:
数据模型:
从资料去复制实体Category类到entity包;
数据库中的表结构:
创建mapper:
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Category;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
}
创建service:
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Category;
/**
* @author LJM
* @create 2022/4/16
*/
public interface CategoryService extends IService<Category> {
}
package com.itheima.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Category;
import com.itheima.reggie.mapper.CategoryMapper;
import com.itheima.reggie.service.CategoryService;
import org.springframework.stereotype.Service;
/**
* @author LJM
* @create 2022/4/16
*/
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
}
编写controller:
我们发现新增菜品分类的请求地址是:http://localhost:8080/category
提交的数据格式为:
{name: "湘菜", type: "1", sort: "1"}
/**
* 新增套餐分类
* @param category
* @return
*/
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("{category}" ,category);
categoryService.save(category);
return R.success("新增分类成功");
}
功能测试:登录后,点击添加新增菜品分类,看是否成功,数据库的数据是否变化;
代码开发:
/**
* 分页查询
* @param page
* @param pageSize
* @return
*/
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
//创建一个分页构造器
Page<Category> categoryPage = new Page<>(page,pageSize);
//创建一个条件构造器 用来排序用的 注意这个条件构造器一定要使用泛型,否则使用条件查询这个方法的时候会报错
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper();
//添加排序条件 ,根据sort字段进行排序
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(categoryPage,queryWrapper);
return R.success(categoryPage);
}
功能测试:
需求分析:
代码实现: 注意这里的删除功能是不完整的,因为可能需要删除的数据是与其他表关联的,所以删除之前要先判断该条数据是否与其他表中的数据关联;
/**
* 根据id来删除分类的数据
* @param id
* @return
*/
@DeleteMapping()
public R<String> delete(@RequestParam("ids") Long ids){ //注意这里前端传过来的数据是ids
categoryService.removeById(ids);
return R.success("分类信息删除成功");
}
功能完善:
创建对应的mapper:
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Dish;
import org.apache.ibatis.annotations.Mapper;
/**
* @author LJM
* @create 2022/4/16
*/
@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}
package com.itheima.reggie.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.reggie.entity.Setmeal;
import org.apache.ibatis.annotations.Mapper;
/**
* @author LJM
* @create 2022/4/16
*/
@Mapper
public interface SetmealMapper extends BaseMapper<Setmeal> {
}
创建service:
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Dish;
/**
* @author LJM
* @create 2022/4/16
*/
public interface DishService extends IService<Dish> {
}
package com.itheima.reggie.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.reggie.entity.Setmeal;
public interface SetmealService extends IService<Setmeal> {
}
package com.itheima.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Dish;
import com.itheima.reggie.mapper.DishMapper;
import com.itheima.reggie.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author LJM
* @create 2022/4/16
*/
@Service
@Slf4j
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}
package com.itheima.reggie.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.entity.Setmeal;
import com.itheima.reggie.mapper.SetmealMapper;
import com.itheima.reggie.service.SetmealService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* @author LJM
* @create 2022/4/16
*/
@Service
@Slf4j
public class SetmealServiceImpl extends ServiceImpl<SetmealMapper, Setmeal> implements SetmealService {
}
添加自定义的service方法:(就是我们需要的业务mybatis没有提供,所以就需要自己另外在service创建新的方法,并且在相关的业务中实现)
//在CategoryService中定义自己需要的方法,直接写就行
void remove(Long id);
在CategoryService实现类中重写该方法:
自定义异常类,因为这里需要抛异常了:
package com.itheima.reggie.common;
/**
* 自定义业务异常类
*/
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
//然后在外面前面写的GlobalExceptionHandler全局异常捕获器中添加该异常,这样就可以把相关的异常信息显示给前端操作的人员看见
/**
* 处理自定义的异常,为了让前端展示我们的异常信息,这里需要把异常进行全局捕获,然后返回给前端
* @param exception
* @return
*/
@ExceptionHandler(CustomException.class)
public R<String> exceptionHandle(CustomException exception){
log.error(exception.getMessage()); //报错记得打日志
//这里拿到的message是业务类抛出的异常信息,我们把它显示到前端
return R.error(exception.getMessage());
}
/**
* 根据id删除 分类,删除之前需要进行判断是否有关联数据
* @param id
*/
@Override
public void remove(Long id) {
LambdaQueryWrapper<Dish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>();
//添加查询条件
dishLambdaQueryWrapper.eq(Dish::getCategoryId,id);
//注意:这里使用count方法的时候一定要传入条件查询的对象,否则计数会出现问题,计算出来的是全部的数据的条数
int count = dishService.count(dishLambdaQueryWrapper);
//查询当前分类是否关联了菜品,如果已经管理,直接抛出一个业务异常
if (count > 0){
//已经关联了菜品,抛出一个业务异常
throw new CustomException("当前分类项关联了菜品,不能删除");
}
//查询当前分类是否关联了套餐,如果已经管理,直接抛出一个业务异常
LambdaQueryWrapper<Setmeal> setmealLambdaQueryWrapper = new LambdaQueryWrapper<>();
setmealLambdaQueryWrapper.eq(Setmeal::getCategoryId,id);
//注意:这里使用count方法的时候一定要传入条件查询的对象,否则计数会出现问题,计算出来的是全部的数据的条数
int setmealCount = setmealService.count(setmealLambdaQueryWrapper);
if (setmealCount > 0){
//已经关联了套餐,抛出一个业务异常
throw new CustomException("当前分类项关联了套餐,不能删除");
}
//正常删除
super.removeById(id);
}
然后在controller调用刚刚实现的方法就行:把之前的remove方法给删除就行,重新调用我们自己实现的方法;
/**
* 根据id来删除分类的数据
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(@RequestParam("ids") Long id){ //注意这里前端传过来的数据是ids
categoryService.remove(id);
return R.success("分类信息删除成功");
}
测试:自己添加测试数据测试就行;记得一定要测试一下删除有相关联的数据,看会不会删除和在前端提示异常信息;
这里的编辑的数据回显,前端已经帮我们做好了,所以我们就不需要去数据库查询了,这样可以减少对数据库的操作;
/**
* 根据id修改分类
* @param category
* @return
*/
@PutMapping
public R<String> update(@RequestBody Category category){
categoryService.updateById(category);
return R.success("修改分类信息成功");
}
记得在对应的实体类加上公共字段的值设置:前面我们配置了这个,所以这里只需要加注解就行;
//创建时间
@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;