leveldb存储token的简单实现

在之前的文章中leveldb的grpc接口没有实现ttl,这期加上。

一、改造leveldb的grpc接口支持ttl

这里简单改造,value值多存一个时间值,查的时候比较这个值即可

leveldb.proto

syntax = "proto3";

option go_package = "../src/leveldb;leveldb";
package leveldb;

service LevelDBService {
  rpc Put(PutRequest) returns (PutResponse) {}
  rpc Get(GetRequest) returns (GetResponse) {}
  rpc Has(HasRequest) returns (HasResponse) {}
  rpc Delete(DeleteRequest) returns (DeleteResponse) {}
  rpc GetTTL(GetTTLRequest) returns (GetTTLResponse) {}  // 新增方法
}

message PutRequest {
  string key = 1;
  string value = 2;
  int64 ttl = 3;  // 单位:秒
}

message PutResponse {}

message GetRequest {
  string key = 1;
}

message GetResponse {
  string value = 1;
}

message HasRequest {
  string key = 1;
}

message HasResponse {
  bool exists = 1;
}

message DeleteRequest {
  string key = 1;
}

message DeleteResponse {}

message GetTTLRequest {
  string key = 1;
}

message GetTTLResponse {
  int64 ttl = 1;  // 单位:秒
}

main.go

package main

import (
	"context"
	"encoding/binary"
	"log"
	"net"
	"time"

	pb "gin/src/leveldb" // 替换为实际路径
	leveldb "github.com/syndtr/goleveldb/leveldb"
	"google.golang.org/grpc"
	"google.golang.org/grpc/reflection"
)

// server 结构体嵌入 UnimplementedLevelDBServiceServer
type server struct {
	pb.UnimplementedLevelDBServiceServer
	db *leveldb.DB
}

func (s *server) Put(ctx context.Context, req *pb.PutRequest) (*pb.PutResponse, error) {
	key := []byte(req.GetKey())
	value := []byte(req.GetValue())

	// 处理 TTL
	ttl := time.Duration(req.GetTtl()) * time.Second
	if ttl > 0 {
		// 设置过期时间
		expirationTime := time.Now().Add(ttl).UnixNano()
		expirationData := make([]byte, 8)
		binary.BigEndian.PutUint64(expirationData, uint64(expirationTime))
		value = append(value, expirationData...)
	}

	err := s.db.Put(key, value, nil)
	if err != nil {
		return nil, err
	}
	return &pb.PutResponse{}, nil
}

func (s *server) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
	key := []byte(req.GetKey())

	value, err := s.db.Get(key, nil)
	if err == leveldb.ErrNotFound {
		return &pb.GetResponse{Value: ""}, nil
	} else if err != nil {
		return nil, err
	}

	// 检查过期时间
	if len(value) > 8 {
		expirationTime := int64(binary.BigEndian.Uint64(value[len(value)-8:]))
		if time.Now().UnixNano() > expirationTime {
			return &pb.GetResponse{Value: ""}, nil
		}
		value = value[:len(value)-8]
	}

	return &pb.GetResponse{Value: string(value)}, nil
}

func (s *server) Has(ctx context.Context, req *pb.HasRequest) (*pb.HasResponse, error) {
	key := []byte(req.GetKey())

	value, err := s.db.Get(key, nil)
	if err == leveldb.ErrNotFound {
		return &pb.HasResponse{Exists: false}, nil
	} else if err != nil {
		return nil, err
	}

	// 检查过期时间
	if len(value) > 8 {
		expirationTime := int64(binary.BigEndian.Uint64(value[len(value)-8:]))
		if time.Now().UnixNano() > expirationTime {
			return &pb.HasResponse{Exists: false}, nil
		}
	}

	return &pb.HasResponse{Exists: true}, nil
}

func (s *server) Delete(ctx context.Context, req *pb.DeleteRequest) (*pb.DeleteResponse, error) {
	key := []byte(req.GetKey())
	err := s.db.Delete(key, nil)
	if err != nil {
		return nil, err
	}
	return &pb.DeleteResponse{}, nil
}

