目录
- 一、在本地部署并启动Nginx服务
-
- 1. 解压Nginx压缩包
- 2. 启动Nginx服务
- 3. 验证Nginx是否启动成功:
- 二、导入接口文档
-
- 1. 黑马程序员提供的YApi平台
- 2. YApi Pro平台
- 3. 推荐工具:Apifox
- 三、Swagger
-
- 1. 常用注解
-
- 1.1 @Api与@ApiModel
- 1.2 @ApiModelProperty与@ApiOperation
- 四、DTO的使用原因
-
- 1. 实体类 Employee
- 2. DTO EmployeeDTO
- 3. 使用 DTO 的场景
-
- 3.1 查询员工信息
- 3.2 更新员工信息
- 3.3 新增员工
- 4. DTO、VO和实体类的区别
- 五、为什么使用 XML 注解而不是 MyBatis 注解
-
- 1. 使用 XML 注解的原因
-
- 1.1 动态 SQL 支持
- 1.2 SQL 与代码分离
- 1.3 复用性
- 1.4 工具支持
- 2. MyBatis 注解的局限性
-
- 2.1 动态 SQL 支持有限
- 2.2 可读性差
- 2.3 维护困难
- 六、Spring Boot 的请求映射规则
-
- 1. 类级别路径
- 2. 方法级别路径
-
- 2.1 分页查询
- 2.2 根据 ID 查询菜品
- 2.3 修改菜品
- 2.4 新增菜品
- 2.5 批量删除菜品
- 3. 如何区分不同的功能
- 4. 示例请求
-
- 4.1 新增菜品
- 4.2 修改菜品
- 4.3 批量删除菜品
- 4.4 分页查询菜品
- 4.5 根据 ID 查询菜品
- 七、接口设计中的是否必须原则
-
- 1. 请求参数说明
- 2. Java代码片段分析
- 3. XML映射文件分析
- 八、阿里云OSS
- 九、JWT令牌
视频链接:黑马程序员Java项目实战《苍穹外卖》,最适合新手的SpringBoot+SSM的企业级Java项目实战
网盘资料:苍穹外卖讲义&前后端源码
一、在本地部署并启动Nginx服务
在开发过程中,我们经常需要使用Nginx来部署前端项目或作为反向代理服务器。
1. 解压Nginx压缩包
首先,确保你已经从黑马程序员资料中下载了Nginx的压缩包。接下来,按照以下步骤解压:
选择解压路径:
- 将Nginx压缩包解压到一个全英文路径中。例如:
D:\nginx
- 注意:路径中不要包含中文或特殊字符,否则可能会导致Nginx无法正常运行。
2. 启动Nginx服务
解压完成后,按照以下步骤启动Nginx:
进入Nginx目录:
- 打开解压后的Nginx文件夹,找到
nginx.exe
文件。路径通常为:C:\nginx\nginx.exe
启动Nginx:
- 双击
nginx.exe
文件,启动Nginx服务。 - 启动后,Nginx会在后台运行,你可以在任务管理器中看到
nginx.exe
进程。
3. 验证Nginx是否启动成功:
- 打开浏览器,访问以下地址(其中80是默认端口可省略不写):
http://localhost:80
- 如果看到此页面,说明Nginx已成功启动。
- 注意:Nginx默认不会随系统自动启动,因此每次重启电脑后,都需要手动启动Nginx
二、导入接口文档
在开发过程中,接口管理平台是团队协作和项目管理的重要工具。以下是几个常用平台的对比:
1. 黑马程序员提供的YApi平台
- 地址:http://yapi.smart-xwork.cn/
- 状态:已弃用
- 功能:适合用于接口管理和文档生成。
2. YApi Pro平台
- 地址:https://yapi.pro/
- 问题:需要挂梯子才能访问,且极易卡顿,使用体验不佳。
3. 推荐工具:Apifox
- 地址:https://apifox.com/
- 优势:
- 无需梯子即可访问。
- 性能流畅,支持接口文档、Mock数据、自动化测试等功能。
- 支持导入YApi数据格式的接口文档,方便无缝迁移现有项目。
三、Swagger
Swagger 是一种用于设计、构建、记录和使用 RESTful Web 服务的开源框架。它提供了一套工具,帮助开发者设计、构建、文档化和测试 API。
启动服务:访问 http://localhost:8080/doc.html
1. 常用注解
通过注解可以控制生成的接口文档,使接口文档拥有更好的可读性,常用注解如下:
注解 | 说明 |
---|---|
@Api | 用在类上,例如Controller,表示对类的说明 |
@ApiModel | 用在类上,例如entity、DTO、VO |
@ApiModelProperty | 用在属性上,描述属性信息 |
@ApiOperation | 用在方法上,例如Controller的方法,说明方法的用途、作用 |
1.1 @Api与@ApiModel
1.2 @ApiModelProperty与@ApiOperation
四、DTO的使用原因
在项目中,Employee
是实体类(Entity),用于表示数据库中的员工记录,而 EmployeeDTO
是数据传输对象(DTO),用于在不同层之间传递数据。以下是使用 DTO 的主要原因和优势:
1. 实体类 Employee
package com.sky.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id; // 员工ID
private String username; // 用户名
private String name; // 姓名
private String password; // 密码(敏感字段)
private String phone; // 手机号
private String sex; // 性别
private String idNumber; // 身份证号
private Integer status; // 状态
private LocalDateTime createTime; // 创建时间(内部字段)
private LocalDateTime updateTime; // 更新时间(内部字段)
private Long createUser; // 创建人(内部字段)
private Long updateUser; // 更新人(内部字段)
}
2. DTO EmployeeDTO
package com.sky.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmployeeDTO implements Serializable {
private Long id; // 员工ID
private String username; // 用户名
private String name; // 姓名
private String phone; // 手机号
private String sex; // 性别
private String idNumber; // 身份证号
}
3. 使用 DTO 的场景
3.1 查询员工信息
- 前端只需要员工的基本信息(如
id
、username
、name
、phone
、sex
、idNumber
)。 - 后端返回
EmployeeDTO
,过滤掉敏感字段(如password
)和内部字段(如createTime
)。
3.2 更新员工信息
- 前端传递
EmployeeDTO
作为请求体,后端根据 DTO 更新员工信息。 - 避免前端传递不必要的字段(如
password
、createTime
)。
3.3 新增员工
- 前端传递
EmployeeDTO
作为请求体,后端将 DTO 转换为实体类并保存到数据库。 - 避免前端传递内部字段(如
createTime
、updateTime
)。
4. DTO、VO和实体类的区别
特性 | DTO | VO | Entity |
---|---|---|---|
目的 | 数据传输 | 数据展示或封装值 | 表示数据库中的数据结构 |
使用场景 | 跨层数据传输(如Controller-Service) | 展示层或领域模型 | 数据库操作、业务逻辑 |
可变性 | 可变(通常有setter) | 通常不可变(无setter) | 可变(用于持久化和业务逻辑) |
字段 | 与传输需求相关 | 与展示或业务逻辑相关 | 与数据库表字段严格对应 |
行为 | 通常无行为 | 可能包含简单行为(如格式化) | 包含业务逻辑和验证规则 |
示例 | UserDTO | UserVO | UserEntity |
五、为什么使用 XML 注解而不是 MyBatis 注解
1. 使用 XML 注解的原因
1.1 动态 SQL 支持
- XML 提供了强大的动态 SQL 支持,例如
<if>
、<foreach>
、<choose>
等标签。 - 在复杂的查询场景中,动态 SQL 可以更灵活地构建 SQL 语句。
1.2 SQL 与代码分离
- 将 SQL 语句写在 XML 文件中,可以使 SQL 与 Java 代码分离,便于维护和管理。
- 对于复杂的 SQL 语句,XML 文件的可读性更高。
1.3 复用性
- XML 文件中的 SQL 语句可以在多个 Mapper 接口中复用。
- 例如,可以在不同的 Mapper 接口中引用同一个 SQL 片段。
1.4 工具支持
- MyBatis 提供了丰富的工具支持 XML 文件的编写和调试。
- 例如,MyBatis Generator 可以自动生成 XML 映射文件。
2. MyBatis 注解的局限性
2.1 动态 SQL 支持有限
- MyBatis 注解对动态 SQL 的支持较弱,复杂的 SQL 语句难以用注解实现。
- 例如,
@Select
注解无法直接实现<foreach>
这样的动态 SQL。
<select id="getSetmealIdsByDishIds" resultType="java.lang.Long">
select setmeal_id from setmeal_dish where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</select>
2.2 可读性差
- 复杂的 SQL 语句写在注解中会导致代码冗长,可读性差。
- 例如,一个包含多个条件的查询语句会显得非常混乱。
2.3 维护困难
- SQL 语句与 Java 代码混合在一起,维护起来不如 XML 文件方便。
- 修改 SQL 语句时需要重新编译 Java 代码。
你提到的代码中有两个 @PutMapping
注解没有指定路径,这意味着它们默认映射到类级别的路径 /admin/dish
。以下是对这个问题的详细解释:
六、Spring Boot 的请求映射规则
在 Spring Boot 中,请求的映射是通过 类级别的 @RequestMapping
和 方法级别的 @PutMapping
、@GetMapping
等注解 共同决定的。
- 类级别的
@RequestMapping
:- 定义了该类中所有方法的公共路径前缀。
- 例如,
@RequestMapping("/admin/dish")
表示该类中的所有方法都映射到/admin/dish
路径下。 - 管理端发出的请求,统一使用
/admin
作为前缀。 - 用户端发出的请求,统一使用
/user
作为前缀。
- 方法级别的
@PutMapping
、@GetMapping
等:- 定义了具体的 HTTP 方法和路径。
- 如果方法级别的注解没有指定路径,则默认使用类级别的路径。
1. 类级别路径
@RestController
@RequestMapping("/admin/dish")
public class DishController {
// 方法定义...
}
- 所有方法的公共路径前缀是
/admin/dish
。
2. 方法级别路径
2.1 分页查询
@GetMapping("/page")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
// 方法实现...
}
- 完整路径是
/admin/dish/page
。
2.2 根据 ID 查询菜品
@GetMapping("/{id}")
public Result<DishVO> getById(@PathVariable Long id) {
// 方法实现...
}
- 完整路径是
/admin/dish/{id}
。
2.3 修改菜品
@PutMapping
public Result update(@RequestBody DishDTO dishDTO) {
// 方法实现...
}
- 由于
@PutMapping
没有指定路径,默认使用类级别的路径/admin/dish
。
2.4 新增菜品
@PostMapping
public Result save(@RequestBody DishDTO dishDTO) {
// 方法实现...
}
- 由于
@PostMapping
没有指定路径,默认使用类级别的路径/admin/dish
。
2.5 批量删除菜品
@DeleteMapping
public Result delete(@RequestParam List<Long> ids) {
// 方法实现...
}
- 由于
@DeleteMapping
没有指定路径,默认使用类级别的路径/admin/dish
。
3. 如何区分不同的功能
Spring Boot 通过 HTTP 方法 来区分不同的功能。例如:
HTTP 方法 | 路径 | 功能 |
---|---|---|
POST |
/admin/dish |
新增菜品 |
PUT |
/admin/dish |
修改菜品 |
DELETE |
/admin/dish |
批量删除菜品 |
GET |
/admin/dish/page |
分页查询菜品 |
GET |
/admin/dish/{id} |
根据 ID 查询菜品 |
4. 示例请求
4.1 新增菜品
- HTTP 方法:
POST
- URL:
/admin/dish
- 请求体:
{ "name": "宫保鸡丁", "price": 38.0, "flavors": [ { "name": "微辣", "value": "少辣" } ] }
4.2 修改菜品
- HTTP 方法:
PUT
- URL:
/admin/dish
- 请求体:
{ "id": 1, "name": "宫保鸡丁", "price": 40.0, "flavors": [ { "name": "微辣", "value": "少辣" } ] }
4.3 批量删除菜品
- HTTP 方法:
DELETE
- URL:
/admin/dish?ids=1,2,3
- 请求参数:
ids=1,2,3
4.4 分页查询菜品
- HTTP 方法:
GET
- URL:
/admin/dish/page?page=1&pageSize=10
- 请求参数:
page=1&pageSize=10
4.5 根据 ID 查询菜品
- HTTP 方法:
GET
- URL:
/admin/dish/1
- 路径参数:
id=1
七、接口设计中的是否必须原则
参数的必需与非必需性是通过不同的方式来体现的,以下是具体案例
1. 请求参数说明
从接口文档中可以看到,请求参数包括以下几项:
参数名 | 类型 | 说明 | 必需性 | 示例值 |
---|---|---|---|---|
categoryId |
string |
分类id | 可选 | 101 |
name |
string |
菜品名称 | 可选 | 官保鸡丁 |
page |
string |
页码 | 必需 | 1 |
pageSize |
string |
每页记录数 | 必需 | 10 |
status |
string |
分类状态 | 可选 | 1 |
必需参数:
page
和pageSize
是分页查询的必需参数,用于指定查询的页码和每页的记录数。
可选参数:
categoryId
、name
和status
是可选参数,用于过滤查询结果。
2. Java代码片段分析
在此 Java 代码中,DishPageQueryDTO
是一个数据传输对象(DTO),用于封装分页查询的参数。以下是代码的详细分析:
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
// 1. 使用 PageHelper 进行分页
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
// 2. 调用 Mapper 进行查询
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
// 3. 返回分页结果
return new PageResult(page.getTotal(), page.getResult());
}
- 必需参数:
dishPageQueryDTO.getPage()
和dishPageQueryDTO.getPageSize()
是分页查询的必需参数。- 如果这两个参数为空或未提供,分页功能将无法正常工作。
3. XML映射文件分析
在 SQL 代码中,动态 SQL 语句根据传入的参数生成查询条件。以下是代码的详细分析:
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.* , c.name as categoryName
from dish d
left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
- 可选参数:
name
、categoryId
和status
是可选参数,通过<if>
标签动态生成查询条件。- 如果某个参数为
null
,则对应的条件不会添加到 SQL 查询中。
通过这种设计,分页查询接口既满足了基本的查询需求,又提供了灵活的过滤选项,适用于不同的业务场景。