sso单点登陆双token

前端Vue、后端go+jwt 实现sso 双token机制

前端代码:

– Login.vue (登陆页面)
<template>
    <div class="container">
        <el-card style="max-width: 480px">
            <el-form :model="userInfo" style="max-width: 600px">
                <el-form-item>
                    <el-input placeholder="用户名" v-model="userInfo.username"/>
                </el-form-item>
                <el-form-item>
                    <el-input show-password placeholder="密码" v-model="userInfo.password"/>
                </el-form-item>
                <div class="btn">
                    <el-button type="primary" @click="Login" :disabled="isLoggingIn">登陆</el-button>
                    <el-button type="warning">重置</el-button>
                </div>
            </el-form>
        </el-card>
    </div>
</template>

<script lang="ts" setup>
import { onMounted, onBeforeUnmount,ref } from 'vue';
import request from '../utils/request';
import router from '../router';

const userInfo = ref({
    username: "",
    password: "",
});

const isLoggingIn = ref(false); // 新增状态变量,跟踪登录状态

const Login = async () => {
    if (isLoggingIn.value) return; // 如果正在登录,则不再执行
    isLoggingIn.value = true; // 设置为正在登录状态

    try {
        await request({
            url: '/login',
            method: 'post',
            data: {
                username: userInfo.value.username,
                password: userInfo.value.password,
            }
        });
        alert("登陆成功");
        router.push('/home');
    } catch (error: any) {
        console.error('Login failed:', error.response); // 错误处理
    } finally {
        isLoggingIn.value = false; // 重置为未登录状态
    }
};

const keyDown = (e: any) => {
    // 如果是回车则执行登录方法
    if (e.keyCode === 13) {
        Login();
    }
};
	// 创建监听回车事件
onMounted(() => {
    window.addEventListener('keydown', keyDown);
});
    // 挂在完毕移除事件监听器
onBeforeUnmount(() => {
    window.removeEventListener('keydown', keyDown);
});
</script>

<style scoped>
.container {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100vh;
}
.el-form {
    width: 300px !important;
    height: 200px;
    position: relative;
}
.el-form-item {
    margin-top: 40px;
}
.btn {
    position: absolute;
    bottom: 10px;
    display: flex;
    justify-content: space-between;
    width: 260px;
    padding: 0 20px; /* 添加左右边距 */
}
.el-button {
    width: 100px;
}
</style>
– TableList.vue (获取数据页面)
<template>
    <div class="fix">
  <el-table :data="tableData" :border="true" style="width: 100%">
    <el-table-column prop="id" label="序号" width="180" />
    <el-table-column prop="name" label="名称" width="180" />
    <el-table-column prop="price" label="价格" />
    <el-table-column prop="inventory_id" label="库存序号" />
    <el-table-column prop="inventory_count" label="库存" />
  </el-table>
  <div class="btn">
  </div>
  </div>
</template>

<script lang="ts" setup>
import {  onMounted, ref } from 'vue';
import request from '../utils/request'
const tableData = ref([])
const getMsg = async () => {  
    try {
    const res:any = await request({
        method: 'get',
        url: '/goods_list',
    });    
    tableData.value=res.items
}catch (error) {
        console.error('请求失败:', error);
    }
}
onMounted(()=>{
  getMsg()
})
</script>


<style lang='scss' scoped>
.fix {
    position: relative;
    padding-bottom: 60px; /* 预留按钮的高度 */
}
.btn {
    margin-top: 10px;
    position: fixed;
    bottom: 50px;
    right: 473px;
}
</style>
– handleToken.ts (封装一些处理函数)
// 保存token
export function setToken(tokenKey: string | null, token: string) {
    if (tokenKey) { // 确保 tokenKey 不为 null
        localStorage.setItem(tokenKey, token);
    }
}
// 获取accesstoken
export function getAccessToken() {
    return localStorage.getItem('accesstoken')
}

// 获取refreshtoken
export function getRefreshToken() {
    return localStorage.getItem('refreshtoken')
}

// 删除token
export function removeToken(tokenKey: string) {
    return localStorage.removeItem(tokenKey)
}
– refreshToken.ts (刷新Token逻辑)
import request from './request'
import { getRefreshToken } from './handleToken.ts'
import { AxiosRequestConfig } from 'axios';

