[golang gin框架] 36.Gin 商城项目-RESTful API 设计指南,允许Cros跨域 ,提供api接口实现前后端分离,以及JWT的使用

news2024/11/19 19:38:47

一.RESTful API 设计指南

请看:Restful API 的接口规范

二.Gin 中配置服务器端允许跨域

github官方地址: https://github.com/gin-contrib/cors
main.go文件中配置跨域请求

代码如下:

在使用cors时,需要 引入该插件,先:
import (
"github.com/gin-contrib/cors"
)
然后在main.go下运行命令 : go mod tidy, 即可
package main

import (
    "fmt"
    "github.com/gin-contrib/sessions"
    _ "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-contrib/sessions/redis"
    "github.com/gin-gonic/gin"
    "gopkg.in/ini.v1"
    "goshop/models"
    "goshop/routers"
    "html/template"
    "github.com/gin-contrib/cors"
    "os"
    "path/filepath"
    "strings"
    "time"
)

func main() {
    //初始化路由,会设置默认中间件:engine.Use(Logger(), Recovery()),可以使用gin.New()来设置路由
    r := gin.Default()
    //配置gin允许跨域请求
    //默认配置
    //r.Use(cors.Default())
    r.Use(cors.New(cors.Config{  //自定义配置
        AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},  //允许的方法
        AllowHeaders:     []string{"Origin", "Content-Length", "Content-Type", "Authorization"},  //header允许山高月小
        AllowCredentials: false,
        MaxAge:           12 * time.Hour,  //有效时间
        ExposeHeaders:    []string{"Content-Length"},
        AllowOriginFunc: func(origin string) bool {  //允许的域
            return true  //所有
        },
    }))
    r.run()
}

三.api接口实现前后端分离

在前后端分离模式下的项目,需要使用api接口来实现数据的交互,一般接口使用 RESTful API模式设计,当前端请求接口时,有时候会发生跨域问题,需要服务器方面进行跨域配置(见 二.Gin 中配置服务器端允许跨域),下面上案例说明:
  1. 路由

配置 路由时,可以项目迭代,存在多个版本的api,格式如下:
package routers

import (
    "goshop/controllers/api"
    "github.com/gin-gonic/gin"
)

//设置api路由
func ApiRoutersInit(r *gin.Engine) {
    //多版本api
    apiRouters := r.Group("/v1")
    {
        //获取导航列表
        apiRouters.GET("/navList", api.NavController{}.Navlist)
        //登录操作
        apiRouters.POST("/doLogin", api.UserController{}.DoLogin)
        //编辑文章操作
        apiRouters.PUT("/editArticle", api.ArticleController{}.EditArticle)
        //删除评论操作
        apiRouters.DELETE("/deleteComment", api.CommentController{}.DeleteComment)
    }

    api2Routers := r.Group("/v2")
    {
        //获取导航列表
        api2Routers.GET("/navList", api.NavController{}.Navlist)
        //登录操作
        api2Routers.POST("/doLogin", api.UserController{}.DoLogin)
        //编辑文章操作
        api2Routers.PUT("/editArticle", api.ArticleController{}.EditArticle)
        //删除评论操作
        api2Routers.DELETE("/deleteComment", api.CommentController{}.DeleteComment)
   }
}
  1. 控制器代码

各个不同的功能模块可以在controllers/api/下创建不同的控制器,比如:
导航相关api: 在controllers/api/下创建NavController.go控制器,里面存放导航相关api(获取顶部导航,中部导航...)
用户相关api: 在controllers/api/下创建UserController.go控制器,里面存放用户相关api(获取用户信息,登录,登出,...)
文章相关api: 在controllers/api/下创建ArticleController.go控制器,里面存放文章相关api(获取文章列表,文章详情,...)
评论相关api: 在controllers/api/下创建CommentController.go控制器,里面存放用户评论相关api(获取用户评论列表,增加评论,删除评论,...)
下面就增删改查各举一个案例:

导航相关控制器

