@@ -136,6 +136,7 @@ func init() { | |||
new(AiModelManage), | |||
new(OfficialTag), | |||
new(OfficialTagRepos), | |||
new(WechatBindLog), | |||
) | |||
tablesStatistic = append(tablesStatistic, | |||
@@ -177,6 +177,9 @@ type User struct { | |||
//BlockChain | |||
PublicKey string `xorm:"INDEX"` | |||
PrivateKey string `xorm:"INDEX"` | |||
WechatOpenId string `xorm:"INDEX"` | |||
} | |||
// SearchOrganizationsOptions options to filter organizations | |||
@@ -0,0 +1,74 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"time" | |||
) | |||
type WechatBindAction int | |||
const ( | |||
WECHAT_BIND WechatBindAction = iota + 1 | |||
WECHAT_UNBIND | |||
) | |||
type WechatBindLog struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
UserID int64 `xorm:"INDEX"` | |||
WechatOpenId string `xorm:"INDEX"` | |||
Action int | |||
CreateTime time.Time `xorm:"INDEX created"` | |||
} | |||
func BindWechatOpenId(userId int64, wechatOpenId string) error { | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
if err := sess.Begin(); err != nil { | |||
return err | |||
} | |||
param := &User{WechatOpenId: wechatOpenId} | |||
n, err := sess.Where("ID = ?", userId).Update(param) | |||
if err != nil { | |||
log.Error("update wechat_open_id failed,e=%v", err) | |||
return err | |||
} | |||
if n == 0 { | |||
log.Error("update wechat_open_id failed,user not exist,userId=%d", userId) | |||
return nil | |||
} | |||
logParam := &WechatBindLog{ | |||
UserID: userId, | |||
WechatOpenId: wechatOpenId, | |||
Action: int(WECHAT_BIND), | |||
} | |||
sess.Insert(logParam) | |||
return sess.Commit() | |||
} | |||
func UnbindWechatOpenId(userId int64) error { | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
if err := sess.Begin(); err != nil { | |||
return err | |||
} | |||
param := &User{WechatOpenId: ""} | |||
n, err := x.Where("ID = ?", userId).Update(param) | |||
if err != nil { | |||
log.Error("update wechat_open_id failed,e=%v", err) | |||
return err | |||
} | |||
if n == 0 { | |||
log.Error("update wechat_open_id failed,user not exist,userId=%d", userId) | |||
return nil | |||
} | |||
//todo 是否记录原有微信openId | |||
logParam := &WechatBindLog{ | |||
UserID: userId, | |||
Action: int(WECHAT_UNBIND), | |||
} | |||
sess.Insert(logParam) | |||
return sess.Commit() | |||
} |
@@ -14,7 +14,7 @@ func GetWechatAccessToken() string { | |||
if token == EMPTY_REDIS_VAL { | |||
return "" | |||
} | |||
live, _ := redis_client.TTL(token) | |||
live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey()) | |||
//refresh wechat access token when expire time less than 5 minutes | |||
if live > 0 && live < 300 { | |||
refreshAccessTokenCache() | |||
@@ -0,0 +1,11 @@ | |||
package wechat | |||
import "code.gitea.io/gitea/models" | |||
func BindWechat(userId int64, wechatOpenId string) error { | |||
return models.BindWechatOpenId(userId, wechatOpenId) | |||
} | |||
func UnbindWechat(userId int64) error { | |||
return models.UnbindWechatOpenId(userId) | |||
} |
@@ -0,0 +1,5 @@ | |||
package wechat | |||
type WechatCall interface { | |||
call() | |||
} |
@@ -4,7 +4,9 @@ import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
"encoding/json" | |||
"fmt" | |||
"github.com/go-resty/resty/v2" | |||
"strconv" | |||
"time" | |||
) | |||
@@ -17,6 +19,9 @@ const ( | |||
ACCESS_TOKEN_PATH = "/cgi-bin/token" | |||
QR_CODE_Path = "/cgi-bin/qrcode/create" | |||
ACTION_QR_STR_SCENE = "QR_STR_SCENE" | |||
ERR_CODE_ACCESSTOKEN_EXPIRE = 42001 | |||
ERR_CODE_ACCESSTOKEN_INVALID = 40001 | |||
) | |||
type AccessTokenResponse struct { | |||
@@ -44,6 +49,11 @@ type Scene struct { | |||
Scene_str string | |||
} | |||
type ErrorResponse struct { | |||
Errcode int | |||
Errmsg string | |||
} | |||
func getWechatRestyClient() *resty.Client { | |||
if client == nil { | |||
client = resty.New() | |||
@@ -69,7 +79,7 @@ func callAccessToken() *AccessTokenResponse { | |||
return &result | |||
} | |||
func callQRCodeCreate(sceneStr string) *QRCodeResponse { | |||
func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) { | |||
client := getWechatRestyClient() | |||
body := &QRCodeRequest{ | |||
@@ -85,11 +95,29 @@ func callQRCodeCreate(sceneStr string) *QRCodeResponse { | |||
SetBody(bodyJson). | |||
SetResult(&result). | |||
Post(setting.WechatApiHost + QR_CODE_Path) | |||
if err != nil || result.Url == "" { | |||
if err != nil { | |||
log.Error("create QR code failed,e=%v", err) | |||
return nil | |||
return nil, false | |||
} | |||
errCode := getErrorCodeFromResponse(r) | |||
if errCode == ERR_CODE_ACCESSTOKEN_EXPIRE || errCode == ERR_CODE_ACCESSTOKEN_INVALID { | |||
return nil, true | |||
} | |||
if result.Url == "" { | |||
return nil, false | |||
} | |||
//todo 识别token失效的错误码,重试机制 | |||
log.Info("%v", r) | |||
return &result | |||
return &result, false | |||
} | |||
func getErrorCodeFromResponse(r *resty.Response) int { | |||
a := r.Body() | |||
resultMap := make(map[string]interface{}, 0) | |||
json.Unmarshal(a, &resultMap) | |||
code := resultMap["errcode"] | |||
if code == nil { | |||
return -1 | |||
} | |||
c, _ := strconv.Atoi(fmt.Sprint(code)) | |||
return c | |||
} |
@@ -1,6 +1,10 @@ | |||
package wechat | |||
func GetWechatQRCode4Bind(sceneStr string) *QRCodeResponse { | |||
r := callQRCodeCreate(sceneStr) | |||
return r | |||
result, retryFlag := callQRCodeCreate(sceneStr) | |||
if retryFlag { | |||
refreshAccessTokenCache() | |||
result, _ = callQRCodeCreate(sceneStr) | |||
} | |||
return result | |||
} |
@@ -3,6 +3,7 @@ package redis_client | |||
import ( | |||
"code.gitea.io/gitea/modules/labelmsg" | |||
"fmt" | |||
"github.com/gomodule/redigo/redis" | |||
"math" | |||
"strconv" | |||
"time" | |||
@@ -36,7 +37,24 @@ func Get(key string) (string, error) { | |||
if reply == nil { | |||
return "", err | |||
} | |||
return fmt.Sprint(reply), nil | |||
s, _ := redis.String(reply, nil) | |||
return s, nil | |||
} | |||
func Del(key string) (int, error) { | |||
redisClient := labelmsg.Get() | |||
defer redisClient.Close() | |||
reply, err := redisClient.Do("DEL", key) | |||
if err != nil { | |||
return 0, err | |||
} | |||
if reply == nil { | |||
return 0, err | |||
} | |||
s, _ := redis.Int(reply, nil) | |||
return s, nil | |||
} | |||
@@ -59,6 +59,7 @@ | |||
package v1 | |||
import ( | |||
"code.gitea.io/gitea/routers/authentication" | |||
"net/http" | |||
"strings" | |||
@@ -995,6 +996,9 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Group("/topics", func() { | |||
m.Get("/search", repo.TopicSearch) | |||
}) | |||
m.Group("/from_wechat", func() { | |||
m.Get("/event", authentication.AcceptWechatEvent) | |||
}) | |||
}, securityHeaders(), context.APIContexter(), sudo()) | |||
} | |||
@@ -27,14 +27,14 @@ func GetQRCode4Bind(ctx *context.Context) { | |||
r, err := createQRCode4Bind(userId) | |||
if err != nil { | |||
ctx.JSON(200, map[string]interface{}{ | |||
"code": 9, | |||
"code": "9999", | |||
"msg": "Get QR code failed", | |||
}) | |||
return | |||
} | |||
ctx.JSON(200, map[string]interface{}{ | |||
"code": 0, | |||
"code": "00", | |||
"msg": "success", | |||
"data": r, | |||
}) | |||
@@ -0,0 +1,68 @@ | |||
package authentication | |||
import ( | |||
"code.gitea.io/gitea/modules/auth/wechat" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/redis/redis_client" | |||
"code.gitea.io/gitea/modules/redis/redis_key" | |||
"encoding/xml" | |||
"io/ioutil" | |||
"strconv" | |||
"time" | |||
) | |||
//<xml> | |||
// <ToUserName><![CDATA[toUser]]></ToUserName> | |||
// <FromUserName><![CDATA[FromUser]]></FromUserName> | |||
// <CreateTime>123456789</CreateTime> | |||
// <MsgType><![CDATA[event]]></MsgType> | |||
// <Event><![CDATA[SCAN]]></Event> | |||
// <EventKey><![CDATA[SCENE_VALUE]]></EventKey> | |||
// <Ticket><![CDATA[TICKET]]></Ticket> | |||
//</xml> | |||
type WechatEvent struct { | |||
ToUserName string | |||
FromUserName string | |||
CreateTime int64 | |||
MsgType string | |||
Event string | |||
EventKey string | |||
Ticket string | |||
} | |||
type Xml struct { | |||
ToUserName string | |||
FromUserName string | |||
CreateTime int64 | |||
MsgType string | |||
Content string | |||
} | |||
// AcceptWechatEvent | |||
func AcceptWechatEvent(ctx *context.Context) { | |||
b, _ := ioutil.ReadAll(ctx.Req.Request.Body) | |||
we := WechatEvent{} | |||
xml.Unmarshal(b, &we) | |||
log.Info("accept wechat event= %v", b) | |||
key := redis_key.WechatBindingUserIdKey(we.EventKey) | |||
val, _ := redis_client.Get(key) | |||
if val == "" { | |||
log.Error("sceneStr is not exist,sceneStr=%s", we.EventKey) | |||
ctx.XML(200, "") | |||
return | |||
} | |||
userId, _ := strconv.ParseInt(val, 10, 64) | |||
//更新微信openId和流水 | |||
wechat.BindWechat(userId, we.EventKey) | |||
reply := &Xml{ | |||
ToUserName: we.FromUserName, | |||
FromUserName: we.ToUserName, | |||
CreateTime: time.Now().Unix(), | |||
MsgType: "text", | |||
Content: "启智账号认证微信成功", | |||
} | |||
redis_client.Del(key) | |||
ctx.XML(200, reply) | |||
} |