简版抖音项目——项目需求、项目整体设计、Gin 框架使用、视频模块方案设计、用户与鉴权模块方案设计、JWT

文章目录

项目需求

这个项目是字节第3届青训营的大项目,要求实现一个极简版抖音的后端服务,满足基本的抖音功能

具体的功能可以划分为以下几个部分

视频功能

1. 视频Feed流

即视频推送,用户可以刷短视频,新投稿的视频按时间倒序推出

2. 视频投稿

用户登录之后,可以自己上传短视频

3. 视频列表

用户登录之后可以查看自己的投稿视频列表

用户功能

1. 用户注册

新用户可以在抖音平台完成注册

2. 用户登录

抖音平台的已注册用户可以在平台登录

3. 个人主页

用户在登录后可以查看自己的个人主页,及基本信息

点赞功能

1. 点赞操作

已登录用户在刷到自己喜欢的短视频之后可以对当前短视频点赞

2. 取消点赞

已登录用户可以对已经点赞的短视频取消点赞

3. 查看点赞视频列表

已登录用户可以查看已经点赞的视频列表

关注功能

1. 关注用户

已登录用户可以关注其他用户、也可以获取关注列表、获取粉丝列表

2. 取关用户

已登录用户可以取关其他用户

3. 获取关注列表

已登录用户可以获取关注的用户列表

4. 获取粉丝列表

已登录用户可以获取粉丝列表

评论功能

1. 发表评论

已登录用户可以对某个短视频发表评论

2. 删除评论

已登录用户可以删除对某个短视频的评论

3. 查看评论

已登录用户可以查看对某个短视频的评论

项目整体设计

1. 项目介绍

本项目是字节第三届青训营的后端项目,主要是实现极简版抖音的基本功能,包括发布视频、视频列表、用户登录注册、视频feed流、上传视频、评论、用户粉丝、点赞等功能。

2. 实现功能

基础功能

视频feed流、视频投稿、个人信息、用户登录、用户注册

扩展功能1

视频点赞/取消点赞、点赞列表、用户评论/删除评论、视频评论列表

扩展功能2

用户关注/取关、用户关注列表、粉丝列表

3. 技术选型

Gin 框架中文文档
GORM指南
Go语言搬砖 操作minio
在Go语言项目中使用Zap日志库
Go语言配置管理神器------Viper中文教程
golang 使用 FFmpeg 截取视频第一帧实例 | 青训营笔记
Go 教程:从零实现 JWT 认证
「Go工具箱」redis官网推荐的go版本的分布式锁:redsync
Go Web 编程--使用 bcrpyt 哈希用户密码
Go语言操作Redis
gRPC 官方文档中文版
protocol buffers使用指南

Gin 框架中文文档

go 复制代码
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	// 创建带默认中间件(日志与恢复)的 Gin 路由器
	r := gin.Default()

	// 定义简单的 GET 路由
	r.GET("/ping", func(c *gin.Context) {
		// 返回 JSON 响应
		c.JSON(http.StatusOK, gin.H{
			"message": "pong",
		})
	})

	// 默认端口 8080 启动服务器
	// 监听 0.0.0.0:8080(Windows 下为 localhost:8080)
	r.Run()
}

4. 架构设计

整体系统后端采用微服务来完成,结合go语言,选择最简单的grpc框架。总共分为6个微服务server

Gatewaysvr:处理前端请求,聚合后台各个微服务server的处理结果,jwt鉴权,router路由

Commentsvr:评论、删除评论、以及获取评论列表逻辑

Favoritesvr:点赞、取消点赞、以及获取点赞视频的逻辑

Relationsvr:关注、取消关注、以及关注列表的逻辑

Usersvr:用户登录、注册、以及拉取用户信息等与用户相关的逻辑

Videosvr:刷视频、上传视频、视频点赞信息变更等逻辑

所有抖音客户端的请求都是打到gatewaysvr,由gatewaysvr负责路由到下游具体的业务server,再由下游对应的业务server处理完之后将数据返回到gatewaysvr,gatewaysvr再根据需求,看是否需要做下游数据聚合,最终将数据处理成前端所需要的格式返回给抖音客户端

整体架构如下:

gatewaysvr与抖音客户端之间通过http协议进行交互

gatewaysvr与下游各个server之间通过rpc协议交互,这里选择的是protobuf

对go语言使用protobuf不熟悉的可以看下这篇文章 go protobuf入门示例

5. 数据库表设计

Users 表