// 防止重复请求
let promise: Promise<any> | null;

interface CustomAxiosRequestConfig extends AxiosRequestConfig {
    __isRefreshToken?: boolean;
}
// 刷新rtoken
export async function refreshTokenWay() {
    // 如果已经发出请求,后续的请求则不再触发新的
    if (promise) {
        return promise;
    }
    // 获得请求的结果resp
    promise = new Promise(async (resolve) => {
        const resp = await request.post('/refreshToken', {
        }, {
            headers: {
                Authorization: `Bearer ${getRefreshToken()}`,
            },
            __isRefreshToken: true,
        } as CustomAxiosRequestConfig);
        resolve(resp)
    })
    // 清空这个请求
    promise.finally(() => {
        promise = null;
    })
    return promise
}
// 判断是否为请求 /refreshToken
export function isRefreshRequest(config: any) {
    return !!config.__isRefreshToken;
}
– loginerr.ts (刷新失败逻辑)
import router from '../router';
import { removeToken } from './handleToken.ts'

export function logerr() {
    removeToken('accesstoken')
    removeToken('refreshtoken')
    alert('无权限请重新登陆')
    router.push('/login')
}
– request.ts (axios实例封装)
import axios from 'axios';
import { setToken, getAccessToken } from './handleToken.ts'
import { refreshTokenWay, isRefreshRequest } from '../utils/refreToken'
import { logerr } from './loginerr.ts';
// 创建axios实例,设置默认参数
const ins = axios.create({
    baseURL: 'http://127.0.0.1:8088', // 替换为你的 API 基础 URL
    timeout: 10000, // 请求超时时间
    headers: {
        Authorization: `Bearer ${getAccessToken()}`
    }
});
// 创建响应拦截器,针对后端的code值进行请求重试
ins.interceptors.response.use(async (res: any) => {
    // 如果响应中包含了新的authorization,则更新本地accesstoken
    if (res.headers.authorization) {
        const token = res.headers.authorization.replace('Bearer ', '');
        setToken('accesstoken', token);
        ins.defaults.headers.Authorization = `Bearer ${token}`
    }
    // 如果响应中包含了新的refreshToken,则更新本地的refreshToken
    if (res.headers.refreshtoken) {
        const refreshtoken = res.headers.refreshtoken.replace('Bearer ', '');
        setToken('refreshtoken', refreshtoken)
    }
    // 如果返回的响应码是上一个请求没有accessToken,并且本次请求没有刷新refreshToken则
    if (res.data.code === 292 && !isRefreshRequest(res.config)) {
        // 去刷新 /refreshToken
        const resp: any = await refreshTokenWay();
        // 如果token全都刷新成功了
        if (resp.code === 291) {
            // 修改请求头为新的accessToken
            res.config.headers.Authorization = `Bearer ${getAccessToken()}`
            // 重新请求,需要等待请求完成拿到新的响应并返回出来
            const resp = await ins.request(res.config)
            return resp
        } else {
            // 刷新失败则跳转到登陆页。
            logerr()
        }
    } else if (res.data.code === 293 || res.data.code === 294) {
        // 出现其它错误的情况也跳转到登陆页。
        logerr()
    }
    return res.data
})

export default ins;

后端代码:

goods_service grpc客户端
– myjwt.go (jwt认证的逻辑)
package myjwt

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt/v5"
	"testgo.ww.www/tools"
	"time"
)

// 过期时间
const (
	accessTokenExpiration  = 5 * time.Second
	refreshTokenExpiration = 2 * time.Minute
)

var jwtSecretKey = []byte("aaaaaaaabb")

/*
	返回的状态码

CodeTokenRefreshSuccess acesstoken refreshtoken 刷新成功
ErrorCodeAccessTokenExpired acesstoken  不合法
ErrorCodeTokensBothExpired acesstoken refreshtoken 不合法
ErrCreateTokenAccess acesstoken 刷新失败
*/
const (
	CodeTokenRefreshSuccess     = 291
	ErrorCodeAccessTokenExpired = 292
	ErrorCodeTokensBothExpired  = 293
	ErrCreateTokenAccess        = 294
)

type JwtPayLoad struct {
	UserId   int    `json:"userid"`
	UserName string `json:"username"`
}

