HttpRequest.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. import CodeMgr from "../../scriptMain/CodeMgr"
  2. import { StringMap } from "../Collections/StringMap"
  3. import Gamecfg from "../common/gameCfg"
  4. import { gameMethod } from "../common/gameMethod"
  5. import { SevBack } from "../common/Xys"
  6. import Config from "../Config"
  7. import { GameEvent, NetworkEvent } from "../data/const/EventConst"
  8. import GameDataCenter from "../data/GameDataCenter"
  9. import GameController from "../GameController"
  10. import UIHelp, { DialogParams } from "../logic/ui/UIHelp"
  11. import EventMng from "../manager/EventMng"
  12. import { I18n } from "../utils/I18nUtil"
  13. import NetConfig from "./NetConfig"
  14. export interface POST_DATA {
  15. cmdId: number
  16. url: string,
  17. body: Object,
  18. cb1?: (sev: SevBack) => void,
  19. cb2?: (sev: SevBack) => void,
  20. deal: number
  21. postTime?: number
  22. }
  23. export default class HttpRequest {
  24. // 网络请求所需时间
  25. static netMs: number = 0
  26. // 网络请求最长时间(超时时间)
  27. static timeoutMs: number = 15000
  28. // 请求失败计数
  29. static errCount: number = 0
  30. //请求失败的接口
  31. static errPostMap: StringMap<POST_DATA> = new StringMap<POST_DATA>();
  32. //当前请求的接口
  33. static curPostMap: StringMap<POST_DATA> = new StringMap<POST_DATA>();
  34. static GET(path: string, callback: Function = () => { }) {
  35. let isJiami = this.isJiamiPlatform();
  36. let r = new XMLHttpRequest()
  37. r.open("GET", path + `?x=${isJiami ? 1 : 0}`, true)
  38. r.timeout = this.timeoutMs
  39. let isTimeOut = false;
  40. let isError = false;
  41. r.onerror = () => {
  42. isError = true;
  43. console.log("发生错误,url=", path)
  44. callback(null)
  45. }
  46. r.ontimeout = () => {
  47. isTimeOut = true;
  48. console.log("超时了,url=", path)
  49. callback(null)
  50. }
  51. r.onloadend = function () {
  52. if (isTimeOut || isError) return;
  53. let temp = {}
  54. if (r.status >= 200 && r.status <= 400 && r.readyState == 4) {
  55. let txt = gameMethod.xorEncrypt(r.response, isJiami);
  56. temp = JSON.parse(txt)
  57. }
  58. callback(temp)
  59. }
  60. r.send()
  61. }
  62. // 只发送,无需处理回调
  63. static POST_SIMPLE(url: string, body: Object) {
  64. let r = new XMLHttpRequest()
  65. r.open("POST", url, true)
  66. r.setRequestHeader("Content-Type", "application/json")
  67. r.send(JSON.stringify(body))
  68. }
  69. // 发送业务外,需处理回调
  70. static POST_SIMPLE_2(url: string, body: Object, callback: Function = () => { }) {
  71. let r = new XMLHttpRequest()
  72. r.open("POST", url, true)
  73. r.setRequestHeader("Content-Type", "application/json")
  74. r.onreadystatechange = () => {
  75. if (r.status >= 200 && r.status <= 400 && r.readyState == 4) {
  76. callback(r.response)
  77. } else {
  78. console.error("POST Faile:", r.status)
  79. }
  80. }
  81. r.send(JSON.stringify(body))
  82. }
  83. // 业务逻辑协议调用接口
  84. static POST(path: string, data: { [key: string]: string }, body: Object, callback: Function = () => { }, isLogin: boolean = false) {
  85. body = body || {}
  86. if (CC_JSB) {
  87. if (!gameMethod.isEmpty(GameDataCenter.login.playerInfo?.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo?.switch.clientLog)) {
  88. console.warn("C->S:", path, JSON.stringify(body))
  89. }
  90. } else {
  91. if (!gameMethod.isEmpty(GameDataCenter.login.playerInfo?.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo?.switch.clientLog)) {
  92. console.warn("C->S:", path, body)
  93. }
  94. }
  95. let delay = new Date().getTime()
  96. if (NetConfig.needWait(data.url, body)) {
  97. EventMng.emit(NetworkEvent.WAIT, 1)
  98. }
  99. //淘宝需要通过云服务调用服务器
  100. if (cc.sys.platform == cc.sys.TAOBAO_MINIGAME) {
  101. this.taoBaoCloudRequest(data, body, callback, isLogin)
  102. return
  103. }
  104. let r = new XMLHttpRequest()
  105. r.open("POST", path, true)
  106. r.timeout = this.timeoutMs
  107. r.setRequestHeader("Content-Type", "application/json")
  108. let xor: number = this.isJiamiPlatform() ? 1 : 0;//原生端接收到加密数据会不全,所以就不加密了
  109. // r.onerror = () => {
  110. // // console.warn("=======https onerror", r.status, r.readyState)
  111. // callback({ type: 0 })
  112. // if (isLogin) {
  113. // EventMng.emit(NetworkEvent.LOGIN_FAILED)
  114. // return
  115. // }
  116. // this.onError(data.url)
  117. // }
  118. r.onerror = r.ontimeout = () => {
  119. // console.warn("=======https ontimeout", r.status, r.readyState)
  120. this.netMs = r.timeout
  121. // 超时的时候,先把waiting关掉
  122. if (NetConfig.needWait(data.url, body)) {
  123. EventMng.emit(NetworkEvent.WAIT_CLOSE)
  124. }
  125. //请求失败时关闭强请求遮罩
  126. EventMng.emit(NetworkEvent.NETWAIT_MASK, false)
  127. // 如果是登录过程中出错,特殊处理
  128. if (isLogin) {
  129. EventMng.emit(NetworkEvent.LOGIN_FAILED)
  130. return
  131. }
  132. this.onTimeOut(data.url)
  133. }
  134. r.onreadystatechange = () => {
  135. // console.log("=======https onreadystatechange", r.status, r.readyState)
  136. if (r.readyState == 4) {
  137. this.netMs = new Date().getTime() - delay
  138. if (this.netMs > 1000) {
  139. console.warn("服务器回调时间超过1秒:", path, JSON.stringify(body))
  140. }
  141. }
  142. let temp = {}
  143. if (r.status >= 200 && r.status <= 400 && r.readyState == 4) {
  144. this.errCount = 0
  145. if (r.response == null || r.response == "") {
  146. EventMng.emit(NetworkEvent.ON_EXCEPTION)
  147. this.showErrDialog(data.url, data.uuid, data.version, JSON.stringify(body), r.response == null ? "null" : r.response)
  148. return
  149. }
  150. try {
  151. // temp = JSON.parse(r.response) // gameMethod.jiemi(r.response)
  152. // let jiemiTime = new Date().getTime()
  153. let txt = gameMethod.xorEncrypt(r.response, xor == 1);
  154. temp = JSON.parse(txt)
  155. // console.log("消息解密耗时:", new Date().getTime() - jiemiTime)
  156. } catch (error) {
  157. EventMng.emit(NetworkEvent.ON_EXCEPTION)
  158. console.error("解析回调数据失败:", r.response)
  159. this.showErrDialog(data.url, data.uuid, data.version, JSON.stringify(body), r.response == null ? "null" : r.response)
  160. }
  161. // try {
  162. if (temp && Object.keys(temp).length > 0) {
  163. if (CC_JSB) {
  164. if (!gameMethod.isEmpty(GameDataCenter.sevBack) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch.clientLog)) {
  165. console.warn("S->C:", JSON.stringify(temp))
  166. }
  167. } else {
  168. if (!gameMethod.isEmpty(GameDataCenter.sevBack) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch.clientLog)) {
  169. console.warn("S->C:", temp)
  170. }
  171. }
  172. //请求成功移除缓存的消息请求
  173. this.errPostMap.Remove(data.url)
  174. this.curPostMap.Remove(data.url)
  175. callback(temp)
  176. }
  177. // } catch (error) {
  178. // console.error("服务端回调数据处理时出现异常:", path, error)
  179. // }
  180. }
  181. if (r.readyState == 4 && NetConfig.needWait(data.url, body)) {
  182. EventMng.emit(NetworkEvent.WAIT, -1)
  183. }
  184. if (r.status < 200 || r.status > 400) {
  185. // 异常情况
  186. if (isLogin) {
  187. EventMng.emit(NetworkEvent.LOGIN_FAILED)
  188. } else {
  189. this.onTimeOut(data.url)
  190. }
  191. }
  192. };
  193. // r.send(JSON.stringify({ cs: CodeMgr.jiami(body) }))
  194. r.send(JSON.stringify({ xor: gameMethod.xorEncrypt(JSON.stringify(body)), x: xor }))
  195. // r.send(JSON.stringify(body))
  196. }
  197. //淘宝云服请求
  198. static taoBaoCloudRequest(data: { [key: string]: string }, body: Object, callback: Function = () => { }, isLogin: boolean = false) {
  199. let path = data.url + "?uuid=" + GameDataCenter.user.uuid +
  200. "&token=" + GameDataCenter.user.token +
  201. "&version=" + Config.appVersion +
  202. "&time=" + GameDataCenter.time.sevTime
  203. let self = this;
  204. async function taoBaoCloud() {
  205. let result
  206. try {
  207. result = await window['cloud'].application.httpRequest({
  208. //不需要完整域名,只需要接口访问路径即可
  209. 'path': path,
  210. 'method': 'POST',
  211. 'headers': { "Content-Type": "application/json" },
  212. 'params': {},
  213. 'body': { xor: gameMethod.xorEncrypt(JSON.stringify(body)) },
  214. //cloudAppId 云应用的Id
  215. 'exts': {
  216. "cloudAppId": "55088",
  217. "timeout": 4000,
  218. //空应用调用需要填写该字段,包括协议头以及端口号(可省略),支持http、https
  219. "domain": Gamecfg.packageInfo.getItem(Config.pid).wayhttp
  220. }
  221. });
  222. } catch (error) {
  223. console.log(error)
  224. }
  225. // console.log("result:", JSON.stringify(gameMethod.xorEncrypt(result)));
  226. let temp = {}
  227. try {
  228. // temp = JSON.parse(result) // gameMethod.jiemi(r.response)
  229. temp = JSON.parse(gameMethod.xorEncrypt(result))
  230. } catch (error) {
  231. EventMng.emit(NetworkEvent.ON_EXCEPTION)
  232. console.error("解析回调数据失败:", result)
  233. // self.showErrDialog(data.url, data.uuid, data.version, JSON.stringify(body), result == null ? "null" : result)
  234. }
  235. try {
  236. if (temp && Object.keys(temp).length > 0) {
  237. if (CC_JSB) {
  238. if (!gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch.clientLog)) {
  239. console.warn("S->C:", JSON.stringify(temp))
  240. }
  241. } else {
  242. if (!gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch) && !gameMethod.isEmpty(GameDataCenter.login.playerInfo.switch.clientLog)) {
  243. console.warn("S->C:", temp)
  244. }
  245. }
  246. //请求成功移除缓存的消息请求
  247. self.errPostMap.Remove(data.url);
  248. self.curPostMap.Remove(data.url);
  249. callback(temp)
  250. }
  251. } catch (error) {
  252. console.error("服务端回调数据处理时出现异常:", error)
  253. }
  254. if (NetConfig.needWait(data.url, body)) {
  255. EventMng.emit(NetworkEvent.WAIT, -1)
  256. }
  257. }
  258. this.errCount = 0
  259. let delayTime = new Date().getTime()
  260. taoBaoCloud().then(res => {
  261. if (!gameMethod.isEmpty(GameDataCenter.login.playerInfo?.switch?.clientLog)) {
  262. console.log("net delay time == " + (new Date().getTime() - delayTime))
  263. }
  264. });
  265. }
  266. // 发生服务器错误时,提示重试
  267. private static onError(url: string) {
  268. let dialogParam: DialogParams = {
  269. content: I18n.getI18nText("http_error", url),
  270. cbConfirm: () => {
  271. this.errCount = 0
  272. GameController.clear()
  273. cc.game.restart()
  274. },
  275. txtConfirm: I18n.getI18nText("http_overtime_confirm_1"),
  276. onlyConfirm: true
  277. }
  278. UIHelp.ShowSystemDialog(dialogParam)
  279. }
  280. // 发生服务器错误时,提示重试
  281. private static onTimeOut(url: string) {
  282. this.errCount++
  283. if (this.errCount > 3) {
  284. // 超过三次重连失败,提示退出游戏
  285. GameController.network.stopRequest = true
  286. let dialogParam: DialogParams = {
  287. content: I18n.getI18nText("http_error", url),
  288. cbConfirm: () => {
  289. this.errCount = 0
  290. GameController.clear()
  291. cc.game.restart()
  292. },
  293. txtConfirm: I18n.getI18nText("http_overtime_confirm_1"),
  294. onlyConfirm: true
  295. }
  296. UIHelp.ShowSystemDialog(dialogParam)
  297. } else {
  298. // 提示网络错误并引导重连
  299. let dialogParam: DialogParams = {
  300. content: I18n.getI18nText("http_overtime"),
  301. cbConfirm: () => {
  302. GameDataCenter.user.sendPlayerReconnect(() => {
  303. GameController.network.stopRequest = false
  304. //重连成功,重新发送失败请求
  305. this.errPostMap.Foreach((key: string, value: POST_DATA) => {
  306. console.log(`重新发送:${key}`)
  307. GameController.network.send(value.url, value.body, value.cb1, value.cb2, value.deal)
  308. })
  309. this.errPostMap.Clear()
  310. this.curPostMap.Clear()
  311. GameController.network.RemoveTimers()
  312. })
  313. },
  314. txtConfirm: I18n.getI18nText("http_overtime_confirm_2"),
  315. onlyConfirm: true
  316. }
  317. UIHelp.ShowSystemDialog(dialogParam)
  318. }
  319. }
  320. private static showErrDialog(url: string, uuid: string, version: string, body: string, response: string) {
  321. UIHelp.ShowDialog({
  322. content: "回调数据丢失,请将此界面截图给开发者\n"
  323. + `url:${url} uuid:${uuid} version:${version}\n`
  324. + `body:` + JSON.stringify(body) + "\n"
  325. + `response:${response.slice(0, 200)}`
  326. })
  327. }
  328. static isJiamiPlatform(): boolean {//以下平台不加密,因为以下加密数据会接受不全
  329. return !cc.sys.isNative && cc.sys.platform != cc.sys.VIVO_GAME && cc.sys.platform != cc.sys.XIAOMI_GAME && cc.sys.platform != cc.sys.OPPO_GAME;
  330. }
  331. }