瑞吉外卖日常总结
一:用到的知识点:
1.
1:@Slf4j
2:导入到项目中的静态资源时:(1)放到resources目录下的:static/template文件夹中
之后才可以被访问到;
--否则的话就会被拦截:所以我们需要配置(2)" 拦截器 "
3.如何接收的参数是"json"格式的:可以在 参数前 +" @ResponseBody"注解;
!!!:而且 " @ResponseBody"注解也可以加到 "类的最上面" ---->:代表这个的类的数据都以json的形式返回;
前后端交互一般都使用json!!!
2.拦截器的配置:
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("访问静态资源");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
}
}
4.如果要返回json形式:handler形参处 加上:@RequestBody
5.
3.全局异常处理器:----Aop(面向切面编程)思想:
全局异常处理Aop:底层是基于代理,"代理controller"---将controller的handler方法"拦截"到,如果抛异常的话,统一在全局异常处理类的某一个方法中进行处理;!!!
步骤:
1创建异常处理类
2.在异常处理类上加@ControllerAdvice注解:
@ControllerAdvice(annotations={RestController.class,Controller.class}) :代表全局异常处理器会 "拦截" "加了 @RestController和@Controller 注解的 类及其方法"!!!!
3.定义全局异常处理器的方法:a:都要加方法上注解:"@ExceptionHandler(异常类型.class)" b:在方法形参处写相应的异常类对象:用来接收controller的异常信息; eg:Except ex
之后,类报了相关异常,就会到全局异常处理类匹配对应异常处理方法.
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@Slf4j
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.info(ex.getMessage());
return R.error("失败了,out");
}
}
之后Controller层中的handler报异常之后就会进入异常处理器来找对应的异常处理方法;
4.分页查询:
一:定义配置类:"分页操作是在拦截器中执行的,所以先new一个拦截器,再new一个分页插件,然后将分页插件放入拦截器中返回"
:定义一个配置类:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
二:在Controller中书写handler方法:
"只需要1.构造page对象 2.构造wrapper对象 并 添加过滤“条件” , 3.使用service对象调用page()方法:将page对象和wrapper对象传入即可;
//(4)分页查询:
//流程:前端页面发送请求-->将分页查询参数(page,pageSize,name)提交到服务器-->
// 服务端Controller接收前端提交的数据 + 调用service层查询数据-->:service层调用Mapper层来操作数据库进行分页查询
// -->Controller层将查询到"分页数据"响应给前端--->页面接收到分页数据并展示
//handler:接收前端参数
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page={},pageSize={},name={}",page,pageSize,name);
//分页查询:select ... from +条件 (lambda构造器)
//1:构造分页构造器page对象
Page pageInfo=new Page(page,pageSize);
//2.构造条件构造器:()wrapper对象
//(1):
LambdaQueryWrapper<Employee>queryWrapper=new LambdaQueryWrapper<>();
//(2).添加过滤条件: a:name不为null的话才将name作为条件
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);//意为:如果name不为null的话就将name作为条件加入sql语句!!!
//b:添加排序条件:
queryWrapper.orderByDesc(Employee::getUpdateTime); //将更新时间作为条件加入sql语句
//3.执行查询语句!!!!!!!:使用service调用"page()方法" 进行分页操作
employeeService.page(pageInfo,queryWrapper); //不需要返回值,因为它会自己进行分页查询:之后会自动将结果封装到pageInfo对象中;!!!!!!!
return R.success(pageInfo);
}
5.过滤器:
package com.itheima.reggie.Filter;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.reggie.common.BaseContext;
import com.itheima.reggie.common.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.util.AntPathMatcher;
import javax.print.DocFlavor;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
public 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;
String requestUrl=request.getRequestURI();
log.info("拦截到的请求:{}",request.getRequestURI());
String [] urls= new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
boolean check = check(urls, requestUrl);
if(check){
log.info("本次请求{}不需要处理",requestUrl);
filterChain.doFilter(request,response);
return;
}
Object employee = request.getSession().getAttribute("employee");
if(employee!=null){
log.info("用户已经登录,用户id为{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
Long empId=(Long) employee;
BaseContext.setCurrentId(empId);
"(3)"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}
log.info("用户未登录");
return;
}
public boolean check(String [] urls,String requestUrl){
for (String url : urls) {
boolean match=PATH_MATCHER.match(url,requestUrl);
if(match){
return true;
}
}
return false;
}
}
6.公共字段填充:
(1)公共字段的自动填充:
eg:在新增员工时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工时需要设置修改时间和修改人等字段
这些字段是公共字段,在很多表中都有这些字段----所以我们可以使用Mybatis Plus提供的“公共字段自动填充功能”来将这些公共字段进行统一管理简化开发
Mybatis Plus 公共字段自动填充,也就是在插入或者更新的时候为指定字段赋予指定的值,使用它的好处就是可以统一对这些字段进行处理,避免了重复代码;
"实现步骤": 1..在相应实体类的对应属性上加入@TbaleField注解,指定哪些字段需要自动填充以及自动填充的策略
2. 按照框架要求编写元数据对象处理器,在此类中为公共字段赋值,这个类要实现MetaObjectHandler接口
'之后,进行自动填充的字段,我们就不需要每次都管理了,eg:每次新增数据时都要设置更新时间,但是在我们将“更新时间”设置为:插入//更新 自动填充时,在每次插入数据时就不需要再管更新时间了'
1..在相应实体类的对应属性上加入@TbaleField注解,指定哪些字段需要自动填充以及自动填充的策略
a:insert:在进行插入操作时,进行字段的自动填充 b;update: 在进行更新操作时,进行字段的自动填充
eg:"Employee实体类":
@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.具体需要填充什么数据:
a:创建一个类MyMetaObjectHandler ,implement MetaObjectHandler类,实现两个方法:insertFill(当执行插入操作时,就会进入这个方法)、updateFill(当执行更新操作时,就会进行这个方法)
b:设置自动填充的值:
meta.setValue("实体类中需要管理的属性名","自动填充的值")
eg:
"(3)" !!!!!!!!
@Component
@Slf4j
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());
long id = Thread.currentThread().getId();
log.info("线程id为:{}",id);
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser",BaseContext.getCurrentId());
}
}
1.现在存在的问题是:需要设置 登录用户/更新用户id 自动填充,但是用户id是从前端传过来的,在 MyMetaObjectHandler 类中无法 取到,所以无法设置id自动填充的值,但是我们可以使用ThreadLocal!!!!
2.前端每发送一次http请求,就会在后端开启一个新的线程。
LoginCheckFilter的doFilter方法
EmployeeController中update方法
MyMetaObjectHandler的updateFill方法 "这三个都属于同一个线程"
3.ThreadLocal并不是一个Thread,而是Thread的局部变量 ,ThreadLocal为"每一个线程"提供一份单独的存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问; 作用域是单个线程内
ThreadLocal常用的方法:
public void set(T value) :设置当前的线程局部变量的值
public T get() :返回当前线程所对应的线程局部变量的值
4:"!!!":获取id的方法:
"a:我们可以在 LoginCheckFilter的doFilter方法 中获取当前登录用户的id(即从session中获取),b:并调用ThreadLocal的set方法设置当前线程的线程局部变量的值(用户id), c:然后在 MyMetaObjectHandler的updateFill的updateFill方法 中调用ThreadLocal的get方法来获得当前线程所对应 的线程局部变量的值(用户id)"
因为是同一个线程,所以可以获得!!!
实现步骤:
(1)编写BaseConyext工具类,基于ThreadLocal封装的工具类:里面放其两个方法
(2)在LoginCheckFilter中的doFilter中调用BaseContext方法来设置当前登录用户的id
(3)在MyMetaObjectHandler的方法中调用BaseContext方法来获取当前登录用户的id
(1) !!!!!!!!!
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)在过滤器上!!!!!;
因为过滤器可以通过session获取id,然后将id用ThreadLocal的set方法设置,以供自动填充类上 通过 get 方法调用
一:登录/退出功能
(1)登录功能:
分析!!!: 用户输入账号、密码-->之后点击登录按钮--->发送登录请求--->请求服务端的controller、service、mapper、数据库-->验证数据库中是否存在(通过" 查询数据库 ")
@Slf4j
@RestController
@RequestMapping("/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping("login")
public R<Employee> login(HttpServletRequest request,@RequestBody Employee employee) {
String password = employee.getPassword();
password = DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<Employee> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(Employee::getUsername, employee.getUsername());
Employee emp = employeeService.getOne(lambdaQueryWrapper);
if (emp == null) {
return R.error("登录失败");
}
if (!emp.getPassword() .equals(password))
{
return R.error("登录失败");
}
if (emp.getStatus() == 0) {
return R.error("账号已禁用");
}
request.getSession().setAttribute("employee", emp.getId());
return R.success(emp);
}
}
(2)退出功能:
退出功能实现:
用户点击页面中的 “退出按钮”-->发送请求到服务端-->服务端在controller中接收请求.
Controller中的逻辑:1.接收前端参数+响应结果 2.调用执行相关逻辑:清理session中的用户id() :(因为上一次登录成功之后就将id存到session中并返回前端了,下次请求时仍然会携带session,但是它已经退出了,所以要清楚session中的id)
@PostMapping("/logout")
public R<String> logout(HttpServletRequest request){
request.getSession().removeAttribute("employee");
return R.success("退出成功");
}
二:员工管理:
(1)完善登录功能
a:存在问题:我们可以直接访问其它页面,即使我们没有登录!!!
-->用户先登录才能访问应用中的页面以及相关数据;
b:方法:使用拦截器或者过滤器-->:如果没有登录就跳转到登录页面;
---->这里我们使用"过滤器"来实现:
步骤:
1.:创建自定义过滤器(类):
a:加@WebFilter("LoginCheckFilter(拦截器名称随便起)",urlPattern="/"("\"代表拦截所有))
b:实现implents Filter 接口 + 实现doFilter方法
2.:在启动类上加入@ServletComponentScan(开启组件扫描,过滤器才会生效)
3.:完善处理逻辑
@Slf4j
@WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")
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;
String requestUrl=request.getRequestURI();
log.info("拦截到的请求:{}",request.getRequestURI());
String [] urls= new String[]{
"/employee/login",
"/employee/logout",
"/backend/**",
"/front/**"
};
boolean check = check(urls, requestUrl);
if(check){
log.info("本次请求{}不需要处理",requestUrl);
filterChain.doFilter(request,response);
return;
}
Object employee = request.getSession().getAttribute("employee");
if(employee!=null){
log.info("用户已经登录,用户id为{}",request.getSession().getAttribute("employee"));
filterChain.doFilter(request,response);
}
log.info("用户未登录");
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return;
}
public boolean check(String [] urls,String requestUrl){
for (String url : urls) {
boolean match=PATH_MATCHER.match(url,requestUrl);
if(match){
return true;
}
}
return false;
}
}
(2)新增员工:
执行流程:
在前端页面填入相关信息-->提交-->前端发送请求到服务端-->服务端controller接收+返回 +调用service将数据进行保存-->Service调用mapper操作数据库,保存数据
@PostMapping
public R<String> save(HttpServletRequest request, @RequestBody Employee employee) {
log.info("新增员工,员工信息是:{}",employee.toString());
employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
Object object = request.getSession().getAttribute("employee");
Long empId = (Long) object;
employee.setCreateUser(empId);
employee.setUpdateUser(empId);
employeeService.save(employee);
return R.success("新增员工成功");
}
(3)分页查询员工信息:
(1)定义配置类:
一:定义配置类:"分页操作是在拦截器中执行的,所以先new一个拦截器,再new一个分页插件,然后将分页插件放入拦截器中返回"
:定义一个配置类:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
(2)在controller中书写handler方法:
@GetMapping("/page")
public R<Page> page(int page,int pageSize,String name){
log.info("page={},pageSize={},name={}",page,pageSize,name);
Page pageInfo=new Page(page,pageSize);
LambdaQueryWrapper<Employee>queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name);
queryWrapper.orderByDesc(Employee::getUpdateTime);
employeeService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
(4)启用/禁用员工账号:
1.账号被禁用的员工不能登录系统,启用后的员工才可以正常登录
只有管理员admin可以对其它员工进行禁用、启用
2.过程分析:
前端发送请求-->将id、status(根据id修改其status)提交到服务端--->服务端接收页面提交的参数 + 调用service更新数据
-->service调用mapper操作数据库
3. !!!" 启用、禁用员工账号,本质上就是 "更新操作" ,即对status状态字段进行操作,在Controller中创建一个update方法,此方法是一个通用的修改员工信息的方法"
*/
@PutMapping
public R<String> update(HttpServletRequest request,@RequestBody Employee employee){
"这些就不需要了,因为我们设置了自动填充,在更新时,会自动进行填充更新时间、更新人等!!!"
employeeService.updateById(employee);
return R.success("员工信息修改成功");
}
(5)修改员工信息
(6)编辑员工信息:(1:即先要将已有数据回显:即先去数据库查询数据--->仍是根据id查询!!!)
之后的更新操作:复用上方的新增员工操作
执行过程: 前端修改内容,点击“保存”,请求服务端,同时提交员工的id参数!!-->后端接收,然后根据id查询用户信息,然后以json形式返回给页面
*/
@GetMapping("/{id}")
public R<Employee> getById(@PathVariable Long id){
log.info("根据id查询员工信息...");
Employee employee = employeeService.getById(id);
return R.success(employee);
}
三:菜品分类管理:
(1)新增菜品分类
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public R<String> save(@RequestBody Category category){
log.info("新增分类的信息为:{}",category.toString());
categoryService.save(category);
return R.success("新增分类成功");
}
}
(2)菜品分页查询
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GetMapping("/page")
public R<Page> page(int page,int pageSize){
Page<Category> pageInfo=new Page<>(page,pageSize);
LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.orderByAsc(Category::getSort);
categoryService.page(pageInfo,queryWrapper);
return R.success(pageInfo);
}
}
(3)删除菜品分类:!!!
1."Controller层":
@RestController
@RequestMapping("/category")
@Slf4j
public class CategoryController {
@Autowired
private CategoryService categoryService;
@DeleteMapping
public R<String> delete(Long id){
categoryService.remove(id);
return R.success("删除分类成功");
检查删除的分类是否关联了菜品或者套餐,所以就不能直接使用mybatisPlus直接提供的:categoryService.removeById(id)来 进行直接删除
因为直接调用这个方法的话,没有 " 判断是否可以删除的逻辑 ",所以需要我们自己在service层中定义逻辑!!!
}
2.Service接口层:
public interface CategoryService extends IService<Category> {
public void remove(Long id);
}
3."ServiceImpl"层:
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService {
@Autowired
private DishService dishService;
@Autowired
private SetmealService setmealService;
@Override
public void remove(Long id){
LambdaQueryWrapper<Dish>queryWrapper=new LambdaQueryWrapper<>();
queryWrapper.eq(Dish::getId,id);
int count = dishService.count(queryWrapper);
if(count>0){
throw new CustomException("当前分类下关联了菜品,不能删除");
}
LambdaQueryWrapper<Setmeal>queryWrapper1=new LambdaQueryWrapper<>();
queryWrapper1.eq(Setmeal::getCategoryId,id);
int count1 = setmealService.count(queryWrapper1);
if(count1>0){
throw new CustomException("当前分类下关联了套餐,不能删除");
}
super.removeById(id);
}
}
4.因为在service层中:如果不能删除,需要抛出异常,所以我们需要
a:定义一个 "异常处理类"
b:为了让前端也返回相同的异常处理信息,我们需要把"异常处理类"加入到"全局异常处理器"中
eg:
'自定义业务异常类:
public class CustomException extends RuntimeException{ //继承运行时异常
public CustomException(String message){ //构造器
super(message); //调用父类的有参构造器
}
之后我们可以在其它类中国通过:"throw new CustomException(提示信息)"来将异常处理信息传进来!!!
}
"加入全局异常处理器":
@ControllerAdvice(annotations = {RestController.class, Controller.class}) //全局异常处理器拦截的类
@Slf4j
@ResponseBody //返回json形式结果
public class GlobalExceptionHandler {
//异常处理方法1:
@ExceptionHandler(SQLIntegrityConstraintViolationException.class) //Sql异常处理:
public R<String> exceptionHandler(SQLIntegrityConstraintViolationException ex){
//接收异常
log.info(ex.getMessage());
return R.error("失败了,out"); //返回到前端页面!!
}
//异常处理方法2:
@ExceptionHandler(CustomException.class) //自定义异常处理:
public R<String> exceptionHandler(CustomException ex){
//接收异常
log.info(ex.getMessage());
return R.error(ex.getMessage()); //返回到前端页面!!!
}
}
(4)修改菜品分类信息:
(4)修改分类:"即首先让数据回显-->:根据"id"查数据库"
@PutMapping
public R<String> update(@RequestBody Category category){
categoryService.updateById(category);
return R.success("修改分类信息成功");
}