学成-部署总结

一、部署

1.DevOps

DevOps 是 Development(开发)+ Operations(运维) 的合成词,是一种文化理念 + 实践方法论 + 工具链的组合,核心目标是打破开发团队和运维团队之间的壁垒,让软件更快、更稳定地交付

核心理念

image-20260408205010518

常见工具实现

1
2
3
代码提交 → 构建 → 测试 → 打包 → 部署 → 监控
Git Maven JUnit Docker K8s Prometheus
GitLab npm pytest 镜像 Helm Grafana

2.手动部署

父工程聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
<modules>
<module>../xuecheng-plus-base</module>
<module>../xuecheng-plus-checkcode</module>
<module>../xuecheng-plus-gateway</module>
<module>../xuecheng-plus-auth</module>
<module>../xuecheng-plus-content</module>
<module>../xuecheng-plus-learning</module>
<module>../xuecheng-plus-media</module>
<module>../xuecheng-plus-orders</module>
<module>../xuecheng-plus-message-sdk</module>
<module>../xuecheng-plus-search</module>
<module>../xuecheng-plus-system</module>
</modules>
image-20260408205734123

配置spring-boot-maven-plugin插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

通 Maven 打出来的 jar 是不可执行的,只包含自己写的 class 文件,不含依赖

image-20260408205840685

部署到 Linux Docker

1
2
3
4
5
6
7
8
9
本地jar包
↓ 上传到Linux
编写 Dockerfile
↓ docker build
生成镜像 checkcode:1.0
↓ docker run
运行容器,暴露端口

外部访问 192.168.101.65:63075
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM java:8u20
# 基础镜像,包含JDK8,容器里才能跑java

MAINTAINER docker_maven docker_maven@email.com
# 镜像作者信息,纯注释性质

WORKDIR /ROOT
# 设置容器内工作目录,后续命令都在这里执行

ADD xuecheng-plus-checkcode-0.0.1-SNAPSHOT.jar xuecheng-plus-checkcode.jar
# 把宿主机的jar包复制进容器,顺便重命名(去掉版本号,方便)

CMD ["java", "-version"]
# 这行其实会被 ENTRYPOINT 覆盖,意义不大,可以删掉

ENTRYPOINT ["java", "-Dfile.encoding=utf-8","-jar", "xuecheng-plus-checkcode.jar"]
# 容器启动时执行的命令,真正启动Spring Boot应用

EXPOSE 63075
# 声明容器对外暴露的端口(文档性质,不加也能映射)
1
2
3
4
5
6
7
8
# 构建镜像(注意末尾有个点,表示当前目录为构建上下文)
docker build -t checkcode:1.0 .

# 创建并启动容器
docker run --name xuecheng-plus-checkcode \
-p 63075:63075 \ # 宿主机端口:容器端口
-idt \ # -i交互 -d后台 -t终端
checkcode:1.0

3.自动部署

整体

1
2
3
4
5
6
7
8
9
10
11
12
13
开发者 push 代码

Gogs (Git仓库) 触发 Webhook

Jenkins 收到通知,自动拉取代码

Maven 构建 + docker-maven-plugin 打镜像

镜像推送到 Docker 私服 (192.168.101.65:5000)

自动创建/启动容器

服务上线

1.docker-maven-plugin 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 镜像名:私服地址/项目名:版本号 -->
<imageName>192.168.101.65:5000/${project.artifactId}:${project.version}</imageName>

<!-- 相当于 Dockerfile 的 FROM java:8u20 -->
<baseImage>java:8u20</baseImage>

<!-- 连接远程Docker守护进程(2375是Docker远程API端口) -->
<dockerHost>http://192.168.101.65:2375</dockerHost>

<!-- 相当于 Dockerfile 的 ENTRYPOINT -->
<entryPoint>["java", "-Dfile.encoding=utf-8","-jar", "/root/${project.build.finalName}.jar"]</entryPoint>

<!-- 构建完自动push到私服 -->
<pushImage>true</pushImage>

<!-- 把jar包拷贝到容器的 /root 目录 -->
<resources>
<resource>
<targetPath>/root</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>

本质上就是用 pom.xml 的配置替代了手写 Dockerfile,插件会自动生成镜像并推送

2.Jenkins 流水线

image-20260408210629664

4.对比

image-20260408210702437

二、技术点

1.接口的定义

接口路径

1
2
好的例子: /api/v1/users (指向用户集合)或 /api/v1/users/123 (指向特定用户)
坏的例子: /api/getUser 或 /api/deleteUser

请求方法

1
2
3
4
5
6
使用不同的 HTTP 动词来表示对资源执行的不同操作:
GET:获取资源(例如:获取用户列表)
POST:创建新资源(例如:注册新用户)
PUT:更新资源的全部内容(例如:修改用户的全部个人信息)
PATCH:更新资源的部分内容(例如:仅修改用户密码)
DELETE:删除资源(例如:注销用户)

请求参数

1
2
3
4
5
明确调用者需要传递什么数据给你。参数通常分为三种类型:
路径参数 (Path Variables): 包含在 URL 中,用于定位特定资源。如 /users/{id} 中的 id。
查询参数 (Query Parameters): 附加在 URL 问号之后,多用于过滤、排序或分页。如 /users?role=admin&page=1。
请求体 (Request Body): 通常用于 POST/PUT 请求,携带较复杂的数据(通常为 JSON 格式)。
请求头 (Request Headers): 传递元数据或鉴权信息。如 Authorization: Bearer <token>。

响应内容

1
2
3
4
5
接口处理完成后,需要告诉调用者结果如何。
HTTP 状态码 (Status Code): * 200 OK (成功) / 201 Created (创建成功)
400 Bad Request (客户端参数错误) / 401 Unauthorized (未登录) / 404 Not Found (资源不存在)
500 Internal Server Error (服务器内部错误)
响应体 (Response Body): 返回给客户端的数据,通常是统一格式的 JSON。

