简介:这套源码是一套面向PHP开发者、AI应用爱好者及网站二次开发者的轻量级在线聊天系统,核心程序压缩后仅23KB,部署门槛低,适合快速搭建或集成到现有项目。系统内置用户管理、一键添加与修改接口、在线AI多模型聊天、文转图、图转图等功能,并附带5种不同模式的API接口源码,方便需要对外提供AI能力的开发者直接对接。资源包共22个文件,以17个PHP业务文件为主体,配合3个TXT说明或配置文档、1个HTML前端演示页面,以及1个ZIP示例包,整体体积约42KB,目录结构精简,便于按需查阅。目前已有208人学习下载,适合想低成本拥有AI聊天站点或研究轻量级接口方案的开发者。通过源码可掌握接口动态配置与多模型切换的核心思路,附带的对接demo还能帮助快速理解从页面提交到AI模型返回的完整调用过程。
1. 一套PHP源码包如何撑起AI在线聊天网站系统
这类 PHP 网站系统解决的需求很直接:给你一个带前端聊天气泡、后端会话管理和 API 接口调用的完整站点,上传到服务器,填上模型 API Key,就能跑起一个 AI 聊天网站。很多团队要的不是从零写代码,而是把一套现成源码快速变成客服、内网知识助手或私人对话服务。这套代码的关键不在聊天 UI 有多炫,而在 PHP 如何把用户消息、会话历史和模型 API 接口编排起来。反直觉的一点是,越成熟的源码包越会把模型接口藏在后端,前端只拿到一个业务返回,这样既避免密钥泄露,也方便你切换模型供应商。我下面按源码包里最常遇到的实现方式,从请求链路、配置文件、LNMP 部署到 API 接口的流式封装逐层拆开,让拿到同类型 PHP 源码的人能直接上手改配置,也能定位到具体文件去改逻辑。
2. 拆解AI聊天网站系统的核心链路:PHP会话与API接口编排
2.1 聊天请求在PHP里的流转路径:从Session到模型网关
一个成熟的 AI 在线聊天网站,前端不会直接拿模型 API 的地址和密钥去发请求,而是把请求统一交给 PHP 后端。常见流转路径是这样的:用户在输入框提交消息,JavaScript 把消息和会话 ID 通过 POST 提交到/api/chat这样的控制器;控制器先做参数校验和内容安全检查,再读取当前会话的历史消息,拼装成模型需要的messages结构;随后把这次请求交给模型网关服务类,网关负责向实际的大模型 API 发请求,并处理超时、限流和响应解析;最后把模型返回的内容写回数据库或 Redis,同时追加为新的历史消息,前端拿到结果后渲染到页面。
这个链路里最容易忽略的是“历史会话”的读写位置。有些源码把历史存在数据库的conversation_messages表里,有些存在 Swoole Table 或 Redis 里,两者对性能的影响差别很大。你拿到源码后,第一件事不是去看模型调用,而是先找message相关的模型类,确认会话上下文是从哪读的。实际排错中我发现,很多“答非所问”的问题并不是模型参数问题,而是上下文拼接时把角色顺序打乱了,或者把系统提示词丢掉了。
另外一个值得留意的点是 API 接口的幂等设计。聊天页面在弱网下会多次重试提交,如果不做请求去重,用户会看到模型回复两次,数据库里也会留下重复记录。常规做法是在前端生成一个client_msg_id,后端在固定时间窗口内对同一个 ID 只处理一次,这个逻辑通常写在中件间层或控制器构造函数里。
2.2 对接大模型API的最小PHP代码:先跑通一个非流式对话
不管源码包用的是原生 PHP 还是 ThinkPHP、Laravel,底层要跑通一个模型对话,最可靠的方式就是封装一个类,通过 cURL 发起 HTTPS 请求。下面这段代码是 OpenAI 兼容接口的最小实现,绝大多数服务商都支持这种格式:
<?php /** * 最小模型 API 调用示例 * 对应源码包中 app/service/ModelGateway.php 的简化版本 */ function chat(string $message, string $sessionId): string { $apiKey = 'YOUR_API_KEY'; $url = 'https://api.example.com/v1/chat/completions'; // 从 Session 或缓存中取出该会话的历史,没有则初始化 $messages = load_history($sessionId); $messages[] = ['role' => 'user', 'content' => $message]; $payload = [ 'model' => 'gpt-4o-mini', // 后台配置的模型标识,可多套切换 'messages' => $messages, // 角色包含 system/user/assistant 'temperature' => 0.7, // 0~2,越高越发散 'max_tokens' => 1024, // 单次回复的最大 token 数 'stream' => false, // 先关闭流式,跑通整体链路 ]; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey, ], CURLOPT_RETURNTRANSFER => true, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 120, CURLOPT_SSL_VERIFYPEER => true, ]); $response = curl_exec($ch); if (curl_errno($ch)) { throw new RuntimeException('API 请求失败: ' . curl_error($ch)); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { throw new RuntimeException("API 返回 HTTP {$httpCode}: " . $response); } $data = json_decode($response, true); $reply = $data['choices'][0]['message']['content'] ?? ''; // 将用户消息和助手回复一起写回历史,供下一轮使用 save_history($sessionId, $messages, $reply); return $reply; }这段代码把三件事压在一个函数里:构造messages数组、发起 HTTP 请求、保存上下文。CURLOPT_SSL_VERIFYPEER一定要保持为true,有些源码为了本地调试把它关掉,生产环境容易招致中间人攻击。CURLOPT_CONNECTTIMEOUT建议设 10 秒,防止模型服务端 IP 不通时整个 PHP 进程长时间挂住。CURLOPT_TIMEOUT设为 120 秒是因为某些长文本模型处理时间会超过 60 秒,但你如果把stream打开,这个值要重新考虑,因为流式连接是持续占用,超时判断要按空闲时间而不是总时长。
2.3 必调参数与鉴权方式:temperature、max_tokens和stream
不同聊天源码对参数的暴露程度不一样,有的后台只放三个选项,有的给你完整的模型参数面板。下面这张表是几个直接影响回复质量和成本的参数,也是你拿到源码后最需要确认是否对上位的配置项。
| 参数 | 作用范围 | 典型值 | 调整逻辑 |
|---|---|---|---|
temperature | 影响随机性 | 0.3 ~ 0.9 | 客服和代码场景调低,创意写作调高 |
top_p | 影响候选词集合 | 0.8 ~ 1.0 | 和 temperature 不要同时猛调,会让输出极端 |
max_tokens | 限制单次回复长度 | 512 ~ 2048 | 后台往往用“最大回复字数”字段控制的是它 |
presence_penalty | 鼓励引入新话题 | 0.0 ~ 0.6 | 多轮长时间对话里调高,可减少重复词 |
frequency_penalty | 惩罚高频词汇 | 0.0 ~ 0.6 | 和 presence 一起调,能改善“嗯”“好的”这类词 |
stream | 是否流式返回 | false / true | 聊天网站必须 true,非流式只用于接口调试 |
鉴权部分,我见过最稳妥的做法是服务端持有主 Key,然后在 PHP 里生成一个短期 token 给前端。前端请求/api/chat时带上这个 token,后端先解析出user_id和session_id,再结合当前用户是否允许调用该模型做二次判定。有些源码简化成前端无条件传一个api_key参数,这种做法只适合本机 demo,一旦暴露到公网,别人可以直接盗刷你的模型额度。拿到源码后,请优先搜索api_key出现在哪些文件里,如果出现在public/static/js下,这个包需要做权限改造。常见安全改法是所有模型请求统一走后端,前端只传递消息内容和服务端下发的会话标识。
在鉴权之外,还需要关心 API 接口的返回结构。一些国内服务商虽然兼容 OpenAI 格式,但错误返回体里的字段是自定义的,比如error.response.message而不是error.message。你改配置后如果一直报“解析错误”,先拉起接口文档对比choices这块的层级。为了减少这种差异,网关层通常可以做一次统一封装,把不同服务商的响应转换成项目内部标准结构,再抛给上层控制器。
3. 源码包目录分工与关键配置项:找对入口再改代码
3.1 一份典型的PHP源码目录结构长什么样
拆开.zip后,你应当先用文件管理器看顶层结构,而不急着传到服务器。常见的 AI 聊天 PHP 源码,即便框架不同,目录设计也往往遵循“入口、应用、配置、外部库”的划分方式。下面是一份较典型的结构,命名可能略有出入,但角色一致:
. ├── app │ ├── controller │ │ ├── Chat.php # 聊天主控制器 │ │ ├── Auth.php # 登录/鉴权 │ │ └── Admin.php # 后台管理 │ ├── service │ │ ├── ModelGateway.php # 模型网关,负责调 API 接口 │ │ └── SessionService.php # 会话管理 │ ├── middleware │ │ └── ContentCheck.php # 内容安全中间件 │ └── model │ ├── Conversation.php │ └── Message.php ├── config │ ├── app.php # 应用基础配置 │ ├── database.php # 数据库连接 │ ├── model.php # 模型服务商配置 │ └── session.php # 会话驱动配置 ├── public │ ├── index.php # 单入口文件 │ ├── static │ │ ├── js/chat.js │ │ └── css/style.css │ └── uploads # 用户上传文件目录 ├── runtime │ ├── logs │ ├── cache │ └── session ├── vendor # Composer 依赖 └── install ├── install.php # Web 安装引导 └── sql └── install.sql这个结构里有几个点需要你特别留意。public是 Web 根目录,Nginx 必须在配置里把root指向它,如果把 root 指到项目根目录,别人就能直接下载config或.env文件。runtime目录必须允许 PHP-FPM 写,否则日志和 session 写不进去,系统会表现成一直登录失败。install目录在部署完成后要直接删除或改名,否则有被重装覆盖数据库的风险。
3.2 config配置文件里最值得改的字段
不要一上来就改业务代码,配置项才是你真正要动的部分。不同源码的配置文件名可能不同,但字段含义大同小异。我整理了一份高频字段对照,按优先级排列:
| 字段 | 配置含义 | 取值建议 | 改错后果 |
|---|---|---|---|
API_KEY | 模型服务商提供的密钥 | 放在服务端,不要提交到前端 | 盗刷、额度耗尽 |
API_BASE_URL | 模型接口的域名前缀 | 以官方文档为准,注意末尾不能多/v1 | 接口 404 |
DEFAULT_MODEL | 默认模型标识 | 例如gpt-4o-mini或qwen-plus | 页面报模型不存在 |
MODEL_LIST | 前端可选模型下拉列表 | 与控制台实际开通的模型对齐 | 选择后调用失败 |
STREAM_OUTPUT | 是否开启流式输出 | 聊天一般设为true | 用户看到一句话整体卡住 |
MAX_HISTORY | 携带的历史消息条数 | 10 ~ 20 条 | 上下文太长,费用和耗时上升 |
SESSION_DRIVER | 会话存储方式 | file/redis/database | 多机部署时应使用 redis |
CONVERSATION_LIMIT | 每个用户会话数量上限 | 100 ~ 500 | 数据库无限膨胀 |
REQUEST_RATE_LIMIT | 分钟级请求限制 | 页面访问 30,API 请求 60 | 刷接口导致成本不可控 |
API_BASE_URL是最容易出问题的一项。有些源码内部用rtrim($baseUrl, '/') . '/chat/completions'拼接地址,你在后台多写了一个/v1,最终就会变成https://api.example.com/v1/v1/chat/completions。我建议在配置加载完成后,用var_dump(trim(...))直接打出最终构造出来的 URL,别靠猜。
MAX_HISTORY决定一次请求携带多少轮上下文。数值太大,token 成本会成倍增长;数值太小,多轮对话容易失忆。常规做法是取最近 8 到 15 条消息,并且限制单条消息不超过 2000 字。好的源码会把超长历史做自动裁剪,而不是直接截断,因为硬切可能会切掉半句话。
3.3 多模型切换与API接口路由的常见实现
这类源码还有一个卖点:后台可以切换多家模型服务商。实现方式通常不是写死每个请求的 URL,而是维护一个网关配置,在配置里注册多个服务商,然后按当前站点设置的“默认服务商”去发请求。下面是一个常见的配置结构:
<?php // config/model.php return [ 'default' => 'openai', // 当前启用哪个服务商 'gateways' => [ 'openai' => [ 'api_key' => getenv('OPENAI_API_KEY'), 'base_url' => 'https://api.example.com/v1', 'models' => ['gpt-4o-mini', 'gpt-4o'], ], 'local' => [ 'api_key' => getenv('LOCAL_API_KEY'), 'base_url' => 'http://127.0.0.1:8000/v1', 'models' => ['qwen2.5:7b'], ], ], ];网关服务类里会先读取这份配置,再根据外部传来的model参数决定去向。没有配置过的模型,直接返回“模型不存在”,而不是把请求发给默认服务商。这样做的真正好处是隔离异常:某个服务商限流或调整接口后,你只需要在default字段换一个值,全站入口就切换走了。
我一般会在测试环境里同时配两个服务商,用一个“低成本模型”做冒烟测试,确认配置类别和权限都没问题,再切到正式模型。这样可以避免在做界面联调时消耗正式模型额度。如果你在源码里看到temp_model这种命名,那通常是给管理员用的测试槽位,和线上聊天主通道分开的,不要混淆。
4. 在LNMP上把AI网站系统跑起来:环境、权限与排错
4.1 环境准备:PHP要求、扩展和伪静态规则
这类 PHP 源码包一般在README.md或install目录里标注了最低 PHP 版本。目前较新的版本要求 8.1 或 8.2,因为代码里会用到enum、readonly等新语法。如果服务器还是 PHP 7.4,很多包会直接白屏或报语法错误。需要关注的扩展至少包括:curl、openssl、json、mbstring、pdo_mysql、redis(配合 Redis 会话驱动时)。你可以在服务器上执行下面命令快速检查:
php -v php -m | grep -E 'curl|openssl|mbstring|pdo_mysql|redis'缺扩展时用apt install php8.1-curl这类命令补装即可。但要注意,不同 Linux 发行版的 PHP 包名不一样,先确认自己的 PHP 版本再搜对应扩展包。
Nginx 伪静态规则是部署的第二道关。多数源码使用单入口模式,所有请求都进入public/index.php。下面是一份可以直接用的 Nginx 配置:
server { listen 80; server_name chat.example.com; root /data/www/ai-chat/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.1-fpm.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~* \.(js|css|png|jpg|svg|woff2)$ { expires 7d; access_log off; } }try_files这一行的意思是:如果磁盘上存在同名静态文件,直接返回;如果不存在,就把请求交给index.php处理,同时保留原始查询参数。这套规则对原生 PHP 和 ThinkPHP 都通用。root指向public目录,而不是项目根目录,这一步能挡住大量目录遍历风险。如果你发现后台路由一直 404,第一件事就是看 Nginx 里是不是少了try_files或者遭遇了location /与location ~ \.php$的匹配冲突。
4.2 部署步骤:从上传源码到写入后台配置
部署过程比写代码简单,但每一步都可能拦你一下。我习惯按下面顺序操作,每完成一步就做一次验证,不要全部做完再查问题。
# 1. 上传源码到目标目录,且必须在项目根目录安装依赖 cd /data/www/ai-chat composer install --no-dev --optimize-autoloader # 2. 创建 .env 或 config.local.php,从示例文件复制 cp .env.example .env vim .env # 3. 给运行目录写权限,PHP-FPM 才能写日志和 session chmod -R 775 runtime uploads chown -R www-data:www-data /data/www/ai-chat # 4. 如果包带数据库迁移脚本,则执行安装页或命令行迁移 php think migratecomposer install的作用是拉取vendor目录里的第三方依赖。很多从 Windows 上下载再打包进 zip 的源码,会在压缩包里带上vendor,但缺少一些 Linux 扩展对应的库。此时直接删掉vendor,重新执行composer install更可靠。.env文件一定不要提交到版本库,部署完成后可以用chmod 600 .env收紧权限。
安装数据库时,常见的是浏览器访问http://你的域名/install/install.php,按引导填数据库名、用户名和密码。这一步要注意字符集选择,务必选utf8mb4,否则 Emoji 和中文人名会存成乱码。安装完成后,立即删除install目录。有些源码的安装页在文件末尾会提示你“安装完成”,但不会自动删除,留给你的风险只能自己处理。
4.3 高频故障排查:白屏、500、空响应和超时
实际跑起来后,问题集中在下面几类。我把现象、常见原因和排查手段列成一张表,方便你对着处理:
| 故障现象 | 常见原因 | 排查命令 / 方法 |
|---|---|---|
| 访问首页直接白屏 | PHP 语法错误或缺少扩展 | php -l public/index.php,看runtime/logs |
| 打开页面报 500 | 目录权限或伪静态错误 | 检查 Nginx error.log,tail -f /var/log/nginx/error.log |
| 提交消息后一直“正在输入” | 流式响应被输出缓冲拦截 | 在接口里输出前调用ob_end_clean() |
| 模型回复内容为空 | 响应解析层级写错 | 打印$data['choices'][0]的原始 JSON |
| 请求模型接口超时 | 服务器到服务商网络慢或 DNS 问题 | curl -I https://api.example.com看耗时 |
| API 报 401 / 403 | API Key 配置错误或权限不足 | 核对.env中 Key 是否有多余空格 |
| 后台登录成功但前端一直跳回登录页 | Session 目录不可写 | `ps aux |
排查白屏时,先开 PHP 错误显示:在.env里临时把APP_DEBUG设为true,或者直接修改php.ini里的display_errors = On。上线前再把这些关掉,否则日志会把数据库密码打出来。
SSE 空响应是聊天场景里最讨厌的问题。即使接口代码正确,Nginx 也可能对响应做缓冲,导致前端迟迟收不到第一段文本。解决办法是第 5 章要讲的流式输出技巧,同时在 Nginx 配置里为带X-Accel-Buffering: no的接口去掉代理缓冲。排查超时问题,多留意 PHP-FPM 的request_terminate_timeout,默认 30 秒对一些长文本模型根本不够用,要改成 0 或在fastcgi.conf里单独给聊天接口设置更大的值。
5. 进阶:把AI网站系统的API接口封装成可复用的模型网关
5.1 用PHP输出SSE流式响应,聊天体验更接近官方客户端
聊天网站如果每次都要等模型生成完再返回,体验非常差。源码包到后期通常会支持 SSE 流式输出,也就是服务端一边接收模型返回,一边把数据块推送给浏览器。实现核心在于两点:发出text/event-stream头,以及禁掉中间层缓冲。下面是最小可用的流式发送封装:
<?php function streamChat(string $message, string $sessionId): void { header('Content-Type: text/event-stream; charset=utf-8'); header('Cache-Control: no-cache'); header('X-Accel-Buffering: no'); // 组装模型请求,stream 固定为 true $payload = build_payload($message, $sessionId); $ch = curl_init($apiUrl); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey, ], CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { echo $chunk; ob_flush(); flush(); return strlen($chunk); }, ]); curl_exec($ch); curl_close($ch); }CURLOPT_WRITEFUNCTION会在每次收到数据块时被调用,直接把原始 SSE 帧转发给浏览器。ob_flush和flush的作用是绕过 PHP 自身的输出缓冲,让数据尽快到 Nginx。X-Accel-Buffering: no是告诉 Nginx 不要吞掉小块响应,否则前端也要等攒够一定字节才触发onmessage。这条代码里的关键参数是build_payload必须设置'stream' => true,否则模型接口会把整段 JSON 一次性返回,流式前端反而解析失败。
5.2 给API接口加一层校验:非法请求拦截在网关之前
暴露在公网的聊天接口,最怕被其他站点盗用或刷量。只靠前端按钮肯定不够,我建议在网关入口处统一做三层校验:请求频率、会话归属、内容安全。频率限制可以用 PHP 内置的session记录时间戳,也可以用 Redis 的INCR+EXPIRE,后者对集群部署更友好。
# 校验你的接口是否按预期返回 429 限流响应 curl -i -s -X POST https://chat.example.com/api/chat \ -H 'Content-Type: application/json' \ -H 'X-Session-Token: your-token' \ -d '{"message":"hello"}' \ -w '\n耗时: %{time_total}s\n'关注响应头里的HTTP/1.1 200和Content-Type,就能判断 Nginx 和 PHP 链路是否正常。加上-i能看到原始头,确认X-Accel-Buffering是否生效。接口验证时可以把max_tokens调到 10,用最便宜的模型试通,这样既不拖慢调试,也不浪费额度。
5.3 收尾技巧:用联合接口实现模型故障自动降级
如果你不只做演示,还希望聊天接口的可用性更高,我推荐一个轻量技巧:在网关层写一个“故障降级”循环。当默认服务商返回 429、5xx 或连续超时,自动把请求切到第二个服务商,而不是直接告诉用户“模型服务不可用”。实现思路很简单,把各个服务商封装成同一个接口,用循环把可用项依次尝试一遍。
<?php function chatWithFallback(string $message, string $sessionId): string { foreach (['openai', 'local'] as $gateway) { try { return call_gateway($gateway, $message, $sessionId); } catch (GatewayThrottleException $e) { log_warning("{$gateway} 触发限流或状态异常", $e->getMessage()); continue; } } throw new RuntimeException('所有模型网关均不可用'); }这个函数本身不长,放到ModelGateway里即可。你还可以再接一层:把失败日志写入文件或 Redis,后台看到某个服务商连续失败 5 次就发告警。相比在前端做重试,服务端降级能让调用方感知不到切换。注意continue前建议短暂usleep(200000)等待 200 毫秒,给前一个服务商留出释放连接的时间,也避免在异常风暴下把第二个服务商也打爆。
本文还有配套的精品资源,点击获取