获取导航列表
package api

import (
    "encoding/json"
    "goshop/models"
    "github.com/gin-gonic/gin"
    "net/http"
)

type V1Controller struct{}

//获取导航列表
func (con V1Controller) Navlist(c *gin.Context) {
    navList := []models.Nav{}
    models.DB.Find(&navList)
    c.JSON(http.StatusOK, gin.H{
        "navList": navList,
    })
}

返回的json数据如下:

{
    "navList": [
        {
            "id": 1,
            "title": "商城1",
            "link": "http://www.xxx.com",
            "position": 2,
            "is_opennew": 2,
            "relation": "36,35",
            "sort": 10,
            "status": 1,
            "add_time": 1592919226,
            "goods_items": null
       },
       ...
    ]
}

用户相关控制器

用户登录操作:
POST方式
注意: api请求有两种请求格式:
1. form-data 表单格式,服务器需使用 c.PostForm获取数据
2. Content-Type: application/json格式,服务器需使用 c.GetRawData()获取数据
案例如下:
//api 当前端发送请求类型为:Content-Type: application/json,时,c.PostForm没法获取,需要通过c.GetRawData() 获取
//Content-Type: application/json; 发过来的数据需要通过c.GetRawData() 获取
//用户相关结构体: 在实际项目中,可以在models下创建相关结构体
type UserInfo struct {
    Username string `form:"username" json:"username"`
    Password string `form:"password" json:"password"`
}

//Content-Type: application/json; 发过来的数据需要通过c.GetRawData() 获取
func (con V1Controller) DoLogin(c *gin.Context) {
    var userInfo UserInfo
    b, _ := c.GetRawData() //从 c.Request.Body 读取请求数据
    err := json.Unmarshal(b, &userInfo)
    if err != nil {
        c.JSON(200, gin.H{
            "err": err.Error(),
        })
    } else {
        c.JSON(200, gin.H{
            "userInfo": userInfo,
        })
    }
}

//form-data 表单格式,服务器需使用c.PostForm获取数据
func (con V1Controller) DoLoginPost(c *gin.Context) {
    //实例化user结构体
    userInfo := models.User{}
    //获取请求的数据
    username:= c.PostForm("username")
    if username == "" {
        c.JSON(200, gin.H{
            "err": "用户名不能为空",
        })
    } else {
        c.JSON(200, gin.H{
            "username": username,
        })
    }
}

文章相关控制器

修改文章数据:
//文章结构体: 在项目中可以在models下面创建结构体
type Article struct {
    Title   string `form:"title" json:"title"`
    Content string `form:"content" json:"content"`
}

//编辑
//Content-Type: application/json,发过来的数据需要通过c.GetRawData() 获取
func (con V1Controller) EditArticle(c *gin.Context) {
    var article Article
    b, _ := c.GetRawData() //从 c.Request.Body 读取请求数据
    err := json.Unmarshal(b, &article)
    if err != nil {
        c.JSON(200, gin.H{
            "err": err.Error(),
        })
    } else {
        c.JSON(200, gin.H{
            "article": article,
        })
    }
}

评论相关控制器

删除相关评论
//删除
func (con V1Controller) DeleteNav(c *gin.Context) {
    id := c.Query("id")
    //执行删除逻辑操作
    c.JSON(200, gin.H{
        "message": "删除数据成功",
        "id":      id,
    })
}

四.JWT接口权限验证

  1. 关于接口的安全验证

关于接口安全验证的解决方案有很多:
  • 可以用 Session 来实现安全验证

  • 对请求接口的参数进行签名,来实现接口的签名验证

  • 使用 JWT 实现接口的验证

  • ...

  1. 基于 Session 的安全验证

Session 存储在服务器,用户用户比较少的话是一种简单的安全验证机制,但是涉及到跨域的话需要进行一些配置,用户量非常非常大的话会耗费一定的服务器资源,关于 cookie 和 session 跨域可以参考: 解决vue请求gin框架接口cros跨域cookie和session失效的问题
  1. 对请求参数进行加密的签名验证

