api.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. import $app from "./app";
  2. import $config from "./config";
  3. const api = {};
  4. // 标志位:控制登录提示弹窗只显示一次
  5. let isLoginPromptShown = false;
  6. // 标志位:控制token刷新状态,防止重复刷新
  7. let isRefreshing = false;
  8. let failedQueue = [];
  9. // 网络请求封装
  10. const Request = function (opts) {
  11. const originalComplete = opts.complete;
  12. return new Promise((resolve, reject) => {
  13. uni.request(
  14. Object.assign({}, opts, {
  15. complete: (res) => {
  16. const hasSuccess = Boolean(
  17. res.errMsg == "request:ok" &&
  18. res.statusCode >= 200 &&
  19. res.statusCode < 300
  20. );
  21. hasSuccess ? resolve(res.data) : reject(res);
  22. if (originalComplete && typeof originalComplete == "function")
  23. originalComplete(res);
  24. },
  25. })
  26. );
  27. });
  28. };
  29. // 路径拼接
  30. const pathJoin = function (...args) {
  31. return args.join("/").replace(/(?<!:)(\/{2,})/g, "/");
  32. };
  33. // 显示LOADING
  34. const showLoading = function () {
  35. uni.showLoading({ title: "", mask: true });
  36. };
  37. // 关闭LOADING
  38. const hideLoading = function () {
  39. uni.hideLoading();
  40. };
  41. // 获取用户TOKEN
  42. const getUserToken = function () {
  43. return uni.getStorageSync($config.keyname.userToken);
  44. };
  45. /**
  46. * 基础请求
  47. * @param {String} method 请求类型
  48. * @param {String} url 请求地址
  49. * @param {Object} data 请求数据
  50. * @param {Object} opts 配置参数
  51. */
  52. api.base = function (method, url, data, opts) {
  53. return new Promise(async (resolve, reject) => {
  54. const options = Object.assign(
  55. {
  56. // 请求地址
  57. url: pathJoin($config.url.request, url),
  58. // 请求数据
  59. data: data || {},
  60. // 请求类型
  61. method: method || "post",
  62. // 数据类型
  63. dataType: "json",
  64. // 自动显示LOADING
  65. loading: true,
  66. // 启用错误自动拦截
  67. error: true,
  68. // 请求头设定
  69. header: {
  70. "content-type": "application/json",
  71. Authorization: "Bearer " + getAccessToken(), // 修改这里,使用新的获取token方法
  72. },
  73. // 设置显示LOADING的方法
  74. showLoading: showLoading,
  75. // 设置关闭LOADING的方法
  76. hideLoading: hideLoading,
  77. },
  78. opts || {}
  79. );
  80. if (options.loading && typeof options.showLoading == "function") {
  81. options.showLoading();
  82. }
  83. Request(options)
  84. .then(async (res) => {
  85. if (options.loading && typeof options.hideLoading == "function") {
  86. options.hideLoading();
  87. }
  88. switch (res.code) {
  89. // 请求正常
  90. case "00000":
  91. resolve(res);
  92. break;
  93. // 约定401为未登录状态
  94. case "A0230":
  95. try {
  96. // 尝试刷新token
  97. console.log("进入刷新请求");
  98. const refreshResult = await refreshToken();
  99. if (refreshResult) {
  100. // 刷新成功,更新header中的Authorization并重新请求
  101. options.header.Authorization = "Bearer " + getAccessToken();
  102. try {
  103. const db = await Request(options);
  104. resolve(db);
  105. } catch (err) {
  106. reject(err);
  107. }
  108. } else {
  109. // 刷新失败,执行登录
  110. // await api.login();
  111. options.header.Authorization = "Bearer " + getAccessToken();
  112. try {
  113. const db = await Request(options);
  114. resolve(db);
  115. } catch (err) {
  116. reject(err);
  117. }
  118. }
  119. } catch (err) {
  120. reject(err);
  121. }
  122. break;
  123. // 其他错误请求
  124. default:
  125. if (options.error) {
  126. $app.popup.alert(res.msg || `${url}\r\n未知错误`, "数据请求");
  127. } else {
  128. reject(res);
  129. }
  130. break;
  131. }
  132. })
  133. .catch(async (err) => {
  134. if (err.data?.code == "A0230") {
  135. try {
  136. // 尝试刷新token
  137. const refreshResult = await refreshToken();
  138. if (refreshResult) {
  139. // 刷新成功,更新header中的Authorization并重新请求
  140. options.header.Authorization = "Bearer " + getAccessToken();
  141. try {
  142. const db = await Request(options);
  143. resolve(db);
  144. } catch (err) {
  145. reject(err);
  146. }
  147. } else {
  148. // 刷新失败,执行登录
  149. const currentPage = getCurrentPages().pop();
  150. const currentRoute = currentPage ? currentPage.route : "";
  151. if (currentRoute === "pages/index/index") {
  152. // 在首页时只显示弱提示
  153. $app.popup.toast(`${err.data?.msg}`);
  154. } else {
  155. // 非首页时显示登录确认框
  156. if (!isLoginPromptShown) {
  157. isLoginPromptShown = true;
  158. // 清除登录缓存
  159. $app.popup
  160. .confirm(`${err.data?.msg},点击确定去登录`, "提示", {
  161. showCancel: true,
  162. })
  163. .then((confirmed) => {
  164. isLoginPromptShown = false;
  165. if (confirmed) {
  166. uni.clearStorage();
  167. uni.navigateTo({ url: "/pages/login/login" });
  168. }
  169. });
  170. }
  171. }
  172. }
  173. } catch (err) {
  174. reject(err);
  175. }
  176. } else {
  177. $app.popup.alert(`服务器响应失败\r\n${err.data?.msg}`, "数据请求");
  178. }
  179. /*失败处理,此处为非200状态码引起的错误*/
  180. if (options.loading && typeof options.hideLoading == "function") {
  181. options.hideLoading();
  182. }
  183. });
  184. });
  185. };
  186. // 新增:获取accessToken的方法
  187. const getAccessToken = function () {
  188. return uni.getStorageSync($config.keyname.accessToken);
  189. };
  190. // 处理请求队列
  191. const processQueue = (error, token = null) => {
  192. failedQueue.forEach((prom) => {
  193. if (error) {
  194. prom.reject(error);
  195. } else {
  196. prom.resolve(token);
  197. }
  198. });
  199. failedQueue = [];
  200. };
  201. // 新增:刷新token的方法
  202. const refreshToken = async function () {
  203. // 如果正在刷新,将请求加入队列
  204. if (isRefreshing) {
  205. return new Promise((resolve, reject) => {
  206. failedQueue.push({ resolve, reject });
  207. });
  208. }
  209. isRefreshing = true;
  210. const refreshToken = uni.getStorageSync($config.keyname.refreshToken);
  211. console.log("刷新token", refreshToken);
  212. if (!refreshToken) {
  213. isRefreshing = false;
  214. processQueue(new Error("No refresh token available"));
  215. return false;
  216. }
  217. try {
  218. const res = await Request({
  219. url: pathJoin($config.url.request, $config.api.refreshToken),
  220. method: "post",
  221. data: { refreshToken },
  222. header: {
  223. "content-type": "application/json",
  224. },
  225. });
  226. if (res.code === "00000") {
  227. uni.setStorageSync($config.keyname.accessToken, res.data.accessToken);
  228. uni.setStorageSync($config.keyname.refreshToken, res.data.refreshToken);
  229. const newToken = getAccessToken();
  230. isRefreshing = false;
  231. processQueue(null, newToken);
  232. return true;
  233. } else {
  234. isRefreshing = false;
  235. processQueue(new Error("Token refresh failed"));
  236. return false;
  237. }
  238. } catch (e) {
  239. console.error("Token refresh error:", e);
  240. isRefreshing = false;
  241. processQueue(e);
  242. return false;
  243. }
  244. };
  245. /**
  246. * POST请求
  247. * @param {String} url 请求地址
  248. * @param {Object} data 请求数据
  249. * @param {Object} opts 配置参数
  250. */
  251. api.post = function (url, data, opts) {
  252. return api.base("post", url, data, Object.assign({}, opts));
  253. };
  254. /**
  255. * GET请求
  256. * @param {String} url 请求地址
  257. * @param {Object} data 请求数据
  258. * @param {Object} opts 配置参数
  259. */
  260. api.get = function (url, data, opts) {
  261. return api.base("get", url, data, Object.assign({}, opts));
  262. };
  263. /**
  264. * PUT请求
  265. * @param {String} url 请求地址
  266. * @param {Object} data 请求数据
  267. * @param {Object} opts 配置参数
  268. */
  269. api.put = function (url, data, opts) {
  270. return api.base("put", url, data, Object.assign({}, opts));
  271. };
  272. /**
  273. * 获取静态数据
  274. * @param {String} url 静态文件地址
  275. * @param {Object} data 参数数据
  276. * @param {Object} opts 配置参数
  277. */
  278. api.static = function (url, data, opts) {
  279. return new Promise(async (resolve, reject) => {
  280. // 设置选项
  281. var _opts = Object.assign(
  282. {
  283. url: /^http/.test(url) ? url : API_BASE + (url || ""),
  284. data: Object.assign({ cache: Date.now() }, data),
  285. },
  286. opts || {}
  287. );
  288. uni.request({
  289. ..._opts,
  290. success: (res) => {
  291. resolve(res.data);
  292. },
  293. });
  294. });
  295. };
  296. /**
  297. * 文件上传
  298. * @param {String} file 文件临时路径
  299. * @param {Object} opts 上传参数
  300. */
  301. api.upload = function (file, opts) {
  302. return new Promise((resolve, reject) => {
  303. const options = Object.assign(
  304. {
  305. // 请求地址
  306. url: pathJoin($config.url.upload, opts?.url || ""),
  307. // 请求数据
  308. filePath: file,
  309. // 自动显示LOADING
  310. loading: true,
  311. // 启用错误自动拦截
  312. error: true,
  313. // 请求头设定
  314. header: {
  315. token: uni.getStorageSync($config.keyname.userToken),
  316. },
  317. // 设置显示LOADING的方法
  318. showLoading: showLoading,
  319. // 设置关闭LOADING的方法
  320. hideLoading: hideLoading,
  321. },
  322. opts || {}
  323. );
  324. uni.uploadFile({
  325. ...options,
  326. success: (res) => {
  327. if (options.loading && typeof options.hideLoading == "function") {
  328. options.hideLoading();
  329. }
  330. try {
  331. const data = JSON.parse(res.data);
  332. resolve(data);
  333. } catch (e) {
  334. resolve(res);
  335. }
  336. },
  337. fail: (err) => {
  338. if (options.loading && typeof options.hideLoading == "function") {
  339. options.hideLoading();
  340. }
  341. reject(err);
  342. },
  343. });
  344. });
  345. };
  346. /**
  347. * 获取登录code
  348. */
  349. api.userCode = function () {
  350. return new Promise((resolve, reject) => {
  351. uni.login({
  352. provider: "weixin",
  353. success: (res) => resolve(res.code),
  354. fail: (err) => reject(err),
  355. });
  356. });
  357. };
  358. /**
  359. * 自动登录
  360. * @param {Object} data 登录数据(非必填)
  361. */
  362. api.login = function (data) {
  363. return new Promise(async (resolve, reject) => {
  364. const code = await api.userCode();
  365. api
  366. .post($config.api.login, { ...(data || {}), code })
  367. .then(async (res) => {
  368. // 保存 accessToken 和 refreshToken
  369. uni.setStorageSync($config.keyname.accessToken, res?.data?.accessToken);
  370. uni.setStorageSync(
  371. $config.keyname.refreshToken,
  372. res?.data?.refreshToken
  373. );
  374. // 保留原有的 userInfo 存储
  375. uni.setStorageSync($config.keyname.userInfo, res?.data?.userInfo);
  376. // 返回 accessToken 保持向后兼容
  377. resolve(res.data.accessToken);
  378. })
  379. .catch((err) => reject(err));
  380. });
  381. };
  382. export default api;