ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

苍穹外卖学习笔记-DAY01

苍穹外卖学习笔记-DAY01 下面是在原有讲义基础上加上了一点自己的实际运行截图和一些问题也删除了一些原讲义的一些东西。DAY 011.技术选型1). 用户层本项目中在构建系统管理后台的前端页面我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时我们会使用到微信小程序。2). 网关层Nginx是一个服务器主要用来作为Http服务器部署静态资源访问性能高。在Nginx中还有两个比较重要的作用 反向代理和负载均衡 在进行项目部署时要实现Tomcat的负载均衡就可以通过Nginx来实现。3). 应用层SpringBoot 快速构建Spring项目, 采用 约定优于配置 的思想, 简化Spring项目的配置开发。SpringMVCSpringMVC是spring框架的一个模块springmvc和spring无需通过中间整合层进行整合可以无缝集成。Spring Task: 由Spring提供的定时任务框架。httpclient: 主要实现了对http请求的发送。Spring Cache: 由Spring提供的数据缓存框架JWT: 用于对应用程序上的用户进行身份验证的标记。阿里云OSS: 对象存储服务在项目中主要存储文件如图片等。Swagger 可以自动的帮助开发人员生成接口文档并对接口进行测试。POI: 封装了对Excel表格的常用操作。WebSocket: 一种通信网络协议使客户端和服务器之间的数据交换更加简单用于项目的来单、催单功能实现。4). 数据层MySQL 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。Redis 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。Mybatis 本项目持久层将会使用Mybatis开发。pagehelper: 分页插件。spring data redis: 简化java代码操作Redis的API。5). 工具git: 版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。maven: 项目构建工具。junit单元测试工具开发人员功能实现完毕后需要通过junit对功能进行单元测试。postman: 接口测工具模拟用户发起的各类HTTP请求获取对应的响应结果。2.开发环境搭建2.1前端环境搭建从资料中找到前端运行环境的nginx移动到非中文目录下。遇到的报错双击 nginx.exe 没反应看到error.log里面有错误日志 2026/10/06 12:01:06 [emerg] 5428#21892: CreateDirectory() E:\onlylo_codes\nginx-1.20.2/temp/client_body_temp failed (3: The system cannot find the path specified)手动创建 temp/client_body_temp 目录解决启动后输入 http://localhost:80 看到2.2 后端环境搭建熟悉项目结构后端工程基于 maven 进行项目构建并且进行分模块开发。1). 从当天资料中找到后端初始工程2). 用 IDEA 打开初始工程了解项目的整体结构对工程的每个模块作用说明序号名称说明1sky-take-outmaven父工程统一管理依赖版本聚合其他子模块2sky-common子模块存放公共类例如工具类、常量类、异常类等3sky-pojo子模块存放实体类、VO、DTO等4sky-server子模块后端服务存放配置文件、Controller、Service、Mapper等详细分析上述的每个子模块sky-common:模块中存放的是一些公共类可以供其他模块使用名称说明constant存放相关常量类context存放上下文类enumeration项目的枚举类存储exception存放自定义异常类json处理json转换的类properties存放SpringBoot相关的配置属性类result返回结果类的封装utils常用工具类sky-pojo:模块中存放的是一些 entity、DTO、VO分析sky-pojo模块的每个包的作用名称说明数据流向Entity实体通常和数据库中的表对应Mapper/DB ↔ ServiceDTO数据传输对象通常用于程序中各层之间传递数据前端 → Controller → ServiceVO视图对象为前端展示数据提供的对象Service → Controller → 前端POJO普通Java对象只有属性和对应的getter和settersky-server:模块中存放的是 配置文件、配置类、拦截器、controller、service、mapper、启动类等分析sky-server模块的每个包的作用名称说明config存放配置类controller存放controller类interceptor存放拦截器类mapper存放mapper接口service存放service类SkyApplication启动类数据库环境搭建从资料中找到sky.sql直接打开sky.sql文件通过该sql文件直接可创建数据库所以不需要提前创建数据库直接导入该文件执行即可。双击打开截图如下执行sky.sql文件这里我用的是 Navicat执行完成后共创建出11张表记得navicat 刷新一下每张表的说明序号表名中文名1employee员工表2category分类表3dish菜品表4dish_flavor菜品口味表5setmeal套餐表6setmeal_dish套餐菜品关系表7user用户表8address_book地址表9shopping_cart购物车表10orders订单表11order_detail订单明细表对于具体的表结构, 以及表结构中的字段, 可以参考资料中的**《数据库设计文档》**部分截图如下前后端联调后端的初始工程中已经实现了登录功能直接进行前后端联调测试即可我点击登录后没反应F12查看网络想起来后端都没启动hhh启动后再次点击登录还是没反应后端日志发现后端收到了登录请求但是连接数据库报错了找了下发现是配置没修改修改下host改为本地password改为自己数据库的再一次启动密码错误。。。还挺奇怪的数据库里面 和 负载是一样的看了下登录ServiceImpl原来要md5加密。。。去网上找了个md5加密修改了下数据库终于登录成功、实现思路1.Controller层在sky-server模块中com.sky.controller.admin.EmployeeController/** * 登录 * * param employeeLoginDTO * return */ PostMapping(/login) public ResultEmployeeLoginVO login(RequestBody EmployeeLoginDTO employeeLoginDTO) { // DTO 前端传给后端的对象 log.info(员工登录{}, employeeLoginDTO); //调用service方法查询数据库 Employee employee employeeService.login(employeeLoginDTO); //登录成功后生成jwt令牌 MapString, Object claims new HashMap(); claims.put(JwtClaimsConstant.EMP_ID, employee.getId()); String token JwtUtil.createJWT( jwtProperties.getAdminSecretKey(), jwtProperties.getAdminTtl(), claims); // VO 给前端展示的数据 EmployeeLoginVO employeeLoginVO EmployeeLoginVO.builder() .id(employee.getId()) .userName(employee.getUsername()) .name(employee.getName()) .token(token) .build(); return Result.success(employeeLoginVO); }2.Service层在sky-server模块中com.sky.service.impl.EmployeeServiceImpl/** * 员工登录 * * param employeeLoginDTO * return */ public Employee login(EmployeeLoginDTO employeeLoginDTO) { String username employeeLoginDTO.getUsername(); String password employeeLoginDTO.getPassword(); //1、根据用户名查询数据库中的数据 Employee employee employeeMapper.getByUsername(username); //2、处理各种异常情况用户名不存在、密码不对、账号被锁定 if (employee null) { //账号不存在 throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND); } //密码比对 if (!password.equals(employee.getPassword())) { //密码错误 throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); } if (employee.getStatus() StatusConstant.DISABLE) { //账号被锁定 throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED); } //3、返回实体对象 return employee; }3.Mapper层在sky-server模块中com.sky.mapper.EmployeeMapperpackage com.sky.mapper; import com.sky.entity.Employee; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; Mapper public interface EmployeeMapper { /** * 根据用户名查询员工 * param username * return */ Select(select * from employee where username #{username}) Employee getByUsername(String username); }nginx反向代理和负载均衡对登录功能测试完毕后接下来我们思考一个问题前端发送的请求是如何请求到后端服务的前端请求地址http://localhost/api/employee/login后端接口地址http://localhost:8080/admin/employee/login前端请求地址后端接口地址很明显两个地址不一致那是如何请求到后端服务的呢1). nginx反向代理nginx 反向代理就是将前端发送的动态请求由 nginx 转发到后端服务器nginx 反向代理的好处提高访问速度因为nginx本身可以进行缓存如果访问的同一接口并且做了数据缓存nginx就直接可把数据返回不需要真正地访问服务端从而提高访问速度。进行负载均衡所谓负载均衡,就是把大量的请求按照我们指定的方式均衡的分配给集群中的每台服务器。保证后端服务安全因为一般后台服务地址不会暴露所以使用浏览器不能直接访问可以把nginx作为请求访问的入口请求到达nginx后转发到具体的服务中从而保证后端服务的安全。nginx 反向代理的配置方式server{ listen 80; server_name localhost; location /api/{ proxy_pass http://localhost:8080/admin/; #反向代理 } }proxy_pass该指令是用来设置代理服务器的地址可以是主机名称IP地址加端口号等形式。如上代码的含义是监听80端口号 然后当我们访问 http://localhost:80/api/../..这样的接口的时候它会通过 location /api/ {} 这样的反向代理到 http://localhost:8080/admin/上来。接下来进到nginx-1.20.2\conf打开nginx配置# 反向代理,处理管理端发送的请求 location /api/ { proxy_pass http://localhost:8080/admin/; #proxy_pass http://webservers/admin/; }当在访问http://localhost/api/employee/loginnginx接收到请求后转到http://localhost:8080/admin/故最终的请求地址为http://localhost:8080/admin/employee/login和后台服务的访问地址一致。2). nginx 负载均衡当如果服务以集群的方式进行部署时那nginx在转发请求到服务器时就需要做相应的负载均衡。其实负载均衡从本质上来说也是基于反向代理来实现的最终都是转发请求。问题 nginx实际开发中部署在哪里客户端吗deepseek: Nginx 在实际开发中一定是部署在服务端或者更准确地说是服务端的基础设施层开发环境你现在的状态浏览器 (客户端)↓ 请求 http://localhost/api/employee/login (端口80)Nginx (部署在你的Windows电脑上或Linux虚拟机中)↓ 反向代理负载均衡Spring Boot 后端集群 (部署在本地IDEA端口8080、8081等)实际生产环境真实上线用户手机/浏览器 (客户端)↓ 请求 https://api.changqiong.com/api/employee/login (公网域名)云服务器 (公网IP) 上的 Nginx (监听80/443端口)↓ 负载均衡内网服务器集群 (多台Linux服务器运行Spring Boot Jar包端口8080)nginx 负载均衡的配置方式upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; } server{ listen 80; server_name localhost; location /api/{ proxy_pass http://webservers/admin;#负载均衡 } }upstream如果代理服务器是一组服务器的话我们可以使用upstream指令配置后端服务器组。如上代码的含义是监听80端口号 然后当我们访问 http://localhost:80/api/../..这样的接口的时候它会通过 location /api/ {} 这样的反向代理到 http://webservers/admin根据webservers名称找到一组服务器根据设置的负载均衡策略(默认是轮询)转发到具体的服务器。注upstream后面的名称可自定义但要上下保持一致。nginx 负载均衡策略名称说明轮询默认方式weight权重方式默认为1权重越高被分配的客户端请求就越多ip_hash依据ip分配方式这样每个访客可以固定访问一个后端服务least_conn依据最少连接方式把请求优先分配给连接数少的后端服务url_hash依据url分配方式这样相同的url会被分配到同一个后端服务fair依据响应时间方式响应时间短的服务将会被优先分配具体配置方式轮询upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; }weight:upstream webservers{ server 192.168.100.128:8080 weight90; server 192.168.100.129:8080 weight10; }ip_hash:upstream webservers{ ip_hash; server 192.168.100.128:8080; server 192.168.100.129:8080; }least_conn:upstream webservers{ least_conn; server 192.168.100.128:8080; server 192.168.100.129:8080; }url_hash:upstream webservers{ hash request_uri; server 192.168.100.128:8080; server 192.168.100.129:8080; }fair:upstream webservers{ server 192.168.100.128:8080; server 192.168.100.129:8080; fair; }3 完善登录功能问题员工表中的密码是明文存储安全性太低。早知道看完再写笔记了hhh解决思路将密码加密后存储提高安全性使用MD5加密方式对明文密码加密实现步骤修改数据库中明文密码改为MD5加密后的密文打开employee表修改密码修改Java代码前端提交的密码进行MD5加密后再跟数据库中密码比对打开EmployeeServiceImpl.java修改比对密码/** * 员工登录 * * param employeeLoginDTO * return */ public Employee login(EmployeeLoginDTO employeeLoginDTO) { //1、根据用户名查询数据库中的数据 //2、处理各种异常情况用户名不存在、密码不对、账号被锁定 //....... //密码比对 // TODO 后期需要进行md5加密然后再进行比对 password DigestUtils.md5DigestAsHex(password.getBytes()); if (!password.equals(employee.getPassword())) { //密码错误 throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR); } //........ //3、返回实体对象 return employee; }4. 导入接口文档前后端分离开发方式要求我们之前需要先将接口定义好这样前后端人员才能并行开发所以这个章节就需要将接口文档导入到管理平台为我们后面业务开发做好准备。其实在真实的企业开发中接口设计过程其实是一个非常漫长的过程可能需要多次开会讨论调整甚至在开发的过程中才会发现某些接口定义还需要再调整这种情况其实是非常常见的但是由于项目时间原因所以选择一次性导入所有的接口4.1 前后端分离开发流程第一步定义接口确定接口的路径、请求方式、传入参数、返回参数。第二步前端开发人员和后端开发人员并行开发同时也可自测。第三步前后端人员进行连调测试。第四步提交给测试人员进行最终测试。4.2 操作步骤将课程资料中提供的项目接口导入YApi。访问地址http://yapi.smart-xwork.cn/1). 从资料中找到项目接口文件2). 导入到YApi平台暂时懒得导入所以没有截图在YApi平台创建出两个项目选择苍穹外卖-管理端接口.json导入导入成功另一个用户端json文件也执行相同操作。5. Swagger5.1 介绍Swagger 是一个规范和完整的框架用于生成、描述、调用和可视化 RESTful 风格的 Web 服务(API Documentation Design Tools for Teams | Swagger)。 它的主要作用是使得前后端分离开发更加方便有利于团队协作接口的文档在线自动生成降低后端开发人员编写接口文档的负担功能测试 Spring已经将Swagger纳入自身的标准建立了Spring-swagger项目现在叫Springfox。通过在项目中引入Springfox 即可非常简单快捷的使用Swagger。knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!目前一般都使用knife4j框架。5.2 使用步骤导入 knife4j 的maven坐标在pom.xml中添加依赖dependency groupIdcom.github.xiaoymin/groupId artifactIdknife4j-spring-boot-starter/artifactId /dependency在配置类中加入 knife4j 相关配置WebMvcConfiguration.java/** * 通过knife4j生成接口文档 * return */ Bean public Docket docket() { ApiInfo apiInfo new ApiInfoBuilder() .title(苍穹外卖项目接口文档) .version(2.0) .description(苍穹外卖项目接口文档) .build(); Docket docket new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo) .select() .apis(RequestHandlerSelectors.basePackage(com.sky.controller)) .paths(PathSelectors.any()) .build(); return docket; }设置静态资源映射否则接口文档页面无法访问WebMvcConfiguration.java/** * 设置静态资源映射 * param registry */ protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/doc.html).addResourceLocations(classpath:/META-INF/resources/); registry.addResourceHandler(/webjars/**).addResourceLocations(classpath:/META-INF/resources/webjars/); }访问测试接口文档访问路径为 http://ip:port/doc.html --- http://localhost:8080/doc.html接口测试:测试登录功能思考通过 Swagger 就可以生成接口文档那么我们就不需要 Yapi 了1、Yapi 是设计阶段使用的工具管理和维护接口2、Swagger 在开发阶段使用的框架帮助后端开发人员做后端的接口测试5.3 常用注解通过注解可以控制生成的接口文档使接口文档拥有更好的可读性常用注解如下注解说明Api用在类上例如Controller表示对类的说明ApiModel用在类上例如entity、DTO、VOApiModelProperty用在属性上描述属性信息ApiOperation用在方法上例如Controller的方法说明方法的用途、作用接下来使用上述注解生成可读性更好的接口文档在sky-pojo模块中EmployeeLoginDTO.javapackage com.sky.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; Data ApiModel(description 员工登录时传递的数据模型) public class EmployeeLoginDTO implements Serializable { ApiModelProperty(用户名) private String username; ApiModelProperty(密码) private String password; }EmployeeLoginVo.javapackage com.sky.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; Data Builder NoArgsConstructor AllArgsConstructor ApiModel(description 员工登录返回的数据格式) public class EmployeeLoginVO implements Serializable { ApiModelProperty(主键值) private Long id; ApiModelProperty(用户名) private String userName; ApiModelProperty(姓名) private String name; ApiModelProperty(jwt令牌) private String token; }在sky-server模块中EmployeeController.javapackage com.sky.controller.admin; import com.sky.constant.JwtClaimsConstant; import com.sky.dto.EmployeeLoginDTO; import com.sky.entity.Employee; import com.sky.properties.JwtProperties; import com.sky.result.Result; import com.sky.service.EmployeeService; import com.sky.utils.JwtUtil; import com.sky.vo.EmployeeLoginVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; /** * 员工管理 */ RestController RequestMapping(/admin/employee) Slf4j Api(tags 员工相关接口) public class EmployeeController { Autowired private EmployeeService employeeService; Autowired private JwtProperties jwtProperties; /** * 登录 * * param employeeLoginDTO * return */ PostMapping(/login) ApiOperation(value 员工登录) public ResultEmployeeLoginVO login(RequestBody EmployeeLoginDTO employeeLoginDTO) { //.............. } /** * 退出 * * return */ PostMapping(/logout) ApiOperation(员工退出) public ResultString logout() { return Result.success(); } }启动服务访问http://localhost:8080/doc.html
返回列表