07955ddf07
服务号「植趣 ZeeQ」已认证,走网页授权(snsapi_base,只拿 openid、用户无感), 不接管消息推送,副作用最小。 流程:PC 建 ticket → 二维码指向 /wx/mp?t= → 用户微信扫码 → 302 到微信授权页 → 回调 /api/v1/wx/mp/callback 用 code 换 openid → 找/建用户 → ticket 置 authorized → PC 轮询 /wx/mp/poll 拿到 authorized → 签发 JWT。ticket 一次性消费防重放。 - 配置(appid/secret/base_url)后台可改,secret AES 加密入库,与微信支付同一套 secrets; - ticket 存 Redis(短 TTL),无 Redis 时回退进程内内存(本地单实例可用,生产必须有 Redis); - User 加 wechat_openid。**部分唯一索引**(WHERE openid <> '')而非普通唯一: 存量邮箱用户该列是空串,普通唯一索引会让多个空串互撞、AutoMigrate 直接失败 —— 与之前 NULL 余额同类的坑,这次提前避开。 单测覆盖:授权 URL 拼接(含 #wechat_redirect 锚点必须在末尾)、secret 加密往返、 建号/查号、空 openid 不误命中存量用户。微信 API 调用依赖公网回调,本地测不了, 留待部署后真机扫码。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
54 lines
1.3 KiB
Go
54 lines
1.3 KiB
Go
package wechat
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
func TestConfig_Enabled(t *testing.T) {
|
||
if (Config{AppID: "x"}).Enabled() {
|
||
t.Fatal("缺 secret 不该 enabled")
|
||
}
|
||
if !(Config{AppID: "x", AppSecret: "y"}).Enabled() {
|
||
t.Fatal("齐全应 enabled")
|
||
}
|
||
}
|
||
|
||
func TestAuthorizeURL(t *testing.T) {
|
||
c := Config{AppID: "wxAPP"}
|
||
u := c.AuthorizeURL("https://a.b/cb", "tkt123")
|
||
// 必备参数与微信要求的锚点
|
||
for _, want := range []string{
|
||
"open.weixin.qq.com/connect/oauth2/authorize",
|
||
"appid=wxAPP",
|
||
"scope=snsapi_base",
|
||
"state=tkt123",
|
||
"redirect_uri=https%3A%2F%2Fa.b%2Fcb", // 必须 URL 编码
|
||
"#wechat_redirect", // 缺了微信不跳转
|
||
} {
|
||
if !strings.Contains(u, want) {
|
||
t.Fatalf("授权 URL 缺 %q:%s", want, u)
|
||
}
|
||
}
|
||
// 锚点必须在最后
|
||
if !strings.HasSuffix(u, "#wechat_redirect") {
|
||
t.Fatalf("#wechat_redirect 必须在末尾:%s", u)
|
||
}
|
||
}
|
||
|
||
// secret 加密往返:落库是密文,取出还原成明文。
|
||
func TestConfig_SecretRoundTrip(t *testing.T) {
|
||
c := Config{AppID: "x", AppSecret: "the-plain-secret"}
|
||
stored, err := c.EncryptedForStore()
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if stored.AppSecret == "the-plain-secret" {
|
||
t.Fatal("落库的 secret 不该是明文")
|
||
}
|
||
back := stored.DecryptFromStore()
|
||
if back.AppSecret != "the-plain-secret" {
|
||
t.Fatalf("还原失败:%q", back.AppSecret)
|
||
}
|
||
}
|