type CustomClaims struct {
	JwtPayLoad
	jwt.RegisteredClaims
}

// 生成 JWT token

func GenerateTokens(userid int, username string) (string, string, error) {
	// 创建登陆token
	accessClaims := CustomClaims{
		JwtPayLoad: JwtPayLoad{
			UserId:   userid,
			UserName: username,
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(accessTokenExpiration)),
		},
	}
	accessTokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
	accessToken, err := accessTokenObj.SignedString(jwtSecretKey)
	if err != nil {
		return "", "", err
	}

	// 创建刷新token
	refreshClaims := CustomClaims{
		JwtPayLoad: JwtPayLoad{
			UserId:   userid,
			UserName: username,
		},
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(refreshTokenExpiration)),
		},
	}
	refreshTokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
	refreshToken, err := refreshTokenObj.SignedString(jwtSecretKey)
	if err != nil {
		return "", "", err
	}

	return accessToken, refreshToken, nil
}

// 验证 JWT token

func ValidateToken(tokenString string) (*jwt.Token, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return jwtSecretKey, nil
	})
	if err != nil {
		return nil, err
	}
	return token, nil
}

// 刷新Token的处理函数

func RefreshToken(c *gin.Context) {
	// 获取refreshToken
	refreshToken := c.GetHeader("authorization")
	refreshToken = tools.CleanStr(refreshToken)

	//	如果refreshToken也没有,告诉前端跳转到登陆页面
	if refreshToken == "" {
		c.JSON(ErrorCodeTokensBothExpired, gin.H{"code": ErrorCodeTokensBothExpired, "msg": "没有accessToken、refreshToken"})
		return
	}
	// 如果有refreshToken进行验证
	token, err := ValidateToken(refreshToken)

	// 如果报错并且Token不合法,告诉前端
	if err != nil || !token.Valid {
		c.JSON(ErrorCodeTokensBothExpired, gin.H{"code": ErrorCodeTokensBothExpired, "msg": "refreshToken过期了,请重新登陆"})
		return
	}

	// 获取token值生成新的accessToken
	userClaims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		c.JSON(ErrCreateTokenAccess, gin.H{"code": ErrCreateTokenAccess, "msg": "无效的 token 声明"})
		return
	}
	userid := userClaims["userid"].(float64)
	username := userClaims["username"].(string)

	if username == "" {
		c.JSON(ErrCreateTokenAccess, gin.H{"code": ErrCreateTokenAccess, "msg": "无效的token声明,没有解析出用户名"})
		return
	}

	// 如果refreshToken正确,则重新刷新accessToken和refreshToken返回给前端,重新返回给前端存储
	newAccessToken, newRefreshToken, err := GenerateTokens(int(userid), username)
	if err != nil {
		c.JSON(ErrCreateTokenAccess, gin.H{"code": ErrCreateTokenAccess, "msg": "生成新的 accessToken 失败"})
		return
	}
	// 生成新的accessToken和refreshToken,返回给前端存储,进行登陆跳转处理
	// 设置响应头
	c.Header("Authorization", "Bearer "+newAccessToken)
	c.Header("refreshToken", "Bearer "+newRefreshToken)

	c.JSON(CodeTokenRefreshSuccess, gin.H{
		"code": CodeTokenRefreshSuccess,
		"msg":  "token refreshed success",
	})
}

// 中间件验证有没有accessToken

func TokenAuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 获取accessToken
		tokenString := c.GetHeader("Authorization")
		// 如果没有accessToken
		if tokenString == "" {
			c.JSON(ErrorCodeAccessTokenExpired, gin.H{"code": ErrorCodeAccessTokenExpired, "msg": "没有accessToken"})
			c.Abort()
			return
		}
		// 如果有,则解析accessToken是否正确如果不正确,告诉前端去请求/refreshToken

		// 去除 "Bearer " 前缀
		tokenString = tools.CleanStr(tokenString)
		fmt.Println(tokenString)
		token, err := ValidateToken(tokenString)

		if err != nil || !token.Valid {
			fmt.Println(err)
			c.JSON(ErrorCodeAccessTokenExpired, gin.H{"code": ErrorCodeAccessTokenExpired, "error": "accessToken不正确或已过期"})
			c.Abort()
			return
		}
		c.Next()
	}
}
– controller.go (gin的一些路由函数)
package controller

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"google.golang.org/grpc"
	"net/http"
	"strconv"
	"testgo.ww.www/goods_service/Svctype"
	"testgo.ww.www/goods_service/myjwt"
	pb "testgo.ww.www/goods_service/proto"
	pb2 "testgo.ww.www/inventory_service/proto"
)

