request.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. const path = require('path')
  2. const Logger = require('../../lib/Logger')
  3. const { getJkesSettings, normalizeApiBase } = require('./jkesSettings')
  4. const { postJkes } = require('./jkesHttp')
  5. const logger = new Logger(path.join(__dirname, '../../logs/JKES.log'), 'INFO')
  6. function buildJkesHeaders(token) {
  7. const tokenClean = String(token ?? '').trim()
  8. const s = getJkesSettings()
  9. return {
  10. 'x-auth-token': tokenClean,
  11. 'content-type': 'application/json',
  12. 'Accept-Encoding': 'gzip,compress,br,deflate',
  13. 'User-Agent': s.userAgent,
  14. Referer: s.referer
  15. }
  16. }
  17. function isJkesLoginExpiredPayload(payload) {
  18. if (!payload || typeof payload !== 'object') return false
  19. const code = Number(payload.code)
  20. const msg = String(payload.message ?? payload.msg ?? '').trim()
  21. return (
  22. (code === 500 && msg.includes('无效用户')) ||
  23. (code === 401 && msg.includes('您的认证已经失效'))
  24. )
  25. }
  26. function makeJkesLoginExpiredError(payload) {
  27. const msg = '乐跑账号登录失效,请重新使用乐跑登录器进行登录'
  28. const err = new Error(msg)
  29. err.code = 'JKES_AUTH_EXPIRED'
  30. err.loginExpired = true
  31. err.retryable = false
  32. err.payload = payload
  33. return err
  34. }
  35. /**
  36. * @param {string} url 如 /sys/user/getMyInfo
  37. * @param {object} [data] POST body
  38. * @param {string} token
  39. * @param {object} [options]
  40. * @param {'auto'|'direct'|'proxy'} [options.outboundMode='auto']
  41. */
  42. async function jkesRequest(url, data, token, options = {}) {
  43. const tokenClean = String(token ?? '').trim()
  44. if (!tokenClean) {
  45. logger.error('[JKES] 缺少 token')
  46. return null
  47. }
  48. const s = getJkesSettings()
  49. const pathPart = url.startsWith('/') ? url : `/${url}`
  50. const fullUrl = `${normalizeApiBase(s.apiBase)}${pathPart}`
  51. const body = data === undefined || data === null ? {} : data
  52. const { outboundMode = 'auto' } = options
  53. let parsed
  54. try {
  55. parsed = new URL(fullUrl)
  56. } catch (e) {
  57. logger.error(`[JKES] 非法 URL: ${fullUrl}`)
  58. return null
  59. }
  60. if (parsed.protocol !== 'https:') {
  61. logger.error(`[JKES] 必须使用 https: ${fullUrl}`)
  62. return null
  63. }
  64. try {
  65. logger.info(`[JKES] POST ${pathPart}`)
  66. const res = await postJkes(fullUrl, body, {
  67. headers: buildJkesHeaders(tokenClean),
  68. timeout: Number(s.requestTimeoutMs) || 30000,
  69. validateStatus: () => true,
  70. outboundMode,
  71. logger
  72. })
  73. const payload = res.data
  74. const payloadPreview =
  75. typeof payload === 'object' && payload !== null
  76. ? JSON.stringify(payload)
  77. : String(payload ?? '')
  78. if (isJkesLoginExpiredPayload(payload)) {
  79. throw makeJkesLoginExpiredError(payload)
  80. }
  81. if (res.status !== 200) {
  82. logger.error(
  83. `[JKES] HTTP ${res.status} ${pathPart} 响应: ${payloadPreview.slice(0, 1000)}`
  84. )
  85. if (typeof payload === 'object' && payload !== null) {
  86. return payload
  87. }
  88. return null
  89. }
  90. return payload
  91. } catch (error) {
  92. if (error?.loginExpired) throw error
  93. const st = error.response?.status
  94. const dataErr = error.response?.data
  95. const errStr =
  96. typeof dataErr === 'object' && dataErr !== null
  97. ? JSON.stringify(dataErr)
  98. : String(dataErr ?? '')
  99. logger.error(
  100. `[JKES] 请求异常 ${pathPart} http=${st ?? 'n/a'} body=${errStr.slice(0, 500)} ${error.message}`
  101. )
  102. return null
  103. }
  104. }
  105. module.exports = {
  106. jkesRequest,
  107. isJkesLoginExpiredPayload,
  108. makeJkesLoginExpiredError,
  109. get BASE_URL() {
  110. return normalizeApiBase(getJkesSettings().apiBase)
  111. }
  112. }