1 Star 0 Fork 0

姜春/vlog

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
OSL-3.0

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版本

Open Software License ("OSL") v 3.0 This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: Licensed under the Open Software License version 3.0 1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: a) to reproduce the Original Work in copies, either alone or as part of a collective work; b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; d) to perform the Original Work publicly; and e) to display the Original Work publicly. 2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. 3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. 4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. 5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). 6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. 7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. 8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. 9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). 10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. 11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. 12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. 13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. 14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. 16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.

简介

博客 展开 收起
OSL-3.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Go
1
https://gitee.com/baicaijc/vblog.git
git@gitee.com:baicaijc/vblog.git
baicaijc
vblog
vlog
master

搜索帮助