Files
cunkebao_v3/Server/application/chukebao/controller/KnowledgeBaseController.php
Manus AI 8b83269b28 sync(本地→GitHub): 2026-06-08 23:40 全量单向同步
## GitHub 同步说明

| 项 | 内容 |
|:---|:---|
| 仓库 | https://github.com/fnvtk/cunkebao_v3 |
| 分支 | develop |
| 方向 | 本地 → GitHub(单向 push) |
| 是否拉取远程 | 否(未 fetch / pull / merge) |
| 是否改本地源文件 | 否(仅 git add/commit,未编辑任何业务/文档正文) |
| 远程基准 | origin/develop @ 2753265e |
| 本批工作区变更 | 563 files, +30324 / -7013 lines |

## 一并推送的本地已有 commit(此前未 push)

1. f7a7c56c chore(触客宝): 全量构建部署 kr-kf 生产并通过线上 smoke
2. 8801f5ff feat(触客宝): TKB-120 快捷语整组互拷与 AI 五步整理
3. c396ed35 docs(触客宝): 0603 已完成项归档并交付 TKB-117-B 超管存储
4. e4ed53bc fix(部署): SSH 主机密钥变更兼容并完成五端全量上线

## 本 commit 目录统计

| 目录 | 文件数 | 说明 |
|:---|---:|:---|
| 开发文档/ | 251 | 触客宝 0603~0608 归档、未完成项、五端账号速查 |
| Touchkebao/ | 109 | 微信客服 CustomerList、快捷语、AI 获客等 |
| Server/ | 108 | BFF/API、TaskCustomerList 等 |
| Cunkebao/ | 53 | 存客宝移动端迭代 |
| SuperAdmin/ | 24 | 超管后台 |
| 官网/ | 7 | 官网小改 |
| 其他 | 21 | .cursor、Moncter、docker-compose 等 |

## 重点文件(本地为准)

- Touchkebao/.../CustomerList/index.tsx(微信客户列表)
- 开发文档/1、需求/修改/触客宝_进行中_20260608.md
- 开发文档/1、需求/修改/未完成项与跨仓验收汇总.md
- 开发文档/10、项目管理/线上五端网址与账号密码速查.md
- 开发文档/10、项目管理/已完成/触客宝_0607* 归档
- 开发文档/5、接口/02-存客宝对接/触客宝PC接口对接索引.md

## 刻意未上传

- node_modules/、.DS_Store、.smart-env/
- .开发文档_nested_git_backup/
- **/__pycache__/

## 验收

- 本提交说明: https://github.com/fnvtk/cunkebao_v3/commit/HEAD
- 分支: https://github.com/fnvtk/cunkebao_v3/tree/develop

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-08 23:41:22 +08:00

