补充hash
This commit is contained in:
parent
cc4729de15
commit
426e493967
20
devcloud/etc/application.toml
Normal file
20
devcloud/etc/application.toml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[app]
|
||||||
|
name = "devcloud"
|
||||||
|
description = "app desc"
|
||||||
|
address = "localhost"
|
||||||
|
encrypt_key = "defualt app encrypt key"
|
||||||
|
|
||||||
|
[datasource]
|
||||||
|
provider = "mysql"
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 3306
|
||||||
|
database = "devcloud_go18"
|
||||||
|
username = "root"
|
||||||
|
password = "123456"
|
||||||
|
auto_migrate = true
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[http]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 8080
|
||||||
|
path_prefix = "api"
|
@ -4,7 +4,7 @@
|
|||||||
+ 撤销访问令牌: 令牌失效了 Logout
|
+ 撤销访问令牌: 令牌失效了 Logout
|
||||||
+ 校验访问令牌:检查令牌的合法性, 是不是伪造的
|
+ 校验访问令牌:检查令牌的合法性, 是不是伪造的
|
||||||
|
|
||||||
## 详情设计的时候
|
## 详情设计
|
||||||
|
|
||||||
字段(业务需求)
|
字段(业务需求)
|
||||||
|
|
||||||
@ -18,3 +18,18 @@
|
|||||||
问题: 无刷新功能, 令牌到期了,自动退出了, 过期时间设置长一点, 长时间不过期 又有安全问题
|
问题: 无刷新功能, 令牌到期了,自动退出了, 过期时间设置长一点, 长时间不过期 又有安全问题
|
||||||
1. 业务功能: 令牌的刷新, 令牌过期了过后,允许用户进行刷新(需要使用刷新Token来刷新, 刷新Token也是需要有过期时间, 这个时间决定回话长度),有了刷新token用户不会出现 使用中被中断的情况, 并且长时间未使用,系统也户自动退出(刷新Token过期)
|
1. 业务功能: 令牌的刷新, 令牌过期了过后,允许用户进行刷新(需要使用刷新Token来刷新, 刷新Token也是需要有过期时间, 这个时间决定回话长度),有了刷新token用户不会出现 使用中被中断的情况, 并且长时间未使用,系统也户自动退出(刷新Token过期)
|
||||||
|
|
||||||
|
|
||||||
|
## 转化为接口定义
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Service interface {
|
||||||
|
// 颁发访问令牌: Login
|
||||||
|
IssueToken(context.Context, *IssueTokenRequest) (*Token, error)
|
||||||
|
// 撤销访问令牌: 令牌失效了 Logout
|
||||||
|
RevolkToken(context.Context, *RevolkTokenRequest) (*Token, error)
|
||||||
|
|
||||||
|
// 校验访问令牌:检查令牌的合法性, 是不是伪造的
|
||||||
|
ValiateToken(context.Context, *ValiateTokenRequest) (*Token, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="rJ2wD46cwpVMIQue_TYe" name="第 1 页">
|
||||||
|
<mxGraphModel dx="934" dy="434" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="2" value="user" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="40" y="250" width="220" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="9" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="3" target="2">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="token" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="9">
|
||||||
|
<mxGeometry x="-0.0681" y="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="token" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="380" y="250" width="200" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="4" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="460" y="10" width="350" height="140" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="5" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="570" y="40" width="120" height="20" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="570" y="80" width="120" height="20" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8" value="记住登录" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="720" y="100" width="50" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
1
devcloud/mcenter/apps/token/impl/impl.go
Normal file
1
devcloud/mcenter/apps/token/impl/impl.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package impl
|
1
devcloud/mcenter/apps/token/impl/token.go
Normal file
1
devcloud/mcenter/apps/token/impl/token.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package impl
|
@ -12,7 +12,7 @@ type Service interface {
|
|||||||
RevolkToken(context.Context, *RevolkTokenRequest) (*Token, error)
|
RevolkToken(context.Context, *RevolkTokenRequest) (*Token, error)
|
||||||
|
|
||||||
// 校验访问令牌:检查令牌的合法性, 是不是伪造的
|
// 校验访问令牌:检查令牌的合法性, 是不是伪造的
|
||||||
ValidateToken(context.Context, *ValidateTokenRequest) (*Token, error)
|
ValiateToken(context.Context, *ValiateTokenRequest) (*Token, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户会给我们 用户的身份凭证,用于换取Token
|
// 用户会给我们 用户的身份凭证,用于换取Token
|
||||||
@ -63,8 +63,25 @@ func (p IssueParameter) SetAccessToken(v string) {
|
|||||||
p["access_token"] = v
|
p["access_token"] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
type RevolkTokenRequest struct {
|
func NewRevolkTokenRequest(at, rk string) *RevolkTokenRequest {
|
||||||
|
return &RevolkTokenRequest{
|
||||||
|
AccessToken: at,
|
||||||
|
RefreshToken: rk,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidateTokenRequest struct {
|
// 万一的Token泄露, 不知道refresh_token,也没法推出
|
||||||
|
type RevolkTokenRequest struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewValiateTokenRequest(accessToken string) *ValiateTokenRequest {
|
||||||
|
return &ValiateTokenRequest{
|
||||||
|
AccessToken: accessToken,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValiateTokenRequest struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
|
"github.com/infraboard/mcube/v2/tools/pretty"
|
||||||
|
)
|
||||||
|
|
||||||
// 需要存储到数据库里面的对象(表)
|
// 需要存储到数据库里面的对象(表)
|
||||||
|
|
||||||
@ -41,6 +47,97 @@ type Token struct {
|
|||||||
Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息"`
|
Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" description:"其他扩展信息"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Token) TableName() string {
|
||||||
|
return "tokens"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断访问令牌是否过期,没设置代表用不过期
|
||||||
|
func (t *Token) IsAccessTokenExpired() error {
|
||||||
|
if t.AccessTokenExpiredAt != nil {
|
||||||
|
// now expiredTime
|
||||||
|
expiredSeconds := time.Since(*t.AccessTokenExpiredAt).Seconds()
|
||||||
|
if expiredSeconds > 0 {
|
||||||
|
return exception.NewAccessTokenExpired("access token %s 过期了 %f秒",
|
||||||
|
t.AccessToken, expiredSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断刷新Token是否过期
|
||||||
|
func (t *Token) IsRreshTokenExpired() error {
|
||||||
|
if t.RefreshTokenExpiredAt != nil {
|
||||||
|
expiredSeconds := time.Since(*t.RefreshTokenExpiredAt).Seconds()
|
||||||
|
if expiredSeconds > 0 {
|
||||||
|
return exception.NewRefreshTokenExpired("refresh token %s 过期了 %f秒",
|
||||||
|
t.RefreshToken, expiredSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新Token的过期时间 是一个系统配置, 刷新token的过期时间 > 访问token的时间
|
||||||
|
// 给一些默认设置: 刷新token的过期时间 = 访问token的时间 * 4
|
||||||
|
func (t *Token) SetExpiredAtByDuration(duration time.Duration, refreshMulti uint) {
|
||||||
|
t.SetAccessTokenExpiredAt(time.Now().Add(duration))
|
||||||
|
t.SetRefreshTokenExpiredAt(time.Now().Add(duration * time.Duration(refreshMulti)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) SetAccessTokenExpiredAt(v time.Time) {
|
||||||
|
t.AccessTokenExpiredAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) SetRefreshAt(v time.Time) {
|
||||||
|
t.RefreshAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) AccessTokenExpiredTTL() int {
|
||||||
|
if t.AccessTokenExpiredAt != nil {
|
||||||
|
return int(t.AccessTokenExpiredAt.Sub(t.IssueAt).Seconds())
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) SetRefreshTokenExpiredAt(v time.Time) {
|
||||||
|
t.RefreshTokenExpiredAt = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
return pretty.ToJSON(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) SetIssuer(issuer string) *Token {
|
||||||
|
t.Issuer = issuer
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) SetSource(source SOURCE) *Token {
|
||||||
|
t.Source = source
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) UserIdString() string {
|
||||||
|
return fmt.Sprintf("%d", t.UserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) CheckRefreshToken(refreshToken string) error {
|
||||||
|
if t.RefreshToken != refreshToken {
|
||||||
|
return exception.NewPermissionDeny("refresh token not conrect")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Token) Lock(l LOCK_TYPE, reason string) {
|
||||||
|
if t.Status == nil {
|
||||||
|
t.Status = NewStatus()
|
||||||
|
}
|
||||||
|
t.Status.LockType = l
|
||||||
|
t.Status.LockReason = reason
|
||||||
|
t.Status.SetLockAt(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
func NewStatus() *Status {
|
func NewStatus() *Status {
|
||||||
return &Status{}
|
return &Status{}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,46 @@
|
|||||||
# 用户管理
|
# 用户管理
|
||||||
|
|
||||||
|
+ 创建用户
|
||||||
|
+ 删除用户
|
||||||
|
+ 更新用户
|
||||||
|
+ 用户列表
|
||||||
|
+ 用户详情
|
||||||
|
+ 重置密码
|
||||||
|
|
||||||
|
## 详情设计
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 定义User包的能力 就是接口定义
|
||||||
|
// 站在使用放的角度来定义的 userSvc.Create(ctx, req), userSvc.DeleteUser(id)
|
||||||
|
// 接口定义好了,不要试图 随意修改接口, 要保证接口的兼容性
|
||||||
|
type Service interface {
|
||||||
|
// 创建用户
|
||||||
|
CreateUser(context.Context, *CreateUserRequest) (*User, error)
|
||||||
|
// 删除用户
|
||||||
|
DeleteUser(context.Context, *DeleteUserRequest) (*User, error)
|
||||||
|
// 查询用户详情
|
||||||
|
DescribeUser(context.Context, *DescribeUserRequest) (*User, error)
|
||||||
|
// 查询用户列表
|
||||||
|
QueryUser(context.Context, *QueryUserRequest) (*types.Set[*User], error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 业务功能
|
||||||
|
|
||||||
|
加解迷的方式有分类:
|
||||||
|
1. Hash (消息摘要): 单向Hash, 可以通过原文 获取摘要信息,但是无法通过摘要信息推断原文, 只要摘要信息相同,原文就相同: md5, sha*, bcrypt ...
|
||||||
|
2. 对称加解密: 加密和解密的秘密(key) 用于数据的加解密文
|
||||||
|
3. 非对称加解密: 加密(公钥)和解密(私用)不是用的同一个秘密, 用于密码的加解密
|
||||||
|
|
||||||
|
1. 用户密码怎么存储的问题, 存储用户密码的hash,避免直接存储用户密码。 11111 -> abcd, 可能导致 用户的秘密在其他平台泄露 知道了这个影视关系abcd --> 1111,
|
||||||
|
能不能有什么办法解决这个问题, 加盐: 相同密码 --> 每次hash会产生不同的结果
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
password --> hash
|
||||||
|
password + 随机字符串(salt) --> (salt)hash它也是随机
|
||||||
|
|
||||||
|
输入: password + 随机字符串(salt: 原来hash中的sal部分) == hash它也是随机(数据库)
|
||||||
|
|
||||||
|
|
||||||
|
48
devcloud/mcenter/apps/user/enum.go
Normal file
48
devcloud/mcenter/apps/user/enum.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
type PROVIDER int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 本地数据库
|
||||||
|
PROVIDER_LOCAL PROVIDER = 0
|
||||||
|
// 来源LDAP
|
||||||
|
PROVIDER_LDAP PROVIDER = 1
|
||||||
|
// 来源飞书
|
||||||
|
PROVIDER_FEISHU PROVIDER = 2
|
||||||
|
// 来源钉钉
|
||||||
|
PROVIDER_DINGDING PROVIDER = 3
|
||||||
|
// 来源企业微信
|
||||||
|
PROVIDER_WECHAT_WORK PROVIDER = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type CEATE_TYPE int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 系统初始化
|
||||||
|
CREATE_TYPE_INIT = iota
|
||||||
|
// 管理员创建
|
||||||
|
CREATE_TYPE_ADMIN
|
||||||
|
// 用户自己注册
|
||||||
|
CREATE_TYPE_REGISTRY
|
||||||
|
)
|
||||||
|
|
||||||
|
type TYPE int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
TYPE_SUB TYPE = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type SEX int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SEX_UNKNOWN = iota
|
||||||
|
SEX_MALE
|
||||||
|
SEX_FEMALE
|
||||||
|
)
|
||||||
|
|
||||||
|
type DESCRIBE_BY int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DESCRIBE_BY_ID DESCRIBE_BY = iota
|
||||||
|
DESCRIBE_BY_USERNAME
|
||||||
|
)
|
BIN
devcloud/mcenter/apps/user/image.png
Normal file
BIN
devcloud/mcenter/apps/user/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
34
devcloud/mcenter/apps/user/impl/impl.go
Normal file
34
devcloud/mcenter/apps/user/impl/impl.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ioc.Controller().Registry(&UserServiceImpl{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ user.Service = (*UserServiceImpl)(nil)
|
||||||
|
|
||||||
|
// 他是user service 服务的控制器
|
||||||
|
type UserServiceImpl struct {
|
||||||
|
ioc.ObjectImpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *UserServiceImpl) Init() error {
|
||||||
|
// 自动创建表
|
||||||
|
if datasource.Get().AutoMigrate {
|
||||||
|
err := datasource.DB().AutoMigrate(&user.User{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义托管到Ioc里面的名称
|
||||||
|
func (i *UserServiceImpl) Name() string {
|
||||||
|
return user.APP_NAME
|
||||||
|
}
|
1
devcloud/mcenter/apps/user/impl/impl_test.go
Normal file
1
devcloud/mcenter/apps/user/impl/impl_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package impl_test
|
109
devcloud/mcenter/apps/user/impl/user.go
Normal file
109
devcloud/mcenter/apps/user/impl/user.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package impl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
|
||||||
|
"github.com/infraboard/mcube/v2/exception"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc/config/datasource"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
func (i *UserServiceImpl) CreateUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *user.CreateUserRequest) (
|
||||||
|
*user.User, error) {
|
||||||
|
// 1. 校验用户参数
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 生成一个User对象(ORM对象)
|
||||||
|
ins := user.NewUser(req)
|
||||||
|
|
||||||
|
if err := datasource.DBFromCtx(ctx).
|
||||||
|
Create(ins).
|
||||||
|
Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 返回结果
|
||||||
|
return ins, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
func (i *UserServiceImpl) DeleteUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *user.DeleteUserRequest,
|
||||||
|
) (*user.User, error) {
|
||||||
|
u, err := i.DescribeUser(ctx,
|
||||||
|
user.NewDescribeUserRequestById(req.Id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return u, datasource.DBFromCtx(ctx).
|
||||||
|
Where("id = ?", req.Id).
|
||||||
|
Delete(&user.User{}).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户列表
|
||||||
|
func (i *UserServiceImpl) QueryUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *user.QueryUserRequest) (
|
||||||
|
*types.Set[*user.User], error) {
|
||||||
|
set := types.New[*user.User]()
|
||||||
|
|
||||||
|
query := datasource.DBFromCtx(ctx).Model(&user.User{})
|
||||||
|
|
||||||
|
// 查询总量
|
||||||
|
err := query.Count(&set.Total).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.
|
||||||
|
Order("created_at desc").
|
||||||
|
Offset(int(req.ComputeOffset())).
|
||||||
|
Limit(int(req.PageSize)).
|
||||||
|
Find(&set.Items).
|
||||||
|
Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return set, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户详情
|
||||||
|
func (i *UserServiceImpl) DescribeUser(
|
||||||
|
ctx context.Context,
|
||||||
|
req *user.DescribeUserRequest) (
|
||||||
|
*user.User, error) {
|
||||||
|
|
||||||
|
query := datasource.DBFromCtx(ctx)
|
||||||
|
|
||||||
|
// 1. 构造我们的查询条件
|
||||||
|
switch req.DescribeBy {
|
||||||
|
case user.DESCRIBE_BY_ID:
|
||||||
|
query = query.Where("id = ?", req.DescribeValue)
|
||||||
|
case user.DESCRIBE_BY_USERNAME:
|
||||||
|
query = query.Where("user_name = ?", req.DescribeValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SELECT * FROM `users` WHERE username = 'admin' ORDER BY `users`.`id` LIMIT 1
|
||||||
|
ins := &user.User{}
|
||||||
|
if err := query.First(ins).Error; err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, exception.NewNotFound("user %s not found", req.DescribeValue)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库里面存储的就是Hash
|
||||||
|
ins.SetIsHashed()
|
||||||
|
return ins, nil
|
||||||
|
}
|
1
devcloud/mcenter/apps/user/impl/user_test.go
Normal file
1
devcloud/mcenter/apps/user/impl/user_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package impl_test
|
83
devcloud/mcenter/apps/user/interface.go
Normal file
83
devcloud/mcenter/apps/user/interface.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/infraboard/mcube/v2/http/request"
|
||||||
|
"github.com/infraboard/mcube/v2/ioc"
|
||||||
|
"github.com/infraboard/mcube/v2/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_NAME = "user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetService() Service {
|
||||||
|
return ioc.Controller().Get(APP_NAME).(Service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义User包的能力 就是接口定义
|
||||||
|
// 站在使用放的角度来定义的 userSvc.Create(ctx, req), userSvc.DeleteUser(id)
|
||||||
|
// 接口定义好了,不要试图 随意修改接口, 要保证接口的兼容性
|
||||||
|
type Service interface {
|
||||||
|
// 创建用户
|
||||||
|
CreateUser(context.Context, *CreateUserRequest) (*User, error)
|
||||||
|
// 删除用户
|
||||||
|
DeleteUser(context.Context, *DeleteUserRequest) (*User, error)
|
||||||
|
// 查询用户详情
|
||||||
|
DescribeUser(context.Context, *DescribeUserRequest) (*User, error)
|
||||||
|
// 查询用户列表
|
||||||
|
QueryUser(context.Context, *QueryUserRequest) (*types.Set[*User], error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryUserRequest() *QueryUserRequest {
|
||||||
|
return &QueryUserRequest{
|
||||||
|
PageRequest: request.NewDefaultPageRequest(),
|
||||||
|
UserIds: []uint64{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueryUserRequest struct {
|
||||||
|
*request.PageRequest
|
||||||
|
UserIds []uint64 `form:"user" json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *QueryUserRequest) AddUser(userIds ...uint64) *QueryUserRequest {
|
||||||
|
for _, uid := range userIds {
|
||||||
|
if !slices.Contains(r.UserIds, uid) {
|
||||||
|
r.UserIds = append(r.UserIds, uid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDescribeUserRequestById(id string) *DescribeUserRequest {
|
||||||
|
return &DescribeUserRequest{
|
||||||
|
DescribeValue: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDescribeUserRequestByUserName(username string) *DescribeUserRequest {
|
||||||
|
return &DescribeUserRequest{
|
||||||
|
DescribeBy: DESCRIBE_BY_USERNAME,
|
||||||
|
DescribeValue: username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时支持通过Id来查询,也要支持通过username来查询
|
||||||
|
type DescribeUserRequest struct {
|
||||||
|
DescribeBy DESCRIBE_BY `json:"describe_by"`
|
||||||
|
DescribeValue string `json:"describe_value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteUserRequest(id string) *DeleteUserRequest {
|
||||||
|
return &DeleteUserRequest{
|
||||||
|
Id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户的请求
|
||||||
|
type DeleteUserRequest struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
124
devcloud/mcenter/apps/user/model.go
Normal file
124
devcloud/mcenter/apps/user/model.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/infraboard/modules/iam/apps"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewUser(req *CreateUserRequest) *User {
|
||||||
|
req.PasswordHash()
|
||||||
|
|
||||||
|
return &User{
|
||||||
|
ResourceMeta: *apps.NewResourceMeta(),
|
||||||
|
CreateUserRequest: *req,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于存放 存入数据库的对象(PO)
|
||||||
|
type User struct {
|
||||||
|
// 基础数据
|
||||||
|
apps.ResourceMeta
|
||||||
|
// 用户传递过来的请求
|
||||||
|
CreateUserRequest
|
||||||
|
|
||||||
|
// 密码强度
|
||||||
|
PwdIntensity int8 `json:"pwd_intensity" gorm:"column:pwd_intensity;type:tinyint(1);not null" optional:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) String() string {
|
||||||
|
dj, _ := json.Marshal(u)
|
||||||
|
return string(dj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断该用户的密码是否正确
|
||||||
|
func (u *User) CheckPassword(password string) error {
|
||||||
|
return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明你这个对象存储在users表里面
|
||||||
|
// orm 负责调用TableName() 来动态获取你这个对象要存储的表的名称
|
||||||
|
func (u *User) TableName() string {
|
||||||
|
return "users"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateUserRequest() *CreateUserRequest {
|
||||||
|
return &CreateUserRequest{
|
||||||
|
Extras: map[string]string{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateUserRequest struct {
|
||||||
|
// 账号提供方
|
||||||
|
Provider PROVIDER `json:"provider" gorm:"column:provider;type:tinyint(1);not null;index" description:"账号提供方"`
|
||||||
|
// 创建方式
|
||||||
|
CreateType CEATE_TYPE `json:"create_type" gorm:"column:create_type;type:tinyint(1);not null;index" optional:"true"`
|
||||||
|
// 用户名
|
||||||
|
UserName string `json:"user_name" gorm:"column:user_name;type:varchar(100);not null;uniqueIndex" description:"用户名"`
|
||||||
|
// 密码(Hash过后的)
|
||||||
|
Password string `json:"password" gorm:"column:password;type:varchar(200);not null" description:"用户密码"`
|
||||||
|
// 用户描述
|
||||||
|
Description string `json:"description" gorm:"column:description;type:varchar(200);not null" description:"用户描述"`
|
||||||
|
// 用户类型
|
||||||
|
Type TYPE `json:"type" gorm:"column:type;type:varchar(200);not null" description:"用户类型"`
|
||||||
|
// 用户描述
|
||||||
|
Domain string `json:"domain" gorm:"column:domain;type:varchar(200);" description:"用户所属域"`
|
||||||
|
|
||||||
|
// 支持接口调用
|
||||||
|
EnabledApi bool `json:"enabled_api" gorm:"column:enabled_api;type:tinyint(1)" optional:"true" description:"支持接口调用"`
|
||||||
|
// 是不是管理员
|
||||||
|
IsAdmin bool `json:"is_admin" gorm:"column:is_admin;type:tinyint(1)" optional:"true" description:"是不是管理员"`
|
||||||
|
// 用户状态,01:正常,02:冻结
|
||||||
|
Locked bool `json:"stat" gorm:"column:stat;type:tinyint(1)" optional:"true" description:"用户状态, 01:正常, 02:冻结"`
|
||||||
|
// 激活,1:激活,0:未激活
|
||||||
|
Activate bool `json:"activate" gorm:"column:activate;type:tinyint(1)" optional:"true" description:"激活, 1: 激活, 0: 未激活"`
|
||||||
|
// 生日
|
||||||
|
Birthday *time.Time `json:"birthday" gorm:"column:birthday;type:varchar(200)" optional:"true" description:"生日"`
|
||||||
|
// 昵称
|
||||||
|
NickName string `json:"nick_name" gorm:"column:nick_name;type:varchar(200)" optional:"true" description:"昵称"`
|
||||||
|
// 头像图片
|
||||||
|
UserIcon string `json:"user_icon" gorm:"column:user_icon;type:varchar(500)" optional:"true" description:"头像图片"`
|
||||||
|
// 性别, 1:男,2:女,0:保密
|
||||||
|
Sex SEX `json:"sex" gorm:"column:sex;type:tinyint(1)" optional:"true" description:"性别, 1:男, 2:女, 0: 保密"`
|
||||||
|
|
||||||
|
// 邮箱
|
||||||
|
Email string `json:"email" gorm:"column:email;type:varchar(200);index" description:"邮箱" unique:"true"`
|
||||||
|
// 邮箱是否验证ok
|
||||||
|
IsEmailConfirmed bool `json:"is_email_confirmed" gorm:"column:is_email_confirmed;type:tinyint(1)" optional:"true" description:"邮箱是否验证ok"`
|
||||||
|
// 手机
|
||||||
|
Mobile string `json:"mobile" gorm:"column:mobile;type:varchar(200);index" optional:"true" description:"手机" unique:"true"`
|
||||||
|
// 手机释放验证ok
|
||||||
|
IsMobileConfirmed bool `json:"is_mobile_confirmed" gorm:"column:is_mobile_confirmed;type:tinyint(1)" optional:"true" description:"手机释放验证ok"`
|
||||||
|
// 手机登录标识
|
||||||
|
MobileTGC string `json:"mobile_tgc" gorm:"column:mobile_tgc;type:char(64)" optional:"true" description:"手机登录标识"`
|
||||||
|
// 标签
|
||||||
|
Label string `json:"label" gorm:"column:label;type:varchar(200);index" optional:"true" description:"标签"`
|
||||||
|
// 其他扩展信息
|
||||||
|
Extras map[string]string `json:"extras" gorm:"column:extras;serializer:json;type:json" optional:"true" description:"其他扩展信息"`
|
||||||
|
|
||||||
|
isHashed bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *CreateUserRequest) SetIsHashed() {
|
||||||
|
req.isHashed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *CreateUserRequest) Validate() error {
|
||||||
|
if req.UserName == "" || req.Password == "" {
|
||||||
|
return fmt.Errorf("用户名或者密码需要填写")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (req *CreateUserRequest) PasswordHash() {
|
||||||
|
if req.isHashed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||||
|
req.Password = string(b)
|
||||||
|
req.isHashed = true
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user