添加token颁发器

This commit is contained in:
yumaojun03 2025-05-31 16:14:34 +08:00
parent 426e493967
commit 47e4772618
28 changed files with 889 additions and 8 deletions

7
devcloud/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"go.testEnvVars": {
"workspaceFolder": "${workspaceFolder}",
"CONFIG_PATH": "${workspaceFolder}/etc/application.toml"
},
"go.testEnvFile": "${workspaceFolder}/etc/unit_test.env"
}

View File

View File

@ -1 +1,12 @@
package apps
import (
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/user/api"
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/user/impl"
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/api"
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/impl"
// 颁发器
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers"
)

View File

@ -0,0 +1 @@
package api

View File

@ -0,0 +1 @@
package api

View File

@ -0,0 +1,21 @@
package token
import "github.com/infraboard/mcube/v2/exception"
const (
ACCESS_TOKEN_HEADER_NAME = "Authorization"
ACCESS_TOKEN_COOKIE_NAME = "access_token"
ACCESS_TOKEN_RESPONSE_HEADER_NAME = "X-OAUTH-TOKEN"
REFRESH_TOKEN_HEADER_NAME = "X-REFRUSH-TOKEN"
)
// 自定义非导出类型,避免外部包直接实例化
type tokenContextKey struct{}
var (
CTX_TOKEN_KEY = tokenContextKey{}
)
var (
CookieNotFound = exception.NewUnauthorized("cookie %s not found", ACCESS_TOKEN_COOKIE_NAME)
)

View File

@ -29,3 +29,9 @@ const (
// 异常Ip登陆
LOCK_TYPE_OTHER_IP_LOGGED_IN
)
type DESCRIBE_BY int
const (
DESCRIBE_BY_ACCESS_TOKEN DESCRIBE_BY = iota
)

View File

@ -1 +1,56 @@
package impl
import (
"time"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/datasource"
"github.com/infraboard/mcube/v2/ioc/config/log"
"github.com/rs/zerolog"
)
func init() {
ioc.Controller().Registry(&TokenServiceImpl{
AutoRefresh: true,
RereshTTLSecond: 1 * 60 * 60,
})
}
var _ token.Service = (*TokenServiceImpl)(nil)
type TokenServiceImpl struct {
ioc.ObjectImpl
user user.Service
log *zerolog.Logger
// policy policy.PermissionService
// 自动刷新, 直接刷新Token的过期时间而不是生成一个新Token
AutoRefresh bool `json:"auto_refresh" toml:"auto_refresh" yaml:"auto_refresh" env:"AUTO_REFRESH"`
// 刷新TTL
RereshTTLSecond uint64 `json:"refresh_ttl" toml:"refresh_ttl" yaml:"refresh_ttl" env:"REFRESH_TTL"`
// Api最多多少个, 这种Token往往过期时间比较长, 为了安全不要申请太多
MaxActiveApiToken uint8 `json:"max_active_api_token" toml:"max_active_api_token" yaml:"max_active_api_token" env:"MAX_ACTIVE_API_TOKEN"`
refreshDuration time.Duration
}
func (i *TokenServiceImpl) Init() error {
i.log = log.Sub(i.Name())
i.user = user.GetService()
// i.policy = policy.GetService()
i.refreshDuration = time.Duration(i.RereshTTLSecond) * time.Second
if datasource.Get().AutoMigrate {
err := datasource.DB().AutoMigrate(&token.Token{})
if err != nil {
return err
}
}
return nil
}
func (i *TokenServiceImpl) Name() string {
return token.APP_NAME
}

View File

@ -0,0 +1,18 @@
package impl_test
import (
"context"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
)
var (
svc token.Service
ctx = context.Background()
)
func init() {
test.DevelopmentSet()
svc = token.GetService()
}

View File

