diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31a401273..e5cafb6ed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,113 +1,149 @@ -#### **⚠️注意:仅想修改前端的朋友可不用部署后端服务** +- [前置工作](#前置工作) +- [启动后端服务](#启动后端服务) + - [本地 IDEA](#本地-idea) + - [环境变量配置](#环境变量配置) + - [配置参数](#配置参数) + - [配置 MySQL](#配置-mysql) + - [Docker 环境](#docker-环境) + - [环境变量配置](#环境变量配置-1) + - [构建并启动镜像](#构建并启动镜像) +- [启动前端服务](#启动前端服务) + - [环境变量配置](#环境变量配置-2) + - [安装依赖和运行](#安装依赖和运行) +- [其他配置](#其他配置) -## 如何部署 +## 前置工作 -> Step1 先克隆仓库 +先克隆仓库: ```shell git clone https://github.com/nagisa77/OpenIsle.git cd OpenIsle ``` -> Step2 后端部署 +## 启动后端服务 -```shell -cd backend +启动后端服务有多种方式,选择一种即可。 + +> [!IMPORTANT] +> 仅想修改前端的朋友可不用部署后端服务。转到 [启动前端服务](#启动前端服务) 章节。 + +### 本地 IDEA + +IDEA 打开 `backend/` 文件夹。 + +#### 环境变量配置 + +1. 生成环境变量文件 + + ```shell + cp open-isle.env.example open-isle.env + ``` + + `open-isle.env.example` 是环境变量模板,`open-isle.env` 才是真正读取的内容 + +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 ``` -以IDEA编辑器为例,IDEA打开backend文件夹。 +另一种方式是修改 `.properities` 文件(但不建议),位于 `src/main/application.properties`,该配置同样来源于 `open-isle.env`,但修改 `.properties` 文件会被 Git 追踪。 -- 设置VM Option,最好运行在其他端口,非8080,这里设置8081 +![配置数据库](assets/contributing/backend_img_5.png) + +#### 配置参数 + +- 设置 JDK 版本为 java 17 +- 设置 VM Option,最好运行在其他端口,非 `8080`,这里设置 `8081` + + ```shell + -Dserver.port=8081 + ``` + +![配置1](assets/contributing/backend_img_3.png) + +![配置2](assets/contributing/backend_img_2.png) + +#### 配置 MySQL + +1. 本机配置 MySQL 服务(网上很多教程,忽略) + + + 可以用 Laragon,自带 MySQL 包括 Nodejs,版本建议 `6.x`,`7` 以后需要 Lisence + + [下载地址](https://github.com/leokhoa/laragon/releases) + + > [!TIP] + > 如果不知道怎么配置数据库可以参考 [Docker 环境](#docker-环境) 章节 + +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.sql 脚本,导入基本的数据 + + ![初始化脚本](assets/contributing/resources_img.png) + +4. 处理完环境问题直接跑起来就能通了 + + ![运行画面](assets/contributing/backend_img_4.png) + +### Docker 环境 +#### 环境变量配置 + +同上,见 [环境变量配置](#环境变量配置) + +#### 构建并启动镜像 ```shell --Dserver.port=8081 +cd docker +docker compose up -d ``` -![CleanShot 2025-08-04 at 11 .35.49.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/4cf210cfc6ea478a80dfc744c85ccdc4.png) - -- 设置jdk版本为java 17 - -![CleanShot 2025-08-04 at 11 .38.03@2x.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/392eeec753ae436ca12a78f750dfea2d.png) - -- 本机配置MySQL服务(网上很多教程,忽略) -- 设置环境变量.env 文件 或.properties 文件(二选一) - -1. 环境变量文件生成 - -```shell -cp open-isle.env.example open-isle.env -``` - -修改环境变量,留下需要的,比如你要开发Google登录业务,就需要谷歌相关的变量,数据库是一定要的 - -![CleanShot 2025-08-04 at 11 .41.36@2x.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/896c8363b6e64ea19d18c12ec4dae2b4.png) - -应用环境文件, 选择刚刚的`open-isle.env` - -![CleanShot 2025-08-04 at 11 .44.41.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/f588e37838014a6684c141605639b9fa.png) - -2. 直接修改 .properities 文件 - -位置src/main/application.properties, 数据库需要修改标红处,其他按需修改 - -![CleanShot 2025-08-04 at 11 .47.11@2x.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/28c3104448a245419e0b06aee861abb4.png) - -处理完环境问题直接跑起来就能通了 - -![CleanShot 2025-08-04 at 11 .49.01@2x.png](https://openisle-1307107697.cos.accelerate.myqcloud.com/dynamic_assert/2c945eae44b1477db09e80fc96b5e02d.png) - -> Step3 前端部署 +## 启动前端服务 **⚠️ 环境要求:Node.js 版本最低 20.0.0(因为 Nuxt 框架要求)** -前端可以依赖本机部署的后端,也可以直接调用线上的后端接口 - ```shell -cd ../frontend_nuxt/ +cd frontend_nuxt/ ``` -copy环境.env文件 +### 环境变量配置 -```shell -cp .env.staging.example .env -``` +前端可以依赖本机部署的后端,也可以直接调用线上的后端接口。 -1. 依赖本机部署的后端:打开本文件夹,修改.env 修改为瞄准本机后端端口 +- 利用预发环境:**(⚠️强烈推荐只部署前端的朋友使用该环境)** -```yaml -; 本地部署后端 -NUXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8081 -; 预发环境后端 -; NUXT_PUBLIC_API_BASE_URL=https://staging.open-isle.com -; 生产环境后端 -; NUXT_PUBLIC_API_BASE_URL=https://www.open-isle.com -; 开发环境 -NUXT_PUBLIC_WEBSITE_BASE_URL=localhost:3000 -``` + ```shell + cp .env.staging.example .env + ``` -2. 依赖预发环境后台环境 +- 利用生产环境 -**(⚠️强烈推荐只部署前端的朋友使用该环境)** + ```shell + cp .env.production.example .env + ``` -```yaml -; 本地部署后端 -; 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://www.open-isle.com -``` +- 利用本地环境 -4. 依赖线上后台环境 + ```shell + cp .env.dev.example .env + ``` -```yaml -; 本地部署后端 -; 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://www.open-isle.com -``` +### 安装依赖和运行 + +前端安装编译并启动服务 ```shell # 安装依赖 @@ -118,3 +154,21 @@ npm run dev ``` 如此一来,浏览器访问 http://127.0.0.1:3000 即可访问前端页面 + +## 其他配置 + +配置第三方登录,这里以 GitHub 为例 + +- 修改 `application.properties` 配置 + + ![后端配置](assets/contributing/backend_img.png) + +- 修改 `.env` 配置 + + ![前端](assets/contributing/fontend_img.png) + +- 配置第三方登录回调地址 + + ![github配置](assets/contributing/github_img.png) + + ![github配置2](assets/contributing/github_img_2.png) \ No newline at end of file diff --git a/assets/contributing/backend_img.png b/assets/contributing/backend_img.png new file mode 100644 index 000000000..5084f5ff6 Binary files /dev/null and b/assets/contributing/backend_img.png differ diff --git a/assets/contributing/backend_img_2.png b/assets/contributing/backend_img_2.png new file mode 100644 index 000000000..94cd3fb48 Binary files /dev/null and b/assets/contributing/backend_img_2.png differ diff --git a/assets/contributing/backend_img_3.png b/assets/contributing/backend_img_3.png new file mode 100644 index 000000000..948a44c03 Binary files /dev/null and b/assets/contributing/backend_img_3.png differ diff --git a/assets/contributing/backend_img_4.png b/assets/contributing/backend_img_4.png new file mode 100644 index 000000000..263352f10 Binary files /dev/null and b/assets/contributing/backend_img_4.png differ diff --git a/assets/contributing/backend_img_5.png b/assets/contributing/backend_img_5.png new file mode 100644 index 000000000..a2aa10377 Binary files /dev/null and b/assets/contributing/backend_img_5.png differ diff --git a/assets/contributing/backend_img_6.png b/assets/contributing/backend_img_6.png new file mode 100644 index 000000000..66d675da5 Binary files /dev/null and b/assets/contributing/backend_img_6.png differ diff --git a/assets/contributing/backend_img_7.png b/assets/contributing/backend_img_7.png new file mode 100644 index 000000000..1a6c75385 Binary files /dev/null and b/assets/contributing/backend_img_7.png differ diff --git a/assets/contributing/fontend_img.png b/assets/contributing/fontend_img.png new file mode 100644 index 000000000..d622416ea Binary files /dev/null and b/assets/contributing/fontend_img.png differ diff --git a/assets/contributing/github_img.png b/assets/contributing/github_img.png new file mode 100644 index 000000000..b6b9fc688 Binary files /dev/null and b/assets/contributing/github_img.png differ diff --git a/assets/contributing/github_img_2.png b/assets/contributing/github_img_2.png new file mode 100644 index 000000000..288b59489 Binary files /dev/null and b/assets/contributing/github_img_2.png differ diff --git a/assets/contributing/resources_img.png b/assets/contributing/resources_img.png new file mode 100644 index 000000000..3b1d95c00 Binary files /dev/null and b/assets/contributing/resources_img.png differ diff --git a/backend/open-isle.env.example b/backend/open-isle.env.example index a3516e2b5..3535f4cd2 100644 --- a/backend/open-isle.env.example +++ b/backend/open-isle.env.example @@ -1,7 +1,11 @@ +# === Spring Boot === +SERVER_PORT=8080 + # === Database === MYSQL_URL=jdbc:mysql://<数据库地址>:<端口>/<数据库名>?useUnicode=yes&characterEncoding=UTF-8&useInformationSchema=true&useSSL=false&serverTimezone=UTC MYSQL_USER=<数据库用户名> MYSQL_PASSWORD=<数据库密码> +MYSQL_ROOT_PASSWORD=<数据库 root 用户密码> # === JWT === JWT_SECRET= diff --git a/backend/pom.xml b/backend/pom.xml index ab71a3266..f162908bc 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -114,6 +114,11 @@ bcprov-jdk15on 1.70 + + org.springdoc + springdoc-openapi-starter-webmvc-api + 2.2.0 + @@ -141,6 +146,26 @@ + + + + org.springdoc + springdoc-openapi-maven-plugin + 1.4 + + + + generate + + + + + + http://localhost:8080/v3/api-docs + openapi.json + ${project.build.directory} + + diff --git a/backend/src/main/java/com/openisle/config/OpenApiConfig.java b/backend/src/main/java/com/openisle/config/OpenApiConfig.java new file mode 100644 index 000000000..5af5cf000 --- /dev/null +++ b/backend/src/main/java/com/openisle/config/OpenApiConfig.java @@ -0,0 +1,48 @@ +package com.openisle.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class OpenApiConfig { + + @Value("${springdoc.info.title}") + private String title; + + @Value("${springdoc.info.description}") + private String description; + + @Value("${springdoc.info.version}") + private String version; + + @Value("${springdoc.info.scheme}") + private String scheme; + + @Value("${springdoc.info.header}") + private String header; + + @Bean + public OpenAPI openAPI() { + SecurityScheme securityScheme = new SecurityScheme() + .type(SecurityScheme.Type.HTTP) + .scheme(scheme.toLowerCase()) + .bearerFormat("JWT") + .in(SecurityScheme.In.HEADER) + .name(header); + + return new OpenAPI() + .info(new Info() + .title(title) + .description(description) + .version(version)) + .components(new Components() + .addSecuritySchemes("JWT", securityScheme)) + .addSecurityItem(new SecurityRequirement().addList("JWT")); + } +} diff --git a/backend/src/main/java/com/openisle/config/SecurityConfig.java b/backend/src/main/java/com/openisle/config/SecurityConfig.java index a864b6ba8..72421850c 100644 --- a/backend/src/main/java/com/openisle/config/SecurityConfig.java +++ b/backend/src/main/java/com/openisle/config/SecurityConfig.java @@ -110,6 +110,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers("/api/ws/**", "/api/sockjs/**").permitAll() + .requestMatchers("/v3/api-docs/**").permitAll() .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/comments/**").permitAll() @@ -180,7 +181,8 @@ public class SecurityConfig { return; } } else if (!uri.startsWith("/api/auth") && !publicGet - && !uri.startsWith("/api/ws") && !uri.startsWith("/api/sockjs")) { + && !uri.startsWith("/api/ws") && !uri.startsWith("/api/sockjs") + && !uri.startsWith("/v3/api-docs")) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType("application/json"); response.getWriter().write("{\"error\": \"Missing token\"}"); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 62692b596..b0f9a0a79 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,3 +1,6 @@ +# for spring boot +server.port=${SERVER_PORT:8080} + # for mysql logging.level.root=${LOG_LEVEL:INFO} logging.level.com.openisle.service.CosImageUploader=DEBUG @@ -93,3 +96,13 @@ spring.rabbitmq.password=${RABBITMQ_PASSWORD:guest} # RabbitMQ 队列配置 - 修改为非持久化以匹配现有队列 rabbitmq.queue.durable=true rabbitmq.sharding.enabled=true + +# springdoc-openapi-starter-webmvc-api +# see https://springdoc.org/#springdoc-openapi-core-properties +springdoc.api-docs.path=/v3/api-docs +springdoc.api-docs.enabled=true +springdoc.info.title=OpenIsle +springdoc.info.description=OpenIsle Open API Documentation +springdoc.info.version=0.0.1 +springdoc.info.scheme=Bearer +springdoc.info.header=Authorization diff --git a/backend/src/main/resources/db/init/init_script.sql b/backend/src/main/resources/db/init/init_script.sql new file mode 100644 index 000000000..bf3ab1099 --- /dev/null +++ b/backend/src/main/resources/db/init/init_script.sql @@ -0,0 +1,27 @@ +-- 2025-09-02 +-- 本地化开发,初始化脚本 +-- 抽奖的时候奖品图片是必须的,把相关代码注释掉即可跳过check + +-- 清空users表 +DELETE FROM `users`; +-- 插入用户,两个普通用户,一个管理员 +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$m.lLbT3wFtnzFMi7JqN17ecv/dzH704WzU1f/xvQ0nVz4XxTXPT0K', NULL, 110, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'ADMIN', '管理员', NULL, b'1'), + (2, b'1', '', '2025-09-03 16:08:17.426430', 'PIONEER', 'usermail2@openisle.com', 70, NULL, '$2a$10$m.lLbT3wFtnzFMi7JqN17ecv/dzH704WzU1f/xvQ0nVz4XxTXPT0K', NULL, 110, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'USER', '普通用户2', NULL, b'1'), + (3, b'1', '', '2025-09-02 17:21:21.617666', 'PIONEER', 'usermail1@openisle.com', 40, NULL, '$2a$10$m.lLbT3wFtnzFMi7JqN17ecv/dzH704WzU1f/xvQ0nVz4XxTXPT0K', NULL, 40, '测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', 'USER', '普通用户1', NULL, b'1'); + +-- 清空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表 +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); \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 000000000..909d04f65 --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,41 @@ +services: + # MySQL service + mysql: + image: mysql:8.0 + container_name: openisle-mysql + restart: always + env_file: + - ../backend/open-isle.env + ports: + - '3306:3306' + volumes: + - mysql-data:/var/lib/mysql + - ../backend/src/main/resources/db/init/init_script.sql:/docker-entrypoint-initdb.d/init.sql + networks: + - openisle-network + + # Java spring boot service + springboot: + image: maven:3.9-eclipse-temurin-17 + container_name: openisle-springboot + working_dir: /app + volumes: + - ../backend:/app + - maven-repo:/root/.m2 + ports: + - '8080:8080' + env_file: + - ../backend/open-isle.env + depends_on: + - mysql + command: mvn clean spring-boot:run -Dmaven.test.skip=true + networks: + - openisle-network + +networks: + openisle-network: + driver: bridge + +volumes: + mysql-data: + maven-repo: diff --git a/frontend_nuxt/.env.dev.example b/frontend_nuxt/.env.dev.example new file mode 100644 index 000000000..3a38feb58 --- /dev/null +++ b/frontend_nuxt/.env.dev.example @@ -0,0 +1,9 @@ +; 本地部署后端 +NUXT_PUBLIC_API_BASE_URL=https://127.0.0.1:8081 +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_DISCORD_CLIENT_ID=1394985417044000779 +NUXT_PUBLIC_TWITTER_CLIENT_ID=ZTRTU05KSk9KTTJrTTdrVC1tc1E6MTpjaQ +NUXT_PUBLIC_TELEGRAM_BOT_ID=8450237135 diff --git a/frontend_nuxt/.env.production.example b/frontend_nuxt/.env.production.example new file mode 100644 index 000000000..273a2dff6 --- /dev/null +++ b/frontend_nuxt/.env.production.example @@ -0,0 +1,11 @@ + +; 生产环境后端 +NUXT_PUBLIC_API_BASE_URL=https://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 diff --git a/frontend_nuxt/.env.staging.example b/frontend_nuxt/.env.staging.example index 726f59e44..0dcca3f41 100644 --- a/frontend_nuxt/.env.staging.example +++ b/frontend_nuxt/.env.staging.example @@ -1,15 +1,11 @@ ; 本地部署后端 ; NUXT_PUBLIC_API_BASE_URL=https://127.0.0.1:8081 ; NUXT_PUBLIC_WEBSOCKET_URL=https://127.0.0.1:8082 + ; 预发环境后端 NUXT_PUBLIC_API_BASE_URL=https://staging.open-isle.com -; 生产环境后端 -; NUXT_PUBLIC_API_BASE_URL=https://open-isle.com - ; 预发环境 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