func (s *server) GetTTL(ctx context.Context, req *pb.GetTTLRequest) (*pb.GetTTLResponse, error) {
	key := []byte(req.GetKey())

	value, err := s.db.Get(key, nil)
	if err == leveldb.ErrNotFound {
		return &pb.GetTTLResponse{Ttl: -1}, nil // 返回 -1 表示键不存在
	} else if err != nil {
		return nil, err
	}

	// 检查过期时间
	if len(value) > 8 {
		expirationTime := int64(binary.BigEndian.Uint64(value[len(value)-8:]))
		currentTime := time.Now().UnixNano()
		if currentTime > expirationTime {
			return &pb.GetTTLResponse{Ttl: 0}, nil // 返回 0 表示已过期
		}
		remainingTTL := (expirationTime - currentTime) / int64(time.Second)
		return &pb.GetTTLResponse{Ttl: remainingTTL}, nil
	}

	return &pb.GetTTLResponse{Ttl: -1}, nil // 返回 -1 表示没有设置 TTL
}

func main() {
	// 打开或创建一个新的LevelDB数据库
	dbPath := "./data/leveldb/"
	db, err := leveldb.OpenFile(dbPath, nil)
	if err != nil {
		log.Fatalf("Failed to open database: %v", err)
	}
	defer db.Close()

	lis, err := net.Listen("tcp", ":3051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	pb.RegisterLevelDBServiceServer(s, &server{db: db})

	// 启用服务器反射
	reflection.Register(s)

	// 在启动时输出一条日志信息
	log.Println("gRPC server started on :3051")

	if err := s.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

其他代码不变。

二、实现token认证工具类

leveldbTokenOperator.go

package middleware

import "time"

import (
	"context"
	"encoding/json"
	"gin-epg/src/leveldb" // 假设 gRPC 服务的 proto 文件已经编译并生成了 Go 代码
	"github.com/google/uuid"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const (
	TokenPrefixKey         = "token:"
	UserPrefixKey          = "user:"
	SingleUserMaxTokenSize = 10
	defaultExpirationTime  = 1209600 // 2 weeks in seconds
)

type LeveldbSimpleTokenOperator struct {
	expirationTimeInSecond int64
	client                 leveldb.LevelDBServiceClient
}

func NewLeveldbSimpleTokenOperator(addr string, expirationTimeInSecond ...int64) (*LeveldbSimpleTokenOperator, error) {
	conn, err := grpc.Dial(addr, grpc.WithInsecure())
	if err != nil {
		return nil, err
	}
	expiration := defaultExpirationTime
	if len(expirationTimeInSecond) > 0 {
		expiration = int(expirationTimeInSecond[0])
	}
	client := leveldb.NewLevelDBServiceClient(conn)
	return &LeveldbSimpleTokenOperator{
		expirationTimeInSecond: int64(expiration),
		client:                 client,
	}, nil
}

// getUserMapFromToken 从 token 中获取 UserMap
func (r *LeveldbSimpleTokenOperator) getUserMapFromToken(token string) (map[string]interface{}, error) {
	key := TokenPrefixKey + token
	resp, err := r.client.Get(context.Background(), &leveldb.GetRequest{Key: key})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil, nil
		}
		return nil, err
	}
	var userMap map[string]interface{}
	err = json.Unmarshal([]byte(resp.Value), &userMap)
	if err != nil {
		return nil, err
	}
	return userMap, nil
}

// getExpirationDateFromToken 从 token 中获取过期日
func (r *LeveldbSimpleTokenOperator) getExpirationDateFromToken(token string) (time.Time, error) {
	resp, err := r.client.GetTTL(context.Background(), &leveldb.GetTTLRequest{Key: token})
	if err != nil {
		return time.Time{}, err
	}
	return time.Now().Add(time.Duration(resp.Ttl) * time.Second), nil
}

// isTokenExpired 判断 token 是否过期
func (r *LeveldbSimpleTokenOperator) isTokenExpired(token string) (bool, error) {
	key := TokenPrefixKey + token
	resp, err := r.client.Has(context.Background(), &leveldb.HasRequest{Key: key})
	if err != nil {
		return false, err
	}
	return !resp.Exists, nil
}

// generateToken 生成 token
func (r *LeveldbSimpleTokenOperator) generateToken(userMap map[string]interface{}) (string, error) {
	token := uuid.New().String()
	key := TokenPrefixKey + token
	value, err := json.Marshal(userMap)
	if err != nil {
		return "", err
	}
	_, err = r.client.Put(context.Background(), &leveldb.PutRequest{Key: key, Value: string(value), Ttl: r.expirationTimeInSecond})
	if err != nil {
		return "", err
	}
	return token, nil
}

// validateToken 判断 token 是否有效
func (r *LeveldbSimpleTokenOperator) validateToken(token string) (bool, error) {
	isExpired, err := r.isTokenExpired(token)
	if err != nil {
		return false, err
	}
	return !isExpired, nil
}

gin中间件封装

middlewareUtil.go

package middleware

import (
	"fmt"
	"gin-epg/src/common/util"
	"github.com/gin-gonic/gin"
	"net/http"
	"strings"
)

// TokenMiddleware 是一个用于校验 token 的中间件
func TokenMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		var token string

		// 尝试从 Authorization 请求头获取 token
		authHeader := c.GetHeader("Authorization")
		if authHeader != "" {
			// 按空格分割
			parts := strings.SplitN(authHeader, " ", 2)
			if len(parts) == 2 && parts[0] == "Bearer" {
				token = parts[1]
			} else {
				c.JSON(http.StatusBadRequest, gin.H{
					"code": 2004,
					"msg":  "请求头中auth格式有误",
				})
				c.Abort()
				return
			}
		} else {
			// 如果 Authorization 头不存在,尝试从 token 请求头获取 token
			token = c.GetHeader("token")
			if token == "" {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供 token"})
				c.Abort()
				return
			}
		}

		// 使用解析JWT的函数来解析 token
		claims, err := ParseToken(token)
		if err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 2005,
				"msg":  "无效的Token",
			})
			c.Abort()
			return
		}

		// 将用户信息设置到请求上下文中
		c.Set("id", claims["id"])
		c.Set("username", claims["username"])
		c.Set("role", claims["role"])
		c.Set("avatarUrl", claims["avatarUrl"])
		c.Next() // 后续的处理函数可以通过c.Get("username")等来获取当前请求的用户信息
	}
}

