10.26 瑞吉外卖 DAY1
1.头 所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述 了解软件开发整体介绍
开发环境的搭建 数据库环境的搭建 课件提供了sql文件,可以直接source 导入mysql中 早就习以为常了
Maven项目创建 也很简单,普通的maven项目,复习导入坐标和yaml文件,因为不是直接创建的springBoot 项目,所以要创建Boot程序入口
3.1 后台登录开发 先创建一个实体类和mapper以及service
再封装一个R作为返回的结果类 给EmployeeController类添加一个login方法
@RequestBody 主要用于接收前端传递给后端的json字符串(请求体中的数据)
HttpServletRequest 作用:如果登录成功,将员工对应的id存到session一份,这样想获取一份登录用户的信息就可以随时获取出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 JAVA @Slf4j @RestController @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @PostMapping("/login") public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){//接收前端的json数据,这个json数据是在请求体中的 // 1、将页面提交的密码password进行md5加密处理 String password = employee.getPassword();//从前端用户获取到登录时候的密码 password = DigestUtils.md5DigestAsHex(password.getBytes()); //加密 // 2、根据页面提交的用户名username查询数据库 LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>(); //泛型需要填写Employee queryWrapper.eq(Employee::getUsername,employee.getUsername()); //eq用lambda:: //在设计数据库的时候我们对username使用了唯一索引,所以这里可以使用getOne方法 Employee emp = employeeService.getOne(queryWrapper); // 3、如果没有查询到则返回登录失败结果 if (emp == null){ return R.error("用户不存在"); } // 4、密码比对,如果不一致则返回登录失败结果 if (! emp.getPassword().equals(password)){ return R.error("密码错误"); } // 5、查看员工状态,如果为已禁用状态,则返回员工已禁用结果 if (emp.getStatus() == 0){ return R.error("用户已被禁用"); } // 6、登录成功,将员工id存入Session并返回登录成功结果 request.getSession().setAttribute("employee",emp.getId()); //把从数据库中查询到的用户返回出去 return R.success(emp); } //员工退出 @PostMapping("logout") public R<String> logout(HttpServletRequest request){ //清除Session中当前登录员工的id request.getSession().removeAttribute("employee"); return R.success("退出成功"); } }
3.2后台系统退出功能 这个比较简单 点击按钮退出到登陆页面就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 JAVA /** * 退出功能 * ①在controller中创建对应的处理方法来接受前端的请求,请求方式为post; * ②清理session中的用户id * ③返回结果(前端页面会进行跳转到登录页面) * @return */ @PostMapping("/logout") public R<String> logout(HttpServletRequest request){ //清理session中的用户id request.getSession().removeAttribute("employee"); return R.success("退出成功"); }
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案)
3.3 完善登录功能 之前的登陆功能是有个bug的,像之前web的一个登陆案例,直接输入地址的话,就可以跳过登录页面,达到不登录就能进入需要登录的 页面,之前web是用过滤器的,现在用拦截器解决这个小bug
要先在启动项上面加入@ServletComponentScan注解 识别bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 JAVA /** * 检查用户是否已经完成登陆 * filterName过滤器名字 * urlPatterns拦截的请求,这里是拦截所有的请求 */ @WebFilter(filterName = "LongCheckFilter", urlPatterns = "/*") @Slf4j 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; //1、获取本次请求的URI String requestURI = request.getRequestURI(); //定义不需要处理的请求路径 比如静态资源(静态页面我们不需要拦截,因为此时的静态页面是没有数据的) String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" }; log.info("拦截到的请求:{}",requestURI); //2、判断本次请求是否需要处理 boolean check = check(urls, requestURI); //3、如果不需要处理,则直接放行 if (check){ //对请求进行放行 filterChain.doFilter(request,response); return; } //4、判断登录状态,如果已登录,则直接放行 Object employee = request.getSession().getAttribute("employee"); //用户登录状态 if (employee != null){ log.info("用户已登录,用户id为:{}",employee); //对请求进行放行 filterChain.doFilter(request,response); return; } log.info("用户未登录"); //5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据,具体响应什么数据,看前端的需求,然后前端会根据登陆状态做页面跳转 response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN"))); } public boolean check(String[] urls, String requestURI){ for (String url: urls){ boolean match = PATH_MATCHER.match(url, requestURI); if (match){ return true; } } return false; } }
4.扩展学习部分 5.总结 刚进入瑞吉外卖,下载了一些资料,没找到老师的官方的ppt文档,就CSDN去搜的,第一天的任务不是很难,主要是帮助回看一下springboot的部分,算是小复习,就做了一个登录和退出,还有个登录的小BUG,基本都是后台的代码,前端的一些功能都做好了,后端加bean,调用一下响应和请求就好了,比较基础的还是。学习方法就是看老师过一遍功能如何实现,如何测试,然后就自己试着去敲,慢慢做出来,这样学,进度可能会有些慢,但是能搞清楚每一块是干嘛的,每个注解和类的作用和功能。
10.27 瑞吉外卖 DAY2 1.头:日期、所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述💻 添加员工 执行流程分析
页面发送ajax请求,将新增员工页面中输入的数据以json的形式提交到服务端
服务端Controller接收页面提交的数据并调用Service将数据进行保存
Service调用Mapper操作数据库,保存数据
具体实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 JAVA @PostMapping public R<String> save(HttpServletRequest request, @RequestBody Employee employee) { log.info("新增的员工信息:{}", employee.toString()); //设置默认密码123456 MD5加密 employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes())); ////设置createTime和updateTime employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now()); // //根据session来获取创建人的id Long empId = (Long) request.getSession().getAttribute("employee"); //设置id employee.setCreateUser(empId); employee.setUpdateUser(empId); //存入数据库 employeeService.save(employee); return R.success("添加员工成功"); }
完善全局异常处理 现在的代码其实是有BUG存在的,username不能重复,因为在建表的时候设定了unique,只能存在唯一的username,如果存入相同的username则会报错 java.sql.SQLIntegrityConstraintViolationException: Duplicate entry ‘Kyle’ for key ‘employee.idx_username’ 看的出来报错类是SQLIntegrityConstraintViolationException
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 JAVA @Slf4j @RestControllerAdvice @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler(SQLIntegrityConstraintViolationException sqlBug){ log.error(sqlBug.getMessage()); //包含Duplicate entry是说明有条目重复 if (sqlBug.getMessage().contains("Duplicate entry")){ //对字符串切片 String[] error = sqlBug.getMessage().split(" "); //Duplicate entry 'zhangsan' for key 'employee.idx_username' //这句日志信息 下标为2的刚好是用户名 返回对应用户名的报错信息就好了 String username = error[2]; log.error(username +"用户已存在"); return R.error(username +"用户已存在"); } return R.error("未知错误"); } }
员工信息分页查询 添加MP自带的分页插件 前段时间刚写过分页 还是很熟悉的
1 2 3 4 5 6 7 8 9 10 11 JAVA @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mybatisPlusInterceptor; } }
看前端代码利用检查功能,发现是get请求 /page 利用Rest规范 写业务逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 JAVA @GetMapping("/page") public R<Page> page(int page, int pageSize, String name) { log.info("page={},pageSize={},name={}", page, pageSize, name); //构造分页构造器 Page<Employee> pageInfo = new Page<>(page, pageSize); //构造条件构造器 LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>(); //添加过滤条件(当我们没有输入name时,就相当于查询所有了) wrapper.like(StringUtils.isNotEmpty(name),Employee::getName,name); //并对查询的结果进行降序排序,根据更新时间 wrapper.orderByDesc(Employee::getUpdateTime); //执行查询 employeeService.page(pageInfo,wrapper); return R.success(pageInfo); }
可以修改显示一页多少行,直接在前端的comment包中的list.html中修改
启用/禁用 员工账户 其实很简单对于后端来说,权限问题只有管理员才能使用,这样的功能是在前端实现的,后端的话只要点击按钮的时候注入对应的Rest请求,就好了。 启用、禁用员工账号,本质上就是一个更新操作,也就是对status状态字段进行操作在Controller中创建update方法,此方法是一个通用的修改员工信息的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 JAVA @PutMapping public R<String> Update(HttpServletRequest request, @RequestBody Employee employee){ log.info(employee.toString()); Long empId = (Long) request.getSession().getAttribute("employee"); //获取id employee.setUpdateUser(empId); //设置更新用户id employee.setUpdateTime(LocalDateTime.now()); //更新时间为当前时间 employeeService.updateById(employee);/ /调用update方法 return R.success("员工信息修改成功"); }
出现了问题,ajdx返回的id值是和实际的id值不一致,是js对Long类型的数据处理时候丢失了精度 json数据时进行处理,将Long型数据统一转为String字符串 直接把课件中的 对象映射器JacksonObjectMapper 复制到common包中 扩展Mvc框架中的消息转换器
1 2 3 4 5 6 7 8 9 JAVA @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); }
编辑员工信息 先跟着老师的文档分析了前端代码是如何实现页面的 回显什么也是前端所实现的。
我所实现的服务端接受请求,并根据员工id查询员工信息,并将员工信息以json形式响应给页面
1 2 3 4 5 6 7 8 9 10 JAVA @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("没有此用户的信息"); }
服务端接受员工信息,并进行处理,完成后给页面响应 由于修改员工信息也是发送的PUT请求,与之前启用/禁用员工账号是一致的,而且前面我们已经写过了PUT请求的Controller层 所以当我们点击保存按钮时,调用submitForm函数,而在submitForm函数中我们又调用了editEmployee函数,发送PUT请求,实现修改功能,直接就调用前面启动/禁用员工账户的update方法了,所以只用实现根据id查询员工信息就行了
公共字段填充 前面我们已经完成了对员工数据的添加与修改,在添加/修改员工数据的时候,都需要指定一下创建人、创建时间、修改人、修改时间等字段,而这些字段又属于公共字段,不仅员工表有这些字段,在菜品表、分类表等其他表中,也拥有这些字段。所以下面做菜单页面的时候为了方便,降低耦合度,可以把这些字段称为公共字段。
实现方式 创建一个类 实现MyMetaObjectHandler 接口 利用ThreadLocal中的方法实现对id的获取 前面更新和插入时候 对时间和用户id的修改 可以注释掉了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 JAVA @Slf4j @Component 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()); metaObject.setValue("updateTime", LocalDateTime.now()); metaObject.setValue("updateUser",BaseContext.getCurrentId()); } }
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案) 今天基本没什么bug,小bug就是导入包的时候导入错了,导致对应类的方法没找到,但是很快就反应过来了。
4.扩展学习部分 5.总结 今天学习难度一般,学习状态极佳,静下心来完成了项目对员工的基本功能实现,发现所用到的很多方法都是之前所没有接触和学过的,也有一些小技巧是前面某个案例所使用的,像分页什么的,因为在赶进度,想快点把项目做完,基本都是二倍数过一遍视频,了解原理,代码使用,然后就开始自己敲了,感觉这个项目前端方面其实比较难,我现在做来,后端处理一下请求就好了,一些复杂的功能都是前端所实现的,最后做了一个公共字段的自动填充,其实有点麻烦的,而且不是很理解,用到了多线程解决,自己其实是看一遍视频,自己就能实现了,也大致明白这样做的原因。
10.28 github hexo 个人博客搭建 1.头:日期、所学内容出处 b站以及QQ请教 还有github博客讲解
2.所学内容概述 实现流程 开始需要安装ndoejs 然后npm 下载hexo 和hexo-cli github创建自己仓库,用git命令绑定 hexo也绑定仓库 npm可以找别人的主题模版 下载到global仓库 分析代码 看主题步骤 跟着做 hexo clean 清理缓存 hexo g 上传文件 hexo s 启动服务 hexo d 上传到github仓库 直接到页面
目前的笔记页面效果如下,还有很多地方需要完善,想到什么等闲下来再去做好,已经知道一些地方的原理了。
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案) 看报错信息以为是butterfly的源文件被我不小心修改了,然后我去github中把之前备份的拉下来,重写添加了一遍,还是报错,那唯一和初始不同的地方只有,yml文件了,然后拿默认的和原本的对比,发现在添加社交图标的时候,qq和微信的图标标注了,但是后面没输入信息。把两个注释掉,就没报错了,但是很奇怪,因为之前yml文件报错,都会说哪一行报错,这个应该是骗过了js的编译。
4.扩展学习部分 5.总结 今天搞了一天的个人博客的小开发,昨天看到一个大佬记的笔记,发现页面很好看也很清楚,但是不知道是什么笔记网站,然后问他,发现是利用github和hexo搭建的自己的网站,可以放md文件,一些笔记可以梳理,我觉得这样很好,今天就搞了出来,大致基本都弄出来了,还没有美化,过程还是有很多磕磕绊绊的,尤其是npm下载的时候,太吃网络了,之前一直下不下来,有点看运气,github有时候也访问不进去。等项目做完了,美化一下,把自己之前的笔记,都上传到自己的博客网站,个人网站已经建好了,因为还没实现typora上传图片,这样会导致自己的笔记,图片会丢失,所以明天把上传问题解决一下。 页面如下 把去年学的一点笔记当作实验了效果还可以https://u7-u7.github.io/
10.29 分类管理功能实现 1.头:日期、所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述 今天先完成了公共字段的自动填充 对更新时间和创建时间,这样对后面菜品和菜单,套餐等等页面功能实现就不需要再写一遍了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 JAVA @Component @Slf4j //利用MetaObjectHandler类 实现还是很简单 易懂的 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()); } @Override public void updateFill(MetaObject metaObject) { log.info("公共字段自动填充(update)..."); log.info(metaObject.toString()); metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime", LocalDateTime.now()); } }
完成了菜品的基本增删改查的操作(代码比较多,就不放在笔记中去了,需要注意的点也在代码中注解了),难度不是很大,在之前员工中一样,实现方法的流程都是差不多的,分析页面请求,看前端代码,写后端代码。
在删除功能完善的时候,又创建了一个自定义异常类 在删除不能删除的信息的时候,抛出自定义运行类的异常
1 2 3 4 5 6 7 8 9 10 11 12 JAVA public class CustomException extends RuntimeException{ //运行时异常 public CustomException(String msg){ super(msg); } } JAVA @ExceptionHandler(CustomException.class) public Result<String> exceptionHandler(CustomException exception) { log.error(exception.getMessage()); return Result.error(exception.getMessage()); }
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案)
删除页面显示 删除成功 但是数据依旧存在 不知道原因 看返回的日志中id=null 也就是根本没有获取到id的值。
发现前端 发送post 请求的时候 页面是?ids id后面带了一个s 应该前端的问题,把方法内参数id修改成ids 没问题了。这个请求的设置在js目录下面的category.js 19行 上面写着ids 应该是打错了 因为我看老师的代码是id
4.扩展学习部分 把之前hexo中图片无法显示的问题,解决了,申请了一个免费的图库,之前原本尝试过gitee图库和bilibili图库,不知道为什么到了hexo中是不显示的,gitee图库,typora一直都验证不到,插件下载总是失败,具体原因也不知道。使用的自定义web图床也只能上传2000张图片,等到放不下了,就要考虑换图床了。
5.总结 今天学习内容简单,因为今天是周末,在寝室学习的,学习效率就一般了,下午还去练车了,基本只学了一上午到2点,学习内容很简单,基本都是自己独立完成的,看业务需求是对菜单功能完善,自己去前端页面看请求方式,进行需求分析一下,自己就能打出来了,出了点小插曲,好在顺利解决了,解决bug的过程还是蛮顺利的,也明白了参数名对代码的影响,一定要统一好。然后在晚上又去把博客图片无法显示的问题解决了一下,过两天还是换一个图床比较好。
10.31 菜品和套餐功能实现 1.头:日期、所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述 对菜品和套餐基本功能的实现,因为有的参数是实体类不满足接收的参数,需要创建导入Dto层的类,封装页面提交数据。其实就是把不能接受的参数,重写一下方法,封装为list,再循环赋值,就好了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 JAVA @Service public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { @Autowired private DishFlavorService dishFlavorService; @Override public void saveWithFlavor(DishDto dishDto) { //将菜品数据保存到dish表 this.save(dishDto); //获取dishId Long dishId = dishDto.getId(); //将获取到的dishId赋值给dishFlavor的dishId属性 List<DishFlavor> flavors = dishDto.getFlavors(); for (DishFlavor dishFlavor : flavors) { dishFlavor.setDishId(dishId); } //同时将菜品口味数据保存到dish_flavor表 dishFlavorService.saveBatch(flavors); } }
注意点!!!
多表操作的时候需要在方法上面加@Transactional
,我比较偷懒就直接在业务层Impl类上面加了。然后运行类上面加@EnableTransactionManagement
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案)
难点写在扩展学习部分了
4.扩展学习部分 因为在视频中和博客中,对有一个列表的stream().map加lambda的方式,一下子没怎么懂,然后就去看了视频,对代码的解释,以及作用,看弹幕,很多人用增强for循环做了出来,因为暑假学了scala,看了一会他的代码也大致的懂了,然后自己去用for循环,发现写出来似乎更方便,而且可读性高,效率倒是还没有去测试过,不过这种小项目,应该相差不大,效率分析过了应该是lambda快很多,用stream流。那一块lambda也是今天的难点所在。
两种方式对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 JAVA @GetMapping("/page") public Result<Page> page(int page, int pageSize, String name) { Page<Setmeal> pageInfo = new Page<>(page, pageSize); Page<SetmealDto> dtoPage = new Page<>(page, pageSize); LambdaQueryWrapper<Setmeal> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(name != null, Setmeal::getName, name); queryWrapper.orderByDesc(Setmeal::getUpdateTime); setmealService.page(pageInfo, queryWrapper); BeanUtils.copyProperties(pageInfo, dtoPage, "records"); List<Setmeal> records = pageInfo.getRecords(); //stream流 调用records中元素 拷贝给新建的setmealDto 再通过collect转换为list List<SetmealDto> list = records.stream().map((item) -> { SetmealDto setmealDto = new SetmealDto(); BeanUtils.copyProperties(item, setmealDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null) { setmealDto.setCategoryName(category.getName()); } return setmealDto; }).collect(Collectors.toList()); dtoPage.setRecords(list); return Result.success(dtoPage); }
5.总结 今天学习难度稍难,而且下午练了回车,但是今日的学习状态极佳,一股脑扎进去敲代码。今天的难点其实是在多表操作中,因为在菜品功能和套餐中,都有调用另外一个关联表的操作,就需要用LambdaQueryWrapper进行公共字段匹配,有点像sql中的多表连接,但是是用java实现的,和left join这样差不多,LambdaQueryWrapper就是类似于on,实现连接条件,还有一个lambda对列表的处理,分页查询那边,代码量有点太大了,但是如果分析好步骤,自己也是可以敲出来的。其他的修改和删除操作,也比之前的稍难点,需要自定义在业务层写方法,进行一个自定义处理,因为有些条件是需要添加的,很多categoryId是需要后面添加的。总体来说今天学习的内容还是很丰富的,早上做文件上传和下载,把java基础的流又算复习了一遍,明天状态好的话,项目的简单实现应该就完成了,再花个两三天把redis和优化解决。
11.1 移动端功能的实现 1.头:日期、所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述 移动端功能实现(过段时间,会写在自己博客中,笔记内容就不放了,代码很多)
https://u7-u7.github.io/这里会放代码,以及流程梳理
修改手机验证 为邮箱 教程是用手机发送一个虚假的请求验证码输入验证,但是手机号要真正实现是要钱的,突然记得之前自己学过邮箱的发送,然后自己去改成了自己的邮箱,把邮箱的SMTP打开,是可以发送的,自己改前端代码的时候有点问题,去CSDN搜到了邮箱的正则匹配的表达式,然后页面把手机号都改成了邮箱,验证码也实现了,但是发现验证码是用equals的这样大小写一定要统一,我们日常使用是不用的,就改成了不区分大小写,倒是不难。
准备工作 导入坐标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 XML <!-- https://mvnrepository.com/artifact/javax.activation/activation --> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!-- https://mvnrepository.com/artifact/javax.mail/mail --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-email --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-email</artifactId> <version>1.4</version> </dependency>
发送邮箱的工具类 带测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 JAVA package com.itheima.reggie.Utils; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Properties; import javax.mail.Authenticator; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage.RecipientType; public class MailUtils { public static void main(String[] args) throws MessagingException { //可以在这里直接测试方法,填自己的邮箱即可 sendTestMail("1452582554@qq.com", new MailUtils().achieveCode()); } public static void sendTestMail(String email, String code) throws MessagingException { // 创建Properties 类用于记录邮箱的一些属性 Properties props = new Properties(); // 表示SMTP发送邮件,必须进行身份验证 props.put("mail.smtp.auth", "true"); //此处填写SMTP服务器 props.put("mail.smtp.host", "smtp.qq.com"); //端口号,QQ邮箱端口587 props.put("mail.smtp.port", "587"); // 此处填写,写信人的账号 props.put("mail.user", "1452582554@qq.com"); // 此处填写16位STMP口令 props.put("mail.password", "vxccjkvvlrokgigh"); // 构建授权信息,用于进行SMTP进行身份验证 Authenticator authenticator = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { // 用户名、密码 String userName = props.getProperty("mail.user"); String password = props.getProperty("mail.password"); return new PasswordAuthentication(userName, password); } }; // 使用环境属性和授权信息,创建邮件会话 Session mailSession = Session.getInstance(props, authenticator); // 创建邮件消息 MimeMessage message = new MimeMessage(mailSession); // 设置发件人 InternetAddress form = new InternetAddress(props.getProperty("mail.user")); message.setFrom(form); // 设置收件人的邮箱 InternetAddress to = new InternetAddress(email); message.setRecipient(RecipientType.TO, to); // 设置邮件标题 message.setSubject("u7's Blog 邮件测试"); // 设置邮件的内容体 message.setContent("尊敬的用户:你好!\n注册验证码为:" + code + "(有效期为一分钟,请勿告知他人)", "text/html;charset=UTF-8"); // 最后当然就是发送邮件啦 Transport.send(message); } public static String achieveCode() { //由于数字 1 、 0 和字母 O 、l 有时分不清楚,所以,没有数字 1 、 0 String[] beforeShuffle = new String[]{"2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"}; List<String> list = Arrays.asList(beforeShuffle);//将数组转换为集合 Collections.shuffle(list); //打乱集合顺序 StringBuilder sb = new StringBuilder(); for (String s : list) { sb.append(s); //将集合转化为字符串 } return sb.substring(4, 8); } }
修改拦截器 1 2 3 4 5 6 7 8 9 JAVA String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/user/sendMsg",//移动端发送短信 "/user/login" };
判断用户是否登录
1 2 3 4 5 6 7 8 9 JAVA //判断用户是否登录 if(request.getSession().getAttribute("user") != null){ log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("user")); Long userId = (Long)request.getSession().getAttribute("user"); BaseContext.setCurrentId(userId); filterChain.doFilter(request,response); return; }
修改的前段页面 (把手机号都先改成邮箱登录) 然后修改 front中的login.html 判断手机号的正则表达式换成判断邮箱的正则表达式 ^\w+([-+.]\w+)@\w+([-.]\w+) .\w+([-.]\w+)*$(上网搜的)
发送验证码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 JAVA @PostMapping("/sendMsg") //请求体/user/sendMsg public R<String> sendMsg(@RequestBody User user, HttpSession session) throws MessagingException { String phone = user.getPhone(); if (!phone.isEmpty()) { //随机生成一个验证码 String code = MailUtils.achieveCode(); log.info(code); //这里的phone其实就是邮箱,code是我们生成的验证码 MailUtils.sendTestMail(phone, code); //验证码存session,方便后面拿出来比对 session.setAttribute(phone, code); return R.success("验证码发送成功"); } return R.error("验证码发送失败"); }
邮箱收到了就成功了就可以写login登录的实现了 顺利完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 JAVA @PostMapping("/login") public R<User> login(@RequestBody Map map, HttpSession session) { log.info(map.toString()); //获取邮箱 String phone = map.get("phone").toString(); //获取验证码 String code = map.get("code").toString(); //从session中获取验证码 String codeInSession = session.getAttribute(phone).toString(); String codeInSessionUpp = codeInSession.toUpperCase(); //全部变成大写 //比较这用户输入的验证码和session中存的验证码是否一致 String upperCase = code.toUpperCase(); //全部变成大写 这样输入验证码就不需要大小写了 if (upperCase.equals(codeInSessionUpp)) { //如果输入正确,判断一下当前用户是否存在 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); //判断依据是从数据库中查询是否有其邮箱 queryWrapper.eq(User::getPhone, phone); User user = userService.getOne(queryWrapper); //如果不存在,则创建一个,存入数据库 if (user == null) { user = new User(); user.setPhone(phone); userService.save(user); user.setName("用户" + codeInSession); } //存个session,表示登录状态 session.setAttribute("user",user.getId()); //并将其作为结果返回 return R.success(user); } return R.error("登录失败"); }
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案)
输入验证码以后,就自动又跳会登录页面,浏览器没有login 的请求,看日志跳到了最后的用户未登录,说明check没有匹配上,检查代码,发现拦截器的数组中,“/user/login” 少了一个/,加上以后顺利解决了问题
客户端list查询找的时候,因为调用的是同一个请求,但是我一直没有显示,找不到原因,后面发现自己写的list代码和老师的有一些区别,有一块条件判断的时候,我少加了一个 不等于null的判断,导致我的type在前端显示是null
1 2 JAVA wrapper.eq(category.getType() != null,Category::getType,category.getType());
4.扩展学习部分 移动端补充一些视频未完善的功能(自己写的) 过段时间把这几个功能的实现以及流程 思路 写进来
历史订单功能 需要先添加一个OrderDto层 然后直接在OrderController编写方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 JAVA @Data public class OrdersDto extends Orders { private String userName; private String phone; private String address; private String consignee; private List<OrderDetail> orderDetails; }
登出功能
1 2 3 4 5 6 7 JAVA //登出功能 @PostMapping("/loginout") public R<String> loginout(HttpServletRequest httpServletRequest){ httpServletRequest.getSession().removeAttribute("user"); //获取登录状态的Attribute return R.success("退出成功"); }
修改/删除地址 数据回显
修改第一步和之前一样完成数据回显
同样看前端请求
/addressBook/{id}请求方式是
GET 在AddressBookController中写方法
1 2 3 4 5 6 7 8 9 JAVA @GetMapping("{id}") public R<AddressBook> getById(@PathVariable Long id){ AddressBook byId = addressBookService.getById(id); if (byId == null){ throw new CustomException("地址信息不存在"); } return R.success(byId); }
修改地址
请求网址: http://localhost/addressBook 请求方法: PUT
直接在AddressBookController写Put方法
1 2 3 4 5 6 7 8 9 JAVA @PutMapping public R<String> updateAdd(@RequestBody AddressBook addressBook) { if (addressBook == null) { throw new CustomException("地址信息不存在,请刷新重试"); } addressBookService.updateById(addressBook); //调用MP方法 return R.success("地址修改成功"); }
删除地址
请求网址: http://localhost/addressBook?ids=1579828298672885762 请求方法: DELETE
直接在AddressBookController写Delete方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 JAVA @DeleteMapping public R<String> delete(@RequestParam Long ids){ //删除地址 if (ids == null) { throw new CustomException("地址信息不存在,请刷新重试"); } AddressBook addressBook = addressBookService.getById(ids); if (addressBook == null) { throw new CustomException("地址信息不存在,请刷新重试"); } addressBookService.removeById(ids); return R.success("删除成功"); }
减号按钮
加入购物车以后,前端是给了一个减号的按钮,平常自己点外卖也有使用,大概功能就是点一下数量-1 0的时候菜品就会消失
请求网址: http://localhost/shoppingCart/sub 请求方法: POST
有返回json数据
1 2 3 4 JSON { dishId: null, setmealId: "1579044544635232258" }
思路:通过这两个ID 实现对套餐和菜品的number属性的修改 最后为0的话调用删除操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 JAVA @PostMapping("/sub") public R<ShoppingCart> deleteSub(@RequestBody ShoppingCart shoppingCart) { Long dishId = shoppingCart.getDishId(); Long setmealId = shoppingCart.getSetmealId(); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>(); //只查询当前用户ID的购物车 lambdaQueryWrapper.eq(ShoppingCart::getUserId, BaseContext.getCurrentId()); if (dishId != null) { //匹配出菜品数据 lambdaQueryWrapper.eq(ShoppingCart::getDishId, dishId); ShoppingCart dishCart = shoppingCartService.getOne(lambdaQueryWrapper); dishCart.setNumber(dishCart.getNumber() - 1); Integer number = dishCart.getNumber(); if (number > 0) { shoppingCartService.updateById(dishCart); } else { shoppingCartService.removeById(dishCart.getId()); } return R.success(dishCart); } if (setmealId != null) { //通过setmealId查询购物车套餐数据 lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, setmealId); ShoppingCart setmealCart = shoppingCartService.getOne(lambdaQueryWrapper); //将查出来的数据的数量-1 setmealCart.setNumber(setmealCart.getNumber() - 1); Integer currentNum = setmealCart.getNumber(); //然后判断 if (currentNum > 0) { //大于0则更新 shoppingCartService.updateById(setmealCart); } else if (currentNum == 0) { //等于0则删除 shoppingCartService.removeById(setmealCart.getId()); } return R.success(setmealCart); } return R.error("系统繁忙,请稍后再试"); }
点图片查看套餐的详情 这个前端其实是写好了的,阿贾克斯请求在api也看得到 先看下请求
请求网址: http://localhost/setmeal/dish/1579044544635232258 请求方法: GET
是通过id可以实现的 restFul风格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 JAVA @GetMapping("/dish/{id}") public R<List<DishDto>> showSetmealDish(@PathVariable Long id) { //条件构造器 LambdaQueryWrapper<SetmealDish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>(); //手里的数据只有setmealId dishLambdaQueryWrapper.eq(SetmealDish::getSetmealId, id); //查询数据 List<SetmealDish> records = setmealDishService.list(dishLambdaQueryWrapper); List<DishDto> dtoList = records.stream().map((item) -> { DishDto dishDto = new DishDto(); //copy数据 BeanUtils.copyProperties(item,dishDto); //查询对应菜品id Long dishId = item.getDishId(); //根据菜品id获取具体菜品数据,这里要自动装配 dishService Dish dish = dishService.getById(dishId); BeanUtils.copyProperties(dish,dishDto); //这里不写也没事 但是我想验证数据 return dishDto; }).collect(Collectors.toList()); return R.success(dtoList); }
5.总结 今天的学习难度较难,因为之前做了三四天的客户端的功能完善,功能实现方法什么,自己基本就知道,所以移动端的差不多的功能一天就完成了,很多都是跟客户端大差不大。难点在刚开始的时候,自己把教程的手机号接受验证码验证改成了邮箱(写在扩展学习了),还有后面的购物车和用户下单的代码也是挺难的,看了老师敲了一遍理解完,才尝试去敲。在下午练完车以后 ,快到晚自习了,把移动端的基本功能都实现了。但是自己发现很多功能都没完善,去前端页面是能看到请求的,就想自己尝试完善一下,晚自习将视频中没完善的功能给完善了,大概五六个功能,自己后台代码写掉了。
11.2 瑞吉外卖完结 1.头:日期、所学内容出处 【黑马程序员2022新版SSM框架教程_Spring+SpringMVC+Maven高级+SpringBoot+MyBatisPlus企业实用开发技术】 https://www.bilibili.com/video/BV1Fi4y1S7ix?p=31&share_source=copy_web&vd_source=c8ae4150b2286ee39a13a79bbe12b843
2.所学内容概述 上午把管理端老师未完善的功能去完善了一下,下午和晚上花时间,将项目重新回顾梳理了一下。自己用自己的手机操作了一下,客户端的功能是没有问题的。
3. BUG点 难点(关键代码或关键配置,BUG截图+解决方案)
在扩展学习写批量启售/停售的时候,运行时候就报错,刚开始不知道啥原因,注解掉写的方法,就没报错了,问题在自己写的allStatus方法,以为是把return写到循环体中了,放外面还是报错,翻译报错信息,有一条说DishController什么已存在,奥就发现因为启售和停售的请求是一样的,只是请求体不一样,所以@PostMapepr里面我写的其实是一样的,可能这样会导致匹配不到,就把原本写的单独启售和停售注释了(批量启售/停售也适用单独的),问题解决了,功能也实现了
4.扩展学习部分
菜品批量启售/停售 查看前端请求
请求网址: http://localhost/dish/status/0?ids=1578942037036703745 请求方法: POST
和之前修改状态一样,前端已经对status取反了,所以直接用发送的status更新状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 JAVA @PostMapping("/status/{status}") public Result<String> status(@PathVariable Integer status, Long ids) { log.info("status:{},ids:{}", status, ids); Dish dish = dishService.getById(ids); if (dish != null) { //直接用它传进来的这个status改就行 dish.setStatus(status); dishService.updateById(dish); return Result.success("售卖状态修改成功"); } return Result.error("系统繁忙,请稍后再试"); }
菜品批量删除 查看前端请求
请求网址: http://localhost/dish?ids=1578674689490825217 请求方法: DELETE
同样要判断菜品是不是停售状态
我直接写批量的了,区别就在queryWrapper.in 和传入的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 JAVA @DeleteMapping public R<String> allDelete(@RequestParam List<Long> ids){ log.info("删除的ids:{}", ids); LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(Dish::getId, ids); queryWrapper.eq(Dish::getStatus, 1); int count = dishService.count(queryWrapper); if (count > 0) { throw new CustomException("删除列表中存在启售状态商品,无法删除"); } dishService.removeByIds(ids); return R.success("删除成功"); }
套餐批量启售/停售 查看请求
请求网址: http://localhost/setmeal/status/1?ids=1580361600576114689 请求方法: POST
和菜品操作的基本一样
1 2 3 4 5 6 7 8 9 10 JAVA @PostMapping("/status/{status}") @CacheEvict(value = "setmealCache", allEntries = true)//后面的加入缓存 现在忽略 public R<String> status(@PathVariable String status, @RequestParam List<Long> ids) { LambdaUpdateWrapper<Setmeal> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.in(Setmeal::getId, ids); updateWrapper.set(Setmeal::getStatus, status); setmealService.update(updateWrapper); return R.success("批量操作成功"); }
套餐修改 和其他修改操作一样 需要先数据回显 再修改
看请求
回显的请求 请求网址: http://localhost/setmeal/1580361496716759041 请求方法: GET
修改的请求 网址: http://localhost/setmeal 请求方法: PUT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 JAVA @GetMapping("/{id}") //用SetmealDto public R<SetmealDto> getById(@PathVariable Long id){ Setmeal setmeal = setmealService.getById(id); SetmealDto setmealDto = new SetmealDto(); //拷贝数据 BeanUtils.copyProperties(setmeal,setmealDto); LambdaQueryWrapper<SetmealDish> queryWrapper = new LambdaQueryWrapper<>(); //配setmealId queryWrapper.eq(SetmealDish::getSetmealId,id); List<SetmealDish> list = setmealDishService.list(queryWrapper); setmealDto.setSetmealDishes(list); return R.success(setmealDto); }
查看订单明细 查看肯定是get请求 但是这个请求比较多
请求网址: [http://localhost/order/page?page=1&pageSize=10&number=1580166484741677057&beginTime=2022-10-19 00%3A00%3A00&endTime=2022-11-16 23%3A59%3A59](http://localhost/order/page?page=1&pageSize=10&number=1580166484741677057&beginTime=2022-10-19 00%3A00%3A00&endTime=2022-11-16 23%3A59%3A59) 请求方法: GET
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 JAVA @GetMapping("/page") public R<Page> page(int page, int pageSize, Long number, String beginTime, String endTime) { //获取当前id Page<Orders> pageInfo = new Page<>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page<>(page, pageSize); //条件构造器 LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); //按时间降序排序 queryWrapper.orderByDesc(Orders::getOrderTime); //订单号 queryWrapper.eq(number != null, Orders::getId, number); //时间段,大于开始,小于结束 queryWrapper.gt(!StringUtils.isEmpty(beginTime), Orders::getOrderTime, beginTime) .lt(!StringUtils.isEmpty(endTime), Orders::getOrderTime, endTime); orderService.page(pageInfo, queryWrapper); List<OrdersDto> list = pageInfo.getRecords().stream().map((item) -> { OrdersDto ordersDto = new OrdersDto(); //获取orderId,然后根据这个id,去orderDetail表中查数据 Long orderId = item.getId(); LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> details = orderDetailService.list(wrapper); BeanUtils.copyProperties(item, ordersDto); //之后set一下属性 ordersDto.setOrderDetails(details); return ordersDto; }).collect(Collectors.toList()); BeanUtils.copyProperties(pageInfo, ordersDtoPage, "records"); ordersDtoPage.setRecords(list); //日志输出看一下 log.info("list:{}", list); return R.success(ordersDtoPage); }
修改订单状态
这个需要看下前端写的js文件 先看下请求和返回json
请求网址: http://localhost/order 请求方法: PUT
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 JSON { status: 3, id: "1580166484741677057" } JS switch(row.status){ case 1: str = '待付款' break; case 2: str = '正在派送' break; case 3: str = '已派送' break; case 4: str = '已完成' break; case 5: str = '已取消' break; }
因为返回值已经写好了 我们只要传入参数就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 JAVA @PutMapping public R<String> changeStatus(@RequestBody Map<String, String> map) { int status = Integer.parseInt(map.get("status")); Long orderId = Long.valueOf(map.get("id")); log.info("修改订单状态:status={},id={}", status, orderId); LambdaUpdateWrapper<Orders> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Orders::getId, orderId); updateWrapper.set(Orders::getStatus, status); orderService.update(updateWrapper); return R.success("订单状态修改成功"); }
5.总结 今天学习状态还不错,但是学习时间有点少了,对自己今天的任务要求也不重,上午完善完老师没写的功能,晚上自己梳理了一遍,以及对自己博客的美化工作,早上第一次做批量启售和停售的时候,出现了一点小插曲,解决完以后,后面的批量删除以及菜单的功能,实现起来也很顺利。本来想加入公司项目的但是好像接口都差不多写完了,计划还是往后面学,项目部署也去学一下,计划挂到服务器里面,再把git和Redis过一遍,这周之前把部署的任务完成了,然后去开始Cloud了打算。