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:
+53
-16
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user