Полный продакшен-рефактор fix25: split peer/admin, удаление legacy, шифрование secret, auth_id, новые API/роуты и зачистка subscription
This commit is contained in:
+52
-234
@@ -3,19 +3,19 @@ package controller
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"hy2xs-admin/model/constant"
|
||||
"hy2xs-admin/model/dto"
|
||||
"hy2xs-admin/model/entity"
|
||||
"hy2xs-admin/model/vo"
|
||||
"hy2xs-admin/service"
|
||||
"hy2xs-admin/util"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func resolveID(c *gin.Context) (int64, error) {
|
||||
@@ -37,18 +37,12 @@ func Login(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
token, forcePasswordChange, err := service.Login(*loginDto.Username, *loginDto.Pass)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
jwtVo := vo.JwtVo{
|
||||
TokenType: constant.TokenType,
|
||||
AccessToken: token,
|
||||
ForcePasswordChange: forcePasswordChange,
|
||||
}
|
||||
vo.Success(jwtVo, c)
|
||||
vo.Success(vo.JwtVo{TokenType: constant.TokenType, AccessToken: token, ForcePasswordChange: forcePasswordChange}, c)
|
||||
}
|
||||
|
||||
func PagePeer(c *gin.Context) {
|
||||
@@ -56,85 +50,25 @@ func PagePeer(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
accounts, total, err := service.PagePeer(peerPageDto)
|
||||
records, total, err := service.PagePeer(peerPageDto)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
onlineUsers, err := service.Hysteria2Online()
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
var accountVos []vo.AccountVo
|
||||
for _, item := range accounts {
|
||||
accountVo := vo.AccountVo{
|
||||
Username: *item.Username,
|
||||
Quota: *item.Quota,
|
||||
Download: *item.Download,
|
||||
Upload: *item.Upload,
|
||||
ExpireTime: *item.ExpireTime,
|
||||
KickUtilTime: *item.KickUtilTime,
|
||||
DeviceNo: *item.DeviceNo,
|
||||
Role: *item.Role,
|
||||
Deleted: *item.Deleted,
|
||||
BaseVo: vo.BaseVo{
|
||||
Id: *item.Id,
|
||||
CreateTime: *item.CreateTime,
|
||||
},
|
||||
LoginAt: *item.LoginAt,
|
||||
ConAt: *item.ConAt,
|
||||
Remark: *item.Remark,
|
||||
}
|
||||
if value, exists := onlineUsers[*item.Username]; exists {
|
||||
accountVo.Online = true
|
||||
accountVo.Device = value
|
||||
delete(onlineUsers, *item.Username)
|
||||
}
|
||||
accountVos = append(accountVos, accountVo)
|
||||
}
|
||||
accountPageVo := vo.AccountPageVo{
|
||||
AccountVos: accountVos,
|
||||
Total: total,
|
||||
}
|
||||
vo.Success(accountPageVo, c)
|
||||
vo.Success(vo.PeerPageVo{Records: records, Total: total}, c)
|
||||
}
|
||||
|
||||
func SavePeer(c *gin.Context) {
|
||||
accountSaveDto, err := validateField(c, dto.AccountSaveDto{})
|
||||
peerSaveDto, err := validateField(c, dto.PeerSaveDto{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if service.ExistPeerName(*accountSaveDto.Username, 0) {
|
||||
vo.Fail(fmt.Sprintf("username %s already exists", *accountSaveDto.Username), c)
|
||||
return
|
||||
}
|
||||
|
||||
passEncrypt, err := util.HashPassword(*accountSaveDto.Pass)
|
||||
peerVo, err := service.CreatePeer(peerSaveDto)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
conPass := fmt.Sprintf("%s.%s", *accountSaveDto.Username, *accountSaveDto.ConPass)
|
||||
account := entity.Account{
|
||||
Username: accountSaveDto.Username,
|
||||
Pass: &passEncrypt,
|
||||
ConPass: &conPass,
|
||||
Quota: accountSaveDto.Quota,
|
||||
ExpireTime: accountSaveDto.ExpireTime,
|
||||
DeviceNo: accountSaveDto.DeviceNo,
|
||||
Deleted: accountSaveDto.Deleted,
|
||||
Remark: accountSaveDto.Remark,
|
||||
}
|
||||
err = service.SavePeer(account)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(nil, c)
|
||||
vo.Success(peerVo, c)
|
||||
}
|
||||
|
||||
func DeletePeer(c *gin.Context) {
|
||||
@@ -142,17 +76,7 @@ func DeletePeer(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
account, err := service.GetPeer(id)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if *account.Role == "admin" {
|
||||
vo.Fail("admin cannot be deleted", c)
|
||||
return
|
||||
}
|
||||
err = service.DeletePeer([]int64{id})
|
||||
if err != nil {
|
||||
if err = service.DeletePeer(id); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
@@ -160,105 +84,19 @@ func DeletePeer(c *gin.Context) {
|
||||
}
|
||||
|
||||
func UpdatePeer(c *gin.Context) {
|
||||
accountUpdateDto, err := validateField(c, dto.AccountUpdateDto{})
|
||||
id, err := resolveID(c)
|
||||
if err != nil {
|
||||
// PATCH /peers/:id compatibility flow: id in route param, payload without id
|
||||
if c.Request.Method == "PATCH" && strings.TrimSpace(c.Param("id")) != "" {
|
||||
id, parseErr := strconv.ParseInt(strings.TrimSpace(c.Param("id")), 10, 64)
|
||||
if parseErr != nil || id <= 0 {
|
||||
vo.Fail(constant.InvalidError, c)
|
||||
return
|
||||
}
|
||||
var body map[string]interface{}
|
||||
if bindErr := c.ShouldBindJSON(&body); bindErr != nil {
|
||||
vo.Fail(constant.InvalidError, c)
|
||||
return
|
||||
}
|
||||
legacy := entity.Account{BaseEntity: entity.BaseEntity{Id: &id}}
|
||||
if v, ok := body["username"].(string); ok {
|
||||
legacy.Username = &v
|
||||
}
|
||||
if v, ok := body["pass"].(string); ok && strings.TrimSpace(v) != "" {
|
||||
hash, hashErr := util.HashPassword(v)
|
||||
if hashErr != nil {
|
||||
vo.Fail(hashErr.Error(), c)
|
||||
return
|
||||
}
|
||||
legacy.Pass = &hash
|
||||
}
|
||||
if v, ok := body["conPass"].(string); ok {
|
||||
legacy.ConPass = &v
|
||||
}
|
||||
if v, ok := body["quota"].(float64); ok {
|
||||
t := int64(v)
|
||||
legacy.Quota = &t
|
||||
}
|
||||
if v, ok := body["expireTime"].(float64); ok {
|
||||
t := int64(v)
|
||||
legacy.ExpireTime = &t
|
||||
}
|
||||
if v, ok := body["deviceNo"].(float64); ok {
|
||||
t := int64(v)
|
||||
legacy.DeviceNo = &t
|
||||
}
|
||||
if v, ok := body["deleted"].(float64); ok {
|
||||
t := int64(v)
|
||||
legacy.Deleted = &t
|
||||
}
|
||||
if v, ok := body["remark"].(string); ok {
|
||||
legacy.Remark = &v
|
||||
}
|
||||
if uErr := service.UpdatePeer(legacy); uErr != nil {
|
||||
vo.Fail(uErr.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(nil, c)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if accountUpdateDto.Username != nil && *accountUpdateDto.Username != "" && service.ExistPeerName(*accountUpdateDto.Username, *accountUpdateDto.Id) {
|
||||
vo.Fail(fmt.Sprintf("username %s already exists", *accountUpdateDto.Username), c)
|
||||
peerUpdateDto, err := validateField(c, dto.PeerUpdateDto{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if accountUpdateDto.Deleted != nil && *accountUpdateDto.Deleted == 1 {
|
||||
account, err := service.GetPeer(*accountUpdateDto.Id)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
if *account.Role == "admin" {
|
||||
vo.Fail("the admin account cannot be deleted", c)
|
||||
return
|
||||
}
|
||||
if peerUpdateDto.Name != nil && *peerUpdateDto.Name != "" && service.ExistPeerName(*peerUpdateDto.Name, id) {
|
||||
vo.Fail(fmt.Sprintf("name %s already exists", *peerUpdateDto.Name), c)
|
||||
return
|
||||
}
|
||||
|
||||
var passEncrypt *string
|
||||
if accountUpdateDto.Pass != nil && *accountUpdateDto.Pass != "" {
|
||||
passEncryptHash, hashErr := util.HashPassword(*accountUpdateDto.Pass)
|
||||
if hashErr != nil {
|
||||
vo.Fail(hashErr.Error(), c)
|
||||
return
|
||||
}
|
||||
passEncrypt = &passEncryptHash
|
||||
}
|
||||
|
||||
account := entity.Account{
|
||||
Username: accountUpdateDto.Username,
|
||||
Pass: passEncrypt,
|
||||
ConPass: accountUpdateDto.ConPass,
|
||||
Quota: accountUpdateDto.Quota,
|
||||
ExpireTime: accountUpdateDto.ExpireTime,
|
||||
DeviceNo: accountUpdateDto.DeviceNo,
|
||||
Deleted: accountUpdateDto.Deleted,
|
||||
Remark: accountUpdateDto.Remark,
|
||||
BaseEntity: entity.BaseEntity{
|
||||
Id: accountUpdateDto.Id,
|
||||
},
|
||||
}
|
||||
if err = service.UpdatePeer(account); err != nil {
|
||||
if err = service.UpdatePeer(id, peerUpdateDto); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
@@ -270,54 +108,24 @@ func ResetTraffic(c *gin.Context) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = service.ResetTraffic(id); err != nil {
|
||||
if err = service.ResetPeerTraffic(id); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(nil, c)
|
||||
}
|
||||
|
||||
func GetAdminInfo(c *gin.Context) {
|
||||
accountInfoVo, err := service.GetAdminInfo(c)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
// Обновление времени последнего входа
|
||||
now := time.Now().UnixMilli()
|
||||
if err = service.UpdateAdminLastLoginAt(accountInfoVo.Id, now); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(accountInfoVo, c)
|
||||
}
|
||||
|
||||
func GetPeer(c *gin.Context) {
|
||||
id, err := resolveID(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
account, err := service.GetPeer(id)
|
||||
peer, err := service.GetPeerVo(id)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
accountVo := vo.AccountVo{
|
||||
BaseVo: vo.BaseVo{
|
||||
Id: *account.Id,
|
||||
CreateTime: *account.CreateTime,
|
||||
},
|
||||
Username: *account.Username,
|
||||
Quota: *account.Quota,
|
||||
Download: *account.Download,
|
||||
Upload: *account.Upload,
|
||||
ExpireTime: *account.ExpireTime,
|
||||
DeviceNo: *account.DeviceNo,
|
||||
Role: *account.Role,
|
||||
Deleted: *account.Deleted,
|
||||
Remark: *account.Remark,
|
||||
}
|
||||
vo.Success(accountVo, c)
|
||||
vo.Success(peer, c)
|
||||
}
|
||||
|
||||
func ImportPeer(c *gin.Context) {
|
||||
@@ -326,12 +134,10 @@ func ImportPeer(c *gin.Context) {
|
||||
vo.Fail(constant.SysError, c)
|
||||
return
|
||||
}
|
||||
// Размер файла 2 MB
|
||||
if header.Size > 1024*1024*2 {
|
||||
vo.Fail("the file is too big", c)
|
||||
return
|
||||
}
|
||||
// Расширение файла .json
|
||||
if !strings.HasSuffix(header.Filename, ".json") {
|
||||
vo.Fail(constant.InvalidError, c)
|
||||
return
|
||||
@@ -346,7 +152,7 @@ func ImportPeer(c *gin.Context) {
|
||||
vo.Fail("content Unmarshal err", c)
|
||||
return
|
||||
}
|
||||
if err = service.UpsertPeer(accounts); err != nil {
|
||||
if err = service.UpsertPeerLegacy(accounts); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
@@ -354,21 +160,18 @@ func ImportPeer(c *gin.Context) {
|
||||
}
|
||||
|
||||
func ExportPeer(c *gin.Context) {
|
||||
accountExports, err := service.ListExportPeer()
|
||||
includeSecrets := strings.EqualFold(strings.TrimSpace(c.Query("includeSecrets")), "true")
|
||||
peerExports, err := service.ListExportPeer(includeSecrets)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("AccountExport-%s.json", time.Now().Format("20060102150405"))
|
||||
fileName := fmt.Sprintf("PeerExport-%s.json", time.Now().Format("20060102150405"))
|
||||
filePath := filepath.Join(constant.ExportPathDir, fileName)
|
||||
|
||||
if err = util.ExportFile(filePath, accountExports, 0); err != nil {
|
||||
if err = util.ExportFile(filePath, peerExports, 0); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// Скачивание
|
||||
if !util.Exists(filePath) {
|
||||
vo.Fail("file not exist", c)
|
||||
return
|
||||
@@ -385,23 +188,38 @@ func ReleaseKickPeer(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err = service.ReleaseKickPeer(id); err != nil {
|
||||
logrus.Debugf("release kick err: %v", err)
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(nil, c)
|
||||
}
|
||||
|
||||
func VerifyDefaultPass(c *gin.Context) {
|
||||
info, err := service.GetAdminInfo(c)
|
||||
func KickPeer(c *gin.Context) {
|
||||
id, err := resolveID(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kickDto, err := validateField(c, dto.PeerKickDto{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if err = service.KickPeer(id, *kickDto.BannedUntil); err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
admin, err := service.GetAdminAccount(info.Id)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(admin.PasswordHash != nil && !util.IsBcryptHash(*admin.PasswordHash), c)
|
||||
vo.Success(nil, c)
|
||||
}
|
||||
|
||||
func PeerClientConfig(c *gin.Context) {
|
||||
id, err := resolveID(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data, err := service.BuildPeerClientConfig(id)
|
||||
if err != nil {
|
||||
vo.Fail(err.Error(), c)
|
||||
return
|
||||
}
|
||||
vo.Success(data, c)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user