097aed6f40
ci/woodpecker/tag/build Pipeline was successful
实测发现任意代理 token 可访问 /admin/all-cdks、/admin/all-agents、 /agent/list 查看全站数据,并可创建全站强制弹窗公告、篡改系统计费与 SMTP 配置。为这些接口补充 AdminOnly 中间件;公告收取仍走 /my-announcements,代理自管理功能(自有CDK/下级代理)不受影响。
194 lines
7.7 KiB
Go
194 lines
7.7 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"time"
|
||
|
||
"cpdaily-new/internal/config"
|
||
"cpdaily-new/internal/database"
|
||
"cpdaily-new/internal/handler"
|
||
"cpdaily-new/internal/middleware"
|
||
"cpdaily-new/internal/ratelimit"
|
||
"cpdaily-new/internal/service"
|
||
|
||
"github.com/gofiber/fiber/v2"
|
||
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||
)
|
||
|
||
func main() {
|
||
// 加载配置
|
||
if err := config.Load(""); err != nil {
|
||
log.Fatalf("加载配置失败: %v", err)
|
||
}
|
||
|
||
// 连接数据库
|
||
database.Init()
|
||
|
||
// 连接 Redis(验证码存储/限流)
|
||
service.InitRedis()
|
||
|
||
// 启动限流记录定时清理(每5分钟清理一次)
|
||
ratelimit.Cleanup(5 * time.Minute)
|
||
|
||
// 创建 Fiber 应用
|
||
app := fiber.New(fiber.Config{
|
||
AppName: "Cpdaily New",
|
||
// 信任 openresty 反代(host 网络,经 docker NAT 后源 IP 为 172.22.0.1),
|
||
// 从 X-Forwarded-For 取真实客户端 IP(如待适配学校提交记录)
|
||
TrustedProxies: []string{"172.22.0.0/16"},
|
||
EnableTrustedProxyCheck: true,
|
||
ProxyHeader: fiber.HeaderXForwardedFor,
|
||
})
|
||
|
||
// 中间件
|
||
app.Use(middleware.CORS())
|
||
|
||
// 路由
|
||
api := app.Group("/api")
|
||
|
||
// 认证相关
|
||
auth := api.Group("/auth")
|
||
auth.Get("/captcha", handler.GetCaptcha)
|
||
auth.Get("/schools", handler.SearchSchools)
|
||
auth.Post("/login", handler.Login)
|
||
auth.Post("/refresh", handler.RefreshToken)
|
||
auth.Get("/userinfo", middleware.AuthRequired(), handler.GetUserInfo)
|
||
auth.Post("/generate-invite", middleware.AuthRequired(), handler.GenerateMyInviteCode)
|
||
auth.Get("/my-invite", middleware.AuthRequired(), handler.GetMyInviteInfo)
|
||
auth.Post("/redeem-invite", middleware.AuthRequired(), handler.RedeemInviteReward)
|
||
|
||
// 签到任务相关
|
||
task := api.Group("/task", middleware.AuthRequired())
|
||
task.Get("/titles", handler.GetTaskTitles)
|
||
task.Post("/create", handler.CreateTask)
|
||
task.Get("/list", handler.ListTasks)
|
||
task.Get("/form/:id", handler.GetTaskForm)
|
||
task.Post("/form/:id", handler.SaveTaskForm)
|
||
task.Put("/form/:id", handler.SaveTaskForm)
|
||
task.Put("/update/:id", handler.UpdateTask)
|
||
task.Delete("/delete/:id", handler.DeleteTask)
|
||
task.Get("/execute/:id", handler.ExecuteTask)
|
||
task.Get("/logs", handler.ListTaskLogs)
|
||
task.Delete("/logs/:id", handler.DeleteTaskLog)
|
||
task.Post("/upload-policy", handler.GetUploadPolicy)
|
||
task.Post("/preview-url", handler.GetPreviewUrl)
|
||
|
||
// CDK 兑换
|
||
cdk := api.Group("/cdk", middleware.AuthRequired())
|
||
cdk.Post("/redeem", handler.UserRedeemCDK)
|
||
|
||
// 设备绑定
|
||
device := api.Group("/device")
|
||
device.Post("/generate", middleware.AuthRequired(), handler.GenerateDeviceQr)
|
||
device.Post("/submit", handler.SubmitDeviceCode)
|
||
device.Get("/status", middleware.AuthRequired(), handler.GetDeviceStatus)
|
||
|
||
// 管理后台 API
|
||
admin := api.Group("/admin")
|
||
admin.Get("/setup/status", handler.AdminSetupStatus)
|
||
admin.Post("/setup/init", handler.AdminSetupInit)
|
||
admin.Post("/login", handler.AdminLogin)
|
||
admin.Get("/me", middleware.AdminAuthRequired(), handler.AdminMe)
|
||
|
||
// 管理后台 - 代理/管理员
|
||
adminAuth := api.Group("/admin", middleware.AdminAuthRequired())
|
||
adminAuth.Get("/cdks", handler.ListCDKs)
|
||
adminAuth.Post("/cdks", handler.GenerateCDK)
|
||
adminAuth.Put("/cdks/:id/status", handler.UpdateCDKStatus)
|
||
// 全量数据仅超管可见(代理只能看自己生成的)
|
||
adminAuth.Get("/all-cdks", middleware.AdminOnly(), handler.ListAllCDKs)
|
||
adminAuth.Get("/agents", handler.ListSubAgents)
|
||
adminAuth.Post("/agents", handler.CreateSubAgent)
|
||
adminAuth.Put("/agents/:id", handler.UpdateSubAgent)
|
||
adminAuth.Get("/agents/:id", handler.GetAgentDetail)
|
||
adminAuth.Put("/agents/:id/password", handler.UpdateAgentPassword)
|
||
adminAuth.Put("/agents/:id/status", handler.UpdateAgentStatus)
|
||
adminAuth.Get("/all-agents", middleware.AdminOnly(), handler.ListAllAgents)
|
||
adminAuth.Get("/agent/list", middleware.AdminOnly(), handler.ListAllAgents)
|
||
|
||
// 管理后台 - 用户管理(仅admin)
|
||
adminAuth.Get("/users", middleware.AdminOnly(), handler.ListUsers)
|
||
adminAuth.Get("/users/:id", middleware.AdminOnly(), handler.GetUserDetail)
|
||
adminAuth.Put("/users/:id/status", middleware.AdminOnly(), handler.UpdateUserStatus)
|
||
|
||
// 管理后台 - 公告(公告面向全站代理/用户推送,管理操作仅超管;代理/管理员通过 /my-announcements 收取公告)
|
||
adminAuth.Post("/announcements", middleware.AdminOnly(), handler.CreateAnnouncement)
|
||
adminAuth.Get("/announcements", middleware.AdminOnly(), handler.ListAnnouncements)
|
||
adminAuth.Get("/announcements/:id", middleware.AdminOnly(), handler.GetAnnouncement)
|
||
adminAuth.Put("/announcements/:id", middleware.AdminOnly(), handler.UpdateAnnouncement)
|
||
adminAuth.Delete("/announcements/:id", middleware.AdminOnly(), handler.DeleteAnnouncement)
|
||
|
||
// 管理后台 - 系统设置(全站计费/奖励/SMTP 配置,仅超管)
|
||
adminAuth.Get("/settings", middleware.AdminOnly(), handler.GetSettings)
|
||
adminAuth.Post("/settings", middleware.AdminOnly(), handler.UpdateSetting)
|
||
adminAuth.Put("/settings", middleware.AdminOnly(), handler.UpdateSetting)
|
||
adminAuth.Post("/settings/test-mail", middleware.AdminOnly(), handler.TestMail)
|
||
|
||
// 管理后台 - 我的公告(代理/管理员视角,共用普通用户公告处理器,但 target_type=agent)
|
||
adminAuth.Get("/my-announcements", handler.ListUserAnnouncements)
|
||
adminAuth.Get("/my-announcements/unread-count", handler.GetUnreadCount)
|
||
adminAuth.Get("/my-announcements/my-info", handler.GetMyAnnouncementInfo)
|
||
adminAuth.Post("/my-announcements/:id/read", handler.MarkAnnouncementRead)
|
||
|
||
// 用户公告
|
||
api.Get("/announcements", middleware.AuthRequired(), handler.ListUserAnnouncements)
|
||
api.Get("/announcements/unread-count", middleware.AuthRequired(), handler.GetUnreadCount)
|
||
api.Get("/announcements/my-info", middleware.AuthRequired(), handler.GetMyAnnouncementInfo)
|
||
api.Post("/announcements/:id/read", middleware.AuthRequired(), handler.MarkAnnouncementRead)
|
||
|
||
// 设备授权页面(纯 HTML)
|
||
api.Get("/device/authorize/:token", handler.AuthorizePage)
|
||
api.Get("/device/close", handler.ClosePage)
|
||
|
||
// 工单系统
|
||
ticket := api.Group("/ticket", middleware.AuthRequired())
|
||
ticket.Post("/create", handler.CreateTicket)
|
||
ticket.Get("/list", handler.ListMyTickets)
|
||
ticket.Get("/:id", handler.GetTicketDetail)
|
||
ticket.Post("/:id/message", handler.AddTicketMessage)
|
||
ticket.Put("/:id/close", handler.CloseTicket)
|
||
ticket.Delete("/:id", handler.DeleteTicket)
|
||
ticket.Get("/notifications/list", handler.ListTicketNotifications)
|
||
ticket.Get("/notifications/unread-count", handler.GetTicketUnreadCount)
|
||
ticket.Post("/notifications/:id/read", handler.MarkTicketNotificationRead)
|
||
|
||
// 用户报告登录失败(无需认证,有独立速率限制 + 验证码校验)
|
||
// 使用独立路径避免与 /ticket 组的 AuthRequired 中间件冲突
|
||
api.Post("/report-failed-login", handler.ReportFailedLogin)
|
||
|
||
// 管理后台 - 工单管理
|
||
adminAuth.Post("/ticket/reply/:id", handler.AdminReplyTicket)
|
||
adminAuth.Put("/ticket/status/:id", handler.AdminUpdateTicketStatus)
|
||
adminAuth.Get("/ticket/list", handler.AdminListTickets)
|
||
adminAuth.Get("/ticket/:id", handler.AdminGetTicket)
|
||
|
||
// 管理后台 - 待适配学校
|
||
adminAuth.Get("/pending-schools", handler.AdminListPendingSchools)
|
||
adminAuth.Post("/pending-schools/:id/mark-done", handler.AdminMarkSchoolDone)
|
||
|
||
// 健康检查(用于蓝绿部署)
|
||
app.Get("/health", func(c *fiber.Ctx) error {
|
||
return c.SendString("ok")
|
||
})
|
||
|
||
// 静态文件服务(前端 SPA)
|
||
app.Static("/assets", "./dist/assets", fiber.Static{
|
||
MaxAge: 86400,
|
||
})
|
||
app.Use(filesystem.New(filesystem.Config{
|
||
Root: http.Dir("./dist"),
|
||
Index: "index.html",
|
||
NotFoundFile: "index.html",
|
||
MaxAge: 3600,
|
||
}))
|
||
|
||
// 启动
|
||
addr := fmt.Sprintf(":%d", config.Global.Server.Port)
|
||
log.Printf("服务器启动在 %s", addr)
|
||
if err := app.Listen(addr); err != nil {
|
||
log.Fatalf("服务器启动失败: %v", err)
|
||
}
|
||
}
|