springboot瑞吉外卖
创始人
2024-01-28 16:02:14
0
  1. 创建数据库,项目初始化
  2. 静态资源不在static目录下,如何映射
  3. 结果类
  4. 登录
  5. 过滤器拦截路径
  6. 全局异常处理器
  7. 分页查询
  8. 消息转换器
  9. 修改禁用
  10. 分页
  11. 编辑
  12. 公共字段自动填充使用ThreadLocal
  13. 新增用户
  14. 绑定的数据不可删除
  15. 上传
  16. 下载
  17. 前端传递的数据,不在同一张表时,DTO
  18. 响应的值,不在一个类中,多表分页
  19. 修改新增菜品,对两张表新增/修改数据
  20. 批量删除
  21. 用户下单
  22. 优化redis
  23. Springcache->redis简化开发
  24. 主从复制、读写分离,Sharding-JDBC
  25. Nginx概述
  26. Yapi
  27. swagger

数据库

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

  1. 员工表
  2. 菜品菜单分类表
  3. 菜品表
  4. 菜单表
  5. 套餐菜品关系表
  6. 菜品口味关系表
  7. 用户表
  8. 地址表
  9. 购物车表
  10. .订单表
  11. 订单明细表

流程:

  1. 创建实体类entity
  2. 创建controller
  3. 创建service 以及 impl
  4. 创建mapper
  5. 创建config配置类
  6. 创建common返回结果 R

静态资源不在static目录下,如何映射

编写web配置类,继承WebMvcConfigurationSupport 类

/***   web配置类*/
@Slf4j   //  输出日志
@Configuration   // 声明配置类
public class WebConfig extends WebMvcConfigurationSupport {/*** 设置静态资源映射* @param registry*/@Overrideprotected void addResourceHandlers(ResourceHandlerRegistry registry) {log.info("静态资源映射开始");
//        前端资源方式的请求  /backend/**registry.addResourceHandler("/backend/**")
//         前端资源所在的位置.addResourceLocations("classpath:/backend/");registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");}
}

结果类

/*** 通用返回结果类* @param */
@Data
public class R {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据  实体对象private Map map = new HashMap(); //动态数据//返回成功public static  R success(T object) {R r = new R();r.data = object;r.code = 1;return r;}//失败public static  R error(String msg) {R r = new R();r.msg = msg;r.code = 0;return r;}//动态数据public R add(String key, Object value) {this.map.put(key, value);return this;}}

登录

//Mapper
@Mapper
public interface EmployeeMapper extends BaseMapper {}
//service
public interface EmployeeService extends IService {}
//ServiceImpl
@Service
public class EmployeeServiceImpl extends ServiceImpl implements EmployeeService {}
//config
@Slf4j
@RestController   // @Controller + @ResponseBody(响应数据)
@RequestMapping("/employee")
public class EmployeeCpntroller {@AutowiredEmployeeService employeeService;@PostMapping("/login")public R  EmployeeLogin(@RequestBody Employee employee, HttpServletRequest request){
//        1.获取提交的密码,进行加密String password = employee.getPassword();
//          将密码转化为字体节数组继续加密后,赋值给passwordpassword = DigestUtils.md5DigestAsHex(password.getBytes());
//        2.根据用户进行查询,查看数据是否存在LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
//        Employee::getUsername(创建Employee对象获取username属性) = 前端传递的usernamequeryWrapper.eq(Employee::getUsername,employee.getUsername());
//        根据查询结果封装为实体类对象Employee one = employeeService.getOne(queryWrapper);
//        3.判断当前username的返回结果if (one == null){return R.error("当前用户不存在");}
//        4.密码比对if (!one.getPassword().equals(password)){return R.error("密码错误,请重新输入");}
//        5.查看员工状态if (one.getStatus() == 0){return R.error("当前用户已被禁用");}
//        6.登录成功,将id保存到session中,返回登录成功结果HttpSession session = request.getSession();session.setAttribute("employee",one.getId());return R.success(one);}@RequestMapping("/logout")
//          退出不需要返回详细数据public R logout(HttpServletRequest request ){HttpSession session = request.getSession();
//        退出操作,将session的employee信息删除session.removeAttribute("employee");return R.success("操作成功");}
}

使用过滤器拦截路径

/*** 配置过滤器*/
//                       过滤器名称              过滤器拦截路径
@WebFilter(filterName = "LoginCheckFilter" , urlPatterns = "/*")
@Component
@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 {
//        向下转型HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;log.info("开始获取路径");
//        1.获取本次请求的URLString requestURI = request.getRequestURI();
//        2.判断请求是否处理    urls是放行路径   /employee/page是当前列表的查询请求String[] urls = new String[]{"/employee/login","/employee/page","/employee/logout","/backend/**","/front/**"};boolean check = check(urls, requestURI);
//        check  = true时 ,标识存在当前放行目录中,进行放行if (check){log.info("放行目录放行");  //放行路径filterChain.doFilter(request,response);  //放行return;}log.info("登录放行前置");
//        判断用户是否登录HttpSession session = request.getSession();Object employee = session.getAttribute("employee");log.info("登录放行中置"+requestURI);if (employee != null){log.info("登录放行路径");  //放行路径filterChain.doFilter(request,response);  //放行return;}
//        不放行,将R对象转为JSON,响应给前端处理log.info("放行跳转");response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/***   路径匹配,判断本次请求是否放行*/public boolean check(String[] urls , String requestURL){for (String s:urls){
//            是否匹配boolean match = PATH_MATCHER.match(s, requestURL);if (match){return true;}}return false;}
}

指定异常处理器


/***   指定异常处理   annotations(拦截哪个注解下的异常) = (RestController.class)*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法,处理sql异常*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R exceptionHandler(SQLIntegrityConstraintViolationException ex){if (ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String name = s[2] + "已存在";return R.error(name);}return R.error("未知异常");}}

分页查询

分页插件

/***   分页插件*/
@Configuration()
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}}

使用mybatis-plus进行分页

