+20
-11
@@ -11,13 +11,13 @@
|
|||||||
| 项 | 结果 |
|
| 项 | 结果 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 触发方式 | push 到 master(另支持手动 workflow_dispatch 重跑) |
|
| 触发方式 | push 到 master(另支持手动 workflow_dispatch 重跑) |
|
||||||
| 后端 freedom-admin | CI 构建 `freedom-admin:latest` → compose 部署 backend 容器(内网 8800) |
|
| 后端 freedom-admin | CI 构建 `freedom-admin:latest` → compose 部署 backend 容器(宿主映射 `8800:8800`,API 直连) |
|
||||||
| 前端 freedom-web | CI 构建 `freedom-web:latest` → compose 部署 web 容器(宿主 8880 → 容器 80) |
|
| 前端 freedom-web | CI 构建 `freedom-web:latest` → compose 部署 web 容器(宿主映射 `8880:80`) |
|
||||||
| 统一入口 | `freedom-web`(nginx):`/api/*` 反代到 `backend:8800`(去前缀),其余走前端静态文件 |
|
| 统一入口 | `freedom-web`(nginx):`/api/*` 反代到 `backend:8800`(去前缀),其余走前端静态文件 |
|
||||||
| 公网访问 | frp 隧道:`http://124.221.214.237:8880` → `192.168.1.158:8880` |
|
| 公网访问 | frp 隧道:页面 `http://124.221.214.237:8880` ↔ `192.168.1.158:8880`;API 直连 `http://124.221.214.237:8800` ↔ `192.168.1.158:8800` |
|
||||||
|
|
||||||
**验证通过的完整链路**:
|
**验证通过的完整链路**:
|
||||||
`公网 :8880 → frp → freedom-web(nginx) → freedom-admin(SpringBoot) → MySQL`
|
`公网 :8880(页面) → frp → freedom-web(nginx) → freedom-admin(SpringBoot) → MySQL`;API 亦可直连公网 `:8800`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -164,7 +164,6 @@ job 容器的 `/workspace/<owner>/<repo>` 是**空目录**(实测)。必须
|
|||||||
- **首次构建慢**:基础镜像(maven/openjdk/node/nginx)从 docker.io 拉取慢(~10-20KB/s)。宿主 `/etc/docker/daemon.json` 已配 11 个 registry-mirrors,小镜像快、大镜像慢;首次忍一忍,之后走本地缓存。
|
- **首次构建慢**:基础镜像(maven/openjdk/node/nginx)从 docker.io 拉取慢(~10-20KB/s)。宿主 `/etc/docker/daemon.json` 已配 11 个 registry-mirrors,小镜像快、大镜像慢;首次忍一忍,之后走本地缓存。
|
||||||
- **Maven/npm 源**:Dockerfile 内分别固定阿里云 Maven 镜像与 npmmirror,避免依赖下载卡死。
|
- **Maven/npm 源**:Dockerfile 内分别固定阿里云 Maven 镜像与 npmmirror,避免依赖下载卡死。
|
||||||
- **compose 里或容器环境有历史遗留**:出现 `Found orphan containers ([wechatpadpro])` 警告属无害噪音。
|
- **compose 里或容器环境有历史遗留**:出现 `Found orphan containers ([wechatpadpro])` 警告属无害噪音。
|
||||||
- **frpc 配置双重来源**:直接手改 `/opt/1panel/apps/frpc/frpc/data/frpc.toml` 后,若再经 frpc 管理界面(:7400)保存配置,界面会按自身存储重新生成整个文件,**手改内容会被覆盖**。改完端口后务必实测公网入口,并以界面最终生成的配置为准。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -231,7 +230,8 @@ services:
|
|||||||
image: freedom-admin:${IMAGE_TAG:-latest}
|
image: freedom-admin:${IMAGE_TAG:-latest}
|
||||||
container_name: freedom-admin
|
container_name: freedom-admin
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
expose: ["8800"] # 仅内网,供 nginx 反代
|
ports:
|
||||||
|
- "8800:8800" # 宿主 8800 → 容器 8800(API 直连入口;nginx 反代仍走内网同端口)
|
||||||
environment:
|
environment:
|
||||||
DB_URL: ${DB_URL}
|
DB_URL: ${DB_URL}
|
||||||
DB_USERNAME: ${DB_USERNAME}
|
DB_USERNAME: ${DB_USERNAME}
|
||||||
@@ -261,10 +261,18 @@ name = "freedom"
|
|||||||
type = "tcp"
|
type = "tcp"
|
||||||
localIP = "192.168.1.158"
|
localIP = "192.168.1.158"
|
||||||
localPort = 8880 # freedom-web 宿主端口
|
localPort = 8880 # freedom-web 宿主端口
|
||||||
remotePort = 8880 # 公网端口
|
remotePort = 8880 # 公网端口(前端页面)
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "freedom-api"
|
||||||
|
type = "tcp"
|
||||||
|
localIP = "192.168.1.158"
|
||||||
|
localPort = 8800 # freedom-admin 宿主端口
|
||||||
|
remotePort = 8800 # 公网端口(API 直连)
|
||||||
```
|
```
|
||||||
|
|
||||||
改后 `docker restart 1Panel-frpc-hn0g` 生效。
|
改后 `docker restart 1Panel-frpc-hn0g` 生效。
|
||||||
|
> 注:早期 `remotePort=8888` 版本已弃用,统一改为 `8880`(页面)/ `8800`(API)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -286,10 +294,11 @@ curl -s -X POST -H "Authorization: token <TOKEN>" -H "Content-Type: application/
|
|||||||
"http://127.0.0.1:3000/api/v1/repos/yiciheng/freedom-admin/actions/workflows/deploy.yml/dispatches"
|
"http://127.0.0.1:3000/api/v1/repos/yiciheng/freedom-admin/actions/workflows/deploy.yml/dispatches"
|
||||||
|
|
||||||
# 端到端验证
|
# 端到端验证
|
||||||
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.158:8880/ # 本机前端
|
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.1.158:8880/login # 本机前端
|
||||||
curl -s -o /dev/null -w "%{http_code}\n" http://124.221.214.237:8880/ # 公网前端
|
curl -s -o /dev/null -w "%{http_code}\n" http://124.221.214.237:8880/login # 公网前端
|
||||||
curl -s -o /dev/null -w "%{http_code}\n" -X POST -d '{"username":"t","password":"t"}' \
|
curl -s -o /dev/null -w "%{http_code}\n" http://124.221.214.237:8800/login # 公网 API 直连(后端)
|
||||||
http://124.221.214.237:8880/api/login # 公网 API(经 nginx 去前缀转后端)
|
curl -s -o /dev/null -w "%{http_code}\n" -X POST -d 'username=t&password=t' \
|
||||||
|
http://124.221.214.237:8880/api/login # 公网经 nginx 反代到达后端
|
||||||
docker logs --tail 20 freedom-admin # 确认请求与 SQL 到达后端
|
docker logs --tail 20 freedom-admin # 确认请求与 SQL 到达后端
|
||||||
|
|
||||||
# 判断 runner 配置是否真正加载(最关键的一步排查)
|
# 判断 runner 配置是否真正加载(最关键的一步排查)
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author zhangjiaxun
|
* @author zhangjiaxun
|
||||||
* @date 2021/1/5 17:40
|
* @date 2021/1/5 17:40
|
||||||
@@ -100,23 +102,41 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 跨越配置
|
* 跨域配置
|
||||||
|
*
|
||||||
|
* 生产环境页面与后端同源(web 容器 nginx 将 /api/* 反代到 backend),但浏览器对
|
||||||
|
* 同源 POST 请求同样会携带 Origin 头,而 Spring 5.x 的 CORS 过滤器只要检测到
|
||||||
|
* Origin 头即按跨域请求处理,来源不在白名单内会直接返回 403。
|
||||||
|
* 这里动态放行:
|
||||||
|
* - 同源请求(Origin 的 host 与请求 Host 一致):回显该 Origin;
|
||||||
|
* - 跨域请求:仅放行本地开发来源 http://localhost:8080(npm run dev);
|
||||||
|
* - 无 Origin 的非浏览器请求:不做 CORS 限制。
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public CorsConfigurationSource corsConfigurationSource() {
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
return httpServletRequest -> {
|
return request -> {
|
||||||
|
String origin = request.getHeader("Origin");
|
||||||
|
if (origin == null) {
|
||||||
|
return null; // 非浏览器请求,不应用 CORS 处理
|
||||||
|
}
|
||||||
CorsConfiguration cfg = new CorsConfiguration();
|
CorsConfiguration cfg = new CorsConfiguration();
|
||||||
|
String reqHost = request.getHeader("Host");
|
||||||
|
String originHost = origin.replaceFirst("^https?://", "").split("[:/]")[0];
|
||||||
|
boolean sameOrigin = originHost != null && originHost.equalsIgnoreCase(
|
||||||
|
reqHost == null ? "" : reqHost.split(":")[0]);
|
||||||
|
cfg.setAllowCredentials(true);
|
||||||
|
if (sameOrigin) {
|
||||||
|
// 同源回显,确保经任意入口(如 8880 反代)访问都放行
|
||||||
|
cfg.setAllowedOriginPatterns(Collections.singletonList(origin));
|
||||||
|
} else if ("localhost".equalsIgnoreCase(originHost) || "127.0.0.1".equals(originHost)) {
|
||||||
|
cfg.setAllowedOriginPatterns(Collections.singletonList("http://localhost:8080"));
|
||||||
|
} else {
|
||||||
|
cfg.setAllowedOriginPatterns(Collections.emptyList());
|
||||||
|
}
|
||||||
|
// 允许任何请求头/方法
|
||||||
cfg.addAllowedHeader("*");
|
cfg.addAllowedHeader("*");
|
||||||
cfg.addAllowedMethod("*");
|
cfg.addAllowedMethod("*");
|
||||||
// 设置允许跨域请求的域名
|
|
||||||
cfg.addAllowedOrigin("http://localhost:8080");
|
|
||||||
// 允许任何请求
|
|
||||||
cfg.addAllowedHeader("*");
|
|
||||||
cfg.addExposedHeader("token");
|
cfg.addExposedHeader("token");
|
||||||
// 是否允许证书
|
|
||||||
cfg.setAllowCredentials(true);
|
|
||||||
//
|
|
||||||
cfg.checkOrigin("http://localhost:8080");
|
|
||||||
return cfg;
|
return cfg;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user