涉及公钥、私钥、签名等,比如支付相关功能接口
  1. JWT

JWT 全称 JSON Web Token,是目前比较流行的另一种跨域身份验证解决方案。也是被很多人用坏的一种安全验证机制
  1. Golang 中使用 JWT 实现接口的安全验证

这里使用 https://github.com/dgrijalva/jwt-go模块,使用步骤如下 :

(1).下载引入模块

import 中引入 github.com/dgrijalva/jwt-go,然后在main.go目录下运行go mod tidy 即可
import ( 
    "fmt"
    "strings"
    "time"
    "github.com/gin-gonic/gin"
    "github.com/dgrijalva/jwt-go"
)

(2).生成 Jwt Token

1).自定义一个结构体

首先需要 自定义一个结构体,这个结构体需要继承 jwt.StandardClaims 结构体,这个结构体也可以 自定义结构体属性,自定义的属性用于 Jwt 传值
type MyClaims struct {
    Uid int
    jwt.StandardClaims
}

2).定义生成结构体的私钥 key 以及过期时间

var jwtKey = []byte("123456")
var expireTime = time.Now().Add(24 * time.Hour).Unix()

3).实例化自定义的结构体,创建 token

myClaimsObj := MyClaims{
    12, // 生成 token 的时候传值
    jwt.StandardClaims{
        ExpiresAt: expireTime, Issuer: "userinfo", // 签发人
}, }
// 使用指定的签名方法创建签名对象
tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsObj)
// 使用指定的 secret 签名并获得完整的编码后的字符串 token
tokenStr, _ := tokenObj.SignedString(jwtkey)

(3).验证 Jwt Token

1).Gin 中获取客户端穿过来的值 token 值

注意:
1.服务器生成的token传给客户端后,客户端保存在 Authorization
2.服务端生成的token校验方法使用的是 OAuth2.0,故客户端请求服务器时,Authorization的TYPE应该为OAuth2.0
tokenData := c.Request.Header.Get("Authorization")
tokenString := strings.Split(tokenData, " ")[1]

2).服务端定义一个方法验证 token

func ParseToken(tokenString string) (*jwt.Token, *MyClaims, error) {
    s := &MyClaims{}
    token, err := jwt.ParseWithClaims(tokenString, s, func(token *jwt.Token)(i interface{}, err error) {
    return jwtkey, nil
})

gin.Info(token, s)
return token, s, err
}

3).验证完整代码

tokenData := c.Ctx.Input.Header("Authorization")
tokenString := strings.Split(tokenData, " ")[1]
if tokenString == "" {
    fmt.Println("权限不足")
} else {
    token, claims, err := ParseToken(tokenString)
if err != nil || !token.Valid {
    fmt.Println("权限不足")
} else {
    fmt.Println("验证通过")
    fmt.Println(claims.Uid)
}
}
  1. 具体使用JWT案例

以用户登录后,获取收货地址为例(在这里为了方便,请求都以GET方式),具体步骤:
1.客户端请求路由login,获取服务端生成的token并保存到 Authorization
2.客户端请求路由addressList,获取用户收货地址; 注意:客户端一定要把 Authorization 传给服务端校验,并且TYPE= OAuth2.0

(1).路由

在routers/apiRouters.go下增加以下路由
//登录操作(生成token)
apiRouters.GET("/login", api.UserController{}.Login)
//获取收货地址(校验token)
apiRouters.GET("/addressList", api.UserController{}.AddressList)

(2).服务端生成token

在models文件下,创建MyClaims.go,封装一个MyClaims结构体,创建方法:设置token,获取token的方法
package models

import (
    "github.com/dgrijalva/jwt-go"
    "strings"
    "time"
)

//定义key和过期时间
var jwtKey = []byte("www.xxx.comx") //byte类型的切片
var expireTime = time.Now().Add(24 * time.Hour).Unix()

