您的浏览器过于古老 & 陈旧。为了更好的访问体验, 请 升级你的浏览器
二周 发布于2020年01月15日 17:27 最近更新于 2020年01月15日 17:31

原创 Ant Design Vue + Golang 实现前后端分离系统

7505 次浏览 读完需要≈ 14 分钟 JavaScriptGolang

内容目录

Ant Design Vue是一套优秀的前端开发框架,作为一个后端程序员都对他喜爱有加,后端程序要想很好的使用这个框架,有一些基本的工作要做。这里我使用Go语言作为后端程序,并且使用了Gin作为web框架。

浏览器跨域问题

这里要允许浏览器的跨域,浏览器在涉及到跨域请求的时候会先发送一个OPTION类型的请求,所以我们要及时的响应这种类型的请求(直接响应200状态码就好),并且设置好响应头。

// 浏览器跨域
func cors(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
	c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, Access-Token")
	c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
	c.Header("X-Frame-cors", "DENY")
	c.Header("X-Content-Type-cors", "nosniff")
	c.Header("X-XSS-Protection", "1; mode=block")
	if c.Request.TLS != nil {
		c.Header("Strict-Transport-Security", "max-age=31536000")
	}

	if c.Request.Method == http.MethodOptions {
		c.AbortWithStatus(http.StatusOK)
		return
	}
	c.Next()
}

// 然后在对应的接口下进行调用,比如这里的登录接口
auth := r.Group("/auth")
auth .Use(cors)
{
	auth.POST("/login", login)
}

请注意,这里有一个

token问题

Ant Design Vue在登录成功后要求获得token,后面的每次请求都会带上这个token,并且服务端要及时的去验证这些token的有效性。

如何生成token

现在广泛使用的是jwt token.这里我们使用github.com/dgrijalva/jwt-go这个开源库。

package router

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

// token加密key
var tokenSigningKey = []byte("my-token")
var TokenInvalid = errors.New("token is invalid")

// 这里我让token携带两个参数,Channel 和 ChannelId
type AdsClaims struct {
	ChannelId int64  `json:"channelId"`
	Channel   string `json:"channel"`
	jwt.StandardClaims
}

// 生成token,这里token的有效期是2小时
func signToken(id int64, channel string) (string, error) {
	claims := AdsClaims{
		ChannelId: id,
		Channel:   channel,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 2).Unix(),
			Issuer:    "test",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(tokenSigningKey)
}

// 解析token,并且判断token是否有效
func parseToken(tokenString string) (*AdsClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &AdsClaims{}, func(token *jwt.Token) (interface{}, error) {
		return tokenSigningKey, nil
	})
	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*AdsClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, TokenInvalid
}

如何使用token

token 用在两个地方,登录和校验:

func login(c *gin.Context) {
	var u db.Channel
	if c.BindJSON(&u) != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}
	if msg := u.Login(); msg != "" {
		c.JSON(http.StatusUnauthorized, gin.H{"message": msg})
	} else {
		token, err := signToken(u.Id, u.Username)
		if err != nil {
			c.AbortWithError(http.StatusInternalServerError, err)
			return
		}
		// 登录成功,响应token出去
		c.JSON(http.StatusOK, build(gin.H{
			"id":          u.Id,
			"companyName": u.CompanyName,
			"token":       token,
		}))
	}
}

一般来说我们除了登录,注册等一些方法外都需要校验token.比如如下配置

api := r.Group("/api")
api.Use(cors,authCheck())
{
	admin.GET("/user/info", userInfo)
	admin.GET("/user/list", userList)
}

func authCheck() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Parse the json web token.
		if claims, err := parseRequest(c); err != nil {
			c.JSON(http.StatusUnauthorized, err)
			c.Abort()
			return
		} else {
			// 把channelId放入到context中,后续环节可以使用
			c.Set(config.ChannelIdKey, claims.ChannelId)
			c.Set(config.ChannelKey, claims.Channel)
		}
		c.Next()
	}
}

func parseRequest(c *gin.Context) (*AdsClaims, error) {
	accessToken := c.Request.Header.Get("Access-Token")
	if len(accessToken) == 0 {
		return nil, ErrMissingHeader
	}
	return parseToken(accessToken)
}

这里就保证了每次请求都会优先校验token,如果校验不通过或者token已过期,都会直接响应Unauthorized状态码(401)

导出Excel文件

有些报表需要导出Excel文件,这里前后端都要请求重写的哦!请注意

先说后端:

package router

import (
	"github.com/360EntSecGroup-Skylar/excelize"
	"github.com/gin-gonic/gin"
)

func excelExport(c *gin.Context) {
	// 生成Excel文件
	f := excelize.NewFile()
	// Create a new sheet.
	index := f.NewSheet("Sheet2")
	// Set value of a cell.
	f.SetCellValue("Sheet2", "A2", "Hello world.")
	f.SetCellValue("Sheet1", "B2", 100)
	// Set active sheet of the workbook.
	f.SetActiveSheet(index)

	c.Header("Content-Type", "application/octet-stream")
	c.Header("Content-Disposition", "attachment; filename=Book1.xlsx")
	c.Header("Content-Transfer-Encoding", "binary")

	_ = f.Write(c.Writer)
}

再说前端:一定要设置好responseType: 'blob'

export function excelExport (parameter) {
  return axios({
    url: '/api/excel_export',
    method: 'post',
    data: parameter,
	responseType: 'blob'
  })
}

不仅如此,前端也要实现自己的下载逻辑:

excelExport () {
      var tm = this.queryParam.tm.format('YYYY-MM-DD')
      return reportLog({
        tm,
        export: true
      }).then(data => {
        const url = window.URL.createObjectURL(new Blob([data]))
        const link = document.createElement('a')
        link.style.display = 'none'
        link.href = url
        link.setAttribute('download', `Book1.xlsx`)
        document.body.appendChild(link)
        link.click()
      }).catch(err => {
        console.log(err)
      })
    }
  }

如有问题,欢迎浏览讨论~

  • CodePlayer技术交流群1
  • CodePlayer技术交流群2

0 条评论

撰写评论