持久层 mapper –> 服务层 service –> 响应层 controller

2.异常处理

全局异常处理器

在代码实现上,绝对不要在每一个 Controller 或业务方法里写满 try-catch。现代 Web 框架都提供了“全局异常处理”的机制

只需要在最外层定义一个拦截器,拦截所有未被捕获的异常,然后将其转换为上述的统一 JSON 格式

Java (Spring Boot): 使用 @RestControllerAdvice@ExceptionHandler 注解

自定义业务异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Getter
public class BusinessException extends RuntimeException {
private final int code;

public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
}

// 错误码枚举
public enum ErrorCode {
USER_NOT_FOUND(40401, "用户不存在"),
PARAM_ERROR(40001, "参数错误"),
TOKEN_EXPIRED(40101, "Token 已过期");

private final int code;
private final String message;
}

可预知异常使用自定义异常类

3.跨域问题

3.1、跨域

浏览器的**同源策略(Same-Origin Policy)**规定:协议 + 域名 + 端口三者必须完全一致,否则请求被拦截

1
2
3
4
5
http://a.com:8080  →  http://a.com:8081     ❌ 端口不同
http://a.com → https://a.com ❌ 协议不同
http://a.com → http://api.a.com ❌ 子域名不同
http://a.com → http://b.com ❌ 域名不同
http://a.com:8080 → http://a.com:8080 ✅ 同源

⚠️ 跨域是浏览器行为,请求实际已到达服务器,是响应被浏览器拦截


3.2、CORS 请求类型

1
2
3
4
5
6
7
8
9
10
11
12
                  ┌─────────────────────────────────┐
│ 跨域请求 │
└────────────┬────────────────────┘

┌──────────────────┴──────────────────┐
▼ ▼
简单请求 预检请求(Preflight)
(GET/POST + 普通Header) (PUT/DELETE/自定义Header/JSON Body)
│ │
│ 先发 OPTIONS 请求
▼ ▼
直接发送请求 服务器返回允许后再发真实请求

简单请求条件: Method 为 GET/POST/HEAD,且 Content-Type 为 text/plain / multipart/form-data / application/x-www-form-urlencoded


3.3、解决方案

后端设置 CORS Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 方式一:全局配置(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://your-frontend.com") // 生产环境指定域名
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true) // 允许携带 Cookie
.maxAge(3600); // 预检缓存时间(秒)
}
}

// 方式二:Filter 方式(优先级更高,可处理 Spring Security 场景)
@Component
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;

response.setHeader("Access-Control-Allow-Origin", "https://your-frontend.com");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization,Content-Type");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Max-Age", "3600");

// 预检请求直接返回 200
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
chain.doFilter(req, res);
}
}

Nginx 反向代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
listen 80;
server_name your-frontend.com;

# 前端静态资源
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}

# 代理 API 请求,消除跨域
location /api/ {
proxy_pass http://backend-server:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# 由 Nginx 统一处理 CORS(二选一,避免重复设置)
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods 'GET,POST,PUT,DELETE,OPTIONS' always;
add_header Access-Control-Allow-Headers 'Authorization,Content-Type' always;
add_header Access-Control-Allow-Credentials 'true' always;

if ($request_method = 'OPTIONS') {
return 204;
}
}
}

3.4、常见问题

现象 原因 解决
No 'Access-Control-Allow-Origin' header 后端未设置 CORS 添加响应头
allowCredentials=true* 通配不生效 携带凭证时不能用 * 改为指定具体域名
预检请求返回 401/403 Spring Security 拦截了 OPTIONS 放行 OPTIONS 请求
CORS 已配置但仍跨域 响应头被重复设置冲突 检查是否 Nginx 和后端都设置了
Cookie 无法携带 前端未设 withCredentials axios.defaults.withCredentials = true

4.微服务之间调用

调用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────┐
│ 微服务调用方式 │
└──────────────────┬──────────────────────────────────┘

┌───────────┴───────────┐
▼ ▼
同步调用 异步调用
(HTTP/RPC) (消息队列)
│ │
┌────┴────┐ ┌──────┴──────┐
│ Feign │ │ RabbitMQ │
│ RestTpl │ │ RocketMQ │
│ gRPC │ │ Kafka │
└─────────┘ └─────────────┘

Feign 调用(主流方案)

1.引入依赖

1
spring-cloud-starter-openfeign

2.启动类开启 Feign

1
2
3
@SpringBootApplication
@EnableFeignClients
public class OrderApplication { ... }

3.定义 Feign 接口(order-service 调用 user-service)

1
2
3
4
5
6
7
8
9
10
11
12
13
14

@FeignClient(
name = "user-service", // 注册中心的服务名
path = "/api/user",
fallback = UserFeignFallback.class // 降级实现类
)
public interface UserFeignClient {

@GetMapping("/{id}")
ApiResponse<UserDTO> getUserById(@PathVariable Long id);

@PostMapping("/batch")
ApiResponse<List<UserDTO>> batchGetUsers(@RequestBody List<Long> ids);
}

三、业务相关

1.内容管理

课程发布分布式任务调度

image-20260330213932791

freemarker 实现页面静态化

2.媒资管理

文件分块上传

image (1)

文件访问

通过 nginx 访问 minio 服务

3.认证授权

授权流程

1280X1280

网关认证,微服务授权,jwt存在客户端

OUTH2协议

be9bbb62-1f3f-4dc2-9c20-c161e622fa3f

验证码服务

4f934ec1-6a02-4fde-a36b-d72abe95c80d

spring security 统一认证入口

image-20260330215301186

4.选课学习

image (2)

订单支付

image (3)