字段 数据类型 说明
id bigint (主键,自增)用户标识
user_name varchar 用户名
password varchar 密码
follow_count bigint 关注其他用户个数,没有 trigger 根据 relations 表变化
follower_count bigint 粉丝个数,没有 trigger 根据 relations 表变化
total_favorited bigint 用户被喜欢的视频数量,没有 trigger 根据 favorites 表变化
favorite_count bigint 用户喜欢的视频数量,没有 trigger 根据 favorites 表变化
avatar varchar 用户头像
background_image varchar 主页背景

Videos 表

字段 数据类型 说明
id bigint (主键,自增)视频标识
author_id bigint 视频作者 id,外键到 users 表
play_url varchar 视频播放地址
cover_url varchar 视频封面地址
favorite_count bigint 视频的点赞数量,没有 trigger 根据 favorites 表变化
comment_count bigint 视频评论数量,没有 trigger 根据 comments 表变化
publish_time bigint 发布的时间
title varchar 标题

Favorites 表

字段 数据类型 说明
id bigint (主键,自增)点赞 id
user_id bigint 发出点赞请求的用户 id
video_id bigint 点赞的视频 id

Relations 表

字段 数据类型 说明
id bigint (主键,自增)用户关系 id
follow_id bigint 关注的用户 id
follower_id bigint 粉丝用户 id

Comments 表

字段 数据类型 说明
id bigint (主键,自增)评论 id
user_id bigint 评论用户 id
video_id bigint 视频 id
comment varchar 评论内容
time varchar 评论时间

6. 接口文档

所有功能接口与第三版抖音app的http交互协议:抖音后端接口文档

视频模块方案设计

1. 需求分析

视频模块的功能主要都是与视频相关的,主要包括视频feed流,即用户的刷视频功能,还有上传视频,获取视频列表 ,比如用户获取自己点过赞的视频列表或者是获取发布的视频作品

2. 接口设计

