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/上来。
负载均衡
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后面的名称可自定义,但要上下保持一致。
负载策略:
轮询:默认
upstream webservers{
server 192.168.100.128:8080;
server 192.168.100.129:8080;
}
weight:添加权重
upstream webservers{
server 192.168.100.128:8080 weight=90;
server 192.168.100.129:8080 weight=10;
}
配置前端环境
nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/json;
sendfile on;
keepalive_timeout 65;
upstream webservers{
server 127.0.0.1:8080 weight=90 ;
#server 127.0.0.1:8088 weight=10 ;
}
server {
listen 80;
server_name localhost;
# 指定前端项目所在的位置
location / {
root html/sky;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# location /api {
# default_type application/json;
# #internal;
# keepalive_timeout 30s;
# keepalive_requests 1000;
# #支持keep-alive
# proxy_http_version 1.1;
# rewrite /api(/.*) $1 break;
# proxy_pass_request_headers on;
# #more_clear_input_headers Accept-Encoding;
# proxy_next_upstream error timeout;
# #proxy_pass http://127.0.0.1:8081;
# proxy_pass http://backend;
# }
# 反向代理,处理管理端发送的请求
location /api/ {
proxy_pass http://localhost:8080/admin/;
#proxy_pass http://webservers/admin/;
}
# 反向代理,处理用户端发送的请求
location /user/ {
proxy_pass http://webservers/user/;
}
# WebSocket
# location /ws/ {
# proxy_pass http://webservers/ws/;
# proxy_http_version 1.1;
# proxy_read_timeout 3600s;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "$connection_upgrade";
# }
}
# upstream backend {
# server 127.0.0.1:8080 weight=90 ;
# # server 127.0.0.1:8081 max_fails=5 fail_timeout=10s weight=1;
# # server 127.0.0.1:8082 max_fails=5 fail_timeout=10s weight=1;
# }
}
输入nginx -s reload重启服务,访问80端口有效
项目整体架构:
sky-take-out 父工程,通过pom统一管理依赖版本并聚合其他子模块
sky-common 存放常量,工具类,异常类等子模块
sky-pojo 存放实体,VO、DTO等
sky-server 子模块,存放配置文件、Controller、Service、mapper等配置文件
common:
constant 相关常量
context 上下文
enumeration 枚举操作
exception 自定义异常
json 处理json转换 基于jackson将Java对象转为json,或者将json转为Java对象
properties 存放springboot相关配置属性类,如阿里OSS服务,jwt令牌和微信支付接口等配置
result 返回结果封装,分页返回类和标准返回格式
Serializable接口是Java提供的一个标记接口,用于表示一个类的对象是否可被序列化,序列化意味着将对象转换为字节流,还能从字节重构对象,
- 一个类只有实现了 Serializable 接口,它的对象才是可序列化的。因此如果要序列化某些类的对象,这些类就必须实现 Serializable 接口。而实际上,Serializable 的源码是一个空接口,没有什么具体内容,它的目的只是简单的标识一个类的对象可以被序列化。
util 常见工具类
pojo
entity :存放与数据库表对应的实体
DTO :数据传输对象,在后端各层中进行传输
VO :视图,用于为前端展示数据所提供的对象
server
config:配置类
controller
interceptor:拦截器类
mapper:
service:
SkyApplication :启动类
前后端整体实现思路:
controller层:
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
1.接受登陆请求:
方法接收了一个EmplyeeLoginDTO类型的参数,这个DTO包括前端传来的用户名和密码,用于员工登陆,@RequestBody注解表示请求体以JSON格式传递
2.调用Service层进行验证
调用emplyeeService的login()方法,将登陆信息传递给服务层,服务层若验证成功会返回对应的Employee实体对象
3.生成JWT Token
使用JwtUtil.createJWT()方法根据员工id生成JWT令牌
令牌包括用户标识emplyee.getId()
过期时间以及签名算法
4.构建返回值对象给EmployeeLoginVO
创建EmployeeLoginVO对象,封装登录成功后需要返回给前端的信息
包括员工ID,用户名、姓名,token(用于后续请求的身份凭证)
service层:
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);
}
//密码比对
// TODO 后期需要进行md5加密,然后再进行比对
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;
}
1.获取用户名与密码
从传入的DTO中提取用户名和密码
2.查询数据库中的员工信息
emplyeeMapper.getByUsername(Username),通过MyBatis框架调用数据库接口
3.异常处理逻辑
异常类型:AccountNofFoundException(用户名不存在)
4.密码比对并检查账号状态
5.返回员工实体
所有校验通过后,返回查询到的员工对象,该对象将用于生成JWT Token,并构建成功响应
Mapper层
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
这部分代码定义了一个名为getByUsername
的方法,该方法接收一个String
类型的参数username
。@Select
注解表示这是一个用于执行 SQL 查询的语句,其注解内的 SQL 语句select * from employee where username = #{username}
意思是从名为employee
的表中查询所有列,条件是username
字段的值等于传入的username
参数值。方法返回类型为Employee
,即查询结果会封装成Employee
对象返回,整体功能是根据给定的用户名从employee
表中获取对应的员工信息。
完善登录功能:
Employee employee = employeeMapper.getByUsername(username);
log.info("password={}",password);
使用MD5加密的方式对明文密码进行加密得到:
e10adc3949ba59abbe56e057f20f883e
将sql里的admin密码从123456改为以上
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(employee.getPassword())) {
//密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
修改代码后登陆成功
Swagger
用于生成、描述、调用和可视化RESful风格的Web服务,作用:
使得前后端分离开发更加方便
接口文档自动生成,减轻后端开发人员编写借口的负担
可进行功能测试
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍!
目前,一般都使用knife4j框架。
步骤
1.导入pom坐标
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
2.在配置类中加入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;
}
3.设置静态资源映射
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/");
}
4.访问
在http://localhost:8080/doc.html页面进行访问
常见注解
@Api 类上,例如Controller,说明类
@ApiModel 用于类,例如entity,DTO,VO
@ApiModelProperty 用于属性,描述属性信息
@ApiOperation 用于方法上,说明方法用途、作用
使用上述注解,生成可读性更好的接口文档
@ApiModel(description = "员工登录时传递的数据模型")
public class EmployeeLoginDTO implements Serializable
@ApiModel(description = "员工登录返回的数据格式")
public class EmployeeLoginVO implements Serializable
@ApiOperation(value = "员工登录")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO)
总结
1. Nginx 反向代理的作用是什么?如何配置?
作用:
- 转发前端请求到后端服务器,隐藏真实地址,保障安全;
- 缓存数据提速,支持负载均衡分配多台服务器请求。
配置示例:
server {
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://localhost:8080/admin/; # 反向代理到后端地址
}
}
2. Nginx 负载均衡的策略有哪些?如何配置权重?
常见策略:
- 轮询(默认):请求依次分配到各服务器;
- weight 权重:按权重比例分配,适用于性能不同的服务器。
权重配置示例:
upstream webservers {
server 192.168.100.128:8080 weight=90; # 权重90%
server 192.168.100.129:8080 weight=10; # 权重10%
}
3. 苍穹外卖项目的整体架构如何设计?各模块的作用是什么?
架构设计:
- sky-take-out:父工程,管理依赖版本并聚合子模块;
- sky-common:存放常量、工具类、异常类等公共资源;
- sky-pojo:定义实体类(Entity)、数据传输对象(DTO)、视图对象(VO);
- sky-server:核心模块,包含配置、Controller、Service、Mapper 等业务逻辑。
4. 登录功能的后端实现流程是什么?如何保证密码安全?
流程:
- Controller 层:接收前端登录请求,解析用户名和密码;
- Service 层:根据用户名查询数据库,验证密码和账号状态(是否锁定);
- 生成 JWT 令牌:登录成功后,基于员工 ID 生成令牌并返回给前端。
密码安全:
使用 MD5 加密算法对明文密码加密
password = DigestUtils.md5DigestAsHex(password.getBytes()); // 加密后比对
5. 什么是 Knife4j?如何配置接口文档?
Knife4j:
是 Swagger 的增强解决方案,用于自动生成、可视化 RESTful 接口文档,简化前后端协作。
配置步骤:
- 导入依赖:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
- 配置 Docket Bean(如 WebMvcConfiguration):
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder().title("接口文档").build())
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.build();
}
- 访问地址:
http://localhost:8080/doc.html
6. JWT 令牌在登录功能中的作用是什么?如何生成?
作用:
- 作为用户身份凭证,避免每次请求都查询数据库;
- 实现无状态认证,减轻服务器负担。
// 基于员工ID生成JWT令牌
Map<String, Object> claims = new HashMap<>();
claims.put("empId", employee.getId());
String token = JwtUtil.createJWT(
secretKey, // 签名密钥
ttlMillis, // 过期时间
claims // 载荷数据
);