
做AI对话类App的人现在是真多但我发现社区里问得最多的往往不是“大模型怎么选”“Prompt怎么写”而是最基础的“项目怎么创建、怎么跑起来”。IDEA里创建Spring Boot项目卡住、pnpm命令找不到、Flutter初始化报错、后端起来了前端连不上——这些问题看着小却能让一个新手卡上一整天。这篇文章就围绕“AI对话App从项目创建到运行”这条主线把后端的Spring Boot、Python FastAPI客户端的Flutter、Web端从环境准备、项目创建、代码骨架到联调运行完整拆一遍最后附上我踩过的常见错误清单。适合正在搭第一个AI对话项目、或者被安排快速出Demo的开发者参考。1. 项目整体设计与技术选型思路1.1 先搞清楚AI对话App的核心链路任何AI对话App不管界面多花哨核心链路只有四段用户在客户端的输入框里输入问题。客户端把请求发送到自己的后端服务。后端调用大模型API或者调用自建的模型推理服务。模型返回内容后端再转发给客户端渲染展示。很多第一次做这类项目的人会问为什么客户端不直接调大模型API省掉一个后端不是更简单吗这里有个非常实际的原因——API密钥。大模型厂商的密钥一旦写进客户端代码里别人反编译一下App就能拿走然后拿着你的密钥去刷接口账单直接爆炸。另外后端还能承担统一鉴权、限流、日志审计、模型切换这些事。比如你今天接的是厂商A的模型明天想换厂商B如果客户端直连就得发版走后端的话改一个配置文件就行。所以不管你是做小程序、Web还是原生App我都建议至少保留一个薄后端。这个后端未必要多复杂能转发请求、管理密钥、处理流式响应就够了。1.2 技术选型我的推荐组合与理由技术选型这件事没有标准答案取决于你的团队背景和交付目标。我先给一个推荐的组合再说理由。层级推荐方案备选方案适用场景后端Spring Boot 3.x Java 17Python FastAPI、DjangoJava团队、需要稳定工程化体系客户端FlutterReact Vite、Android原生需要一套代码同时覆盖Android/iOS/Web模型接入OpenAI兼容协议的API各家大模型厂商SDK、本地Ollama想灵活切换模型供应商我之所以把Spring Boot放在首位是因为它的生态太成熟了。IDEA对它的支持极其完善创建项目、调试、热部署、打包部署都有大量现成方案。你遇到问题去搜一定能搜到答案这对新手极其重要。Flutter作为客户端方案优势在于跨平台。AI对话类App的界面形态很单一——上面是消息列表下面是输入框加上流式打字效果。这种UI在Flutter里用ListView加TextField就能实现而且Flutter的热重载能让你改完代码立刻看到效果非常适合快速迭代。如果团队是Python背景或者你只是想两天内验证一个想法那FastAPI是更好的选择。它代码量极少自带交互式API文档内置异步支持和OpenAI的Python SDK配合得非常好。1.3 项目基础设施规划动手之前先把项目结构想清楚能省掉后面大量麻烦。我个人的习惯是建一个工作目录下面分两个子项目ai-chat-workspace/ ├── server/ # 后端服务 │ ├── ai-chat-server/ # Spring Boot 或 FastAPI 项目 ├── client/ # 客户端 │ ├── ai_chat_app/ # Flutter 项目 │ ├── web_chat/ # 如果做Web端用 Vite React端口统一规划后端8080Flutter Web开发服务器5173Vite默认也是5173。如果两个前端项目同时开注意端口冲突。后端端口可以写成配置项后面联调时用模拟器访问宿主机还有一套特殊地址规则这些细节我在第5章详细说。另外从第一天起就要用Git做版本管理。我见过太多人项目跑起来才想起来初始化Git结果中间改了什么全不记得。项目创建成功、空服务能启动之后马上提交第一个commit这是一个非常好的习惯。2. 开发环境准备工具链安装与验证2.1 JDK与IDEAJava后端的起点如果你决定用Spring Boot第一步是装JDK。这里有一个新手最容易踩的坑Spring Boot 3.x要求Java 17以上但很多人电脑里装的是JDK 8结果项目创建成功却启动不了。我建议直接安装JDK 17或者JDK 21这两个版本都是长期支持版本稳定。装完之后务必配置环境变量。Windows上需要设置JAVA_HOME指向JDK安装目录并把%JAVA_HOME%\bin加到Path里。验证方法是在终端执行java -version能看到版本号输出的就是成功了。IDEA我用的是2024版本社区版对一个AI对话项目来说完全够用。需要提醒的是IDEA里创建Spring Boot项目时很多新手会选错项目类型——选了“Empty Project”而不是“Spring Initializr”导致后面所有依赖都要手动加。我在第3章会详细讲正确的创建方式。2.2 Node.js与pnpm前端工具链做Flutter Web或者Vite React都需要Node.js环境。我推荐安装Node.js 18或20的LTS版本这两个版本稳定性最好兼容性也最广。装完验证node -v npm -v再看pnpm。很多人的报错信息是pnpm : 无法将“pnpm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。这个问题的本质很简单pnpm根本没有被安装或者安装之后没有被加入系统的PATH环境变量。Node.js从16.13版本开始内置了corepack你可以通过corepack来启用pnpmcorepack enable pnpm如果这条命令无效那就直接全局安装npm install -g pnpm装完以后如果终端里还是无法识别关掉当前终端重新开一个或者检查一下npm全局安装目录是否在PATH里。Windows系统下npm的全局目录通常是C:\Users\你的用户名\AppData\Roaming\npm你可以手动确认。2.3 Flutter与Android Studio跨平台客户端Flutter环境的搭建在我做过的项目里是最折腾的因为涉及的东西多Flutter SDK、Android Studio、Android SDK、还有各种平台的命令行工具。先从Flutter官网下载SDK压缩包解压到一个没有空格的路径比如D:\flutter。然后把flutter\bin加入PATH。验证flutter doctor这个命令会检查所有依赖项是否齐全包括Android Studio、Android SDK、连接设备等。它给出的提示信息非常明确照着修就行。Android Studio我建议直接装因为开发Android端必须要Android SDK。安装完成后在Android Studio的SDK Manager里安装对应版本的SDK Platform。如果你遇到flutter doctor提示Android SDK路径找不到手动指定Android Studio安装目录下的SDK路径即可。关于“如何用Android Studio创建Flutter项目”这个问题其实有两种途径一种是Android Studio里File → New → New Flutter Project填写项目名和SDK路径另一种是我更习惯的方式——直接用命令行创建然后Android Studio打开。命令行方式可控性更强后面的章节我会按这个来写。2.4 Python环境FastAPI方案的前置准备如果你选Python后端建议用虚拟环境不要直接把依赖装到全局。Python 3自带venv模块操作很简单mkdir ai-chat-server cd ai-chat-server python -m venv venvWindows下激活虚拟环境激活成功后终端前面会出现(venv)提示符venv\Scripts\activate然后再安装依赖。AI对话项目最基础的三件套pip install fastapi uvicorn openai这里特别提醒一点如果安装某个包时报一堆红色错误千万不要着急绝大多数情况下只需要看最后一行。报错信息末尾通常会告诉你缺什么依赖、或者用了哪个Python版本照着处理就行。3. 后端项目创建实操两种主流方案一次搞定3.1 方案一IDEA 2024创建Spring Boot项目打开IDEA 2024选择New Project左侧选择Spring Initializr。这里的关键几步选择语言为Java类型选择MavenJava版本选17或21。在Dependencies里勾选Spring Web、Lombok、Spring Validation。Group填com.exampleArtifact填ai-chat-server项目名会自动生成。点Finish等IDEA下载依赖。如果你发现IDEA默认的Spring Initializr地址访问很慢可以在设置里切换镜像源。这个具体配置地址网上搜“Spring Initializr镜像”就能找到操作就是改一下URL前缀很快。创建成功之后项目结构长这样ai-chat-server/ ├── src/main/java/com/example/aichatserver/ │ ├── AiChatServerApplication.java │ ├── controller/ │ ├── service/ │ └── config/ ├── src/main/resources/ │ └── application.properties └── pom.xml在这个基础上只需要写一个Controller和Service就能把AI对话的接口骨架搭起来。先建一个ChatControllerRestController RequestMapping(/api/chat) public class ChatController { Resource private ChatService chatService; PostMapping public String chat(RequestBody ChatRequest request) { return chatService.chat(request.getMessage()); } }再写一个ChatService内部用Spring 6.1引入的RestClient调用大模型APIService public class ChatService { Value(${ai.model.api-key}) private String apiKey; Value(${ai.model.base-url}) private String baseUrl; private final RestClient restClient; public ChatService() { this.restClient RestClient.builder() .defaultHeader(Authorization, Bearer apiKey) .build(); } public String chat(String message) { // 构造请求体 MapString, Object requestBody Map.of( model, gpt-3.5-turbo, messages, List.of( Map.of(role, user, content, message) ) ); return restClient.post() .uri(baseUrl /chat/completions) .body(requestBody) .retrieve() .body(String.class); } }这里把API地址和密钥放到了配置文件里后面我会讲怎么管理密钥先记住一个原则不要在代码里硬编码密钥。3.2 方案二Python FastAPI快速搭一个后端FastAPI的方式更轻量。在虚拟环境激活的状态下创建一个main.pyfrom fastapi import FastAPI from pydantic import BaseModel from openai import OpenAI app FastAPI() # 初始化客户端api_key 从环境变量读取 client OpenAI() class ChatRequest(BaseModel): message: str app.post(/api/chat) def chat(request: ChatRequest): response client.chat.completions.create( modelgpt-3.5-turbo, messages[ {role: user, content: request.message} ] ) return {reply: response.choices[0].message.content}然后启动服务uvicorn main:app --reload --port 8080加了--reload参数后代码有任何修改服务会自动重启开发调试非常方便。启动后打开http://localhost:8080/docs你会看到FastAPI自带的交互式文档页面可以直接在页面上测试接口。这个特性在后端联调时特别好用省去了打开Postman的时间。3.3 Django创建App的方式什么时候用它再补充一种方案。如果你做的AI对话App还有用户注册登录、后台管理、内容发布这些需求可以考虑Django。Django创建项目的命令是django-admin startproject config . python manage.py startapp chat然后需要在config/settings.py里把chat这个App注册到INSTALLED_APPS列表中否则Django不会识别你的新App。Django的优势在于全家桶自带Admin后台和ORM数据库操作做个带用户系统的产品级项目速度快。但如果只是做一个AI对话接口它就显得重了FastAPI或Spring Boot更合适。所以选型还是那句话先看需求再选框架。4. 客户端项目创建实操Flutter与Web端4.1 用命令行创建Flutter项目Flutter项目我不推荐用IDE向导创建命令行方式更透明。打开终端flutter create ai_chat_app如果想控制平台范围可以加--platforms参数。比如只要Android、iOS和Webflutter create --platformsandroid,ios,web ai_chat_app项目创建完成后目录结构如下ai_chat_app/ ├── lib/ │ ├── main.dart │ └── pages/ │ └── chat_page.dart ├── pubspec.yaml └── android/ ios/ web/核心文件是pubspec.yaml相当于Flutter项目的依赖清单。AI对话项目需要发起HTTP请求我推荐用dio这个库比官方http库功能更全支持超时设置和拦截器。在pubspec.yaml中添加dependencies: flutter: sdk: flutter dio: ^5.4.0保存后执行flutter pub get依赖就装好了。4.2 配置Android Studio打开项目命令行创建的项目可以直接用Android Studio打开。打开Android Studio选择Open定位到ai_chat_app目录等它完成Gradle同步就行。如果同步过程中有大段报错先检查网络是否稳定、Gradle JDK版本是否匹配这两个是最高频的原因。如果你更喜欢用Android Studio的向导创建Flutter项目也可以在File → New → New Flutter Project里选择Flutter SDK路径、填写项目名和包名即可。两种方式创建出来的项目骨架几乎一样差别只是入口不同。我个人建议学会命令行方式因为后面会有很多CI场景命令行是不可替代的。4.3 一个能跑的对话页面创建纯空项目是不够的至少要写一个能交互的页面才能验证端到端的链路。在lib/pages/chat_page.dart里实现一个最简单的对话界面class ChatPage extends StatefulWidget { const ChatPage({super.key}); override StateChatPage createState() _ChatPageState(); } class _ChatPageState extends StateChatPage { final ListMapString, String _messages []; final TextEditingController _controller TextEditingController(); final Dio _dio Dio(); Futurevoid _sendMessage() async { final text _controller.text; if (text.isEmpty) return; setState(() { _messages.add({role: user, content: text}); }); _controller.clear(); try { final response await _dio.post( http://localhost:8080/api/chat, data: {message: text}, ); final reply response.data[reply]; setState(() { _messages.add({role: assistant, content: reply}); }); } catch (e) { setState(() { _messages.add({role: assistant, content: 请求失败$e}); }); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(AI对话)), body: Column( children: [ Expanded( child: ListView.builder( itemCount: _messages.length, itemBuilder: (context, index) { final msg _messages[index]; return ListTile( title: Text(msg[content]!), trailing: msg[role] user ? const Text(我) : null, ); }, ), ), TextField( controller: _controller, onSubmitted: (_) _sendMessage(), decoration: const InputDecoration(hintText: 输入问题), ), ElevatedButton( onPressed: _sendMessage, child: const Text(发送), ), ], ), ); } }这段代码把用户消息和AI回复展示在同一个ListView里输入框发送按钮齐全。这里先用一个固定的localhost下一章我会专门讲不同平台的地址差异这个坑相当经典。4.4 Web端快速方案Vite React如果只需要在浏览器上演示Vite React是启动最快的方案。创建命令npm create vitelatest web_chat -- --template react cd web_chat npm install npm install axios npm run dev然后写一个简单的聊天组件用axios发POST请求到后端。Vite的热更新很快改完页面刷新就能看到。这个方案的好处是不用装Android Studio和Flutter SDK适合快速原型验证或者给前端团队做后续的正式开发起点。5. 项目运行与联调让AI对话真正跑起来5.1 后端启动流程与验证先启动后端。Spring Boot项目在IDEA里找到启动类AiChatServerApplication直接点击运行。启动日志里如果看到Tomcat started on port 8080说明启动成功。FastAPI的话确认虚拟环境已经激活然后运行uvicorn main:app --reload --port 8080无论用哪个后端启动后先用简单请求验证一下curl -X POST http://localhost:8080/api/chat \ -H Content-Type: application/json \ -d {message: 你好}如果返回了AI的回复后端链路就通了。这一步成功之前不建议去动前端。5.2 一个非常关键的地址配置接下来是重头戏。很多人的前端页面打开了一发送就报错“网络请求失败”原因就是后端地址配错了。不同运行环境下后端地址完全不一样运行环境后端地址说明Flutter Web浏览器http://localhost:8080直接用本机地址Android模拟器http://10.0.2.2:8080模拟器里的特殊地址指向宿主机iOS模拟器http://localhost:8080和宿主机共享网络Android真机http://192.168.x.x:8080需要电脑和手机在同一局域网iPhone真机http://192.168.x.x:8080同上为什么Android模拟器不能用localhost因为模拟器是一个独立的虚拟机它自己就是一个独立的网络环境localhost指向的是虚拟机自己而不是你的电脑。10.0.2.2是Android模拟器预留的、专门用来访问宿主机的别名地址。这个坑我见太多人踩过了一踩就是半天。真机调试的话还需要在电脑防火墙里放行8080端口并且用ipconfigWindows或ifconfigmacOS查一下电脑的局域网IP。另外Spring Boot默认没有开启跨域Flutter Web直接跨域请求会失败。解决办法是在后端加一个CORS配置类允许所有来源访问——本地开发阶段图省事可以这么干生产环境还是要收紧。5.3 流式对话SSE与关键配置我刚才写的示例都是先等大模型回复完一整段然后一次性返回。这在演示Demo里够用但真实产品体验会很差——大模型生成一段话需要好几秒用户看着空白界面干等着早就关掉了。更好的方案是流式输出。大模型边生成边返回用户立刻看到第一个字体验完全不同。实现流式在Spring Boot里可以用SseEmitter在FastAPI里可以用StreamingResponse。流式输出有几个常见的坑我在这里一并说明第一个坑是代理和网关缓存导致SSE失效。如果你的后端前面还有Nginx或者Spring Cloud Gateway默认配置下它们会缓冲响应导致前端收到的不是流式数据而是等服务全部结束才一次性拿到。需要在代理层关掉缓冲比如Nginx里设置proxy_buffering off。第二个坑是字符编码。中文乱码问题在流式接口里尤其常见因为数据是被一块一块推送的有时候看到半截乱码还以为代码写错了。处理方式很简单后端接口统一返回Content-Type: text/event-stream;charsetUTF-8。第三个坑是超时时间。大模型流式接口的耗时比普通接口长很多前端HTTP客户端的默认超时时间往往不够。用dio的时候要显式设置_dio.options.receiveTimeout const Duration(minutes: 2);调connectTimeout没多大用阻塞的时间主要在等待响应体中间数据所以要调的是receiveTimeout。5.4 环境变量与密钥管理密钥管理是很多人容易忽略的一个环节。把API Key直接写在代码里然后提交到Git仓库这等于把密码贴在大门上。我自己的做法是在项目根目录创建.env文件存放API Key和模型配置。在.gitignore里加上.env确保不提交到仓库。代码里通过读取环境变量的方式获取密钥。Spring Boot项目里可以这样读取环境变量ai: model: api-key: ${AI_API_KEY} base-url: ${AI_BASE_URL}这样你的代码仓库里没有任何真实密钥即使代码泄露了密钥还是安全的。如果你用的是IDEA本地启动需要在Run Configuration里配置环境变量或者在application-local.yml里写默认值但后者绝不推荐提交到公共仓库。6. 常见运行错误与排查技巧速查6.1 高频错误速查表这一节是纯经验输出。下面这些错误几乎都是我在实际项目里遇到的按频次排序整理成了一张速查表。错误现象根本原因解决方案pnpm : 无法将“pnpm”项识别为 cmdletpnpm未安装或PATH未配置corepack enable pnpm或npm install -g pnpmIDEA创建Spring Boot项目一直转圈初始izr默认地址访问慢切换为可用的镜像源地址项目启动报错端口被占用8080被其他进程占用了找到占用进程并结束或修改端口配置Android模拟器请求后端失败用了localhost而不是10.0.2.2按本章表格替换后端地址Flutter Web跨域请求报CORS错误后端未配置跨域策略在后端增加CORS配置大模型API报401鉴权失败API Key错误或已过期检查环境变量是否生效重新生成密钥流式输出一直没有数据代理缓冲了响应或超时设置过短关闭代理缓冲调大receiveTimeout中文乱码响应头未指定charsetUTF-8设置响应头为text/event-stream;charsetUTF-86.2 学会看报错信息解决一切问题的底层能力很多时候问题根本没到“bug”的级别是新手不会看报错信息。遇到报错先深呼吸然后按这个顺序读看最后一段的异常类型。Java里是Exception或Error开头的那行Python里是Traceback里的最后一行。看异常类型后面跟的那句话比如Connection refused、Port already in use、File not found这句话直接告诉你问题是什么。再看最上面的堆栈第一行通常指向你代码里出错的那一行以及它对应的文件位置。用异常里的关键短语去搜索比完整复制报错内容更有效。比如有些人会看到“运行 core 失败请查看提示信息”这类弹窗。它只是告诉你去看细节真正的答案在日志文件或者控制台的详细输出里。不要被这种笼统的错误提示吓住因为提示信息真正的价值是引导你去看日志而不是错误本身。6.3 我的实战避坑清单最后分享几条我自己做AI对话项目时总结的经验第一永远先跑通最小闭环再做锦上添花。什么是最小闭环后端一个接口返回固定文本前端把这段文本显示出来。这个链路通了再接入真实大模型再做流式再做UI美化。一次只加一个变量出问题能立刻定位。第二每次改动配置后重启服务。Spring Boot的配置文件加载是启动时做的你改了application.yml不重启项目不会读取新值。有几次我改了端口发现不起作用折腾半天才反应过来没重启。如果配置修改频繁建议用Spring Boot DevTools它会监听文件变化自动重启。第三保持依赖版本统一。团队里多个人协作时一定要锁定依赖版本。后端看pom.xml前端看pubspec.yaml和package.json都锁定具体版本号不要用范围版本。否则你本地好好的同事一拉代码就是一堆兼容性报错。第四用好后端的接口文档工具。FastAPI自带的/docs页面能直接测试接口Spring Boot可以集成SpringDoc。联调的时候先用接口文档确认后端是好的再从前端发请求这样能把问题快速隔离在前端还是后端。这个项目如果继续往下扩展可以考虑在服务端接入向量数据库做知识库问答、增加流式输出的自动停止控制、把对话记录持久化到数据库。但无论功能怎么叠加项目创建和运行这个地基打得越扎实后面才越不容易返工。我自己做了这些年项目最大的体会就是项目最开始的创建和运行这半小时决定了后面整个开发的顺畅程度。工具链顺了后面都是水到渠成的事。