diff --git a/dao/user.go b/dao/user.go index 5bfdbc0..57fc820 100644 --- a/dao/user.go +++ b/dao/user.go @@ -22,6 +22,8 @@ type User struct { CIDFunc bool `gorm:"column:cid_func"` //持续集成功能 Avatar string `gorm:"column:avatar"` CreateTime string `gorm:"column:create_time"` + QQ int64 `gorm:"column:qq"` + QQOpenID string `gorm:"column:qq_openid"` UpdateTime string `gorm:"column:update_time"` } @@ -98,6 +100,7 @@ func UpdateUserByID2(id int, req proto.UpdateUserInfoReq) error { updateData["CIDFunc"] = req.CIDFunc updateData["Avatar"] = req.Avatar updateData["Gender"] = req.Gender + updateData["QQ"] = req.QQ res := DB.Model(&User{}).Where("id =?", id).Updates(updateData) if res.Error != nil { return res.Error @@ -107,7 +110,7 @@ func UpdateUserByID2(id int, req proto.UpdateUserInfoReq) error { // 用户修改自己的信息 func UpdateUserByID3(id int, req proto.UpdateUserInfoReq) error { - res := DB.Model(&User{}).Where("id = ?", id).Updates(User{Name: req.Username, Age: req.Age, Avatar: req.Avatar, Gender: req.Gender}) + res := DB.Model(&User{}).Where("id = ?", id).Updates(User{Name: req.Username, Age: req.Age, Avatar: req.Avatar, Gender: req.Gender, QQ: req.QQ}) return res.Error } diff --git a/handler/tool.go b/handler/tool.go index 60581fe..fac4476 100644 --- a/handler/tool.go +++ b/handler/tool.go @@ -12,6 +12,7 @@ import ( "io" "log" "net/http" + "net/url" "os" "strconv" "time" @@ -54,6 +55,8 @@ func SetUpToolGroup(router *gin.Engine) { toolGroup.POST("/file_del", DelFile) //服务器、设备状态接口 toolGroup.POST("/monitor", SetDeviceStatusV2) + toolGroup.POST("/qq_callback", handleQQCallback) + toolGroup.POST("/qq_auth", GetAuthCode) //发送邮件 toolGroup.POST("/send_mail", SendMailTool) toolGroup.POST("/dashboard", DashBoardStatistics) @@ -102,6 +105,47 @@ func SetDeviceStatusV2(c *gin.Context) { } +type QQCallbackReq struct { + Code string `json:"code" form:"code"` + State string `json:"state" form:"state"` +} + +func GetAuthCode(c *gin.Context) { + //query + uuid := c.Query("uuid") + if uuid == "" { + var resp proto.GenerateResp + resp.Code = proto.ParameterError + resp.Message = "uuid is empty" + c.JSON(http.StatusOK, resp) + return + } + params := url.Values{} + params.Add("response_type", "code") + params.Add("client_id", worker.AppId) + params.Add("state", "saw_"+uuid) + str := fmt.Sprintf("%s&redirect_uri=%s", params.Encode(), worker.RedirectURI) + loginURL := fmt.Sprintf("%s?%s", "https://graph.qq.com/oauth2.0/authorize", str) + c.Redirect(http.StatusFound, loginURL) //重定向到QQ登录页面 +} + +func handleQQCallback(c *gin.Context) { + var resp proto.GenerateResp + resp.Code = 0 + var req QQCallbackReq + //query参数 + if err := c.ShouldBindQuery(&req); err != nil { + resp.Code = 1 + resp.Message = "参数错误" + c.JSON(http.StatusOK, resp) + return + } else { + + } + + c.JSON(http.StatusOK, resp) +} + func GetFileList(c *gin.Context) { //解析请求参数 var req GetFileListReq diff --git a/handler/user.go b/handler/user.go index 5749f4e..86a5846 100644 --- a/handler/user.go +++ b/handler/user.go @@ -402,7 +402,7 @@ func loginHandler(c *gin.Context) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": user.Name, "id": user.ID, - "exp": time.Now().Add(time.Hour * 10).Unix(), // 令牌过期时间, 10小时后过期 + "exp": time.Now().Add(time.Hour * 24).Unix(), // 令牌过期时间, 24小时后过期 }) tokenString, err = token.SignedString(proto.SigningKey) if err != nil { @@ -410,8 +410,8 @@ func loginHandler(c *gin.Context) { return } - worker.SetRedisWithExpire("user_"+user.Name, tokenString, time.Hour*10) // 将用户信息存入 - worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*10) // 设置过期时间为10分钟 + worker.SetRedisWithExpire("user_"+user.Name, tokenString, time.Hour*24) // 将用户信息存入 + worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*24) // 设置过期时间为24h data := make(map[string]interface{}) data["id"] = user.ID data["username"] = user.Name @@ -459,7 +459,7 @@ func registerHandler(c *gin.Context) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "username": req_data.User, "id": id, - "exp": time.Now().Add(time.Hour * 10).Unix(), // 令牌过期时间, 1分钟后过期 + "exp": time.Now().Add(time.Hour * 24).Unix(), // 令牌过期时间, 1分钟后过期 }) tokenString, err = token.SignedString(proto.SigningKey) if err != nil { @@ -471,7 +471,7 @@ func registerHandler(c *gin.Context) { return } fmt.Println(req_data) - res := worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*10) // 设置过期时间为10分钟 + res := worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*24) // 设置过期时间为24h if !res { c.JSON(200, gin.H{"error": "set token error", "code": proto.RedisSetError, "message": "error"}) return diff --git a/proto/conf.go b/proto/conf.go index b3e0f65..dcb593e 100644 --- a/proto/conf.go +++ b/proto/conf.go @@ -9,7 +9,7 @@ import ( var Config ConfigStruct var SigningKey = []byte{} -var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true, "/user/reset": true} // 不需要token验证的url +var Url_map = map[string]bool{"/login": true, "/register": true, "/uuid": true, "/gqr": true, "/cid/callback": true, "/tool/monitor": true, "/user/sync": true, "/tool/file/": true, "/user/reset": true, "/tool/qq_auth": true, "/tool/qq_callback": true} // 不需要token验证的url var Per_menu_map = map[string]int{"/video/": 1, "/device/": 2, "/cid/": 3} var File_Type = map[string]int{"im": 1, "avatar": 2, "file": 3, "config": 4} // 文件类型 const ( diff --git a/proto/user_req.go b/proto/user_req.go index 84af5b1..bcad560 100644 --- a/proto/user_req.go +++ b/proto/user_req.go @@ -16,6 +16,7 @@ type UpdateUserInfoReq struct { DeviceFunc bool `json:"device_func" form:"device_func"` //设备功能 CIDFunc bool `json:"cid_func" form:"cid_func"` //持续集成功能 Run bool `json:"run" form:"run"` //是否运行 + QQ int64 `json:"qq" form:"qq"` //QQ Avatar string `json:"avatar" form:"avatar"` //头像 } diff --git a/service/userService.go b/service/userService.go index be797c4..56734e8 100644 --- a/service/userService.go +++ b/service/userService.go @@ -421,3 +421,18 @@ func CreateTokenAndSave(user dao.User) (string, error) { func GetUserStatistics(userID int) dao.UserStatistics { return dao.UserStatisticsData(userID) } + +func CalculateUserTokenAndSetCache(user dao.User) (string, error) { + // 生成 JWT 令牌 + token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{ + "username": user.Name, + "id": user.ID, + "exp": time.Now().Add(time.Hour * 24).Unix(), // 令牌过期时间, 24小时后过期 + }) + tokenString, err := token.SignedString(proto.SigningKey) + //设置缓存 + worker.SetRedisWithExpire("user_"+user.Name, tokenString, time.Hour*24) // 将用户信息存入 + worker.SetRedisWithExpire(tokenString, tokenString, time.Hour*24) // 设置过期时间为24小时 + + return tokenString, err +} diff --git a/worker/qq.go b/worker/qq.go new file mode 100644 index 0000000..647ea37 --- /dev/null +++ b/worker/qq.go @@ -0,0 +1,141 @@ +package worker + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +const ( + AppId = "101827468" + AppKey = "0d2d856e48e0ebf6b98e0d0c879fe74d" + RedirectURI = "https://www.ljsea.top/qq_callback.php" +) + +type PrivateInfo struct { + AccessToken string `json:"access_token"` + ExpiresIn string `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + OpenId string `json:"openid"` +} + +//func main() { +// http.HandleFunc("/toLogin", GetAuthCode) +// http.HandleFunc("/qqLogin", GetToken) +// +// fmt.Println("started...") +// err := http.ListenAndServe(":9090", nil) +// if err != nil { +// panic(err) +// } +//} + +type GetCodeResponse struct { +} +type GetCodeRequest struct { + ResponseType string `json:"response_type"` + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + State string `json:"state"` + Scope string `json:"scope,omitempty"` + Display string `json:"display,omitempty"` +} + +type QQAccessTokenRequest struct { + GrantType string `json:"grant_type"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + Code string `json:"code"` + RedirectURI string `json:"redirect_uri"` + Fmt string `json:"fmt,omitempty"` + NeedOpenID string `json:"need_openid,omitempty"` +} + +type QQAccessTokenResponse struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` +} + +type QQRefreshTokenRequest struct { + GrantType string `json:"grant_type"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RefreshToken string `json:"refresh_token"` + Fmt string `json:"fmt,omitempty"` +} + +// 2. Get Access Token +func GetToken(w http.ResponseWriter, r *http.Request) { + code := r.FormValue("code") + params := url.Values{} + params.Add("grant_type", "authorization_code") + params.Add("client_id", AppId) + params.Add("client_secret", AppKey) + params.Add("code", code) + str := fmt.Sprintf("%s&redirect_uri=%s", params.Encode(), RedirectURI) + loginURL := fmt.Sprintf("%s?%s", "https://graph.qq.com/oauth2.0/token", str) + + response, err := http.Get(loginURL) + if err != nil { + w.Write([]byte(err.Error())) + } + defer response.Body.Close() + + bs, _ := ioutil.ReadAll(response.Body) + body := string(bs) + + resultMap := convertToMap(body) + + info := &PrivateInfo{} + info.AccessToken = resultMap["access_token"] + info.RefreshToken = resultMap["refresh_token"] + info.ExpiresIn = resultMap["expires_in"] + + GetOpenId(info, w) +} + +// 3. Get OpenId +func GetOpenId(info *PrivateInfo, w http.ResponseWriter) { + resp, err := http.Get(fmt.Sprintf("%s?access_token=%s", "https://graph.qq.com/oauth2.0/me", info.AccessToken)) + if err != nil { + w.Write([]byte(err.Error())) + } + defer resp.Body.Close() + + bs, _ := ioutil.ReadAll(resp.Body) + body := string(bs) + info.OpenId = body[45:77] + + GetUserInfo(info, w) +} + +// 4. Get User info +func GetUserInfo(info *PrivateInfo, w http.ResponseWriter) { + params := url.Values{} + params.Add("access_token", info.AccessToken) + params.Add("openid", info.OpenId) + params.Add("oauth_consumer_key", AppId) + + uri := fmt.Sprintf("https://graph.qq.com/user/get_user_info?%s", params.Encode()) + resp, err := http.Get(uri) + if err != nil { + w.Write([]byte(err.Error())) + } + defer resp.Body.Close() + + bs, _ := ioutil.ReadAll(resp.Body) + w.Write(bs) +} + +func convertToMap(str string) map[string]string { + var resultMap = make(map[string]string) + values := strings.Split(str, "&") + for _, value := range values { + vs := strings.Split(value, "=") + resultMap[vs[0]] = vs[1] + } + return resultMap +}