前端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
}