func GetGoodsCount(inventoryId string) int {
	inventoryClient, conn, err := Svctype.CreateGRPCClient("inventory")
	if err != nil {
		fmt.Println(err)
	}
	// 关闭连接
	defer func(conn *grpc.ClientConn) {
		err := conn.Close()
		if err != nil {
			fmt.Println(err)
		}
	}(conn)

	res, err := inventoryClient.GetInventoryMsg(context.Background(), &pb2.GetInventoryRequest{
		InventoryId: inventoryId,
	})
	if err != nil {
		panic(err)
	}
	return int(res.Count)
}

// 获取单个商品信息和库存数量的处理函数

func GetGoodsAndInventory(c *gin.Context) {
	goodsIdString := c.Query("goods_id")
	if goodsIdString == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "goods_id is required"})
		return
	}
	goodsID, err := strconv.ParseInt(goodsIdString, 10, 64)

	// 获得商品信息
	req := &pb.GetGoodsMsgRequest{Id: goodsID}
	resp, err := (&Svctype.Server{}).GetGoodsMsg(context.Background(), req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	// 获取库存数量
	resp.InventoryCount = int64(GetGoodsCount(resp.InventoryId))

	c.JSON(http.StatusOK, gin.H{
		"goods_id":        resp.Id,
		"name":            resp.Name,
		"price":           resp.Price,
		"inventory_id":    resp.InventoryId,
		"inventory_count": resp.InventoryCount,
	})
}

// 获取所有商品信息和库存数量的处理函数

func GetAllGoodsAndInventory(c *gin.Context) {
	// 获得所有商品信息
	req := &pb.Empty{}
	resp, err := (&Svctype.Server{}).GetGoodsMsgList(context.Background(), req)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	for _, item := range resp.Items {
		inventoryCount := GetGoodsCount(item.InventoryId)
		item.InventoryCount = int64(inventoryCount)
	}
	c.JSON(http.StatusOK, resp)
}

type UserInfo struct {
	UserName string `json:"username"`
	Password string `json:"password"`
}

func Login(c *gin.Context) {
	var userinfo UserInfo
	err2 := c.ShouldBindJSON(&userinfo)
	if err2 != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err2.Error()})
		return
	}
	if userinfo.UserName == "" || userinfo.Password == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Username and password are required"})
		return
	}
	var userRes Svctype.StoredInfo
	err := Svctype.Db.QueryRow("SELECT userid,username,password FROM user WHERE username = ?", userinfo.UserName).Scan(&userRes.UserId, &userRes.Username, &userRes.StoredPassword)
	if err != nil {
		if err.Error() == "sql: no rows in result set" {
			c.JSON(http.StatusUnauthorized, gin.H{"error": "账号或密码不正确"})
			return
		}
		fmt.Println(err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "连接数据库错误"})
		return
	}
	if userRes.StoredPassword != userinfo.Password {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名密码错误"})
	}
	accessToken, refreshToken, err := myjwt.GenerateTokens(userRes.UserId, userRes.Username)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "生成token错误"})
		return
	}
	// 设置响应头
	c.Header("Authorization", "Bearer "+accessToken)
	c.Header("RefreshToken", "Bearer "+refreshToken)

	c.JSON(http.StatusOK, gin.H{
		"status": "login successful",
	})
}
– Svctype (定义的一些模型)
package Svctype

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"
	pb "testgo.ww.www/goods_service/proto"
	pb2 "testgo.ww.www/inventory_service/proto"
	"testgo.ww.www/tools"
)

// 声明数据库及Nacos配置信息
var (
	Db               *sql.DB
	GoodsPort        = 9091
	GoodsSVCName     = "goods"
	GoodsClusterName = "myCluster"
	GoodsGroupName   = "myGroup"
	GoodsWeight      = 10
	GoodsMetadata    = map[string]string{"app": "goods"}
)

// 创建服务类

type Server struct {
	pb.UnimplementedGoodServiceServer
}

