# 苍穹外卖
## 踩坑总结
## 项目简介
### 整体介绍
本项目(苍穹外卖)是专门为餐饮企业(餐厅、饭店)定制的一款软件产品,包括 **系统管理后台** 和 **小程序端应用** 两部分。其中系统管理后台主要提供给餐饮企业内部员工使用,可以对餐厅的分类、菜品、套餐、订单、员工等进行管理维护,对餐厅的各类数据进行统计,同时也可进行来单语音播报功能。小程序端主要提供给消费者使用,可以在线浏览菜品、添加购物车、下单、支付、催单等。

**1). 管理端功能**
员工登录/退出 , 员工信息管理 , 分类管理 , 菜品管理 , 套餐管理 , 菜品口味管理 , 订单管理 ,数据统计,来单提醒。
**2). 用户端功能**
微信登录 , 收件人地址管理 , 用户历史订单查询 , 菜品规格查询 , 购物车功能 , 下单 , 支付、分类及菜品浏览。
### 技术选型

**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 地址
#### 后端环境搭建

工程的每个模块作用说明:
| **序号** | **名称** | **说明** |
| -------- | ------------ | ------------------------------------------------------------ |
| 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 将结果传回前端。
windows下更新nginx配置:先cd到nginx安装目录
```bash
nginx -t
nginx -s reload
```
#### APIFox
使用APIFox管理、测试接口、导出接口文档...
**优势:**
1.多格式支持
APIFox 能够导入包括 YApi 格式在内的多种接口文档,同时支持导出为 OpenAPI、Postman Collection、Markdown 和 HTML 等格式,使得接口文档在不同工具间无缝迁移和使用。
2.接口调试与 Mock
内置接口调试工具可以直接发送请求、查看返回结果,同时内置 Mock 服务功能,方便前后端联调和接口数据模拟,提升开发效率。
3.易用性与团队协作
界面直观、操作便捷,支持多人协作,通过分支管理和版本控制,团队成员可以并行开发并进行变更管理,确保接口维护有序。
**迭代分支功能:**
新建迭代分支,新增的待测试的接口在这里充分测试,没问题之后合并回主分支。
**导出接口文档:**
推荐导出数据格式为OpenAPI Spec,它是一种通用的 API 描述标准,Postman和APIFox都支持。
#### 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;
}
```

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) {
//..............
}
}
```
## 实战开发
### 分页查询
传统员工分页查询分析:

**采用分页插件PageHelper:**

**在执行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);
}
}
```
### 条件分页查询
思路分析:

```java