Compare commits

..

69 Commits

Author SHA1 Message Date
Tim
55b680ef83 Update CONTRIBUTING.md 2025-10-04 02:02:43 +08:00
Tim
024e52b763 docs: update docker compose dev instructions 2025-10-04 02:01:32 +08:00
tim
536979501e fix: 修改为资源图片 2025-10-04 01:53:19 +08:00
tim
85a67a6215 fix: 本站自部署方法 2025-10-04 01:44:56 +08:00
tim
57a9a98da6 fix: 修改deploy地址 2025-10-03 16:52:00 +08:00
tim
e8976a98d4 fix: 新增nginx配置,修改deploy地址 2025-10-03 16:43:38 +08:00
tim
57e6bcaa0c Revert "feat: add admin point grants and history UI"
This reverts commit adfc05b9b2.
2025-10-03 00:58:24 +08:00
tim
c95b2ebdc2 fix: 修改staging部署 2025-10-03 00:48:00 +08:00
tim
83cf7439c9 fix: 删除dashboard 2025-10-02 22:35:19 +08:00
tim
994f4028fc fix: 取消opensearch 2025-10-02 22:29:35 +08:00
tim
2362458024 fix: volumes 修改 2025-10-02 22:25:15 +08:00
tim
03c92d4861 Merge branch 'main' of github.com:nagisa77/OpenIsle 2025-10-02 22:00:20 +08:00
tim
8df566a9c9 fix: 修改为main 2025-10-02 22:00:08 +08:00
Tim
870d1e2940 Merge pull request #1042 from nagisa77/codex/add-iframe-support-for-markdown-rendering
Allow iframe embeds in markdown sanitizer
2025-10-02 21:43:04 +08:00
Tim
0033374481 Allow iframe embeds in markdown sanitizer 2025-10-02 21:42:52 +08:00
tim
8f36422609 fix: 解决传参 2025-10-02 15:28:25 +08:00
tim
b98871bed9 fix: mysql 时区 2025-10-01 16:40:23 +08:00
tim
2cb8c12f65 fix: 修改main 2025-10-01 16:33:14 +08:00
Tim
87a256ba0c Merge pull request #1037 from nagisa77/feature/docker
所有业务适配Docker
2025-10-01 16:19:10 +08:00
tim
737157e557 fix: add timezone 2025-10-01 16:08:53 +08:00
tim
6f9570dc95 fix: 时区限制 2025-10-01 15:38:12 +08:00
tim
12bc405856 fix: 时区限制 2025-10-01 15:36:18 +08:00
tim
a2b0cd1a47 fix: 新增deploy 2025-10-01 11:36:55 +08:00
tim
25a7f1e138 fix: add deploy 2025-10-01 11:35:02 +08:00
tim
a6dd2bfbc2 Revert "fix: 修改文件名"
This reverts commit a0ea63700f.
2025-09-30 21:40:14 +08:00
tim
a0ea63700f fix: 修改文件名 2025-09-30 21:34:30 +08:00
tim
b49e20d010 fix: 添加环境名、变量名 2025-09-30 21:33:14 +08:00
tim
e44443a605 Merge remote-tracking branch 'origin/main' into feature/docker 2025-09-30 20:12:43 +08:00
Tim
0a3bfb9451 Merge pull request #1041 from nagisa77/codex/add-points-module-and-history-tracking
feat: add admin point grants and history UI
2025-09-30 20:12:31 +08:00
Tim
adfc05b9b2 feat: add admin point grants and history UI 2025-09-30 20:11:45 +08:00
tim
18a6953ff7 fix: 解决opensearch问题 2025-09-30 15:15:11 +08:00
tim
181ac7bc8f fix: 容器port修改 2025-09-30 15:09:59 +08:00
tim
9dc9ca9bd8 Revert "fix: 端口适配"
This reverts commit 180c45bf2d.
2025-09-30 15:02:40 +08:00
tim
2457efd11d Revert "fix: healthy check 修改"
This reverts commit b62b9c691f.
2025-09-30 15:02:36 +08:00
tim
b62b9c691f fix: healthy check 修改 2025-09-30 14:54:57 +08:00
tim
180c45bf2d fix: 端口适配 2025-09-30 14:52:27 +08:00
tim
263f2deeb1 fix: 修改yaml 2025-09-30 14:32:30 +08:00
tim
22b813e40b fix: 修改yaml 2025-09-30 14:16:10 +08:00
tim
d00dbbbd03 fix: 修改前端生产构建方案 2025-09-30 13:51:43 +08:00
Tim
3b92bdaf2a Merge pull request #1038 from smallclover/main
修改按钮样式
2025-09-30 10:46:07 +08:00
tim
7ce5de7f7c fix: 自部署基本完善 2025-09-30 10:45:31 +08:00
tim
28618c7452 fix: springboot healthy检测完成 2025-09-30 10:22:39 +08:00
tim
f8a2ee6ee9 fix: use server port 2025-09-30 01:45:47 +08:00
tim
ca26b931da fix: use server port 2025-09-30 01:19:13 +08:00
tim
24fe90cfc6 fix: change port 2025-09-30 00:47:18 +08:00
tim
5971700e8a fix: 新增依赖 2025-09-30 00:33:05 +08:00
smallclover
f872a32410 修改按钮样式
1. 文字变为白色
2. 按钮样式和其他按钮统一
2025-09-29 21:47:12 +09:00
Tim
fffd335ebb fix: 两个springboot新增探活机制 2025-09-29 19:54:37 +08:00
Tim
287d52df10 feat: healthy.检测 2025-09-29 19:47:55 +08:00
Tim
73790d1992 feat: healthy.检测 2025-09-29 19:42:54 +08:00
Tim
3d5cee6e68 feat: mysql 乱码处理 2025-09-29 19:41:04 +08:00
Tim
2f509cc2d8 feat: mysql 自定义初始化 2025-09-29 19:27:24 +08:00
Tim
35c503eb6c feat: mysql 自定义初始化 2025-09-29 19:26:02 +08:00
Tim
0cf8113691 Revert "feat: 移动文件位置"
This reverts commit b2a29913aa.
2025-09-29 19:14:48 +08:00
Tim
b2a29913aa feat: 移动文件位置 2025-09-29 19:11:18 +08:00
Tim
2b6d7c5ab9 fix: 新增多种url供开发者选择 2025-09-29 18:12:55 +08:00
Tim
e9878487e8 fix: 容器内流量转发 2025-09-29 18:08:35 +08:00
Tim
201af061e4 fix: 简单修改 2025-09-29 17:55:19 +08:00
Tim
4080f60f60 fix: rabbitmq 初始化 2025-09-29 16:46:25 +08:00
Tim
06d76438e8 fix: 前端初步调通 2025-09-29 16:04:14 +08:00
Tim
bb955c98ba fix: 后台实现链接各个服务 2025-09-29 15:16:32 +08:00
Tim
a12368602d fix: 尝试docker部署 2025-09-29 10:52:59 +08:00
Tim
208c875868 fix: 去除compose中重复声明 2025-09-29 10:42:17 +08:00
Tim
39ae8c02cb fix: 修改.env.example 2025-09-29 10:29:37 +08:00
tim
0119605649 feat: 先把每日定时构件给注释掉 2025-09-29 01:14:50 +08:00
Tim
0d7dc93a67 fix: 初步转移为docker 2025-09-28 21:06:52 +08:00
Tim
774611f3a8 Merge pull request #1033 from nagisa77/feature/open_search
Feature: Open Search
2025-09-28 19:19:21 +08:00
Tim
61f6e7c90a Merge pull request #1034 from smallclover/main
UI调整
2025-09-28 10:06:28 +08:00
smallclover
892aa6a7c6 UI调整
https://github.com/nagisa77/OpenIsle/issues/855
2025-09-27 08:59:11 +09:00
37 changed files with 1124 additions and 424 deletions

105
.env.example Normal file
View File