接口设计主要是说明videosvr中一些跟视频相关功能联系紧密的rpc接口的设计实现(对protobuf接口不太熟悉可以先阅读go protobuf入门示例

主要功能接口

go 复制代码
// 获取发布的视频列表
rpc GetPublishVideoList(GetPublishVideoListRequest) returns (GetPublishVideoListResponse);
// 获取点赞的视频列表
rpc GetFavoriteVideoList(GetFavoriteVideoListReq) returns (GetFavoriteVideoListRsp);
// 视频feed流
rpc GetFeedList(GetFeedListRequest) returns (GetFeedListResponse);
// 上传视频
rpc PublishVideo(PublishVideoRequest) returns (PublishVideoResponse);

2.1 PublishVideo(视频上传)

已登录用户选择视频上传

接口ProtoBuf:

go 复制代码
// 发布视频请求参数
message PublishVideoRequest {
  int64 userId = 1;          // 用户id
  string saveFile = 2;       // 视频文件保存路径
  string title = 3;          // 视频标题
}

// 发布视频响应参数,为空,只有成功或失败,通过error处理即可
message PublishVideoResponse {

}

2.2 GetFeedList(视频Feed流)

刷视频没有用户登录限制,即这个接口给登录用户和非登录用户都可以提供,不需要做鉴权处理

接口ProtoBuf:

go 复制代码
// 获取视频流请求参数
message GetFeedListRequest {
  int64 currentTime = 1;
  int64 tokenUserId = 2;
}

// 获取视频流返回参数
message GetFeedListResponse {
  repeated VideoInfo video_list = 1;
  int64 nextTime = 2;
}

// videoinfo信息结构
message VideoInfo {
  int64 id = 1;              // 视频唯一标识
  int64 author_id = 2;       // 视频作者信息
  string play_url = 3;       // 视频播放地址
  string cover_url = 4;      // 视频封面地址
  int64 favorite_count = 5;  // 视频的点赞总数
  int64 comment_count = 6;   // 视频的评论总数
  bool is_favorite = 7;      // true-已点赞,false-未点赞
  string title = 8;          // 视频标题
}

2.3 GetPublishVideoList(获取发布的视频作品列表)

已登录用户获取自己发布过的视频列表

接口ProtoBuf:

go 复制代码
// 用户发布的视频列表请求参数
message GetPublishVideoListRequest {
  int64 tokenUserId = 1;
  int64 userID = 2;
}

// 用户发布的视频列表返回参数
message GetPublishVideoListResponse {
  repeated VideoInfo video_list = 1;  // 用户发布的视频列表
}

// videoinfo信息结构
message VideoInfo {
  int64 id = 1;              // 视频唯一标识
  int64 author_id = 2;       // 视频作者信息
  string play_url = 3;       // 视频播放地址
  string cover_url = 4;      // 视频封面地址
  int64 favorite_count = 5;  // 视频的点赞总数
  int64 comment_count = 6;   // 视频的评论总数
  bool is_favorite = 7;      // true-已点赞,false-未点赞
  string title = 8;          // 视频标题
}

2.4 GetFavoriteVideoList(获取点赞的视频列表)

已登录用户获取自己点过赞过的视频列表

接口ProtoBuf:

go 复制代码
// 获取用户点赞视频列表请求参数
message GetFavoriteVideoListReq {
  int64 user_id = 1;
}

// 获取用户点赞视频列表返回参数
message GetFavoriteVideoListRsp {
  repeated VideoInfo video_list = 1;  // 用户收藏的视频列表
}

// videoinfo信息结构
message VideoInfo {
  int64 id = 1;              // 视频唯一标识
  int64 author_id = 2;       // 视频作者信息
  string play_url = 3;       // 视频播放地址
  string cover_url = 4;      // 视频封面地址
  int64 favorite_count = 5;  // 视频的点赞总数
  int64 comment_count = 6;   // 视频的评论总数
  bool is_favorite = 7;      // true-已点赞,false-未点赞
  string title = 8;          // 视频标题
}

3. 详细设计

详细设计模块不仅仅是设计videosvr的各个接口实现,而是以功能划分,跟视频处理相关的功能设计实现

3.1 GetFeedList(视频Feed流)

用户通过客户端传递当前的时间获取20个小于当前时间上传的视频返回并且返回一个nexttime字段,作为下次请求的时间戳,并且在gatewaysvr中通过调用其他微服务获取作者信息,评论数量,点赞数量等信息进行聚合,并组织响应信息,返回响应。

3.2 PublishVideo(视频上传)

已登录用户选择一个短视频进行上传。gatewaysvr 收到视频上传请求后,首先根据 token 中解析出的 userId 进行鉴权。鉴权通过后,网关会解析上传的视频文件,将其暂存到本地【服务端】指定路径,并将该本地文件路径传递给下游的 videosvr。

videosvr 根据该路径找到对应视频文件后,会对视频进行 抽帧 处理以生成视频封面 。随后,videosvr 将 视频文件 与 封面图片 上传到对象存储服务 minio,并生成对应的 视频 URL 与 封面 URL 等元信息。最后,videosvr 将这些视频元信息写入 MySQL。后续客户端获取视频内容时,只需根据返回的 视频 URL 发起请求即可完成播放。

3.3 GetPublishVideoList(获取发布的视频作品列表)

已登录用户可以查看自己已经上传的视频列表。客户端请求中会携带用户的 token,gatewaysvr 对 token 进行鉴权,鉴权通过后表示用户已登录。随后,网关根据用户的 userId 获取该用户发布的视频列表,并通过调用其他微服务获取 作者信息、评论数量、点赞数量 等数据进行聚合,最终组织响应并返回给客户端。

4. 视频存储方案设计

4.1 当前主流的存储方案

单机存储 单机数据库 分布式数据库 分布式存储
文件系统 关系型数据库 关系型数据库 分布式文件系统
key-value存储 非关系型数据库 非关系型数据库 对象存储

4.2 存储选型

要选用一种合适存储方式,首先要明确当下的业务需求,以及存储数据的性质,

  • 第一点:短视频的数据主要是以视频格式为主,这就区别于传统的关系型数据,比如用户信息这种,一般用关系型会拆分出很多字段,每个字段的数据量很小,且相互关联,而视频信息往往一个视频就会作为一个整体

  • 第二点:短视频的存储数据量相较于传统的关系行数据要大的多了,试想一下,一个短视频比一行mysql的信息要大多少?每天每个用户要上传多少的短视频,所以对于短视频的存储可以说是真正的海量存储了

  • 第三点:易用,好的存储能够解放业务,让业务专注于业务逻辑开发

  • 第四点:这么大的存储量,越便宜就越能省下宝贵的经费

4.2.1 分布式存储

结合我们分析的特点再从上述存储方案中做下选型

存储备选 海量支持 适合数据类型 选定与否
单机存储 单机文件/kv
单机数据库 少量(半)结构化数据
分布式数据库 少量(半)结构化数据
分布式存储 大数据计算中间结果/视频图片等

了解了各类存储。单机存储我们先排除,数据量实在是太大了,单机肯定存不下,单机数据库同理

那分布式数据库呢,现代的分布式数据库在容量和弹性上面都有很大进展,是否可以呢?答案也是No,因为分布式数据库只适合存储结构化or半结构化数据。

首先明确一下什么是结构化数据,就是数据和数据之间有一定关系,如用户信息,一个用户会关联包括电话号码、性别等各种维度的信息,这些信息一般都是不超过KB级别的,超过MB级别就不适合使用数据库处理了

唯一的选择就是分布式存储,分布式存储是针对海量存储场景特别设计的存储,能够存储海量的大数据

4.2.2 对象存储

下面再来看一下分布式文件系统和对象存储的选择

还是从海量、易用性、便宜程度三个方面来分析

存储备选 海量 易用 便宜
分布式文件系统HDFS 支持PB->EB海量存储 文件数量受NAME NODE限制 伪Posix文件接口,开发起来略微复杂 视频/图片生态不完善,接入复杂 普通x86服务器,成本低
对象存储TOS 支持PB->EB级别海量存储 对象数量无限制 Restful Http接口,开发简单 视频/图片生态丰富,接入简单 普通x86服务器,成本低

从上述对比可以看到,二者最大的差异在易用性上,主要是体现在对视频/图片生态上,这方面对象存储要优于分布式文件系统存储,所以最终选型时选择对象存储作为短视频最终的存储

4.2.3 HDFS和TOS的接口对比

上面小节对比了在易用性、存储量、以及价格上分布式文件系统和对象存储的差异,这一小节主要来分析一下HDFS接口和TOS接口的一个对比

任何一种存储对于使用者来说其实都是通过接口来接入,接口的易用性以及对功能的支持程度决定了是否好用

下面通过两个图来对比一下HDFS接口和TOS接口

  • Bucket:存储对象的桶,可类比一个云上的Map

  • Object:对象,包含如下三个部分

    • Key:对象的名字,可类比Map的Key

    • Data:对象的内容,例如视频/图片内容

    • MetaData:对象的一些元信息,如对象大小,对象Content-Type,也可以存储自定义元信息

对比分析可以看到,TOS存储使用起来更加方便,要获取某个对象就像使用map一样方便,且可以通过浏览器、app和sdk三种方式访问对象,都是通过http协议完成

4.3 Minio

在明确了存储类型之后,对业界比较主流的存储方案做下对比,选择比较合适的一种方案作为本项目的存储方案

目前业界比较流行的存储框架主要有MinIO、Ceph、SeaweedFS

存储框架 实现语言 开源协议 拓展方式 拓展限制 不宕机拓展 学习运维成本 官方文档
MinIO golang Apache License V2.0 对等扩容;联邦扩容 单机群对等扩容节点不超过32,联邦扩容无限制 联邦集群可支持 详细
Ceph c++ LGPL 向集群内添加节点(Monitor节点和OSD节点均可) 无限制 支持 详细
SeaweedFS golang Apache License V2.0 向集群内添加节点(三类节点均可) 无限制 不同类型节点扩展要求不同 不太详细

由于本项目只是做一个简易版抖音后台,主要考虑框架的实现语言、学习成本以及官方文档的详细程度,所以最终选择minio作为短视频存储与点播基础框架

用户与鉴权模块方案设计

1. 需求分析

用户与鉴权模块是每一个后台系统都必须要有的,其中对每个用户而言需要能够在系统实现注册,登陆,获取用户信息。对于本系统中的其他操作,比如上传视频点赞其他视频则需要登录过的用户才有权限,未登录的用户执行该操作将跳转到用户登录界面

2. 接口设计

接口设计主要是针对usersvr中一些跟用户相关功能联系紧密的rpc接口的设计实现(对protobuf接口不太熟悉可以先阅读go protobuf入门示例

主要功能接口

go 复制代码
// 获取用户信息
rpc GetUserInfo(GetUserInfoRequest) returns (GetUserInfoResponse);
// 注册
rpc Register(RegisterRequest) returns (RegisterResponse);
// 登陆校验
rpc CheckPassWord(CheckPasswordRequest) returns (CheckPasswordResponse);

2.1 Register(用户注册)

新用户在抖音完成注册

接口ProtoBuf:

go 复制代码
// 用户注册请求参数
message RegisterRequest {
  string username = 1;
  string password = 2;
}

// 用户注册返回参数
message RegisterResponse {
  int64 user_id = 1;
  string token = 2;
}

2.2 CheckPassWord(登陆校验)

已完成注册的用户,登陆过程中,usersvr的逻辑

go 复制代码
// 用户登陆请求参数
message CheckPasswordRequest {
  string username = 1;
  string password = 2;
}

// 用户登陆返回参数
message CheckPasswordResponse {
  int64 user_id = 1;
  string token = 3;
}

2.3 GetUserInfo(获取用户基本信息)

已登陆的用户,获取自己的基本信息,包括喜欢的视频列表、发布的视频列表,粉丝数,关注数等等

go 复制代码
// 获取用户基本信息请求参数
message GetUserInfoRequest {
  int64 id = 1;
}

// 获取用户基本信息返回参数
message GetUserInfoResponse {
  UserInfo user_info = 1;
}

// 用户信息
message UserInfo {
  int64 id = 1;              // 用户id
  string name = 2;           // 用户名称
  int64 follow_count = 3;    // 关注总数
  int64 follower_count = 4;  // 粉丝总数
  bool is_follow = 5;        // true-已关注,false-未关注
  string avatar = 6;         // 头像地址
  string background_image = 7;// 背景图片地址
  string signature = 8;      // 个性签名
  int64 total_favorited = 9;
  int64 favorite_count = 10;
}

3. 详细设计

用户登录、注册管理以及权限校验相关的功能的详细设计与实现

3.1 用户注册

新用户主注册功能,现在用户表设计及较简单,一个用户名和密码就可以唯一标识一个用户,所有用户的头像都用的是一样的,都用你默认头像,昵称也没有加入,后续完善功能的时候可以加入

用户提交用户名和密码之后,gatewaysvr将请求转发到usersvr,usersvr处理完注册逻辑后,将返回结果处理成前端所要的json格式返回

3.2 用户登录

已注册用户在客户端发起登录请求,gatewaysvr收到登录请求之后,将请求转发到usersvr做登录逻辑,将usersvr返回的登录结果信息组装成客户端所需要的json格式返回

3.3 获取用户基本信息

已登陆用户在客户端点击个人头像发起获取用户基本信息接口,将请求转发到usersvr做登录逻辑,将usersvr返回的登录结果信息组装成客户端所需要的json格式返回

3.4 用户信息缓存

在上一步的用户信息获取的流程中,是在鉴权通过之后在db里面获取用户信息返回,这里其实只是做了一个流程简化,用户信息是用redis做过一次缓存的,因为用户信息的获取不仅仅只是点击用户头像的时候需要,在获取每个视频信息的时候,每个视频都要有作者信息,在获取视频的关注列表的时候,以及获取每条点赞或者是评论的时候都会涉及到对应的作者信息,就会涉及到用户信息的获取,所以用户信息的读取频次是很高的,可以用redis加一层缓存,加快效率。

3.4.1 缓存策略

用户信息缓存用的哪种缓存方式?

用户信息的缓存使用的是同步模式

  • a. 读取数据:读取用户信息的时候,优先读取redis,当redis中不存在再去读取db,若从db中读到,则将用户信息同步到redis中
  • b. 修改过程:对于用户信息的修改,先直接操作db,保证持久化数据的正确性,db更新成功之后再同步更新redis,如果更新失败,则反复重试几次,依然更新失败的话,就删除redis缓存,变为旁路缓存

旁路缓存(Cache Aside Pattern)是一种最常见的缓存使用方式:缓存不主动参与数据一致性维护,应用程序自己决定什么时候读缓存、什么时候写数据库、什么时候更新/删除缓存。缓存就像"旁边放着的加速器",所以叫旁路。

3.4.2 为什么不直接用旁路缓存,而要采用db,mysql同步更新?

这里用同步缓存主要是保证缓存的一个命中率,让用户信息获取请求尽量的走缓存,因为这个用户信息获取其实是非常高频调用的 ,比如调用视频feed流接口,每次刷视频请求,每个视频都要包含视频的作者信息,同理关注,评论,点赞等信息的获取也要用到这个接口,所以用户信息的读频次很高,另一方面,因为用户信息中包含用户的点赞数和评论数,以及关注数,这就导致在评论,关注,点赞的时候都会修改用户信息,而不仅仅是在用户修改自己的基本信息的时候才会修改,所以用户信息的写频次也很高,每次更新后就删除显然也不是很合理,所以最终选用了这种同步缓存

4. 鉴权设计

在抖音系统上上传视频,评论以及点赞都要是已经在抖音系统完成注册并且登陆过的用户才有权限操作,游客是被拒绝的,所以抖音后台在调用这些功能接口的时候需要判断这次请求是否来自于一个登录用户发起,这就需要在完成这些功能接口之前有一个权限校验的步骤

鉴权的处理逻辑都放在gatewaysvr来完成,由gin框架提供的局部中间件来处理

以发布视频为例

go 复制代码
func PublishRoutes(r *gin.RouterGroup) {
    publish := r.Group("publish")
    {
        publish.POST("/action/", common.AuthMiddleware(), controller.PublishAction)
        publish.GET("/list/", common.AuthWithOutMiddleware(), controller.GetPublishList)
    }
}

在做下游路由之前,先通过 common.AuthMiddleware() 做权限校验,通过之后才会执行 controller.PublishAction 将请求路由到下游 videosvr

本系统鉴权过程通过 jwt 来实现,关于鉴权不太熟悉的同学可以学习一下鉴权相关知识

前端鉴权的兄弟们:cookie、session、token、jwt、单点登录

4.1 token 设计

token 通过 jwt-go 插件来实现,由 3 部分组成,头部 header、载荷 payload 和签证 signature。前两个部分是 JSON 对象,已进行 Base64URL 编码。

  • 声明类型,这里是 jwt

  • 声明加密的算法 通常直接使用 HMAC SHA256

playload

  • iss (issuer):签发人

  • exp (expiration time):过期时间

  • sub (subject):主题

  • aud (audience):受众

  • nbf (Not Before):生效时间

  • iat (Issued At):签发时间

  • jti (JWT ID):编号

signature

Signature 部分是对前两部分的签名,防止数据篡改。需要指定一个密钥(secret),这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照相应的公式产生签名。

token 的验证逻辑是在用户登录的时候,服务器会生成一个 token 返回给客户端,客户端保存起来,后面每次请求的时候携带这个 token,后端会对这个 token 进行验证,验证通过就说明用户是处于登陆状态的,会有点赞,评论等权限;验证不通过则说明用户没登录

关于 token 的操作主要有两个:创建 token 和验证 token,采用的是 jwt-go 来处理

创建 token

go 复制代码
// GenToken 生成token
func GenToken(userid int64, userName string) (string, error) {
    claims := JWTClaims{
        UserId:   userid,
        Username: userName,
        RegisteredClaims: jwt.RegisteredClaims{
            Issuer: "server",
            // ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), 可用于设定token过期时间
        },
    }
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    signedToken, err := token.SignedString([]byte("TikTok"))
    if err != nil {
        return "", err
    }
    return signedToken, nil
}

验证 token

把 token 解析成自定义的 claims(JWTClaims),并验证签名是否正确

go 复制代码
// ParseToken 解析token
func ParseToken(tokenString string) (*JWTClaims, error) {
    token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (i interface{}, err error) {
        return Secret, nil
    })
    if err != nil {
        return nil, err
    }
    if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
        return claims, nil
    }
    return nil, errors.New("invalid token")
}