@ -1 +1,201 @@
package impl
import (
"context"
"time"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"github.com/infraboard/mcube/v2/desense"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/ioc/config/datasource"
"github.com/infraboard/mcube/v2/types"
)
// 登录接口(颁发Token)
func (i *TokenServiceImpl) IssueToken(ctx context.Context, in *token.IssueTokenRequest) (*token.Token, error) {
// 颁发Token
// user/password
// ldap
// 飞书,企业微信 ...
issuer := token.GetIssuer(in.Issuer)
if issuer == nil {
return nil, exception.NewBadRequest("issuer %s no support", in.Issuer)
}
tk, err := issuer.IssueToken(ctx, in.Parameter)
if err != nil {
return nil, err
}
tk.SetIssuer(in.Issuer).SetSource(in.Source)
// 判断当前数据库有没有已经存在的Token
activeTokenQueryReq := token.NewQueryTokenRequest().
AddUserId(tk.UserId).
SetSource(in.Source).
SetActive(true)
tks, err := i.QueryToken(ctx, activeTokenQueryReq)
if err != nil {
return nil, err
}
switch in.Source {
// 每个端只能有1个活跃登录
case token.SOURCE_WEB, token.SOURCE_IOS, token.SOURCE_ANDROID, token.SOURCE_PC:
if tks.Len() > 0 {
i.log.Debug().Msgf("use exist active token: %s", desense.Default().DeSense(tk.AccessToken, "4", "3"))
return tks.Items[0], nil
}
case token.SOURCE_API:
if tks.Len() > int(i.MaxActiveApiToken) {
return nil, exception.NewBadRequest("max active api token overflow")
}
}
// 保持Token
if err := datasource.DBFromCtx(ctx).
Create(tk).
Error; err != nil {
return nil, err
}
return tk, nil
}
// 校验Token 是给内部中间层使用 身份校验层
func (i *TokenServiceImpl) ValiateToken(ctx context.Context, req *token.ValiateTokenRequest) (*token.Token, error) {
// 1. 查询Token (是不是我们这个系统颁发的)
tk := token.NewToken()
err := datasource.DBFromCtx(ctx).
Where("access_token = ?", req.AccessToken).
First(tk).
Error
if err != nil {
return nil, err
}
// 2.1 判断Ak是否过期
if err := tk.IsAccessTokenExpired(); err != nil {
// 判断刷新Token是否过期
if err := tk.IsRreshTokenExpired(); err != nil {
return nil, err
}
// 如果开启了自动刷新
if i.AutoRefresh {
tk.SetRefreshAt(time.Now())
tk.SetExpiredAtByDuration(i.refreshDuration, 4)
if err := datasource.DBFromCtx(ctx).Save(tk); err != nil {
i.log.Error().Msgf("auto refresh token error, %s", err.Error)
}
}
return nil, err
}
return tk, nil
}
func (i *TokenServiceImpl) DescribeToken(ctx context.Context, in *token.DescribeTokenRequest) (*token.Token, error) {
query := datasource.DBFromCtx(ctx)
switch in.DescribeBy {
case token.DESCRIBE_BY_ACCESS_TOKEN:
query = query.Where("access_token = ?", in.DescribeValue)
default:
return nil, exception.NewBadRequest("unspport describe type %s", in.DescribeValue)
}
tk := token.NewToken()
if err := query.First(tk).Error; err != nil {
return nil, err
}
return tk, nil
}
// 退出接口(销毁Token)
func (i *TokenServiceImpl) RevolkToken(ctx context.Context, in *token.RevolkTokenRequest) (*token.Token, error) {
tk, err := i.DescribeToken(ctx, token.NewDescribeTokenRequest(in.AccessToken))
if err != nil {
return nil, err
}
if err := tk.CheckRefreshToken(in.RefreshToken); err != nil {
return nil, err
}
tk.Lock(token.LOCK_TYPE_REVOLK, "user revolk token")
err = datasource.DBFromCtx(ctx).Model(&token.Token{}).
Where("access_token = ?", in.AccessToken).
Where("refresh_token = ?", in.RefreshToken).
Updates(tk.Status.ToMap()).
Error
if err != nil {
return nil, err
}
return tk, err
}
// 查询已经颁发出去的Token
func (i *TokenServiceImpl) QueryToken(ctx context.Context, in *token.QueryTokenRequest) (*types.Set[*token.Token], error) {
set := types.New[*token.Token]()
query := datasource.DBFromCtx(ctx).Model(&token.Token{})
if in.Active != nil {
if *in.Active {
query = query.
Where("lock_at IS NULL AND refresh_token_expired_at > ?", time.Now())
} else {
query = query.
Where("lock_at IS NOT NULL OR refresh_token_expired_at <= ?", time.Now())
}
}
if in.Source != nil {
query = query.Where("source = ?", *in.Source)
}
if len(in.UserIds) > 0 {
query = query.Where("user_id IN ?", in.UserIds)
}
// 查询总量
err := query.Count(&set.Total).Error
if err != nil {
return nil, err
}
err = query.
Order("issue_at desc").
Offset(int(in.ComputeOffset())).
Limit(int(in.PageSize)).
Find(&set.Items).
Error
if err != nil {
return nil, err
}
return set, nil
}
// 用户切换空间
// func (i *TokenServiceImpl) ChangeNamespce(ctx context.Context, in *token.ChangeNamespceRequest) (*token.Token, error) {
// set, err := i.policy.QueryNamespace(ctx, policy.NewQueryNamespaceRequest().SetUserId(in.UserId).SetNamespaceId(in.NamespaceId))
// if err != nil {
// return nil, err
// }
// ns := set.First()
// if ns == nil {
// return nil, exception.NewPermissionDeny("你没有该空间访问权限")
// }
// // 更新Token
// tk, err := i.DescribeToken(ctx, token.NewDescribeTokenRequest(in.AccessToken))
// if err != nil {
// return nil, err
// }
// tk.NamespaceId = ns.Id
// tk.NamespaceName = ns.Name
// // 保存状态
// if err := datasource.DBFromCtx(ctx).
// Updates(tk).
// Error; err != nil {
// return nil, err
// }
// return tk, nil
// }

