修复登录及验证码功能
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
@@ -69,20 +70,23 @@ func drawImage(code string) image.Image {
|
||||
bg := color.RGBA{240, 245, 255, 255}
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.Point{}, draw.Src)
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
for i := 0; i < 3; i++ {
|
||||
x1, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
|
||||
y1, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
|
||||
x2, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
|
||||
y2, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
|
||||
drawLine(img, int(x1.Int64()), int(y1.Int64()), int(x2.Int64()), int(y2.Int64()), randomColor())
|
||||
}
|
||||
for i := 0; i < 30; i++ {
|
||||
for i := 0; i < 25; i++ {
|
||||
x, _ := rand.Int(rand.Reader, big.NewInt(int64(width)))
|
||||
y, _ := rand.Int(rand.Reader, big.NewInt(int64(height)))
|
||||
img.Set(int(x.Int64()), int(y.Int64()), randomColor())
|
||||
}
|
||||
// 字符居中绘制:字符高 21px (7*3),画布高 40px,垂直居中起始 y 约 9px;4 字符总宽 90px,水平居中起始 x 为 15px
|
||||
for i, ch := range code {
|
||||
drawChar(img, 15+i*25, 28, string(ch), randomDarkColor())
|
||||
offsetY, _ := rand.Int(rand.Reader, big.NewInt(3))
|
||||
charY := 8 + int(offsetY.Int64()) // 8..10px,垂直完美居中
|
||||
drawChar(img, 15+i*25, charY, string(ch), randomDarkColor())
|
||||
}
|
||||
return img
|
||||
}
|
||||
@@ -134,7 +138,7 @@ func drawChar(img *image.RGBA, x, y int, ch string, c color.Color) {
|
||||
if px == '1' {
|
||||
for dy := 0; dy < scale; dy++ {
|
||||
for dx := 0; dx < scale; dx++ {
|
||||
img.Set(x+col*scale+dx, y-row*scale-7*scale+dy, c)
|
||||
img.Set(x+col*scale+dx, y+row*scale+dy, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +196,9 @@ func randomDarkColor() color.RGBA {
|
||||
}
|
||||
|
||||
func encodeToBase64(img image.Image) string {
|
||||
var buf strings.Builder
|
||||
png.Encode(base64.NewEncoder(base64.StdEncoding, &buf), img)
|
||||
var buf bytes.Buffer
|
||||
enc := base64.NewEncoder(base64.StdEncoding, &buf)
|
||||
_ = png.Encode(enc, img)
|
||||
_ = enc.Close()
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"image/png"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDrawImageValidPNG(t *testing.T) {
|
||||
img := drawImage("ABCD")
|
||||
b64 := encodeToBase64(img)
|
||||
data, err := base64.StdEncoding.DecodeString(b64)
|
||||
if err != nil {
|
||||
t.Fatalf("base64 decode failed: %v", err)
|
||||
}
|
||||
|
||||
decoded, err := png.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatalf("png decode failed: %v", err)
|
||||
}
|
||||
|
||||
bounds := decoded.Bounds()
|
||||
if bounds.Dx() != 120 || bounds.Dy() != 40 {
|
||||
t.Fatalf("expected 120x40, got %dx%d", bounds.Dx(), bounds.Dy())
|
||||
}
|
||||
|
||||
// Verify all characters in chars set can be drawn without panic
|
||||
for _, ch := range chars {
|
||||
_ = drawImage(strings.Repeat(string(ch), 4))
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package geetest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"photowall/internal/config"
|
||||
)
|
||||
|
||||
// 极验行为验证封装
|
||||
@@ -10,14 +9,18 @@ import (
|
||||
|
||||
var ErrNotConfigured = errors.New("极验未配置,请联系管理员")
|
||||
|
||||
type Config struct {
|
||||
CaptchaID string
|
||||
CaptchaKey string
|
||||
}
|
||||
|
||||
// Validate 校验极验二次验证结果
|
||||
// lotNumber: 极验返回的 lot_number
|
||||
// captchaOutput: 极验返回的 captcha_output
|
||||
// passToken: 极验返回的 pass_token
|
||||
// genTime: 极验返回的 gen_time
|
||||
func Validate(lotNumber, captchaOutput, passToken, genTime string) error {
|
||||
cfg := config.C
|
||||
if cfg.GeeTestID == "" || cfg.GeeTestKey == "" {
|
||||
func Validate(cfg Config, lotNumber, captchaOutput, passToken, genTime string) error {
|
||||
if cfg.CaptchaID == "" || cfg.CaptchaKey == "" {
|
||||
return ErrNotConfigured
|
||||
}
|
||||
// TODO: 接入极验官方 SDK 进行二次验证
|
||||
|
||||
+15
-7
@@ -24,26 +24,34 @@ type Config struct {
|
||||
DB int
|
||||
}
|
||||
|
||||
// Init 初始化 Redis 连接,失败则降级为内存模式
|
||||
// Init 初始化 Redis 连接,未开启或连接失败则降级为内存模式
|
||||
func Init(cfg Config) {
|
||||
if cfg.Host == "" {
|
||||
log.Printf("[redis] Redis 未配置,已降级为内存模式运行")
|
||||
Enabled = false
|
||||
Client = nil
|
||||
return
|
||||
}
|
||||
|
||||
Client = redis.NewClient(&redis.Options{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Host, cfg.Port),
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Host, cfg.Port),
|
||||
Password: cfg.Password,
|
||||
DB: cfg.DB,
|
||||
DialTimeout: 2 * time.Second,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := Client.Ping(ctx).Err(); err != nil {
|
||||
log.Printf("[redis] connection failed (%v), fallback to memory mode", err)
|
||||
log.Printf("[redis] Redis 没开或连接失败 (%v),已降级为内存模式运行", err)
|
||||
Enabled = false
|
||||
Client = nil
|
||||
return
|
||||
}
|
||||
|
||||
Enabled = true
|
||||
log.Printf("[redis] connected to %s:%s (db=%d)", cfg.Host, cfg.Port, cfg.DB)
|
||||
log.Printf("[redis] Redis 连接成功: %s:%s (db=%d)", cfg.Host, cfg.Port, cfg.DB)
|
||||
}
|
||||
|
||||
// Set 设置键值(带过期时间,0=不过期)
|
||||
|
||||
+10
-4
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"photowall/internal/config"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -15,6 +14,14 @@ import (
|
||||
|
||||
var ErrNotConfigured = errors.New("短信服务未配置,请联系管理员")
|
||||
|
||||
type Config struct {
|
||||
Provider string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
SignName string
|
||||
TemplateCode string
|
||||
}
|
||||
|
||||
type codeItem struct {
|
||||
code string
|
||||
expireAt time.Time
|
||||
@@ -26,11 +33,10 @@ var (
|
||||
|
||||
// SendCode 发送短信验证码
|
||||
// 开发阶段(未配置短信服务商):验证码输出到日志,方便测试
|
||||
func SendCode(phone string) (string, error) {
|
||||
cfg := config.C
|
||||
func SendCode(cfg Config, phone string) (string, error) {
|
||||
code := generateCode()
|
||||
|
||||
if cfg.SMSAccessKey == "" || cfg.SMSSecretKey == "" || cfg.SMSSignName == "" || cfg.SMSTemplateCode == "" {
|
||||
if cfg.AccessKey == "" || cfg.SecretKey == "" || cfg.SignName == "" || cfg.TemplateCode == "" {
|
||||
// 未配置短信服务商,开发模式:日志输出验证码
|
||||
fmt.Printf("[SMS-DEV] 手机号 %s 的验证码: %s (5分钟有效)\n", phone, code)
|
||||
store.Store(phone, codeItem{code: code, expireAt: time.Now().Add(5 * time.Minute)})
|
||||
|
||||
Reference in New Issue
Block a user