黑马程序员Java项目实战《瑞吉外卖》,轻松掌握springboot + mybatis plus开发核心技术的真java实战项目——第二部分

黑马程序员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("项目启动成功...");}
}

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

③完善过滤器的处理逻辑

过滤器具体的处理逻辑如下:

  1. 获取本次请求的URI
  2. 判断本次请求是否需要处理
  3. 如果不需要处理,则直接放行
  4. 判断登录状态,如果已登录,则直接放行
  5. 如果未登录则返回未登录结果

在这里插入图片描述)

具体逻辑的代码实现:

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,表示员工状态可以正常登陆;

在这里插入图片描述

代码开发:

在开发代码之前,需要梳理一下整个程序的执行过程:

  1. 页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
  2. 服务端Controller接收页面提交的数据并调用Service将数据进行保存
  3. 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 可以这么保存,但是未来越来越多的代码 都会面临这样的问题,那就会写很多的trycatch。所以,我现在利用全局异常捕获这个异常。

在这里插入图片描述

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("未知错误");}
}

功能测试:登陆后,添加一个一个已经存在账号名,看前端页面提示的是什么信息,以及看后台是否输出了报错日志;

在这里插入图片描述

在这里插入图片描述

  1. 根据产品原型明确业务需求
  2. 重点分析数据的流转过程和数据格式
  3. 通过debug断点调试跟踪程序执行过程

在这里插入图片描述

在这里插入图片描述

1.3 员工信息分页查询

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

在这里插入图片描述

流程分析:

在开发代码之前,需要梳理一下整个程序的执行过程:

  1. 页面发送ajax请求,将分页查询参数(page、 pageSize、 name)提交到服务端
  2. 服务端Controller接收页面提交的数据并调用Service查询数据
  3. Service调用Mapper操作数据库,查询分页数据
  4. Controller将查询到的分页数据响应给页面
  5. 页面接收到分页数据并通过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用户)才可以对其他普通用户进行启用操作,禁用操作,所以普通用户登录系统后启用,禁用按钮不显示;

并且如果某个员工账号的状态为正常,则按钮显示为’‘禁用’,如果员工账号状态为已禁用,则按钮显示为“启用”。

普通员工登录系统后,启用,禁用按钮不显示;

代码开发:

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

注意:这里修改状态码要反着来,因为正常的用户你只能把它设置为禁用;已经禁用的账号你只能把它设置为正常
在这里插入图片描述

流程分析:

在开发代码之前,需要梳理一下整个程序的执行过程:

  1. 页面发送ajax请求,将参数(id、status)提交到服务端
  2. 服务端Controller接收页面提交的数据并调用Service更新数据
  3. 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-plusid使用了雪花算法,所以存入数据库中的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 编辑员工信息

需求分析

在员工管理列表页面点击编辑按钮,跳转到编辑页面,在编辑页面回显员工信息并进行修改,最后点击保存按钮完成编辑操作

在这里插入图片描述

在开发代码之前需要梳理一下操作过程和对应的程序的执行流程:

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

注意: 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;

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/596558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot整合gateway网关

一、网关基本概念 1、API网关介绍 API 网关出现的原因是微服务架构的出现&#xff0c;不同的微服务一般会有不同的网络地址&#xff0c;而外部客户端可能需要调用多个服务的接口才能完成一个业务需求&#xff0c;如果让客户端直接与各个微服务通信&#xff0c;会有以下的问题…

React Admin 前端脚手架之ant-design-pro

文章目录 一、React Admin 前端脚手架选型二、React Admin 前端脚手架之ant-design-pro三、ant-design-pro使用步骤四、常用总结&#xff08;持续更新&#xff09;EditableProTable组件 常用组件EditableProTable组件 编辑某行后&#xff0c;保存时候触发发送请求EditableProTa…

linux 系统 kill 指令笔记

kill 名称 kill - send a signal to a process 向指定的线程或进程发送信号 描述 The default signal for kill is TERM. Use -l or -L to list availablesignals. Particularly useful signals include HUP, INT, KILL, STOP,CONT, and 0. Alternate signals …

k8s笔记1- 初步认识k8s

k8s简介&#xff1a; kubernetes&#xff0c;俗称k8是&#xff0c;用于自动部署&#xff0c;扩缩和管理容器化应用程序的开源系统&#xff0c;它将组成应用程序的容器&#xff0c;组合成逻辑单元&#xff0c;便于管理和服务发现。 k8s的作用 自动化上线和回滚、存储编排…

Spring中的工厂类、bean的作用范围和生命周期

1.Spring中的工厂类 1.1ApplicationContext ClassPathXmlApplicationContext&#xff1a;加载类路径下 Spring 的配置文件 FileSystemXmlApplicationContext&#xff1a;加载本地磁盘下 Spring 的配置文件 1.1.1service ApplicationContext&#xff1a;只要一读取配置文件…

PyTorch|PyTorch张量解释