214 lines
7.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\chukebao\controller;
use app\chukebao\model\AiKnowledgeBaseType;
use library\ResponseHelper;
use think\Db;
/**
* 模块 D · 知识库(需求三 §3.2 · 需求六 §6.7
* 列表 = 存客宝已分配enabledForTouchkebao=true非全量默认库由存客宝项目配置。
* 配置真源在存客宝 H5 + Server触客宝只读已分配。
* 数据表ck_kb_touchkebao_config懒建存放存客宝下发的分配结果
*/
class KnowledgeBaseController extends BaseController
{
private function ensureTable()
{
Db::execute("CREATE TABLE IF NOT EXISTS `ck_kb_touchkebao_config` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`companyId` int(11) NOT NULL DEFAULT 0,
`kbId` int(11) NOT NULL DEFAULT 0,
`kbName` varchar(128) NOT NULL DEFAULT '',
`isDefault` tinyint(1) NOT NULL DEFAULT 0,
`kefuId` int(11) NOT NULL DEFAULT 0,
`currentKbId` int(11) NOT NULL DEFAULT 0,
`enabled` tinyint(1) NOT NULL DEFAULT 1,
`updateTime` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_company` (`companyId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
}
/**
* 仅返回已分配知识库
* GET /v1/kefu/ai/knowledge-bases
*/
public function knowledgeBases()
{
$this->ensureTable();
$companyId = $this->getUserInfo('companyId');
$kefuId = $this->getUserInfo('id');
// 仅返回 enabled 的库(存客宝下发);无分配 → 回退读存客宝项目知识库REQ-TKB-133-C
$rows = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('enabled', 1)
->where('kefuId', 0) // 项目级开放
->select();
$list = [];
$defaultKbId = null;
foreach ($rows as $r) {
$list[] = [
'id' => (int)$r['kbId'],
'name' => $r['kbName'],
'isDefault' => (bool)$r['isDefault'],
];
if ($r['isDefault']) {
$defaultKbId = (int)$r['kbId'];
}
}
if (empty($list)) {
$fallback = $this->fallbackKnowledgeFromWorkspace($companyId);
$list = $fallback['list'];
$defaultKbId = $fallback['defaultKbId'];
}
// 客服个人当前选择(覆盖默认,须在已开放范围内)
$currentKbId = $defaultKbId;
$myCurrent = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('kefuId', $kefuId)
->value('currentKbId');
if ($myCurrent && in_array((int)$myCurrent, array_column($list, 'id'))) {
$currentKbId = (int)$myCurrent;
}
return ResponseHelper::success([
'defaultKbId' => $defaultKbId,
'currentKbId' => $currentKbId,
'list' => $list,
]);
}
/**
* 客服在已分配范围内切换默认库
* POST /v1/kefu/ai/knowledge-base/select
*/
public function selectKnowledgeBase()
{
$this->ensureTable();
$companyId = $this->getUserInfo('companyId');
$kefuId = $this->getUserInfo('id');
$kbId = (int)$this->request->param('kbId', 0);
if (!$kbId) {
return ResponseHelper::error('kbId 缺失', 400);
}
// 校验是否在已开放范围内
$allowed = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('enabled', 1)
->where('kbId', $kbId)
->count();
if (!$allowed) {
return ResponseHelper::error('该知识库未对触客宝开放', 403);
}
$exist = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('kefuId', $kefuId)
->find();
if ($exist) {
Db::name('kb_touchkebao_config')->where('id', $exist['id'])
->update(['currentKbId' => $kbId, 'updateTime' => time()]);
} else {
Db::name('kb_touchkebao_config')->insert([
'companyId' => $companyId,
'kefuId' => $kefuId,
'currentKbId' => $kbId,
'enabled' => 0,
'updateTime' => time(),
]);
}
return ResponseHelper::success(['currentKbId' => $kbId], '已切换');
}
/**
* REQ-TKB-133-C本地分配表为空时聚合存客宝 AiKnowledgeTouchkebao 配置 + 项目知识库
*
* @return array{list:array<int,array{id:int,name:string,isDefault:bool}>,defaultKbId:?int}
*/
private function fallbackKnowledgeFromWorkspace(int $companyId): array
{
$kbRows = AiKnowledgeBaseType::where([
['isDel', '=', 0],
['status', '=', 1],
])->where(function ($q) use ($companyId) {
$q->where('companyId', $companyId)->whereOr('companyId', 0);
})->field('id,name,companyId')
->order('companyId desc,id desc')
->select();
if (empty($kbRows)) {
return ['list' => [], 'defaultKbId' => null];
}
$cfgRows = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('kefuId', 0)
->select();
$cfgMap = [];
$defaultKbId = null;
foreach ($cfgRows ?: [] as $c) {
$cfgMap[(int)$c['kbId']] = $c;
if (!empty($c['isDefault'])) {
$defaultKbId = (int)$c['kbId'];
}
}
$list = [];
$now = time();
foreach ($kbRows as $kb) {
$kbId = (int)$kb['id'];
$cfg = $cfgMap[$kbId] ?? null;
$enabled = $cfg ? (bool)$cfg['enabled'] : ((int)$kb['companyId'] === $companyId);
if (!$enabled) {
continue;
}
$isDefault = $cfg ? (bool)$cfg['isDefault'] : false;
$list[] = [
'id' => $kbId,
'name' => (string)($kb['name'] ?: ''),
'isDefault' => $isDefault,
];
if ($isDefault) {
$defaultKbId = $kbId;
}
}
// 仍无默认:优先项目库首条,其次系统库
if ($defaultKbId === null && !empty($list)) {
$defaultKbId = (int)$list[0]['id'];
$list[0]['isDefault'] = true;
}
// 懒同步写入分配表,便于后续 select 校验
if (!empty($list)) {
foreach ($list as $item) {
$exist = Db::name('kb_touchkebao_config')
->where('companyId', $companyId)
->where('kefuId', 0)
->where('kbId', $item['id'])
->find();
if ($exist) {
continue;
}
Db::name('kb_touchkebao_config')->insert([
'companyId' => $companyId,
'kbId' => $item['id'],
'kbName' => $item['name'],
'isDefault' => $item['isDefault'] ? 1 : 0,
'kefuId' => 0,
'currentKbId' => 0,
'enabled' => 1,
'updateTime' => $now,
]);
}
}
return ['list' => $list, 'defaultKbId' => $defaultKbId];
}
}