    /**   分页查询*/@GetMapping("/page")        //   /employee/page?page=1&pageSize=10&name=123public R page(int page,int pageSize,String name){log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
//        分页条件   查询的页码   每页数据Page employeePage = new Page(page, pageSize);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
//        添加过滤条件   StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行      getUsername的值 ?=  namequeryWrapper.like(StringUtils.isNotEmpty(name),Employee::getUsername,name);
//        添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);
//        将分页  和  查询条件传入进去employeeService.page(employeePage,queryWrapper);return R.success(employeePage);}

消息转换器

将json转为java 将java转为json

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 org.springframework.stereotype.Component;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);}
}
@Slf4j   //  输出日志
@Configuration   // 声明配置类
public class WebConfig extends WebMvcConfigurationSupport {/***   扩展springmvc的消息转换器* @param converters*/@Overrideprotected void extendMessageConverters(List> converters) {//创建消息转换器MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();//设置对象转换器,底层使用jackson将对象转化为jsonmessageConverter.setObjectMapper(new JacksonObjectMapper());//将上面的转换器,追加到mvc的框架集合中,优先使用converters.add(0,messageConverter);}
}

修改->禁用

    /***  修改*/@PutMappingpublic R BanEmployee(@RequestBody  Employee employee ,HttpServletRequest request){//前端已修改禁用状态Integer status = employee.getStatus();System.out.println("禁用状态为:"+status);
//        拿出登录时存储的当前用户idLong employee1 = (Long)request.getSession().getAttribute("employee");employee.setUpdateTime(LocalDateTime.now());employee.setUpdateUser(employee1);employeeService.updateById(employee);return R.success("操作成功");}

分页

分页插件

import com.baomidou.mybatisplus.annotation.DbType;
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;/***   分页插件*/
@Configuration()
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}}
/**   分页查询*/@GetMapping("/page")        //   /employee/page?page=1&pageSize=10&name=123public R page(int page,int pageSize,String name){log.info("page = {} , pageSize = {} , name = {}",page,pageSize,name);
//        分页条件   查询的页码   每页数据Page employeePage = new Page(page, pageSize);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
//        添加过滤条件   StringUtils.isNotEmpty()->判断当前属性是否为空,为空不执行      getUsername的值 ?=  namequeryWrapper.like(StringUtils.isNotEmpty(name),Employee::getUsername,name);
//        添加排序条件queryWrapper.orderByDesc(Employee::getUpdateTime);
//        将分页  和  查询条件传入进去employeeService.page(employeePage,queryWrapper);return R.success(employeePage);}

