# 苍穹外卖 ## 项目简介 ### 整体介绍 本项目(苍穹外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括 **系统管理后台** 和 **小程序端应用** 两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护,对餐厅的各类数据进行统计,同时也可进行来单语音播报功能。小程序端主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单、支付、催单等。 ![image-20221106194424735](https://pic.bitday.top/i/2025/04/10/qipsdw-0.png) **1). 管理端功能** 员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ,数据统计,来单提醒。 **2). 用户端功能** 微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览。 ### 技术选型 ![image-20221106185646994](https://pic.bitday.top/i/2025/04/10/qk8jfn-0.png) **1). 用户层** 本项目中在构建系统管理后台的前端页面,我们会用到H5、Vue.js、ElementUI、apache echarts(展示图表)等技术。而在构建移动端应用时,我们会使用到微信小程序。 **2). 网关层** Nginx是一个服务器,主要用来作为Http服务器,部署静态资源,访问性能高。在Nginx中还有两个比较重要的作用: 反向代理和负载均衡, 在进行项目部署时,要实现Tomcat的负载均衡,就可以通过Nginx来实现。 **3). 应用层** SpringBoot: 快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发。 SpringMVC:SpringMVC是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请求,获取对应的响应结果。 ### 准备工作 //待完善,最后写一套本地java开发、nginx部署前端;服务器docker部署的方案!!! #### 前端环境搭建 1.构建和打包前端项目 ```bash npm run build ``` 2.将构建文件复制到指定目录 Nginx 默认的静态文件根目录通常是 **`/usr/share/nginx/html`**,你可以选择将打包好的静态文件拷贝到该目录 或者使用自定义目录`/var/www/my-frontend`,并修改 Nginx 配置文件来指向这个目录。 3.配置 Nginx 打开 Nginx 的配置文件,通常位于 `/etc/nginx/nginx.conf` 以下是一个使用自定义目录 `/var/www/my-frontend` 作为站点根目录的示例配置: ```nginx server { listen 80; server_name your-domain.com; # 如果没有域名可以使用 _ 或 localhost root /var/www/my-frontend; index index.html; location / { try_files $uri $uri/ /index.html; } } ``` 4.启动或重启 Nginx ```bash sudo nginx -t # 检查配置是否正确 sudo systemctl restart nginx # 重启 Nginx 服务 ``` 5.访问前端项目 在浏览器中输入你配置的域名或服务器 IP 地址 #### 后端环境搭建 ![image-20250410161050451](https://pic.bitday.top/i/2025/04/10/qmunln-0.png) 工程的每个模块作用说明: | **序号** | **名称** | **说明** | | -------- | ------------ | ------------------------------------------------------------ | | 1 | sky-take-out | maven父工程,统一管理依赖版本,聚合其他子模块 | | 2 | sky-common | 子模块,存放公共类,例如:工具类、常量类、异常类等 | | 3 | sky-pojo | 子模块,存放实体类、VO、DTO等 | | 4 | sky-server | 子模块,后端服务,存放配置文件、Controller、Service、Mapper等 | 分析sky-common模块的每个包的作用: | 名称 | 说明 | | ----------- | ------------------------------ | | constant | 存放相关常量类 | | context | 存放上下文类 | | enumeration | 项目的枚举类存储 | | exception | 存放自定义异常类 | | json | 处理json转换的类 | | properties | 存放SpringBoot相关的配置属性类 | | result | 返回结果类的封装 | | utils | 常用工具类 | 分析sky-pojo模块的每个包的作用: | **名称** | **说明** | | -------- | ------------------------------------------------------------ | | Entity | 实体,通常和数据库中的表对应 | | DTO | 数据传输对象,通常用于程序中各层之间传递数据(接收从web来的数据) | | VO | 视图对象,为前端展示数据提供的对象(响应给web) | | POJO | 普通Java对象,只有属性和对应的getter和setter | 分析sky-server模块的每个包的作用: | 名称 | 说明 | | -------------- | ---------------- | | config | 存放配置类 | | controller | 存放controller类 | | interceptor | 存放拦截器类 | | mapper | 存放mapper接口 | | service | 存放service类 | | SkyApplication | 启动类 | #### 数据库初始化 执行sky.sql文件 | 序号 | 数据表名 | 中文名称 | | ---- | ------------- | -------------- | | 1 | employee | 员工表 | | 2 | category | 分类表 | | 3 | dish | 菜品表 | | 4 | dish_flavor | 菜品口味表 | | 5 | setmeal | 套餐表 | | 6 | setmeal_dish | 套餐菜品关系表 | | 7 | user | 用户表 | | 8 | address_book | 地址表 | | 9 | shopping_cart | 购物车表 | | 10 | orders | 订单表 | | 11 | order_detail | 订单明细表 | #### Nginx **1.静态资源托管** 直接高效地托管前端静态文件(HTML/CSS/JS/图片等)。 ```nginx server { root /var/www/html; index index.html; location / { try_files $uri $uri/ /index.html; # 支持前端路由(如 React/Vue) } } ``` - **`try_files`**:按顺序尝试多个文件或路径,直到找到第一个可用的为止。 - `$uri`:尝试直接访问请求的路径对应的文件(例如 `/css/style.css`)。 - `$uri/`:尝试将路径视为目录(例如 `/blog/` 会查找 `/blog/index.html`)。 - `/index.html`:如果前两者均未找到,最终返回前端入口文件 `index.html`。 **2.nginx 反向代理:** **反向代理的好处:** - 提高访问速度 因为nginx本身可以进行缓存,如果访问的同一接口,并且做了数据缓存,nginx就直接可把数据返回,不需要真正地访问服务端,从而提高访问速度。 - 保证后端服务安全 因为一般后台服务地址不会暴露,所以使用浏览器不能直接访问,可以把nginx作为请求访问的入口,请求到达nginx后转发到具体的服务中,从而保证后端服务的安全。 - 统一入口解决跨域问题(无需后端配置 CORS)。 **nginx 反向代理的配置方式:** ```nginx server{ listen 80; server_name localhost; location /api/{ proxy_pass http://localhost:8080/admin/; #反向代理 } } ``` 监听80端口号, 然后当我们访问 http://localhost:80/api/../..这样的接口的时候,它会通过 location /api/ {} 这样的反向代理到 http://localhost:8080/admin/上来。 **3.负载均衡配置**(默认是轮询) 将流量分发到多个后端服务器,提升系统吞吐量和容错能力。 ```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;#负载均衡 } } ``` **完整流程示例** 1. **用户访问**:浏览器打开 `http://yourdomain.com`。 2. **Nginx 返回静态文件**:返回 `index.html` 和前端资源。 3. **前端发起 API 请求**:前端代码调用 `/api/data`。 4. **Nginx 代理请求**:将 `/api/data` 转发到 `http://backend_server:3000/api/data`。 5. **后端响应**:处理请求并返回数据,Nginx 将结果传回前端。 跨域问题: #### APIFox 使用APIFox管理、测试接口、导出接口文档... **优势:** 1.多格式支持 APIFox 能够导入包括 YApi 格式在内的多种接口文档,同时支持导出为 OpenAPI、Postman Collection、Markdown 和 HTML 等格式,使得接口文档在不同工具间无缝迁移和使用。 2.接口调试与 Mock 内置接口调试工具可以直接发送请求、查看返回结果,同时内置 Mock 服务功能,方便前后端联调和接口数据模拟,提升开发效率。 3.易用性与团队协作 界面直观、操作便捷,支持多人协作,通过分支管理和版本控制,团队成员可以并行开发并进行变更管理,确保接口维护有序。 **迭代分支功能:** 新建迭代分支,新增的待测试的接口在这里充分测试,没问题之后合并回主分支。 image-20250410180705866 **导出接口文档:** 推荐导出数据格式为OpenAPI Spec,它是一种通用的 API 描述标准,Postman和APIFox都支持。 image-20250410182110385 #### Swagger 1. 使得前后端分离开发更加方便,有利于团队协作 2. 接口的文档在线自动生成,降低后端开发人员编写接口文档的负担 3. 功能测试 **使用:** **1.导入 knife4j 的maven坐标** 在pom.xml中添加依赖 ```java com.github.xiaoymin knife4j-spring-boot-starter ``` **2.在配置类中加入 knife4j 相关配置** WebMvcConfiguration.java ```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; } ``` **3.设置静态资源映射,否则接口文档页面无法访问** WebMvcConfiguration.java ```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/"); } ``` **4.访问测试** 接口文档访问路径为 http://ip:port/doc.html ---> http://localhost:8080/doc.html 这是根据后端 Java 代码(通常是注解)自动生成接口文档,访问是通过**后端服务的端口**,这些文档最终会以静态文件的形式存在于 jar 包内,通常存放在 `META-INF/resources/` **常用注解** 通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下: | **注解** | **说明** | | ----------------- | ---------------------------------------------------------- | | @Api | 用在类上,例如Controller,表示对类的说明 | | @ApiModel | 用在类上,例如entity、DTO、VO | | @ApiModelProperty | 用在属性上,描述属性信息 | | @ApiOperation | 用在**方法**上,例如Controller的方法,说明方法的用途、作用 | EmployeeLoginDTO.java ```java @Data @ApiModel(description = "员工登录时传递的数据模型") public class EmployeeLoginDTO implements Serializable { @ApiModelProperty("用户名") private String username; @ApiModelProperty("密码") private String password; } ``` ![image-20240327170247852](https://pic.bitday.top/i/2025/03/19/u7ybj9-2.png) EmployeeController.java ```java @Api(tags = "员工相关接口") public class EmployeeController { @Autowired private EmployeeService employeeService; @Autowired private JwtProperties jwtProperties; /** * 登录 * * @param employeeLoginDTO * @return */ @PostMapping("/login") @ApiOperation(value = "员工登录") public Result login(@RequestBody EmployeeLoginDTO employeeLoginDTO) { //.............. } } ``` ## 实战开发 ### 分页查询 传统员工分页查询分析: ![image-20221215153413290](https://pic.bitday.top/i/2025/03/19/u6p5ae-2.png) **采用分页插件PageHelper:** ![image-20221215170038833](https://pic.bitday.top/i/2025/03/19/u6o1ho-2.png) **在执行empMapper.list()方法时,就是执行:select * from emp 语句,怎么能够实现分页操作呢?** 分页插件帮我们完成了以下操作: 1. 先获取到要执行的SQL语句: ``` select * from emp ``` 2. 为了实现分页,第一步是获取符合条件的总记录数。分页插件将原始 SQL 查询中的 `SELECT *` 改成 `SELECT count(*)` ```mysql select count(*) from emp; ``` 3. 一旦知道了总记录数,分页插件会将 `SELECT *` 的查询语句进行修改,加入 `LIMIT` 关键字,限制返回的记录数。 ```mysql select * from emp limit ?, ? ``` 第一个参数(`?`)是 起始位置,通常是 `(当前页 - 1) * 每页显示的记录数`,即从哪一行开始查询。 第二个参数(`?`)是 每页显示的记录数,即返回多少条数据。 4. 执行分页查询,例如,假设每页显示 10 条记录,你请求第 2 页数据,那么 SQL 语句会变成: ```mysql select * from emp limit 10, 10; ``` **使用方法:** 当使用 **PageHelper** 分页插件时,无需在 Mapper 中手动处理分页。只需在 Mapper 中编写常规的列表查询。 - 在 **Service 层**,调用 Mapper 方法之前,**设置分页参数**。 - 调用 Mapper 查询后,**自动进行分页**,并将结果封装到 `PageBean` 对象中返回。 1、在pom.xml引入依赖 ```java com.github.pagehelper pagehelper-spring-boot-starter 1.4.2 ``` 2、EmpMapper ```java @Mapper public interface EmpMapper { //获取当前页的结果列表 @Select("select * from emp") public List list(); } ``` 3、EmpServiceImpl 当调用 `PageHelper.startPage(page, pageSize)` 时,PageHelper 插件会拦截随后的 SQL 查询,自动修改查询,加入 `LIMIT` 子句来实现分页功能。 ```java @Override public PageBean page(Integer page, Integer pageSize) { // 设置分页参数 PageHelper.startPage(page, pageSize); //page是页号,不是起始索引 // 执行分页查询 List empList = empMapper.list(); // 获取分页结果 Page p = (Page) empList; //封装PageBean PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); return pageBean; } ``` 4、Controller ```java @Slf4j @RestController @RequestMapping("/emps") public class EmpController { @Autowired private EmpService empService; //条件分页查询 @GetMapping public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize) { //记录日志 log.info("分页查询,参数:{},{}", page, pageSize); //调用业务层分页查询功能 PageBean pageBean = empService.page(page, pageSize); //响应 return Result.success(pageBean); } } ``` ### 条件分页查询 思路分析: ![image-20221215180528415](https://pic.bitday.top/i/2025/03/19/u6mjck-2.png) ```java