This commit is contained in:
李志强 2026-06-02 21:15:13 +08:00
parent 31fc86e878
commit e6b84aad80
12 changed files with 2650 additions and 130 deletions

View File

@ -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,6 +91,7 @@ func (c *ApiGetCardController) GetCard() {
}
func (c *ApiGetCardController) extractCursor(platform, dataType string, now time.Time) {
for {
var row models.PlatformAccountPoolCursor
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("is_extracted", 0).
@ -106,6 +107,7 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, now time
}
return
}
_, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("id", row.ID).
Update(map[string]interface{}{
@ -117,7 +119,14 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, now time
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
}
}
}
func (c *ApiGetCardController) extractWindsurf(platform, dataType string, now time.Time) {

322
controllers/backend_auth.go Normal file
View File

@ -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": "发送找回密码验证码暂未实现",
})
}

907
controllers/backend_file.go Normal file
View File

@ -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()
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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

View File

@ -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("未登录")
@ -96,7 +96,7 @@ type normalInfosOutput struct {
}
// 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())
@ -176,7 +176,7 @@ type normalInfosPayload struct {
}
// 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())
@ -269,4 +269,3 @@ func (c *SiteSettingsController) SaveNormalInfos() {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"}
_ = c.ServeJSON()
}

View File

@ -26,6 +26,5 @@ type TenantSiteSetting struct {
}
func (m *TenantSiteSetting) TableName() string {
return "yz_tenant_site_setting"
return "yz_system_tenant_site_setting"
}

View File

@ -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")

View File

@ -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")