ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
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)
|
|
}
|