README ¶
设计手册
directory
// file: directory.go
// DirectoryInfo 目录信息
type DirectoryInfo struct {
Description string `json:"description,omitempty" example:"course materials" bson:"description,omitempty"`
Cover string `json:"cover,omitempty" example:"https://www.motwo.cn/cover" bson:"cover,omitempty"`
}
// Directory 目录
type Directory struct {
ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"_id,omitempty"`
ParentID primitive.ObjectID `json:"parent_id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"parent_id,omitempty"`
Name string `json:"name,omitempty" example:"records" bson:"name,omitempty"`
Info DirectoryInfo `json:"info,omitempty" bson:"info,omitempty"`
OwnerIDs []primitive.ObjectID `json:"owner_ids,omitempty" bson:"owner_ids,omitempty"`
}
字段含义
- ParentID:父目录的id
- Name:名称
- OwnerIDs:归属者的id列表,用于访问控制
设计思路
需要层级目录的对象都可以复用,比如收藏夹,群组共享等
将应用于不同对象的数据存放在不同的表中,下面均以第一个实现的category为例:
- 首先,用户初始化category功能,生成一个category的对象(定义名称为
root
)- parent_id为创建用户id
- 此时的ownerIDs也添加用户id
- 若为category新增子category,对子归档(subCategory)进行修改:
- parent_id为父category的id
- 此时的ownerIDs也添加用户id
这种设计的优势有:
- 易得某目录c的子目录s(c.ID==s.ParentID)
- 易得子目录s的父目录p(p.ID==s.ParentID)
- 易得某用户u的根目录r(u.ID==r.ParentID)
- 易得某用户u的所有目录cs(u.ID in cs.OwnerIDs)
api设计
由于新增了归档这一实体,其内部之间的关系、归档与博客之间的关系、归档与用户之间的关系都需要设定与查询。
与此相类,之后复用目录结构的实体也将面对这一需求,因此,设计泛化通用的api势在必行
遵循REST API的设计理念,将api的路径对应相应资源,方法代表操作,于是引入以下实体:
dto/directory.go
// file: dto/directory.go
// RelateEntity2Entity 将单实体关联到单实体dto
type RelateEntity2Entity struct {
RelatedID primitive.ObjectID `json:"related_id,omitempty"`
RelateToID primitive.ObjectID `json:"relateTo_id,omitempty"`
}
// RelateEntitySet2EntitySet 关联两个实体集dto
type RelateEntitySet2EntitySet struct {
RelatedIDs []primitive.ObjectID `json:"related_ids,omitempty"`
RelateToIDs []primitive.ObjectID `json:"relateTo_ids,omitempty"`
}
// RelateEntity2EntitySet 关联单实体到多实体集dto
type RelateEntity2EntitySet struct {
RelatedID primitive.ObjectID `json:"related_id,omitempty"`
RelateToIDs []primitive.ObjectID `json:"relateTo_ids,omitempty"`
}
// RelateEntitySet2Entity 关联实体集到单实体dto
type RelateEntitySet2Entity struct {
RelatedIDs []primitive.ObjectID `json:"related_ids,omitempty"`
RelateToID primitive.ObjectID `json:"relateTo_id,omitempty"`
}
分别对应实现:
- 将单实体关联到单实体
- 关联两个实体集
- 关联单实体到多实体集
- 关联实体集到单实体
api使用
api := middleware.H.Group("/api")
{
//...
relation := api.Group("relation", model.OrdinaryUser)
{
relation.Post("categories/:type", c.RelateCategories2Entity)
relation.Post("category/:type", c.RelateCategory2Entity)
relation.Get("category/:type/:ID", c.FindCategoriesByType)
}
//...
}
- 未实现前加(x)
-
基础
- 访问控制未完善
- api/blog/category [post]
- upsert增改category信息
- api/blog/category [get]
- 查询category信息
- api/directories/category [delete] (important)
- 删除category信息
- 解除与该category相关的所有联系:blog字段寻找相关字段并删去
-
relation部分
- categories
-
多对一
- api/relation/categories/:type [post]
- 建立categories与type之间的联系,目前可选:
- category:将多实体集categories的父categoryid均设为单实体的id
- blog:将多实体集categories的ids添加到blog的categories列表中去
- api/relation/categories/:type [post]
-
(x)多对多
-
- category
- 一对一
- api/relation/category/:type [post]
- 建立category与type之间的联系,目前可选:
- user:将user的id添加到category的ownerIDs列表中
- userMain:将user的id设定为category的parent_id
- category:将related id的parent_id设定为relateTo的category的id
- blog:将category的id添加到blog的categories列表中去
- api/relation/category/:type [post]
- 一对一/多
- api/relation/category/:type/:id [get]
- 获取categories与type之间的联系,目前可选:
- user:将多实体集categories的父categoryid均设为单实体的id
- sub:将多实体集categories添加到blog的categories列表中去
- api/relation/category/:type/:id [get]
- 一对一
- categories
新增部分
- 若upsert中的id为零值,初始化id
- 若parentID为零值,寻找此次请求用户的根目录
root
id,亦即ownerIDs的最末一位- 若
root
不存在,则为该用户新建root,并返回新建root的id - 若
root
存在,则返回已有root的id - 返回id作为本次新增directory的parentID
- 若
删除部分
因为删除某directory涉及到冗余关联数据的删除,因此需要:
- 对blog端:删除所有blog中categories列表里的相关id即可
- 对category端:所有删除category(
dCat
)的子category加入到其上一级(id=dCat.parent_id
)中 - 增加鉴权,只有操作用户id in owner_ids匹配成功的可以进行删除操作
- 新思路,增加过滤器,在请求的id列表中过滤出可以进行操作的id列表
BLOG
API
删除部分
- UPDATE: 3.15
添加回收站规则 需要新增/修改api 新增表示删除(回收recycle)时间
-
api/blog [delete]
-
彻底删除文章
-
api/blog/{operation}/{id} [put]
*[operation]
:- recycle:加入回收站
- 新增关于本blog/draft的recycleBin信息
- 且isDeleted字段置为true状态
- restore:从回收站还原
- 将recycleBin中关于本blog/draft的信息进行删除
- 且isDeleted字段恢复为false状态
*
[id]
:被操作对象的id - recycle:加入回收站
recycleItem
为定时清空回收站,新建一个表,用来记录所有需要延时删除的对象,分别记录:
- 删除对象ID
- 加入删除列表时间
- 预计删除时间
- 删除方法
这样删除工作可统一通过遍历此表进行
// RecycleItem 回收站中的对象信息,记录加入回收站时间和预计被删除时间,以及处理函数
type RecycleItem struct {
ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
ItemID primitive.ObjectID `json:"item_id,omitempty" bson:"item_id,omitempty"`
CreateTime time.Time `json:"create_time,omitempty" example:"2020-10-1" bson:"create_time,omitempty"`
DeleteTime time.Time `json:"delete_time,omitempty" example:"2020-10-1" bson:"delete_time,omitempty"`
Handler string `json:"handler,omitempty" example:"blog" bson:"handler,omitempty"`
}
group
群组功能允许:
- 创建群组
- 邀请成员
- 管理成员权限
- 创建群组共享文章
想法是这样的,把群组视为一个权限过滤器。那么文章的authorId设置为群组的id,也就是权限过滤器的id
(突然感觉这个想法不错,可以抽象出一个权限过滤器的类,这样需要有处理权限功能的事务都可复用)
但这个权限管理器与设计的abac的关系是怎么样的呢?是否能够完全交给abac实现?
aha,accessFilter 可以实现RuleType的接口
type RuleType interface {
JudgeRule() (bool, error)
ProcessContext(ctx ContextType)
}
那么判断一个用户有权访问某项资源为:
- 判断用户的id是否是authorID
- 判断用户是否通过资源的权限过滤器
权限管理器可以注册不同用户的在该资源中的身份:admin/read/write
群组中文章
需要将群组的id加入到blog的ownerID中
是否有必要把一个群组的分离?不若直接把filter作为群组的部分,减少查询次数
Documentation ¶
Index ¶
- Constants
- Variables
- func AddRoles(a *Account, roles ...Erole)
- func IsTimeValid(time2 time.Time) (valid bool)
- type AccessManager
- type Account
- type AddAccount
- type AddAccountRole
- type Blog
- type Comment
- type DeleteAccount
- type Directory
- func (c *Directory) Init()
- func (c *Directory) InitWithName(name string)
- func (c *Directory) InitWithNameAndParent(name string, parentId primitive.ObjectID)
- func (c *Directory) IsValid() (valid bool)
- func (c *Directory) UpdateName(name string)
- func (c *Directory) UpdateParent(parent Directory)
- func (c *Directory) UpdateParentId(id primitive.ObjectID)
- type DirectoryInfo
- type Entity
- type Erole
- type Filter
- type Group
- type LoginAccount
- type Praiseable
- type Project
- type RecycleItem
- type Subcomment
- type VerifyEmail
Constants ¶
const ( Token = "token" Avatar = "avatar" IsActive = "isActive" True = "true" False = "false" )
User info consts
const ( HandlerDraft = "draft" HandlerBlog = "blog" )
最好与collection名称保持一致!
Variables ¶
var ( ErrNameInvalid = errors.New("name is empty") ErrEmailInvalid = errors.New("email is empty") ErrPasswordInvalid = errors.New("password is invalid") )
example
var IndexModels = []mongo.IndexModel{ {Keys: bson.M{"entity_info.create_time": -1}}, {Keys: bson.M{"entity_info.update_time": -1}}, {Keys: bson.M{"entity_info.isdeleted": 1}}, }
IndexModels Index Models to index entity
Functions ¶
Types ¶
type AccessManager ¶
type Account ¶
type Account struct { ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxx==" bson:"_id,omitempty"` UserName string `json:"userName" example:"account name" bson:"username,omitempty"` Email string `json:"email" example:"email@qq.com" bson:"email,omitempty"` HashedPwd string `json:"hashedPassword" example:"$2a$10$rXMPcOyfgdU6y5n3pkYQAukc3avJE9CLsx1v0Kn99GKV1NpREvN2i" bson:"hashedpwd,omitempty"` EntityInfo Entity `json:"entityInfo,omitempty" bson:"entity_info,omitempty"` Roles []Erole `json:"roles" bson:"roles"` Infos map[string]string `json:"infos" example:"'token': 'xxxxxxxx'(private data)" bson:"infos,omitempty"` Settings map[string]string `` /* 127-byte string literal not displayed */ }
Account example
type AddAccount ¶
type AddAccount struct { UserName string `json:"userName" example:"account name"` Email string `json:"email" example:"email@mo2.com"` Password string `json:"password" example:"p@ssword"` }
AddAccount example
type AddAccountRole ¶
type AddAccountRole struct { ID primitive.ObjectID `json:"id" example:"xxxxxxxxxxxxx==" ` Roles []Erole `json:"roles"` SuperKey string `json:"super_key" example:"special"` }
AddAccountRole example
type Blog ¶
type Blog struct { ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxx==" bson:"_id,omitempty"` AuthorID primitive.ObjectID `json:"authorId,omitempty" example:"xxxxxxxxxxxxx==" bson:"author_id"` Title string `json:"title,omitempty" example:"mouse ❤ monkey" bson:"title,omitempty"` Description string `json:"description,omitempty" example:"mouse ❤ monkey" bson:"description,omitempty"` Content string `json:"content,omitempty" example:"xxxx\nxxxx" bson:"content,omitempty"` EntityInfo Entity `json:"entityInfo,omitempty" bson:"entity_info,omitempty"` Cover string `json:"cover,omitempty" example:"https://xxx/xxx" bson:"cover,omitempty"` KeyWords []string `json:"keyWords,omitempty" example:"xxx,xxx" bson:"key_words,omitempty"` CategoryIDs []primitive.ObjectID `json:"categories,omitempty" bson:"categories,omitempty"` YDoc string `json:"y_doc,omitempty" bson:"y_doc,omitempty"` // 用于collaboration IsYDoc bool `json:"is_y_doc,omitempty" bson:"is_y_doc,omitempty"` // 用于collaboration YToken primitive.ObjectID `json:"y_token,omitempty" bson:"y_token,omitempty"` //用于collaboration ScoreSum float64 `json:"score_sum" bson:"score_sum,omitempty"` ScoreNum int `json:"score_num" bson:"score_num,omitempty"` ProjectID primitive.ObjectID `json:"project_id" bson:"project_id"` CoAuthorIDs []primitive.ObjectID `json:"coauthors,omitempty" bson:"coauthors"` }
Blog example 若修改字段,需注意依赖此model的使用地方 important: dto.QueryBlog UpsertBlog()
func (*Blog) Add2Categories ¶
func (*Blog) Add2Category ¶
type Comment ¶
type Comment struct { ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"_id,omitempty"` Content string `json:"content,omitempty" example:"a comment" bson:"content,omitempty"` EntityInfo Entity `json:"entity_info,omitempty" bson:"entity_info,omitempty"` Praise Praiseable `json:"praise,omitempty" bson:"praise,omitempty"` Author primitive.ObjectID `json:"aurhor,omitempty" bson:"aurhor,omitempty"` Article primitive.ObjectID `json:"article,omitempty" bson:"article,omitempty"` Subs []Subcomment `json:"subs" bson:"subs"` }
Comment a comment
type DeleteAccount ¶
type DeleteAccount struct { Email string `json:"email" example:"email@mo2.com"` Password string `json:"password" example:"p@ssword"` }
DeleteAccount example
type Directory ¶
type Directory struct { ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"_id,omitempty"` ParentID primitive.ObjectID `json:"parent_id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"parent_id,omitempty"` Name string `json:"name,omitempty" example:"records" bson:"name,omitempty"` Info DirectoryInfo `json:"info,omitempty" bson:"info,omitempty"` OwnerIDs []primitive.ObjectID `json:"owner_ids" bson:"owner_ids,omitempty"` }
Directory 归档
func (*Directory) InitWithName ¶
func (*Directory) InitWithNameAndParent ¶
func (*Directory) UpdateName ¶
func (*Directory) UpdateParent ¶
UpdateParent 以父目录更新当前目录
func (*Directory) UpdateParentId ¶
type DirectoryInfo ¶
type DirectoryInfo struct { Description string `json:"description,omitempty" example:"course materials" bson:"description,omitempty"` Cover string `json:"cover,omitempty" example:"https://www.motwo.cn/cover" bson:"cover,omitempty"` }
DirectoryInfo 归档信息
type Entity ¶
type Entity struct { CreateTime time.Time `json:"createTime" example:"2020-10-1" bson:"create_time,omitempty"` UpdateTime time.Time `json:"updateTime" example:"2020-10-1" bson:"update_time,omitempty"` IsDeleted bool `json:"is_deleted,omitempty" example:"true" bson:"isdeleted"` }
Entity example
type Group ¶
type Group struct { ID primitive.ObjectID `json:"id" bson:"id"` OwnerID primitive.ObjectID `json:"owner_id" bson:"owner_id"` AccessManager AccessManager `json:"access_manager,omitempty" bson:"access_manager,omitempty"` }
type LoginAccount ¶
type LoginAccount struct { UserNameOrEmail string `json:"userNameOrEmail" example:"account name/email@mo2.com"` Password string `json:"password" example:"p@ssword"` }
LoginAccount example
func (LoginAccount) Validation ¶
func (a LoginAccount) Validation() error
Validation is for LoginAccount Validation
type Praiseable ¶
type Praiseable struct { Up uint64 `json:"up" bson:"up"` Down uint64 `json:"down" bson:"down"` Weighted uint64 `json:"weighted" bson:"weighted"` }
Praiseable 可被点赞的
type Project ¶
type Project struct { ID primitive.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` Tags []string `bson:"tags"` OwnerID primitive.ObjectID `bson:"owner_id"` ManagerIDs []primitive.ObjectID `bson:"manager_i_ds"` MemberIDs []primitive.ObjectID `bson:"member_i_ds"` BlogIDs []primitive.ObjectID `bson:"blog_i_ds"` Description string `bson:"description"` Avatar string `bson:"avatar"` EntityInfo Entity `bson:"entity_info"` }
type RecycleItem ¶
type RecycleItem struct { ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` ItemID primitive.ObjectID `json:"item_id,omitempty" bson:"item_id,omitempty"` CreateTime time.Time `json:"create_time,omitempty" example:"2020-10-1" bson:"create_time,omitempty"` DeleteTime time.Time `json:"delete_time,omitempty" example:"2020-10-1" bson:"delete_time,omitempty"` Handler string `json:"handler,omitempty" example:"blog" bson:"handler,omitempty"` }
RecycleItem 回收站中的对象信息,记录加入回收站时间和预计被删除时间,以及处理函数
type Subcomment ¶
type Subcomment struct { ID primitive.ObjectID `json:"id,omitempty" example:"xxxxxxxxxxxxxx==" bson:"_id,omitempty"` Content string `json:"content,omitempty" example:"a comment" bson:"content,omitempty"` Aurhor primitive.ObjectID `json:"aurhor,omitempty" bson:"aurhor,omitempty"` EntityInfo Entity `json:"entity_info,omitempty" bson:"entity_info,omitempty"` Praise Praiseable `json:"praise,omitempty" bson:"praise,omitempty"` }
Subcomment level 2 comment
type VerifyEmail ¶
type VerifyEmail struct { Email string `json:"Email" example:"email@mo2.com"` Token string `json:"token" example:"p@ssword"` }
VerifyEmail struct for email verify