diff --git a/README.md b/README.md
index 143dc1fd6..e625162f9 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ OpenIsle 基于 Spring Boot 构建,提供社区后台常见的注册、登录
* **用户体系**:注册、登录,密码使用 BCrypt 加密
* **JWT 认证**:登录后获得 Token,接口通过 `Authorization: Bearer` 认证
* **Google 登录**:支持使用 Google OAuth 登录
+* **GitHub 登录**:支持使用 GitHub OAuth 登录
* **邮件通知**:抽象 `EmailSender`,默认实现基于 Resend
* **角色权限**:内置 `ADMIN` 与 `USER`,管理员接口以 `/api/admin/**` 提供
* **文章与评论**:支持分类、评论及多级回复
@@ -43,10 +44,13 @@ OpenIsle 基于 Spring Boot 构建,提供社区后台常见的注册、登录
- `MYSQL_USER`:数据库用户名
- `MYSQL_PASSWORD`:数据库密码
- `RESEND_API_KEY`:Resend 邮件服务 API Key
- - `COS_BASE_URL`:腾讯云 COS 访问域名
- - `GOOGLE_CLIENT_ID`:Google OAuth 客户端 ID
- - `VUE_APP_GOOGLE_CLIENT_ID`:前端 Google OAuth 客户端 ID
- - `JWT_SECRET`:JWT 签名密钥
+ - `COS_BASE_URL`:腾讯云 COS 访问域名
+ - `GOOGLE_CLIENT_ID`:Google OAuth 客户端 ID
+ - `VUE_APP_GOOGLE_CLIENT_ID`:前端 Google OAuth 客户端 ID
+ - `GITHUB_CLIENT_ID`:GitHub OAuth 客户端 ID
+ - `GITHUB_CLIENT_SECRET`:GitHub OAuth 客户端密钥
+ - `VUE_APP_GITHUB_CLIENT_ID`:前端 GitHub OAuth 客户端 ID
+ - `JWT_SECRET`:JWT 签名密钥
- `JWT_EXPIRATION`:JWT 过期时间(毫秒)
- `PASSWORD_STRENGTH`:密码强度(LOW、MEDIUM、HIGH)
- `CAPTCHA_ENABLED`:是否启用验证码(true/false)
@@ -68,6 +72,7 @@ mvn spring-boot:run
- `POST /api/auth/register`:注册新用户
- `POST /api/auth/login`:登录并获取 Token
- `POST /api/auth/google`:Google 登录并获取 Token
+- `POST /api/auth/github`:GitHub 登录并获取 Token
- `GET /api/config`:查看验证码开关配置
- 需要认证的接口示例:`GET /api/hello`(需 `Authorization` 头)
- 管理员接口示例:`GET /api/admin/hello`
diff --git a/open-isle-cli/src/assets/icons/github.svg b/open-isle-cli/src/assets/icons/github.svg
new file mode 100644
index 000000000..c4af465ca
--- /dev/null
+++ b/open-isle-cli/src/assets/icons/github.svg
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+ Page not found · GitHub
+
+
+
+
+
+
+

+
+
+
+
+

+
+

+
+

+
+

+
+

+
+

+
+

