1 Star 0 Fork 0

guohao / blog

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
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 mod init gitee.com/guohao88/blog 
1、访问流程:

2、接口设计

3、项目架构

项目采用轻量级ddd架构

model 是数据 impl 是逻辑

三、模块及相关技术

1、Ioc

为解决对象之间调用的依赖问题,这里使用Ioc将所有对象注入,解决依赖问题

image-20240506145040007

Ioc存储分为两类,一是Controller实例对象,二是Apihandler实例对象,Ioc有3个方法,1是Registry()用于将对象注册到ioc容器中,2是Get()方法,根据key获取对应的ioc对象,3是Init()方法,用于初始化对象的一些操作

// 定义接口 就是定义逻辑
// 定义一个对象的注册表,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方式

// 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方式

// 注册将对象注册到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模块

接口设计:

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)
}

接口中的结构体设计:

//创建用户的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设计:

// 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:

//注册将对象注册到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模块

接口设计

type Service interface {
	// 登录
	Login(context.Context, *LoginRequest) (*Token, error)
	// 校验token,用于验证用户身份
	ValidateToken(context.Context, *ValidateTokenRequest) (*Token, error)
	// 登出
	Logout(context.Context, *LogoutRequest) error
}

token模块相关接口对应的接口体

// 登录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设计:

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:

//注册将对象注册到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模块

接口设计

// 定义博客模块接口
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)
}

接口内的结构体设计:

// 用户参数
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设计:

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:

//注册将对象注册到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服务的认证

是否需要根据业务需求

// 鉴权方法
// 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
}

中间件使用

// 后台管理接口 需要认证
v1.Use(middlewares.NewTokenAuther().Auth)

使用请求上下文

// 从Gin请求上下文中: c.Keys, 获取认证过后的鉴权结果
tkObj := c.Keys[token.TOKEN_GIN_KEY_NAME]
tk := tkObj.(*token.Token)

以下是使用认证例子

// 创建博客
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

空文件

简介

一个基于go+vue3的博客系统 展开 收起
Go 等 5 种语言
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Go
1
https://gitee.com/guohao88/blog.git
git@gitee.com:guohao88/blog.git
guohao88
blog
blog
master

搜索帮助