vblog

command module
v0.0.0-...-8e0b234 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2024 License: OSL-3.0 Imports: 3 Imported by: 0

README

Web全栈开发(Vblog)

需求

博客编写与发布

目标用户:

  • 文章管理员(作者): 写博客的后台
  • 访客: 浏览文章的前台

原型

博客后台(作者)
  1. 列表页

  1. 编辑页

博客前台(访客)

博客浏览页

架构设计

整体架构

业务架构

项目设计

概要设计(流程)
  1. 业务交互流程

  • 博客管理(Blog)
  • 用户管理(User)
  • 令牌管理(Token)
  1. 登录过期

数据库设计
  1. 博客管理
CREATE TABLE `blogs` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '文章的Id',
  `tags` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '标签',
  `created_at` int NOT NULL COMMENT '创建时间',
  `published_at` int NOT NULL COMMENT '发布时间',
  `updated_at` int NOT NULL COMMENT '更新时间',
  `title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章标题',
  `author` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '作者',
  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章内容',
  `status` tinyint NOT NULL COMMENT '文章状态',
  `summary` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章概要信息',
  `create_by` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',
  `audit_at` int NOT NULL COMMENT '审核时间',
  `is_audit_pass` tinyint NOT NULL COMMENT '是否审核通过',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_title` (`title`) COMMENT 'titile添加唯一键约束'
) ENGINE=InnoDB AUTO_INCREMENT=47 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  1. 用户管理
CREATE TABLE `users` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `created_at` int NOT NULL COMMENT '创建时间',
  `updated_at` int NOT NULL COMMENT '更新时间',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名, 用户名不允许重复的',
  `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '不能保持用户的明文密码',
  `label` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户标签',
  `role` tinyint NOT NULL COMMENT '用户的角色',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_user` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
  1. 令牌管理
