# blog **Repository Path**: guohao88/blog ## Basic Information - **Project Name**: blog - **Description**: 一个基于go+vue3的博客系统 - **Primary Language**: Go - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-04-22 - **Last Updated**: 2024-06-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # blog_system ## 一、开发环境及三方库 + ``go版本: 1.21`` + ``MYSQL版本:8.0.31`` + ``web服务:Gin`` + ``orm框架:Gorm`` + ``Token加密:xid(github.com/rs/xid)`` + ``密码加密:golang.org/x/crypto/bcrypt`` ## 二、项目介绍及架构 此项目包括3大模块: + `user`:用户相关操作,用户增删改查 + `token`:登录(颁发token)、登出(修改token状态)、校验token + `blog`:博客管理,针对不同角色,有不同的权限 + 访客:可以查看文章列表、文章详情 + 作者:可以创建文章、发布文章、修改文章、删除文章 + 审核员:可以对已经创建的文章进行审核,审核成功发布 + 管理员 vblog项目初始化 ```go go mod init gitee.com/guohao88/blog ``` ##### 1、访问流程: ![](./assets/image-20240506142004770.png) ##### 2、接口设计 ![](./assets/image-20240506143739616.png) ##### 3、项目架构 项目采用轻量级ddd架构 ```go model 是数据 impl 是逻辑 ``` ![](./assets/image-20240506143851386.png) ## 三、模块及相关技术 ### 1、Ioc 为解决对象之间调用的依赖问题,这里使用Ioc将所有对象注入,解决依赖问题 ![image-20240506145040007](./assets/image-20240506145040007.png) Ioc存储分为两类,一是`Controller`实例对象,二是`Apihandler`实例对象,Ioc有3个方法,1是`Registry()`用于将对象注册到ioc容器中,2是`Get()`方法,根据key获取对应的ioc对象,3是`Init()`方法,用于初始化对象的一些操作 ```go // 定义接口 就是定义逻辑 // 定义一个对象的注册表,IocContainter type IocContainter struct { // 采用Map来保持对象注册 store map[string]IocObject } //Init // 负责初始化所有的对象,因为map是无序的,这里如果要控制初始化的顺序,可以替换成list func (c *IocContainter) Init() error { for _, obj := range c.store { if err := obj.Init(); err != nil { return err } } return nil } // Registry // 将对象注册到ioc中 func (c *IocContainter) Registry(obj IocObject) { c.store[obj.Name()] = obj } // Get // 获取对象名字,从ioc容器里面获取对象 func (c *IocContainter) Get(name string) any { return c.store[name] } // Gin // gin api的接口,用于注册路由 type GinApiHandler interface { Registry(r gin.IRouter) } // 管理者所有的对象(Api Handler) // 把每个 ApiHandler的路由注册给Root Router func (c *IocContainter) RouteRegistry(r gin.IRouter) { // 获取要被托管的ApiHandler for _, obj := range c.store { // 断言一下得到的对象是否是ApiHandler的对象 if api, ok := obj.(GinApiHandlers); ok { api.RegisterRouter(router) } } } ``` ApiHandler方式 ```go // ioc程序有两个阶段,第一对象注册阶段,第二对象初始化阶段 // 将tokenapi对象注册到ioc func init() { ioc.ApiHandler().Registry(&TokenApiHandler{}) } // 实现iocObj 接口Name func (t *TokenApiHandler) Name() string { return token.AppName } // 实现iocobj接口,初始化 func (t *TokenApiHandler) Init() error { t.svc = ioc.Controller().Get(token.AppName).(token.Service) return nil } ``` Controller方式 ```go // 注册将对象注册到ioc func init() { ioc.Controller().Registry(&TokenServiceImpl{}) } // 实现iocObj 接口Name func (i *TokenServiceImpl) Name() string { return token.AppName } // 实现iocobj接口,初始化 func (i *TokenServiceImpl) Init() error { i.db = conf.C().Mysql.GetConn().Debug() i.user = ioc.Controller().Get(user.AppName).(user.Service) //通过ioc获取user依赖 return nil } ``` ### 2、User模块 接口设计: ```go type Service interface { // 创建用户 //第一个肯定是context,第二个是请求参数,第三个是返回参数 CreateUser(context.Context, *CreateUserRequest) (*User, error) // 删除用户 DeleteUser(context.Context, *DeleteUserRequest) error // 查询用户信息 DescribeUser(context.Context, *DescribeUserRequest) (*User, error) // 更新用户 UpdateUser(context.Context, *UpdateUserRequest) (*User, error) } ``` 接口中的结构体设计: ```go //创建用户的request type CreateUserRequest struct { Username string `json:"username"` Password string `json:"password"` // 对象标签 // 这里的Label不是一个结构化的数据,想要存储到数据库中,就需要存储为json,我们就需要用的gorm的tag:serializer序列化 Label map[string]string `json:"label" gorm:"serializer:json"` // 指定用户的角色 Role Role `json:"role"` } // 根据用户id查询用户详细信息构造函数 func NewDescribeUserRequestById(id string) *DescribeUserRequest { return &DescribeUserRequest{ DescribeValue: id, } } // 根据username查询用户详细信息的构造函数 func NewDescribeUserRequestByUsername(username string) *DescribeUserRequest { return &DescribeUserRequest{ DescribeBy: DESCRIBE_BY_NAME, DescribeValue: username, } } //查询用户详细信息的request,同时可以用userid和username查询 type DescribeUserRequest struct { // DescribeBy 使用枚举,用户可以使用id和用户名来查 DescribeBy DescribeBy `json:"describe_by"` DescribeValue string `json:"describe_value"` } //更新用户的requst type UpdateUserRequest struct { Id int `json:"id"` Username string `json:"username"` Password string `json:"password"` Label map[string]string `json:"label" gorm:"serializer:json"` Role Role `json:"role"` UpdatedAt int64 `json:"updated_at"` } // 请求对象ID,删除用户 type DeleteUserRequest struct { Id int `json:"id"` } ``` model设计: ```go // common.Metadata type Metadata struct { Id int `json:"id"` // 创建的时间 CreatedAt int64 `json:"created_at"` // 更新的时间 UpdatedAt int64 `json:"updated_at"` } type User struct { // 通用的信息 *common.Metadata // 用户传入的请求 *CreateUserRequest } ``` 注册ioc: ```go //注册将对象注册到ioc func init() { ioc.Controller().Registry(&UserImpl{}) } //UserImpl type UserImpl struct { db *gorm.DB } // 定义对象的初始化 func (i *UserServiceImpl) Init() error { i.db = conf.C().MySql.GetConn().Debug() return nil } // 托管到ioc容器中的名称 func (i *UserServiceImpl) Name() string { return users.AppName } ``` ### 3、token模块 接口设计 ```go type Service interface { // 登录 Login(context.Context, *LoginRequest) (*Token, error) // 校验token,用于验证用户身份 ValidateToken(context.Context, *ValidateTokenRequest) (*Token, error) // 登出 Logout(context.Context, *LogoutRequest) error } ``` token模块相关接口对应的接口体 ```go // 登录request type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } // 利用ak校验用户身份,校验token request type ValidateTokenRequest struct { AccessToken string `json:"access_token"` } // 登出request type LogoutRequest struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } ``` model设计: ```go type Token struct { // 该Token是颁发 UserId int64 `json:"user_id"` // 用户名称, user_name UserName string `json:"username" gorm:"column:username"` // 颁发给用户的访问令牌(用户需要携带Token来访问接口) AccessToken string `json:"access_token"` // 过期时间(2h), 单位是秒 AccessTokenExpiredAt int `json:"access_token_expired_at"` // 刷新Token RefreshToken string `json:"refresh_token"` // 刷新Token过期时间(7d) RefreshTokenExpiredAt int `json:"refresh_token_expired_at"` // 创建时间 CreatedAt int64 `json:"created_at"` // 更新实现 UpdatedAt int64 `json:"updated_at"` // 额外补充信息, gorm忽略处理 Role user.Role `gorm:"-"` } ``` 注册ioc: ```go //注册将对象注册到ioc func init() { ioc.Controller().Registry(&TokenImpl{}) } //tokenimpl实例,包括db对象,依赖user.service接口(user实例)类型 type TokenServiceImpl struct { db *gorm.DB // 这里依赖于用户管理领域 users users.Service } // 对象的初始化 func (i *TokenServiceImpl) Init() error { i.db = conf.C().MySql.GetConn().Debug() i.users = ioc.Controller().Get(users.AppName).(users.Service) return nil } // 托管到ioc里面的名称,实现ioc OBJ接口的name方法,指定存入ioc 容器的对象的名字 func (i *TokenServiceImpl) Name() string { return token.AppName } ``` ### 4、Blog模块 接口设计 ```go // 定义博客模块接口 type Service interface { //创建博客 CreateBlog(context.Context, *CreateBlogRequest) (*Blog, error) // 修改文章状态 UpdateBlogStatus(context.Context, *UpdateBlogStatusRequest) (*Blog, error) //更新文章 UpdateBlog(context.Context, *UpdateBlogRequest) (*Blog, error) //删除文章 DeleteBlog(context.Context, *DeleteBlogRequest) error // 详情页, 尽量多的把关联的数据查询出来, content DescribeBlog(context.Context, *DescribeBlogRequest) (*Blog, error) // 查询文章列表, 列表查询, 没有必要查询文章的具体内容 QueryBlog(context.Context, *QueryBlogRequest) (*BlogSet, error) // 文章审核, 审核通过的文章才能被看到 AuditBlog(context.Context, *AuditBlogRequest) (*Blog, error) } ``` 接口内的结构体设计: ```go // 用户参数 type CreateBlogRequest struct { // 文章标题 Title string `json:"title"` // 作者 Author string `json:"author"` // 用户登录后, 我们通过Token知道是那个用户 CreateBy string `json:"create_by"` // 文章内容 Content string `json:"content"` // 概要 Summary string `json:"summary"` // 标签, 基于标签做分类, 语言:Golang, 分类:后端 Tags map[string]string `json:"tags" gorm:"serializer:json"` } // 修改文章状态 type UpdateBlogStatusRequest struct { // 如果定义一篇文章, 使用对象Id, 具体的某一篇文章 BlogID int64 `json:"blog_id"` // 修改的状态: DRAFT/PUBLISHED 发布前后都可以修改草稿 Status Status `json:"status"` } //更新文章 type UpdateBlogRequest struct { // 如果定义一篇文章, 使用对象Id, 具体的某一篇文章 BlogId string `json:"blog_id"` // blog的范围, 不是用户传递进来的, 是api接口层 自动填充 Scope *common.Scope `json:"scope"` // 更新方式 区分全量更新/部分更新 默认全量 UpdateMode UpdateMode `json:"update_mode"` // 用户更新请求, 用户只传了个标签 *CreateBlogRequest } //删除文章 type DeleteBlogRequest struct { // 如果定义一篇文章, 使用对象Id, 具体的某一篇文章 BlogId string `json:"blog_id"` } // 详情页, 尽量多的把关联的数据查询出来 type DescribeBlogRequest struct { BlogId string `json:"blog_id"` } // 查询文章列表 type QueryBlogRequest struct { // 页的大小 PageSize int `json:"page_size"` // 当前处于几页 PageNumber int `json:"page_number"` // 0 表示草稿状态, 要查询所有的博客 // nil 没有这个过滤条件 // 0 DRAFT // 1 PUBLISHED Status *Status `json:"status"` } //文章审核 type AuditBlogRequest struct { // 审核的文章 BlogId string `json:"blog_id"` // 是否审核成功 IsAuditPass bool `json:"is_audit_pass"` } ``` model设计: ```go type Blog struct { // 文章的唯一标识符, 给程序使用 Id int64 `json:"id"` // 创建时间 CreatedAt int64 `json:"created_at"` // 更新时间 UpdatedAt int64 `json:"updated_at"` // 发布时间 PublishedAt int64 `json:"published_at"` // 文章的状态 Status Status `json:"status"` // 审核时间 AuditAt int64 `json:"audit_at"` // 是否审核成功 IsAuditPass bool `json:"is_audit_pass"` // 用户创建博客参数 *CreateBlogRequest } ``` 注册ioc: ```go //注册将对象注册到ioc func init() { ioc.Controller().Registry(&blogServiceImpl{}) } //BlogImpl实例,包括db对象 type blogServiceImpl struct { db *gorm.DB } // 对象的初始化 func (i *blogServiceImpl) Init() error { i.db = conf.C().MySql.GetConn().Debug() return nil } // 托管到ioc里面的名称 func (i *blogServiceImpl) Name() string { return token.AppName } ``` ## 四、其他 ### git流程 ``` git add . git add . git status git commit -m "更新xxxx" git push 打tag流程 git tag git tag test.v1.0 git push --tags ``` ### Api服务的认证 是否需要根据业务需求 ```go // 鉴权方法 // Gin中间件 func(*Context) func (a *TokenAuther) Auth(c *gin.Context) { // 1. 获取Token at, err := c.Cookie(token.TOKEN_COOKIE_NAME) if err != nil { if err == http.ErrNoCookie { response.Failed(c, token.CookieNotFound) return } response.Failed(c, err) return } // 2.调用Token模块来认证 in := token.NewValiateToken(at) tk, err := a.tk.ValiateToken(c.Request.Context(), in) if err != nil { response.Failed(c, err) return } // 把鉴权后的 结果: tk, 放到请求的上下文, 方便后面的业务逻辑使用 if c.Keys == nil { c.Keys = map[string]any{} } c.Keys[token.TOKEN_GIN_KEY_NAME] = tk } ``` 中间件使用 ```go // 后台管理接口 需要认证 v1.Use(middlewares.NewTokenAuther().Auth) ``` 使用请求上下文 ```go // 从Gin请求上下文中: c.Keys, 获取认证过后的鉴权结果 tkObj := c.Keys[token.TOKEN_GIN_KEY_NAME] tk := tkObj.(*token.Token) ``` 以下是使用认证例子 ```go // 创建博客 func (h *blogApiHandler) CreateBlog(c *gin.Context) { // 从Gin请求上下文中: c.Keys, 获取认证过后的鉴权结果 tkObj := c.Keys[token.TOKEN_GIN_KEY_NAME] tk := tkObj.(*token.Token) in := blog.NewCreateBlogRequest() err := c.BindJSON(in) if err != nil { response.Failed(c, err) return } // 充上下文中补充 用户信息· in.CreateBy = tk.UserName ins, err := h.svc.CreateBlog(c.Request.Context(), in) if err != nil { response.Failed(c, err) return } response.Success(c, ins) } ``` ![image-20240506214006381](assets/image-20240506214006381.png) ![](assets/image-20240506214036708.png)