diff --git a/README.md b/README.md index c5de401..8d2d84d 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ ## 功能 + [x] Access Token 自动刷新 & 提供外部访问接口 -+ [ ] 自定义菜单 -+ [ ] 登录验证 ++ [x] 自定义菜单 ++ [x] 登录验证 + [ ] 自定义回复 ## 配置 @@ -22,7 +22,7 @@ 4. 初始账户用户名为 `root`,密码为 `123456`,记得登录后立刻修改密码。 5. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)获取 AppID 和 AppSecret,并在我们的配置页面填入上述信息,另外还需要配置 IP 白名单,按照页面上的提示完成即可。 6. 前往[微信公众号配置页面 -> 设置与开发 -> 基本配置](https://mp.weixin.qq.com/)填写以下配置: - 1. `URL` 填:`https:///api/wechat_verification` + 1. `URL` 填:`https:///api/wechat` 2. `Token` 首先在我们的配置页面随便填写一个 Token,然后在微信公众号的配置页面填入同一个 Token 即可。 3. `EncodingAESKey` 点随机生成,然后在我们的配置页面填入该值。 4. 消息加解密方式选择明文模式。 @@ -31,5 +31,13 @@ ## API ### 获取 Access Token 1. 请求方法:`GET` -2. URL:`/api/access_token` -3. 无参数,但是需要设置 HTTP 头部:`Authorization: your token` \ No newline at end of file +2. URL:`/api/wechat/access_token` +3. 无参数,但是需要设置 HTTP 头部:`Authorization: ` + +### 通过验证码查询用户 ID +1. 请求方法:`GET` +2. URL:`/api/wechat/user?code=` +3. 需要设置 HTTP 头部:`Authorization: ` + +### 注意 +需要将 `` 和 `` 替换为实际的内容。 \ No newline at end of file diff --git a/common/verification.go b/common/verification.go index cea9bda..b63ec66 100644 --- a/common/verification.go +++ b/common/verification.go @@ -2,6 +2,9 @@ package common import ( "github.com/google/uuid" + "math" + "math/rand" + "strconv" "strings" "sync" "time" @@ -13,14 +16,15 @@ type verificationValue struct { } const ( - EmailVerificationPurpose = "v" - PasswordResetPurpose = "r" + EmailVerificationPurpose = "v" + PasswordResetPurpose = "r" + WeChatVerificationPurpose = "w" ) var verificationMutex sync.Mutex var verificationMap map[string]verificationValue -var verificationMapMaxSize = 10 -var VerificationValidMinutes = 10 +var verificationMapMaxSize = 20 +var VerificationValidMinutes = 3 func GenerateVerificationCode(length int) string { code := uuid.New().String() @@ -31,6 +35,27 @@ func GenerateVerificationCode(length int) string { return code[:length] } +func GenerateAllNumberVerificationCode(length int) string { + min := math.Pow10(length) + max := math.Pow10(length+1) - 1 + return strconv.Itoa(rand.Intn(int(max-min)) + int(min)) +} + +func RegisterWeChatCodeAndID(code string, id string) { + RegisterVerificationCodeWithKey(code, id, WeChatVerificationPurpose) +} + +func GetWeChatIDByCode(code string) string { + verificationMutex.Lock() + defer verificationMutex.Unlock() + value, okay := verificationMap[WeChatVerificationPurpose+code] + now := time.Now() + if !okay || int(now.Sub(value.time).Seconds()) >= VerificationValidMinutes*60 { + return "" + } + return value.code +} + func RegisterVerificationCodeWithKey(key string, code string, purpose string) { verificationMutex.Lock() defer verificationMutex.Unlock() diff --git a/common/wechat-message.go b/common/wechat-message.go new file mode 100644 index 0000000..3484c70 --- /dev/null +++ b/common/wechat-message.go @@ -0,0 +1,33 @@ +package common + +import "encoding/xml" + +type WeChatMessageRequest struct { + XMLName xml.Name `xml:"xml"` + ToUserName string `xml:"ToUserName"` + FromUserName string `xml:"FromUserName"` + CreateTime int64 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` + MsgId int64 `xml:"MsgId"` + MsgDataId int64 `xml:"MsgDataId"` + Idx int64 `xml:"Idx"` +} + +type WeChatMessageResponse struct { + XMLName xml.Name `xml:"xml"` + ToUserName string `xml:"ToUserName"` + FromUserName string `xml:"FromUserName"` + CreateTime int64 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` +} + +func ProcessWeChatMessage(req *WeChatMessageRequest, res *WeChatMessageResponse) { + switch req.Content { + case "验证码": + code := GenerateAllNumberVerificationCode(6) + RegisterWeChatCodeAndID(code, req.FromUserName) + res.Content = code + } +} diff --git a/controller/wechat.go b/controller/wechat.go index 211f7d4..3127894 100644 --- a/controller/wechat.go +++ b/controller/wechat.go @@ -3,10 +3,12 @@ package controller import ( "crypto/sha1" "encoding/hex" + "encoding/xml" "github.com/gin-gonic/gin" "net/http" "sort" "strings" + "time" "wechat-server/common" ) @@ -33,6 +35,47 @@ func WeChatVerification(c *gin.Context) { } } +func ProcessWeChatMessage(c *gin.Context) { + var req common.WeChatMessageRequest + err := xml.NewDecoder(c.Request.Body).Decode(&req) + if err != nil { + common.SysError(err.Error()) + c.Abort() + return + } + res := common.WeChatMessageResponse{ + ToUserName: req.FromUserName, + FromUserName: req.ToUserName, + CreateTime: time.Now().Unix(), + MsgType: "text", + Content: "", + } + common.ProcessWeChatMessage(&req, &res) + if res.Content == "" { + c.String(http.StatusOK, "") + return + } + c.XML(http.StatusOK, &res) +} + +func GetUserIDByCode(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.JSON(http.StatusOK, gin.H{ + "message": "无效的参数", + "success": false, + }) + return + } + id := common.GetWeChatIDByCode(code) + c.JSON(http.StatusOK, gin.H{ + "message": "", + "success": true, + "data": id, + }) + return +} + func GetAccessToken(c *gin.Context) { accessToken, expiration := common.GetAccessTokenAndExpirationSeconds() c.JSON(http.StatusOK, gin.H{ diff --git a/router/api-router.go b/router/api-router.go index 5dac690..9191791 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -12,12 +12,12 @@ func SetApiRouter(router *gin.Engine) { { apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/notice", controller.GetNotice) - apiRouter.GET("/wechat_verification", controller.WeChatVerification) + apiRouter.GET("/wechat", controller.WeChatVerification) + apiRouter.POST("/wechat", controller.ProcessWeChatMessage) apiRouter.GET("/verification", middleware.CriticalRateLimit(), controller.SendEmailVerification) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), controller.SendPasswordResetEmail) apiRouter.GET("/user/reset", controller.SendNewPasswordEmail) apiRouter.GET("/oauth/github", controller.GitHubOAuth) - apiRouter.GET("/access_token", middleware.AdminAuth(), middleware.TokenOnlyAuth(), controller.GetAccessToken) userRoute := apiRouter.Group("/user") { @@ -57,5 +57,11 @@ func SetApiRouter(router *gin.Engine) { fileRoute.POST("/", middleware.UserAuth(), middleware.UploadRateLimit(), controller.UploadFile) fileRoute.DELETE("/:id", middleware.UserAuth(), controller.DeleteFile) } + wechatRoute := apiRouter.Group("/wechat") + wechatRoute.Use(middleware.AdminAuth(), middleware.TokenOnlyAuth()) + { + wechatRoute.GET("/access_token", controller.GetAccessToken) + wechatRoute.GET("/user", controller.GetUserIDByCode) + } } }