Skip to content

Commit

Permalink
#12 resubmit middlerware
Browse files Browse the repository at this point in the history
  • Loading branch information
xinliangnote committed Mar 6, 2021
1 parent 499e171 commit 5d79750
Show file tree
Hide file tree
Showing 21 changed files with 317 additions and 142 deletions.
13 changes: 4 additions & 9 deletions configs/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,10 @@ type Config struct {
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"jwt"`

Aes struct {
Key string `toml:"key"`
Iv string `toml:"iv"`
} `toml:"aes"`

Rsa struct {
Private string `toml:"private"`
Public string `toml:"public"`
} `toml:"rsa"`
URLToken struct {
Secret string `toml:"secret"`
ExpireDuration time.Duration `toml:"expireDuration"`
} `toml:"urlToken"`

Cmd struct {
GenTables string `toml:"genTables"`
Expand Down
45 changes: 3 additions & 42 deletions configs/fat_configs.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,48 +34,9 @@ to = "" # 收件人邮箱,多个可以逗号(,)分割
secret = 'i1ydX9RtHyuJTrw7frcu' # JWT secret
expireDuration = 24 # JWT ExpiresAt 过期时间(单位:小时)

[aes]
key = 'IgkibX71IEf382PT'
iv = 'IgkibX71IEf382PT'

[rsa]
private = '-----BEGIN RSA PRIVATE KEY-----
MIIEpgIBAAKCAQEA1O3p0JN0/RrP7eY3f81izPf16FS0WMNGCJkd+y5c6yBzUvN0
IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNom/UCAzAYEgdIsNee5LSN/7e0T2/Q
vsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT34GGRdrSJ+m4iVk/Kt95tavBNk+ND
VSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e9Tn8f99M2VMqpzXHXZzv1ABmqufz
S20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HMxLYEFN9h2MWYgxLm9Z0rLMrWwMM+
E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbzowIDAQABAoIBAQCO1RE1ItUlO6kj
Un0ENAgEqojAUqGvsT33Yo7kAZO+/cOeb0UEqk0iq5bf7L9ncBynWDg6ZPc6X3/g
wdFdKxAvHck9zjM3VL+EMP+bNyrR0K8ZYk5Kx+Q/PEK+Mp8dfRdgggAUsZaNWB+a
rVVspiMo1wo28KBl5x8NevTnJkOLqXAyB7UyLWqnOL1fb988lZvZPR7ZUYroVIZa
pyXtZcafIJeKyQ3bvWI5+eFqOe61Z4Bx1+TpfZ3fKfSDW0vhxzNqaimOa8jSXtMJ
jMeOctL4nZ0TPo/jS3I+XlaH4ZQlFLuUWGscpxwfEeBN23I8HRLkZXJsw66yvRN3
s4bUKPXRAoGBAP/3oSZAECvfsYYzs76tnrAmR/0GxCqgguxDlWn5DowQzdWFOdHC
ZbTo/hUVoMSQnO1EKCFlnBS+wg/3TuIzUO0ewC1aeT7qHbOMDl0zKbNpS2Z9/j+U
zro+qz7XmkWolMCfmDrCrw9CtCxcMSII+ajbI8SAgFVMz9XnDt+xW9E9AoGBANT0
4F6kCUJTEyqf2+v84tjQ2wGIF6XtZPU9JR806zeMyahQ9F6z3hY8BYb0tIy5b3uJ
VlJ9TG1qg/t59TWxIq43mYSUJHe0aJi3ilooObQtHlhPu8nwmmX47sX0PyG2hMoD
kBVxTpTDmBaDz7O9uBnlMXJN5qEygctaixpEbmZfAoGBAMBA9kEMjRjnAyeRXcgy
D6aumhNqKZz6wltCx864yjxZwsBFOJBcOpgPCAg+HmqFU9jCAIJVF05dmNT1I8Ky
WG5BUoa+FaMzpOtenstRylh/Far9pyGKW1t4BpdEyRLY9CFZvbUk1OfZagqHlD/E
DgDN16eX/MwUzWYUDg/l3tjhAoGBAKGip/ZNjVWRFpggs9z/mfK1O7WC5Wgksp9N
ZLK2CN6l9p3RrFmBLk00C4HulGfHi+15RVLhFbRqx3iFje/N3iPbwaMWikNtZIKd
tN5Pb9To9gJTqpZRD+/cLOeFRrHBBjMK1z7fPKS/fN2B+JFVq7nD827t3+J0In4F
4FT0odMDAoGBAJk3ELB/FHY8xzZ4jF1wG/a1CK681Xm6SuU5KIELDSAUNoou6OPG
mS8gU20MMPAeV2z7khyDcSxlHsUyL73eLeaakbQov9NMW7cc99XX4wnP4W7FRpmr
QbHmKuHIRFHCFv+XX8c0aK2mDZMUlzJdy4FgD/YCEZ7kZMZKyvZW/ZuV
-----END RSA PRIVATE KEY-----'

public = '-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1O3p0JN0/RrP7eY3f81i
zPf16FS0WMNGCJkd+y5c6yBzUvN0IEeoxiIWIBhoMKH0pzlzBg0rfttojSodOgNo
m/UCAzAYEgdIsNee5LSN/7e0T2/QvsIAHINuA8gI8fGoGiSA2TEzpUo6aVXwhZT3
4GGRdrSJ+m4iVk/Kt95tavBNk+NDVSeb5xAjxBchT5BjAMMlE0ffGZb0MMjjO5+e
9Tn8f99M2VMqpzXHXZzv1ABmqufzS20iWcSvnjhWcJ9hiKwO8Z30GgJyACmml+HM
xLYEFN9h2MWYgxLm9Z0rLMrWwMM+E2rCs8tsxAD5sO9RZMJPl1C0FIsMR53ngqbz
owIDAQAB
-----END PUBLIC KEY-----'
[urlToken]
secret = 'i1ydX9RtHyuJTrw7frcu' # URL Token secret
expireDuration = 10 # URL Token ExpiresAt 过期时间(单位:分钟)

[cmd]
genTables = 'user_demo'
4 changes: 4 additions & 0 deletions internal/api/code/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
ParamBindError = 10103
AuthorizationError = 10104
CallHTTPError = 10105
ResubmitError = 10106
ResubmitMsg = 10107

// 模块级错误码 - 用户模块
IllegalUserName = 20101
Expand All @@ -29,6 +31,8 @@ var codeText = map[int]string{
ParamBindError: "参数信息有误",
AuthorizationError: "签名信息有误",
CallHTTPError: "调用第三方 HTTP 接口失败",
ResubmitError: "Resubmit Error",
ResubmitMsg: "请勿重复提交",

IllegalUserName: "非法用户名",
UserCreateError: "创建用户失败",
Expand Down
2 changes: 1 addition & 1 deletion internal/api/controller/demo_handler/func_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type authResponse struct {
func (h *handler) Auth() core.HandlerFunc {
return func(c core.Context) {
cfg := configs.Get().JWT
tokenString, err := token.New(cfg.Secret).Sign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
tokenString, err := token.New(cfg.Secret).JwtSign(1, "xinliangnote", time.Hour*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package auth
package middleware

import (
"net/http"
Expand All @@ -12,7 +12,7 @@ import (
"github.com/pkg/errors"
)

func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Error) {
func (m *middleware) Jwt(ctx core.Context) (userId int64, userName string, err errno.Error) {
auth := ctx.GetHeader("Authorization")
if auth == "" {
err = errno.NewError(
Expand All @@ -24,7 +24,7 @@ func AuthHandler(ctx core.Context) (userId int64, userName string, err errno.Err
}

cfg := configs.Get().JWT
claims, errParse := token.New(cfg.Secret).Parse(auth)
claims, errParse := token.New(cfg.Secret).JwtParse(auth)
if errParse != nil {
err = errno.NewError(
http.StatusUnauthorized,
Expand Down
70 changes: 70 additions & 0 deletions internal/api/router/middleware/middle_resubmit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package middleware

import (
"net/http"
"time"

"github.com/xinliangnote/go-gin-api/configs"
"github.com/xinliangnote/go-gin-api/internal/api/code"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/pkg/errno"
"github.com/xinliangnote/go-gin-api/pkg/token"

"github.com/pkg/errors"
)

func (m *middleware) Resubmit() core.HandlerFunc {

redisKeyPrefix := configs.ProjectName() + ":request-id:"

return func(c core.Context) {
cfg := configs.Get().URLToken

tokenString, err := token.New(cfg.Secret).UrlSign(c.Path(), c.Method(), c.RequestInputParams())
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}

redisKey := redisKeyPrefix + tokenString
if !m.cache.Exists(redisKey) {
err = m.cache.Set(redisKey, "1", time.Minute*cfg.ExpireDuration)
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}

return
}

redisValue, err := m.cache.Get(redisKey, cache.WithTrace(c.Trace()))
if err != nil {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitError,
code.Text(code.ResubmitError)).WithErr(err),
)
return
}

if redisValue == "1" {
c.AbortWithError(errno.NewError(
http.StatusBadRequest,
code.ResubmitMsg,
code.Text(code.ResubmitMsg)).WithErr(errors.New("resubmit")),
)
return
}

return
}
}
40 changes: 40 additions & 0 deletions internal/api/router/middleware/middlerware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package middleware

import (
"github.com/xinliangnote/go-gin-api/internal/api/service/user_service"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
"github.com/xinliangnote/go-gin-api/internal/pkg/grpc"
"github.com/xinliangnote/go-gin-api/pkg/errno"

"go.uber.org/zap"
)

var _ Middleware = (*middleware)(nil)

type Middleware interface {
// i 为了避免被其他包实现
i()

// JWT 中间件
Jwt(ctx core.Context) (userId int64, userName string, err errno.Error)

// Resubmit 中间件
Resubmit() core.HandlerFunc
}

type middleware struct {
logger *zap.Logger
cache cache.Repo
grpConn grpc.ClientConn
userService user_service.UserService
}

func New(logger *zap.Logger, cache cache.Repo) Middleware {
return &middleware{
logger: logger,
cache: cache,
}
}

func (m *middleware) i() {}
42 changes: 22 additions & 20 deletions internal/api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package router
import (
"github.com/xinliangnote/go-gin-api/internal/api/controller/demo_handler"
"github.com/xinliangnote/go-gin-api/internal/api/controller/user_handler"
"github.com/xinliangnote/go-gin-api/internal/api/router/middleware/auth"
"github.com/xinliangnote/go-gin-api/internal/api/router/middleware"
"github.com/xinliangnote/go-gin-api/internal/graph/handler"
"github.com/xinliangnote/go-gin-api/internal/pkg/cache"
"github.com/xinliangnote/go-gin-api/internal/pkg/core"
Expand Down Expand Up @@ -33,6 +33,10 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
panic(err)
}

// 中间件
middles := middleware.New(logger, cache)

// graphQL 控制器
gqlHandler := handler.New(logger, db, cache)

gql := mux.Group("/graphql")
Expand All @@ -41,33 +45,31 @@ func NewHTTPMux(logger *zap.Logger, db db.Repo, cache cache.Repo, grpConn grpc.C
gql.POST("/query", gqlHandler.Query())
}

// demo 控制器
demoHandler := demo_handler.New(logger, db, cache, grpConn)
userHandler := user_handler.New(logger, db, cache)

// user_demo CURD
user := mux.Group("/user", core.WrapAuthHandler(auth.AuthHandler))
demo := mux.Group("/demo", core.WrapAuthHandler(middles.Jwt)) // 使用 jwt 验证
{
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
demo.GET("/trace", demoHandler.Trace())

// 模拟数据
demo.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
demo.POST("post", demoHandler.Post())
}

// auth
a := mux.Group("/auth")
demoNoAuth := mux.Group("/auth") // 不使用 jwt 验证
{
a.POST("/get", demoHandler.Auth())
demoNoAuth.POST("/get", demoHandler.Auth())
}

// demo
d := mux.Group("/demo", core.WrapAuthHandler(auth.AuthHandler)) //使用 auth 验证
// user 控制器
userHandler := user_handler.New(logger, db, cache)
user := mux.Group("/user", core.WrapAuthHandler(middles.Jwt))
{
// 为了演示 Trace ,增加了一些看起来无意义的调试信息和 SQL 信息。
d.GET("/trace", demoHandler.Trace())

// 模拟数据
d.GET("get/:name", core.AliasForRecordMetrics("/demo/get"), demoHandler.Get())
d.POST("post", demoHandler.Post())
user.POST("/create", userHandler.Create())
user.PUT("/update", userHandler.UpdateNickNameByID())
user.PATCH("/delete/:id", userHandler.Delete())
user.GET("/info/:username", core.AliasForRecordMetrics("/user/info"), userHandler.Detail())
}

return mux, nil
Expand Down
10 changes: 10 additions & 0 deletions internal/pkg/cache/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Repo interface {
Expire(key string, ttl time.Duration) bool
ExpireAt(key string, ttl time.Time) bool
Del(keys ...string) bool
Exists(keys ...string) bool
Incr(key string, options ...Option) int64
Close() error
}
Expand Down Expand Up @@ -148,6 +149,15 @@ func (c *cacheRepo) ExpireAt(key string, ttl time.Time) bool {
return ok
}

//
func (c *cacheRepo) Exists(keys ...string) bool {
if len(keys) == 0 {
return true
}
value, _ := c.client.Exists(keys...).Result()
return value > 0
}

// Del del some key from redis
func (c *cacheRepo) Del(keys ...string) bool {
if len(keys) == 0 {
Expand Down
16 changes: 16 additions & 0 deletions internal/pkg/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ type Context interface {
Alias() string
setAlias(path string)

// RequestInputParams 获取所有参数
RequestInputParams() url.Values
// RequestPostFormParams 获取 PostForm 参数
RequestPostFormParams() url.Values
// Request 获取 Request 对象
Request() *http.Request
// RawData 获取 Request.Body
Expand Down Expand Up @@ -326,6 +330,18 @@ func (c *context) setAlias(path string) {
}
}

// RequestInputParams 获取所有参数
func (c *context) RequestInputParams() url.Values {
_ = c.ctx.Request.ParseForm()
return c.ctx.Request.Form
}

// RequestPostFormParams 获取 PostForm 参数
func (c *context) RequestPostFormParams() url.Values {
_ = c.ctx.Request.ParseForm()
return c.ctx.Request.PostForm
}

// Request 获取 Request
func (c *context) Request() *http.Request {
return c.ctx.Request
Expand Down
20 changes: 20 additions & 0 deletions pkg/token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 与 UrlSign 对应的 PHP 加密算法

```php
// 对 params key 进行排序
ksort($params);

// 对 sortParams 进行 Encode
$sortParamsEncode = http_build_query($params);

// 加密字符串规则 path + method + sortParamsEncode + secret
$encryptStr = $path . $method . $sortParamsEncode . $secret

// 对加密字符串进行 md5
$md5Str = md5($encryptStr);

// 对 md5Str 进行 base64 encode
$tokenString = base64_encode($md5Str);

echo $tokenString;
```
Loading

0 comments on commit 5d79750

Please sign in to comment.