//自定义一个结构体,这个结构体需要继承 jwt.StandardClaims 结构体
type MyClaims struct {
    Uid int //自定义的属性 用于不同接口传值
    jwt.StandardClaims
}

//设置token
func SetToken(uid int) (string, error) {
    //实例化 存储token的结构体
    myClaimsObj := MyClaims{
        uid, //自定义参数: 可自行传值
        jwt.StandardClaims{
            ExpiresAt: expireTime, //过期时间
            Issuer:    "www.xxx.com",
        },
    }

    // 使用指定的签名方法创建签名对象
    tokenObj := jwt.NewWithClaims(jwt.SigningMethodHS256, myClaimsObj)
    // 使用指定的 secret 签名并获得完整的编码后的字符串 token
    tokenStr, err := tokenObj.SignedString(jwtKey)
    if err != nil {
        return "", err
    }

    return tokenStr, nil
}

func GetToken(tokenData string, uid int) (int, error) {
    //获取token
    tokenStr := strings.Split(tokenData, " ")[1]
    //校验token
    token, myClaims, err := ParseToken(tokenStr)

    if err != nil || !token.Valid { //校验失败
        return 0, err
    } else {
        return myClaims.Uid, nil
    }
}

//验证token是否合法
func ParseToken(tokenStr string) (*jwt.Token, *MyClaims, error) {
    myClaims := &MyClaims{}
    token, err := jwt.ParseWithClaims(tokenStr, myClaims, func(token *jwt.Token) (i interface{}, err error) {
        return jwtKey, nil
    })
    return token, myClaims, err
}
在controllers/api/UserController.go下创建login,addressList方法,并调用models.Myclaims结构体中的GetToken,SetToken来获取以及校验token
//登录操作:获取token
func (con V1Controller) Login(c *gin.Context) {
    tokenStr, err := models.SetToken(11)
    if err != nil {
        c.JSON(200, gin.H{
            "message": "生成token失败重试",
            "success": false,
        })
        return
    }
    c.JSON(200, gin.H{
        "message": "获取token成功",
        "token":   tokenStr,
        "success": true,
    })
}

//获取收货地址
func (con V1Controller) AddressList(c *gin.Context) {
    //获取token
    tokenData := c.Request.Header.Get("Authorization")
    if len(tokenData) <= 0 {
        c.JSON(http.StatusOK, gin.H{
            "message": "token传入错误长度不合法",
            "success": false,
        })
    }

    uid, err := models.GetToken(tokenData, 11)
    if err != nil {  //校验失败
        c.JSON(http.StatusOK, gin.H{
            "message": err,
            "success": false,
        })
    }

    //校验成功
    c.JSON(http.StatusOK, gin.H{
        "uid": uid,
        "success": true,
    })
}

(3).PostMan校验

  1. Vue React Angular 使用 Axios 访问基于 Jwt 的接口

var token = localStorage.getItem('token');
this.$http.get("http://localhost:8080/api/addressList", {
    headers: {
    'Authorization': 'Bearer ' + token, }
    }).then(function (response) {
        console.log(response);
}).catch(function (error) {
    console.log(error);
})
  1. 关于 Jwt 的一些问题

  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询

数据库的次数。

  • JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某

个 token,或者更改 token 的权限,也就是说,一旦 JWT 签发了,在到期之前就会始终有

效,除非服务器部署额外的逻辑。

  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限,为了减

少盗用,JWT 的有效期应该设置得比较短,对于一些比较重要的权限,使用时应该再次对

用户进行认证

  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输

[上一节][golang gin框架] 35.Gin 商城项目- 用户中心制作以及订单列表数据渲染(分页展示,订单状态,筛选订单 搜索订单,订单详情, 以及后台订单管理功能实现逻辑 )

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cocos CreatorXR 1.2.0 今日发布,正式支持 WebXR ,并开启 MR 之路

