黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第二部分
- 1.员工管理模块
- 1.1 完善登陆功能
- 1.2 新增员工
- 1.2.1 全局异常捕获
- 1.3 员工信息分页查询
- 1.4 启用/禁用员工账号
- 1.4.1 使用自定义消息转换器
- 1.5 编辑员工信息
- 2. 菜品分类管理
- 2.1 公共字段填充(这里有重点)
- 2.2 新增分类
- 2.3 菜品类的分页
- 2.4 删除分类(这里有注意点)
- 2.5 修改分类
1.员工管理模块
1.1 完善登陆功能
问题分析:前面的登陆存在一个问题,如果用户不进行登陆,直接访问系统的首页,照样可以正常访问,这种设计是不合理的,我们希望看到的效果是只有完成了登陆后才可以访问系统中的页面,如果没有登陆则跳转到登陆页面;
那么如何实现?
答案就是使用过滤器或者是拦截器,在拦截器或者是过滤器中判断用户是否已经完成了登陆,如果没有登陆则跳转到登陆页面;
代码实现:这里使用的是过滤器;
①创建自定义过滤器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 {@Overridepublic 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("项目启动成功...");}
}
③完善过滤器的处理逻辑
过滤器具体的处理逻辑如下:
- 获取本次请求的URI
- 判断本次请求是否需要处理
- 如果不需要处理,则直接放行
- 判断登录状态,如果已登录,则直接放行
- 如果未登录则返回未登录结果
)
具体逻辑的代码实现:
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();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//对请求和响应进行强转,我们需要的是带http的HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//1、获取本次请求的URIString 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;}
}
功能测试: 发起几个请求看看后台的输出,和能不能访问到资源里面的数据,和能不能跳转,注意,上面的后台日志代码已经被注释,需要在后台看到日志的话,需要把注释去掉;
1.2 新增员工
数据模型:
新增员工,其实就是将我们的新增页面录入的员工数据插入到employee表;注意:employee表中对username字段加入了唯一的约束,因为username是员工的登陆账号,必须是唯一的!
employee表中的status字段默认设置为1,表示员工状态可以正常登陆;
代码开发:
在开发代码之前,需要梳理一下整个程序的执行过程:
- 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
- 服务端Controller接收页面提交的数据并调用Service将数据进行保存
- Service调用Mapper操作数据库,保存数据
/*** 新增员工* @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());//获得当前登录用户的idLong 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
。所以,我现在利用全局异常捕获这个异常。
1.2.1 全局异常捕获
这个全局异常捕获写在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("未知错误");}
}
功能测试:登陆后,添加一个一个已经存在账号名,看前端页面提示的是什么信息,以及看后台是否输出了报错日志;
- 根据产品原型明确业务需求
- 重点分析数据的流转过程和数据格式
- 通过debug断点调试跟踪程序执行过程
1.3 员工信息分页查询
需求分析:系统中的员工比较多的时候,如果在一个页面中全部展示出来会显得比较乱,不便于查看,所以一般都系统中都会以分页的方式来展示列表数据。
流程分析:
在开发代码之前,需要梳理一下整个程序的执行过程:
- 页面发送ajax请求,将分页查询参数(page、 pageSize、 name)提交到服务端
- 服务端Controller接收页面提交的数据并调用Service查询数据
- Service调用Mapper操作数据库,查询分页数据
- Controller将查询到的分页数据响应给页面
- 页面接收到分页数据并通过ElementUl的Table组件展示到页面上
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 {@Beanpublic 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);}
功能测试:
分页的三个时机:
①用户登录成功时,分页查询一次
②用户使用条件查询的时候分页一次
③跳转页面的时候分页查询一次
1.4 启用/禁用员工账号
需求分析:
在员工管理列表页面中,可以对某个员工账号进行启用或者是禁用操作。账号禁用的员工不能登陆系统,启用后的员工可以正常登陆;
需要注意的是:只有管理员(admin用户)才可以对其他普通用户进行启用操作,禁用操作,所以普通用户登录系统后启用,禁用按钮不显示;
并且如果某个员工账号的状态为正常,则按钮显示为’‘禁用’,如果员工账号状态为已禁用,则按钮显示为“启用”。
普通员工登录系统后,启用,禁用按钮不显示;
代码开发:
注意:这里修改状态码要反着来,因为正常的用户你只能把它设置为禁用;已经禁用的账号你只能把它设置为正常
流程分析:
在开发代码之前,需要梳理一下整个程序的执行过程:
- 页面发送ajax请求,将参数(id、status)提交到服务端
- 服务端Controller接收页面提交的数据并调用Service更新数据
- Service调 用Mapper操作数据库
注意:启用,禁用的员工账号,本质上就是一个更新操作,也就是对status状态字段进行修改操作;
在controller中创建update方法,此方法是一个通用的修改员工信息的方法,因为status也是employee中的一个属性而已;这里使用了动态SQL的功能,根据具体的数据修改对应的字段信息;
/*** 根据id修改员工信息* @param employee* @return*/@PutMappingpublic 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就行;
1.4.1 使用自定义消息转换器
代码bug修复:
思路:既然js对long型的数据会进行精度丢失,那么我们就对数据进行转型,我们可以在服务端(Java端)给页面响应json格式的数据时进行处理,将long型的数据统一转换为string字符串;
代码实现步骤:
- 提供对象转换器JacksonObjectMapper,基于Jackson进行Java对象到json数据的转换(资料中已经提供,直接复制到项目中使用)
- 在WebMvcConfig配置类中扩展Spring mvc的
消息转换器
,在此消息转换器中使用提供的对象转换器
进行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*/@Overrideprotected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {//log.info("扩展消息转换器...");//创建消息转换器对象MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用Jackson将Java对象转为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的消息转换器对象追加到mvc框架的转换器集合中//转换器是有优先级顺序的,这里我们把自己定义的消息转换器设置为第一优先级,所以会优先使用我们的转换器来进行相关数据进行转换,如果我们的转换器没有匹配到相应的数据来转换,那么就会去寻找第二个优先级的转换器,以此类推converters.add(0,messageConverter);}
然后启动程序,使用f12查看服务器响应到浏览器的用户id是不是变成了字符串,和数据库中是否相对应;
发现对应,即消息转换器配置成功;
然后再去测试 启用与禁用 员工账号这个功能,发现操作更新成功,并且数据库修改成功;
1.5 编辑员工信息
需求分析:
在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作
在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:
- 点击编辑按钮时,页面跳转到add.html, 并在url中携带参数[员Iid]
- 在add.html页面获取url中的参数[员工id]
- 发送ajax请求,请求服务端,同时提交员工id参数
- 服务端接收请求,根据员工id查询员工信息,将员工信息以json形式响应给页面
- 页面接收服务端响应的json数据,通过VUE的数据绑定进行员工信息回显
- 点击保存按钮,发送ajax请求,将页面中的员工信息以json方式提交给服务端
- 服务端接收员工信息,并进行处理,完成后给页面响应
- 页面接收到服务端响应信息后进行相应处理
注意: 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,所以只需要在前端跳转发请求就行;这样就实现了方法的复用,减少了代码量;
功能测试:自己测试编辑,看能不能数据回显,可不可以修改成功,修改后数据库的数据有没有跟着变化;
2. 菜品分类管理
2.1 公共字段填充(这里有重点)
问题分析:
把相关的注解加在需要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*/@Overridepublic void insertFill(MetaObject metaObject) {metaObject.setValue("createTime", LocalDateTime.now());metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("createUser", new Long(1)); //这里的id是不能直接获取的,所以这里先写死,后面教你怎么动态获取员工idmetaObject.setValue("updateUser",new Long(1));}/*** 更新操作,自动填充* @param metaObject*/@Overridepublic 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 {//用来存储用户idprivate 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存储到本地的threadLocalLong 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;
2.2 新增分类
需求分析:
数据模型:
从资料去复制实体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*/@PostMappingpublic R<String> save(@RequestBody Category category){log.info("{category}" ,category);categoryService.save(category);return R.success("新增分类成功");}
功能测试:登录后,点击添加新增菜品分类,看是否成功,数据库的数据是否变化;
2.3 菜品类的分页
代码开发:
/*** 分页查询* @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);}
功能测试:
2.4 删除分类(这里有注意点)
需求分析:
代码实现: 注意这里的删除功能是不完整的,因为可能需要删除的数据是与其他表关联的,所以删除之前要先判断该条数据是否与其他表中的数据关联;
/*** 根据id来删除分类的数据* @param id* @return*/@DeleteMapping()public R<String> delete(@RequestParam("ids") Long ids){ //注意这里前端传过来的数据是idscategoryService.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*/@Overridepublic 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*/@DeleteMappingpublic R<String> delete(@RequestParam("ids") Long id){ //注意这里前端传过来的数据是idscategoryService.remove(id);return R.success("分类信息删除成功");}
测试:自己添加测试数据测试就行;记得一定要测试一下删除有相关联的数据,看会不会删除和在前端提示异常信息;
2.5 修改分类
这里的编辑的数据回显,前端已经帮我们做好了,所以我们就不需要去数据库查询了,这样可以减少对数据库的操作;
/*** 根据id修改分类* @param category* @return*/@PutMappingpublic 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;