Полная зачистка legacy + закрытие fix24.1/fix24.2 + обновление логотипа

This commit is contained in:
2026-05-08 23:16:25 +05:00
parent de4a65a959
commit 1bca73814e
85 changed files with 4119 additions and 1911 deletions
+407
View File
@@ -0,0 +1,407 @@
package controller
import (
"encoding/json"
"fmt"
"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) {
if raw := strings.TrimSpace(c.Param("id")); raw != "" {
parsed, err := strconv.ParseInt(raw, 10, 64)
if err == nil && parsed > 0 {
return parsed, nil
}
}
idDto, err := validateField(c, dto.IdDto{})
if err != nil {
return 0, err
}
return *idDto.Id, nil
}
func Login(c *gin.Context) {
loginDto, err := validateField(c, dto.LoginDto{})
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)
}
func PagePeer(c *gin.Context) {
peerPageDto, err := validateField(c, dto.PeerPageDto{})
if err != nil {
return
}
accounts, 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)
}
func SavePeer(c *gin.Context) {
accountSaveDto, err := validateField(c, dto.AccountSaveDto{})
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)
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)
}
func DeletePeer(c *gin.Context) {
id, err := resolveID(c)
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 {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
func UpdatePeer(c *gin.Context) {
accountUpdateDto, err := validateField(c, dto.AccountUpdateDto{})
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)
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
}
}
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 {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
func ResetTraffic(c *gin.Context) {
id, err := resolveID(c)
if err != nil {
return
}
if err = service.ResetTraffic(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)
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)
}
func ImportPeer(c *gin.Context) {
file, header, err := c.Request.FormFile("file")
if err != nil {
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
}
content, err := io.ReadAll(file)
if err != nil {
vo.Fail("json file read err", c)
return
}
var accounts []entity.Account
if err = json.Unmarshal(content, &accounts); err != nil {
vo.Fail("content Unmarshal err", c)
return
}
if err = service.UpsertPeer(accounts); err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
func ExportPeer(c *gin.Context) {
accountExports, err := service.ListExportPeer()
if err != nil {
vo.Fail(err.Error(), c)
return
}
fileName := fmt.Sprintf("AccountExport-%s.json", time.Now().Format("20060102150405"))
filePath := filepath.Join(constant.ExportPathDir, fileName)
if err = util.ExportFile(filePath, accountExports, 0); err != nil {
vo.Fail(err.Error(), c)
return
}
// Скачивание
if !util.Exists(filePath) {
vo.Fail("file not exist", c)
return
}
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Transfer-Encoding", "binary")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
c.File(filePath)
}
func ReleaseKickPeer(c *gin.Context) {
id, err := resolveID(c)
if err != nil {
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)
if 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)
}