nls-token.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. /**
  2. * 取阿里云 NLS 的 token(实时语音识别用)——**走后端网关**。
  3. *
  4. * 端点是 `{VITE_NLS_TOKEN_API}`(默认 `/aly-api/proxy/verify/getALYToken`,同源路径:
  5. * dev 由 vite 代理到 `http://121.43.55.7:10011`,生产由 nginx 转发)。
  6. * 返回形态:`{ code: 200, message: "成功", content: "<32 位 token>" }`。
  7. *
  8. * **前端不签 token**:签名要 AccessKeySecret,而前端 bundle 是公开的;
  9. * 网关侧(或后端)做这件事,前端只拿结果。
  10. * 与阿里云 `CreateToken` 的差别:网关**不返回过期时间**,按官方文档的 24 小时估。
  11. *
  12. * 缓存策略:缓存到过期前 5 分钟;并发调用共用同一个在途请求
  13. * (说话时前端可能连着触发几次,不该打多次网关)。
  14. */
  15. import { getNlsAppkey, getNlsTokenApiUrl } from "@/utils/runtime-config";
  16. export interface NlsTokenResult {
  17. token: string;
  18. /** 过期时间(epoch 毫秒)—— 网关不给,按 24 小时估 */
  19. expireAt: number;
  20. }
  21. /** NLS token 的有效期(官方文档 24 小时;网关不返回,所以按它估) */
  22. const TOKEN_LIFETIME_MS = 24 * 60 * 60 * 1000;
  23. /** 提前多久视为过期(留出换 token + 重连的余量) */
  24. const REFRESH_AHEAD_MS = 5 * 60 * 1000;
  25. let cached: NlsTokenResult | null = null;
  26. let inflight: Promise<NlsTokenResult> | null = null;
  27. const isValid = () => !!cached && cached.expireAt - Date.now() > REFRESH_AHEAD_MS;
  28. export const fetchNlsToken = async (): Promise<NlsTokenResult> => {
  29. if (isValid()) {
  30. return cached as NlsTokenResult;
  31. }
  32. if (inflight) {
  33. return inflight;
  34. }
  35. inflight = (async () => {
  36. const response = await fetch(getNlsTokenApiUrl(), { method: "GET" });
  37. let body: any = null;
  38. try {
  39. body = await response.json();
  40. } catch {
  41. /* 非 JSON 时按状态码报错 */
  42. }
  43. if (!response.ok) {
  44. throw new Error(body?.message || `获取语音识别 token 失败(HTTP ${response.status})`);
  45. }
  46. // 网关用 code 表达业务结果(HTTP 200 也可能是失败)
  47. if (Number(body?.code) !== 200 || !body?.content) {
  48. throw new Error(body?.message || "获取语音识别 token 失败:响应里没有 token");
  49. }
  50. cached = {
  51. token: String(body.content).trim(),
  52. expireAt: Date.now() + TOKEN_LIFETIME_MS,
  53. };
  54. return cached;
  55. })();
  56. try {
  57. return await inflight;
  58. } finally {
  59. inflight = null;
  60. }
  61. };
  62. /** NLS 的 appkey(识别请求要带)—— 见 `getNlsAppkey` 的说明 */
  63. export const getNlsTokenAppkey = (): string => getNlsAppkey();
  64. /** 丢掉缓存(排查用:怀疑 token 失效时强制重取) */
  65. export const clearNlsTokenCache = (): void => {
  66. cached = null;
  67. };