news 2026/1/22 10:49:35

长耗时接口异步改造总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
长耗时接口异步改造总结

一次从同步到异步的华丽转身 ✨

📋 目录

  • 背景问题
  • 解决方案
  • 技术实现
  • 踩坑记录
  • 经验总结

🎬 背景问题

问题场景 😱

长耗时接口(30-60秒)遇到网关超时(30秒)的问题:

  • 接口调用需要30-60秒才能返回
  • 网关设置了30秒超时重试
  • 结果:接口超时 → 网关重试 → 重复调用 → 用户体验差 😭

解决方案 💡

异步处理 + Redis缓存= 完美解决!

  • 用户发起请求 → 立即返回"处理中" → 后台异步处理
  • 处理完成后 → Redis存储结果 → 发送通知
  • 用户点击通知链接 → 自动打开页面 → 显示处理结果

🎯 核心改动

后端改动

1️⃣ 新增 Redis Key
// constant/redis.jsOP_TASK_AI:'task_ai'// 用于存储异步任务状态
2️⃣ 新增查询路由
// router/xxx.jsrouter.get('/api/task/result_by_key',controller.task.getTaskResult,)
3️⃣ Controller层:两个接口的职责分离
接口用途参数返回
create_task🚀创建任务input,type,related_id处理结果 or{status: 'processing'}
get_task_result🔍查询结果taskKeyRedis中的完整数据 ornull

关键代码

// 创建任务接口(主动)asynccreateTask(ctx){const{input,type,related_id}=ctx.queryconstresult=awaitthis.ctx.service.task.createTask(input,type,related_id)// ...}// 查询结果接口(被动)asyncgetTaskResult(ctx){const{taskKey}=ctx.queryconstresult=awaitthis.ctx.service.task.getTaskResult(taskKey)// ...}
4️⃣ Service层:核心方法

createTask- 创建异步任务