// 创建校验类

type ClientTokenAuth struct {
}

// 请求接收类

type StoredInfo struct {
	UserId         int    `json:"userid" db:"userid"`
	Username       string `json:"username" db:"username"`
	StoredPassword string `json:"password" db:"stored_password"`
}

func (c ClientTokenAuth) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
	return map[string]string{
		"appUser":     "user1",
		"appPassword": "passwd1",
	}, nil
}
func (c ClientTokenAuth) RequireTransportSecurity() bool {
	return false
}

// 获取单个商品信息

func (s *Server) GetGoodsMsg(ctx context.Context, req *pb.GetGoodsMsgRequest) (res *pb.GetGoodsMsgResponse, err error) {
	var (
		id          int
		name        string
		price       int
		inventoryId string
	)
	// 查询数据库并保存结果
	err = Db.QueryRow("SELECT id,name,price,inventory_id FROM `goods` WHERE id = ?", req.GetId()).Scan(&id, &name, &price, &inventoryId)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, status.Errorf(codes.NotFound, "Inventory with ID %d not found", req.GetId())
		}
		return nil, status.Errorf(codes.Internal, "Failed to query inventory: %v", err)
	}
	res = &pb.GetGoodsMsgResponse{
		Id:          int64(id),
		Name:        name,
		Price:       int64(price),
		InventoryId: inventoryId,
	}
	return res, nil
}

// 获取所有商品信息

func (s *Server) GetGoodsMsgList(context.Context, *pb.Empty) (res *pb.GetGoodsMsgListResponse, err error) {
	rows, err := Db.Query("SELECT id, name, price, inventory_id FROM `goods`")
	if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to query goods: %v", err)
	}
	defer func(rows *sql.Rows) {
		err := rows.Close()
		if err != nil {
			panic(err)
		}
	}(rows)
	goodsList := &pb.GetGoodsMsgListResponse{
		Items: []*pb.GetGoodsMsgResponse{},
	}
	for rows.Next() {
		var item pb.GetGoodsMsgResponse
		if err := rows.Scan(&item.Id, &item.Name, &item.Price, &item.InventoryId); err != nil {
			return nil, status.Errorf(codes.Internal, "Failed to scan goods row: %v", err)
		}
		goodsList.Items = append(goodsList.Items, &item)
	}
	return goodsList, nil
}

// 初始化数据库连接

func InitDB() (err error) {
	dsn := "dbuser1:NSD2021@tedu.cn@tcp(192.168.40.199:13306)/goods"
	Db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	return nil
}

func CreateGRPCClient(serviceName string) (pb2.InventoryServiceClient, *grpc.ClientConn, error) {
	clientAddr, err := tools.GetNacosSVC(serviceName)
	if err != nil || clientAddr == "" {
		return nil, nil, fmt.Errorf("获取nacos的服务失败: %v", err)
	}
	fmt.Println("获取的nacos服务为:", clientAddr)

	var opts []grpc.DialOption

	opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	opts = append(opts, grpc.WithPerRPCCredentials(new(ClientTokenAuth)))

	conn, err := grpc.NewClient(clientAddr, opts...)
	if err != nil {
		return nil, nil, fmt.Errorf("创建gRPC连接失败: %v", err)
	}

	return pb2.NewInventoryServiceClient(conn), conn, nil
}
– main.go (入口函数)
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	_ "github.com/go-sql-driver/mysql"
	"testgo.ww.www/goods_service/Svctype"
	"testgo.ww.www/goods_service/controller"
	"testgo.ww.www/goods_service/myjwt"
	"testgo.ww.www/tools"
)

// 创建 gRPC 客户端

