refactor: 重构 token 管理,新增 NATS adapter 和多示例

按照 TOKEN_REFACTOR.md / FIX_CONFIG.md 方案重构核心层:
- miot: Init() 优先读 storage token,新增 RefreshAuthInfo() 和刷新回调
- xiaomi: Config 改用 AuthInfo 结构体(含 UUID/UID/DataDir/OnTokenRefreshed)
- xiaomi: NewClient(ctx, Config) 新签名,新增 RefreshToken(),后台自动刷新
- xiaomi/devices: 设备控制接口重构
- 新增示例 12_ac_subscribe,其余示例适配新 Config
This commit is contained in:
2026-06-30 12:16:49 +08:00
parent a3d94c4b9f
commit 8ffc9e4e44
33 changed files with 1019 additions and 593 deletions
+53 -16
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"sync"
"time"
"xiaomihome/miot"
)
@@ -15,6 +16,7 @@ type Client struct {
http *miot.MIoTHttpClient
mipsCloud *miot.MipsCloudClient
specParser *miot.MIoTSpecParser
uuid string
deviceMu sync.RWMutex
devices map[string]*miot.MIoTDevice
@@ -23,32 +25,48 @@ type Client struct {
mu sync.RWMutex
}
// NewClient 创建并初始化客户端(零 miot import,一个 Config 搞定)。
// NewClient 创建并初始化客户端。
func NewClient(ctx context.Context, cfg Config) (*Client, error) {
if cfg.AuthInfo.AccessToken == "" {
return nil, fmt.Errorf("xiaomi: Config.AuthInfo.AccessToken is required")
}
if cfg.UUID == "" {
return nil, fmt.Errorf("xiaomi: Config.UUID is required")
}
if cfg.DataDir == "" {
cfg.DataDir = "./data"
}
// 计算 expires_ts(如果未提供,从 expires_in 推算)
expiresTS := float64(cfg.AuthInfo.ExpiresTS)
if expiresTS == 0 && cfg.AuthInfo.ExpiresIn > 0 {
expiresTS = float64(time.Now().Unix() + int64(cfg.AuthInfo.ExpiresIn))
}
uuid := cfg.UUID
entryData := map[string]interface{}{
"access_token": cfg.AccessToken,
"access_token": cfg.AuthInfo.AccessToken,
"refresh_token": cfg.AuthInfo.RefreshToken,
"expires_ts": expiresTS,
"uuid": uuid,
}
miotClient := miot.NewMIoTClient("xiaomi_sdk", entryData, cfg.UID, "cn", miot.CtrlModeAuto)
miotClient.SetStorage(miot.NewMIoTStorage("./data"))
miotClient.SetSpecParser(miot.NewMIoTSpecParser("en", miot.NewMIoTStorage("./data"), "./data/specs"))
miotClient.SetStorage(miot.NewMIoTStorage(cfg.DataDir))
miotClient.SetSpecParser(miot.NewMIoTSpecParser("en", miot.NewMIoTStorage(cfg.DataDir), cfg.DataDir+"/specs"))
httpClient, err := miot.NewMIoTHttpClient("cn", miot.OAUTH2_CLIENT_ID, cfg.AccessToken)
httpClient, err := miot.NewMIoTHttpClient("cn", miot.OAUTH2_CLIENT_ID, cfg.AuthInfo.AccessToken)
if err != nil {
return nil, fmt.Errorf("xiaomi: http: %w", err)
}
miotClient.SetHTTPClient(httpClient)
// MIPS client ID 使用 config 中的 UUID,空则自动生成
mipsUUID := cfg.UUID
if mipsUUID == "" {
mipsUUID = generateUUID()
}
mipsCloud := miot.NewMipsCloudClient(
"ssl://cn-ha.mqtt.io.mi.com:8883",
"ha."+mipsUUID,
"ha."+uuid,
miot.OAUTH2_CLIENT_ID,
cfg.AccessToken,
cfg.AuthInfo.AccessToken,
)
miotClient.SetMipsCloud(mipsCloud)
@@ -56,20 +74,25 @@ func NewClient(ctx context.Context, cfg Config) (*Client, error) {
return nil, fmt.Errorf("xiaomi: init: %w", err)
}
// 初始刷新设备列表(对齐 full_client 示例)
_ = miotClient.RefreshDevices()
// 连接 MIPS(对齐 full_client 示例 line 83)
if err := mipsCloud.Connect(); err != nil {
return nil, fmt.Errorf("xiaomi: mips connect: %w", err)
}
// 启动后台 token 自动刷新
if cfg.OnTokenRefreshed != nil {
miotClient.SetTokenRefreshCallback(cfg.OnTokenRefreshed)
}
miotClient.RequestRefreshAuthInfo(10)
return &Client{
cfg: cfg,
inner: miotClient,
http: httpClient,
mipsCloud: mipsCloud,
specParser: miot.NewMIoTSpecParser("en", miot.NewMIoTStorage("./data"), "./data/specs"),
uuid: uuid,
specParser: miot.NewMIoTSpecParser("en", miot.NewMIoTStorage(cfg.DataDir), cfg.DataDir+"/specs"),
devices: make(map[string]*miot.MIoTDevice),
propSubs: make(map[string]string),
eventSubs: make(map[string]string),
@@ -87,6 +110,21 @@ func (c *Client) Close() error {
return nil
}
// RefreshToken 手动刷新 token,返回新值供调用方持久化。
func (c *Client) RefreshToken(ctx context.Context) (*TokenResult, error) {
result, err := c.inner.RefreshAuthInfo()
if err != nil {
return nil, fmt.Errorf("xiaomi: refresh token: %w", err)
}
return &TokenResult{
AccessToken: strVal(result, "access_token"),
RefreshToken: strVal(result, "refresh_token"),
ExpiresIn: intVal(result, "expires_in"),
ExpiresTS: int64(intVal(result, "expires_ts")),
UID: strVal(result, "uid"),
}, nil
}
func (c *Client) getOrCreateDevice(did string, info map[string]interface{}) *miot.MIoTDevice {
c.deviceMu.Lock()
defer c.deviceMu.Unlock()
@@ -94,7 +132,6 @@ func (c *Client) getOrCreateDevice(did string, info map[string]interface{}) *mio
return dev
}
// 从 device info 获取 URN 和 model 用于 SPEC 加载
urn, _ := info["urn"].(string)
model, _ := info["model"].(string)
var spec *miot.MIoTSpecInstance