asynccreateTask(input,type,related_id){// 1. 检查Redis是否有结果// 2. 如果有 → 直接返回// 3. 如果没有 → 创建任务,异步处理// 4. 返回 { status: 'processing' }}

processTaskAsync- 异步处理任务

asyncprocessTaskAsync(input,type,redisKey,related_id){// 1. 调用长耗时接口// 2. 处理成功 → 更新Redis状态为 'completed'// 3. 发送通知(包含跳转链接)}

getTaskResult- 查询任务结果

asyncgetTaskResult(taskKey){// 直接从Redis查询,不创建任务constredisKey=`${REDIS_KEYS.OP_TASK_AI}:${taskKey}`constredisValue=awaitthis.ctx.getRedisValue(redisKey)returnredisValue?JSON.parse(redisValue):null}

Redis数据结构

// processing 状态{status:'processing',type:'task_type',input:'input_data',related_id:123,createdAt:1234567890}// completed 状态{status:'completed',result:'处理结果',type:'task_type',input:'input_data',related_id:123,completedAt:1234567890}

前端改动

1️⃣ Service层:新增查询接口
// service/xxx/index.tsasyncfunctiongetTaskResult(params:{taskKey:string}){const{data}=awaithttpGet('/api/task/result_by_key',params)returndata}
2️⃣ 列表页:URL参数处理
// 解析URL参数const{relatedId}=querystring.parse(window.location.search)// 通过URL打开详情constopenDetailByUrl=async()=>{if(relatedId){constdetail=data.list.find((item)=>`${item.id}`===relatedId)if(detail){dispatchUpdate(detail)}}}// 监听数据加载完成,自动打开useEffect(()=>{if(data.list.length>0){openDetailByUrl()}},[relatedId,data])

关闭时清除URL参数

consthandleClose=()=>{dispatchClose()onReset()// 重置搜索参数,URL会自动更新}
3️⃣ 详情页:从URL查询结果
// 解析URL参数const{taskKey}=querystring.parse(window.location.search)as{taskKey:string}// 查询Redis并显示结果constloadTaskResult=async()=>{if(taskKey){consttaskResult=awaitgetTaskResult({taskKey})if(taskResult){const{status,result,type}=taskResult// 设置类型if(type){setTaskType(type)}// 根据状态显示结果if(status==='completed'&&result){setShowResult(true)setResultValue(result)}elseif(status==='processing'){message.info('处理中,请稍候...')}elseif(status==='failed'){message.warning('处理失败,请重试')}}}}// 页面打开时自动查询useEffect(()=>{if(taskKey&&visible){loadTaskResult()}},[taskKey,visible])

重要:Form和State的区别

// ❌ 错误:组件不在Form.Item中,不能用form管理form.setFieldsValue({field:value})// 没用!// ✅ 正确:直接用state管理const[field,setField]=useState<string>()<Select value={field}// 绑定stateonChange={setField}// 更新state>

🛠️ 技术实现

Redis Key设计

规则${REDIS_KEYS.OP_TASK_AI}:${taskKey}

为什么只用单个唯一标识?

  • ✅ 简单:不需要组合多个参数
  • ✅ 唯一:taskKey本身就是唯一的
  • ✅ 易查:前端只需要提取唯一标识

示例

input: https://example.com/file/1765423642667_169.jpeg taskKey: 1765423642667_169.jpeg redisKey: task_ai:1765423642667_169.jpeg

异步处理流程

用户点击识别 ↓ 检查Redis是否有结果 ↓ 有结果 → 直接返回 ↓ 无结果 → 设置Redis状态为 'processing' ↓ 异步调用 processRecognitionTask ↓ 调用AI识别接口(30-60秒) ↓ 识别成功 → 更新Redis状态为 'completed' ↓ 发送通知(包含跳转链接)

通知链接格式

https://example.com/page/detail?relatedId=${related_id}&taskKey=${taskKey}

为什么这样设计?

  • relatedId:用于获取关联数据详情,打开页面
  • taskKey:用于查询Redis任务结果

URL参数处理

前端解析

importquerystringfrom'query-string'// 解析URL参数const{relatedId,taskKey}=querystring.parse(window.location.search)// ⚠️ 注意:querystring.parse 返回的类型可能是 string | string[] | null// 需要类型断言或类型守卫const{taskKey}=querystring.parse(window.location.search)as{taskKey:string}

清除URL参数

// 方法1:重置defaultParams(推荐)onReset()// 会设置所有参数为默认值,空值会被过滤// 方法2:手动清除特定参数constclearUrlExtraParams=()=>{constcurrentParams=querystring.parse(location.search)const{relatedId,taskKey,...restParams}=currentParams// 只保留 restParams}

🐛 踩坑记录

坑1:Select组件不在Form.Item中

问题

// Select不在Form.Item中<Select onChange={onVendorTypeChange}>{/* ... */}</Select>// 但是代码中用了form.setFieldsValueform.setFieldsValue({vendorType:value})// ❌ 没用!

原因:Form只能管理Form.Item中的字段

解决

// ✅ 直接用state管理const[vendorType,setVendorType]=useState<string>()constonVendorTypeChange=(value:string)=>{setVendorType(value)// 只更新state}<Select value={vendorType}onChange={onVendorTypeChange}>

坑2:URL参数被useSyncParams重置

问题

  • URL中有额外参数(如relatedId,taskKey
  • 但是useSyncParamshook 会重写URL,只保留defaultParams中的参数
  • 结果:额外参数被清除了 😱

原因

// useSyncParams 的 onParamSync 会重写URLconstonParamSync=()=>{history.push(`${pathname}?${querystring.stringify(params,{sort:false})}`)// params 只包含 defaultParams 中的字段}

解决

// 方案1:在defaultParams中添加这些字段(推荐)constdefaultParams={// ...其他字段taskKey:'',relatedId:'',}// 方案2:修改useSyncParams hook,保留额外参数// (但可能影响其他页面,需谨慎)

坑3:后端获取域名的方式

问题:前端用window.location.host,后端用什么?

答案

// 方式1:配置文件(当前使用)constbaseUrl=this.app.config.baseUrl||''// 方式2:从请求获取(更灵活)constbaseUrl=`${this.ctx.request.protocol}://${this.ctx.request.host}`// 或constbaseUrl=this.ctx.request.origin

坑4:异步处理中的this上下文

问题:异步函数中this可能为undefined

解决

// ✅ 项目风格:直接调用,不捕获thisthis.processRecognitionTask(imageUrl,vendorType,redisKey,maintain_id)// 如果出错,会在processRecognitionTask内部用this.ctx.throwValidateWarn处理

💡 经验总结

后端经验

  1. Redis Key设计要简单

    • ✅ 只用单个唯一标识(如taskKey),不要组合多个参数
    • ✅ 便于前端查询,不需要复杂的key生成逻辑
    • ✅ 从输入中提取唯一标识(如文件名、ID等)
  2. 异步处理要"fire and forget"

    • ✅ 不等待异步任务完成
    • ✅ 错误处理在异步函数内部完成
    • ✅ 符合项目现有代码风格
  3. 两个接口职责要清晰

    • create_task:创建任务(主动,会创建新任务)
    • get_task_result:查询结果(被动,只查询不创建)
  4. Redis数据结构要完整

    • 存储任务相关的完整信息(type、input、related_id等)
    • 便于前端直接使用,不需要额外查询
    • 包含状态、结果、时间戳等元数据

前端经验

  1. URL参数处理要统一

    • ✅ 使用querystring.parse(window.location.search)
    • ✅ 注意类型处理(可能是string | string[] | null
    • ✅ 使用类型断言或类型守卫
  2. Form和State要分清

    • ✅ Form只能管理Form.Item中的字段
    • ✅ 不在Form.Item中的组件用state管理
    • ✅ 不要混用,避免无效操作
  3. useSyncParams要小心

    • ✅ 会重写URL,只保留defaultParams中的参数
    • ✅ 额外参数需要在defaultParams中声明(即使为空)
    • ✅ 避免URL参数被意外清除
  4. 页面打开时机要准确

    • ✅ 等数据加载完成后再打开
    • ✅ 通过useEffect监听数据变化
    • ✅ 避免在数据未加载时操作
  5. 代码清理很重要

    • ✅ 删除无意义的form操作
    • ✅ 删除console.log和调试代码
    • ✅ 保持代码整洁和可维护

通用经验

  1. 异步改造的核心

    • 立即返回 → 后台处理 → 结果存储 → 通知用户
  2. Redis的妙用

    • 临时存储任务状态
    • 跨请求共享数据
    • 设置TTL自动清理
  3. URL参数传递

    • 简单直接,便于分享
    • 刷新页面不丢失状态
    • 但要处理好清除逻辑
  4. 代码风格一致性

    • 参考项目现有代码
    • 保持风格统一
    • 便于团队协作

🎉 最终效果

用户体验提升

  1. 发起请求→ 立即返回"处理中",可以关闭页面
  2. 处理完成→ 收到通知,点击链接
  3. 自动打开→ 页面自动打开,处理结果自动填充
  4. 完美!

技术指标

  • ✅ 接口响应时间:从30-60秒 →<1秒
  • ✅ 用户体验:从等待 →异步处理
  • ✅ 代码质量:逻辑清晰,职责分明
  • ✅ 可维护性:,符合项目风格

🚀 适用场景

什么时候用这个模式?

适合

  • 长耗时接口(>10秒)
  • 网关有超时限制
  • 用户不需要立即看到结果
  • 可以通过通知告知用户

不适合

  • 短耗时接口(<5秒)
  • 需要实时反馈
  • 用户必须等待结果

📝 核心要点总结

  1. 两个接口分离:创建任务 vs 查询结果
  2. Redis存储状态:processing → completed/failed
  3. 异步处理:fire and forget模式
  4. URL参数传递:通过链接跳转并自动加载结果
  5. 前端状态管理:Form vs State要分清

Happy Coding! 🎊

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/20 11:14:53

启点创新智慧景区小程序系统,景区智能化售票系统,景区购票管理系统

启点创新智慧景区小程序系统&#xff1a;重塑旅游体验新范式在数字化浪潮席卷全球的当下&#xff0c;旅游行业正经历着前所未有的变革。游客对于旅游体验的需求不再局限于欣赏自然风光和人文景观&#xff0c;更追求便捷、个性化、沉浸式的旅游服务。启点创新智慧景区小程序系统…

作者头像 李华
网站建设 2026/1/20 10:13:10

3种快速安装readr数据读取工具的方法:从入门到精通

3种快速安装readr数据读取工具的方法&#xff1a;从入门到精通 【免费下载链接】readr 项目地址: https://gitcode.com/gh_mirrors/rea/readr readr是R语言生态系统中专门用于高效数据读取和文件解析的核心工具包&#xff0c;能够快速处理CSV、TSV等多种分隔格式的数据…

作者头像 李华
网站建设 2026/1/7 20:52:24

对比实测:传统vs自动化VMware安装,效率提升300%

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个自动化工具&#xff0c;能够批量部署VMware虚拟机并自动完成操作系统安装和基础配置。要求支持无人值守安装&#xff0c;提供与手动安装方式的详细对比报告&#xff0c;包括…

作者头像 李华
网站建设 2026/1/22 8:40:16

跨平台字体革命:PingFangSC字体包的终极解决方案

跨平台字体革命&#xff1a;PingFangSC字体包的终极解决方案 【免费下载链接】PingFangSC PingFangSC字体包文件、苹果平方字体文件&#xff0c;包含ttf和woff2格式 项目地址: https://gitcode.com/gh_mirrors/pi/PingFangSC 还在为不同设备上的字体显示差异而烦恼吗&am…

作者头像 李华
网站建设 2026/1/21 21:19:14

14 类圣诞核心 SVG 交互方案拆解(附案例 + 资源)

1. 选择类交互&#xff1a;精准匹配礼物需求 交互方案核心逻辑品牌案例关键组件 / 操作要点学习资源挤压伸长以 “选择” 为核心&#xff0c;通过挤压交互引导用户筛选礼物类型OPARTMENT《圣诞爱意》、尚美《你的礼想型》「挤压伸长」&#xff08;UGC 组件&#xff09;&#x…

作者头像 李华