@@ -0,0 +1,105 @@
# === Core Service Ports ===
SERVER_PORT=8080
FRONTEND_PORT=3000
WEBSOCKET_PORT=8082
MYSQL_PORT=3306
REDIS_PORT=6379
RABBITMQ_PORT=5672
RABBITMQ_MANAGEMENT_PORT=15672
# === OpenSearch Configuration ===
OPENSEARCH_PORT=9200
OPENSEARCH_METRICS_PORT=9600
OPENSEARCH_DASHBOARDS_PORT=5601
OPENSEARCH_ENABLED=true
OPENSEARCH_SCHEME=http
OPENSEARCH_USERNAME=
OPENSEARCH_PASSWORD=
OPENSEARCH_HOST=opensearch
# === Database Configuration ===
MYSQL_DATABASE=openisle
MYSQL_ROOT_PASSWORD=openisle
MYSQL_USER=openisle
MYSQL_PASSWORD=openisle
MYSQL_HOST=mysql
# === Redis Configuration ===
REDIS_HOST=redis
REDIS_DATABASE=0
# === RabbitMQ Configuration ===
RABBITMQ_HOST=rabbitmq
RABBITMQ_USERNAME=nagisa
RABBITMQ_PASSWORD=nagisa
# === Backend Application Secrets ===
JWT_SECRET=change-me-jwt-secret
JWT_REASON_SECRET=change-me-jwt-reason-secret
JWT_RESET_SECRET=change-me-jwt-reset-secret
JWT_INVITE_SECRET=change-me-jwt-invite-secret
JWT_EXPIRATION=2592000000
PASSWORD_STRENGTH=LOW
POST_PUBLISH_MODE=DIRECT
REGISTER_MODE=WHITELIST
UPLOAD_CHECK_TYPE=true
UPLOAD_MAX_SIZE=5242880
AVATAR_STYLE=pixel-art-neutral
AVATAR_SIZE=128
AVATAR_BASE_URL=https://api.dicebear.com/6.x
USER_POSTS_LIMIT=10
USER_REPLIES_LIMIT=50
SNIPPET_LENGTH=200
SEARCH_INDEX_PREFIX=openisle
SEARCH_HIGHLIGHT_FRAGMENT_SIZE=200
SEARCH_REINDEX_ON_STARTUP=true
SEARCH_REINDEX_BATCH_SIZE=500
CAPTCHA_ENABLED=false
RECAPTCHA_SECRET_KEY=
CAPTCHA_REGISTER_ENABLED=false
CAPTCHA_LOGIN_ENABLED=false
CAPTCHA_POST_ENABLED=false
CAPTCHA_COMMENT_ENABLED=false
RESEND_API_KEY=
RESEND_FROM_EMAIL=
COS_BASE_URL=https://<你的cos>.cos.accelerate.myqcloud.com
COS_SECRET_ID=
COS_SECRET_KEY=
COS_REGION=ap-guangzhou
COS_BUCKET_NAME=
GITHUB_CLIENT_SECRET=
DISCORD_CLIENT_SECRET=
TWITTER_CLIENT_SECRET=
TELEGRAM_BOT_TOKEN=
OPENAI_API_KEY=
OPENAI_MODEL=gpt-4o
AI_FORMAT_LIMIT=3
WEBSITE_URL=http://localhost:3000
WEBPUSH_PUBLIC_KEY=
WEBPUSH_PRIVATE_KEY=
LOG_LEVEL=INFO
# === Frontend (Nuxt) ===
NUXT_PUBLIC_API_BASE_URL=http://localhost:8080
# NUXT_PUBLIC_API_BASE_URL=https://www.open-isle.com
# NUXT_PUBLIC_API_BASE_URL=https://www.staging.open-isle.com
NUXT_PUBLIC_WEBSOCKET_URL=http://localhost:8082
# NUXT_PUBLIC_WEBSOCKET_URL=https://www.open-isle.com
# NUXT_PUBLIC_WEBSOCKET_URL=https://www.staging.open-isle.com
NUXT_PUBLIC_WEBSITE_BASE_URL=http://localhost:3000
# 线上 & 本地均可使用
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
# 线上
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liOlrZnPKRF7s7NN
# 本地
# NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liOlrZnPKRF7s7NN
# 线上 & 本地均可使用
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
# 线上 & 本地均可使用
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
# 线上
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135

View File

@@ -23,7 +23,7 @@ jobs:
host: ${{ secrets.SSH_HOST }}
username: root
key: ${{ secrets.SSH_KEY }}
script: bash /opt/openisle/deploy-staging.sh
script: bash /opt/openisle/OpenIsle/deploy/deploy_staging.sh
deploy-docs:
needs: build-and-deploy

View File

@@ -2,8 +2,8 @@ name: CI & CD
on:
workflow_dispatch:
schedule:
- cron: "0 19 * * *" # 每天 UTC 19:00相当于北京时间凌晨3点
# schedule:
# - cron: "0 19 * * *" # 每天 UTC 19:00相当于北京时间凌晨3点
jobs:
build-and-deploy:
@@ -19,4 +19,4 @@ jobs:
host: ${{ secrets.SSH_HOST }}
username: root
key: ${{ secrets.SSH_KEY }}
script: bash /opt/openisle/deploy.sh
script: bash /opt/openisle/OpenIsle/deploy/deploy.sh

View File