View File

@ -0,0 +1,27 @@
package impl_test
import (
"testing"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
)
func TestIssueToken(t *testing.T) {
req := token.NewIssueTokenRequest()
req.IssueByPassword("admin", "123456")
req.Source = token.SOURCE_WEB
set, err := svc.IssueToken(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(set)
}
func TestQueryToken(t *testing.T) {
req := token.NewQueryTokenRequest()
set, err := svc.QueryToken(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(set)
}

View File

@ -3,18 +3,84 @@ package token
import (
"context"
"time"
"github.com/infraboard/mcube/v2/http/request"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/types"
)
const (
APP_NAME = "token"
)
func GetService() Service {
return ioc.Controller().Get(APP_NAME).(Service)
}
type Service interface {
// 颁发访问令牌: Login
IssueToken(context.Context, *IssueTokenRequest) (*Token, error)
// 撤销访问令牌: 令牌失效了 Logout
RevolkToken(context.Context, *RevolkTokenRequest) (*Token, error)
// 查询已经颁发出去的Token
QueryToken(context.Context, *QueryTokenRequest) (*types.Set[*Token], error)
// 查询Token详情
DescribeToken(context.Context, *DescribeTokenRequest) (*Token, error)
// 校验访问令牌:检查令牌的合法性, 是不是伪造的
ValiateToken(context.Context, *ValiateTokenRequest) (*Token, error)
}
func NewDescribeTokenRequest(accessToken string) *DescribeTokenRequest {
return &DescribeTokenRequest{
DescribeBy: DESCRIBE_BY_ACCESS_TOKEN,
DescribeValue: accessToken,
}
}
type DescribeTokenRequest struct {
DescribeBy DESCRIBE_BY `json:"describe_by"`
DescribeValue string `json:"describe_value"`
}
func NewQueryTokenRequest() *QueryTokenRequest {
return &QueryTokenRequest{
PageRequest: request.NewDefaultPageRequest(),
UserIds: []uint64{},
}
}
type QueryTokenRequest struct {
*request.PageRequest
// 当前可用的没过期的Token
Active *bool `json:"active"`
// 用户来源
Source *SOURCE `json:"source"`
// Uids
UserIds []uint64 `json:"user_ids"`
}
func (r *QueryTokenRequest) SetActive(v bool) *QueryTokenRequest {
r.Active = &v
return r
}
func (r *QueryTokenRequest) SetSource(v SOURCE) *QueryTokenRequest {
r.Source = &v
return r
}
func (r *QueryTokenRequest) AddUserId(uids ...uint64) *QueryTokenRequest {
r.UserIds = append(r.UserIds, uids...)
return r
}
func NewIssueTokenRequest() *IssueTokenRequest {
return &IssueTokenRequest{
Parameter: make(IssueParameter),
}
}
// 用户会给我们 用户的身份凭证用于换取Token
type IssueTokenRequest struct {
// 端类型
@ -25,6 +91,16 @@ type IssueTokenRequest struct {
Parameter IssueParameter `json:"parameter"`
}
func (i *IssueTokenRequest) IssueByPassword(username, password string) {
i.Issuer = ISSUER_PASSWORD
i.Parameter.SetUsername(username)
i.Parameter.SetPassword(password)
}
func NewIssueParameter() IssueParameter {
return make(IssueParameter)
}
type IssueParameter map[string]any
/*
@ -39,12 +115,14 @@ func (p IssueParameter) Password() string {
return GetIssueParameterValue[string](p, "password")
}
func (p IssueParameter) SetUsername(v string) {
func (p IssueParameter) SetUsername(v string) IssueParameter {
p["username"] = v
return p
}
func (p IssueParameter) SetPassword(v string) {
func (p IssueParameter) SetPassword(v string) IssueParameter {
p["password"] = v
return p
}
/*
@ -59,8 +137,14 @@ func (p IssueParameter) ExpireTTL() time.Duration {
return time.Second * time.Duration(GetIssueParameterValue[int64](p, "expired_ttl"))
}
func (p IssueParameter) SetAccessToken(v string) {
func (p IssueParameter) SetAccessToken(v string) IssueParameter {
p["access_token"] = v
return p
}
func (p IssueParameter) SetExpireTTL(v int64) IssueParameter {
p["expired_ttl"] = v
return p
}
func NewRevolkTokenRequest(at, rk string) *RevolkTokenRequest {

View File

@ -0,0 +1,44 @@
package token
import (
"context"
"fmt"
"math/rand/v2"
)
const (
ISSUER_LDAP = "ldap"
ISSUER_FEISHU = "feishu"
ISSUER_PASSWORD = "password"
ISSUER_PRIVATE_TOKEN = "private_token"
)
var issuers = map[string]Issuer{}
func RegistryIssuer(name string, p Issuer) {
issuers[name] = p
}
func GetIssuer(name string) Issuer {
fmt.Println(issuers)
return issuers[name]
}
type Issuer interface {
IssueToken(context.Context, IssueParameter) (*Token, error)
}
var (
charlist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
// MakeBearer https://tools.ietf.org/html/rfc6750#section-2.1
// b64token = 1*( ALPHA / DIGIT /"-" / "." / "_" / "~" / "+" / "/" ) *"="
func MakeBearer(lenth int) string {
t := make([]byte, 0)
for range lenth {
rn := rand.IntN(len(charlist))
t = append(t, charlist[rn])
}
return string(t)
}

View File

@ -0,0 +1,12 @@
package token_test
import (
"testing"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
)
func TestMakeBearer(t *testing.T) {
t.Log(token.MakeBearer(24))
t.Log(token.MakeBearer(24))
}

View File

@ -0,0 +1,67 @@
package password
import (
"context"
"time"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/ioc"
)
func init() {
ioc.Config().Registry(&PasswordTokenIssuer{
ExpiredTTLSecond: 1 * 60 * 60,
})
}
type PasswordTokenIssuer struct {
ioc.ObjectImpl
// 通过用户模块 来判断用户凭证是否正确
user user.Service
// Password颁发的Token 过去时间由系统配置, 不允许用户自己设置
ExpiredTTLSecond int `json:"expired_ttl_second" toml:"expired_ttl_second" yaml:"expired_ttl_second" env:"EXPIRED_TTL_SECOND"`
expiredDuration time.Duration
}
func (p *PasswordTokenIssuer) Name() string {
return "password_token_issuer"
}
func (p *PasswordTokenIssuer) Init() error {
p.user = user.GetService()
p.expiredDuration = time.Duration(p.ExpiredTTLSecond) * time.Second
token.RegistryIssuer(token.ISSUER_PASSWORD, p)
return nil
}
func (p *PasswordTokenIssuer) IssueToken(ctx context.Context, parameter token.IssueParameter) (*token.Token, error) {
// 1. 查询用户
uReq := user.NewDescribeUserRequestByUserName(parameter.Username())
u, err := p.user.DescribeUser(ctx, uReq)
if err != nil {
if exception.IsNotFoundError(err) {
return nil, exception.NewUnauthorized("%s", err)
}
return nil, err
}
// 2. 比对密码
err = u.CheckPassword(parameter.Password())
if err != nil {
return nil, err
}
// 3. 颁发token
tk := token.NewToken()
tk.UserId = u.Id
tk.UserName = u.UserName
tk.IsAdmin = u.IsAdmin
tk.SetExpiredAtByDuration(p.expiredDuration, 4)
return tk, nil
}

View File

@ -0,0 +1,22 @@
package password_test
import (
"context"
"testing"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
)
func TestPasswordIssuer(t *testing.T) {
issuer := token.GetIssuer(token.ISSUER_PASSWORD)
tk, err := issuer.IssueToken(context.Background(), token.NewIssueParameter().SetUsername("admin").SetPassword("123456"))
if err != nil {
t.Fatal(err)
}
t.Log(tk)
}
func init() {
test.DevelopmentSet()
}

View File

@ -0,0 +1,22 @@
package privatetoken_test
import (
"context"
"testing"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
)
func TestPasswordIssuer(t *testing.T) {
issuer := token.GetIssuer(token.ISSUER_PRIVATE_TOKEN)
tk, err := issuer.IssueToken(context.Background(), token.NewIssueParameter().SetAccessToken("LccvuTwISJRheu8PtqAFTJBy").SetExpireTTL(24*60*60))
if err != nil {
t.Fatal(err)
}
t.Log(tk)
}
func init() {
test.DevelopmentSet()
}

View File

@ -0,0 +1,67 @@
package privatetoken
import (
"context"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/ioc"
)
func init() {
ioc.Config().Registry(&PrivateTokenIssuer{})
}
type PrivateTokenIssuer struct {
ioc.ObjectImpl
user user.Service
token token.Service
}
func (p *PrivateTokenIssuer) Name() string {
return "private_token_issuer"
}
func (p *PrivateTokenIssuer) Init() error {
p.user = user.GetService()
p.token = token.GetService()
token.RegistryIssuer(token.ISSUER_PRIVATE_TOKEN, p)
return nil
}
func (p *PrivateTokenIssuer) IssueToken(ctx context.Context, parameter token.IssueParameter) (*token.Token, error) {
// 1. 校验Token合法
oldTk, err := p.token.ValiateToken(ctx, token.NewValiateTokenRequest(parameter.AccessToken()))
if err != nil {
return nil, err
}
// 2. 查询用户
uReq := user.NewDescribeUserRequestById(oldTk.UserIdString())
u, err := p.user.DescribeUser(ctx, uReq)
if err != nil {
if exception.IsNotFoundError(err) {
return nil, exception.NewUnauthorized("%s", err)
}
return nil, err
}
if !u.EnabledApi {
return nil, exception.NewPermissionDeny("未开启接口登录")
}
// 3. 颁发token
tk := token.NewToken()
tk.UserId = u.Id
tk.UserName = u.UserName
tk.IsAdmin = u.IsAdmin
expiredTTL := parameter.ExpireTTL()
if expiredTTL > 0 {
tk.SetExpiredAtByDuration(expiredTTL, 4)
}
return tk, nil
}

View File

@ -0,0 +1,6 @@
package issuers
import (
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers/password"
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps/token/issuers/private_token"
)

View File

@ -1,13 +1,65 @@
package token
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/tools/pretty"
)
func GetAccessTokenFromHTTP(r *http.Request) string {
// 先从Token中获取
tk := r.Header.Get(ACCESS_TOKEN_HEADER_NAME)
// 1. 获取Token
if tk == "" {
cookie, err := r.Cookie(ACCESS_TOKEN_COOKIE_NAME)
if err != nil {
return ""
}
tk, _ = url.QueryUnescape(cookie.Value)
} else {
// 处理 带格式: Bearer <Your API key>
ft := strings.Split(tk, " ")
if len(ft) > 1 {
tk = ft[1]
}
}
return tk
}
func GetTokenFromCtx(ctx context.Context) *Token {
if v := ctx.Value(CTX_TOKEN_KEY); v != nil {
return v.(*Token)
}
return nil
}
func GetRefreshTokenFromHTTP(r *http.Request) string {
// 先从Token中获取
tk := r.Header.Get(REFRESH_TOKEN_HEADER_NAME)
return tk
}
func NewToken() *Token {
tk := &Token{
// 生产一个UUID的字符串
AccessToken: MakeBearer(24),
RefreshToken: MakeBearer(32),
IssueAt: time.Now(),
Status: NewStatus(),
Extras: map[string]string{},
Scope: map[string]string{},
}
return tk
}
// 需要存储到数据库里面的对象(表)
type Token struct {

View File

@ -43,4 +43,25 @@ password + 随机字符串(salt) --> (salt)hash它也是随机
输入: password + 随机字符串(salt: 原来hash中的sal部分) == hash它也是随机(数据库)
```go
import (
"golang.org/x/crypto/bcrypt"
)
func (req *CreateUserRequest) PasswordHash() {
if req.isHashed {
return
}
b, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
req.Password = string(b)
req.isHashed = true
}
// 判断该用户的密码是否正确
func (u *User) CheckPassword(password string) error {
// u.Password hash过后的只
// (password 原始值 + hash值中提区salt)
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
}
```

View File

@ -0,0 +1 @@
package api

View File

@ -1 +1,18 @@
package impl_test
import (
"context"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"122.51.31.227/go-course/go18/devcloud/mcenter/test"
)
var (
impl user.Service
ctx = context.Background()
)
func init() {
test.DevelopmentSet()
impl = user.GetService()
}

View File

@ -1 +1,91 @@
package impl_test
import (
"testing"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
)
func TestQueryUser(t *testing.T) {
req := user.NewQueryUserRequest()
set, err := impl.QueryUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(set)
}
func TestCreateAdminUser(t *testing.T) {
req := user.NewCreateUserRequest()
req.UserName = "admin"
req.Password = "123456"
req.EnabledApi = true
req.IsAdmin = true
u, err := impl.CreateUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(u)
}
func TestCreateAuthor2(t *testing.T) {
req := user.NewCreateUserRequest()
req.UserName = "张三"
req.Password = "123456"
req.EnabledApi = true
u, err := impl.CreateUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(u)
}
func TestCreateGuestUser(t *testing.T) {
req := user.NewCreateUserRequest()
req.UserName = "guest"
req.Password = "123456"
req.EnabledApi = true
u, err := impl.CreateUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(u)
}
func TestDeleteUser(t *testing.T) {
_, err := impl.DeleteUser(ctx, &user.DeleteUserRequest{
Id: "9",
})
if err != nil {
t.Fatal(err)
}
}
func TestDescribeUserRequestById(t *testing.T) {
req := user.NewDescribeUserRequestById("2")
ins, err := impl.DescribeUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(ins)
}
// SELECT * FROM `users` WHERE username = 'admin' ORDER BY `users`.`id` LIMIT 1
func TestDescribeUserRequestByName(t *testing.T) {
req := user.NewDescribeUserRequestByUserName("admin")
ins, err := impl.DescribeUser(ctx, req)
if err != nil {
t.Fatal(err)
}
t.Log(ins)
err = ins.CheckPassword("1234561")
if err != nil {
t.Fatal(err)
}
}
func TestUserJson(t *testing.T) {
u := user.NewUser(user.NewCreateUserRequest())
t.Log(u)
}

View File

@ -1,10 +1,11 @@
package user
import (
"encoding/json"
"fmt"
"time"
"github.com/infraboard/mcube/v2/exception"
"github.com/infraboard/mcube/v2/tools/pretty"
"github.com/infraboard/modules/iam/apps"
"golang.org/x/crypto/bcrypt"
)
@ -30,13 +31,18 @@ type User struct {
}
func (u *User) String() string {
dj, _ := json.Marshal(u)
return string(dj)
return pretty.ToJSON(u)
}
// 判断该用户的密码是否正确
func (u *User) CheckPassword(password string) error {
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
// u.Password hash过后的只
// (password 原始值 + hash值中提区salt)
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil {
return exception.NewUnauthorized("用户名或者密码对正确")
}
return nil
}
// 声明你这个对象存储在users表里面

View File

@ -0,0 +1,17 @@
package test
import (
"os"
"github.com/infraboard/mcube/v2/ioc"
// 要注册哪些对象, Book, Comment
// 加载的业务对象
_ "122.51.31.227/go-course/go18/devcloud/mcenter/apps"
)
func DevelopmentSet() {
// import 后自动执行的逻辑
// 工具对象的初始化, 需要的是绝对路径
ioc.DevelopmentSetupWithPath(os.Getenv("CONFIG_PATH"))
}

4
go.mod
View File

@ -6,8 +6,11 @@ require (
github.com/caarlos0/env/v6 v6.10.1
github.com/gin-gonic/gin v1.10.0
github.com/infraboard/mcube/v2 v2.0.59
github.com/infraboard/modules v0.0.12
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.10.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.26.0
@ -83,7 +86,6 @@ require (
go.opentelemetry.io/otel/trace v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect

4
go.sum
View File

@ -75,6 +75,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/infraboard/mcube/v2 v2.0.59 h1:NONiCPjN6xlbGCJx8+e+ZYZfXV58JByEMlzQ6ZZ+pXk=
github.com/infraboard/mcube/v2 v2.0.59/go.mod h1:TbYs8cnD8Cg19sTdU0D+vqWAN+LzoxhMYWmAC2pfJkQ=
github.com/infraboard/modules v0.0.12 h1:vQqm+JwzmhL+hcD9SV+WVlp9ecInc7NsbGahcTmJ0Wk=
github.com/infraboard/modules v0.0.12/go.mod h1:NdgdH/NoeqibJmFPn9th+tisMuR862/crbXeH4FPMaU=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
@ -197,6 +199,8 @@ golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=