main.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package main
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "time"
  10. )
  11. // 运行方式
  12. // export FWH_TOKEN="你的logintoken值"
  13. // cd service/test/tou
  14. // go run main.go
  15. // ===== 配置 =====
  16. const (
  17. baseURL = "https://test.franklinwh.com"
  18. lang = "ZH_CN"
  19. )
  20. // ===== 数据结构 =====
  21. type APIResponse struct {
  22. Code int `json:"code"`
  23. Message string `json:"message"`
  24. Result json.RawMessage `json:"result"`
  25. Success bool `json:"success"`
  26. }
  27. type Province struct {
  28. ID int `json:"id"`
  29. NameEn string `json:"nameEn"`
  30. NameZh string `json:"nameZh"`
  31. }
  32. type TemplateData struct {
  33. Template TemplateInfo `json:"template"`
  34. StrategyList []StrategyInfo `json:"strategyList"`
  35. }
  36. type TemplateInfo struct {
  37. ID int `json:"id"`
  38. ElectricCompany string `json:"electricCompany"`
  39. EleCompanyFullName string `json:"eleCompanyFullName"`
  40. DerSchdule string `json:"derSchdule"`
  41. GridType string `json:"gridType"`
  42. Name string `json:"name"`
  43. ElectricityType int `json:"electricityType"`
  44. CountryId int `json:"countryId"`
  45. ProvinceId int `json:"provinceId"`
  46. ProvinceEn string `json:"provinceEn"`
  47. Env int `json:"env"`
  48. MookRunCount int `json:"mookRunCount"`
  49. Priority int `json:"priority"`
  50. }
  51. type StrategyInfo struct {
  52. SeasonName string `json:"seasonName"`
  53. Month string `json:"month"`
  54. DayName string `json:"dayName"`
  55. DayType int `json:"dayType"`
  56. CcociateSn string `json:"ccociateSn"`
  57. CcociateType int `json:"ccociateType"`
  58. EleticRatePeak *float64 `json:"eleticRatePeak"`
  59. EleticRateShoulder *float64 `json:"eleticRateShoulder"`
  60. EleticRateValley *float64 `json:"eleticRateValley"`
  61. EleticRateSharp *float64 `json:"eleticRateSharp"`
  62. EleticRateSuperOffPeak *float64 `json:"eleticRateSuperOffPeak"`
  63. EleticRateGridFee *float64 `json:"eleticRateGridFee"`
  64. EleticSellPeak *float64 `json:"eleticSellPeak"`
  65. EleticSellShoulder *float64 `json:"eleticSellShoulder"`
  66. EleticSellValley *float64 `json:"eleticSellValley"`
  67. EleticSellSharp *float64 `json:"eleticSellSharp"`
  68. EleticSellSuperOffPeak *float64 `json:"eleticSellSuperOffPeak"`
  69. DetailVoList []DetailInfo `json:"detailVoList"`
  70. }
  71. type DetailInfo struct {
  72. StartHourTime string `json:"startHourTime"`
  73. EndHourTime string `json:"endHourTime"`
  74. WaveType int `json:"waveType"`
  75. Name string `json:"name"`
  76. BriefDescribe string `json:"briefDescribe"`
  77. SolarPriority string `json:"solarPriority"`
  78. GridDischargeMax *float64 `json:"gridDischargeMax"`
  79. GridChargeMax *float64 `json:"gridChargeMax"`
  80. ChargePower *float64 `json:"chargePower"`
  81. DischargePower *float64 `json:"dischargePower"`
  82. ChargeMax *float64 `json:"chargeMax"`
  83. DischargeMax *float64 `json:"dischargeMax"`
  84. GcaoMax *float64 `json:"gcaoMax"`
  85. LoadPriority string `json:"loadPriority"`
  86. GridFeedMax *float64 `json:"gridFeedMax"`
  87. GridMax *float64 `json:"gridMax"`
  88. MinDischargeSoc *int `json:"minDischargeSoc"`
  89. MaxChargeSoc *int `json:"maxChargeSoc"`
  90. HeatEnable *int `json:"heatEnable"`
  91. PowerOffApower string `json:"powerOffApower"`
  92. RampTime *int `json:"rampTime"`
  93. OffGrid *int `json:"offGrid"`
  94. SolarCutoff *int `json:"solarCutoff"`
  95. UseModeFlag int `json:"useModeFlag"`
  96. DispatchId int `json:"dispatchId"`
  97. BuyRate *float64 `json:"buyRate"`
  98. SellRate *float64 `json:"sellRate"`
  99. }
  100. // 创建模版的请求体
  101. type CreateTemplatePayload struct {
  102. Template CreateTemplateInfo `json:"template"`
  103. StrategyList []StrategyInfo `json:"strategyList"`
  104. Lang string `json:"lang"`
  105. }
  106. type CreateTemplateInfo struct {
  107. CountryId int `json:"countryId"`
  108. ProvinceId int `json:"provinceId"`
  109. DerSchdule string `json:"derSchdule"`
  110. EleCompanyFullName string `json:"eleCompanyFullName"`
  111. ElectricCompany string `json:"electricCompany"`
  112. GridType string `json:"gridType"`
  113. ElectricityType int `json:"electricityType"`
  114. Name string `json:"name"`
  115. MookRunCount string `json:"mookRunCount"`
  116. Priority string `json:"priority"`
  117. Env string `json:"env"`
  118. }
  119. // ===== HAR 数据:三个模版的策略数据 =====
  120. func getTemplateData() []TemplateData {
  121. // 模版 4477 - ZEROHERO (GloBird Energy, Queensland)
  122. int90 := 90
  123. float5 := 5.0
  124. float0_1 := 0.1
  125. float10 := 10.0
  126. float0 := 0.0
  127. int1 := 1
  128. int0 := 0
  129. template4477 := TemplateData{
  130. Template: TemplateInfo{
  131. ID: 4477, ElectricCompany: "GloBird Energy", EleCompanyFullName: "GloBird Energy",
  132. DerSchdule: "ZEROHERO", GridType: "ZEROHERO", Name: "ZEROHERO",
  133. ElectricityType: 1, CountryId: 3, ProvinceId: 88, ProvinceEn: "Queensland",
  134. Env: 0, MookRunCount: 0, Priority: 0,
  135. },
  136. StrategyList: []StrategyInfo{
  137. {
  138. SeasonName: "All months", Month: "1,2,3,4,5,6,7,8,9,10,11,12",
  139. DayName: "Every day", DayType: 3, CcociateSn: "", CcociateType: 1,
  140. DetailVoList: []DetailInfo{
  141. {StartHourTime: "00:00", EndHourTime: "11:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  142. {StartHourTime: "11:00", EndHourTime: "14:00", WaveType: 0, Name: "off-peak", BriefDescribe: "aPower charger from solar", SolarPriority: "1,2,3", GridChargeMax: &float5, LoadPriority: "1,2,0", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 3, PowerOffApower: ""},
  143. {StartHourTime: "14:00", EndHourTime: "16:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", MinDischargeSoc: &int90, SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  144. {StartHourTime: "16:00", EndHourTime: "18:00", WaveType: 2, Name: "on-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", MinDischargeSoc: &int90, SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  145. {StartHourTime: "18:00", EndHourTime: "21:00", WaveType: 2, Name: "on-peak", BriefDescribe: "Zero Import", SolarPriority: "9,9,9", GridDischargeMax: &float0_1, GridFeedMax: &float0_1, DischargePower: &float10, GridMax: &float0, GcaoMax: &float0, LoadPriority: "9,9,9", HeatEnable: &int1, SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 9, PowerOffApower: ""},
  146. {StartHourTime: "21:00", EndHourTime: "23:00", WaveType: 2, Name: "on-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  147. {StartHourTime: "23:00", EndHourTime: "24:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  148. },
  149. },
  150. },
  151. }
  152. // 模版 4478 - FOUR4FREE (GloBird Energy, Queensland)
  153. template4478 := TemplateData{
  154. Template: TemplateInfo{
  155. ID: 4478, ElectricCompany: "GloBird Energy", EleCompanyFullName: "GloBird Energy",
  156. DerSchdule: "FOUR4FREE", GridType: "FOUR4FREE", Name: "FOUR4FREE",
  157. ElectricityType: 1, CountryId: 3, ProvinceId: 88, ProvinceEn: "Queensland",
  158. Env: 0, MookRunCount: 0, Priority: 0,
  159. },
  160. StrategyList: []StrategyInfo{
  161. {
  162. SeasonName: "All months", Month: "1,2,3,4,5,6,7,8,9,10,11,12",
  163. DayName: "Every day", DayType: 3, CcociateSn: "", CcociateType: 1,
  164. DetailVoList: []DetailInfo{
  165. {StartHourTime: "00:00", EndHourTime: "10:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  166. {StartHourTime: "10:00", EndHourTime: "14:00", WaveType: 0, Name: "off-peak", BriefDescribe: "aPower charge from solar", SolarPriority: "1,2,3", GridChargeMax: &float5, LoadPriority: "1,2,0", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 3, PowerOffApower: ""},
  167. {StartHourTime: "14:00", EndHourTime: "16:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", MinDischargeSoc: &int90, SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  168. {StartHourTime: "16:00", EndHourTime: "23:00", WaveType: 2, Name: "on-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  169. {StartHourTime: "23:00", EndHourTime: "24:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  170. },
  171. },
  172. },
  173. }
  174. // 模版 4479 - Basic 3 Free (OVO Energy, Queensland)
  175. template4479 := TemplateData{
  176. Template: TemplateInfo{
  177. ID: 4479, ElectricCompany: "OVO Energy", EleCompanyFullName: "OVO Energy",
  178. DerSchdule: "Basic 3 Free", GridType: "Basic 3 Free", Name: "Basic 3 Free",
  179. ElectricityType: 1, CountryId: 3, ProvinceId: 88, ProvinceEn: "Queensland",
  180. Env: 0, MookRunCount: 0, Priority: 0,
  181. },
  182. StrategyList: []StrategyInfo{
  183. {
  184. SeasonName: "All months", Month: "1,2,3,4,5,6,7,8,9,10,11,12",
  185. DayName: "Every day", DayType: 3, CcociateSn: "", CcociateType: 1,
  186. DetailVoList: []DetailInfo{
  187. {StartHourTime: "00:00", EndHourTime: "11:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  188. {StartHourTime: "11:00", EndHourTime: "14:00", WaveType: 0, Name: "off-peak", BriefDescribe: "aPower charge from solar", SolarPriority: "1,2,3", GridChargeMax: &float5, LoadPriority: "1,2,0", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 3, PowerOffApower: ""},
  189. {StartHourTime: "14:00", EndHourTime: "16:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "Self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", MinDischargeSoc: &int90, SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  190. {StartHourTime: "16:00", EndHourTime: "23:00", WaveType: 2, Name: "on-peak", BriefDescribe: "self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  191. {StartHourTime: "23:00", EndHourTime: "24:00", WaveType: 1, Name: "mid-peak", BriefDescribe: "self-consumption", SolarPriority: "1,3,2", LoadPriority: "1,3,2", SolarCutoff: &int0, UseModeFlag: 0, DispatchId: 6, PowerOffApower: ""},
  192. },
  193. },
  194. },
  195. }
  196. return []TemplateData{template4477, template4478, template4479}
  197. }
  198. // ===== HAR 数据:澳洲所有州 =====
  199. func getAustraliaProvinces() []Province {
  200. return []Province{
  201. {ID: 93, NameEn: "Australian Capital Territory", NameZh: "澳大利亚首都特区"},
  202. {ID: 95, NameEn: "Jervis Bay Territory", NameZh: "杰维斯湾领地"},
  203. {ID: 87, NameEn: "New South Wales", NameZh: "新南威尔士州"},
  204. {ID: 94, NameEn: "Northern Territory", NameZh: "北领地"},
  205. {ID: 88, NameEn: "Queensland", NameZh: "昆士兰州"},
  206. {ID: 89, NameEn: "South Australia", NameZh: "南澳大利亚州"},
  207. {ID: 90, NameEn: "Tasmania", NameZh: "塔斯马尼亚州"},
  208. {ID: 91, NameEn: "Victoria", NameZh: "維多利亞省"},
  209. {ID: 92, NameEn: "Western Australia", NameZh: "西澳大利亚州"},
  210. }
  211. }
  212. // ===== HTTP 客户端 =====
  213. type Client struct {
  214. httpClient *http.Client
  215. token string
  216. }
  217. func NewClient(token string) *Client {
  218. return &Client{
  219. httpClient: &http.Client{Timeout: 30 * time.Second},
  220. token: token,
  221. }
  222. }
  223. // POST JSON 请求
  224. func (c *Client) PostJSON(path string, payload interface{}) ([]byte, error) {
  225. reqURL := baseURL + path
  226. jsonData, err := json.Marshal(payload)
  227. if err != nil {
  228. return nil, fmt.Errorf("序列化请求体失败: %w", err)
  229. }
  230. req, err := http.NewRequest("POST", reqURL, bytes.NewBuffer(jsonData))
  231. if err != nil {
  232. return nil, fmt.Errorf("创建请求失败: %w", err)
  233. }
  234. req.Header.Set("Content-Type", "application/json")
  235. req.Header.Set("Logintoken", c.token)
  236. req.Header.Set("Lang", lang)
  237. req.Header.Set("Accept", "application/json, text/plain, */*")
  238. resp, err := c.httpClient.Do(req)
  239. if err != nil {
  240. return nil, fmt.Errorf("请求失败: %w", err)
  241. }
  242. defer resp.Body.Close()
  243. body, err := io.ReadAll(resp.Body)
  244. if err != nil {
  245. return nil, fmt.Errorf("读取响应失败: %w", err)
  246. }
  247. if resp.StatusCode != http.StatusOK {
  248. return nil, fmt.Errorf("HTTP状态码: %d, 响应: %s", resp.StatusCode, string(body))
  249. }
  250. return body, nil
  251. }
  252. // ===== 业务逻辑 =====
  253. // 创建模版
  254. func createTemplate(client *Client, payload *CreateTemplatePayload) (int, error) {
  255. body, err := client.PostJSON("/hes-gateway/manage/updateOrSaveIotTouTemplate", payload)
  256. if err != nil {
  257. return 0, err
  258. }
  259. var resp APIResponse
  260. if err := json.Unmarshal(body, &resp); err != nil {
  261. return 0, fmt.Errorf("解析创建模版响应失败: %w", err)
  262. }
  263. if resp.Code != 200 {
  264. return 0, fmt.Errorf("创建模版失败: %s", resp.Message)
  265. }
  266. var newID int
  267. if err := json.Unmarshal(resp.Result, &newID); err != nil {
  268. return 0, fmt.Errorf("解析新模版ID失败: %w", err)
  269. }
  270. return newID, nil
  271. }
  272. // 构建创建模版的请求体
  273. func buildCreatePayload(tmplData *TemplateData, province Province) *CreateTemplatePayload {
  274. return &CreateTemplatePayload{
  275. Template: CreateTemplateInfo{
  276. CountryId: tmplData.Template.CountryId,
  277. ProvinceId: province.ID,
  278. DerSchdule: tmplData.Template.DerSchdule,
  279. EleCompanyFullName: tmplData.Template.EleCompanyFullName,
  280. ElectricCompany: tmplData.Template.ElectricCompany,
  281. GridType: tmplData.Template.GridType,
  282. ElectricityType: tmplData.Template.ElectricityType,
  283. Name: tmplData.Template.Name,
  284. MookRunCount: fmt.Sprintf("%d", tmplData.Template.MookRunCount),
  285. Priority: fmt.Sprintf("%d", tmplData.Template.Priority),
  286. Env: fmt.Sprintf("%d", tmplData.Template.Env),
  287. },
  288. StrategyList: tmplData.StrategyList,
  289. Lang: lang,
  290. }
  291. }
  292. func main() {
  293. // 从环境变量获取 token
  294. token := os.Getenv("FWH_TOKEN")
  295. if token == "" {
  296. fmt.Println("请设置 FWH_TOKEN 环境变量")
  297. fmt.Println("例如: export FWH_TOKEN=eyJhbGciOiJIUzI1NiIs...")
  298. os.Exit(1)
  299. }
  300. client := NewClient(token)
  301. // 获取硬编码的模版数据和州列表
  302. templates := getTemplateData()
  303. allProvinces := getAustraliaProvinces()
  304. // 收集已有模版对应的州ID(Queensland, id=88)
  305. existingProvinceIDs := make(map[int]bool)
  306. for _, tmpl := range templates {
  307. existingProvinceIDs[tmpl.Template.ProvinceId] = true
  308. }
  309. // 过滤出需要创建的州
  310. var targetProvinces []Province
  311. for _, p := range allProvinces {
  312. if !existingProvinceIDs[p.ID] {
  313. targetProvinces = append(targetProvinces, p)
  314. }
  315. }
  316. fmt.Printf("模版数量: %d, 目标州数量: %d (已排除: Queensland)\n", len(templates), len(targetProvinces))
  317. fmt.Printf("预计创建模版总数: %d\n\n", len(templates)*len(targetProvinces))
  318. // 为每个目标州创建所有模版
  319. successCount := 0
  320. failCount := 0
  321. for _, province := range targetProvinces {
  322. fmt.Printf("--- 州: %s (%s) ---\n", province.NameEn, province.NameZh)
  323. for _, tmplData := range templates {
  324. payload := buildCreatePayload(&tmplData, province)
  325. newID, err := createTemplate(client, payload)
  326. if err != nil {
  327. fmt.Printf(" ❌ [%s] 创建失败: %v\n", tmplData.Template.Name, err)
  328. failCount++
  329. continue
  330. }
  331. fmt.Printf(" ✅ [%s] 创建成功, 新ID: %d\n", tmplData.Template.Name, newID)
  332. successCount++
  333. // 请求间隔,避免过快
  334. time.Sleep(500 * time.Millisecond)
  335. }
  336. fmt.Println()
  337. }
  338. // 输出统计
  339. fmt.Printf("===== 完成 =====\n")
  340. fmt.Printf("成功: %d, 失败: %d, 总计: %d\n", successCount, failCount, successCount+failCount)
  341. }