更新短信功能
This commit is contained in:
parent
1314e18142
commit
5ed24a8003
@ -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()
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,9 @@ package controllers
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"server/pkg/jwtutil"
|
||||
"server/services"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
@ -74,12 +76,59 @@ func (c *PlatformAuthController) Login() {
|
||||
"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
controllers/platform_email.go
Normal file
265
controllers/platform_email.go
Normal 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
controllers/platform_file.go
Normal file
924
controllers/platform_file.go
Normal 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()
|
||||
}
|
||||
@ -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
controllers/platform_sms.go
Normal file
519
controllers/platform_sms.go
Normal 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-tasks,header: 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
|
||||
}
|
||||
}
|
||||
@ -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_user;uid 为空时由 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")
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
7
main.go
7
main.go
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"server/models"
|
||||
_ "server/routers"
|
||||
"server/version"
|
||||
@ -18,6 +20,11 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) {
|
||||
method := ctx.Input.Method()
|
||||
if method == "PUT" || method == "POST" || method == "PATCH" {
|
||||
uri := ctx.Request.URL.Path
|
||||
// 大文件 multipart 不能先 CopyBody 截断,否则上传解析失败
|
||||
if strings.Contains(uri, "/uploadfile") || strings.Contains(uri, "/uploadfiles") || strings.Contains(uri, "/uploadavatar") {
|
||||
return
|
||||
}
|
||||
ctx.Input.CopyBody(1024 * 1024) // 1MB 缓冲区
|
||||
}
|
||||
})
|
||||
|
||||
@ -2,7 +2,7 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AdminRole 平台角色表 yz_admin_role
|
||||
// AdminRole 平台角色表 yz_system_admin_role
|
||||
type AdminRole struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Cid uint8 `orm:"column(cid);default(1)" json:"cid"` // 1平台角色 2租户角色
|
||||
@ -15,6 +15,5 @@ type AdminRole struct {
|
||||
}
|
||||
|
||||
func (m *AdminRole) TableName() string {
|
||||
return "yz_admin_role"
|
||||
return "yz_system_admin_role"
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// AdminUser 平台管理员信息表 yz_admin_user
|
||||
// AdminUser 平台管理员信息表 yz_system_admin_user
|
||||
type AdminUser struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Account string `orm:"column(account);size(64)" json:"account"`
|
||||
@ -18,7 +18,7 @@ type AdminUser struct {
|
||||
Qq *string `orm:"column(qq);size(16);null" json:"qq"`
|
||||
Sex uint8 `orm:"column(sex);default(0)" json:"sex"`
|
||||
Avatar *string `orm:"column(avatar);size(255);null" json:"avatar"`
|
||||
RoleID uint64 `orm:"column(role_id)" json:"group_id"`
|
||||
RoleID uint64 `orm:"column(role_id)" json:"rid"`
|
||||
LoginCount uint64 `orm:"column(login_count);default(0)" json:"login_count"`
|
||||
LastLoginIP *string `orm:"column(last_login_ip);size(255);null" json:"last_login_ip"`
|
||||
Status uint8 `orm:"column(status);default(1)" json:"status"`
|
||||
@ -28,7 +28,7 @@ type AdminUser struct {
|
||||
}
|
||||
|
||||
func (m *AdminUser) TableName() string {
|
||||
return "yz_admin_user"
|
||||
return "yz_system_admin_user"
|
||||
}
|
||||
|
||||
func md5Hex(s string) string {
|
||||
@ -93,4 +93,3 @@ func ListAdminUsers() ([]AdminUser, int64, error) {
|
||||
_, err = Orm.QueryTable(new(AdminUser)).OrderBy("-id").All(&rows)
|
||||
return rows, total, err
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
@ -38,9 +38,13 @@ func Init(_ string) {
|
||||
new(SystemMenu),
|
||||
new(AdminUser),
|
||||
new(AdminRole),
|
||||
new(SystemFile),
|
||||
new(SystemFilesCategory),
|
||||
new(SystemEmail),
|
||||
new(SystemSMS),
|
||||
new(SystemSMSTask),
|
||||
)
|
||||
|
||||
// 创建全局 Ormer
|
||||
Orm = orm.NewOrm()
|
||||
}
|
||||
|
||||
|
||||
23
models/system_email.go
Normal file
23
models/system_email.go
Normal file
@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemEmail 系统邮箱配置表 yz_system_email
|
||||
type SystemEmail struct {
|
||||
ID uint `orm:"column(id);pk;auto" json:"id"`
|
||||
FromAddress string `orm:"column(from_address);size(191)" json:"from_address"`
|
||||
FromName *string `orm:"column(from_name);size(191);null" json:"from_name"`
|
||||
Host string `orm:"column(host);size(191)" json:"host"`
|
||||
Port uint `orm:"column(port);default(465)" json:"port"`
|
||||
Password string `orm:"column(password);size(255)" json:"password"`
|
||||
Encryption string `orm:"column(encryption);size(8)" json:"encryption"` // ssl / tls / none
|
||||
Timeout uint `orm:"column(timeout);default(30)" json:"timeout"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
Remark *string `orm:"column(remark);size(255);null" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add;null" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
|
||||
}
|
||||
|
||||
func (m *SystemEmail) TableName() string {
|
||||
return "yz_system_email"
|
||||
}
|
||||
25
models/system_file.go
Normal file
25
models/system_file.go
Normal file
@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemFile 附件表 yz_system_files
|
||||
type SystemFile struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid)" json:"tid"`
|
||||
Uid *uint64 `orm:"column(uid);null" json:"uid"`
|
||||
Tuid *uint64 `orm:"column(tuid);null" json:"tuid"`
|
||||
Name string `orm:"column(name);size(255)" json:"name"`
|
||||
Type uint8 `orm:"column(type);default(2)" json:"type"`
|
||||
Cate uint64 `orm:"column(cate);default(0)" json:"cate"`
|
||||
Size uint64 `orm:"column(size);default(0)" json:"size"`
|
||||
Src string `orm:"column(src);size(512)" json:"src"`
|
||||
Uploader uint64 `orm:"column(uploader);default(0)" json:"uploader"`
|
||||
Md5 string `orm:"column(md5);size(32)" json:"md5"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemFile) TableName() string {
|
||||
return "yz_system_files"
|
||||
}
|
||||
19
models/system_files_category.go
Normal file
19
models/system_files_category.go
Normal file
@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemFilesCategory 文件分类表 yz_system_files_category
|
||||
type SystemFilesCategory struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid)" json:"tid"`
|
||||
Uid *uint64 `orm:"column(uid);null" json:"uid"`
|
||||
Tuid *uint64 `orm:"column(tuid);null" json:"tuid"`
|
||||
Name string `orm:"column(name);size(128)" json:"name"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null;auto_now" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemFilesCategory) TableName() string {
|
||||
return "yz_system_files_category"
|
||||
}
|
||||
32
models/system_sms.go
Normal file
32
models/system_sms.go
Normal file
@ -0,0 +1,32 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemSMS 短信配置表 yz_system_sms
|
||||
type SystemSMS struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
ConfigCode string `orm:"column(config_code);size(64)" json:"config_code"`
|
||||
ConfigName string `orm:"column(config_name);size(128)" json:"config_name"`
|
||||
ChannelType int8 `orm:"column(channel_type);default(1)" json:"channel_type"`
|
||||
|
||||
ApiURL string `orm:"column(api_url);size(512);default('')" json:"api_url"`
|
||||
ApiKey string `orm:"column(api_key);size(256);default('')" json:"api_key"`
|
||||
ApiSecret string `orm:"column(api_secret);size(256);default('')" json:"api_secret"`
|
||||
|
||||
SignName string `orm:"column(sign_name);size(128);default('')" json:"sign_name"`
|
||||
TemplateID string `orm:"column(template_id);size(128);default('')" json:"template_id"`
|
||||
ExtraParams *string `orm:"column(extra_params);type(json);null" json:"extra_params"`
|
||||
|
||||
TestPhone string `orm:"column(test_phone);size(64);default('')" json:"test_phone"`
|
||||
|
||||
Weight int `orm:"column(weight);default(10)" json:"weight"`
|
||||
IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"`
|
||||
Status int8 `orm:"column(status);default(1)" json:"status"`
|
||||
Remark string `orm:"column(remark);size(512);default('')" json:"remark"`
|
||||
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
func (m *SystemSMS) TableName() string {
|
||||
return "yz_system_sms"
|
||||
}
|
||||
22
models/system_sms_task.go
Normal file
22
models/system_sms_task.go
Normal file
@ -0,0 +1,22 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// SystemSMSTask 短信任务表 yz_system_sms_tasks
|
||||
type SystemSMSTask struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid *uint64 `orm:"column(tid);null" json:"tid"` // 测试允许为空
|
||||
ApiKey string `orm:"column(api_key);size(255)" json:"api_key"`
|
||||
Phone string `orm:"column(phone);size(50)" json:"phone"`
|
||||
Content *string `orm:"column(content);type(text);null" json:"content"`
|
||||
Status int `orm:"column(status);default(0)" json:"status"`
|
||||
Code string `orm:"column(code);size(20);default('')" json:"code"`
|
||||
ReportRaw *string `orm:"column(report_raw);type(text);null" json:"report_raw"`
|
||||
CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"`
|
||||
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
|
||||
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
|
||||
}
|
||||
|
||||
func (m *SystemSMSTask) TableName() string {
|
||||
return "yz_system_sms_tasks"
|
||||
}
|
||||
@ -2,7 +2,7 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// Tenant 租户表 yz_tenant
|
||||
// Tenant 租户表 yz_system_tenant
|
||||
type Tenant struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"` // 租户唯一标识(主键)
|
||||
TenantCode string `orm:"column(tenant_code);size(32);unique" json:"tenantCode"` // 租户编码
|
||||
@ -21,6 +21,5 @@ type Tenant struct {
|
||||
|
||||
// TableName 自定义表名
|
||||
func (t *Tenant) TableName() string {
|
||||
return "yz_tenant"
|
||||
return "yz_system_tenant"
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// TenantUser 租户用户绑定关系表 yz_tenant_user
|
||||
// TenantUser 租户用户绑定关系表 yz_system_tenant_user
|
||||
type TenantUser struct {
|
||||
ID uint64 `orm:"column(id);pk;auto" json:"id"`
|
||||
Tid uint64 `orm:"column(tid)" json:"tid"` // 租户ID
|
||||
@ -22,7 +22,7 @@ type TenantUser struct {
|
||||
|
||||
// TableName 自定义表名
|
||||
func (m *TenantUser) TableName() string {
|
||||
return "yz_tenant_user"
|
||||
return "yz_system_tenant_user"
|
||||
}
|
||||
|
||||
// BindTenantUser 绑定用户到租户(若已存在则更新状态/默认值)
|
||||
@ -103,4 +103,3 @@ func SetDefaultTenant(uid, tid uint64) error {
|
||||
Update(map[string]interface{}{"is_default": 1})
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
func Register() {
|
||||
// 平台登录相关
|
||||
beego.Router("/platform/login", &controllers.PlatformAuthController{}, "post:Login")
|
||||
beego.Router("/platform/currentUser", &controllers.PlatformAuthController{}, "get:GetCurrentUser")
|
||||
beego.Router("/platform/sendLoginCode", &controllers.PlatformAuthController{}, "post:SendLoginCode")
|
||||
beego.Router("/platform/loginBySms", &controllers.PlatformAuthController{}, "post:LoginBySms")
|
||||
beego.Router("/platform/logout", &controllers.PlatformAuthController{}, "post:Logout")
|
||||
@ -47,7 +48,7 @@ func Register() {
|
||||
beego.Router("/platform/tenantUser/edit/:id", &controllers.PlatformTenantUserController{}, "post:EditTenantUser")
|
||||
beego.Router("/platform/tenantUser/delete/:id", &controllers.PlatformTenantUserController{}, "delete:DeleteTenantUser")
|
||||
|
||||
// 平台管理员用户管理(yz_admin_user)
|
||||
// 平台管理员用户管理(yz_system_admin_user)
|
||||
beego.Router("/platform/getAllUsers", &controllers.PlatformAdminUserController{}, "get:GetAllUsers")
|
||||
beego.Router("/platform/getUserInfo/:id", &controllers.PlatformAdminUserController{}, "get:GetUserInfo")
|
||||
beego.Router("/platform/addUser", &controllers.PlatformAdminUserController{}, "post:AddUser")
|
||||
@ -55,11 +56,42 @@ func Register() {
|
||||
beego.Router("/platform/deleteUser/:id", &controllers.PlatformAdminUserController{}, "delete:DeleteUser")
|
||||
beego.Router("/platform/changePassword", &controllers.PlatformAdminUserController{}, "post:ChangePassword")
|
||||
|
||||
// 平台角色管理(yz_admin_role)
|
||||
// 平台角色管理(yz_system_admin_role)
|
||||
beego.Router("/platform/allRoles", &controllers.PlatformRoleController{}, "get:GetAllRoles")
|
||||
beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "get:GetRoleByID")
|
||||
beego.Router("/platform/roles", &controllers.PlatformRoleController{}, "post:CreateRole")
|
||||
beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "put:UpdateRole")
|
||||
beego.Router("/platform/roles/:id", &controllers.PlatformRoleController{}, "delete:DeleteRole")
|
||||
}
|
||||
|
||||
// 系统邮箱配置(yz_system_email)
|
||||
beego.Router("/platform/email/info", &controllers.PlatformEmailController{}, "get:GetInfo")
|
||||
beego.Router("/platform/email/editinfo", &controllers.PlatformEmailController{}, "post:EditInfo")
|
||||
beego.Router("/platform/email/sendtestemail", &controllers.PlatformEmailController{}, "post:SendTestEmail")
|
||||
|
||||
// 短信配置(yz_system_sms)
|
||||
beego.Router("/platform/sms/info", &controllers.PlatformSMSController{}, "get:GetSmsInfo")
|
||||
beego.Router("/platform/sms/editinfo", &controllers.PlatformSMSController{}, "post:EditSmsInfo")
|
||||
beego.Router("/platform/sms/sendtest", &controllers.PlatformSMSController{}, "post:SendTestSms")
|
||||
beego.Router("/platform/sms/taskList", &controllers.PlatformSMSController{}, "get:GetSmsTaskList")
|
||||
beego.Router("/platform/sms/taskEdit/:id", &controllers.PlatformSMSController{}, "post:EditSmsTask")
|
||||
|
||||
// 文件管理(yz_system_files / yz_system_files_category)
|
||||
beego.Router("/platform/usercate", &controllers.PlatformFileController{}, "get:GetUserCate")
|
||||
beego.Router("/platform/allfiles", &controllers.PlatformFileController{}, "get:GetAllFiles")
|
||||
beego.Router("/platform/catefiles/:id", &controllers.PlatformFileController{}, "get:GetCateFiles")
|
||||
beego.Router("/platform/file/:id", &controllers.PlatformFileController{}, "get:GetFileByID")
|
||||
beego.Router("/platform/deletefilepermanently/:id", &controllers.PlatformFileController{}, "delete:DeleteFilePermanently")
|
||||
beego.Router("/platform/uploadfile", &controllers.PlatformFileController{}, "post:UploadFile")
|
||||
beego.Router("/platform/uploadfiles", &controllers.PlatformFileController{}, "post:UploadFile")
|
||||
beego.Router("/platform/updatefile/:id", &controllers.PlatformFileController{}, "post:UpdateFile")
|
||||
beego.Router("/platform/deletefile/:id", &controllers.PlatformFileController{}, "delete:DeleteFile")
|
||||
beego.Router("/platform/movefile/:id", &controllers.PlatformFileController{}, "get:MoveFile")
|
||||
beego.Router("/platform/createfilecate", &controllers.PlatformFileController{}, "post:CreateFileCate")
|
||||
beego.Router("/platform/renamefilecate/:id", &controllers.PlatformFileController{}, "post:RenameFileCate")
|
||||
beego.Router("/platform/deletefilecate/:id", &controllers.PlatformFileController{}, "delete:DeleteFileCate")
|
||||
beego.Router("/platform/uploadavatar", &controllers.PlatformFileController{}, "post:UploadAvatar")
|
||||
beego.Router("/platform/uploadavatar/:id", &controllers.PlatformFileController{}, "post:UpdateAvatar")
|
||||
beego.Router("/platform/batchdeletefiles", &controllers.PlatformFileController{}, "post:BatchDeleteFiles")
|
||||
beego.Router("/platform/batchDeleteFilesPermanently", &controllers.PlatformFileController{}, "post:BatchDeleteFilesPermanently")
|
||||
beego.Router("/platform/batchMoveFiles", &controllers.PlatformFileController{}, "post:BatchMoveFiles")
|
||||
}
|
||||
|
||||
BIN
server.exe
BIN
server.exe
Binary file not shown.
@ -16,6 +16,38 @@ type PlatformLoginUser struct {
|
||||
Name string
|
||||
Rid uint64
|
||||
Avatar string
|
||||
RoleName string
|
||||
}
|
||||
|
||||
func adminRoleNameByID(roleID uint64) string {
|
||||
if roleID == 0 {
|
||||
return ""
|
||||
}
|
||||
var role models.AdminRole
|
||||
err := models.Orm.QueryTable(new(models.AdminRole)).Filter("id", roleID).One(&role)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return role.Name
|
||||
}
|
||||
|
||||
func toPlatformLoginUser(user *models.AdminUser) *PlatformLoginUser {
|
||||
name := ""
|
||||
if user.Name != nil {
|
||||
name = *user.Name
|
||||
}
|
||||
avatar := ""
|
||||
if user.Avatar != nil {
|
||||
avatar = *user.Avatar
|
||||
}
|
||||
return &PlatformLoginUser{
|
||||
ID: user.ID,
|
||||
Account: user.Account,
|
||||
Name: name,
|
||||
Rid: user.RoleID,
|
||||
Avatar: avatar,
|
||||
RoleName: adminRoleNameByID(user.RoleID),
|
||||
}
|
||||
}
|
||||
|
||||
func md5Hex(s string) string {
|
||||
@ -23,7 +55,7 @@ func md5Hex(s string) string {
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// PlatformLogin 平台登录业务(仅允许平台用户 yz_admin_user 登录)
|
||||
// PlatformLogin 平台登录业务(仅允许平台用户 yz_system_admin_user 登录)
|
||||
func PlatformLogin(account, password string) (string, *PlatformLoginUser, error) {
|
||||
account = strings.TrimSpace(account)
|
||||
password = strings.TrimSpace(password)
|
||||
@ -52,21 +84,18 @@ func PlatformLogin(account, password string) (string, *PlatformLoginUser, error)
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
name := ""
|
||||
if user.Name != nil {
|
||||
name = *user.Name
|
||||
}
|
||||
avatar := ""
|
||||
if user.Avatar != nil {
|
||||
avatar = *user.Avatar
|
||||
}
|
||||
loginUser := &PlatformLoginUser{
|
||||
ID: user.ID,
|
||||
Account: user.Account,
|
||||
Name: name,
|
||||
Rid: user.RoleID,
|
||||
Avatar: avatar,
|
||||
}
|
||||
loginUser := toPlatformLoginUser(&user)
|
||||
return token, loginUser, nil
|
||||
}
|
||||
|
||||
// PlatformGetCurrentUser 根据平台管理员用户 ID 返回登录用户信息(含角色名称)
|
||||
func PlatformGetCurrentUser(uid uint64) (*PlatformLoginUser, error) {
|
||||
u, err := models.GetAdminUserByID(uid)
|
||||
if err != nil {
|
||||
return nil, errors.New("用户不存在")
|
||||
}
|
||||
if u.Status == 0 {
|
||||
return nil, errors.New("账号已禁用")
|
||||
}
|
||||
return toPlatformLoginUser(u), nil
|
||||
}
|
||||
|
||||
126
services/system_email_smtp.go
Normal file
126
services/system_email_smtp.go
Normal file
@ -0,0 +1,126 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SMTPConfig 发送邮件所需参数(与 yz_system_email 字段对应)
|
||||
type SMTPConfig struct {
|
||||
FromAddress string
|
||||
FromName string
|
||||
Host string
|
||||
Port uint
|
||||
Password string
|
||||
Encryption string // ssl / tls / none
|
||||
Timeout uint // 秒
|
||||
}
|
||||
|
||||
// SendTestEmailSMTP 发送一封简单测试邮件(纯文本 UTF-8)
|
||||
func SendTestEmailSMTP(cfg SMTPConfig, to string) error {
|
||||
to = strings.TrimSpace(to)
|
||||
if to == "" {
|
||||
return fmt.Errorf("收件人不能为空")
|
||||
}
|
||||
if cfg.Host == "" || cfg.FromAddress == "" {
|
||||
return fmt.Errorf("SMTP 主机或发件人不能为空")
|
||||
}
|
||||
if cfg.Port == 0 {
|
||||
cfg.Port = 465
|
||||
}
|
||||
timeout := cfg.Timeout
|
||||
if timeout == 0 {
|
||||
timeout = 30
|
||||
}
|
||||
d := net.Dialer{Timeout: time.Duration(timeout) * time.Second}
|
||||
addr := net.JoinHostPort(cfg.Host, strconv.FormatUint(uint64(cfg.Port), 10))
|
||||
enc := strings.ToLower(strings.TrimSpace(cfg.Encryption))
|
||||
if enc == "" {
|
||||
enc = "ssl"
|
||||
}
|
||||
|
||||
var client *smtp.Client
|
||||
var err error
|
||||
|
||||
switch enc {
|
||||
case "ssl":
|
||||
conn, derr := tls.DialWithDialer(&d, "tcp", addr, &tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12})
|
||||
if derr != nil {
|
||||
return fmt.Errorf("连接 SMTP 失败: %w", derr)
|
||||
}
|
||||
defer conn.Close()
|
||||
client, err = smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SMTP 握手失败: %w", err)
|
||||
}
|
||||
case "tls":
|
||||
conn, derr := d.Dial("tcp", addr)
|
||||
if derr != nil {
|
||||
return fmt.Errorf("连接 SMTP 失败: %w", derr)
|
||||
}
|
||||
defer conn.Close()
|
||||
client, err = smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SMTP 握手失败: %w", err)
|
||||
}
|
||||
if ok, _ := client.Extension("STARTTLS"); ok {
|
||||
if err = client.StartTLS(&tls.Config{ServerName: cfg.Host, MinVersion: tls.VersionTLS12}); err != nil {
|
||||
_ = client.Close()
|
||||
return fmt.Errorf("STARTTLS 失败: %w", err)
|
||||
}
|
||||
}
|
||||
case "none":
|
||||
conn, derr := d.Dial("tcp", addr)
|
||||
if derr != nil {
|
||||
return fmt.Errorf("连接 SMTP 失败: %w", derr)
|
||||
}
|
||||
defer conn.Close()
|
||||
client, err = smtp.NewClient(conn, cfg.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SMTP 握手失败: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("不支持的加密方式: %s", cfg.Encryption)
|
||||
}
|
||||
defer func() { _ = client.Close() }()
|
||||
|
||||
auth := smtp.PlainAuth("", cfg.FromAddress, cfg.Password, cfg.Host)
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("SMTP 认证失败: %w", err)
|
||||
}
|
||||
if err = client.Mail(cfg.FromAddress); err != nil {
|
||||
return fmt.Errorf("MAIL FROM 失败: %w", err)
|
||||
}
|
||||
if err = client.Rcpt(to); err != nil {
|
||||
return fmt.Errorf("RCPT TO 失败: %w", err)
|
||||
}
|
||||
wc, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("DATA 失败: %w", err)
|
||||
}
|
||||
fromName := strings.TrimSpace(cfg.FromName)
|
||||
subject := "平台邮箱测试"
|
||||
body := "这是一封来自管理后台「邮箱管理」的测试邮件。\r\nThis is a test email from the platform email settings.\r\n"
|
||||
headers := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: 8bit\r\n\r\n",
|
||||
formatFromHeader(fromName, cfg.FromAddress), to, subject)
|
||||
if _, err = wc.Write([]byte(headers + body)); err != nil {
|
||||
return fmt.Errorf("写入邮件内容失败: %w", err)
|
||||
}
|
||||
if err = wc.Close(); err != nil {
|
||||
return fmt.Errorf("结束 DATA 失败: %w", err)
|
||||
}
|
||||
return client.Quit()
|
||||
}
|
||||
|
||||
func formatFromHeader(name, addr string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return addr
|
||||
}
|
||||
return fmt.Sprintf("%s <%s>", name, addr)
|
||||
}
|
||||
77
services/system_email_store.go
Normal file
77
services/system_email_store.go
Normal file
@ -0,0 +1,77 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"server/models"
|
||||
)
|
||||
|
||||
// ListSystemEmails 返回全部邮箱配置(按 id 升序,通常仅一条)
|
||||
func ListSystemEmails() ([]models.SystemEmail, error) {
|
||||
var rows []models.SystemEmail
|
||||
_, err := models.Orm.QueryTable(new(models.SystemEmail)).OrderBy("id").All(&rows)
|
||||
return rows, err
|
||||
}
|
||||
|
||||
// UpsertFirstSystemEmail 若已有记录则更新第一条,否则插入
|
||||
func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, port uint, password string, encryption string, timeout uint, status int8, remark *string) error {
|
||||
if encryption == "" {
|
||||
encryption = "ssl"
|
||||
}
|
||||
if port == 0 {
|
||||
port = 465
|
||||
}
|
||||
if timeout == 0 {
|
||||
timeout = 30
|
||||
}
|
||||
if status == 0 {
|
||||
status = 1
|
||||
}
|
||||
fromAddress = strings.TrimSpace(fromAddress)
|
||||
host = strings.TrimSpace(host)
|
||||
|
||||
cnt, err := models.Orm.QueryTable(new(models.SystemEmail)).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt == 0 {
|
||||
if strings.TrimSpace(password) == "" {
|
||||
return fmt.Errorf("首次保存必须填写授权码/密码")
|
||||
}
|
||||
row := &models.SystemEmail{
|
||||
FromAddress: fromAddress,
|
||||
FromName: fromName,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Password: strings.TrimSpace(password),
|
||||
Encryption: encryption,
|
||||
Timeout: timeout,
|
||||
Status: status,
|
||||
Remark: remark,
|
||||
}
|
||||
_, err = models.Orm.Insert(row)
|
||||
return err
|
||||
}
|
||||
|
||||
var first models.SystemEmail
|
||||
if err := models.Orm.QueryTable(new(models.SystemEmail)).OrderBy("id").Limit(1).One(&first); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
up := map[string]interface{}{
|
||||
"from_address": fromAddress,
|
||||
"from_name": fromName,
|
||||
"host": host,
|
||||
"port": port,
|
||||
"encryption": encryption,
|
||||
"timeout": timeout,
|
||||
"status": status,
|
||||
"remark": remark,
|
||||
}
|
||||
if strings.TrimSpace(password) != "" {
|
||||
up["password"] = strings.TrimSpace(password)
|
||||
}
|
||||
_, err = models.Orm.QueryTable(new(models.SystemEmail)).Filter("id", first.ID).Update(up)
|
||||
return err
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user