package main import ( "encoding/json" "fmt" "strings" "time" ) // buy_records.go - 充值/缴费记录获取与缓存 // 接口: POST /app/electric/queryISIMSRoomBuyRecord // 返回每笔充值记录(金额/类型/时间),用于精确计算日耗电 // buyRecord 单笔充值记录 type buyRecord struct { Date string `json:"date"` // 充值日期 YYYY-MM-DD Money float64 `json:"money"` // 充值金额(元) Kwh float64 `json:"kwh"` // 换算电量(度) = money / price Type string `json:"type"` // 现金充值 / 下发补助 Detail string `json:"detail"` // 完整时间 YYYY-M-D H:M:S } // 充值记录缓存 var ( buyRecordsCache []buyRecord buyRecordsTime int64 ) // queryBuyRecords 获取某房间的充值记录(需 application 域 shiroJID) func queryBuyRecords(shiroJID string, room *roomInfo) ([]buyRecord, error) { price := unitPrice() s := newXfSession(shiroJID) body := fmt.Sprintf("areaId=%s&buildingCode=%s&floorCode=%s&roomCode=%s&mdtype=room&platform=%s", room.AreaID, room.BuildingCode, room.FloorCode, room.RoomCode, xfPlatform) b, _, err := s.postForm("https://application.xiaofubao.com/app/electric/queryISIMSRoomBuyRecord", body, "https://application.xiaofubao.com/") if err != nil { return nil, err } var res struct { StatusCode flexInt `json:"statusCode"` Rows []struct { DateTime string `json:"datetime"` // "2026-8-23 0:45:49" BuyType string `json:"buytype"` // 现金充值 / 下发补助 BuyUsing string `json:"buyusingtpe"` // 照明用电 Money string `json:"money"` // "40.00" } `json:"rows"` } if err := json.Unmarshal(b, &res); err != nil { return nil, err } if res.StatusCode != 0 { return nil, fmt.Errorf("queryISIMSRoomBuyRecord 失败: %s", string(b)) } var records []buyRecord for _, r := range res.Rows { var money float64 fmt.Sscanf(r.Money, "%f", &money) rec := buyRecord{ Date: datePart(r.DateTime), Money: round2(money), Kwh: round2(money / price), Type: r.BuyType, Detail: r.DateTime, } records = append(records, rec) } return records, nil } // getBuyRecordsCached 获取充值记录(带缓存,10 分钟有效) func getBuyRecordsCached(shiroJID string, room *roomInfo) ([]buyRecord, error) { if buyRecordsCache != nil && time.Now().Unix()-buyRecordsTime < 600 { return buyRecordsCache, nil } recs, err := queryBuyRecords(shiroJID, room) if err != nil { return nil, err } buyRecordsCache = recs buyRecordsTime = time.Now().Unix() return recs, nil } // rechargeKwhByDate 生成 map[date] -> 当天充值电量(度) func rechargeKwhByDate(records []buyRecord) map[string]float64 { m := map[string]float64{} for _, r := range records { m[r.Date] = round2(m[r.Date] + r.Kwh) } return m } // unitPrice 电价(元/度),默认 0.535,可用 XF_PRICE 环境变量覆盖 func unitPrice() float64 { if v := envOr("XF_PRICE", ""); v != "" { var p float64 if _, err := fmt.Sscanf(v, "%f", &p); err == nil && p > 0 { return p } } return 0.535 } // datePart 从 "2026-8-23 0:45:49" 提取 "2026-08-23" func datePart(dt string) string { parts := strings.SplitN(dt, " ", 2) if len(parts) == 0 { return dt } // 规范化月份/日补零 ymd := strings.Split(parts[0], "-") if len(ymd) != 3 { return parts[0] } return fmt.Sprintf("%s-%02s-%02s", ymd[0], ymd[1], ymd[2]) }