## GitHub 同步说明
| 项 | 内容 |
|:---|:---|
| 仓库 | https://github.com/fnvtk/cunkebao_v3 |
| 分支 | develop |
| 方向 | 本地 → GitHub(单向 push) |
| 是否拉取远程 | 否(未 fetch / pull / merge) |
| 是否改本地文件 | 否(仅 git add/commit,未编辑任何源文件正文) |
| 基准 commit | 0643e78a |
| 变更规模 | 709 files, +26436 / -3407 lines |
## 目录变更统计
| 目录 | 文件数 | 说明 |
|:---|---:|:---|
| 开发文档/ | 404 | 四端进行中/已完成需求、接口文档、官网需求、部署脚本 |
| Server/ | 95 | 后端 API、迁移脚本、触客宝/超管/存客宝服务 |
| Cunkebao/ | 85 | 移动端 H5、设备绑定、客服、转号审批等 |
| Touchkebao/ | 68 | PC 工作台、AI 获客、算力中心、Glass UI |
| SuperAdmin/ | 33 | 超管项目中心、算力计费、内容库 |
| 官网/ | 19 | 新增存客宝官网静态页(index/pricing/solutions 等) |
| .cursor/ | 3 | Cursor 规则与 Skill |
| .obsidian/ | 2 | Obsidian 工作区配置 |
## 重点新增/更新(本地为准)
- 官网/: 完整静态站点(index、pricing、manual、solutions、api)
- 开发文档/1、需求/修改/*_进行中_20260530.md(存客宝/触客宝/AI数智员工)
- 开发文档/1、需求/官网需求.md
- 开发文档/5、接口/开发进度_接口文档.md 等接口文档迭代
- Touchkebao Glass UI 与 P0 收尾、通道获客算力
- SuperAdmin 算力计费规则、项目工作台
## 刻意未上传(本地保留)
- node_modules/
- .DS_Store、.smart-env/
- .开发文档_nested_git_backup/(旧嵌套 git 备份)
- **/__pycache__/
## 验收
GitHub 浏览: https://github.com/fnvtk/cunkebao_v3/tree/develop
Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
2.9 KiB
PHP
96 lines
2.9 KiB
PHP
<?php
|
||
|
||
namespace app\ai\controller;
|
||
|
||
use app\common\service\AiGatewayService;
|
||
use app\common\util\JwtUtil;
|
||
use library\ResponseHelper;
|
||
use think\Controller;
|
||
|
||
/**
|
||
* AI Gateway · 各端统一消费入口(§2.5)
|
||
*
|
||
* - GET /v1/ai/gateway/config 脱敏配置(各端只读)
|
||
* - POST /v1/ai/chat/completions 统一补全(slug / tier? / messages[] / companyId)
|
||
*/
|
||
class AiGatewayController extends Controller
|
||
{
|
||
/** 脱敏配置(不含 Key) */
|
||
public function config()
|
||
{
|
||
return ResponseHelper::success(AiGatewayService::config());
|
||
}
|
||
|
||
/** 统一补全 */
|
||
public function chat()
|
||
{
|
||
$slug = (string) $this->request->param('slug', '');
|
||
$tier = (string) $this->request->param('tier', '');
|
||
$messages = $this->request->param('messages', []);
|
||
if (!is_array($messages)) {
|
||
$messages = json_decode((string) $messages, true) ?: [];
|
||
}
|
||
$companyId = (int) $this->request->param('companyId', 0);
|
||
$maxTokens = (int) $this->request->param('maxTokens', 0);
|
||
|
||
// companyId 缺省时从 JWT 兜底
|
||
if ($companyId <= 0) {
|
||
$companyId = $this->companyIdFromToken();
|
||
}
|
||
$userId = $this->userIdFromToken();
|
||
|
||
if (empty($messages)) {
|
||
return ResponseHelper::error('messages 不能为空', 400);
|
||
}
|
||
|
||
$result = AiGatewayService::complete($slug, $messages, $companyId, $userId, $tier, [
|
||
'maxTokens' => $maxTokens,
|
||
]);
|
||
|
||
if (!$result['ok']) {
|
||
return ResponseHelper::error($result['error'] ?? 'AI 调用失败', (int) ($result['code'] ?? 500), [
|
||
'tier' => $result['tier'] ?? '',
|
||
'balance' => $result['balance'] ?? null,
|
||
]);
|
||
}
|
||
return ResponseHelper::success([
|
||
'content' => $result['content'],
|
||
'providerId' => $result['providerId'],
|
||
'model' => $result['model'],
|
||
'tier' => $result['tier'],
|
||
'strategy' => $result['strategy'] ?? '',
|
||
'deducted' => $result['deducted'],
|
||
'balance' => $result['balance'],
|
||
'usage' => $result['usage'] ?? [],
|
||
]);
|
||
}
|
||
|
||
protected function companyIdFromToken(): int
|
||
{
|
||
$payload = $this->jwtPayload();
|
||
if (!$payload) {
|
||
return 0;
|
||
}
|
||
return (int) (is_array($payload) ? ($payload['companyId'] ?? 0) : ($payload->companyId ?? 0));
|
||
}
|
||
|
||
protected function userIdFromToken(): int
|
||
{
|
||
$payload = $this->jwtPayload();
|
||
if (!$payload) {
|
||
return 0;
|
||
}
|
||
return (int) (is_array($payload) ? ($payload['id'] ?? 0) : ($payload->id ?? 0));
|
||
}
|
||
|
||
protected function jwtPayload()
|
||
{
|
||
try {
|
||
$token = JwtUtil::getRequestToken();
|
||
return $token ? JwtUtil::verifyToken($token) : false;
|
||
} catch (\Throwable $e) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|