101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package onboarding
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"errors"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"data-terminal-cloud/internal/outbox"
|
||
|
|
)
|
||
|
|
|
||
|
|
// Wait in this HTTP request, not in the binding transaction. The outbox worker
|
||
|
|
// uses the same durable registration fence and may finish the work concurrently.
|
||
|
|
func (s *Service) finishBLEVoice(ctx context.Context, d Device, in BLEBindInput) (out map[string]any, err error) {
|
||
|
|
ctx, cancel := context.WithTimeout(ctx, 35*time.Second)
|
||
|
|
defer cancel()
|
||
|
|
defer func() {
|
||
|
|
if errors.Is(err, context.DeadlineExceeded) {
|
||
|
|
err = fail(504, "VOICE_REGISTRATION_TIMEOUT", "获取火山配置超时,绑定已保存,请使用原绑定令牌重试")
|
||
|
|
}
|
||
|
|
}()
|
||
|
|
started := false
|
||
|
|
for {
|
||
|
|
fields, event, e := s.bleVoiceSnapshot(ctx, d, in)
|
||
|
|
if e != nil {
|
||
|
|
return nil, e
|
||
|
|
}
|
||
|
|
switch fields["voice_status"] {
|
||
|
|
case "ready":
|
||
|
|
v, ok := fields["voice"].(DeviceVoiceCredential)
|
||
|
|
if !ok || v.InstanceID == "" || v.ProductKey == "" || v.DeviceName == "" || !validVoiceSecret(v.DeviceSecret) || !validRTCAppID(v.RTCAppID) {
|
||
|
|
return nil, fail(502, "VOICE_CONFIG_INVALID", "火山配置不完整,绑定已保存,请联系平台处理")
|
||
|
|
}
|
||
|
|
return map[string]any{"status": "success", "instance_id": v.InstanceID, "product_key": v.ProductKey, "device_name": v.DeviceName, "device_secret": v.DeviceSecret, "RTCAppID": v.RTCAppID}, nil
|
||
|
|
case "paused":
|
||
|
|
return nil, fail(503, "VOICE_REGISTRATION_PAUSED", "语音服务暂不可用,绑定已保存,请稍后使用原绑定令牌重试")
|
||
|
|
case "migration_required":
|
||
|
|
return nil, fail(409, "VOICE_MIGRATION_REQUIRED", "绑定已保存,旧设备需要补交原火山凭据,请联系平台处理")
|
||
|
|
case "reconcile_required":
|
||
|
|
return nil, fail(502, "VOICE_REGISTRATION_FAILED", "绑定已保存,火山注册结果未确认,请联系平台核对")
|
||
|
|
}
|
||
|
|
if !started {
|
||
|
|
started = true
|
||
|
|
if e = s.processServerActivation(ctx, event); e != nil {
|
||
|
|
if ctx.Err() != nil {
|
||
|
|
return nil, ctx.Err()
|
||
|
|
}
|
||
|
|
return nil, fail(502, "VOICE_REGISTRATION_FAILED", "绑定已保存,获取火山配置失败,请稍后重试或联系平台处理")
|
||
|
|
}
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
timer := time.NewTimer(250 * time.Millisecond)
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
timer.Stop()
|
||
|
|
return nil, ctx.Err()
|
||
|
|
case <-timer.C:
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *Service) bleVoiceSnapshot(ctx context.Context, authenticated Device, in BLEBindInput) (map[string]any, outbox.Event, error) {
|
||
|
|
var event outbox.Event
|
||
|
|
tx, err := s.pool.Begin(ctx)
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
defer tx.Rollback(ctx)
|
||
|
|
d, err := readDevice(ctx, tx, authenticated.ID, true)
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
c, err := readBLEClaim(ctx, tx, in.Token, d.ID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
result, err := bleResult(ctx, tx, d, c, s.now())
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
if !deviceAllowed(d) || result["status"] != "success" {
|
||
|
|
return nil, event, fail(409, "BINDING_CHANGED", "设备已解绑或归属发生变化,请重新绑定")
|
||
|
|
}
|
||
|
|
if err = bleClaimPermission(ctx, tx, c); err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
fields, err := s.voiceFields(ctx, tx, d)
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
j, err := readJob(ctx, tx, d.ID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
event.Payload, err = json.Marshal(map[string]any{"device_id": d.ID, "activation_id": j.ID, "binding_generation": d.Generation})
|
||
|
|
if err != nil {
|
||
|
|
return nil, event, err
|
||
|
|
}
|
||
|
|
return fields, event, tx.Commit(ctx)
|
||
|
|
}
|