// Package router 负责 HTTP 路由的注册与管理 // 按功能模块分为前台路由、后台管理路由和 API 路由三类 package router import ( "html/template" "net/http" "resume-platform/internal/handler" "resume-platform/internal/middleware" "resume-platform/internal/model" "resume-platform/pkg/config" "github.com/gin-gonic/gin" ) // Router 路由管理器,持有各模块 Handler 实例和全局配置 type Router struct { resumeHandler *handler.ResumeHandler adminHandler *handler.AdminHandler aiHandler *handler.AIHandler documentHandler *handler.DocumentHandler chatHandler *handler.ChatHandler imHandler *handler.IMHandler knowledgeHandler *handler.KnowledgeHandler cfg *config.Config } // NewRouter 创建路由管理器实例 // @param resumeHandler 简历相关处理器 // @param adminHandler 后台管理相关处理器 // @param aiHandler AI 相关处理器 // @param documentHandler 文档相关处理器 // @param chatHandler 聊天相关处理器 // @param cfg 全局配置 // @return *Router 路由管理器实例 // @author sunct func NewRouter(resumeHandler *handler.ResumeHandler, adminHandler *handler.AdminHandler, aiHandler *handler.AIHandler, documentHandler *handler.DocumentHandler, chatHandler *handler.ChatHandler, imHandler *handler.IMHandler, knowledgeHandler *handler.KnowledgeHandler, cfg *config.Config) *Router { return &Router{ resumeHandler: resumeHandler, adminHandler: adminHandler, aiHandler: aiHandler, documentHandler: documentHandler, chatHandler: chatHandler, imHandler: imHandler, knowledgeHandler: knowledgeHandler, cfg: cfg, } } // Setup 初始化 Gin 引擎并注册所有路由、中间件和模板函数 // 注册顺序:中间件 → 模板函数 → 静态资源 → API 路由 → 后台路由 → 前台路由 → 404 处理 // @return *gin.Engine 配置完成的 Gin 引擎 // @author sunct func (r *Router) Setup() *gin.Engine { gin.SetMode(r.cfg.Server.Mode) engine := gin.New() engine.Use(middleware.Tracing("resume-platform")) engine.Use(middleware.RequestLogger()) engine.Use(gin.Recovery()) engine.SetFuncMap(template.FuncMap{ "dict": dict, "set": set, "merge": merge, "slice": slice, "append": appendFunc, "substr": substr, "first": func(n int, s interface{}) interface{} { switch v := s.(type) { case []*model.Resume: if n > len(v) { return v } return v[:n] default: return s } }, }) engine.LoadHTMLGlob("templates/web/*.html") engine.Static("/static", "./static") r.setupAPIRoutes(engine) r.setupAdminRoutes(engine) r.setupFrontendRoutes(engine) engine.NoRoute(func(c *gin.Context) { if len(c.Request.URL.Path) > 10 && c.Request.URL.Path[:10] == "/dashboard" { r.adminHandler.DashboardNotFound(c) return } c.Status(http.StatusNotFound) c.HTML(http.StatusNotFound, "404.html", nil) }) return engine } // dict 模板函数,根据传入的键值对构建 map[string]interface{} // 传入参数必须为偶数个,按 key, value 交替排列 // @param values 键值对列表,格式:key1, value1, key2, value2... // @return map[string]interface{} 构建的字典 // @author sunct func dict(values ...interface{}) (map[string]interface{}, error) { if len(values)%2 != 0 { return nil, nil } m := make(map[string]interface{}, len(values)/2) for i := 0; i < len(values); i += 2 { key, ok := values[i].(string) if !ok { return nil, nil } m[key] = values[i+1] } return m, nil } // set 模板函数,向 map 中设置键值对 // @param m 目标 map // @param key 键名 // @param value 值 // @return interface{} 始终返回 nil // @author sunct func set(m map[string]interface{}, key string, value interface{}) interface{} { m[key] = value return nil } // merge 模板函数,将 src map 中的所有键值合并到 dest map // @param dest 目标 map // @param src 源 map // @return map[string]interface{} 合并后的 dest // @author sunct func merge(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { for k, v := range src { dest[k] = v } return dest } // slice 模板函数,将传入的多个参数组装为切片 // @param values 任意数量的参数 // @return []interface{} 参数切片 // @author sunct func slice(values ...interface{}) []interface{} { return values } // appendFunc 模板函数,向切片末尾追加一个元素 // @param slice 目标切片 // @param value 待追加元素 // @return []interface{} 追加后的切片 // @author sunct func appendFunc(slice []interface{}, value interface{}) []interface{} { return append(slice, value) } // substr 模板函数,按 rune 截取字符串,支持中文等 Unicode 字符 // @param s 源字符串 // @param start 起始位置,支持负数(从末尾倒数) // @param length 可选,截取长度;不传则截取到末尾 // @return string 截取后的子串 // @author sunct func substr(s string, start int, length ...int) string { if len(s) == 0 { return "" } runes := []rune(s) runeLen := len(runes) startIdx := 0 if start < 0 { startIdx = runeLen + start if startIdx < 0 { startIdx = 0 } } else { startIdx = start } if startIdx >= runeLen { return "" } if len(length) == 0 { return string(runes[startIdx:]) } endIdx := startIdx + length[0] if endIdx > runeLen { endIdx = runeLen } return string(runes[startIdx:endIdx]) }