// ParseToken 解析token并返回用户信息
func ParseToken(s string) (map[string]interface{}, error) {
	// 拼接 fileDownloadUrl 和 filePath
	addr := "localhost:3051"
	configAddr, err := util.GetConfigValue("leveldbRpcUrl")
	if err == nil {
		addr = configAddr.(string) // 进行类型断言
	}
	operator, err := NewLeveldbSimpleTokenOperator(addr)
	if err != nil {
		return nil, err
	}
	claims, err := operator.getUserMapFromToken(s)
	if err != nil {
		return nil, err
	}
	return claims, nil
}

// GenerateToken 生成Token
func GenerateToken(userMap map[string]interface{}) (string, error) {
	// 获取配置中的地址
	addr := "localhost:3051"
	configAddr, err := util.GetConfigValue("leveldbRpcUrl")
	if err == nil {
		addr = configAddr.(string) // 进行类型断言
	}

	// 创建LevelDB Token操作器
	operator, err := NewLeveldbSimpleTokenOperator(addr)
	if err != nil {
		return "", fmt.Errorf("failed to create LevelDB token operator: %w", err)
	}

	// 生成Token
	claims, err := operator.generateToken(userMap)
	if err != nil {
		return "", fmt.Errorf("failed to generate token: %w", err)
	}

	return claims, nil
}

登录时生成token调用示例

package controller

import (
	"crypto/md5"
	"fmt"
	"gin-epg/src/entity"
	"gin-epg/src/middleware"
	"gin-epg/src/service"
	"github.com/gin-gonic/gin"
	"net/http"
)

// RestResponse 响应结构体
type RestResponse struct {
	Status  int         `json:"status"`
	Result  interface{} `json:"result,omitempty"`
	Message string      `json:"message,omitempty"`
}

// LoginData 登录数据结构体
type LoginData struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

// UserInfo 用户信息结构体
type UserInfo struct {
	Token string       `json:"token"`
	User  *entity.User `json:"user"`
}