@@ -1,25 +1,19 @@
- [前置工作](#前置工作)
- [前端极速调试Docker 全量环境)](#前端极速调试docker-全量环境)
- [启动后端服务](#启动后端服务)
- [本地 IDEA](#本地-idea)
- [配置环境变量](#配置环境变量)
- [配置 IDEA 参数](#配置-idea-参数)
- [配置 MySQL](#配置-mysql)
- [配置 Redis](#配置-redis)
- [配置 RabbitMQ](#配置-rabbitmq)
- [Docker 环境](#docker-环境)
- [配置环境变量](#配置环境变量-1)
- [构建并启动镜像](#构建并启动镜像)
- [启动前端服务](#启动前端服务)
- [配置环境变量](#配置环境变量-2)
- [安装依赖和运行](#安装依赖和运行)
- [连接预发或正式环境](#连接预发或正式环境)
- [其他配置](#其他配置)
- [配置第三方登录以GitHub为例](#配置第三方登录以GitHub为例)
- [配置Resend邮箱服务](#配置Resend邮箱服务)
- [配置第三方登录以GitHub为例](#配置第三方登录以github为例)
- [配置Resend邮箱服务](#配置resend邮箱服务)
- [API文档](#api文档)
- [OpenAPI文档](#openapi文档)
- [部署时间线以及文档时效性](#部署时间线以及文档时效性)
- [OpenAPI文档使用](#OpenAPI文档使用)
- [OpenAPI文档应用场景](#OpenAPI文档应用场景)
- [OpenAPI文档使用](#openapi文档使用)
- [OpenAPI文档应用场景](#openapi文档应用场景)
## 前置工作
@@ -35,6 +29,60 @@ cd OpenIsle
- 前端开发环境
- Node.JS 20+
## 前端极速调试Docker 全量环境)
想要最快速地同时体验前端和后端,可直接使用仓库提供的 Docker Compose。该方案会一次性拉起数据库、消息队列、搜索、后端、WebSocket 以及前端 Dev Server适合需要全链路联调的场景。
1. 准备环境变量文件:
```shell
cp .env.example .env
```
`.env.example` 是模板,可在 `.env` 中按需覆盖如端口、密钥等配置。确保 `NUXT_PUBLIC_API_BASE_URL`、`NUXT_PUBLIC_WEBSOCKET_URL` 等仍指向 `localhost`,方便前端直接访问容器映射端口。
2. 启动 Dev Profile
```shell
docker compose \
-f docker/docker-compose.yaml \
--env-file .env \
--profile dev build
```
```shell
docker compose \
-f docker/docker-compose.yaml \
--env-file .env \
--profile dev up -d
```
该命令会创建名为 `frontend_dev` 的容器并运行 `npm run dev`,浏览器访问 http://127.0.0.1:3000 即可查看页面。
修改代码后,可以强制重新创建所有容器,执行:
```shell
docker compose \
-f docker/docker-compose.yaml \
--env-file .env \
--profile dev up -d --force-recreate
```
3. 查看服务状态:
```shell
docker compose -f docker/docker-compose.yaml --env-file .env ps
docker compose -f docker/docker-compose.yaml --env-file .env logs -f frontend_dev
```
4. 停止所有容器:
```shell
docker compose -f docker/docker-compose.yaml --env-file .env --profile dev down
```
如需自定义 Node 依赖缓存、数据库持久化等,可参考 `docker/docker-compose.yaml` 中各卷的定义进行调整。
## 启动后端服务
启动后端服务有多种方式,选择一种即可。
@@ -52,183 +100,32 @@ IDEA 打开 `backend/` 文件夹。
#### 配置环境变量
1. 生成环境变量文件
```shell
cp open-isle.env.example open-isle.env
1. 在 IDEA 中配置「Environment file」将 `Run/Debug Configuration` 的 `Environment variables` 指向刚刚复制的 `.env`,即可让 IDE 读取该文件
2. 需要调整端口或功能开关时,优先修改 `.env`,例如:
```ini
SERVER_PORT=8081
LOG_LEVEL=DEBUG
```
`open-isle.env.example` 是环境变量模板,`open-isle.env` 才是真正读取的内容
也可以修改 `src/main/resources/application.properties`,但该文件会被 Git 追踪,通常不推荐。
2. 修改环境变量,留下需要的,比如你要开发 Google 登录业务,就需要谷歌相关的变量,数据库是一定要的
![环境变量](assets/contributing/backend_img_7.png)
3. 应用环境文件,选择刚刚的 `open-isle.env`
可以在 `open-isle.env` 按需填写个性化的配置,该文件不会被 Git 追踪。比如你想把服务跑在 `8082`(默认为 `8080`),那么直接改 `open-isle.env` 即可:
```ini
SERVER_PORT=8082
```
另一种方式是修改 `.properities` 文件(但不建议),位于 `src/main/application.properties`,该配置同样来源于 `open-isle.env`,但修改 `.properties` 文件会被 Git 追踪。
![配置数据库](assets/contributing/backend_img_5.png)
![backend_img_5.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/89658e5d5c0443a5939ef57ccfeab740.png)
#### 配置 IDEA 参数
- 设置 JDK 版本为 java 17
- 设置 VM Option最好运行在其他端口非 `8080`,这里设置 `8081`
若上面在环境变量中设置了端口,那这里就不需要再额外设置
- 设置 JDK 版本为 Java 17
- 设置 VM Option最好运行在其他端口例如 `8081`)。若已经在 `open-isle.env` 中调整端口,可省略此步骤。
```shell
-Dserver.port=8081
```
![配置1](assets/contributing/backend_img_3.png)
![backend_img_3.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/6905f7cd4b694b1fa7214dd2b3ef9d81.png)
![配置2](assets/contributing/backend_img_2.png)
![backend_img_2.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/2bf8e139c21f4b529384ed68cef053da.png)
#### 配置 MySQL
完成环境变量和运行参数设置后,即可启动 Spring Boot 应用。
> [!TIP]
> 如果不知道怎么配置数据库可以参考 [Docker 环境](#docker-环境) 章节
1. 本机配置 MySQL 服务(网上很多教程,忽略)
- 可以用 Laragon自带 MySQL 包括 Nodejs版本建议 `6.x``7` 以后需要 Lisence
- [下载地址](https://github.com/leokhoa/laragon/releases)
2. 填写环境变量
![环境变量](assets/contributing/backend_img_6.png)
```ini
MYSQL_URL=jdbc:mysql://<数据库地址>:<端口>/<数据库名>?useUnicode=yes&characterEncoding=UTF-8&useInformationSchema=true&useSSL=false&serverTimezone=UTC
MYSQL_USER=<数据库用户名>
MYSQL_PASSWORD=<数据库密码>
```
3. 执行 [`db/init/init_script.sql`](backend/src/main/resources/db/init/init_script.sql) 脚本,导入基本的数据
管理员:**admin/123456**
普通用户1**user1/123456**
普通用户2**user2/123456**
![初始化脚本](assets/contributing/resources_img.png)
#### 配置 Redis
后端的登录态缓存、访问频控等都依赖 Redis请确保本地有可用的 Redis 实例。
1. **启动 Redis 服务**(已有服务可跳过)
```bash
docker run --name openisle-redis -p 6379:6379 -d redis:7-alpine
```
该命令会在本机暴露 `6379` 端口。若你已有其他端口的 Redis可以根据实际情况调整映射关系。
2. **在 `backend/open-isle.env` 中填写连接信息**
```ini
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# 可选:若需要切换逻辑库,可新增此变量,默认使用 0 号库
REDIS_DATABASE=0
```
`application.properties` 中的默认值为 `localhost:6379`、数据库 `0`,如果你的环境恰好一致,也可以不额外填写;显式声明可以避免 IDE/运行时读取到意外配置。
3. **验证连接**
```bash
redis-cli -h 127.0.0.1 -p 6379 ping
```
启动后端后,日志中会出现 `Redis connection established ...`(来自 `RedisConnectionLogger`),说明已成功连通。
#### 配置 RabbitMQ
消息通知和 WebSocket 推送链路依赖 RabbitMQ。后端会自动声明交换机与队列确保本地 RabbitMQ 可用即可。
1. **启动 RabbitMQ 服务**(推荐包含管理界面)
```bash
docker run --name openisle-rabbitmq \
-e RABBITMQ_DEFAULT_USER=openisle \
-e RABBITMQ_DEFAULT_PASS=openisle \
-p 5672:5672 -p 15672:15672 \
-d rabbitmq:3.13-management
```
管理界面位于 http://127.0.0.1:15672 ,可用于查看队列、交换机等资源。
2. **同步填写后端与 WebSocket 服务的环境变量**
```ini
# backend/open-isle.env
RABBITMQ_HOST=127.0.0.1
RABBITMQ_PORT=5672
RABBITMQ_USERNAME=openisle
RABBITMQ_PASSWORD=openisle
# 如果需要启动 websocket_service也需要在 websocket_service.env 中保持一致
```
如果沿用 RabbitMQ 默认的 `guest/guest`可以不显式设置Spring Boot 会回退到 `application.properties` 中的默认值 (`localhost:5672`、`guest/guest`、虚拟主机 `/`)。
3. **确认自动声明的资源**
- 交换机:`openisle-exchange`
- 旧版兼容队列:`notifications-queue`
- 分片队列:`notifications-queue-0` ~ `notifications-queue-f`(共 16 个,对应路由键 `notifications.shard.0` ~ `notifications.shard.f`
- 队列持久化默认开启,来自 `rabbitmq.queue.durable=true`,如需仅在本地短暂测试,可在 `application.properties` 中调整该配置。
启动后端时可在日志中看到 `=== 开始主动声明 RabbitMQ 组件 ===` 与后续的声明结果,也可以在管理界面中查看是否创建成功。
完成 Redis 与 RabbitMQ 配置后,即可继续启动后端服务。
![运行画面](assets/contributing/backend_img_4.png)
### Docker 环境
#### 配置环境变量
```shell
cd docker/
```
主要配置两个 `.env` 文件
- `backend/open-isle.env`:后端环境变量,配置同上,见 [配置环境变量](#配置环境变量)。
- `docker/.env`Docker Compose 环境变量,主要配置 MySQL 相关
```shell
cp .env.example .env
```
> [!TIP]
> 使用单独的 `.env` 文件是为了兼容线上环境或已启用 MySQL 服务的情况,如果只是想快速体验或者启动统一的环境,则推荐使用本方式。
在指定 `docker/.env` 后,`backend/open-isle.env` 中以下配置会被覆盖,这样就确保使用了同一份配置。
```ini
MYSQL_URL=
MYSQL_USER=
MYSQL_PASSWORD=
```
#### 构建并启动镜像
```shell
docker compose up -d
```
如果想了解启动过程发生了什么可以查看日志
```shell
docker compose logs
```
![backend_img_4.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/474d995ddda34b6f80badffa58cec5b9.png)
## 启动前端服务
@@ -239,43 +136,29 @@ docker compose logs
cd frontend_nuxt/
```
### 配置环境变量
前端可以依赖本机部署的后端,也可以直接调用线上的后端接口。
- 利用预发环境:**(⚠️ 强烈推荐只开发前端的朋友使用该环境)**
```shell
cp .env.staging.example .env
```
- 利用生产环境
```shell
cp .env.production.example .env
```
- 利用本地环境
```shell
cp .env.dev.example .env
```
若依赖本机部署的后端,需要修改 `.env` 中的 `NUXT_PUBLIC_API_BASE_URL` 值与后端服务端口一致
### 安装依赖和运行
前端安装依赖并启动服务。
安装依赖并启动开发服务器:
```shell
# 安装依赖
npm install --verbose
# 运行前端服务
npm run dev
```
如此一来,浏览器访问 http://127.0.0.1:3000 即可访问前端页面。
默认情况下,浏览器访问 http://127.0.0.1:3000 即可访问前端页面。
### 连接预发或正式环境
前端默认读取 `.env` 中的接口地址,可通过修改以下变量快速切换到预发或正式环境:
1. 按需覆盖关键变量:
```ini
NUXT_PUBLIC_API_BASE_URL=https://www.staging.open-isle.com
NUXT_PUBLIC_WEBSOCKET_URL=https://www.staging.open-isle.com
```
将 `staging` 替换为 `www` 即可连接正式环境。其他变量(如 OAuth Client ID、站点地址等可根据需求调整。
2. 已经存在 `.env` 时,可直接编辑上述变量并重启 `npm run dev` 让配置生效。
## 其他配置
@@ -283,42 +166,41 @@ npm run dev
- 修改 `application.properties` 配置
![后端配置](assets/contributing/backend_img.png)
![backend_img.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/1dbb388cd1004e1d8822224cf87c9303.png)
- 修改 `.env` 配置
![前端](assets/contributing/fontend_img.png)
![fontend_img.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/3cc276e4795a407a90a47f2d77de2760.png)
- 配置第三方登录回调地址
![github配置](assets/contributing/github_img.png)
![github_img.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/14667457a15c4fbea9d91226797b7b59.png)
![github配置2](assets/contributing/github_img_2.png)
![github_img_2.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/7e901e1c648f4330be1d248379d699f1.png)
### 配置Resend邮箱服务
https://resend.com/emails 创建账号并登录
- `Domains` -> `Add Domain`
![image-20250906150459400](assets/contributing/image-20250906150459400.png)
![image-20250906150459400.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/0168a039ca5e47239ab859f6049c4c93.png)
- 填写域名
![image-20250906150541817](assets/contributing/image-20250906150541817.png)
![image-20250906150541817.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/d328cd89f21c4b8fb50c8213c9b784f0.png)
- 等待一段时间后解析成功,创建 key
`API Keys` -> `Create API Key`,输入名称,设置 `Permission` 为 `Sending access`
**Key 只能查看一次,务必保存下来**
![image-20250906150811572](assets/contributing/image-20250906150811572.png)
![image-20250906150924975](assets/contributing/image-20250906150924975.png)
![image-20250906150944130](assets/contributing/image-20250906150944130.png)
![image-20250906150811572.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/d72959670b22451cb93e94d6e8316e05.png)
![image-20250906150924975.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/b46f1a56f2d744a381d361ac8369a231.png)
![image-20250906150944130.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/8eaba3fa791f4b10a5c24948812d306d.png)
- 修改 `.env` 配置中的 `RESEND_API_KEY` 和 `RESEND_FROM_EMAIL`
`RESEND_FROM_EMAIL` **noreply@域名**
`RESEND_API_KEY`**刚刚复制的 Key**
![image-20250906151218330](assets/contributing/image-20250906151218330.png)
![image-20250906151218330.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/8319dc5037bb40b68ed35358d810552a.png)
## API文档
### OpenAPI文档
https://docs.open-isle.com
### 部署时间线以及文档时效性
@@ -334,7 +216,7 @@ https://docs.open-isle.com
### OpenAPI文档使用
- 预发环境/正式环境切换,以通过如下位置切换API环境
- 预发环境/正式环境切换以通过如下位置切换API环境
![CleanShot 2025-09-10 at 12.08.00@2x.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/f9fb7a0f020d4a0e94159d7820783224.png)

View File

@@ -1,3 +1,6 @@
# 所有环境变量已集中在仓库根目录的 .env.*.example 文件。
# 此文件保留作参考用途,如需在 Docker 之外手动配置,可按需复制。
# === Spring Boot ===
SERVER_PORT=8080

View File

@@ -132,6 +132,10 @@
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 高阶 Java 客户端 -->
<dependency>
<groupId>org.opensearch.client</groupId>

View File

@@ -97,6 +97,8 @@ public class SecurityConfig {
"http://localhost:8081",
"http://localhost:8082",
"http://localhost:3000",
"http://frontend_dev:3000",
"http://frontend_service:3000",
"http://localhost:3001",
"http://localhost",
"http://30.211.97.238:3000",
@@ -177,6 +179,8 @@ public class SecurityConfig {
.permitAll()
.requestMatchers(HttpMethod.POST, "/api/point-goods")
.permitAll()
.requestMatchers("/actuator/**")
.permitAll()
.requestMatchers(HttpMethod.POST, "/api/categories/**")
.hasAuthority("ADMIN")
.requestMatchers(HttpMethod.POST, "/api/tags/**")
@@ -230,6 +234,7 @@ public class SecurityConfig {
uri.startsWith("/api/channels") ||
uri.startsWith("/api/sitemap.xml") ||
uri.startsWith("/api/medals") ||
uri.startsWith("/actuator") ||
uri.startsWith("/api/rss"));
if (authHeader != null && authHeader.startsWith("Bearer ")) {

View File

@@ -4,7 +4,7 @@ server.port=${SERVER_PORT:8080}
# for mysql
logging.level.root=${LOG_LEVEL:INFO}
logging.level.com.openisle.service.CosImageUploader=DEBUG
spring.datasource.url=${MYSQL_URL:jdbc:mysql://localhost:3306/openisle}
spring.datasource.url=jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT}/${MYSQL_DATABASE}?useUnicode=yes&characterEncoding=UTF-8&useInformationSchema=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
spring.datasource.username=${MYSQL_USER:root}
spring.datasource.password=${MYSQL_PASSWORD:password}
spring.jpa.hibernate.ddl-auto=update
@@ -47,11 +47,11 @@ app.snippet-length=${SNIPPET_LENGTH:200}
# OpenSearch integration
app.search.enabled=${SEARCH_ENABLED:true}
app.search.host=${SEARCH_HOST:localhost}
app.search.port=${SEARCH_PORT:9200}
app.search.scheme=${SEARCH_SCHEME:http}
app.search.username=${SEARCH_USERNAME:}
app.search.password=${SEARCH_PASSWORD:}
app.search.host=${OPENSEARCH_HOST:opensearch}
app.search.port=${OPENSEARCH_PORT:9200}
app.search.scheme=${OPENSEARCH_SCHEME:http}
app.search.username=${OPENSEARCH_USERNAME:}
app.search.password=${OPENSEARCH_PASSWORD:}
app.search.index-prefix=${SEARCH_INDEX_PREFIX:openisle}
app.search.highlight-fragment-size=${SEARCH_HIGHLIGHT_FRAGMENT_SIZE:${SNIPPET_LENGTH:200}}
app.search.reindex-on-startup=${SEARCH_REINDEX_ON_STARTUP:true}
@@ -81,15 +81,15 @@ cos.bucket-name=${COS_BUCKET_NAME:}
# your image upload services: ...
# Google OAuth configuration
google.client-id=${GOOGLE_CLIENT_ID:}
google.client-id=${NUXT_PUBLIC_GOOGLE_CLIENT_ID:}
# GitHub OAuth configuration
github.client-id=${GITHUB_CLIENT_ID:}
github.client-id=${NUXT_PUBLIC_GITHUB_CLIENT_ID:}
github.client-secret=${GITHUB_CLIENT_SECRET:}
# Discord OAuth configuration
discord.client-id=${DISCORD_CLIENT_ID:}
discord.client-id=${NUXT_PUBLIC_DISCORD_CLIENT_ID:}
discord.client-secret=${DISCORD_CLIENT_SECRET:}
# Twitter OAuth configuration
twitter.client-id=${TWITTER_CLIENT_ID:}
twitter.client-id=${NUXT_PUBLIC_TWITTER_CLIENT_ID:}
twitter.client-secret=${TWITTER_CLIENT_SECRET:}
# Telegram login configuration
telegram.bot-token=${TELEGRAM_BOT_TOKEN:}
@@ -129,3 +129,6 @@ springdoc.info.description=OpenIsle Open API Documentation
springdoc.info.version=0.0.1
springdoc.info.scheme=Bearer
springdoc.info.header=Authorization
management.endpoints.web.exposure.include=health,info
management.endpoint.health.probes.enabled=true

View File

@@ -0,0 +1,13 @@
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
SET collation_connection = utf8mb4_0900_ai_ci;
CREATE DATABASE IF NOT EXISTS `openisle`
CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;
CREATE USER IF NOT EXISTS 'openisle'@'%' IDENTIFIED BY 'openisle';
GRANT ALL PRIVILEGES ON `openisle`.* TO 'openisle'@'%';
FLUSH PRIVILEGES;
USE `openisle`;

View File

@@ -0,0 +1,54 @@
USE `openisle`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE TABLE IF NOT EXISTS `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`approved` bit(1) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`created_at` datetime(6) DEFAULT NULL,
`display_medal` varchar(255) DEFAULT NULL,
`email` varchar(255) NOT NULL,
`experience` int DEFAULT NULL,
`introduction` text,
`password` varchar(255) NOT NULL,
`password_reset_code` varchar(255) DEFAULT NULL,
`point` int DEFAULT NULL,
`register_reason` text,
`role` varchar(20) DEFAULT 'USER',
`username` varchar(50) NOT NULL,
`verification_code` varchar(255) DEFAULT NULL,
`verified` bit(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_users_email` (`email`),
UNIQUE KEY `UK_users_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS `categories` (
`id` bigint NOT NULL AUTO_INCREMENT,
`description` text,
`icon` varchar(255) DEFAULT NULL,
`name` varchar(50) NOT NULL,
`small_icon` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_categories_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE IF NOT EXISTS `tags` (
`id` bigint NOT NULL AUTO_INCREMENT,
`approved` bit(1) DEFAULT NULL,
`created_at` datetime(6) DEFAULT NULL,
`description` text,
`icon` varchar(255) DEFAULT NULL,
`name` varchar(50) NOT NULL,
`small_icon` varchar(255) DEFAULT NULL,
`creator_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_tags_name` (`name`),
KEY `FK_tags_creator` (`creator_id`),
CONSTRAINT `FK_tags_creator` FOREIGN KEY (`creator_id`) REFERENCES `users` (`id`)
ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -0,0 +1,26 @@
USE `openisle`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM `tags`;
DELETE FROM `categories`;
DELETE FROM `users`;
-- 插入用户,两个普通用户,一个管理员
-- username:admin/user1/user2 password:123456
INSERT INTO `users` (`id`, `approved`, `avatar`, `created_at`, `display_medal`, `email`, `experience`, `introduction`, `password`, `password_reset_code`, `point`, `register_reason`, `role`, `username`, `verification_code`, `verified`) VALUES
(1, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-01 16:08:17.426430', 'PIONEER', 'adminmail@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'ADMIN', 'admin', NULL, b'1'),
(2, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-03 16:08:17.426430', 'PIONEER', 'usermail2@openisle.com', 70, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 110, '测试测试测试……', 'USER', 'user1', NULL, b'1'),
(3, b'1', 'https://openisle-1307107697.cos.ap-guangzhou.myqcloud.com/assert/image.png', '2025-09-02 17:21:21.617666', 'PIONEER', 'usermail1@openisle.com', 40, NULL, '$2a$10$x7HXjUyJTmrvqjnBlBQZH.vmfsC56NzTSWqQ6WqZqRjUO859EhviS', NULL, 40, '测试测试测试……', 'USER', 'user2', NULL, b'1');
INSERT INTO `categories` (`id`,`description`,`icon`,`name`,`small_icon`) VALUES
(1,'测试用分类1','star','测试用分类1',NULL),
(2,'测试用分类2','star','测试用分类2',NULL),
(3,'测试用分类3','star','测试用分类3',NULL);
INSERT INTO `tags` (`id`,`approved`,`created_at`,`description`,`icon`,`name`,`small_icon`,`creator_id`) VALUES
(1,b'1','2025-09-02 10:51:56.000000','测试用标签1',NULL,'测试用标签1',NULL,NULL),
(2,b'1','2025-09-02 10:51:56.000000','测试用标签2',NULL,'测试用标签2',NULL,NULL),
(3,b'1','2025-09-02 10:51:56.000000','测试用标签3',NULL,'测试用标签3',NULL,NULL);
SET FOREIGN_KEY_CHECKS = 1;

View File

@@ -1,81 +0,0 @@
-- 2025-09-02
-- 本地化开发,初始化脚本
-- 抽奖的时候奖品图片是必须的把相关代码注释掉即可跳过check
-- 设置字符集和排序规则
SET NAMES utf8;
SET CHARACTER SET utf8;
SET collation_connection = utf8_general_ci;
-- 创建 users 表(如果不存在)
CREATE TABLE IF NOT EXISTS `users` (
`id` bigint NOT NULL AUTO_INCREMENT,
`approved` bit(1) DEFAULT NULL,
`avatar` varchar(255) DEFAULT NULL,
`created_at` datetime(6) DEFAULT NULL,
`display_medal` varchar(255) DEFAULT NULL,
`email` varchar(255) NOT NULL,
`experience` int DEFAULT NULL,
`introduction` text,
`password` varchar(255) NOT NULL,
`password_reset_code` varchar(255) DEFAULT NULL,
`point` int DEFAULT NULL,
`register_reason` text,
`role` varchar(20) DEFAULT 'USER',
`username` varchar(50) NOT NULL,
`verification_code` varchar(255) DEFAULT NULL,
`verified` bit(1) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_users_email` (`email`),
UNIQUE KEY `UK_users_username` (`username`)
);
-- 清空users表
DELETE FROM `users`;
-- 插入用户,两个普通用户,一个管理员
-- username:admin/user1/user2 password:123321
INSERT INTO `users` (`id`, `approved`, `avatar`, `created_at`, `display_medal`, `email`, `experience`, `introduction`, `password`, `password_reset_code`, `point`, `register_reason`, `role`, `username`, `verification_code`, `verified`) VALUES
(1, b'1', '', '2025-09-01 16:08:17.426430', 'PIONEER', 'adminmail@openisle.com', 70, NULL, '$2a$10$dux.NXwW09cCsdZ05BgcnOtxVqqjcmnbj3.8xcxGl/iiIlv06y7Oe', NULL, 110, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'ADMIN', 'admin', NULL, b'1'),
(2, b'1', '', '2025-09-03 16:08:17.426430', 'PIONEER', 'usermail2@openisle.com', 70, NULL, '$2a$10$dux.NXwW09cCsdZ05BgcnOtxVqqjcmnbj3.8xcxGl/iiIlv06y7Oe', NULL, 110, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'USER', 'user1', NULL, b'1'),
(3, b'1', '', '2025-09-02 17:21:21.617666', 'PIONEER', 'usermail1@openisle.com', 40, NULL, '$2a$10$dux.NXwW09cCsdZ05BgcnOtxVqqjcmnbj3.8xcxGl/iiIlv06y7Oe', NULL, 40, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'USER', 'user2', NULL, b'1');
-- 创建 tags 表(如果不存在)
CREATE TABLE IF NOT EXISTS `tags` (
`id` bigint NOT NULL AUTO_INCREMENT,
`approved` bit(1) DEFAULT NULL,
`created_at` datetime(6) DEFAULT NULL,
`description` text,
`icon` varchar(255) DEFAULT NULL,
`name` varchar(50) NOT NULL,
`small_icon` varchar(255) DEFAULT NULL,
`creator_id` bigint DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_tags_name` (`name`),
KEY `FK_tags_creator` (`creator_id`),
CONSTRAINT `FK_tags_creator` FOREIGN KEY (`creator_id`) REFERENCES `users` (`id`)
);
-- 清空tags表
DELETE FROM `tags`;
-- 插入标签,三个测试用标签
INSERT INTO `tags` (`id`, `approved`, `created_at`, `description`, `icon`, `name`, `small_icon`, `creator_id`) VALUES
(1, b'1', '2025-09-02 10:51:56.000000', '测试用标签1', NULL, '测试用标签1', NULL, NULL),
(2, b'1', '2025-09-02 10:51:56.000000', '测试用标签2', NULL, '测试用标签2', NULL, NULL),
(3, b'1', '2025-09-02 10:51:56.000000', '测试用标签3', NULL, '测试用标签3', NULL, NULL);
-- 创建 categories 表(如果不存在)
CREATE TABLE IF NOT EXISTS `categories` (
`id` bigint NOT NULL AUTO_INCREMENT,
`description` text,
`icon` varchar(255) DEFAULT NULL,
`name` varchar(50) NOT NULL,
`small_icon` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UK_categories_name` (`name`)
);
-- 清空categories表
DELETE FROM `categories`;
-- 插入分类,三个测试用分类
INSERT INTO `categories` (`id`, `description`, `icon`, `name`, `small_icon`) VALUES
(1, '测试用分类1', '1', '测试用分类1', NULL),
(2, '测试用分类2', '2', '测试用分类2', NULL),
(3, '测试用分类3', '3', '测试用分类3', NULL);

56
deploy/deploy.sh Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
# 可用法:
# ./deploy.sh
# ./deploy.sh feature/docker
deploy_branch="${1:-main}"
repo_dir="/opt/openisle/OpenIsle"
compose_file="${repo_dir}/docker/docker-compose.yaml"
env_file="${repo_dir}/.env"
project="openisle"
echo "👉 Enter repo..."
cd "$repo_dir"
echo "👉 Syncing code & switching to branch: $deploy_branch"
git fetch --all --prune
git checkout -B "$deploy_branch" "origin/$deploy_branch"
git reset --hard "origin/$deploy_branch"
echo "👉 Ensuring env file: $env_file"
if [ ! -f "$env_file" ]; then
echo "${env_file} not found. Create it based on .env.example (with domains)."
exit 1
fi
export COMPOSE_PROJECT_NAME="$project"
# 供 compose 内各 service 的 env_file 使用
export ENV_FILE="$env_file"
echo "👉 Validate compose..."
docker compose -f "$compose_file" --env-file "$env_file" config >/dev/null
echo "👉 Pull base images (for image-based services)..."
docker compose -f "$compose_file" --env-file "$env_file" pull --ignore-pull-failures
echo "👉 Build images ..."
# 前端 + OpenSearch 都是自建镜像;--pull 更新其基础镜像
docker compose -f "$compose_file" --env-file "$env_file" \
build --pull \
--build-arg NUXT_ENV=production \
frontend_service
echo "👉 Recreate & start all target services (no dev profile)..."
docker compose -f "$compose_file" --env-file "$env_file" \
up -d --force-recreate --remove-orphans --no-deps \
mysql redis rabbitmq websocket-service springboot frontend_service
echo "👉 Current status:"
docker compose -f "$compose_file" --env-file "$env_file" ps
echo "👉 Pruning dangling images..."
docker image prune -f
echo "✅ Stack deployed at $(date)"

56
deploy/deploy_staging.sh Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
# 可用法:
# ./deploy-staging.sh
# ./deploy-staging.sh feature/docker
deploy_branch="${1:-main}"
repo_dir="/opt/openisle/OpenIsle-staging"
compose_file="${repo_dir}/docker/docker-compose.yaml"
env_file="${repo_dir}/.env"
project="openisle_staging"
echo "👉 Enter repo..."
cd "$repo_dir"
echo "👉 Syncing code & switching to branch: $deploy_branch"
git fetch --all --prune
git checkout -B "$deploy_branch" "origin/$deploy_branch"
git reset --hard "origin/$deploy_branch"
echo "👉 Ensuring env file: $env_file"
if [ ! -f "$env_file" ]; then
echo "${env_file} not found. Create it based on .env.example (with staging domains)."
exit 1
fi
export COMPOSE_PROJECT_NAME="$project"
# 供 compose 内各 service 的 env_file 使用
export ENV_FILE="$env_file"
echo "👉 Validate compose..."
docker compose -f "$compose_file" --env-file "$env_file" config >/dev/null
echo "👉 Pull base images (for image-based services)..."
docker compose -f "$compose_file" --env-file "$env_file" pull --ignore-pull-failures
echo "👉 Build images (staging)..."
# 前端 + OpenSearch 都是自建镜像;--pull 更新其基础镜像
docker compose -f "$compose_file" --env-file "$env_file" \
build --pull \
--build-arg NUXT_ENV=staging \
frontend_service
echo "👉 Recreate & start all target services (no dev profile)..."
docker compose -f "$compose_file" --env-file "$env_file" \
up -d --force-recreate --remove-orphans --no-deps \
mysql redis rabbitmq websocket-service springboot frontend_service
echo "👉 Current status:"
docker compose -f "$compose_file" --env-file "$env_file" ps
echo "👉 Pruning dangling images..."
docker image prune -f
echo "✅ Staging stack deployed at $(date)"

View File

@@ -1,16 +1,4 @@
# 前端访问端口
SERVER_PORT=8080
# OpenSearch 配置
OPENSEARCH_PORT=9200
OPENSEARCH_METRICS_PORT=9600
OPENSEARCH_DASHBOARDS_PORT=5601
# MySQL 配置
MYSQL_ROOT_PASSWORD=toor
# 会覆盖 `open-isle.env`
MYSQL_PORT=3306
MYSQL_DATABASE=openisle
MYSQL_USER=<数据库用户名>
MYSQL_PASSWORD=<数据库密码>
# 已迁移到仓库根目录的 .env.*.example 文件。
# 请复制对应环境的示例文件到项目根目录,例如:
# cp ../.env.dev.example ../.env
# docker-compose 将自动读取 ../.env。

1
docker/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
data

View File

@@ -2,25 +2,37 @@ services:
# MySQL service
mysql:
image: mysql:8.0
container_name: openisle-mysql
container_name: ${COMPOSE_PROJECT_NAME}-openisle-mysql
restart: always
env_file:
- ../backend/open-isle.env
- ./.env
- ${ENV_FILE:-../.env}
command: >
--character-set-server=utf8mb4
--collation-server=utf8mb4_0900_ai_ci
--default-time-zone=+08:00
--skip-character-set-client-handshake
ports:
- "${MYSQL_PORT}:3306"
- "${MYSQL_PORT:-3306}:3306"
volumes:
- mysql-data:/var/lib/mysql
- ../backend/src/main/resources/db/init:/docker-entrypoint-initdb.d
- ../backend/src/main/resources/db/init:/docker-entrypoint-initdb.d:ro
- ./mysql/conf.d:/etc/mysql/conf.d:ro
networks:
- openisle-network
healthcheck:
test: ["CMD","mysqladmin","ping","-h","127.0.0.1","-u","root","-p$MYSQL_ROOT_PASSWORD"]
interval: 5s
timeout: 3s
retries: 30
start_period: 20s
# OpenSearch Service
opensearch:
user: "1000:1000"
build:
context: .
dockerfile: Dockerfile
container_name: opensearch
dockerfile: opensearch.Dockerfile
container_name: ${COMPOSE_PROJECT_NAME}-opensearch
environment:
- cluster.name=os-single
- node.name=os-node-1
@@ -31,53 +43,257 @@ services:
- cluster.blocks.create_index=false
ulimits:
memlock: { soft: -1, hard: -1 }
nofile: { soft: 65536, hard: 65536 }
nofile: { soft: 65536, hard: 65536 }
volumes:
- ./data:/usr/share/opensearch/data
- ./snapshots:/snapshots
- opensearch-data:/usr/share/opensearch/data
- opensearch-snapshots:/snapshots
ports:
- "${OPENSEARCH_PORT:-9200}:9200"
- "${OPENSEARCH_METRICS_PORT:-9600}:9600"
restart: unless-stopped
healthcheck:
test:
- CMD-SHELL
- curl -fsS http://127.0.0.1:9200/_cluster/health >/dev/null
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
networks:
- openisle-network
dashboards:
image: opensearchproject/opensearch-dashboards:3.0.0
container_name: os-dashboards
container_name: ${COMPOSE_PROJECT_NAME}-os-dashboards
environment:
- OPENSEARCH_HOSTS=["http://opensearch:9200"]
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
OPENSEARCH_HOSTS: '["http://opensearch:9200"]'
DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
ports:
- "${OPENSEARCH_DASHBOARDS_PORT:-5601}:5601"
depends_on:
- opensearch
restart: unless-stopped
networks:
- openisle-network
rabbitmq:
image: rabbitmq:3.13-management
container_name: ${COMPOSE_PROJECT_NAME}-openisle-rabbitmq
restart: unless-stopped
environment:
RABBITMQ_DEFAULT_VHOST: "${RABBITMQ_VHOST:-/}"
ports:
- "${RABBITMQ_PORT:-5672}:5672"
- "${RABBITMQ_MANAGEMENT_PORT:-15672}:15672"
volumes:
- rabbitmq-data:/var/lib/rabbitmq
- ./rabbitmq/conf/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro
- ./rabbitmq/conf/enabled_plugins:/etc/rabbitmq/enabled_plugins:ro
- ./rabbitmq/definitions.json:/etc/rabbitmq/definitions.json:ro
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
networks:
- openisle-network
# Java spring boot service
redis:
image: redis:7
container_name: ${COMPOSE_PROJECT_NAME}-openisle-redis
restart: unless-stopped
env_file:
- ${ENV_FILE:-../.env}
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis-data:/data
networks:
- openisle-network
# Java spring boot service (开发便捷镜像,后续可换成打包镜像)
springboot:
image: maven:3.9-eclipse-temurin-17
container_name: openisle-springboot
container_name: ${COMPOSE_PROJECT_NAME}-openisle-springboot
working_dir: /app
env_file:
- ../backend/open-isle.env
- ./.env
- ${ENV_FILE:-../.env}
environment:
- MYSQL_URL=jdbc:mysql://mysql:${MYSQL_PORT}/${MYSQL_DATABASE}?useUnicode=yes&characterEncoding=UTF-8&useInformationSchema=true&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
TZ: "Asia/Shanghai"
SPRING_HEALTH_PATH: ${SPRING_HEALTH_PATH:-/actuator/health}
SERVER_PORT: ${SERVER_PORT:-8080}
RABBITMQ_PORT: 5672
OPENSEARCH_PORT: 9200
MYSQL_PORT: 3306
REDIS_PORT: 6379
JAVA_OPTS: "-Duser.timezone=Asia/Shanghai"
ports:
- "${SERVER_PORT}:8080"
- "${SERVER_PORT:-8080}:${SERVER_PORT:-8080}"
volumes:
- ../backend:/app
- maven-repo:/root/.m2
depends_on:
- mysql
command: mvn clean spring-boot:run -Dmaven.test.skip=true
mysql:
condition: service_healthy
redis:
condition: service_started
rabbitmq:
condition: service_started
websocket-service:
condition: service_healthy
opensearch:
condition: service_healthy
command: >
sh -c "apt-get update && apt-get install -y --no-install-recommends curl &&
mvn clean spring-boot:run -Dmaven.test.skip=true"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${SERVER_PORT:-8080}${SPRING_HEALTH_PATH:-/actuator/health} || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
networks:
- openisle-network
websocket-service:
image: maven:3.9-eclipse-temurin-17
container_name: ${COMPOSE_PROJECT_NAME}-openisle-websocket
working_dir: /app
env_file:
- ${ENV_FILE:-../.env}
environment:
WS_HEALTH_PATH: ${WS_HEALTH_PATH:-/actuator/health}
WEBSOCKET_PORT: ${WEBSOCKET_PORT:-8082}
SERVER_PORT: ${WEBSOCKET_PORT:-8082}
RABBITMQ_PORT: 5672
ports:
- "${WEBSOCKET_PORT:-8082}:${WEBSOCKET_PORT:-8082}"
volumes:
- ../websocket_service:/app
- websocket-maven-repo:/root/.m2
depends_on:
rabbitmq:
condition: service_healthy
command: >
sh -c "apt-get update && apt-get install -y --no-install-recommends curl &&
mvn clean spring-boot:run -Dmaven.test.skip=true"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:${WEBSOCKET_PORT:-8082}${WS_HEALTH_PATH:-/actuator/health} || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 60s
networks:
- openisle-network
frontend_dev:
image: node:20
container_name: ${COMPOSE_PROJECT_NAME}-openisle-frontend-dev
working_dir: /app
env_file:
- ${ENV_FILE:-../.env}
command: sh -c "npm install && npm run dev"
volumes:
- ../frontend_nuxt:/app
- frontend-node-modules:/app/node_modules
ports:
- "${FRONTEND_PORT:-3000}:3000"
depends_on:
springboot:
condition: service_healthy
websocket-service:
condition: service_healthy
networks:
- openisle-network
profiles:
- dev
frontend_service:
build:
context: ..
dockerfile: docker/frontend-service.Dockerfile
args:
NUXT_ENV: ${NUXT_ENV:-staging}
container_name: ${COMPOSE_PROJECT_NAME}-openisle-frontend
env_file:
- ${ENV_FILE:-../.env}
ports:
- "${FRONTEND_PORT:-3000}:3000"
depends_on:
springboot:
condition: service_healthy
websocket-service:
condition: service_healthy
restart: unless-stopped
loopback_8080:
image: alpine/socat
container_name: ${COMPOSE_PROJECT_NAME}-loopback-8080
# 监听“frontend_dev 容器自身的” 127.0.0.1:8080 → 转发到 springboot:8080
command:
- -d
- -d
- -ly
- TCP4-LISTEN:8080,bind=127.0.0.1,reuseaddr,fork
- TCP4:springboot:8080
depends_on:
springboot:
condition: service_healthy
network_mode: "service:frontend_dev"
profiles: ["dev"]
healthcheck:
test: ["CMD", "sh", "-c", "nc -z 127.0.0.1 8080"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
loopback_8082:
image: alpine/socat
container_name: ${COMPOSE_PROJECT_NAME}-loopback-8082
# 监听 127.0.0.1:8082 → 转发到 websocket-service:8082WS 纯 TCP 可直接过)
command:
- -d
- -d
- -ly
- TCP4-LISTEN:8082,bind=127.0.0.1,reuseaddr,fork
- TCP4:websocket-service:8082
depends_on:
websocket-service:
condition: service_healthy
network_mode: "service:frontend_dev"
profiles: ["dev"]
healthcheck:
test: ["CMD", "sh", "-c", "nc -z 127.0.0.1 8082"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
networks:
openisle-network:
name: "${COMPOSE_PROJECT_NAME}_net"
driver: bridge
volumes:
mysql-data:
name: "${COMPOSE_PROJECT_NAME}_mysql-data"
maven-repo:
name: "${COMPOSE_PROJECT_NAME}_maven-repo"
redis-data:
name: "${COMPOSE_PROJECT_NAME}_redis-data"
rabbitmq-data:
name: "${COMPOSE_PROJECT_NAME}_rabbitmq-data"
websocket-maven-repo:
name: "${COMPOSE_PROJECT_NAME}_websocket-maven-repo"
frontend-node-modules:
name: "${COMPOSE_PROJECT_NAME}_frontend-node-modules"
frontend-service-node-modules:
name: "${COMPOSE_PROJECT_NAME}_frontend-service-node-modules"
frontend-static:
name: "${COMPOSE_PROJECT_NAME}_frontend-static"
opensearch-data:
name: "${COMPOSE_PROJECT_NAME}_opensearch-data"
opensearch-snapshots:
name: "${COMPOSE_PROJECT_NAME}_opensearch-snapshots"

View File

@@ -0,0 +1,39 @@
# ==== builder ====
FROM node:20-bullseye AS builder
WORKDIR /app
# 通过构建参数选择环境staging / production默认 staging
ARG NUXT_ENV=staging
ENV NODE_ENV=production \
NUXT_TELEMETRY_DISABLED=1
# 复制源代码(假设仓库根目录包含 frontend_nuxt
# 构建上下文由 docker-compose 指向仓库根目录
COPY ./frontend_nuxt/package*.json /app/
RUN npm ci
# 拷贝剩余代码
COPY ./frontend_nuxt/ /app/
# 若存在环境样例文件,则在构建期复制为 .env你也可以用 --build-arg 覆盖)
RUN if [ -f ".env.${NUXT_ENV}.example" ]; then cp ".env.${NUXT_ENV}.example" .env; fi
# 构建 SSR产物在 .output
RUN npm run build
# ==== runner ====
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production \
NUXT_TELEMETRY_DISABLED=1 \
PORT=3000 \
HOST=0.0.0.0
# 复制构建产物
COPY --from=builder /app/.output /app/.output
# 健康检查(简洁起见,探测首页)
HEALTHCHECK --interval=10s --timeout=5s --retries=30 CMD wget -qO- http://127.0.0.1:${PORT}/ >/dev/null 2>&1 || exit 1
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

View File

@@ -0,0 +1,10 @@
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci
skip-character-set-client-handshake
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4

View File

@@ -0,0 +1 @@
[rabbitmq_management, rabbitmq_prometheus].

View File

@@ -0,0 +1,6 @@
# 管理插件加载 definitions仅空库时生效
management.load_definitions = /etc/rabbitmq/definitions.json
# (可选)禁用管理老式统计采集,转 Prometheus避免弃用告警
management_agent.disable_metrics_collector = true
management.disable_stats = true

View File

@@ -0,0 +1,31 @@
{
"users": [
{ "name": "nagisa", "password": "nagisa", "tags": "administrator" }
],
"vhosts": [{ "name": "/" }],
"permissions": [
{ "user": "nagisa", "vhost": "/", "configure": ".*", "write": ".*", "read": ".*" }
],
"queues": [
{ "name": "notifications-queue", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-0", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-1", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-2", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-3", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-4", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-5", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-6", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-7", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-8", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-9", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-a", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-b", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-c", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-d", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-e", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} },
{ "name": "notifications-queue-f", "vhost": "/", "durable": true, "auto_delete": false, "arguments": {} }
],
"exchanges": [],
"bindings": []
}

View File

@@ -1,12 +1,3 @@
; 本地部署后端
NUXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8080
NUXT_PUBLIC_WEBSOCKET_URL=https://127.0.0.1:8082
NUXT_PUBLIC_WEBSITE_BASE_URL=http://localhost:3000
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
# NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
; 本地
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liOlrZnPKRF7s7NN
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135
# 环境变量已统一迁移至仓库根目录的 .env.*.example 文件。
# 如需在本地运行 Nuxt请复制对应的示例文件到项目根目录
# cp ../.env.dev.example ../.env

View File

@@ -1,19 +1,5 @@
; 本地部署后端
; NUXT_PUBLIC_API_BASE_URL=https://127.0.0.1:8081
; 预发环境后端
; NUXT_PUBLIC_API_BASE_URL=https://staging.open-isle.com
; 生产环境后端
NUXT_PUBLIC_API_BASE_URL=https://open-isle.com
; 生产环境ws后端
NUXT_PUBLIC_WEBSOCKET_URL=https://open-isle.com/websocket
; 预发环境
; NUXT_PUBLIC_WEBSITE_BASE_URL=https://staging.open-isle.com
; 正式环境/生产环境
NUXT_PUBLIC_WEBSITE_BASE_URL=https://open-isle.com
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135
# 环境变量已统一迁移至仓库根目录的 .env.*.example 文件。
# 根据环境选择对应文件复制至项目根目录:
# cp ../.env.dev.example ../.env
# cp ../.env.staging.example ../.env
# cp ../.env.production.example ../.env

View File

@@ -1,13 +1,3 @@
; 生产环境后端
NUXT_PUBLIC_API_BASE_URL=https://www.open-isle.com
; 正式环境/生产环境
NUXT_PUBLIC_WEBSITE_BASE_URL=https://www.open-isle.com
; 生产环境ws后端
NUXT_PUBLIC_WEBSOCKET_URL=https://www.open-isle.com/websocket
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135
# 环境变量已统一迁移至仓库根目录的 .env.*.example 文件。
# 如需配置生产环境,请复制并修改对应示例文件:
# cp ../.env.production.example ../.env

View File

@@ -1,17 +1,3 @@
; 本地部署后端
; NUXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8080
; 预发环境后端
NUXT_PUBLIC_API_BASE_URL=https://staging.open-isle.com
; 预发环境ws后端
NUXT_PUBLIC_WEBSOCKET_URL=https://staging.open-isle.com/websocket
; 预发环境
NUXT_PUBLIC_WEBSITE_BASE_URL=https://staging.open-isle.com
NUXT_PUBLIC_GOOGLE_CLIENT_ID=777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com
NUXT_PUBLIC_GITHUB_CLIENT_ID=Ov23liVkO1NPAX5JyWxJ
NUXT_PUBLIC_DISCORD_CLIENT_ID=1394985417044000779
NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ
NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135
# 环境变量已统一迁移至仓库根目录的 .env.*.example 文件。
# 如需配置预发环境,请复制并修改对应示例文件:
# cp ../.env.staging.example ../.env

View File

@@ -119,7 +119,7 @@ export default {
.cropper-btn {
padding: 6px 12px;
border-radius: 4px;
border-radius: 10px;
color: var(--primary-color);
border: none;
background: transparent;
@@ -128,7 +128,7 @@ export default {
.cropper-btn.primary {
background: var(--primary-color);
color: var(--text-color);
color: #ffff;
border-color: var(--primary-color);
}

View File

@@ -297,6 +297,7 @@ export default {
border: 1px solid var(--normal-border-color);
border-radius: 5px;
padding: 5px 10px;
margin-bottom: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
@@ -315,8 +316,9 @@ export default {
right: 0;
background: var(--background-color);
border: 1px solid var(--normal-border-color);
border-radius: 5px;
z-index: 10000;
max-height: 200px;
max-height: 300px;
min-width: 350px;
overflow-y: auto;
}

View File

@@ -9,7 +9,9 @@ export default defineNuxtConfig({
modules: ['@nuxt/image'],
runtimeConfig: {
public: {
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL || '',
apiBaseUrl: process.server
? process.env.NUXT_PUBLIC_API_BASE_URL_SSR
: process.env.NUXT_PUBLIC_API_BASE_URL,
websocketUrl: process.env.NUXT_PUBLIC_WEBSOCKET_URL || '',
websiteBaseUrl: process.env.NUXT_PUBLIC_WEBSITE_BASE_URL || '',
googleClientId: process.env.NUXT_PUBLIC_GOOGLE_CLIENT_ID || '',

View File

@@ -157,6 +157,7 @@ const SANITIZE_CFG = {
'th',
'video',
'source',
'iframe',
],
// 允许的属性
allowedAttributes: {
@@ -180,6 +181,16 @@ const SANITIZE_CFG = {
'crossorigin',
],
source: ['src', 'type'],
iframe: [
'src',
'title',
'width',
'height',
'allow',
'allowfullscreen',
'frameborder',
'referrerpolicy',
],
},
// 允许的类名(保留你的样式钩子)
allowedClasses: {

174
nginx/openisle Normal file
View File

@@ -0,0 +1,174 @@
server {
listen 443 ssl;
server_name open-isle.com www.open-isle.com;
ssl_certificate /etc/letsencrypt/live/open-isle.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/open-isle.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header Cache-Control "no-store" always;
add_header X-Upstream $upstream_addr always;
}
location ^~ /api/ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# 升级所需
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 统一透传这些头(你在 /api/ 有,/api/ws 也要有)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
}
# 2) SockJS包含 /info、/iframe.html、/.../websocket 等)
location ^~ /api/sockjs {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
# 如要同源 iframe 回退,下面两行二选一(或者交给 Spring Security 的 sameOrigin
# proxy_hide_header X-Frame-Options;
# add_header X-Frame-Options "SAMEORIGIN" always;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# 通过 https://open-isle.com/rabbitmq/ 访问管理界面
location ^~ /rabbitmq/ {
# 关键点proxy_pass 以 "/" 结尾,保留后缀子路径映射
proxy_pass http://127.0.0.1:15672/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
# 把上游返回的绝对重定向 /... 改写为 /rabbitmq/...
proxy_redirect ~^(/.*)$ /rabbitmq$1;
# 为了做 HTML/CSS/JS 内绝对路径替换,需要关闭压缩
proxy_set_header Accept-Encoding "";
# 将页面中以 "/" 开头的 src/href 替换为 "/rabbitmq/..."
sub_filter_types text/html text/css application/javascript;
sub_filter 'href="/' 'href="/rabbitmq/';
sub_filter 'src="/' 'src="/rabbitmq/';
sub_filter_once off;
# 建议对管理台再加一道保护(可选)
# auth_basic "RabbitMQ Console";
# auth_basic_user_file /etc/nginx/.htpasswd;
}
# 通过 https://open-isle.com/docker/ 访问 Portainer上游是自签名 HTTPS
location ^~ /docker/ {
proxy_pass https://127.0.0.1:19000/; # 末尾 / 保留子路径
proxy_http_version 1.1;
# 上游是自签证书,关闭校验(仅内网/自签场景)
proxy_ssl_verify off;
# 透传头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# WebSocket/事件流Portainer 某些功能会用到)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
# 把上游返回的绝对重定向 /... 改写为 /docker/...
proxy_redirect ~^(/.*)$ /docker$1;
# 为了替换 HTML/CSS/JS 中的绝对路径,需要关闭压缩
proxy_set_header Accept-Encoding "";
# 将页面中以 "/" 开头的 src/href 替换为 "/docker/..."
sub_filter_types text/html text/css application/javascript;
sub_filter 'href="/' 'href="/docker/';
sub_filter 'src="/' 'src="/docker/';
sub_filter_once off;
# 可选:再加一道基本认证
# auth_basic "Portainer";
# auth_basic_user_file /etc/nginx/.htpasswd;
}
# ---------- WEBSOCKET GATEWAY TO :8082 ----------
location ^~ /websocket/ {
proxy_pass http://127.0.0.1:8082/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
add_header Cache-Control "no-store" always;
}
}
server {
listen 80;
server_name open-isle.com www.open-isle.com;
return 301 https://$host$request_uri;
}

133
nginx/openisle-staging Normal file
View File

@@ -0,0 +1,133 @@
# 放在 http { } 里一次定义
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name staging.open-isle.com www.staging.open-isle.com;
ssl_certificate /etc/letsencrypt/live/staging.open-isle.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/staging.open-isle.com/privkey.pem;
# ssl_certificate /etc/letsencrypt/live/open-isle.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/open-isle.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# ---------- SSR ----------
location / {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
# 正确的升级头(仅在有 Upgrade 时)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 透传真实主机/协议/源 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
# 合理超时,避免 SSR 首屏慢查询导致 502/504
proxy_read_timeout 120s;
proxy_send_timeout 120s;
add_header Cache-Control "no-store" always;
add_header X-Upstream $upstream_addr always;
}
# 1) 原生 WebSocket
location ^~ /api/ws {
proxy_pass http://127.0.0.1:8081; # 不要尾随 /,保留原样 URI
proxy_http_version 1.1;
# 升级所需
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# 统一透传这些头(你在 /api/ 有,/api/ws 也要有)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
}
# 2) SockJS包含 /info、/iframe.html、/.../websocket 等)
location ^~ /api/sockjs {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
# 如要同源 iframe 回退,下面两行二选一(或者交给 Spring Security 的 sameOrigin
# proxy_hide_header X-Frame-Options;
# add_header X-Frame-Options "SAMEORIGIN" always;
}
# ---------- API ----------
location /api/ {
proxy_pass http://127.0.0.1:8081/api/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
proxy_no_cache 1;
proxy_cache_bypass 1;
}
# ---------- WEBSOCKET GATEWAY TO :8083 ----------
location ^~ /websocket/ {
proxy_pass http://127.0.0.1:8083/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;
proxy_cache off;
add_header Cache-Control "no-store" always;
}
}

View File

@@ -51,10 +51,10 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-actuator</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@@ -43,6 +43,8 @@ public class SecurityConfig {
"http://30.211.97.238",
"http://192.168.7.98",
"http://192.168.7.98:3000",
"http://frontend_dev:3000",
"http://frontend_service:3000",
websiteUrl,
websiteUrl.replace("://www.", "://")
));

View File

@@ -1,4 +1,4 @@
server.port=${SERVER_PORT:8082}
server.port=${WEBSOCKET_PORT:8082}
# 服务器配置
spring.application.name=websocket-service
@@ -19,4 +19,7 @@ logging.level.org.springframework.messaging=${MESSAGING_LOG_LEVEL:DEBUG}
logging.level.org.springframework.web.socket=${WEBSOCKET_LOG_LEVEL:DEBUG}
# 网站 URL 配置
app.website-url=${WEBSITE_URL:https://www.open-isle.com}
app.website-url=${WEBSITE_URL:https://www.open-isle.com}
management.endpoints.web.exposure.include=health,info
management.endpoint.health.probes.enabled=true

View File

@@ -1,3 +1,5 @@
# 所有环境变量已集中在仓库根目录的 .env.*.example 文件。
# 如需在独立环境中运行,可参考以下字段:
SERVER_PORT=<your-server-port>
# RabbitMQ 配置