func main() {
	// 创建数据库连接
	err := Svctype.InitDB()
	if err != nil {
		panic(fmt.Sprintf("连接数据库失败%v", err))
	}
	// 获取本地IP
	goodsSVCIP, err := tools.GetLocalIP()
	if err != nil {
		panic(fmt.Sprintf("获取IP失败%v", err))
	}
	// 注册goods服务到nacos
	err = tools.InitNacosSVC(Svctype.GoodsSVCName, goodsSVCIP, Svctype.GoodsClusterName, Svctype.GoodsGroupName, Svctype.GoodsPort, float64(Svctype.GoodsWeight), Svctype.GoodsMetadata)
	if err != nil {
		panic(fmt.Sprintf("注册NACOS失败%v", err))
	}
	// 创建路由
	r := gin.Default()
	r.Use(tools.Cors())

	// 无需身份验证的路由
	r.POST("/login", controller.Login)
	r.POST("/refreshToken", myjwt.RefreshToken)

	protected := r.Group("/")
    // 使用中间件需要验证token的路由
	protected.Use(myjwt.TokenAuthMiddleware())
	protected.GET("/goods", controller.GetGoodsAndInventory)
	protected.GET("/goods_list", controller.GetAllGoodsAndInventory)

	if err := r.Run(":8088"); err != nil {
		panic(fmt.Sprintf("启动服务失败: %v", err))
	}
}
inventory_service grpc服务端

代码量少,没拆分

—main.go (入口函数)
package main

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
	"net"
	pb "testgo.ww.www/inventory_service/proto"
	"testgo.ww.www/tools"
)

var db *sql.DB

// inventory server
type server struct {
	pb.UnimplementedInventoryServiceServer
}

func initDB() error {
	var err error
	dsn := "user1:passwd1@tcp(192.168.40.199:13306)/inventory"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return err
	}
	return db.Ping()
}
func (s *server) GetInventoryMsg(ctx context.Context, req *pb.GetInventoryRequest) (*pb.GetInventoryResponse, error) {
	// 获取rpc元数据
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return nil, status.Errorf(codes.Unauthenticated, "could not get metadata")
	}
	fmt.Println(md)
	var appUser string
	var appPassword string
	if v, ok := md["xxx"]; ok {
		appUser = v[0]
	}
	if v, ok := md["xxxxx"]; ok {
		appPassword = v[0]
	}

	if appUser != "user1" || appPassword != "passwd1" {
		return nil, status.Errorf(codes.Unauthenticated, "token不正确")
	}

	// 查询数据库
	var inventoryMsg int32
	err := db.QueryRow("SELECT count FROM `inventory` WHERE id = ?", req.GetInventoryId()).Scan(&inventoryMsg)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, status.Errorf(codes.NotFound, "Inventory with ID %v not found", req.GetInventoryId())
		}
		return nil, status.Errorf(codes.Internal, "Failed to query inventory: %v", err)
	}

	return &pb.GetInventoryResponse{
		InventoryId: req.InventoryId,
		Count:       inventoryMsg,
	}, nil
}

var (
	inventoryPort        = 9090
	inventorySVCName     = "inventory"
	inventoryClusterName = "myCluster"
	inventoryGroupName   = "myGroup"
	inventoryWeight      = 10
	inventoryMetadata    = map[string]string{"app": "inventory"}
)

func main() {
	// 初始化数据库
	err := initDB()
	if err != nil {
		panic(fmt.Sprintf("Failed to initialize database: %v", err))
	}
	inventorySVCIP, err := tools.GetLocalIP()
	if err != nil {
		panic(fmt.Sprintf("获取IP地址失败: %v", err))
	}
	// 创建nacos服务实例
	err = tools.InitNacosSVC(inventorySVCName, inventorySVCIP, inventoryClusterName, inventoryGroupName, inventoryPort, float64(inventoryWeight), inventoryMetadata)
	if err != nil {
		panic(err)
	}
	// 开启端口			
	listen, err := net.Listen("tcp", fmt.Sprintf(":%d", inventoryPort))
	if err != nil {
		panic(fmt.Sprintf("Failed to listen on port %d: %v", inventoryPort, err))
	}
	// 创建grpc服务
	grpcServer := grpc.NewServer()
	// 注册服务
	pb.RegisterInventoryServiceServer(grpcServer, &server{})
	// 启动服务
	fmt.Println("Grpc service is start running... ", inventoryPort)
	err = grpcServer.Serve(listen)
	if err != nil {
		fmt.Println(err)
		return
	}
}
tools 工具
– core.go (同源跨域策略)
package tools

import (
	"github.com/gin-gonic/gin"
	"net/http"
)

// 解决前后端跨域

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		method := c.Request.Method
		c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
		c.Header("Access-Control-Allow-Methods", "*")
		c.Header("Access-Control-Allow-Headers", "*")
		c.Header("Access-Control-Expose-Headers", "*")
		c.Header("Access-Control-Allow-Credentials", "true")
		if method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
		}
		c.Next()
	}
}
– getip.go获取IP地址
package tools

