Полная зачистка 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
+30
View File
@@ -0,0 +1,30 @@
package controller
import (
"github.com/gin-gonic/gin"
"hy2xs-admin/model/dto"
"hy2xs-admin/model/vo"
"hy2xs-admin/service"
)
func AdminMe(c *gin.Context) {
info, err := service.GetAdminInfo(c)
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(info, c)
}
func AdminChangePassword(c *gin.Context) {
changeDto, err := validateField(c, dto.AdminChangePasswordDto{})
if err != nil {
return
}
if err = service.ChangeAdminPassword(c, *changeDto.OldPassword, *changeDto.NewPassword); err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
+1 -1
View File
@@ -287,7 +287,7 @@ func ImportConfig(c *gin.Context) {
return
}
if !strings.HasSuffix(header.Filename, ".json") {
vo.Fail("file format not supported", c)
vo.Fail(constant.InvalidError, c)
return
}
content, err := io.ReadAll(file)
+53
View File
@@ -0,0 +1,53 @@
package controller
import (
"github.com/gin-gonic/gin"
"hy2xs-admin/model/vo"
"hy2xs-admin/service"
"strconv"
)
func DashboardSummary(c *gin.Context) {
data, err := service.DashboardSummary()
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(data, c)
}
func DashboardTimeseries(c *gin.Context) {
rangeKey := c.DefaultQuery("range", "24h")
data, err := service.DashboardTimeseries(rangeKey)
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(data, c)
}
func DashboardTopPeers(c *gin.Context) {
rangeKey := c.DefaultQuery("range", "24h")
limit := 10
if raw := c.Query("limit"); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 {
limit = parsed
}
}
data, err := service.DashboardTopPeers(rangeKey, limit)
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(data, c)
}
func DashboardSecurity(c *gin.Context) {
data, err := service.DashboardSecurity()
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(data, c)
}
+24 -16
View File
@@ -4,12 +4,25 @@ import (
"github.com/gin-gonic/gin"
"github.com/skip2/go-qrcode"
"hy2xs-admin/model/dto"
"hy2xs-admin/model/entity"
"hy2xs-admin/model/vo"
"hy2xs-admin/service"
"strconv"
"strings"
"time"
)
func resolvePeerID(c *gin.Context) (*int64, bool) {
raw := strings.TrimSpace(c.Param("id"))
if raw == "" {
return nil, false
}
parsed, err := strconv.ParseInt(raw, 10, 64)
if err != nil || parsed <= 0 {
return nil, false
}
return &parsed, true
}
func Hysteria2Auth(c *gin.Context) {
var req dto.Hysteria2AuthDto
if err := c.ShouldBindJSON(&req); err != nil {
@@ -28,10 +41,7 @@ func Hysteria2Auth(c *gin.Context) {
// Обновление времени последнего подключения
now := time.Now().UnixMilli()
if err = service.UpdateAccount(entity.Account{
BaseEntity: entity.BaseEntity{Id: &id},
ConAt: &now,
}); err != nil {
if err = service.UpdatePeerLastConnectionAt(id, now); err != nil {
vo.Fail(err.Error(), c)
return
}
@@ -60,9 +70,15 @@ func ListRelease(c *gin.Context) {
}
func Hysteria2Url(c *gin.Context) {
hysteria2UrlDto, err := validateField(c, dto.Hysteria2UrlDto{})
if err != nil {
return
hysteria2UrlDto := dto.Hysteria2UrlDto{}
if id, ok := resolvePeerID(c); ok {
hysteria2UrlDto.AccountId = id
} else {
var err error
hysteria2UrlDto, err = validateField(c, dto.Hysteria2UrlDto{})
if err != nil {
return
}
}
url, err := service.Hysteria2Url(*hysteria2UrlDto.AccountId)
@@ -82,11 +98,3 @@ func Hysteria2Url(c *gin.Context) {
}
vo.Success(hysteria2UrlVo, c)
}
func Hysteria2SubscribeUrl(c *gin.Context) {
vo.Fail("subscription delivery is out of scope in HY2XS baseline", c)
}
func Hysteria2Subscribe(c *gin.Context) {
vo.Fail("subscription delivery is out of scope in HY2XS baseline", c)
}
-25
View File
@@ -1,25 +0,0 @@
package controller
import (
"github.com/gin-gonic/gin"
"hy2xs-admin/model/vo"
"hy2xs-admin/service"
)
func MonitorSystem(c *gin.Context) {
systemMonitorVo, err := service.MonitorSystem()
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(systemMonitorVo, c)
}
func MonitorHysteria2(c *gin.Context) {
hysteria2MonitorVo, err := service.MonitorHysteria2()
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(hysteria2MonitorVo, c)
}
@@ -4,6 +4,7 @@ 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"
@@ -12,21 +13,31 @@ import (
"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
}
if !service.ExistAccountUsername(*loginDto.Username, 0) {
vo.Fail("account not exist", c)
return
}
token, forcePasswordChange, err := service.Login(*loginDto.Username, *loginDto.Pass)
if err != nil {
vo.Fail(err.Error(), c)
@@ -40,12 +51,12 @@ func Login(c *gin.Context) {
vo.Success(jwtVo, c)
}
func PageAccount(c *gin.Context) {
accountPageDto, err := validateField(c, dto.AccountPageDto{})
func PagePeer(c *gin.Context) {
peerPageDto, err := validateField(c, dto.PeerPageDto{})
if err != nil {
return
}
accounts, total, err := service.PageAccount(accountPageDto)
accounts, total, err := service.PagePeer(peerPageDto)
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -91,13 +102,13 @@ func PageAccount(c *gin.Context) {
vo.Success(accountPageVo, c)
}
func SaveAccount(c *gin.Context) {
func SavePeer(c *gin.Context) {
accountSaveDto, err := validateField(c, dto.AccountSaveDto{})
if err != nil {
return
}
if service.ExistAccountUsername(*accountSaveDto.Username, 0) {
if service.ExistPeerName(*accountSaveDto.Username, 0) {
vo.Fail(fmt.Sprintf("username %s already exists", *accountSaveDto.Username), c)
return
}
@@ -118,7 +129,7 @@ func SaveAccount(c *gin.Context) {
Deleted: accountSaveDto.Deleted,
Remark: accountSaveDto.Remark,
}
err = service.SaveAccount(account)
err = service.SavePeer(account)
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -126,12 +137,12 @@ func SaveAccount(c *gin.Context) {
vo.Success(nil, c)
}
func DeleteAccount(c *gin.Context) {
idDto, err := validateField(c, dto.IdDto{})
func DeletePeer(c *gin.Context) {
id, err := resolveID(c)
if err != nil {
return
}
account, err := service.GetAccount(*idDto.Id)
account, err := service.GetPeer(id)
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -140,7 +151,7 @@ func DeleteAccount(c *gin.Context) {
vo.Fail("admin cannot be deleted", c)
return
}
err = service.DeleteAccount([]int64{*idDto.Id})
err = service.DeletePeer([]int64{id})
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -148,19 +159,72 @@ func DeleteAccount(c *gin.Context) {
vo.Success(nil, c)
}
func UpdateAccount(c *gin.Context) {
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.ExistAccountUsername(*accountUpdateDto.Username, *accountUpdateDto.Id) {
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.GetAccount(*accountUpdateDto.Id)
account, err := service.GetPeer(*accountUpdateDto.Id)
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -194,7 +258,7 @@ func UpdateAccount(c *gin.Context) {
Id: accountUpdateDto.Id,
},
}
if err = service.UpdateAccount(account); err != nil {
if err = service.UpdatePeer(account); err != nil {
vo.Fail(err.Error(), c)
return
}
@@ -202,41 +266,38 @@ func UpdateAccount(c *gin.Context) {
}
func ResetTraffic(c *gin.Context) {
idDto, err := validateField(c, dto.IdDto{})
id, err := resolveID(c)
if err != nil {
return
}
if err = service.ResetTraffic(*idDto.Id); err != nil {
if err = service.ResetTraffic(id); err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
func GetAccountInfo(c *gin.Context) {
accountInfoVo, err := service.GetAccountInfo(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.UpdateAccount(entity.Account{
BaseEntity: entity.BaseEntity{Id: &accountInfoVo.Id},
LoginAt: &now,
}); err != nil {
if err = service.UpdateAdminLastLoginAt(accountInfoVo.Id, now); err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(accountInfoVo, c)
}
func GetAccount(c *gin.Context) {
idDto, err := validateField(c, dto.IdDto{})
func GetPeer(c *gin.Context) {
id, err := resolveID(c)
if err != nil {
return
}
account, err := service.GetAccount(*idDto.Id)
account, err := service.GetPeer(id)
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -259,7 +320,7 @@ func GetAccount(c *gin.Context) {
vo.Success(accountVo, c)
}
func ImportAccount(c *gin.Context) {
func ImportPeer(c *gin.Context) {
file, header, err := c.Request.FormFile("file")
if err != nil {
vo.Fail(constant.SysError, c)
@@ -272,7 +333,7 @@ func ImportAccount(c *gin.Context) {
}
// Расширение файла .json
if !strings.HasSuffix(header.Filename, ".json") {
vo.Fail("file format not supported", c)
vo.Fail(constant.InvalidError, c)
return
}
content, err := io.ReadAll(file)
@@ -285,15 +346,15 @@ func ImportAccount(c *gin.Context) {
vo.Fail("content Unmarshal err", c)
return
}
if err = service.UpsertAccount(accounts); err != nil {
if err = service.UpsertPeer(accounts); err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(nil, c)
}
func ExportAccount(c *gin.Context) {
accountExports, err := service.ListExportAccount()
func ExportPeer(c *gin.Context) {
accountExports, err := service.ListExportPeer()
if err != nil {
vo.Fail(err.Error(), c)
return
@@ -318,12 +379,13 @@ func ExportAccount(c *gin.Context) {
c.File(filePath)
}
func ReleaseKickAccount(c *gin.Context) {
idDto, err := validateField(c, dto.IdDto{})
func ReleaseKickPeer(c *gin.Context) {
id, err := resolveID(c)
if err != nil {
return
}
if err = service.ReleaseKickAccount(*idDto.Id); err != nil {
if err = service.ReleaseKickPeer(id); err != nil {
logrus.Debugf("release kick err: %v", err)
vo.Fail(err.Error(), c)
return
}
@@ -331,15 +393,15 @@ func ReleaseKickAccount(c *gin.Context) {
}
func VerifyDefaultPass(c *gin.Context) {
info, err := service.GetAccountInfo(c)
info, err := service.GetAdminInfo(c)
if err != nil {
vo.Fail(err.Error(), c)
return
}
account, err := service.GetAccount(info.Id)
admin, err := service.GetAdminAccount(info.Id)
if err != nil {
vo.Fail(err.Error(), c)
return
}
vo.Success(account.Pass != nil && !util.IsBcryptHash(*account.Pass), c)
vo.Success(admin.PasswordHash != nil && !util.IsBcryptHash(*admin.PasswordHash), c)
}