✨ 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
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
.air.toml
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
@@ -3,12 +3,14 @@ package api
|
|||||||
import (
|
import (
|
||||||
"schisandra-cloud-album/api/auth_api"
|
"schisandra-cloud-album/api/auth_api"
|
||||||
"schisandra-cloud-album/api/captcha_api"
|
"schisandra-cloud-album/api/captcha_api"
|
||||||
|
"schisandra-cloud-album/api/sms_api"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apis 统一导出的api
|
// Apis 统一导出的api
|
||||||
type Apis struct {
|
type Apis struct {
|
||||||
AuthApi auth_api.AuthAPI
|
AuthApi auth_api.AuthAPI
|
||||||
CaptchaAPI captcha_api.CaptchaAPI
|
CaptchaApi captcha_api.CaptchaAPI
|
||||||
|
SmsApi sms_api.SmsAPI
|
||||||
}
|
}
|
||||||
|
|
||||||
// Api new函数实例化,实例化完成后会返回结构体地指针类型
|
// Api new函数实例化,实例化完成后会返回结构体地指针类型
|
||||||
|
@@ -3,32 +3,359 @@ package captcha_api
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"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"
|
"log"
|
||||||
|
"schisandra-cloud-album/api/captcha_api/model"
|
||||||
|
"schisandra-cloud-album/common/redis"
|
||||||
|
"schisandra-cloud-album/common/result"
|
||||||
"schisandra-cloud-album/global"
|
"schisandra-cloud-album/global"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GenerateTextCaptcha 生成文本验证码
|
// GenerateRotateCaptcha 生成旋转验证码
|
||||||
func GenerateTextCaptcha() {
|
// @Summary 生成旋转验证码
|
||||||
captData, err := global.TextCaptcha.Generate()
|
// @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 {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dotData := captData.GetData()
|
dotData := captData.GetData()
|
||||||
if dotData == nil {
|
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)
|
dotsByte, err := json.Marshal(dotData)
|
||||||
fmt.Println(">>>>> ", string(dots))
|
|
||||||
|
|
||||||
err = captData.GetMasterImage().SaveToFile("./.caches/master.jpg", option.QualityNone)
|
|
||||||
if err != nil {
|
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 {
|
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`结构体和其字段(各表模型)
|
// WithDefaultQuery 生成默认查询结构体(作为全局变量使用), 即`Q`结构体和其字段(各表模型)
|
||||||
// WithoutContext 生成没有context调用限制的代码供查询
|
// WithoutContext 生成没有context调用限制的代码供查询
|
||||||
// WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
|
// WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
|
||||||
Mode: gen.WithDefaultQuery | gen.WithQueryInterface,
|
Mode: gen.WithDefaultQuery | gen.WithoutContext,
|
||||||
|
|
||||||
// 表字段可为 null 值时, 对应结体字段使用指针类型
|
// 表字段可为 null 值时, 对应结体字段使用指针类型
|
||||||
FieldNullable: true, // generate pointer when field is nullable
|
FieldNullable: true, // generate pointer when field is nullable
|
||||||
|
@@ -13,6 +13,7 @@ const (
|
|||||||
ParamsMatchError ErrorCode = 1008
|
ParamsMatchError ErrorCode = 1008
|
||||||
ParamsNotUniqueError ErrorCode = 1009
|
ParamsNotUniqueError ErrorCode = 1009
|
||||||
FileSizeExceeded ErrorCode = 1010
|
FileSizeExceeded ErrorCode = 1010
|
||||||
|
CaptchaExpireError ErrorCode = 1011
|
||||||
)
|
)
|
||||||
|
|
||||||
type ErrorMap map[ErrorCode]string
|
type ErrorMap map[ErrorCode]string
|
||||||
@@ -28,4 +29,5 @@ var ErrMap = ErrorMap{
|
|||||||
1008: "参数值不匹配",
|
1008: "参数值不匹配",
|
||||||
1009: "参数值不唯一",
|
1009: "参数值不唯一",
|
||||||
1010: "超出文件上传大小限制",
|
1010: "超出文件上传大小限制",
|
||||||
|
1011: "验证码已过期",
|
||||||
}
|
}
|
||||||
|
@@ -38,12 +38,17 @@ func OkWithMessage(msg string, c *gin.Context) {
|
|||||||
func Fail(msg string, data any, c *gin.Context) {
|
func Fail(msg string, data any, c *gin.Context) {
|
||||||
Result(FAIL, msg, data, false, c)
|
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) {
|
func FailWithMessage(msg string, c *gin.Context) {
|
||||||
Result(FAIL, msg, nil, false, c)
|
Result(FAIL, msg, nil, false, c)
|
||||||
}
|
}
|
||||||
func FailWithData(data any, c *gin.Context) {
|
func FailWithData(data any, c *gin.Context) {
|
||||||
Result(FAIL, "fail", data, false, c)
|
Result(FAIL, "fail", data, false, c)
|
||||||
|
}
|
||||||
|
func FailWithNull(c *gin.Context) {
|
||||||
|
Result(FAIL, "fail", nil, false, c)
|
||||||
}
|
}
|
||||||
func FailWithCode(code ErrorCode, c *gin.Context) {
|
func FailWithCode(code ErrorCode, c *gin.Context) {
|
||||||
msg, ok := ErrMap[code]
|
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"`
|
Logger Logger `yaml:"logger"`
|
||||||
System System `yaml:"system"`
|
System System `yaml:"system"`
|
||||||
Redis Redis `yaml:"redis"`
|
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/bindata/chars"
|
||||||
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
|
"github.com/wenlng/go-captcha-assets/resources/fonts/fzshengsksjw"
|
||||||
"github.com/wenlng/go-captcha-assets/resources/images"
|
"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-assets/resources/tiles"
|
||||||
"github.com/wenlng/go-captcha/v2/base/option"
|
"github.com/wenlng/go-captcha/v2/base/option"
|
||||||
"github.com/wenlng/go-captcha/v2/click"
|
"github.com/wenlng/go-captcha/v2/click"
|
||||||
@@ -14,28 +15,125 @@ import (
|
|||||||
"schisandra-cloud-album/global"
|
"schisandra-cloud-album/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func InitCaptcha() {
|
||||||
|
initRotateCaptcha()
|
||||||
|
}
|
||||||
|
|
||||||
// initTextCaptcha 初始化点选验证码
|
// initTextCaptcha 初始化点选验证码
|
||||||
func 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
|
||||||
fonts, err := fzshengsksjw.GetFont()
|
fonts, err := fzshengsksjw.GetFont()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
global.LOG.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// background images
|
// background images
|
||||||
imgs, err := images.GetImages()
|
imgs, err := images.GetImages()
|
||||||
if err != nil {
|
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(
|
builder.SetResources(
|
||||||
click.WithChars(chars.GetChineseChars()),
|
click.WithChars(chars.GetChineseChars()),
|
||||||
click.WithFonts([]*truetype.Font{fonts}),
|
click.WithFonts([]*truetype.Font{fonts}),
|
||||||
click.WithBackgrounds(imgs),
|
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 初始化滑动验证码
|
// initSlideCaptcha 初始化滑动验证码
|
||||||
@@ -95,6 +193,38 @@ func initRotateCaptcha() {
|
|||||||
global.RotateCaptcha = builder.Make()
|
global.RotateCaptcha = builder.Make()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitCaptcha() {
|
// initSlideRegionCaptcha 初始化滑动区域验证码
|
||||||
initTextCaptcha()
|
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:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
paths:
|
paths:
|
||||||
@@ -96,4 +148,178 @@ paths:
|
|||||||
summary: 根据uuid查询用户
|
summary: 根据uuid查询用户
|
||||||
tags:
|
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"
|
swagger: "2.0"
|
||||||
|
@@ -12,11 +12,14 @@ import (
|
|||||||
|
|
||||||
// Config 全局配置文件
|
// Config 全局配置文件
|
||||||
var (
|
var (
|
||||||
CONFIG *config.Config
|
CONFIG *config.Config
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
LOG *logrus.Logger
|
LOG *logrus.Logger
|
||||||
TextCaptcha click.Captcha
|
TextCaptcha click.Captcha
|
||||||
SlideCaptcha slide.Captcha
|
LightTextCaptcha click.Captcha
|
||||||
RotateCaptcha rotate.Captcha
|
ClickShapeCaptcha click.Captcha
|
||||||
REDIS *redis.Client
|
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-playground/validator/v10 v10.22.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // 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/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // 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/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
|
github.com/nicksnyder/go-i18n/v2 v2.4.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // 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/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.8.0 // 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/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 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
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 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
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 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/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 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
|
||||||
|
@@ -31,6 +31,5 @@ CaptchaNotMatch = "captcha not match!"
|
|||||||
CaptchaSendFailed = "captcha send failed!"
|
CaptchaSendFailed = "captcha send failed!"
|
||||||
CaptchaSendSuccess = "captcha send successfully!"
|
CaptchaSendSuccess = "captcha send successfully!"
|
||||||
CaptchaTooOften = "captcha too often!"
|
CaptchaTooOften = "captcha too often!"
|
||||||
CaptchaTooShort = "captcha length must be greater than 6!"
|
PhoneNotEmpty = "phone number can not be empty!"
|
||||||
CaptchaTooLong = "captcha length must be less than 20!"
|
EmailNotEmpty = "email can not be empty!"
|
||||||
CaptchaSendLimit = "captcha send limit!"
|
|
||||||
|
@@ -31,6 +31,5 @@ CaptchaNotMatch = "验证码不匹配!"
|
|||||||
CaptchaSendFailed = "验证码发送失败!"
|
CaptchaSendFailed = "验证码发送失败!"
|
||||||
CaptchaSendSuccess = "验证码发送成功!"
|
CaptchaSendSuccess = "验证码发送成功!"
|
||||||
CaptchaTooOften = "验证码发送过于频繁!"
|
CaptchaTooOften = "验证码发送过于频繁!"
|
||||||
CaptchaTooShort = "验证码长度不能少于6位!"
|
PhoneNotEmpty = "手机号不能为空!"
|
||||||
CaptchaTooLong = "验证码长度不能多于20位!"
|
EmailNotEmpty = "邮箱不能为空!"
|
||||||
CaptchaSendLimit = "验证码发送次数已达上限!"
|
|
||||||
|
12
main.go
12
main.go
@@ -9,18 +9,18 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 初始化配置
|
// 初始化配置
|
||||||
core.InitConfig()
|
core.InitConfig() // 读取配置文件
|
||||||
core.InitLogger()
|
core.InitLogger() // 初始化日志
|
||||||
core.InitGorm()
|
core.InitGorm() // 初始化数据库
|
||||||
core.InitCaptcha()
|
core.InitRedis() // 初始化redis
|
||||||
core.InitRedis()
|
core.InitCaptcha() // 初始化验证码
|
||||||
// 命令行参数绑定
|
// 命令行参数绑定
|
||||||
option := cmd.Parse()
|
option := cmd.Parse()
|
||||||
if cmd.IsStopWeb(&option) {
|
if cmd.IsStopWeb(&option) {
|
||||||
cmd.SwitchOption(&option)
|
cmd.SwitchOption(&option)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r := router.InitRouter()
|
r := router.InitRouter() // 初始化路由
|
||||||
addr := global.CONFIG.System.Addr()
|
addr := global.CONFIG.System.Addr()
|
||||||
global.LOG.Info("Server run on ", addr)
|
global.LOG.Info("Server run on ", addr)
|
||||||
err := r.Run(addr)
|
err := r.Run(addr)
|
||||||
|
@@ -18,7 +18,7 @@ func I18n() gin.HandlerFunc {
|
|||||||
}),
|
}),
|
||||||
ginI18n.WithGetLngHandle(
|
ginI18n.WithGetLngHandle(
|
||||||
func(context *gin.Context, defaultLng string) string {
|
func(context *gin.Context, defaultLng string) string {
|
||||||
lang := context.Query("lang")
|
lang := context.GetHeader("Accept-Language")
|
||||||
if lang == "" {
|
if lang == "" {
|
||||||
return defaultLng
|
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)
|
||||||
|
}
|
@@ -22,7 +22,9 @@ func InitRouter() *gin.Engine {
|
|||||||
// 国际化设置
|
// 国际化设置
|
||||||
publicGroup.Use(middleware.I18n())
|
publicGroup.Use(middleware.I18n())
|
||||||
|
|
||||||
modules.SwaggerRouter(router) // 注册swagger路由
|
modules.SwaggerRouter(router) // 注册swagger路由
|
||||||
modules.AuthRouter(publicGroup) // 注册鉴权路由
|
modules.AuthRouter(publicGroup) // 注册鉴权路由
|
||||||
|
modules.CaptchaRouter(publicGroup) // 注册验证码路由
|
||||||
|
modules.SmsRouter(publicGroup) // 注册短信验证码路由
|
||||||
return router
|
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