去年九月&#xff0c;Cocos CreatorXR v1.0.1 版本支持了 VR 内容创作&#xff0c;成为率先支持 XR 的国产引擎&#xff0c;今年三月&#xff0c;Cocos CreatorXR v1.1.0 版本实现了对 AR 内容开发的支持。在完成基本功能的建设后&#xff0c;更多开发者开始尝试使用 Cocos Cre…

Linux内核oops panic简析

源码基于&#xff1a;Linux 5.4 0. 前言 内核异常的级别大致分为三个&#xff1a;BUG、oops、panic。 BUG 是指那些不符合内核的正常设计&#xff0c;但内核能够检测出来并且对系统运行不会产生影响的问题&#xff0c;比如在原子上下文中休眠&#xff0c;在内核中用 BUG 标识。…

case

[rootes3 data]# cat case11.sh #!/bin/bash. /etc/rc.d/init.d/functionsdisable_selinux(){sed -i s/SELNUXenforcing/SELINUXdisabled/ /etc/selinux/config action "SELINUX 已经禁用&#xff0c;重启生效" }disable_firewalld(){ systemctl disable --now firew…

chatgpt赋能Python-python3_9_1怎么打开

Python 3.9.1 使用指南&#xff1a;如何打开 Python 3.9.1 Python 3.9.1 是 Python 最新版本的一个分支&#xff0c;包含各种新特性和改进&#xff0c;能够让开发者快速、简单地创建并运行 Python 程序。如果您想要使用 Python 3.9.1&#xff0c;下面是一个简单的指南&#xf…

菜单选择shell

[rootes3 data]# vi action.sh #!/bin/bash . /etc/init.d/functionsecho -en "\E[$[RANDOM%731];1m"cat <<EOF请选择&#xff1a;1) 备份数据库2)清理日志3)软件升级4)软件回滚5)删库跑路EOFecho -en \E[0mread -p "请选择上面的项对应的数字1-5&#xf…

Spring Boot-如何让你的 bean 在其他 bean 之前完成加载

今天有个小伙伴给我出了一个难题&#xff1a;在 SpringBoot 中如何让自己的某个指定的 Bean 在其他 Bean 前完成被 Spring 加载&#xff1f;我听到这个问题的第一反应是&#xff0c;为什么会有这样奇怪的需求&#xff1f;Talk is cheap&#xff0c;show me the code&#xff0c…

「读书感悟系列」原则:应对变化中的世界秩序(达利欧)

作者 | gongyouliu 编辑 | gongyouliu 最近2个月读完了达利欧的『原则2&#xff1a;应对变化中的世界秩序』&#xff0c;收获非常大。几年之前读他的『原则1&#xff1a;工作与生活』就非常喜欢&#xff0c;很有启发&#xff0c;这次读起来一如既往的喜欢。这本书利用周期的思路…

Qt推流程序自动生成网页远程查看实时视频流(视频文件/视频流/摄像头/桌面转成流媒体rtmp+hls+webrtc)

一、前言说明 推流程序将视频流推送到流媒体服务器后&#xff0c;此时就等待验证拉流播放&#xff0c;一般可以选择ffplay命令行播放或者vlc等播放器打开播放&#xff0c;也可以选择网页直接打开拉流地址播放&#xff0c;一般主流的浏览器都支持网页直接播放hls/m3u8/webrtc类…

流批一体的近实时数仓的思考与设计

摘要&#xff1a;基于对数据时间旅行的思考&#xff0c;引出了对目前三种数仓形态和两种数仓架构的思考。结合数据湖在 Flink 的应用和数据湖元数据类型的思考&#xff0c;探索了基于数据湖的 Flink SQL 流批一体的实践&#xff0c;在流批一体 SQL 表达一致、结果一致性、流批任…

【JS】1686- 重学 JavaScript API - Clipboard API

&#x1f3dd; 1. 什么是 Clipboard API 1.1 概念介绍 Clipboard API[1] 是一组 JavaScript API&#xff0c;用于在浏览器中操作剪贴板。通过 Clipboard API&#xff0c;开发者可以将文本、图片和其他数据复制到剪贴板&#xff0c;也可以从剪贴板中读取数据&#xff0c;实现复制…

