更新短信功能

This commit is contained in:
2026-04-01 15:15:21 +08:00
parent 1314e18142
commit 5ed24a8003
24 changed files with 2267 additions and 93 deletions
+12 -13
View File
@@ -11,7 +11,7 @@ import (
beego "github.com/beego/beego/v2/server/web"
)
// PlatformAdminUserController 平台管理员用户管理(yz_admin_user
// PlatformAdminUserController 平台管理员用户管理(yz_system_admin_user
type PlatformAdminUserController struct {
beego.Controller
}
@@ -25,7 +25,7 @@ type adminUserDTO struct {
Qq *string `json:"qq"`
Sex uint8 `json:"sex"`
Avatar *string `json:"avatar"`
GroupID uint64 `json:"group_id"`
Rid uint64 `json:"rid"`
LoginCount uint64 `json:"login_count"`
LastLoginIP *string `json:"last_login_ip"`
Status uint8 `json:"status"`
@@ -48,7 +48,7 @@ func toAdminUserDTO(u models.AdminUser) adminUserDTO {
Qq: u.Qq,
Sex: u.Sex,
Avatar: u.Avatar,
GroupID: u.RoleID,
Rid: u.RoleID,
LoginCount: u.LoginCount,
LastLoginIP: u.LastLoginIP,
Status: u.Status,
@@ -111,11 +111,11 @@ type adminAddUserPayload struct {
Qq *string `json:"qq"`
Sex *uint8 `json:"sex"`
Avatar *string `json:"avatar"`
GroupID *uint64 `json:"group_id"`
Rid *uint64 `json:"rid"`
Status *uint8 `json:"status"`
}
// AddUser 添加平台管理员用户(仅写 yz_admin_user,不处理 tid
// AddUser 添加平台管理员用户(仅写 yz_system_admin_user,不处理 tid
// POST /platform/addUser
func (c *PlatformAdminUserController) AddUser() {
var p adminAddUserPayload
@@ -146,12 +146,12 @@ func (c *PlatformAdminUserController) AddUser() {
if p.Sex != nil {
sex = *p.Sex
}
groupID := uint64(1)
if p.GroupID != nil && *p.GroupID != 0 {
groupID = *p.GroupID
roleID := uint64(1)
if p.Rid != nil && *p.Rid != 0 {
roleID = *p.Rid
}
id, err := models.CreateAdminUser(p.Account, p.Password, p.Name, p.Phone, p.Email, p.Qq, p.Avatar, sex, groupID, status)
id, err := models.CreateAdminUser(p.Account, p.Password, p.Name, p.Phone, p.Email, p.Qq, p.Avatar, sex, roleID, status)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败"}
_ = c.ServeJSON()
@@ -175,7 +175,7 @@ type editUserPayload struct {
Qq *string `json:"qq"`
Sex *uint8 `json:"sex"`
Avatar *string `json:"avatar"`
GroupID *uint64 `json:"group_id"`
Rid *uint64 `json:"rid"`
Status *uint8 `json:"status"`
}
@@ -222,8 +222,8 @@ func (c *PlatformAdminUserController) EditUser() {
if p.Avatar != nil {
fields["avatar"] = *p.Avatar
}
if p.GroupID != nil && *p.GroupID != 0 {
fields["role_id"] = *p.GroupID
if p.Rid != nil && *p.Rid != 0 {
fields["role_id"] = *p.Rid
}
if p.Status != nil {
fields["status"] = *p.Status
@@ -300,4 +300,3 @@ func (c *PlatformAdminUserController) ChangePassword() {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"}
_ = c.ServeJSON()
}
+54 -5
View File
@@ -3,7 +3,9 @@ package controllers
import (
"encoding/json"
"io"
"strings"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
@@ -69,17 +71,64 @@ func (c *PlatformAuthController) Login() {
"data": map[string]interface{}{
"token": token,
"user": map[string]interface{}{
"id": loginUser.ID,
"account": loginUser.Account,
"name": loginUser.Name,
"rid": loginUser.Rid,
"avatar": loginUser.Avatar,
"id": loginUser.ID,
"account": loginUser.Account,
"name": loginUser.Name,
"rid": loginUser.Rid,
"avatar": loginUser.Avatar,
"role_name": loginUser.RoleName,
},
},
}
_ = c.ServeJSON()
}
// GetCurrentUser 当前登录平台用户信息(含角色名称),需 Bearer Token
func (c *PlatformAuthController) GetCurrentUser() {
authHeader := c.Ctx.Request.Header.Get("Authorization")
if authHeader == "" {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录"}
_ = c.ServeJSON()
return
}
authParts := strings.SplitN(authHeader, " ", 2)
if len(authParts) != 2 || authParts[0] != "Bearer" {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}
_ = c.ServeJSON()
return
}
claims, err := jwtutil.ParseToken(authParts[1])
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "无效的token"}
_ = c.ServeJSON()
return
}
if claims.UserType != "platform" {
c.Data["json"] = map[string]interface{}{"code": 403, "msg": "无权访问"}
_ = c.ServeJSON()
return
}
loginUser, err := services.PlatformGetCurrentUser(uint64(claims.UserID))
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": err.Error()}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"id": loginUser.ID,
"account": loginUser.Account,
"name": loginUser.Name,
"rid": loginUser.Rid,
"avatar": loginUser.Avatar,
"role_name": loginUser.RoleName,
},
}
_ = c.ServeJSON()
}
// SendLoginCode 发送登录验证码(占位实现)
func (c *PlatformAuthController) SendLoginCode() {
c.Data["json"] = map[string]interface{}{
+265
View File
@@ -0,0 +1,265 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"server/models"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformEmailController 系统邮箱配置(yz_system_email
type PlatformEmailController struct {
beego.Controller
}
func (c *PlatformEmailController) platformClaims() (*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 != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *PlatformEmailController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func emailRowToMap(m models.SystemEmail) map[string]interface{} {
out := map[string]interface{}{
"id": m.ID,
"from_address": m.FromAddress,
"host": m.Host,
"port": m.Port,
"password": m.Password,
"encryption": m.Encryption,
"timeout": m.Timeout,
"status": m.Status,
"create_time": m.CreateTime.Format("2006-01-02 15:04:05"),
"update_time": m.UpdateTime.Format("2006-01-02 15:04:05"),
}
if m.FromName != nil {
out["from_name"] = *m.FromName
} else {
out["from_name"] = ""
}
if m.Remark != nil {
out["remark"] = *m.Remark
} else {
out["remark"] = ""
}
return out
}
// GetInfo GET /platform/email/info
func (c *PlatformEmailController) GetInfo() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
rows, err := services.ListSystemEmails()
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, emailRowToMap(rows[i]))
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": list}
_ = c.ServeJSON()
}
type emailFormPayload struct {
FromAddress string `json:"fromAddress"`
FromName string `json:"fromName"`
Host string `json:"host"`
Port interface{} `json:"port"`
Password string `json:"password"`
Encryption string `json:"encryption"`
Timeout interface{} `json:"timeout"`
}
type testEmailPayload struct {
emailFormPayload
TestEmail string `json:"testEmail"`
}
func parseUintFlexible(v interface{}) uint {
if v == nil {
return 0
}
switch x := v.(type) {
case float64:
if x < 0 {
return 0
}
return uint(x)
case string:
n, err := parseUintString(x)
if err != nil {
return 0
}
return n
default:
return 0
}
}
func parseUintString(s string) (uint, error) {
s = strings.TrimSpace(s)
if s == "" {
return 0, fmt.Errorf("empty")
}
n, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return 0, err
}
return uint(n), nil
}
func normalizeEncryption(s string) string {
s = strings.ToLower(strings.TrimSpace(s))
switch s {
case "ssl", "tls", "none":
return s
default:
return "ssl"
}
}
// EditInfo POST /platform/email/editinfo
func (c *PlatformEmailController) EditInfo() {
if _, err := c.platformClaims(); 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 emailFormPayload
if uerr := json.Unmarshal(raw, &p); uerr != nil {
c.jsonErr(400, 400, "参数错误")
return
}
from := strings.TrimSpace(p.FromAddress)
host := strings.TrimSpace(p.Host)
if from == "" || host == "" {
c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空")
return
}
port := parseUintFlexible(p.Port)
if port == 0 {
port = 465
}
timeout := parseUintFlexible(p.Timeout)
if timeout == 0 {
timeout = 30
}
enc := normalizeEncryption(p.Encryption)
cnt, cerr := models.Orm.QueryTable(new(models.SystemEmail)).Count()
if cerr != nil {
c.jsonErr(500, 500, "读取邮箱配置失败: "+cerr.Error())
return
}
if strings.TrimSpace(p.Password) == "" && cnt == 0 {
c.jsonErr(400, 400, "授权码/密码不能为空")
return
}
var fn *string
if strings.TrimSpace(p.FromName) != "" {
s := strings.TrimSpace(p.FromName)
fn = &s
}
err = services.UpsertFirstSystemEmail(from, fn, host, port, strings.TrimSpace(p.Password), enc, timeout, 1, nil)
if err != nil {
c.jsonErr(500, 500, "保存邮箱配置失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"}
_ = c.ServeJSON()
}
// SendTestEmail POST /platform/email/sendtestemail
func (c *PlatformEmailController) SendTestEmail() {
if _, err := c.platformClaims(); 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 testEmailPayload
if uerr := json.Unmarshal(raw, &p); uerr != nil {
c.jsonErr(400, 400, "参数错误")
return
}
to := strings.TrimSpace(p.TestEmail)
if to == "" {
c.jsonErr(400, 400, "测试收件邮箱不能为空")
return
}
from := strings.TrimSpace(p.FromAddress)
host := strings.TrimSpace(p.Host)
if from == "" || host == "" {
c.jsonErr(400, 400, "发件人邮箱与 SMTP 主机不能为空")
return
}
port := parseUintFlexible(p.Port)
if port == 0 {
port = 465
}
timeout := parseUintFlexible(p.Timeout)
if timeout == 0 {
timeout = 30
}
enc := normalizeEncryption(p.Encryption)
pass := strings.TrimSpace(p.Password)
if pass == "" {
rows, lerr := services.ListSystemEmails()
if lerr == nil && len(rows) > 0 {
pass = rows[0].Password
}
}
if pass == "" {
c.jsonErr(400, 400, "授权码/密码不能为空(请填写或先保存配置)")
return
}
cfg := services.SMTPConfig{
FromAddress: from,
FromName: strings.TrimSpace(p.FromName),
Host: host,
Port: port,
Password: pass,
Encryption: enc,
Timeout: timeout,
}
if err := services.SendTestEmailSMTP(cfg, to); err != nil {
c.jsonErr(500, 500, "发送失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"}
_ = c.ServeJSON()
}
+924
View File
@@ -0,0 +1,924 @@
package controllers
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformFileController 平台端文件管理(yz_system_files / yz_system_files_category
type PlatformFileController struct {
beego.Controller
}
const fileUploadMaxBytes = 50 * 1024 * 1024
var fileTypeByCategory = map[string]uint8{
"image": 1,
"document": 2,
"video": 3,
"audio": 4,
}
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"},
}
func (c *PlatformFileController) platformClaims() (*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 != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *PlatformFileController) 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 *PlatformFileController) 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 *PlatformFileController) 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 {
return fileTypeByCategory[cat]
}
}
}
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 /platform/allfiles
func (c *PlatformFileController) GetAllFiles() {
claims, err := c.platformClaims()
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 /platform/usercate
func (c *PlatformFileController) GetUserCate() {
claims, err := c.platformClaims()
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 /platform/createfilecate
func (c *PlatformFileController) CreateFileCate() {
claims, err := c.platformClaims()
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 /platform/renamefilecate/:id
func (c *PlatformFileController) RenameFileCate() {
claims, err := c.platformClaims()
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 /platform/deletefilecate/:id
func (c *PlatformFileController) DeleteFileCate() {
claims, err := c.platformClaims()
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 /platform/catefiles/:id
func (c *PlatformFileController) GetCateFiles() {
claims, err := c.platformClaims()
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 /platform/file/:id
func (c *PlatformFileController) GetFileByID() {
claims, err := c.platformClaims()
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 /platform/uploadfile
func (c *PlatformFileController) UploadFile() {
claims, err := c.platformClaims()
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, "文件大小不能超过50MB")
return
}
ext := fileExt(header.Filename)
if ext == "" {
c.jsonErr(400, 400, "无法识别文件扩展名")
return
}
tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("up_%d_%s", time.Now().UnixNano(), header.Filename))
tmp, err := os.Create(tmpPath)
if err != nil {
c.jsonErr(500, 500, "创建临时文件失败")
return
}
n, copyErr := io.Copy(tmp, fh)
_ = tmp.Close()
if copyErr != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "读取文件失败")
return
}
if n > fileUploadMaxBytes {
_ = os.Remove(tmpPath)
c.jsonErr(400, 400, "文件大小不能超过50MB")
return
}
sum, err := md5HashFile(tmpPath)
if err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "计算文件摘要失败")
return
}
var exist models.SystemFile
err = models.Orm.QueryTable(new(models.SystemFile)).
Filter("md5", sum).
Filter("tid", tid).
Filter("delete_time__isnull", true).
One(&exist)
if err == nil {
_ = os.Remove(tmpPath)
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
}
datePath := time.Now().Format("2006/01/02")
saveName := fmt.Sprintf("%s/%d.%s", datePath, time.Now().UnixNano(), ext)
destDir := filepath.Join("uploads", filepath.FromSlash(datePath))
if err := os.MkdirAll(destDir, 0755); err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "创建目录失败: "+err.Error())
return
}
destPath := filepath.Join("uploads", filepath.FromSlash(saveName))
if err := os.Rename(tmpPath, destPath); err != nil {
_ = os.Remove(tmpPath)
c.jsonErr(500, 500, "保存文件失败: "+err.Error())
return
}
webURL := "/" + strings.ReplaceAll(filepath.ToSlash(destPath), "\\", "/")
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(n),
Src: webURL,
Uploader: adminID,
Md5: sum,
}
id, err := models.Orm.Insert(row)
if err != nil {
removePhysicalBySrc(webURL)
c.jsonErr(500, 500, "上传失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "上传成功",
"data": map[string]interface{}{
"url": webURL,
"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 /platform/updatefile/:id
func (c *PlatformFileController) UpdateFile() {
claims, err := c.platformClaims()
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 /platform/deletefile/:id
func (c *PlatformFileController) DeleteFile() {
claims, err := c.platformClaims()
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 /platform/deletefilepermanently/:id
func (c *PlatformFileController) DeleteFilePermanently() {
claims, err := c.platformClaims()
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 /platform/movefile/:id
func (c *PlatformFileController) MoveFile() {
claims, err := c.platformClaims()
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 /platform/batchdeletefiles
func (c *PlatformFileController) BatchDeleteFiles() {
claims, err := c.platformClaims()
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 /platform/batchDeleteFilesPermanently
func (c *PlatformFileController) BatchDeleteFilesPermanently() {
claims, err := c.platformClaims()
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 /platform/uploadavatar(占位)
func (c *PlatformFileController) UploadAvatar() {
c.Data["json"] = map[string]interface{}{"code": 501, "msg": "上传头像暂未实现"}
_ = c.ServeJSON()
}
// UpdateAvatar POST /platform/uploadavatar/:id(占位)
func (c *PlatformFileController) UpdateAvatar() {
c.Data["json"] = map[string]interface{}{"code": 501, "msg": "更新头像暂未实现"}
_ = c.ServeJSON()
}
// BatchMoveFiles POST /platform/batchMoveFiles
func (c *PlatformFileController) BatchMoveFiles() {
claims, err := c.platformClaims()
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()
}
+1 -2
View File
@@ -11,7 +11,7 @@ import (
beego "github.com/beego/beego/v2/server/web"
)
// PlatformRoleController 平台角色管理(yz_admin_role
// PlatformRoleController 平台角色管理(yz_system_admin_role
type PlatformRoleController struct {
beego.Controller
}
@@ -199,4 +199,3 @@ func (c *PlatformRoleController) DeleteRole() {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
+519
View File
@@ -0,0 +1,519 @@
package controllers
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformSMSController 短信配置(yz_system_sms),兼容旧前端 /platform/sms/* 接口
type PlatformSMSController struct {
beego.Controller
}
func (c *PlatformSMSController) platformClaims() (*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 != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *PlatformSMSController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
// GetSmsInfo GET /platform/sms/info
// 返回 data[0],字段兼容 backend_url/api_key 与 backendUrl/apiKey(沿用旧前端)
func (c *PlatformSMSController) GetSmsInfo() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
var row models.SystemSMS
// 优先默认通道,其次 custom
err := models.Orm.QueryTable(new(models.SystemSMS)).
Filter("is_default", 1).
Filter("status", 1).
OrderBy("-weight", "-id").
Limit(1).
One(&row)
if err != nil {
_ = models.Orm.QueryTable(new(models.SystemSMS)).
Filter("config_code", "custom").
OrderBy("-id").
Limit(1).
One(&row)
}
backendURL := strings.TrimSpace(row.ApiURL)
apiKey := strings.TrimSpace(row.ApiKey)
data := []map[string]interface{}{{
"backend_url": backendURL,
"api_key": apiKey,
"backendUrl": backendURL,
"apiKey": apiKey,
}}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "获取成功", "data": data}
_ = c.ServeJSON()
}
type smsEditPayload struct {
BackendUrl string `json:"backendUrl"`
BackendURL string `json:"backend_url"`
ApiKey string `json:"apiKey"`
APIKey string `json:"api_key"`
}
// EditSmsInfo POST /platform/sms/editinfo
// 将旧前端的 backendUrl/apiKey 落到 yz_system_sms 的 api_url/api_key(写入 config_code=custom
func (c *PlatformSMSController) EditSmsInfo() {
if _, err := c.platformClaims(); 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 smsEditPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
backendURL := strings.TrimSpace(p.BackendUrl)
if backendURL == "" {
backendURL = strings.TrimSpace(p.BackendURL)
}
apiKey := strings.TrimSpace(p.ApiKey)
if apiKey == "" {
apiKey = strings.TrimSpace(p.APIKey)
}
if backendURL == "" {
c.jsonErr(400, 400, "请输入短信网关地址")
return
}
if apiKey == "" {
c.jsonErr(400, 400, "请输入API KEY")
return
}
// 确保只有一个默认:先清空默认,再 upsert custom 为默认
_, _ = models.Orm.QueryTable(new(models.SystemSMS)).Update(map[string]interface{}{"is_default": 0})
var existed models.SystemSMS
e := models.Orm.QueryTable(new(models.SystemSMS)).Filter("config_code", "custom").Limit(1).One(&existed)
if e == nil && existed.ID > 0 {
_, err = models.Orm.QueryTable(new(models.SystemSMS)).Filter("id", existed.ID).Update(map[string]interface{}{
"config_name": "自定义网关",
"channel_type": 2,
"api_url": backendURL,
"api_key": apiKey,
"weight": 10,
"is_default": 1,
"status": 1,
})
if err != nil {
c.jsonErr(500, 500, "更新失败: "+err.Error())
return
}
} else {
row := &models.SystemSMS{
ConfigCode: "custom",
ConfigName: "自定义网关",
ChannelType: 2,
ApiURL: backendURL,
ApiKey: apiKey,
ApiSecret: "",
SignName: "",
TemplateID: "",
TestPhone: "",
Weight: 10,
IsDefault: 1,
Status: 1,
Remark: "",
}
if _, err := models.Orm.Insert(row); err != nil {
c.jsonErr(500, 500, "更新失败: "+err.Error())
return
}
}
updated := []map[string]interface{}{{
"backend_url": backendURL,
"api_key": apiKey,
}}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "更新成功", "data": updated}
_ = c.ServeJSON()
}
type smsTestPayload struct {
BackendUrl string `json:"backendUrl"`
BackendURL string `json:"backend_url"`
ApiKey string `json:"apiKey"`
APIKey string `json:"api_key"`
Tid *uint64 `json:"tid"`
Phone string `json:"phone"`
Content string `json:"content"`
}
// SendTestSms POST /platform/sms/sendtest
// 调用短信网关入队接口:{backendUrl}/api/v1/business/outbound-tasksheader: X-Api-Key
func (c *PlatformSMSController) SendTestSms() {
if _, err := c.platformClaims(); 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 smsTestPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
phone := strings.TrimSpace(p.Phone)
if phone == "" {
c.jsonErr(400, 400, "缺少测试手机号")
return
}
if !strings.HasPrefix(phone, "+") {
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
return
}
for _, ch := range phone[1:] {
if ch < '0' || ch > '9' {
c.jsonErr(400, 400, "请使用国际格式手机号(以 + 开头,后为数字)")
return
}
}
backendURL := strings.TrimSpace(p.BackendUrl)
if backendURL == "" {
backendURL = strings.TrimSpace(p.BackendURL)
}
apiKey := strings.TrimSpace(p.ApiKey)
if apiKey == "" {
apiKey = strings.TrimSpace(p.APIKey)
}
// 兜底:body 未带时从默认配置取
if backendURL == "" || apiKey == "" {
var row models.SystemSMS
_ = models.Orm.QueryTable(new(models.SystemSMS)).
Filter("is_default", 1).
Filter("status", 1).
OrderBy("-weight", "-id").
Limit(1).
One(&row)
if backendURL == "" {
backendURL = strings.TrimSpace(row.ApiURL)
}
if apiKey == "" {
apiKey = strings.TrimSpace(row.ApiKey)
}
}
if backendURL == "" {
c.jsonErr(400, 400, "请先配置短信网关地址 backendUrl")
return
}
if apiKey == "" {
c.jsonErr(400, 400, "请先配置短信网关 API KEY")
return
}
content := strings.TrimSpace(p.Content)
code := randomDigits6()
if content == "" {
content = "短信测试验证码:" + code
}
enqueueURL := strings.TrimRight(backendURL, "/") + "/api/v1/business/outbound-tasks"
payload := map[string]interface{}{
"phone": phone,
"content": content,
}
bs, _ := json.Marshal(payload)
client := &http.Client{Timeout: 10 * time.Second}
req, err := http.NewRequest("POST", enqueueURL, bytes.NewReader(bs))
if err != nil {
c.jsonErr(500, 500, "创建请求失败: "+err.Error())
return
}
req.Header.Set("X-Api-Key", apiKey)
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
c.jsonErr(500, 500, "短信网关入队失败: "+err.Error())
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
msg := strings.TrimSpace(string(body))
if msg == "" {
msg = resp.Status
}
c.jsonErr(500, 500, "短信网关入队失败: "+msg)
return
}
bodyStr := string(body)
report := strings.TrimSpace(bodyStr)
var reportPtr *string
if report != "" {
reportPtr = &bodyStr
}
// 网关 HTTP 2xx:平台侧视为「已受理并成功提交」;与前端 tasklist 中 status=3「发送成功」对齐
taskStatus := 3
// 若网关返回 JSON 且含通用状态字段,则优先映射(便于以后网关回传异步状态)
var gw map[string]interface{}
if json.Unmarshal(body, &gw) == nil {
if v, ok := gw["status"]; ok {
switch x := v.(type) {
case float64:
taskStatus = mapGatewayStatus(int(x))
case string:
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
taskStatus = mapGatewayStatus(n)
}
}
}
}
// 写入本地任务表(用于前端列表/对账)
now := time.Now()
task := &models.SystemSMSTask{
Tid: p.Tid, // 测试可为空
ApiKey: apiKey,
Phone: phone,
Content: &content,
Status: taskStatus,
Code: code,
ReportRaw: reportPtr,
CreateTime: &now,
UpdateTime: &now,
}
taskID, terr := models.Orm.Insert(task)
if terr != nil {
// 入队已成功,任务写库失败也不影响短信发送,只返回提示
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "短信测试任务入队成功(任务写库失败)",
"data": map[string]interface{}{
"taskId": nil,
"code": code,
"gatewayResp": json.RawMessage(body),
},
}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "短信测试任务入队成功",
"data": map[string]interface{}{
"taskId": uint64(taskID),
"code": code,
"gatewayResp": json.RawMessage(body),
},
}
_ = c.ServeJSON()
}
// GetSmsTaskList GET /platform/sms/taskList
func (c *PlatformSMSController) GetSmsTaskList() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
statusStr := strings.TrimSpace(c.GetString("status"))
phoneKw := strings.TrimSpace(c.GetString("phone"))
tidStr := strings.TrimSpace(c.GetString("tid"))
qs := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("delete_time__isnull", true)
if statusStr != "" {
if st, err := strconv.Atoi(statusStr); err == nil {
qs = qs.Filter("status", st)
}
}
if phoneKw != "" {
qs = qs.Filter("phone__icontains", phoneKw)
}
if tidStr != "" {
if tid, err := strconv.ParseUint(tidStr, 10, 64); err == nil && tid > 0 {
qs = qs.Filter("tid", tid)
}
}
var rows []models.SystemSMSTask
_, err := qs.OrderBy("-id").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,
"api_key": rows[i].ApiKey,
"phone": rows[i].Phone,
"content": "",
"status": rows[i].Status,
"code": rows[i].Code,
"report_raw": rows[i].ReportRaw,
"create_time": "",
"update_time": "",
}
if rows[i].Tid != nil {
item["tid"] = *rows[i].Tid
}
if rows[i].Content != nil {
item["content"] = *rows[i].Content
}
if rows[i].CreateTime != nil {
item["create_time"] = rows[i].CreateTime.Format("2006-01-02 15:04:05")
}
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", "list": list}
_ = c.ServeJSON()
}
// EditSmsTask POST /platform/sms/taskEdit/:id
func (c *PlatformSMSController) EditSmsTask() {
if _, err := c.platformClaims(); 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
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
var p map[string]interface{}
_ = json.Unmarshal(raw, &p)
up := map[string]interface{}{}
if v, ok := p["status"]; ok {
switch x := v.(type) {
case float64:
up["status"] = int(x)
case string:
if n, e := strconv.Atoi(strings.TrimSpace(x)); e == nil {
up["status"] = n
}
}
}
if v, ok := p["report_raw"]; ok {
if s, ok := v.(string); ok {
up["report_raw"] = s
}
}
if v, ok := p["content"]; ok {
if s, ok := v.(string); ok {
up["content"] = s
}
}
if len(up) == 0 {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
return
}
now := time.Now()
up["update_time"] = now
n, err := models.Orm.QueryTable(new(models.SystemSMSTask)).Filter("id", id).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": "success"}
_ = c.ServeJSON()
}
func randomDigits6() string {
// 生成 6 位数字字符串
b := make([]byte, 4)
if _, err := rand.Read(b); err != nil {
return "123456"
}
n := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
if n < 0 {
n = -n
}
code := n%900000 + 100000
return strconv.Itoa(code)
}
// mapGatewayStatus 将网关侧 status 粗略映射到前端列表:0待发送 1发送中 2失败 3成功
func mapGatewayStatus(st int) int {
switch st {
case 0:
return 0
case 1, 4, 5:
return 1
case 2, 6:
return 2
case 3:
return 3
default:
// 网关枚举未约定时:HTTP 已 2xx,按「已成功提交」显示为发送成功
return 3
}
}
+34 -6
View File
@@ -11,6 +11,7 @@ import (
"server/models"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
@@ -32,18 +33,46 @@ type tenantUserPayload struct {
Remark *string `json:"remark"`
}
// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤)
// GET /platform/tenantUser/list?tid=1&uid=2
// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤keyword 对姓名/手机/邮箱/账号模糊 OR 匹配
// GET /platform/tenantUser/list?tid=1&uid=2&keyword=张
func (c *PlatformTenantUserController) GetTenantUserList() {
tid, _ := c.GetUint64("tid")
uid, _ := c.GetUint64("uid")
keyword := strings.TrimSpace(c.GetString("keyword"))
qs := models.Orm.QueryTable(new(models.TenantUser))
var cond *orm.Condition
needCond := false
if tid > 0 {
qs = qs.Filter("tid", tid)
if cond == nil {
cond = orm.NewCondition()
}
cond = cond.And("tid", tid)
needCond = true
}
if uid > 0 {
qs = qs.Filter("uid", uid)
if cond == nil {
cond = orm.NewCondition()
}
cond = cond.And("uid", uid)
needCond = true
}
if keyword != "" {
kwCond := orm.NewCondition()
kwCond = kwCond.Or("name__icontains", keyword).
Or("phone__icontains", keyword).
Or("email__icontains", keyword).
Or("account__icontains", keyword)
if cond == nil {
cond = kwCond
} else {
cond = cond.AndCond(kwCond)
}
needCond = true
}
if needCond {
qs = qs.SetCond(cond)
}
var rows []models.TenantUser
@@ -115,7 +144,7 @@ func (c *PlatformTenantUserController) GetTenantUserDetail() {
_ = c.ServeJSON()
}
// CreateTenantUser 创建绑定
// CreateTenantUser 创建租户用户绑定(写入表 yz_system_tenant_useruid 为空时由 generateTenantUID 生成)
// POST /platform/tenantUser/create
func (c *PlatformTenantUserController) CreateTenantUser() {
p, ok := c.parsePayload()
@@ -286,4 +315,3 @@ func generateTenantUID(tid uint64) (uint64, error) {
}
return 0, errors.New("uid collision")
}
+1 -2
View File
@@ -12,7 +12,7 @@ import (
beego "github.com/beego/beego/v2/server/web"
)
// PlatformUserController 平台端用户相关(简化:当前用户信息落在 yz_tenant_user
// PlatformUserController 平台端用户相关(简化:当前用户信息落在 yz_system_tenant_user
type PlatformUserController struct {
beego.Controller
}
@@ -97,4 +97,3 @@ func (c *PlatformUserController) AddUser() {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败,请重试"}
_ = c.ServeJSON()
}