// doLogin 登录处理函数
func DoLogin(c *gin.Context) {
	var loginData LoginData
	if err := c.ShouldBindJSON(&loginData); err != nil {
		c.JSON(http.StatusBadRequest, RestResponse{Status: 400, Message: "请求参数错误"})
		return
	}

	username := loginData.Username
	password := loginData.Password

	loginUser, err := service.FindUserByName(username)
	if err != nil || loginUser == nil {
		c.JSON(http.StatusUnauthorized, RestResponse{Status: 401, Message: "用户不存在"})
		return
	}

	dbPassword := loginUser.Password
	encryptedText := MD5(password)

	if dbPassword != encryptedText {
		c.JSON(http.StatusUnauthorized, RestResponse{Status: 401, Message: "登录失败"})
		return
	}

	userInfoClaims := map[string]interface{}{
		"id":        loginUser.ID,
		"username":  loginUser.Username,
		"role":      "user",
		"avatarUrl": loginUser.AvatarURL,
	}

	token, err := middleware.GenerateToken(userInfoClaims)
	if err != nil {
		c.JSON(http.StatusInternalServerError, RestResponse{Status: 500, Message: "生成Token失败"})
		return
	}

	userInfo := UserInfo{
		Token: token,
		User:  loginUser,
	}

	c.JSON(http.StatusOK, RestResponse{Status: 200, Result: userInfo})
}

func MD5(str string) string {
	data := []byte(str) //切片
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%x", has) //将[]byte转成16进制
	return md5str
}

调用接口校验token示例

单个接口使用

epgChannelGroup.GET("/deleteChannelByName", middleware.TokenMiddleware(), controller.DeleteEpgChannelByName)

group使用

epgChannelGroup.Use(middleware.TokenMiddleware())


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915776.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

neo4j desktop基本入门

