mirror of
https://github.com/woodchen-ink/random-api-go.git
synced 2025-07-18 05:42:01 +08:00
重构Handlers结构,新增HandlePublicEndpoints方法以处理公开端点信息请求,优化URL统计缓存逻辑,更新Router以支持新路由,简化认证信息管理,移除不再使用的刷新令牌逻辑。
This commit is contained in:
parent
0465adbb36
commit
5176cc85db
@ -12,6 +12,7 @@ import (
|
||||
"random-api-go/stats"
|
||||
"random-api-go/utils"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -22,6 +23,19 @@ type Router interface {
|
||||
type Handlers struct {
|
||||
Stats *stats.StatsManager
|
||||
endpointService *services.EndpointService
|
||||
urlStatsCache map[string]struct {
|
||||
TotalURLs int `json:"total_urls"`
|
||||
}
|
||||
urlStatsCacheTime time.Time
|
||||
urlStatsMutex sync.RWMutex
|
||||
cacheDuration time.Duration
|
||||
}
|
||||
|
||||
func NewHandlers(statsManager *stats.StatsManager) *Handlers {
|
||||
return &Handlers{
|
||||
Stats: statsManager,
|
||||
cacheDuration: 5 * time.Minute, // 缓存5分钟
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handlers) HandleAPIRequest(w http.ResponseWriter, r *http.Request) {
|
||||
@ -116,10 +130,82 @@ func (h *Handlers) HandleStats(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handlers) HandlePublicEndpoints(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// 使用端点服务获取端点信息
|
||||
if h.endpointService == nil {
|
||||
h.endpointService = services.GetEndpointService()
|
||||
}
|
||||
|
||||
endpoints, err := h.endpointService.ListEndpoints()
|
||||
if err != nil {
|
||||
http.Error(w, "Error getting endpoints", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 只返回公开信息,不包含数据源配置
|
||||
type PublicEndpoint struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
IsActive bool `json:"is_active"`
|
||||
ShowOnHomepage bool `json:"show_on_homepage"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
var publicEndpoints []PublicEndpoint
|
||||
for _, endpoint := range endpoints {
|
||||
publicEndpoints = append(publicEndpoints, PublicEndpoint{
|
||||
ID: endpoint.ID,
|
||||
Name: endpoint.Name,
|
||||
URL: endpoint.URL,
|
||||
Description: endpoint.Description,
|
||||
IsActive: endpoint.IsActive,
|
||||
ShowOnHomepage: endpoint.ShowOnHomepage,
|
||||
SortOrder: endpoint.SortOrder,
|
||||
CreatedAt: endpoint.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: endpoint.UpdatedAt.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"data": publicEndpoints,
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
http.Error(w, "Error encoding response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handlers) HandleURLStats(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// 使用新的端点服务获取统计信息
|
||||
// 检查缓存是否有效
|
||||
h.urlStatsMutex.RLock()
|
||||
if h.urlStatsCache != nil && time.Since(h.urlStatsCacheTime) < h.cacheDuration {
|
||||
// 使用缓存数据
|
||||
cache := h.urlStatsCache
|
||||
h.urlStatsMutex.RUnlock()
|
||||
|
||||
if err := json.NewEncoder(w).Encode(cache); err != nil {
|
||||
http.Error(w, "Error encoding response", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
h.urlStatsMutex.RUnlock()
|
||||
|
||||
// 缓存过期或不存在,重新计算
|
||||
if h.endpointService == nil {
|
||||
h.endpointService = services.GetEndpointService()
|
||||
}
|
||||
@ -166,6 +252,12 @@ func (h *Handlers) HandleURLStats(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
h.urlStatsMutex.Lock()
|
||||
h.urlStatsCache = response
|
||||
h.urlStatsCacheTime = time.Now()
|
||||
h.urlStatsMutex.Unlock()
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
http.Error(w, "Error encoding response", http.StatusInternalServerError)
|
||||
return
|
||||
@ -186,4 +278,7 @@ func (h *Handlers) Setup(r *router.Router) {
|
||||
r.HandleFunc("/api/stats", h.HandleStats)
|
||||
r.HandleFunc("/api/urlstats", h.HandleURLStats)
|
||||
r.HandleFunc("/api/metrics", h.HandleMetrics)
|
||||
|
||||
// 公开的端点信息接口
|
||||
r.HandleFunc("/api/endpoints", h.HandlePublicEndpoints)
|
||||
}
|
||||
|
4
main.go
4
main.go
@ -81,9 +81,7 @@ func (a *App) Initialize() error {
|
||||
}
|
||||
|
||||
// 创建 handlers
|
||||
handlers := &handlers.Handlers{
|
||||
Stats: a.Stats,
|
||||
}
|
||||
handlers := handlers.NewHandlers(a.Stats)
|
||||
|
||||
// 设置路由
|
||||
a.router.Setup(handlers)
|
||||
|
90
middleware/auth.go
Normal file
90
middleware/auth.go
Normal file
@ -0,0 +1,90 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserInfo OAuth用户信息结构
|
||||
type UserInfo struct {
|
||||
ID int `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
|
||||
// AuthMiddleware 认证中间件
|
||||
type AuthMiddleware struct{}
|
||||
|
||||
// NewAuthMiddleware 创建新的认证中间件
|
||||
func NewAuthMiddleware() *AuthMiddleware {
|
||||
return &AuthMiddleware{}
|
||||
}
|
||||
|
||||
// RequireAuth 认证中间件,验证 OAuth 令牌
|
||||
func (am *AuthMiddleware) RequireAuth(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// 从 Authorization header 获取令牌
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
http.Error(w, "Authorization header required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查 Bearer 前缀
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
http.Error(w, "Invalid authorization header format", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
token := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if token == "" {
|
||||
http.Error(w, "Token required", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证令牌(通过调用用户信息接口)
|
||||
userInfo, err := am.getUserInfo(token)
|
||||
if err != nil {
|
||||
log.Printf("Token validation failed: %v", err)
|
||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 令牌有效,继续处理请求
|
||||
log.Printf("Authenticated user: %s (%s)", userInfo.Username, userInfo.Email)
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// getUserInfo 通过访问令牌获取用户信息
|
||||
func (am *AuthMiddleware) getUserInfo(accessToken string) (*UserInfo, error) {
|
||||
req, err := http.NewRequest("GET", "https://connect.czl.net/api/oauth2/userinfo", nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user info: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get user info, status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode user info: %w", err)
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
@ -2,14 +2,17 @@ package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"random-api-go/middleware"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Router struct {
|
||||
mux *http.ServeMux
|
||||
staticHandler StaticHandler
|
||||
mux *http.ServeMux
|
||||
staticHandler StaticHandler
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
}
|
||||
|
||||
// Handler 接口定义处理器需要的方法
|
||||
type Handler interface {
|
||||
Setup(r *Router)
|
||||
}
|
||||
@ -54,7 +57,8 @@ type AdminHandler interface {
|
||||
|
||||
func New() *Router {
|
||||
return &Router{
|
||||
mux: http.NewServeMux(),
|
||||
mux: http.NewServeMux(),
|
||||
authMiddleware: middleware.NewAuthMiddleware(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,44 +74,44 @@ func (r *Router) SetupStaticRoutes(staticHandler StaticHandler) {
|
||||
|
||||
// SetupAdminRoutes 设置管理后台路由
|
||||
func (r *Router) SetupAdminRoutes(adminHandler AdminHandler) {
|
||||
// OAuth配置API(前端需要获取client_id等信息)
|
||||
// OAuth配置API(前端需要获取client_id等信息)- 不需要认证
|
||||
r.HandleFunc("/api/admin/oauth-config", adminHandler.GetOAuthConfig)
|
||||
// OAuth令牌验证API(保留,以防需要)
|
||||
// OAuth令牌验证API(保留,以防需要)- 不需要认证
|
||||
r.HandleFunc("/api/admin/oauth-verify", adminHandler.VerifyOAuthToken)
|
||||
// OAuth回调处理(使用API前缀以便区分前后端)
|
||||
// OAuth回调处理(使用API前缀以便区分前后端)- 不需要认证
|
||||
r.HandleFunc("/api/admin/oauth/callback", adminHandler.HandleOAuthCallback)
|
||||
|
||||
// 管理后台API路由
|
||||
r.HandleFunc("/api/admin/endpoints", adminHandler.HandleEndpoints)
|
||||
// 管理后台API路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/endpoints", r.authMiddleware.RequireAuth(adminHandler.HandleEndpoints))
|
||||
|
||||
// 端点排序路由
|
||||
r.HandleFunc("/api/admin/endpoints/sort-order", adminHandler.UpdateEndpointSortOrder)
|
||||
// 端点排序路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/endpoints/sort-order", r.authMiddleware.RequireAuth(adminHandler.UpdateEndpointSortOrder))
|
||||
|
||||
// 数据源路由 - 需要在端点路由之前注册,因为路径更具体
|
||||
r.HandleFunc("/api/admin/data-sources", adminHandler.CreateDataSource)
|
||||
// 数据源路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/data-sources", r.authMiddleware.RequireAuth(adminHandler.CreateDataSource))
|
||||
|
||||
// 端点相关路由 - 使用通配符处理所有端点相关请求
|
||||
r.HandleFunc("/api/admin/endpoints/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// 端点相关路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/endpoints/", r.authMiddleware.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if strings.Contains(path, "/data-sources") {
|
||||
adminHandler.HandleEndpointDataSources(w, r)
|
||||
} else {
|
||||
adminHandler.HandleEndpointByID(w, r)
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// 数据源操作路由 - 使用通配符处理所有数据源相关请求
|
||||
r.HandleFunc("/api/admin/data-sources/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// 数据源操作路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/data-sources/", r.authMiddleware.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
if strings.Contains(path, "/sync") {
|
||||
adminHandler.SyncDataSource(w, r)
|
||||
} else {
|
||||
adminHandler.HandleDataSourceByID(w, r)
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// URL替换规则路由
|
||||
r.HandleFunc("/api/admin/url-replace-rules", func(w http.ResponseWriter, r *http.Request) {
|
||||
// URL替换规则路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/url-replace-rules", r.authMiddleware.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
adminHandler.ListURLReplaceRules(w, r)
|
||||
} else if r.Method == http.MethodPost {
|
||||
@ -115,27 +119,27 @@ func (r *Router) SetupAdminRoutes(adminHandler AdminHandler) {
|
||||
} else {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
r.HandleFunc("/api/admin/url-replace-rules/", adminHandler.HandleURLReplaceRuleByID)
|
||||
}))
|
||||
r.HandleFunc("/api/admin/url-replace-rules/", r.authMiddleware.RequireAuth(adminHandler.HandleURLReplaceRuleByID))
|
||||
|
||||
// 首页配置路由
|
||||
r.HandleFunc("/api/admin/home-config", func(w http.ResponseWriter, r *http.Request) {
|
||||
// 首页配置路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/home-config", r.authMiddleware.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
adminHandler.GetHomePageConfig(w, r)
|
||||
} else {
|
||||
adminHandler.UpdateHomePageConfig(w, r)
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
||||
// 通用配置管理路由
|
||||
r.HandleFunc("/api/admin/configs", adminHandler.ListConfigs)
|
||||
r.HandleFunc("/api/admin/configs/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// 通用配置管理路由 - 需要认证
|
||||
r.HandleFunc("/api/admin/configs", r.authMiddleware.RequireAuth(adminHandler.ListConfigs))
|
||||
r.HandleFunc("/api/admin/configs/", r.authMiddleware.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodDelete {
|
||||
adminHandler.DeleteConfigByKey(w, r)
|
||||
} else {
|
||||
adminHandler.CreateOrUpdateConfig(w, r)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func (r *Router) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
|
||||
|
@ -86,7 +86,7 @@ async function getSystemMetrics(): Promise<SystemMetrics | null> {
|
||||
|
||||
async function getEndpoints() {
|
||||
try {
|
||||
const res = await apiFetch('/api/admin/endpoints')
|
||||
const res = await apiFetch('/api/endpoints')
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to fetch endpoints')
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TOKEN_COOKIE_NAME = 'admin_token'
|
||||
const REFRESH_TOKEN_COOKIE_NAME = 'admin_refresh_token'
|
||||
const USER_INFO_COOKIE_NAME = 'admin_user'
|
||||
|
||||
// Cookie配置
|
||||
@ -19,13 +18,9 @@ export interface AuthUser {
|
||||
}
|
||||
|
||||
// 保存认证信息
|
||||
export function saveAuthInfo(token: string, user: AuthUser, refreshToken?: string) {
|
||||
export function saveAuthInfo(token: string, user: AuthUser) {
|
||||
Cookies.set(TOKEN_COOKIE_NAME, token, COOKIE_OPTIONS)
|
||||
Cookies.set(USER_INFO_COOKIE_NAME, JSON.stringify(user), COOKIE_OPTIONS)
|
||||
|
||||
if (refreshToken) {
|
||||
Cookies.set(REFRESH_TOKEN_COOKIE_NAME, refreshToken, COOKIE_OPTIONS)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取访问令牌
|
||||
@ -33,11 +28,6 @@ export function getAccessToken(): string | null {
|
||||
return Cookies.get(TOKEN_COOKIE_NAME) || null
|
||||
}
|
||||
|
||||
// 获取刷新令牌
|
||||
export function getRefreshToken(): string | null {
|
||||
return Cookies.get(REFRESH_TOKEN_COOKIE_NAME) || null
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserInfo(): AuthUser | null {
|
||||
const userStr = Cookies.get(USER_INFO_COOKIE_NAME)
|
||||
@ -53,7 +43,6 @@ export function getUserInfo(): AuthUser | null {
|
||||
// 清除认证信息
|
||||
export function clearAuthInfo() {
|
||||
Cookies.remove(TOKEN_COOKIE_NAME, { path: '/' })
|
||||
Cookies.remove(REFRESH_TOKEN_COOKIE_NAME, { path: '/' })
|
||||
Cookies.remove(USER_INFO_COOKIE_NAME, { path: '/' })
|
||||
}
|
||||
|
||||
@ -78,63 +67,20 @@ export async function authenticatedFetch(url: string, options: RequestInit = {})
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include', // 包含cookie
|
||||
})
|
||||
|
||||
// 如果token过期,尝试刷新
|
||||
// 如果token过期或无效,清除认证信息并重定向到登录
|
||||
if (response.status === 401) {
|
||||
const refreshed = await refreshAccessToken()
|
||||
if (refreshed) {
|
||||
// 重新发送请求
|
||||
const newToken = getAccessToken()
|
||||
if (newToken) {
|
||||
headers['Authorization'] = `Bearer ${newToken}`
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
}
|
||||
// 刷新失败,清除认证信息
|
||||
clearAuthInfo()
|
||||
// 可以选择重定向到登录页面或显示登录提示
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/admin'
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// 刷新访问令牌
|
||||
async function refreshAccessToken(): Promise<boolean> {
|
||||
const refreshToken = getRefreshToken()
|
||||
if (!refreshToken) return false
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/admin/auth/refresh', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ refresh_token: refreshToken }),
|
||||
credentials: 'include',
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success && data.data.access_token) {
|
||||
const user = getUserInfo()
|
||||
if (user) {
|
||||
saveAuthInfo(data.data.access_token, user, data.data.refresh_token)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh token:', error)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// OAuth状态管理
|
||||
export function saveOAuthState(state: string) {
|
||||
sessionStorage.setItem('oauth_state', state)
|
||||
|
Loading…
x
Reference in New Issue
Block a user