代码拉取完成,页面将自动刷新
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状态)、校验tokenblog
:博客管理,针对不同角色,有不同的权限
vblog项目初始化
go mod init gitee.com/guohao88/blog
项目采用轻量级ddd架构
model 是数据 impl 是逻辑
为解决对象之间调用的依赖问题,这里使用Ioc将所有对象注入,解决依赖问题
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
}
接口设计:
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
}
接口设计
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
}
接口设计
// 定义博客模块接口
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 add .
git add .
git status
git commit -m "更新xxxx"
git push
打tag流程
git tag
git tag test.v1.0
git push --tags
是否需要根据业务需求
// 鉴权方法
// 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)
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。