// 验证token
func VerifyToken(tokenString string) (int64, error) {
    zap.L().Debug("tokenString", zap.String("tokenString", tokenString))

    if tokenString == "" {
        return int64(0), nil
    }

    claims, err := ParseToken(tokenString)
    if err != nil {
        return int64(0), err
    }
    return claims.UserId, nil
}

之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!

相关推荐
浅念-2 小时前
C++ STL stack、queue 与容器适配器详解
开发语言·c++·经验分享·笔记·学习·面试
nix.gnehc2 小时前
深入浅出 Go 内存管理(二):预分配、GC 与内存复用实战
golang
creator_Li2 小时前
Golang的Channel
golang·channel
赵谨言2 小时前
基于Python的汽车CAN总线报文格式转换系统的设计与实现
大数据·开发语言·经验分享·笔记·python
礼拜天没时间.2 小时前
Node.js运维部署实战:从0到1开始搭建Node.js运行环境
linux·运维·后端·centos·node.js·sre
Dragon Wu3 小时前
SpringCache 缓存使用总结
spring boot·后端·spring·缓存·springcloud
三水不滴3 小时前
消息队列消费性能优化:批量消费 + 手动 ACK 提升吞吐量
经验分享·笔记·中间件·性能优化
夜瞬3 小时前
【Flask 框架学习】01:编写第一个 Flask 应用
后端·python·学习·flask
JavaLearnerZGQ3 小时前
Spring Boot 流式响应接口核心组件解析
java·spring boot·后端