diff --git a/internal/api/v1/handler/admin.go b/internal/api/v1/handler/admin.go index ad5fb54..48a10b9 100644 --- a/internal/api/v1/handler/admin.go +++ b/internal/api/v1/handler/admin.go @@ -5,12 +5,12 @@ import ( "mc-client-updater-server/internal/service" "mc-client-updater-server/pkg/param" "mc-client-updater-server/pkg/result" + "mc-client-updater-server/pkg/util" + "time" ) func HandleLogin(c *gin.Context) { - srv := service.NewUserService(c) res := result.NewResult(c) - loginParam := param.LoginParam{} err := c.ShouldBindJSON(&loginParam) if err != nil { @@ -18,6 +18,62 @@ func HandleLogin(c *gin.Context) { return } + srv := service.NewUserService(c) srv.Login(loginParam.Username, loginParam.Password) - +} + +func HandleGrantAdd(c *gin.Context) { + res := result.NewResult(c) + p := param.NewGrantTokenParam{} + err := c.ShouldBindJSON(&p) + if err != nil { + res.BadRequest() + return + } + if p.ExpireAt != "" { + isValid := util.IsSQLTimeFormat(p.ExpireAt) + if !isValid { + res.BadRequestWithMsg("请求参数错误:时间格式错误,应为2006-01-02 15:04:05格式") + return + } + } else { + if p.TTL == 0 { + res.BadRequestWithMsg("请求参数错误:至少提供expire_at和ttl中的一项且不为0") + return + } + p.ExpireAt = util.ToSQLTimeFormat(time.Now().Add(time.Duration(p.TTL) * time.Second)) + } + + srv := service.NewInstanceService(c) + // 验证 target -> instance(name) 是否存在 + _, err = srv.GetInstanceByName(p.Target) + if err != nil { + res.InvalidInstance(p.Target) + return + } + + grantEntity, err := srv.NewGrantToken(p.Target, p.ExpireAt) + if err != nil { + res.InternalServerError("生成授权码失败") + return + } + res.Success(grantEntity) +} + +func HandleNewInstance(c *gin.Context) { + res := result.NewResult(c) + p := param.AddInstanceParam{} + err := c.ShouldBindJSON(&p) + if err != nil { + res.BadRequest() + return + } + + srv := service.NewInstanceService(c) + inst, err := srv.AddInstance(p.Name, p.UpdateURL) + if err != nil { + res.DuplicatedValue("实例名称已存在") + return + } + res.Success(inst) } diff --git a/internal/api/v1/handler/instance.go b/internal/api/v1/handler/instance.go new file mode 100644 index 0000000..c74f51a --- /dev/null +++ b/internal/api/v1/handler/instance.go @@ -0,0 +1,5 @@ +package handler + +func HandleInstanceUpdate() { + +} \ No newline at end of file diff --git a/internal/api/v1/middleware/admin.go b/internal/api/v1/middleware/admin.go index b024a09..d1a3895 100644 --- a/internal/api/v1/middleware/admin.go +++ b/internal/api/v1/middleware/admin.go @@ -11,7 +11,7 @@ func AdminRequired(c *gin.Context) { res := result.NewResult(c) authorization := c.GetHeader("Authorization") if authorization == "" { - res.Unauthorized() + res.UnLogin() return } diff --git a/internal/api/v1/middleware/grant.go b/internal/api/v1/middleware/grant.go index c714eac..e97f196 100644 --- a/internal/api/v1/middleware/grant.go +++ b/internal/api/v1/middleware/grant.go @@ -2,10 +2,37 @@ package middleware import ( "github.com/gin-gonic/gin" - "mc-client-updater-server/pkg/log" + "gorm.io/gorm" + "mc-client-updater-server/internal/service" + "mc-client-updater-server/pkg/result" ) func GrantRequired(c *gin.Context) { instName := c.Param("name") - log.Logger.Info(instName) + // 判断instance name是否存在 + srv := service.NewInstanceService(c) + res := result.NewResult(c) + instEntity, err := srv.GetInstanceByName(instName) + if err == gorm.ErrRecordNotFound { + res.InvalidInstance(instName) + return + } else if err != nil { + res.InternalServerError("查询实例对象时出现错误") + return + } + c.Set("instance", instEntity) + + // 判断grant_code是否合法 + grantCode := c.GetHeader("GrantCode") + if grantCode == "" { + res.Unauthorized() + return + } + grantEntity, err := srv.GetGrantByToken(grantCode) + if err != nil { + res.Unauthorized() + return + } + c.Set("grant", grantEntity) + c.Next() } diff --git a/internal/api/v1/router.go b/internal/api/v1/router.go index 32bee74..8181247 100644 --- a/internal/api/v1/router.go +++ b/internal/api/v1/router.go @@ -27,7 +27,7 @@ func NewRouter() *gin.Engine { */ inst := v1.Group("/instance/:name", middleware.GrantRequired) { - inst.GET("/detail") + inst.POST("/upload", ) } /** @@ -37,9 +37,8 @@ func NewRouter() *gin.Engine { */ admin := v1.Group("/admin", middleware.AdminRequired) { - admin.GET("/instances") - admin.GET("/users") - admin.GET("/updates") + admin.POST("/new_instance", handler.HandleNewInstance) + admin.POST("/grant/add", handler.HandleGrantAdd) } return r diff --git a/internal/service/instance.go b/internal/service/instance.go new file mode 100644 index 0000000..ed5049f --- /dev/null +++ b/internal/service/instance.go @@ -0,0 +1,54 @@ +package service + +import ( + "github.com/gin-gonic/gin" + "mc-client-updater-server/pkg/dao" + "mc-client-updater-server/pkg/dao/entity" + "mc-client-updater-server/pkg/util" +) + +type InstanceService struct { + ctx *gin.Context +} + +func NewInstanceService(c *gin.Context) *InstanceService { + return &InstanceService{ctx: c} +} + +func (s *InstanceService) AddInstance(name, updateURL string) (*entity.Instance, error) { + instEntity := entity.Instance{ + Name: name, + UpdateURL: updateURL, + } + tx := dao.DB().Create(&instEntity) + if tx.Error != nil { + return nil, tx.Error + } + + tx = dao.DB().Where(&instEntity).Last(&instEntity) + return &instEntity, tx.Error +} + +func (s *InstanceService) GetInstanceByName(name string) (*entity.Instance, error) { + instEntity := entity.Instance{} + tx := dao.DB().Where("name=?", name).Last(&instEntity) + return &instEntity, tx.Error +} + +func (s *InstanceService) NewGrantToken(instName string, expireStr string) (*entity.Grant, error) { + expireAt := util.MustParseSQLTime(expireStr) + grantEntity := entity.Grant{GrantTo: instName, ExpireAt: expireAt, Token: util.RandStr(32)} + tx := dao.DB().Create(&grantEntity) + if tx.Error != nil { + return nil, tx.Error + } + + tx = dao.DB().Where(&grantEntity).Last(&grantEntity) + return &grantEntity, tx.Error +} + +func (s *InstanceService) GetGrantByToken(token string) (*entity.Grant, error) { + grantEntity := entity.Grant{} + tx := dao.DB().Where("token=?", token).Last(&grantEntity) + return &grantEntity, tx.Error +} diff --git a/internal/service/token.go b/internal/service/token.go index 7c27861..6678435 100644 --- a/internal/service/token.go +++ b/internal/service/token.go @@ -22,7 +22,7 @@ func (s *TokenService) VerifyToken(token string) (*entity.Token, bool) { // 是否存在 tokenRow := s.getToken(token) if tokenRow == nil { - res.Unauthorized() + res.UnLogin() return nil, false } // 是否过期 @@ -34,8 +34,8 @@ func (s *TokenService) VerifyToken(token string) (*entity.Token, bool) { } func (s *TokenService) getToken(token string) *entity.Token { - tokenRow := entity.Token{Token: token} - tx := dao.DB().Last(&tokenRow) + tokenRow := entity.Token{} + tx := dao.DB().Where("token=?", token).Last(&tokenRow) if tx.Error == gorm.ErrRecordNotFound { return nil } @@ -43,8 +43,8 @@ func (s *TokenService) getToken(token string) *entity.Token { } func (s *TokenService) getTokenByUsername(username string) *entity.Token { - tokenRow := entity.Token{GrantTo: username} - tx := dao.DB().First(&tokenRow) + tokenRow := entity.Token{} + tx := dao.DB().Where("grant_to=?", username).Last(&tokenRow) if tx.Error == gorm.ErrRecordNotFound { return nil } diff --git a/internal/service/user.go b/internal/service/user.go index c817c4a..d291a59 100644 --- a/internal/service/user.go +++ b/internal/service/user.go @@ -64,8 +64,8 @@ func (s *UserService) hasRole(role string, user *entity.User) bool { } func (s *UserService) getUserByUsername(name string) *entity.User { - user := entity.User{Username: name} - tx := dao.DB().First(&user) + user := entity.User{} + tx := dao.DB().Where("username=?", name).First(&user) if tx.Error != nil { return nil } diff --git a/pkg/common/errcode.go b/pkg/common/errcode.go index 7c45dd8..d13046e 100644 --- a/pkg/common/errcode.go +++ b/pkg/common/errcode.go @@ -1,7 +1,16 @@ package common +/* +2000 用户操作错误 +3000 预留 +4000 数据库操作错误 +5000 系统错误 +*/ const ( - LoginErrorCode = 2001 - NoPermission = 2002 - LoginExpired = 2003 + LoginErrorCode = 2001 + NoPermission = 2002 + LoginExpired = 2003 + InvalidInstance = 2004 + + DuplicatedValue = 4001 ) diff --git a/pkg/dao/dao.go b/pkg/dao/dao.go index 7d18551..d715634 100644 --- a/pkg/dao/dao.go +++ b/pkg/dao/dao.go @@ -69,6 +69,7 @@ func migrate() { err = db.AutoMigrate(&entity.Update{}) err = db.AutoMigrate(&entity.Grant{}) err = db.AutoMigrate(&entity.Token{}) + err = db.AutoMigrate(&entity.Metadata{}) if err != nil { log.Logger.Fatal("关联数据表失败:", err) diff --git a/pkg/dao/entity/grant.go b/pkg/dao/entity/grant.go index 78ebc78..87d2e93 100644 --- a/pkg/dao/entity/grant.go +++ b/pkg/dao/entity/grant.go @@ -2,12 +2,13 @@ package entity import ( "gorm.io/gorm" + "time" ) // Grant Grant是授权给实例的,给予实例访问权限 type Grant struct { gorm.Model `json:"model"` - Token string `gorm:"unique;not null" json:"token,omitempty"` - TTL int `gorm:"not null;default:0" json:"ttl,omitempty"` - GrantTo uint `gorm:"not null;default:0;comment:instances(id) 授权给实例,0表示无指定(所有)" json:"grant_to,omitempty"` + Token string `gorm:"unique;not null" json:"token"` + ExpireAt time.Time `gorm:"index" json:"expire_at"` + GrantTo string `gorm:"not null;default:'';comment:instances(name)授权给实例,空表示无指定(所有)" json:"grant_to"` } diff --git a/pkg/dao/entity/instance.go b/pkg/dao/entity/instance.go index f2e6feb..d21448a 100644 --- a/pkg/dao/entity/instance.go +++ b/pkg/dao/entity/instance.go @@ -6,6 +6,6 @@ import ( type Instance struct { gorm.Model `json:"model"` - Name string `gorm:"unique;not null" json:"name,omitempty"` - UpdateURL string `gorm:"column:update_url;not null;default:'';comment:更新URL,未指定使用默认" json:"update_url,omitempty"` + Name string `gorm:"unique;not null" json:"name"` + UpdateURL string `gorm:"column:update_url;not null;default:'';comment:更新URL,未指定使用默认" json:"update_url"` } diff --git a/pkg/dao/entity/metadata.go b/pkg/dao/entity/metadata.go new file mode 100644 index 0000000..87a6fe4 --- /dev/null +++ b/pkg/dao/entity/metadata.go @@ -0,0 +1,7 @@ +package entity + +type Metadata struct { + ID uint `json:"id"` + Key string `gorm:"unique;not null" json:"key"` + Value string `gorm:"not null;default:''" json:"value"` +} diff --git a/pkg/dao/entity/token.go b/pkg/dao/entity/token.go index 4e01e04..00fa454 100644 --- a/pkg/dao/entity/token.go +++ b/pkg/dao/entity/token.go @@ -5,7 +5,7 @@ import "gorm.io/gorm" // Token Token是授权给用户的,给予用户登录权限 type Token struct { gorm.Model `json:"model"` - Token string `gorm:"unique;not null" json:"token,omitempty"` - GrantTo string `gorm:"index;not null;default:''" json:"grant_to,omitempty"` - TTL int `gorm:"not null;default:0" json:"ttl,omitempty"` + Token string `gorm:"unique;not null" json:"token"` + GrantTo string `gorm:"index;not null;default:''" json:"grant_to"` + TTL int `gorm:"not null;default:0" json:"ttl"` } diff --git a/pkg/dao/entity/update.go b/pkg/dao/entity/update.go index 6ba74ea..ac4414c 100644 --- a/pkg/dao/entity/update.go +++ b/pkg/dao/entity/update.go @@ -4,7 +4,7 @@ import "gorm.io/gorm" type Update struct { gorm.Model `json:"model"` - HashID string `gorm:"index;not null" json:"hash_id,omitempty"` - Comment string `gorm:"not null;default:'';comment:更新内容或注释" json:"comment,omitempty"` - Changes string `gorm:"not null;comment:更改的文件列表,逗号分隔,引用files(hash_id)" json:"changes,omitempty"` + HashID string `gorm:"index;not null" json:"hash_id"` + Comment string `gorm:"not null;default:'';comment:更新内容或注释" json:"comment"` + Changes string `gorm:"not null;comment:更改的文件列表,逗号分隔,引用files(hash_id)" json:"changes"` } diff --git a/pkg/dao/entity/user.go b/pkg/dao/entity/user.go index 968a1d6..0f12d44 100644 --- a/pkg/dao/entity/user.go +++ b/pkg/dao/entity/user.go @@ -4,7 +4,7 @@ import "gorm.io/gorm" type User struct { gorm.Model `json:"model"` - Username string `gorm:"unique;not null" json:"username,omitempty"` - Password string `gorm:"not null" json:"password,omitempty"` - Roles string `gorm:"not null;default:''" json:"roles,omitempty"` + Username string `gorm:"unique;not null" json:"username"` + Password string `gorm:"not null" json:"password"` + Roles string `gorm:"not null;default:''" json:"roles"` } diff --git a/pkg/param/authorize.go b/pkg/param/authorize.go deleted file mode 100644 index 33a5273..0000000 --- a/pkg/param/authorize.go +++ /dev/null @@ -1,10 +0,0 @@ -package param - -type AuthorizeQueryParam struct { - ClientId uint `form:"client_id" binding:"required"` - ResponseType string `form:"response_type" binding:"required"` - State string `form:"state" binding:"required"` - Scope string `form:"scope" binding:"required"` - CodeChallenge string `form:"code_challenge" binding:"required"` - CodeChallengeMethod string `form:"code_challenge_method" binding:"required"` -} diff --git a/pkg/param/instance.go b/pkg/param/instance.go new file mode 100644 index 0000000..df0acb4 --- /dev/null +++ b/pkg/param/instance.go @@ -0,0 +1,12 @@ +package param + +type AddInstanceParam struct { + Name string `json:"name" binding:"required"` + UpdateURL string `json:"update_url"` +} + +type NewGrantTokenParam struct { + Target string `json:"target" binding:"required"` + TTL int `json:"ttl"` + ExpireAt string `json:"expire_at"` +} diff --git a/pkg/param/login.go b/pkg/param/login.go index de246ad..cc848f7 100644 --- a/pkg/param/login.go +++ b/pkg/param/login.go @@ -1,6 +1,6 @@ package param type LoginParam struct { - Username string `json:"username"` - Password string `json:"password"` + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` } diff --git a/pkg/result/base.go b/pkg/result/result.go similarity index 77% rename from pkg/result/base.go rename to pkg/result/result.go index b0f53e0..950e50d 100644 --- a/pkg/result/base.go +++ b/pkg/result/result.go @@ -37,7 +37,7 @@ func (r *Result) Fail(code int, msg string) { res := Root{ Code: code, Msg: msg, - Data: gin.H{}, + Data: nil, } r.ctx.JSON(http.StatusOK, res) r.ctx.Abort() @@ -57,14 +57,22 @@ func (r *Result) BadRequest() { r.Fail(http.StatusBadRequest, "请求参数错误") } +func (r *Result) BadRequestWithMsg(msg string) { + r.Fail(http.StatusBadRequest, msg) +} + func (r *Result) LoginError() { r.Fail(common.LoginErrorCode, "账号或密码错误") } -func (r *Result) Unauthorized() { +func (r *Result) UnLogin() { r.Fail(http.StatusUnauthorized, "未登录") } +func (r *Result) Unauthorized() { + r.Fail(http.StatusUnauthorized, "未授权") +} + func (r *Result) NoPermission() { r.Fail(common.NoPermission, "权限不足") } @@ -72,3 +80,11 @@ func (r *Result) NoPermission() { func (r *Result) LoginExpired() { r.Fail(common.LoginExpired, "登录过期") } + +func (r *Result) DuplicatedValue(msg string) { + r.Fail(common.DuplicatedValue, msg) +} + +func (r *Result) InvalidInstance(instName string) { + r.Fail(common.InvalidInstance, "指定的实例不存在:"+instName) +} diff --git a/pkg/util/time.go b/pkg/util/time.go new file mode 100644 index 0000000..fb662d4 --- /dev/null +++ b/pkg/util/time.go @@ -0,0 +1,23 @@ +package util + +import "time" + +func ToSQLTimeFormat(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +func MustParseSQLTime(timeStr string) time.Time { + timeObj, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) + if err != nil { + panic(err) + } + return timeObj +} + +func IsSQLTimeFormat(timeStr string) bool { + _, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local) + if err != nil { + return false + } + return true +} diff --git a/pkg/util/token.go b/pkg/util/token.go deleted file mode 100644 index f013a34..0000000 --- a/pkg/util/token.go +++ /dev/null @@ -1,5 +0,0 @@ -package util - -func GenSessionID() string { - return RandStr(32) -}