苍穹外卖
引言标题欧克接下来让我们步入正题项目背景黑马程序员《苍穹外卖》作为Java企业级开发实战项目涵盖SpringBoot、MySQL、Redis等技术栈学习目标掌握外卖系统核心功能开发与全栈技术整合技术选择技术选型技术栈概览后端框架SpringBoot MyBatis-Plus Spring MVC数据库MySQL主从复制 Redis缓存中间件Nginx负载均衡 Swagger接口文档前端技术H5 Vue.js可选扩展部分编译环境的配置前端环境的搭建在课程提供的资料中有Nginx负载均衡服务器双击之后等待屏幕一闪即前端运行环境启动成功注意不要将上诉文件放在一个有中文的目录文件中否则可能启动不了后端环境的搭建将课程提供的后端基本框架导入项目之中制造git提交代码环境注意要选择正确的目录不要选择错了根据上面的操作将初始代码上传到本地仓库接着我们创建一个空的远程仓库并且将本地仓库的代码文件推送到远程仓库之中这样之后我们就可以用git来提交代码啦远程仓库刷新后后端数据库的准备通过给的文件资料阅读11个表相关结构并且将sql语句完成该项目所需要的11个表如图所示简单的前后端联调测试注意你在开启springboot项目的时候要连接数据库并且在配置文件中将密码改成自己的密码否则报错java.sql.SQLException解决方法然后在浏览器访问刚刚搭建的前端环境点击登录就可以啦Nginx反向代理思考为什么这里访问路径不一致却可以成功访问原因如下用了Nginx反向代理这里用了proxy_pass 配置进行了方向代理负载均衡的配置如下注意这里还是用的proxy_pass 指令webservers是上面upstream自己定义的 服务器导入接口文档基于yapi导入客户端和管理端的接口新建项目之后完成图示操作完成之后接口就导入成功啦这里就展示一下管理端接口Swagger介绍可以在后端生成接口文档进行在线接口测试注意Knife4j是Java mvc框架集成Swagger生成api文档的增强解决方案使用步骤导入maven坐标注意版本号在上面已经集中定义方便管理黑马提供的资料文件已导入配置类中配置设置映射注意注意 如果你不配置这个映射springboot mvc不会认为你访问的是一个静态资源而是去访问一个动态的Controller成果展示你也可以添加一些日志来帮助你知道这个方法是否调用常用命令示例成果展示由于运用了注解所以不再是上面出现的login /logout代码部分完善登录代码提高代码的安全性我们使用MD5加密方式对密码进行加密注意MD5只能加密不能解密数据库中对应的也要更改成e10adc3949ba59abbe56e057f20f883e更改Java中代码这里运用spring提供的DigestUtils中提供的方法进行加密那么到这一步重启服务你的密码就加密成功啦新增员工接口文档的分析与设计代码Controller层Service层注意我写错了是TODO更改如下Mapper层测试配置接口文档的全局设置获取令牌完成测试完善代码逻辑问题处理新增重复异常问题在全局异常中处理处理刚刚TODO需优化的部分我们资料中有BaseContext类里面定义了方法理解我们每一次的请求都会在一个线程中完成ThreadLocal是一个线程的局部变量线程外的数据不会影响我们可以通过这个当令牌解析出员工id时 利用set方法获取到id接着在线程到了service层时用get方法优化后端程序步骤如下service层员工的分页查询接口的设计实现代码Controller层Service层实现类接口类很简单可以自己写Mapper层xml文件测试代码完善解决方案方法一方法二启用和禁用账号功能接口文档的设计代码实现Controller层Service层实现类mapper层前后端联调编辑员工接口文档的分析查询回显编辑代码部分Controller层Service层实现类Mapper层测试分类管理新增分类接口文档分析代码Controller层注意这里接收前端的是Jason格式所以要注解并且应该用PutMapping注解否则会报错给前端相应的是请求错误因为错用注解没有找到PutMappingService层实现类Mapper层这里语句简单通过注解的方式写了测试分页查询接口文档分析代码Controller层Service层实现类Mapper层xml映射文件前后端联调删除分类接口文档分析请求方式 Delete 请求数据query类型代码Controller层Service层实现类注意因为这里关联了套餐和菜品所以不能随意删除需要判断该分类下是否有有关联则不删除反之则删除Mapper层统计菜品数量套餐数量CategoryMapper层测试通过类型查询分类接口文档的分析代码部分Controller层Service层实现类注意这里是查询所以要用集合去接收你查询到的数据Mapper层xml文件前后端联调测试修改分类接口文档的分析代码Conroller层Service层实现类Mapper层xml文件前后端联调测试分类的启用和禁用接口文档的分析代码Controller层Service层实现类Mapper层注意由于这里分类的禁用和启动都是修改前面我们通过new对象已经把状态封装到了Category实体类中了这里还是直接调用修改的语句就行前后端联调测试自动补充公共字段问题描述注意不可能写每一个公共字段的时候都在service实现类中写这样代码冗余过多不利于维护代码实现思路自定义注解定义切面类到这里我们就通过反射的方法完成了然后自己手动将我们之前写的补充代码注释一下就完成了测试可以看到修改时间等四个公共字段已经填充新增菜品注意这里根据类型查询分类已经写了所以只需要完成 文件上传和新增菜品文件上传接口分析代码Controller层1.获取原始文件名利用前端传入的file.getOriginalname()2.获取原始文件后缀利用lastIndexof(.)获取后缀3.利用UUID.randomUUID.toString()你获取的文件后缀名工具类注意这里就是写upload方法 让Controller层获取路径简单来说就是controller层获取图片url地址然后生成一个新的地址防止多用户传过来的地址重名然后调用阿里oss工具类然后阿里oss可以接收到这个文件并且生成一个url地址返回给前端前端自己去oss访问这个url然后获得图片其中要保证accesskey等信息正确且服务器端配置了正确的环境系统变量测试前端显示了图片新增菜品文档接口分析代码Controller层Service层实现类Mapper层dishMapper注意这里使用了自动填充DishFlavorMapper测试注意这里我已经写了分页查询所以展示出来了但是这里的阿里云bucket我是用的自己的所以照片没有展示出来要收费分页查询接口文档代码Controller层Service层实现类Mapper层测试删除菜单接口文档分析代码部分Controller层Service层实现类Mapper删除口味修改菜单接口文档分析代码部分Controlller层Service层实现类Mapper层营业状态设置代码部分由于营业状态就一个字段所以这里使用redis首先我们引入redis的相关依赖接着我们要创建redis配置类创建redisTemplate对象并且解决一下序列化的问题接下来就可以使用redis通过内存来存储营业状态admin端代码user端代码微信登录接口小程序与后端服务器是如何联系在一起的?可以看到是用户在小程序登录然后生成code微信小程序在发送请求并且把code携带给java后端后端通过Requestbody注解接收这个小程序传过来的Jason数据code 后端这时候通过调用httpclient这个api发送请求给微信官方服务器携带appid等信息这个时候微信官方服务器处理这些信息并且返回一个openid ,session_keyController层Service层这个 是获取openid的方法注意这里Controller层为什么要返回openid token后端将这些信息发送给小程序小程序把这些信息缓存下来之后的每次请求都是把这个信息带给后端这时候后端要创建拦截器拦截验证并且解析token拦截器写拦截器并且注册将商品浏览代码导入缓存菜品数据由于菜品数据访问次数多如果多人同时访问数据库压力大会导致响应很慢等用户体验不好的情况所以通过redis缓存数据代码部分这里将信息存到redis String类型的键值对中将查询过来的数组序列化成redis中的String类型清理缓存当管理员在修改删除增加菜品之后微信小程序展示的菜品都会变所以要清除缓存代码部分写一个方法直接调用减少代码冗余后面修改菜品删除菜品直接调用这个方法就可以套餐缓存原生操作 Redis我们要写大量重复代码redisTemplate.opsForValue().get()、set()、delete()每个查询方法都要写一遍代码冗余。SpringCache 是 Spring 提供的缓存抽象框架基于注解不用手动写 Redis 操作代码。Spring Cache 的方法代码部分添加购物车基本思路代码部分controller层service层你要判断添加的是菜品还是套餐分别通过id去查询然后每个属性自己设置就好了Mapper层判断是否有时你不知道添加的时套餐还是菜品不知道添加的菜品有没有口味所以用动态sql去查询查询购物车代码部分controller层service层清除购物车代码导入地址薄相关代码用户订单接口接口文档分析代码部分controller层service实现类mapper层思路第一步用户提交的订单你要确保购物车不为空地址不为空所以处理异常第二步 插入订单表我们每个表结构对应的实体类所以我们要先把前端收到的dto属性值赋值给实体类并且补全相关属性值这里还要返回主键值给订单明细表用第三步插入订单明细表因为订单明细是通过购物车来写的所以我们通过查询购物车将信息赋值给orderdetail第四步清除购物车调用clean方法第五步返回vowei微信支付由于我们不是商户这里我们模拟微信支付即可我这里添加了模拟定单支付的流程代码部分这里将微信预支付订单给小程序小程序在给微信服务端实现类调用wechatpayutils这个工具类里面的pay方法然后pay方法里面调用了httpclient这个api 给微信服务器发送一个post请求并且返回微信预支付交易单标识也将所需信息返回一起封装在了orderPaymentVo这个实体类中返回给小程序中后小程序通过wx.requestPayment拉起微信支付弹窗WebsocketWebSocket 是全双工长连接协议先通过 HTTP 完成握手协议升级成功后服务端、客户端可以互相主动发消息苍穹外卖用来做新订单推送、催单提醒apache poiava 用来读写 Office 文件的开源库苍穹外卖导出 Excel 报表就是用它核心板块模块作用HSSF读写.xlsExcel 97-2003老版XSSF读写.xlsxExcel2007常用SXSSF超大 Excel 导出防止内存溢出大数据推荐HWPFWord(.doc)XWPFWord(.docx)代码部分依赖:!-- POI 核心 导出xlsx -- dependency groupIdorg.apache.poi/groupId artifactIdpoi-ooxml/artifactId version5.2.4/version /dependencypublic void export(HttpServletResponse response) {//先查询数据库近30天的营业数据LocalDate beginTime LocalDate.now().minusDays(30);LocalDate endTime LocalDate.now().minusDays(1);BusinessDataVO businessDataVO workspaceService.getBusinessData(LocalDateTime.of(beginTime, LocalTime.MIN), LocalDateTime.of(endTime, LocalTime.MAX));//通过poi将数据写入excel中try {//设置response响应头浏览器下载文件response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet);response.setCharacterEncoding(utf-8);String fileName URLEncoder.encode(运营数据报表, StandardCharsets.UTF_8).toString();response.setHeader(Content-Disposition, attachment;filename fileName .xlsx);//基于模板文件创建一个新的Excel文件XSSFWorkbook excel new XSSFWorkbook(in);//获取表格文件的Sheet页XSSFSheet sheet excel.getSheet(Sheet1);//填充数据--时间sheet.getRow(1).getCell(1).setCellValue(时间 beginTime 至 endTime);//获得第4行索引从0开始XSSFRow row sheet.getRow(3);row.getCell(2).setCellValue(businessDataVO.getTurnover());row.getCell(4).setCellValue(businessDataVO.getOrderCompletionRate());row.getCell(6).setCellValue(businessDataVO.getNewUsers());//获得第5行row sheet.getRow(4);row.getCell(2).setCellValue(businessDataVO.getValidOrderCount());row.getCell(4).setCellValue(businessDataVO.getUnitPrice());//填充明细数据 循环30天for (int i 0; i 30; i) {LocalDate date beginTime.plusDays(i);//查询某一天的营业数据BusinessDataVO businessData workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN),LocalDateTime.of(date, LocalTime.MAX));//获得某一行从第8行开始下标7row sheet.getRow(7 i);row.getCell(1).setCellValue(date.toString());row.getCell(2).setCellValue(businessData.getTurnover());row.getCell(3).setCellValue(businessData.getValidOrderCount());row.getCell(4).setCellValue(businessData.getOrderCompletionRate());row.getCell(5).setCellValue(businessData.getUnitPrice());row.getCell(6).setCellValue(businessData.getNewUsers());}//3. 通过输出流将Excel文件下载到客户端浏览器ServletOutputStream out response.getOutputStream();excel.write(out);//关闭资源out.flush();out.close();excel.close();in.close();} catch (IOException e) {e.printStackTrace();}}其他的都是跟之前一样的crud这里就不重复了