import (
	"net"
)

// 获取IP

func GetLocalIP() (string, error) {
	Addresses, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}
	var LocalIP string
	for _, addr := range Addresses {
		if IpNet, ok := addr.(*net.IPNet); ok && !IpNet.IP.IsLoopback() {
			if IpNet.IP.To4() != nil {
				if IpNet.IP.To4()[0] == 192 && IpNet.IP.To4()[1] == 168 {
					LocalIP = IpNet.IP.String()
				}
			}
		}
	}
	return LocalIP, nil
}
– nacos (nacos相关api)
package tools

import (
	"fmt"
	"github.com/nacos-group/nacos-sdk-go/clients"
	"github.com/nacos-group/nacos-sdk-go/clients/naming_client"
	"github.com/nacos-group/nacos-sdk-go/common/constant"
	"github.com/nacos-group/nacos-sdk-go/vo"
)

// 获取nacos客户端

func initConfigClient() (iClient naming_client.INamingClient, err error) {
	sv := []constant.ServerConfig{
		{
			IpAddr:      "192.168.40.180",
			ContextPath: "/nacos",
			Port:        8848,
			Scheme:      "http",
		},
	}

	cc := constant.ClientConfig{
		NamespaceId:         "c0c576b7-1d60-48e9-9b2f-8d440d68c19b", // 如果需要支持多namespace,我们可以创建多个client,它们有不同的NamespaceId。当namespace是public时,此处填空字符串。
		TimeoutMs:           50000,
		NotLoadCacheAtStart: true,
		LogLevel:            "debug",
		Username:            "nacos",
		Password:            "nacos",
	}

	iClient, err = clients.NewNamingClient(
		vo.NacosClientParam{
			ClientConfig:  &cc,
			ServerConfigs: sv,
		},
	)
	if err != nil {
		return nil, err
	}
	return iClient, nil
}

// 初始化nacos并注册服务到nacos

func InitNacosSVC(ServiceName, Ip, ClusterName, GroupName string, Port int, Weight float64, Metadata map[string]string) error {

	iClient, err := initConfigClient()
	if err != nil {
		fmt.Println("初始化Nacos客户端失败了")
		return err
	}
	if iClient == nil {
		fmt.Println("iClient是空的")
		panic("iClient空指针")
	}
	// 注册服务到nacos

	success, err := iClient.RegisterInstance(vo.RegisterInstanceParam{
		Ip:          Ip,
		Port:        uint64(Port),
		ServiceName: ServiceName,
		Weight:      Weight,
		Enable:      true,
		Healthy:     true,
		Ephemeral:   true,
		Metadata:    Metadata,
		ClusterName: ClusterName, // 默认值DEFAULT
		GroupName:   GroupName,   // 默认值DEFAULT_GROUP
	})

	if err != nil {
		return err
	}
	if success {
		fmt.Println("register nacos success")
		return nil
	}
	return err
}

// 获取服务信息

func GetNacosSVC(svcName string) (addr string, err error) {
	iClient, err := initConfigClient()
	if err != nil {
		return "", err
	}
	if iClient == nil {
		fmt.Println("iClient是空的")
		panic("iClient空指针")
	}
	// SelectInstances 为只返回满足这些条件的实例列表:healthy=${HealthyOnly},enable=true 和weight>0
	instances, err := iClient.SelectInstances(vo.SelectInstancesParam{
		ServiceName: svcName,
		GroupName:   "myGroup",             // 默认值DEFAULT_GROUP
		Clusters:    []string{"myCluster"}, // 默认值DEFAULT
		HealthyOnly: true,
	})
	if err != nil {
		fmt.Println("获取实例失败:", err)
	}
	var Addr string
	// 解析实例列表
	for _, instance := range instances {
		Addr = fmt.Sprintf("%s:%d", instance.Ip, instance.Port)
	}
	return Addr, nil
}
– strclean.go (删掉两个token的Bearer前缀)
package tools

import "strings"

func CleanStr(str string) string {
	str = strings.Trim(str, `"`)
	str = strings.TrimPrefix(str, "Bearer ")
	return str
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值