鸿蒙聊天 Demo 练习 03:接入 Next.js 后端接口,实现真机前后端联调

发布时间:2026/5/22 1:35:23

鸿蒙聊天 Demo 练习 03:接入 Next.js 后端接口,实现真机前后端联调 鸿蒙聊天 Demo 练习 03接入 Next.js 后端接口实现真机前后端联调一、本次分支feat/server-init二、本次目标本次在原有聊天 Demo 的基础上把前端写死的模拟回复改造成调用自己写的 Next.js 后端接口。本次完成的核心流程在鸿蒙项目中新增server后端目录。使用 Next.js 初始化后端项目。新增GET /api/ping测试接口。新增POST /api/chat模拟聊天接口。鸿蒙前端封装 HTTP 请求。聊天页面调用后端接口。真机通过局域网 IP 请求电脑上的后端服务。页面展示后端返回的 assistant 回复。最终效果用户输入消息 ↓ 鸿蒙前端先展示用户消息 ↓ 调用 Next.js 后端 POST /api/chat ↓ 后端返回模拟 assistant 回复 ↓ 鸿蒙页面展示后端返回内容本次还没有接入 MySQL也没有接入真实 AI只是先跑通最重要的前后端通信链路。三、涉及文件server/app/api/ping/route.ts server/app/api/chat/route.ts entry/src/main/ets/constants/ApiConstants.ets entry/src/main/ets/api/ChatApi.ets entry/src/main/ets/pages/Setting.ets entry/src/main/module.json5四、为什么要加后端之前聊天 Demo 的回复都是前端自己模拟的用户输入 ↓ 前端创建用户消息 ↓ 前端创建 AI 假回复 ↓ 更新 chatList这种方式适合练习页面布局、状态更新、列表渲染和滚动到底部但它不是真实业务。真实业务中前端通常只负责输入和展示消息要发送给后端由后端处理后再返回结果。所以本次把聊天流程改造成鸿蒙前端 ↓ HTTP 请求 ↓ Next.js 后端 ↓ JSON 响应 ↓ 鸿蒙页面更新这样后续才能继续扩展 MySQL、历史消息、会话列表和真实 AI 接口。五、项目结构变化本次新增了一个server目录专门放 Next.js 后端代码。MyApplication ├── entry │ └── src/main/ets │ ├── api │ │ └── ChatApi.ets │ ├── constants │ │ └── ApiConstants.ets │ └── pages │ └── Setting.ets │ ├── server │ └── app │ └── api │ ├── ping │ │ └── route.ts │ └── chat │ └── route.ts │ └── docs现在这个项目变成了entry鸿蒙前端 serverNext.js 后端 docs复盘文档这种结构适合练习全栈 Demo因为前后端代码都在一个仓库里提交记录也比较完整。六、初始化 Next.js 后端在项目根目录执行npx create-next-applatest server初始化时选择TypeScript: Yes ESLint: Yes Tailwind CSS: No src directory: No App Router: Yes Turbopack: Yes 或 No 都可以 Import alias: No进入后端目录启动cdservernpmrun dev启动成功后终端会显示类似Local: http://localhost:3000 Network: http://192.168.20.8:3000其中localhost:3000是电脑自己访问。192.168.20.8:3000是局域网内其他设备访问比如鸿蒙真机。七、Node 版本问题初始化 Next.js 时遇到过 Node 版本问题You are using Node.js 18.20.1. For Next.js, Node.js version 20.9.0 is required.解决方式是用 nvm 安装 Node 20nvminstall20.18.1 nvm use20.18.1node-v正常结果v20.18.1后来发现 DevEco Studio 终端里还是 Node 18因为 DevEco Studio 自带了 Node并且路径排在前面。检查命令where.exe node看到C:\Program Files\Huawei\DevEco Studio\tools\node\node.exe C:\Program Files\nodejs\node.exe说明 DevEco 自带 Node 抢了优先级。本次最终采用的方式是DevEco Studio 写鸿蒙代码 外部 PowerShell 跑 Next.js 后端这样最稳定不影响后续开发。八、新增 ping 测试接口为了先确认后端能不能正常访问新增了一个最小测试接口。文件server/app/api/ping/route.ts代码import{NextResponse}fromnext/serverexportasyncfunctionGET(){returnNextResponse.json({message:pong,service:harmony-chat-demo-server})}浏览器访问http://localhost:3000/api/ping或者局域网访问http://192.168.20.8:3000/api/ping正常返回{message:pong,service:harmony-chat-demo-server}这个接口主要用于验证Next.js 服务是否启动成功。app/api路由是否正常。后端是否可以返回 JSON。真机是否能访问电脑后端。九、新增聊天接口本次新增的聊天接口是POST /api/chat文件server/app/api/chat/route.ts完整代码import{NextResponse}fromnext/servertypeChatRequestBody{conversationId?:numbercontent?:string}exportasyncfunctionPOST(request:Request){try{constbodyawaitrequest.json()asChatRequestBodyconstcontentString(body.content||).trim()constconversationIdbody.conversationId||Date.now()if(!content){returnNextResponse.json({code:400,message:消息内容不能为空},{status:400})}constnowDate.now()returnNextResponse.json({code:0,message:success,data:{conversationId,messages:[{id:now,role:user,content,createTime:now},{id:now1,role:assistant,content:这是 Next.js 后端返回的模拟回复${content},createTime:now1}]}})}catch{returnNextResponse.json({code:500,message:服务端解析请求失败},{status:500})}}十、聊天接口数据结构请求体{content:你好}响应体{code:0,message:success,data:{conversationId:1779345862737,messages:[{id:1779345862737,role:user,content:你好,createTime:1779345862737},{id:1779345862738,role:assistant,content:这是 Next.js 后端返回的模拟回复你好,createTime:1779345862738}]}}这里统一使用role:user|assistant而不是之前的type:user|ai原因是role更接近真实聊天接口设计后续接入真实 AI 接口时也更容易对齐。十一、测试聊天接口PowerShell 测试Invoke-RestMethod-Urihttp://localhost:3000/api/chat-Method POST -ContentTypeapplication/json-Body{content:你好后端}如果要看完整 JSON$responseInvoke-RestMethod-Urihttp://localhost:3000/api/chat-Method POST -ContentTypeapplication/json-Body{content:你好后端}$response|ConvertTo-Json-Depth 10局域网地址也要测试$responseInvoke-RestMethod-Urihttp://192.168.20.8:3000/api/chat-Method POST -ContentTypeapplication/json-Body{content:你好局域网后端}$response|ConvertTo-Json-Depth 10如果这个也能成功说明后端接口和局域网访问都没问题。十二、鸿蒙前端接口地址新增文件entry/src/main/ets/constants/ApiConstants.ets代码exportconstAPI_BASE_URL:stringhttp://192.168.20.8:3000这里不能写exportconstAPI_BASE_URL:stringhttp://localhost:3000因为真机里的localhost指的是手机自己不是电脑。所以真机访问电脑上的后端服务时要写电脑的局域网 IP。十三、鸿蒙 HTTP 请求封装新增文件entry/src/main/ets/api/ChatApi.ets代码import{http}fromkit.NetworkKitimport{API_BASE_URL}from../constants/ApiConstantsexportinterfaceChatRequest{conversationId?:numbercontent:string}exportinterfaceChatMessageDTO{id:numberrole:user|assistantcontent:stringcreateTime:number}exportinterfaceChatResponseData{conversationId:numbermessages:ChatMessageDTO[]}exportinterfaceChatResponse{code:numbermessage:stringdata:ChatResponseData}interfaceRequestHeader{Content-Type:string}exportfunctionsendChatMessage(params:ChatRequest):PromiseChatResponse{returnnewPromise((resolve,reject){consthttpRequesthttp.createHttp()constrequestHeader:RequestHeader{Content-Type:application/json}constrequestOptions:http.HttpRequestOptions{method:http.RequestMethod.POST,header:requestHeader,extraData:JSON.stringify(params),connectTimeout:10000,readTimeout:10000}constrequestUrl:string${API_BASE_URL}/api/chatconsole.info(chat api request url:${requestUrl})console.info(chat api request body:${JSON.stringify(params)})httpRequest.request(requestUrl,requestOptions,(err,data){httpRequest.destroy()if(err){console.error(chat api request error:${JSON.stringify(err)})reject(err)return}try{constrawResult:stringString(data.result)console.info(chat api response code:${data.responseCode})console.info(chat api response result:${rawResult})constresult:ChatResponseJSON.parse(rawResult)asChatResponseresolve(result)}catch(parseError){console.error(chat api parse error:${JSON.stringify(parseError)})reject(parseError)}})})}这个文件的作用是把请求细节封装起来页面里不用直接写http.createHttp()。主要流程创建 httpRequest ↓ 配置 POST 请求 ↓ 发送 JSON 数据 ↓ 解析后端返回结果 ↓ 销毁 httpRequest十四、配置网络权限鸿蒙 App 访问网络需要在entry/src/main/module.json5中添加网络权限requestPermissions: [ { name: ohos.permission.INTERNET } ]没有这个权限App 可能无法正常发起 HTTP 请求。十五、修改聊天页面Setting.ets的发送逻辑从“前端生成假回复”改成了“调用后端接口”。核心流程读取输入内容 ↓ 先展示用户消息 ↓ 调用 sendChatMessage ↓ 取出后端返回的 assistant 消息 ↓ 追加到 chatList ↓ 滚动到底部消息结构改为interfaceChatItem{id:numberrole:user|assistantcontent:stringcreateTime:number}核心发送方法asyncsendMessage():Promisevoid{constcontent:stringthis.inputValue.trim()if(!content||this.isSending){return}this.isSendingtruethis.inputValueconstnow:numberDate.now()consttempUserMessage:ChatItem{id:now,role:user,content:content,createTime:now}this.chatListthis.chatList.concat([tempUserMessage])this.scrollToBottom()try{constrequestParams:ChatRequest{content:content}if(this.conversationId0){requestParams.conversationIdthis.conversationId}constresawaitsendChatMessage(requestParams)this.conversationIdres.data.conversationIdconstassistantMessages:ChatItem[]res.data.messages.filter((item:ChatMessageDTO)item.roleassistant).map((item:ChatMessageDTO):ChatItem{constmessage:ChatItem{id:item.id,role:item.role,content:item.content,createTime:item.createTime}returnmessage})this.chatListthis.chatList.concat(assistantMessages)this.scrollToBottom()}catch(error){consterrorNow:numberDate.now()consterrorMessage:ChatItem{id:errorNow,role:assistant,content:请求后端失败请检查 Next.js 服务是否启动以及接口地址是否正确。,createTime:errorNow}this.chatListthis.chatList.concat([errorMessage])this.scrollToBottom()}finally{this.isSendingfalse}}这里前端只取后端返回的assistant消息是因为用户消息已经提前展示了如果再展示后端返回的user消息就会重复。十六、ArkTS 对象字面量报错开发时遇到过这个报错Object literal must correspond to some explicitly declared class or interface原因是 ArkTS 对对象字面量比较严格不能随便传匿名对象。不推荐constresawaitsendChatMessage({conversationId:this.conversationId||undefined,content})推荐constrequestParams:ChatRequest{content:content}if(this.conversationId0){requestParams.conversationIdthis.conversationId}constresawaitsendChatMessage(requestParams)这次学到的是ArkTS 比普通 TypeScript 更严格写对象时最好先定义 interface再用明确类型的变量接住。十七、真机请求超时问题真机调试时遇到过请求失败日志是chat api request error: {code:2300028,message:Operation timeout}这说明请求发出去了但是连接目标地址超时。一开始电脑浏览器访问http://localhost:3000/api/ping是正常的但这只能证明电脑自己能访问后端。真机要单独测试http://192.168.20.8:3000/api/ping而且要用手机浏览器测试这个地址。如果手机浏览器打不开就说明不是鸿蒙代码问题而是网络问题。可能原因手机和电脑不在同一个 WiFi。Windows 防火墙拦截了 Node.js。路由器开启了设备隔离。后端没有正常启动。前端接口地址写错了。最终真机可以访问ping接口后App 请求/api/chat也成功了。十八、localhost 和局域网 IP 的区别这次最大的坑是localhost。电脑里的 localhost 电脑自己 手机里的 localhost 手机自己所以真机里不能写http://localhost:3000要写http://电脑局域网IP:3000本次是http://192.168.20.8:3000这是移动端真机联调很常见的问题。十九、本次知识点总结本次练习涉及以下知识点在鸿蒙项目中新增 Next.js 后端目录。使用 Next.js App Router 编写接口。GET /api/ping测试接口。POST /api/chat聊天接口。PowerShell 测试 POST 请求。鸿蒙http.createHttp()请求封装。module.json5配置网络权限。真机访问电脑后端不能使用localhost。使用局域网 IP 进行前后端联调。ArkTS 对对象字面量类型要求更严格。使用role: user | assistant统一前后端消息结构。使用日志定位请求失败原因。Operation timeout的排查思路。二十、面试表达这个功能可以这样说我在鸿蒙聊天 Demo 中把原本前端写死的模拟回复改造成了调用自己写的 Next.js 后端接口。后端使用 Next.js App Router 提供POST /api/chat接口接收用户输入的content并返回一条模拟的assistant消息。鸿蒙侧单独封装了ChatApi.ets使用kit.NetworkKit的http.createHttp()发起 POST 请求并把响应解析成统一的消息结构。真机调试时我还处理了localhost无法访问电脑后端的问题改用电脑局域网 IP并通过手机浏览器访问ping接口排查网络连通性。这个功能让我完整练习了鸿蒙前端到 Next.js 后端的请求链路、接口封装、类型定义和真机网络调试。二十一、本次提交命令gitaddserver/app/api/ping/route.tsgitaddserver/app/api/chat/route.tsgitaddentry/src/main/ets/constants/ApiConstants.etsgitaddentry/src/main/ets/api/ChatApi.etsgitaddentry/src/main/ets/pages/Setting.etsgitaddentry/src/main/module.json5gitadddocs/03-harmony-nextjs-mock-api.mdgitcommit-mfeat: connect harmony chat page to mock apigitpush origin feat/server-init如果合并到 maingitcheckout maingitpullgitmerge feat/server-initgitpush删除分支gitbranch-dfeat/server-initgitpush origin--deletefeat/server-init二十二、本次练习总结这一节的重点不是做一个复杂聊天系统而是跑通一个最小真实链路鸿蒙输入 ↓ HTTP 请求 ↓ Next.js 接口 ↓ JSON 返回 ↓ 鸿蒙渲染通过这次练习我理解了前后端联调时几个关键点前端假数据只是练页面真实项目一定要接接口。Next.js 可以很方便地作为轻量后端。鸿蒙网络请求需要单独封装。真机调试不能使用localhost。请求失败时要看真实错误日志。ArkTS 的类型规则比普通 TypeScript 更严格。前后端字段统一非常重要。目前 Demo 已经完成了阶段性目标后续如果继续扩展可以接入 MySQL、Prisma、会话列表、历史消息和真实 AI 接口。不过当前阶段可以先暂停 Demo回到公司业务项目重点分析真实项目的目录结构、架构设计、路由体系、接口封装和常用 ArkTS 语法。

相关新闻