更新补号机制
This commit is contained in:
+68
-64
@@ -91,7 +91,7 @@ func (c *ApiGetCardController) GetCard() {
|
||||
}
|
||||
|
||||
func (c *ApiGetCardController) extractCursor(platform, dataType string, now time.Time) {
|
||||
for {
|
||||
c.extractWithProbe("cursor", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) {
|
||||
var row models.PlatformAccountPoolCursor
|
||||
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
|
||||
Filter("is_extracted", 0).
|
||||
@@ -100,6 +100,55 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, now time
|
||||
qs = qs.Filter("data_type", dataType)
|
||||
}
|
||||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||||
return 0, nil, nil, "", "", nil, err
|
||||
}
|
||||
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, row.IsUsed, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ApiGetCardController) extractWindsurf(platform, dataType string, now time.Time) {
|
||||
c.extractWithProbe("windsurf", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) {
|
||||
var row models.PlatformAccountPoolWindsurf
|
||||
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("delete_time__isnull", true)
|
||||
if dataType != "" {
|
||||
qs = qs.Filter("data_type", dataType)
|
||||
}
|
||||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||||
return 0, nil, nil, "", "", nil, err
|
||||
}
|
||||
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ApiGetCardController) extractKrio(platform, dataType string, now time.Time) {
|
||||
c.extractWithProbe("krio", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) {
|
||||
var row models.PlatformAccountPoolKiro
|
||||
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("delete_time__isnull", true)
|
||||
if dataType != "" {
|
||||
qs = qs.Filter("data_type", dataType)
|
||||
}
|
||||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||||
return 0, nil, nil, "", "", nil, err
|
||||
}
|
||||
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil
|
||||
})
|
||||
}
|
||||
|
||||
type poolRowFetcher func() (id uint64, account, password *string, token, rowDataType string, isUsed *int8, err error)
|
||||
|
||||
// extractWithProbe 按 id 顺序提取并探测 Token 可用性;不可用则标记已提取并继续下一条。
|
||||
func (c *ApiGetCardController) extractWithProbe(
|
||||
module, platform, dataType string,
|
||||
now time.Time,
|
||||
fetch poolRowFetcher,
|
||||
) {
|
||||
for {
|
||||
id, account, password, token, rowDataType, isUsed, err := fetch()
|
||||
if err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
c.cardErr(404, 404, "暂无可用卡密")
|
||||
} else {
|
||||
@@ -108,85 +157,40 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, now time
|
||||
return
|
||||
}
|
||||
|
||||
_, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
|
||||
Filter("id", row.ID).
|
||||
tableName := poolTableName(module)
|
||||
if tableName == "" {
|
||||
c.cardErr(500, 500, "无效模块")
|
||||
return
|
||||
}
|
||||
_, err = models.Orm.QueryTable(tableName).
|
||||
Filter("id", id).
|
||||
Update(map[string]interface{}{
|
||||
"is_extracted": 1,
|
||||
"extracted_time": now,
|
||||
"extracted_platform": platform,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.cardErr(500, 500, "提取失败")
|
||||
return
|
||||
}
|
||||
|
||||
// Cursor 号池需要先判断可用状态:is_used=1 才发送给前端;
|
||||
// is_used=0(已用完/不可用)或 NULL(未探测)则继续提取下一条。
|
||||
if row.IsUsed != nil && *row.IsUsed == 1 {
|
||||
c.cardOK(buildCardResult(&row.Account, &row.Password, row.Token, row.DataType))
|
||||
return
|
||||
// 已有探测结论:可用则直接返回,不可用则继续下一条。
|
||||
if known, available := poolIsUsedAvailable(isUsed); known {
|
||||
if available {
|
||||
c.cardOK(buildCardResult(account, password, token, rowDataType))
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ApiGetCardController) extractWindsurf(platform, dataType string, now time.Time) {
|
||||
var row models.PlatformAccountPoolWindsurf
|
||||
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("delete_time__isnull", true)
|
||||
if dataType != "" {
|
||||
qs = qs.Filter("data_type", dataType)
|
||||
}
|
||||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
c.cardErr(404, 404, "暂无可用卡密")
|
||||
} else {
|
||||
c.cardErr(500, 500, "查询失败")
|
||||
if !poolProbeToken(module, rowDataType, token, id) {
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
_, err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
|
||||
Filter("id", row.ID).
|
||||
Update(map[string]interface{}{
|
||||
"is_extracted": 1,
|
||||
"extracted_time": now,
|
||||
"extracted_platform": platform,
|
||||
})
|
||||
if err != nil {
|
||||
c.cardErr(500, 500, "提取失败")
|
||||
return
|
||||
}
|
||||
c.cardOK(buildCardResult(&row.Account, &row.Password, row.Token, row.DataType))
|
||||
}
|
||||
|
||||
func (c *ApiGetCardController) extractKrio(platform, dataType string, now time.Time) {
|
||||
var row models.PlatformAccountPoolKiro
|
||||
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("delete_time__isnull", true)
|
||||
if dataType != "" {
|
||||
qs = qs.Filter("data_type", dataType)
|
||||
}
|
||||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
c.cardErr(404, 404, "暂无可用卡密")
|
||||
} else {
|
||||
c.cardErr(500, 500, "查询失败")
|
||||
}
|
||||
c.cardOK(buildCardResult(account, password, token, rowDataType))
|
||||
return
|
||||
}
|
||||
_, err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
|
||||
Filter("id", row.ID).
|
||||
Update(map[string]interface{}{
|
||||
"is_extracted": 1,
|
||||
"extracted_time": now,
|
||||
"extracted_platform": platform,
|
||||
})
|
||||
if err != nil {
|
||||
c.cardErr(500, 500, "提取失败")
|
||||
return
|
||||
}
|
||||
c.cardOK(buildCardResult(&row.Account, &row.Password, row.Token, row.DataType))
|
||||
}
|
||||
|
||||
// buildCardResult 根据账号类型返回格式化字符串
|
||||
|
||||
@@ -0,0 +1,954 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// BackendArticleController CMS 文章管理
|
||||
type BackendArticleController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// BackendArticleCategoryController CMS 文章分类管理
|
||||
type BackendArticleCategoryController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func (c *BackendArticleCategoryController) cmsClaims() (*jwtutil.Claims, error) {
|
||||
return cmsBackendClaims(&c.Controller)
|
||||
}
|
||||
|
||||
func cmsBackendClaims(c *beego.Controller) (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "backend" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func cmsEffectiveTid(c *beego.Controller, claims *jwtutil.Claims) uint64 {
|
||||
_ = c.ParseForm(1 << 20)
|
||||
if tid, err := c.GetUint64("tid"); err == nil && tid > 0 {
|
||||
return tid
|
||||
}
|
||||
if h := strings.TrimSpace(c.Ctx.Request.Header.Get("X-Tenant-Id")); h != "" {
|
||||
if v, e := strconv.ParseUint(h, 10, 64); e == nil {
|
||||
return v
|
||||
}
|
||||
}
|
||||
if claims != nil && claims.TenantId > 0 {
|
||||
return uint64(claims.TenantId)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) 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 *BackendArticleCategoryController) cmsJSONErr(httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func cmsEnsureTables(c *beego.Controller) bool {
|
||||
if err := models.EnsureCmsArticleTables(); err != nil {
|
||||
c.Ctx.Output.SetStatus(500)
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "初始化文章表失败: " + err.Error()}
|
||||
_ = c.ServeJSON()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func cmsParseUintArg(v interface{}) uint64 {
|
||||
switch x := v.(type) {
|
||||
case float64:
|
||||
if x > 0 {
|
||||
return uint64(x)
|
||||
}
|
||||
case string:
|
||||
if n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 64); err == nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func cmsArticleToListItem(row models.CmsArticle, cateName string) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"title": row.Title,
|
||||
"author": row.Author,
|
||||
"cate": cateName,
|
||||
"cate_id": row.CateID,
|
||||
"status": row.Status,
|
||||
"views": row.Views,
|
||||
"likes": row.Likes,
|
||||
"top": row.Top,
|
||||
"recommend": row.Recommend,
|
||||
"publish_date": models.CmsFormatTime(row.PublishTime),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
func cmsArticleToDetail(row models.CmsArticle, cateName string) map[string]interface{} {
|
||||
pub := models.CmsFormatTime(row.PublishTime)
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"title": row.Title,
|
||||
"author": row.Author,
|
||||
"cate": cateName,
|
||||
"cate_id": row.CateID,
|
||||
"content": row.Content,
|
||||
"desc": row.Desc,
|
||||
"image": row.Image,
|
||||
"is_trans": row.IsTrans,
|
||||
"transurl": row.TransURL,
|
||||
"status": row.Status,
|
||||
"views": row.Views,
|
||||
"view_count": row.Views,
|
||||
"likes": row.Likes,
|
||||
"top": row.Top,
|
||||
"recommend": row.Recommend,
|
||||
"publish_time": pub,
|
||||
"publish_date": pub,
|
||||
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": models.CmsFormatTime(row.UpdateTime),
|
||||
}
|
||||
}
|
||||
|
||||
func cmsCategoryToMap(row models.CmsArticleCategory) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"name": row.Name,
|
||||
"label": row.Name,
|
||||
"cid": row.Cid,
|
||||
"parentId": row.Cid,
|
||||
"image": row.Image,
|
||||
"desc": row.Desc,
|
||||
"remark": row.Desc,
|
||||
"sort": row.Sort,
|
||||
"status": row.Status,
|
||||
}
|
||||
}
|
||||
|
||||
// List GET /backend/articlesList
|
||||
func (c *BackendArticleController) List() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
cateFilter := strings.TrimSpace(c.GetString("cate"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("title__icontains", keyword)
|
||||
}
|
||||
if cateFilter != "" {
|
||||
if cid, err := strconv.ParseUint(cateFilter, 10, 64); err == nil && cid > 0 {
|
||||
qs = qs.Filter("cate_id", cid)
|
||||
}
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsArticle
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("-top", "-id").Limit(pageSize, offset).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.cmsJSONErr(500, 500, "获取文章列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
cateIDs := make([]uint64, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
if r.CateID > 0 {
|
||||
cateIDs = append(cateIDs, r.CateID)
|
||||
}
|
||||
}
|
||||
cateNames := models.CmsCategoryNameMap(tid, cateIDs)
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
list = append(list, cmsArticleToListItem(r, cateNames[r.CateID]))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ListAll GET /backend/allarticles
|
||||
func (c *BackendArticleController) ListAll() {
|
||||
c.List()
|
||||
}
|
||||
|
||||
// Detail GET /backend/articles/:id
|
||||
func (c *BackendArticleController) Detail() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
var row models.CmsArticle
|
||||
err = models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row)
|
||||
if err == orm.ErrNoRows {
|
||||
c.cmsJSONErr(404, 404, "文章不存在")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
cateName := ""
|
||||
if row.CateID > 0 {
|
||||
names := models.CmsCategoryNameMap(tid, []uint64{row.CateID})
|
||||
cateName = names[row.CateID]
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": cmsArticleToDetail(row, cateName),
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsArticlePayload struct {
|
||||
Title string `json:"title"`
|
||||
Author string `json:"author"`
|
||||
Cate interface{} `json:"cate"`
|
||||
Content string `json:"content"`
|
||||
Desc string `json:"desc"`
|
||||
Image string `json:"image"`
|
||||
IsTrans int8 `json:"is_trans"`
|
||||
TransURL *string `json:"transurl"`
|
||||
Status int8 `json:"status"`
|
||||
IgnoreSimilarity int `json:"ignore_similarity"`
|
||||
}
|
||||
|
||||
// Create POST /backend/createarticle
|
||||
func (c *BackendArticleController) Create() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 cmsArticlePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
title := strings.TrimSpace(p.Title)
|
||||
if title == "" {
|
||||
c.cmsJSONErr(400, 400, "标题不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if p.IgnoreSimilarity != 1 {
|
||||
similar, serr := models.CmsSimilarArticles(tid, title, 5)
|
||||
if serr == nil && len(similar) > 0 {
|
||||
c.Ctx.Output.SetStatus(409)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 409,
|
||||
"msg": "检测到相似标题",
|
||||
"data": map[string]interface{}{"similar_articles": similar},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
cateID := cmsParseUintArg(p.Cate)
|
||||
row := models.CmsArticle{
|
||||
Tid: tid,
|
||||
Title: title,
|
||||
Author: strings.TrimSpace(p.Author),
|
||||
CateID: cateID,
|
||||
Content: p.Content,
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
Image: strings.TrimSpace(p.Image),
|
||||
IsTrans: p.IsTrans,
|
||||
TransURL: p.TransURL,
|
||||
Status: p.Status,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
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/editarticle/:id
|
||||
func (c *BackendArticleController) Update() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 cmsArticlePayload
|
||||
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),
|
||||
"author": strings.TrimSpace(p.Author),
|
||||
"cate_id": cmsParseUintArg(p.Cate),
|
||||
"content": p.Content,
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"image": strings.TrimSpace(p.Image),
|
||||
"is_trans": p.IsTrans,
|
||||
"transurl": p.TransURL,
|
||||
"status": p.Status,
|
||||
"update_time": now,
|
||||
}
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
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/deletearticle/:id
|
||||
func (c *BackendArticleController) Delete() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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.CmsArticle)).
|
||||
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()
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) setArticleFlag(field string, value int8) {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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()
|
||||
fields := map[string]interface{}{field: value, "update_time": now}
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
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": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) Publish() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
var uid uint64
|
||||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||||
if len(raw) > 0 {
|
||||
var body struct {
|
||||
UID uint64 `json:"uid"`
|
||||
}
|
||||
_ = json.Unmarshal(raw, &body)
|
||||
uid = body.UID
|
||||
}
|
||||
if uid == 0 && claims != nil {
|
||||
uid = uint64(claims.UserID)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
fields := map[string]interface{}{
|
||||
"status": int8(2),
|
||||
"publish_time": now,
|
||||
"publisher_id": uid,
|
||||
"update_time": now,
|
||||
}
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
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()
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) Unpublish() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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.CmsArticle)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"status": int8(3), "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()
|
||||
}
|
||||
|
||||
func (c *BackendArticleController) Recommend() { c.setArticleFlag("recommend", 1) }
|
||||
func (c *BackendArticleController) Unrecommend() { c.setArticleFlag("recommend", 0) }
|
||||
func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) }
|
||||
func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) }
|
||||
|
||||
// List GET /backend/categories
|
||||
func (c *BackendArticleCategoryController) List() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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)
|
||||
pageSize, _ := c.GetInt("pageSize", 0)
|
||||
if pageSize == 0 {
|
||||
pageSize, _ = c.GetInt("limit", 1000)
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 1000
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("name__icontains", keyword)
|
||||
}
|
||||
|
||||
total, _ := qs.Count()
|
||||
var rows []models.CmsArticleCategory
|
||||
offset := (page - 1) * pageSize
|
||||
_, err = qs.OrderBy("sort", "id").Limit(pageSize, 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, cmsCategoryToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total, "records": list},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ListAll GET /backend/allcategories
|
||||
func (c *BackendArticleCategoryController) ListAll() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
qs := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("name__icontains", keyword)
|
||||
}
|
||||
|
||||
var rows []models.CmsArticleCategory
|
||||
_, err = qs.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, cmsCategoryToMap(r))
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Detail GET /backend/categories/:id
|
||||
func (c *BackendArticleCategoryController) Detail() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
var row models.CmsArticleCategory
|
||||
err = models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row)
|
||||
if err == orm.ErrNoRows {
|
||||
c.cmsJSONErr(404, 404, "分类不存在")
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.cmsJSONErr(500, 500, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cmsCategoryToMap(row)}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type cmsCategoryPayload struct {
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Desc string `json:"desc"`
|
||||
Sort int `json:"sort"`
|
||||
Status int8 `json:"status"`
|
||||
Cid uint64 `json:"cid"`
|
||||
}
|
||||
|
||||
// Create POST /backend/createCategory
|
||||
func (c *BackendArticleCategoryController) Create() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 cmsCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
name := strings.TrimSpace(p.Name)
|
||||
if name == "" {
|
||||
c.cmsJSONErr(400, 400, "分类名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
row := models.CmsArticleCategory{
|
||||
Tid: tid,
|
||||
Cid: p.Cid,
|
||||
Name: name,
|
||||
Image: strings.TrimSpace(p.Image),
|
||||
Desc: strings.TrimSpace(p.Desc),
|
||||
Sort: p.Sort,
|
||||
Status: p.Status,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
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/editCategory/:id
|
||||
func (c *BackendArticleCategoryController) Update() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 cmsCategoryPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{
|
||||
"name": strings.TrimSpace(p.Name),
|
||||
"image": strings.TrimSpace(p.Image),
|
||||
"desc": strings.TrimSpace(p.Desc),
|
||||
"sort": p.Sort,
|
||||
"status": p.Status,
|
||||
"cid": p.Cid,
|
||||
"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()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/categories/:id
|
||||
func (c *BackendArticleCategoryController) Delete() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
childCnt, _ := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("tid", tid).
|
||||
Filter("cid", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if childCnt > 0 {
|
||||
c.cmsJSONErr(400, 400, "请先删除子分类")
|
||||
return
|
||||
}
|
||||
|
||||
articleCnt, _ := models.Orm.QueryTable(new(models.CmsArticle)).
|
||||
Filter("tid", tid).
|
||||
Filter("cate_id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if articleCnt > 0 {
|
||||
c.cmsJSONErr(400, 400, "该分类下还有文章,无法删除")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
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()
|
||||
}
|
||||
|
||||
// UpdateStatus PATCH /backend/categories/:id/status
|
||||
func (c *BackendArticleCategoryController) UpdateStatus() {
|
||||
if !cmsEnsureTables(&c.Controller) {
|
||||
return
|
||||
}
|
||||
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 struct {
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
c.cmsJSONErr(400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.CmsArticleCategory)).
|
||||
Filter("id", id).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"status": p.Status, "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()
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// BackendDomainPoolController 主域名池管理
|
||||
type BackendDomainPoolController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
// BackendTenantDomainController 租户域名管理
|
||||
type BackendTenantDomainController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func requireBackend(c *beego.Controller) (*jwtutil.Claims, error) {
|
||||
auth := c.Ctx.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return nil, fmt.Errorf("未登录")
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, fmt.Errorf("认证信息格式错误")
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无效的token")
|
||||
}
|
||||
if claims.UserType != "backend" {
|
||||
return nil, fmt.Errorf("无权访问")
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// ===== 主域名池 =====
|
||||
|
||||
// Index GET /backend/domain/pool/index?page=&pageSize=&main_domain=&status=
|
||||
func (c *BackendDomainPoolController) Index() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
mainDomain := strings.TrimSpace(c.GetString("main_domain"))
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemDomainPool)).Filter("delete_time__isnull", true)
|
||||
if mainDomain != "" {
|
||||
qs = qs.Filter("main_domain__icontains", mainDomain)
|
||||
}
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemDomainPool
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名池失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
item := map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"main_domain": rows[i].MainDomain,
|
||||
"status": rows[i].Status,
|
||||
"create_time": rows[i].CreateTime.Format("2006-01-02 15:04:05"),
|
||||
"update_time": "",
|
||||
}
|
||||
if rows[i].UpdateTime != nil {
|
||||
item["update_time"] = rows[i].UpdateTime.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{
|
||||
"list": list,
|
||||
"total": total,
|
||||
},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetEnabledDomains GET /backend/domain/pool/getEnabledDomains
|
||||
func (c *BackendDomainPoolController) GetEnabledDomains() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemDomainPool
|
||||
_, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取主域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
out := make([]map[string]interface{}, 0, len(rows))
|
||||
for i := range rows {
|
||||
out = append(out, map[string]interface{}{
|
||||
"id": rows[i].ID,
|
||||
"main_domain": rows[i].MainDomain,
|
||||
"status": rows[i].Status,
|
||||
})
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Create POST /backend/domain/pool/create
|
||||
func (c *BackendDomainPoolController) Create() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p domainPoolPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
md := strings.TrimSpace(p.MainDomain)
|
||||
if md == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不能为空")
|
||||
return
|
||||
}
|
||||
if p.Status != 0 && p.Status != 1 {
|
||||
p.Status = 1
|
||||
}
|
||||
// 简单去重
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("main_domain", md).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名已存在")
|
||||
return
|
||||
}
|
||||
row := &models.SystemDomainPool{MainDomain: md, Status: p.Status}
|
||||
if _, err := models.Orm.Insert(row); err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "创建成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Update POST /backend/domain/pool/update
|
||||
func (c *BackendDomainPoolController) Update() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p domainPoolPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "id 不能为空")
|
||||
return
|
||||
}
|
||||
md := strings.TrimSpace(p.MainDomain)
|
||||
if md == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不能为空")
|
||||
return
|
||||
}
|
||||
if p.Status != 0 && p.Status != 1 {
|
||||
p.Status = 1
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"main_domain": md, "status": p.Status, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/domain/pool/delete/:id
|
||||
func (c *BackendDomainPoolController) Delete() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "无效ID")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ToggleStatus POST /backend/domain/pool/toggleStatus body:{id}
|
||||
func (c *BackendDomainPoolController) ToggleStatus() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemDomainPool
|
||||
if err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "记录不存在")
|
||||
return
|
||||
}
|
||||
newStatus := int8(1)
|
||||
if row.Status == 1 {
|
||||
newStatus = 0
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("id", p.ID).
|
||||
Update(map[string]interface{}{"status": newStatus, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "切换失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ===== 租户域名 =====
|
||||
|
||||
// Index GET /backend/domain/tenant/index?page=&pageSize=&tid=&status=&sub_domain=
|
||||
func (c *BackendTenantDomainController) Index() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
if pageSize > 200 {
|
||||
pageSize = 200
|
||||
}
|
||||
|
||||
tid, _ := c.GetUint64("tid")
|
||||
statusStr := strings.TrimSpace(c.GetString("status"))
|
||||
subDomain := strings.TrimSpace(c.GetString("sub_domain"))
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("delete_time__isnull", true)
|
||||
if tid > 0 {
|
||||
qs = qs.Filter("tid", tid)
|
||||
}
|
||||
if statusStr != "" {
|
||||
if st, err := strconv.Atoi(statusStr); err == nil {
|
||||
qs = qs.Filter("status", st)
|
||||
}
|
||||
}
|
||||
if subDomain != "" {
|
||||
qs = qs.Filter("sub_domain__icontains", subDomain)
|
||||
}
|
||||
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
var rows []models.SystemTenantDomain
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取租户域名失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
list := make([]models.SystemTenantDomain, 0, len(rows))
|
||||
list = append(list, rows...)
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": map[string]interface{}{"list": list, "total": total},
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// MyDomains GET /backend/domain/tenant/myDomains?tid=1
|
||||
func (c *BackendTenantDomainController) MyDomains() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
tid, _ := c.GetUint64("tid")
|
||||
if tid == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "租户ID不能为空")
|
||||
return
|
||||
}
|
||||
var rows []models.SystemTenantDomain
|
||||
_, err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("tid", tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "获取失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Apply POST /backend/domain/tenant/apply body:{tid,sub_domain,main_domain}
|
||||
func (c *BackendTenantDomainController) Apply() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
Tid uint64 `json:"tid"`
|
||||
SubDomain string `json:"sub_domain"`
|
||||
MainDomain string `json:"main_domain"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
if p.Tid == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "租户ID不能为空")
|
||||
return
|
||||
}
|
||||
sub := strings.TrimSpace(p.SubDomain)
|
||||
main := strings.TrimSpace(p.MainDomain)
|
||||
if sub == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "二级域名前缀不能为空")
|
||||
return
|
||||
}
|
||||
if main == "" {
|
||||
jsonErr(&c.Controller, 400, 400, "请选择主域名")
|
||||
return
|
||||
}
|
||||
if !subDomainRe.MatchString(sub) {
|
||||
jsonErr(&c.Controller, 400, 400, "二级域名前缀格式不正确")
|
||||
return
|
||||
}
|
||||
|
||||
// 该租户是否已有域名
|
||||
cnt, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("tid", p.Tid).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if cnt > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该租户已有域名,请删除后再次申请")
|
||||
return
|
||||
}
|
||||
|
||||
// 主域名存在且启用
|
||||
var pool models.SystemDomainPool
|
||||
if err := models.Orm.QueryTable(new(models.SystemDomainPool)).
|
||||
Filter("main_domain", main).
|
||||
Filter("status", 1).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&pool); err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "主域名不存在或已禁用")
|
||||
return
|
||||
}
|
||||
|
||||
// 二级域名是否已被使用(同主域名下)
|
||||
used, _ := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("sub_domain", sub).
|
||||
Filter("main_domain", main).
|
||||
Filter("delete_time__isnull", true).
|
||||
Count()
|
||||
if used > 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该二级域名已被使用")
|
||||
return
|
||||
}
|
||||
|
||||
full := sub + "." + main
|
||||
now := time.Now()
|
||||
tid := p.Tid
|
||||
row := &models.SystemTenantDomain{
|
||||
Tid: &tid,
|
||||
SubDomain: &sub,
|
||||
MainDomain: &main,
|
||||
FullDomain: &full,
|
||||
Status: 0,
|
||||
CreateTime: now,
|
||||
UpdateTime: &now,
|
||||
}
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "申请失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "申请提交成功,等待审核", "data": map[string]interface{}{"id": uint64(id)}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Audit POST /backend/domain/tenant/audit body:{id,action} action=approve/reject
|
||||
func (c *BackendTenantDomainController) Audit() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemTenantDomain
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
if row.Status != 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "该域名已审核过了")
|
||||
return
|
||||
}
|
||||
newStatus := 2
|
||||
msg := "已拒绝"
|
||||
if strings.ToLower(strings.TrimSpace(p.Action)) == "approve" {
|
||||
newStatus = 1
|
||||
msg = "审核通过"
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "审核失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ToggleStatus POST /backend/domain/tenant/toggleStatus body:{id}
|
||||
func (c *BackendTenantDomainController) ToggleStatus() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var p struct {
|
||||
ID uint64 `json:"id"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &p); err != nil || p.ID == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
var row models.SystemTenantDomain
|
||||
if err := models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).One(&row); err != nil {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
if row.Status == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "审核中不可操作")
|
||||
return
|
||||
}
|
||||
newStatus := 2
|
||||
if row.Status == 2 {
|
||||
newStatus = 1
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.SystemTenantDomain)).Filter("id", p.ID).Update(map[string]interface{}{
|
||||
"status": newStatus,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "操作失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/domain/tenant/delete/:id
|
||||
func (c *BackendTenantDomainController) Delete() {
|
||||
if _, err := requireBackend(&c.Controller); err != nil {
|
||||
jsonErr(&c.Controller, 401, 401, err.Error())
|
||||
return
|
||||
}
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
jsonErr(&c.Controller, 400, 400, "参数错误")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
n, err := models.Orm.QueryTable(new(models.SystemTenantDomain)).
|
||||
Filter("id", id).
|
||||
Filter("delete_time__isnull", true).
|
||||
Update(map[string]interface{}{"delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
jsonErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
if n == 0 {
|
||||
jsonErr(&c.Controller, 404, 404, "域名不存在")
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// 用于复杂筛选时可扩展:当前保留 orm.Condition import,避免被 gofmt 删除
|
||||
var _ = orm.NewCondition
|
||||
@@ -0,0 +1,21 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
func jsonErr(c *beego.Controller, httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type domainPoolPayload struct {
|
||||
ID uint64 `json:"id"`
|
||||
MainDomain string `json:"main_domain"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
var subDomainRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`)
|
||||
@@ -619,69 +619,150 @@ func replenishPoolRow(c *beego.Controller, module string) {
|
||||
platform := payload.Platform
|
||||
remark := strings.TrimSpace(payload.Remark)
|
||||
|
||||
replenishWithProbe(c, module, payload.Type, platform, remark, now)
|
||||
}
|
||||
|
||||
type poolReplenishCandidate struct {
|
||||
id uint64
|
||||
dataType string
|
||||
token string
|
||||
isUsed *int8
|
||||
row interface{}
|
||||
}
|
||||
|
||||
type poolReplenishFetcher func() (*poolReplenishCandidate, error)
|
||||
|
||||
// replenishWithProbe 按 id 顺序补号并探测;不可用则标记 is_extracted=2 后继续下一条。
|
||||
func replenishWithProbe(c *beego.Controller, module, dataType, platform, remark string, now time.Time) {
|
||||
var fetch poolReplenishFetcher
|
||||
switch module {
|
||||
case "cursor":
|
||||
var row models.PlatformAccountPoolCursor
|
||||
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
|
||||
Filter("is_extracted", 0).Filter("data_type", payload.Type).
|
||||
OrderBy("id").One(&row); err != nil {
|
||||
poolJSONErr(c, 404, 404, "暂无可用账号")
|
||||
return
|
||||
fetch = func() (*poolReplenishCandidate, error) {
|
||||
var row models.PlatformAccountPoolCursor
|
||||
err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("data_type", dataType).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("id").
|
||||
One(&row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &poolReplenishCandidate{
|
||||
id: row.ID, dataType: row.DataType, token: row.Token, isUsed: row.IsUsed, row: row,
|
||||
}, nil
|
||||
}
|
||||
if _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", row.ID).Update(map[string]interface{}{
|
||||
"is_extracted": int8(2), "extracted_time": now, "extracted_platform": platform, "remark": remark,
|
||||
}); err != nil {
|
||||
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
row.IsExtracted = 2
|
||||
row.ExtractedTime = &now
|
||||
row.ExtractedPlatform = &platform
|
||||
row.Remark = remark
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": row}
|
||||
case "windsurf":
|
||||
var row models.PlatformAccountPoolWindsurf
|
||||
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
|
||||
Filter("is_extracted", 0).Filter("data_type", payload.Type).
|
||||
OrderBy("id").One(&row); err != nil {
|
||||
poolJSONErr(c, 404, 404, "暂无可用账号")
|
||||
return
|
||||
fetch = func() (*poolReplenishCandidate, error) {
|
||||
var row models.PlatformAccountPoolWindsurf
|
||||
err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("data_type", dataType).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("id").
|
||||
One(&row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &poolReplenishCandidate{
|
||||
id: row.ID, dataType: row.DataType, token: row.Token, row: row,
|
||||
}, nil
|
||||
}
|
||||
if _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", row.ID).Update(map[string]interface{}{
|
||||
"is_extracted": int8(2), "extracted_time": now, "extracted_platform": platform, "remark": remark,
|
||||
}); err != nil {
|
||||
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
row.IsExtracted = 2
|
||||
row.ExtractedTime = &now
|
||||
row.ExtractedPlatform = &platform
|
||||
row.Remark = remark
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": row}
|
||||
case "krio":
|
||||
var row models.PlatformAccountPoolKiro
|
||||
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
|
||||
Filter("is_extracted", 0).Filter("data_type", payload.Type).
|
||||
OrderBy("id").One(&row); err != nil {
|
||||
poolJSONErr(c, 404, 404, "暂无可用账号")
|
||||
return
|
||||
fetch = func() (*poolReplenishCandidate, error) {
|
||||
var row models.PlatformAccountPoolKiro
|
||||
err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
|
||||
Filter("is_extracted", 0).
|
||||
Filter("data_type", dataType).
|
||||
Filter("delete_time__isnull", true).
|
||||
OrderBy("id").
|
||||
One(&row)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &poolReplenishCandidate{
|
||||
id: row.ID, dataType: row.DataType, token: row.Token, row: row,
|
||||
}, nil
|
||||
}
|
||||
if _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", row.ID).Update(map[string]interface{}{
|
||||
"is_extracted": int8(2), "extracted_time": now, "extracted_platform": platform, "remark": remark,
|
||||
}); err != nil {
|
||||
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
row.IsExtracted = 2
|
||||
row.ExtractedTime = &now
|
||||
row.ExtractedPlatform = &platform
|
||||
row.Remark = remark
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": row}
|
||||
default:
|
||||
poolJSONErr(c, 400, 400, "无效模块")
|
||||
return
|
||||
}
|
||||
_ = c.ServeJSON()
|
||||
|
||||
tableName := poolTableName(module)
|
||||
if tableName == "" {
|
||||
poolJSONErr(c, 400, 400, "无效模块")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
candidate, err := fetch()
|
||||
if err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
poolJSONErr(c, 404, 404, "暂无可用账号")
|
||||
} else {
|
||||
poolJSONErr(c, 500, 500, "查询失败")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
updateFields := map[string]interface{}{
|
||||
"is_extracted": int8(2),
|
||||
"extracted_time": now,
|
||||
"extracted_platform": platform,
|
||||
"remark": remark,
|
||||
"update_time": now,
|
||||
}
|
||||
if _, err = models.Orm.QueryTable(tableName).
|
||||
Filter("id", candidate.id).
|
||||
Update(updateFields); err != nil {
|
||||
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if known, available := poolIsUsedAvailable(candidate.isUsed); known {
|
||||
if !available {
|
||||
continue
|
||||
}
|
||||
} else if !poolProbeToken(module, candidate.dataType, candidate.token, candidate.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
data := replenishApplyResponse(candidate.row, platform, remark, now)
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": data}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func replenishApplyResponse(row interface{}, platform, remark string, now time.Time) interface{} {
|
||||
pf := platform
|
||||
switch r := row.(type) {
|
||||
case models.PlatformAccountPoolCursor:
|
||||
r.IsExtracted = 2
|
||||
r.ExtractedTime = &now
|
||||
r.ExtractedPlatform = &pf
|
||||
r.Remark = remark
|
||||
if r.IsUsed == nil || *r.IsUsed != 1 {
|
||||
used := int8(1)
|
||||
r.IsUsed = &used
|
||||
}
|
||||
return r
|
||||
case models.PlatformAccountPoolWindsurf:
|
||||
r.IsExtracted = 2
|
||||
r.ExtractedTime = &now
|
||||
r.ExtractedPlatform = &pf
|
||||
r.Remark = remark
|
||||
return r
|
||||
case models.PlatformAccountPoolKiro:
|
||||
r.IsExtracted = 2
|
||||
r.ExtractedTime = &now
|
||||
r.ExtractedPlatform = &pf
|
||||
r.Remark = remark
|
||||
return r
|
||||
default:
|
||||
return row
|
||||
}
|
||||
}
|
||||
|
||||
func updatePoolRemark(c *beego.Controller, module string) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -45,12 +44,6 @@ func requirePlatform(c *beego.Controller) (*jwtutil.Claims, error) {
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func jsonErr(c *beego.Controller, httpStatus, bizCode int, msg string) {
|
||||
c.Ctx.Output.SetStatus(httpStatus)
|
||||
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// ===== 主域名池 =====
|
||||
|
||||
// Index GET /platform/domain/pool/index?page=&pageSize=&main_domain=&status=
|
||||
@@ -150,12 +143,6 @@ func (c *PlatformDomainPoolController) GetEnabledDomains() {
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
type domainPoolPayload struct {
|
||||
ID uint64 `json:"id"`
|
||||
MainDomain string `json:"main_domain"`
|
||||
Status int8 `json:"status"`
|
||||
}
|
||||
|
||||
// Create POST /platform/domain/pool/create
|
||||
func (c *PlatformDomainPoolController) Create() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
@@ -397,8 +384,6 @@ func (c *PlatformTenantDomainController) MyDomains() {
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
var subDomainRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]$`)
|
||||
|
||||
// Apply POST /platform/domain/tenant/apply body:{tid,sub_domain,main_domain}
|
||||
func (c *PlatformTenantDomainController) Apply() {
|
||||
if _, err := requirePlatform(&c.Controller); err != nil {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/tokenprobe"
|
||||
)
|
||||
|
||||
func poolTableName(module string) string {
|
||||
switch module {
|
||||
case "cursor":
|
||||
return new(models.PlatformAccountPoolCursor).TableName()
|
||||
case "windsurf":
|
||||
return new(models.PlatformAccountPoolWindsurf).TableName()
|
||||
case "krio":
|
||||
return new(models.PlatformAccountPoolKiro).TableName()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// poolNeedsTokenProbe account 类型无 Token,无需探测;tk / account_tk 需探测。
|
||||
func poolNeedsTokenProbe(dataType, token string) bool {
|
||||
if strings.TrimSpace(token) == "" {
|
||||
return false
|
||||
}
|
||||
return dataType != "account"
|
||||
}
|
||||
|
||||
func poolSaveCursorIsUsed(id uint64, isUsed int8) {
|
||||
_, _ = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
|
||||
Filter("id", id).
|
||||
Update(map[string]interface{}{
|
||||
"is_used": isUsed,
|
||||
"update_time": time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
// poolProbeToken 探测 Token;cursor 模块会回写 is_used。
|
||||
func poolProbeToken(module, dataType, token string, rowID uint64) bool {
|
||||
if !poolNeedsTokenProbe(dataType, token) {
|
||||
return true
|
||||
}
|
||||
r := tokenprobe.ProbeOfficial(module, token)
|
||||
if module == "cursor" && rowID > 0 {
|
||||
var isUsed int8
|
||||
if r.OK {
|
||||
isUsed = 1
|
||||
}
|
||||
poolSaveCursorIsUsed(rowID, isUsed)
|
||||
}
|
||||
return r.OK
|
||||
}
|
||||
|
||||
// poolIsUsedAvailable 已有探测结论时:1=可用,0=不可用,nil=未探测。
|
||||
func poolIsUsedAvailable(isUsed *int8) (known bool, available bool) {
|
||||
if isUsed == nil {
|
||||
return false, false
|
||||
}
|
||||
return true, *isUsed == 1
|
||||
}
|
||||
Reference in New Issue
Block a user