页面回显

    /***  页面回显*/@GetMapping("/{id}")public  R ShowDate(@PathVariable Long id){log.info("当前id是:"+id);LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();queryWrapper.eq(Employee::getId,id);Employee one = employeeService.getOne(queryWrapper);return R.success(one);}

自动填充ThreadLocal

客户端每次发送的http请求,对应的服务器都会分配一个新的线程来处理,在处理请求时,多个类的多个方法均为同一个显示。->拦截器->执行update方法->自定义字段处理器 用的都是同一个线程

ThreadLocal:不是线程,而是线程的局部变量,使用ThreadLocal维护变量时,每一个ThreadLocal为当前变量的副本,每个线程都可以独立的改变副本,而不会影响其他线程对应的版本,ThreadLocal为每个线程提供单独的存储时间,具有线程隔离的效果,只有在线程内才可以取的到对应的值,线程外,不能访问该值.

1.加入注解

@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.自定义对象处理器通过 ThreadLocal 获取当前操作对象的id值

/***    自定义元数据对象处理器,将公共字段的值key*/
@Component
@Slf4j
public class MyMetaObjecthandler implements MetaObjectHandler {@Override   // 执行insert操作时,执行public void insertFill(MetaObject metaObject) {long id = Thread.currentThread().getId();log.info("自定义线程id为:"+id);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) {long id = Thread.currentThread().getId();log.info("自定义线程id为:"+id);metaObject.setValue("updateTime",LocalDateTime.now());metaObject.setValue("updateUser",BaseContext.getCurrentId());}
}

3.过滤器中,判断当前员工是否登录时,加入了 ThreadLocal 的set方法存储当前用户id的值

在这里插入图片描述4.自定义ThreadLocal

/***  基于ThreadLocal的封装工具类,用户保存和获取当前登录Id*/
public class BaseContext {//    Long表示当前存储线程的字段属性private static ThreadLocal threadLocal = new ThreadLocal<>();/***  获取id,存储id* @param id*/public static void serCurrentId(Long id){threadLocal.set(id);}/***  取出存储id* @return*/public static Long getCurrentId(){return threadLocal.get();}
}