CREATE TABLE `tokens` (
  `created_at` int NOT NULL COMMENT '创建时间',
  `updated_at` int NOT NULL COMMENT '更新时间',
  `user_id` int NOT NULL COMMENT '用户的Id',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名, 用户名不允许重复的',
  `access_token` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户的访问令牌',
  `access_token_expired_at` int NOT NULL COMMENT '令牌过期时间',
  `refresh_token` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '刷新令牌',
  `refresh_token_expired_at` int NOT NULL COMMENT '刷新令牌过期时间',
  PRIMARY KEY (`access_token`) USING BTREE,
  UNIQUE KEY `idx_token` (`access_token`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

注意:

现在流行尽量避免使用外建, 由程序更加业务逻辑 自行决定整个关联关系怎么处理
Restful API设计
  1. Restful: (Resource) Representational State Transfer(资源状态转换) API 风格
  • Resource Representational: 资源定义(服务端对象或者数据库内的一行纪录)
  • State Transfer: 创建/修改/删除

这张风格如何表现在API?

1. 资源定义
1.1 一类资源
/vblogs/api/v1/blogs: blogs 就是资源的类型: blogs 博客
/vblogs/api/v1/users: users 就是资源的类型: users 用户
1.2 一个资源
/vblogs/api/v1/users/1: 1 就是资源的id, id为1的资源

2. 状态转换: 通过HTTP Method来定义只有的状态转化, 理解为用户的针对某类或者某个资源的动作
POST: 创建一个类型的资源, POST /vblogs/api/v1/users 创建一个用户, 具体的参数存放在body
PATCH: 部分修改(补丁), PATCH /vblogs/api/v1/users/1, 对id为1的用户 做属性的部分修改, name:abc ("usera" ---> "abc")
PUT: 全量修改(覆盖), PUT /vblogs/api/v1/users/1, 对id为1的用户 做属性的全量修改, name:abc  除去name之外的所有属性全部清空
DELETE: 资源删除
GET: 获取一类资源: GET /vblogs/api/v1/users, 获取一个资源 GET /vblogs/api/v1/users/1

其他风格的API

POST url命名动作来表示资源的操作:  POST /vblogs/api/v1/users/(list/get/delete/update/...)
POST /pods/poda/logs/watch
博客管理(设计完整的RESTful API)
  1. 创建博客: POST /vblogs/api/v1/blogs
{
 "title": "",
 "author": "", 
 "content": "",
 "summary": ""
}
  1. 修改博客(部分): PATCH /vblogs/api/v1/blogs/:id
{
 "title": "",
 "author": "", 
 "content": "",
 "summary": ""
}
  1. 修改博客(全量): PUT /vblogs/api/v1/blogs/:id
{
 "title": "",
 "author": "", 
 "content": "",
 "summary": ""
}
  1. 删除博客: DELETE /vblogs/api/v1/blogs/:id
body不传数据
  1. GET /vblogs/api/v1/blogs/:id
body不传数据
令牌管理(设计基础必须)
  1. POST /vblogs/api/v1/tokens
{
  "username": "",
  "password": "",
  "remember": true,
}
  1. DELETE /vblogs/api/v1/tokens
body不传数据
用户管理
功能完整, 不做API, 可以直接操作数据库, 也可以通过单元测试

项目开发

编写流程
  • 整体框架(上-->下)
  • 业务代码(下-->上)
  1. 顶层设计: 从上往下进行设计
  2. 业务代码: 从下往上写, 核心应该业务的实现
项目结构
go mod init 'gitee.com/baicaijc/vblog'
  • main.go: 入口文件
  • conf: 程序的配置处理
  • exception: 业务自定义异常
  • response: 请求返会的统一数据格式: {"code": 0, "msg": ""}
  • protocol: 协议服务器
  • apps: 业务模块开发区域
业务模块开发

业务模块开发遵循如下规则:

  • 定义业务(Interface): 梳理需求, 抽象业务逻辑, 定义出业务的数据结构与接口约束
  • 业务实现(Controller): 根据业务定义, 选择具体的技术(比如MySQL/MongoDB/ES),做具体的业务实现
  • 业务接口(API): 如果需要对外提供 API, 则按需将需要的对外暴露API接口

表现在目录结构上:

  • 定义业务: 业务模块顶层目录, 具体表现为: user/interface.go(接口定义)
  • 业务实现: 业务模块内impl目录, 具体表现为: user/impl/impl.go(业务实现对象)
  • 业务接口: 业务模块内api目录, 具体表现为: user/api/api.go(HTTP Restful接口实现对象)

API和Interface的区别

  • API: 应用编程接口, HTTP 接口,通过网络 可以调用的
  • Interface: 对某个对象(Struct)的约束
用户管理模块开发
定义业务
// 面向对象
// user.Service, 设计你这个模块提供的接口
// 接口定义, 一定要考虑兼容性, 接口的参数不能变
type Service interface {
	// 用户创建
	// CreateUser(username, password, role string, lable map[string]string)
	// 设计CreateUserRequest, 可以扩展对象, 而不影响接口的定义
	// 1. 这个接口支持取消吗? 要支持取消应该怎么办?
	// 2. 这个接口支持Trace, TraceId怎么传递?
	// 中间件参数,取消/Trace/... 怎么产生怎么传递
	CreateUser(context.Context, *CreateUserRequest) (*User, error)
	// 查询用户列表, 对象列表 [{}]
	QueryUser(context.Context, *QueryUserRequest) (*UserSet, error)
	// 查询用户详情, 通过Id查询,
	DescribeUser(context.Context, *DescribeUserRequest) (*User, error)

	// 作业:
	// 用户修改
	// 用户删除
}
业务实现

业务定义层(对业务的抽象), 由impl模块来完成具体的功能实现

// 实现 user.Service
// 怎么判断这个服务有没有实现这个接口喃?
// &UserServiceImpl{} 是会分配内存, 怎么才能不分配内存
// nil 如何生命 *UserServiceImpl 的 nil
// (*UserServiceImpl)(nil) ---> int8 1  int32(1)  (int32)(1)
// nil 就是一个*UserServiceImpl的空指针
var _ user.Service = (*UserServiceImpl)(nil)

// 用户创建
func (i *UserServiceImpl) CreateUser(
	ctx context.Context,
	in *user.CreateUserRequest) (
	*user.User, error) {
	return nil, nil
}

// 查询用户列表, 对象列表 [{}]
func (i *UserServiceImpl) QueryUser(
	ctx context.Context,
	in *user.QueryUserRequest) (
	*user.UserSet, error) {
	return nil, nil
}

// 查询用户详情, 通过Id查询,
func (i *UserServiceImpl) DescribeUser(
	ctx context.Context,
	in *user.DescribeUserRequest) (
	*user.User, error) {
	return nil, nil
}

TDD的思想: 保证代码的质量

  1. 怎么验证当前这个业务实现是不是正确的? 写单元测试(TDD)
// 怎么引入被测试的对象
func TestCreateUser(t *testing.T) {
	// 单元测试异常怎么处理
	u, err := i.CreateUser(ctx, nil)
	// 直接报错中断单元流程并且失败
	if err != nil {
		t.Fatal(err)
	}

	// 自己进行期望对比,进行单元测试报错
	if u == nil {
		t.Fatal("user not created")
	}

	// 正常打印对象
	t.Log(u)
}
  1. 业务控制器 如何获取 额外依赖(GORM DB对象)
// 怎么实现user.Service接口?
// 定义UserServiceImpl来实现接口
type UserServiceImpl struct {
	// 依赖了一个数据库操作的链接池对象
	db *gorm.DB
}
  1. 为程序提供配置:
package conf

import (
	"fmt"
	"sync"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// 这里不采用直接暴露变量的方式, 比较好的方式 使用函数
var config *Config

// 这里就可以补充逻辑
func C() *Config {
	// sync.Lock
	if config == nil {
		// 给个默认值
		config = &Config{}
	}
	return config
}

// 程序配置对象, 启动时 会读取配置, 并且为程序提供需要全局变量
// 把配置对象做出全局变量(单列模式)
type Config struct {
	MySQL *MySQL
}

// db对象也是一个单列模式
type MySQL struct {
	Host     string `json:"host" yaml:"host" toml:"host" env:"DATASOURCE_HOST"`
	Port     int    `json:"port" yaml:"port" toml:"port" env:"DATASOURCE_PORT"`
	DB       string `json:"database" yaml:"database" toml:"database" env:"DATASOURCE_DB"`
	Username string `json:"username" yaml:"username" toml:"username" env:"DATASOURCE_USERNAME"`
	Password string `json:"password" yaml:"password" toml:"password" env:"DATASOURCE_PASSWORD"`
	Debug    bool   `json:"debug" yaml:"debug" toml:"debug" env:"DATASOURCE_DEBUG"`

	// 判断这个私有属性, 来判断是否返回已有的对象
	db *gorm.DB
	l  sync.Mutex
}

// dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
func (m *MySQL) DSN() string {
	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		m.Username,
		m.Password,
		m.Host,
		m.Port,
		m.DB,
	)
}

// 通过配置就能通过一个DB 实例
func (m *MySQL) GetDB() *gorm.DB {
	m.l.Lock()
	defer m.l.Unlock()

	if m.db == nil {
		db, err := gorm.Open(mysql.Open(m.DSN()), &gorm.Config{})
		if err != nil {
			panic(err)
		}
		m.db = db
	}

	return m.db
}

// 配置对象提供全局单列配置
func (c *Config) DB() *gorm.DB {
	return c.MySQL.GetDB()
}
  1. 使用配置提供DB对象完成控制期的依赖
func NewUserServiceImpl() *UserServiceImpl {
	return &UserServiceImpl{
		// 获取全局的DB对象
		// 前提: 配置对象准备完成
		db: conf.C().DB(),
	}
}
  1. 程序的校验

使用validator来进行参数的校验 "github.com/go-playground/validator/v10"

  1. 使用单元测试验证实现的准确性:
package impl_test

import (
	"context"
	"testing"

	"gitlab.com/go-course-project/go13/vblog/apps/user"
	"gitlab.com/go-course-project/go13/vblog/apps/user/impl"
)

var (
	i   user.Service
	ctx = context.Background()
)

// 怎么引入被测试的对象
func TestCreateUser(t *testing.T) {
	// 使用构造函数创建请求对象
	// user.CreateUserRequest{}
	req := user.NewCreateUserRequest()
	req.Username = "member"
	req.Password = "123456"
	req.Role = user.ROLE_ADMIN

	// 单元测试异常怎么处理
	u, err := i.CreateUser(ctx, req)
	// 直接报错中断单元流程并且失败
	if err != nil {
		t.Fatal(err)
	}

	// 自己进行期望对比,进行单元测试报错
	if u == nil {
		t.Fatal("user not created")
	}

	// 正常打印对象
	t.Log(u)
}

func TestQueryUser(t *testing.T) {
	req := user.NewQueryUserRequest()
	ul, err := i.QueryUser(ctx, req)
	// 直接报错中断单元流程并且失败
	if err != nil {
		t.Fatal(err)
	}
	t.Log(ul)
}

func TestDescribeUser(t *testing.T) {
	req := user.NewDescribeUserRequest(6)
	ul, err := i.DescribeUser(ctx, req)
	if err != nil {
		t.Fatal(err)
	}
	t.Log(ul)
}

func init() {
	// 加载被测试对象, i 就是User Service接口的具体实现对象
	i = impl.NewUserServiceImpl()
}
用户密码的存储问题

问题:

  1. 用户密码明文存储在数据库当中
  2. 哪些情况下需要把用户的密码查询出来, 进程内调用可以查询, 接口队伍暴露时 屏蔽

方案:

  1. HashPassword 方法内实现 hash
// $2a$10$1MvkjvWOS0/Rf.cEKKxeie/Y7ADz9XZTq09Wd/bKwX/vUv0kdYJ4.
// $2a$10$IyB.w1NVOrBmZ9WOsT6gEuruaynjse2CNmce9399yUErnufV10DX2
// https://gitee.com/infraboard/go-course/blob/master/day09/go-hash.md#bcrypt
func TestHashPassword(t *testing.T) {
	req := user.NewCreateUserRequest()
	req.Password = "123456"
	req.HashPassword()
	t.Log(req.Password)

	t.Log(req.CheckPassword("1234561"))
}
令牌管理模块开发
业务定义
// Token Service接口定义
type Service interface {
	// 登录: 颁发令牌
	IssueToken(context.Context, *IssueTokenRequest) (*Token, error)

	// 退出: 撤销令牌
	RevokeToken(context.Context, *RevokeTokenRequest) (*Token, error)

	// 校验令牌
	ValidateToken(context.Context, *ValidateTokenRequest) (*Token, error)
}
业务具体实现
  1. 如何处理模块间关联关系(面向接口编写)
// 登录: 颁发令牌
// 依赖User模块来检验 用户的密码是否正确
func (i *TokenServiceImpl) IssueToken(
	ctx context.Context,
	in *token.IssueTokenRequest) (
	*token.Token, error) {
	// 1. 确认用户密码是否正确
	req := user.NewQueryUserRequest()
	req.Username = in.Username
	us, err := i.user.QueryUser(ctx, req)
	if err != nil {
		return nil, err
	}
	if len(us.Items) == 0 {
		return nil, fmt.Errorf("用户名或者密码错误")
	}

	// 校验密码是否正确
	if err := us.Items[0].CheckPassword(in.Password); err != nil {
		return nil, err
	}

	// 2. 正确的请求下 就颁发用户令牌

	return nil, nil
}
  1. 颁发Token
/*
	{
	          "user_id": "9",
	          "username": "admin",
	          "access_token": "cmh62ncbajf1m8ddlpa0",
	          "access_token_expired_at": 7200,
	          "refresh_token": "cmh62ncbajf1m8ddlpag",
	          "refresh_token_expired_at": 28800,
	          "created_at": 1705140573,
	          "updated_at": 1705140573,
	          "role": 1
	}
*/
func TestIssueToken(t *testing.T) {
	req := token.NewIssueTokenRequest("admin", "123456")
	req.RemindMe = true
	tk, err := i.IssueToken(ctx, req)
	if err != nil {
		t.Fatal(err)
	}
	t.Log(tk)
}
  1. 撤销令牌
func TestRevokeToken(t *testing.T) {
	// cmlu26du48h27s06e9sg
	req := token.NewRevokeTokenRequest(
		"cmlu26du48h27s06e9sg",
		"cmlu26du48h27s06e9t0",
	)
	tk, err := i.RevokeToken(ctx, req)
	if err != nil {
		t.Fatal(err)
	}

	t.Log(tk)
}
  1. 校验token
// refresh token expired 8666.516636 minutes
/*
{
	"user_id": "7",
	"username": "jack",
	"access_token": "cmjv69du48h4442rkf0g",
	"access_token_expired_at": 604800,
	"refresh_token": "cmjv69du48h4442rkf10",
	"refresh_token_expired_at": 604800,
	"created_at": 1705505573,
	"updated_at": 1705505573,
	"role": 0
	}
*/
func TestValidateToken(t *testing.T) {
	req := token.NewValidateTokenRequest("cmh63mkbajf1o5uh5cb0")
	tk, err := i.ValidateToken(ctx, req)
	if err != nil {
		t.Fatal(err)
	}
	t.Log(tk)
}
  1. 业务异常 业务异常定义和处理
业务API开发

使用Gin做开发API的接口: 接口的状态管理(Cookie)

  • LogIn: 登录,令牌的颁发
      1. Token服务颁发Token
      1. 颁发完成后, 使用SetCookie 通知前端(浏览器), 把cookie设置到本地(前端)
  • LogOut: 登出, 令牌的销毁
      1. Token服务销毁Token
      1. 使用SetCookie 通知前端 从新设置Cookie为""
  1. 定义实现接口对象: TokenApiHandler:
// 来实现对外提供 RESTful 接口
type TokenApiHandler struct {
	svc token.Service
}

// 如何为Handler添加路径, 如果把路由注册给 Http Server
func (h *TokenApiHandler) Registry() {
	// 每个业务模块 都需要往Gin Engine对象注册路由
	r := gin.Default()
	r.POST("/vblog/api/v1/tokens", h.Login)
	r.DELETE("/vblog/api/v1/tokens", h.Logout)
}

// 登录
func (h *TokenApiHandler) Login(ctx *gin.Context) {

}

// 退出
func (h *TokenApiHandler) Logout(ctx *gin.Context) {

}
  1. 设计模块路由: 如何让每个模块的路由不冲突, 每个业务模块,当作一个路由分组: /vblog/api/v1/tokens
  • 前缀: vblog 是服务名称 /oder/ /bill/ /product/ /catalog/
  • 功能: api/ui 为了区分api 还是 ui(前端) api(后端)
  • 资源版本: v1/v2/v3
  • 业务模块名称: tokens, 或者资源名称
root path --> /vblog/api/v1
module path --> /vblog/api/v1/tokens
// 如何为Handler添加路径, 如果把路由注册给 Http Server
// 需要一个Root Router: path prefix: /vblog/api/v1
func (h *TokenApiHandler) Registry(rr gin.IRouter) {
	// 每个业务模块 都需要往Gin Engine对象注册路由
	// r := gin.Default()
	// rr := r.Group("/vblog/api/v1")

	// 模块路径
	// /vblog/api/v1/tokens
	mr := rr.Group(token.AppName)
	mr.POST("tokens", h.Login)
	mr.DELETE("tokens", h.Logout)
}
  1. 接口如果携带请求参数:
  • URL Path: /tokens/xxxx/
  • URL Query String: ?token=xxx&a=1&b=2
  • Header
  • Body
// Body 必须Json
req := token.NewIssueTokenRequest("", "")
if err := c.BindJSON(req); err != nil {
	return
}
  1. 如果规范API请求的数据响应格式

数据响应格式统一

  1. 实现登录与退出
// 登录
func (h *TokenApiHandler) Login(c *gin.Context) {
	// 1. 解析用户请求
	// http 的请求可以放到哪里, 放body, bytes
	// io.ReadAll(c.Request.Body)
	// defer c.Request.Body.Close()
	// json unmarshal json.Unmaral(body, o)

	// Body 必须Json
	req := token.NewIssueTokenRequest("", "")
	if err := c.BindJSON(req); err != nil {
		response.Failed(c, err)
		return
	}

	// 2. 业务逻辑处理
	tk, err := h.svc.IssueToken(c.Request.Context(), req)
	if err != nil {
		response.Failed(c, err)
		return
	}

	// 2.1 set cookie
	c.SetCookie(
		token.TOKEN_COOKIE_KEY,
		tk.AccessToken,
		tk.AccessTokenExpiredAt,
		"/",
		conf.C().Application.Domain,
		false,
		true,
	)

	// 3. 返回处理的结果
	response.Success(c, tk)
}

// 退出
func (h *TokenApiHandler) Logout(c *gin.Context) {
	// 1. 解析用户请求
	// token为了安全 存放在Cookie获取自定义Header中
	accessToken := token.GetAccessTokenFromHttp(c.Request)
	req := token.NewRevokeTokenRequest(accessToken, c.Query("refresh_token"))
	// 2. 业务逻辑处理
	_, err := h.svc.RevokeToken(c.Request.Context(), req)
	if err != nil {
		response.Failed(c, err)
		return
	}

	// 2.1 删除前端的cookie
	c.SetCookie(
		token.TOKEN_COOKIE_KEY,
		"",
		-1,
		"/",
		conf.C().Application.Domain,
		false,
		true,
	)

	// 3. 返回处理的结果
	response.Success(c, "退出成功")
}
组装业务(main)
组装
package main

import (
	"github.com/gin-gonic/gin"
	"gitlab.com/go-course-project/go13/vblog/apps/token/api"
	token_impl "gitlab.com/go-course-project/go13/vblog/apps/token/impl"
	user_impl "gitlab.com/go-course-project/go13/vblog/apps/user/impl"
)

func main() {
	// user service impl
	usvc := user_impl.NewUserServiceImpl()

	// token service impl
	tsvc := token_impl.NewTokenServiceImpl(usvc)

	// api
	TokenApiHander := api.NewTokenApiHandler(tsvc)

	// Protocol
	engine := gin.Default()

	rr := engine.Group("/vblog/api/v1")
	TokenApiHander.Registry(rr)

	// 把Http协议服务器启动起来
	if err := engine.Run(":8080"); err != nil {
		panic(err)
	}
}
启动
PS E:\浏览器下载\mage_course\vblog> go run "e:\浏览器下载\mage_course\vblog\main.go"
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /vblog/api/v1/tokens/     --> gitee.com/baicaijc/vblog/apps/token/api.(*TokenApiHandler).Login-fm (3 handlers)
[GIN-debug] DELETE /vblog/api/v1/tokens/     --> gitee.com/baicaijc/vblog/apps/token/api.(*TokenApiHandler).Logout-fm (3 
handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080
测试

使用 postman进行测试

  1. 登录
POST http://127.0.0.1:8080/vblog/api/v1/tokens

body josn格式
{
    "username": "jack03",
    "password": "123456"
}
{
    "user_id": "7",
    "username": "jack03",
    "access_token": "cmmhhstu48h3c23gdmkg",
    "access_token_expired_at": 7200,
    "refresh_token": "cmmhhstu48h3c23gdml0",
    "refresh_token_expired_at": 28800,
    "created_at": 1705842931,
    "updated_at": 1705842931,
    "role": 0
}

  1. 退出
DELETE http://127.0.0.1:8080/vblog/api/v1/tokens?refresh_token=cmmhhstu48h3c23gdml0

"退出成功"
v2版本

v2版本

Documentation

The Go Gopher

There is no documentation for this package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL