mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 13:47:15 +08:00
feat: captcha and login
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/goravel/framework/contracts/console/command"
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/goravel/framework/support/carbon"
|
||||
|
||||
"panel/app/models"
|
||||
"panel/packages/helpers"
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package console
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/console"
|
||||
"github.com/goravel/framework/contracts/schedule"
|
||||
|
||||
"panel/app/console/commands"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ package controllers
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/http"
|
||||
"github.com/goravel/framework/facades"
|
||||
models "panel/app/Models"
|
||||
|
||||
"panel/app/http/requests"
|
||||
"panel/app/models"
|
||||
)
|
||||
|
||||
type UserController struct {
|
||||
@@ -88,7 +89,21 @@ func (r *UserController) Login(ctx http.Context) {
|
||||
}
|
||||
|
||||
func (r *UserController) Info(ctx http.Context) {
|
||||
user, ok := ctx.Value("user").(models.User)
|
||||
if !ok {
|
||||
ctx.Request().AbortWithStatusJson(http.StatusUnauthorized, http.Json{
|
||||
"code": 401,
|
||||
"message": "登录已过期",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Response().Success().Json(http.Json{
|
||||
"Hello": "Goravel",
|
||||
"code": 0,
|
||||
"message": "获取用户信息成功",
|
||||
"data": http.Json{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
|
||||
CaptchaID string `json:"captcha_id" form:"captcha_id"`
|
||||
Captcha string `json:"captcha" form:"captcha"`
|
||||
}
|
||||
|
||||
func (r *LoginRequest) Authorize(ctx http.Context) error {
|
||||
@@ -18,6 +21,7 @@ func (r *LoginRequest) Rules(ctx http.Context) map[string]string {
|
||||
return map[string]string{
|
||||
"login": "required",
|
||||
"password": "required|min_len:8",
|
||||
"captcha": "captcha:true",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +30,7 @@ func (r *LoginRequest) Messages(ctx http.Context) map[string]string {
|
||||
"login.required": "登录名不能为空",
|
||||
"password.required": "密码不能为空",
|
||||
"password.min_len": "密码长度不能小于 8 位",
|
||||
"captcha.captcha": "验证码错误",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/goravel/framework/contracts/foundation"
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/goravel/framework/facades"
|
||||
|
||||
"panel/app/rules"
|
||||
)
|
||||
|
||||
type ValidationServiceProvider struct {
|
||||
@@ -20,5 +22,9 @@ func (receiver *ValidationServiceProvider) Boot(app foundation.Application) {
|
||||
}
|
||||
|
||||
func (receiver *ValidationServiceProvider) rules() []validation.Rule {
|
||||
return []validation.Rule{}
|
||||
return []validation.Rule{
|
||||
&rules.Exists{},
|
||||
&rules.NotExists{},
|
||||
&rules.Captcha{},
|
||||
}
|
||||
}
|
||||
|
||||
46
app/rules/captcha.go
Normal file
46
app/rules/captcha.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
|
||||
"panel/packages/captcha"
|
||||
)
|
||||
|
||||
type Captcha struct {
|
||||
}
|
||||
|
||||
// Signature The name of the rule.
|
||||
func (receiver *Captcha) Signature() string {
|
||||
return "captcha"
|
||||
}
|
||||
|
||||
// Passes Determine if the validation rule passes.
|
||||
func (receiver *Captcha) Passes(data validation.Data, val any, options ...any) bool {
|
||||
captchaID, exist := data.Get("captcha_id")
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
|
||||
// 第一个参数(如果有),是否清除验证码,如 false
|
||||
clear := true
|
||||
var err error
|
||||
if len(options) > 0 {
|
||||
clear, err = strconv.ParseBool(options[0].(string))
|
||||
if err != nil {
|
||||
clear = true
|
||||
}
|
||||
}
|
||||
|
||||
if !captcha.NewCaptcha().VerifyCaptcha(captchaID.(string), val.(string), clear) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
func (receiver *Captcha) Message() string {
|
||||
return ""
|
||||
}
|
||||
54
app/rules/exists.go
Normal file
54
app/rules/exists.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type Exists struct {
|
||||
}
|
||||
|
||||
// Signature The name of the rule.
|
||||
func (receiver *Exists) Signature() string {
|
||||
return "exists"
|
||||
}
|
||||
|
||||
// Passes Determine if the validation rule passes.
|
||||
func (receiver *Exists) Passes(_ validation.Data, val any, options ...any) bool {
|
||||
|
||||
// 第一个参数,表名称,如 categories
|
||||
tableName := options[0].(string)
|
||||
// 第二个参数,字段名称,如 id
|
||||
fieldName := options[1].(string)
|
||||
// 用户请求过来的数据
|
||||
requestValue, ok := val.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断是否为空
|
||||
if len(requestValue) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断是否存在
|
||||
var count int64
|
||||
query := facades.Orm().Query().Table(tableName).Where(fieldName, requestValue)
|
||||
// 判断第三个参数及之后的参数是否存在
|
||||
if len(options) > 2 {
|
||||
for i := 2; i < len(options); i++ {
|
||||
query = query.OrWhere(options[i].(string), requestValue)
|
||||
}
|
||||
}
|
||||
err := query.Count(&count)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return count != 0
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
func (receiver *Exists) Message() string {
|
||||
return "记录不存在"
|
||||
}
|
||||
54
app/rules/not_exists.go
Normal file
54
app/rules/not_exists.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package rules
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/contracts/validation"
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
type NotExists struct {
|
||||
}
|
||||
|
||||
// Signature The name of the rule.
|
||||
func (receiver *NotExists) Signature() string {
|
||||
return "not_exists"
|
||||
}
|
||||
|
||||
// Passes Determine if the validation rule passes.
|
||||
func (receiver *NotExists) Passes(_ validation.Data, val any, options ...any) bool {
|
||||
|
||||
// 第一个参数,表名称,如 categories
|
||||
tableName := options[0].(string)
|
||||
// 第二个参数,字段名称,如 id
|
||||
fieldName := options[1].(string)
|
||||
// 用户请求过来的数据
|
||||
requestValue, ok := val.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断是否为空
|
||||
if len(requestValue) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断是否存在
|
||||
var count int64
|
||||
query := facades.Orm().Query().Table(tableName).Where(fieldName, requestValue)
|
||||
// 判断第三个参数及之后的参数是否存在
|
||||
if len(options) > 2 {
|
||||
for i := 2; i < len(options); i++ {
|
||||
query = query.OrWhere(options[i].(string), requestValue)
|
||||
}
|
||||
}
|
||||
err := query.Count(&count)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return count == 0
|
||||
}
|
||||
|
||||
// Message Get the validation error message.
|
||||
func (receiver *NotExists) Message() string {
|
||||
return "记录已存在"
|
||||
}
|
||||
34
config/captcha.go
Normal file
34
config/captcha.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config := facades.Config()
|
||||
config.Add("captcha", map[string]any{
|
||||
// 验证码图片高度
|
||||
"height": 60,
|
||||
|
||||
// 验证码图片宽度
|
||||
"width": 120,
|
||||
|
||||
// 验证码的长度
|
||||
"length": 6,
|
||||
|
||||
// 数字的最大倾斜角度
|
||||
"maxskew": 0.6,
|
||||
|
||||
// 图片背景里的混淆点数量
|
||||
"dotcount": 40,
|
||||
|
||||
// 过期时间,单位是分钟
|
||||
"expire_time": 5,
|
||||
|
||||
// debug 模式下的过期时间,方便本地开发调试
|
||||
"debug_expire_time": 3600,
|
||||
|
||||
// 非 production 环境,使用此 key 可跳过验证,方便测试
|
||||
"testing_key": "captcha_skip_test",
|
||||
})
|
||||
}
|
||||
5
go.mod
5
go.mod
@@ -39,6 +39,7 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gertd/go-pluralize v0.2.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-contrib/static v0.0.1 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
@@ -58,6 +59,7 @@ require (
|
||||
github.com/golang-module/carbon/v2 v2.2.3 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@@ -79,6 +81,7 @@ require (
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/iancoleman/strcase v0.2.0 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
@@ -101,6 +104,7 @@ require (
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mojocn/base64Captcha v1.3.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.1.5 // indirect
|
||||
@@ -143,6 +147,7 @@ require (
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.10.0 // indirect
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/oauth2 v0.7.0 // indirect
|
||||
|
||||
10
go.sum
10
go.sum
@@ -153,6 +153,8 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA=
|
||||
github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
@@ -242,6 +244,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0kt
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -362,6 +366,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
@@ -454,6 +460,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0=
|
||||
github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
@@ -665,6 +673,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
|
||||
62
packages/captcha/captcha.go
Normal file
62
packages/captcha/captcha.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Package captcha 处理图片验证码逻辑
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
type Captcha struct {
|
||||
Base64Captcha *base64Captcha.Captcha
|
||||
}
|
||||
|
||||
// once 确保 internalCaptcha 对象只初始化一次
|
||||
var once sync.Once
|
||||
|
||||
// internalCaptcha 内部使用的 Captcha 对象
|
||||
var internalCaptcha *Captcha
|
||||
|
||||
// NewCaptcha 单例模式获取
|
||||
func NewCaptcha() *Captcha {
|
||||
once.Do(func() {
|
||||
// 初始化 Captcha 对象
|
||||
internalCaptcha = &Captcha{}
|
||||
|
||||
// 使用 Cache facade 进行存储,并配置存储 Key 的前缀
|
||||
store := CacheStore{
|
||||
KeyPrefix: facades.Config().GetString("app.name") + ":captcha:",
|
||||
}
|
||||
|
||||
// 配置 base64Captcha 驱动信息
|
||||
driver := base64Captcha.NewDriverDigit(
|
||||
facades.Config().GetInt("captcha.height"), // 宽
|
||||
facades.Config().GetInt("captcha.width"), // 高
|
||||
facades.Config().GetInt("captcha.length"), // 长度
|
||||
facades.Config().Get("captcha.maxskew").(float64), // 数字的最大倾斜角度
|
||||
facades.Config().GetInt("captcha.dotcount"), // 图片背景里的混淆点数量
|
||||
)
|
||||
|
||||
// 实例化 base64Captcha 并赋值给内部使用的 internalCaptcha 对象
|
||||
internalCaptcha.Base64Captcha = base64Captcha.NewCaptcha(driver, &store)
|
||||
})
|
||||
|
||||
return internalCaptcha
|
||||
}
|
||||
|
||||
// GenerateCaptcha 生成图片验证码
|
||||
func (c *Captcha) GenerateCaptcha() (id string, b64s string, err error) {
|
||||
return c.Base64Captcha.Generate()
|
||||
}
|
||||
|
||||
// VerifyCaptcha 验证验证码是否正确
|
||||
func (c *Captcha) VerifyCaptcha(id string, answer string, clear bool) (match bool) {
|
||||
|
||||
// 方便本地和 API 自动测试
|
||||
if facades.Config().GetBool("app.debug") && id == facades.Config().GetString("captcha.testing_key") {
|
||||
return true
|
||||
}
|
||||
// 这样方便用户多次提交,防止表单提交错误需要多次输入图片验证码
|
||||
return c.Base64Captcha.Verify(id, answer, clear)
|
||||
}
|
||||
45
packages/captcha/store_cache.go
Normal file
45
packages/captcha/store_cache.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/goravel/framework/facades"
|
||||
)
|
||||
|
||||
// CacheStore 实现 base64Captcha.Store interface
|
||||
type CacheStore struct {
|
||||
KeyPrefix string
|
||||
}
|
||||
|
||||
// Set 实现 base64Captcha.Store interface 的 Set 方法
|
||||
func (s *CacheStore) Set(key string, value string) error {
|
||||
|
||||
ExpireTime := time.Minute * time.Duration(facades.Config().GetInt("captcha.expire_time"))
|
||||
// 方便本地开发调试
|
||||
if facades.Config().GetBool("app.debug") {
|
||||
ExpireTime = time.Minute * time.Duration(facades.Config().GetInt("captcha.debug_expire_time"))
|
||||
}
|
||||
|
||||
err := facades.Cache().Put(s.KeyPrefix+key, value, ExpireTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get 实现 base64Captcha.Store interface 的 Get 方法
|
||||
func (s *CacheStore) Get(key string, clear bool) string {
|
||||
key = s.KeyPrefix + key
|
||||
val := facades.Cache().Get(key, "").(string)
|
||||
if clear {
|
||||
facades.Cache().Forget(key)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Verify 实现 base64Captcha.Store interface 的 Verify 方法
|
||||
func (s *CacheStore) Verify(key, answer string, clear bool) bool {
|
||||
v := s.Get(key, clear)
|
||||
return v == answer
|
||||
}
|
||||
32
packages/str/str.go
Normal file
32
packages/str/str.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Package str 字符串辅助方法
|
||||
package str
|
||||
|
||||
import (
|
||||
"github.com/gertd/go-pluralize"
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
// Plural 转为复数 user -> users
|
||||
func Plural(word string) string {
|
||||
return pluralize.NewClient().Plural(word)
|
||||
}
|
||||
|
||||
// Singular 转为单数 users -> user
|
||||
func Singular(word string) string {
|
||||
return pluralize.NewClient().Singular(word)
|
||||
}
|
||||
|
||||
// Snake 转为 snake_case,如 TopicComment -> topic_comment
|
||||
func Snake(s string) string {
|
||||
return strcase.ToSnake(s)
|
||||
}
|
||||
|
||||
// Camel 转为 CamelCase,如 topic_comment -> TopicComment
|
||||
func Camel(s string) string {
|
||||
return strcase.ToCamel(s)
|
||||
}
|
||||
|
||||
// LowerCamel 转为 lowerCamelCase,如 TopicComment -> topicComment
|
||||
func LowerCamel(s string) string {
|
||||
return strcase.ToLowerCamel(s)
|
||||
}
|
||||
Reference in New Issue
Block a user