前端公共部分开发完成,支持图片展示功能

This commit is contained in:
2026-01-27 12:56:17 +08:00
parent 911e58c29b
commit be0bcb4d51
25 changed files with 3252 additions and 19 deletions

255
webapp/CORS_CONFIG.md Normal file
View File

@@ -0,0 +1,255 @@
# Go 后端 CORS 配置示例
## 问题说明
开发环境中,前端运行在 `http://localhost:5173`,后端运行在 `http://localhost:8080`
由于跨域限制,需要在后端配置 CORS 才能正常访问 API。
## 使用 Gin 框架
### 1. 安装 CORS 中间件
```bash
go get github.com/gin-contrib/cors
```
### 2. 配置 CORS
```go
package main
import (
"github.com/gin-gonic/gin"
"github.com/gin-contrib/cors"
"time"
)
func main() {
r := gin.Default()
// 开发环境:配置 CORS
if gin.Mode() == gin.DebugMode {
config := cors.Config{
AllowOrigins: []string{"http://localhost:5173"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
r.Use(cors.New(config))
}
// API 路由
api := r.Group("/api/v1")
{
// 图片相关
api.GET("/images", getImages)
api.GET("/image/today/meta", getTodayImageMeta)
api.GET("/image/date/:date/meta", getImageMetaByDate)
api.GET("/image/random/meta", getRandomImageMeta)
// 管理员相关
api.POST("/admin/login", adminLogin)
// ... 其他路由
}
// 生产环境:静态文件服务
if gin.Mode() == gin.ReleaseMode {
r.Static("/assets", "./web/assets")
r.StaticFile("/", "./web/index.html")
// SPA fallback
r.NoRoute(func(c *gin.Context) {
c.File("./web/index.html")
})
}
r.Run(":8080")
}
```
### 3. 更灵活的 CORS 配置
```go
// 根据环境变量动态配置
func setupCORS(r *gin.Engine) {
allowOrigins := os.Getenv("ALLOW_ORIGINS")
if allowOrigins == "" {
allowOrigins = "http://localhost:5173"
}
origins := strings.Split(allowOrigins, ",")
config := cors.Config{
AllowOrigins: origins,
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
r.Use(cors.New(config))
}
func main() {
r := gin.Default()
// 只在开发环境启用 CORS
if gin.Mode() == gin.DebugMode {
setupCORS(r)
}
// ... 其他配置
}
```
## 使用标准库
如果不使用 Gin 框架,可以手动实现 CORS
```go
package main
import (
"net/http"
)
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置 CORS 头
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Max-Age", "43200") // 12 hours
// 处理预检请求
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
// 注册路由
mux.HandleFunc("/api/v1/images", getImages)
// ... 其他路由
// 包装 CORS 中间件
handler := corsMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
```
## 配置文件方式
可以通过配置文件管理 CORS 设置:
```yaml
# config.yaml
server:
port: 8080
mode: debug # debug 或 release
cors:
enabled: true
allow_origins:
- http://localhost:5173
- http://127.0.0.1:5173
allow_methods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
allow_headers:
- Origin
- Content-Type
- Accept
- Authorization
allow_credentials: true
max_age: 43200
```
```go
type CORSConfig struct {
Enabled bool `yaml:"enabled"`
AllowOrigins []string `yaml:"allow_origins"`
AllowMethods []string `yaml:"allow_methods"`
AllowHeaders []string `yaml:"allow_headers"`
AllowCredentials bool `yaml:"allow_credentials"`
MaxAge int `yaml:"max_age"`
}
func setupCORSFromConfig(r *gin.Engine, cfg *CORSConfig) {
if !cfg.Enabled {
return
}
config := cors.Config{
AllowOrigins: cfg.AllowOrigins,
AllowMethods: cfg.AllowMethods,
AllowHeaders: cfg.AllowHeaders,
AllowCredentials: cfg.AllowCredentials,
MaxAge: time.Duration(cfg.MaxAge) * time.Second,
}
r.Use(cors.New(config))
}
```
## 安全建议
1. **生产环境不要启用 CORS**:生产环境前后端在同一域名下,不需要 CORS
2. **限制允许的源**:不要使用 `*`,明确指定允许的域名
3. **使用环境变量**:允许的源应该通过环境变量配置,不要硬编码
4. **限制方法和头**:只允许必要的 HTTP 方法和请求头
5. **注意凭证**:如果使用 `AllowCredentials: true`,不能使用通配符源
## 测试 CORS 配置
可以使用 curl 测试 CORS
```bash
# 测试预检请求
curl -X OPTIONS http://localhost:8080/api/v1/images \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: GET" \
-v
# 测试实际请求
curl -X GET http://localhost:8080/api/v1/images \
-H "Origin: http://localhost:5173" \
-v
```
应该能看到响应头中包含:
```
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Origin, Content-Type, Accept, Authorization
Access-Control-Allow-Credentials: true
```
## 常见问题
### Q: 为什么要区分开发和生产环境?
A: 生产环境前后端部署在同一域名下,不需要 CORS。只在开发环境需要。
### Q: 可以使用代理吗?
A: 可以在 Vite 中配置代理,但不推荐。直接配置 CORS 更接近生产环境,便于发现问题。
### Q: 如何处理认证?
A: 如果使用 Cookie 或需要发送凭证,必须设置 `AllowCredentials: true` 和明确的源。
### Q: 预检请求是什么?
A: 浏览器在某些跨域请求前会发送 OPTIONS 请求,询问服务器是否允许该跨域请求。