✨ add jwt / complete the rotation verification
This commit is contained in:
Binary file not shown.
Before Width: | Height: | Size: 57 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.0 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
.air.toml
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
@@ -3,12 +3,14 @@ package api
|
||||
import (
|
||||
"schisandra-cloud-album/api/auth_api"
|
||||
"schisandra-cloud-album/api/captcha_api"
|
||||
"schisandra-cloud-album/api/sms_api"
|
||||
)
|
||||
|
||||
// Apis 统一导出的api
|
||||
type Apis struct {
|
||||
AuthApi auth_api.AuthAPI
|
||||
CaptchaAPI captcha_api.CaptchaAPI
|
||||
CaptchaApi captcha_api.CaptchaAPI
|
||||
SmsApi sms_api.SmsAPI
|
||||
}
|
||||
|
||||
// Api new函数实例化,实例化完成后会返回结构体地指针类型
|
||||
|
@@ -3,32 +3,359 @@ package captcha_api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/wenlng/go-captcha/v2/base/option"
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/wenlng/go-captcha-assets/helper"
|
||||
"github.com/wenlng/go-captcha/v2/click"
|
||||
"github.com/wenlng/go-captcha/v2/rotate"
|
||||
"github.com/wenlng/go-captcha/v2/slide"
|
||||
"log"
|
||||
"schisandra-cloud-album/api/captcha_api/model"
|
||||
"schisandra-cloud-album/common/redis"
|
||||
"schisandra-cloud-album/common/result"
|
||||
"schisandra-cloud-album/global"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GenerateTextCaptcha 生成文本验证码
|
||||
func GenerateTextCaptcha() {
|
||||
captData, err := global.TextCaptcha.Generate()
|
||||
// GenerateRotateCaptcha 生成旋转验证码
|
||||
// @Summary 生成旋转验证码
|
||||
// @Description 生成旋转验证码
|
||||
// @Tags 旋转验证码
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/rotate/get [get]
|
||||
func (CaptchaAPI) GenerateRotateCaptcha(c *gin.Context) {
|
||||
captchaData, err := global.RotateCaptcha.Generate()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
}
|
||||
blockData := captchaData.GetData()
|
||||
if blockData == nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
var masterImageBase64, thumbImageBase64 string
|
||||
masterImageBase64 = captchaData.GetMasterImage().ToBase64()
|
||||
thumbImageBase64 = captchaData.GetThumbImage().ToBase64()
|
||||
dotsByte, err := json.Marshal(blockData)
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
key := helper.StringToMD5(string(dotsByte))
|
||||
err = redis.Set(key, dotsByte, time.Minute).Err()
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
bt := map[string]interface{}{
|
||||
"key": key,
|
||||
"image": masterImageBase64,
|
||||
"thumb": thumbImageBase64,
|
||||
}
|
||||
result.OkWithData(bt, c)
|
||||
}
|
||||
|
||||
// CheckRotateData 验证旋转验证码
|
||||
// @Summary 验证旋转验证码
|
||||
// @Description 验证旋转验证码
|
||||
// @Tags 旋转验证码
|
||||
// @Param angle query string true "验证码角度"
|
||||
// @Param key query string true "验证码key"
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/rotate/check [post]
|
||||
func (CaptchaAPI) CheckRotateData(c *gin.Context) {
|
||||
rotateRequest := model.RotateCaptchaRequest{}
|
||||
err := c.ShouldBindJSON(&rotateRequest)
|
||||
angle := rotateRequest.Angle
|
||||
key := rotateRequest.Key
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
cacheDataByte, err := redis.Get(key).Bytes()
|
||||
if len(cacheDataByte) == 0 || err != nil {
|
||||
result.FailWithCodeAndMessage(1011, ginI18n.MustGetMessage(c, "CaptchaExpired"), c)
|
||||
return
|
||||
}
|
||||
var dct *rotate.Block
|
||||
if err := json.Unmarshal(cacheDataByte, &dct); err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
sAngle, _ := strconv.ParseFloat(fmt.Sprintf("%v", angle), 64)
|
||||
chkRet := rotate.CheckAngle(int64(sAngle), int64(dct.Angle), 2)
|
||||
if chkRet {
|
||||
result.OkWithMessage("success", c)
|
||||
return
|
||||
}
|
||||
result.FailWithMessage("fail", c)
|
||||
}
|
||||
|
||||
// GenerateBasicTextCaptcha 生成基础文字验证码
|
||||
// @Summary 生成基础文字验证码
|
||||
// @Description 生成基础文字验证码
|
||||
// @Tags 基础文字验证码
|
||||
// @Param type query string true "验证码类型"
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/text/get [get]
|
||||
func (CaptchaAPI) GenerateBasicTextCaptcha(c *gin.Context) {
|
||||
var capt click.Captcha
|
||||
if c.Query("type") == "light" {
|
||||
capt = global.LightTextCaptcha
|
||||
} else {
|
||||
capt = global.TextCaptcha
|
||||
}
|
||||
captData, err := capt.Generate()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
}
|
||||
dotData := captData.GetData()
|
||||
if dotData == nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
var masterImageBase64, thumbImageBase64 string
|
||||
masterImageBase64 = captData.GetMasterImage().ToBase64()
|
||||
thumbImageBase64 = captData.GetThumbImage().ToBase64()
|
||||
|
||||
dotsByte, err := json.Marshal(dotData)
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
key := helper.StringToMD5(string(dotsByte))
|
||||
err = redis.Set(key, dotsByte, time.Minute).Err()
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
bt := map[string]interface{}{
|
||||
"key": key,
|
||||
"image": masterImageBase64,
|
||||
"thumb": thumbImageBase64,
|
||||
}
|
||||
result.OkWithData(bt, c)
|
||||
}
|
||||
|
||||
// CheckClickData 验证基础文字验证码
|
||||
// @Summary 验证基础文字验证码
|
||||
// @Description 验证基础文字验证码
|
||||
// @Tags 基础文字验证码
|
||||
// @Param captcha query string true "验证码"
|
||||
// @Param key query string true "验证码key"
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/text/check [get]
|
||||
func (CaptchaAPI) CheckClickData(c *gin.Context) {
|
||||
dots := c.Query("dots")
|
||||
key := c.Query("key")
|
||||
if dots == "" || key == "" {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
cacheDataByte, err := redis.Get(key).Bytes()
|
||||
if len(cacheDataByte) == 0 || err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
src := strings.Split(dots, ",")
|
||||
|
||||
var dct map[int]*click.Dot
|
||||
if err := json.Unmarshal(cacheDataByte, &dct); err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
chkRet := false
|
||||
if (len(dct) * 2) == len(src) {
|
||||
for i := 0; i < len(dct); i++ {
|
||||
dot := dct[i]
|
||||
j := i * 2
|
||||
k := i*2 + 1
|
||||
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[j]), 64)
|
||||
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[k]), 64)
|
||||
|
||||
chkRet = click.CheckPoint(int64(sx), int64(sy), int64(dot.X), int64(dot.Y), int64(dot.Width), int64(dot.Height), 0)
|
||||
if !chkRet {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if chkRet {
|
||||
result.OkWithMessage("success", c)
|
||||
return
|
||||
}
|
||||
result.FailWithMessage("fail", c)
|
||||
}
|
||||
|
||||
// GenerateClickShapeCaptcha 生成点击形状验证码
|
||||
// @Summary 生成点击形状验证码
|
||||
// @Description 生成点击形状验证码
|
||||
// @Tags 点击形状验证码
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/shape/get [get]
|
||||
func (CaptchaAPI) GenerateClickShapeCaptcha(c *gin.Context) {
|
||||
captData, err := global.ClickShapeCaptcha.Generate()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
dotData := captData.GetData()
|
||||
if dotData == nil {
|
||||
log.Fatalln(">>>>> generate err")
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
var masterImageBase64, thumbImageBase64 string
|
||||
masterImageBase64 = captData.GetMasterImage().ToBase64()
|
||||
thumbImageBase64 = captData.GetThumbImage().ToBase64()
|
||||
|
||||
dots, _ := json.Marshal(dotData)
|
||||
fmt.Println(">>>>> ", string(dots))
|
||||
|
||||
err = captData.GetMasterImage().SaveToFile("./.caches/master.jpg", option.QualityNone)
|
||||
dotsByte, err := json.Marshal(dotData)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
err = captData.GetThumbImage().SaveToFile("./.caches/thumb.png")
|
||||
key := helper.StringToMD5(string(dotsByte))
|
||||
err = redis.Set(key, dotsByte, time.Minute).Err()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
bt := map[string]interface{}{
|
||||
"key": key,
|
||||
"image": masterImageBase64,
|
||||
"thumb": thumbImageBase64,
|
||||
}
|
||||
result.OkWithData(bt, c)
|
||||
}
|
||||
|
||||
// GenerateSlideBasicCaptData 验证点击形状验证码
|
||||
// @Summary 验证点击形状验证码
|
||||
// @Description 验证点击形状验证码
|
||||
// @Tags 点击形状验证码
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/shape/check [get]
|
||||
func (CaptchaAPI) GenerateSlideBasicCaptData(c *gin.Context) {
|
||||
captData, err := global.SlideCaptcha.Generate()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
}
|
||||
blockData := captData.GetData()
|
||||
if blockData == nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
var masterImageBase64, tileImageBase64 string
|
||||
masterImageBase64 = captData.GetMasterImage().ToBase64()
|
||||
|
||||
tileImageBase64 = captData.GetTileImage().ToBase64()
|
||||
|
||||
dotsByte, err := json.Marshal(blockData)
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
key := helper.StringToMD5(string(dotsByte))
|
||||
err = redis.Set(key, dotsByte, time.Minute).Err()
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
bt := map[string]interface{}{
|
||||
"key": key,
|
||||
"image": masterImageBase64,
|
||||
"tile": tileImageBase64,
|
||||
"tile_width": blockData.Width,
|
||||
"tile_height": blockData.Height,
|
||||
"tile_x": blockData.TileX,
|
||||
"tile_y": blockData.TileY,
|
||||
}
|
||||
result.OkWithData(bt, c)
|
||||
}
|
||||
|
||||
// CheckSlideData 验证点击形状验证码
|
||||
// @Summary 验证点击形状验证码
|
||||
// @Description 验证点击形状验证码
|
||||
// @Tags 点击形状验证码
|
||||
// @Param point query string true "点击坐标"
|
||||
// @Param key query string true "验证码key"
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/shape/slide/check [get]
|
||||
func (CaptchaAPI) CheckSlideData(c *gin.Context) {
|
||||
point := c.Query("point")
|
||||
key := c.Query("key")
|
||||
if point == "" || key == "" {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
|
||||
cacheDataByte, err := redis.Get(key).Bytes()
|
||||
if len(cacheDataByte) == 0 || err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
src := strings.Split(point, ",")
|
||||
|
||||
var dct *slide.Block
|
||||
if err := json.Unmarshal(cacheDataByte, &dct); err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
|
||||
chkRet := false
|
||||
if 2 == len(src) {
|
||||
sx, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[0]), 64)
|
||||
sy, _ := strconv.ParseFloat(fmt.Sprintf("%v", src[1]), 64)
|
||||
chkRet = slide.CheckPoint(int64(sx), int64(sy), int64(dct.X), int64(dct.Y), 4)
|
||||
}
|
||||
|
||||
if chkRet {
|
||||
result.OkWithMessage("success", c)
|
||||
return
|
||||
}
|
||||
result.FailWithMessage("fail", c)
|
||||
}
|
||||
|
||||
// GenerateSlideRegionCaptData 验证点击形状验证码
|
||||
// @Summary 验证点击形状验证码
|
||||
// @Description 验证点击形状验证码
|
||||
// @Tags 点击形状验证码
|
||||
// @Success 200 {string} json
|
||||
// @Router /api/captcha/shape/slide/region/get [get]
|
||||
func (CaptchaAPI) GenerateSlideRegionCaptData(c *gin.Context) {
|
||||
captData, err := global.SlideRegionCaptcha.Generate()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
}
|
||||
|
||||
blockData := captData.GetData()
|
||||
if blockData == nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
|
||||
var masterImageBase64, tileImageBase64 string
|
||||
masterImageBase64 = captData.GetMasterImage().ToBase64()
|
||||
tileImageBase64 = captData.GetTileImage().ToBase64()
|
||||
|
||||
blockByte, err := json.Marshal(blockData)
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
key := helper.StringToMD5(string(blockByte))
|
||||
err = redis.Set(key, blockByte, time.Minute).Err()
|
||||
if err != nil {
|
||||
result.FailWithNull(c)
|
||||
return
|
||||
}
|
||||
bt := map[string]interface{}{
|
||||
"code": 0,
|
||||
"key": key,
|
||||
"image": masterImageBase64,
|
||||
"tile": tileImageBase64,
|
||||
"tile_width": blockData.Width,
|
||||
"tile_height": blockData.Height,
|
||||
"tile_x": blockData.TileX,
|
||||
"tile_y": blockData.TileY,
|
||||
}
|
||||
result.OkWithData(bt, c)
|
||||
}
|
||||
|
6
api/captcha_api/model/request_model.go
Normal file
6
api/captcha_api/model/request_model.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package model
|
||||
|
||||
type RotateCaptchaRequest struct {
|
||||
Angle int `json:"angle"`
|
||||
Key string `json:"key"`
|
||||
}
|
3
api/sms_api/sms.go
Normal file
3
api/sms_api/sms.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package sms_api
|
||||
|
||||
type SmsAPI struct{}
|
80
api/sms_api/sms_api.go
Normal file
80
api/sms_api/sms_api.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package sms_api
|
||||
|
||||
import (
|
||||
ginI18n "github.com/gin-contrib/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
gosms "github.com/pkg6/go-sms"
|
||||
"github.com/pkg6/go-sms/gateways"
|
||||
"github.com/pkg6/go-sms/gateways/aliyun"
|
||||
"github.com/pkg6/go-sms/gateways/smsbao"
|
||||
"schisandra-cloud-album/common/result"
|
||||
"schisandra-cloud-album/global"
|
||||
"schisandra-cloud-album/utils"
|
||||
)
|
||||
|
||||
// SendMessageByAli 发送短信验证码
|
||||
// @Summary 发送短信验证码
|
||||
// @Description 发送短信验证码
|
||||
// @Tags 短信验证码
|
||||
// @Produce json
|
||||
// @Param phone query string true "手机号"
|
||||
// @Router /api/sms/ali/send [get]
|
||||
func (SmsAPI) SendMessageByAli(c *gin.Context) {
|
||||
phone := c.Query("phone")
|
||||
if phone == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c)
|
||||
return
|
||||
}
|
||||
sms := gosms.NewParser(gateways.Gateways{
|
||||
ALiYun: aliyun.ALiYun{
|
||||
Host: global.CONFIG.SMS.Ali.Host,
|
||||
AccessKeyId: global.CONFIG.SMS.Ali.AccessKeyID,
|
||||
AccessKeySecret: global.CONFIG.SMS.Ali.AccessKeySecret,
|
||||
},
|
||||
})
|
||||
code := utils.GenValidateCode(6)
|
||||
_, err := sms.Send(phone, gosms.MapStringAny{
|
||||
"content": "您的验证码是:****。请不要把验证码泄露给其他人。",
|
||||
"template": global.CONFIG.SMS.Ali.TemplateID,
|
||||
//"signName": global.CONFIG.SMS.Ali.Signature,
|
||||
"data": gosms.MapStrings{
|
||||
"code": code,
|
||||
},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// SendMessageBySmsBao 短信宝发送短信验证码
|
||||
// @Summary 短信宝发送短信验证码
|
||||
// @Description 发送短信验证码
|
||||
// @Tags 短信验证码
|
||||
// @Produce json
|
||||
// @Param phone query string true "手机号"
|
||||
// @Router /api/sms/smsbao/send [get]
|
||||
func (SmsAPI) SendMessageBySmsBao(c *gin.Context) {
|
||||
phone := c.Query("phone")
|
||||
if phone == "" {
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "PhoneNotEmpty"), c)
|
||||
return
|
||||
}
|
||||
sms := gosms.NewParser(gateways.Gateways{
|
||||
SmsBao: smsbao.SmsBao{
|
||||
User: global.CONFIG.SMS.SmsBao.User,
|
||||
Password: global.CONFIG.SMS.SmsBao.Password,
|
||||
},
|
||||
})
|
||||
code := utils.GenValidateCode(6)
|
||||
_, err := sms.Send(phone, gosms.MapStringAny{
|
||||
"content": "您的验证码是:" + code + "。请不要把验证码泄露给其他人。",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
result.FailWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendFailed"), c)
|
||||
return
|
||||
}
|
||||
result.OkWithMessage(ginI18n.MustGetMessage(c, "CaptchaSendSuccess"), c)
|
||||
}
|
@@ -54,7 +54,7 @@ func initInfo() (db *gorm.DB, g *gen.Generator, fieldOpts []gen.ModelOpt) {
|
||||
// WithDefaultQuery 生成默认查询结构体(作为全局变量使用), 即`Q`结构体和其字段(各表模型)
|
||||
// WithoutContext 生成没有context调用限制的代码供查询
|
||||
// WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
|
||||
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
|
||||
Mode: gen.WithDefaultQuery | gen.WithoutContext,
|
||||
|
||||
// 表字段可为 null 值时, 对应结体字段使用指针类型
|
||||
FieldNullable: true, // generate pointer when field is nullable
|
||||
|
@@ -13,6 +13,7 @@ const (
|
||||
ParamsMatchError ErrorCode = 1008
|
||||
ParamsNotUniqueError ErrorCode = 1009
|
||||
FileSizeExceeded ErrorCode = 1010
|
||||
CaptchaExpireError ErrorCode = 1011
|
||||
)
|
||||
|
||||
type ErrorMap map[ErrorCode]string
|
||||
@@ -28,4 +29,5 @@ var ErrMap = ErrorMap{
|
||||
1008: "参数值不匹配",
|
||||
1009: "参数值不唯一",
|
||||
1010: "超出文件上传大小限制",
|
||||
1011: "验证码已过期",
|
||||
}
|
||||
|
@@ -38,12 +38,17 @@ func OkWithMessage(msg string, c *gin.Context) {
|
||||
func Fail(msg string, data any, c *gin.Context) {
|
||||
Result(FAIL, msg, data, false, c)
|
||||
}
|
||||
func FailWithCodeAndMessage(code int, msg string, c *gin.Context) {
|
||||
Result(code, msg, nil, false, c)
|
||||
}
|
||||
func FailWithMessage(msg string, c *gin.Context) {
|
||||
Result(FAIL, msg, nil, false, c)
|
||||
}
|
||||
func FailWithData(data any, c *gin.Context) {
|
||||
Result(FAIL, "fail", data, false, c)
|
||||
|
||||
}
|
||||
func FailWithNull(c *gin.Context) {
|
||||
Result(FAIL, "fail", nil, false, c)
|
||||
}
|
||||
func FailWithCode(code ErrorCode, c *gin.Context) {
|
||||
msg, ok := ErrMap[code]
|
||||
|
10
config/conf_jwt.go
Normal file
10
config/conf_jwt.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package config
|
||||
|
||||
type JWT struct {
|
||||
Secret string `yaml:"secret"`
|
||||
Expiration string `yaml:"expiration"`
|
||||
RefreshExpiration string `yaml:"refresh-expiration"`
|
||||
RefreshTokenKey string `yaml:"refresh-token-key"`
|
||||
HeaderKey string `yaml:"header-key"`
|
||||
HeaderPrefix string `yaml:"header-prefix"`
|
||||
}
|
18
config/conf_sms.go
Normal file
18
config/conf_sms.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package config
|
||||
|
||||
type SMS struct {
|
||||
Ali Ali `yaml:"ali"` //阿里云短信配置
|
||||
SmsBao SmsBao `yaml:"sms-bao"` //短信宝配置
|
||||
}
|
||||
|
||||
type Ali struct {
|
||||
Host string `yaml:"host"` //主机地址
|
||||
AccessKeyID string `yaml:"access-key-id"` //阿里云AccessKeyId
|
||||
AccessKeySecret string `yaml:"access-key-secret"` //阿里云AccessKeySecret
|
||||
TemplateID string `yaml:"template-id"` //短信模板ID
|
||||
Signature string `yaml:"signature"` //短信签名
|
||||
}
|
||||
type SmsBao struct {
|
||||
User string `yaml:"user"` //短信宝用户名
|
||||
Password string `yaml:"password"` //短信宝密码
|
||||
}
|
@@ -5,4 +5,6 @@ type Config struct {
|
||||
Logger Logger `yaml:"logger"`
|
||||
System System `yaml:"system"`
|
||||
Redis Redis `yaml:"redis"`
|
||||
SMS SMS `yaml:"sms"`
|
||||
JWT JWT `yaml:"jwt"`
|
||||
}
|
||||
|
142
core/captcha.go
142
core/captcha.go
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/wenlng/go-captcha-assets/bindata/chars"
|
||||
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
|
||||
"github.com/wenlng/go-captcha-assets/resources/images"
|
||||
"github.com/wenlng/go-captcha-assets/resources/shapes"
|
||||
"github.com/wenlng/go-captcha-assets/resources/tiles"
|
||||
"github.com/wenlng/go-captcha/v2/base/option"
|
||||
"github.com/wenlng/go-captcha/v2/click"
|
||||
@@ -14,28 +15,125 @@ import (
|
||||
"schisandra-cloud-album/global"
|
||||
)
|
||||
|
||||
func InitCaptcha() {
|
||||
initRotateCaptcha()
|
||||
}
|
||||
|
||||
// initTextCaptcha 初始化点选验证码
|
||||
func initTextCaptcha() {
|
||||
builder := click.NewBuilder()
|
||||
builder := click.NewBuilder(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#1f55c4",
|
||||
"#780592",
|
||||
"#2f6b00",
|
||||
"#910000",
|
||||
"#864401",
|
||||
"#675901",
|
||||
"#016e5c",
|
||||
}),
|
||||
click.WithRangeColors([]string{
|
||||
"#fde98e",
|
||||
"#60c1ff",
|
||||
"#fcb08e",
|
||||
"#fb88ff",
|
||||
"#b4fed4",
|
||||
"#cbfaa9",
|
||||
"#78d6f8",
|
||||
}),
|
||||
)
|
||||
|
||||
// fonts
|
||||
fonts, err := fzshengsksjw.GetFont()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// background images
|
||||
imgs, err := images.GetImages()
|
||||
if err != nil {
|
||||
global.LOG.Fatalln(err)
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// thumb images
|
||||
//thumbImages, err := thumbs.GetThumbs()
|
||||
//if err != nil {
|
||||
// log.Fatalln(err)
|
||||
//}
|
||||
|
||||
// set resources
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
//click.WithChars([]string{
|
||||
// "1A",
|
||||
// "5E",
|
||||
// "3d",
|
||||
// "0p",
|
||||
// "78",
|
||||
// "DL",
|
||||
// "CB",
|
||||
// "9M",
|
||||
//}),
|
||||
//click.WithChars(chars.GetAlphaChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
//click.WithThumbBackgrounds(thumbImages),
|
||||
)
|
||||
global.TextCaptcha = builder.Make()
|
||||
|
||||
// ============================
|
||||
|
||||
builder.Clear()
|
||||
builder.SetOptions(
|
||||
click.WithRangeLen(option.RangeVal{Min: 4, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 4}),
|
||||
click.WithRangeThumbColors([]string{
|
||||
"#4a85fb",
|
||||
"#d93ffb",
|
||||
"#56be01",
|
||||
"#ee2b2b",
|
||||
"#cd6904",
|
||||
"#b49b03",
|
||||
"#01ad90",
|
||||
}),
|
||||
)
|
||||
builder.SetResources(
|
||||
click.WithChars(chars.GetChineseChars()),
|
||||
click.WithFonts([]*truetype.Font{fonts}),
|
||||
click.WithBackgrounds(imgs),
|
||||
)
|
||||
global.TextCaptcha = builder.Make()
|
||||
global.LightTextCaptcha = builder.Make()
|
||||
}
|
||||
|
||||
// initClickShapeCaptcha 初始化点击形状验证码
|
||||
func initClickShapeCaptcha() {
|
||||
builder := click.NewBuilder(
|
||||
click.WithRangeLen(option.RangeVal{Min: 3, Max: 6}),
|
||||
click.WithRangeVerifyLen(option.RangeVal{Min: 2, Max: 3}),
|
||||
click.WithRangeThumbBgDistort(1),
|
||||
click.WithIsThumbNonDeformAbility(true),
|
||||
)
|
||||
|
||||
// shape
|
||||
// click.WithUseShapeOriginalColor(false) -> Random rewriting of graphic colors
|
||||
shapeMaps, err := shapes.GetShapes()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// background images
|
||||
imgs, err := images.GetImages()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// set resources
|
||||
builder.SetResources(
|
||||
click.WithShapes(shapeMaps),
|
||||
click.WithBackgrounds(imgs),
|
||||
)
|
||||
global.ClickShapeCaptcha = builder.MakeWithShape()
|
||||
}
|
||||
|
||||
// initSlideCaptcha 初始化滑动验证码
|
||||
@@ -95,6 +193,38 @@ func initRotateCaptcha() {
|
||||
global.RotateCaptcha = builder.Make()
|
||||
}
|
||||
|
||||
func InitCaptcha() {
|
||||
initTextCaptcha()
|
||||
// initSlideRegionCaptcha 初始化滑动区域验证码
|
||||
func initSlideRegionCaptcha() {
|
||||
builder := slide.NewBuilder(
|
||||
slide.WithGenGraphNumber(2),
|
||||
slide.WithEnableGraphVerticalRandom(true),
|
||||
)
|
||||
|
||||
// background image
|
||||
imgs, err := images.GetImages()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
graphs, err := tiles.GetTiles()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
var newGraphs = make([]*slide.GraphImage, 0, len(graphs))
|
||||
for i := 0; i < len(graphs); i++ {
|
||||
graph := graphs[i]
|
||||
newGraphs = append(newGraphs, &slide.GraphImage{
|
||||
OverlayImage: graph.OverlayImage,
|
||||
MaskImage: graph.MaskImage,
|
||||
ShadowImage: graph.ShadowImage,
|
||||
})
|
||||
}
|
||||
|
||||
// set resources
|
||||
builder.SetResources(
|
||||
slide.WithGraphImages(newGraphs),
|
||||
slide.WithBackgrounds(imgs),
|
||||
)
|
||||
|
||||
global.SlideRegionCaptcha = builder.MakeWithRegion()
|
||||
}
|
||||
|
335
docs/docs.go
335
docs/docs.go
@@ -162,6 +162,341 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/auth/user/register": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"鉴权模块"
|
||||
],
|
||||
"summary": "用户注册",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "用户信息",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.ScaAuthUser"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/rotate/check": {
|
||||
"post": {
|
||||
"description": "验证旋转验证码",
|
||||
"tags": [
|
||||
"旋转验证码"
|
||||
],
|
||||
"summary": "验证旋转验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码角度",
|
||||
"name": "angle",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/rotate/get": {
|
||||
"get": {
|
||||
"description": "生成旋转验证码",
|
||||
"tags": [
|
||||
"旋转验证码"
|
||||
],
|
||||
"summary": "生成旋转验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/check": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/get": {
|
||||
"get": {
|
||||
"description": "生成点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "生成点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/slide/check": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "点击坐标",
|
||||
"name": "point",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/slide/region/get": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/text/check": {
|
||||
"get": {
|
||||
"description": "验证基础文字验证码",
|
||||
"tags": [
|
||||
"基础文字验证码"
|
||||
],
|
||||
"summary": "验证基础文字验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码",
|
||||
"name": "captcha",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/text/get": {
|
||||
"get": {
|
||||
"description": "生成基础文字验证码",
|
||||
"tags": [
|
||||
"基础文字验证码"
|
||||
],
|
||||
"summary": "生成基础文字验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码类型",
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/sms/ali/send": {
|
||||
"get": {
|
||||
"description": "发送短信验证码",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"短信验证码"
|
||||
],
|
||||
"summary": "发送短信验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "手机号",
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/api/sms/smsbao/send": {
|
||||
"get": {
|
||||
"description": "发送短信验证码",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"短信验证码"
|
||||
],
|
||||
"summary": "发送短信验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "手机号",
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"model.ScaAuthUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"description": "头像",
|
||||
"type": "string"
|
||||
},
|
||||
"blog": {
|
||||
"description": "博客",
|
||||
"type": "string"
|
||||
},
|
||||
"company": {
|
||||
"description": "公司",
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"description": "创建人",
|
||||
"type": "string"
|
||||
},
|
||||
"created_time": {
|
||||
"description": "创建时间",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "邮箱",
|
||||
"type": "string"
|
||||
},
|
||||
"gender": {
|
||||
"description": "性别",
|
||||
"type": "string"
|
||||
},
|
||||
"introduce": {
|
||||
"description": "介绍",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"description": "地址",
|
||||
"type": "string"
|
||||
},
|
||||
"nickname": {
|
||||
"description": "昵称",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "电话",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "状态 0 正常 1 封禁",
|
||||
"type": "integer"
|
||||
},
|
||||
"update_by": {
|
||||
"description": "更新人",
|
||||
"type": "string"
|
||||
},
|
||||
"update_time": {
|
||||
"description": "更新时间",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名",
|
||||
"type": "string"
|
||||
},
|
||||
"uuid": {
|
||||
"description": "唯一ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
@@ -151,6 +151,341 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/auth/user/register": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"鉴权模块"
|
||||
],
|
||||
"summary": "用户注册",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "用户信息",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.ScaAuthUser"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/rotate/check": {
|
||||
"post": {
|
||||
"description": "验证旋转验证码",
|
||||
"tags": [
|
||||
"旋转验证码"
|
||||
],
|
||||
"summary": "验证旋转验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码角度",
|
||||
"name": "angle",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/rotate/get": {
|
||||
"get": {
|
||||
"description": "生成旋转验证码",
|
||||
"tags": [
|
||||
"旋转验证码"
|
||||
],
|
||||
"summary": "生成旋转验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/check": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/get": {
|
||||
"get": {
|
||||
"description": "生成点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "生成点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/slide/check": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "点击坐标",
|
||||
"name": "point",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/shape/slide/region/get": {
|
||||
"get": {
|
||||
"description": "验证点击形状验证码",
|
||||
"tags": [
|
||||
"点击形状验证码"
|
||||
],
|
||||
"summary": "验证点击形状验证码",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/text/check": {
|
||||
"get": {
|
||||
"description": "验证基础文字验证码",
|
||||
"tags": [
|
||||
"基础文字验证码"
|
||||
],
|
||||
"summary": "验证基础文字验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码",
|
||||
"name": "captcha",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码key",
|
||||
"name": "key",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/captcha/text/get": {
|
||||
"get": {
|
||||
"description": "生成基础文字验证码",
|
||||
"tags": [
|
||||
"基础文字验证码"
|
||||
],
|
||||
"summary": "生成基础文字验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "验证码类型",
|
||||
"name": "type",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/sms/ali/send": {
|
||||
"get": {
|
||||
"description": "发送短信验证码",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"短信验证码"
|
||||
],
|
||||
"summary": "发送短信验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "手机号",
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
},
|
||||
"/api/sms/smsbao/send": {
|
||||
"get": {
|
||||
"description": "发送短信验证码",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"短信验证码"
|
||||
],
|
||||
"summary": "发送短信验证码",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "手机号",
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"model.ScaAuthUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar": {
|
||||
"description": "头像",
|
||||
"type": "string"
|
||||
},
|
||||
"blog": {
|
||||
"description": "博客",
|
||||
"type": "string"
|
||||
},
|
||||
"company": {
|
||||
"description": "公司",
|
||||
"type": "string"
|
||||
},
|
||||
"created_by": {
|
||||
"description": "创建人",
|
||||
"type": "string"
|
||||
},
|
||||
"created_time": {
|
||||
"description": "创建时间",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "邮箱",
|
||||
"type": "string"
|
||||
},
|
||||
"gender": {
|
||||
"description": "性别",
|
||||
"type": "string"
|
||||
},
|
||||
"introduce": {
|
||||
"description": "介绍",
|
||||
"type": "string"
|
||||
},
|
||||
"location": {
|
||||
"description": "地址",
|
||||
"type": "string"
|
||||
},
|
||||
"nickname": {
|
||||
"description": "昵称",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "电话",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "状态 0 正常 1 封禁",
|
||||
"type": "integer"
|
||||
},
|
||||
"update_by": {
|
||||
"description": "更新人",
|
||||
"type": "string"
|
||||
},
|
||||
"update_time": {
|
||||
"description": "更新时间",
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"description": "用户名",
|
||||
"type": "string"
|
||||
},
|
||||
"uuid": {
|
||||
"description": "唯一ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,3 +1,55 @@
|
||||
definitions:
|
||||
model.ScaAuthUser:
|
||||
properties:
|
||||
avatar:
|
||||
description: 头像
|
||||
type: string
|
||||
blog:
|
||||
description: 博客
|
||||
type: string
|
||||
company:
|
||||
description: 公司
|
||||
type: string
|
||||
created_by:
|
||||
description: 创建人
|
||||
type: string
|
||||
created_time:
|
||||
description: 创建时间
|
||||
type: string
|
||||
email:
|
||||
description: 邮箱
|
||||
type: string
|
||||
gender:
|
||||
description: 性别
|
||||
type: string
|
||||
introduce:
|
||||
description: 介绍
|
||||
type: string
|
||||
location:
|
||||
description: 地址
|
||||
type: string
|
||||
nickname:
|
||||
description: 昵称
|
||||
type: string
|
||||
phone:
|
||||
description: 电话
|
||||
type: string
|
||||
status:
|
||||
description: 状态 0 正常 1 封禁
|
||||
type: integer
|
||||
update_by:
|
||||
description: 更新人
|
||||
type: string
|
||||
update_time:
|
||||
description: 更新时间
|
||||
type: string
|
||||
username:
|
||||
description: 用户名
|
||||
type: string
|
||||
uuid:
|
||||
description: 唯一ID
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
@@ -96,4 +148,178 @@ paths:
|
||||
summary: 根据uuid查询用户
|
||||
tags:
|
||||
- 鉴权模块
|
||||
/api/auth/user/register:
|
||||
post:
|
||||
parameters:
|
||||
- description: 用户信息
|
||||
in: body
|
||||
name: user
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/model.ScaAuthUser'
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 用户注册
|
||||
tags:
|
||||
- 鉴权模块
|
||||
/api/captcha/rotate/check:
|
||||
post:
|
||||
description: 验证旋转验证码
|
||||
parameters:
|
||||
- description: 验证码角度
|
||||
in: query
|
||||
name: angle
|
||||
required: true
|
||||
type: string
|
||||
- description: 验证码key
|
||||
in: query
|
||||
name: key
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 验证旋转验证码
|
||||
tags:
|
||||
- 旋转验证码
|
||||
/api/captcha/rotate/get:
|
||||
get:
|
||||
description: 生成旋转验证码
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 生成旋转验证码
|
||||
tags:
|
||||
- 旋转验证码
|
||||
/api/captcha/shape/check:
|
||||
get:
|
||||
description: 验证点击形状验证码
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 验证点击形状验证码
|
||||
tags:
|
||||
- 点击形状验证码
|
||||
/api/captcha/shape/get:
|
||||
get:
|
||||
description: 生成点击形状验证码
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 生成点击形状验证码
|
||||
tags:
|
||||
- 点击形状验证码
|
||||
/api/captcha/shape/slide/check:
|
||||
get:
|
||||
description: 验证点击形状验证码
|
||||
parameters:
|
||||
- description: 点击坐标
|
||||
in: query
|
||||
name: point
|
||||
required: true
|
||||
type: string
|
||||
- description: 验证码key
|
||||
in: query
|
||||
name: key
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 验证点击形状验证码
|
||||
tags:
|
||||
- 点击形状验证码
|
||||
/api/captcha/shape/slide/region/get:
|
||||
get:
|
||||
description: 验证点击形状验证码
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 验证点击形状验证码
|
||||
tags:
|
||||
- 点击形状验证码
|
||||
/api/captcha/text/check:
|
||||
get:
|
||||
description: 验证基础文字验证码
|
||||
parameters:
|
||||
- description: 验证码
|
||||
in: query
|
||||
name: captcha
|
||||
required: true
|
||||
type: string
|
||||
- description: 验证码key
|
||||
in: query
|
||||
name: key
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 验证基础文字验证码
|
||||
tags:
|
||||
- 基础文字验证码
|
||||
/api/captcha/text/get:
|
||||
get:
|
||||
description: 生成基础文字验证码
|
||||
parameters:
|
||||
- description: 验证码类型
|
||||
in: query
|
||||
name: type
|
||||
required: true
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: string
|
||||
summary: 生成基础文字验证码
|
||||
tags:
|
||||
- 基础文字验证码
|
||||
/api/sms/ali/send:
|
||||
get:
|
||||
description: 发送短信验证码
|
||||
parameters:
|
||||
- description: 手机号
|
||||
in: query
|
||||
name: phone
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses: {}
|
||||
summary: 发送短信验证码
|
||||
tags:
|
||||
- 短信验证码
|
||||
/api/sms/smsbao/send:
|
||||
get:
|
||||
description: 发送短信验证码
|
||||
parameters:
|
||||
- description: 手机号
|
||||
in: query
|
||||
name: phone
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses: {}
|
||||
summary: 发送短信验证码
|
||||
tags:
|
||||
- 短信验证码
|
||||
swagger: "2.0"
|
||||
|
@@ -16,7 +16,10 @@ var (
|
||||
DB *gorm.DB
|
||||
LOG *logrus.Logger
|
||||
TextCaptcha click.Captcha
|
||||
LightTextCaptcha click.Captcha
|
||||
ClickShapeCaptcha click.Captcha
|
||||
SlideCaptcha slide.Captcha
|
||||
RotateCaptcha rotate.Captcha
|
||||
SlideRegionCaptcha slide.Captcha
|
||||
REDIS *redis.Client
|
||||
)
|
||||
|
3
go.mod
3
go.mod
@@ -43,6 +43,7 @@ require (
|
||||
github.com/go-playground/validator/v10 v10.22.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@@ -55,6 +56,8 @@ require (
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pkg6/go-requests v0.2.2 // indirect
|
||||
github.com/pkg6/go-sms v0.1.2 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
|
6
go.sum
6
go.sum
@@ -58,6 +58,8 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
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=
|
||||
@@ -111,6 +113,10 @@ github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC
|
||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pkg6/go-requests v0.2.2 h1:wL0aFmyybM/Wuqj8xQa3sNL5ioAL97hQZ78TJovltbM=
|
||||
github.com/pkg6/go-requests v0.2.2/go.mod h1:/rcVm8Itd2djtxDVxjRnHURChV86TB4ooZnP+IBZBmg=
|
||||
github.com/pkg6/go-sms v0.1.2 h1:HZQlBkRVF9xQHhyCMB3kXY/kltfvuNgMTKuN/DoSg7w=
|
||||
github.com/pkg6/go-sms v0.1.2/go.mod h1:PwFBEssnkYXw+mfSmQ+6fwgXgrcUB9NK5dLUglx+ZW4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||
|
@@ -31,6 +31,5 @@ CaptchaNotMatch = "captcha not match!"
|
||||
CaptchaSendFailed = "captcha send failed!"
|
||||
CaptchaSendSuccess = "captcha send successfully!"
|
||||
CaptchaTooOften = "captcha too often!"
|
||||
CaptchaTooShort = "captcha length must be greater than 6!"
|
||||
CaptchaTooLong = "captcha length must be less than 20!"
|
||||
CaptchaSendLimit = "captcha send limit!"
|
||||
PhoneNotEmpty = "phone number can not be empty!"
|
||||
EmailNotEmpty = "email can not be empty!"
|
||||
|
@@ -31,6 +31,5 @@ CaptchaNotMatch = "验证码不匹配!"
|
||||
CaptchaSendFailed = "验证码发送失败!"
|
||||
CaptchaSendSuccess = "验证码发送成功!"
|
||||
CaptchaTooOften = "验证码发送过于频繁!"
|
||||
CaptchaTooShort = "验证码长度不能少于6位!"
|
||||
CaptchaTooLong = "验证码长度不能多于20位!"
|
||||
CaptchaSendLimit = "验证码发送次数已达上限!"
|
||||
PhoneNotEmpty = "手机号不能为空!"
|
||||
EmailNotEmpty = "邮箱不能为空!"
|
||||
|
12
main.go
12
main.go
@@ -9,18 +9,18 @@ import (
|
||||
|
||||
func main() {
|
||||
// 初始化配置
|
||||
core.InitConfig()
|
||||
core.InitLogger()
|
||||
core.InitGorm()
|
||||
core.InitCaptcha()
|
||||
core.InitRedis()
|
||||
core.InitConfig() // 读取配置文件
|
||||
core.InitLogger() // 初始化日志
|
||||
core.InitGorm() // 初始化数据库
|
||||
core.InitRedis() // 初始化redis
|
||||
core.InitCaptcha() // 初始化验证码
|
||||
// 命令行参数绑定
|
||||
option := cmd.Parse()
|
||||
if cmd.IsStopWeb(&option) {
|
||||
cmd.SwitchOption(&option)
|
||||
return
|
||||
}
|
||||
r := router.InitRouter()
|
||||
r := router.InitRouter() // 初始化路由
|
||||
addr := global.CONFIG.System.Addr()
|
||||
global.LOG.Info("Server run on ", addr)
|
||||
err := r.Run(addr)
|
||||
|
@@ -18,7 +18,7 @@ func I18n() gin.HandlerFunc {
|
||||
}),
|
||||
ginI18n.WithGetLngHandle(
|
||||
func(context *gin.Context, defaultLng string) string {
|
||||
lang := context.Query("lang")
|
||||
lang := context.GetHeader("Accept-Language")
|
||||
if lang == "" {
|
||||
return defaultLng
|
||||
}
|
||||
|
14
router/modules/captcha_router.go
Normal file
14
router/modules/captcha_router.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package modules
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"schisandra-cloud-album/api"
|
||||
)
|
||||
|
||||
var captchaApi = api.Api.CaptchaApi
|
||||
|
||||
func CaptchaRouter(router *gin.RouterGroup) {
|
||||
group := router.Group("/captcha")
|
||||
group.GET("/rotate/get", captchaApi.GenerateRotateCaptcha)
|
||||
group.POST("/rotate/check", captchaApi.CheckRotateData)
|
||||
}
|
14
router/modules/sms_router.go
Normal file
14
router/modules/sms_router.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package modules
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"schisandra-cloud-album/api"
|
||||
)
|
||||
|
||||
var smsApi = api.Api.SmsApi
|
||||
|
||||
func SmsRouter(router *gin.RouterGroup) {
|
||||
group := router.Group("/sms")
|
||||
group.GET("/ali/send", smsApi.SendMessageByAli)
|
||||
group.GET("/smsbao/send", smsApi.SendMessageBySmsBao)
|
||||
}
|
@@ -24,5 +24,7 @@ func InitRouter() *gin.Engine {
|
||||
|
||||
modules.SwaggerRouter(router) // 注册swagger路由
|
||||
modules.AuthRouter(publicGroup) // 注册鉴权路由
|
||||
modules.CaptchaRouter(publicGroup) // 注册验证码路由
|
||||
modules.SmsRouter(publicGroup) // 注册短信验证码路由
|
||||
return router
|
||||
}
|
||||
|
67
utils/cache.go
Normal file
67
utils/cache.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type cachedata = struct {
|
||||
data []byte
|
||||
createAt time.Time
|
||||
}
|
||||
|
||||
var mux sync.Mutex
|
||||
|
||||
var cachemaps = make(map[string]*cachedata)
|
||||
|
||||
// WriteCache .
|
||||
func WriteCache(key string, data []byte) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
cachemaps[key] = &cachedata{
|
||||
createAt: time.Now(),
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadCache .
|
||||
func ReadCache(key string) []byte {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
if cd, ok := cachemaps[key]; ok {
|
||||
return cd.data
|
||||
}
|
||||
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
// ClearCache .
|
||||
func ClearCache(key string) {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
delete(cachemaps, key)
|
||||
}
|
||||
|
||||
// RunTimedTask .
|
||||
func RunTimedTask() {
|
||||
ticker := time.NewTicker(time.Minute * 5)
|
||||
go func() {
|
||||
for range ticker.C {
|
||||
checkCacheOvertimeFile()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func checkCacheOvertimeFile() {
|
||||
var keys = make([]string, 0)
|
||||
for key, data := range cachemaps {
|
||||
ex := time.Now().Unix() - data.createAt.Unix()
|
||||
if ex > (60 * 30) {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
ClearCache(key)
|
||||
}
|
||||
}
|
20
utils/genValidateCode.go
Normal file
20
utils/genValidateCode.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenValidateCode(width int) string {
|
||||
numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
r := len(numeric)
|
||||
rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
var sb strings.Builder
|
||||
for i := 0; i < width; i++ {
|
||||
fmt.Fprintf(&sb, "%d", numeric[rand.Intn(r)])
|
||||
}
|
||||
return sb.String()
|
||||
}
|
49
utils/jwt.go
Normal file
49
utils/jwt.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"schisandra-cloud-album/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
type JWTPayload struct {
|
||||
UserID int `json:"user_id"`
|
||||
Role string `json:"role"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
type JWTClaims struct {
|
||||
JWTPayload
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
var MySecret = []byte(global.CONFIG.JWT.Secret)
|
||||
|
||||
// GenerateToken generates a JWT token with the given payload
|
||||
func GenerateToken(payload JWTPayload) (string, error) {
|
||||
claims := JWTClaims{
|
||||
JWTPayload: payload,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString(MySecret)
|
||||
}
|
||||
|
||||
// ParseToken parses a JWT token and returns the payload
|
||||
func ParseToken(tokenString string) (*JWTPayload, error) {
|
||||
token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return MySecret, nil
|
||||
})
|
||||
if err != nil {
|
||||
global.LOG.Error(err)
|
||||
return nil, err
|
||||
}
|
||||
if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
|
||||
return &claims.JWTPayload, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
Reference in New Issue
Block a user