+
+
+
+
+
+
+
diff --git a/open-isle-cli/src/main.js b/open-isle-cli/src/main.js
index b284c951d..09b01c013 100644
--- a/open-isle-cli/src/main.js
+++ b/open-isle-cli/src/main.js
@@ -18,6 +18,7 @@ export const API_PORT = 8081
// export const API_BASE_URL = API_PORT ? `${API_DOMAIN}:${API_PORT}` : API_DOMAIN
export const API_BASE_URL = "";
export const GOOGLE_CLIENT_ID = '777830451304-nt8afkkap18gui4f9entcha99unal744.apps.googleusercontent.com'
+export const GITHUB_CLIENT_ID = ''
export const toast = useToast()
initTheme()
diff --git a/open-isle-cli/src/router/index.js b/open-isle-cli/src/router/index.js
index a870d149c..cfeef79ed 100644
--- a/open-isle-cli/src/router/index.js
+++ b/open-isle-cli/src/router/index.js
@@ -11,6 +11,7 @@ import NewPostPageView from '../views/NewPostPageView.vue'
import SettingsPageView from '../views/SettingsPageView.vue'
import ProfileView from '../views/ProfileView.vue'
import NotFoundPageView from '../views/NotFoundPageView.vue'
+import GithubCallbackPageView from '../views/GithubCallbackPageView.vue'
const routes = [
{
@@ -68,6 +69,11 @@ const routes = [
name: 'users',
component: ProfileView
},
+ {
+ path: '/github-callback',
+ name: 'github-callback',
+ component: GithubCallbackPageView
+ },
{
path: '/404',
name: 'not-found',
diff --git a/open-isle-cli/src/utils/github.js b/open-isle-cli/src/utils/github.js
new file mode 100644
index 000000000..9b2e4bc78
--- /dev/null
+++ b/open-isle-cli/src/utils/github.js
@@ -0,0 +1,41 @@
+import { API_BASE_URL, GITHUB_CLIENT_ID, toast } from '../main'
+import { setToken, loadCurrentUser } from './auth'
+
+export function githubAuthorize(state = '') {
+ if (!GITHUB_CLIENT_ID) {
+ toast.error('GitHub 登录不可用')
+ return
+ }
+ const redirectUri = `${window.location.origin}/github-callback`
+ const url = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=user:email&state=${state}`
+ window.location.href = url
+}
+
+export async function githubExchange(code, state, reason) {
+ try {
+ const res = await fetch(`${API_BASE_URL}/api/auth/github`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ code, redirectUri: `${window.location.origin}/github-callback`, reason, state })
+ })
+ const data = await res.json()
+ if (res.ok && data.token) {
+ setToken(data.token)
+ await loadCurrentUser()
+ toast.success('登录成功')
+ return true
+ } else if (data.reason_code === 'NOT_APPROVED') {
+ toast.info('当前为注册审核模式,请填写注册理由')
+ sessionStorage.setItem('github_code', code)
+ return 'NEED_REASON'
+ } else if (data.reason_code === 'IS_APPROVING') {
+ toast.info('您的注册理由正在审批中')
+ return true
+ } else {
+ toast.error(data.error || '登录失败')
+ }
+ } catch (e) {
+ toast.error('登录失败')
+ }
+ return false
+}
diff --git a/open-isle-cli/src/views/GithubCallbackPageView.vue b/open-isle-cli/src/views/GithubCallbackPageView.vue
new file mode 100644
index 000000000..c8d285698
--- /dev/null
+++ b/open-isle-cli/src/views/GithubCallbackPageView.vue
@@ -0,0 +1,32 @@
+
+ GitHub 登录中...
+
+
+
+
+
diff --git a/open-isle-cli/src/views/LoginPageView.vue b/open-isle-cli/src/views/LoginPageView.vue
index e78462850..f8bc84df0 100644
--- a/open-isle-cli/src/views/LoginPageView.vue
+++ b/open-isle-cli/src/views/LoginPageView.vue
@@ -34,6 +34,10 @@
Google 登录
+
+

+
GitHub 登录
+
@@ -42,6 +46,7 @@
import { API_BASE_URL, toast } from '../main'
import { setToken, loadCurrentUser } from '../utils/auth'
import { googleSignIn } from '../utils/google'
+import { githubAuthorize } from '../utils/github'
import BaseInput from '../components/BaseInput.vue'
export default {
name: 'LoginPageView',
@@ -89,6 +94,9 @@ export default {
}, () => {
this.$router.push('/signup-reason?google=1')
})
+ },
+ loginWithGithub() {
+ githubAuthorize()
}
}
}
diff --git a/open-isle-cli/src/views/SignupPageView.vue b/open-isle-cli/src/views/SignupPageView.vue
index f68bbc7ff..348217030 100644
--- a/open-isle-cli/src/views/SignupPageView.vue
+++ b/open-isle-cli/src/views/SignupPageView.vue
@@ -76,6 +76,10 @@
Google 注册
+
+

+
GitHub 注册
+
@@ -83,6 +87,7 @@