OPPO解散芯片团队的真相,真的不缺钱?

OPPO解散芯片研发团队&#xff0c;各方都喜欢说OPPO不缺钱&#xff0c;解散芯片研发团队应该不是因为资金问题&#xff0c;然而仔细看看当下全球智能手机市场的表现&#xff0c;就未必会如此想了。 全球手机市场的出货量在2022年下跌了12%&#xff0c;跌穿了12亿部&#xff1b;…

【JVM】4. 虚拟机栈

文章目录 4.1. 虚拟机栈概述4.1.2. 初步印象4.1.3. 内存中的栈与堆4.1.4. 虚拟机栈基本内容Java虚拟机栈是什么&#xff1f;生命周期作用栈的特点面试题&#xff1a;开发中遇到哪些异常&#xff1f; 4.2. 栈的存储单位4.2.1. 栈中存储什么&#xff1f;4.2.2. 栈运行原理4.2.3. …

GPT理解的CV:基于Yolov5的半监督目标检测

关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 作者&#xff1a;Edison_G 主要贡献是提出了一种名为“Efficient Teacher”的半监督目标检测算法。与传统的监督学习算法不同&…

10人面试9个答错?鹅厂T12详解MySQL加锁机制

&#x1f449;腾小云导读 鹅厂有一道关于「数据库锁」的面试题。我们发现其实很多 DBA &#xff08;数据库管理员&#xff0c;Database administrator&#xff09;包括工作好几年的 DBA 都答得不太好。这说明 MySQL 锁的机制其实还是比较复杂&#xff0c;值得深入研究。本文对3…

探索Vue的组件世界-实现Vue插件

一个好的框架满足几大设计原则&#xff1a; 开闭原则&#xff1a;对修改源码关闭&#xff0c;对功能扩展开放 vue作为一个优秀的组件框架&#xff1a;满足开闭原则&#xff0c;提供良好的插件机制&#xff0c;以提供三方来扩展功能 Mixin模式 Vue.mixin(mixin) 全局注册的m…

嵌入式 QT 定时器与计时器

目录 1、定时器 2、计时器 2.1 QTime 时间转换成字符串函数 3、QT 获取日期&#xff0c;时间&#xff0c;星期 4、综合应用 定时器是用来处理周期性事件的一种对象&#xff0c;类似于硬件定时器。例如设置一个定时器的定时周期为 1000 毫 秒&#xff0c;那么每 1000 毫秒就会…

现在的00后,真是卷死了呀,辞职信准备好了·····

都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;三月份春招我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪23K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了…

Kali-linux使用社会工程学工具包(SET)

社会工程学工具包&#xff08;SET&#xff09;是一个开源的、Python驱动的社会工程学渗透测试工具。这套工具包由David Kenned设计&#xff0c;而且已经成为业界部署实施社会工程学攻击的标准。SET利用人们的好奇心、信任、贪婪及一些愚蠢的错误&#xff0c;攻击人们自身存在的…

python使用海龟turtle实现绘制汉字、中文

一、实现要求 使用python中的turtle库绘制指定汉字、中文 二、实现思路 1、要想实现汉字的绘制&#xff0c;首先需要知道汉字的笔画坐标&#xff0c;汉字的笔画坐标在网上有&#xff0c;需要使用爬虫技术抓取到指定汉字的笔画坐标信息 2、根据汉字的笔画坐标信息&#xff0c;使…

基于Kubernetes的电商平台部署:实现高可用、弹性伸缩与容器化管理

▲ 点击上方"DevOps和k8s全栈技术"关注公众号 背景&#xff1a;电商平台的高可用性和可伸缩性是保证用户体验和业务发展的重要因素。Kubernetes&#xff08;K8s&#xff09;作为一个容器编排平台&#xff0c;可以提供强大的容器管理和自动化部署能力&#xff0c;使得…