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
| @Slf4j @RestController @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @PostMapping("/login") public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){
String password = employee.getPassword(); password = DigestUtils.md5DigestAsHex(password.getBytes());
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Employee::getUsername,employee.getUsername()); Employee emp = employeeService.getOne(queryWrapper);
if (emp == null){ return R.error("用户不存在"); }
if (! emp.getPassword().equals(password)){ return R.error("密码错误"); }
if (emp.getStatus() == 0){ return R.error("用户已被禁用"); }
request.getSession().setAttribute("employee",emp.getId()); return R.success(emp); } @PostMapping("logout") public R<String> logout(HttpServletRequest request){ request.getSession().removeAttribute("employee"); return R.success("退出成功"); } }
|
3.2后台系统退出功能
这个比较简单 点击按钮退出到登陆页面就行了
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@PostMapping("/logout") public R<String> logout(HttpServletRequest request){ 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
|
@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; String requestURI = request.getRequestURI(); String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**" }; log.info("拦截到的请求:{}",requestURI); boolean check = check(urls, requestURI); if (check){ filterChain.doFilter(request,response); return; } Object employee = request.getSession().getAttribute("employee"); if (employee != null){ log.info("用户已登录,用户id为:{}",employee); filterChain.doFilter(request,response); return; } log.info("用户未登录"); 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
| @PostMapping public R<String> save(HttpServletRequest request, @RequestBody Employee employee) { log.info("新增的员工信息:{}", employee.toString()); employee.setPassword(DigestUtils.md5DigestAsHex("123456".getBytes())); employee.setCreateTime(LocalDateTime.now()); employee.setUpdateTime(LocalDateTime.now());
Long empId = (Long) request.getSession().getAttribute("employee");
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
| @Slf4j @RestControllerAdvice @ControllerAdvice public class GlobalExceptionHandler {
@ExceptionHandler(SQLIntegrityConstraintViolationException.class) public R<String> exceptionHandler(SQLIntegrityConstraintViolationException sqlBug){ log.error(sqlBug.getMessage()); if (sqlBug.getMessage().contains("Duplicate entry")){ String[] error = sqlBug.getMessage().split(" "); 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
| @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
| @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<>();
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
| @PutMapping public R<String> Update(HttpServletRequest request, @RequestBody Employee employee){ log.info(employee.toString()); Long empId = (Long) request.getSession().getAttribute("employee"); employee.setUpdateUser(empId); 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
| @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); messageConverter.setObjectMapper(new JacksonObjectMapper()); converters.add(0, messageConverter); }
|
编辑员工信息
先跟着老师的文档分析了前端代码是如何实现页面的 回显什么也是前端所实现的。
我所实现的服务端接受请求,并根据员工id查询员工信息,并将员工信息以json形式响应给页面
1 2 3 4 5 6 7 8 9
| @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
| @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
| @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()); }
@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
| public class CustomException extends RuntimeException{ public CustomException(String msg){ super(msg); } }
|
1 2 3 4 5
| @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
| @Service public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService { @Autowired private DishFlavorService dishFlavorService;
@Override public void saveWithFlavor(DishDto dishDto) { this.save(dishDto); Long dishId = dishDto.getId(); List<DishFlavor> flavors = dishDto.getFlavors(); for (DishFlavor dishFlavor : flavors) { dishFlavor.setDishId(dishId); } 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
| @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(); 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); }
|
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
| @GetMapping("/page") public R<Page> page(int page, int pageSize ,String name){ log.info("page={},pageSize={},name={}", page, pageSize, name); Page<Setmeal> pageInfo = new Page<>(page, pageSize); Page<SetmealDto> setmealDtoPage = new Page<>(); LambdaQueryWrapper<Setmeal> wrapper = new LambdaQueryWrapper<>(); wrapper.like(name != null,Setmeal::getName,name); wrapper.orderByDesc(Setmeal::getUpdateTime); setmealService.page(pageInfo,wrapper); BeanUtils.copyProperties(pageInfo,setmealDtoPage,"records"); List<Setmeal> records = pageInfo.getRecords(); List<SetmealDto> list = new ArrayList<>(); for (Setmeal item : records){ SetmealDto setmealDto = new SetmealDto(); BeanUtils.copyProperties(item,setmealDto); Long categoryId = item.getCategoryId(); Category category = categoryService.getById(categoryId); if (category != null){ String categoryName = category.getName(); setmealDto.setCategoryName(categoryName); } list.add(setmealDto); } setmealDtoPage.setRecords(list); return R.success(setmealDtoPage); }
|
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
| <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency>
<dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency>
<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
| 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 props = new Properties(); props.put("mail.smtp.auth", "true"); props.put("mail.smtp.host", "smtp.qq.com"); props.put("mail.smtp.port", "587"); props.put("mail.user", "1452582554@qq.com"); props.put("mail.password", "vxccjkvvlrokgigh"); 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() { 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
| String[] urls = new String[]{ "/employee/login", "/employee/logout", "/backend/**", "/front/**", "/user/sendMsg", "/user/login" };
|
判断用户是否登录
1 2 3 4 5 6 7 8
| 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
| @PostMapping("/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); MailUtils.sendTestMail(phone, code); 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
| @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(); String codeInSession = session.getAttribute(phone).toString(); String codeInSessionUpp = codeInSession.toUpperCase(); 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.setAttribute("user",user.getId()); return R.success(user); } return R.error("登录失败"); }
|
3. BUG点
难点(关键代码或关键配置,BUG截图+解决方案)
输入验证码以后,就自动又跳会登录页面,浏览器没有login 的请求,看日志跳到了最后的用户未登录,说明check没有匹配上,检查代码,发现拦截器的数组中,“/user/login” 少了一个/,加上以后顺利解决了问题
客户端list查询找的时候,因为调用的是同一个请求,但是我一直没有显示,找不到原因,后面发现自己写的list代码和老师的有一些区别,有一块条件判断的时候,我少加了一个 不等于null的判断,导致我的type在前端显示是null
1
| 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
| @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 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
| @GetMapping("/userPage") public R<Page> page(int page, int pageSize) { Long userId = BaseContext.getCurrentId(); Page<Orders> pageInfo = new Page<>(page, pageSize); Page<OrdersDto> ordersDtoPage = new Page<>(); LambdaQueryWrapper<Orders> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(Orders::getUserId,userId); queryWrapper.orderByAsc(Orders::getOrderTime); orderService.page(pageInfo,queryWrapper); BeanUtils.copyProperties(pageInfo,ordersDtoPage,"records");
List<Orders> records = pageInfo.getRecords(); List<OrdersDto> list = new ArrayList<>(); for (Orders item : records) { OrdersDto ordersDto = new OrdersDto(); BeanUtils.copyProperties(item,ordersDto); Long orderId = item.getId(); LambdaQueryWrapper<OrderDetail> qw = new LambdaQueryWrapper<>(); qw.eq(OrderDetail::getOrderId,orderId); List<OrderDetail> details = orderDetailService.list(qw); ordersDto.setOrderDetails(details); list.add(ordersDto); } ordersDtoPage.setRecords(list); return R.success(ordersDtoPage); }
|
登出功能
1 2 3 4 5 6
| @PostMapping("/loginout") public R<String> loginout(HttpServletRequest httpServletRequest){ httpServletRequest.getSession().removeAttribute("user"); return R.success("退出成功"); }
|
修改/删除地址
数据回显
1 2 3 4 5 6 7 8
| @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
| @PutMapping public R<String> updateAdd(@RequestBody AddressBook addressBook) { if (addressBook == null) { throw new CustomException("地址信息不存在,请刷新重试"); } addressBookService.updateById(addressBook); return R.success("地址修改成功"); }
|
删除地址
请求网址: http://localhost/addressBook?ids=1579828298672885762
请求方法: DELETE
- 直接在AddressBookController写Delete方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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
| { 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
| @PostMapping("/sub") public R<ShoppingCart> deleteSub(@RequestBody ShoppingCart shoppingCart) { Long dishId = shoppingCart.getDishId(); Long setmealId = shoppingCart.getSetmealId(); LambdaQueryWrapper<ShoppingCart> lambdaQueryWrapper = new LambdaQueryWrapper<>(); 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) { lambdaQueryWrapper.eq(ShoppingCart::getSetmealId, setmealId); ShoppingCart setmealCart = shoppingCartService.getOne(lambdaQueryWrapper); setmealCart.setNumber(setmealCart.getNumber() - 1); Integer currentNum = setmealCart.getNumber(); if (currentNum > 0) { shoppingCartService.updateById(setmealCart); } else if (currentNum == 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
| @GetMapping("/dish/{id}") public R<List<DishDto>> showSetmealDish(@PathVariable Long id) { LambdaQueryWrapper<SetmealDish> dishLambdaQueryWrapper = new LambdaQueryWrapper<>(); dishLambdaQueryWrapper.eq(SetmealDish::getSetmealId, id); List<SetmealDish> records = setmealDishService.list(dishLambdaQueryWrapper); List<DishDto> dtoList = records.stream().map((item) -> { DishDto dishDto = new DishDto(); BeanUtils.copyProperties(item,dishDto); Long dishId = item.getDishId(); 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
| @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) { dish.setStatus(status); dishService.updateById(dish); return Result.success("售卖状态修改成功"); } return Result.error("系统繁忙,请稍后再试"); }
|
1 2 3 4 5 6 7 8 9
| @PostMapping("/status/{status}") public Result<String> status(@PathVariable Integer status, @RequestParam List<Long> ids) { log.info("status:{},ids:{}", status, ids); LambdaUpdateWrapper<Dish> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.in(ids != null, Dish::getId, ids); updateWrapper.set(Dish::getStatus, status); dishService.update(updateWrapper); return Result.success("批量操作成功"); }
|
菜品批量删除
查看前端请求
请求网址: http://localhost/dish?ids=1578674689490825217
请求方法: DELETE
同样要判断菜品是不是停售状态
我直接写批量的了,区别就在queryWrapper.in 和传入的参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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
| @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
| @GetMapping("/{id}") 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<>(); queryWrapper.eq(SetmealDish::getSetmealId,id); List<SetmealDish> list = setmealDishService.list(queryWrapper); setmealDto.setSetmealDishes(list); return R.success(setmealDto); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @PutMapping @CacheEvict(value = "setmealCache", allEntries = true) public R<Setmeal> updateWithDish(@RequestBody SetmealDto setmealDto){ List<SetmealDish> setmealDishes = setmealDto.getSetmealDishes(); Long id = setmealDto.getId(); LambdaQueryWrapper<SetmealDish> qw = new LambdaQueryWrapper<>(); qw.eq(SetmealDish::getSetmealId,id); setmealDishService.remove(qw); setmealDishes.stream().map((item) -> { item.setSetmealId(id); return item; }).collect(Collectors.toList()); setmealService.updateById(setmealDto); setmealDishService.saveBatch(setmealDishes); 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
| @GetMapping("/page") public R<Page> page(int page, int pageSize, Long number, String beginTime, String endTime) { 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(); Long orderId = item.getId(); LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> details = orderDetailService.list(wrapper); BeanUtils.copyProperties(item, ordersDto); 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
| { status: 3, id: "1580166484741677057" }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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
| @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了打算。