This commit is contained in:
xing 2021-05-31 17:40:47 +08:00
parent 882149eee5
commit 6459a79c32
17 changed files with 396 additions and 4 deletions

4
.gitignore vendored
View File

@ -12,9 +12,9 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
.idea .idea
storage/*
blog.iml blog.iml
go.sum go.sum
# Dependency directories (remove the comment below to include it) # Dependency directories (remove the comment below to include it)
# vendor/ # vendor/
storage/logs/*
storage/uploads/*

View File

@ -26,4 +26,8 @@ Database:
Charset: utf8mb4 Charset: utf8mb4
ParseTime: True ParseTime: True
MaxIdleConns: 10 MaxIdleConns: 10
MaxOpenConns: 30 MaxOpenConns: 30
JWT:
Secret: eddycjy
Issuer: blog-service
Expire: 7200

View File

@ -32,6 +32,13 @@ var doc = `{
], ],
"summary": "获取多个标签", "summary": "获取多个标签",
"parameters": [ "parameters": [
{
"type": "string",
"description": "token",
"name": "token",
"in": "query",
"required": true
},
{ {
"maxLength": 100, "maxLength": 100,
"type": "string", "type": "string",
@ -246,6 +253,62 @@ var doc = `{
} }
} }
}, },
"/auth": {
"post": {
"produces": [
"application/json"
],
"summary": "获取token",
"parameters": [
{
"type": "string",
"description": "appkey",
"name": "app_key",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "appsecret",
"name": "app_secret",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/api.res"
},
{
"type": "object",
"properties": {
"token": {
"type": "string"
}
}
}
]
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/upload/file": { "/upload/file": {
"post": { "post": {
"produces": [ "produces": [
@ -297,6 +360,9 @@ var doc = `{
} }
}, },
"definitions": { "definitions": {
"api.res": {
"type": "object"
},
"app.Pager": { "app.Pager": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -15,6 +15,13 @@
], ],
"summary": "获取多个标签", "summary": "获取多个标签",
"parameters": [ "parameters": [
{
"type": "string",
"description": "token",
"name": "token",
"in": "query",
"required": true
},
{ {
"maxLength": 100, "maxLength": 100,
"type": "string", "type": "string",
@ -229,6 +236,62 @@
} }
} }
}, },
"/auth": {
"post": {
"produces": [
"application/json"
],
"summary": "获取token",
"parameters": [
{
"type": "string",
"description": "appkey",
"name": "app_key",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "appsecret",
"name": "app_secret",
"in": "formData",
"required": true
}
],
"responses": {
"200": {
"description": "成功",
"schema": {
"allOf": [
{
"$ref": "#/definitions/api.res"
},
{
"type": "object",
"properties": {
"token": {
"type": "string"
}
}
}
]
}
},
"400": {
"description": "请求错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
},
"500": {
"description": "内部错误",
"schema": {
"$ref": "#/definitions/errorcode.Error"
}
}
}
}
},
"/upload/file": { "/upload/file": {
"post": { "post": {
"produces": [ "produces": [
@ -280,6 +343,9 @@
} }
}, },
"definitions": { "definitions": {
"api.res": {
"type": "object"
},
"app.Pager": { "app.Pager": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1,4 +1,6 @@
definitions: definitions:
api.res:
type: object
app.Pager: app.Pager:
properties: properties:
page: page:
@ -57,6 +59,11 @@ paths:
/api/v1/tags: /api/v1/tags:
get: get:
parameters: parameters:
- description: token
in: query
name: token
required: true
type: string
- description: 标签名称 - description: 标签名称
in: query in: query
maxLength: 100 maxLength: 100
@ -205,6 +212,40 @@ paths:
schema: schema:
$ref: '#/definitions/errorcode.Error' $ref: '#/definitions/errorcode.Error'
summary: 更新标签 summary: 更新标签
/auth:
post:
parameters:
- description: appkey
in: formData
name: app_key
required: true
type: string
- description: appsecret
in: formData
name: app_secret
required: true
type: string
produces:
- application/json
responses:
"200":
description: 成功
schema:
allOf:
- $ref: '#/definitions/api.res'
- properties:
token:
type: string
type: object
"400":
description: 请求错误
schema:
$ref: '#/definitions/errorcode.Error'
"500":
description: 内部错误
schema:
$ref: '#/definitions/errorcode.Error'
summary: 获取token
/upload/file: /upload/file:
post: post:
parameters: parameters:

View File

@ -10,4 +10,5 @@ var (
AppSetting *setting.AppSettingS AppSetting *setting.AppSettingS
DatabaseSetting *setting.DatabaseSettingS DatabaseSetting *setting.DatabaseSettingS
Logger *logger.Logger Logger *logger.Logger
JWTSetting *setting.JWT
) )

1
go.mod
View File

@ -5,6 +5,7 @@ go 1.16
require ( require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/gin-gonic/gin v1.7.2 github.com/gin-gonic/gin v1.7.2
github.com/go-openapi/spec v0.20.3 // indirect github.com/go-openapi/spec v0.20.3 // indirect

8
internal/dao/auth.go Normal file
View File

@ -0,0 +1,8 @@
package dao
import "blog/internal/model"
func (d *Dao) GetAuth(appKey, appSecret string) (model.Auth, error) {
auth := model.Auth{AppKey: appKey, AppSecret: appSecret}
return auth.Get(d.engine)
}

View File

@ -0,0 +1,44 @@
package middleware
import (
"blog/pkg/app"
"blog/pkg/errorcode"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
)
func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var (
token string
ecode = errorcode.Success
)
if s, exist := c.GetQuery("token"); exist {
token = s
} else {
token = c.GetHeader("token")
}
if token == "" {
ecode = errorcode.InvalidParams
} else {
_, err := app.ParseToken(token)
if err != nil {
switch err.(*jwt.ValidationError).Errors {
case jwt.ValidationErrorExpired:
ecode = errorcode.UnauthorizedTokenTimeout
default:
ecode = errorcode.UnauthorizedTokenError
}
}
}
if ecode != errorcode.Success {
response := app.NewResponse(c)
response.ToErrorResponse(ecode)
c.Abort()
return
}
c.Next()
}
}

24
internal/model/auth.go Normal file
View File

@ -0,0 +1,24 @@
package model
import "github.com/jinzhu/gorm"
type Auth struct {
*Model
AppKey string `json:"app_key"`
AppSecret string `json:"app_secret"`
}
func (a Auth) TableName() string {
return "blog_auth"
}
func (a Auth) Get(db *gorm.DB) (Auth, error) {
var auth Auth
db = db.Where("app_key = ? AND app_secret = ? AND is_del = ?", a.AppKey, a.AppSecret, 0)
err := db.First(&auth).Error
if err != nil && err != gorm.ErrRecordNotFound {
return auth, err
}
return auth, nil
}

View File

@ -0,0 +1,51 @@
package api
import (
"blog/global"
"blog/internal/service"
"blog/pkg/app"
"blog/pkg/errorcode"
"github.com/gin-gonic/gin"
)
type res struct {
token string
}
// @Summary 获取token
// @Produce json
// @Param app_key formData string true "appkey" required
// @Param app_secret formData string true "appsecret" required
// @Success 200 {object} res{token=string} "成功"
// @Failure 400 {object} errorcode.Error "请求错误"
// @Failure 500 {object} errorcode.Error "内部错误"
// @Router /auth [post]
func GetAuth(c *gin.Context) {
param := service.AuthRequest{}
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, &param)
if !valid {
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errorcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
svc := service.New(c.Request.Context())
err := svc.CheckAuth(&param)
if err != nil {
global.Logger.Errorf("svc.CheckAuth err: %v", err)
response.ToErrorResponse(errorcode.UnauthorizedAuthNotExist)
return
}
token, err := app.GenerateToken(param.AppKey, param.AppSecret)
if err != nil {
global.Logger.Errorf("app.GenerateToken err: %v", err)
response.ToErrorResponse(errorcode.UnauthorizedTokenGenerate)
return
}
response.ToResponse(gin.H{
"token": token,
})
}

View File

@ -20,6 +20,7 @@ func (t Tag) Get(c *gin.Context) {}
// @Summary 获取多个标签 // @Summary 获取多个标签
// @Produce json // @Produce json
// @Param token query string true "token"
// @Param name query string false "标签名称" maxlength(100) // @Param name query string false "标签名称" maxlength(100)
// @Param state query int false "状态" Enums(0, 1) default(1) // @Param state query int false "状态" Enums(0, 1) default(1)
// @Param page query int false "页码" default(1) // @Param page query int false "页码" default(1)

View File

@ -17,6 +17,7 @@ func NewRouter() *gin.Engine {
r.Use(gin.Logger()) r.Use(gin.Logger())
r.Use(gin.Recovery()) r.Use(gin.Recovery())
r.Use(middleware.Translations()) r.Use(middleware.Translations())
article := v1.NewArticle() article := v1.NewArticle()
tag := v1.NewTag() tag := v1.NewTag()
upload := api.NewUpload() upload := api.NewUpload()
@ -24,7 +25,9 @@ func NewRouter() *gin.Engine {
r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath)) r.StaticFS("/static", http.Dir(global.AppSetting.UploadSavePath))
//r.GET("/static/*any",api.ReadFile) //r.GET("/static/*any",api.ReadFile)
r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) r.GET("/doc/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.POST("/auth", api.GetAuth)
apiv1 := r.Group("/api/v1") apiv1 := r.Group("/api/v1")
apiv1.Use(middleware.JWT())
{ {
apiv1.POST("/tags", tag.Create) apiv1.POST("/tags", tag.Create)
apiv1.DELETE("/tags/:id", tag.Delete) apiv1.DELETE("/tags/:id", tag.Delete)

21
internal/service/auth.go Normal file
View File

@ -0,0 +1,21 @@
package service
import "errors"
type AuthRequest struct {
AppKey string `form:"app_key" binding:"required"`
AppSecret string `form:"app_secret" binding:"required"`
}
func (svc *Service) CheckAuth(param *AuthRequest) error {
auth, err := svc.dao.GetAuth(param.AppKey, param.AppSecret)
if err != nil {
return err
}
if auth.ID > 0 {
return nil
}
return errors.New("auth info does not exist")
}

View File

@ -64,7 +64,12 @@ func setupSetting() error {
if err != nil { if err != nil {
return err return err
} }
err = newSetting.ReadSection("JWT", &global.JWTSetting)
if err != nil {
return err
}
global.JWTSetting.Expire *= time.Second
global.ServerSetting.ReadTimeout *= time.Second global.ServerSetting.ReadTimeout *= time.Second
global.ServerSetting.WriteTimeout *= time.Second global.ServerSetting.WriteTimeout *= time.Second
return nil return nil
@ -76,7 +81,6 @@ func setupSetting() error {
// @termsOfService https://github.com/go-programming-tour-book // @termsOfService https://github.com/go-programming-tour-book
func main() { func main() {
gin.SetMode(global.ServerSetting.RunMode) gin.SetMode(global.ServerSetting.RunMode)
global.Logger.Infof("%s: go-programming-tour-book/%s", "eddycjy", "blog-service")
r := routess.NewRouter() r := routess.NewRouter()
s := &http.Server{ s := &http.Server{

51
pkg/app/jwt.go Normal file
View File

@ -0,0 +1,51 @@
package app
import (
"blog/global"
"blog/pkg/util"
"github.com/dgrijalva/jwt-go"
"time"
)
type Claims struct {
AppKey string `json:"app_key"`
AppSecret string `json:"app_secret"`
jwt.StandardClaims
}
func GetJWTSecret() []byte {
return []byte(global.JWTSetting.Secret)
}
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return GetJWTSecret(), nil
})
if err != nil {
return nil, err
}
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}
func GenerateToken(appKey, appSecret string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(global.JWTSetting.Expire)
claims := Claims{
AppKey: util.EncodeMD5(appKey),
AppSecret: util.EncodeMD5(appSecret),
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(),
Issuer: global.JWTSetting.Issuer,
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(GetJWTSecret())
return token, err
}

View File

@ -9,6 +9,12 @@ type ServerSettingS struct {
WriteTimeout time.Duration WriteTimeout time.Duration
} }
type JWT struct {
Secret string
Issuer string
Expire time.Duration
}
type AppSettingS struct { type AppSettingS struct {
DefaultPageSize int DefaultPageSize int
MaxPageSize int MaxPageSize int