文章目录
- 1.业务开发
- day01
- 1.软件开发整体介绍
- 2.项目整体介绍:star:
- 3.开发环境搭建
- 4.登录功能:star:
- 4.1代码实现
- 5.退出功能
- 6.页面效果出现
- day02
- 1.完善登录功能
- 2.新增员工功能
- 3.启用禁用员工信息:star:(自定义消息转换器使用)
- 4.编辑员工信息
- day03
- 1.公共字段自动填充
- 2.新增分类
- 3.分类的分页查询
- 4.分类删除:star:
- 3.修改分类
- day04
- 1.文件上传:star:
- 2.文件下载
- 3.新增菜品:star:
- 4.菜品信息分页查询
- 5.菜品修改信息:star:
- day05
- 1.概述
- 数据模型
- 2.新增套餐
- 3.套餐分页查询
- 4.删除套餐信息
- 5.短信发送
- 6.手机验证码登录
- day06
- 1.地址簿相关功能
- 2.菜品展示
- 3.加入购物车
- 4.用户下单:star:
- 2.项目优化
- day01
- 1.引入版本控制
- 2.环境搭建
- 3.短信验证码(Redis)
- 4.缓存菜品数据
- 5.SpringCache
- 6.使用springCache缓存套餐数据
- day02
- 1.MySQL主从复制
- 2.Nginx
- 1.概述
- 2.基本命令
- 3.配置文件
- 4.niginx具体应用:star:
- 1.部署静态资源
- 2.反向代理
- 3.负载均衡
- day03
- 1.前后端分离
- 1.概述
- 2.前后端分离开发
- 3.YApi
- 4.Swagger(生成接口文档)
- 2.项目部署
- day03
- 1.前后端分离
- 1.概述
- 2.前后端分离开发
- 3.YApi
- 4.Swagger(生成接口文档)
- 2.项目部署
1.业务开发
1.对后端返回请求值的分析
2.对不同种请求参数的分析
3.事务管理
day01
1.软件开发整体介绍
2.项目整体介绍⭐️
- 后端:管理菜品和员工信息
- 前台:通过手机端,可以浏览菜品和添加客户端
开发项目流程:
- 实现基本需求,用户能在手机浏览器访问
- 对移动端应用改进,使用微信小程序实现
- 对系统进行优化升级,提高系统的使用性能
技术选型:
功能架构:
角色:
3.开发环境搭建
- 涉及数据库 + maven
-
数据库表介绍:
-
Maven项目搭建
- 第一步,先创建一个maven空项目,然后设置好pom.xml文件和application.yml文件
- 第二步,配置springboot环境,启动测试
- 第三部,导入前端静态资源,加入配置类来将浏览器路径和本地项目文件路径做匹配
@Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 设置静态资源映射 * @param registry */ @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开始进行静态资源映射..."); registry.addResourceHandler("/backend/**").//浏览器地址栏 //映射到真实的路径(映射的真实路径末尾必须添加斜杠`/`) addResourceLocations("classPath:/backend/");//这里不要加空格符,贴着放 registry.addResourceHandler("/front/**") .addResourceLocations("classpath:/front/"); } }
4.登录功能⭐️
查看项目代码的一般逻辑:
前端html界面,找到响应的元素,找到对应的js动态方法,分析发送(Ajax)请求到后端的过程,处理好后端代码,返回处理的R对象给前端来判断使用(判断运用是否正确),最后前端再决定跳转到哪一个界面
-
需求分析
前端代码
-
功能结构
4.1代码实现
-
导入通用返回结果类R类
前端代码与R类关系
R类
-
梳理登录方法逻辑
-
代码实现
5.退出功能
-
功能逻辑
6.页面效果出现
-
index.html
menuList属性值封装了不同页面的信息
day02
完成功能:
1.完善登录功能
-
问题分析
使用过滤器或者拦截器实现
-
代码实现步骤
-
具体实现
-
1.拦截器用原生的Servlet拦截,因此主加载类要加上@ServletComponentScan注解拦截
2.加上日志注解,能够使用日志输出
- 2.具体逻辑
前端处理部分
前端处理响应拦截器:如果是这个状态那么自动跳转回登录页面
后端部分:
package com.itiheima.reggie.filter; import com.alibaba.fastjson.JSON; import com.itiheima.reggie.entity.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; /** * @author qin start * @create 2023-04-24-11:28 */ @WebFilter(filterName = "LoginCheckFilter",urlPatterns = "/*")//拦截所有路径 @Slf4j public class LoginCheckFilter implements Filter { //spring路径匹配器 public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher(); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { //转成http格式的Servlet HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // 1、获取本次请求的URI //定义不需要处理的请求路径 String url = request.getRequestURI(); String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**",//静态资源放行 "/front/**" }; // 2、判断本次请求是否需要处理 boolean check = check(url, urls); // 3、如果不需要处理,则直接放行 if(check){ log.info("拦截到的请求:{}",url); filterChain.doFilter(request,response); return; } // 4、判断登录状态,如果已登录,则直接放行 //通过判断session存储的数据 if(request.getSession().getAttribute("id") != null){ log.info("登陆成功!用户id为:{}",request.getSession().getAttribute("id")); filterChain.doFilter(request,response); return; } // 5、如果未登录则返回未登录结果 // //这里要用输出流,因为不是控制器自动返回json格式对象 log.info("登陆失败!跳转回登录界面"); response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));//不放行 return; } /** * 判断请求路径是否在不需要处理的路径里 * @param url * @param urls * @return */ public boolean check(String url,String[] urls){ for (String pattern : urls) { //这里顺序不能搞反,第一个参数为匹配模式 if(PATH_MATCHER.match(pattern,url)){ return true; } } return false; } }
-
2.新增员工功能
- 功能
- 数据模型中,employee字段要唯一
-
执行流程
-
代码实现
@PostMapping public R<String> addEmployee(HttpServletRequest request,@RequestBody Employee employee){ // log.info(employee.toString()); //设置初始密码 employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes(StandardCharsets.UTF_8))); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); employee.setCreateUser((long)request.getSession().getAttribute("id")); employee.setUpdateUser((long)request.getSession().getAttribute("id")); employeeService.save(employee); return R.success("新增员工成功"); }
-
处理数据库插入重复名字异常
全局异常处理器来处理异常
关键点在@ControllerAdvice和@ExceptionHandler,一个用来拦截方法,一个用来处理异常
@ControllerAdvice捕获方法后,有异常就处理
@ControllerAdvice(annotations = {RestController.class, Controller.class}) @ResponseBody//java对象转为json格式的数据 @Slf4j public class GlobalExceptionHandler { //用来捕获插入重复数据异常 @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){ log.error(exception.getMessage()); return R.error("failed"); } }
//用来捕获插入重复数据异常 @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler (SQLIntegrityConstraintViolationException exception){ if (exception.getMessage().contains("Duplicate entry")){ String[] split = exception.getMessage().split(" ");//根据空格符分割数组 String msg = split[2] + "已存在"; return R.error(msg); } return R.error("unknown error"); }
-
小结:
-
3.启用禁用员工信息⭐️(自定义消息转换器使用)
- 需求分析
-
启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作在Controller中创建update方法,此方法是一个通用的修改员工信息的方法
@PutMapping public R<String> update(HttpServletRequest request, @RequestBody Employee employee){ log.info(employee.toString()); Long empID = (Long)request.getSession().getAttribute("employee"); employee.setUpdateTime(LocalDateTime.now()); employee.setUpdateUser(empID); employeeService.updateById(employee); return R.success("员工修改信息成功"); }
-
出现问题:
原因:js对后端传过来的数据long类型精度丢失,因为Java对象默认通过SpringMVC消息转换器传递过来的数据默认是一般的json格式,"id"字段会被当做整型数据处理,而js中Long型精度和后端不匹配。
**解决:**对SpringMVC配置自定义的消息处理器,将"id"对应的json格式数据转为字符串值
-
具体解决步骤:
①提供对象转换器Jackson0bjectMapper,基于Jackson进行Java对象到json数据的转换
②在WebMcConfig配置类中扩展Spring mvc的消息转换器,在此消息转换器中使用提供的对象转换器进行Java对象到json数据的转换//JacksonObjectMapper package com.itzq.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); } }
/** * 扩展mvc框架的消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { //创建消息转换器对象 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); //设置对象转化器,底层使用jackson将java对象转为json messageConverter.setObjectMapper(new JacksonObjectMapper()); //将上面的消息转换器对象追加到mvc框架的转换器集合当中(index设置为0,表示设置在第一个位置,避免被其它转换器接收,从而达不到想要的功能) converters.add(0,messageConverter); }
4.编辑员工信息
-
功能分析
-
后端功能代码实现
回显数据
@GetMapping("/{id}") public R<Employee> getById(@PathVariable Long id){ log.info("根据id查询员工信息。。。"); Employee employee = employeeService.getById(id); if (employee != null){ return R.success(employee); } return R.error("没有查询到该员工信息"); }
修改功能
前面在禁用员工的时候使用到了修改数据,所以这里不用配置
day03
1.公共字段自动填充
-
问题分析:
设置修改时间和修改人等字段在每张表中基本上都有,而且属于多条记录共有的具有相似功能的字段,因此可以每次修改或者插入的时候自动处理
-
用到技术:①Mybatis Plus公共字段自动填充②ThreadLocal线程内部属性
-
技术详解:
ThreadLocal线程内部属性:客户端发送的每次http请求,对应的在服务端都会分配一个新的线程来处理,在处理过程中涉及到下面类中的方法都属于相同的一个线程,因此一次请求中ThreadLocal对象也应该相同
因此,可以用ThreadLocal用于保存登录用户id,解决实现公共字段填充类无法在方法上自动装配HttpServletRequest的困境,做到会话间数据共享
-
实现步骤
-
具体代码实现
第一版
package com.itzq.reggie.common; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @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", new Long(1)); metaObject.setValue("updateUser", new Long(1)); } @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充【update】"); log.info(metaObject.toString()); } }
第二版
@Component @Slf4j public class MyMetaObjectHandler implements MetaObjectHandler { /** * 插入时对公共字段赋值 * @param metaObject */ @Override public void insertFill(MetaObject metaObject) { log.info("公共字段自动填充【insert】"); //1.直接给公共字段设置值 metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("createUser", BaseContext.get()); metaObject.setValue("updateUser", BaseContext.get()); } /** * 更新时对公共字段赋值 * @param metaObject */ @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充【update】"); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser", new Long(1)); } } /** * 工具类,用来获取当前登录用户的id * 这里设置为静态的可以,因为每个线程的ThreadLocal值不同,这样声明成静态的时候不同线程会赋予不同的ThreadLocal值 * @author qin start * @create 2023-04-26-17:34 */ public class BaseContext { private static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void set(Long id){ threadLocal.set(id); } public static Long get(){ return threadLocal.get(); } }
2.新增分类
-
功能分析
-
代码实现
@RestController @RequestMapping("/category") @Slf4j public class CategoryController { @Autowired private CategoryService categoryService; @PostMapping public R<String> save(@RequestBody Category category){ log.info("新增菜品:{}",category); categoryService.save(category); return R.success("1"); } }
3.分类的分页查询
-
代码实现
@GetMapping("/page") public R<Page> page(int page,int pageSize){ //1.定义分页构造器 Page<Category> pageInfo = new Page<>(page,pageSize); //2.定义条件构造器 LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.orderByDesc(Category::getSort); //3.进行查询 categoryService.page(pageInfo,queryWrapper); return R.success(pageInfo); }
4.分类删除⭐️
-
简单代码开发(第一版)
@DeleteMapping public R<String> delete(long ids){//这里一定要为long类型 log.info("删除菜品id:{}",ids); categoryService.removeById(ids); return R.success("1"); }
-
第二版代码开发思路+实现⭐️
因为
菜品``和套餐
都有关联到分类的可能性,因此如果删除分类时,分类里有相应的菜品和套餐,那么要判断不能删除①页面发送Ajax请求,传过来要删除菜品分类的id
②根据id去
菜品表
和套餐表
去查询有几条数据,如果有数据的话抛出一个自定义业务异常,提示不能删除③没有业务异常的话,进行正常的删除操作
1.增加一个业务方法
@Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements CategoryService { @Autowired private DishService dishService; @Autowired private SetMealService setMealService; @Override public void remove(long id) { //1.根据id去查询菜品表 LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Dish::getCategoryId,id); int count = dishService.count(queryWrapper); //抛出自定义业务处理异常 if(count > 0){ throw new CustomException("无法删除分类,该分类下存在菜品信息"); } //2.根据id去查询套餐表 LambdaQueryWrapper<Setmeal> queryWrapper1 = new LambdaQueryWrapper<>(); queryWrapper1.eq(Setmeal::getCategoryId,id); int count1 = setMealService.count(queryWrapper1); //抛出自定义业务处理异常 if(count1 > 0){ throw new CustomException("无法删除分类,该分类下存在套餐信息"); } //3.如果没有关联,那么调用父类ServiceImpl的方法删除 super.removeById(id); } }
2.自定义异常类和全局异常类方法
public class CustomException extends RuntimeException{ public CustomException(String message){ super(message); } }
//用来处理删除菜品分类信息 @ExceptionHandler(CustomException.class) public R<String> exceptionHandler (CustomException ex){ return R.error(ex.getMessage()); }
3.修改分类
-
需求分析
-
代码实现
/** * 修改分类信息 * @param category * @return */ @PutMapping public R<String> update(@RequestBody Category category){ log.info("修改分类信息为:{}",category); categoryService.updateById(category); return R.success("修改分类信息成功"); }
day04
菜品管理相关内容
1.文件上传⭐️
如果部署上去之后,无法打开页面,先clean一下,再打开项目
-
需求分析
- 文件过滤器会先拦截下来请求,直接返回“未登录”的信息
-
具体实现
-
MutipartFile文件名必须为file,与前端标签名保持一致
-
文件上传后转存问题:如果文件上传后不转存到指定位置,那么默认存储在一个本地的临时文件中,程序运行后就会删除,因此要文件转存
-
代码:
注意点:①读取路径用@Value注解从配置文件中读取
②文件名的拼接
③文件路径的判别合法问题
④返回文件名称
@RestController @RequestMapping("/common") @Slf4j public class CommonController { //@Value注解用来取值 @Value("${reggie.path}") private String basePath; /** * 上传文件 * @param file 文件参数名必须为file * @return */ @RequestMapping("/upload") public R<String> upload(@RequestBody MultipartFile file){ log.info("上传图片:{}",file.toString()); //1.生成文件名字,实现文件的保存功能,目前先保存到本地上 //获取文件类型 String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //使用UUID生成自定义文件名 String fileName = UUID.randomUUID().toString() + suffix; //创建存放的目录对象 File dir = new File(basePath); if(!dir.exists()){ dir.mkdirs(); } try { file.transferTo(new File(basePath + fileName)); } catch (IOException e) { e.printStackTrace(); } //返回文件名称,因为页面之后要使用 return R.success(fileName); } }
-
2.文件下载
-
前端分析
-
代码实现
- 注意name字段由前端传过来的参数
/common/download?name=${response.data}
自动赋值 - 注意这里读取是从服务端读,发送是HttpServletResponse获取的输出去,传送回去需要先设置响应头数据格式
@GetMapping("/download") //这的name由前端自动赋值 public void downLoad(String name, HttpServletResponse response){ //获取文件名称,从本地数据来创建一个input流进行读取,output流进行输出 File file = new File(basePath + name); try { FileInputStream is = new FileInputStream(file); OutputStream outputStream = response.getOutputStream(); response.setContentType("image/jpeg"); //文件读写操作 int len = 0; byte[] bytes = new byte[1024]; while((len = is.read(bytes)) != -1){ outputStream.write(bytes); //所储存的数据全部清空 outputStream.flush(); } is.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } }
- 注意name字段由前端传过来的参数
3.新增菜品⭐️
-
需求分析
-
功能实现
-
回显菜品分类信息
因为一进去发送地址在category下,因此要在category下编写相应的请求
/** * 根据条件查询分类信息,并返回json数据 * @param category * @return */ @GetMapping("/list") public R<List<Category>> list(Category category){ LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(category.getType() != null,Category::getType,category.getType()); queryWrapper.orderByDesc(Category::getSort).orderByDesc(Category::getUpdateTime); List<Category> list = categoryService.list(queryWrapper); return R.success(list); }
-
处理前端发过来的请求
DTO继承实体类,扩展实体类
-
将DTO中的数据保存到两张表中
@Service @Transactional public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { @Autowired DishFlavorService dishFlavorService; /** * 根据菜品信息,保存数据到菜品表和菜品口味表 * @param dishDto */ @Override public void saveWithFlavor(DishDto dishDto) { //保存菜品的基本信息到菜品的基本表中,如果有的菜品的属性对不上,那么不保存 super.save(dishDto); //这里保存之后,会将表中的数据重新填回到dishDto,因此也就获取了此时的菜品id //获取新增的菜品id,为口味表中的每一个口味增加相应的菜品id Long id = dishDto.getId(); //获取菜品口味 List<DishFlavor> flavors = dishDto.getFlavors(); for (DishFlavor flavor : flavors) { flavor.setDishId(id); } //批量保存菜品口味数据到菜品口味表 dishFlavorService.saveBatch(flavors); } }
-
-
总结分析
- 处理前端请求
- 后端返回值识前端需求的数据为准,后端定义了数据模型之后只要根据前端需求,将相应的需求放在R.data属性中即可
4.菜品信息分页查询
-
需求分析
- 处理分页查询中的难点在于:如何将dish表中的categoryId字段切换为categoryName,因为前端页面需要菜品分类而不是菜品ID。所以我们就要用到dishDto这个类扩展dish类,① 先从dish表中查数据然后封装到page类中 ② 将page类中的分页信息数据数据拷贝到Page这个分页信息类 ③ 将page中的records信息映射到page的分页信息类
封装Dish数据
-
代码实现
- BeanUtils工具类属于
org.springframework.beans.BeanUtils
,是spring框架提供的工具类,简化数据封装,用于封装JavaBean,避免了大量的get,set方法进行拷贝赋值
@GetMapping("/page") public R<Page> page(int page,int pageSize,String name){ //1.根据名字查询分页信息 //首先保证名字不为空 Page<Dish> dishPageInfo = new Page<>(page,pageSize); Page<DishDto> dtoPageInfo = new Page<>(); LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(name != null,Dish::getName,name); dishService.page(dishPageInfo,queryWrapper); //2.对dish分页信息进行拷贝 BeanUtils.copyProperties(dishPageInfo,dtoPageInfo,"records"); List<Dish> records = dishPageInfo.getRecords(); //3.返回页面的新records数据,就是data数据 List<DishDto> list = records.stream().map((item) -> { //用来将每一个item变为dishDto,然后返回给Page<dishDto> DishDto dishDto = new DishDto(); BeanUtils.copyProperties(item,dishDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if(category != null){ String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } return dishDto; }).collect(Collectors.toList()); dtoPageInfo.setRecords(list); return R.success(dtoPageInfo); }
- BeanUtils工具类属于
5.菜品修改信息⭐️
- 凡是涉及对数据库数据的
多次增删改(>=2次)
,都需要事务控制,来防止一次修改出错而接着照常执行的错误
-
需求分析
- 保存修改数据的时候发送的请求
-
代码实现
-
DishServiceImpl —> 回显功能
进行简单的查询两个表dish和Flavor,中间桥梁是dishID;查询数据封装到DishDto,返回给页面
dish中有dishID,而Flavor表中有dishID,可根据dishId查询口味表
/** * 根据id查询查询dishDto来赋值 * 回显信息 * @param id * @return */ @Override public DishDto getByIdWithFlavors(Long id) { //查询dish表信息,获取对应菜品 Dish dish = this.getById(id); DishDto dishDto = new DishDto(); //拷贝到拓展对象中 BeanUtils.copyProperties(dish,dishDto); Long dishId = dish.getId(); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(dishId != null,DishFlavor::getDishId,dishId); List<DishFlavor> list = dishFlavorService.list(queryWrapper); dishDto.setFlavors(list); //返回主角数据 return dishDto; }
-
保存修改数据:
操作:清理当前菜品口味信息,然后批量插入口味信息 —> 避免了还需要判断是清楚还是增加的麻烦
①更新dish表②更新口味表(先清除之间口味信息,再批量插入当前口味信息)
DishServiceImpl
/** * 修改菜品信息 * @param dishDto */ @Override @Transactional//保证事务一致性 public void updateWithFlavor(DishDto dishDto) { //1.更新dish表中的信息 this.updateById(dishDto); //清理当前菜品,然后批量插入口味信息 //2.根据id清除相关口味信息 Long id = dishDto.getId(); LambdaQueryWrapper<DishFlavor> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(DishFlavor::getDishId,id); dishFlavorService.remove(queryWrapper); //3.批量插入口味信息,重新设置flavor信息,因为传递过来的flavor的dishId属性没赋上值 List<DishFlavor> flavors = dishDto.getFlavors(); flavors = flavors.stream().map((item) -> { item.setDishId(dishDto.getId()); return item; }).collect(Collectors.toList()); dishFlavorService.saveBatch(flavors); }
-
day05
1.概述
数据模型
注意套餐关系表中存储的数据,
一个套餐对应多个菜品
,分开存储
2.新增套餐
-
分析⭐️
和上一个开发类似
-
前端提交过来的信息包含
套餐基本信息
和套餐与菜品关联信息
因此需要设置一个setmealDto,Dto中包含套餐基本信息和套餐与菜品关联信息
-
后端在setmealController中接收这个Dto,然后新增业务方法去处理Dto
-
业务方法:
①将dto基本信息传入到
套餐基本信息表
中②将
套餐id
和这个对象中的list集合
中的数据添加到套餐菜品表
中③涉及操作两张表,需要加入@transactional注解,要么同时成功,要么同时失败
-
-
功能实现一:回显添加菜品
根据分类categoryId,来去相应dish表中查询信息,进而回显信息
/** * 根据categoryId,回显对应分类下的菜品信息 * @param dish * @return */ @GetMapping("/list") public R<List<Dish>> list(Dish dish){ LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); //两个eq信息 queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId()); queryWrapper.eq(Dish::getStatus,1); //添加排序条件 queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper); return R.success(list); }
功能实现二:
实现
添加菜品
功能- 这里要注意
setMealId
在前端传过来的数据没有,需要将前端基本信息添加到SetMeal表中,才能得到相应的Id,然后为套餐菜品对象
赋值上值
SetMealServiceImpl
@Service public class SetMealServiceImpl extends ServiceImpl<SetMealMapper, Setmeal> implements SetMealService { @Autowired private SetMealDishService setMealDishService; /** * 新增菜品套餐 * @param setmealDto */ @Override public void saveWithDish(SetmealDto setmealDto) { //1.调用setMeal本有的功能,顺便得到套餐id this.save(setmealDto); //2.将从数据库得到的套餐id封装回setmealDishes对象中 List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); setmealDishes.stream().map((item) -> { item.setSetmealId(setmealDto.getId()); return item; }).collect(Collectors.toList()); //3. setMealDishService.saveBatch(setmealDishes); } }
- 这里要注意
3.套餐分页查询
- 功能与day04的菜品信息分类查询相似
- 在套餐管理界面,套餐分类字段显示的是
categoryId对应的中文
,但在数据库里查询到的是categoryId,因此需要利用categoryId查询到categoryName,并赋值给数据传输对象SetmealDto
/**
* 套餐分页查询
* @param page
* @param pageSize
* @param name
* @return
*/
@GetMapping("/page")
public R<Page> list(int page, int pageSize, String name){
//分页构造器对象
Page<Setmeal> pageInfo = new Page<>(page, pageSize);
Page<SetmealDto> dtoPage = new Page<>();
//构造查询条件对象
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(name != null, Setmeal::getName, name);
//操作数据库
setMealService.page(pageInfo,queryWrapper);
//对象拷贝
BeanUtils.copyProperties(pageInfo,dtoPage,"records");
List<Setmeal> records = pageInfo.getRecords();
List<SetmealDto> list = records.stream().map((item) -> {
SetmealDto setmealDto = new SetmealDto();
BeanUtils.copyProperties(item, setmealDto);
//获取categoryId
Long categoryId = item.getCategoryId();
Category category = categoryService.getById(categoryId);
if (category != null) {
String categoryName = category.getName();
setmealDto.setCategoryName(categoryName);
}
return setmealDto;
}).collect(Collectors.toList());
dtoPage.setRecords(list);
return R.success(dtoPage);
}
4.删除套餐信息
-
需求分析
提供一个方法处理删除一个和删除多个请求
-
代码开发
注意点:
①接受前端ids数据,传过来的数据本身是数组形式,所以加不加注解无所谓,但是
List是列表,所以要加注解@RequestParam
②根据id删除套餐,不仅
删除套餐
,也删除关联套餐表
中的信息业务逻辑:(SetMealServiceImpl)
1.查询套餐状态,确定是否可用删除
2.如果不能删除,抛出一个业务异常,提示在售卖中
3.如果可以删除,先删除套餐表中的数据
4.删除关系表中的数据
/**
* 根据ids删除套餐信息
* @param ids
*/
@Override
@Transactional
public void removeWithDish(List<Long> ids) {
// 1.查询套餐状态,确定是否可用删除
//SQL语句:select count(*) from setMeal where id in ids and status = 1;
LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Setmeal::getId,ids);
queryWrapper.eq(Setmeal::getStatus,1);
int count = this.count(queryWrapper);
// 2.如果不能删除,抛出一个业务异常,提示**在售卖中**
if(count > 0){
throw new CustomException("商品还在销售,不能删除");
}
// 3.如果可以删除,先删除套餐表中的数据
this.removeByIds(ids);
// 4.删除关系表中的数据
//根据套餐id去关系表中去查数据,然后匹配删除
//delete from setMealDish where setmealId in ids
LambdaQueryWrapper<SetmealDish> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.in(SetmealDish::getSetmealId,ids);
setMealDishService.remove(lambdaQueryWrapper);
}
-
起售,停售操作(SetMealServiceImpl)
- 这里采用遍历操作实现批量停售,起售不太好,应该用mp的具体更新方法操作,等学了mp之后再来补吧,希望还记得
/**
* 更改售卖状态
* @param ids
* @param status 1表示启售,0表示停售
*/
@Override
public void changeStatus(List<Long> ids, int status) {
//改变售卖状态
for (int i = 0; i < ids.size(); i++) {
Long id = ids.get(i);
//根据id得到每个dish菜品。
Setmeal setmeal = this.getById(id);
setmeal.setStatus(status);
this.updateById(setmeal);
}
}
5.短信发送
-
概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zEC2oEPD-1685267442440)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305082027150.png)]
-
代码开发
6.手机验证码登录
-
需求分析
-
代码开发(前期准备)
①对
短信服务
进行放行,否则会自动跳回到登录页面②改写全局过滤器,对移动端用户进行验证码访问,进行放行
-
代码开发(发送验证码)
避坑:
这个测试的时候,前端页面有问题,login.html不发送ajax请求,解决办法:
把day05代码中的所有前端代码
替换到自己的项目中就行了
/**
* 发送手机验证码
* @param user
*/
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session){
//1.获取手机号,并进行检验
String phone = user.getPhone();
//2.生成验证码,采用短信发送服务进行发送短信
if(phone != null){
String code = String.valueOf(ValidateCodeUtils.generateValidateCode(4));
log.info("验证码为:{}",code);
// SMSUtils.sendMessage("瑞吉外卖","SMS_460725810",phone,code);
//3.将发送的验证码保存在session中,便于之后查验
session.setAttribute(phone,code);
return R.success("发送验证码成功,等待查收");
}
//4.短信发送错误,返回错误信息;成功,返回成功信息
return R.error("发送验证码失败");
}
-
代码开发(验证码登录)
-
因前端传过来的对象,后端没有相应的实体类与其对应
这时可以采取
拓展实体类dto
或者是map集合的方式
接收 -
登录方法返回值为User对象,这样让
浏览器也保存一份用户信息
-
/**
* 发送手机验证码
* @param map
*/
@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session){
log.info("登录操作:{}",map);
//1.获取手机号和验证码
String phone = map.get("phone").toString();
String code = map.get("code").toString();
//2.进行手机号和验证码比对,如果成功进行登录的逻辑
Object codeInSession = session.getAttribute("phone");
if(codeInSession != null && codeInSession.equals(code)){
//3.匹配结果对上之后,如果手机号在表中不存在自动完成祖册
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone,phone);
User user = userService.getOne(queryWrapper);
if(user == null){
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}
session.setAttribute("id",user.getId());
return R.success(user);
}
//4.短信发送错误,返回错误信息;成功,返回成功信息
return R.error("登陆失败");
}
day06
这一天都是移动端开发
1.地址簿相关功能
- 需求分析
/**
* 地址簿管理
*/
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
addressBook.setUserId(BaseContext.get());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
//把返回的信息,交给前端存起来
//每次前端已查询
return R.success(addressBook);
}
/**
* 设置默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId, BaseContext.get());
wrapper.set(AddressBook::getIsDefault, 0);
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
/**
* 根据id查询地址
*/
@GetMapping("/{id}")
public R get(@PathVariable Long id) {
AddressBook addressBook = addressBookService.getById(id);
if (addressBook != null) {
return R.success(addressBook);
} else {
return R.error("没有找到该对象");
}
}
/**
* 查询默认地址
*/
@GetMapping("default")
public R<AddressBook> getDefault() {
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getUserId, BaseContext.get());
queryWrapper.eq(AddressBook::getIsDefault, 1);
//SQL:select * from address_book where user_id = ? and is_default = 1
AddressBook addressBook = addressBookService.getOne(queryWrapper);
if (null == addressBook) {
return R.error("没有找到该对象");
} else {
return R.success(addressBook);
}
}
/**
* 查询指定用户的全部地址
*/
@GetMapping("/list")
public R<List<AddressBook>> list(AddressBook addressBook) {
addressBook.setUserId(BaseContext.get());
log.info("addressBook:{}", addressBook);
//条件构造器
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
queryWrapper.orderByDesc(AddressBook::getUpdateTime);
//SQL:select * from address_book where user_id = ? order by update_time desc
return R.success(addressBookService.list(queryWrapper));
}
}
2.菜品展示
-
需求分析
①因为发送两次请求,第二次失败,所以展示信息有误,这里将第二次的改用假数据
②存在问题:页面发送的请求为http://localhost:8080/dish/list?categoryId=1397844263642378242&status=1,这个list请求在Controller中只设置了返回List类型,而Dish实体类中没有相应的口味信息,因此在前端页面上不会显示
口味信息
,所以要拓展dish实体类,返回dishDto- 修改list方法,返回dishDto的代码
/** * 根据categoryId,回显对应分类以及菜品口味信息 * @param dish * @return */ @GetMapping("/list") public R<List<DishDto>> list(Dish dish){ LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); //两个eq信息 queryWrapper.eq(dish != null,Dish::getCategoryId,dish.getCategoryId()); queryWrapper.eq(Dish::getStatus,1); //添加排序条件 queryWrapper.orderByAsc(Dish::getSort).orderByDesc(Dish::getUpdateTime); List<Dish> list = dishService.list(queryWrapper);//查出来所有菜品信息 //将每一条信息都 List<DishDto> dishDtoList = list.stream().map((item) ->{ DishDto dishDto = new DishDto(); //1.对象拷贝(每一个list数据) BeanUtils.copyProperties(item,dishDto); Long categoryId = item.getCategoryId(); //分类id //通过categoryId查询到category内容 Category category = categoryService.getById(categoryId); //判空 if(category != null){ String categoryName = category.getName(); dishDto.setCategoryName(categoryName); } //2.将菜品口味信息赋值给dto Long id = item.getId(); LambdaQueryWrapper<DishFlavor> dishFlavorLambdaQueryWrapper = new LambdaQueryWrapper<>(); dishFlavorLambdaQueryWrapper.eq(DishFlavor::getDishId,id); List<DishFlavor> dishFlavors = dishFlavorService.list(dishFlavorLambdaQueryWrapper); dishDto.setFlavors(dishFlavors); return dishDto; }).collect(Collectors.toList()); return R.success(dishDtoList); }
- 设置setMeal方法,用于展示套餐
/** * 用于移动端展示数据 * @param setmeal * @return */ @GetMapping("/list") public R<List<Setmeal>> list(Setmeal setmeal){ //创建条件构造器 LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>(); //添加条件 queryWrapper.eq(setmeal.getCategoryId() != null,Setmeal::getCategoryId,setmeal.getCategoryId()); queryWrapper.eq(setmeal.getStatus() != null,Setmeal::getStatus,setmeal.getStatus()); //排序 queryWrapper.orderByDesc(Setmeal::getUpdateTime); List<Setmeal> list = setMealService.list(queryWrapper); return R.success(list); }
3.加入购物车
-
数据分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btcgqkdD-1685267442442)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305141908129.png)]
-
代码开发
设置userId,确定哪个用户点的,然后判断number数据类型
@PostMapping("/add") public R<ShoppingCart> add(@RequestBody ShoppingCart shoppingCart){ log.info("购物车数据:{}",shoppingCart); //1.将当前用户的id设置进数据库中 Long currentId = BaseContext.get(); shoppingCart.setUserId(currentId); LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(ShoppingCart::getUserId,currentId); //2.判断当前传过来的是菜品信息还是套餐信息。用于后续判断当前菜品或者套餐是否在购物车中 if(shoppingCart.getDishId() != null){ queryWrapper.eq(ShoppingCart::getDishId,shoppingCart.getDishId()); }else{ queryWrapper.eq(ShoppingCart::getSetmealId,shoppingCart.getSetmealId()); } //select * from shoppingCart where userId = ? and ... ShoppingCart cartServiceOne = shoppingCartService.getOne(queryWrapper); //3.进行查询记录,如果能查到记录,那么说明数据库中有,需要在原有基础上+1 if(cartServiceOne != null){ Integer number = cartServiceOne.getNumber(); number += 1; cartServiceOne.setNumber(number); shoppingCartService.updateById(cartServiceOne); }else{ shoppingCart.setNumber(1); shoppingCartService.save(shoppingCart); cartServiceOne.setNumber(1); } return R.success(cartServiceOne); }
-
展示购物车信息 /list
根据userId,去查购物车返回list集合就可
按照登录id去数据库中查询信息
不同用户的userid不同,所以购物车信息不同
-
4.用户下单⭐️
用到了很多张表,具有可学习性
- 订单表插入数据 — 从购物表中算出总金额 查询用户信息
- 订单明细表中插入多条数据 — 从购物表得出
-
需求分析
订单明细表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMTzZb4b-1685267442445)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305142010371.png)]
-
代码开发
用户下单分析一:(下单之后,存入
订单表
和订单明细表
中) 传递参数:
不需要传递过来
购物车信息
和用户id
,因为在登陆过程中已经知道用户id且购物车信息可根据用户id查询出来用户下单分析二:
获得当前用户id
查询当前用户的
购物车数据
向订单表插入数据,一条数据
向订单明细表插入数据,多条数据
清空购物车数据
2.项目优化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3E0jDZj-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262033884.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wz3p48bs-1685267442447)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305262034094.png)]
day01
1.引入版本控制
- 先创建一个远程的空仓库,复制链接
- 本地先创建仓库,然后连接远程仓库推送
- 创建两个分支,
master
和v1.0
,v1.0
用于开发缓存内容,开发完成后,合并到master
分支
2.环境搭建
springboot-redis-starter
redis
文件配置设置
redis配置类
,便于观察
3.短信验证码(Redis)
-
实现思路
4.缓存菜品数据
-
实现思路
主从一致
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TAWU5g3p-1685267442449)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271513707.png)]
-
实现代码
- 查询菜品
- 先根据前端传递过来的categoryId和status构造一个key,从缓存数据库中获取,如果能获取到,那么直接返回
- 获取不到dishDto,进行1去数据库查询,然后放入缓存数据库,然后返回
//查询方法 @GetMapping("/list") public R<List<DishDto>> list(Dish dish){ List<DishDto> dishDtoList = null; //1.第一次访问构造key,存入缓存数据库中 String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus(); //2.从缓存数据库中拿数据 dishDtoList = (List<DishDto>)redisTemplate.opsForValue().get(key); //3.判断缓存数据库中有没有,有的话直接去拿数据 if(dishDtoList != null){ return R.success(dishDtoList); } //4.如果没有的话,去数据库查询 //..... }
//修改方法 @PostMapping public R<String> add(@RequestBody DishDto dishDto){ log.info(dishDto.toString()); dishService.saveWithFlavor(dishDto); //添加后后删除数据库中对应的key 删除dish开头+dishid String key = "dish_" + dishDto.getCategoryId() + "_1"; redisTemplate.delete(key); return R.success("新增菜品成功"); }
5.SpringCache
-
概述
常用注解
-
使用
spring cache
使用哪种缓存技术,就导入对应的包,然后
开启注解
即可 -
使用jar包
使用基础功能的缓存,导入
web
包即可使用基于redis等缓存技术,那么要导入
spring-boot-starter-cache
-
-
普通cache使用
-
普通的缓存在加注解之后放在一个
线程安全的map
中,基于内存 -
初始化缓存方式
①注入
cachemanager
,加上注解缓存操作
(获取方法内参数,通过SPEL
),缓存的对象的类要实现序列化接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FrJVSACv-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271636494.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icyXONac-1685267442450)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271638756.png)]
删除缓存
查询数据
-
-
redis使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gUFBCIaN-1685267442452)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271707236.png)]
6.使用springCache缓存套餐数据
缓存的数据:
分类
+不同种类
day02
1.MySQL主从复制
-
介绍
实现读写分离,减轻单台数据库压力和防止主数据库数据损毁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZYOYNoS-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271739718.png)]
-
原理—从库和主库通过
日志
做一样的操作[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kgG6J8Kg-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271745822.png)]
-
操作步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jLGSubZH-1685267442454)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271859784.png)]
第二步:重启MySQL
#mysql8第三步执行 create user xiaoming identified by 'Root@123456'; grant replication slave on *.* to xiaoming;
MySQL主从复制略过了。。。之后有时间再来补吧
2.Nginx
1.概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gebXbH18-1685267442455)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271928408.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0j5WVZVf-1685267442456)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271939160.png)]
2.基本命令
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s0W8kKqq-1685267442457)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305271942072.png)]
./nginx -v
查看版本
./nginx -t
检查配置文件是否正确
3.配置文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KB1hPHBa-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281346785.png)]
4.niginx具体应用⭐️
1.部署静态资源
- 配置信息在config文件里
- 服务可
开多个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mk1fM9XZ-1685267442458)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281352592.png)]
2.反向代理
正向代理是在
客户端设置
,反向代理是在服务端设置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGfUbUtV-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281401642.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KvR5Nqlq-1685267442459)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281404563.png)]
实现反向代理 ----
转发
3.负载均衡
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZuNYVVu-1685267442460)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281413916.png)]
实现
负载均衡算法
默认
轮询算法
day03
1.前后端分离
1.概述
2.前后端分离开发
变化:前后端代码不再混合在一个工程中
-
开发流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgWOZ1cB-1685267442461)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281432025.png)]
3.YApi
提供API接口数据
4.Swagger(生成接口文档)
-
介绍
-
具体实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ehhnnKDk-1685267442463)(https://weifengqin-image.oss-cn-nanjing.aliyuncs.com/img/202305281443013.png)]
-
功能
因为包扫描,所以能真正地controller
-
常用注解
目的是为了生成接口的时候,个性化定制
2.项目部署
部署架构
- 前端项目
-
部署静态资源nginx上
将前端静态资源文件放在nginx的html文件夹下,然后修改nginx配置信息,映射静态文件
-
配置反向代理
-
后端项目
-
打包项目 部署工程
(如果访问后端项目,访问超时,可能是因为数据库的问题,可能
远端
没有配置数据库)
-
总结
之后再启动项目,先启动前端项目,再启动后端项目
.aliyuncs.com/img/202305281417603.png" alt=“image-20230528141758552” style=“zoom:33%;” />
day03
1.前后端分离
1.概述
[外链图片转存中…(img-RglKcxwc-1685267442460)]
[外链图片转存中…(img-gqfy4Vyd-1685267442461)]
2.前后端分离开发
变化:前后端代码不再混合在一个工程中
-
开发流程
[外链图片转存中…(img-MgWOZ1cB-1685267442461)]
3.YApi
提供API接口数据
4.Swagger(生成接口文档)
-
介绍
[外链图片转存中…(img-OcSGU8NN-1685267442462)]
-
具体实现
[外链图片转存中…(img-ehhnnKDk-1685267442463)]
[外链图片转存中…(img-qddnjUoD-1685267442464)]
-
功能
因为包扫描,所以能真正地controller
-
常用注解
目的是为了生成接口的时候,个性化定制
[外链图片转存中…(img-OkPdI6g0-1685267442464)]
2.项目部署
部署架构
- 前端项目
-
部署静态资源nginx上
将前端静态资源文件放在nginx的html文件夹下,然后修改nginx配置信息,映射静态文件
-
配置反向代理
[外链图片转存中…(img-mFWI408T-1685267442465)]
-
后端项目
-
[外链图片转存中…(img-sitC0vrN-1685267442465)]
-
打包项目 部署工程
(如果访问后端项目,访问超时,可能是因为数据库的问题,可能
远端
没有配置数据库)
-
-
总结
之后再启动项目,先启动前端项目,再启动后端项目