From 800670e5c2335875636d769a44af9b8e0b5aba66 Mon Sep 17 00:00:00 2001 From: liuhaodong Date: Thu, 27 Aug 2026 17:33:25 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E7=9B=B4=E6=92=AD=E8=AE=A1?= =?UTF-8?q?=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/config.go | 1 + config-prod.yaml | 1 + config.yaml | 1 + mock/main.go | 27 ++++++++++++++++++++++++++- service/live_service.go | 5 +++++ service/scheduler.go | 8 ++++++-- sql/003_seed.sql | 16 ++++++++-------- sql/009_account_security_invoice.sql | 2 +- 8 files changed, 49 insertions(+), 12 deletions(-) diff --git a/common/config.go b/common/config.go index d208bda..6c4f747 100644 --- a/common/config.go +++ b/common/config.go @@ -83,6 +83,7 @@ type Live struct { ViewerLeaseSeconds int `mapstructure:"viewer-lease-seconds"` StopGraceSeconds int `mapstructure:"stop-grace-seconds"` ReconcileIntervalSeconds int `mapstructure:"reconcile-interval-seconds"` + BillingIntervalSeconds int `mapstructure:"billing-interval-seconds"` RecordPolicy string `mapstructure:"record-policy"` CallbackAuthToken string `mapstructure:"callback-auth-token"` AllowRealCloud bool `mapstructure:"allow-real-cloud"` diff --git a/config-prod.yaml b/config-prod.yaml index ee32b0e..3f495ad 100644 --- a/config-prod.yaml +++ b/config-prod.yaml @@ -43,6 +43,7 @@ live: viewer-lease-seconds: 30 stop-grace-seconds: 15 reconcile-interval-seconds: 10 + billing-interval-seconds: 60 record-policy: disabled callback-auth-token: "db570a886e3d65c3f6c55d648ddb41506a658f6be4892719345056602fc2e7ac" allow-real-cloud: true diff --git a/config.yaml b/config.yaml index 0a326c5..68ec9b9 100644 --- a/config.yaml +++ b/config.yaml @@ -44,6 +44,7 @@ live: viewer-lease-seconds: 30 stop-grace-seconds: 15 reconcile-interval-seconds: 10 + billing-interval-seconds: 60 record-policy: disabled allow-real-cloud: false heartbeat: diff --git a/mock/main.go b/mock/main.go index 2a2520c..3cc3287 100644 --- a/mock/main.go +++ b/mock/main.go @@ -4,6 +4,7 @@ import ( "log" "os" "os/signal" + "strings" "syscall" "time" ) @@ -50,7 +51,7 @@ func main() { }, } - for _, spec := range specs { + for _, spec := range selectDockSpecs(specs, os.Getenv("MOCK_DOCKS")) { dock := newMockDock(spec) if err := dock.start(broker, username, password); err != nil { log.Fatalf("启动机巢 %s 失败: %v", spec.DockID, err) @@ -65,6 +66,30 @@ func main() { time.Sleep(200 * time.Millisecond) } +func selectDockSpecs(specs []dockSpec, selected string) []dockSpec { + if strings.TrimSpace(selected) == "" { + return specs + } + + allowed := make(map[string]struct{}) + for _, dockID := range strings.Split(selected, ",") { + if dockID = strings.TrimSpace(dockID); dockID != "" { + allowed[dockID] = struct{}{} + } + } + + selectedSpecs := make([]dockSpec, 0, len(allowed)) + for _, spec := range specs { + if _, ok := allowed[spec.DockID]; ok { + selectedSpecs = append(selectedSpecs, spec) + } + } + if len(selectedSpecs) == 0 { + log.Fatalf("MOCK_DOCKS 未匹配任何设备: %s", selected) + } + return selectedSpecs +} + func envOr(key, def string) string { if v := os.Getenv(key); v != "" { return v diff --git a/service/live_service.go b/service/live_service.go index 41c21fb..a7c8dbf 100644 --- a/service/live_service.go +++ b/service/live_service.go @@ -246,6 +246,11 @@ func (s *LiveService) reconcileCloudSession(provider liveprovider.Adapter, sessi if session.DeviceStreaming && online.Online { updates["cloud_confirmed_at"] = now } + if provider.Provider() == "fake" && session.Phase == "stopping" && !session.DeviceStreaming { + updates["cloud_online"] = false + updates["phase"] = "stopped" + updates["stopped_at"] = now + } switch session.Phase { case "starting", "reconnecting": if session.DeviceStreaming && online.Online { diff --git a/service/scheduler.go b/service/scheduler.go index 52d6bce..2beb5c5 100644 --- a/service/scheduler.go +++ b/service/scheduler.go @@ -32,8 +32,12 @@ func (s *Scheduler) Start() { } _, _ = s.cron.AddFunc("@every "+(time.Duration(interval)*time.Second).String(), DefaultLiveService.ReconcileLeases) _, _ = s.cron.AddFunc("@every "+(time.Duration(interval)*time.Second).String(), DefaultLiveService.ReconcileCloudSessions) - // 直播计费:每 60s 扫描进行中的 live_session 按码率扣减 - _, _ = s.cron.AddFunc("@every 60s", DefaultBillingService.chargeLiveSessions) + // 直播计费:按配置周期扫描进行中的 live_session 按码率扣减 + billingInterval := common.AppConf.Live.BillingIntervalSeconds + if billingInterval <= 0 { + billingInterval = 60 + } + _, _ = s.cron.AddFunc("@every "+(time.Duration(billingInterval)*time.Second).String(), DefaultBillingService.chargeLiveSessions) // 余额快照:每 30min 将 Redis 权威余额回写 MySQL _, _ = s.cron.AddFunc("@every 30m", DefaultBillingService.flushBalanceSnapshot) // SIM 卡用量:每 1h 从运营商同步用量 diff --git a/sql/003_seed.sql b/sql/003_seed.sql index f757b98..8ccdc9e 100644 --- a/sql/003_seed.sql +++ b/sql/003_seed.sql @@ -36,11 +36,11 @@ SET @demo_user_id = (SELECT id FROM `user` WHERE phone = '13900000000'); -- 演示机巢 / 无人机(与 mock 设备 dock-1/2/3 对应) -- 若 MQTT 已自发现同 dock_id 的 pending 记录,则认领并补全名称坐标。 -- --------------------------------------------------------------- -INSERT INTO dock (id, user_id, dock_id, name, code, sn, iccid, longitude, latitude, altitude, location, status, register_status, dock_id_source, software_ver, protocol_ver, created_at, updated_at) +INSERT INTO dock (id, user_id, dock_id, name, code, sn, iccid, longitude, latitude, altitude, location, register_status, dock_id_source, software_ver, protocol_ver, created_at, updated_at) VALUES - (1001, @demo_user_id, 'dock-1', '青山湖 01 号机巢', 'JG-DOCK-001', 'dock-1', '8986000000000000001', 119.802631, 30.262438, 15, '青山湖科技城 A 区', 'offline', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()), - (1002, @demo_user_id, 'dock-2', '青山湖 02 号机巢', 'JG-DOCK-002', 'dock-2', '8986000000000000002', 119.818264, 30.255917, 18, '青山湖科技城 B 区', 'offline', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()), - (1003, @demo_user_id, 'dock-3', '青山湖 03 号机巢', 'JG-DOCK-003', 'dock-3', '8986000000000000003', 119.791450, 30.248610, 12, '青山湖科技城 C 区', 'offline', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()) + (1001, @demo_user_id, 'dock-1', '青山湖 01 号机巢', 'JG-DOCK-001', 'dock-1', '8986000000000000001', 119.802631, 30.262438, 15, '青山湖科技城 A 区', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()), + (1002, @demo_user_id, 'dock-2', '青山湖 02 号机巢', 'JG-DOCK-002', 'dock-2', '8986000000000000002', 119.818264, 30.255917, 18, '青山湖科技城 B 区', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()), + (1003, @demo_user_id, 'dock-3', '青山湖 03 号机巢', 'JG-DOCK-003', 'dock-3', '8986000000000000003', 119.791450, 30.248610, 12, '青山湖科技城 C 区', 'registered', 'seed', '1.3.0', '1.0', NOW(), NOW()) ON DUPLICATE KEY UPDATE user_id = @demo_user_id, name = VALUES(name), @@ -52,11 +52,11 @@ ON DUPLICATE KEY UPDATE location = VALUES(location), register_status = 'registered'; -INSERT INTO drone (id, user_id, drone_sn, dock_id, name, code, model, status, battery, firmware_ver, created_at, updated_at) +INSERT INTO drone (id, user_id, drone_sn, dock_id, name, code, model, battery, firmware_ver, created_at, updated_at) VALUES - (2001, @demo_user_id, 'JG-UAV-001', 'dock-1', '巡检无人机 001', 'JG-UAV-001', 'LAIC-X1', 'offline', 86, '1.0.0', NOW(), NOW()), - (2002, @demo_user_id, 'JG-UAV-002', 'dock-2', '巡检无人机 002', 'JG-UAV-002', 'LAIC-X1', 'offline', 72, '1.0.0', NOW(), NOW()), - (2003, @demo_user_id, 'JG-UAV-003', 'dock-3', '巡检无人机 003', 'JG-UAV-003', 'LAIC-X1', 'offline', 41, '1.0.0', NOW(), NOW()) + (2001, @demo_user_id, 'JG-UAV-001', 'dock-1', '巡检无人机 001', 'JG-UAV-001', 'LAIC-X1', 86, '1.0.0', NOW(), NOW()), + (2002, @demo_user_id, 'JG-UAV-002', 'dock-2', '巡检无人机 002', 'JG-UAV-002', 'LAIC-X1', 72, '1.0.0', NOW(), NOW()), + (2003, @demo_user_id, 'JG-UAV-003', 'dock-3', '巡检无人机 003', 'JG-UAV-003', 'LAIC-X1', 41, '1.0.0', NOW(), NOW()) ON DUPLICATE KEY UPDATE user_id = @demo_user_id, dock_id = VALUES(dock_id), diff --git a/sql/009_account_security_invoice.sql b/sql/009_account_security_invoice.sql index 7db43fd..a2fa7c9 100644 --- a/sql/009_account_security_invoice.sql +++ b/sql/009_account_security_invoice.sql @@ -1,4 +1,4 @@ -ALTER TABLE `user` ADD COLUMN `password_changed_at` DATETIME NULL COMMENT '密码最后修改时间' AFTER `last_login`; +-- 001_schema.sql 已包含 password_changed_at;本脚本仅补充账户会话和发票表。 CREATE TABLE IF NOT EXISTS `user_login_session` ( id BIGINT PRIMARY KEY COMMENT '主键 ID',