From e6b84aad80f2ffa460dd11fa02f9478ff8880d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E5=BC=BA?= <357099073@qq.com> Date: Tue, 2 Jun 2026 21:15:13 +0800 Subject: [PATCH] gengxin --- controllers/api_getcard.go | 61 +- controllers/backend_auth.go | 322 +++++++ controllers/backend_file.go | 907 ++++++++++++++++++ controllers/backend_login_verify.go | 249 +++++ controllers/backend_modules.go | 70 ++ controllers/backend_operation_log.go | 332 +++++++ controllers/backend_site_settings.go | 607 ++++++++++++ controllers/platform_file.go | 64 +- ..._settings.go => platform_site_settings.go} | 99 +- ...tting.go => system_tenant_site_setting.go} | 5 +- routers/backend/backend.go | 60 +- routers/platform/platform.go | 4 +- 12 files changed, 2650 insertions(+), 130 deletions(-) create mode 100644 controllers/backend_auth.go create mode 100644 controllers/backend_file.go create mode 100644 controllers/backend_login_verify.go create mode 100644 controllers/backend_modules.go create mode 100644 controllers/backend_operation_log.go create mode 100644 controllers/backend_site_settings.go rename controllers/{site_settings.go => platform_site_settings.go} (68%) rename models/{tenant_site_setting.go => system_tenant_site_setting.go} (91%) diff --git a/controllers/api_getcard.go b/controllers/api_getcard.go index 8fce360..886f307 100644 --- a/controllers/api_getcard.go +++ b/controllers/api_getcard.go @@ -32,7 +32,7 @@ var validModules = map[string]bool{ "krio": true, } -func (c *ApiGetCardController) cardErr(httpStatus, code int, msg string) { +func (c *ApiGetCardController) cardErr(_ int, _ int, msg string) { c.Ctx.Output.SetStatus(200) c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8") _ = c.Ctx.Output.Body([]byte("error:" + msg)) @@ -91,33 +91,42 @@ func (c *ApiGetCardController) GetCard() { } func (c *ApiGetCardController) extractCursor(platform, dataType string, now time.Time) { - var row models.PlatformAccountPoolCursor - qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - 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, "查询失败") + for { + var row models.PlatformAccountPoolCursor + qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + 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, "查询失败") + } + return + } + + _, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + 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 + } + + // 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 } - return } - _, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). - 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) extractWindsurf(platform, dataType string, now time.Time) { diff --git a/controllers/backend_auth.go b/controllers/backend_auth.go new file mode 100644 index 0000000..0ad1747 --- /dev/null +++ b/controllers/backend_auth.go @@ -0,0 +1,322 @@ +package controllers + +import ( + "encoding/json" + "io" + "strings" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +type backendAuthLoginRequest struct { + TenantName string `json:"tenant_name"` + Account string `json:"account"` + Password string `json:"password"` + Code string `json:"code"` + // 极验4验证参数 + CaptchaID string `json:"captcha_id"` + LotNumber string `json:"lot_number"` + PassToken string `json:"pass_token"` + GenTime string `json:"gen_time"` + CaptchaOutput string `json:"captcha_output"` +} + +// BackendAuthController backend 端认证控制器 +type BackendAuthController struct { + beego.Controller +} + +func (c *BackendAuthController) serveJSON(data map[string]interface{}) { + c.Data["json"] = data + _ = c.ServeJSON() +} + +// LoginBackend backend 登录(需要租户) +func (c *BackendAuthController) LoginBackend() { + var req backendAuthLoginRequest + + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + var err error + body, err = io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + } + if len(body) == 0 { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + if err := json.Unmarshal(body, &req); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + + req.TenantName = strings.TrimSpace(req.TenantName) + req.Account = strings.TrimSpace(req.Account) + req.Password = strings.TrimSpace(req.Password) + if req.TenantName == "" || req.Account == "" || req.Password == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "租户名称、用户名或密码不能为空"}) + return + } + + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled == 1 { + if cfg.VerifyType == "geetest4" { + if req.LotNumber == "" || req.PassToken == "" || req.GenTime == "" || req.CaptchaOutput == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) + return + } + // TODO: 集成极验4服务端 SDK 后在这里进行二次校验 + } else if cfg.VerifyType == "geetest3" { + if req.CaptchaOutput == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请完成人机验证"}) + return + } + // TODO: 集成极验3服务端 SDK 后在这里进行二次校验 + } else if cfg.VerifyType == "sms" || cfg.VerifyType == "email" { + if strings.TrimSpace(req.Code) == "" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "请输入验证码"}) + return + } + if err := services.VerifyBackendLoginCode(req.TenantName, req.Account, cfg.VerifyType, req.Code); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) + return + } + } + } + + token, loginUser, err := services.BackendLogin(req.TenantName, req.Account, req.Password) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": err.Error()}) + return + } + + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "登录成功", + "data": map[string]interface{}{ + "token": token, + "user": map[string]interface{}{ + "id": loginUser.ID, + "account": loginUser.Account, + "name": loginUser.Name, + "tid": loginUser.Tid, + "rid": loginUser.Rid, + "avatar": loginUser.Avatar, + "role_name": loginUser.RoleName, + }, + }, + }) +} + +// GetCurrentUser 当前登录 backend 用户信息,需 Bearer Token +func (c *BackendAuthController) GetCurrentUser() { + authHeader := c.Ctx.Request.Header.Get("Authorization") + if authHeader == "" { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "未登录"}) + return + } + authParts := strings.SplitN(authHeader, " ", 2) + if len(authParts) != 2 || authParts[0] != "Bearer" { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}) + return + } + claims, err := jwtutil.ParseToken(authParts[1]) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "无效的token"}) + return + } + if claims.UserType != "backend" { + c.serveJSON(map[string]interface{}{"code": 403, "msg": "无权访问"}) + return + } + + var tenantUser models.SystemTenantUser + err = models.Orm.QueryTable(new(models.SystemTenantUser)). + Filter("uid", claims.UserID). + Filter("tid", claims.TenantId). + One(&tenantUser) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "用户不存在"}) + return + } + if tenantUser.Status == 0 { + c.serveJSON(map[string]interface{}{"code": 401, "msg": "账号已禁用"}) + return + } + + account := "" + if tenantUser.Account != nil { + account = strings.TrimSpace(*tenantUser.Account) + } + name := "" + if tenantUser.Name != nil { + name = strings.TrimSpace(*tenantUser.Name) + } + + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "id": tenantUser.Uid, + "account": account, + "name": name, + "tid": tenantUser.Tid, + "rid": 0, + "avatar": "", + "role_name": "", + }, + }) +} + +// SendLoginCode 发送 backend 登录验证码 +func (c *BackendAuthController) SendLoginCode() { + var req struct { + Account string `json:"account"` + TenantName string `json:"tenant_name"` + Channel string `json:"channel"` + } + body := c.Ctx.Input.RequestBody + if len(body) == 0 { + var err error + body, err = io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + } + if err := json.Unmarshal(body, &req); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "参数错误"}) + return + } + + cfg, _ := models.GetPlatformLoginVerify() + if cfg.OpenVerifyEnabled != 1 { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "当前未开启验证"}) + return + } + channel := strings.TrimSpace(req.Channel) + if channel == "" { + channel = cfg.VerifyType + } + if channel != "sms" && channel != "email" { + c.serveJSON(map[string]interface{}{"code": 400, "msg": "仅支持短信/邮箱验证码"}) + return + } + if err := services.SendBackendLoginCode(req.TenantName, req.Account, channel); err != nil { + c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()}) + return + } + c.serveJSON(map[string]interface{}{"code": 200, "msg": "验证码已发送"}) +} + +// LoginBySms 手机号验证码登录(占位实现) +func (c *BackendAuthController) LoginBySms() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "手机号验证码登录暂未实现", + }) +} + +// Logout backend 退出登录(当前为无状态直接返回成功) +func (c *BackendAuthController) Logout() { + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "退出成功", + }) +} + +// GetGeetest3Infos 获取 backend 极验3.0配置 +func (c *BackendAuthController) GetGeetest3Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest3ID == nil || cfg.Geetest3Key == nil { + c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验3参数"}) + return + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest3ID, + "captcha_key": *cfg.Geetest3Key, + }, + }) +} + +// GetGeetest4Infos 获取 backend 极验4.0配置 +func (c *BackendAuthController) GetGeetest4Infos() { + cfg, _ := models.GetPlatformLoginVerify() + if cfg.Geetest4ID == nil || cfg.Geetest4Key == nil { + c.serveJSON(map[string]interface{}{"code": 404, "msg": "未配置极验4参数"}) + return + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "captcha_id": *cfg.Geetest4ID, + "captcha_key": *cfg.Geetest4Key, + }, + }) +} + +// GetOpenVerify 判断是否开启 backend 登录验证 +func (c *BackendAuthController) GetOpenVerify() { + cfg, _ := models.GetPlatformLoginVerify() + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } + c.serveJSON(map[string]interface{}{ + "code": 200, + "msg": "ok", + "data": []map[string]string{ + { + "label": "openVerify", + "value": openVerify, + }, + { + "label": "verifyType", + "value": cfg.VerifyType, + }, + }, + }) +} + +// Register 注册(占位实现) +func (c *BackendAuthController) Register() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "注册暂未实现", + }) +} + +// SendRegisterCode 发送注册验证码(占位实现) +func (c *BackendAuthController) SendRegisterCode() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "发送注册验证码暂未实现", + }) +} + +// ResetPassword 忘记密码重置(占位实现) +func (c *BackendAuthController) ResetPassword() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "重置密码暂未实现", + }) +} + +// SendResetCode 发送找回密码验证码(占位实现) +func (c *BackendAuthController) SendResetCode() { + c.serveJSON(map[string]interface{}{ + "code": 501, + "msg": "发送找回密码验证码暂未实现", + }) +} diff --git a/controllers/backend_file.go b/controllers/backend_file.go new file mode 100644 index 0000000..e7217b6 --- /dev/null +++ b/controllers/backend_file.go @@ -0,0 +1,907 @@ +package controllers + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + "server/services" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendFileController 平台端文件管理(yz_system_files / yz_system_files_category) +type BackendFileController struct { + beego.Controller +} + +const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 + +var fileTypeByCategory = map[string]uint8{ + "image": 1, + "document": 2, + "video": 3, + "audio": 4, + "appsupgrade": 2, +} + +var allowedExtByCategory = map[string][]string{ + "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, + "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, + "video": {"mp4", "webm", "mov"}, + "audio": {"mp3", "wav", "ogg"}, + // 安装包 / 软件升级(上传时 cate 选 appsupgrade 分类即可,扩展名在此放行) + "appsupgrade": {"zip", "exe", "dmg", "msi", "msix", "apk", "deb", "rpm", "7z", "tar", "gz", "pkg"}, +} + +func (c *BackendFileController) backendClaims() (*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 (c *BackendFileController) effectiveTid(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 *BackendFileController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendFileController) jsonOK(data interface{}) { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +func detectFileType(ext string) uint8 { + ext = strings.ToLower(strings.TrimPrefix(ext, ".")) + for cat, exts := range allowedExtByCategory { + for _, e := range exts { + if e == ext { + if t, ok := fileTypeByCategory[cat]; ok { + return t + } + return 2 + } + } + } + return 2 +} + +func fileExt(name string) string { + name = strings.TrimSpace(name) + if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { + return strings.ToLower(name[i+1:]) + } + return "" +} + +func fileToMap(f *models.SystemFile) map[string]interface{} { + ct := f.CreateTime.Format("2006-01-02 15:04:05") + m := map[string]interface{}{ + "id": f.ID, + "tid": f.Tid, + "name": f.Name, + "type": f.Type, + "cate": f.Cate, + "size": f.Size, + "src": f.Src, + "uploader": f.Uploader, + "md5": f.Md5, + "create_time": ct, + "createTime": ct, + "groupId": f.Cate, + "url": f.Src, + } + if f.Uid != nil { + m["uid"] = *f.Uid + } + if f.Tuid != nil { + m["tuid"] = *f.Tuid + } + return m +} + +func removePhysicalBySrc(webSrc string) { + webSrc = strings.TrimSpace(webSrc) + if webSrc == "" { + return + } + webSrc = strings.TrimPrefix(webSrc, "/") + _ = os.Remove(webSrc) +} + +// GetAllFiles GET /backend/allfiles +func (c *BackendFileController) GetAllFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 10 + } + cate, _ := c.GetUint64("cate") + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("delete_time__isnull", true) + if cate > 0 { + qs = qs.Filter("cate", cate) + } + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取文件列表失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, fileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + }) +} + +// GetUserCate GET /backend/usercate +func (c *BackendFileController) GetUserCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + + var cates []models.SystemFilesCategory + _, err = models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + OrderBy("id"). + All(&cates) + if err != nil { + c.jsonErr(500, 500, "获取用户分类失败: "+err.Error()) + return + } + out := make([]map[string]interface{}, 0, len(cates)) + for i := range cates { + cnt, _ := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cates[i].ID). + Filter("delete_time__isnull", true). + Count() + out = append(out, map[string]interface{}{ + "id": cates[i].ID, + "name": cates[i].Name, + "total": cnt, + }) + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type createCateBody struct { + Name string `json:"name"` + Tuid *uint64 `json:"tuid"` +} + +// CreateFileCate POST /backend/createfilecate +func (c *BackendFileController) CreateFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body createCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + uid := uint64(claims.UserID) + row := &models.SystemFilesCategory{ + Tid: tid, + Name: name, + Uid: &uid, + Tuid: body.Tuid, + } + id, err := models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "新建文件分组失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "新建文件分组成功", + "data": map[string]interface{}{"id": uint64(id)}, + } + _ = c.ServeJSON() +} + +type renameCateBody struct { + Name string `json:"name"` +} + +// RenameFileCate POST /backend/renamefilecate/:id +func (c *BackendFileController) RenameFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body renameCateBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + name := strings.TrimSpace(body.Name) + if name == "" { + c.jsonErr(400, 400, "分组名称不能为空") + return + } + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"name": name}) + if err != nil { + c.jsonErr(500, 500, "重命名文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "重命名文件分组成功"} + _ = c.ServeJSON() +} + +// DeleteFileCate DELETE /backend/deletefilecate/:id +func (c *BackendFileController) DeleteFileCate() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的分组ID") + return + } + cnt, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("cate", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if cnt > 0 { + c.jsonErr(400, 400, fmt.Sprintf("该分组下还有 %d 个文件,请先删除分组内文件!", cnt)) + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFilesCategory)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除文件分组失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "分组不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除文件分组成功"} + _ = c.ServeJSON() +} + +// GetCateFiles GET /backend/catefiles/:id +func (c *BackendFileController) GetCateFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + cateID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.jsonErr(400, 400, "无效的分类ID") + return + } + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 24) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 24 + } + keyword := strings.TrimSpace(c.GetString("keyword")) + + qs := models.Orm.QueryTable(new(models.SystemFile)). + Filter("tid", tid). + Filter("cate", cateID). + Filter("delete_time__isnull", true) + if keyword != "" { + qs = qs.Filter("name__icontains", keyword) + } + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + var rows []models.SystemFile + _, err = qs.OrderBy("-create_time").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取分类文件失败: "+err.Error()) + return + } + list := make([]map[string]interface{}, 0, len(rows)) + for i := range rows { + list = append(list, fileToMap(&rows[i])) + } + c.jsonOK(map[string]interface{}{ + "list": list, + "total": total, + "page": page, + "pageSize": pageSize, + "categoryId": cateID, + }) +} + +// GetFileByID GET /backend/file/:id +func (c *BackendFileController) GetFileByID() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + c.jsonOK(fileToMap(&f)) +} + +// UploadFile POST /backend/uploadfile +func (c *BackendFileController) UploadFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { + c.jsonErr(400, 400, "解析上传失败: "+err.Error()) + return + } + fh, header, err := c.GetFile("file") + if err != nil || fh == nil { + c.jsonErr(400, 400, "请选择要上传的文件") + return + } + defer fh.Close() + + if header != nil && header.Size > fileUploadMaxBytes { + c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB)) + return + } + + ext := fileExt(header.Filename) + if ext == "" { + c.jsonErr(400, 400, "无法识别文件扩展名") + return + } + + // 获取存储服务 + storageService, err := services.GetStorageService() + if err != nil { + c.jsonErr(500, 500, "获取存储服务失败: "+err.Error()) + return + } + + // 上传文件 + result, err := storageService.Upload(fh, header) + if err != nil { + c.jsonErr(500, 500, "上传文件失败: "+err.Error()) + return + } + + // 检查文件是否已存在(通过MD5) + var exist models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("md5", result.MD5). + Filter("tid", tid). + Filter("delete_time__isnull", true). + One(&exist) + if err == nil { + // 文件已存在,返回已有记录 + c.Data["json"] = map[string]interface{}{ + "code": 201, + "msg": "文件已存在", + "data": map[string]interface{}{ + "url": exist.Src, + "id": exist.ID, + "name": exist.Name, + }, + } + _ = c.ServeJSON() + return + } + + // 获取分类 + cateStr := c.GetString("cate") + var cate uint64 + if cateStr != "" { + cate, _ = strconv.ParseUint(cateStr, 10, 64) + } + + adminID := uint64(claims.UserID) + var tuidPtr *uint64 + if ts := strings.TrimSpace(c.GetString("tuid")); ts != "" { + if v, e := strconv.ParseUint(ts, 10, 64); e == nil { + tuidPtr = &v + } + } + + // 保存文件记录到数据库 + row := &models.SystemFile{ + Tid: tid, + Uid: &adminID, + Tuid: tuidPtr, + Name: header.Filename, + Type: detectFileType(ext), + Cate: cate, + Size: uint64(result.Size), + Src: result.URL, + Uploader: adminID, + Md5: result.MD5, + } + id, err := models.Orm.Insert(row) + if err != nil { + // 数据库插入失败,尝试删除已上传的文件 + _ = storageService.Delete(result.Key) + c.jsonErr(500, 500, "上传失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "上传成功", + "data": map[string]interface{}{ + "url": result.URL, + "id": uint64(id), + "name": header.Filename, + }, + } + _ = c.ServeJSON() +} + +func md5HashFile(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + h := md5.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + +type updateFileBody struct { + Name *string `json:"name"` + Cate *uint64 `json:"cate"` +} + +// UpdateFile POST /backend/updatefile/:id +func (c *BackendFileController) UpdateFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body updateFileBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + up := map[string]interface{}{} + if body.Name != nil { + up["name"] = strings.TrimSpace(*body.Name) + } + if body.Cate != nil { + up["cate"] = *body.Cate + } + if len(up) == 0 { + c.jsonErr(400, 400, "无更新数据") + return + } + now := time.Now() + up["update_time"] = now + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "更新失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功"} + _ = c.ServeJSON() +} + +// DeleteFile DELETE /backend/deletefile/:id +func (c *BackendFileController) DeleteFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +// DeleteFilePermanently DELETE /backend/deletefilepermanently/:id +func (c *BackendFileController) DeleteFilePermanently() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + var f models.SystemFile + err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if err != nil { + c.jsonErr(404, 404, "文件不存在") + return + } + removePhysicalBySrc(f.Src) + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "永久删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "永久删除成功"} + _ = c.ServeJSON() +} + +// MoveFile GET /backend/movefile/:id +func (c *BackendFileController) MoveFile() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效的文件ID") + return + } + cate, _ := c.GetUint64("cate") + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "移动成功"} + _ = c.ServeJSON() +} + +type idsBody struct { + IDs []uint64 `json:"ids"` + Cate *uint64 `json:"cate"` +} + +// BatchDeleteFiles POST /backend/batchdeletefiles +func (c *BackendFileController) BatchDeleteFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的文件") + return + } + now := time.Now() + for _, id := range body.IDs { + var f models.SystemFile + e := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id", id). + Filter("tid", tid). + One(&f) + if e == nil && f.Src != "" { + removePhysicalBySrc(f.Src) + } + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// BatchDeleteFilesPermanently POST /backend/batchDeleteFilesPermanently +func (c *BackendFileController) BatchDeleteFilesPermanently() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要彻底删除的文件") + return + } + var rows []models.SystemFile + _, err = models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + for i := range rows { + removePhysicalBySrc(rows[i].Src) + } + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Delete() + if err != nil { + c.jsonErr(500, 500, "批量彻底删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量彻底删除成功"} + _ = c.ServeJSON() +} + +// UploadAvatar POST /backend/uploadavatar(占位) +func (c *BackendFileController) UploadAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"} + _ = c.ServeJSON() +} + +// UpdateAvatar POST /backend/uploadavatar/:id(占位) +func (c *BackendFileController) UpdateAvatar() { + c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"} + _ = c.ServeJSON() +} + +// BatchMoveFiles POST /backend/batchMoveFiles +func (c *BackendFileController) BatchMoveFiles() { + claims, err := c.backendClaims() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + tid := c.effectiveTid(claims) + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var body idsBody + if err := json.Unmarshal(raw, &body); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(body.IDs) == 0 { + c.jsonErr(400, 400, "请选择要移动的文件") + return + } + if body.Cate == nil { + c.jsonErr(400, 400, "缺少目标分类") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemFile)). + Filter("id__in", body.IDs). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"cate": *body.Cate, "update_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量移动失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "文件不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量移动成功"} + _ = c.ServeJSON() +} diff --git a/controllers/backend_login_verify.go b/controllers/backend_login_verify.go new file mode 100644 index 0000000..9a8e0a9 --- /dev/null +++ b/controllers/backend_login_verify.go @@ -0,0 +1,249 @@ +package controllers + +import ( + "encoding/json" + "io" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendLoginVerifyController 后台登录验证配置 +// 对应前端 backend/src/api/sitesettings.js: +// - GET /backend/loginVerifyInfos +// - POST /backend/saveloginVerifyInfos +type BackendLoginVerifyController struct { + beego.Controller +} + +func (c *BackendLoginVerifyController) backendLoginVerifyClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, errBackendLoginVerify("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, errBackendLoginVerify("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, errBackendLoginVerify("无效的token") + } + if claims.UserType != "backend" { + return nil, errBackendLoginVerify("无权访问") + } + return claims, nil +} + +type backendLoginVerifyError string + +func (e backendLoginVerifyError) Error() string { + return string(e) +} + +func errBackendLoginVerify(msg string) error { + return backendLoginVerifyError(msg) +} + +func (c *BackendLoginVerifyController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +type backendLoginVerifyPayload struct { + OpenVerify *bool `json:"openVerify"` + OpenVerifyInt *int8 `json:"openVerify_enabled"` + VerifyModel string `json:"verifyModel"` + UseGeetest string `json:"use_geetest"` + Geetest3ID *string `json:"geetest3ID"` + Geetest3IDSnake *string `json:"geetest3_id"` + Geetest3Key *string `json:"geetest3KEY"` + Geetest3KeySnake *string `json:"geetest3_key"` + Geetest4ID *string `json:"geetest4ID"` + Geetest4IDSnake *string `json:"geetest4_id"` + Geetest4Key *string `json:"geetest4KEY"` + Geetest4KeySnake *string `json:"geetest4_key"` +} + +func backendVerifyTypeToModel(v string) string { + switch strings.TrimSpace(v) { + case "captcha": + return "1" + case "sms": + return "2" + case "email": + return "3" + case "geetest3": + return "4" + case "geetest", "geetest4": + return "5" + default: + return "1" + } +} + +func backendVerifyModelToType(v string) string { + switch strings.TrimSpace(v) { + case "1": + return "captcha" + case "2": + return "sms" + case "3": + return "email" + case "4": + return "geetest3" + case "5": + return "geetest4" + default: + switch strings.TrimSpace(v) { + case "captcha", "sms", "email", "geetest", "geetest3", "geetest4": + return strings.TrimSpace(v) + default: + return "captcha" + } + } +} + +func backendStringPtrValue(primary, fallback *string) string { + if primary != nil { + return *primary + } + if fallback != nil { + return *fallback + } + return "" +} + +func backendStringPtrOrNil(primary, fallback *string) *string { + value := strings.TrimSpace(backendStringPtrValue(primary, fallback)) + if value == "" { + return nil + } + return &value +} + +// GetLoginVerifyInfos GET /backend/loginVerifyInfos +func (c *BackendLoginVerifyController) GetLoginVerifyInfos() { + if _, err := c.backendLoginVerifyClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + cfg, err := models.GetPlatformLoginVerify() + if err != nil { + c.jsonErr(500, 500, "获取配置失败") + return + } + + openVerify := "0" + if cfg.OpenVerifyEnabled == 1 { + openVerify = "1" + } + + data := []map[string]string{ + {"label": "openVerify", "value": openVerify}, + {"label": "verifyModel", "value": backendVerifyTypeToModel(cfg.VerifyType)}, + {"label": "geetest3ID", "value": backendStringPtrValue(cfg.Geetest3ID, nil)}, + {"label": "geetest3KEY", "value": backendStringPtrValue(cfg.Geetest3Key, nil)}, + {"label": "geetest4ID", "value": backendStringPtrValue(cfg.Geetest4ID, nil)}, + {"label": "geetest4KEY", "value": backendStringPtrValue(cfg.Geetest4Key, nil)}, + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data} + _ = c.ServeJSON() +} + +// SaveLoginVerifyInfos POST /backend/saveloginVerifyInfos +func (c *BackendLoginVerifyController) SaveLoginVerifyInfos() { + if _, err := c.backendLoginVerifyClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p backendLoginVerifyPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + openVerifyEnabled := int8(0) + if p.OpenVerify != nil && *p.OpenVerify { + openVerifyEnabled = 1 + } + if p.OpenVerifyInt != nil { + openVerifyEnabled = *p.OpenVerifyInt + } + + verifyModel := p.VerifyModel + if strings.TrimSpace(verifyModel) == "" { + verifyModel = p.UseGeetest + } + verifyType := backendVerifyModelToType(verifyModel) + + geetest3ID := backendStringPtrOrNil(p.Geetest3ID, p.Geetest3IDSnake) + geetest3Key := backendStringPtrOrNil(p.Geetest3Key, p.Geetest3KeySnake) + geetest4ID := backendStringPtrOrNil(p.Geetest4ID, p.Geetest4IDSnake) + geetest4Key := backendStringPtrOrNil(p.Geetest4Key, p.Geetest4KeySnake) + + if verifyType == "geetest3" { + if geetest3ID == nil || geetest3Key == nil { + c.jsonErr(400, 400, "极验3.0 ID和KEY不能为空") + return + } + } + if verifyType == "geetest4" || verifyType == "geetest" { + if geetest4ID == nil || geetest4Key == nil { + c.jsonErr(400, 400, "极验4.0 ID和KEY不能为空") + return + } + } + + now := time.Now() + var existed models.PlatformLoginVerify + err = models.Orm.QueryTable(new(models.PlatformLoginVerify)).OrderBy("-id").One(&existed) + if err == nil { + _, err = models.Orm.QueryTable(new(models.PlatformLoginVerify)). + Filter("id", existed.ID). + Update(map[string]interface{}{ + "open_verify_enabled": openVerifyEnabled, + "verify_type": verifyType, + "geetest3_id": geetest3ID, + "geetest3_key": geetest3Key, + "geetest4_id": geetest4ID, + "geetest4_key": geetest4Key, + "update_time": now, + }) + if err != nil { + c.jsonErr(500, 500, "保存失败") + return + } + } else { + row := &models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: geetest3ID, + Geetest3Key: geetest3Key, + Geetest4ID: geetest4ID, + Geetest4Key: geetest4Key, + UpdateTime: &now, + } + if _, err := models.Orm.Insert(row); err != nil { + c.jsonErr(500, 500, "保存失败") + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/controllers/backend_modules.go b/controllers/backend_modules.go new file mode 100644 index 0000000..973e07c --- /dev/null +++ b/controllers/backend_modules.go @@ -0,0 +1,70 @@ +package controllers + +import ( + "fmt" + "strings" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendModulesController backend 模块接口(yz_system_modules) +type BackendModulesController struct { + beego.Controller +} + +func (c *BackendModulesController) backendModulesClaims() (*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 (c *BackendModulesController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetTenantList GET /backend/modules/getTenantList +// 返回当前 backend 账号可见的模块。当前实现:返回 status=1 且 is_show=1 的全部模块。 +func (c *BackendModulesController) GetTenantList() { + if _, err := c.backendModulesClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + var rows []models.SystemModules + _, err := models.Orm.QueryTable(new(models.SystemModules)). + Filter("delete_time__isnull", true). + Filter("status", 1). + Filter("is_show", 1). + OrderBy("sort", "id"). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败:"+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "获取成功", + "data": map[string]interface{}{ + "list": rows, + "total": len(rows), + }, + } + _ = c.ServeJSON() +} diff --git a/controllers/backend_operation_log.go b/controllers/backend_operation_log.go new file mode 100644 index 0000000..837e48d --- /dev/null +++ b/controllers/backend_operation_log.go @@ -0,0 +1,332 @@ +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" +) + +// BackendOperationLogController 操作日志(yz_system_operation_log) +type BackendOperationLogController struct { + beego.Controller +} + +func (c *BackendOperationLogController) backendClaims() (*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 (c *BackendOperationLogController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// List GET /backend/operationLogs?page=1&pageSize=20&keyword=&module=&action=&status=&startTime=&endTime= +func (c *BackendOperationLogController) List() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + page, _ := c.GetInt("page", 1) + pageSize, _ := c.GetInt("pageSize", 20) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 20 + } + if pageSize > 200 { + pageSize = 200 + } + + keyword := strings.TrimSpace(c.GetString("keyword")) + module := strings.TrimSpace(c.GetString("module")) + action := strings.TrimSpace(c.GetString("action")) + statusStr := strings.TrimSpace(c.GetString("status")) + startTimeStr := strings.TrimSpace(c.GetString("startTime")) + endTimeStr := strings.TrimSpace(c.GetString("endTime")) + + qs := models.Orm.QueryTable(new(models.SystemOperationLog)).Filter("delete_time__isnull", true) + + // 条件拼装 + cond := orm.NewCondition() + needCond := false + + if module != "" { + cond = cond.And("module", module) + needCond = true + } + if action != "" { + cond = cond.And("action", action) + needCond = true + } + if statusStr != "" { + if st, err := strconv.Atoi(statusStr); err == nil { + cond = cond.And("status", st) + needCond = true + } + } + if keyword != "" { + kw := orm.NewCondition(). + Or("module__icontains", keyword). + Or("action__icontains", keyword). + Or("method__icontains", keyword). + Or("url__icontains", keyword). + Or("ip__icontains", keyword). + Or("user_agent__icontains", keyword) + if uid, err := strconv.ParseUint(keyword, 10, 64); err == nil && uid > 0 { + kw = kw.Or("user_id", uid) + } + cond = cond.AndCond(kw) + needCond = true + } + if t, err := parseTimeFlexible(startTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__gte", t) + needCond = true + } + if t, err := parseTimeFlexible(endTimeStr); err == nil && !t.IsZero() { + cond = cond.And("create_time__lte", t) + needCond = true + } + + if needCond { + qs = qs.SetCond(cond) + } + + total, err := qs.Count() + if err != nil { + c.jsonErr(500, 500, "获取操作日志失败: "+err.Error()) + return + } + + var rows []models.SystemOperationLog + _, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows) + if err != nil { + c.jsonErr(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, + "tid": rows[i].Tid, + "user_id": rows[i].UserID, + "module": rows[i].Module, + "action": rows[i].Action, + "method": rows[i].Method, + "url": rows[i].URL, + "ip": rows[i].IP, + "user_agent": rows[i].UserAgent, + "request_data": rows[i].RequestData, + "response_data": rows[i].ResponseData, + "status": rows[i].Status, + "error_message": rows[i].ErrorMessage, + "execution_time": rows[i].ExecutionTime, + "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() +} + +// Detail GET /backend/operationLogs/:id +func (c *BackendOperationLogController) Detail() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + var row models.SystemOperationLog + err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.jsonErr(404, 404, "记录不存在") + return + } + out := map[string]interface{}{ + "id": row.ID, + "tid": row.Tid, + "user_id": row.UserID, + "module": row.Module, + "action": row.Action, + "method": row.Method, + "url": row.URL, + "ip": row.IP, + "user_agent": row.UserAgent, + "request_data": row.RequestData, + "response_data": row.ResponseData, + "status": row.Status, + "error_message": row.ErrorMessage, + "execution_time": row.ExecutionTime, + "create_time": row.CreateTime.Format("2006-01-02 15:04:05"), + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +// Delete DELETE /backend/operationLogs/:id +func (c *BackendOperationLogController) Delete() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + idStr := c.Ctx.Input.Param(":id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil || id == 0 { + c.jsonErr(400, 400, "无效ID") + return + } + now := time.Now() + n, err := models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id", id). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "删除失败: "+err.Error()) + return + } + if n == 0 { + c.jsonErr(404, 404, "记录不存在") + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"} + _ = c.ServeJSON() +} + +type backendBatchDeletePayload struct { + IDs []uint64 `json:"ids"` +} + +// BatchDelete POST /backend/operationLogs/batchDelete +func (c *BackendOperationLogController) BatchDelete() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendBatchDeletePayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + if len(p.IDs) == 0 { + c.jsonErr(400, 400, "请选择要删除的日志") + return + } + now := time.Now() + _, err = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("id__in", p.IDs). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{"delete_time": now}) + if err != nil { + c.jsonErr(500, 500, "批量删除失败: "+err.Error()) + return + } + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "批量删除成功"} + _ = c.ServeJSON() +} + +// Statistics GET /backend/operationLogs/statistics +// 供前端筛选项:modules/actions +func (c *BackendOperationLogController) Statistics() { + if _, err := c.backendClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + var moduleRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("module__isnull", false). + Limit(1000). + All(&moduleRows, "Module") + modSet := map[string]struct{}{} + for i := range moduleRows { + m := strings.TrimSpace(moduleRows[i].Module) + if m != "" { + modSet[m] = struct{}{} + } + } + modules := make([]string, 0, len(modSet)) + for k := range modSet { + modules = append(modules, k) + } + + var actionRows []models.SystemOperationLog + _, _ = models.Orm.QueryTable(new(models.SystemOperationLog)). + Filter("delete_time__isnull", true). + Filter("action__isnull", false). + Limit(1000). + All(&actionRows, "Action") + actSet := map[string]struct{}{} + for i := range actionRows { + a := strings.TrimSpace(actionRows[i].Action) + if a != "" { + actSet[a] = struct{}{} + } + } + actions := make([]string, 0, len(actSet)) + for k := range actSet { + actions = append(actions, k) + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "modules": modules, + "actions": actions, + }, + } + _ = c.ServeJSON() +} diff --git a/controllers/backend_site_settings.go b/controllers/backend_site_settings.go new file mode 100644 index 0000000..e3ee990 --- /dev/null +++ b/controllers/backend_site_settings.go @@ -0,0 +1,607 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +// BackendSiteSettingsController 租户站点设置(站点基本信息) +// 对应前端 normalSettings.vue 的: +// - GET /backend/normalInfos +// - POST /backend/saveNormalInfos +// - GET /platform/normalInfos +// - POST /platform/saveNormalInfos +type BackendSiteSettingsController struct { + beego.Controller +} + +func (c *BackendSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) claimsByPath() (*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") + } + + path := strings.ToLower(c.Ctx.Request.URL.Path) + if strings.HasPrefix(path, "/platform/") { + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + } else if strings.HasPrefix(path, "/backend/") { + if claims.UserType != "backend" { + return nil, fmt.Errorf("无权访问") + } + } + + return claims, nil +} + +func parseBackendUint64Flexible(v interface{}) uint64 { + if v == nil { + return 0 + } + switch x := v.(type) { + case float64: + if x <= 0 { + return 0 + } + return uint64(x) + case string: + s := strings.TrimSpace(x) + if s == "" { + return 0 + } + n, err := strconv.ParseUint(s, 10, 64) + if err != nil || n == 0 { + return 0 + } + return n + default: + return 0 + } +} + +type backendNormalInfosOutput struct { + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` +} + +// GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos +func (c *BackendSiteSettingsController) GetNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + // 优先使用 token 中的租户 id;若为 0,则允许前端通过查询参数传入(兼容历史/平台端)。 + tid := uint64(claims.TenantId) + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + + out := backendNormalInfosOutput{ + Sitename: "", + Companyintroduction: "", + Description: "", + Copyright: "", + Companyname: "", + Icp: "", + Logo: "", + Logow: "", + Ico: "", + } + + // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var rows []models.TenantSiteSetting + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Limit(1). + All(&rows) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + if len(rows) > 0 { + r := rows[0] + out.Sitename = r.Sitename + out.Companyintroduction = r.Companyintroduction + out.Logo = r.Logo + out.Logow = r.Logow + out.Ico = r.Ico + out.Description = r.Description + out.Copyright = r.Copyright + out.Companyname = r.Companyname + out.Icp = r.Icp + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendNormalInfosPayload struct { + // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) + Tid interface{} `json:"tid"` + + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` +} + +// SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos +func (c *BackendSiteSettingsController) SaveNormalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + var p backendNormalInfosPayload + if uerr := json.Unmarshal(raw, &p); uerr != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(p.Tid) + } + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + sitename := strings.TrimSpace(p.Sitename) + if sitename == "" { + c.jsonErr(400, 400, "站点名称不能为空") + return + } + + now := time.Now() + + up := map[string]interface{}{ + "tid": tid, + "sitename": sitename, + "companyintroduction": strings.TrimSpace(p.Companyintroduction), + "logo": strings.TrimSpace(p.Logo), + "logow": strings.TrimSpace(p.Logow), + "ico": strings.TrimSpace(p.Ico), + "description": strings.TrimSpace(p.Description), + "copyright": strings.TrimSpace(p.Copyright), + "companyname": strings.TrimSpace(p.Companyname), + "icp": strings.TrimSpace(p.Icp), + "update_time": now, + } + + cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Count() + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + if cnt == 0 { + row := &models.TenantSiteSetting{ + Tid: tid, + Sitename: sitename, + Companyintroduction: strings.TrimSpace(p.Companyintroduction), + Logo: strings.TrimSpace(p.Logo), + Logow: strings.TrimSpace(p.Logow), + Ico: strings.TrimSpace(p.Ico), + Description: strings.TrimSpace(p.Description), + Copyright: strings.TrimSpace(p.Copyright), + Companyname: strings.TrimSpace(p.Companyname), + Icp: strings.TrimSpace(p.Icp), + CreateTime: now, + UpdateTime: &now, + } + _, err = models.Orm.Insert(row) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + _, err = models.Orm.QueryTable(new(models.TenantSiteSetting)). + Filter("tid", tid). + Filter("delete_time__isnull", true). + Update(up) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +func (c *BackendSiteSettingsController) resolveBackendTenantID(claims *jwtutil.Claims, payloadTid interface{}) uint64 { + tid := uint64(claims.TenantId) + if tid == 0 { + tid = parseBackendUint64Flexible(payloadTid) + } + if tid == 0 { + tidStr := strings.TrimSpace(c.GetString("tid")) + if tidStr != "" { + if n, err := strconv.ParseUint(tidStr, 10, 64); err == nil { + tid = n + } + } + } + return tid +} + +func (c *BackendSiteSettingsController) ensureBackendSettingItemsTable() error { + _, err := models.Orm.Raw(` +CREATE TABLE IF NOT EXISTS yz_system_tenant_setting_items ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + tid BIGINT UNSIGNED NOT NULL DEFAULT 0, + setting_key VARCHAR(64) NOT NULL DEFAULT '', + setting_value LONGTEXT NULL, + create_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP, + update_time DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + delete_time DATETIME NULL DEFAULT NULL, + PRIMARY KEY (id), + UNIQUE KEY uk_tid_key (tid, setting_key), + KEY idx_tid (tid), + KEY idx_delete_time (delete_time) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户站点扩展设置'; +`).Exec() + return err +} + +func (c *BackendSiteSettingsController) getBackendSettingItems(tid uint64, keys []string) (map[string]string, error) { + out := make(map[string]string, len(keys)) + for _, key := range keys { + out[key] = "" + } + + if err := c.ensureBackendSettingItemsTable(); err != nil { + return out, err + } + + type rowItem struct { + SettingKey string + SettingValue string + } + var rows []rowItem + _, err := models.Orm.Raw( + "SELECT setting_key, IFNULL(setting_value, '') AS setting_value FROM yz_system_tenant_setting_items WHERE tid = ? AND setting_key IN ('"+strings.Join(keys, "','")+"') AND delete_time IS NULL", + tid, + ).QueryRows(&rows) + if err != nil { + return out, err + } + + for _, row := range rows { + out[row.SettingKey] = row.SettingValue + } + return out, nil +} + +func (c *BackendSiteSettingsController) saveBackendSettingItems(tid uint64, values map[string]string) error { + if err := c.ensureBackendSettingItemsTable(); err != nil { + return err + } + for key, value := range values { + _, err := models.Orm.Raw(` +INSERT INTO yz_system_tenant_setting_items (tid, setting_key, setting_value, create_time, update_time) +VALUES (?, ?, ?, NOW(), NOW()) +ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), update_time = NOW(), delete_time = NULL +`, tid, key, value).Exec() + if err != nil { + return err + } + } + return nil +} + +// GetLegalInfos GET /backend/legalInfos +func (c *BackendSiteSettingsController) GetLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": ""}, + {"label": "privacyTerms", "value": ""}, + }} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"legalNotice", "privacyTerms"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": []map[string]string{ + {"label": "legalNotice", "value": values["legalNotice"]}, + {"label": "privacyTerms", "value": values["privacyTerms"]}, + }} + _ = c.ServeJSON() +} + +type backendLegalInfosPayload struct { + Tid interface{} `json:"tid"` + LegalNotice string `json:"legalNotice"` + PrivacyTerms string `json:"privacyTerms"` +} + +// SaveLegalInfos POST /backend/saveLegalInfos +func (c *BackendSiteSettingsController) SaveLegalInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendLegalInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "legalNotice": strings.TrimSpace(p.LegalNotice), + "privacyTerms": strings.TrimSpace(p.PrivacyTerms), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanyInfos GET /backend/companyInfos +func (c *BackendSiteSettingsController) GetCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]interface{}{ + "contact_phone": "", + "contact_email": "", + "address": "", + "worktime": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + var row models.SystemTenant + err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + One(&row) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + if row.ContactPhone != nil { + out["contact_phone"] = *row.ContactPhone + } + if row.ContactEmail != nil { + out["contact_email"] = *row.ContactEmail + } + if row.Address != nil { + out["address"] = *row.Address + } + if row.Worktime != nil { + out["worktime"] = *row.Worktime + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanyInfosPayload struct { + Tid interface{} `json:"tid"` + ContactPhone string `json:"contact_phone"` + ContactEmail string `json:"contact_email"` + Address string `json:"address"` + Worktime string `json:"worktime"` +} + +// SaveCompanyInfos POST /backend/saveCompanyInfos +func (c *BackendSiteSettingsController) SaveCompanyInfos() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanyInfosPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + _, err = models.Orm.QueryTable(new(models.SystemTenant)). + Filter("id", tid). + Filter("delete_time__isnull", true). + Update(map[string]interface{}{ + "contact_phone": strings.TrimSpace(p.ContactPhone), + "contact_email": strings.TrimSpace(p.ContactEmail), + "address": strings.TrimSpace(p.Address), + "worktime": strings.TrimSpace(p.Worktime), + "update_time": time.Now(), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +// GetCompanySeo GET /backend/companySeo +func (c *BackendSiteSettingsController) GetCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + tid := c.resolveBackendTenantID(claims, nil) + out := map[string]string{ + "seoTitle": "", + "seoKeywords": "", + "seoDescription": "", + } + if tid == 0 { + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() + return + } + + values, err := c.getBackendSettingItems(tid, []string{"seoTitle", "seoKeywords", "seoDescription"}) + if err != nil { + c.jsonErr(500, 500, "获取失败: "+err.Error()) + return + } + out["seoTitle"] = values["seoTitle"] + out["seoKeywords"] = values["seoKeywords"] + out["seoDescription"] = values["seoDescription"] + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": out} + _ = c.ServeJSON() +} + +type backendCompanySeoPayload struct { + Tid interface{} `json:"tid"` + SeoTitle string `json:"seoTitle"` + SeoKeywords string `json:"seoKeywords"` + SeoDescription string `json:"seoDescription"` +} + +// SaveCompanySeo POST /backend/saveCompanySeo +func (c *BackendSiteSettingsController) SaveCompanySeo() { + claims, err := c.claimsByPath() + if err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p backendCompanySeoPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + tid := c.resolveBackendTenantID(claims, p.Tid) + if tid == 0 { + c.jsonErr(400, 400, "tid不能为空") + return + } + + err = c.saveBackendSettingItems(tid, map[string]string{ + "seoTitle": strings.TrimSpace(p.SeoTitle), + "seoKeywords": strings.TrimSpace(p.SeoKeywords), + "seoDescription": strings.TrimSpace(p.SeoDescription), + }) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} diff --git a/controllers/platform_file.go b/controllers/platform_file.go index 003a222..43942c5 100644 --- a/controllers/platform_file.go +++ b/controllers/platform_file.go @@ -23,10 +23,10 @@ type PlatformFileController struct { beego.Controller } -const fileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 -const fileUploadMaxBytes = fileUploadMaxMB * 1024 * 1024 +const platformFileUploadMaxMB = 2048 // 2GB,适用于大型软件安装包 +const platformFileUploadMaxBytes = platformFileUploadMaxMB * 1024 * 1024 -var fileTypeByCategory = map[string]uint8{ +var platformFileTypeByCategory = map[string]uint8{ "image": 1, "document": 2, "video": 3, @@ -34,7 +34,7 @@ var fileTypeByCategory = map[string]uint8{ "appsupgrade": 2, } -var allowedExtByCategory = map[string][]string{ +var platformAllowedExtByCategory = map[string][]string{ "image": {"jpg", "jpeg", "png", "gif", "bmp", "webp"}, "document": {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt"}, "video": {"mp4", "webm", "mov"}, @@ -89,12 +89,12 @@ func (c *PlatformFileController) jsonOK(data interface{}) { _ = c.ServeJSON() } -func detectFileType(ext string) uint8 { +func platformDetectFileType(ext string) uint8 { ext = strings.ToLower(strings.TrimPrefix(ext, ".")) - for cat, exts := range allowedExtByCategory { + for cat, exts := range platformAllowedExtByCategory { for _, e := range exts { if e == ext { - if t, ok := fileTypeByCategory[cat]; ok { + if t, ok := platformFileTypeByCategory[cat]; ok { return t } return 2 @@ -104,7 +104,7 @@ func detectFileType(ext string) uint8 { return 2 } -func fileExt(name string) string { +func platformFileExt(name string) string { name = strings.TrimSpace(name) if i := strings.LastIndex(name, "."); i >= 0 && i < len(name)-1 { return strings.ToLower(name[i+1:]) @@ -112,7 +112,7 @@ func fileExt(name string) string { return "" } -func fileToMap(f *models.SystemFile) map[string]interface{} { +func platformFileToMap(f *models.SystemFile) map[string]interface{} { ct := f.CreateTime.Format("2006-01-02 15:04:05") m := map[string]interface{}{ "id": f.ID, @@ -138,7 +138,7 @@ func fileToMap(f *models.SystemFile) map[string]interface{} { return m } -func removePhysicalBySrc(webSrc string) { +func platformRemovePhysicalBySrc(webSrc string) { webSrc = strings.TrimSpace(webSrc) if webSrc == "" { return @@ -188,7 +188,7 @@ func (c *PlatformFileController) GetAllFiles() { } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { - list = append(list, fileToMap(&rows[i])) + list = append(list, platformFileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, @@ -234,7 +234,7 @@ func (c *PlatformFileController) GetUserCate() { _ = c.ServeJSON() } -type createCateBody struct { +type platformCreateCateBody struct { Name string `json:"name"` Tuid *uint64 `json:"tuid"` } @@ -252,7 +252,7 @@ func (c *PlatformFileController) CreateFileCate() { c.jsonErr(400, 400, "参数错误") return } - var body createCateBody + var body platformCreateCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return @@ -282,7 +282,7 @@ func (c *PlatformFileController) CreateFileCate() { _ = c.ServeJSON() } -type renameCateBody struct { +type platformRenameCateBody struct { Name string `json:"name"` } @@ -305,7 +305,7 @@ func (c *PlatformFileController) RenameFileCate() { c.jsonErr(400, 400, "参数错误") return } - var body renameCateBody + var body platformRenameCateBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return @@ -421,7 +421,7 @@ func (c *PlatformFileController) GetCateFiles() { } list := make([]map[string]interface{}, 0, len(rows)) for i := range rows { - list = append(list, fileToMap(&rows[i])) + list = append(list, platformFileToMap(&rows[i])) } c.jsonOK(map[string]interface{}{ "list": list, @@ -456,7 +456,7 @@ func (c *PlatformFileController) GetFileByID() { c.jsonErr(404, 404, "文件不存在") return } - c.jsonOK(fileToMap(&f)) + c.jsonOK(platformFileToMap(&f)) } // UploadFile POST /platform/uploadfile @@ -467,7 +467,7 @@ func (c *PlatformFileController) UploadFile() { return } tid := c.effectiveTid(claims) - if err := c.Ctx.Request.ParseMultipartForm(fileUploadMaxBytes); err != nil { + if err := c.Ctx.Request.ParseMultipartForm(platformFileUploadMaxBytes); err != nil { c.jsonErr(400, 400, "解析上传失败: "+err.Error()) return } @@ -478,12 +478,12 @@ func (c *PlatformFileController) UploadFile() { } defer fh.Close() - if header != nil && header.Size > fileUploadMaxBytes { - c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", fileUploadMaxMB)) + if header != nil && header.Size > platformFileUploadMaxBytes { + c.jsonErr(400, 400, fmt.Sprintf("文件大小不能超过%dMB", platformFileUploadMaxMB)) return } - ext := fileExt(header.Filename) + ext := platformFileExt(header.Filename) if ext == "" { c.jsonErr(400, 400, "无法识别文件扩展名") return @@ -546,7 +546,7 @@ func (c *PlatformFileController) UploadFile() { Uid: &adminID, Tuid: tuidPtr, Name: header.Filename, - Type: detectFileType(ext), + Type: platformDetectFileType(ext), Cate: cate, Size: uint64(result.Size), Src: result.URL, @@ -573,7 +573,7 @@ func (c *PlatformFileController) UploadFile() { _ = c.ServeJSON() } -func md5HashFile(path string) (string, error) { +func platformMd5HashFile(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err @@ -586,7 +586,7 @@ func md5HashFile(path string) (string, error) { return hex.EncodeToString(h.Sum(nil)), nil } -type updateFileBody struct { +type platformUpdateFileBody struct { Name *string `json:"name"` Cate *uint64 `json:"cate"` } @@ -610,7 +610,7 @@ func (c *PlatformFileController) UpdateFile() { c.jsonErr(400, 400, "参数错误") return } - var body updateFileBody + var body platformUpdateFileBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return @@ -700,7 +700,7 @@ func (c *PlatformFileController) DeleteFilePermanently() { c.jsonErr(404, 404, "文件不存在") return } - removePhysicalBySrc(f.Src) + platformRemovePhysicalBySrc(f.Src) _, err = models.Orm.QueryTable(new(models.SystemFile)). Filter("id", id). Filter("tid", tid). @@ -746,7 +746,7 @@ func (c *PlatformFileController) MoveFile() { _ = c.ServeJSON() } -type idsBody struct { +type platformIdsBody struct { IDs []uint64 `json:"ids"` Cate *uint64 `json:"cate"` } @@ -764,7 +764,7 @@ func (c *PlatformFileController) BatchDeleteFiles() { c.jsonErr(400, 400, "参数错误") return } - var body idsBody + var body platformIdsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return @@ -781,7 +781,7 @@ func (c *PlatformFileController) BatchDeleteFiles() { Filter("tid", tid). One(&f) if e == nil && f.Src != "" { - removePhysicalBySrc(f.Src) + platformRemovePhysicalBySrc(f.Src) } } n, err := models.Orm.QueryTable(new(models.SystemFile)). @@ -813,7 +813,7 @@ func (c *PlatformFileController) BatchDeleteFilesPermanently() { c.jsonErr(400, 400, "参数错误") return } - var body idsBody + var body platformIdsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return @@ -832,7 +832,7 @@ func (c *PlatformFileController) BatchDeleteFilesPermanently() { return } for i := range rows { - removePhysicalBySrc(rows[i].Src) + platformRemovePhysicalBySrc(rows[i].Src) } n, err := models.Orm.QueryTable(new(models.SystemFile)). Filter("id__in", body.IDs). @@ -875,7 +875,7 @@ func (c *PlatformFileController) BatchMoveFiles() { c.jsonErr(400, 400, "参数错误") return } - var body idsBody + var body platformIdsBody if err := json.Unmarshal(raw, &body); err != nil { c.jsonErr(400, 400, "参数错误") return diff --git a/controllers/site_settings.go b/controllers/platform_site_settings.go similarity index 68% rename from controllers/site_settings.go rename to controllers/platform_site_settings.go index c8d2401..28f8c23 100644 --- a/controllers/site_settings.go +++ b/controllers/platform_site_settings.go @@ -14,23 +14,23 @@ import ( beego "github.com/beego/beego/v2/server/web" ) -// SiteSettingsController 租户站点设置(站点基本信息) +// PlatformSiteSettingsController 租户站点设置(站点基本信息) // 对应前端 normalSettings.vue 的: // - GET /backend/normalInfos // - POST /backend/saveNormalInfos // - GET /platform/normalInfos // - POST /platform/saveNormalInfos -type SiteSettingsController struct { +type PlatformSiteSettingsController struct { beego.Controller } -func (c *SiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { +func (c *PlatformSiteSettingsController) jsonErr(httpStatus, bizCode int, msg string) { c.Ctx.Output.SetStatus(httpStatus) c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} _ = c.ServeJSON() } -func (c *SiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { +func (c *PlatformSiteSettingsController) claimsByPath() (*jwtutil.Claims, error) { auth := c.Ctx.Request.Header.Get("Authorization") if auth == "" { return nil, fmt.Errorf("未登录") @@ -84,19 +84,19 @@ func parseUint64Flexible(v interface{}) uint64 { } type normalInfosOutput struct { - Sitename string `json:"sitename"` - Companyintroduction string `json:"companyintroduction"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` + Sitename string `json:"sitename"` + Companyintroduction string `json:"companyintroduction"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` } // GetNormalInfos GET /backend/normalInfos 或 /platform/normalInfos -func (c *SiteSettingsController) GetNormalInfos() { +func (c *PlatformSiteSettingsController) GetNormalInfos() { claims, err := c.claimsByPath() if err != nil { c.jsonErr(401, 401, err.Error()) @@ -115,15 +115,15 @@ func (c *SiteSettingsController) GetNormalInfos() { } out := normalInfosOutput{ - Sitename: "", + Sitename: "", Companyintroduction: "", Description: "", Copyright: "", Companyname: "", - Icp: "", - Logo: "", - Logow: "", - Ico: "", + Icp: "", + Logo: "", + Logow: "", + Ico: "", } // tid 缺失时不报错,直接返回空对象给前端渲染(避免 UI 直接崩)。 @@ -164,19 +164,19 @@ type normalInfosPayload struct { // 前端会传 tid(但我们仍优先使用 token 的 tenant_id) Tid interface{} `json:"tid"` - Sitename string `json:"sitename"` + Sitename string `json:"sitename"` Companyintroduction string `json:"companyintroduction"` - Logo string `json:"logo"` - Logow string `json:"logow"` - Ico string `json:"ico"` - Description string `json:"description"` - Copyright string `json:"copyright"` - Companyname string `json:"companyname"` - Icp string `json:"icp"` + Logo string `json:"logo"` + Logow string `json:"logow"` + Ico string `json:"ico"` + Description string `json:"description"` + Copyright string `json:"copyright"` + Companyname string `json:"companyname"` + Icp string `json:"icp"` } // SaveNormalInfos POST /backend/saveNormalInfos 或 /platform/saveNormalInfos -func (c *SiteSettingsController) SaveNormalInfos() { +func (c *PlatformSiteSettingsController) SaveNormalInfos() { claims, err := c.claimsByPath() if err != nil { c.jsonErr(401, 401, err.Error()) @@ -213,17 +213,17 @@ func (c *SiteSettingsController) SaveNormalInfos() { now := time.Now() up := map[string]interface{}{ - "tid": tid, - "sitename": sitename, + "tid": tid, + "sitename": sitename, "companyintroduction": strings.TrimSpace(p.Companyintroduction), - "logo": strings.TrimSpace(p.Logo), - "logow": strings.TrimSpace(p.Logow), - "ico": strings.TrimSpace(p.Ico), - "description": strings.TrimSpace(p.Description), - "copyright": strings.TrimSpace(p.Copyright), - "companyname": strings.TrimSpace(p.Companyname), - "icp": strings.TrimSpace(p.Icp), - "update_time": now, + "logo": strings.TrimSpace(p.Logo), + "logow": strings.TrimSpace(p.Logow), + "ico": strings.TrimSpace(p.Ico), + "description": strings.TrimSpace(p.Description), + "copyright": strings.TrimSpace(p.Copyright), + "companyname": strings.TrimSpace(p.Companyname), + "icp": strings.TrimSpace(p.Icp), + "update_time": now, } cnt, err := models.Orm.QueryTable(new(models.TenantSiteSetting)). @@ -237,18 +237,18 @@ func (c *SiteSettingsController) SaveNormalInfos() { if cnt == 0 { row := &models.TenantSiteSetting{ - Tid: tid, - Sitename: sitename, + Tid: tid, + Sitename: sitename, Companyintroduction: strings.TrimSpace(p.Companyintroduction), - Logo: strings.TrimSpace(p.Logo), - Logow: strings.TrimSpace(p.Logow), - Ico: strings.TrimSpace(p.Ico), - Description: strings.TrimSpace(p.Description), - Copyright: strings.TrimSpace(p.Copyright), - Companyname: strings.TrimSpace(p.Companyname), - Icp: strings.TrimSpace(p.Icp), - CreateTime: now, - UpdateTime: &now, + Logo: strings.TrimSpace(p.Logo), + Logow: strings.TrimSpace(p.Logow), + Ico: strings.TrimSpace(p.Ico), + Description: strings.TrimSpace(p.Description), + Copyright: strings.TrimSpace(p.Copyright), + Companyname: strings.TrimSpace(p.Companyname), + Icp: strings.TrimSpace(p.Icp), + CreateTime: now, + UpdateTime: &now, } _, err = models.Orm.Insert(row) if err != nil { @@ -269,4 +269,3 @@ func (c *SiteSettingsController) SaveNormalInfos() { c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} _ = c.ServeJSON() } - diff --git a/models/tenant_site_setting.go b/models/system_tenant_site_setting.go similarity index 91% rename from models/tenant_site_setting.go rename to models/system_tenant_site_setting.go index 6ddd0aa..6d539ac 100644 --- a/models/tenant_site_setting.go +++ b/models/system_tenant_site_setting.go @@ -18,7 +18,7 @@ type TenantSiteSetting struct { Description string `orm:"column(description);size(255);null" json:"description"` Copyright string `orm:"column(copyright);size(255);null" json:"copyright"` Companyname string `orm:"column(companyname);size(255);null" json:"companyname"` - Icp string `orm:"column(icp);size(255);null" json:"icp"` + Icp string `orm:"column(icp);size(255);null" json:"icp"` CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"` UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` @@ -26,6 +26,5 @@ type TenantSiteSetting struct { } func (m *TenantSiteSetting) TableName() string { - return "yz_tenant_site_setting" + return "yz_system_tenant_site_setting" } - diff --git a/routers/backend/backend.go b/routers/backend/backend.go index 202efaa..28d5cf3 100644 --- a/routers/backend/backend.go +++ b/routers/backend/backend.go @@ -15,37 +15,63 @@ func Register() { // RegisterAuthRoutes 注册 backend 认证相关路由。 func RegisterAuthRoutes() { // backend 登录相关(统一走 /backend/*) - beego.Router("/backend/login", &controllers.PlatformAuthController{}, "post:LoginBackend") - beego.Router("/backend/sendLoginCode", &controllers.PlatformAuthController{}, "post:SendLoginCode") - beego.Router("/backend/loginBySms", &controllers.PlatformAuthController{}, "post:LoginBySms") - beego.Router("/backend/logout", &controllers.PlatformAuthController{}, "post:Logout") + beego.Router("/backend/login", &controllers.BackendAuthController{}, "post:LoginBackend") + beego.Router("/backend/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode") + beego.Router("/backend/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms") + beego.Router("/backend/logout", &controllers.BackendAuthController{}, "post:Logout") // 极验与登录验证配置 - beego.Router("/backend/login/getGeetest3Infos", &controllers.PlatformAuthController{}, "get:GetGeetest3Infos") - beego.Router("/backend/login/getGeetest4Infos", &controllers.PlatformAuthController{}, "get:GetGeetest4Infos") - beego.Router("/backend/login/getOpenVerify", &controllers.PlatformAuthController{}, "get:GetOpenVerify") + beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos") + beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos") + beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify") // 登录相关接口 - beego.Router("/backend/login/getGeetest3Infos", &controllers.PlatformAuthController{}, "get:GetGeetest3Infos") - beego.Router("/backend/login/getGeetest4Infos", &controllers.PlatformAuthController{}, "get:GetGeetest4Infos") - beego.Router("/backend/login/getOpenVerify", &controllers.PlatformAuthController{}, "get:GetOpenVerify") + beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos") + beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos") + beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify") // 注册与找回密码 - beego.Router("/backend/register", &controllers.PlatformAuthController{}, "post:Register") - beego.Router("/backend/sendRegisterCode", &controllers.PlatformAuthController{}, "post:SendRegisterCode") - beego.Router("/backend/resetPassword", &controllers.PlatformAuthController{}, "post:ResetPassword") - beego.Router("/backend/sendResetCode", &controllers.PlatformAuthController{}, "post:SendResetCode") + beego.Router("/backend/register", &controllers.BackendAuthController{}, "post:Register") + beego.Router("/backend/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode") + beego.Router("/backend/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword") + beego.Router("/backend/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode") // 租户站点设置 - beego.Router("/backend/normalInfos", &controllers.SiteSettingsController{}, "get:GetNormalInfos") - beego.Router("/backend/saveNormalInfos", &controllers.SiteSettingsController{}, "post:SaveNormalInfos") + beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos") + beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos") // 菜单接口 beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu") beego.Router("/backend/allmenu", &controllers.BackendMenuController{}, "get:GetAllBackendMenus") + // 操作日志(yz_system_operation_log) + beego.Router("/backend/operationLogs", &controllers.BackendOperationLogController{}, "get:List") + beego.Router("/backend/operationLogs/statistics", &controllers.BackendOperationLogController{}, "get:Statistics") + beego.Router("/backend/operationLogs/:id", &controllers.BackendOperationLogController{}, "get:Detail;delete:Delete") + beego.Router("/backend/operationLogs/batchDelete", &controllers.BackendOperationLogController{}, "post:BatchDelete") + + // 文件管理(yz_system_files / yz_system_files_category) + beego.Router("/backend/usercate", &controllers.BackendFileController{}, "get:GetUserCate") + beego.Router("/backend/allfiles", &controllers.BackendFileController{}, "get:GetAllFiles") + beego.Router("/backend/catefiles/:id", &controllers.BackendFileController{}, "get:GetCateFiles") + beego.Router("/backend/file/:id", &controllers.BackendFileController{}, "get:GetFileByID") + beego.Router("/backend/deletefilepermanently/:id", &controllers.BackendFileController{}, "delete:DeleteFilePermanently") + beego.Router("/backend/uploadfile", &controllers.BackendFileController{}, "post:UploadFile") + beego.Router("/backend/uploadfiles", &controllers.BackendFileController{}, "post:UploadFile") + beego.Router("/backend/updatefile/:id", &controllers.BackendFileController{}, "post:UpdateFile") + beego.Router("/backend/deletefile/:id", &controllers.BackendFileController{}, "delete:DeleteFile") + beego.Router("/backend/movefile/:id", &controllers.BackendFileController{}, "get:MoveFile") + beego.Router("/backend/createfilecate", &controllers.BackendFileController{}, "post:CreateFileCate") + beego.Router("/backend/renamefilecate/:id", &controllers.BackendFileController{}, "post:RenameFileCate") + beego.Router("/backend/deletefilecate/:id", &controllers.BackendFileController{}, "delete:DeleteFileCate") + beego.Router("/backend/uploadavatar", &controllers.BackendFileController{}, "post:UploadAvatar") + beego.Router("/backend/uploadavatar/:id", &controllers.BackendFileController{}, "post:UpdateAvatar") + beego.Router("/backend/batchdeletefiles", &controllers.BackendFileController{}, "post:BatchDeleteFiles") + beego.Router("/backend/batchDeleteFilesPermanently", &controllers.BackendFileController{}, "post:BatchDeleteFilesPermanently") + beego.Router("/backend/batchMoveFiles", &controllers.BackendFileController{}, "post:BatchMoveFiles") + // 模块接口 - beego.Router("/backend/modules/getTenantList", &controllers.PlatformModulesController{}, "get:GetTenantList") + beego.Router("/backend/modules/getTenantList", &controllers.BackendModulesController{}, "get:GetTenantList") // 用户接口 beego.Router("/backend/getTenantUsers/:tid", &controllers.BackendAdminUserController{}, "get:GetTenantUsers") diff --git a/routers/platform/platform.go b/routers/platform/platform.go index 85e56eb..0c94d36 100644 --- a/routers/platform/platform.go +++ b/routers/platform/platform.go @@ -118,8 +118,8 @@ func Register() { beego.Router("/platform/softwareupgrade/:id", &controllers.PlatformSoftwareUpgradeController{}, "get:Detail;post:Update;delete:Delete") // 租户站点设置(yz_tenant_site_setting) - beego.Router("/platform/normalInfos", &controllers.SiteSettingsController{}, "get:GetNormalInfos") - beego.Router("/platform/saveNormalInfos", &controllers.SiteSettingsController{}, "post:SaveNormalInfos") + beego.Router("/platform/normalInfos", &controllers.PlatformSiteSettingsController{}, "get:GetNormalInfos") + beego.Router("/platform/saveNormalInfos", &controllers.PlatformSiteSettingsController{}, "post:SaveNormalInfos") // 系统邮箱配置(yz_system_email) beego.Router("/platform/email/info", &controllers.PlatformEmailController{}, "get:GetInfo")