Files
cunkebao_v3/Server/application/chukebao/controller/SettingsKefuController.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

234 lines
8.7 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\common\service\KefuAvatarSyncService;
use library\ResponseHelper;
use think\Db;
/**
* 模块 I · 设置页聚合(需求六 §6.4
* 聚合层在 chukebao内部调 cunkebao/共享表;触客宝不拼多个异构接口。
*/
class SettingsKefuController extends BaseController
{
/** GET /v1/kefu/settings/profile */
public function profile()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$accountId = $this->getUserInfo('s2_accountId');
$user = Db::name('users')->where('id', $userId)->find();
$companyName = Db::name('company')->where('id', $companyId)->value('name');
// 该客服管理的微信号
$wechatAccounts = [];
if (!empty($accountId)) {
$ids = Db::table('s2_wechat_friend')
->where(['accountId' => $accountId, 'isDeleted' => 0])
->group('wechatAccountId')
->column('wechatAccountId');
if (!empty($ids)) {
$wechatAccounts = Db::table('s2_wechat_account')
->whereIn('id', $ids)
->field('id,nickname,alias')
->select();
}
}
return ResponseHelper::success([
'avatar' => $user['avatar'] ?? '',
'nickname' => $user['username'] ?? '',
'jobNo' => (string)($user['s2_accountId'] ?? ''),
'companyId' => $companyId,
'companyName' => $companyName ?: ('项目 ' . $companyId),
'wechatAccounts' => $wechatAccounts,
]);
}
/**
* POST /v1/kefu/settings/avatar
* body: { avatar } — 写入 ck_users.avatar 并回写 s2_company_account.avatar
*/
public function saveAvatar()
{
$userId = (int)$this->getUserInfo('id');
$companyId = (int)$this->getUserInfo('companyId');
$accountId = (int)$this->getUserInfo('s2_accountId');
$avatar = trim((string)$this->request->param('avatar', ''));
if ($avatar === '') {
return ResponseHelper::error('avatar 不能为空');
}
Db::name('users')->where('id', $userId)->update([
'avatar' => $avatar,
'updateTime' => time(),
]);
$synced = false;
if ($accountId > 0) {
$synced = KefuAvatarSyncService::syncToS2Account($companyId, $accountId, $avatar);
}
return ResponseHelper::success([
'avatar' => $avatar,
'synced' => $synced,
'accountId' => $accountId,
], '已保存');
}
/** GET /v1/kefu/settings/ai */
public function getAi()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$row = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
$tk = [];
if ($row && !empty($row['config'])) {
$cfg = json_decode($row['config'], true) ?: [];
$tk = $cfg['touchkebao'] ?? [];
}
return ResponseHelper::success([
'defaultEnabled' => $tk['defaultEnabled'] ?? false,
'takeoverDelay' => $tk['takeoverDelay'] ?? 30,
'defaultKbId' => $tk['defaultKbId'] ?? null,
'kbIds' => array_values(array_map('intval', $tk['kbIds'] ?? [])),
'contentLibraryIds' => array_values(array_map('intval', $tk['contentLibraryIds'] ?? [])),
'contentLibraries' => $tk['contentLibraries'] ?? [],
'costTip' => $tk['costTip'] ?? true,
]);
}
/** POST /v1/kefu/settings/ai */
public function saveAi()
{
$userId = $this->getUserInfo('id');
$companyId = $this->getUserInfo('companyId');
$kbIds = $this->request->param('kbIds/a', []);
if (!is_array($kbIds)) {
$kbIds = [];
}
$kbIds = array_values(array_unique(array_filter(array_map('intval', $kbIds))));
$contentLibraryIds = $this->request->param('contentLibraryIds/a', []);
if (!is_array($contentLibraryIds)) {
$contentLibraryIds = [];
}
$contentLibraryIds = array_values(array_unique(array_filter(array_map('intval', $contentLibraryIds))));
$contentLibraries = $this->request->param('contentLibraries/a', []);
if (!is_array($contentLibraries)) {
$contentLibraries = [];
}
$defaultKbId = $this->request->param('defaultKbId', null);
if ($defaultKbId !== null && $defaultKbId !== '') {
$defaultKbId = (int)$defaultKbId;
} else {
$defaultKbId = null;
}
if ($defaultKbId && !empty($kbIds) && !in_array($defaultKbId, $kbIds, true)) {
$defaultKbId = $kbIds[0];
}
if (!$defaultKbId && !empty($kbIds)) {
$defaultKbId = $kbIds[0];
}
$tk = [
'defaultEnabled' => (bool)$this->request->param('defaultEnabled', false),
'takeoverDelay' => (int)$this->request->param('takeoverDelay', 30),
'defaultKbId' => $defaultKbId,
'kbIds' => $kbIds,
'contentLibraryIds' => $contentLibraryIds,
'contentLibraries' => array_values(array_filter($contentLibraries, function ($row) {
return is_array($row) && !empty($row['id']);
})),
'costTip' => (bool)$this->request->param('costTip', true),
];
$row = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
if ($row) {
$cfg = json_decode($row['config'], true) ?: [];
$cfg['touchkebao'] = $tk;
Db::name('ai_settings')->where('id', $row['id'])
->update(['config' => json_encode($cfg, 256), 'updateTime' => time()]);
} else {
Db::name('ai_settings')->insert([
'companyId' => $companyId,
'userId' => $userId,
'config' => json_encode(['touchkebao' => $tk], 256),
'createTime' => time(),
'updateTime' => time(),
]);
}
// 同步客服当前默认知识库(供旧接口只读)
if ($defaultKbId) {
$this->syncKefuCurrentKb($companyId, $userId, (int)$defaultKbId);
}
return ResponseHelper::success(true, '已保存');
}
/** 写入 ck_kb_touchkebao_config 客服级 currentKbId */
private function syncKefuCurrentKb(int $companyId, int $kefuId, int $kbId): void
{
try {
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");
} catch (\Throwable $e) {
// ignore
}
$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(),
]);
}
}
/**
* GET /v1/kefu/settings/map-config
* 地图 JS API Key · 解析优先级:项目(companyId)覆盖 → 超管全局 → Server .env [TENCENT_MAP]
*/
public function mapConfig()
{
$companyId = (int)$this->getUserInfo('companyId');
$cfg = \app\common\service\MapConfigService::resolve($companyId);
$provider = $cfg['provider'] ?? 'amap';
$key = trim((string)($cfg['key'] ?? ''));
$securityCode = trim((string)($cfg['securityCode'] ?? ''));
// 触客宝端:有 Key 即下发,不在客户端展示「未配置/超管设置」类提示probe 仅超管后台用)
return ResponseHelper::success([
'provider' => $provider,
'key' => $key,
'securityCode' => $securityCode,
'valid' => $key !== '',
'reason' => '',
]);
}
}