下载安装不在赘述,本文只记述一些neo4j的基本入门操作 连接本地neo4j数据库 1. 点击ADD添加连接 端口一般是7687 账户名和密码忘记了,可以通过neo4j web(默认为neo4jneo4j://localhost:7687/neo4j - Neo4j Browser)重置密码 AL…

ElasticSearch的Python Client测试

一、Python环境准备 1、下载Python安装包并安装 https://www.python.org/ftp/python/3.13.0/python-3.13.0-amd64.exe 2、安装 SDK 参考ES官方文档: https://www.elastic.co/guide/en/elasticsearch/client/index.html python -m pip install elasticsearch一、Client 代…

强化学习入门笔记(Reinforcement Learning,RL) 强推!

由于本人的近期研究方向涉及到强化学习,本科时已经学习过了,但是感觉还是有些概念和算法没有学懂学透,所以想重新系统性的学习一下,记录了整个学习过程,而且对当时没有理解不是特别深刻的内容有了一些更加深刻的理解&a…

redis 原理篇 26 网络模型 Redis是单线程的吗?为什么使用单线程

都是学cs的,有人月薪几万,有人月薪几千,哎, 相信 边际效用, 也就是说, 随着技术提升的越来越多,薪资的提升比例会更大 一个月几万,那肯定是高级开发了, 一个月几千&…

UE4 Cook 从UAT传递参数给UE4Editor

需求 一句Cook的命令如下: ${EnginePath}/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun -project${ClientPath}/${ProjectName}.uproject -noP4 -platformIOS -cooksinglepackage -client -clientconfig${CookConfig} -iterate -skipbuild -nocompile -NoMutex…

jmeter基础05_第1个http请求

本节课使用网站“httpbin.org”进行基础的http请求全流程。 请求获取httpbin.org的首页: 请求方法:GET URL:http://httpbin.org 参数:无 1、操作步骤 ① 打开jmeter:命令行窗口输入“jmeter”并回车。 ② 添加线程组…

【Ubuntu24.04】从双系统到虚拟机再到单系统的故事

故事 在大学前期,我使用Ubuntu系统都是为了学习一些命令或者其它Linux的东西,对性能的要求不高,所以选择了虚拟机,后来为了做毕设,选择安装了Ubuntu20.04双系统,因为虚拟机实在带不动,那时我的主…

力扣 LeetCode 18. 四数之和(Day3:哈希表)

解题思路: 需要先弄懂三数之和,思路类似 三数之和:指针 i ,left ,right 四数之和:指针 k ,i ,left ,right(相当于多了一个 k ,多了一个外层 fo…

30 秒!用通义灵码画 SpaceX 星链发射流程图

不想读前人“骨灰级”代码, 不想当“牛马”程序员, 想像看图片一样快速读复杂代码和架构? 来了,灵码又加新 buff!! 通义灵码支持代码逻辑可视化, 可以把你的每段代码画成流程图。 你可以把…

sql注入之二次注入(sqlilabs-less24)

二阶注入(Second-Order Injection)是一种特殊的 SQL 注入攻击,通常发生在用户输入的数据首先被存储在数据库中,然后在后续的操作中被使用时,触发了注入漏洞。与传统的 SQL 注入(直接注入)不同&a…

Warped Universe游戏即将在Sui上推出,为玩家提供多样化的游戏体验

Warped Games选择Sui作为其即将推出的创新多类型游戏Warped Universe的首选Web3技术。Warped Universe让玩家可以体验第三视角实时动作、回合制策略和基地建设等玩法。该游戏使用Unreal Engine 5开发,将借助Sui的技术使玩家能够拥有、交易和变现其游戏内资产。 War…

显示微服务间feign调用的日志

第一步 package com.niuniu.common.config;import com.niuniu.common.CommonConstant; import com.niuniu.common.utils.UserContext; import feign.Logger; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.…

nginx部署H5端程序与PC端进行区分及代理多个项目及H5内页面刷新出现404问题。

在项目中会碰见需要在nginx代理多个项目,如果在加上uniapp开发的H5端的项目,你还要在nginx中区分PC端和手机H5端,这就会让人很头大!网上大部分的资料都是采用在nginx的conf配置文件中添加区分pc和手机端的变量例如:set…

redis 原理篇 31 redis内存回收 内存淘汰策略

哦哦, 内存满了咋搞 就算过期key 删除,还是不够用, 这种问题没办法,只能了解一下啥解决方案了, 内存是有限的,一直存,肯定会满,这时,咋处理? 首先&#xff…

C++《继承》

在之前学习学习C类和对象时我们就初步了解到了C当中有三大特性,分别是封装、继承、多态,通过之前的学习我们已经了解了C的封装特性,那么接下来我们将继续学习另外的两大特性,在此将分为两个章节来分别讲解继承和多态。本篇就先来学…

[C++11] 包装器 : function 与 bind 的原理及使用

文章目录 functionstd::function 的基本语法使用 std::function 包装不同的可调用对象function包装普通成员函数为什么要传入 this 指针参数?传入对象指针与传入对象实例的区别 例题 :150. 逆波兰表达式求值 - ⼒扣(LeetCode) bin…

设计模式(主要的五种)

1.设计模式: 设计模式就是代码设计经验 2.设计模式的类型: 分为三大类:创建型模式,结构型模式,行为模式 创建型模式: 单例模式:某个类只能有一个实例,提供一个全局的访问点。 工…

Wireshark中的length栏位

注:Ethernet II的最小data length为46,如果小于,会补全到46. 1.指定网卡抓取的,链路为ethernet。 IPv4 Ethernet II 长度为 14 bytes - L1ipv4 header中的length包括header和payload的总长度 - L2wireshark中length表示抓取的pac…

Java线程池浅谈(创建线程池及线程池任务处理)

1-认识线程池 什么是线程池? 线程池就是一个可以复用线程的技术。 不使用线程池的问题 比方说淘宝,不使用线程池,现在有一亿个线程同时进来,CPU就爆了。用户每发起一个请求,后台就需要创建一个新线程来处理&#xf…

缓冲区溢出,数据被踩的案例学习

继续在ubuntu上学习GDB,今天要学习的是缓冲区溢出。 程序的地址: GitHub - gedulab/gebypass: bypass password by heap buffer overflow 编译的方法: gcc -g -O2 -o gebypass gebypass.c 照例设置一下科学shangwang代理: e…