神经网络中的输入、输出和转换都使用张量表示&#xff0c;因此&#xff0c;神经网络编程大量使用张量&#xff0c;张量是我们在 PyTorch 中编程神经网络时将使用的数据结构。 关于张量及其维数的简要说明&#xff0c;以及术语&#xff1a; 你有时会看到一个称为向量的一维张量…

[论文分享]TimesURL:通用时间序列表示学习的自监督对比学习

论文题目&#xff1a;TimesURL: Self-supervised Contrastive Learning for Universal Time Series Representation Learning 论文地址&#xff1a;https://arxiv.org/abs/2312.15709 代码地址&#xff1a;暂无 摘要 学习适用于各种下游任务的通用时间序列表示具有挑战性&…

Springboot整合RocketMQ 基本消息处理

目录 1. 同步消息 2. 异步消息 3. 单向消息 4. 延迟消息 5. 批量消息 6. 顺序消息 7. Tag过滤 导入依赖 <dependency><groupId>org.apache.rocketmq</groupId><artifactId>rocketmq-spring-boot-starter</artifactId></dependency> …

14:00面试,14:08就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到10月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40…

机器学习原理到Python代码实现之LinearRegression

Linear Regression 线性回归模型 该文章作为机器学习的第一篇文章&#xff0c;主要介绍线性回归模型的原理和实现方法。 更多相关工作请参考&#xff1a;Github 算法介绍 线性回归模型是一种常见的机器学习模型&#xff0c;用于预测一个连续的目标变量&#xff08;也称为响应变…

Spring的bean的生命周期!!!

一.单例模式 单例&#xff1a;[启动容器]--->通过构造方法&#xff08;创建对象&#xff09;---->调用set方法&#xff08;注入&#xff09;--->调用init方法&#xff08;初始化&#xff09;----[容器关闭]----->调用destroy方法&#xff08;销毁&#xff09; app…

死锁的处理策略“检测和解除”-第三十九天

目录 前言 死锁的检测 数据结构资源分配图 基于“图”检测死锁 可以消除所有边 不能消除所有边 结论 死锁定理 死锁的解除 本节思维导图 前言 如果系统中既不采取预防死锁的措施&#xff0c;也不采取避免死锁的措施&#xff0c;系统就很可能发生死锁&#xff0c;在这种…

西电期末1019.校验和计算

一.题目 二.分析与思路 难点在于逐个取出数据的每一位&#xff0c;我们编写f函数&#xff0c;使用了一个while函数&#xff0c;每次循环中用取余的运算符找到数据的个位累加&#xff0c;再将n/10&#xff0c;如此n便被去除了个位&#xff0c;十位就成了新的个位&#xff0c;最…

案例精选|淄博绿能燃气工程有限公司日志审计系统建设方案

淄博绿能燃气工程有限公司&#xff0c;成立于1994年&#xff0c;前身为淄博市煤气公司管道液化气分公司。公司业务主要涉及天然气、液化气等市政工程施工及城镇燃气供应等领域&#xff0c;具有市政公用工程施工总承包二级资质&#xff0c;《压力管道安装许可证》压力管道安装GB…

利用Embedding优化搜索功能

我们继续用Gemini学习LLM编程之旅。 Embedding是一种自然语言处理 (NLP) 技术&#xff0c;可将文本转换为数值向量。Embedding捕获语义含义和上下文&#xff0c;从而导致具有相似含义的文本具有更接近的Embedding。例如&#xff0c;句子“我带我的狗去看兽医”和“我带我的猫去…

LeetCode---378周赛

题目列表 2980. 检查按位或是否存在尾随零 2981. 找出出现至少三次的最长特殊子字符串 I 2982. 找出出现至少三次的最长特殊子字符串 II 2983. 回文串重新排列查询 一、检查按位或是否存在尾随零 这题和位运算有关&#xff0c;不是很难&#xff0c;题目要求至少有两个数的…

案例073:基于微信小程序的智慧旅游平台开发

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

用贪心算法编程求解任务安排问题

题目&#xff1a;用贪心算法编程求解以下任务安排问题 一个单位时间任务是恰好需要一个单位时间完成的任务。给定一个单位时间任务的有限集S。关于S的一个时间表用于描述S中单位时间任务的执行次序。时间表中第1个任务从时间0 开始执行直至时间1 结束&#xff0c;第2 个任务从时…

20240104确认AIO-3399J的开发板适配ov13850摄像头不支持4K分辨率录像

20240104确认AIO-3399J的开发板适配ov13850摄像头不支持4K分辨率录像 2024/1/4 13:23 开发板&#xff1a;Firefly的AIO-3399J【RK3399】 SDK&#xff1a;rk3399-android-11-r20211216.tar.xz【Android11】 Android11.0.tar.bz2.aa【ToyBrick】 Android11.0.tar.bz2.ab Android1…

人工智能如何重塑金融服务业

在体验优先的世界中识别金融服务业中的AI使用场景 人工智能&#xff08;AI&#xff09;作为主要行业的大型组织的重要业务驱动力&#xff0c;持续受到关注。众所周知&#xff0c;传统金融服务业在采用新技术方面相对滞后&#xff0c;一些组织使用的还是上世纪50年代和60年代发…