新增用户

   @PostMappingpublic R  Employee(@RequestBody Employee employee, HttpServletRequest request){String username = employee.getUsername();
//        2.根据用户进行查询,查看数据是否存在LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
//        Employee::getUsername(创建Employee对象获取username属性) = 前端传递的usernamequeryWrapper.eq(Employee::getUsername,employee.getUsername());Employee user = employeeService.getOne(queryWrapper);
//        System.out.println(user);if (user != null){return R.error("当前用户名称已经存在");}
//        设置默认密码加密密码employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes()));
//        获取当前sessionHttpSession session = request.getSession();Long empId =(Long) session.getAttribute("employee");
//        employee.setCreateUser(empId);
//        employee.setUpdateUser(empId);employeeService.save(employee);return R.success("操作成功");}

绑定的数据不能删除

查看当前分类id是否绑定了菜品和套餐,如果绑定了菜品和套餐抛出自定义异常,没有则删除当前分类

    /***   根据id进行删除分类* 	@param id*/@Overridepublic void remove(Long id) {//        查询当前分类是否关联了菜品LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();queryWrapper.eq(Dish::getCategoryId,id);int count = dishService.count(queryWrapper);if (count > 0){//  关联了菜品throw  new CustomException("当前分类已存在菜品,不可被删除");}//        查询当前分类是否关联了套餐LambdaQueryWrapper qw = new LambdaQueryWrapper<>();qw.eq(Setmeal::getCategoryId,id);int count1 = setmealService.count(qw);if (count1 > 0){//  关联了菜品throw  new CustomException("当前分类已存在套餐,不可被删除");}
//        删除分类super.removeById(id);}

自定义异常

/***   业务自定义异常*/
public class CustomException extends RuntimeException {public CustomException(String message){super(message);}}

指定自定义异常处理

/***   指定异常处理   annotations(拦截哪个注解下的异常) = (RestController.class)*/
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {/*** 异常处理方法,处理sql异常*/@ExceptionHandler(SQLIntegrityConstraintViolationException.class)public R exceptionHandler(SQLIntegrityConstraintViolationException ex){if (ex.getMessage().contains("Duplicate entry")){String[] s = ex.getMessage().split(" ");String name = s[2] + "已存在";return R.error(name);}return R.error("未知异常");}/***  自定义异常* @param ex* @return*/@ExceptionHandler(CustomException.class)public R exceptionHandlerone(CustomException ex){
//        抛出异常提示return R.error(ex.getMessage());}}

上传

1.获取当前文件名称,根据文件名称获取文件后缀,使用uuid生成新的文件名称
2.判断当前目录是否已存在
3.输出文件到指定位置
在这里插入图片描述``

 @PostMapping("/upload")  // 文件上传public R upload(MultipartFile file){// 获取原始文件名称String fileName = file.getOriginalFilename();// 随机生成文件名称,uuid       获取文件后缀名String hzName = fileName.substring(fileName.lastIndexOf("."));//  使用uuid  +  后缀名称fileName = UUID.randomUUID().toString() + hzName;//  判断当前目录是否存在,不存在则创建File directory = new File(basePath);if (!directory.exists()){directory.mkdirs();}try {
//            指定下载图片的位置file.transferTo(new File(basePath+fileName));} catch (IOException e) {e.printStackTrace();}return R.success(fileName);}

下载

1.输入流 获取当前文件所在的路径
2.创建输出流,用于响应文件
3.输入流读取文件,输出流写文件
4.刷新并关闭流

    @GetMapping("/download")public void download(String name, HttpServletResponse response){try {
//            获取输入流,读取当前上传图片的内容FileInputStream fileInputStream = new FileInputStream(new File(basePath + name));
//            输出流,用于读取当前下载文件ServletOutputStream outputStream = response.getOutputStream();
//            上传的文件格式response.setContentType("image/jpeg");
//            读取输出流内容至输出流int len= 0;byte[] bytes = new byte[1024];while ((len = fileInputStream.read(bytes))!=-1){outputStream.write(bytes,0,len);
//                刷新outputStream.flush();}
//              关闭流outputStream.close();fileInputStream.close();} catch (Exception e) {e.printStackTrace();}}

不在同一个表时

一般用于前端传递的值与封装的对象不统一,业务需要多表处理,简化操作,把两个表的属性写在一起
在这里插入图片描述
在这里插入图片描述

响应的字段不在同一个类中 响应数据 分页

1.创建关联表对象DTO,关联表中为响应的全部字段
2.创建响应类的原对象,进行数据的条件查询
在这里插入图片描述
3.dto拷贝源对象,排除原对象缺少的字段 (list)
4.源对象获取缺少的字段(list),遍历该对象,拿出对象的值赋值给dto对象,根据拿出的对象id进行分类表格的查询,查询出获取分类表的name值,赋值给dto对象,再将新的list集合,排除拷贝的对象赋值给Dto对象
在这里插入图片描述

    @GetMapping("/page")public R pageDish(int page,int pageSize,String name){Page pg = new Page<>(page,pageSize);Page pgDto = new Page<>();  //获取DishDto对象,里面有dish类中缺少的页面显示字段LambdaQueryWrapper queryWrapper= new LambdaQueryWrapper<>();
//        添加排序条件queryWrapper.like(name!=null,Dish::getName,name);queryWrapper.orderByDesc(Dish::getUpdateTime);dishService.page(pg,queryWrapper);//拷贝对象       records是页面查出的缺少数据集合   要排除掉BeanUtils.copyProperties(pg,pgDto,"records");//获取列表排除的字段   records  给其添加字段List records = pg.getRecords();// 得到records集合的原对象,map方法取出对象,List list = records.stream().map((item)->{DishDto dishDto = new DishDto();//创建dto对象BeanUtils.copyProperties(item,dishDto);//将取出的对象拷贝到dto对象Long categoryId = item.getCategoryId(); //获取分类idCategory byId = categoryService.getById(categoryId);//根据id查询出该条分类数据String name1 = byId.getName();//获取当前对象的name字段值dishDto.setCategoryName(name1);//查询出的name字段值通过dto的set方法进行赋值return dishDto;}).collect(Collectors.toList());  //创建新的list集合pgDto.setRecords(list);      //将新的list集合,赋值给当前排除的集合的对象return R.success(pg);}

新增 修改 存在不同表的数据

新增:
1.先新增菜品,获取菜品id
2.获取dto对象的getFlavors(为list)属性,属性封装了口味表
3.item遍历list,获取菜品id,储存到口味表中
4.将dto封装的口味表list新增通过saveBatch方法新增

    /*** 新增* @param dishDto*/@Overridepublic void saveWithFlaow(DishDto dishDto) {//1.新增菜品表, 获取idthis.save(dishDto);Long Id = dishDto.getId();//2.获取dto对象的getFlavors()属性,属性封装了口味表List flavors = dishDto.getFlavors();//3.item遍历list,获取菜品id,储存到口味表中flavors = flavors.stream().map((item) -> {item.setDishId(Id);return item;}).collect(Collectors.toList());//4.再将dto封装的口味表list新增dishFlavorService.saveBatch(flavors);}

修改:
1.新增菜品
2.将口味表删除
3.获取当前Dta的请求参数list值
4.遍历list,得到的对象获取菜品表id
5.新增口味表

    /**** @param dishDto    修改菜品*/@Overridepublic void UpdateDish(DishDto dishDto) {//1.新增菜品this.updateById(dishDto);//2.删除口味表LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper();lambdaQueryWrapper.eq(DishFlavor::getDishId,dishDto.getId());dishFlavorService.remove(lambdaQueryWrapper);//3.获取flavors属性 -> 为口味表List flavors = dishDto.getFlavors();//4.item遍历当前口味集合,获取菜品的id,口味表进行赋值存储flavors = flavors.stream().map((item) -> {item.setDishId(dishDto.getId());return item;}).collect(Collectors.toList());//5.完成口味表新增dishFlavorService.saveBatch(flavors);}

删除

1.批量删除时,传递list值
2.queryWrapper使用in,查询出删除的全部数据

    /***  删除* @param ids*/@Overridepublic void DeleteDisable(List ids) {LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.in(Setmeal::getId,ids); // 查询所有ids的数据queryWrapper.eq(Setmeal::getStatus,1); //查询ids数据中是否有在售的int count = this.count(queryWrapper);if (count > 0){   // 大于0代表有在售套餐throw new CustomException("套餐正在售卖,不可删除");}this.removeByIds(ids);  //否则批量删除所有ids的数据LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);setmealDishesService.remove(lambdaQueryWrapper);}

用户下单

1.从ThreadLocal获取当前用户的id
2.根据用户id,查询当前用户添加到购物车中的数据
3.根据当前用户id查询,地址数据
4.遍历当前购物车的数据,订单详情表通过set的方式进行复制
5.通过set的方式赋值订单表
6.像订单表插入输入,向订单明细表插入数据,删除购物车数据

    /**   用户下单* @param orders*/@Override@Transactionalpublic void Submit(Orders orders) {// 1.获取用户idLong currentId = BaseContext.getCurrentId();// 2.根据用户,获取当前购物车数据LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(ShoppingCart::getUserId,currentId);List list = shoppingCartService.list(lambdaQueryWrapper);if (list == null || list.size() == 0){throw new CustomException("购物车不能为空");}// 查询用户数据 、 地址数据User byId = userService.getById(currentId);Long addressBookId = orders.getAddressBookId();AddressBook addressBook = addressBookService.getById(addressBookId);if (addressBook == null){throw new CustomException("地址不能为空");}// 3.订单表1条//  生成订单号long orderId = IdWorker.getId();// 计算总金额,遍历购物车数据AtomicInteger amount = new AtomicInteger(); //保证多线程,保证数据正确List orderDetails = list.stream().map((item)->{OrderDetail orderDetail = new OrderDetail();orderDetail.setOrderId(orderId);orderDetail.setNumber(item.getNumber());orderDetail.setDishFlavor(item.getDishFlavor());orderDetail.setDishId(item.getDishId());orderDetail.setSetmealId(item.getSetmealId());orderDetail.setName(item.getName());orderDetail.setImage(item.getImage());orderDetail.setAmount(item.getAmount());amount.addAndGet(item.getAmount().multiply(new BigDecimal(item.getNumber())).intValue());return orderDetail;}).collect(Collectors.toList());orders.setNumber(String.valueOf(orderId));orders.setId(orderId);orders.setOrderTime(LocalDateTime.now());orders.setCheckoutTime(LocalDateTime.now());orders.setStatus(2);orders.setAmount(new BigDecimal(amount.get()));//总金额orders.setUserId(currentId);orders.setNumber(String.valueOf(orderId));orders.setUserName(byId.getName());orders.setConsignee(addressBook.getConsignee());orders.setPhone(addressBook.getPhone());orders.setAddress((addressBook.getProvinceName() == null ? "" : addressBook.getProvinceName())+ (addressBook.getCityName() == null ? "" : addressBook.getCityName())+ (addressBook.getDistrictName() == null ? "" : addressBook.getDistrictName())+ (addressBook.getDetail() == null ? "" : addressBook.getDetail()));//向订单表插入数据,一条数据this.save(orders);// 4.订单明细表插入数据orderDetailServicel.saveBatch(orderDetails);// 5.下单完成后,清空购物车数据shoppingCartService.remove(lambdaQueryWrapper);}

redis 优化

spring:redis:host: localhostport: 6379database: 0
    org.springframework.bootspring-boot-starter-data-redis

序列化器

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate redisTemplate = new RedisTemplate<>();//默认的Key序列化器为:JdkSerializationRedisSerializerredisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);return redisTemplate;}
}

1.查询数据,保存redis里面

  // 1.先从redis里面获取数据
/***   查询套餐* @return*/@GetMapping("/list")public R> QueryDishCategoryId(Dish dish){List listDto = null;String key = "dish_" + dish.getCategoryId() +"_" +dish.getStatus();// 1.先从redis里面获取数据listDto = (List)redisTemplate.opsForValue().get(key);if (listDto != null){return R.success(listDto);}// 2.没有的话查询数据库//3. 如果不存在,查询数据库,把查询到的菜品缓存到redis里// key = dish_当前套餐的id_1    listDao是查询出的套餐结果redisTemplate.opsForValue().set(key,listDto,60, TimeUnit.MINUTES);return R.success(listDto);

2.修改/删除/更新后,删除当前redis

    @PutMappingpublic R UpdateDish(@RequestBody DishDto dishDto){dishService.UpdateDish(dishDto);// 清理指定缓存String key = "dish_" + dishDto.getCategoryId() +"_1";redisTemplate.delete(key);return R.success("操作成功");}

SpringCache简化开发

注解:
EnableCaching 开启缓存注解功能,一般放在启动类上
使用map的方式


@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate CacheManager cacheManager;@Autowiredprivate UserService userService;/***       CachePut将方法的返回值放入缓存*       value:缓存的名称*       key :缓存的key,存储的值为 user对象*/@CachePut(value = "userCache" , key ="#user.id" )@PostMappingpublic User save(User user){userService.save(user);return user;}/***  清理指定缓存根据value和key,删除缓存*/@CacheEvict(value = "userCache" , key = "#id")@DeleteMapping("/{id}")public void delete(@PathVariable Long id){userService.removeById(id);}/***  更新数据时,删除缓存数据*/@CacheEvict(value = "userCache" , key = "#user.id")@PutMappingpublic User update(User user){userService.updateById(user);return user;}/**  在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,查询数据*  condition->条件,满足条件时,才缓存数据*/@Cacheable(value = "userCache" , key = "#id" , condition = "#result != null" )@GetMapping("/{id}")public User getById(@PathVariable Long id){User user = userService.getById(id);return user;}/***  在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,查询数据*/@Cacheable(value = "userCache" , key = "#user.id+'_'+#user.name" , condition = "#result != null" )@GetMapping("/list")public List list(User user){LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(user.getId() != null,User::getId,user.getId());queryWrapper.eq(user.getName() != null,User::getName,user.getName());List list = userService.list(queryWrapper);return list;}
}

开发的方式:

1.启动类+ @EnableCaching 开启缓存注解功能
2.配置文件

spring:cache:redis:time-to-live: 1800000  #30分钟到期redis:host: localhostport: 6379username: rootdatabase: 0

3.jar包

      org.springframework.bootspring-boot-starter-cacheorg.springframework.bootspring-boot-starter-data-redis

使用:
1.当查询时,有缓存的时候,直接走缓存,如果没有匹配到缓存的时候,那么查询数据库后,再将结果加入缓存中

    /***  查询套餐*/@GetMapping("/list")@Cacheable(value = "setSetmea" , key = "#dish.categoryId")// 设置名称为setSetmea,key为#dish.categoryId, value为返回值的缓存数据public R> QuerySetmea(Setmeal dish){LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Setmeal::getCategoryId,dish.getCategoryId());lambdaQueryWrapper.eq(dish.getStatus()!=null,Setmeal::getStatus,dish.getStatus());List list = setmealService.list(lambdaQueryWrapper);return R.success(list);}

2.当使用删除/新增/修改的方法时,会直接删除改缓存

    /***  删除菜单* @return*/@CacheEvict(value = "setSetmea" , allEntries = true)// 删除名称为setSetmea的全部数据@DeleteMappingpublic R DeleteDisable(@RequestParam List ids){setmealService.DeleteDisable(ids);return R.success("删除成功");}

其他方法

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Autowiredprivate CacheManager cacheManager;@Autowiredprivate UserService userService;/***       CachePut将方法的返回值放入缓存*       value:缓存的名称*       key :缓存的key,存储的值为 user对象*/@CachePut(value = "userCache" , key ="#user.id" )@PostMappingpublic User save(User user){userService.save(user);return user;}/***  清理指定缓存根据value和key,删除缓存*/@CacheEvict(value = "userCache" , key = "#id")@DeleteMapping("/{id}")public void delete(@PathVariable Long id){userService.removeById(id);}/***  更新数据时,删除缓存数据*/@CacheEvict(value = "userCache" , key = "#user.id")@PutMappingpublic User update(User user){userService.updateById(user);return user;}/**  在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,*  查询数据后,将方法的返回值放到缓存中*  condition->条件,满足条件时,才缓存数据*  unless -> 条件满足时,不缓存*/@Cacheable(value = "userCache" , key = "#id" , unless = "#result == null" )@GetMapping("/{id}")public User getById(@PathVariable Long id){User user = userService.getById(id);return user;}/***  在执行方法时,先查看cacheManager里有没有缓存的数据,如果有的话,注解返回数据,如果没有的话,*  查询数据后,将方法的返回值放到缓存中*/@Cacheable(value = "userCache" , key = "#user.id+'_'+#user.name" , condition = "#result != null" )@GetMapping("/list")public List list(User user){LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(user.getId() != null,User::getId,user.getId());queryWrapper.eq(user.getName() != null,User::getName,user.getName());List list = userService.list(queryWrapper);return list;}}

读写分离、主从复制

对同一时间段有大量并发,将数据库拆分为主库和从库,主库负责增删改操作,从库负责查询操作,能够有效的避免由数据更新导致的行锁,使整个系统的查询性能得到极大的改善

        org.apache.shardingspheresharding-jdbc-spring-boot-starter4.0.0-RC1

server:port: 8080
mybatis-plus:configuration:#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: ASSIGN_ID
spring:shardingsphere:datasource:names:master,slave# 主数据源master:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.138.100:3306/rw?characterEncoding=utf-8username: rootpassword: root# 从数据源slave:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.138.101:3306/rw?characterEncoding=utf-8username: rootpassword: rootmasterslave:# 读写分离配置load-balance-algorithm-type: round_robin #轮询# 最终的数据源名称name: dataSource# 主库数据源名称master-data-source-name: master    # 从库数据源名称列表,多个逗号分隔slave-data-source-names: slaveprops:sql:show: true #开启SQL显示,默认falsemain:allow-bean-definition-overriding: true

Nginx

nginx是一款轻量级别的web服务器/反向代理服务器。内存少,并发能力强。
Linux安装
1.yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
2.wget 看看是否下载 yum install wget 下载
3. wget http://nginx.org/download/nginx-1.16.1.tar.gz 下载
4. tar -zxvf nginx-1.16.1.tar.gz 解压
5. cd nginx-1.16.1 mkdir /usr/local/nginx
./configure --prefix=/usr/local/nginx 安装到哪里去
6.make && make install; 安装

nginx 常用命令

  1. ./nginx -v 查看版本
  2. ./nginx -t 查看配置文件是否错误
  3. ./nginx 启动nginx
  4. /usr/local/nginx/sbin/nginx(地址) -s stop 关闭nginx
  5. log里面 cat nginx.pid 当前进程id
  6. nginx -s reload 重新加载
    vim /etc/profile 里面将Nginx的地址输入到里面去可以不用输入地址就可以使用了(PATH=/usr/local/nginx/sbin:JAVAHOME/bin:JAVA_HOME/bin:JAVAH​OME/bin:PATH)

全局块
和nginx运行相关的全局配置
events块
和网络连接相关的网络配置
http块
代理、缓存、日志记录
http全局快:
servere模:server全局块 / location 块

1.部署静态资源/vim nginx.conf 将静态资源放到html目录下,即可访问
在这里插入图片描述

2.反向代理
服务端进行设置,为服务器提供服务。
用户访问反向代理服务器,反向代理将用户发送的请求转到到指定服务上
在这里插入图片描述

3.负载均衡
客户端发送请求至负载均衡服务器,由负载均衡服务器进行web服务器的分发操作。
在这里插入图片描述
在这里插入图片描述

yapi

介绍:
Api管理平台

Swagger

生成接口文档

  1. 导入maven坐标
        com.github.xiaoyminknife4j-jfinal3.0.2
  1. 导入相关配置
    在这里插入图片描述3.设置静态资源映射
 registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

在这里插入图片描述
4.配置拦截器的放行请求
在这里插入图片描述

可以直接查看接口
在这里插入图片描述
常用注解
在这里插入图片描述

相关内容

热门资讯

话费小本创业的项目 话费小本创... 小本创业项目:快餐小吃店话费小本创业的项目随着社会压力的增大,很多人没有时间本人做饭,为了工作会选择...
2014小本创业不容错过的项目... 2014小本创业做什么比较好?其实对于大部分创业者来说,小本创业更能取得成功,今天就给大家介绍几个小...
2014小本创业不容错过 卤中... 如今,随着近年来熟食行业的快速发展,给广大的消费者带来了美味,也给投资者带来了绝好的契机,卤中仙熟食...
小本创业好项目摆地摊做什么好?... 小本创业一直以来都是比较受普通大众所喜爱的,而很多人白手起家最开始做的就是摆地摊,不要瞧不起这个小本...
重访抗战地标丨英雄之城·不能忘...   为什么要永远铭记抗战英雄?因为当“中华民族到了最危险的时候”,“用血肉筑起新的长城”并不是艺术升...
各地高校持续实施“宏志助航计划...   央视网消息:当前正值高校毕业生就业关键期,教育部要求各地高校加快实施年度“宏志助航计划”,为离校...
2018小本创业好2017十个... 1、农户住宅设计业我们知道盖房是农民生活中的头等大事,随着社会经济的发展,农户住宅也向实用、美观、个...
环保创业好项目 环保行业有哪些... 酿酒蒸馏/餐饮食堂,全国90%传统锅炉需要改造。千亿市场商机等你来分享。具有竞争性独特产品,市场唯一...
2015年有哪些小本创业好项目... 对于普通大众来说,由于资金的限制,进行小本创业是最恰当的选择.都有什么项目适宜小本创业经营呢?  第...
创业小本好项目 创业小本好项目... 生活水平越来越高,这对于刚毕业的年轻人来说或许就产生了不小的压力,进入职场的时间不长,工资也没有太高...