package controllers import ( "encoding/json" "fmt" "io" "strconv" "strings" "time" "server/models" "server/pkg/jwtutil" "github.com/beego/beego/v2/client/orm" beego "github.com/beego/beego/v2/server/web" ) // BackendBannerController 官网 Banner 管理(租户端,tid 严格隔离) type BackendBannerController struct { beego.Controller } // BackendFriendlinkController 官网友情链接管理(租户端) type BackendFriendlinkController struct { beego.Controller } // BackendOnepageController 官网单页管理(租户端) type BackendOnepageController struct { beego.Controller } func (c *BackendBannerController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func (c *BackendFriendlinkController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func (c *BackendOnepageController) cmsClaims() (*jwtutil.Claims, error) { return cmsBackendClaims(&c.Controller) } func (c *BackendBannerController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendFriendlinkController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func (c *BackendOnepageController) cmsJSONErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } func cmsBannerToMap(row models.CmsFrontendBanner) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "title": row.Title, "desc": row.Desc, "image": row.Image, "url": row.Url, "sort": row.Sort, "status": row.Status, "create_time": row.CreateTime.Unix(), } } func cmsFriendlinkToMap(row models.CmsFrontendFriendlink) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "link_name": row.LinkName, "link_url": row.LinkUrl, "link_logo": row.LinkLogo, "description": row.Description, "sort": row.Sort, "status": row.Status, "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), } } func cmsOnepageToMap(row models.CmsFrontendOnepage) map[string]interface{} { return map[string]interface{}{ "id": row.ID, "title": row.Title, "path": row.Path, "content": row.Content, "sort": row.Sort, "status": row.Status, "create_time": row.CreateTime.Unix(), } } // ---------------- Banner ---------------- type cmsBannerPayload struct { Title string `json:"title"` Desc string `json:"desc"` Image string `json:"image"` Url string `json:"url"` Sort int `json:"sort"` Status int8 `json:"status"` } // readBannerPayload 兼容 JSON 与 multipart/form-data 两种请求体 // (前端 createbanner 以 multipart 头发送,实际可能是 JSON 体) func (c *BackendBannerController) readBannerPayload() (*cmsBannerPayload, error) { ct := c.Ctx.Request.Header.Get("Content-Type") if strings.HasPrefix(ct, "multipart/form-data") { // cmsEffectiveTid 已触发 ParseForm,表单字段可直接读取 sort, _ := c.GetInt("sort") status, _ := c.GetInt8("status", 1) return &cmsBannerPayload{ Title: strings.TrimSpace(c.GetString("title")), Desc: strings.TrimSpace(c.GetString("desc")), Image: strings.TrimSpace(c.GetString("image")), Url: strings.TrimSpace(c.GetString("url")), Sort: sort, Status: status, }, nil } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { return nil, err } var p cmsBannerPayload p.Status = 1 if len(raw) > 0 { if err := json.Unmarshal(raw, &p); err != nil { return nil, fmt.Errorf("参数错误") } } return &p, nil } // ListAll GET /backend/allbanners func (c *BackendBannerController) ListAll() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } var rows []models.CmsFrontendBanner _, err = models.Orm.QueryTable(new(models.CmsFrontendBanner)). Filter("tid", tid). Filter("delete_time__isnull", true). OrderBy("sort", "id"). All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取Banner列表失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsBannerToMap(r)) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} _ = c.ServeJSON() } // Create POST /backend/createbanner func (c *BackendBannerController) Create() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } p, err := c.readBannerPayload() if err != nil { c.cmsJSONErr(400, 400, err.Error()) return } if strings.TrimSpace(p.Title) == "" && strings.TrimSpace(p.Image) == "" { c.cmsJSONErr(400, 400, "标题与图片不能都为空") return } now := time.Now() row := models.CmsFrontendBanner{ Tid: tid, Title: strings.TrimSpace(p.Title), Desc: strings.TrimSpace(p.Desc), Image: strings.TrimSpace(p.Image), Url: strings.TrimSpace(p.Url), Sort: p.Sort, Status: p.Status, CreateTime: now, UpdateTime: &now, } if row.Status == 0 { row.Status = 1 } id, err := models.Orm.Insert(&row) if err != nil { c.cmsJSONErr(500, 500, "创建失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } // Update POST /backend/editbanner/:id func (c *BackendBannerController) Update() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsBannerPayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } now := time.Now() fields := map[string]interface{}{ "title": strings.TrimSpace(p.Title), "desc": strings.TrimSpace(p.Desc), "image": strings.TrimSpace(p.Image), "url": strings.TrimSpace(p.Url), "sort": p.Sort, "update_time": now, } if p.Status > 0 { fields["status"] = p.Status } n, err := models.Orm.QueryTable(new(models.CmsFrontendBanner)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "Banner不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // Delete DELETE /backend/deletebanner/:id func (c *BackendBannerController) Delete() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsFrontendBanner)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } if n == 0 { c.cmsJSONErr(404, 404, "Banner不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // ---------------- 友情链接 ---------------- // List GET /backend/friendlinks(分页:page/limit/keyword/status) func (c *BackendFriendlinkController) List() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } page, _ := c.GetInt("page", 1) limit, _ := c.GetInt("limit", 10) if page < 1 { page = 1 } if limit < 1 { limit = 10 } if limit > 200 { limit = 200 } qs := models.Orm.QueryTable(new(models.CmsFrontendFriendlink)). Filter("tid", tid). Filter("delete_time__isnull", true) if kw := strings.TrimSpace(c.GetString("keyword")); kw != "" { qs = qs.Filter("link_name__icontains", kw) } if st := strings.TrimSpace(c.GetString("status")); st != "" { if v, e := strconv.Atoi(st); e == nil { qs = qs.Filter("status", v) } } total, _ := qs.Count() var rows []models.CmsFrontendFriendlink offset := (page - 1) * limit _, err = qs.OrderBy("sort", "id").Limit(limit, offset).All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取友情链接失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsFriendlinkToMap(r)) } c.Data["json"] = map[string]interface{}{ "code": 200, "msg": "success", "data": map[string]interface{}{"list": list, "total": total}, } _ = c.ServeJSON() } // ListAll GET /backend/friendlinks/all func (c *BackendFriendlinkController) ListAll() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } var rows []models.CmsFrontendFriendlink _, err = models.Orm.QueryTable(new(models.CmsFrontendFriendlink)). Filter("tid", tid). Filter("status", 1). Filter("delete_time__isnull", true). OrderBy("sort", "id"). All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取友情链接失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsFriendlinkToMap(r)) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} _ = c.ServeJSON() } type cmsFriendlinkPayload struct { LinkName string `json:"link_name"` LinkUrl string `json:"link_url"` LinkLogo string `json:"link_logo"` Description string `json:"description"` Sort int `json:"sort"` Status int8 `json:"status"` } // Create POST /backend/friendlinks func (c *BackendFriendlinkController) Create() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsFriendlinkPayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } if strings.TrimSpace(p.LinkName) == "" { c.cmsJSONErr(400, 400, "链接名称不能为空") return } if strings.TrimSpace(p.LinkUrl) == "" { c.cmsJSONErr(400, 400, "链接地址不能为空") return } now := time.Now() row := models.CmsFrontendFriendlink{ Tid: tid, LinkName: strings.TrimSpace(p.LinkName), LinkUrl: strings.TrimSpace(p.LinkUrl), LinkLogo: strings.TrimSpace(p.LinkLogo), Description: strings.TrimSpace(p.Description), Sort: p.Sort, Status: p.Status, CreateTime: now, UpdateTime: &now, } if row.Status == 0 { row.Status = 1 } id, err := models.Orm.Insert(&row) if err != nil { c.cmsJSONErr(500, 500, "创建失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } // Update PUT /backend/friendlinks/:id(支持只传部分字段,如 {status:1}) func (c *BackendFriendlinkController) Update() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var body map[string]interface{} if err := json.Unmarshal(raw, &body); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } fields := map[string]interface{}{} if v, ok := body["link_name"]; ok { fields["link_name"] = strings.TrimSpace(fmt.Sprintf("%v", v)) } if v, ok := body["link_url"]; ok { fields["link_url"] = strings.TrimSpace(fmt.Sprintf("%v", v)) } if v, ok := body["link_logo"]; ok { fields["link_logo"] = strings.TrimSpace(fmt.Sprintf("%v", v)) } if v, ok := body["description"]; ok { fields["description"] = strings.TrimSpace(fmt.Sprintf("%v", v)) } if v, ok := body["sort"]; ok { if n, e := strconv.Atoi(fmt.Sprintf("%.0f", toFloat(v))); e == nil { fields["sort"] = n } } if v, ok := body["status"]; ok { fields["status"] = int8(toFloat(v)) } if len(fields) == 0 { c.cmsJSONErr(400, 400, "没有可更新的字段") return } fields["update_time"] = time.Now() n, err := models.Orm.QueryTable(new(models.CmsFrontendFriendlink)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "友情链接不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // toFloat 宽松地把 JSON 值转成数字(兼容字符串数字) func toFloat(v interface{}) float64 { switch x := v.(type) { case float64: return x case string: if n, err := strconv.ParseFloat(strings.TrimSpace(x), 64); err == nil { return n } } return 0 } // Delete DELETE /backend/friendlinks/:id func (c *BackendFriendlinkController) Delete() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsFrontendFriendlink)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } if n == 0 { c.cmsJSONErr(404, 404, "友情链接不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // BatchDelete POST /backend/friendlinks/batchdelete func (c *BackendFriendlinkController) BatchDelete() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var body struct { IDs []interface{} `json:"ids"` } if err := json.Unmarshal(raw, &body); err != nil || len(body.IDs) == 0 { c.cmsJSONErr(400, 400, "参数错误") return } ids := make([]uint64, 0, len(body.IDs)) for _, v := range body.IDs { if n := cmsParseUintArg(v); n > 0 { ids = append(ids, n) } } if len(ids) == 0 { c.cmsJSONErr(400, 400, "参数错误") return } now := time.Now() _, err = models.Orm.QueryTable(new(models.CmsFrontendFriendlink)). Filter("tid", tid). Filter("id__in", ids). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() } // ---------------- 单页 ---------------- type cmsOnepagePayload struct { Title string `json:"title"` Path string `json:"path"` Content string `json:"content"` Sort int `json:"sort"` Status int8 `json:"status"` } // ListAll GET /backend/allonepages func (c *BackendOnepageController) ListAll() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } var rows []models.CmsFrontendOnepage _, err = models.Orm.QueryTable(new(models.CmsFrontendOnepage)). Filter("tid", tid). Filter("delete_time__isnull", true). OrderBy("sort", "id"). All(&rows) if err != nil && err != orm.ErrNoRows { c.cmsJSONErr(500, 500, "获取单页列表失败") return } list := make([]map[string]interface{}, 0, len(rows)) for _, r := range rows { list = append(list, cmsOnepageToMap(r)) } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list} _ = c.ServeJSON() } // Create POST /backend/createonepage func (c *BackendOnepageController) Create() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) if tid == 0 { c.cmsJSONErr(400, 400, "tid不能为空") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsOnepagePayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } title := strings.TrimSpace(p.Title) path := strings.TrimSpace(p.Path) if title == "" { c.cmsJSONErr(400, 400, "标题不能为空") return } if path == "" { c.cmsJSONErr(400, 400, "路径标识不能为空") return } if strings.ContainsAny(path, "/\\ ") { c.cmsJSONErr(400, 400, "路径标识只能包含字母数字与下划线") return } // path 租户内唯一 cnt, _ := models.Orm.QueryTable(new(models.CmsFrontendOnepage)). Filter("tid", tid). Filter("path", path). Filter("delete_time__isnull", true). Count() if cnt > 0 { c.cmsJSONErr(400, 400, "该路径标识已存在") return } now := time.Now() row := models.CmsFrontendOnepage{ Tid: tid, Title: title, Path: path, Content: p.Content, Sort: p.Sort, Status: p.Status, CreateTime: now, UpdateTime: &now, } if row.Status == 0 { row.Status = 1 } id, err := models.Orm.Insert(&row) if err != nil { c.cmsJSONErr(500, 500, "创建失败") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功", "data": map[string]interface{}{"id": id}} _ = c.ServeJSON() } // Update POST /backend/editonepage/:id func (c *BackendOnepageController) Update() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } raw, err := io.ReadAll(c.Ctx.Request.Body) if err != nil { c.cmsJSONErr(400, 400, "参数错误") return } var p cmsOnepagePayload if err := json.Unmarshal(raw, &p); err != nil { c.cmsJSONErr(400, 400, "参数错误") return } path := strings.TrimSpace(p.Path) if path != "" && strings.ContainsAny(path, "/\\ ") { c.cmsJSONErr(400, 400, "路径标识只能包含字母数字与下划线") return } if path != "" { dup, _ := models.Orm.QueryTable(new(models.CmsFrontendOnepage)). Filter("tid", tid). Filter("path", path). Filter("delete_time__isnull", true). Exclude("id", id). Count() if dup > 0 { c.cmsJSONErr(400, 400, "该路径标识已存在") return } } now := time.Now() fields := map[string]interface{}{ "title": strings.TrimSpace(p.Title), "content": p.Content, "sort": p.Sort, "update_time": now, } if path != "" { fields["path"] = path } if p.Status > 0 { fields["status"] = p.Status } n, err := models.Orm.QueryTable(new(models.CmsFrontendOnepage)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(fields) if err != nil { c.cmsJSONErr(500, 500, "更新失败") return } if n == 0 { c.cmsJSONErr(404, 404, "单页不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} _ = c.ServeJSON() } // Delete DELETE /backend/deleteonepage/:id func (c *BackendOnepageController) Delete() { claims, err := c.cmsClaims() if err != nil { c.cmsJSONErr(401, 401, err.Error()) return } tid := cmsEffectiveTid(&c.Controller, claims) id, _ := c.GetUint64(":id") if id == 0 { c.cmsJSONErr(400, 400, "无效ID") return } now := time.Now() n, err := models.Orm.QueryTable(new(models.CmsFrontendOnepage)). Filter("id", id). Filter("tid", tid). Filter("delete_time__isnull", true). Update(map[string]interface{}{"delete_time": now, "update_time": now}) if err != nil { c.cmsJSONErr(500, 500, "删除失败") return } if n == 0 { c.cmsJSONErr(404, 404, "单页不存在") return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} _ = c.ServeJSON() }