Merge branch 'release/v1.1.3'
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
.idea
|
||||
.env
|
||||
.vscode
|
||||
.DS_Store
|
||||
runtime
|
||||
public/upload
|
||||
vendor
|
||||
/public/.user.ini
|
||||
/404.html
|
||||
# SpecStory explanation file
|
||||
.specstory/.what-is-this.md
|
||||
/
|
||||
.cursorindexingignore
|
||||
.user.ini
|
||||
nginx.htaccess
|
||||
.cursor/
|
||||
thinkphp/
|
||||
public/static/
|
||||
36
README.en.md
36
README.en.md
@@ -1,36 +0,0 @@
|
||||
# 应用总接口
|
||||
|
||||
#### Description
|
||||
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
|
||||
|
||||
#### Software Architecture
|
||||
Software architecture description
|
||||
|
||||
#### Installation
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Instructions
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### Contribution
|
||||
|
||||
1. Fork the repository
|
||||
2. Create Feat_xxx branch
|
||||
3. Commit your code
|
||||
4. Create Pull Request
|
||||
|
||||
|
||||
#### Gitee Feature
|
||||
|
||||
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
||||
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
||||
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
||||
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
||||
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
39
README.md
39
README.md
@@ -1,39 +0,0 @@
|
||||
# 应用总接口
|
||||
|
||||
#### 介绍
|
||||
{**以下是 Gitee 平台说明,您可以替换此简介**
|
||||
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
|
||||
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
|
||||
|
||||
#### 软件架构
|
||||
软件架构说明
|
||||
|
||||
|
||||
#### 安装教程
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 使用说明
|
||||
|
||||
1. xxxx
|
||||
2. xxxx
|
||||
3. xxxx
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码
|
||||
4. 新建 Pull Request
|
||||
|
||||
|
||||
#### 特技
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
147
README_moments.md
Normal file
147
README_moments.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 微信朋友圈数据处理功能
|
||||
|
||||
本模块提供了微信朋友圈数据的获取、存储和查询功能,支持保留驼峰命名结构的原始数据。
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
项目包含一个数据表:
|
||||
|
||||
**wechat_moments** - 存储朋友圈基本信息
|
||||
- `id`: 自增主键
|
||||
- `wechatAccountId`: 微信账号ID
|
||||
- `wechatFriendId`: 微信好友ID
|
||||
- `snsId`: 朋友圈消息ID
|
||||
- `commentList`: 评论列表JSON
|
||||
- `createTime`: 创建时间戳
|
||||
- `likeList`: 点赞列表JSON
|
||||
- `content`: 朋友圈内容
|
||||
- `lat`: 纬度
|
||||
- `lng`: 经度
|
||||
- `location`: 位置信息
|
||||
- `picSize`: 图片大小
|
||||
- `resUrls`: 资源URL列表
|
||||
- `userName`: 用户名
|
||||
- `type`: 朋友圈类型
|
||||
- `create_time`: 数据创建时间
|
||||
- `update_time`: 数据更新时间
|
||||
|
||||
## API接口
|
||||
|
||||
### 1. 获取朋友圈信息
|
||||
|
||||
```
|
||||
GET/POST /api/websocket/getMoments
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wechatAccountId`: 微信账号ID
|
||||
- `wechatFriendId`: 微信好友ID
|
||||
- `count`: 获取条数,默认5条
|
||||
|
||||
获取指定账号和好友的朋友圈信息,并自动保存到数据库。
|
||||
|
||||
### 2. 保存单条朋友圈数据
|
||||
|
||||
```
|
||||
POST /api/websocket/saveSingleMoment
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `commentList`: 评论列表
|
||||
- `createTime`: 创建时间戳
|
||||
- `likeList`: 点赞列表
|
||||
- `momentEntity`: 朋友圈实体,包含以下字段:
|
||||
- `content`: 朋友圈内容
|
||||
- `lat`: 纬度
|
||||
- `lng`: 经度
|
||||
- `location`: 位置信息
|
||||
- `picSize`: 图片大小
|
||||
- `resUrls`: 资源URL列表
|
||||
- `urls`: 媒体URL列表
|
||||
- `userName`: 用户名
|
||||
- `snsId`: 朋友圈ID
|
||||
- `type`: 朋友圈类型
|
||||
- `wechatAccountId`: 微信账号ID
|
||||
- `wechatFriendId`: 微信好友ID
|
||||
|
||||
保存单条朋友圈数据到数据库,保持原有的驼峰数据结构。系统会将`momentEntity`中的字段提取并单独存储,不包括`objectType`和`createTime`字段。
|
||||
|
||||
### 3. 获取朋友圈数据列表
|
||||
|
||||
```
|
||||
GET/POST /api/websocket/getMomentsList
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `wechatAccountId`: 微信账号ID (可选)
|
||||
- `wechatFriendId`: 微信好友ID (可选)
|
||||
- `page`: 页码,默认1
|
||||
- `pageSize`: 每页条数,默认10
|
||||
- `startTime`: 开始时间戳 (可选)
|
||||
- `endTime`: 结束时间戳 (可选)
|
||||
|
||||
获取已保存的朋友圈数据列表,支持分页和条件筛选。返回的数据会自动构建`momentEntity`字段以保持API兼容性。
|
||||
|
||||
### 4. 获取朋友圈详情
|
||||
|
||||
```
|
||||
GET/POST /api/websocket/getMomentDetail
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `snsId`: 朋友圈ID
|
||||
- `wechatAccountId`: 微信账号ID
|
||||
|
||||
获取单条朋友圈的详细信息,包括评论、点赞和资源URL等。返回的数据会自动构建`momentEntity`字段以保持API兼容性。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 保存单条朋友圈数据
|
||||
|
||||
```php
|
||||
$data = [
|
||||
'commentList' => [],
|
||||
'createTime' => 1742777232,
|
||||
'likeList' => [],
|
||||
'momentEntity' => [
|
||||
'content' => "第一位个人与Stussy联名的中国名人,不是陈冠希,不是葛民辉,而是周杰伦!",
|
||||
'lat' => 0,
|
||||
'lng' => 0,
|
||||
'location' => "",
|
||||
'picSize' => 0,
|
||||
'resUrls' => [],
|
||||
'snsId' => "-3827269039168736643",
|
||||
'urls' => ["http://wxapp.tc.qq.com/251/20304/stodownload?encfilekey=..."],
|
||||
'userName' => "wxid_afixeeh53lt012"
|
||||
],
|
||||
'snsId' => "-3827269039168736643",
|
||||
'type' => 28,
|
||||
'wechatAccountId' => 123456, // 替换为实际的微信账号ID
|
||||
'wechatFriendId' => "wxid_example" // 替换为实际的微信好友ID
|
||||
];
|
||||
|
||||
// 发送请求
|
||||
$result = curl_post('/api/websocket/saveSingleMoment', $data);
|
||||
```
|
||||
|
||||
### 查询朋友圈列表
|
||||
|
||||
```php
|
||||
// 获取特定账号的朋友圈
|
||||
$params = [
|
||||
'wechatAccountId' => 123456,
|
||||
'page' => 1,
|
||||
'pageSize' => 20
|
||||
];
|
||||
|
||||
// 发送请求
|
||||
$result = curl_get('/api/websocket/getMomentsList', $params);
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有JSON格式的数据在保存时都会进行编码,查询时会自动解码并还原为原始数据结构。
|
||||
2. 数据库中的字段名保持驼峰命名格式,与微信API返回的数据结构保持一致。
|
||||
3. 尽管数据库中将`momentEntity`的字段拆分为独立字段存储,但API接口返回时会重新构建`momentEntity`结构,以保持与原始API的兼容性。
|
||||
4. `objectType`和`createTime`字段已从`momentEntity`中移除,不再单独存储。
|
||||
5. 图片或视频资源URLs直接存储在朋友圈主表中,不再单独存储到资源表。
|
||||
254
README_scheduler.md
Normal file
254
README_scheduler.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 统一任务调度器使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
统一任务调度器(TaskSchedulerCommand)是一个集中管理所有定时任务的调度系统,支持:
|
||||
- ✅ 单条 crontab 配置管理所有任务
|
||||
- ✅ 多进程并发执行任务
|
||||
- ✅ 自动根据 cron 表达式判断任务执行时间
|
||||
- ✅ 任务锁机制,防止重复执行
|
||||
- ✅ 完善的日志记录
|
||||
|
||||
## 安装配置
|
||||
|
||||
### 1. 配置文件
|
||||
|
||||
任务配置位于 `config/task_scheduler.php`,格式如下:
|
||||
|
||||
```php
|
||||
'任务标识' => [
|
||||
'command' => '命令名称', // 必填:执行的命令
|
||||
'schedule' => 'cron表达式', // 必填:cron表达式
|
||||
'options' => ['--option=value'], // 可选:命令参数
|
||||
'enabled' => true, // 可选:是否启用
|
||||
'max_concurrent' => 1, // 可选:最大并发数
|
||||
'timeout' => 3600, // 可选:超时时间(秒)
|
||||
'log_file' => 'custom.log', // 可选:自定义日志文件
|
||||
]
|
||||
```
|
||||
|
||||
### 2. Cron 表达式格式
|
||||
|
||||
标准 cron 格式:`分钟 小时 日 月 星期`
|
||||
|
||||
示例:
|
||||
- `*/1 * * * *` - 每分钟执行
|
||||
- `*/5 * * * *` - 每5分钟执行
|
||||
- `*/30 * * * *` - 每30分钟执行
|
||||
- `0 2 * * *` - 每天凌晨2点执行
|
||||
- `0 3 */3 * *` - 每3天的3点执行
|
||||
|
||||
### 3. Crontab 配置
|
||||
|
||||
**只需要在 crontab 中添加一条任务:**
|
||||
|
||||
```bash
|
||||
# 每分钟执行一次调度器(调度器内部会根据 cron 表达式判断哪些任务需要执行)
|
||||
* * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think scheduler:run >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/scheduler.log 2>&1
|
||||
```
|
||||
|
||||
### 4. 系统要求
|
||||
|
||||
- PHP >= 5.6.0
|
||||
- 推荐启用 `pcntl` 扩展以支持多进程并发(非必需,未启用时使用单进程顺序执行)
|
||||
|
||||
检查 pcntl 扩展:
|
||||
```bash
|
||||
php -m | grep pcntl
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 手动执行调度器
|
||||
|
||||
```bash
|
||||
# 执行调度器(会自动判断当前时间需要执行的任务)
|
||||
php think scheduler:run
|
||||
```
|
||||
|
||||
### 查看任务配置
|
||||
|
||||
```bash
|
||||
# 查看所有已注册的命令
|
||||
php think list
|
||||
```
|
||||
|
||||
### 启用/禁用任务
|
||||
|
||||
编辑 `config/task_scheduler.php`,设置 `'enabled' => false` 即可禁用任务。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 多进程并发执行
|
||||
|
||||
- 默认最大并发数:10 个进程
|
||||
- 自动管理进程池
|
||||
- 自动清理僵尸进程
|
||||
|
||||
### 2. 任务锁机制
|
||||
|
||||
- 每个任务在执行时会设置锁(5分钟内不重复执行)
|
||||
- 防止任务重复执行
|
||||
- 锁存储在缓存中,自动过期
|
||||
|
||||
### 3. 日志记录
|
||||
|
||||
- 调度器日志:`runtime/log/scheduler.log`
|
||||
- 每个任务的日志:`runtime/log/{log_file}`
|
||||
- 任务执行开始和结束都有标记
|
||||
|
||||
### 4. 超时控制
|
||||
|
||||
- 默认超时时间:3600 秒(1小时)
|
||||
- 可在配置中为每个任务单独设置超时时间
|
||||
- 超时后自动终止任务
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 高频任务(每分钟)
|
||||
|
||||
```php
|
||||
'wechat_friends_active' => [
|
||||
'command' => 'wechatFriends:list',
|
||||
'schedule' => '*/1 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 中频任务(每5分钟)
|
||||
|
||||
```php
|
||||
'device_active' => [
|
||||
'command' => 'device:list',
|
||||
'schedule' => '*/5 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 每日任务
|
||||
|
||||
```php
|
||||
'wechat_calculate_score' => [
|
||||
'command' => 'wechat:calculate-score',
|
||||
'schedule' => '0 2 * * *', // 每天凌晨2点
|
||||
'options' => [],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 定期任务(每3天)
|
||||
|
||||
```php
|
||||
'sync_all_friends' => [
|
||||
'command' => 'sync:allFriends',
|
||||
'schedule' => '0 3 */3 * *', // 每3天的3点
|
||||
'options' => [],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
## 从旧配置迁移
|
||||
|
||||
### 旧配置(多条 crontab)
|
||||
|
||||
```bash
|
||||
*/5 * * * * cd /path && php think device:list --isDel=0 >> log1.log 2>&1
|
||||
*/1 * * * * cd /path && php think wechatFriends:list >> log2.log 2>&1
|
||||
```
|
||||
|
||||
### 新配置(单条 crontab + 配置文件)
|
||||
|
||||
**Crontab:**
|
||||
```bash
|
||||
* * * * * cd /path && php think scheduler:run >> scheduler.log 2>&1
|
||||
```
|
||||
|
||||
**config/task_scheduler.php:**
|
||||
```php
|
||||
'device_active' => [
|
||||
'command' => 'device:list',
|
||||
'schedule' => '*/5 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'log_file' => 'log1.log',
|
||||
],
|
||||
'wechat_friends' => [
|
||||
'command' => 'wechatFriends:list',
|
||||
'schedule' => '*/1 * * * *',
|
||||
'log_file' => 'log2.log',
|
||||
],
|
||||
```
|
||||
|
||||
## 监控和调试
|
||||
|
||||
### 查看调度器日志
|
||||
|
||||
```bash
|
||||
tail -f runtime/log/scheduler.log
|
||||
```
|
||||
|
||||
### 查看任务执行日志
|
||||
|
||||
```bash
|
||||
tail -f runtime/log/crontab_device_active.log
|
||||
```
|
||||
|
||||
### 检查任务是否在执行
|
||||
|
||||
```bash
|
||||
# 查看进程
|
||||
ps aux | grep "php think"
|
||||
```
|
||||
|
||||
### 手动测试任务
|
||||
|
||||
```bash
|
||||
# 直接执行某个任务
|
||||
php think device:list --isDel=0
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间同步**:确保服务器时间准确,调度器依赖系统时间判断任务执行时间
|
||||
2. **资源限制**:根据服务器性能调整 `maxConcurrent` 参数
|
||||
3. **日志清理**:定期清理日志文件,避免占用过多磁盘空间
|
||||
4. **任务冲突**:如果任务执行时间较长,建议调整执行频率或增加并发数
|
||||
5. **缓存依赖**:任务锁使用缓存,确保缓存服务正常运行
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 任务未执行
|
||||
|
||||
1. 检查任务是否启用:`'enabled' => true`
|
||||
2. 检查 cron 表达式是否正确
|
||||
3. 检查调度器是否正常运行:查看 `scheduler.log`
|
||||
4. 检查任务锁:任务可能在5分钟内重复执行被跳过
|
||||
|
||||
### 任务执行失败
|
||||
|
||||
1. 查看任务日志:`runtime/log/{log_file}`
|
||||
2. 检查命令是否正确:手动执行命令测试
|
||||
3. 检查权限:确保有执行权限和日志写入权限
|
||||
|
||||
### 多进程不工作
|
||||
|
||||
1. 检查 pcntl 扩展:`php -m | grep pcntl`
|
||||
2. 检查系统限制:`ulimit -u` 查看最大进程数
|
||||
3. 查看调度器日志中的错误信息
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **合理设置并发数**:根据服务器 CPU 核心数和内存大小调整
|
||||
2. **错开高频任务**:避免所有任务在同一分钟执行
|
||||
3. **优化任务执行时间**:减少任务执行时长
|
||||
4. **使用队列**:对于耗时任务,建议使用队列异步处理
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2024-01-XX)
|
||||
- 初始版本
|
||||
- 支持多进程并发执行
|
||||
- 支持 cron 表达式调度
|
||||
- 支持任务锁机制
|
||||
|
||||
105
README_wechat_chatroom_sync.md
Normal file
105
README_wechat_chatroom_sync.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 微信群聊同步功能
|
||||
|
||||
本功能用于自动同步微信群聊数据,支持分页获取群聊列表以及群成员信息,并将数据保存到数据库中。
|
||||
|
||||
## 功能特点
|
||||
|
||||
1. 支持分页获取微信群聊列表
|
||||
2. 自动获取每个群聊的成员信息
|
||||
3. 支持通过关键词筛选群聊
|
||||
4. 支持按微信账号筛选群聊
|
||||
5. 可选择是否包含已删除的群聊
|
||||
6. 使用队列处理,支持大量数据的同步
|
||||
7. 支持失败重试机制
|
||||
8. 提供命令行和HTTP接口两种触发方式
|
||||
|
||||
## 数据表结构
|
||||
|
||||
本功能使用以下数据表:
|
||||
|
||||
1. **wechat_chatroom** - 存储微信群聊信息
|
||||
2. **wechat_chatroom_member** - 存储微信群聊成员信息
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. HTTP接口触发
|
||||
|
||||
```
|
||||
GET/POST /api/wechat_chatroom/syncChatrooms
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `pageIndex`: 起始页码,默认0
|
||||
- `pageSize`: 每页大小,默认100
|
||||
- `keyword`: 群名关键词,可选
|
||||
- `wechatAccountKeyword`: 微信账号关键词,可选
|
||||
- `isDeleted`: 是否包含已删除群聊,可选
|
||||
|
||||
**示例:**
|
||||
```
|
||||
/api/wechat_chatroom/syncChatrooms?pageSize=50
|
||||
```
|
||||
|
||||
### 2. 命令行触发
|
||||
|
||||
```bash
|
||||
php think sync:wechat:chatrooms [选项]
|
||||
```
|
||||
|
||||
**选项:**
|
||||
- `-p, --pageIndex`: 起始页码,默认0
|
||||
- `-s, --pageSize`: 每页大小,默认100
|
||||
- `-k, --keyword`: 群名关键词,可选
|
||||
- `-a, --account`: 微信账号关键词,可选
|
||||
- `-d, --deleted`: 是否包含已删除群聊,可选
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
# 基本用法
|
||||
php think sync:wechat:chatrooms
|
||||
|
||||
# 指定页大小和关键词
|
||||
php think sync:wechat:chatrooms -s 50 -k "测试群"
|
||||
|
||||
# 指定账号关键词
|
||||
php think sync:wechat:chatrooms --account "张三"
|
||||
```
|
||||
|
||||
### 3. 定时任务配置
|
||||
|
||||
可以将命令添加到系统的定时任务(crontab)中,实现定期自动同步:
|
||||
|
||||
```
|
||||
# 每天凌晨3点执行微信群聊同步
|
||||
0 3 * * * cd /path/to/your/project && php think sync:wechat:chatrooms
|
||||
```
|
||||
|
||||
## 队列消费者配置
|
||||
|
||||
为了处理同步任务,需要启动队列消费者:
|
||||
|
||||
```bash
|
||||
# 启动微信群聊队列消费者
|
||||
php think queue:work --queue wechat_chatrooms
|
||||
```
|
||||
|
||||
建议在生产环境中使用supervisor等工具来管理队列消费者进程。
|
||||
|
||||
## 同步过程
|
||||
|
||||
1. 触发同步任务,将初始页任务加入队列
|
||||
2. 队列消费者处理任务,获取当前页的群聊列表
|
||||
3. 如果当前页有数据且数量等于页大小,则将下一页任务加入队列
|
||||
4. 对每个获取到的群聊,添加获取群成员的任务
|
||||
5. 所有数据会自动保存到数据库中
|
||||
|
||||
## 调试与日志
|
||||
|
||||
同步过程的日志会记录在应用的日志目录中,可以通过查看日志了解同步状态和错误信息。
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 页大小建议设置为合理值(50-100),过大会导致请求超时
|
||||
2. 当数据量较大时,建议增加队列消费者的数量
|
||||
3. 确保系统授权信息正确,否则无法获取数据
|
||||
4. 数据同步是增量的,会自动更新已存在的记录
|
||||
249
RFM客户价值评分体系技术实施文档.md
Normal file
249
RFM客户价值评分体系技术实施文档.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# RFM 客户价值评分体系技术实施文档
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
本文档旨在明确 RFM(Recency-Frequency-Monetary)客户价值评分体系的技术实现标准,包括维度定义、评分规则、数据处理流程、参数配置及异常处理方案,为系统开发、数据分析及业务应用提供统一依据。
|
||||
|
||||
## 2. 核心术语定义
|
||||
|
||||
|
||||
|
||||
| 术语 | 英文缩写 | 定义 | 数据来源 | 统计周期说明 |
|
||||
| ------ | ------------ | ------------------------------- | --------- | ---------------------------------- |
|
||||
| 最近消费时间 | Recency(R) | 客户最后一次有效消费行为距统计截止日的时间间隔(单位:天) | 订单系统、交易日志 | 支持自定义配置(默认 3-12 个月,按业务场景调整) |
|
||||
| 消费频率 | Frequency(F) | 统计周期内客户发生有效消费行为的总次数 | 订单系统、交易日志 | 与 R 维度统计周期一致,剔除重复下单、取消订单等无效记录 |
|
||||
| 消费金额 | Monetary(M) | 统计周期内客户有效消费行为的总金额(单位:元,支持多币种换算) | 订单系统、支付日志 | 仅统计已支付完成的订单金额,剔除退款、优惠抵扣部分 |
|
||||
| RFM 总分 | RFM Score | 基于 R、F、M 三个维度的分项得分,按预设权重计算的综合得分 | 系统计算生成 | 得分范围 1-15 分(5 分制单项)或 1-100 分(标准化后) |
|
||||
|
||||
## 3. 评分规则技术规范
|
||||
|
||||
### 3.1 分项评分规则(默认 5 分制)
|
||||
|
||||
#### 3.1.1 Recency(R)评分规则
|
||||
|
||||
|
||||
|
||||
* 核心逻辑:时间间隔越短,得分越高(反向映射)
|
||||
|
||||
* 分段标准:采用**五分位法**(按数据分布自动划分区间,避免均分失真)
|
||||
|
||||
|
||||
|
||||
| 得分 | 时间间隔区间(天) | 划分逻辑 |
|
||||
| --- | ---------- | ----------------- |
|
||||
| 5 分 | \[0, T1] | 统计周期内最近消费的 20% 客户 |
|
||||
| 4 分 | (T1, T2] | 统计周期内次近消费的 20% 客户 |
|
||||
| 3 分 | (T2, T3] | 统计周期内中间消费的 20% 客户 |
|
||||
| 2 分 | (T3, T4] | 统计周期内较久消费的 20% 客户 |
|
||||
| 1 分 | (T4, Tmax] | 统计周期内最久消费的 20% 客户 |
|
||||
|
||||
|
||||
|
||||
* 区间计算方式:T1=PERCENTILE\_CONT (0.2)、T2=PERCENTILE\_CONT (0.4)、T3=PERCENTILE\_CONT (0.6)、T4=PERCENTILE\_CONT (0.8),其中 Tmax 为统计周期总天数
|
||||
|
||||
#### 3.1.2 Frequency(F)评分规则
|
||||
|
||||
|
||||
|
||||
* 核心逻辑:消费次数越多,得分越高(正向映射)
|
||||
|
||||
* 分段标准:采用**五分位法**(支持最小消费次数阈值配置)
|
||||
|
||||
|
||||
|
||||
| 得分 | 消费次数区间 | 划分逻辑 |
|
||||
| --- | ----------- | ------------------- |
|
||||
| 5 分 | \[F4, +∞) | 统计周期内消费次数最多的 20% 客户 |
|
||||
| 4 分 | \[F3, F4) | 统计周期内消费次数次多的 20% 客户 |
|
||||
| 3 分 | \[F2, F3) | 统计周期内消费次数中间的 20% 客户 |
|
||||
| 2 分 | \[F1, F2) | 统计周期内消费次数较少的 20% 客户 |
|
||||
| 1 分 | \[Fmin, F1) | 统计周期内消费次数最少的 20% 客户 |
|
||||
|
||||
|
||||
|
||||
* 区间计算方式:F1=PERCENTILE\_CONT (0.2)、F2=PERCENTILE\_CONT (0.4)、F3=PERCENTILE\_CONT (0.6)、F4=PERCENTILE\_CONT (0.8),其中 Fmin 为 1(仅统计有效消费次数≥1 的客户)
|
||||
|
||||
#### 3.1.3 Monetary(M)评分规则
|
||||
|
||||
|
||||
|
||||
* 核心逻辑:消费金额越高,得分越高(正向映射)
|
||||
|
||||
* 分段标准:采用**五分位法**(支持剔除大额异常值后划分)
|
||||
|
||||
|
||||
|
||||
| 得分 | 消费金额区间(元) | 划分逻辑 |
|
||||
| --- | ----------- | ------------------- |
|
||||
| 5 分 | \[M4, +∞) | 统计周期内消费金额最高的 20% 客户 |
|
||||
| 4 分 | \[M3, M4) | 统计周期内消费金额次高的 20% 客户 |
|
||||
| 3 分 | \[M2, M3) | 统计周期内消费金额中间的 20% 客户 |
|
||||
| 2 分 | \[M1, M2) | 统计周期内消费金额较低的 20% 客户 |
|
||||
| 1 分 | \[Mmin, M1) | 统计周期内消费金额最低的 20% 客户 |
|
||||
|
||||
|
||||
|
||||
* 区间计算方式:M1=PERCENTILE\_CONT (0.2)、M2=PERCENTILE\_CONT (0.4)、M3=PERCENTILE\_CONT (0.6)、M4=PERCENTILE\_CONT (0.8),其中 Mmin 为统计周期内最小有效订单金额
|
||||
|
||||
### 3.2 总分计算规则
|
||||
|
||||
#### 3.2.1 加权求和公式
|
||||
|
||||
$RFM_{Score} = R_{Score} \times W_R + F_{Score} \times W_F + M_{Score} \times W_M$
|
||||
|
||||
|
||||
|
||||
* 权重配置:支持自定义(默认配置:$W_R=0.4$,$W_F=0.3$,$W_M=0.3$)
|
||||
|
||||
* 权重约束:$W_R + W_F + W_M = 1.0$,且单个权重取值范围为 \[0.1, 0.8]
|
||||
|
||||
#### 3.2.2 得分标准化(可选)
|
||||
|
||||
|
||||
|
||||
* 若需将总分映射为 1-100 分,采用线性标准化公式:
|
||||
|
||||
$RFM_{StandardScore} = \frac{RFM_{Score} - RFM_{Min}}{RFM_{Max} - RFM_{Min}} \times 99 + 1$
|
||||
|
||||
* 其中:$RFM_{Min}=W_R \times 1 + W_F \times 1 + W_M \times 1$,$RFM_{Max}=W_R \times 5 + W_F \times 5 + W_M \times 5$
|
||||
|
||||
## 4. 数据处理流程
|
||||
|
||||
### 4.1 数据输入要求
|
||||
|
||||
|
||||
|
||||
| 数据项 | 数据类型 | 格式要求 | 校验规则 |
|
||||
| ------ | ------------- | ------------------- | ------------ |
|
||||
| 客户唯一标识 | String/Int | 全局唯一(如用户 ID、会员 ID) | 非空、去重 |
|
||||
| 订单唯一标识 | String/Int | 全局唯一(如订单号) | 非空、去重 |
|
||||
| 消费时间 | DateTime | yyyy-MM-dd HH:mm:ss | 需在统计周期内 |
|
||||
| 消费金额 | Decimal(18,2) | 大于 0 | 剔除负数、0 值 |
|
||||
| 订单状态 | String | 枚举值(已支付、已取消、已退款等) | 仅保留 “已支付” 状态 |
|
||||
|
||||
### 4.2 数据预处理步骤
|
||||
|
||||
|
||||
|
||||
1. **数据过滤**:
|
||||
|
||||
* 剔除统计周期外的订单数据
|
||||
|
||||
* 剔除订单状态为 “已取消”“已退款”“无效” 的记录
|
||||
|
||||
* 剔除员工内部订单、测试订单(按订单标签或用户标签过滤)
|
||||
|
||||
* 剔除单笔金额超过$M_{99分位值} \times 3$的异常大额订单(可配置开关)
|
||||
|
||||
1. **数据聚合**:
|
||||
|
||||
* 按客户唯一标识分组,计算 R、F、M 原始指标:
|
||||
|
||||
|
||||
* R:MAX (消费时间) 到统计截止日的时间间隔(天)
|
||||
|
||||
* F:COUNT (DISTINCT 订单唯一标识)
|
||||
|
||||
* M:SUM (消费金额)
|
||||
|
||||
1. **缺失值处理**:
|
||||
|
||||
* 统计周期内无消费记录的客户:R = 统计周期总天数,F=0,M=0,分项得分均为 1 分
|
||||
|
||||
* 单个指标缺失(如仅缺失 M):按 1 分计分项得分
|
||||
|
||||
### 4.3 评分计算流程
|
||||
|
||||
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[数据输入] --> B[数据过滤]
|
||||
B --> C[数据聚合计算R/F/M原始值]
|
||||
C --> D[缺失值处理]
|
||||
D --> E[按五分位法划分各维度区间]
|
||||
E --> F[计算R/F/M分项得分]
|
||||
F --> G[按权重计算RFM总分]
|
||||
G --> H[可选:标准化为1-100分]
|
||||
H --> I[输出客户RFM评分结果]
|
||||
```
|
||||
|
||||
## 5. 参数配置说明
|
||||
|
||||
|
||||
|
||||
| 参数名称 | 配置项 | 取值范围 | 默认值 | 配置方式 |
|
||||
| ------- | ---------------------- | ------------- | ------ | --------------- |
|
||||
| 统计周期 | cycle\_days | 30-365 | 180 | 系统配置页手动输入 |
|
||||
| R 维度权重 | weight\_R | 0.1-0.8 | 0.4 | 系统配置页滑动条调整 |
|
||||
| F 维度权重 | weight\_F | 0.1-0.8 | 0.3 | 系统配置页滑动条调整 |
|
||||
| M 维度权重 | weight\_M | 0.1-0.8 | 0.3 | 系统配置页滑动条调整 |
|
||||
| 异常金额阈值 | abnormal\_money\_ratio | 1.5-5.0 | 3.0 | 系统配置页手动输入(倍数关系) |
|
||||
| 评分分制 | score\_scale | 5 分制 / 100 分制 | 5 分制 | 系统配置页单选 |
|
||||
| 缺失值处理策略 | missing\_strategy | 按 1 分计 / 剔除客户 | 按 1 分计 | 系统配置页单选 |
|
||||
|
||||
## 6. 异常处理方案
|
||||
|
||||
### 6.1 数据异常
|
||||
|
||||
|
||||
|
||||
| 异常类型 | 表现形式 | 处理逻辑 | 影响范围 |
|
||||
| ------ | ------------------------------ | ---------------------- | --------------- |
|
||||
| 重复订单 | 同一客户同一时间相同订单号 | 去重保留 1 条有效记录 | 不影响 F、M 计算 |
|
||||
| 大额异常订单 | 单笔金额 > $M_{99分位值} \times 异常阈值$ | 自动标记,可选择剔除或保留 | 仅影响 M 维度区间划分 |
|
||||
| 消费时间异常 | 消费时间晚于统计截止日 | 视为无效数据,剔除 | 不影响最终结果 |
|
||||
| 客户标识重复 | 同一客户多个唯一标识 | 按客户合并规则(如手机号、身份证号关联)聚合 | 需提前完成客户统一 ID 映射 |
|
||||
|
||||
### 6.2 计算异常
|
||||
|
||||
|
||||
|
||||
| 异常类型 | 触发条件 | 处理逻辑 | 输出结果 |
|
||||
| -------- | --------------------- | --------------------------------------- | --------------- |
|
||||
| 维度区间为空 | 某维度所有客户数据相同(如 F 均为 1) | 强制均分 5 个区间 | 分项得分按 1-5 分依次分配 |
|
||||
| 权重总和不为 1 | 配置权重时计算错误 | 系统自动归一化处理($W'_X = W_X / (W_R+W_F+W_M)$) | 不影响总分有效性 |
|
||||
| 统计周期过短 | 小于 30 天导致数据量不足 | 系统给出警告,允许强制执行 | 区间划分可能失真,建议延长周期 |
|
||||
|
||||
## 7. 输出结果格式
|
||||
|
||||
### 7.1 单客户评分结果
|
||||
|
||||
|
||||
|
||||
| 字段名 | 数据类型 | 示例 |
|
||||
| --------- | ------------- | ----------------------- |
|
||||
| 客户 ID | String | CUST2023001 |
|
||||
| R 原始值(天) | Int | 15 |
|
||||
| R 得分 | Int | 5 |
|
||||
| F 原始值(次) | Int | 8 |
|
||||
| F 得分 | Int | 4 |
|
||||
| M 原始值(元) | Decimal(18,2) | 2560.00 |
|
||||
| M 得分 | Int | 5 |
|
||||
| RFM 总分 | Decimal(5,2) | 4.70 |
|
||||
| 标准化得分(可选) | Int | 94 |
|
||||
| 统计周期 | String | 2023-01-01 至 2023-06-30 |
|
||||
| 计算时间 | DateTime | 2023-07-01 00:30:25 |
|
||||
|
||||
### 7.2 批量输出文件格式
|
||||
|
||||
|
||||
|
||||
* 支持 CSV、Parquet、JSON 格式导出
|
||||
|
||||
* 编码格式:UTF-8
|
||||
|
||||
* 压缩方式:默认 GZIP(可配置关闭)
|
||||
|
||||
## 8. 业务适配建议
|
||||
|
||||
|
||||
|
||||
| 业务场景 | 统计周期建议 | 权重调整建议 | 特殊配置 |
|
||||
| --------------- | ------- | --------------------------- | ----------------- |
|
||||
| 快消零售 | 3-6 个月 | $W_R=0.5, W_F=0.3, W_M=0.2$ | 提高 R 维度权重,关注复购及时性 |
|
||||
| 高客单价行业(如奢侈品、家居) | 12 个月 | $W_R=0.3, W_F=0.2, W_M=0.5$ | 提高 M 维度权重,关注消费能力 |
|
||||
| 新品推广期 | 1-3 个月 | $W_R=0.6, W_F=0.2, W_M=0.2$ | 重点关注近期新客户 |
|
||||
| 会员体系运营 | 6-12 个月 | $W_R=0.4, W_F=0.4, W_M=0.2$ | 提高 F 维度权重,鼓励高频消费 |
|
||||
|
||||
> (注:文档部分内容可能由 AI 生成)
|
||||
1
application/.htaccess
Normal file
1
application/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
deny from all
|
||||
18
application/ai/config/route.php
Normal file
18
application/ai/config/route.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
// 定义RESTful风格的API路由
|
||||
Route::group('v1/ai', function () {
|
||||
|
||||
//openai、chatGPT
|
||||
Route::group('openai', function () {
|
||||
Route::post('text', 'app\ai\controller\OpenAI@text');
|
||||
});
|
||||
|
||||
|
||||
//豆包ai
|
||||
Route::group('doubao', function () {
|
||||
Route::post('text', 'app\ai\controller\DouBaoAI@text');
|
||||
});
|
||||
})->middleware(['jwt']);
|
||||
406
application/ai/controller/CozeAI.php
Normal file
406
application/ai/controller/CozeAI.php
Normal file
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
namespace app\ai\controller;
|
||||
|
||||
use think\facade\Env;
|
||||
use think\Controller;
|
||||
|
||||
class CozeAI extends Controller
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $accessToken;
|
||||
protected $headers;
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// 从环境变量获取配置
|
||||
$this->apiUrl = Env::get('cozeAi.api_url');
|
||||
$this->accessToken = Env::get('cozeAi.token');
|
||||
|
||||
if (empty($this->accessToken) || empty($this->apiUrl)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$this->headers = [
|
||||
'Authorization: Bearer ' . $this->accessToken,
|
||||
'Content-Type: application/json'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createBot($data = [])
|
||||
{
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$model_id = !empty($data['model_id']) ? $data['model_id'] : '';
|
||||
$prompt_info = !empty($data['prompt_info']) ? $data['prompt_info'] : '';
|
||||
$plugin_id_list = [
|
||||
'id_list' => [
|
||||
['api_id' => '7362852017859035163', 'plugin_id' => '7362852017859018779'],
|
||||
['api_id' => '7472045461050851367', 'plugin_id' => '7472045461050834983'],
|
||||
]
|
||||
];
|
||||
if (empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$model_info_config = [
|
||||
'model_id' => (string)$model_id,
|
||||
];
|
||||
|
||||
$params = [
|
||||
'space_id' => $space_id,
|
||||
'name' => $name,
|
||||
'model_info_config' => (object)$model_info_config,
|
||||
'plugin_id_list' => (object)$plugin_id_list
|
||||
];
|
||||
|
||||
if (!empty($prompt_info)){
|
||||
$new_prompt_info = [
|
||||
'prompt' => $prompt_info
|
||||
];
|
||||
$params['prompt_info'] = (object) $new_prompt_info;
|
||||
}
|
||||
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/create', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功', 'data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function updateBot($data = [])
|
||||
{
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$model_id = !empty($data['model_id']) ? $data['model_id'] : '';
|
||||
$prompt_info = !empty($data['prompt_info']) ? $data['prompt_info'] : '';
|
||||
$dataset_ids = !empty($data['dataset_ids']) ? $data['dataset_ids'] : '';
|
||||
$plugin_id_list = [
|
||||
'id_list' => [
|
||||
['api_id' => '7362852017859035163', 'plugin_id' => '7362852017859018779'],
|
||||
['api_id' => '7472045461050851367', 'plugin_id' => '7472045461050834983'],
|
||||
]
|
||||
];
|
||||
if (empty($name) || empty($bot_id)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$model_info_config = [
|
||||
'model_id' => (string)$model_id,
|
||||
];
|
||||
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'space_id' => $space_id,
|
||||
'name' => $name,
|
||||
'model_info_config' => (object)$model_info_config,
|
||||
'plugin_id_list' => (object)$plugin_id_list
|
||||
];
|
||||
|
||||
|
||||
if (!empty($prompt_info)){
|
||||
$new_prompt_info = [
|
||||
'prompt' => $prompt_info
|
||||
];
|
||||
$params['prompt_info'] = (object) $new_prompt_info;
|
||||
}
|
||||
|
||||
if (!empty($dataset_ids)){
|
||||
$knowledge = [
|
||||
'dataset_ids' => $dataset_ids
|
||||
];
|
||||
$params['knowledge'] = (object) $knowledge;
|
||||
}
|
||||
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/update', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '更新成功', 'data' => []]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发布智能体
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function botPublish($data = [])
|
||||
{
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$connector_ids = ['1024', '999'];
|
||||
if (empty($bot_id) || empty($connector_ids)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'connector_ids' => $connector_ids,
|
||||
];
|
||||
$result = requestCurl($this->apiUrl . '/v1/bot/publish', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发布成功', 'data' => []]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建知识库
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createKnowledge($data = [])
|
||||
{
|
||||
|
||||
$space_id = Env::get('cozeAi.space_id');
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
if (empty($space_id) || empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
$params = [
|
||||
'space_id' => $space_id,
|
||||
'format_type' => 0,
|
||||
'name' => $name,
|
||||
];
|
||||
$result = requestCurl($this->apiUrl . '/v1/datasets', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
public function createDocument($data = [])
|
||||
{
|
||||
// 文件路径
|
||||
$filePath = !empty($data['filePath']) ? $data['filePath'] : '';
|
||||
$fileName = !empty($data['fileName']) ? $data['fileName'] : '';
|
||||
if (empty($filePath)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
// 读取文件内容
|
||||
$fileContent = file_get_contents($filePath);
|
||||
// 将文件内容编码为Base64
|
||||
$base64EncodedContent = base64_encode($fileContent);
|
||||
|
||||
|
||||
$dataset_id = !empty($data['dataset_id']) ? $data['dataset_id'] : '';
|
||||
|
||||
$document_bases = [
|
||||
['name' => $fileName,'source_info' => ['file_base64' => $base64EncodedContent]]
|
||||
];
|
||||
|
||||
$chunk_strategy = [
|
||||
'chunk_type' => 0,
|
||||
'remove_extra_spaces' => true
|
||||
];
|
||||
$params = [
|
||||
'dataset_id' => (string) $dataset_id,
|
||||
'document_bases' => $document_bases,
|
||||
'chunk_strategy' => (object) $chunk_strategy,
|
||||
'format_type' => 0
|
||||
];
|
||||
$headers = array_merge($this->headers, ['Agw-Js-Conv: str']);
|
||||
$result = requestCurl($this->apiUrl . '/open_api/knowledge/document/create', $params, 'POST', $headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['document_infos']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除知识库文件
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function deleteDocument($data = [])
|
||||
{
|
||||
if (empty($data)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'document_ids' => $data,
|
||||
];
|
||||
$headers = array_merge($this->headers, ['Agw-Js-Conv: str']);
|
||||
$result = requestCurl($this->apiUrl . '/open_api/knowledge/document/delete', $params, 'POST', $headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '删除成功', 'data' => []]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建会话
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createConversation($data = [])
|
||||
{
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$name = !empty($data['name']) ? $data['name'] : '';
|
||||
$meta_data = !empty($data['meta_data']) ? $data['meta_data'] : [];
|
||||
|
||||
if (empty($bot_id) || empty($name)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'bot_id' => $bot_id,
|
||||
'name' => $name,
|
||||
];
|
||||
if (!empty($meta_data)){
|
||||
$params['meta_data'] = $meta_data;
|
||||
}
|
||||
$result = requestCurl($this->apiUrl . '/v1/conversation/create', $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '创建成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 开始对话
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function createChat($data = [])
|
||||
{
|
||||
try {
|
||||
$bot_id = !empty($data['bot_id']) ? $data['bot_id'] : '';
|
||||
$uid = !empty($data['uid']) ? $data['uid'] : '';
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$question = !empty($data['question']) ? $data['question'] : [];
|
||||
|
||||
|
||||
if(empty($bot_id)){
|
||||
return json_encode(['code' => 500, 'msg' => '智能体ID不能为空', 'data' => []]);
|
||||
}
|
||||
|
||||
if(empty($conversation_id)){
|
||||
return json_encode(['code' => 500, 'msg' => '会话ID不能为空', 'data' => []]);
|
||||
}
|
||||
|
||||
if(empty($question)){
|
||||
return json_encode(['code' => 500, 'msg' => '问题不能为空', 'data' => []]);
|
||||
}
|
||||
|
||||
// 构建请求数据
|
||||
$params = [
|
||||
'bot_id' => strval($bot_id),
|
||||
'user_id' => strval($uid),
|
||||
'additional_messages' => $question,
|
||||
'stream' => false,
|
||||
'auto_save_history' => true
|
||||
];
|
||||
|
||||
$url = $this->apiUrl . '/v3/chat?conversation_id='.$conversation_id;
|
||||
$result = requestCurl($url, $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '发送成功','data' => $result['data']]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code' => 500, 'msg' => '创建对话失败:' . $e->getMessage(), 'data' => []]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查看对话详情
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function getConversationChat($data = [])
|
||||
{
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$chat_id = !empty($data['chat_id']) ? $data['chat_id'] : '';
|
||||
$url = $this->apiUrl . '/v3/chat/retrieve?conversation_id='.$conversation_id.'&chat_id='.$chat_id;
|
||||
$result = requestCurl($url, [], 'GET', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '获取成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查看对话消息详情
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function listConversationMessage($data = [])
|
||||
{
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$chat_id = !empty($data['chat_id']) ? $data['chat_id'] : '';
|
||||
$url = $this->apiUrl . '/v3/chat/message/list?conversation_id='.$conversation_id.'&chat_id='.$chat_id;
|
||||
$result = requestCurl($url, [], 'GET', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '获取成功','data' => $result['data']]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 取消进行中的对话
|
||||
* @param $data
|
||||
* @return false|string|\think\response\Json
|
||||
*/
|
||||
public function cancelConversationChat($data = [])
|
||||
{
|
||||
$conversation_id = !empty($data['conversation_id']) ? $data['conversation_id'] : '';
|
||||
$chat_id = !empty($data['chat_id']) ? $data['chat_id'] : '';
|
||||
|
||||
// 构建请求数据
|
||||
$params = [
|
||||
'conversation_id' => (string) $conversation_id,
|
||||
'chat_id' => (string) $chat_id
|
||||
];
|
||||
|
||||
$url = $this->apiUrl . '/v3/chat/cancel';
|
||||
$result = requestCurl($url, $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] != 0) {
|
||||
return json_encode(['code' => $result['code'], 'msg' => $result['msg'], 'data' => []]);
|
||||
}
|
||||
return json_encode(['code' => 200, 'msg' => '取消成功', 'data' => []]);
|
||||
}
|
||||
|
||||
}
|
||||
68
application/ai/controller/DouBaoAI.php
Normal file
68
application/ai/controller/DouBaoAI.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace app\ai\controller;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use think\facade\Env;
|
||||
use think\Controller;
|
||||
|
||||
class DouBaoAI extends Controller
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $apiKey;
|
||||
protected $headers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->apiUrl = Env::get('doubaoAi.api_url');
|
||||
$this->apiKey = Env::get('doubaoAi.api_key');
|
||||
|
||||
// 设置请求头
|
||||
$this->headers = [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $this->apiKey
|
||||
];
|
||||
|
||||
if (empty($this->apiKey) || empty($this->apiUrl)) {
|
||||
return json_encode(['code' => 500, 'msg' => '参数缺失']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function text($params = [])
|
||||
{
|
||||
|
||||
if (empty($params)){
|
||||
$content = $this->request->param('content', '');
|
||||
$model = $this->request->param('model', 'doubao-seed-1-8-251215');
|
||||
if(empty($content)){
|
||||
return json_encode(['code' => 500, 'msg' => '提示词缺失']);
|
||||
}
|
||||
$params = [
|
||||
'model' => $model,
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => '你现在是存客宝的AI助理,你精通中国大陆的法律'],
|
||||
['role' => 'user', 'content' => $content],
|
||||
],
|
||||
];
|
||||
}
|
||||
$result = requestCurl($this->apiUrl, $params, 'POST', $this->headers, 'json');
|
||||
$result = json_decode($result, true);
|
||||
if(isset($result['error'])){
|
||||
$error = $result['error'];
|
||||
return json_encode(['code' => 500, 'msg' => $error['message']]);
|
||||
}else{
|
||||
$content = $result['choices'][0]['message']['content'];
|
||||
$token = intval($result['usage']['total_tokens']) * 20;
|
||||
|
||||
exit_data($content);
|
||||
return json_encode(['code' => 200, 'msg' => '成功','data' => ['token' => $token,'content' => $content]]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
145
application/ai/controller/OpenAi.php
Normal file
145
application/ai/controller/OpenAi.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace app\ai\controller;
|
||||
|
||||
use think\facade\Env;
|
||||
use think\Controller;
|
||||
|
||||
|
||||
class OpenAI extends Controller
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $apiKey;
|
||||
protected $headers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->apiUrl = Env::get('openAi.apiUrl');
|
||||
$this->apiKey = Env::get('openAi.apiKey');
|
||||
|
||||
// 设置请求头
|
||||
$this->headers = [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer '.$this->apiKey
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function text()
|
||||
{
|
||||
|
||||
$params = [
|
||||
'model' => 'gpt-3.5-turbo-0125',
|
||||
'input' => 'DHA 从孕期到出生到老年都需要,助力大脑发育🧠/减缓脑压力有助记忆/给大脑动力#贝蒂喜藻油DHA 双标认证每粒 150毫克,高含量、高性价比从小吃到老,长期吃更健康 重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除'
|
||||
];
|
||||
$result = $this->httpRequest( $this->apiUrl, 'POST', $params,$this->headers);
|
||||
exit_data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 示例:调用OpenAI API生成睡前故事
|
||||
* 对应curl命令:
|
||||
* curl "https://api.ai.com/v1/responses" \
|
||||
* -H "Content-Type: application/json" \
|
||||
* -H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||
* -d '{
|
||||
* "model": "gpt-5",
|
||||
* "input": "Write a one-sentence bedtime story about a unicorn."
|
||||
* }'
|
||||
*/
|
||||
public function bedtimeStory()
|
||||
{
|
||||
|
||||
// API请求参数
|
||||
$params = [
|
||||
'model' => 'gpt-5',
|
||||
'input' => 'Write a one-sentence bedtime story about a unicorn.'
|
||||
];
|
||||
|
||||
// 发送请求到OpenAI API
|
||||
$url = 'https://api.openai.com/v1/responses';
|
||||
$result = $this->httpRequest($url, 'POST', $params, $this->headers);
|
||||
|
||||
// 返回结果
|
||||
exit_data($result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CURL请求 - 专门用于JSON API请求
|
||||
*
|
||||
* @param $url 请求url地址
|
||||
* @param $method 请求方法 get post
|
||||
* @param null $postfields post数据数组
|
||||
* @param array $headers 请求header信息
|
||||
* @param int $timeout 超时时间
|
||||
* @param bool|false $debug 调试开启 默认false
|
||||
* @return mixed
|
||||
*/
|
||||
protected function httpRequest($url, $method = "GET", $postfields = null, $headers = array(), $timeout = 30, $debug = false)
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
$ci = curl_init();
|
||||
|
||||
/* Curl settings */
|
||||
curl_setopt($ci, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0");
|
||||
curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, 60); /* 在发起连接前等待的时间,如果设置为0,则无限等待 */
|
||||
curl_setopt($ci, CURLOPT_TIMEOUT, $timeout); /* 设置cURL允许执行的最长秒数 */
|
||||
curl_setopt($ci, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
switch ($method) {
|
||||
case "POST":
|
||||
curl_setopt($ci, CURLOPT_POST, true);
|
||||
if (!empty($postfields)) {
|
||||
// 对于JSON API,直接将数组转换为JSON字符串
|
||||
if (is_array($postfields)) {
|
||||
$tmpdatastr = json_encode($postfields);
|
||||
} else {
|
||||
$tmpdatastr = $postfields;
|
||||
}
|
||||
curl_setopt($ci, CURLOPT_POSTFIELDS, $tmpdatastr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
curl_setopt($ci, CURLOPT_CUSTOMREQUEST, $method); /* //设置请求方式 */
|
||||
break;
|
||||
}
|
||||
|
||||
$ssl = preg_match('/^https:\/\//i', $url) ? TRUE : FALSE;
|
||||
curl_setopt($ci, CURLOPT_URL, $url);
|
||||
if ($ssl) {
|
||||
curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
|
||||
curl_setopt($ci, CURLOPT_SSL_VERIFYHOST, FALSE); // 不从证书中检查SSL加密算法是否存在
|
||||
}
|
||||
|
||||
if (ini_get('open_basedir') == '' && ini_get('safe_mode' == 'Off')) {
|
||||
curl_setopt($ci, CURLOPT_FOLLOWLOCATION, 1);
|
||||
}
|
||||
curl_setopt($ci, CURLOPT_MAXREDIRS, 2);/*指定最多的HTTP重定向的数量,这个选项是和CURLOPT_FOLLOWLOCATION一起使用的*/
|
||||
curl_setopt($ci, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ci, CURLINFO_HEADER_OUT, true);
|
||||
|
||||
$response = curl_exec($ci);
|
||||
$requestinfo = curl_getinfo($ci);
|
||||
$http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
|
||||
|
||||
if ($debug) {
|
||||
echo "=====post data======\r\n";
|
||||
var_dump($postfields);
|
||||
echo "=====info===== \r\n";
|
||||
print_r($requestinfo);
|
||||
echo "=====response=====\r\n";
|
||||
print_r($response);
|
||||
}
|
||||
|
||||
curl_close($ci);
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
109
application/api/config/route.php
Normal file
109
application/api/config/route.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
// store模块路由配置
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
// 定义RESTful风格的API路由
|
||||
Route::group('v1', function () {
|
||||
Route::group('api', function () {
|
||||
// Account控制器路由
|
||||
Route::group('account', function () {
|
||||
Route::get('list', 'app\api\controller\AccountController@getList'); // 获取账号列表 √
|
||||
Route::post('create', 'app\api\controller\AccountController@createAccount'); // 创建账号 √
|
||||
Route::post('createNewAccount', 'app\api\controller\AccountController@createNewAccount'); // 创建新账号(包含创建部门) √
|
||||
Route::post('department/create', 'app\api\controller\AccountController@createDepartment'); // 创建部门 √
|
||||
Route::get('department/list', 'app\api\controller\AccountController@getDepartmentList'); // 获取部门列表 √
|
||||
Route::post('department/update', 'app\api\controller\AccountController@updateDepartment'); // 更新部门 √
|
||||
Route::post('department/delete', 'app\api\controller\AccountController@deleteDepartment'); // 删除部门 √
|
||||
Route::post('department/setPrivileges', 'app\api\controller\AccountController@setPrivileges'); // 设置部门权限 √
|
||||
});
|
||||
|
||||
// Device控制器路由
|
||||
Route::group('device', function () {
|
||||
Route::get('list', 'app\api\controller\DeviceController@getList'); // 获取设备列表 √
|
||||
Route::post('add', 'app\api\controller\DeviceController@addDevice'); // 生成设备二维码(POST方式) √
|
||||
Route::post('updateDeviceGroup', 'app\api\controller\DeviceController@updateDeviceGroup'); // 更新设备分组 √
|
||||
Route::post('updateaccount', 'app\api\controller\DeviceController@updateaccount'); // 更新设备账号 √
|
||||
Route::post('createGroup', 'app\api\controller\DeviceController@createGroup'); // 创建设备分组 √
|
||||
Route::get('groupList', 'app\api\controller\DeviceController@getGroupList'); // 获取设备分组列表 √
|
||||
Route::post('updateDeviceToGroup', 'app\api\controller\DeviceController@updateDeviceToGroup'); // 更新设备的分组 √
|
||||
|
||||
Route::post('importContact', 'app\api\controller\DeviceController@importContact'); // 更新设备联系人 √
|
||||
});
|
||||
|
||||
// FriendTask控制器路由
|
||||
Route::group('friend-task', function () {
|
||||
Route::get('list', 'app\api\controller\FriendTaskController@getList'); // 获取添加好友记录列表 √
|
||||
Route::post('add', 'app\api\controller\FriendTaskController@addFriendTask'); // 添加好友任务 √
|
||||
});
|
||||
|
||||
// Moments控制器路由
|
||||
Route::group('moments', function () {
|
||||
Route::post('add-job', 'app\api\controller\MomentsController@addJob'); // 发布朋友圈
|
||||
Route::get('list', 'app\api\controller\MomentsController@getList'); // 获取朋友圈任务列表 √
|
||||
});
|
||||
|
||||
// Stats控制器路由
|
||||
Route::group('stats', function () {
|
||||
Route::get('basic-data', 'app\api\controller\StatsController@basicData'); // 账号基本信息
|
||||
Route::get('fans-statistics', 'app\api\controller\StatsController@FansStatistics'); // 好友统计
|
||||
});
|
||||
|
||||
// User控制器路由
|
||||
Route::group('user', function () {
|
||||
Route::post('login', 'app\api\controller\UserController@login'); // 登录 √
|
||||
Route::post('token', 'app\api\controller\UserController@getNewToken'); // 获取新的token √
|
||||
Route::get('info', 'app\api\controller\UserController@getAccountInfo'); // 获取商户基本信息 √
|
||||
Route::post('modify-pwd', 'app\api\controller\UserController@modifyPwd'); // 修改密码
|
||||
Route::get('logout', 'app\api\controller\UserController@logout'); // 登出 √
|
||||
Route::get('verify-code', 'app\api\controller\UserController@getVerifyCode'); // 获取验证码 √
|
||||
});
|
||||
|
||||
// WebSocket控制器路由
|
||||
Route::group('websocket', function () {
|
||||
Route::post('send-personal', 'app\api\controller\WebSocketController@sendPersonal'); // 个人消息发送 √
|
||||
Route::post('send-community', 'app\api\controller\WebSocketController@sendCommunity'); // 发送群消息 √
|
||||
Route::get('get-moments', 'app\api\controller\WebSocketController@getMoments'); // 获取指定账号朋友圈信息 √
|
||||
Route::get('get-moment-source', 'app\api\controller\WebSocketController@getMomentSourceRealUrl'); // 获取指定账号朋友圈图片地址
|
||||
});
|
||||
|
||||
// WechatChatroom控制器路由
|
||||
Route::group('chatroom', function () {
|
||||
Route::get('list', 'app\api\controller\WechatChatroomController@getList'); // 获取微信群聊列表 √
|
||||
Route::get('members', 'app\api\controller\WechatChatroomController@listChatroomMember'); // 获取群成员列表 √
|
||||
// Route::get('sync', 'app\api\controller\WechatChatroomController@syncChatrooms'); // 同步微信群聊数据 √
|
||||
});
|
||||
|
||||
// Wechat控制器路由
|
||||
Route::group('wechat', function () {
|
||||
Route::get('list', 'app\api\controller\WechatController@getList'); // 获取微信账号列表 √
|
||||
});
|
||||
|
||||
// WechatFriend控制器路由
|
||||
Route::group('friend', function () {
|
||||
Route::get('list', 'app\api\controller\WechatFriendController@getList'); // 获取微信好友列表数据 √
|
||||
});
|
||||
|
||||
// Message控制器路由
|
||||
Route::group('message', function () {
|
||||
Route::get('getFriendsList', 'app\api\controller\MessageController@getFriendsList'); // 获取微信好友列表 √
|
||||
Route::get('getChatroomList', 'app\api\controller\MessageController@getChatroomList'); // 同步群聊消息 √
|
||||
});
|
||||
|
||||
// AllotRule控制器路由
|
||||
Route::group('allot-rule', function () {
|
||||
Route::get('list', 'app\api\controller\AllotRuleController@getAllRules'); // 获取所有分配规则 √
|
||||
Route::post('create', 'app\api\controller\AllotRuleController@createRule');// 创建分配规则 √
|
||||
Route::post('edit', 'app\api\controller\AllotRuleController@updateRule');// 编辑分配规则 √
|
||||
Route::delete('del', 'app\api\controller\AllotRuleController@deleteRule');// 删除分配规则 √
|
||||
Route::get('autoCreate', 'app\api\controller\AllotRuleController@autoCreateAllotRules');// 自动创建分配规则 √
|
||||
});
|
||||
|
||||
// CallRecording控制器路由
|
||||
Route::group('call-recording', function () {
|
||||
Route::get('list', 'app\api\controller\CallRecordingController@getlist'); // 获取通话记录列表 √
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
748
application/api/controller/AccountController.php
Normal file
748
application/api/controller/AccountController.php
Normal file
@@ -0,0 +1,748 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\CompanyAccountModel;
|
||||
use app\api\model\CompanyModel;
|
||||
use Library\S2\Logics\AccountLogic;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 账号管理控制器
|
||||
* 包含账号管理和部门管理的相关功能
|
||||
*/
|
||||
class AccountController extends BaseController
|
||||
{
|
||||
/************************ 账号管理相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取公司账号列表
|
||||
* @param string $pageIndex 页码
|
||||
* @param string $pageSize 每页数量
|
||||
* @param bool $isInner 是否为定时任务调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($data = [], $isInner = false)
|
||||
{
|
||||
|
||||
$pageIndex = !empty($data['pageIndex']) ? $data['pageIndex'] : 0;
|
||||
$pageSize = !empty($data['pageSize']) ? $data['pageSize'] : 20;
|
||||
$showNormalAccount = !empty($data['showNormalAccount']) ? $data['showNormalAccount'] : '';
|
||||
$keyword = !empty($data['keyword']) ? $data['keyword'] : '';
|
||||
$departmentId = !empty($data['departmentId']) ? $data['departmentId'] : '';
|
||||
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'showNormalAccount' => $showNormalAccount,
|
||||
'keyword' => $keyword,
|
||||
'departmentId' => $departmentId,
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取公司账号列表
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/myTenantPageAccounts', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
$this->saveAccount($item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => '获取公司账号列表成功', 'data' => $response]);
|
||||
} else {
|
||||
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取公司账号列表失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取公司账号列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createAccount()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取并验证请求参数
|
||||
$userName = $this->request->param('userName', '');
|
||||
$password = $this->request->param('password', '');
|
||||
$realName = $this->request->param('realName', '');
|
||||
$nickname = $this->request->param('nickname', '');
|
||||
$memo = $this->request->param('memo', '');
|
||||
$departmentId = $this->request->param('departmentId', 0);
|
||||
|
||||
|
||||
// 参数验证
|
||||
if (empty($userName)) {
|
||||
return errorJson('用户名不能为空');
|
||||
}
|
||||
// if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]{5,9}$/', $userName)) {
|
||||
// return errorJson('用户名必须以字母开头,只能包含字母和数字,长度6-10位');
|
||||
// }
|
||||
if (empty($password)) {
|
||||
return errorJson('密码不能为空');
|
||||
}
|
||||
|
||||
if (empty($realName)) {
|
||||
return errorJson('真实姓名不能为空');
|
||||
}
|
||||
if (empty($departmentId)) {
|
||||
return errorJson('公司ID不能为空');
|
||||
}
|
||||
|
||||
// 检查账号是否已存在
|
||||
$existingAccount = CompanyAccountModel::where('userName', $userName)->find();
|
||||
if (!empty($existingAccount)) {
|
||||
return errorJson('账号已存在');
|
||||
}
|
||||
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'userName' => $userName,
|
||||
'password' => $password,
|
||||
'realName' => $realName,
|
||||
'nickname' => $nickname,
|
||||
'memo' => $memo,
|
||||
'departmentId' => $departmentId,
|
||||
'departmentIdArr' => empty($departmentId) ? [914] : [914, $departmentId]
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求创建账号
|
||||
$result = requestCurl($this->baseUrl . 'api/account/newAccount', $params, 'POST', $header, 'json');
|
||||
|
||||
if (is_numeric($result)) {
|
||||
$res = CompanyAccountModel::create([
|
||||
'id' => $result,
|
||||
'tenantId' => 242,
|
||||
'userName' => $userName,
|
||||
'realName' => $realName,
|
||||
'nickname' => $nickname,
|
||||
'passwordMd5' => md5($password),
|
||||
'passwordLocal' => localEncrypt($password),
|
||||
'memo' => $memo,
|
||||
'accountType' => 11,
|
||||
'departmentId' => $departmentId,
|
||||
'createTime' => time(),
|
||||
'privilegeIds' => json_encode([])
|
||||
]);
|
||||
$this->setPrivileges(['id' => $result]);
|
||||
return successJson($res);
|
||||
} else {
|
||||
return errorJson($result);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('创建账号失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建新账号(包含创建部门)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createNewAccount()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
DB::startTrans();
|
||||
try {
|
||||
// 获取参数
|
||||
$departmentName = $this->request->param('departmentName', '');
|
||||
$departmentMemo = $this->request->param('departmentMemo', '');
|
||||
$accountName = $this->request->param('accountName', '');
|
||||
$accountPassword = $this->request->param('accountPassword', '');
|
||||
$accountRealName = $this->request->param('accountRealName', '');
|
||||
$accountNickname = $this->request->param('accountNickname', '');
|
||||
$accountMemo = $this->request->param('accountMemo', '');
|
||||
|
||||
// 验证参数
|
||||
if (empty($departmentName)) {
|
||||
return errorJson('部门名称不能为空');
|
||||
}
|
||||
if (empty($accountName)) {
|
||||
return errorJson('账号名称不能为空');
|
||||
}
|
||||
if (empty($accountPassword)) {
|
||||
return errorJson('账号密码不能为空');
|
||||
}
|
||||
|
||||
// 检查部门是否已存在
|
||||
$existingDepartment = CompanyModel::where('name', $departmentName)->find();
|
||||
if (!empty($existingDepartment)) {
|
||||
return errorJson('部门以存在');
|
||||
}
|
||||
|
||||
// 检查账号是否已存在
|
||||
$existingAccount = CompanyAccountModel::where('userName', $accountName)->find();
|
||||
if (!empty($existingAccount)) {
|
||||
return errorJson('账号已存在');
|
||||
}
|
||||
|
||||
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 1. 创建部门
|
||||
$departmentParams = [
|
||||
'name' => $departmentName,
|
||||
'memo' => $departmentMemo,
|
||||
'departmentIdArr' => [914],
|
||||
'parentId' => 914
|
||||
];
|
||||
|
||||
$departmentResult = requestCurl($this->baseUrl . 'api/Department/createDepartment', $departmentParams, 'POST', $header, 'json');
|
||||
if (is_numeric($departmentResult)) {
|
||||
// 保存部门到数据库
|
||||
CompanyModel::create([
|
||||
'id' => $departmentResult,
|
||||
'name' => $departmentName,
|
||||
'memo' => $departmentMemo,
|
||||
'tenantId' => 242,
|
||||
'isTop' => 0,
|
||||
'level' => 1,
|
||||
'parentId' => 914,
|
||||
'privileges' => '',
|
||||
'createTime' => time(),
|
||||
'lastUpdateTime' => 0
|
||||
]);
|
||||
|
||||
$this->setPrivileges(['id' => $departmentResult]);
|
||||
|
||||
} else {
|
||||
DB::rollback();
|
||||
return errorJson('创建部门失败:' . $departmentResult);
|
||||
}
|
||||
|
||||
// 2. 创建账号
|
||||
$accountParams = [
|
||||
'userName' => $accountName,
|
||||
'password' => $accountPassword,
|
||||
'realName' => $accountRealName,
|
||||
'nickname' => $accountNickname,
|
||||
'memo' => $accountMemo,
|
||||
'departmentId' => $departmentResult,
|
||||
'departmentIdArr' => [914, $departmentResult]
|
||||
];
|
||||
|
||||
$accountResult = requestCurl($this->baseUrl . 'api/Account/newAccount', $accountParams, 'POST', $header, 'json');
|
||||
|
||||
if (is_numeric($accountResult)) {
|
||||
$res = CompanyAccountModel::create([
|
||||
'id' => $accountResult,
|
||||
'tenantId' => 242,
|
||||
'userName' => $accountName,
|
||||
'realName' => $accountRealName,
|
||||
'nickname' => $accountNickname,
|
||||
'passwordMd5' => md5($accountPassword),
|
||||
'passwordLocal' => localEncrypt($accountPassword),
|
||||
'memo' => $accountMemo,
|
||||
'accountType' => 11,
|
||||
'departmentId' => $departmentResult,
|
||||
'createTime' => time(),
|
||||
'privilegeIds' => json_encode([])
|
||||
]);
|
||||
DB::commit();
|
||||
return successJson($res, '账号创建成功');
|
||||
} else {
|
||||
// 如果创建账号失败,删除已创建的部门
|
||||
$this->deleteDepartment($accountResult);
|
||||
DB::rollback();
|
||||
return errorJson('创建账号失败:' . $accountResult);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
return errorJson('创建账号失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/************************ 部门管理相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取部门列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getDepartmentList($isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取部门列表
|
||||
$url = $this->baseUrl . 'api/Department/fetchMyAndSubordinateDepartment';
|
||||
$result = requestCurl($url, [], 'GET', $header, 'json');
|
||||
|
||||
// 处理返回结果
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
CompanyModel::where('1=1')->delete();
|
||||
$this->processDepartments($response);
|
||||
}
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => '获取部门列表成功', 'data' => $response]);
|
||||
} else {
|
||||
return successJson($response, '获取部门列表成功');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取部门列表失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取部门列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createDepartment()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取并验证请求参数
|
||||
$name = $this->request->param('name', '');
|
||||
$memo = $this->request->param('memo', '');
|
||||
if (empty($name)) {
|
||||
return errorJson('请输入公司名称');
|
||||
}
|
||||
|
||||
// 检查部门名称是否已存在
|
||||
$departmentId = CompanyModel::where('name', $name)->find();
|
||||
if (!empty($departmentId)) {
|
||||
return errorJson('部门已存在');
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'name' => $name,
|
||||
'memo' => $memo,
|
||||
'departmentIdArr' => [914],
|
||||
'parentId' => 914
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求创建部门
|
||||
$result = requestCurl($this->baseUrl . 'api/Department/createDepartment', $params, 'POST', $header, 'json');
|
||||
|
||||
// 处理返回结果
|
||||
if (is_numeric($result)) {
|
||||
$res = CompanyModel::create([
|
||||
'id' => $result,
|
||||
'name' => $name,
|
||||
'memo' => $memo,
|
||||
'tenantId' => 242,
|
||||
'isTop' => 0,
|
||||
'level' => 1,
|
||||
'parentId' => 914,
|
||||
'privileges' => '',
|
||||
'createTime' => time(),
|
||||
'lastUpdateTime' => 0
|
||||
]);
|
||||
return successJson($res);
|
||||
} else {
|
||||
return errorJson($result);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('创建部门失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateDepartment()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取并验证请求参数
|
||||
$id = $this->request->param('id', 0);
|
||||
$name = $this->request->param('name', '');
|
||||
$memo = $this->request->param('memo', '');
|
||||
|
||||
if (empty($id)) {
|
||||
return errorJson('部门ID不能为空');
|
||||
}
|
||||
if (empty($name)) {
|
||||
return errorJson('部门名称不能为空');
|
||||
}
|
||||
|
||||
// 验证部门是否存在
|
||||
$department = CompanyModel::where('id', $id)->find();
|
||||
if (empty($department)) {
|
||||
return errorJson('部门不存在');
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$departmentIdArr = $department->parentId == 914 ? [914] : [914, $department->parentId];
|
||||
$params = [
|
||||
'id' => $id,
|
||||
'name' => $name,
|
||||
'memo' => $memo,
|
||||
'departmentIdArr' => $departmentIdArr,
|
||||
'tenantId' => 242,
|
||||
'createTime' => $department->createTime,
|
||||
'isTop' => $department->isTop,
|
||||
'level' => $department->level,
|
||||
'parentId' => $department->parentId,
|
||||
'lastUpdateTime' => $department->lastUpdateTime,
|
||||
'privileges' => $department->privileges
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求修改部门
|
||||
$result = requestCurl($this->baseUrl . 'api/Department/department', $params, 'PUT', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 更新本地数据库
|
||||
$department->name = $name;
|
||||
$department->memo = $memo;
|
||||
$department->save();
|
||||
|
||||
return successJson([], '部门修改成功');
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('修改部门失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteDepartment($id = '')
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取并验证部门ID
|
||||
$id = !empty($id) ? $id : $this->request->param('id', '');
|
||||
if (empty($id)) {
|
||||
return errorJson('部门ID不能为空');
|
||||
}
|
||||
|
||||
// 验证部门是否存在
|
||||
$department = CompanyModel::where('id', $id)->find();
|
||||
if (empty($department)) {
|
||||
return errorJson('部门不存在');
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送删除请求
|
||||
$result = requestCurl($this->baseUrl . 'api/Department/del/' . $id, [], 'DELETE', $header);
|
||||
|
||||
if ($result) {
|
||||
return errorJson($result);
|
||||
} else {
|
||||
// 删除本地数据库记录
|
||||
$department->delete();
|
||||
return successJson([], '部门删除成功');
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('删除部门失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门权限
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function setPrivileges($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取并验证请求参数
|
||||
$id = !empty($data['id']) ? $data['id'] : $this->request->param('id', 0);
|
||||
if (empty($id)) {
|
||||
return errorJson('部门ID不能为空');
|
||||
}
|
||||
|
||||
$privilegeIds = !empty($data['privilegeIds']) ? $data['privilegeIds'] : '1001,1002,1004,1023,1406,20003,20021,20022,20023,20032,20041,20049,20054,20055,20060,20100,20102,20107';
|
||||
$privilegeIds = explode(',',$privilegeIds);
|
||||
|
||||
// 验证部门是否存在
|
||||
$department = CompanyModel::where('id', $id)->find();
|
||||
if (empty($department)) {
|
||||
return errorJson('部门不存在');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'departmentId' => $id,
|
||||
'privilegeIds' => $privilegeIds,
|
||||
'syncPrivilege' => true
|
||||
];
|
||||
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求修改部门
|
||||
$result = requestCurl($this->baseUrl . 'api/Department/privileges', $params, 'PUT', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
|
||||
return successJson([], '部门权限修改成功');
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('修改部门权限失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function accountModify($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
|
||||
$id = !empty($data['id']) ? $data['id'] : '';
|
||||
if (empty($id)) {
|
||||
return errorJson('账号ID不能为空');
|
||||
}
|
||||
|
||||
$account = CompanyAccountModel::where('id', $id)->find();
|
||||
|
||||
|
||||
|
||||
if (empty($account)) {
|
||||
return errorJson('账号不存在');
|
||||
}
|
||||
$privilegeIds = json_decode($account->privilegeIds,true);
|
||||
$privilegeIds = !empty($privilegeIds) ? $privilegeIds : [1001,1002,1004,1023,1406,20003,20021,20022,20023,20032,20041,20049,20054,20055,20060,20100,20102,20107,20055];
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'accountType' => !empty($data['accountType']) ? $data['accountType'] : $account->accountType,
|
||||
'alive' => !empty($data['alive']) ? $data['alive'] : $account->alive,
|
||||
'avatar' => !empty($data['avatar']) ? $data['avatar'] : $account->avatar,
|
||||
'createTime' => !empty($data['createTime']) ? $data['createTime'] : $account->createTime,
|
||||
'creator' => !empty($data['creator']) ? $data['creator'] : $account->creator,
|
||||
'creatorRealName' => !empty($data['creatorRealName']) ? $data['creatorRealName'] : $account->creatorRealName,
|
||||
'creatorUserName' => !empty($data['creatorUserName']) ? $data['creatorUserName'] : $account->creatorUserName,
|
||||
'departmentId' => !empty($data['departmentId']) ? $data['departmentId'] : $account->departmentId,
|
||||
'departmentIdArr' => !empty($data['departmentIdArr']) ? $data['departmentIdArr'] : [914,$account->departmentId],
|
||||
'departmentName' => !empty($data['departmentName']) ? $data['departmentName'] : $account->departmentName,
|
||||
'hasXiakeAccount' => !empty($data['hasXiakeAccount']) ? $data['hasXiakeAccount'] : false,
|
||||
'id' => !empty($data['id']) ? $data['id'] : $account->id,
|
||||
'memo' => !empty($data['memo']) ? $data['memo'] : $account->memo,
|
||||
'nickname' => !empty($data['nickname']) ? $data['nickname'] : $account->nickname,
|
||||
'privilegeIds' => !empty($data['privilegeIds']) ? $data['privilegeIds'] : $privilegeIds,
|
||||
'realName' => !empty($data['realName']) ? $data['realName'] : $account->realName,
|
||||
'status' => !empty($data['status']) ? $data['status'] : $account->status,
|
||||
'tenantId' => !empty($data['tenantId']) ? $data['tenantId'] : $account->tenantId,
|
||||
'userName' => !empty($data['userName']) ? $data['userName'] : $account->userName,
|
||||
];
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求修改部门
|
||||
$result = requestCurl($this->baseUrl . 'api/account/modify', $params, 'PUT', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
|
||||
if(empty($response)){
|
||||
$newData = [
|
||||
'nickname' => $params['nickname'],
|
||||
'avatar' => $params['avatar'],
|
||||
];
|
||||
CompanyAccountModel::where('id', $id)->update($newData);
|
||||
return json_encode(['code' => 200, 'msg' => '账号修改成功']);
|
||||
}else{
|
||||
return json_encode(['code' => 500, 'msg' => $response]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/************************ 私有辅助方法 ************************/
|
||||
|
||||
/**
|
||||
* 递归处理部门列表
|
||||
* @param array $departments 部门数据
|
||||
*/
|
||||
private function processDepartments($departments)
|
||||
{
|
||||
if (empty($departments) || !is_array($departments)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($departments as $item) {
|
||||
// 保存当前部门
|
||||
$this->saveDepartment($item);
|
||||
|
||||
// 递归处理子部门
|
||||
if (!empty($item['children']) && is_array($item['children'])) {
|
||||
$this->processDepartments($item['children']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存部门数据到数据库
|
||||
* @param array $item 部门数据
|
||||
*/
|
||||
private function saveDepartment($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => isset($item['id']) ? $item['id'] : 0,
|
||||
'name' => isset($item['name']) ? $item['name'] : '',
|
||||
'memo' => isset($item['memo']) ? $item['memo'] : '',
|
||||
'level' => isset($item['level']) ? $item['level'] : 0,
|
||||
'isTop' => isset($item['isTop']) ? $item['isTop'] : false,
|
||||
'parentId' => isset($item['parentId']) ? $item['parentId'] : 0,
|
||||
'tenantId' => isset($item['tenantId']) ? $item['tenantId'] : 0,
|
||||
'privileges' => isset($item['privileges']) ? (is_array($item['privileges']) ? json_encode($item['privileges']) : $item['privileges']) : '',
|
||||
'createTime' => isset($item['createTime']) ? strtotime($item['createTime']) : 0,
|
||||
'lastUpdateTime' => isset($item['lastUpdateTime']) ? ($item['lastUpdateTime'] == '0001-01-01T00:00:00' ? 0 : strtotime($item['lastUpdateTime'])) : 0
|
||||
];
|
||||
|
||||
// 使用id作为唯一性判断
|
||||
$department = CompanyModel::where('id', $item['id'])->find();
|
||||
if ($department) {
|
||||
$department->save($data);
|
||||
} else {
|
||||
CompanyModel::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存账号数据到数据库
|
||||
* @param array $item 账号数据
|
||||
*/
|
||||
private function saveAccount($item)
|
||||
{
|
||||
// 将日期时间字符串转换为时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null;
|
||||
$deleteTime = isset($item['deleteTime']) ? strtotime($item['deleteTime']) : null;
|
||||
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'accountType' => isset($item['accountType']) ? $item['accountType'] : 0,
|
||||
'status' => isset($item['status']) ? $item['status'] : 0,
|
||||
'tenantId' => isset($item['tenantId']) ? $item['tenantId'] : 0,
|
||||
'userName' => isset($item['userName']) ? $item['userName'] : '',
|
||||
'realName' => isset($item['realName']) ? $item['realName'] : '',
|
||||
'nickname' => isset($item['nickname']) ? $item['nickname'] : '',
|
||||
'avatar' => isset($item['avatar']) ? $item['avatar'] : '',
|
||||
'phone' => isset($item['phone']) ? $item['phone'] : '',
|
||||
'memo' => isset($item['memo']) ? $item['memo'] : '',
|
||||
'createTime' => $createTime,
|
||||
'creator' => isset($item['creator']) ? $item['creator'] : 0,
|
||||
'creatorUserName' => isset($item['creatorUserName']) ? $item['creatorUserName'] : '',
|
||||
'creatorRealName' => isset($item['creatorRealName']) ? $item['creatorRealName'] : '',
|
||||
'departmentId' => isset($item['departmentId']) ? $item['departmentId'] : 0,
|
||||
'departmentName' => isset($item['departmentName']) ? $item['departmentName'] : '',
|
||||
'privilegeIds' => isset($item['privilegeIds']) ? json_encode($item['privilegeIds']) : json_encode([]),
|
||||
'alive' => isset($item['alive']) ? $item['alive'] : false,
|
||||
'hasXiakeAccount' => isset($item['hasXiakeAccount']) ? $item['hasXiakeAccount'] : false,
|
||||
'isDeleted' => isset($item['isDeleted']) ? $item['isDeleted'] : false,
|
||||
'deleteTime' => $deleteTime
|
||||
];
|
||||
|
||||
// 使用tenantId作为唯一性判断
|
||||
$account = CompanyAccountModel::where('id', $item['id'])->find();
|
||||
if ($account) {
|
||||
$account->save($data);
|
||||
} else {
|
||||
CompanyAccountModel::create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
362
application/api/controller/AllotRuleController.php
Normal file
362
application/api/controller/AllotRuleController.php
Normal file
@@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\CompanyAccountModel;
|
||||
use app\api\model\AllotRuleModel;
|
||||
use app\api\model\WechatAccountModel;
|
||||
use think\Queue;
|
||||
use app\job\AllotRuleListJob;
|
||||
use think\Db;
|
||||
|
||||
class AllotRuleController extends BaseController
|
||||
{
|
||||
/************************************
|
||||
* 分配规则列表和数据同步相关方法
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 获取所有分配规则
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getAllRules($data = [], $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取所有分配规则
|
||||
$result = requestCurl($this->baseUrl . 'api/AllotRule/all', [], 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
AllotRuleModel::where('1=1')->update(['isDel' => 1]);
|
||||
foreach ($response as $item) {
|
||||
$this->saveAllotRule($item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => 'success', 'data' => $response]);
|
||||
} else {
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取分配规则失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取分配规则失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发分配规则同步任务
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function startJob()
|
||||
{
|
||||
try {
|
||||
$data = [
|
||||
'time' => time()
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 allotrule_list
|
||||
$isSuccess = Queue::push(AllotRuleListJob::class, $data, 'allotrule_list');
|
||||
|
||||
if ($isSuccess !== false) {
|
||||
return successJson([], '分配规则同步任务已添加到队列');
|
||||
} else {
|
||||
return errorJson('添加分配规则同步任务到队列失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('触发分配规则同步任务失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/************************************
|
||||
* 分配规则CRUD操作方法
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 创建分配规则
|
||||
* @param array $data 请求数据
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createRule($data = [], $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$wechatData = input('wechatData', '[]') ?: [];
|
||||
$priorityStrategy = input('priorityStrategy', '[]') ?: [];
|
||||
|
||||
// 构建请求数据
|
||||
$params = [
|
||||
'allotType' => input('allotType', 1),
|
||||
'kefuRange' => input('kefuRange', 5),
|
||||
'wechatRange' => input('wechatRange',3),
|
||||
'kefuData' => input('kefuData', '[]') ?: [],
|
||||
'wechatData' => $wechatData,
|
||||
'labels' => input('labels', '[]') ?: [],
|
||||
'priorityStrategy' => json_encode($priorityStrategy,256)
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求到微信接口
|
||||
$result = requestCurl($this->baseUrl . 'api/AllotRule/new', $params, 'POST', $header, 'json');
|
||||
if (empty($result)) {
|
||||
// 异步更新所有规则列表
|
||||
AllotRuleModel::where('1=1')->update(['isDel' => 1]);
|
||||
$this->getAllRules();
|
||||
$res = AllotRuleModel::where('isDel',0)->order('id','DESC')->find();
|
||||
$res->departmentId = !empty($data['departmentId']) ? $data['departmentId'] : 0;
|
||||
$res->save();
|
||||
return successJson($res, '创建分配规则成功');
|
||||
} else {
|
||||
return errorJson($result);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('创建分配规则失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分配规则
|
||||
* @param array $data 请求数据
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateRule($data = [], $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$id = !empty($data['id']) ? $data['id'] : input('id',0);
|
||||
if (empty($id)) {
|
||||
return errorJson('规则ID不能为空');
|
||||
}
|
||||
|
||||
$rule = AllotRuleModel::where('id', $id)->find();
|
||||
if (empty($rule)) {
|
||||
return errorJson('规则不存在');
|
||||
}
|
||||
|
||||
// 构建请求数据
|
||||
$params = [
|
||||
'id' => $id,
|
||||
'tenantId' => $rule['tenantId'],
|
||||
'allotType' => !empty($data['allotType']) ? $data['allotType'] : input('allotType',1),
|
||||
'allotOnline' => !empty($data['allotOnline']) ? $data['allotOnline'] : input('allotOnline',false),
|
||||
'kefuRange' => !empty($data['kefuRange']) ? $data['kefuRange'] : input('kefuRange',5),
|
||||
'wechatRange' => !empty($data['wechatRange']) ? $data['wechatRange'] : input('wechatRange',3),
|
||||
'kefuData' => !empty($data['kefuData']) ? $data['kefuData'] : input('kefuData',[]),
|
||||
'wechatData' => !empty($data['wechatData']) ? $data['wechatData'] : input('wechatData',[]),
|
||||
'labels' => !empty($data['labels']) ? $data['labels'] : input('labels',[]),
|
||||
'priorityStrategy' => json_encode(!empty($data['priorityStrategy']) ? $data['priorityStrategy'] : input('priorityStrategy',[]),256),
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求到微信接口
|
||||
$result = requestCurl($this->baseUrl . 'api/AllotRule/update', $params, 'PUT', $header, 'json');
|
||||
|
||||
if (empty($result)) {
|
||||
$this->getAllRules();
|
||||
return successJson([], '更新分配规则成功');
|
||||
} else {
|
||||
return errorJson($result);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('更新分配规则失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分配规则
|
||||
* @param array $data 请求数据
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteRule($data = [], $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$id = !empty($data['id']) ? $data['id'] : input('id', 0);
|
||||
if (empty($id)) {
|
||||
return errorJson('规则ID不能为空');
|
||||
}
|
||||
|
||||
// 检查规则是否存在
|
||||
$rule = AllotRuleModel::where('id', $id)->find();
|
||||
if (empty($rule)) {
|
||||
return errorJson('规则不存在');
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求到微信接口
|
||||
$result = requestCurl($this->baseUrl . 'api/AllotRule/delete?id=' . $id, [], 'DELETE', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if (empty($response)) {
|
||||
// 删除成功,同步本地数据库
|
||||
AllotRuleModel::where('id', $id)->update(['isDel' => 1]);
|
||||
return successJson([], '删除分配规则成功');
|
||||
} else {
|
||||
return errorJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('删除分配规则失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/************************************
|
||||
* 数据查询相关方法
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 自动创建分配规则
|
||||
* 根据今日新增微信账号自动创建或更新分配规则
|
||||
* @param array $data 请求数据
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json|string
|
||||
*/
|
||||
public function autoCreateAllotRules($data = [], $isInner = false)
|
||||
{
|
||||
try {
|
||||
// 获取今天的开始时间和结束时间
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 查询今天新增的微信账号
|
||||
$newAccounts = Db::table('s2_wechat_account')
|
||||
->alias('wa')
|
||||
->join(['s2_company_account' => 'ca'], 'wa.deviceAccountId = ca.id', 'LEFT')
|
||||
->field([
|
||||
'wa.id',
|
||||
'wa.wechatId',
|
||||
'wa.nickname',
|
||||
'wa.deviceAccountId',
|
||||
'wa.alias',
|
||||
'wa.createTime',
|
||||
'ca.departmentId',
|
||||
])
|
||||
->where('wa.createTime', 'BETWEEN', [$todayStart, $todayEnd])
|
||||
->where('wa.isDeleted', 0)
|
||||
->order('wa.createTime', 'DESC')
|
||||
->select();
|
||||
|
||||
// 没有今日新增微信账号,直接返回
|
||||
if (empty($newAccounts)) {
|
||||
$result = ['code' => 200, 'msg' => '没有今日新增微信账号,无需创建分配规则', 'data' => []];
|
||||
return $isInner ? json_encode($result) : successJson([], '没有今日新增微信账号,无需创建分配规则');
|
||||
}
|
||||
|
||||
// 获取所有分配规则
|
||||
foreach ($newAccounts as $key => $value) {
|
||||
$rules = AllotRuleModel::where(['departmentId' => $value['departmentId'],'isDel' => 0])->order('id','DESC')->find();
|
||||
if (!empty($rules)) {
|
||||
$wechatData = json_decode($rules['wechatData'], true);
|
||||
if (!in_array($value['id'], $wechatData)) {
|
||||
$wechatData[] = $value['id'];
|
||||
$kefuData = [$value['deviceAccountId']];
|
||||
$this->updateRule(['id' => $rules['id'],'wechatData' => $wechatData,'kefuData' => $kefuData],true);
|
||||
}
|
||||
}else{
|
||||
$wechatData =[$value['id']];
|
||||
$kefuData = [$value['deviceAccountId']];
|
||||
$this->createRule(['wechatData' => $wechatData,'kefuData' => $kefuData,'departmentId' => $value['departmentId']],true);
|
||||
}
|
||||
}
|
||||
$result = ['code' => 200, 'msg' => '自动分配规则成功', 'data' => []];
|
||||
return $isInner ? json_encode($result) : successJson([], '自动分配规则成功');
|
||||
} catch (\Exception $e) {
|
||||
$error = '自动分配规则失败: ' . $e->getMessage();
|
||||
$result = ['code' => 500, 'msg' => $error];
|
||||
return $isInner ? json_encode($result) : errorJson($error);
|
||||
}
|
||||
}
|
||||
|
||||
/************************************
|
||||
* 辅助方法
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 保存分配规则数据到数据库
|
||||
* @param array $item 分配规则数据
|
||||
*/
|
||||
private function saveAllotRule($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => isset($item['id']) ? $item['id'] : '',
|
||||
'tenantId' => isset($item['tenantId']) ? $item['tenantId'] : 0,
|
||||
'allotType' => isset($item['allotType']) ? $item['allotType'] : 0,
|
||||
'allotOnline' => isset($item['allotOnline']) ? $item['allotOnline'] : false,
|
||||
'kefuRange' => isset($item['kefuRange']) ? $item['kefuRange'] : 0,
|
||||
'wechatRange' => isset($item['wechatRange']) ? $item['wechatRange'] : 0,
|
||||
'kefuData' => isset($item['kefuData']) ? json_encode($item['kefuData']) : json_encode([]),
|
||||
'wechatData' => isset($item['wechatData']) ? json_encode($item['wechatData']) : json_encode([]),
|
||||
'labels' => isset($item['labels']) ? json_encode($item['labels']) : json_encode([]),
|
||||
'priorityStrategy' => isset($item['priorityStrategy']) ? json_encode($item['priorityStrategy']) : json_encode([]),
|
||||
'sortIndex' => isset($item['sortIndex']) ? $item['sortIndex'] : 0,
|
||||
'creatorAccountId' => isset($item['creatorAccountId']) ? $item['creatorAccountId'] : 0,
|
||||
'createTime' => isset($item['createTime']) ? (strtotime($item['createTime']) ?: 0) : 0,
|
||||
'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : '',
|
||||
'isDel' => 0,
|
||||
];
|
||||
|
||||
// 使用ID作为唯一性判断
|
||||
$rule = AllotRuleModel::where('id', $item['id'])->find();
|
||||
|
||||
if ($rule) {
|
||||
$rule->save($data);
|
||||
} else {
|
||||
AllotRuleModel::create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
346
application/api/controller/AutomaticAssign.php
Normal file
346
application/api/controller/AutomaticAssign.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\CompanyAccountModel;
|
||||
use app\api\model\CompanyModel;
|
||||
use Library\S2\Logics\AccountLogic;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 账号管理控制器
|
||||
* 包含账号管理和部门管理的相关功能
|
||||
*/
|
||||
class AutomaticAssign extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 自动分配微信好友
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function autoAllotWechatFriend($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', '');
|
||||
$wechatAccountKeyword = !empty($data['wechatAccountKeyword']) ? $data['wechatAccountKeyword'] : input('wechatAccountKeyword', '');
|
||||
$isDeleted = !empty($data['isDeleted']) ? $data['isDeleted'] : input('isDeleted', false);
|
||||
|
||||
if (empty($toAccountId)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'目标账号ID不能为空']);
|
||||
}else{
|
||||
return errorJson('目标账号ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
$params = [
|
||||
'accountKeyword' => !empty($data['accountKeyword']) ? $data['accountKeyword'] : '',
|
||||
'addFrom' => !empty($data['addFrom']) ? $data['addFrom'] : [],
|
||||
'allotAccountId' => !empty($data['allotAccountId']) ? $data['allotAccountId'] : '',
|
||||
'containAllLabel' => !empty($data['containAllLabel']) ? $data['containAllLabel'] : false,
|
||||
'containSubDepartment' => !empty($data['containSubDepartment']) ? $data['containSubDepartment'] : false,
|
||||
'departmentId' => !empty($data['departmentId']) ? $data['departmentId'] : '',
|
||||
'extendFields' => !empty($data['extendFields']) ? $data['extendFields'] : [],
|
||||
'friendKeyword' => !empty($data['friendKeyword']) ? $data['friendKeyword'] : '',
|
||||
'friendPhoneKeyword' => !empty($data['friendPhoneKeyword']) ? $data['friendPhoneKeyword'] : '',
|
||||
'friendPinYinKeyword' => !empty($data['friendPinYinKeyword']) ? $data['friendPinYinKeyword'] : '',
|
||||
'friendRegionKeyword' => !empty($data['friendRegionKeyword']) ? $data['friendRegionKeyword'] : '',
|
||||
'friendRemarkKeyword' => !empty($data['friendRemarkKeyword']) ? $data['friendRemarkKeyword'] : '',
|
||||
'gender' => !empty($data['gender']) ? $data['gender'] : '',
|
||||
'groupId' => !empty($data['groupId']) ? $data['groupId'] : null,
|
||||
'isByRule' => !empty($data['isByRule']) ? $data['isByRule'] : false,
|
||||
'isDeleted' => $isDeleted,
|
||||
'isPass' => !empty($data['isPass']) ? $data['isPass'] : true,
|
||||
'keyword' => !empty($data['keyword']) ? $data['keyword'] : '',
|
||||
'labels' => !empty($data['labels']) ? $data['labels'] : [],
|
||||
'pageIndex' => !empty($data['pageIndex']) ? $data['pageIndex'] : 0,
|
||||
'pageSize' => !empty($data['pageSize']) ? $data['pageSize'] : 100,
|
||||
'preFriendId' => !empty($data['preFriendId']) ? $data['preFriendId'] : '',
|
||||
'toAccountId' => $toAccountId,
|
||||
'wechatAccountKeyword' => $wechatAccountKeyword
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatFriend/allotSearchResult', $params, 'PUT', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if($response){
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'微信好友自动分配成功']);
|
||||
}else{
|
||||
return successJson([],'微信好友自动分配成功');
|
||||
}
|
||||
}else{
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>$response]);
|
||||
}else{
|
||||
return errorJson($response);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'微信好友自动分配失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('微信好友自动分配失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 自动分配微信群聊
|
||||
* @param string $toAccountId 目标账号ID
|
||||
* @param string $wechatAccountKeyword 微信账号关键字
|
||||
* @param bool $isDeleted 是否已删除
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function autoAllotWechatChatroom($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', '');
|
||||
$wechatAccountKeyword = !empty($data['wechatAccountKeyword']) ? $data['wechatAccountKeyword'] : input('wechatAccountKeyword', '');
|
||||
$isDeleted = !empty($data['isDeleted']) ? $data['isDeleted'] : input('isDeleted', false);
|
||||
|
||||
if (empty($toAccountId)) {
|
||||
return errorJson('目标账号ID不能为空');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'AllotBySearch' => true,
|
||||
'byRule' => false,
|
||||
'comment' => '',
|
||||
'groupId' => null,
|
||||
'isDeleted' => $isDeleted,
|
||||
'keyword' => '',
|
||||
'memberKeyword' => '',
|
||||
'notifyReceiver' => false,
|
||||
'toAccountId' => $toAccountId,
|
||||
'wechatAccountKeyword' => $wechatAccountKeyword,
|
||||
'wechatChatroomId' => 0,
|
||||
'wechatChatroomIds' => []
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatChatroom/allotChatroom', $params, 'PUT', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if($response){
|
||||
return successJson([], '微信群聊自动分配成功');
|
||||
}else{
|
||||
return errorJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('微信群聊自动分配失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定微信好友分配到指定账号
|
||||
* @param int $wechatFriendId 微信好友ID
|
||||
* @param int $toAccountId 目标账号ID
|
||||
* @param string $comment 评论/备注
|
||||
* @param bool $notifyReceiver 是否通知接收者
|
||||
* @param int $optFrom 操作来源
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function allotWechatFriend($data = [],$isInner = false,$errorNum = 0)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$wechatFriendId = !empty($data['wechatFriendId']) ? $data['wechatFriendId'] : input('wechatFriendId', 0);
|
||||
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', 0);
|
||||
$comment = !empty($data['comment']) ? $data['comment'] : input('comment', '');
|
||||
$notifyReceiver = !empty($data['notifyReceiver']) ? $data['notifyReceiver'] : input('notifyReceiver', 'false');
|
||||
$optFrom = !empty($data['optFrom']) ? $data['optFrom'] : input('optFrom', 4); // 默认操作来源为4
|
||||
|
||||
// 参数验证
|
||||
if (empty($wechatFriendId)) {
|
||||
return json_encode(['code'=>500,'msg'=>'微信好友ID不能为空']);
|
||||
|
||||
}
|
||||
|
||||
if (empty($toAccountId)) {
|
||||
return json_encode(['code'=>500,'msg'=>'目标账号ID不能为空']);
|
||||
}
|
||||
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$url = $this->baseUrl . 'api/WechatFriend/allot?wechatFriendId='.$wechatFriendId.'¬ifyReceiver='.$notifyReceiver.'&comment='.$comment.'&toAccountId='.$toAccountId.'&optFrom='.$optFrom;
|
||||
$result = requestCurl($url, [], 'PUT', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
if (empty($response)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'微信好友分配成功']);
|
||||
}else{
|
||||
return successJson([], '微信好友分配成功');
|
||||
}
|
||||
} else {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>$result]);
|
||||
}else{
|
||||
return errorJson($result);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'微信好友分配失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
Cache::rm('system_authorization_token');
|
||||
Cache::rm('system_refresh_token');
|
||||
$errorNum ++;
|
||||
if ($errorNum <= 3) {
|
||||
$this->allotWechatFriend($data,$isInner,$errorNum);
|
||||
}
|
||||
return json_encode(['code'=>500,'msg'=> $result]);
|
||||
return errorJson('微信好友分配失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function multiAllotFriendToAccount($data = [],$errorNum = 0){
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}
|
||||
|
||||
$wechatFriendIds = !empty($data['wechatFriendIds']) ? $data['wechatFriendIds'] : input('wechatFriendIds', []);
|
||||
$toAccountId = !empty($data['toAccountId']) ? $data['toAccountId'] : input('toAccountId', 0);
|
||||
$notifyReceiver = !empty($data['notifyReceiver']) ? $data['notifyReceiver'] : input('notifyReceiver', 'false');
|
||||
// 参数验证
|
||||
if (empty($wechatFriendIds)) {
|
||||
return json_encode(['code'=>500,'msg'=>'微信好友ID不能为空']);
|
||||
}
|
||||
|
||||
if (empty($toAccountId)) {
|
||||
return json_encode(['code'=>500,'msg'=>'目标账号ID不能为空']);
|
||||
}
|
||||
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$url = $this->baseUrl . 'api/WechatFriend/multiAllotFriendToAccount?wechatFriendIds='.$wechatFriendIds.'&toAccountId='.$toAccountId.'¬ifyReceiver='.$notifyReceiver;
|
||||
$result = requestCurl($url, [], 'PUT', $header, 'json');
|
||||
if (empty($result)) {
|
||||
return json_encode(['code'=>200,'msg'=>'微信好友分配成功']);
|
||||
} else {
|
||||
Cache::rm('system_authorization_token');
|
||||
Cache::rm('system_refresh_token');
|
||||
$errorNum ++;
|
||||
if ($errorNum <= 3) {
|
||||
$this->multiAllotFriendToAccount($data,$errorNum);
|
||||
}
|
||||
return json_encode(['code'=>500,'msg'=> $result]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分配搜索结果
|
||||
* @param array $data 请求参数
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function allotSearchResult($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
$params = [
|
||||
'accountKeyword' => !empty($data['accountKeyword']) ? $data['accountKeyword'] : '',
|
||||
'addFrom' => !empty($data['addFrom']) ? $data['addFrom'] : [],
|
||||
'allotAccountId' => !empty($data['allotAccountId']) ? $data['allotAccountId'] : '',
|
||||
'containAllLabel' => !empty($data['containAllLabel']) ? $data['containAllLabel'] : false,
|
||||
'containSubDepartment' => !empty($data['containSubDepartment']) ? $data['containSubDepartment'] : false,
|
||||
'departmentId' => !empty($data['departmentId']) ? $data['departmentId'] : '',
|
||||
'extendFields' => !empty($data['extendFields']) ? json_encode($data['extendFields']) : json_encode([]),
|
||||
'friendKeyword' => !empty($data['friendKeyword']) ? $data['friendKeyword'] : '',
|
||||
'friendPhoneKeyword' => !empty($data['friendPhoneKeyword']) ? $data['friendPhoneKeyword'] : '',
|
||||
'friendPinYinKeyword' => !empty($data['friendPinYinKeyword']) ? $data['friendPinYinKeyword'] : '',
|
||||
'friendRegionKeyword' => !empty($data['friendRegionKeyword']) ? $data['friendRegionKeyword'] : '',
|
||||
'friendRemarkKeyword' => !empty($data['friendRemarkKeyword']) ? $data['friendRemarkKeyword'] : '',
|
||||
'gender' => !empty($data['gender']) ? $data['gender'] : '',
|
||||
'groupId' => !empty($data['groupId']) ? $data['groupId'] : null,
|
||||
'isByRule' => !empty($data['isByRule']) ? $data['isByRule'] : false,
|
||||
'isDeleted' => !empty($data['isDeleted']) ? $data['isDeleted'] : false,
|
||||
'isPass' => !empty($data['isPass']) ? $data['isPass'] : true,
|
||||
'keyword' => !empty($data['keyword']) ? $data['keyword'] : '',
|
||||
'labels' => !empty($data['labels']) ? $data['labels'] : [],
|
||||
'pageIndex' => !empty($data['pageIndex']) ? $data['pageIndex'] : 0,
|
||||
'pageSize' => !empty($data['pageSize']) ? $data['pageSize'] : 20,
|
||||
'preFriendId' => !empty($data['preFriendId']) ? $data['preFriendId'] : '',
|
||||
'toAccountId' => !empty($data['toAccountId']) ? $data['toAccountId'] : '',
|
||||
'wechatAccountKeyword' => !empty($data['wechatAccountKeyword']) ? $data['wechatAccountKeyword'] : ''
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatFriend/allotSearchResult', $params, 'POST', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
if($response){
|
||||
return json_encode(['code'=>200,'msg'=>'分配成功']);
|
||||
}else{
|
||||
return json_encode(['code'=>500,'msg'=>$response]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code'=>500,'msg'=>'微信好友分配失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
26
application/api/controller/BaseController.php
Normal file
26
application/api/controller/BaseController.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\facade\Env;
|
||||
use app\common\service\AuthService;
|
||||
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* 令牌
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $token = '';
|
||||
protected $baseUrl;
|
||||
protected $authorization = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->baseUrl = Env::get('api.wechat_url');
|
||||
$this->authorization = AuthService::getSystemAuthorization();
|
||||
}
|
||||
}
|
||||
142
application/api/controller/CallRecordingController.php
Normal file
142
application/api/controller/CallRecordingController.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\CompanyAccountModel;
|
||||
use app\api\model\CompanyModel;
|
||||
use app\api\model\CallRecordingModel;
|
||||
use Library\S2\Logics\AccountLogic;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 通话记录控制器
|
||||
* 包含通话记录管理的相关功能
|
||||
*/
|
||||
class CallRecordingController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取通话记录列表
|
||||
* @param array $data 请求参数
|
||||
* @param bool $isInner 是否为定时任务调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($data = [], $isInner = false)
|
||||
{
|
||||
// 获取请求参数
|
||||
$keyword = !empty($data['keyword']) ? $data['keyword'] : '';
|
||||
$isCallOut = !empty($data['isCallOut']) ? $data['isCallOut'] : '';
|
||||
$secondMin = !empty($data['secondMin']) ? $data['secondMin'] : 0;
|
||||
$secondMax = !empty($data['secondMax']) ? $data['secondMax'] : 99999;
|
||||
$departmentIds = !empty($data['departmentIds']) ? $data['departmentIds'] : '';
|
||||
$pageIndex = !empty($data['pageIndex']) ? $data['pageIndex'] : 0;
|
||||
$pageSize = !empty($data['pageSize']) ? $data['pageSize'] : 100;
|
||||
$from = !empty($data['from']) ? $data['from'] : '2016-01-01 00:00:00';
|
||||
$to = !empty($data['to']) ? $data['to'] : '2025-08-31 00:00:00';
|
||||
$departmentId = !empty($data['departmentId']) ? $data['departmentId'] : '';
|
||||
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $keyword,
|
||||
'isCallOut' => $isCallOut,
|
||||
'secondMin' => $secondMin,
|
||||
'secondMax' => $secondMax,
|
||||
'departmentIds' => $departmentIds,
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize,
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'departmentId' => $departmentId
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取通话记录列表
|
||||
$result = requestCurl($this->baseUrl . 'api/CallRecording/list', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
$this->saveCallRecording($item);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => '获取通话记录列表成功', 'data' => $response]);
|
||||
} else {
|
||||
return successJson($response, '获取通话记录列表成功');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取通话记录列表失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取通话记录列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存通话记录数据到数据库
|
||||
* @param array $item 通话记录数据
|
||||
*/
|
||||
private function saveCallRecording($item)
|
||||
{
|
||||
// 将时间戳转换为秒级时间戳(API返回的是毫秒级)
|
||||
$beginTime = isset($item['beginTime']) ? intval($item['beginTime'] / 1000) : 0;
|
||||
$endTime = isset($item['endTime']) ? intval($item['endTime'] / 1000) : 0;
|
||||
$callBeginTime = isset($item['callBeginTime']) ? intval($item['callBeginTime'] / 1000) : 0;
|
||||
|
||||
// 将日期时间字符串转换为时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : 0;
|
||||
$lastUpdateTime = isset($item['lastUpdateTime']) ? strtotime($item['lastUpdateTime']) : 0;
|
||||
|
||||
$data = [
|
||||
'id' => isset($item['id']) ? $item['id'] : 0,
|
||||
'tenantId' => isset($item['tenantId']) ? $item['tenantId'] : 0,
|
||||
'deviceOwnerId' => isset($item['deviceOwnerId']) ? $item['deviceOwnerId'] : 0,
|
||||
'userName' => isset($item['userName']) ? $item['userName'] : '',
|
||||
'nickname' => isset($item['nickname']) ? $item['nickname'] : '',
|
||||
'realName' => isset($item['realName']) ? $item['realName'] : '',
|
||||
'deviceMemo' => isset($item['deviceMemo']) ? $item['deviceMemo'] : '',
|
||||
'fileName' => isset($item['fileName']) ? $item['fileName'] : '',
|
||||
'imei' => isset($item['imei']) ? $item['imei'] : '',
|
||||
'phone' => isset($item['phone']) ? $item['phone'] : '',
|
||||
'isCallOut' => isset($item['isCallOut']) ? $item['isCallOut'] : false,
|
||||
'beginTime' => $beginTime,
|
||||
'endTime' => $endTime,
|
||||
'audioUrl' => isset($item['audioUrl']) ? $item['audioUrl'] : '',
|
||||
'mp3AudioUrl' => isset($item['mp3AudioUrl']) ? $item['mp3AudioUrl'] : '',
|
||||
'callBeginTime' => $callBeginTime,
|
||||
'callLogId' => isset($item['callLogId']) ? $item['callLogId'] : 0,
|
||||
'callType' => isset($item['callType']) ? $item['callType'] : 0,
|
||||
'duration' => isset($item['duration']) ? $item['duration'] : 0,
|
||||
'skipReason' => isset($item['skipReason']) ? $item['skipReason'] : '',
|
||||
'skipUpload' => isset($item['skipUpload']) ? $item['skipUpload'] : false,
|
||||
'isDeleted' => isset($item['isDeleted']) ? $item['isDeleted'] : false,
|
||||
'createTime' => $createTime,
|
||||
'lastUpdateTime' => $lastUpdateTime
|
||||
];
|
||||
|
||||
// 使用id作为唯一性判断
|
||||
$callRecording = CallRecordingModel::where('id', $item['id'])->find();
|
||||
if ($callRecording) {
|
||||
$callRecording->save($data);
|
||||
} else {
|
||||
CallRecordingModel::create($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
769
application/api/controller/DeviceController.php
Normal file
769
application/api/controller/DeviceController.php
Normal file
@@ -0,0 +1,769 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\DeviceModel;
|
||||
use app\api\model\DeviceGroupModel;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
use think\facade\Env;
|
||||
use Endroid\QrCode\QrCode;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
|
||||
class DeviceController extends BaseController
|
||||
{
|
||||
/************************ 设备管理相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取设备列表
|
||||
* @param string $pageIndex 页码
|
||||
* @param string $pageSize 每页数量
|
||||
* @param bool $isInner 是否为内部调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($data = [],$isInner = false,$isDel = 0)
|
||||
{
|
||||
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 根据isDel设置对应的deleteType值
|
||||
$deleteType = 'unDeleted'; // 默认值
|
||||
if ($isDel == 1) {
|
||||
$deleteType = 'deleted';
|
||||
} elseif ($isDel == 2) {
|
||||
$deleteType = 'deletedAndStop';
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'accountId' => !empty($data['accountId']) ? $data['accountId'] : $this->request->param('accountId', ''),
|
||||
'keyword' => $this->request->param('keyword', ''),
|
||||
'imei' => $this->request->param('imei', ''),
|
||||
'groupId' => $this->request->param('groupId', ''),
|
||||
'brand' => $this->request->param('brand', ''),
|
||||
'model' => $this->request->param('model', ''),
|
||||
'deleteType' => $this->request->param('deleteType', $deleteType),
|
||||
'operatingSystem' => $this->request->param('operatingSystem', ''),
|
||||
'softwareVersion' => $this->request->param('softwareVersion', ''),
|
||||
'phoneAppVersion' => $this->request->param('phoneAppVersion', ''),
|
||||
'recorderVersion' => $this->request->param('recorderVersion', ''),
|
||||
'contactsVersion' => $this->request->param('contactsVersion', ''),
|
||||
'rooted' => $this->request->param('rooted', ''),
|
||||
'xPosed' => $this->request->param('xPosed', ''),
|
||||
'alive' => $this->request->param('alive', ''),
|
||||
'hasWechat' => $this->request->param('hasWechat', ''),
|
||||
'departmentId' => $this->request->param('departmentId', ''),
|
||||
'pageIndex' => !empty($data['pageIndex']) ? $data['pageIndex'] : $this->request->param('pageIndex', 0),
|
||||
'pageSize' => !empty($data['pageSize']) ? $data['pageSize'] : $this->request->param('pageSize', 20)
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取设备列表
|
||||
$result = requestCurl($this->baseUrl . 'api/device/pageResult', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
$this->saveDevice($item);
|
||||
}
|
||||
}
|
||||
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取设备列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取设备列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成设备二维码
|
||||
* @param int $accountId 账号ID
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addDevice($accountId = 0,$isInner = false)
|
||||
{
|
||||
if (empty($accountId)) {
|
||||
$accountId = $this->request->param('accountId', '');
|
||||
}
|
||||
|
||||
if (empty($accountId)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'账号ID不能为空']);
|
||||
}else{
|
||||
return errorJson('账号ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取环境配置
|
||||
$tenantGuid = Env::get('api.guid', '');
|
||||
$deviceSocketHost = Env::get('api.deviceSocketHost', '');
|
||||
|
||||
if (empty($tenantGuid) || empty($deviceSocketHost)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'环境配置不完整,请检查api.guid和api.deviceSocketHost配置']);
|
||||
}else{
|
||||
return errorJson('环境配置不完整,请检查api.guid和api.deviceSocketHost配置');
|
||||
}
|
||||
}
|
||||
|
||||
// 构建设备配置数据
|
||||
$data = [
|
||||
'tenantGuid' => $tenantGuid,
|
||||
'deviceSocketHost' => $deviceSocketHost,
|
||||
'checkVersionUrl' => '',
|
||||
'accountId' => intval($accountId)
|
||||
];
|
||||
|
||||
// 将数据转换为JSON
|
||||
$jsonData = json_encode($data);
|
||||
|
||||
// 生成二维码图片
|
||||
$qrCode = $this->generateQrCodeImage($jsonData);
|
||||
|
||||
return successJson([
|
||||
'qrCode' => $qrCode,
|
||||
'config' => $data
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'生成设备二维码失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('生成设备二维码失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备账号
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateaccount($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取参数
|
||||
$id = !empty($data['id']) ? $data['id'] : $this->request->param('id', '');
|
||||
$accountId = !empty($data['accountId']) ? $data['accountId'] : $this->request->param('accountId', '');
|
||||
|
||||
if (empty($id)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'设备ID不能为空']);
|
||||
}else{
|
||||
return errorJson('设备ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($accountId)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'账号id不能为空']);
|
||||
}else{
|
||||
return errorJson('账号id不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/device/updateaccount?accountId=' . $accountId . '&deviceId=' . $id, [], 'PUT', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if(empty($response)){
|
||||
return successJson([],'操作成功');
|
||||
}else{
|
||||
return errorJson([],$response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'更新设备账号失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('更新设备账号失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备所属分组
|
||||
* @param int $id 设备ID
|
||||
* @param int $groupId 分组ID
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateDeviceToGroup($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取参数
|
||||
$id = !empty($data['id']) ? $data['id'] : $this->request->param('id', '');
|
||||
$groupId = !empty($data['groupId']) ? $data['groupId'] : $this->request->param('groupId', '');
|
||||
|
||||
if (empty($id)) {
|
||||
return errorJson('设备ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($groupId)) {
|
||||
return errorJson('分组ID不能为空');
|
||||
}
|
||||
|
||||
// 验证设备是否存在
|
||||
$device = DeviceModel::where('id', $id)->find();
|
||||
if (empty($device)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
|
||||
// 验证分组是否存在
|
||||
$group = DeviceGroupModel::where('id', $groupId)->find();
|
||||
if (empty($group)) {
|
||||
return errorJson('分组不存在');
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求到微信接口
|
||||
$result = requestCurl($this->baseUrl . 'api/device/updateDeviceGroup?id=' . $id . '&groupId=' . $groupId, [], 'PUT', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if (empty($response)) {
|
||||
// 更新成功,更新本地数据库
|
||||
$device->groupId = $groupId;
|
||||
$device->groupName = $group->groupName;
|
||||
$device->save();
|
||||
|
||||
return successJson([], '设备分组更新成功');
|
||||
} else {
|
||||
return errorJson([], $response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('更新设备分组失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除设备
|
||||
*
|
||||
* @param $deviceId
|
||||
* @return false|string
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function delDevice($deviceId = '')
|
||||
{
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}
|
||||
|
||||
if (empty($deviceId)) {
|
||||
return json_encode(['code'=>500,'msg'=>'删除的设备不能为空']);
|
||||
}
|
||||
|
||||
$device = Db::table('s2_device')->where('id', $deviceId)->find();
|
||||
if (empty($device)) {
|
||||
return json_encode(['code'=>500,'msg'=>'设备不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/device/del/'.$deviceId, [], 'DELETE', $header,'json');
|
||||
if (empty($result)) {
|
||||
Db::table('s2_device')->where('id', $deviceId)->update([
|
||||
'isDeleted' => 1,
|
||||
'deleteTime' => time()
|
||||
]);
|
||||
return json_encode(['code'=>200,'msg'=>'删除成功']);
|
||||
}else{
|
||||
return json_encode(['code'=>200,'msg'=>'删除失败']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code'=>500,'msg'=>'获取设备分组列表失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备联系人
|
||||
* @param int $id 设备ID
|
||||
* @param int $groupId 分组ID
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function importContact($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取参数
|
||||
$deviceId = !empty($data['deviceId']) ? $data['deviceId'] : $this->request->param('deviceId', '');
|
||||
$rawContactJson = !empty($data['contactJson']) ? $data['contactJson'] : $this->request->param('contactJson', '');
|
||||
$clearContact = !empty($data['clearContact']) ? $data['clearContact'] : $this->request->param('clearContact', false);
|
||||
|
||||
|
||||
if (empty($deviceId)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'设备ID不能为空']);
|
||||
}else{
|
||||
return errorJson('设备ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
$contacts = [];
|
||||
if (!empty($rawContactJson)) {
|
||||
if (is_string($rawContactJson)) {
|
||||
$decodedContacts = json_decode($rawContactJson, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
// It's a valid JSON string
|
||||
$contacts = $decodedContacts;
|
||||
} else {
|
||||
// It's not a JSON string, treat as multi-line text
|
||||
$lines = explode("\n", str_replace("\r\n", "\n", $rawContactJson));
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
$parts = explode(',', $line);
|
||||
if (count($parts) == 2) {
|
||||
$contacts[] = ['name' => trim($parts[0]), 'phone' => trim($parts[1])];
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (is_array($rawContactJson)) {
|
||||
// It's already an array
|
||||
$contacts = $rawContactJson;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (empty($contacts)){
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'更新设备联系人失败:通讯录不能为空' ]);
|
||||
}else{
|
||||
return errorJson('更新设备联系人失败:通讯录不能为空' );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Trim whitespace from name and phone in all cases
|
||||
if (!empty($contacts)) {
|
||||
foreach ($contacts as &$contact) {
|
||||
if (isset($contact['name'])) {
|
||||
$contact['name'] = trim($contact['name']);
|
||||
}
|
||||
if (isset($contact['phone'])) {
|
||||
$contact['phone'] = trim($contact['phone']);
|
||||
}
|
||||
}
|
||||
unset($contact); // Unset reference to the last element
|
||||
}
|
||||
|
||||
$contactJsonForApi = json_encode($contacts);
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'deviceId' => $deviceId,
|
||||
'contactJson' => $contactJsonForApi,
|
||||
'clearContact' => $clearContact
|
||||
];
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/device/importContact', $params, 'POST', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
if(empty($response)){
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'更新设备联系人成功' ]);
|
||||
}else{
|
||||
return successJson([],'更新设备联系人失败:通讯录不能为空' );
|
||||
}
|
||||
}else{
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=> $response ]);
|
||||
}else{
|
||||
return successJson([],$response );
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'更新设备联系人失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('更新设备联系人失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/************************ 设备分组相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取设备分组列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getGroupList($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/DeviceGroup/list', [], 'GET', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
foreach ($response as $item) {
|
||||
$this->saveDeviceGroup($item);
|
||||
}
|
||||
}
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取设备分组列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取设备分组列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建设备分组
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createGroup($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取参数
|
||||
$groupName = !empty($data['groupName']) ? $data['groupName'] : $this->request->param('groupName', '');
|
||||
$groupMemo = !empty($data['groupMemo']) ? $data['groupMemo'] : $this->request->param('groupMemo', '');
|
||||
|
||||
if (empty($groupName)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'分组名称不能为空']);
|
||||
}else{
|
||||
return errorJson('分组名称不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'groupName' => $groupName,
|
||||
'groupMemo' => $groupMemo
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/DeviceGroup/new', $params, 'POST', $header,'json');
|
||||
if(empty($result)){
|
||||
// $res = $this->getGroupList([],true);
|
||||
// $res = json_decode($res,true);
|
||||
// if(!empty($res['data'])){
|
||||
// $data = $res['data'][0];
|
||||
// }
|
||||
|
||||
$data = [];
|
||||
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$data]);
|
||||
}else{
|
||||
return successJson($data,'操作成功');
|
||||
}
|
||||
}else{
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=> $result]);
|
||||
}else{
|
||||
return errorJson($result);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'创建设备分组失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('创建设备分组失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新设备分组
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateDeviceGroup($data = [],$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取参数
|
||||
$id = !empty($data['id']) ? $data['id'] : $this->request->param('id', '');
|
||||
$groupName = !empty($data['groupName']) ? $data['groupName'] : $this->request->param('groupName', '');
|
||||
$groupMemo = !empty($data['groupMemo']) ? $data['groupMemo'] : $this->request->param('groupMemo', '');
|
||||
|
||||
if (empty($id)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'分组ID不能为空']);
|
||||
}else{
|
||||
return errorJson('分组ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($groupName)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'分组名称不能为空']);
|
||||
}else{
|
||||
return errorJson('分组名称不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
$group = DeviceGroupModel::where('id', $id)->find();
|
||||
if(empty($group)){
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'分组不存在']);
|
||||
}else{
|
||||
return errorJson('分组不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$isGroupName = DeviceGroupModel::where('groupName', $groupName)->find();
|
||||
if(!empty($isGroupName)){
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'分组名称已存在']);
|
||||
}else{
|
||||
return errorJson('分组名称已存在');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 从数据库获取对象后,创建一个正确格式的数组用于API请求
|
||||
$requestData = [
|
||||
'id' => $group->id,
|
||||
'tenantId' => $group->tenantId,
|
||||
'groupName' => $groupName,
|
||||
'groupMemo' => $groupMemo
|
||||
];
|
||||
|
||||
// 发送请求
|
||||
$result = requestCurl($this->baseUrl . 'api/DeviceGroup/update', $requestData, 'PUT', $header, 'json');
|
||||
if(empty($result)){
|
||||
$group->groupName = $groupName;
|
||||
$group->groupMemo = $groupMemo;
|
||||
$group->save();
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$group]);
|
||||
}else{
|
||||
return successJson($group,'操作成功');
|
||||
}
|
||||
}else{
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=> $result]);
|
||||
}else{
|
||||
return errorJson($result);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'更新设备分组失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('更新设备分组失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************ 私有辅助方法 ************************/
|
||||
|
||||
/**
|
||||
* 保存设备数据到数据库
|
||||
* @param array $item 设备数据
|
||||
*/
|
||||
private function saveDevice($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => isset($item['id']) ? $item['id'] : '',
|
||||
'userName' => isset($item['userName']) ? $item['userName'] : '',
|
||||
'nickname' => isset($item['nickname']) ? $item['nickname'] : '',
|
||||
'realName' => isset($item['realName']) ? $item['realName'] : '',
|
||||
'groupName' => isset($item['groupName']) ? $item['groupName'] : '',
|
||||
'wechatAccounts' => isset($item['wechatAccounts']) ? json_encode($item['wechatAccounts']) : json_encode([]),
|
||||
'alive' => isset($item['alive']) ? $item['alive'] : false,
|
||||
'lastAliveTime' => isset($item['lastAliveTime']) ? $item['lastAliveTime'] : null,
|
||||
'tenantId' => isset($item['tenantId']) ? $item['tenantId'] : 0,
|
||||
'groupId' => isset($item['groupId']) ? $item['groupId'] : 0,
|
||||
'currentAccountId' => isset($item['currentAccountId']) ? $item['currentAccountId'] : 0,
|
||||
'imei' => $item['imei'],
|
||||
'memo' => isset($item['memo']) ? $item['memo'] : '',
|
||||
'createTime' => isset($item['createTime']) ? strtotime($item['createTime']) : 0,
|
||||
'isDeleted' => isset($item['isDeleted']) ? $item['isDeleted'] : false,
|
||||
'deletedAndStop' => isset($item['deletedAndStop']) ? $item['deletedAndStop'] : false,
|
||||
'deleteTime' => empty($item['isDeleted']) ? 0 : strtotime($item['deleteTime']),
|
||||
'rooted' => isset($item['rooted']) ? $item['rooted'] : false,
|
||||
'xPosed' => isset($item['xPosed']) ? $item['xPosed'] : false,
|
||||
'brand' => isset($item['brand']) ? $item['brand'] : '',
|
||||
'model' => isset($item['model']) ? $item['model'] : '',
|
||||
'operatingSystem' => isset($item['operatingSystem']) ? $item['operatingSystem'] : '',
|
||||
'softwareVersion' => isset($item['softwareVersion']) ? $item['softwareVersion'] : '',
|
||||
'extra' => isset($item['extra']) ? json_encode($item['extra']) : json_encode([]),
|
||||
'phone' => isset($item['phone']) ? $item['phone'] : '',
|
||||
'lastUpdateTime' => isset($item['lastUpdateTime']) ? ($item['lastUpdateTime'] == '0001-01-01T00:00:00' ? 0 : strtotime($item['lastUpdateTime'])) : 0
|
||||
];
|
||||
|
||||
if (!empty($data['alive'])){
|
||||
$data['aliveTime'] = time();
|
||||
}
|
||||
|
||||
|
||||
// 使用imei作为唯一性判断
|
||||
$device = DeviceModel::where('id', $item['id'])->find();
|
||||
|
||||
if ($device) {
|
||||
$device->save($data);
|
||||
} else {
|
||||
|
||||
// autoLike:自动点赞
|
||||
// momentsSync:朋友圈同步
|
||||
// autoCustomerDev:自动开发客户
|
||||
// groupMessageDeliver:群消息推送
|
||||
// autoGroup:自动建群
|
||||
|
||||
$data['taskConfig'] = json_encode([
|
||||
'autoLike' => true,
|
||||
'momentsSync' => true,
|
||||
'autoCustomerDev' => true,
|
||||
'groupMessageDeliver' => true,
|
||||
'autoGroup' => true,
|
||||
]);
|
||||
DeviceModel::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存设备分组数据到数据库
|
||||
* @param array $item 设备分组数据
|
||||
*/
|
||||
private function saveDeviceGroup($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'tenantId' => $item['tenantId'],
|
||||
'groupName' => $item['groupName'],
|
||||
'groupMemo' => $item['groupMemo'],
|
||||
'count' => isset($item['count']) ? $item['count'] : 0,
|
||||
'createTime' => $item['createTime'] == '0001-01-01T00:00:00' ? 0 : strtotime($item['createTime'])
|
||||
];
|
||||
|
||||
// 使用ID作为唯一性判断
|
||||
$group = DeviceGroupModel::where('id', $item['id'])->find();
|
||||
|
||||
if ($group) {
|
||||
$group->save($data);
|
||||
} else {
|
||||
DeviceGroupModel::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成二维码图片(base64格式)
|
||||
* @param string $data 二维码数据
|
||||
* @return string base64编码的图片
|
||||
*/
|
||||
private function generateQrCodeImage($data)
|
||||
{
|
||||
// 使用endroid/qr-code 2.5版本生成二维码
|
||||
$qrCode = new QrCode($data);
|
||||
$qrCode->setSize(300);
|
||||
$qrCode->setMargin(10);
|
||||
$qrCode->setWriterByName('png');
|
||||
$qrCode->setEncoding('UTF-8');
|
||||
|
||||
// 使用枚举常量而不是字符串
|
||||
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
|
||||
|
||||
// 直接获取base64内容
|
||||
$base64 = 'data:image/png;base64,' . base64_encode($qrCode->writeString());
|
||||
|
||||
return $base64;
|
||||
}
|
||||
}
|
||||
192
application/api/controller/FriendTaskController.php
Normal file
192
application/api/controller/FriendTaskController.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\FriendTaskModel;
|
||||
use app\common\model\WechatRestricts;
|
||||
use think\facade\Request;
|
||||
|
||||
class FriendTaskController extends BaseController
|
||||
{
|
||||
/************************ 好友任务管理相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取添加好友记录列表
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页数量
|
||||
* @param bool $isInner 是否为定时任务调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($pageIndex, $pageSize, $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $this->request->param('keyword', ''),
|
||||
'status' => $this->request->param('status', ''),
|
||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : $this->request->param('pageIndex', 0),
|
||||
'pageSize' => !empty($pageSize) ? $pageSize : $this->request->param('pageSize', 20),
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取添加好友记录列表
|
||||
$result = requestCurl($this->baseUrl . 'api/AddFriendByPhoneTask/list', $params, 'GET', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
$this->saveFriendTask($item);
|
||||
}
|
||||
}
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'获取添加好友记录列表成功','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取添加好友记录列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取添加好友记录列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加好友任务
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addFriendTask($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization =$this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$phone = !empty($data['phone']) ? $data['phone'] : '';
|
||||
$message = !empty($data['message']) ? $data['message'] : '';
|
||||
$remark = !empty($data['remark']) ? $data['remark'] : '';
|
||||
$labels = !empty($data['labels']) ? $data['labels'] : '';
|
||||
$wechatAccountId = !empty($data['wechatAccountId']) ? $data['wechatAccountId'] : '';
|
||||
|
||||
// 参数验证
|
||||
if (empty($phone)) {
|
||||
return json_encode(['code'=>500,'msg'=>'手机号不能为空']);
|
||||
}
|
||||
|
||||
if (empty($wechatAccountId)) {
|
||||
return json_encode(['code'=>500,'msg'=>'微信号不能为空']);
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'phone' => $phone,
|
||||
'message' => $message,
|
||||
'remark' => $remark,
|
||||
'labels' => is_array($labels) ? $labels : [$labels],
|
||||
'wechatAccountId' => (int)$wechatAccountId
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求添加好友任务
|
||||
$result = requestCurl($this->baseUrl . 'api/AddFriendByPhoneTask/add', $params, 'POST', $header, 'json');
|
||||
|
||||
// 处理响应
|
||||
return json_encode(['code'=>200,'msg'=>'添加好友任务创建成功']);
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code'=>500,'msg'=> '添加好友任务失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/************************ 私有辅助方法 ************************/
|
||||
|
||||
/**
|
||||
* 保存添加好友记录到数据库
|
||||
* @param array $item 添加好友记录数据
|
||||
*/
|
||||
private function saveFriendTask($item)
|
||||
{
|
||||
// 将日期时间字符串转换为时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null;
|
||||
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'tenantId' => $item['tenantId'],
|
||||
'operatorAccountId' => $item['operatorAccountId'],
|
||||
'status' => $item['status'],
|
||||
'phone' => $item['phone'],
|
||||
'msgContent' => $item['msgContent'],
|
||||
'wechatAccountId' => $item['wechatAccountId'],
|
||||
'createTime' => $createTime,
|
||||
'remark' => $item['remark'],
|
||||
'extra' => $item['extra'],
|
||||
'labels' => $item['labels'],
|
||||
'from' => $item['from'],
|
||||
'alias' => $item['alias'],
|
||||
'wechatId' => $item['wechatId'],
|
||||
'wechatAvatar' => $item['wechatAvatar'],
|
||||
'wechatNickname' => $item['wechatNickname'],
|
||||
'accountNickname' => $item['accountNickname'],
|
||||
'accountRealName' => $item['accountRealName'],
|
||||
'accountUsername' => $item['accountUsername']
|
||||
];
|
||||
|
||||
// 使用taskId作为唯一性判断
|
||||
$task = FriendTaskModel::where('id', $item['id'])->find();
|
||||
if ($task) {
|
||||
$task->save($data);
|
||||
} else {
|
||||
FriendTaskModel::create($data);
|
||||
}
|
||||
|
||||
//创建非法记录
|
||||
if ($item['status'] == 2){
|
||||
$data = [
|
||||
'level' => 2,
|
||||
'taskId' => $item['id'],
|
||||
'reason' => '',
|
||||
'memo' => '',
|
||||
'wechatId' => $item['wechatId'],
|
||||
'companyId' => '',
|
||||
'restrictTime' => time(),
|
||||
'recoveryTime' => time() + 3600 * 72,
|
||||
];
|
||||
if (strpos('操作过于频繁', $item['extra']) !== false){
|
||||
$data['reason'] = '频繁添加好友';
|
||||
$data['memo'] = '操作过于频繁,请稍后再试';
|
||||
}
|
||||
|
||||
if (strpos('当前账号存在安全风险', $item['extra']) !== false){
|
||||
$data['reason'] = '账号风险';
|
||||
$data['memo'] = '当前账号存在安全风险,需先到「微信团队」进行安全验证后才能继续使用当前功能';
|
||||
}
|
||||
$res = WechatRestricts::where('taskId', $item['id'])->find();
|
||||
if (empty($res)) {
|
||||
WechatRestricts::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
584
application/api/controller/MessageController.php
Normal file
584
application/api/controller/MessageController.php
Normal file
@@ -0,0 +1,584 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\WechatMessageModel;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
class MessageController extends BaseController
|
||||
{
|
||||
/************************ 好友消息相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取微信好友列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getFriendsList($pageIndex = '',$pageSize = '',$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
$fromTime = $this->request->param('fromTime', date('Y-m-d 00:00:00', strtotime('-1 days')));
|
||||
$toTime = $this->request->param('toTime', date('Y-m-d 23:59:59'));
|
||||
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'chatroomKeyword' => $this->request->param('chatroomKeyword', ''),
|
||||
'friendKeyword' => $this->request->param('friendKeyword', ''),
|
||||
'friendPhoneKeyword' => $this->request->param('friendPhoneKeyword', ''),
|
||||
'friendPinYinKeyword' => $this->request->param('friendPinYinKeyword', ''),
|
||||
'friendRegionKeyword' => $this->request->param('friendRegionKeyword', ''),
|
||||
'friendRemarkKeyword' => $this->request->param('friendRemarkKeyword', ''),
|
||||
'groupId' => $this->request->param('groupId', null),
|
||||
'kefuId' => $this->request->param('kefuId', null),
|
||||
'labels' => $this->request->param('labels', []),
|
||||
'msgFrom' => $fromTime,
|
||||
'msgKeyword' => $this->request->param('msgKeyword', ''),
|
||||
'msgTo' => $toTime,
|
||||
'msgType' => $this->request->param('msgType', ''),
|
||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : input('pageIndex', 0),
|
||||
'pageSize' => !empty($pageSize) ? $pageSize : input('pageSize', 20),
|
||||
'reverse' => $this->request->param('reverse', false),
|
||||
'type' => $this->request->param('type', 'friend'),
|
||||
'wechatAccountIds' => $this->request->param('wechatAccountIds', [])
|
||||
];
|
||||
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取好友列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatFriend/listWechatFriendForMsgPagination', $params, 'POST', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
// 获取同步消息标志
|
||||
$syncMessages = $this->request->param('syncMessages', true);
|
||||
// 如果需要同步消息,则获取每个好友的消息
|
||||
if ($syncMessages && !empty($response['results'])) {
|
||||
$from = strtotime($fromTime) * 1000;
|
||||
$to = strtotime($toTime) * 1000;
|
||||
|
||||
|
||||
foreach ($response['results'] as &$friend) {
|
||||
// 构建获取消息的参数
|
||||
$messageParams = [
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 20,
|
||||
'messageId' => '',
|
||||
'olderData' => true,
|
||||
'wechatAccountId' => $friend['wechatAccountId'],
|
||||
'wechatFriendId' => $friend['wechatFriendId'],
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
|
||||
// 调用获取消息的接口
|
||||
$messageResult = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $messageParams, 'GET', $header, 'json');
|
||||
$messageResponse = handleApiResponse($messageResult);
|
||||
// 保存消息到数据库
|
||||
if (!empty($messageResponse)) {
|
||||
foreach ($messageResponse as $item) {
|
||||
$this->saveMessage($item);
|
||||
}
|
||||
}
|
||||
|
||||
// 将消息列表添加到好友数据中
|
||||
$friend['messages'] = $messageResponse ?? [];
|
||||
}
|
||||
unset($friend);
|
||||
}
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'获取好友列表成功','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取好友列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取好友列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户聊天记录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMessageList()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $this->request->param('keyword', ''),
|
||||
'msgType' => $this->request->param('msgType', ''),
|
||||
'accountId' => $this->request->param('accountId', ''),
|
||||
'count' => $this->request->param('count', 100),
|
||||
'messageId' => $this->request->param('messageId', ''),
|
||||
'olderData' => $this->request->param('olderData', true),
|
||||
'wechatAccountId' => $this->request->param('wechatAccountId', ''),
|
||||
'wechatFriendId' => $this->request->param('wechatFriendId', ''),
|
||||
'from' => $this->request->param('from', ''),
|
||||
'to' => $this->request->param('to', ''),
|
||||
'searchFrom' => $this->request->param('searchFrom', 'admin')
|
||||
];
|
||||
|
||||
// 参数验证
|
||||
if (empty($params['wechatAccountId'])) {
|
||||
return errorJson('微信账号ID不能为空');
|
||||
}
|
||||
if (empty($params['wechatFriendId'])) {
|
||||
return errorJson('好友ID不能为空');
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取聊天记录
|
||||
$result = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
foreach ($response as $item) {
|
||||
$this->saveMessage($item);
|
||||
}
|
||||
}
|
||||
|
||||
return successJson($response);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取聊天记录失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/************************ 群聊消息相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取微信群聊列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getChatroomList($pageIndex = '',$pageSize = '',$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
$fromTime = $this->request->param('fromTime', date('Y-m-d 00:00:00', strtotime('-1 days')));
|
||||
$toTime = $this->request->param('toTime', date('Y-m-d 23:59:59'));
|
||||
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'chatroomKeyword' => $this->request->param('chatroomKeyword', ''),
|
||||
'friendKeyword' => $this->request->param('friendKeyword', ''),
|
||||
'friendInKeyword' => $this->request->param('friendInKeyword', ''),
|
||||
'friendInTimeKeyword' => $this->request->param('friendInTimeKeyword', ''),
|
||||
'friendOutKeyword' => $this->request->param('friendOutKeyword', ''),
|
||||
'friendRemarkKeyword' => $this->request->param('friendRemarkKeyword', ''),
|
||||
'groupId' => $this->request->param('groupId', null),
|
||||
'kefuId' => $this->request->param('kefuId', null),
|
||||
'labels' => $this->request->param('labels', []),
|
||||
'msgFrom' => $fromTime,
|
||||
'msgKeyword' => $this->request->param('msgKeyword', ''),
|
||||
'msgTo' => $toTime,
|
||||
'msgType' => $this->request->param('msgType', ''),
|
||||
'pageIndex' => $this->request->param('pageIndex', 0),
|
||||
'pageSize' => $this->request->param('pageSize', 100),
|
||||
'reverse' => $this->request->param('reverse', false),
|
||||
'type' => $this->request->param('type', 'chatroom'),
|
||||
'wechatAccountIds' => $this->request->param('wechatAccountIds', [])
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取群聊列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatChatroom/listWechatChatroomForMsgPagination', $params, 'POST', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 获取同步消息标志
|
||||
$syncMessages = $this->request->param('syncMessages', true);
|
||||
|
||||
// 如果需要同步消息,则获取每个群的消息
|
||||
if ($syncMessages && !empty($response)) {
|
||||
$from = strtotime($fromTime) * 1000;
|
||||
$to = strtotime($toTime) * 1000;
|
||||
foreach ($response['results'] as &$chatroom) {
|
||||
|
||||
// 构建获取消息的参数
|
||||
$messageParams = [
|
||||
'keyword' => '',
|
||||
'msgType' =>'',
|
||||
'accountId' => '',
|
||||
'count' => 20,
|
||||
'messageId' => '',
|
||||
'olderData' => true,
|
||||
'wechatId' => '',
|
||||
'wechatAccountId' => $chatroom['wechatAccountId'],
|
||||
'wechatChatroomId' => $chatroom['wechatChatroomId'],
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
|
||||
// 调用获取消息的接口
|
||||
$messageResult = requestCurl($this->baseUrl . 'api/ChatroomMessage/searchMessage', $messageParams, 'GET', $header, 'json');
|
||||
$messageResponse = handleApiResponse($messageResult);
|
||||
|
||||
// 保存消息到数据库
|
||||
if (!empty($messageResponse)) {
|
||||
foreach ($messageResponse as $item) {
|
||||
$this->saveChatroomMessage($item);
|
||||
}
|
||||
}
|
||||
|
||||
// 将消息列表添加到群聊数据中
|
||||
$chatroom['messages'] = $messageResponse ?? [];
|
||||
}
|
||||
unset($chatroom);
|
||||
}
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'获取群聊列表成功','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取群聊列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取群聊列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群聊消息列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getChatroomMessages()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $this->request->param('keyword', ''),
|
||||
'msgType' => $this->request->param('msgType', ''),
|
||||
'accountId' => $this->request->param('accountId', ''),
|
||||
'count' => $this->request->param('count', 100),
|
||||
'messageId' => $this->request->param('messageId', ''),
|
||||
'olderData' => $this->request->param('olderData', true),
|
||||
'wechatId' => $this->request->param('wechatId', ''),
|
||||
'wechatAccountId' => $this->request->param('wechatAccountId', ''),
|
||||
'wechatChatroomId' => $this->request->param('wechatChatroomId', ''),
|
||||
'from' => $this->request->param('from', strtotime(date('Y-m-d 00:00:00', strtotime('-1 days')))),
|
||||
'to' => $this->request->param('to', strtotime(date('Y-m-d 00:00:00'))),
|
||||
'searchFrom' => $this->request->param('searchFrom', 'admin')
|
||||
];
|
||||
|
||||
// 参数验证
|
||||
if (empty($params['wechatAccountId'])) {
|
||||
return errorJson('微信账号ID不能为空');
|
||||
}
|
||||
if (empty($params['wechatChatroomId'])) {
|
||||
return errorJson('群聊ID不能为空');
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取群聊消息
|
||||
$result = requestCurl($this->baseUrl . 'api/ChatroomMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
foreach ($response as $item) {
|
||||
$res = $this->saveChatroomMessage($item);
|
||||
if(!$res){
|
||||
return errorJson('保存群聊消息失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return successJson($response);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取群聊消息失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/************************ 私有辅助方法 ************************/
|
||||
|
||||
/**
|
||||
* 保存消息记录到数据库
|
||||
* @param array $item 消息记录数据
|
||||
*/
|
||||
public function saveMessage($item)
|
||||
{
|
||||
// 检查消息是否已存在
|
||||
$exists = WechatMessageModel::where('id', $item['id']) ->find();
|
||||
|
||||
if (!empty($exists) && $exists['sendStatus'] == 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 将毫秒时间戳转换为秒级时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null;
|
||||
$deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : null;
|
||||
$wechatTime = isset($item['wechatTime']) ? floor($item['wechatTime'] / 1000) : null;
|
||||
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'type' => 1,
|
||||
'accountId' => $item['accountId'],
|
||||
'content' => $item['content'],
|
||||
'createTime' => $createTime,
|
||||
'deleteTime' => $deleteTime,
|
||||
'isDeleted' => $item['isDeleted'] ?? false,
|
||||
'isSend' => $item['isSend'] ?? true,
|
||||
'msgId' => $item['msgId'],
|
||||
'msgSubType' => $item['msgSubType'] ?? 0,
|
||||
'msgSvrId' => $item['msgSvrId'] ?? '',
|
||||
'msgType' => $item['msgType'],
|
||||
'origin' => $item['origin'] ?? 0,
|
||||
'recallId' => $item['recallId'] ?? false,
|
||||
'sendStatus' => $item['sendStatus'] ?? 0,
|
||||
'synergyAccountId' => $item['synergyAccountId'] ?? 0,
|
||||
'tenantId' => $item['tenantId'],
|
||||
'wechatAccountId' => $item['wechatAccountId'],
|
||||
'wechatFriendId' => $item['wechatFriendId'],
|
||||
'wechatTime' => $wechatTime
|
||||
];
|
||||
|
||||
|
||||
//已被删除
|
||||
if ($item['msgType'] == 10000 && strpos($item['content'],'开启了朋友验证') !== false) {
|
||||
Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->update(['isDeleted'=> 1,'deleteTime' => $wechatTime]);
|
||||
}else{
|
||||
//优先分配在线客服
|
||||
$friend = Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->find();
|
||||
if (!empty($friend)){
|
||||
$accountId = $item['accountId'];
|
||||
$accountData = Db::table('s2_company_account')->where('id',$accountId)->find();
|
||||
if (!empty($accountData)){
|
||||
$account = new AccountController();
|
||||
$account->getlist(['pageIndex' => 0,'pageSize' => 100,'departmentId' => $accountData['departmentId']]);
|
||||
$accountIds = Db::table('s2_company_account')->where(['departmentId' => $accountData['departmentId'],'alive' => 1])->column('id');
|
||||
if (!empty($accountIds)){
|
||||
if (!in_array($friend['accountId'],$accountIds)){
|
||||
// 执行切换好友命令
|
||||
$randomKey = array_rand($accountIds, 1);
|
||||
$toAccountId = $accountIds[$randomKey];
|
||||
$toAccountData = Db::table('s2_company_account')->where('id',$toAccountId)->find();
|
||||
$automaticAssign = new AutomaticAssign();
|
||||
$automaticAssign->allotWechatFriend([
|
||||
'wechatFriendId' => $friend['id'],
|
||||
'toAccountId' => $toAccountId
|
||||
], true);
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('id',$friend['id'])
|
||||
->update([
|
||||
'accountId' => $toAccountId,
|
||||
'accountUserName' => $toAccountData['userName'],
|
||||
'accountRealName' => $toAccountData['realName'],
|
||||
'accountNickname' => $toAccountData['nickname'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$id = '';
|
||||
if (empty($exists)){
|
||||
// 创建新记录
|
||||
$res = WechatMessageModel::create($data);
|
||||
$id= $res['id'];
|
||||
}else{
|
||||
$id = $data['id'];
|
||||
unset($data['id']);
|
||||
$res = $exists->save($data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 1 文字 3图片 47动态图片 34语言 43视频 42名片 40/20链接 49文件
|
||||
if (!empty($res) && empty($item['isSend']) && in_array($item['msgType'],[1,3,20,34,40,42,43,47,49])){
|
||||
$friend = Db::name('wechat_friendship')->where('id',$item['wechatFriendId'])->find();
|
||||
if (!empty($friend)){
|
||||
$trafficPoolId = Db::name('traffic_pool')->where('identifier',$friend['wechatId'])->value('id');
|
||||
if (!empty($trafficPoolId)){
|
||||
$data = [
|
||||
'type' => 4,
|
||||
'companyId' => $friend['companyId'],
|
||||
'trafficPoolId' => $trafficPoolId,
|
||||
'source' => 0,
|
||||
'uniqueId' => $id,
|
||||
'sourceData' => json_encode([]),
|
||||
'remark' => '用户发送了消息',
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
Db::name('user_portrait')->insert($data);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存群聊消息记录到数据库
|
||||
* @param array $item 消息记录数据
|
||||
* @return bool 是否保存成功
|
||||
*/
|
||||
public function saveChatroomMessage($item)
|
||||
{
|
||||
// 检查消息是否已存在
|
||||
$exists = WechatMessageModel::where('id', $item['id'])->find();
|
||||
|
||||
if (!empty($exists) && $exists['sendStatus'] == 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
// 处理发送者信息
|
||||
$sender = $item['sender'] ?? [];
|
||||
|
||||
// 处理消息内容,提取发送者ID和消息内容
|
||||
$originalContent = $item['content'] ?? '';
|
||||
$processedResult = $this->processMessageContent($originalContent);
|
||||
$senderId = $processedResult['senderId'];
|
||||
$processedContent = $processedResult['content'];
|
||||
|
||||
// 将毫秒时间戳转换为秒级时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null;
|
||||
$deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : null;
|
||||
$wechatTime = isset($item['wechatTime']) ? floor($item['wechatTime'] / 1000) : null;
|
||||
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'type' => 2,
|
||||
'wechatChatroomId' => $item['wechatChatroomId'],
|
||||
// sender信息,添加sender前缀
|
||||
'senderNickname' => $sender['nickname'] ?? '',
|
||||
'senderWechatId' => $sender['wechatId'] ?? $senderId, // 使用提取的发送者ID作为备选
|
||||
'senderIsAdmin' => $sender['isAdmin'] ?? false,
|
||||
'senderIsDeleted' => $sender['isDeleted'] ?? false,
|
||||
'senderChatroomNickname' => $sender['chatroomNickname'] ?? '',
|
||||
'senderWechatAccountId' => $sender['wechatAccountId'] ?? '',
|
||||
// 其他字段
|
||||
'wechatAccountId' => $item['wechatAccountId'],
|
||||
'tenantId' => $item['tenantId'],
|
||||
'accountId' => $item['accountId'],
|
||||
'synergyAccountId' => $item['synergyAccountId'] ?? 0,
|
||||
'content' => $processedContent, // 使用处理后的内容
|
||||
'originalContent' => $originalContent, // 保存原始内容
|
||||
'msgType' => $item['msgType'],
|
||||
'msgSubType' => $item['msgSubType'] ?? 0,
|
||||
'msgSvrId' => $item['msgSvrId'] ?? '',
|
||||
'isSend' => $item['isSend'] ?? true,
|
||||
'createTime' => $createTime,
|
||||
'isDeleted' => $item['isDeleted'] ?? false,
|
||||
'deleteTime' => $deleteTime,
|
||||
'sendStatus' => $item['sendStatus'] ?? 0,
|
||||
'wechatTime' => $wechatTime,
|
||||
'origin' => $item['origin'] ?? 0,
|
||||
'msgId' => $item['msgId'],
|
||||
'recallId' => $item['recallId'] ?? false
|
||||
];
|
||||
|
||||
// 创建新记录
|
||||
try {
|
||||
if(empty($exists)){
|
||||
WechatMessageModel::create($data);
|
||||
}else{
|
||||
unset($data['id']);
|
||||
$exists->save($data);
|
||||
}
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理消息内容,提取发送者ID和消息内容
|
||||
* @param string $content 原始消息内容
|
||||
* @return array 包含senderId和content的数组
|
||||
*/
|
||||
private function processMessageContent($content)
|
||||
{
|
||||
if (empty($content)) {
|
||||
return [
|
||||
'senderId' => '',
|
||||
'content' => ''
|
||||
];
|
||||
}
|
||||
|
||||
// 处理消息格式:wxid_vr2qafb1vg0d22:\n安德玛儿童
|
||||
if (preg_match('/^([^:]+):\n(.+)$/s', $content, $matches)) {
|
||||
$senderId = trim($matches[1]);
|
||||
$messageContent = trim($matches[2]);
|
||||
|
||||
// 检查消息内容是否为JSON格式
|
||||
if (substr($messageContent, 0, 1) === '{' && substr($messageContent, -1) === '}') {
|
||||
try {
|
||||
// 尝试解析JSON
|
||||
$jsonData = json_decode($messageContent, true);
|
||||
if (json_last_error() == JSON_ERROR_NONE && isset($jsonData['text'])) {
|
||||
// 如果是合法的JSON且包含text字段,则提取text字段作为内容
|
||||
$messageContent = $jsonData['text'];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// JSON解析出错,保持原内容不变
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'senderId' => $senderId,
|
||||
'content' => $messageContent
|
||||
];
|
||||
}
|
||||
|
||||
// 如果没有匹配到格式,则返回原始内容
|
||||
return [
|
||||
'senderId' => '',
|
||||
'content' => $content
|
||||
];
|
||||
}
|
||||
}
|
||||
153
application/api/controller/MomentsController.php
Normal file
153
application/api/controller/MomentsController.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use think\facade\Request;
|
||||
|
||||
class MomentsController extends BaseController
|
||||
{
|
||||
/************************ 朋友圈发布相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 发布朋友圈
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addJob($data = [])
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['msg' => '缺少授权信息','code' => 400]);
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$text = $data['text'] ?? ''; // 朋友圈文本内容
|
||||
$picUrlList = $data['picUrlList'] ?? []; // 图片URL列表
|
||||
$videoUrl = $data['videoUrl'] ?? ''; // 视频URL
|
||||
$immediately = $data['immediately'] ?? true; // 是否立即发布
|
||||
$timingTime = $data['timingTime'] ?? ''; // 定时发布时间
|
||||
$beginTime = $data['beginTime'] ?? ''; // 开始时间
|
||||
$endTime = $data['endTime'] ?? ''; // 结束时间
|
||||
$isUseLocation = $data['isUseLocation'] ?? false; // 是否使用位置信息
|
||||
$poiName = $data['poiName'] ?? ''; // 位置名称
|
||||
$poiAddress = $data['poiAddress'] ?? ''; // 位置地址
|
||||
$lat = $data['lat'] ?? 0; // 纬度
|
||||
$lng = $data['lng'] ?? 0; // 经度
|
||||
$momentContentType = $data['momentContentType'] ?? 1; // 朋友圈内容类型
|
||||
$publicMode = $data['publicMode'] ?? 0; // 发布模式
|
||||
$altList = $data['altList'] ?? ''; // 替代列表
|
||||
$link = $data['link'] ?? []; // 链接信息
|
||||
$jobPublishWechatMomentsItems = $data['jobPublishWechatMomentsItems'] ?? []; // 发布账号和评论信息
|
||||
|
||||
// 必填参数验证
|
||||
if (empty($jobPublishWechatMomentsItems) || !is_array($jobPublishWechatMomentsItems)) {
|
||||
return json_encode(['msg' => '至少需要选择一个发布账号','code' => 400]);
|
||||
}
|
||||
|
||||
// 根据朋友圈类型验证必填字段
|
||||
if ($momentContentType == 1 && empty($text)) { // 纯文本
|
||||
return json_encode(['msg' => '朋友圈内容不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 2 && (empty($picUrlList) || empty($text))) { // 图片+文字
|
||||
return json_encode(['msg' => '朋友圈内容和图片不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 3 && (empty($videoUrl) || empty($text))) { // 视频+文字
|
||||
return json_encode(['msg' => '朋友圈内容和视频不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 4 && (empty($link) || empty($text))) { // 链接+文字
|
||||
return json_encode(['msg' => '朋友圈内容和链接不能为空','code' => 400]);
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'text' => $text,
|
||||
'picUrlList' => $picUrlList,
|
||||
'videoUrl' => $videoUrl,
|
||||
'immediately' => $immediately,
|
||||
'timingTime' => $timingTime,
|
||||
'beginTime' => $beginTime,
|
||||
'endTime' => $endTime,
|
||||
'isUseLocation' => $isUseLocation,
|
||||
'poiName' => $poiName,
|
||||
'poiAddress' => $poiAddress,
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'momentContentType' => (int)$momentContentType,
|
||||
'publicMode' => (int)$publicMode,
|
||||
'altList' => $altList,
|
||||
'link' => $link,
|
||||
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems
|
||||
];
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求发布朋友圈
|
||||
$result = requestCurl($this->baseUrl . 'api/JobPublishWechatMoments/addJob', $params, 'POST', $header, 'json');
|
||||
// 处理响应
|
||||
if (empty($result)) {
|
||||
return json_encode(['msg' => '朋友圈任务创建成功','code' => 200]);
|
||||
} else {
|
||||
// 如果返回的是错误信息
|
||||
return json_encode(['msg' => $result,'code' => 400]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['msg' => '发布朋友圈失败','code' => 400]);
|
||||
}
|
||||
}
|
||||
|
||||
/************************ 朋友圈任务管理相关接口 ************************/
|
||||
|
||||
/**
|
||||
* 获取朋友圈任务列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取请求参数
|
||||
$keyword = $this->request->param('keyword', ''); // 关键词搜索
|
||||
$jobStatus = $this->request->param('jobStatus', ''); // 任务状态筛选
|
||||
$contentType = $this->request->param('contentType', ''); // 内容类型筛选
|
||||
$only = $this->request->param('only', 'false'); // 是否只查看自己的
|
||||
$pageIndex = $this->request->param('pageIndex', 0); // 当前页码
|
||||
$pageSize = $this->request->param('pageSize', 10); // 每页数量
|
||||
$from = $this->request->param('from', ''); // 开始日期
|
||||
$to = $this->request->param('to', ''); // 结束日期
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $keyword,
|
||||
'jobStatus' => $jobStatus,
|
||||
'contentType' => $contentType,
|
||||
'only' => $only,
|
||||
'pageIndex' => (int)$pageIndex,
|
||||
'pageSize' => (int)$pageSize
|
||||
];
|
||||
|
||||
// 添加日期筛选条件(如果有)
|
||||
if (!empty($from)) {
|
||||
$params['from'] = $from;
|
||||
}
|
||||
if (!empty($to)) {
|
||||
$params['to'] = $to;
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取朋友圈任务列表
|
||||
$result = requestCurl($this->baseUrl . 'api/JobPublishWechatMoments/listPagination', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
return successJson($response);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取朋友圈任务列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
75
application/api/controller/StatsController.php
Normal file
75
application/api/controller/StatsController.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
/**
|
||||
* 统计控制器
|
||||
* Class StatsController
|
||||
* @package app\frontend\controller
|
||||
*/
|
||||
class StatsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* API客户端类型
|
||||
*/
|
||||
const CLIENT_TYPE = 'system';
|
||||
|
||||
/**
|
||||
* 账号基本信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function basicData()
|
||||
{
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . '/api/DashBoard/ListHomePageStatistics', ['refresh' => 10000], 'GET', $header);
|
||||
return successJson($result);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取基础数据失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 好友统计
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function FansStatistics(){
|
||||
/* 参数说明
|
||||
lidu 数据搜索类型 0 小时 1 天 2月
|
||||
from to 时间 当lidu为 0时(2025-03-12 09:54:42) 当lidu为 1时(2025-03-12) 当lidu为 2时(2025-03)
|
||||
*/
|
||||
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
$lidu = trim($this->request->param('lidu', ''));
|
||||
$from = trim($this->request->param('from', ''));
|
||||
$to = trim($this->request->param('to', ''));
|
||||
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'lidu' => $lidu,
|
||||
'from' => $from,
|
||||
'to' => $to,
|
||||
];
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'api/DashBoard/listStatisticsCountDTOByCreateTimeAsync', $params, 'GET', $header);
|
||||
return successJson($result);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取粉丝统计数据失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
365
application/api/controller/UserController.php
Normal file
365
application/api/controller/UserController.php
Normal file
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\CompanyAccountModel;
|
||||
use think\facade\Env;
|
||||
use think\Response;
|
||||
|
||||
/**
|
||||
* 用户控制器
|
||||
* Class UserController
|
||||
* @package app\frontend\controller
|
||||
*/
|
||||
class UserController extends BaseController
|
||||
{
|
||||
/**
|
||||
* API客户端类型
|
||||
*/
|
||||
const CLIENT_TYPE = 'system';
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
// 获取并验证参数
|
||||
$params = $this->validateLoginParams();
|
||||
if (!is_array($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
// 验证账号是否存在
|
||||
$existingAccount = CompanyAccountModel::where('userName', $params['username'])->find();
|
||||
if (empty($existingAccount)) {
|
||||
// 记录登录失败日志
|
||||
recordUserLog(0, $params['username'], 'LOGIN', '账号不存在', $params, 500, '账号不存在');
|
||||
return errorJson('账号不存在');
|
||||
}
|
||||
|
||||
// 获取验证码会话ID和用户输入的验证码
|
||||
$verifySessionId = $this->request->param('verifySessionId', '');
|
||||
$verifyCode = $this->request->param('verifyCode', '');
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
|
||||
// 如果存在验证码信息,添加到请求头
|
||||
if (!empty($verifySessionId) && !empty($verifyCode)) {
|
||||
$headerData[] = 'verifysessionid:' . $verifySessionId;
|
||||
$headerData[] = 'verifycode:' . $verifyCode;
|
||||
}
|
||||
|
||||
$header = setHeader($headerData, '', 'plain');
|
||||
|
||||
try {
|
||||
// 请求登录接口
|
||||
$result = requestCurl($this->baseUrl . 'token', $params, 'POST', $header);
|
||||
$result_array = handleApiResponse($result);
|
||||
|
||||
if (is_array($result_array) && isset($result_array['error'])) {
|
||||
// 记录登录失败日志
|
||||
recordUserLog(0, $params['username'], 'LOGIN', '登录失败', $params, 500, $result_array['error_description']);
|
||||
return errorJson($result_array['error_description']);
|
||||
}
|
||||
|
||||
// 获取客户端IP地址
|
||||
$ip = $this->request->ip();
|
||||
|
||||
// 登录成功,更新密码信息和登录信息
|
||||
$updateData = [
|
||||
'passwordMd5' => md5($params['password']),
|
||||
'passwordLocal' => localEncrypt($params['password']),
|
||||
'lastLoginIp' => $ip,
|
||||
'lastLoginTime' => time()
|
||||
];
|
||||
|
||||
// 更新密码信息
|
||||
CompanyAccountModel::where('userName', $params['username'])->update($updateData);
|
||||
|
||||
// 记录登录成功日志
|
||||
recordUserLog($existingAccount['id'], $params['username'], 'LOGIN', '登录成功', [], 200, '登录成功');
|
||||
|
||||
return successJson($result_array);
|
||||
} catch (\Exception $e) {
|
||||
// 记录登录异常日志
|
||||
recordUserLog(0, $params['username'], 'LOGIN', '登录请求失败', $params, 500, $e->getMessage());
|
||||
return errorJson('登录请求失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新的token
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getNewToken()
|
||||
{
|
||||
$grant_type = $this->request->param('grant_type', 'refresh_token');
|
||||
$refresh_token = $this->request->param('refresh_token', '');
|
||||
$authorization = $this->request->header('authorization', $this->authorization);
|
||||
|
||||
if (empty($grant_type) || empty($authorization)) {
|
||||
return errorJson('参数错误');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'grant_type' => $grant_type,
|
||||
'refresh_token' => $refresh_token,
|
||||
];
|
||||
|
||||
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'system');
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'token', $params, 'POST', $header);
|
||||
$result_array = handleApiResponse($result);
|
||||
|
||||
if (is_array($result_array) && isset($result_array['error'])) {
|
||||
recordUserLog(0, '', 'REFRESH_TOKEN', '刷新token失败', $params, 500, $result_array['error_description']);
|
||||
return errorJson($result_array['error_description']);
|
||||
}
|
||||
|
||||
recordUserLog(0, '', 'REFRESH_TOKEN', '刷新token成功', $params, 200, '刷新成功');
|
||||
return successJson($result_array);
|
||||
} catch (\Exception $e) {
|
||||
recordUserLog(0, '', 'REFRESH_TOKEN', '刷新token异常', $params, 500, $e->getMessage());
|
||||
return errorJson('获取新token失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商户基本信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getAccountInfo()
|
||||
{
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/self', [], 'GET', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
if (!empty($response['account'])) {
|
||||
$accountData = $response['account'];
|
||||
|
||||
// 准备数据库字段映射,保持驼峰命名
|
||||
$dbData = [
|
||||
'tenantId' => $accountData['id'],
|
||||
'realName' => $accountData['realName'],
|
||||
'nickname' => $accountData['nickname'],
|
||||
'memo' => $accountData['memo'],
|
||||
'avatar' => $accountData['avatar'],
|
||||
'userName' => $accountData['userName'],
|
||||
'secret' => $accountData['secret'],
|
||||
'accountType' => $accountData['accountType'],
|
||||
'companyId' => $accountData['departmentId'],
|
||||
'useGoogleSecretKey' => $accountData['useGoogleSecretKey'],
|
||||
'hasVerifyGoogleSecret' => $accountData['hasVerifyGoogleSecret'],
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
|
||||
// 查找是否存在该账户
|
||||
$existingAccount = CompanyAccountModel::where('userName', $accountData['userName'])->find();
|
||||
if ($existingAccount) {
|
||||
// 更新现有记录
|
||||
CompanyAccountModel::where('userName', $accountData['userName'])->update($dbData);
|
||||
} else {
|
||||
// 创建新记录
|
||||
$dbData['createTime'] = time();
|
||||
CompanyAccountModel::create($dbData);
|
||||
}
|
||||
return successJson($response['account']);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
|
||||
|
||||
} catch (\Exception $e) {
|
||||
recordUserLog(0, '', 'GET_ACCOUNT_INFO', '获取账户信息异常', [], 500, $e->getMessage());
|
||||
return errorJson('获取账户信息失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function modifyPwd($data = [])
|
||||
{
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code' => 400,'msg' => '参数缺失']);
|
||||
}
|
||||
|
||||
if (!isset($data['id']) || !isset($data['pwd'])) {
|
||||
return json_encode(['code' => 401,'msg' => '参数缺失']);
|
||||
}
|
||||
$authorization = $this->authorization;
|
||||
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code' => 400,'msg' => '缺少授权信息']);
|
||||
}
|
||||
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
$params = [
|
||||
'id' => $data['id'],
|
||||
'newPw' => $data['pwd'],
|
||||
];
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/modifypw', $params, 'PUT', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
if (empty($response)) {
|
||||
return json_encode(['code' => 200,'msg' => '修改成功']);
|
||||
}
|
||||
return json_encode(['code' => 400,'msg' => $response]);
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code' => 400,'msg' => '修改密码失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, $authorization, 'system');
|
||||
|
||||
try {
|
||||
// 调用外部退出登录接口
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/SignOut', [], 'GET', $header);
|
||||
return successJson([] , '退出成功');
|
||||
} catch (\Exception $e) {
|
||||
recordUserLog(0, '', 'LOGOUT', '退出登录异常', [], 500, $e->getMessage());
|
||||
return errorJson('退出登录失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getVerifyCode($isJson = false)
|
||||
{
|
||||
$headerData = ['client:' . self::CLIENT_TYPE];
|
||||
$header = setHeader($headerData, '', 'plain');
|
||||
|
||||
try {
|
||||
$result = requestCurl($this->baseUrl . 'api/Account/getVerifyCode', [], 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 检查返回的数据格式
|
||||
if (is_array($response)) {
|
||||
// 如果verifyCodeImage和verifySessionId都不为null,返回它们
|
||||
if (!empty($response['verifyCodeImage']) && !empty($response['verifySessionId'])) {
|
||||
$returnData = [
|
||||
'verifyCodeImage' => $response['verifyCodeImage'],
|
||||
'verifySessionId' => $response['verifySessionId']
|
||||
];
|
||||
return !empty($isJson) ? json_encode(['code' => 200,'data' => $returnData]) : successJson($returnData);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不是预期的格式,返回原始数据
|
||||
return !empty($isJson) ? json_encode(['code' => 200,'msg' => '无需验证码','data' => ['verifyCodeImage' => '', 'verifySessionId' => '']]) : successJson(['verifyCodeImage' => '', 'verifySessionId' => ''],'无需验证码');
|
||||
} catch (\Exception $e) {
|
||||
$msg = '获取验证码失败'. $e->getMessage();
|
||||
return !empty($isJson) ? json_encode(['code' => 400,'msg' => $msg]) : errorJson($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证登录参数
|
||||
* @return array|\think\response\Json
|
||||
*/
|
||||
private function validateLoginParams()
|
||||
{
|
||||
$username = trim($this->request->param('username', ''));
|
||||
$password = trim($this->request->param('password', ''));
|
||||
$verifyCode = trim($this->request->param('verifyCode', ''));
|
||||
$verifySessionId = trim($this->request->param('verifySessionId', ''));
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
return errorJson('用户名和密码不能为空');
|
||||
}
|
||||
|
||||
// 验证密码格式
|
||||
$passwordValidation = validateString($password, 'password',['max_length' => 20]);
|
||||
if (!$passwordValidation['status']) {
|
||||
return errorJson($passwordValidation['message']);
|
||||
}
|
||||
|
||||
// 如果提供了验证码,验证格式
|
||||
if (!empty($verifyCode)) {
|
||||
if (empty($verifySessionId)) {
|
||||
return errorJson('验证码会话ID不能为空');
|
||||
}
|
||||
// 验证码格式验证(假设是4位数字)
|
||||
if (!preg_match('/^\d{4}$/', $verifyCode)) {
|
||||
return errorJson('验证码格式不正确');
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'grant_type' => 'password',
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证修改密码参数
|
||||
* @return array|\think\response\Json
|
||||
*/
|
||||
private function validateModifyPwdParams()
|
||||
{
|
||||
$cPw = trim($this->request->param('cPw', ''));
|
||||
$newPw = trim($this->request->param('newPw', ''));
|
||||
$oldPw = trim($this->request->param('oldPw', ''));
|
||||
|
||||
if (empty($cPw) || empty($newPw) || empty($oldPw)) {
|
||||
return errorJson('密码参数不完整');
|
||||
}
|
||||
|
||||
if ($newPw !== $cPw) {
|
||||
return errorJson('两次输入的新密码不一致');
|
||||
}
|
||||
|
||||
// 验证新密码格式
|
||||
$passwordValidation = validateString($newPw, 'password');
|
||||
if (!$passwordValidation['status']) {
|
||||
return errorJson($passwordValidation['message']);
|
||||
}
|
||||
|
||||
return [
|
||||
'cPw' => $cPw,
|
||||
'newPw' => $newPw,
|
||||
'oldPw' => $oldPw,
|
||||
];
|
||||
}
|
||||
}
|
||||
1157
application/api/controller/WebSocketController.php
Normal file
1157
application/api/controller/WebSocketController.php
Normal file
File diff suppressed because it is too large
Load Diff
559
application/api/controller/WebSocketControllerCopy.php
Normal file
559
application/api/controller/WebSocketControllerCopy.php
Normal file
@@ -0,0 +1,559 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
|
||||
use think\cache\driver\Redis;
|
||||
use think\Db;
|
||||
use think\Log;
|
||||
use WebSocket\Client;
|
||||
use think\facade\Env;
|
||||
|
||||
class WebSocketControllerCopy extends BaseController
|
||||
{
|
||||
protected $authorized;
|
||||
protected $accountId;
|
||||
protected $client;
|
||||
|
||||
/************************************
|
||||
* 初始化相关功能
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 构造函数 - 初始化WebSocket连接
|
||||
* @param array $userData 用户数据
|
||||
*/
|
||||
public function __construct($userData = [])
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if(!empty($userData) && count($userData)){
|
||||
|
||||
if (empty($userData['userName']) || empty($userData['password'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$params = [
|
||||
'grant_type' => 'password',
|
||||
'username' => $userData['userName'],
|
||||
'password' => $userData['password']
|
||||
];
|
||||
|
||||
// 调用登录接口获取token
|
||||
// 设置请求头
|
||||
$headerData = ['client:kefu-client'];
|
||||
$header = setHeader($headerData, '', 'plain');
|
||||
$result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST',$header);
|
||||
$result_array = handleApiResponse($result);
|
||||
|
||||
if (isset($result_array['access_token']) && !empty($result_array['access_token'])) {
|
||||
$authorization = $result_array['access_token'];
|
||||
$this->authorized = $authorization;
|
||||
$this->accountId = $userData['accountId'];
|
||||
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'获取系统授权信息失败']);
|
||||
}
|
||||
}else{
|
||||
$this->authorized = $this->request->header('authorization', '');
|
||||
$this->accountId = $this->request->param('accountId', '');
|
||||
}
|
||||
|
||||
|
||||
if (empty($this->authorized) || empty($this->accountId)) {
|
||||
$data['authorized'] = $this->authorized;
|
||||
$data['accountId'] = $this->accountId;
|
||||
return json_encode(['code'=>400,'msg'=>'缺失关键参数']);
|
||||
}
|
||||
|
||||
//证书
|
||||
$context = stream_context_create();
|
||||
stream_context_set_option($context, 'ssl', 'verify_peer', false);
|
||||
stream_context_set_option($context, 'ssl', 'verify_peer_name', false);
|
||||
//开启WS链接
|
||||
$result = [
|
||||
"accessToken" => $this->authorized,
|
||||
"accountId" => $this->accountId,
|
||||
"client" => "kefu-client",
|
||||
"cmdType" => "CmdSignIn",
|
||||
"seq" => 1,
|
||||
];
|
||||
|
||||
|
||||
$content = json_encode($result);
|
||||
$this->client = new Client("wss://s2.siyuguanli.com:9993",
|
||||
[
|
||||
'filter' => ['text', 'binary', 'ping', 'pong', 'close','receive', 'send'],
|
||||
'context' => $context,
|
||||
'headers' => [
|
||||
'Sec-WebSocket-Protocol' => 'soap',
|
||||
'origin' => 'localhost',
|
||||
],
|
||||
'timeout' => 86400,
|
||||
]
|
||||
);
|
||||
$this->client->send($content);
|
||||
}
|
||||
|
||||
/************************************
|
||||
* 朋友圈相关功能
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 获取指定账号朋友圈信息
|
||||
* @param array $data 请求参数
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMoments($data = [])
|
||||
{
|
||||
|
||||
$count = !empty($data['count']) ? $data['count'] : 10;
|
||||
$wechatAccountId = !empty($data['wechatAccountId']) ? $data['wechatAccountId'] : '';
|
||||
$wechatFriendId = !empty($data['id']) ? $data['id'] : '';
|
||||
|
||||
//过滤消息
|
||||
if (empty($wechatAccountId)) {
|
||||
return json_encode(['code'=>400,'msg'=>'指定账号不能为空']);
|
||||
}
|
||||
if (empty($wechatFriendId)) {
|
||||
return json_encode(['code'=>400,'msg'=>'指定好友不能为空']);
|
||||
}
|
||||
$msg = '获取朋友圈信息成功';
|
||||
$message = [];
|
||||
try {
|
||||
$params = [
|
||||
"cmdType" => "CmdFetchMoment",
|
||||
"count" => $count,
|
||||
"createTimeSec" => time(),
|
||||
"isTimeline" => false,
|
||||
"prevSnsId" => 0,
|
||||
"wechatAccountId" => $wechatAccountId,
|
||||
"wechatFriendId" => $wechatFriendId,
|
||||
"seq" => time(),
|
||||
];
|
||||
$params = json_encode($params);
|
||||
//Log::write('WS获取朋友圈信息参数:' . json_encode($params, 256));
|
||||
$this->client->send($params);
|
||||
$message = $this->client->receive();
|
||||
//Log::write('WS获取朋友圈信息成功,结果:' . $message);
|
||||
$message = json_decode($message, 1);
|
||||
|
||||
// 存储朋友圈数据到数据库
|
||||
if (isset($message['result']) && !empty($message['result'])) {
|
||||
$this->saveMomentsToDatabase($message['result'], $wechatAccountId, $wechatFriendId);
|
||||
}
|
||||
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
} catch (\Exception $e) {
|
||||
$msg = $e->getMessage();
|
||||
}
|
||||
|
||||
return json_encode(['code'=>200,'msg'=>$msg,'data'=>$message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 朋友圈点赞
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function momentInteract()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->param();
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
|
||||
//过滤消息
|
||||
if (empty($dataArray['snsId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'snsId不能为空']);
|
||||
}
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'微信id不能为空']);
|
||||
}
|
||||
|
||||
|
||||
$result = [
|
||||
"cmdType" => "CmdMomentInteract",
|
||||
"momentInteractType" => 1,
|
||||
"seq" => time(),
|
||||
"snsId" => $dataArray['snsId'],
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"wechatFriendId" => 0,
|
||||
];
|
||||
|
||||
$result = json_encode($result);
|
||||
$this->client->send($result);
|
||||
$message = $this->client->receive();
|
||||
$message = json_decode($message, 1);
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
//Log::write('WS个人消息发送');
|
||||
return json_encode(['code'=>200,'msg'=>'点赞成功','data'=>$message]);
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'非法请求']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 朋友圈取消点赞
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function momentCancelInteract()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->param();
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
|
||||
//过滤消息
|
||||
if (empty($dataArray['snsId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'snsId不能为空']);
|
||||
}
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'微信id不能为空']);
|
||||
}
|
||||
|
||||
|
||||
$result = [
|
||||
"CommentId2" => '',
|
||||
"CommentTime" => 0,
|
||||
"cmdType" => "CmdMomentCancelInteract",
|
||||
"optType" => 1,
|
||||
"seq" => time(),
|
||||
"snsId" => $dataArray['snsId'],
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"wechatFriendId" => 0,
|
||||
];
|
||||
|
||||
$result = json_encode($result);
|
||||
$this->client->send($result);
|
||||
$message = $this->client->receive();
|
||||
$message = json_decode($message, 1);
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
//Log::write('WS个人消息发送');
|
||||
return json_encode(['code'=>200,'msg'=>'取消点赞成功','data'=>$message]);
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'非法请求']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定账号朋友圈图片地址
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMomentSourceRealUrl()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->param();
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
//获取数据条数
|
||||
// $count = isset($dataArray['count']) ? $dataArray['count'] : 10;
|
||||
//过滤消息
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'指定账号不能为空']);
|
||||
}
|
||||
if (empty($dataArray['snsId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'指定消息ID不能为空']);
|
||||
}
|
||||
if (empty($dataArray['snsUrls'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'资源信息不能为空']);
|
||||
}
|
||||
$msg = '获取朋友圈资源链接成功';
|
||||
$message = [];
|
||||
try {
|
||||
$params = [
|
||||
"cmdType" => $dataArray['type'],
|
||||
"snsId" => $dataArray['snsId'],
|
||||
"urls" => $dataArray['snsUrls'],
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"seq" => time(),
|
||||
];
|
||||
$params = json_encode($params);
|
||||
$this->client->send($params);
|
||||
$message = $this->client->receive();
|
||||
//Log::write('WS获取朋友圈图片/视频链接成功,结果:' . json_encode($message, 256));
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
} catch (\Exception $e) {
|
||||
$msg = $e->getMessage();
|
||||
}
|
||||
|
||||
return json_encode(['code'=>200,'msg'=>$msg,'data'=>$message]);
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'非法请求']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存朋友圈数据到数据库
|
||||
* @param array $momentList 朋友圈数据列表
|
||||
* @param int $wechatAccountId 微信账号ID
|
||||
* @param string $wechatFriendId 微信好友ID
|
||||
* @return bool
|
||||
*/
|
||||
protected function saveMomentsToDatabase($momentList, $wechatAccountId, $wechatFriendId)
|
||||
{
|
||||
if (empty($momentList) || !is_array($momentList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach ($momentList as $moment) {
|
||||
// 提取momentEntity中的数据
|
||||
$momentEntity = $moment['momentEntity'] ?? [];
|
||||
|
||||
// 检查朋友圈数据是否已存在
|
||||
$momentId = Db::table('s2_wechat_moments')
|
||||
->where('snsId', $moment['snsId'])
|
||||
->where('wechatAccountId', $wechatAccountId)
|
||||
->value('id');
|
||||
|
||||
$dataToSave = [
|
||||
'commentList' => json_encode($moment['commentList'] ?? [], 256),
|
||||
'createTime' => $moment['createTime'] ?? 0,
|
||||
'likeList' => json_encode($moment['likeList'] ?? [], 256),
|
||||
'content' => $momentEntity['content'] ?? '',
|
||||
'lat' => $momentEntity['lat'] ?? 0,
|
||||
'lng' => $momentEntity['lng'] ?? 0,
|
||||
'location' => $momentEntity['location'] ?? '',
|
||||
'picSize' => $momentEntity['picSize'] ?? 0,
|
||||
'resUrls' => json_encode($momentEntity['resUrls'] ?? [], 256),
|
||||
'userName' => $momentEntity['userName'] ?? '',
|
||||
'snsId' => $moment['snsId'] ?? '',
|
||||
'type' => $moment['type'] ?? 0,
|
||||
'title' => $moment['title'] ?? '',
|
||||
'coverImage' => $moment['coverImage'] ?? '',
|
||||
'update_time' => time()
|
||||
];
|
||||
|
||||
if ($momentId) {
|
||||
// 如果已存在,则更新数据
|
||||
Db::table('s2_wechat_moments')->where('id', $momentId)->update($dataToSave);
|
||||
} else {
|
||||
if(empty($wechatFriendId)){
|
||||
$wechatFriendId = WechatFriend::where('wechatAccountId', $wechatAccountId)->where('wechatId', $momentEntity['userName'])->value('id');
|
||||
}
|
||||
// 如果不存在,则插入新数据
|
||||
$dataToSave['wechatAccountId'] = $wechatAccountId;
|
||||
$dataToSave['wechatFriendId'] = $wechatFriendId;
|
||||
$dataToSave['create_time'] = time();
|
||||
Db::table('s2_wechat_moments')->insert($dataToSave);
|
||||
}
|
||||
}
|
||||
|
||||
//Log::write('朋友圈数据已存入数据库,共' . count($momentList) . '条');
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
//Log::write('保存朋友圈数据失败:' . $e->getMessage(), 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/************************************
|
||||
* 消息发送相关功能
|
||||
************************************/
|
||||
|
||||
/**
|
||||
* 个人消息发送
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function sendPersonal()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->param();
|
||||
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
|
||||
//过滤消息
|
||||
if (empty($dataArray['content'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'内容缺失']);
|
||||
}
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'微信id不能为空']);
|
||||
}
|
||||
if (empty($dataArray['wechatFriendId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'接收人不能为空']);
|
||||
}
|
||||
|
||||
if (empty($dataArray['msgType'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'类型缺失']);
|
||||
}
|
||||
|
||||
//消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包 49:小程序)
|
||||
$result = [
|
||||
"cmdType" => "CmdSendMessage",
|
||||
"content" => $dataArray['content'],
|
||||
"msgSubType" => 0,
|
||||
"msgType" => $dataArray['msgType'],
|
||||
"seq" => time(),
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"wechatChatroomId" => 0,
|
||||
"wechatFriendId" => $dataArray['wechatFriendId'],
|
||||
];
|
||||
|
||||
$result = json_encode($result);
|
||||
$this->client->send($result);
|
||||
$message = $this->client->receive();
|
||||
$message = json_decode($message, 1);
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
//Log::write('WS个人消息发送');
|
||||
return json_encode(['code'=>200,'msg'=>'消息成功发送','data'=>$message]);
|
||||
//return successJson($message, '消息成功发送');
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'非法请求']);
|
||||
//return errorJson('非法请求');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function sendCommunity()
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
$data = $this->request->post();
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
|
||||
//过滤消息
|
||||
if (empty($dataArray['content'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'内容缺失']);
|
||||
}
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'微信id不能为空']);
|
||||
}
|
||||
|
||||
if (empty($dataArray['msgType'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'类型缺失']);
|
||||
}
|
||||
if (empty($dataArray['wechatChatroomId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'群id不能为空']);
|
||||
}
|
||||
|
||||
$msg = '消息成功发送';
|
||||
$message = [];
|
||||
try {
|
||||
//消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包 49:小程序)
|
||||
$result = [
|
||||
"cmdType" => "CmdSendMessage",
|
||||
"content" => htmlspecialchars_decode($dataArray['content']),
|
||||
"msgSubType" => 0,
|
||||
"msgType" => $dataArray['msgType'],
|
||||
"seq" => time(),
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"wechatChatroomId" => $dataArray['wechatChatroomId'],
|
||||
"wechatFriendId" => 0,
|
||||
];
|
||||
|
||||
$result = json_encode($result);
|
||||
$this->client->send($result);
|
||||
$message = $this->client->receive();
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
//Log::write('WS群消息发送');
|
||||
//Log::write($message);
|
||||
$message = json_decode($message, 1);
|
||||
} catch (\Exception $e) {
|
||||
$msg = $e->getMessage();
|
||||
}
|
||||
return json_encode(['code'=>200,'msg'=>$msg,'data'=>$message]);
|
||||
|
||||
} else {
|
||||
return json_encode(['code'=>400,'msg'=>'非法请求']);
|
||||
//return errorJson('非法请求');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息(内部调用版)
|
||||
* @param array $data 消息数据
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function sendCommunitys($data = [])
|
||||
{
|
||||
if (empty($data)) {
|
||||
return json_encode(['code'=>400,'msg'=>'参数缺失']);
|
||||
}
|
||||
$dataArray = $data;
|
||||
if (!is_array($dataArray)) {
|
||||
return json_encode(['code'=>400,'msg'=>'数据格式错误']);
|
||||
}
|
||||
|
||||
//过滤消息
|
||||
if (empty($dataArray['content'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'内容缺失']);
|
||||
}
|
||||
if (empty($dataArray['wechatAccountId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'微信id不能为空']);
|
||||
}
|
||||
|
||||
if (empty($dataArray['msgType'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'类型缺失']);
|
||||
}
|
||||
if (empty($dataArray['wechatChatroomId'])) {
|
||||
return json_encode(['code'=>400,'msg'=>'群id不能为空']);
|
||||
}
|
||||
|
||||
$msg = '消息成功发送';
|
||||
$message = [];
|
||||
try {
|
||||
//消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包 49:小程序)
|
||||
$result = [
|
||||
"cmdType" => "CmdSendMessage",
|
||||
"content" => $dataArray['content'],
|
||||
"msgSubType" => 0,
|
||||
"msgType" => $dataArray['msgType'],
|
||||
"seq" => time(),
|
||||
"wechatAccountId" => $dataArray['wechatAccountId'],
|
||||
"wechatChatroomId" => $dataArray['wechatChatroomId'],
|
||||
"wechatFriendId" => 0,
|
||||
];
|
||||
|
||||
$result = json_encode($result);
|
||||
$this->client->send($result);
|
||||
$message = $this->client->receive();
|
||||
//关闭WS链接
|
||||
$this->client->close();
|
||||
//Log::write('WS群消息发送');
|
||||
//Log::write($message);
|
||||
$message = json_decode($message, 1);
|
||||
} catch (\Exception $e) {
|
||||
$msg = $e->getMessage();
|
||||
}
|
||||
|
||||
return json_encode(['code'=>200,'msg'=>$msg,'data'=>$message]);
|
||||
}
|
||||
}
|
||||
255
application/api/controller/WechatChatroomController.php
Normal file
255
application/api/controller/WechatChatroomController.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\WechatChatroomModel;
|
||||
use app\api\model\WechatChatroomMemberModel;
|
||||
use app\job\WechatChatroomJob;
|
||||
use think\facade\Request;
|
||||
|
||||
class WechatChatroomController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信群聊列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($data = [],$isInner = false, $isDel = '')
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 根据isDel设置对应的isDeleted值
|
||||
$isDeleted = '';
|
||||
if ($isDel === '0' || $isDel === 0) {
|
||||
$isDeleted = false;
|
||||
} elseif ($isDel === '1' || $isDel === 1) {
|
||||
$isDeleted = true;
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'keyword' => $data['keyword'] ?? '',
|
||||
'wechatAccountKeyword' => $data['wechatAccountKeyword'] ?? '',
|
||||
'isDeleted' => $data['isDeleted'] ?? $isDeleted ,
|
||||
'allotAccountId' => $data['allotAccountId'] ?? '',
|
||||
'groupId' => $data['groupId'] ?? '',
|
||||
'wechatChatroomId' => $data['wechatChatroomId'] ?? '',
|
||||
'memberKeyword' => $data['memberKeyword'] ?? '',
|
||||
'pageIndex' => $data['pageIndex'] ?? 0,
|
||||
'pageSize' => $data['pageSize'] ?? 20
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
// 发送请求获取群聊列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatChatroom/pagelist', $params, 'GET', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
$isUpdate = false;
|
||||
foreach ($response['results'] as $item) {
|
||||
$updated = $this->saveChatroom($item);
|
||||
if($updated && $isDel == 0){
|
||||
$isUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$response,'isUpdate'=>$isUpdate]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'获取微信群聊列表失败' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取微信群聊列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存群聊数据到数据库
|
||||
* @param array $item 群聊数据
|
||||
*/
|
||||
private function saveChatroom($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'wechatAccountId' => $item['wechatAccountId'],
|
||||
'wechatAccountAlias' => $item['wechatAccountAlias'],
|
||||
'wechatAccountWechatId' => $item['wechatAccountWechatId'],
|
||||
'wechatAccountAvatar' => $item['wechatAccountAvatar'],
|
||||
'wechatAccountNickname' => $item['wechatAccountNickname'],
|
||||
'chatroomId' => $item['chatroomId'],
|
||||
'hasMe' => $item['hasMe'],
|
||||
'chatroomOwnerNickname' => isset($item['chatroomOwnerNickname']) ? $item['chatroomOwnerNickname'] : '',
|
||||
'chatroomOwnerAvatar' => isset($item['chatroomOwnerAvatar']) ? $item['chatroomOwnerAvatar'] : '',
|
||||
'conRemark' => isset($item['conRemark']) ? $item['conRemark'] : '',
|
||||
'nickname' => isset($item['nickname']) ? $item['nickname'] : '',
|
||||
'pyInitial' => isset($item['pyInitial']) ? $item['pyInitial'] : '',
|
||||
'quanPin' => isset($item['quanPin']) ? $item['quanPin'] : '',
|
||||
'chatroomAvatar' => isset($item['chatroomAvatar']) ? $item['chatroomAvatar'] : '',
|
||||
'members' => is_array($item['members']) ? json_encode($item['members']) : json_encode([]),
|
||||
'isDeleted' => isset($item['isDeleted']) ? $item['isDeleted'] : 0,
|
||||
'deleteTime' => !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : 0,
|
||||
'createTime' => isset($item['createTime']) ? strtotime($item['createTime']) : 0,
|
||||
'accountId' => isset($item['accountId']) ? $item['accountId'] : 0,
|
||||
'accountUserName' => isset($item['accountUserName']) ? $item['accountUserName'] : '',
|
||||
'accountRealName' => isset($item['accountRealName']) ? $item['accountRealName'] : '',
|
||||
'accountNickname' => isset($item['accountNickname']) ? $item['accountNickname'] : '',
|
||||
'groupId' => isset($item['groupId']) ? $item['groupId'] : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 使用chatroomId和wechatAccountId的组合作为唯一性判断
|
||||
$chatroom = WechatChatroomModel::where('id',$item['id'])->find();
|
||||
|
||||
if ($chatroom) {
|
||||
$chatroom->save($data);
|
||||
return true;
|
||||
} else {
|
||||
WechatChatroomModel::create($data);
|
||||
return false;
|
||||
}
|
||||
|
||||
// // 同时保存群成员数据
|
||||
// if (!empty($item['members'])) {
|
||||
// foreach ($item['members'] as $member) {
|
||||
// $this->saveChatroomMember($member, $item['chatroomId']);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @param string $wechatChatroomId 微信群ID
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function listChatroomMember($wechatChatroomId = '',$chatroomId = '',$isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
$wechatChatroomId = !empty($wechatChatroomId) ? $wechatChatroomId : $this->request->param('id', '');
|
||||
$chatroomId = !empty($chatroomId) ? $chatroomId : $this->request->param('chatroomId', '');
|
||||
|
||||
|
||||
if (empty($authorization)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'缺少授权信息']);
|
||||
}else{
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($wechatChatroomId)) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'群ID不能为空']);
|
||||
}else{
|
||||
return errorJson('群ID不能为空');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'wechatChatroomId' => $wechatChatroomId
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取群成员列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatChatroom/listChatroomMember', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (!empty($response)) {
|
||||
foreach ($response as $item) {
|
||||
$this->saveChatroomMember($item, $chatroomId);
|
||||
}
|
||||
}
|
||||
|
||||
if($isInner){
|
||||
return json_encode(['code'=>200,'msg'=>'success','data'=>$response]);
|
||||
}else{
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if($isInner){
|
||||
return json_encode(['code'=>500,'msg'=>'获取群成员列表失败:' . $e->getMessage()]);
|
||||
}else{
|
||||
return errorJson('获取群成员列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存群成员数据到数据库
|
||||
* @param array $item 群成员数据
|
||||
* @param string $wechatChatroomId 微信群ID
|
||||
*/
|
||||
private function saveChatroomMember($item, $wechatChatroomId)
|
||||
{
|
||||
$data = [
|
||||
'chatroomId' => $wechatChatroomId,
|
||||
'wechatId' => isset($item['wechatId']) ? $item['wechatId'] : '',
|
||||
'nickname' => isset($item['nickname']) ? $item['nickname'] : '',
|
||||
'avatar' => isset($item['avatar']) ? $item['avatar'] : '',
|
||||
'conRemark' => isset($item['conRemark']) ? $item['conRemark'] : '',
|
||||
'alias' => isset($item['alias']) ? $item['alias'] : '',
|
||||
'friendType' => isset($item['friendType']) ? $item['friendType'] : false,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 使用chatroomId和wechatId的组合作为唯一性判断
|
||||
$member = WechatChatroomMemberModel::where([
|
||||
['chatroomId', '=', $wechatChatroomId],
|
||||
['wechatId', '=', $item['wechatId']]
|
||||
])->find();
|
||||
|
||||
if ($member) {
|
||||
$member->savea($data);
|
||||
} else {
|
||||
$data['createTime'] = time();
|
||||
WechatChatroomMemberModel::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步微信群聊数据
|
||||
* 此方法用于手动触发微信群聊数据同步任务
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function syncChatrooms()
|
||||
{
|
||||
try {
|
||||
// 获取请求参数
|
||||
$pageIndex = $this->request->param('pageIndex', 0);
|
||||
$pageSize = $this->request->param('pageSize', 100);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$wechatAccountKeyword = $this->request->param('wechatAccountKeyword', '');
|
||||
$isDeleted = $this->request->param('isDeleted', '');
|
||||
|
||||
// 添加同步任务到队列
|
||||
$result = WechatChatroomJob::addSyncTask($pageIndex, $pageSize, $keyword, $wechatAccountKeyword, $isDeleted);
|
||||
|
||||
if ($result) {
|
||||
return successJson([], '微信群聊同步任务已添加到队列');
|
||||
} else {
|
||||
return errorJson('添加同步任务失败');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('添加同步任务异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
295
application/api/controller/WechatController.php
Normal file
295
application/api/controller/WechatController.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\WechatAccountModel;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 微信账号管理控制器
|
||||
*/
|
||||
class WechatController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信账号列表(主方法)
|
||||
*
|
||||
* @param string $pageIndex 页码
|
||||
* @param string $pageSize 每页大小
|
||||
* @param bool $isInner 是否为任务调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList($pageIndex = '', $pageSize = '', $isInner = false)
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'wechatAlive' => $this->request->param('wechatAlive', ''),
|
||||
'keyword' => $this->request->param('keyword', ''),
|
||||
'groupId' => $this->request->param('groupId', ''),
|
||||
'departmentId' => $this->request->param('departmentId', ''),
|
||||
'hasDevice' => $this->request->param('hasDevice', ''),
|
||||
'deviceGroupId' => $this->request->param('deviceGroupId', ''),
|
||||
'containSubDepartment' => $this->request->param('containSubDepartment', 'false'),
|
||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : $this->request->param('pageIndex', 0),
|
||||
'pageSize' => !empty($pageSize) ? $pageSize : $this->request->param('pageSize', 10)
|
||||
];
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'plain');
|
||||
|
||||
// 发送请求获取基本信息
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatAccount/list', $params, 'GET', $header);
|
||||
$response = handleApiResponse($result);
|
||||
// 保存基本数据到数据库
|
||||
if (!empty($response['results'])) {
|
||||
foreach ($response['results'] as $item) {
|
||||
$this->saveWechatAccount($item);
|
||||
}
|
||||
|
||||
// 获取并更新微信账号状态信息
|
||||
$this->getListTenantWechatPartial($authorization);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => '获取微信账号列表成功', 'data' => $response]);
|
||||
} else {
|
||||
return successJson($response);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取微信账号列表失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取微信账号列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取微信账号状态信息
|
||||
*
|
||||
* @param string $authorization 授权token
|
||||
* @param int $pageIndex 页码,默认为1
|
||||
* @param int $pageSize 每页数量,默认为40
|
||||
* @return \think\response\Json|void
|
||||
*/
|
||||
public function getListTenantWechatPartial($authorization = '', $pageIndex = 1, $pageSize = 40)
|
||||
{
|
||||
// 获取授权token(如果未传入)
|
||||
if (empty($authorization)) {
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// 从数据库获取微信账号和设备信息
|
||||
$wechatList = Db::table('s2_wechat_account')
|
||||
->where('imei', 'not null')
|
||||
->page($pageIndex, $pageSize)
|
||||
->select();
|
||||
if (empty($wechatList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造请求参数
|
||||
$wechatAccountIds = [];
|
||||
$deviceIds = [];
|
||||
$accountIds = [];
|
||||
|
||||
foreach ($wechatList as $item) {
|
||||
$wechatAccountIds[] = $item['id'];
|
||||
$deviceIds[] = $item['currentDeviceId'] ?: 0;
|
||||
$accountIds[] = $item['deviceAccountId'] ?: 0;
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization, 'json');
|
||||
|
||||
$params = [
|
||||
'wechatAccountIdsStr' => json_encode($wechatAccountIds),
|
||||
'deviceIdsStr' => json_encode($deviceIds),
|
||||
'accountIdsStr' => json_encode($accountIds),
|
||||
'groupId' => ''
|
||||
];
|
||||
// 发送请求获取状态信息
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatAccount/listTenantWechatPartial', $params, 'GET', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
// 如果请求成功并返回数据,则更新数据库
|
||||
if (!empty($response)) {
|
||||
$this->batchUpdateWechatAccounts($response);
|
||||
}
|
||||
|
||||
// 递归调用获取下一页数据
|
||||
$this->getListTenantWechatPartial($authorization, $pageIndex + 1, $pageSize);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if (empty($authorization)) { // 只有作为独立API调用时才返回
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新微信账号数据
|
||||
*
|
||||
* @param array $data 接口返回的数据
|
||||
*/
|
||||
private function batchUpdateWechatAccounts($data)
|
||||
{
|
||||
// 更新微信账号信息
|
||||
if (!empty($data['totalFriend'])) {
|
||||
// 遍历所有微信账号ID
|
||||
$wechatIds = array_keys($data['totalFriend']);
|
||||
foreach ($wechatIds as $wechatId) {
|
||||
// 构建更新数据
|
||||
$updateData = [
|
||||
'maleFriend' => $data['maleFriend'][$wechatId] ?? 0,
|
||||
'femaleFriend' => $data['femaleFriend'][$wechatId] ?? 0,
|
||||
'unknowFriend' => $data['unknowFriend'][$wechatId] ?? 0,
|
||||
'totalFriend' => $data['totalFriend'][$wechatId] ?? 0,
|
||||
'yesterdayMsgCount' => $data['yesterdayMsgCount'][$wechatId] ?? 0,
|
||||
'sevenDayMsgCount' => $data['sevenDayMsgCount'][$wechatId] ?? 0,
|
||||
'thirtyDayMsgCount' => $data['thirtyDayMsgCount'][$wechatId] ?? 0,
|
||||
'wechatAlive' => isset($data['wechatAlive'][$wechatId]) ? (int)$data['wechatAlive'][$wechatId] : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
if (!empty($updateData['wechatAlive'])) {
|
||||
$updateData['wechatAliveTime'] = time();
|
||||
}
|
||||
|
||||
|
||||
// 更新数据库
|
||||
Db::table('s2_wechat_account')
|
||||
->where('id', $wechatId)
|
||||
->update($updateData);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新设备状态
|
||||
if (!empty($data['deviceAlive'])) {
|
||||
foreach ($data['deviceAlive'] as $deviceId => $isAlive) {
|
||||
// 更新微信账号的设备状态
|
||||
Db::table('s2_wechat_account')
|
||||
->where('currentDeviceId', $deviceId)
|
||||
->update([
|
||||
'deviceAlive' => (int)$isAlive,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 更新设备表的状态
|
||||
Db::table('s2_device')
|
||||
->where('id', $deviceId)
|
||||
->update([
|
||||
'alive' => (int)$isAlive,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存微信账号基本数据到数据库
|
||||
*
|
||||
* @param array $item 微信账号数据
|
||||
*/
|
||||
private function saveWechatAccount($item)
|
||||
{
|
||||
// 处理时间字段
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : 0;
|
||||
$deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : 0;
|
||||
|
||||
// 构建数据
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'wechatId' => $item['wechatId'] ?? '',
|
||||
'deviceAccountId' => $item['deviceAccountId'] ?? 0,
|
||||
'imei' => $item['imei'] ?? '',
|
||||
'deviceMemo' => $item['deviceMemo'] ?? '',
|
||||
'accountUserName' => $item['accountUserName'] ?? '',
|
||||
'accountRealName' => $item['accountRealName'] ?? '',
|
||||
'accountNickname' => $item['accountNickname'] ?? '',
|
||||
'wechatGroupName' => $item['wechatGroupName'] ?? '',
|
||||
'alias' => $item['alias'] ?? '',
|
||||
'tenantId' => $item['tenantId'] ?? 0,
|
||||
'nickname' => $item['nickname'] ?? '',
|
||||
'avatar' => $item['avatar'] ?? '',
|
||||
'gender' => $item['gender'] ?? 0,
|
||||
'region' => $item['region'] ?? '',
|
||||
'signature' => $item['signature'] ?? '',
|
||||
'bindQQ' => $item['bindQQ'] ?? '',
|
||||
'bindEmail' => $item['bindEmail'] ?? '',
|
||||
'bindMobile' => $item['bindMobile'] ?? '',
|
||||
'currentDeviceId' => $item['currentDeviceId'] ?? 0,
|
||||
'isDeleted' => $item['isDeleted'] ?? 0,
|
||||
'groupId' => $item['groupId'] ?? 0,
|
||||
'memo' => $item['memo'] ?? '',
|
||||
'wechatVersion' => $item['wechatVersion'] ?? '',
|
||||
'labels' => !empty($item['labels']) ? json_encode($item['labels']) : json_encode([]),
|
||||
'createTime' => $createTime,
|
||||
'deleteTime' => $deleteTime,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 保存或更新数据
|
||||
$account = WechatAccountModel::where('id', $item['id'])->find();
|
||||
if ($account) {
|
||||
$account->save($data);
|
||||
} else {
|
||||
WechatAccountModel::create($data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function chatroomCreate($data = [])
|
||||
{
|
||||
|
||||
$authorization = $this->authorization;
|
||||
|
||||
if (empty($authorization)) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['Client:system'];
|
||||
$header = setHeader($headerData, $authorization,'json');
|
||||
$params = [
|
||||
"chatroomOperateType" => 7,
|
||||
"extra" => "{chatroomName:{$data['chatroomName']}}",
|
||||
"wechatAccountId" => $data['wechatAccountId'],
|
||||
"wechatChatroomId" => 0,
|
||||
"wechatFriendIds" => $data['wechatFriendIds']
|
||||
];
|
||||
|
||||
// 发送请求获取状态信息
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatChatroom/chatroomOperate', $params, 'POST', $header,'json');
|
||||
$response = handleApiResponse($result);
|
||||
if (!empty($response)) {
|
||||
return json_encode(['code' => 500, 'msg' =>$response]);
|
||||
}else{
|
||||
return json_encode(['code' => 200, 'msg' =>'成功']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if (empty($authorization)) { // 只有作为独立API调用时才返回
|
||||
return json_encode(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
165
application/api/controller/WechatFriendController.php
Normal file
165
application/api/controller/WechatFriendController.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\api\model\WechatFriendModel;
|
||||
use think\facade\Request;
|
||||
use think\facade\Log;
|
||||
|
||||
class WechatFriendController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取微信好友列表数据
|
||||
* @param string $pageIndex 页码
|
||||
* @param string $pageSize 每页大小
|
||||
* @param string $preFriendId 上一个好友ID
|
||||
* @param bool $isInner 是否为任务调用
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getlist($data = [], $isInner = false,$isDel = '')
|
||||
{
|
||||
// 获取授权token
|
||||
$authorization = trim($this->request->header('authorization', $this->authorization));
|
||||
if (empty($authorization)) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '缺少授权信息']);
|
||||
} else {
|
||||
return errorJson('缺少授权信息');
|
||||
}
|
||||
}
|
||||
|
||||
$pageIndex = !empty($data['pageIndex']) ? $data['pageIndex'] : '';
|
||||
$pageSize = !empty($data['pageSize']) ? $data['pageSize'] : '';
|
||||
$preFriendId = !empty($data['preFriendId']) ? $data['preFriendId'] : '';
|
||||
$friendKeyword = !empty($data['friendKeyword']) ? $data['friendKeyword'] : '';
|
||||
$wechatAccountKeyword = !empty($data['wechatAccountKeyword']) ? $data['wechatAccountKeyword'] : '';
|
||||
|
||||
|
||||
try {
|
||||
// 初始化isUpdate标志为false
|
||||
$isUpdate = false;
|
||||
|
||||
// 根据isDel设置对应的isDeleted值
|
||||
$isDeleted = null; // 默认值
|
||||
if ($isDel == '0' || $isDel == 0) {
|
||||
$isDeleted = false;
|
||||
} elseif ($isDel == '1' || $isDel == 1) {
|
||||
$isDeleted = true;
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
'accountKeyword' => '',
|
||||
'addFrom' => '[]',
|
||||
'allotAccountId' => input('allotAccountId', ''),
|
||||
'containSubDepartment' => false,
|
||||
'departmentId' => '',
|
||||
'extendFields' => '{}',
|
||||
'gender' => '',
|
||||
'groupId' => null,
|
||||
'isDeleted' => $isDeleted,
|
||||
'isPass' => null,
|
||||
'keyword' => input('keyword', ''),
|
||||
'labels' => '[]',
|
||||
'pageIndex' => !empty($pageIndex) ? $pageIndex : input('pageIndex', 0),
|
||||
'pageSize' => !empty($pageSize) ? $pageSize : input('pageSize', 20),
|
||||
'preFriendId' => !empty($preFriendId) ? $preFriendId : input('preFriendId', ''),
|
||||
'friendKeyword' => !empty($friendKeyword) ? $friendKeyword : input('friendKeyword', ''),
|
||||
'wechatAccountKeyword' => !empty($wechatAccountKeyword) ? $wechatAccountKeyword : input('wechatAccountKeyword', '')
|
||||
];
|
||||
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $authorization);
|
||||
|
||||
// 发送请求获取好友列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatFriend/friendlistData', $params, 'POST', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 保存数据到数据库
|
||||
if (is_array($response)) {
|
||||
$isUpdate = false;
|
||||
foreach ($response as $item) {
|
||||
$updated = $this->saveFriend($item);
|
||||
if($updated && $isDel == 0){
|
||||
$isUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 200, 'msg' => 'success', 'data' => $response, 'isUpdate' => $isUpdate]);
|
||||
} else {
|
||||
return successJson($response);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if ($isInner) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取微信好友列表失败:' . $e->getMessage()]);
|
||||
} else {
|
||||
return errorJson('获取微信好友列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存微信好友数据到数据库
|
||||
* @param array $item 微信好友数据
|
||||
* @return bool 是否创建或更新了记录
|
||||
*/
|
||||
private function saveFriend($item)
|
||||
{
|
||||
$data = [
|
||||
'id' => $item['id'],
|
||||
'wechatAccountId' => $item['wechatAccountId'],
|
||||
'alias' => $item['alias'],
|
||||
'wechatId' => $item['wechatId'],
|
||||
'conRemark' => $item['conRemark'],
|
||||
'nickname' => $item['nickname'],
|
||||
'pyInitial' => $item['pyInitial'],
|
||||
'quanPin' => $item['quanPin'],
|
||||
'avatar' => $item['avatar'],
|
||||
'gender' => $item['gender'],
|
||||
'region' => $item['region'],
|
||||
'addFrom' => $item['addFrom'],
|
||||
'labels' => is_array($item['labels']) ? json_encode($item['labels']) : json_encode([]),
|
||||
'siteLabels' => json_encode([]),
|
||||
'signature' => $item['signature'],
|
||||
'isDeleted' => $item['isDeleted'],
|
||||
'isPassed' => $item['isPassed'],
|
||||
'deleteTime' => !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : 0,
|
||||
'accountId' => $item['accountId'],
|
||||
'extendFields' => is_array($item['extendFields']) ? json_encode($item['extendFields']) : json_encode([]),
|
||||
'accountUserName' => $item['accountUserName'],
|
||||
'accountRealName' => $item['accountRealName'],
|
||||
'accountNickname' => $item['accountNickname'],
|
||||
'ownerAlias' => $item['ownerAlias'],
|
||||
'ownerWechatId' => $item['ownerWechatId'],
|
||||
'ownerNickname' => $item['ownerNickname'],
|
||||
'ownerAvatar' => $item['ownerAvatar'],
|
||||
'phone' => $item['phone'],
|
||||
'thirdParty' => is_array($item['thirdParty']) ? json_encode($item['thirdParty']) : json_encode([]),
|
||||
'groupId' => $item['groupId'],
|
||||
'passTime' => !empty($item['isPassed']) && $item['passTime'] != '0001-01-01T00:00:00' ? strtotime($item['passTime']) : 0,
|
||||
'additionalPicture' => $item['additionalPicture'],
|
||||
'desc' => $item['desc'],
|
||||
'country' => $item['country'],
|
||||
'privince' => isset($item['privince']) ? $item['privince'] : '',
|
||||
'city' => isset($item['city']) ? $item['city'] : '',
|
||||
'createTime' => isset($item['createTime']) ? strtotime($item['createTime']) : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 使用ID作为唯一性判断
|
||||
$friend = WechatFriendModel::where('id', $item['id'])->find();
|
||||
|
||||
if ($friend) {
|
||||
unset($data['siteLabels']);
|
||||
$friend->save($data);
|
||||
return true;
|
||||
} else {
|
||||
WechatFriendModel::create($data);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
application/api/model/AllotRuleModel.php
Normal file
11
application/api/model/AllotRuleModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AllotRuleModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_allot_rule';
|
||||
}
|
||||
11
application/api/model/CallRecordingModel.php
Normal file
11
application/api/model/CallRecordingModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class CallRecordingModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_call_recording';
|
||||
}
|
||||
11
application/api/model/CompanyAccountModel.php
Normal file
11
application/api/model/CompanyAccountModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class CompanyAccountModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_company_account';
|
||||
}
|
||||
10
application/api/model/CompanyModel.php
Normal file
10
application/api/model/CompanyModel.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class CompanyModel extends Model {
|
||||
// 设置表名
|
||||
protected $table = 's2_department';
|
||||
}
|
||||
29
application/api/model/DeviceGroupModel.php
Normal file
29
application/api/model/DeviceGroupModel.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class DeviceGroupModel extends Model {
|
||||
// 设置表名
|
||||
protected $table = 's2_device_group';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
// 设置字段类型
|
||||
protected $type = [
|
||||
'id' => 'integer',
|
||||
'tenantId' => 'integer',
|
||||
'count' => 'integer',
|
||||
'createTime' => 'integer',
|
||||
'updateTime' => 'integer'
|
||||
];
|
||||
}
|
||||
10
application/api/model/DeviceModel.php
Normal file
10
application/api/model/DeviceModel.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class DeviceModel extends Model {
|
||||
// 设置表名
|
||||
protected $table = 's2_device';
|
||||
}
|
||||
11
application/api/model/FriendTaskModel.php
Normal file
11
application/api/model/FriendTaskModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class FriendTaskModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_friend_task';
|
||||
}
|
||||
11
application/api/model/WechatAccountModel.php
Normal file
11
application/api/model/WechatAccountModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatAccountModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_account';
|
||||
}
|
||||
11
application/api/model/WechatChatroomMemberModel.php
Normal file
11
application/api/model/WechatChatroomMemberModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatChatroomMemberModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_chatroom_member';
|
||||
}
|
||||
11
application/api/model/WechatChatroomModel.php
Normal file
11
application/api/model/WechatChatroomModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatChatroomModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_chatroom';
|
||||
}
|
||||
16
application/api/model/WechatFriendModel.php
Normal file
16
application/api/model/WechatFriendModel.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatFriendModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_friend';
|
||||
protected $pk = 'id';
|
||||
|
||||
/*protected $pk = [
|
||||
'uk_owner_wechat_account' => ['ownerWechatId', 'wechatId','wechatAccountId'] // uk_owner_wechat_account 是数据库中组合唯一键的名称
|
||||
];*/
|
||||
}
|
||||
11
application/api/model/WechatMessageModel.php
Normal file
11
application/api/model/WechatMessageModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatMessageModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_message';
|
||||
}
|
||||
11
application/api/model/WechatMomentsModel.php
Normal file
11
application/api/model/WechatMomentsModel.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class WechatMomentsModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_moments';
|
||||
}
|
||||
214
application/chukebao/config/route.php
Normal file
214
application/chukebao/config/route.php
Normal file
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | 设备管理模块路由配置
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Route;
|
||||
|
||||
// 定义RESTful风格的API路由
|
||||
Route::group('v1/', function () {
|
||||
|
||||
Route::group('kefu/', function () {
|
||||
//好友相关
|
||||
Route::group('wechatFriend/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表
|
||||
Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情
|
||||
Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料
|
||||
// 添加好友任务记录相关接口
|
||||
Route::get('addTaskList', 'app\chukebao\controller\WechatFriendController@getAddTaskList'); // 获取添加好友任务记录列表(包含添加者信息、状态、时间等,支持状态筛选,无需传好友ID)
|
||||
});
|
||||
//群相关
|
||||
Route::group('wechatChatroom/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatChatroomController@getList'); // 获取好友列表
|
||||
Route::get('detail', 'app\chukebao\controller\WechatChatroomController@getDetail'); // 获取群详情
|
||||
Route::get('members', 'app\chukebao\controller\WechatChatroomController@getMembers'); // 获取群成员列表
|
||||
Route::post('aiAnnouncement', 'app\chukebao\controller\WechatChatroomController@aiAnnouncement'); // AI群公告
|
||||
});
|
||||
|
||||
//客服相关
|
||||
Route::group('customerService/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\CustomerServiceController@getList'); // 获取好友列表
|
||||
});
|
||||
|
||||
//账号管理
|
||||
Route::group('accounts/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AccountsController@getList'); // 获取账号列表
|
||||
});
|
||||
|
||||
//消息相关
|
||||
Route::group('message/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\MessageController@getList'); // 获取好友列表
|
||||
Route::get('readMessage', 'app\chukebao\controller\MessageController@readMessage'); // 读取消息
|
||||
Route::get('details', 'app\chukebao\controller\MessageController@details'); // 消息详情
|
||||
Route::get('getMessageStatus', 'app\chukebao\controller\MessageController@getMessageStatus'); // 获取单条消息发送状态
|
||||
});
|
||||
|
||||
//微信分组
|
||||
Route::group('wechatGroup/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatGroupController@getList'); // 获取分组列表
|
||||
Route::post('add', 'app\chukebao\controller\WechatGroupController@create'); // 新增分组
|
||||
Route::post('update', 'app\chukebao\controller\WechatGroupController@update'); // 更新分组
|
||||
Route::delete('delete', 'app\chukebao\controller\WechatGroupController@delete'); // 删除分组(假删除)
|
||||
Route::post('move', 'app\chukebao\controller\WechatGroupController@move'); // 移动分组(好友/群移动到指定分组)
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
//AI相关
|
||||
Route::group('ai/', function () {
|
||||
//问答
|
||||
Route::group('questions/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\QuestionsController@getList'); // 问答列表
|
||||
Route::post('add', 'app\chukebao\controller\QuestionsController@create'); // 问答添加
|
||||
Route::post('update', 'app\chukebao\controller\QuestionsController@update'); // 问答更新
|
||||
Route::delete('delete', 'app\chukebao\controller\QuestionsController@delete'); // 问答删除
|
||||
Route::get('detail', 'app\chukebao\controller\QuestionsController@detail'); // 问答详情
|
||||
});
|
||||
|
||||
//全局配置
|
||||
Route::group('settings/', function () {
|
||||
Route::get('get', 'app\chukebao\controller\AiSettingsController@getSetting');
|
||||
Route::post('set', 'app\chukebao\controller\AiSettingsController@setSetting');
|
||||
});
|
||||
|
||||
//好友配置
|
||||
Route::group('friend/', function () {
|
||||
Route::post('set', 'app\chukebao\controller\AiSettingsController@setFriend');
|
||||
Route::get('get', 'app\chukebao\controller\AiSettingsController@getFriend');
|
||||
Route::post('setAll', 'app\chukebao\controller\AiSettingsController@setAllFriend');
|
||||
});
|
||||
|
||||
|
||||
//ai对话
|
||||
Route::get('getUserTokens', 'app\chukebao\controller\AiSettingsController@getUserTokens');
|
||||
Route::post('chat', 'app\chukebao\controller\AiChatController@index');
|
||||
|
||||
});
|
||||
|
||||
|
||||
//代办事项
|
||||
Route::group('todo/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\ToDoController@getList');
|
||||
Route::post('add', 'app\chukebao\controller\ToDoController@create');
|
||||
Route::get('process', 'app\chukebao\controller\ToDoController@process');
|
||||
});
|
||||
|
||||
|
||||
//跟进提醒
|
||||
Route::group('followUp/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\FollowUpController@getList');
|
||||
Route::post('add', 'app\chukebao\controller\FollowUpController@create');
|
||||
Route::get('process', 'app\chukebao\controller\FollowUpController@process');
|
||||
});
|
||||
|
||||
|
||||
//算力相关
|
||||
Route::group('tokensRecord/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\TokensRecordController@getList');
|
||||
});
|
||||
|
||||
|
||||
|
||||
//内容管理
|
||||
Route::group('content/', function () {
|
||||
//素材管理
|
||||
Route::group('material/', function () {
|
||||
Route::get('all', 'app\chukebao\controller\ContentController@getAllMaterial');
|
||||
Route::get('list', 'app\chukebao\controller\ContentController@getMaterial');
|
||||
Route::post('add', 'app\chukebao\controller\ContentController@createMaterial');
|
||||
Route::get('details', 'app\chukebao\controller\ContentController@detailsMaterial');
|
||||
Route::delete('del', 'app\chukebao\controller\ContentController@delMaterial');
|
||||
Route::post('update', 'app\chukebao\controller\ContentController@updateMaterial');
|
||||
});
|
||||
|
||||
//违禁词管理
|
||||
Route::group('sensitiveWord/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\ContentController@getSensitiveWord');
|
||||
Route::post('add', 'app\chukebao\controller\ContentController@createSensitiveWord');
|
||||
Route::get('details', 'app\chukebao\controller\ContentController@detailsSensitiveWord');
|
||||
Route::delete('del', 'app\chukebao\controller\ContentController@delSensitiveWord');
|
||||
Route::post('update', 'app\chukebao\controller\ContentController@updateSensitiveWord');
|
||||
Route::get('setStatus', 'app\chukebao\controller\ContentController@setSensitiveWordStatus');
|
||||
});
|
||||
|
||||
|
||||
//关键词管理
|
||||
Route::group('keywords/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\ContentController@getKeywords');
|
||||
Route::post('add', 'app\chukebao\controller\ContentController@createKeywords');
|
||||
Route::get('details', 'app\chukebao\controller\ContentController@detailsKeywords');
|
||||
Route::delete('del', 'app\chukebao\controller\ContentController@delKeywords');
|
||||
Route::post('update', 'app\chukebao\controller\ContentController@updateKeywords');
|
||||
Route::get('setStatus', 'app\chukebao\controller\ContentController@setKeywordStatus');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//自动问候
|
||||
Route::group('autoGreetings/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AutoGreetingsController@getList');
|
||||
Route::post('add', 'app\chukebao\controller\AutoGreetingsController@create');
|
||||
Route::get('details', 'app\chukebao\controller\AutoGreetingsController@details');
|
||||
Route::delete('del', 'app\chukebao\controller\AutoGreetingsController@del');
|
||||
Route::post('update', 'app\chukebao\controller\AutoGreetingsController@update');
|
||||
Route::get('setStatus', 'app\chukebao\controller\AutoGreetingsController@setStatus');
|
||||
Route::get('copy', 'app\chukebao\controller\AutoGreetingsController@copy');
|
||||
Route::get('stats', 'app\chukebao\controller\AutoGreetingsController@stats');
|
||||
});
|
||||
|
||||
//AI智能推送
|
||||
Route::group('aiPush/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AiPushController@getList'); // 获取推送列表
|
||||
Route::post('add', 'app\chukebao\controller\AiPushController@add'); // 添加推送
|
||||
Route::get('details', 'app\chukebao\controller\AiPushController@details'); // 推送详情
|
||||
Route::delete('del', 'app\chukebao\controller\AiPushController@del'); // 删除推送
|
||||
Route::post('update', 'app\chukebao\controller\AiPushController@update'); // 更新推送
|
||||
Route::get('setStatus', 'app\chukebao\controller\AiPushController@setStatus'); // 修改状态
|
||||
Route::get('stats', 'app\chukebao\controller\AiPushController@stats'); // 统计概览
|
||||
});
|
||||
|
||||
|
||||
Route::group('notice/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\NoticeController@getList');
|
||||
Route::put('readMessage', 'app\chukebao\controller\NoticeController@readMessage');
|
||||
Route::put('readAll', 'app\chukebao\controller\NoticeController@readAll');
|
||||
});
|
||||
|
||||
Route::group('reply/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\ReplyController@getList');
|
||||
Route::post('addGroup', 'app\chukebao\controller\ReplyController@addGroup');
|
||||
Route::post('addReply', 'app\chukebao\controller\ReplyController@addReply');
|
||||
Route::post('updateGroup', 'app\chukebao\controller\ReplyController@updateGroup');
|
||||
Route::post('updateReply', 'app\chukebao\controller\ReplyController@updateReply');
|
||||
Route::delete('deleteGroup', 'app\chukebao\controller\ReplyController@deleteGroup');
|
||||
Route::delete('deleteReply', 'app\chukebao\controller\ReplyController@deleteReply');
|
||||
});
|
||||
|
||||
|
||||
Route::group('moments/', function () {
|
||||
Route::post('add', 'app\chukebao\controller\MomentsController@create');
|
||||
Route::post('update', 'app\chukebao\controller\MomentsController@update');
|
||||
Route::delete('delete', 'app\chukebao\controller\MomentsController@delete');
|
||||
Route::get('list', 'app\chukebao\controller\MomentsController@getList');
|
||||
});
|
||||
|
||||
|
||||
Route::post('dataProcessing', 'app\chukebao\controller\DataProcessing@index'); // 修改数据
|
||||
|
||||
});
|
||||
|
||||
|
||||
})->middleware(['jwt']);
|
||||
|
||||
|
||||
// 客服登录
|
||||
Route::group('v1/kefu', function () {
|
||||
Route::post('login', 'app\chukebao\controller\LoginController@index'); // 登录
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return [];
|
||||
71
application/chukebao/controller/AccountsController.php
Normal file
71
application/chukebao/controller/AccountsController.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class AccountsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取账号列表(过滤掉后缀为 _offline 与 _delete 的账号)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 401);
|
||||
}
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录', 401);
|
||||
}
|
||||
|
||||
$page = max(1, intval($this->request->param('page', 1)));
|
||||
$limit = max(1, intval($this->request->param('limit', 10)));
|
||||
$keyword = trim((string)$this->request->param('keyword', ''));
|
||||
|
||||
$query = Db::table('s2_company_account')
|
||||
->alias('a')
|
||||
->join('users u', 'a.id = u.s2_accountId')
|
||||
->where([
|
||||
['a.departmentId', '=', $companyId],
|
||||
['a.status', '=', 0],
|
||||
])
|
||||
->whereNotLike('a.userName', '%_offline')
|
||||
->whereNotLike('a.userName', '%_delete');
|
||||
|
||||
if ($keyword !== '') {
|
||||
$query->where(function ($subQuery) use ($keyword) {
|
||||
$likeKeyword = '%' . $keyword . '%';
|
||||
$subQuery->whereLike('a.userName', $likeKeyword)
|
||||
->whereOrLike('a.realName', $likeKeyword)
|
||||
->whereOrLike('a.nickname', $likeKeyword);
|
||||
});
|
||||
}
|
||||
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->field([
|
||||
'a.id',
|
||||
'u.id as uid',
|
||||
'a.userName',
|
||||
'a.realName',
|
||||
'a.nickname',
|
||||
'a.departmentId',
|
||||
'a.departmentName',
|
||||
'a.avatar'
|
||||
])
|
||||
->order('a.id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
|
||||
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
]);
|
||||
}
|
||||
}
|
||||
832
application/chukebao/controller/AiChatController.php
Normal file
832
application/chukebao/controller/AiChatController.php
Normal file
@@ -0,0 +1,832 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\ai\controller\CozeAI;
|
||||
use app\ai\controller\DouBaoAI;
|
||||
use app\api\model\WechatFriendModel;
|
||||
use app\chukebao\controller\TokensRecordController as tokensRecord;
|
||||
use app\chukebao\model\AiSettings;
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use app\chukebao\model\TokensCompany;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Cache;
|
||||
use think\facade\Log;
|
||||
|
||||
|
||||
/**
|
||||
* AI聊天控制器
|
||||
* 负责处理与好友的AI对话功能
|
||||
*/
|
||||
class AiChatController extends BaseController
|
||||
{
|
||||
// 对话状态常量
|
||||
const STATUS_CREATED = 'created'; // 对话已创建
|
||||
const STATUS_IN_PROGRESS = 'in_progress'; // 智能体正在处理中
|
||||
const STATUS_COMPLETED = 'completed'; // 智能体已完成处理
|
||||
const STATUS_FAILED = 'failed'; // 对话失败
|
||||
const STATUS_REQUIRES_ACTION = 'requires_action'; // 对话中断,需要进一步处理
|
||||
const STATUS_CANCELED = 'canceled'; // 对话已取消
|
||||
|
||||
// 轮询配置
|
||||
const MAX_RETRY_TIMES = 1000; // 最大重试次数
|
||||
const RETRY_INTERVAL = 500000; // 重试间隔(微秒,即500毫秒)
|
||||
|
||||
// 并发控制
|
||||
const CACHE_EXPIRE = 30; // 缓存过期时间(秒)
|
||||
|
||||
// 请求唯一标识符
|
||||
private $requestKey = '';
|
||||
private $requestId = '';
|
||||
private $currentStep = 0;
|
||||
|
||||
/**
|
||||
* AI聊天主入口
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 1. 参数验证和初始化
|
||||
$params = $this->validateAndInitParams();
|
||||
|
||||
if ($params === false) {
|
||||
return ResponseHelper::error('参数验证失败');
|
||||
}
|
||||
|
||||
// 并发控制:检查并处理同一用户的重复请求
|
||||
$this->requestKey = "aichat_{$params['friendId']}_{$params['wechatAccountId']}";
|
||||
$this->requestId = uniqid('req_', true);
|
||||
|
||||
$concurrentCheck = $this->handleConcurrentRequest($params);
|
||||
if ($concurrentCheck !== true) {
|
||||
return $concurrentCheck; // 返回错误响应
|
||||
}
|
||||
|
||||
$this->currentStep = 1;
|
||||
|
||||
// 2. 验证Tokens余额
|
||||
$this->updateRequestStep(2);
|
||||
if ($this->isRequestCanceled()) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$hasBalance = $this->checkTokensBalance($params['companyId']);
|
||||
|
||||
if (!$hasBalance) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error('Tokens余额不足,请充值后再试');
|
||||
}
|
||||
|
||||
// 3. 获取AI配置
|
||||
$this->updateRequestStep(3);
|
||||
if ($this->isRequestCanceled()) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$setting = $this->getAiSettings($params['companyId']);
|
||||
|
||||
if (!$setting) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error('未找到AI配置信息,请先配置AI策略');
|
||||
}
|
||||
|
||||
// 4. 获取好友AI设置
|
||||
$this->updateRequestStep(4);
|
||||
if ($this->isRequestCanceled()) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$friendSettings = $this->getFriendSettings($params['companyId'], $params['friendId']);
|
||||
|
||||
if (!$friendSettings) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error('该好友未配置或未开启AI功能');
|
||||
}
|
||||
|
||||
// 5. 确保会话存在
|
||||
$this->updateRequestStep(5);
|
||||
if ($this->isRequestCanceled()) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$conversationId = $this->ensureConversation($friendSettings, $setting, $params);
|
||||
|
||||
if (empty($conversationId)) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error('创建会话失败');
|
||||
}
|
||||
|
||||
// 6. 获取历史消息
|
||||
$this->updateRequestStep(6);
|
||||
if ($this->isRequestCanceled()) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$msgData = $this->getHistoryMessages($params['friendId'], $friendSettings);
|
||||
|
||||
// 7. 创建AI对话(从这步开始需要保存对话ID以便取消)
|
||||
$this->updateRequestStep(7);
|
||||
if ($this->isRequestCanceled($conversationId, null)) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$chatId = $this->createAiChat($setting, $friendSettings, $msgData);
|
||||
|
||||
if (empty($chatId)) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error('创建对话失败');
|
||||
}
|
||||
|
||||
// 保存对话ID到缓存,以便新请求可以取消
|
||||
$this->updateRequestStep(7, $conversationId, $chatId);
|
||||
|
||||
// 8. 等待AI处理完成(轮询)
|
||||
$this->updateRequestStep(8, $conversationId, $chatId);
|
||||
if ($this->isRequestCanceled($conversationId, $chatId)) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$chatResult = $this->waitForChatCompletion($conversationId, $chatId);
|
||||
|
||||
if (!$chatResult['success']) {
|
||||
$this->clearRequestCache();
|
||||
return ResponseHelper::error($chatResult['error']);
|
||||
}
|
||||
|
||||
$chatResult = $chatResult['data'];
|
||||
|
||||
// 9. 扣除Tokens
|
||||
$this->updateRequestStep(9, $conversationId, $chatId);
|
||||
if ($this->isRequestCanceled($conversationId, $chatId)) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$this->consumeTokens($chatResult, $params, $friendSettings);
|
||||
|
||||
// 10. 获取对话消息
|
||||
$this->updateRequestStep(10, $conversationId, $chatId);
|
||||
if ($this->isRequestCanceled($conversationId, $chatId)) {
|
||||
return ResponseHelper::error('该好友有新的AI对话请求正在处理中,当前请求已被取消');
|
||||
}
|
||||
$messages = $this->getChatMessages($conversationId, $chatId);
|
||||
|
||||
if (!$messages) {
|
||||
return ResponseHelper::error('获取对话消息失败');
|
||||
}
|
||||
|
||||
// 筛选type为answer的消息(AI回复的内容)
|
||||
$answerContent = '';
|
||||
foreach ($messages as $msg) {
|
||||
if (isset($msg['type']) && $msg['type'] === 'answer') {
|
||||
$answerContent = $msg['content'] ?? '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($answerContent)) {
|
||||
Log::warning('未找到AI回复内容,messages: ' . json_encode($messages));
|
||||
return ResponseHelper::error('未获取到AI回复内容');
|
||||
}
|
||||
|
||||
// 清理请求缓存
|
||||
$this->clearRequestCache();
|
||||
|
||||
// 返回结果
|
||||
return ResponseHelper::success(['content' => $answerContent], '对话成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('AI聊天异常:' . $e->getMessage());
|
||||
|
||||
// 清理请求缓存
|
||||
$this->clearRequestCache();
|
||||
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消AI对话
|
||||
* 取消当前正在进行的AI对话请求
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
|
||||
if (empty($wechatAccountId) || empty($friendId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 生成缓存键
|
||||
$requestKey = "aichat_{$friendId}_{$wechatAccountId}";
|
||||
|
||||
// 获取缓存数据
|
||||
$cacheData = Cache::get($requestKey);
|
||||
|
||||
if (!$cacheData) {
|
||||
return ResponseHelper::error('当前没有正在进行的AI对话');
|
||||
}
|
||||
|
||||
$requestId = $cacheData['request_id'] ?? '';
|
||||
$step = $cacheData['step'] ?? 0;
|
||||
$conversationId = $cacheData['conversation_id'] ?? '';
|
||||
$chatId = $cacheData['chat_id'] ?? '';
|
||||
|
||||
Log::info("手动取消AI对话 - 请求ID: {$requestId}, 步骤: {$step}");
|
||||
|
||||
// 如果已经到达步骤7或之后,需要调用取消API
|
||||
if ($step >= 7 && !empty($conversationId) && !empty($chatId)) {
|
||||
try {
|
||||
$cozeAI = new CozeAI();
|
||||
$cancelResult = $cozeAI->cancelConversationChat([
|
||||
'conversation_id' => $conversationId,
|
||||
'chat_id' => $chatId,
|
||||
]);
|
||||
|
||||
$result = json_decode($cancelResult, true);
|
||||
if ($result['code'] != 200) {
|
||||
Log::error("调用取消API失败 - conversation_id: {$conversationId}, chat_id: {$chatId}, 错误: " . ($result['msg'] ?? '未知错误'));
|
||||
} else {
|
||||
Log::info("成功调用取消API - conversation_id: {$conversationId}, chat_id: {$chatId}");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("调用取消API异常:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
Cache::rm($requestKey);
|
||||
Log::info("已清理AI对话缓存 - 请求ID: {$requestId}");
|
||||
|
||||
return ResponseHelper::success([
|
||||
'canceled_request_id' => $requestId,
|
||||
'step' => $step
|
||||
], 'AI对话已取消');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('取消AI对话异常:' . $e->getMessage());
|
||||
return ResponseHelper::error('系统异常:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证和初始化参数
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
private function validateAndInitParams()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
|
||||
if (empty($wechatAccountId) || empty($friendId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'userId' => $userId,
|
||||
'companyId' => $companyId,
|
||||
'friendId' => $friendId,
|
||||
'wechatAccountId' => $wechatAccountId
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查Tokens余额
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @return bool
|
||||
*/
|
||||
private function checkTokensBalance($companyId)
|
||||
{
|
||||
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
|
||||
return !empty($tokens) && $tokens > 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取AI配置
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @return AiSettings|null
|
||||
*/
|
||||
private function getAiSettings($companyId)
|
||||
{
|
||||
return AiSettings::where(['companyId' => $companyId])->find();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友AI设置
|
||||
*
|
||||
* @param int $companyId 公司ID
|
||||
* @param string $friendId 好友ID
|
||||
* @return FriendSettings|null
|
||||
*/
|
||||
private function getFriendSettings($companyId, $friendId)
|
||||
{
|
||||
$friendSettings = FriendSettings::where([
|
||||
'companyId' => $companyId,
|
||||
'friendId' => $friendId
|
||||
])->find();
|
||||
|
||||
if (empty($friendSettings) || $friendSettings->type == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $friendSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保会话存在
|
||||
*
|
||||
* @param FriendSettings $friendSettings 好友设置
|
||||
* @param AiSettings $setting AI设置
|
||||
* @param array $params 参数
|
||||
* @return string|null 会话ID
|
||||
*/
|
||||
private function ensureConversation($friendSettings, $setting, $params)
|
||||
{
|
||||
if (!empty($friendSettings->conversationId)) {
|
||||
return $friendSettings->conversationId;
|
||||
}
|
||||
|
||||
// 创建新会话
|
||||
$cozeAI = new CozeAI();
|
||||
$data = [
|
||||
'bot_id' => $setting->botId,
|
||||
'name' => '与好友' . $params['friendId'] . '的对话',
|
||||
'meta_data' => [
|
||||
'friendId' => (string)$friendSettings->friendId,
|
||||
'wechatAccountId' => (string)$params['wechatAccountId'],
|
||||
],
|
||||
];
|
||||
|
||||
$res = $cozeAI->createConversation($data);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
Log::error('创建会话失败:' . ($res['msg'] ?? '未知错误'));
|
||||
return null;
|
||||
}
|
||||
|
||||
// 保存会话ID
|
||||
$conversationId = $res['data']['id'];
|
||||
$friendSettings->conversationId = $conversationId;
|
||||
$friendSettings->conversationTime = time();
|
||||
$friendSettings->save();
|
||||
return $conversationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取历史消息
|
||||
*
|
||||
* @param string $friendId 好友ID
|
||||
* @param FriendSettings $friendSettings 好友设置
|
||||
* @return array
|
||||
*/
|
||||
private function getHistoryMessages($friendId, $friendSettings)
|
||||
{
|
||||
$msgData = [];
|
||||
|
||||
// 会话创建时间小于1分钟,加载最近10条消息
|
||||
if ($friendSettings->conversationTime >= time() - 60) {
|
||||
$messages = Db::table('s2_wechat_message')
|
||||
->where('wechatFriendId', $friendId)
|
||||
->where('msgType', '<', 50)
|
||||
->order('wechatTime desc')
|
||||
->field('id,content,msgType,isSend,wechatTime')
|
||||
->limit(10)
|
||||
->select();
|
||||
|
||||
// 按时间正序排列
|
||||
usort($messages, function ($a, $b) {
|
||||
return $a['wechatTime'] <=> $b['wechatTime'];
|
||||
});
|
||||
|
||||
// 处理聊天数据
|
||||
foreach ($messages as $val) {
|
||||
if (empty($val['content'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$msg = [
|
||||
'role' => empty($val['isSend']) ? 'user' : 'assistant',
|
||||
'content' => $val['content'],
|
||||
'type' => empty($val['isSend']) ? 'question' : 'answer',
|
||||
'content_type' => 'text'
|
||||
];
|
||||
$msgData[] = $msg;
|
||||
}
|
||||
} else {
|
||||
// 只加载最新一条用户消息
|
||||
$message = Db::table('s2_wechat_message')
|
||||
->where('wechatFriendId', $friendId)
|
||||
->where('msgType', '<', 50)
|
||||
->where('isSend', 0)
|
||||
->order('wechatTime desc')
|
||||
->field('id,content,msgType,isSend,wechatTime')
|
||||
->find();
|
||||
|
||||
if (!empty($message) && !empty($message['content'])) {
|
||||
$msgData[] = [
|
||||
'role' => 'user',
|
||||
'content' => $message['content'],
|
||||
'type' => 'question',
|
||||
'content_type' => 'text'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $msgData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AI对话
|
||||
*
|
||||
* @param AiSettings $setting AI设置
|
||||
* @param FriendSettings $friendSettings 好友设置
|
||||
* @param array $msgData 消息数据
|
||||
* @return string|null 对话ID
|
||||
*/
|
||||
private function createAiChat($setting, $friendSettings, $msgData)
|
||||
{
|
||||
$cozeAI = new CozeAI();
|
||||
$data = [
|
||||
'bot_id' => $setting->botId,
|
||||
'uid' => $friendSettings->friendId,
|
||||
'conversation_id' => $friendSettings->conversationId,
|
||||
'question' => $msgData,
|
||||
];
|
||||
|
||||
$res = $cozeAI->createChat($data);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
Log::error('创建对话失败:' . ($res['msg'] ?? '未知错误'));
|
||||
return null;
|
||||
}
|
||||
|
||||
return $res['data']['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待AI处理完成(轮询机制)
|
||||
*
|
||||
* @param string $conversationId 会话ID
|
||||
* @param string $chatId 对话ID
|
||||
* @return array ['success' => bool, 'data' => array|null, 'error' => string]
|
||||
*/
|
||||
private function waitForChatCompletion($conversationId, $chatId)
|
||||
{
|
||||
$cozeAI = new CozeAI();
|
||||
$retryCount = 0;
|
||||
|
||||
while ($retryCount < self::MAX_RETRY_TIMES) {
|
||||
// 获取对话状态
|
||||
$res = $cozeAI->getConversationChat([
|
||||
'conversation_id' => $conversationId,
|
||||
'chat_id' => $chatId,
|
||||
]);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
$errorMsg = 'AI接口调用失败:' . ($res['msg'] ?? '未知错误');
|
||||
Log::error($errorMsg);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
}
|
||||
|
||||
$status = $res['data']['status'] ?? '';
|
||||
|
||||
// 处理不同的状态
|
||||
switch ($status) {
|
||||
case self::STATUS_COMPLETED:
|
||||
// 对话完成,返回结果
|
||||
return ['success' => true, 'data' => $res['data'], 'error' => ''];
|
||||
|
||||
case self::STATUS_IN_PROGRESS:
|
||||
case self::STATUS_CREATED:
|
||||
// 继续等待
|
||||
$retryCount++;
|
||||
usleep(self::RETRY_INTERVAL);
|
||||
break;
|
||||
|
||||
case self::STATUS_FAILED:
|
||||
$errorMsg = 'AI对话处理失败';
|
||||
Log::error($errorMsg . ',chat_id: ' . $chatId);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
|
||||
case self::STATUS_CANCELED:
|
||||
$errorMsg = 'AI对话已被取消';
|
||||
Log::error($errorMsg . ',chat_id: ' . $chatId);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
|
||||
case self::STATUS_REQUIRES_ACTION:
|
||||
$errorMsg = 'AI对话需要进一步处理';
|
||||
Log::warning($errorMsg . ',chat_id: ' . $chatId);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
|
||||
default:
|
||||
$errorMsg = 'AI返回未知状态:' . $status;
|
||||
Log::error($errorMsg);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
}
|
||||
}
|
||||
|
||||
// 超时
|
||||
$errorMsg = 'AI对话处理超时,已等待' . (self::MAX_RETRY_TIMES * self::RETRY_INTERVAL / 1000000) . '秒';
|
||||
Log::error($errorMsg . ',chat_id: ' . $chatId);
|
||||
return ['success' => false, 'data' => null, 'error' => $errorMsg];
|
||||
}
|
||||
|
||||
/**
|
||||
* 扣除Tokens
|
||||
*
|
||||
* @param array $chatResult 对话结果
|
||||
* @param array $params 参数
|
||||
* @param FriendSettings $friendSettings 好友设置
|
||||
*/
|
||||
private function consumeTokens($chatResult, $params, $friendSettings)
|
||||
{
|
||||
$tokenCount = $chatResult['usage']['token_count'] ?? 0;
|
||||
|
||||
if (empty($tokenCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取好友昵称
|
||||
$nickname = WechatFriendModel::where('id', $friendSettings->friendId)->value('nickname');
|
||||
$remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天';
|
||||
|
||||
// 扣除Tokens
|
||||
$tokensRecord = new tokensRecord();
|
||||
$data = [
|
||||
'tokens' => $tokenCount * 20,
|
||||
'type' => 0,
|
||||
'form' => 13,
|
||||
'wechatAccountId' => $params['wechatAccountId'],
|
||||
'friendIdOrGroupId' => $params['friendId'],
|
||||
'remarks' => $remarks,
|
||||
];
|
||||
|
||||
$tokensRecord->consumeTokens($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对话消息
|
||||
*
|
||||
* @param string $conversationId 会话ID
|
||||
* @param string $chatId 对话ID
|
||||
* @return array|null
|
||||
*/
|
||||
private function getChatMessages($conversationId, $chatId)
|
||||
{
|
||||
$cozeAI = new CozeAI();
|
||||
$res = $cozeAI->listConversationMessage([
|
||||
'conversation_id' => $conversationId,
|
||||
'chat_id' => $chatId,
|
||||
]);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] != 200) {
|
||||
Log::error('获取对话消息失败:' . ($res['msg'] ?? '未知错误'));
|
||||
return null;
|
||||
}
|
||||
|
||||
return $res['data'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理并发请求
|
||||
* 检查是否有同一用户的旧请求正在处理,如果有则取消旧请求
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @return true|\think\response\Json true表示可以继续,否则返回错误响应
|
||||
*/
|
||||
private function handleConcurrentRequest($params)
|
||||
{
|
||||
$cacheData = Cache::get($this->requestKey);
|
||||
|
||||
if ($cacheData) {
|
||||
// 有旧请求正在处理
|
||||
$oldRequestId = $cacheData['request_id'] ?? '';
|
||||
$oldStep = $cacheData['step'] ?? 0;
|
||||
$oldConversationId = $cacheData['conversation_id'] ?? '';
|
||||
$oldChatId = $cacheData['chat_id'] ?? '';
|
||||
|
||||
Log::info("检测到并发请求 - 旧请求: {$oldRequestId} (步骤{$oldStep}), 新请求: {$this->requestId}");
|
||||
|
||||
// 如果旧请求已经到达步骤7或之后,需要调用取消API
|
||||
if ($oldStep >= 7 && !empty($oldConversationId) && !empty($oldChatId)) {
|
||||
try {
|
||||
$cozeAI = new CozeAI();
|
||||
$cancelResult = $cozeAI->cancelConversationChat([
|
||||
'conversation_id' => $oldConversationId,
|
||||
'chat_id' => $oldChatId,
|
||||
]);
|
||||
Log::info("已调用取消API取消旧请求的对话 - conversation_id: {$oldConversationId}, chat_id: {$oldChatId}");
|
||||
} catch (\Exception $e) {
|
||||
Log::error("取消旧请求对话失败:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 标记旧请求为已取消(通过更新缓存的 canceled 标志)
|
||||
$cacheData['canceled'] = true;
|
||||
$cacheData['canceled_by'] = $this->requestId;
|
||||
Cache::set($this->requestKey, $cacheData, self::CACHE_EXPIRE);
|
||||
}
|
||||
|
||||
// 设置当前请求为活动请求
|
||||
$newCacheData = [
|
||||
'request_id' => $this->requestId,
|
||||
'step' => 1,
|
||||
'start_time' => time(),
|
||||
'canceled' => false,
|
||||
'conversation_id' => '',
|
||||
'chat_id' => '',
|
||||
];
|
||||
Cache::set($this->requestKey, $newCacheData, self::CACHE_EXPIRE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前请求是否被新请求取消
|
||||
*
|
||||
* @param string $conversationId 会话ID(可选,用于取消对话)
|
||||
* @param string $chatId 对话ID(可选,用于取消对话)
|
||||
* @return bool
|
||||
*/
|
||||
private function isRequestCanceled($conversationId = '', $chatId = '')
|
||||
{
|
||||
$cacheData = Cache::get($this->requestKey);
|
||||
|
||||
if (!$cacheData) {
|
||||
// 缓存不存在,说明被清理或过期,视为被取消
|
||||
return true;
|
||||
}
|
||||
|
||||
$currentRequestId = $cacheData['request_id'] ?? '';
|
||||
$isCanceled = $cacheData['canceled'] ?? false;
|
||||
|
||||
// 如果缓存中的请求ID与当前请求ID不一致,或者被标记为取消
|
||||
if ($currentRequestId !== $this->requestId || $isCanceled) {
|
||||
Log::info("当前请求已被取消 - 请求ID: {$this->requestId}, 缓存请求ID: {$currentRequestId}, 取消标志: " . ($isCanceled ? 'true' : 'false'));
|
||||
|
||||
// 如果提供了对话ID,尝试取消对话
|
||||
if (!empty($conversationId) && !empty($chatId) && $this->currentStep >= 7) {
|
||||
try {
|
||||
$cozeAI = new CozeAI();
|
||||
$cancelResult = $cozeAI->cancelConversationChat([
|
||||
'conversation_id' => $conversationId,
|
||||
'chat_id' => $chatId,
|
||||
]);
|
||||
Log::info("已取消当前请求的对话 - conversation_id: {$conversationId}, chat_id: {$chatId}");
|
||||
} catch (\Exception $e) {
|
||||
Log::error("取消当前请求对话失败:" . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新请求步骤
|
||||
*
|
||||
* @param int $step 当前步骤
|
||||
* @param string $conversationId 会话ID(可选)
|
||||
* @param string $chatId 对话ID(可选)
|
||||
*/
|
||||
private function updateRequestStep($step, $conversationId = '', $chatId = '')
|
||||
{
|
||||
$this->currentStep = $step;
|
||||
|
||||
$cacheData = Cache::get($this->requestKey);
|
||||
|
||||
if ($cacheData && $cacheData['request_id'] === $this->requestId) {
|
||||
$cacheData['step'] = $step;
|
||||
$cacheData['update_time'] = time();
|
||||
|
||||
if (!empty($conversationId)) {
|
||||
$cacheData['conversation_id'] = $conversationId;
|
||||
}
|
||||
if (!empty($chatId)) {
|
||||
$cacheData['chat_id'] = $chatId;
|
||||
}
|
||||
|
||||
Cache::set($this->requestKey, $cacheData, self::CACHE_EXPIRE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理请求缓存
|
||||
*/
|
||||
private function clearRequestCache()
|
||||
{
|
||||
if (!empty($this->requestKey)) {
|
||||
$cacheData = Cache::get($this->requestKey);
|
||||
|
||||
// 只有当前请求才能清理自己的缓存
|
||||
if ($cacheData && isset($cacheData['request_id']) && $cacheData['request_id'] === $this->requestId) {
|
||||
Cache::rm($this->requestKey);
|
||||
Log::info("已清理请求缓存 - 请求ID: {$this->requestId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function index2222()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$content = $this->request->param('content', '');
|
||||
|
||||
if (empty($wechatAccountId) || empty($friendId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
|
||||
if (empty($tokens) || $tokens <= 0) {
|
||||
return ResponseHelper::error('用户Tokens余额不足');
|
||||
}
|
||||
|
||||
|
||||
//读取AI配置
|
||||
$setting = Db::name('ai_settings')->where(['companyId' => $companyId, 'userId' => $userId])->find();
|
||||
if (empty($setting)) {
|
||||
return ResponseHelper::error('未找到配置信息,请先配置AI策略');
|
||||
}
|
||||
$config = json_decode($setting['config'], true);
|
||||
$modelSetting = $config['modelSetting'];
|
||||
$round = isset($config['round']) ? $config['round'] : 10;
|
||||
|
||||
|
||||
// 导出聊天
|
||||
$messages = Db::table('s2_wechat_message')
|
||||
->where('wechatFriendId', $friendId)
|
||||
->order('wechatTime desc')
|
||||
->field('id,content,msgType,isSend,wechatTime')
|
||||
->limit($round)
|
||||
->select();
|
||||
|
||||
usort($messages, function ($a, $b) {
|
||||
return $a['wechatTime'] <=> $b['wechatTime'];
|
||||
});
|
||||
|
||||
//处理聊天数据
|
||||
$msg = [];
|
||||
foreach ($messages as $val) {
|
||||
if (empty($val['content'])) {
|
||||
continue;
|
||||
}
|
||||
if (!empty($val['isSend'])) {
|
||||
$msg[] = '客服:' . $val['content'];
|
||||
} else {
|
||||
$msg[] = '用户:' . $val['content'];
|
||||
}
|
||||
}
|
||||
$content = implode("\n", $msg);
|
||||
|
||||
|
||||
$params = [
|
||||
'model' => 'doubao-1-5-pro-32k-250115',
|
||||
'messages' => [
|
||||
// ['role' => 'system', 'content' => '请完成跟客户的对话'],
|
||||
['role' => 'system', 'content' => '角色设定:' . $modelSetting['role']],
|
||||
['role' => 'system', 'content' => '公司背景:' . $modelSetting['businessBackground']],
|
||||
['role' => 'system', 'content' => '对话风格:' . $modelSetting['dialogueStyle']],
|
||||
['role' => 'user', 'content' => $content],
|
||||
],
|
||||
];
|
||||
|
||||
//AI处理
|
||||
$ai = new DouBaoAI();
|
||||
$res = $ai->text($params);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] == 200) {
|
||||
//扣除Tokens
|
||||
$tokensRecord = new tokensRecord();
|
||||
$nickname = Db::table('s2_wechat_friend')->where(['id' => $friendId])->value('nickname');
|
||||
$remarks = !empty($nickname) ? '与好友【' . $nickname . '】聊天' : '与好友聊天';
|
||||
$data = [
|
||||
'tokens' => $res['data']['token'],
|
||||
'type' => 0,
|
||||
'form' => 13,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $friendId,
|
||||
'remarks' => $remarks,
|
||||
];
|
||||
$tokensRecord->consumeTokens($data);
|
||||
return ResponseHelper::success($res['data']['content']);
|
||||
} else {
|
||||
return ResponseHelper::error($res['msg']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
505
application/chukebao/controller/AiPushController.php
Normal file
505
application/chukebao/controller/AiPushController.php
Normal file
@@ -0,0 +1,505 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\AiPush;
|
||||
use app\chukebao\model\AiPushRecord;
|
||||
use app\chukebao\model\AutoGreetings;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class AiPushController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取推送列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['userId', '=', $userId],
|
||||
['isDel', '=', 0],
|
||||
];
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
$query = AiPush::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page, $limit)->order('id desc')->select();
|
||||
|
||||
// 处理数据
|
||||
$list = is_array($list) ? $list : $list->toArray();
|
||||
foreach ($list as &$item) {
|
||||
// 解析标签数组
|
||||
$item['tags'] = json_decode($item['tags'], true);
|
||||
if (!is_array($item['tags'])) {
|
||||
$item['tags'] = [];
|
||||
}
|
||||
// 格式化推送时机显示文本
|
||||
$timingTypes = [
|
||||
1 => '立即推送',
|
||||
2 => 'AI最佳时机',
|
||||
3 => '定时推送'
|
||||
];
|
||||
$item['timingText'] = $timingTypes[$item['pushTiming']] ?? '未知';
|
||||
// 处理定时推送时间
|
||||
if ($item['pushTiming'] == 3 && !empty($item['scheduledTime'])) {
|
||||
$item['scheduledTime'] = date('Y-m-d H:i:s', $item['scheduledTime']);
|
||||
} else {
|
||||
$item['scheduledTime'] = '';
|
||||
}
|
||||
// 从记录表计算实际成功率
|
||||
$pushId = $item['id'];
|
||||
$totalCount = Db::name('kf_ai_push_record')
|
||||
->where('pushId', $pushId)
|
||||
->count();
|
||||
$sendCount = Db::name('kf_ai_push_record')
|
||||
->where('pushId', $pushId)
|
||||
->where('isSend', 1)
|
||||
->count();
|
||||
$item['successRate'] = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 1) : 0;
|
||||
$item['totalPushCount'] = $totalCount; // 推送总数
|
||||
$item['sendCount'] = $sendCount; // 成功发送数
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$name = $this->request->param('name', '');
|
||||
$tags = $this->request->param('tags', ''); // 标签,支持逗号分隔的字符串或数组
|
||||
$content = $this->request->param('content', '');
|
||||
$pushTiming = $this->request->param('pushTiming', 1); // 1=立即推送,2=最佳时机(AI决定),3=定时推送
|
||||
$scheduledTime = $this->request->param('scheduledTime', ''); // 定时推送的时间
|
||||
$status = $this->request->param('status', 1);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($name) || empty($content)) {
|
||||
return ResponseHelper::error('推送名称和推送内容不能为空');
|
||||
}
|
||||
|
||||
// 验证推送时机
|
||||
if (!in_array($pushTiming, [1, 2, 3])) {
|
||||
return ResponseHelper::error('无效的推送时机类型');
|
||||
}
|
||||
|
||||
// 如果是定时推送,需要验证时间
|
||||
if ($pushTiming == 3) {
|
||||
if (empty($scheduledTime)) {
|
||||
return ResponseHelper::error('定时推送需要设置推送时间');
|
||||
}
|
||||
// 验证时间格式
|
||||
$timestamp = strtotime($scheduledTime);
|
||||
if ($timestamp === false || $timestamp <= time()) {
|
||||
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
|
||||
}
|
||||
} else {
|
||||
$scheduledTime = '';
|
||||
}
|
||||
|
||||
// 处理标签
|
||||
$tagsArray = [];
|
||||
if (!empty($tags)) {
|
||||
if (is_string($tags)) {
|
||||
// 如果是字符串,按逗号分割
|
||||
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
|
||||
} elseif (is_array($tags)) {
|
||||
$tagsArray = array_filter(array_map('trim', $tags));
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tagsArray)) {
|
||||
return ResponseHelper::error('目标用户标签不能为空');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$aiPush = new AiPush();
|
||||
$aiPush->name = $name;
|
||||
$aiPush->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
|
||||
$aiPush->content = $content;
|
||||
$aiPush->pushTiming = $pushTiming;
|
||||
$aiPush->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
|
||||
$aiPush->status = $status;
|
||||
$aiPush->successRate = 0; // 初始成功率为0
|
||||
$aiPush->userId = $userId;
|
||||
$aiPush->companyId = $companyId;
|
||||
$aiPush->createTime = time();
|
||||
$aiPush->updateTime = time();
|
||||
$aiPush->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(['id' => $aiPush->id], '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function details()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('该推送已被删除或者不存在');
|
||||
}
|
||||
|
||||
$data = $data->toArray();
|
||||
// 解析标签数组
|
||||
$data['tags'] = json_decode($data['tags'], true);
|
||||
if (!is_array($data['tags'])) {
|
||||
$data['tags'] = [];
|
||||
}
|
||||
// 标签转为逗号分隔的字符串(用于编辑时回显)
|
||||
$data['tagsString'] = implode(',', $data['tags']);
|
||||
|
||||
// 处理定时推送时间
|
||||
if ($data['pushTiming'] == 3 && !empty($data['scheduledTime'])) {
|
||||
$data['scheduledTime'] = date('Y-m-d H:i:s', $data['scheduledTime']);
|
||||
} else {
|
||||
$data['scheduledTime'] = '';
|
||||
}
|
||||
|
||||
// 成功率保留一位小数
|
||||
$data['successRate'] = isset($data['successRate']) ? round($data['successRate'], 1) : 0;
|
||||
|
||||
return ResponseHelper::success($data, '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('该推送已被删除或者不存在');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->isDel = 1;
|
||||
$data->delTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$name = $this->request->param('name', '');
|
||||
$tags = $this->request->param('tags', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$pushTiming = $this->request->param('pushTiming', 1);
|
||||
$scheduledTime = $this->request->param('scheduledTime', '');
|
||||
$status = $this->request->param('status', 1);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id) || empty($name) || empty($content)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 验证推送时机
|
||||
if (!in_array($pushTiming, [1, 2, 3])) {
|
||||
return ResponseHelper::error('无效的推送时机类型');
|
||||
}
|
||||
|
||||
// 如果是定时推送,需要验证时间
|
||||
if ($pushTiming == 3) {
|
||||
if (empty($scheduledTime)) {
|
||||
return ResponseHelper::error('定时推送需要设置推送时间');
|
||||
}
|
||||
// 验证时间格式
|
||||
$timestamp = strtotime($scheduledTime);
|
||||
if ($timestamp === false || $timestamp <= time()) {
|
||||
return ResponseHelper::error('定时推送时间格式不正确或必须大于当前时间');
|
||||
}
|
||||
} else {
|
||||
$scheduledTime = '';
|
||||
}
|
||||
|
||||
// 处理标签
|
||||
$tagsArray = [];
|
||||
if (!empty($tags)) {
|
||||
if (is_string($tags)) {
|
||||
$tagsArray = array_filter(array_map('trim', explode(',', $tags)));
|
||||
} elseif (is_array($tags)) {
|
||||
$tagsArray = array_filter(array_map('trim', $tags));
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tagsArray)) {
|
||||
return ResponseHelper::error('目标用户标签不能为空');
|
||||
}
|
||||
|
||||
$query = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($query)) {
|
||||
return ResponseHelper::error('该推送已被删除或者不存在');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->name = $name;
|
||||
$query->tags = json_encode($tagsArray, JSON_UNESCAPED_UNICODE);
|
||||
$query->content = $content;
|
||||
$query->pushTiming = $pushTiming;
|
||||
$query->scheduledTime = $pushTiming == 3 && !empty($scheduledTime) ? strtotime($scheduledTime) : 0;
|
||||
$query->status = $status;
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改状态
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setStatus()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$status = $this->request->param('status', 1);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (!in_array($status, [0, 1])) {
|
||||
return ResponseHelper::error('状态值无效');
|
||||
}
|
||||
|
||||
$data = AiPush::where(['id' => $id, 'isDel' => 0, 'userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('该推送已被删除或者不存在');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->status = $status;
|
||||
$data->updateTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', $status == 1 ? '启用成功' : '禁用成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('操作失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计概览(整合自动问候和AI推送)
|
||||
* - 活跃规则(自动问候规则,近30天)
|
||||
* - 总触发次数(自动问候记录总数)
|
||||
* - AI推送成功率(AI推送的成功率)
|
||||
* - AI智能推送(AI推送规则,近30天活跃)
|
||||
* - 规则效果排行(自动问候规则,按使用次数排序)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function stats()
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
$start30d = time() - 30 * 24 * 3600;
|
||||
|
||||
try {
|
||||
// 公司维度(用于除排行外的统计)
|
||||
$companyWhere = [
|
||||
['companyId', '=', $companyId],
|
||||
];
|
||||
// 排行维度(限定个人)
|
||||
$rankingWhere = [
|
||||
['companyId', '=', $companyId],
|
||||
['userId', '=', $userId],
|
||||
];
|
||||
|
||||
// ========== 自动问候统计 ==========
|
||||
|
||||
// 1) 活跃规则(自动问候规则,近30天有记录的)
|
||||
$activeRules = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->where('createTime', '>=', $start30d)
|
||||
->distinct(true)
|
||||
->count('autoId');
|
||||
|
||||
// 2) 总触发次数(自动问候记录总数)
|
||||
$totalTriggers = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->count();
|
||||
|
||||
// ========== AI推送统计 ==========
|
||||
|
||||
// 3) AI推送成功率
|
||||
$totalPushes = Db::name('kf_ai_push_record')
|
||||
->where($companyWhere)
|
||||
->count();
|
||||
$sendCount = Db::name('kf_ai_push_record')
|
||||
->where($companyWhere)
|
||||
->where('isSend', '=', 1)
|
||||
->count();
|
||||
// 成功率:百分比,保留整数(75%)
|
||||
$aiPushSuccessRate = $totalPushes > 0 ? round(($sendCount * 100) / $totalPushes, 0) : 0;
|
||||
|
||||
// 4) AI智能推送(AI推送规则,近30天活跃的)
|
||||
$aiPushCount = Db::name('kf_ai_push_record')
|
||||
->where($companyWhere)
|
||||
->where('createTime', '>=', $start30d)
|
||||
->distinct(true)
|
||||
->count('pushId');
|
||||
|
||||
// ========== 规则效果排行(自动问候规则,按使用次数排序)==========
|
||||
$ruleRanking = Db::name('kf_auto_greetings_record')
|
||||
->where($rankingWhere)
|
||||
->field([
|
||||
'autoId AS id',
|
||||
'COUNT(*) AS usageCount'
|
||||
])
|
||||
->group('autoId')
|
||||
->order('usageCount DESC')
|
||||
->limit(20)
|
||||
->select();
|
||||
|
||||
// 附加规则名称和触发类型
|
||||
$autoIds = array_values(array_unique(array_column($ruleRanking, 'id')));
|
||||
$autoIdToRule = [];
|
||||
if (!empty($autoIds)) {
|
||||
$rules = AutoGreetings::where([['id', 'in', $autoIds]])
|
||||
->field('id,name,trigger')
|
||||
->select();
|
||||
foreach ($rules as $rule) {
|
||||
$triggerTypes = [
|
||||
1 => '新好友',
|
||||
2 => '首次发消息',
|
||||
3 => '时间触发',
|
||||
4 => '关键词',
|
||||
5 => '生日触发',
|
||||
6 => '自定义'
|
||||
];
|
||||
$autoIdToRule[$rule['id']] = [
|
||||
'name' => $rule['name'],
|
||||
'trigger' => $rule['trigger'],
|
||||
'triggerText' => $triggerTypes[$rule['trigger']] ?? '未知',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ruleRanking as &$row) {
|
||||
$row['usageCount'] = (int)($row['usageCount'] ?? 0);
|
||||
$row['name'] = $autoIdToRule[$row['id']]['name'] ?? '';
|
||||
$row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null;
|
||||
$row['triggerText'] = $autoIdToRule[$row['id']]['triggerText'] ?? '';
|
||||
// 格式化使用次数显示
|
||||
$row['usageCountText'] = $row['usageCount'] . ' 次';
|
||||
}
|
||||
unset($row);
|
||||
|
||||
// 更新主表中的成功率字段(异步或定期更新)
|
||||
$this->updatePushSuccessRate($companyId);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'activeRules' => (int)$activeRules,
|
||||
'totalTriggers' => (int)$totalTriggers,
|
||||
'aiPushSuccessRate' => (int)$aiPushSuccessRate,
|
||||
'aiPushCount' => (int)$aiPushCount,
|
||||
'ruleRanking' => $ruleRanking,
|
||||
], '统计成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('统计失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新推送表的成功率字段
|
||||
* @param int $companyId
|
||||
* @return void
|
||||
*/
|
||||
private function updatePushSuccessRate($companyId)
|
||||
{
|
||||
try {
|
||||
// 获取所有启用的推送
|
||||
$pushes = AiPush::where([
|
||||
['companyId', '=', $companyId],
|
||||
['isDel', '=', 0]
|
||||
])->field('id')->select();
|
||||
|
||||
foreach ($pushes as $push) {
|
||||
$pushId = $push['id'];
|
||||
$totalCount = Db::name('kf_ai_push_record')
|
||||
->where('pushId', $pushId)
|
||||
->count();
|
||||
$sendCount = Db::name('kf_ai_push_record')
|
||||
->where('pushId', $pushId)
|
||||
->where('isSend', 1)
|
||||
->count();
|
||||
|
||||
$successRate = $totalCount > 0 ? round(($sendCount * 100) / $totalCount, 2) : 0.00;
|
||||
|
||||
AiPush::where('id', $pushId)->update([
|
||||
'successRate' => $successRate,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 静默失败,不影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
266
application/chukebao/controller/AiSettingsController.php
Normal file
266
application/chukebao/controller/AiSettingsController.php
Normal file
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use app\chukebao\model\Questions;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class AiSettingsController extends BaseController
|
||||
{
|
||||
|
||||
|
||||
const SETTING_DEFAULT = [
|
||||
'audioSetting' => false,
|
||||
'round' => 10,
|
||||
'aiStopSetting' => [
|
||||
'status' => true,
|
||||
'key' => ['好', '不错', '好的', '下次', '可以']
|
||||
],
|
||||
'fileSetting' => [
|
||||
'type' => 1,
|
||||
'content' => ''
|
||||
],
|
||||
'modelSetting' => [
|
||||
'model' => 'GPT-4',
|
||||
'role' => '你是一名销售的AI助理,同时也是一个工智能技术专家,你的名字叫小灵,你是单身女性,出生于2003年10月10日,喜欢听音乐和看电影有着丰富的人生阅历,前成熟大方,分享用幽默风趣的语言和客户交流,顾客问起你的感情,回复内容中不要使用号,特别注意不要跟客户问题,不要更多选择发送的信息。',
|
||||
'businessBackground' => '灵销智能公司开发了多款AI营销智能技术产品,以提升销售GPT AI大模型为核心,接入打造的销售/营销/客服等AI智能应用,为企业AI办公,AI助理,AI销售,AI营销,AI直播等大AI应用产品。',
|
||||
'dialogueStyle' => '客户:你们的AI解决方案具体是怎么收费的?销售:嗯,朋友,我们的AI解决方案是根据项目需求来定的,这样吧,你能跟我说说你们的具体情况吗,不过这样一分钱,您看怎么样?我们可以给您做个详细的方案对比。',
|
||||
]
|
||||
];
|
||||
|
||||
const TYPE_DATA = ['audioSetting', 'round', 'aiStopSetting', 'fileSetting', 'modelSetting'];
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getSetting()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$data = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($data)) {
|
||||
$setting = self::SETTING_DEFAULT;
|
||||
$data = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'config' => json_encode($setting, 256),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
Db::name('ai_settings')->insert($data);
|
||||
|
||||
} else {
|
||||
$setting = json_decode($data['config'], true);
|
||||
}
|
||||
|
||||
return ResponseHelper::success($setting, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 配置
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setSetting()
|
||||
{
|
||||
$key = $this->request->param('key', '');
|
||||
$value = $this->request->param('value', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($key) || empty($value)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
|
||||
if (!in_array($key, self::TYPE_DATA)) {
|
||||
return ResponseHelper::error('该类型不在配置项');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data = Db::name('ai_settings')->where(['userId' => $userId, 'companyId' => $companyId])->find();
|
||||
if (empty($data)) {
|
||||
$setting = self::SETTING_DEFAULT;
|
||||
} else {
|
||||
$setting = json_decode($data['config'], true);
|
||||
}
|
||||
$setting[$key] = $value;
|
||||
$setting = json_encode($setting, 256);
|
||||
Db::name('ai_settings')->where(['id' => $data['id']])->update(['config' => $setting, 'updateTime' => time()]);
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ', '配置成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('配置失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getUserTokens()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$tokens = Db::name('users')
|
||||
->where('id', $userId)
|
||||
->where('companyId', $companyId)
|
||||
->value('tokens');
|
||||
|
||||
return ResponseHelper::success($tokens, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getFriend()
|
||||
{
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$aiType = FriendSettings::where(['userId' => $userId, 'companyId' => $companyId,'friendId' => $friendId,'wechatAccountId' => $wechatAccountId])->value('type');
|
||||
if (empty($aiType)) {
|
||||
$aiType = 0;
|
||||
}
|
||||
return ResponseHelper::success($aiType, '获取成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function setFriend()
|
||||
{
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$type = $this->request->param('type', 0);
|
||||
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($friendId) || empty($wechatAccountId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('该好友不存在');
|
||||
}
|
||||
|
||||
$friendSettings = FriendSettings::where(['userId' => $userId, 'companyId' => $companyId,'friendId' => $friendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
Db::startTrans();
|
||||
try {
|
||||
if (empty($friendSettings)) {
|
||||
$friendSettings = new FriendSettings();
|
||||
$friendSettings->companyId = $companyId;
|
||||
$friendSettings->userId = $userId;
|
||||
$friendSettings->type = $type;
|
||||
$friendSettings->wechatAccountId = $wechatAccountId;
|
||||
$friendSettings->friendId = $friendId;
|
||||
$friendSettings->createTime = time();
|
||||
$friendSettings->updateTime = time();
|
||||
}else{
|
||||
$friendSettings->type = $type;
|
||||
$friendSettings->updateTime = time();
|
||||
}
|
||||
$friendSettings->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ', '配置成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('配置失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function setAllFriend()
|
||||
{
|
||||
$packageId = $this->request->param('packageId', []);
|
||||
$type = $this->request->param('type', 0);
|
||||
$isUpdata = $this->request->param('isUpdata', 0);
|
||||
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($packageId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
//列出所有好友
|
||||
$row = Db::name('traffic_source_package_item')->alias('a')
|
||||
->join('wechat_friendship f','a.identifier = f.wechatId and f.companyId = '.$companyId)
|
||||
->join(['s2_wechat_account' => 'wa'],'f.ownerWechatId = wa.wechatId')
|
||||
->whereIn('a.packageId' , $packageId)
|
||||
->field('f.id as friendId,wa.id as wechatAccountId')
|
||||
->group('f.id')
|
||||
->select();
|
||||
|
||||
if (empty($row)) {
|
||||
return ResponseHelper::error('`好友不存在');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($row);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($row, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
$friendIds = array_column($batchRows, 'friendId');
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($friendIds)) {
|
||||
//强制更新
|
||||
if(!empty($isUpdata)){
|
||||
FriendSettings::whereIn('friendId',$friendIds)->update(['type' => $type,'updateTime' => time()]);
|
||||
}
|
||||
|
||||
$existing = FriendSettings::where('companyId', $companyId)->where('friendId', 'in', $friendIds)->field('friendId')->select()->toArray();
|
||||
$existingPhones = array_column($existing, 'friendId');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($friendIds) && !in_array($row['friendId'], $existingPhones)) {
|
||||
$newData[] = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'type' => $type,
|
||||
'wechatAccountId' => $row['wechatAccountId'],
|
||||
'friendId' => $row['friendId'],
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
FriendSettings::insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
return ResponseHelper::success(' ', '配置成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('配置失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
754
application/chukebao/controller/AutoGreetingsController.php
Normal file
754
application/chukebao/controller/AutoGreetingsController.php
Normal file
@@ -0,0 +1,754 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\AutoGreetings;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class AutoGreetingsController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取问候规则列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$is_template = $this->request->param('is_template', 0);
|
||||
$triggerType = $this->request->param('triggerType', ''); // 触发类型筛选
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if($is_template == 1){
|
||||
$where = [
|
||||
['is_template','=',1],
|
||||
['isDel' ,'=', 0],
|
||||
];
|
||||
}else{
|
||||
$where = [
|
||||
['companyId','=',$companyId],
|
||||
['userId' ,'=', $userId],
|
||||
['isDel' ,'=', 0],
|
||||
];
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['name','like','%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
if(!empty($triggerType)){
|
||||
$where[] = ['trigger','=',$triggerType];
|
||||
}
|
||||
|
||||
$query = AutoGreetings::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page,$limit)->order('level asc,id desc')->select();
|
||||
|
||||
// 获取使用次数
|
||||
$list = is_array($list) ? $list : $list->toArray();
|
||||
$ids = array_column($list, 'id');
|
||||
$usageCounts = [];
|
||||
if (!empty($ids)) {
|
||||
$counts = Db::name('kf_auto_greetings_record')
|
||||
->where('autoId', 'in', $ids)
|
||||
->field('autoId, COUNT(*) as count')
|
||||
->group('autoId')
|
||||
->select();
|
||||
foreach ($counts as $count) {
|
||||
$usageCounts[$count['autoId']] = (int)$count['count'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$item['condition'] = json_decode($item['condition'], true);
|
||||
$item['usageCount'] = $usageCounts[$item['id']] ?? 0;
|
||||
// 格式化触发类型显示文本
|
||||
$triggerTypes = [
|
||||
1 => '新好友',
|
||||
2 => '首次发消息',
|
||||
3 => '时间触发',
|
||||
4 => '关键词触发',
|
||||
5 => '生日触发',
|
||||
6 => '自定义'
|
||||
];
|
||||
$item['triggerText'] = $triggerTypes[$item['trigger']] ?? '未知';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验trigger类型对应的condition
|
||||
* @param int $trigger 触发类型
|
||||
* @param mixed $condition 条件参数
|
||||
* @return array|string 返回处理后的condition数组,或错误信息字符串
|
||||
*/
|
||||
private function validateTriggerCondition($trigger, $condition)
|
||||
{
|
||||
// trigger类型:1=新好友,2=首次发消息,3=时间触发,4=关键词触发,5=生日触发,6=自定义
|
||||
switch ($trigger) {
|
||||
case 1: // 新好友
|
||||
// 不需要condition
|
||||
return [];
|
||||
|
||||
case 2: // 首次发消息
|
||||
// 不需要condition
|
||||
return [];
|
||||
|
||||
case 3: // 时间触发
|
||||
// 需要condition,格式为:{"type": "daily_time|yearly_datetime|fixed_range|workday", "value": "..."}
|
||||
if (empty($condition)) {
|
||||
return '时间触发类型需要配置具体的触发条件';
|
||||
}
|
||||
$condition = is_array($condition) ? $condition : json_decode($condition, true);
|
||||
if (empty($condition) || !is_array($condition)) {
|
||||
return '时间触发类型的条件格式不正确,应为数组格式';
|
||||
}
|
||||
|
||||
// 验证必须包含type字段
|
||||
if (!isset($condition['type']) || empty($condition['type'])) {
|
||||
return '时间触发类型必须指定触发方式:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)';
|
||||
}
|
||||
|
||||
$timeType = $condition['type'];
|
||||
$allowedTypes = ['daily_time', 'yearly_datetime', 'fixed_range', 'workday'];
|
||||
// 兼容旧版本的 fixed_time,自动转换为 daily_time
|
||||
if ($timeType === 'fixed_time') {
|
||||
$timeType = 'daily_time';
|
||||
}
|
||||
if (!in_array($timeType, $allowedTypes)) {
|
||||
return '时间触发类型无效,必须为:daily_time(每天固定时间)、yearly_datetime(每年固定日期时间)、fixed_range(固定时间段)、workday(工作日)';
|
||||
}
|
||||
|
||||
// 根据不同的type验证value
|
||||
switch ($timeType) {
|
||||
case 'daily_time': // 每天固定时间(每天的几点几分)
|
||||
// value应该是时间字符串,格式:HH:mm,如 "14:30"
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '每天固定时间类型需要配置具体时间,格式:HH:mm(如 14:30)';
|
||||
}
|
||||
$timeValue = $condition['value'];
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
|
||||
return '每天固定时间格式不正确,应为 HH:mm 格式(如 14:30)';
|
||||
}
|
||||
return [
|
||||
'type' => 'daily_time',
|
||||
'value' => $timeValue
|
||||
];
|
||||
|
||||
case 'yearly_datetime': // 每年固定日期时间(每年的几月几号几点几分)
|
||||
// value应该是日期时间字符串,格式:MM-dd HH:mm,如 "12-25 14:30"
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '每年固定日期时间类型需要配置具体日期和时间,格式:MM-dd HH:mm(如 12-25 14:30)';
|
||||
}
|
||||
$datetimeValue = $condition['value'];
|
||||
// 验证格式:MM-dd HH:mm
|
||||
if (!preg_match('/^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) ([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $datetimeValue)) {
|
||||
return '每年固定日期时间格式不正确,应为 MM-dd HH:mm 格式(如 12-25 14:30)';
|
||||
}
|
||||
// 进一步验证日期是否有效(例如2月30日不存在)
|
||||
list($datePart, $timePart) = explode(' ', $datetimeValue);
|
||||
list($month, $day) = explode('-', $datePart);
|
||||
if (!checkdate((int)$month, (int)$day, 2000)) { // 使用2000年作为参考年份验证日期有效性
|
||||
return '日期无效,请检查月份和日期是否正确(如2月不能有30日)';
|
||||
}
|
||||
return [
|
||||
'type' => 'yearly_datetime',
|
||||
'value' => $datetimeValue
|
||||
];
|
||||
|
||||
case 'fixed_range': // 固定时间段
|
||||
// value应该是时间段数组,格式:["09:00", "18:00"]
|
||||
if (!isset($condition['value']) || !is_array($condition['value'])) {
|
||||
return '固定时间段类型需要配置时间段,格式:["开始时间", "结束时间"](如 ["09:00", "18:00"])';
|
||||
}
|
||||
$rangeValue = $condition['value'];
|
||||
if (count($rangeValue) !== 2) {
|
||||
return '固定时间段应为包含两个时间点的数组,格式:["09:00", "18:00"]';
|
||||
}
|
||||
// 验证时间格式
|
||||
foreach ($rangeValue as $time) {
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
|
||||
return '时间段格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
}
|
||||
// 验证开始时间小于结束时间
|
||||
$startTime = strtotime('2000-01-01 ' . $rangeValue[0]);
|
||||
$endTime = strtotime('2000-01-01 ' . $rangeValue[1]);
|
||||
if ($startTime >= $endTime) {
|
||||
return '开始时间必须小于结束时间';
|
||||
}
|
||||
return [
|
||||
'type' => 'fixed_range',
|
||||
'value' => $rangeValue
|
||||
];
|
||||
|
||||
case 'workday': // 工作日
|
||||
// 工作日需要配置时间,格式:HH:mm(如 09:00)
|
||||
if (!isset($condition['value']) || empty($condition['value'])) {
|
||||
return '工作日触发类型需要配置时间,格式:HH:mm(如 09:00)';
|
||||
}
|
||||
$timeValue = trim($condition['value']);
|
||||
// 验证格式:HH:mm
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $timeValue)) {
|
||||
return '工作日时间格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
return [
|
||||
'type' => 'workday',
|
||||
'value' => $timeValue
|
||||
];
|
||||
|
||||
default:
|
||||
return '时间触发类型无效';
|
||||
}
|
||||
|
||||
case 4: // 关键词触发
|
||||
// 需要condition,格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}
|
||||
if (empty($condition)) {
|
||||
return '关键词触发类型需要配置至少一个关键词';
|
||||
}
|
||||
|
||||
// 如果是字符串,尝试解析JSON
|
||||
if (is_string($condition)) {
|
||||
$decoded = json_decode($condition, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$condition = $decoded;
|
||||
} else {
|
||||
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
|
||||
}
|
||||
}
|
||||
|
||||
// 必须是对象格式
|
||||
if (!is_array($condition) || !isset($condition['keywords'])) {
|
||||
return '关键词触发类型格式错误,应为对象格式:{"keywords": ["关键词1", "关键词2"], "match_type": "exact|fuzzy"}';
|
||||
}
|
||||
|
||||
$keywords = $condition['keywords'];
|
||||
$matchType = isset($condition['match_type']) ? $condition['match_type'] : 'fuzzy';
|
||||
|
||||
// 验证match_type
|
||||
if (!in_array($matchType, ['exact', 'fuzzy'])) {
|
||||
return '匹配类型无效,必须为:exact(精准匹配)或 fuzzy(模糊匹配)';
|
||||
}
|
||||
|
||||
// 处理keywords
|
||||
if (is_string($keywords)) {
|
||||
$keywords = explode(',', $keywords);
|
||||
}
|
||||
if (!is_array($keywords)) {
|
||||
return '关键词格式不正确,应为数组格式';
|
||||
}
|
||||
|
||||
// 过滤空值并去重
|
||||
$keywords = array_filter(array_map('trim', $keywords));
|
||||
if (empty($keywords)) {
|
||||
return '关键词触发类型需要配置至少一个关键词';
|
||||
}
|
||||
|
||||
// 验证每个关键词不为空
|
||||
foreach ($keywords as $keyword) {
|
||||
if (empty($keyword)) {
|
||||
return '关键词不能为空';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'keywords' => array_values($keywords),
|
||||
'match_type' => $matchType
|
||||
];
|
||||
|
||||
case 5: // 生日触发
|
||||
// 需要condition,格式支持:
|
||||
// 1. 月日字符串:'10-10' 或 '10-10 09:00'(MM-DD格式,不包含年份)
|
||||
// 2. 对象格式:{'month': 10, 'day': 10, 'time': '09:00'} 或 {'month': '10', 'day': '10', 'time_range': ['09:00', '10:00']}
|
||||
if (empty($condition)) {
|
||||
return '生日触发类型需要配置日期条件';
|
||||
}
|
||||
|
||||
// 如果是字符串,只接受 MM-DD 格式(不包含年份)
|
||||
if (is_string($condition)) {
|
||||
// 检查是否包含时间部分
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})\s+(\d{2}:\d{2})$/', $condition, $matches)) {
|
||||
// 格式:'10-10 09:00'
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day,
|
||||
'time' => $matches[3]
|
||||
];
|
||||
} elseif (preg_match('/^(\d{1,2})-(\d{1,2})$/', $condition, $matches)) {
|
||||
// 格式:'10-10'(不指定时间,当天任何时间都可以触发)
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
} else {
|
||||
return '生日日期格式不正确,应为 MM-DD 或 MM-DD HH:mm 格式(如 10-10 或 10-10 09:00),不包含年份';
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是数组,可能是对象格式或旧格式
|
||||
if (is_array($condition)) {
|
||||
// 检查是否是旧格式(仅兼容 MM-DD 格式的数组)
|
||||
if (isset($condition[0]) && is_string($condition[0])) {
|
||||
$dateStr = $condition[0];
|
||||
// 只接受 MM-DD 格式:'10-10' 或 '10-10 09:00'
|
||||
if (preg_match('/^(\d{1,2})-(\d{1,2})(?:\s+(\d{2}:\d{2}))?$/', $dateStr, $matches)) {
|
||||
$month = (int)$matches[1];
|
||||
$day = (int)$matches[2];
|
||||
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,月份应为1-12,日期应为1-31';
|
||||
}
|
||||
if (isset($matches[3])) {
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day,
|
||||
'time' => $matches[3]
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
}
|
||||
} else {
|
||||
return '生日日期格式不正确,应为 MM-DD 格式(如 10-10),不包含年份';
|
||||
}
|
||||
}
|
||||
|
||||
// 新格式:{'month': 10, 'day': 10, 'time': '09:00'}
|
||||
if (isset($condition['month']) && isset($condition['day'])) {
|
||||
$month = (int)$condition['month'];
|
||||
$day = (int)$condition['day'];
|
||||
|
||||
if ($month < 1 || $month > 12) {
|
||||
return '生日月份格式不正确,应为1-12';
|
||||
}
|
||||
if ($day < 1 || $day > 31) {
|
||||
return '生日日期格式不正确,应为1-31';
|
||||
}
|
||||
|
||||
$result = [
|
||||
'month' => $month,
|
||||
'day' => $day
|
||||
];
|
||||
|
||||
// 检查是否配置了时间
|
||||
if (isset($condition['time']) && !empty($condition['time'])) {
|
||||
$time = trim($condition['time']);
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $time)) {
|
||||
return '生日时间格式不正确,应为 HH:mm 格式(如 09:00)';
|
||||
}
|
||||
$result['time'] = $time;
|
||||
}
|
||||
|
||||
// 检查是否配置了时间范围
|
||||
if (isset($condition['time_range']) && is_array($condition['time_range']) && count($condition['time_range']) === 2) {
|
||||
$startTime = trim($condition['time_range'][0]);
|
||||
$endTime = trim($condition['time_range'][1]);
|
||||
if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $startTime) ||
|
||||
!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $endTime)) {
|
||||
return '生日时间范围格式不正确,应为 ["HH:mm", "HH:mm"] 格式';
|
||||
}
|
||||
$result['time_range'] = [$startTime, $endTime];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return '生日触发条件格式不正确,需要提供month和day字段';
|
||||
}
|
||||
|
||||
return '生日触发条件格式不正确';
|
||||
|
||||
case 6: // 自定义
|
||||
// 自定义类型,condition可选,如果有则必须是数组格式
|
||||
if (!empty($condition)) {
|
||||
$condition = is_array($condition) ? $condition : json_decode($condition, true);
|
||||
if (!is_array($condition)) {
|
||||
return '自定义类型的条件格式不正确,应为数组格式';
|
||||
}
|
||||
return $condition;
|
||||
}
|
||||
return [];
|
||||
|
||||
default:
|
||||
return '无效的触发类型';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(){
|
||||
$name = $this->request->param('name', '');
|
||||
$trigger = $this->request->param('trigger', 0);
|
||||
$condition = $this->request->param('condition', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$level = $this->request->param('level', 0);
|
||||
$status = $this->request->param('status', 1);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($name) || empty($trigger) || empty($content)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 校验trigger类型
|
||||
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
|
||||
return ResponseHelper::error('无效的触发类型');
|
||||
}
|
||||
|
||||
// 校验并处理condition
|
||||
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
|
||||
if (is_string($conditionResult)) {
|
||||
// 返回的是错误信息
|
||||
return ResponseHelper::error($conditionResult);
|
||||
}
|
||||
$condition = $conditionResult;
|
||||
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$AutoGreetings = new AutoGreetings();
|
||||
$AutoGreetings->name = $name;
|
||||
$AutoGreetings->trigger = $trigger;
|
||||
$AutoGreetings->condition = json_encode($condition,256);
|
||||
$AutoGreetings->content = $content;
|
||||
$AutoGreetings->level = $level;
|
||||
$AutoGreetings->status = $status;
|
||||
$AutoGreetings->userId = $userId;
|
||||
$AutoGreetings->companyId = $companyId;
|
||||
$AutoGreetings->updateTime = time();
|
||||
$AutoGreetings->createTime = time();
|
||||
$AutoGreetings->usageCount = 0; // 初始化使用次数为0
|
||||
$AutoGreetings->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(['id' => $AutoGreetings->id],'创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function details()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该内容已被删除或者不存在');
|
||||
}
|
||||
|
||||
|
||||
$data['condition'] = json_decode($data['condition'],true);
|
||||
|
||||
// 获取使用次数
|
||||
$usageCount = Db::name('kf_auto_greetings_record')
|
||||
->where('autoId', $id)
|
||||
->count();
|
||||
$data['usageCount'] = (int)$usageCount;
|
||||
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function del()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->isDel = 1;
|
||||
$data->delTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('','删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(){
|
||||
$id = $this->request->param('id', '');
|
||||
$name = $this->request->param('name', '');
|
||||
$trigger = $this->request->param('trigger', 0);
|
||||
$condition = $this->request->param('condition', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$level = $this->request->param('level', 0);
|
||||
$status = $this->request->param('status', 1);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id) || empty($name) || empty($trigger) || empty($content)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 校验trigger类型
|
||||
if (!in_array($trigger, [1, 2, 3, 4, 5, 6])) {
|
||||
return ResponseHelper::error('无效的触发类型');
|
||||
}
|
||||
|
||||
// 校验并处理condition
|
||||
$conditionResult = $this->validateTriggerCondition($trigger, $condition);
|
||||
if (is_string($conditionResult)) {
|
||||
// 返回的是错误信息
|
||||
return ResponseHelper::error($conditionResult);
|
||||
}
|
||||
$condition = $conditionResult;
|
||||
|
||||
|
||||
$query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该内容已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->name = $name;
|
||||
$query->trigger = $trigger;
|
||||
$query->condition = !empty($condition) ? json_encode($condition,256) : json_encode([]);
|
||||
$query->content = $content;
|
||||
$query->level = $level;
|
||||
$query->status = $status;
|
||||
$query->userId = $userId;
|
||||
$query->companyId = $companyId;
|
||||
$query->updateTime = time();
|
||||
$query->createTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改状态
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setStatus(){
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$query = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该内容已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$status = $this->request->param('status', '');
|
||||
if ($status !== '') {
|
||||
$query->status = (int)$status;
|
||||
} else {
|
||||
$query->status = $query->status == 1 ? 0 : 1;
|
||||
}
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(['status' => $query->status],'修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 拷贝
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function copy(){
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id) ){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$data = AutoGreetings::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该内容已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query = new AutoGreetings();
|
||||
$query->name = $data['name'] . '_copy';
|
||||
$query->trigger = $data['trigger'];
|
||||
$query->condition = $data['condition'];
|
||||
$query->content = $data['content'];
|
||||
$query->level = $data['level'];
|
||||
$query->status = $data['status'];
|
||||
$query->userId = $userId;
|
||||
$query->companyId = $companyId;
|
||||
$query->updateTime = time();
|
||||
$query->createTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','拷贝成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('拷贝失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 统计概览
|
||||
* - 总触发次数
|
||||
* - 活跃规则(近一个月)
|
||||
* - 发送成功率
|
||||
* - 平均响应时间(秒)
|
||||
* - 规则效果排行(按发送次数降序、平均响应时间升序)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function stats()
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
$start30d = time() - 30 * 24 * 3600;
|
||||
|
||||
try {
|
||||
// 公司维度(用于除排行外的统计)
|
||||
$companyWhere = [
|
||||
['companyId', '=', $companyId],
|
||||
];
|
||||
// 排行维度(限定个人)
|
||||
$rankingWhere = [
|
||||
['companyId', '=', $companyId],
|
||||
['userId', '=', $userId],
|
||||
];
|
||||
|
||||
// 1) 总触发次数
|
||||
$totalTriggers = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->count();
|
||||
|
||||
// 2) 近30天活跃规则(仅返回数量,按公司维度,distinct autoId)
|
||||
$activeRulesCount = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->where('createTime', '>=', $start30d)
|
||||
->distinct(true)
|
||||
->count('autoId');
|
||||
|
||||
// 3) 发送成功率
|
||||
$sendCount = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->where('isSend', '=', 1)
|
||||
->count();
|
||||
// 成功率:百分比,保留两位小数
|
||||
$sendRate = $totalTriggers > 0 ? round(($sendCount * 100) / $totalTriggers, 2) : 0.00;
|
||||
|
||||
// 4) 平均响应时间(receiveTime - sendTime,单位秒)
|
||||
$avgResponse = Db::name('kf_auto_greetings_record')
|
||||
->where($companyWhere)
|
||||
->whereRaw('sendTime IS NOT NULL AND receiveTime IS NOT NULL AND receiveTime >= sendTime')
|
||||
->avg(Db::raw('(receiveTime - sendTime)'));
|
||||
$avgResponse = $avgResponse ? (int)round($avgResponse) : 0;
|
||||
|
||||
// 5) 规则效果排行(按发送次数降序、平均响应时间升序)
|
||||
$ranking = Db::name('kf_auto_greetings_record')
|
||||
->where($rankingWhere)
|
||||
->field([
|
||||
'autoId AS id',
|
||||
'COUNT(*) AS totalCount',
|
||||
'SUM(CASE WHEN isSend = 1 THEN 1 ELSE 0 END) AS sendCount',
|
||||
'AVG(CASE WHEN sendTime IS NOT NULL AND receiveTime IS NOT NULL AND receiveTime >= sendTime THEN (receiveTime - sendTime) END) AS avgResp'
|
||||
])
|
||||
->group('autoId')
|
||||
->orderRaw('sendCount DESC, avgResp ASC')
|
||||
->limit(20)
|
||||
->select();
|
||||
|
||||
// 附加规则名称(如存在)
|
||||
$autoIds = array_values(array_unique(array_column($ranking, 'id')));
|
||||
$autoIdToRule = [];
|
||||
if (!empty($autoIds)) {
|
||||
$rules = AutoGreetings::where([['id', 'in', $autoIds]])
|
||||
->field('id,name,trigger')
|
||||
->select();
|
||||
foreach ($rules as $rule) {
|
||||
$autoIdToRule[$rule['id']] = [
|
||||
'name' => $rule['name'],
|
||||
'trigger' => $rule['trigger'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ranking as &$row) {
|
||||
$row['avgResp'] = isset($row['avgResp']) && $row['avgResp'] !== null ? (int)round($row['avgResp']) : 0;
|
||||
// 百分比,两位小数
|
||||
$row['sendRate'] = ($row['totalCount'] ?? 0) > 0 ? round((($row['sendCount'] ?? 0) * 100) / $row['totalCount'], 2) : 0.00;
|
||||
$row['name'] = $autoIdToRule[$row['id']]['name'] ?? '';
|
||||
$row['trigger'] = $autoIdToRule[$row['id']]['trigger'] ?? null;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'totalTriggers' => (int)$totalTriggers,
|
||||
'activeRules' => (int)$activeRulesCount,
|
||||
'sendSuccessRate' => $sendRate,
|
||||
'avgResponseSeconds' => $avgResponse,
|
||||
'ruleRanking' => $ranking,
|
||||
], '统计成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('统计失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
29
application/chukebao/controller/BaseController.php
Normal file
29
application/chukebao/controller/BaseController.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use think\Controller;
|
||||
|
||||
/**
|
||||
* 基础控制器
|
||||
*/
|
||||
class BaseController extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @param string $column
|
||||
* @return mixed
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getUserInfo(?string $column = null)
|
||||
{
|
||||
$user = $this->request->userInfo;
|
||||
|
||||
if (!$user) {
|
||||
throw new \Exception('未授权访问,缺少有效的身份凭证', 401);
|
||||
}
|
||||
|
||||
return $column ? $user[$column] : $user;
|
||||
}
|
||||
}
|
||||
665
application/chukebao/controller/ContentController.php
Normal file
665
application/chukebao/controller/ContentController.php
Normal file
@@ -0,0 +1,665 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\Keywords;
|
||||
use app\chukebao\model\Material;
|
||||
use app\chukebao\model\SensitiveWord;
|
||||
use think\Db;
|
||||
use library\ResponseHelper;
|
||||
|
||||
class ContentController extends BaseController
|
||||
{
|
||||
//===================================================== 素材管理 =====================================================
|
||||
|
||||
public function getAllMaterial(){
|
||||
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = Material::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0,'status' => 1])
|
||||
->field('id,title,cover')
|
||||
->order('id desc');
|
||||
|
||||
$list = $query->select()->toArray();
|
||||
|
||||
return ResponseHelper::success($list);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 素材列表
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getMaterial(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = Material::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
|
||||
->order('id desc');
|
||||
if (!empty($keyword)){
|
||||
$query->where('title', 'like', '%'.$keyword.'%');
|
||||
}
|
||||
$list = $query->page($page, $limit)->select()->toArray();
|
||||
$total = $query->count();
|
||||
|
||||
foreach ($list as $k => &$v){
|
||||
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
|
||||
if (!empty($user)){
|
||||
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
|
||||
}else{
|
||||
$v['userName'] = '';
|
||||
}
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 素材添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createMaterial(){
|
||||
$title = $this->request->param('title', '');
|
||||
$content = $this->request->param('content', []);
|
||||
$cover = $this->request->param('cover', '');
|
||||
$status = $this->request->param('status', 0);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($content) || empty($cover)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$newContent = [];
|
||||
foreach ($content as $k => $v){
|
||||
if (in_array($v['type'],['text','image','video','audio','file','link'])){
|
||||
$newContent[] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query = new Material();
|
||||
$query->title = $title;
|
||||
$query->content = !empty($newContent) ? json_encode($newContent,256) : json_encode([],256);
|
||||
$query->cover = $cover;
|
||||
$query->status = $status;
|
||||
$query->userId = $userId;
|
||||
$query->companyId = $companyId;
|
||||
$query->createTime = time();
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 素材详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detailsMaterial()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
$data['content'] = json_decode($data['content'],true);
|
||||
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除素材
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delMaterial()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->isDel = 1;
|
||||
$data->delTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('','删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改素材
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateMaterial(){
|
||||
$id = $this->request->param('id', '');
|
||||
$title = $this->request->param('title', '');
|
||||
$content = $this->request->param('content', []);
|
||||
$cover = $this->request->param('cover', '');
|
||||
$status = $this->request->param('status', 0);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id) || empty($title) || empty($content) || empty($cover)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$newContent = [];
|
||||
foreach ($content as $k => $v){
|
||||
if (in_array($v['type'],['text','image','video','audio','file','link'])){
|
||||
$newContent[] = $v;
|
||||
}
|
||||
}
|
||||
$query = Material::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->title = $title;
|
||||
$query->content = !empty($newContent) ? json_encode($newContent,256) : json_encode([],256);
|
||||
$query->cover = $cover;
|
||||
$query->status = $status;
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
//===================================================== 素材管理 =====================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==================================================== 违禁词管理 ====================================================
|
||||
|
||||
/**
|
||||
* 违禁词列表
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getSensitiveWord(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = SensitiveWord::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
|
||||
->order('id desc');
|
||||
if (!empty($keyword)){
|
||||
$query->where('title', 'like', '%'.$keyword.'%');
|
||||
}
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select()->toArray();
|
||||
|
||||
|
||||
foreach ($list as $k => &$v){
|
||||
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
|
||||
if (!empty($user)){
|
||||
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
|
||||
}else{
|
||||
$v['userName'] = '';
|
||||
}
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 违禁词添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createSensitiveWord(){
|
||||
$title = $this->request->param('title', '');
|
||||
$keywords = $this->request->param('keywords', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$status = $this->request->param('status', 0);
|
||||
$operation = $this->request->param('operation', 0);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($keywords)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$keywords = explode(',',$keywords);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query = new SensitiveWord();
|
||||
$query->title = $title;
|
||||
$query->keywords = $keywords;
|
||||
$query->content = $content;
|
||||
$query->status = $status;
|
||||
$query->operation = $operation;
|
||||
$query->userId = $userId;
|
||||
$query->companyId = $companyId;
|
||||
$query->createTime = time();
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 违禁词详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detailsSensitiveWord()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
$data['keywords'] = json_decode($data['keywords'],true);
|
||||
$data['keywords'] = implode(',',$data['keywords']);
|
||||
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 违禁词删除
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delSensitiveWord()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->isDel = 1;
|
||||
$data->delTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('','删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新违禁词
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateSensitiveWord(){
|
||||
$id = $this->request->param('id', '');
|
||||
$title = $this->request->param('title', '');
|
||||
$keywords = $this->request->param('keywords', '');
|
||||
$content = $this->request->param('content', '');
|
||||
$status = $this->request->param('status', 0);
|
||||
$operation = $this->request->param('operation', 0);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id) || empty($title) || empty($keywords)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$keywords = explode(',',$keywords);
|
||||
|
||||
$query = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->title = $title;
|
||||
$query->keywords = $keywords;
|
||||
$query->content = $content;
|
||||
$query->status = $status;
|
||||
$query->operation = $operation;
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改违禁词状态
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setSensitiveWordStatus(){
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$query = SensitiveWord::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->status = !empty($query['status']) ? 0 : 1;;
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//==================================================== 违禁词管理 ====================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================================================== 关键词词管理 ====================================================
|
||||
|
||||
/**
|
||||
* 关键词列表
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getKeywords(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = Keywords::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
|
||||
->order('id desc');
|
||||
if (!empty($keyword)){
|
||||
$query->where('title', 'like', '%'.$keyword.'%');
|
||||
}
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select()->toArray();
|
||||
|
||||
|
||||
foreach ($list as $k => &$v){
|
||||
$v['metailGroups'] = json_decode($v['metailGroups'],true);
|
||||
$v['content'] = json_decode($v['content'],true);
|
||||
$v['keywords'] = json_decode($v['keywords'],true);
|
||||
|
||||
$metailData = Material::where(['isDel' => 0,'userId' => $userId,'companyId' => $companyId])
|
||||
->whereIn('id',$v['metailGroups'])
|
||||
->select()->toArray();
|
||||
$v['metailGroupsOptions'] = $metailData;
|
||||
|
||||
|
||||
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
|
||||
if (!empty($user)){
|
||||
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
|
||||
}else{
|
||||
$v['userName'] = '';
|
||||
}
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 关键词添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function createKeywords(){
|
||||
$title = $this->request->param('title', '');
|
||||
$type = $this->request->param('type', 0);
|
||||
$keywords = $this->request->param('keywords', '');
|
||||
$replyType = $this->request->param('replyType', 0);
|
||||
$content = $this->request->param('content','');
|
||||
$metailGroups = $this->request->param('metailGroups',[]);
|
||||
$status = $this->request->param('status', 0);
|
||||
$level = $this->request->param('level', 50);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($keywords) || (empty(metailGroups) && empty($content))){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$keywords = explode(',',$keywords);
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query = new Keywords();
|
||||
$query->title = $title;
|
||||
$query->type = $type;
|
||||
$query->keywords = !empty($keywords) ? json_encode($keywords,256) : json_encode([]);
|
||||
$query->replyType = $replyType;
|
||||
$query->content = !empty($content) ? json_encode($content,256) : json_encode([]);;
|
||||
$query->metailGroups = !empty($metailGroups) ? json_encode($metailGroups,256) : json_encode([]);;
|
||||
$query->status = $status;
|
||||
$query->level = $level;
|
||||
$query->userId = $userId;
|
||||
$query->companyId = $companyId;
|
||||
$query->createTime = time();
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 关键词详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detailsKeywords()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
|
||||
$data['metailGroups'] = json_decode($data['metailGroups'],true);
|
||||
$metailData = Material::where(['isDel' => 0,'userId' => $userId,'companyId' => $companyId])
|
||||
->whereIn('id',$data['metailGroups'])
|
||||
->select()->toArray();
|
||||
$data['metailGroupsOptions'] = $metailData;
|
||||
|
||||
$data['content'] = json_decode($data['content'],true);
|
||||
$data['keywords'] = json_decode($data['keywords'],true);
|
||||
$data['keywords'] = implode(',',$data['keywords']);
|
||||
unset($data['createTime'],$data['updateTime'],$data['isDel'],$data['delTime']);
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键词删除
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delKeywords()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$data = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('该关键词已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$data->isDel = 1;
|
||||
$data->delTime = time();
|
||||
$data->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success('','删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新关键词
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function updateKeywords(){
|
||||
$id = $this->request->param('id', '');
|
||||
$title = $this->request->param('title', '');
|
||||
$type = $this->request->param('type', 0);
|
||||
$keywords = $this->request->param('keywords', '');
|
||||
$replyType = $this->request->param('replyType', 0);
|
||||
$content = $this->request->param('content','');
|
||||
$metailGroups = $this->request->param('metailGroups','');
|
||||
$status = $this->request->param('status', 0);
|
||||
$level = $this->request->param('level', 50);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($keywords) || (empty($metailGroups) && empty($content))){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$keywords = explode(',',$keywords);
|
||||
|
||||
$query = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->title = $title;
|
||||
$query->type = $type;
|
||||
$query->keywords = !empty($keywords) ? json_encode($keywords,256) : json_encode([]);
|
||||
$query->replyType = $replyType;
|
||||
$query->content = !empty($content) ? json_encode($content,256) : json_encode([]);;
|
||||
$query->metailGroups = !empty($metailGroups) ? json_encode($metailGroups,256) : json_encode([]);;;
|
||||
$query->status = $status;
|
||||
$query->level = $level;
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改关键词状态
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setKeywordStatus(){
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$query = Keywords::where(['id'=>$id,'isDel' => 0,'userId' => $userId,'companyId' => $companyId])->find();
|
||||
if (empty($query)){
|
||||
return ResponseHelper::error('该素材已被删除或者不存在');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$query->status = !empty($query['status']) ? 0 : 1;
|
||||
$query->updateTime = time();
|
||||
$query->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','修改成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('修改失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=================================================== 关键词词管理 ====================================================
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class CustomerServiceController extends BaseController
|
||||
{
|
||||
|
||||
public function getList(){
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
$accountIds1= Db::table('s2_wechat_friend')->where(['accountId' => $accountId,'isDeleted' => 0])->group('wechatAccountId')->column('wechatAccountId');
|
||||
$accountIds2 = Db::table('s2_wechat_chatroom')->where(['accountId' => $accountId,'isDeleted' => 0])->group('wechatAccountId')->column('wechatAccountId');
|
||||
// 确保即使有空数组也不会报错,并且去除重复值
|
||||
$accountIds = array_unique(array_merge($accountIds1 ?: [], $accountIds2 ?: []));
|
||||
|
||||
|
||||
|
||||
$wechatAliveTime = time() - 86400 * 30;
|
||||
$list = Db::table('s2_wechat_account')
|
||||
->whereIn('id',$accountIds)
|
||||
->where('wechatAliveTime','>',$wechatAliveTime)
|
||||
->order('id desc')
|
||||
->group('id')
|
||||
->select();
|
||||
foreach ($list as $k=>&$v){
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s',$v['createTime']) : '';
|
||||
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s',$v['updateTime']) : '';
|
||||
$v['labels'] = json_decode($v['labels'],true);
|
||||
$momentsSetting = Db::name('kf_moments_settings')->where(['userId' => $userId,'companyId' => $companyId,'wechatId' =>$v['id']])->find();
|
||||
$v['momentsMax'] = !empty($momentsSetting['max']) ? $momentsSetting['max'] : 5;
|
||||
$v['momentsNum'] = !empty($momentsSetting['sendNum']) ? $momentsSetting['sendNum'] : 0;
|
||||
|
||||
unset(
|
||||
$v['accountUserName'],
|
||||
$v['accountRealName'],
|
||||
$v['accountNickname'],
|
||||
);
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success($list);
|
||||
}
|
||||
}
|
||||
206
application/chukebao/controller/DataProcessing.php
Normal file
206
application/chukebao/controller/DataProcessing.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\api\model\WechatChatroomModel;
|
||||
use library\ResponseHelper;
|
||||
use app\api\model\WechatFriendModel;
|
||||
use app\api\model\WechatMessageModel;
|
||||
use app\api\controller\MessageController;
|
||||
|
||||
|
||||
class DataProcessing extends BaseController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$type = $this->request->param('type', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
//微信好友
|
||||
$toAccountId = $this->request->param('toAccountId', '');
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
$newRemark = $this->request->param('newRemark', '');
|
||||
$labels = $this->request->param('labels', []);
|
||||
//微信群
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
|
||||
|
||||
//新消息
|
||||
$friendMessage = $this->request->param('friendMessage', '');
|
||||
$chatroomMessage = $this->request->param('chatroomMessage', '');
|
||||
|
||||
$typeData = [
|
||||
'CmdModifyFriendRemark', //好友修改备注 {newRemark、wechatAccountId、wechatFriendId}
|
||||
'CmdModifyFriendLabel', //好友修改标签 {labels、wechatAccountId、wechatFriendId}
|
||||
'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId}
|
||||
'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId}
|
||||
'CmdNewMessage', //接收消息
|
||||
'CmdSendMessageResult', //更新消息状态
|
||||
'CmdPinToTop', //置顶
|
||||
];
|
||||
|
||||
if (empty($type) || empty($wechatAccountId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (!in_array($type, $typeData)) {
|
||||
return ResponseHelper::error('类型错误');
|
||||
}
|
||||
$msg = '';
|
||||
$codee = 200;
|
||||
switch ($type) {
|
||||
case 'CmdModifyFriendRemark': //修改好友备注
|
||||
if(empty($wechatFriendId) || empty($newRemark)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
if(empty($friend)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
$friend->conRemark = $newRemark;
|
||||
$friend->updateTime = time();
|
||||
$friend->save();
|
||||
$msg = '修改备成功';
|
||||
break;
|
||||
case 'CmdModifyFriendLabel': //修改好友标签
|
||||
if(empty($wechatFriendId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
if(empty($friend)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
$friend->labels = json_encode($labels,256);
|
||||
$friend->updateTime = time();
|
||||
$friend->save();
|
||||
$msg = '修标签成功';
|
||||
break;
|
||||
case 'CmdAllotFriend': //迁移好友
|
||||
if(empty($toAccountId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
if(empty($wechatFriendId) && empty($wechatChatroomId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatFriendId)){
|
||||
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = '好友转移成功';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatChatroomId)){
|
||||
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = '群聊转移成功';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('群聊不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$data->accountId = $toAccountId;
|
||||
$data->updateTime = time();
|
||||
$data->save();
|
||||
break;
|
||||
case 'CmdNewMessage':
|
||||
if(empty($friendMessage) && empty($chatroomMessage)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if(is_array($friendMessage) && is_array($chatroomMessage)){
|
||||
return ResponseHelper::error('数据类型错误');
|
||||
}
|
||||
|
||||
|
||||
$messageController = new MessageController();
|
||||
if (!empty($friendMessage)){
|
||||
$res = $messageController->saveMessage($friendMessage[0]);
|
||||
}else{
|
||||
$res = $messageController->saveChatroomMessage($chatroomMessage[0]);
|
||||
}
|
||||
if (!empty($res)){
|
||||
$msg = '消息记录成功';
|
||||
}else{
|
||||
$msg = '消息记录失败';
|
||||
$codee = 200;
|
||||
}
|
||||
break;
|
||||
case 'CmdSendMessageResult':
|
||||
$friendMessageId = $this->request->param('friendMessageId', 0);
|
||||
$chatroomMessageId = $this->request->param('chatroomMessageId', 0);
|
||||
$sendStatus = $this->request->param('sendStatus', null);
|
||||
$wechatTime = $this->request->param('wechatTime', 0);
|
||||
|
||||
if ($sendStatus === null) {
|
||||
return ResponseHelper::error('sendStatus不能为空');
|
||||
}
|
||||
|
||||
if (empty($friendMessageId) && empty($chatroomMessageId)) {
|
||||
return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个');
|
||||
}
|
||||
|
||||
$messageId = $friendMessageId ?: $chatroomMessageId;
|
||||
$update = [
|
||||
'sendStatus' => (int)$sendStatus,
|
||||
];
|
||||
|
||||
if (!empty($wechatTime)) {
|
||||
$update['wechatTime'] = strlen((string)$wechatTime) > 10
|
||||
? intval($wechatTime / 1000)
|
||||
: (int)$wechatTime;
|
||||
}
|
||||
|
||||
$affected = WechatMessageModel::where('id', $messageId)->update($update);
|
||||
|
||||
if ($affected === false) {
|
||||
return ResponseHelper::success('','更新消息状态失败');
|
||||
}
|
||||
|
||||
if ($affected === 0) {
|
||||
return ResponseHelper::success('','消息不存在');
|
||||
}
|
||||
|
||||
$msg = '更新消息状态成功';
|
||||
break;
|
||||
case 'CmdPinToTop': //置顶
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', 0);
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', 0);
|
||||
$isTop = $this->request->param('isTop', null);
|
||||
|
||||
if ($isTop === null) {
|
||||
return ResponseHelper::error('isTop不能为空');
|
||||
}
|
||||
|
||||
if (empty($wechatFriendId) && empty($wechatChatroomId)) {
|
||||
return ResponseHelper::error('wechatFriendId或chatroomId至少提供一个');
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatFriendId)){
|
||||
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatChatroomId)){
|
||||
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('群聊不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$data->updateTime = time();
|
||||
$data->isTop = $isTop;
|
||||
$data->save();
|
||||
break;
|
||||
}
|
||||
return ResponseHelper::success('',$msg,$codee);
|
||||
}
|
||||
}
|
||||
144
application/chukebao/controller/FollowUpController.php
Normal file
144
application/chukebao/controller/FollowUpController.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\FollowUp;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class FollowUpController extends BaseController
|
||||
{
|
||||
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$isRemind = $this->request->param('isRemind', '');
|
||||
$isProcess = $this->request->param('isProcess', '');
|
||||
$type = $this->request->param('type', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId','=',$companyId],
|
||||
['userId' ,'=', $userId]
|
||||
];
|
||||
|
||||
if ($isRemind != '') {
|
||||
$where[] = ['isRemind','=',$isRemind];
|
||||
}
|
||||
if ($type != '') {
|
||||
$where[] = ['type','=',$type];
|
||||
}
|
||||
if ($isProcess != '') {
|
||||
$where[] = ['isProcess','=',$isProcess];
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['title|description','like','%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
$query = FollowUp::where($where);
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
|
||||
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendId']])->value('nickname');
|
||||
$item['nickname'] = !empty($nickname) ? $nickname : '-';
|
||||
$item['reminderTime'] = date('Y-m-d H:i:s',$item['reminderTime']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(){
|
||||
$type = $this->request->param('type', 0);
|
||||
$title = $this->request->param('title', '');
|
||||
$reminderTime = $this->request->param('reminderTime', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($reminderTime) || empty($description) || empty($friendId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->find();
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$FollowUp = new FollowUp();
|
||||
$FollowUp->type = $type;
|
||||
$FollowUp->title = $title;
|
||||
$FollowUp->friendId = $friendId;
|
||||
$FollowUp->reminderTime = !empty($reminderTime) ? strtotime($reminderTime) : time();
|
||||
$FollowUp->description = $description;
|
||||
$FollowUp->userId = $userId;
|
||||
$FollowUp->companyId = $companyId;
|
||||
$FollowUp->updateTime = time();
|
||||
$FollowUp->createTime = time();
|
||||
$FollowUp->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理代办事项
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function process(){
|
||||
$ids = $this->request->param('ids','');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($ids)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$ids = explode(',',$ids);
|
||||
|
||||
if (!is_array($ids)){
|
||||
return ResponseHelper::error('格式错误');
|
||||
}
|
||||
|
||||
$FollowUpIds = FollowUp::where(['userId' => $userId,'companyId' => $companyId,'isProcess' => 0])->whereIn('id',$ids)->column('id');
|
||||
if (empty($FollowUpIds)){
|
||||
return ResponseHelper::error('代办事项不存在');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
FollowUp::whereIn('id',$FollowUpIds)->update(['isProcess' => 1,'isRemind' => 1,'updateTime' => time()]);
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','已处理');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('处理失败:'.$e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
141
application/chukebao/controller/LoginController.php
Normal file
141
application/chukebao/controller/LoginController.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use Exception;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\Controller;
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
* 处理用户登录和身份验证
|
||||
*/
|
||||
class LoginController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index($username = '', $password = '',$verifySessionId = '',$verifyCode = '')
|
||||
{
|
||||
|
||||
$username = !empty($username) ? $username : $this->request->param('account', '');
|
||||
$password = !empty($password) ? $password : $this->request->param('password', '');
|
||||
$verifySessionId =!empty($verifySessionId) ? $verifySessionId : $this->request->param('verifySessionId', '');
|
||||
$verifyCode = !empty($verifyCode) ? $verifyCode : $this->request->param('verifyCode', '');
|
||||
$token = JwtUtil::getRequestToken();
|
||||
$payload = '';
|
||||
if (!empty($token)){
|
||||
$payload = JwtUtil::verifyToken($token);
|
||||
}
|
||||
|
||||
if ((empty($username) || empty($password)) && empty($payload)){
|
||||
return ResponseHelper::error('请输入账号密码');
|
||||
}
|
||||
|
||||
// 验证账号是否存在(支持账号或手机号登录)
|
||||
if (empty($payload11)){
|
||||
$user = Db::name('users')
|
||||
->where(function ($query) use ($username) {
|
||||
$query->where('account', $username)->whereOr('phone', $username);
|
||||
})
|
||||
->where(function ($query2) use ($password) {
|
||||
$query2->where('passwordMd5', md5($password))->whereOr('passwordLocal', localEncrypt($password));
|
||||
})
|
||||
->find();
|
||||
}else{
|
||||
$user = $payload;
|
||||
}
|
||||
|
||||
if (empty($user)) {
|
||||
return ResponseHelper::error('账号不存在或密码错误');
|
||||
}
|
||||
|
||||
if($user['status'] != 1){
|
||||
return ResponseHelper::error('账号已禁用');
|
||||
}
|
||||
|
||||
//登录参数
|
||||
$params = [
|
||||
'grant_type' => 'password',
|
||||
'username' => $user['account'],
|
||||
'password' => !empty($user['passwordLocal']) ? localDecrypt($user['passwordLocal']) : $password
|
||||
];
|
||||
try {
|
||||
// 调用登录接口获取token
|
||||
$headerData = ['client:kefu-client'];
|
||||
if (!empty($verifySessionId) && !empty($verifyCode)){
|
||||
$headerData[] = 'verifysessionid:'.$verifySessionId;
|
||||
$headerData[] = 'verifycode:'.$verifyCode;
|
||||
}
|
||||
$header = setHeader($headerData, '', 'plain');
|
||||
$result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST', $header);
|
||||
$result = handleApiResponse($result);
|
||||
if (isset($result['access_token']) && !empty($result['access_token'])) {
|
||||
$kefuData['token'] = $result;
|
||||
$headerData = ['client:kefu-client'];
|
||||
$header = setHeader($headerData, $result['access_token']);
|
||||
$result2 = requestCurl('https://s2.siyuguanli.com:9991/api/account/self', [], 'GET', $header, 'json');
|
||||
$self = handleApiResponse($result2);
|
||||
$kefuData['self'] = $self;
|
||||
Db::name('users')->where('id', $user['id'])->update(['passwordLocal' => localEncrypt($params['password']),'updateTime' => time()]);
|
||||
}else{
|
||||
$kefuData = [
|
||||
'token' => [
|
||||
"access_token"=> "27gINKZqGux6V4j9QLawOcTKlWXg-j4zxQjKvScvDTq-YlLcwIrDP2AFaNZKnOo9zLzepOBC8qrdXh4z9GxxkwE9TKGRQI1FjITRlMZzrim13IbSEbJUoywGs_BhDmIZnnPhfjqxDB1vjZgVtT2Kp4bxbUCV3i2uO_FTv_DT2G7NUFFLjq8oIuUrd_c1YXeYkH8m8Fw1AM4yPZJZyfdaHSSMOpJ2Bk2LAghnB6OaZCYWNFQcwWARsmh1BSAANUOAoadjkztZC7Fme-GGOm2sLo0WL6Mf26NfeLmnkluewTiPMyacD7RYclAR2LZ_8Mhwr3pwRg",
|
||||
"token_type"=> "bearer",
|
||||
"expires_in"=> 195519999,
|
||||
"refresh_token"=> "a9545daa-d1c4-4c87-8c4c-b713631d4f0d"
|
||||
],
|
||||
'self' => [
|
||||
'account' => [
|
||||
"id"=> 5538,
|
||||
"realName"=> "测试",
|
||||
"nickname"=> "",
|
||||
"memo"=> "",
|
||||
"avatar"=> "",
|
||||
"userName"=> "wz_02",
|
||||
"secret"=> "8f6f743395ad4198b6a4c0e6ca0e452f",
|
||||
"accountType"=> 10,
|
||||
"departmentId"=> 2130,
|
||||
"useGoogleSecretKey"=> false,
|
||||
"hasVerifyGoogleSecret"=> true
|
||||
],
|
||||
'tenant' => [
|
||||
"id" => 242,
|
||||
"name"=> "泉州市卡若网络技术有限公司",
|
||||
"guid"=> "5E2C38F5A275450D935F3ECEC076124E",
|
||||
"thirdParty"=> null,
|
||||
"tenantType"=> 0,
|
||||
"deployName"=> "deploy-s2"
|
||||
]
|
||||
]
|
||||
];
|
||||
//return ResponseHelper::error($result['error_description']);
|
||||
}
|
||||
|
||||
|
||||
unset($user['passwordMd5'],$user['deleteTime']);
|
||||
$userData['member'] = $user;
|
||||
|
||||
// 生成JWT令牌
|
||||
$expired = 86400 * 30;
|
||||
$token = JwtUtil::createToken($user, $expired);
|
||||
$token_expired = time() + $expired;
|
||||
|
||||
$userData['token'] = $token;
|
||||
$userData['token_expired'] = $token_expired;
|
||||
$userData['kefuData'] = $kefuData;
|
||||
|
||||
return ResponseHelper::success($userData, '登录成功');
|
||||
} catch (Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
476
application/chukebao/controller/MessageController.php
Normal file
476
application/chukebao/controller/MessageController.php
Normal file
@@ -0,0 +1,476 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\api\model\WechatMessageModel;
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Env;
|
||||
use app\common\service\AuthService;
|
||||
|
||||
class MessageController extends BaseController
|
||||
{
|
||||
protected $baseUrl;
|
||||
protected $authorization;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->baseUrl = Env::get('api.wechat_url');
|
||||
$this->authorization = AuthService::getSystemAuthorization();
|
||||
}
|
||||
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region,isTop');
|
||||
|
||||
|
||||
// 构建好友子查询
|
||||
$friendSubQuery = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->field('id')
|
||||
->buildSql();
|
||||
|
||||
// 优化后的查询:使用MySQL兼容的查询方式
|
||||
$unionQuery = "
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId, wc.isTop
|
||||
FROM s2_wechat_chatroom wc
|
||||
INNER JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId AND m.type = 2
|
||||
INNER JOIN (
|
||||
SELECT wechatChatroomId, MAX(wechatTime) as maxTime, MAX(id) as maxId
|
||||
FROM s2_wechat_message
|
||||
WHERE type = 2
|
||||
GROUP BY wechatChatroomId
|
||||
) latest ON m.wechatChatroomId = latest.wechatChatroomId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
|
||||
WHERE wc.accountId = {$accountId} AND wc.isDeleted = 0
|
||||
)
|
||||
UNION ALL
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId, 0 as isTop
|
||||
FROM s2_wechat_message m
|
||||
INNER JOIN (
|
||||
SELECT wechatFriendId, MAX(wechatTime) as maxTime, MAX(id) as maxId
|
||||
FROM s2_wechat_message
|
||||
WHERE type = 1 AND wechatFriendId IN {$friendSubQuery}
|
||||
GROUP BY wechatFriendId
|
||||
) latest ON m.wechatFriendId = latest.wechatFriendId AND m.wechatTime = latest.maxTime AND m.id = latest.maxId
|
||||
WHERE m.type = 1 AND m.wechatFriendId IN {$friendSubQuery}
|
||||
)
|
||||
ORDER BY wechatTime DESC
|
||||
LIMIT " . (($page - 1) * $limit) . ", {$limit}
|
||||
";
|
||||
|
||||
$list = Db::query($unionQuery);
|
||||
|
||||
// 对分页后的结果进行排序(按wechatTime降序)
|
||||
usort($list, function ($a, $b) {
|
||||
return $b['wechatTime'] <=> $a['wechatTime'];
|
||||
});
|
||||
|
||||
// 批量统计未读数量(isRead=0),按好友/群聊分别聚合
|
||||
$friendIds = [];
|
||||
$chatroomIds = [];
|
||||
foreach ($list as $row) {
|
||||
if (!empty($row['wechatFriendId'])) {
|
||||
$friendIds[] = $row['wechatFriendId'];
|
||||
}
|
||||
if (!empty($row['wechatChatroomId'])) {
|
||||
$chatroomIds[] = $row['wechatChatroomId'];
|
||||
}
|
||||
}
|
||||
$friendIds = array_values(array_unique(array_filter($friendIds)));
|
||||
$chatroomIds = array_values(array_unique(array_filter($chatroomIds)));
|
||||
|
||||
$friendUnreadMap = [];
|
||||
if (!empty($friendIds)) {
|
||||
// 获取未读消息数量
|
||||
$friendUnreadMap = Db::table('s2_wechat_message')
|
||||
->where(['isRead' => 0])
|
||||
->whereIn('wechatFriendId', $friendIds)
|
||||
->group('wechatFriendId')
|
||||
->column('COUNT(*) AS cnt', 'wechatFriendId');
|
||||
}
|
||||
|
||||
$chatroomUnreadMap = [];
|
||||
if (!empty($chatroomIds)) {
|
||||
// 获取未读消息数量
|
||||
$chatroomUnreadMap = Db::table('s2_wechat_message')
|
||||
->where(['isRead' => 0])
|
||||
->whereIn('wechatChatroomId', $chatroomIds)
|
||||
->group('wechatChatroomId')
|
||||
->column('COUNT(*) AS cnt', 'wechatChatroomId');
|
||||
}
|
||||
|
||||
$aiTypeData = [];
|
||||
if (!empty($friendIds)) {
|
||||
$aiTypeData = FriendSettings::where('friendId', 'in', $friendIds)->column('friendId,type');
|
||||
}
|
||||
|
||||
foreach ($list as $k => &$v) {
|
||||
|
||||
$createTime = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$wechatTime = !empty($v['wechatTime']) ? date('Y-m-d H:i:s', $v['wechatTime']) : '';
|
||||
|
||||
|
||||
$unreadCount = 0;
|
||||
$v['aiType'] = 0;
|
||||
if (!empty($v['wechatFriendId'])) {
|
||||
$v['nickname'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['nickname'] : '';
|
||||
$v['avatar'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['avatar'] : '';
|
||||
$v['conRemark'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['conRemark'] : '';
|
||||
$v['groupId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['groupId'] : '';
|
||||
$v['wechatAccountId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatAccountId'] : '';
|
||||
$v['wechatId'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['wechatId'] : '';
|
||||
$v['extendFields'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['extendFields'] : [];
|
||||
$v['region'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['region'] : '';
|
||||
$v['phone'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['phone'] : '';
|
||||
$v['isTop'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['isTop'] : 0;
|
||||
$v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : [];
|
||||
|
||||
$unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
|
||||
$v['aiType'] = isset($aiTypeData[$v['wechatFriendId']]) ? $aiTypeData[$v['wechatFriendId']] : 0;
|
||||
unset($v['chatroomId']);
|
||||
}
|
||||
|
||||
if (!empty($v['wechatChatroomId'])) {
|
||||
$v['conRemark'] = '';
|
||||
$unreadCount = isset($chatroomUnreadMap[$v['wechatChatroomId']]) ? (int)$chatroomUnreadMap[$v['wechatChatroomId']] : 0;
|
||||
}
|
||||
|
||||
$v['id'] = !empty($v['wechatFriendId']) ? $v['wechatFriendId'] : $v['wechatChatroomId'];
|
||||
$v['config'] = [
|
||||
'top' => !empty($v['isTop']) ? true : false,
|
||||
'unreadCount' => $unreadCount,
|
||||
'chat' => true,
|
||||
'msgTime' => $v['wechatTime'],
|
||||
];
|
||||
$v['createTime'] = $createTime;
|
||||
$v['lastUpdateTime'] = $wechatTime;
|
||||
|
||||
// 最新消息内容已经在UNION查询中获取,直接使用
|
||||
$v['latestMessage'] = [
|
||||
'content' => $v['content'],
|
||||
'wechatTime' => $wechatTime
|
||||
];
|
||||
|
||||
unset($v['wechatFriendId'], $v['wechatChatroomId'],$v['isTop']);
|
||||
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success($list);
|
||||
}
|
||||
|
||||
|
||||
public function readMessage()
|
||||
{
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
if (empty($wechatChatroomId) && empty($wechatFriendId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$where = [];
|
||||
if (!empty($wechatChatroomId)) {
|
||||
$where[] = ['wechatChatroomId', '=', $wechatChatroomId];
|
||||
}
|
||||
|
||||
if (!empty($wechatFriendId)) {
|
||||
$where[] = ['wechatFriendId', '=', $wechatFriendId];
|
||||
}
|
||||
|
||||
Db::table('s2_wechat_message')->where($where)->update(['isRead' => 1]);
|
||||
return ResponseHelper::success([]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单条消息发送状态(带轮询功能)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMessageStatus()
|
||||
{
|
||||
$messageId = $this->request->param('messageId', 0);
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($messageId)) {
|
||||
return ResponseHelper::error('消息ID不能为空');
|
||||
}
|
||||
|
||||
if(empty($wechatFriendId) && empty($wechatChatroomId)) {
|
||||
return ResponseHelper::error('消息类型不能为空');
|
||||
}
|
||||
|
||||
// 查询单条消息的基本信息(只需要发送状态相关字段)
|
||||
$message = Db::table('s2_wechat_message')
|
||||
->where('id', $messageId)
|
||||
->field('id,wechatAccountId,wechatFriendId,wechatChatroomId,sendStatus')
|
||||
->find();
|
||||
|
||||
if (empty($message)) {
|
||||
$message = [
|
||||
'id' => $messageId,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'wechatFriendId' => $wechatFriendId,
|
||||
'wechatChatroomId' => $wechatChatroomId,
|
||||
'sendStatus' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$sendStatus = isset($message['sendStatus']) ? (int)$message['sendStatus'] : 0;
|
||||
$isUpdated = false;
|
||||
$pollCount = 0;
|
||||
$maxPollCount = 10; // 最多轮询10次
|
||||
|
||||
// 如果sendStatus不为0,开始轮询
|
||||
if ($sendStatus != 0) {
|
||||
$messageRequest = [
|
||||
'id' => $message['id'],
|
||||
'wechatAccountId' => !empty($wechatAccountId) ? $wechatAccountId : $message['wechatAccountId'],
|
||||
'wechatFriendId' => !empty($message['wechatFriendId']) ? $message['wechatFriendId'] : '',
|
||||
'wechatChatroomId' => !empty($message['wechatChatroomId']) ? $message['wechatChatroomId'] : '',
|
||||
'from' => '',
|
||||
'to' => '',
|
||||
];
|
||||
|
||||
|
||||
// 轮询逻辑:最多10次
|
||||
while ($pollCount < $maxPollCount && $sendStatus != 0) {
|
||||
$pollCount++;
|
||||
|
||||
// 请求线上接口获取最新状态
|
||||
$newData = $this->fetchLatestMessageFromApi($messageRequest);
|
||||
|
||||
if (!empty($newData)) {
|
||||
// 重新查询消息状态(可能已更新)
|
||||
$updatedMessage = Db::table('s2_wechat_message')
|
||||
->where('id', $messageId)
|
||||
->field('sendStatus')
|
||||
->find();
|
||||
|
||||
if (!empty($updatedMessage)) {
|
||||
$newSendStatus = isset($updatedMessage['sendStatus']) ? (int)$updatedMessage['sendStatus'] : 0;
|
||||
|
||||
// 如果状态已更新为0(已发送),停止轮询
|
||||
if ($newSendStatus == 0) {
|
||||
$sendStatus = 0;
|
||||
$isUpdated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果状态仍然是1,继续轮询(但需要等待一下,避免请求过快)
|
||||
if ($newSendStatus != 0 && $pollCount < $maxPollCount) {
|
||||
// 每次轮询间隔500毫秒(0.5秒)
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果请求失败,等待后继续尝试
|
||||
if ($pollCount < $maxPollCount) {
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回发送状态信息
|
||||
return ResponseHelper::success([
|
||||
'messageId' => $messageId,
|
||||
'sendStatus' => $sendStatus,
|
||||
'statusText' => $sendStatus == 0 ? '已发送' : '发送中'
|
||||
]);
|
||||
}
|
||||
|
||||
public function details()
|
||||
{
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$from = $this->request->param('From', '');
|
||||
$to = $this->request->param('To', '');
|
||||
$olderData = $this->request->param('olderData', false);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
if (empty($wechatChatroomId) && empty($wechatFriendId)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$where = [];
|
||||
if (!empty($wechatChatroomId)) {
|
||||
$where[] = ['wechatChatroomId', '=', $wechatChatroomId];
|
||||
}
|
||||
|
||||
if (!empty($wechatFriendId)) {
|
||||
$where[] = ['wechatFriendId', '=', $wechatFriendId];
|
||||
}
|
||||
|
||||
if (!empty($From) && !empty($To)) {
|
||||
$where[] = ['wechatTime', 'between', [$from, $to]];
|
||||
}
|
||||
|
||||
$total = Db::table('s2_wechat_message')->where($where)->count();
|
||||
$list = Db::table('s2_wechat_message')->where($where)->page($page, $limit)->order('id DESC')->select();
|
||||
|
||||
// 检查消息是否有sendStatus字段,如果有且不为0,则请求线上最新接口
|
||||
foreach ($list as $k => &$item) {
|
||||
// 检查是否存在sendStatus字段且不为0(0表示已发送成功)
|
||||
if (isset($item['sendStatus']) && $item['sendStatus'] != 0) {
|
||||
// 需要请求新的数据
|
||||
$messageRequest = [
|
||||
'id' => $item['id'],
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'wechatFriendId' => $wechatFriendId,
|
||||
'wechatChatroomId' => $wechatChatroomId,
|
||||
'from' => '',
|
||||
'to' => '',
|
||||
];
|
||||
$newData = $this->fetchLatestMessageFromApi($messageRequest);
|
||||
if (!empty($newData)){
|
||||
$item['sendStatus'] = 0;
|
||||
}
|
||||
}
|
||||
// 格式化时间
|
||||
$item['wechatTime'] = !empty($item['wechatTime']) ? date('Y-m-d H:i:s', $item['wechatTime']) : '';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
|
||||
return ResponseHelper::success(['total' => $total, 'list' => $list]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从线上接口获取最新消息
|
||||
* @param array $messageRequest 消息项(包含wechatAccountId、wechatFriendId或wechatChatroomId、id等)
|
||||
* @return array|null 最新消息数据,失败返回null
|
||||
*/
|
||||
private function fetchLatestMessageFromApi($messageRequest)
|
||||
{
|
||||
if (empty($this->baseUrl) || empty($this->authorization)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $this->authorization, 'json');
|
||||
|
||||
// 判断是好友消息还是群聊消息
|
||||
if (!empty($messageRequest['wechatFriendId'])) {
|
||||
// 好友消息接口
|
||||
$params = [
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 20, // 获取多条消息以便找到对应的消息
|
||||
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
|
||||
'olderData' => true,
|
||||
'wechatAccountId' => $messageRequest['wechatAccountId'],
|
||||
'wechatFriendId' => $messageRequest['wechatFriendId'],
|
||||
'from' => $messageRequest['from'],
|
||||
'to' => $messageRequest['to'],
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
$result = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
// 查找对应的消息
|
||||
if (!empty($response) && is_array($response)) {
|
||||
$data = $response[0];
|
||||
if ($data['sendStatus'] == 0){
|
||||
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} elseif (!empty($messageRequest['wechatChatroomId'])) {
|
||||
// 群聊消息接口
|
||||
$params = [
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 20, // 获取多条消息以便找到对应的消息
|
||||
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
|
||||
'olderData' => true,
|
||||
'wechatId' => '',
|
||||
'wechatAccountId' => $messageRequest['wechatAccountId'],
|
||||
'wechatChatroomId' => $messageRequest['wechatChatroomId'],
|
||||
'from' => $messageRequest['from'],
|
||||
'to' => $messageRequest['to'],
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
|
||||
$result = requestCurl($this->baseUrl . 'api/ChatroomMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 查找对应的消息
|
||||
if (!empty($response) && is_array($response)) {
|
||||
$data = $response[0];
|
||||
if ($data['sendStatus'] == 0){
|
||||
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('获取线上最新消息失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据库中的消息
|
||||
* @param array $latestMessage 线上获取的最新消息
|
||||
* @param array $oldMessage 旧消息数据
|
||||
*/
|
||||
private function updateMessageInDatabase($latestMessage, $oldMessage)
|
||||
{
|
||||
try {
|
||||
// 使用API模块的MessageController来保存消息
|
||||
$apiMessageController = new \app\api\controller\MessageController();
|
||||
|
||||
// 判断是好友消息还是群聊消息
|
||||
if (!empty($oldMessage['wechatFriendId'])) {
|
||||
// 保存好友消息
|
||||
$apiMessageController->saveMessage($latestMessage);
|
||||
} elseif (!empty($oldMessage['wechatChatroomId'])) {
|
||||
// 保存群聊消息
|
||||
$apiMessageController->saveChatroomMessage($latestMessage);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('更新数据库消息失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
504
application/chukebao/controller/MomentsController.php
Normal file
504
application/chukebao/controller/MomentsController.php
Normal file
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\KfMoments;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class MomentsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 创建朋友圈
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 获取请求参数
|
||||
$text = $this->request->param('content', ''); // 朋友圈内容
|
||||
$picUrlList = $this->request->param('picUrlList', []); // 图片列表
|
||||
$videoUrl = $this->request->param('videoUrl', ''); // 视频链接
|
||||
$link = $this->request->param('link', []); // 链接信息
|
||||
$momentContentType = (int)$this->request->param('type', 1); // 内容类型 1文本 2图文 3视频 4链接
|
||||
$publicMode = (int)$this->request->param('publicMode', 0); // 公开模式
|
||||
$wechatIds = $this->request->param('wechatIds', []); // 微信账号ID列表
|
||||
$labels = $this->request->param('labels', []); // 标签列表
|
||||
$timingTime = $this->request->param('timingTime', date('Y-m-d H:i:s')); // 定时发布时间
|
||||
$immediately = $this->request->param('immediately', false); // 是否立即发布
|
||||
|
||||
// 格式化时间字符串为统一格式
|
||||
$timingTime = $this->normalizeTimingTime($timingTime);
|
||||
if ($timingTime === false) {
|
||||
return ResponseHelper::error('定时发布时间格式不正确');
|
||||
}
|
||||
|
||||
// 参数验证
|
||||
if (empty($text) && empty($picUrlList) && empty($videoUrl)) {
|
||||
return ResponseHelper::error('朋友圈内容不能为空');
|
||||
}
|
||||
|
||||
if (empty($wechatIds)) {
|
||||
return ResponseHelper::error('请选择发布账号');
|
||||
}
|
||||
|
||||
// 校验内容类型
|
||||
if (!in_array($momentContentType, [1, 2, 3, 4])) {
|
||||
return ResponseHelper::error('内容类型不合法,支持:1文本 2图文 3视频 4链接');
|
||||
}
|
||||
|
||||
if(!empty($labels)){
|
||||
$publicMode = 2;
|
||||
}
|
||||
|
||||
// 根据内容类型校验必要参数
|
||||
switch ($momentContentType) {
|
||||
case 1: // 文本
|
||||
if (empty($text)) {
|
||||
return ResponseHelper::error('文本类型必须填写内容');
|
||||
}
|
||||
break;
|
||||
case 2: // 图文
|
||||
if (empty($text) || empty($picUrlList)) {
|
||||
return ResponseHelper::error('图文类型必须填写内容和上传图片');
|
||||
}
|
||||
break;
|
||||
case 3: // 视频
|
||||
if (empty($videoUrl)) {
|
||||
return ResponseHelper::error('视频类型必须上传视频');
|
||||
}
|
||||
break;
|
||||
case 4: // 链接
|
||||
if (empty($link)) {
|
||||
return ResponseHelper::error('链接类型必须填写链接信息');
|
||||
}
|
||||
if (empty($link['url'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接地址');
|
||||
}
|
||||
if (empty($link['desc'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接描述');
|
||||
}
|
||||
if (empty($link['image'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接图片');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 处理链接信息 - 所有链接都必须验证
|
||||
if (!empty($link)) {
|
||||
$link = [
|
||||
'desc' => $link['desc'] ?? '',
|
||||
'image' => $link['image'] ?? '',
|
||||
'url' => $link['url'] ?? ''
|
||||
];
|
||||
|
||||
// 验证链接URL格式
|
||||
if (!empty($link['url']) && !filter_var($link['url'], FILTER_VALIDATE_URL)) {
|
||||
return ResponseHelper::error('链接地址格式不正确');
|
||||
}
|
||||
} else {
|
||||
$link = ['desc' => '', 'image' => '', 'url' => ''];
|
||||
}
|
||||
|
||||
// 构建发布账号列表
|
||||
$jobPublishWechatMomentsItems = $this->buildJobPublishWechatMomentsItems($wechatIds, $labels);
|
||||
if (empty($jobPublishWechatMomentsItems)) {
|
||||
return ResponseHelper::error('无法获取有效的发布账号信息');
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建发送数据
|
||||
$sendData = [
|
||||
'altList' => '',
|
||||
'beginTime' => $timingTime,
|
||||
'endTime' => date('Y-m-d H:i:s', strtotime($timingTime) + 3600),
|
||||
'immediately' => $immediately,
|
||||
'isUseLocation' => false,
|
||||
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems,
|
||||
'lat' => 0,
|
||||
'lng' => 0,
|
||||
'link' => $link,
|
||||
'momentContentType' => $momentContentType,
|
||||
'picUrlList' => $picUrlList,
|
||||
'poiAddress' => '',
|
||||
'poiName' => '',
|
||||
'publicMode' => $publicMode,
|
||||
'text' => $text,
|
||||
'timingTime' => $timingTime ?: date('Y-m-d H:i:s'),
|
||||
'videoUrl' => $videoUrl
|
||||
];
|
||||
|
||||
// 保存到数据库
|
||||
$moments = new KfMoments();
|
||||
$moments->companyId = $companyId;
|
||||
$moments->userId = $userId;
|
||||
$moments->sendData = json_encode($sendData, 256);
|
||||
$nowTs = time();
|
||||
$moments->createTime = $nowTs;
|
||||
$moments->updateTime = $nowTs;
|
||||
$moments->isDel = 0;
|
||||
$moments->delTime = null;
|
||||
$moments->isSend = $immediately ? 1 : 0;
|
||||
$moments->sendTime = $immediately ? $nowTs : strtotime($timingTime);
|
||||
$moments->save();
|
||||
|
||||
// 如果立即发布,调用发布接口
|
||||
if ($immediately) {
|
||||
$this->publishMoments($sendData);
|
||||
}
|
||||
return ResponseHelper::success('', '朋友圈创建成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑朋友圈
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('ID不合法');
|
||||
}
|
||||
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 获取请求参数(与创建一致的字段名)
|
||||
$text = $this->request->param('content', '');
|
||||
$picUrlList = $this->request->param('picUrlList', []);
|
||||
$videoUrl = $this->request->param('videoUrl', '');
|
||||
$link = $this->request->param('link', []);
|
||||
$momentContentType = (int)$this->request->param('type', 1);
|
||||
$publicMode = (int)$this->request->param('publicMode', 0);
|
||||
$wechatIds = $this->request->param('wechatIds', []);
|
||||
$labels = $this->request->param('labels', []);
|
||||
$timingTime = $this->request->param('timingTime', date('Y-m-d H:i:s'));
|
||||
$immediately = $this->request->param('immediately', false);
|
||||
|
||||
// 格式化时间字符串为统一格式
|
||||
$timingTime = $this->normalizeTimingTime($timingTime);
|
||||
if ($timingTime === false) {
|
||||
return ResponseHelper::error('定时发布时间格式不正确');
|
||||
}
|
||||
|
||||
// 读取待编辑记录
|
||||
/** @var KfMoments|null $moments */
|
||||
$moments = KfMoments::where(['id' => $id, 'companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->find();
|
||||
if (empty($moments)) {
|
||||
return ResponseHelper::error('朋友圈不存在');
|
||||
}
|
||||
|
||||
// 参数校验
|
||||
if (empty($text) && empty($picUrlList) && empty($videoUrl)) {
|
||||
return ResponseHelper::error('朋友圈内容不能为空');
|
||||
}
|
||||
if (empty($wechatIds)) {
|
||||
return ResponseHelper::error('请选择发布账号');
|
||||
}
|
||||
if (!in_array($momentContentType, [1, 2, 3, 4])) {
|
||||
return ResponseHelper::error('内容类型不合法,支持:1文本 2图文 3视频 4链接');
|
||||
}
|
||||
if (!empty($labels)) {
|
||||
$publicMode = 2;
|
||||
}
|
||||
switch ($momentContentType) {
|
||||
case 1:
|
||||
if (empty($text)) {
|
||||
return ResponseHelper::error('文本类型必须填写内容');
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (empty($text) || empty($picUrlList)) {
|
||||
return ResponseHelper::error('图文类型必须填写内容和上传图片');
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (empty($videoUrl)) {
|
||||
return ResponseHelper::error('视频类型必须上传视频');
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (empty($link)) {
|
||||
return ResponseHelper::error('链接类型必须填写链接信息');
|
||||
}
|
||||
if (empty($link['url'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接地址');
|
||||
}
|
||||
if (empty($link['desc'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接描述');
|
||||
}
|
||||
if (empty($link['image'])) {
|
||||
return ResponseHelper::error('链接类型必须填写链接图片');
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!empty($link)) {
|
||||
$link = [
|
||||
'desc' => $link['desc'] ?? '',
|
||||
'image' => $link['image'] ?? '',
|
||||
'url' => $link['url'] ?? ''
|
||||
];
|
||||
if (!empty($link['url']) && !filter_var($link['url'], FILTER_VALIDATE_URL)) {
|
||||
return ResponseHelper::error('链接地址格式不正确');
|
||||
}
|
||||
} else {
|
||||
$link = ['desc' => '', 'image' => '', 'url' => ''];
|
||||
}
|
||||
|
||||
// 构建账号列表
|
||||
$jobPublishWechatMomentsItems = $this->buildJobPublishWechatMomentsItems($wechatIds, $labels);
|
||||
if (empty($jobPublishWechatMomentsItems)) {
|
||||
return ResponseHelper::error('无法获取有效的发布账号信息');
|
||||
}
|
||||
|
||||
try {
|
||||
$sendData = [
|
||||
'altList' => '',
|
||||
'beginTime' => $timingTime,
|
||||
'endTime' => date('Y-m-d H:i:s', strtotime($timingTime) + 1800),
|
||||
'immediately' => $immediately,
|
||||
'isUseLocation' => false,
|
||||
'jobPublishWechatMomentsItems' => $jobPublishWechatMomentsItems,
|
||||
'lat' => 0,
|
||||
'lng' => 0,
|
||||
'link' => $link,
|
||||
'momentContentType' => $momentContentType,
|
||||
'picUrlList' => $picUrlList,
|
||||
'poiAddress' => '',
|
||||
'poiName' => '',
|
||||
'publicMode' => $publicMode,
|
||||
'text' => $text,
|
||||
'timingTime' => $timingTime ?: date('Y-m-d H:i:s'),
|
||||
'videoUrl' => $videoUrl
|
||||
];
|
||||
|
||||
$moments->sendData = json_encode($sendData, 256);
|
||||
$moments->isSend = $immediately ? 1 : 0;
|
||||
$moments->sendTime = $immediately ? time() : strtotime($timingTime);
|
||||
$moments->updateTime = time();
|
||||
$moments->save();
|
||||
|
||||
if ($immediately) {
|
||||
$this->publishMoments($sendData);
|
||||
}
|
||||
|
||||
return ResponseHelper::success('', '朋友圈更新成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建发布账号列表
|
||||
* @param array $wechatIds 微信账号ID列表
|
||||
* @param array $labels 标签列表
|
||||
* @return array
|
||||
*/
|
||||
private function buildJobPublishWechatMomentsItems($wechatIds, $labels)
|
||||
{
|
||||
try {
|
||||
// 查询微信账号信息
|
||||
$wechatAccounts = Db::table('s2_wechat_account')
|
||||
->whereIn('id', $wechatIds)
|
||||
->field('id,labels')
|
||||
->select();
|
||||
if (empty($wechatAccounts)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($wechatAccounts as $account) {
|
||||
$accountLabels = [];
|
||||
|
||||
// 如果账号有标签,解析标签
|
||||
if (!empty($account['labels'])) {
|
||||
$accountLabels = is_string($account['labels'])
|
||||
? json_decode($account['labels'], true)
|
||||
: $account['labels'];
|
||||
}
|
||||
|
||||
// 取传入标签与账号标签的交集
|
||||
$finalLabels = array_intersect($labels, $accountLabels);
|
||||
|
||||
$result[] = [
|
||||
'wechatAccountId' => $account['id'],
|
||||
'labels' => array_values($finalLabels), // 重新索引数组
|
||||
'comments' => []
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('构建发布账号列表失败:' . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布朋友圈到微信
|
||||
* @param array $sendData
|
||||
* @return bool
|
||||
*/
|
||||
private function publishMoments($sendData)
|
||||
{
|
||||
try {
|
||||
// 这里调用实际的朋友圈发布接口
|
||||
// 根据您的系统架构,可能需要调用 WebSocket 或其他服务
|
||||
// 示例:调用 MomentsController 的 addJob 方法
|
||||
$moments = new \app\api\controller\MomentsController();
|
||||
return $moments->addJob($sendData);
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志
|
||||
\think\facade\Log::error('朋友圈发布失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取朋友圈列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$page = (int)$this->request->param('page', 1);
|
||||
$limit = (int)$this->request->param('limit', 10);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
try {
|
||||
$list = KfMoments::where(['companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])
|
||||
->order('createTime desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
$total = KfMoments::where(['companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->count();
|
||||
|
||||
// 处理数据
|
||||
$data = [];
|
||||
foreach ($list as $item) {
|
||||
$sendData = json_decode($item->sendData,true);
|
||||
$data[] = [
|
||||
'id' => $item->id,
|
||||
'content' => $sendData['text'] ?? '',
|
||||
'momentContentType' => $sendData['momentContentType'] ?? 1,
|
||||
'picUrlList' => $sendData['picUrlList'] ?? [],
|
||||
'videoUrl' => $sendData['videoUrl'] ?? '',
|
||||
'link' => $sendData['link'] ?? [],
|
||||
'publicMode' => $sendData['publicMode'] ?? 2,
|
||||
'isSend' => $item->isSend,
|
||||
'sendTime' => date('Y-m-d H:i:s',$item->sendTime),
|
||||
'accountCount' => count($sendData['jobPublishWechatMomentsItems'] ?? [])
|
||||
];
|
||||
}
|
||||
|
||||
return ResponseHelper::success([
|
||||
'list' => $data,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
], '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除朋友圈
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('ID不合法');
|
||||
}
|
||||
|
||||
try {
|
||||
$moments = KfMoments::where(['id' => $id, 'companyId' => $companyId, 'userId' => $userId, 'isDel' => 0])->find();
|
||||
if (empty($moments)) {
|
||||
return ResponseHelper::error('朋友圈不存在');
|
||||
}
|
||||
|
||||
$moments->isDel = 1;
|
||||
$moments->delTime = time();
|
||||
$moments->updateTime = time();
|
||||
$moments->save();
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化时间字符串为 Y-m-d H:i:s 格式
|
||||
* 支持多种时间格式:
|
||||
* - "2026年1月5日15:43:00"
|
||||
* - "2026-01-05 15:43:00"
|
||||
* - "2026/01/05 15:43:00"
|
||||
* - 时间戳
|
||||
* @param string|int $timingTime 时间字符串或时间戳
|
||||
* @return string|false 格式化后的时间字符串,失败返回false
|
||||
*/
|
||||
private function normalizeTimingTime($timingTime)
|
||||
{
|
||||
if (empty($timingTime)) {
|
||||
return date('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
// 如果是时间戳
|
||||
if (is_numeric($timingTime) && strlen($timingTime) == 10) {
|
||||
return date('Y-m-d H:i:s', $timingTime);
|
||||
}
|
||||
|
||||
// 如果是毫秒时间戳
|
||||
if (is_numeric($timingTime) && strlen($timingTime) == 13) {
|
||||
return date('Y-m-d H:i:s', intval($timingTime / 1000));
|
||||
}
|
||||
|
||||
// 如果已经是标准格式,直接返回
|
||||
if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $timingTime)) {
|
||||
return $timingTime;
|
||||
}
|
||||
|
||||
// 处理中文日期格式:2026年1月5日15:43:00 或 2026年01月05日15:43:00
|
||||
if (preg_match('/^(\d{4})年(\d{1,2})月(\d{1,2})日(\d{1,2}):(\d{1,2}):(\d{1,2})$/', $timingTime, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
|
||||
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
|
||||
$hour = str_pad($matches[4], 2, '0', STR_PAD_LEFT);
|
||||
$minute = str_pad($matches[5], 2, '0', STR_PAD_LEFT);
|
||||
$second = str_pad($matches[6], 2, '0', STR_PAD_LEFT);
|
||||
return "{$year}-{$month}-{$day} {$hour}:{$minute}:{$second}";
|
||||
}
|
||||
|
||||
// 处理中文日期格式(无秒):2026年1月5日15:43
|
||||
if (preg_match('/^(\d{4})年(\d{1,2})月(\d{1,2})日(\d{1,2}):(\d{1,2})$/', $timingTime, $matches)) {
|
||||
$year = $matches[1];
|
||||
$month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
|
||||
$day = str_pad($matches[3], 2, '0', STR_PAD_LEFT);
|
||||
$hour = str_pad($matches[4], 2, '0', STR_PAD_LEFT);
|
||||
$minute = str_pad($matches[5], 2, '0', STR_PAD_LEFT);
|
||||
return "{$year}-{$month}-{$day} {$hour}:{$minute}:00";
|
||||
}
|
||||
|
||||
// 尝试使用 strtotime 解析其他格式
|
||||
$timestamp = strtotime($timingTime);
|
||||
if ($timestamp !== false) {
|
||||
return date('Y-m-d H:i:s', $timestamp);
|
||||
}
|
||||
|
||||
// 如果所有方法都失败,返回 false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
116
application/chukebao/controller/NoticeController.php
Normal file
116
application/chukebao/controller/NoticeController.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\FollowUp;
|
||||
use app\chukebao\model\NoticeModel;
|
||||
use app\chukebao\model\ToDo;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class NoticeController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
$noRead = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId,'isRead' => 0])->count();
|
||||
|
||||
$query = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId])
|
||||
->order('id desc');
|
||||
if (!empty($keyword)) {
|
||||
$query->where('title|message', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->order('isRead ASC,id DESC')->select()->toArray();
|
||||
|
||||
|
||||
foreach ($list as $k => &$v) {
|
||||
if ($v['type'] == 1) {
|
||||
$friendId = ToDo::where(['id' => $v['bindId']])->value('friendId');
|
||||
} elseif ($v['type'] == 2) {
|
||||
$friendId = FollowUp::where(['id' => $v['bindId']])->value('friendId');
|
||||
}
|
||||
if (!empty($friendId)) {
|
||||
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->field('nickname,avatar')->find();
|
||||
} else {
|
||||
$friend = ['nickname' => '', 'avatar' => ''];
|
||||
}
|
||||
$v['friendData'] = $friend;
|
||||
|
||||
$v['readTime'] = !empty($v['readTime']) ? date('Y-m-d H:i:s', $v['readTime']) : '';
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total,'noRead' => $noRead]);
|
||||
}
|
||||
|
||||
|
||||
public function readMessage()
|
||||
{
|
||||
$id = $this->request->param('id', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$notice = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $id, 'isRead' => 0])->find();
|
||||
if (empty($notice)) {
|
||||
return ResponseHelper::error('该消息不存在或标记已读');
|
||||
}
|
||||
$notice->isRead = 1;
|
||||
$notice->readTime = time();
|
||||
$notice->save();
|
||||
if ($notice->type == 1) {
|
||||
ToDo::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $notice->bindId])->update(['isProcess' => 1, 'updateTime' => time()]);
|
||||
} elseif ($notice->type == 2) {
|
||||
FollowUp::where(['userId' => $userId, 'companyId' => $companyId, 'id' => $notice->bindId])->update(['isProcess' => 1, 'updateTime' => time()]);
|
||||
}
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ', '处理成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('处理失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function readAll()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$noticeData = NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'isRead' => 0])->select()->toArray();
|
||||
if (empty($noticeData)) {
|
||||
return ResponseHelper::error('暂未有新消息');
|
||||
}
|
||||
NoticeModel::where(['userId' => $userId, 'companyId' => $companyId, 'isRead' => 0])->update(['isRead' => 1, 'readTime' => time()]);
|
||||
FollowUp::where(['userId' => $userId, 'companyId' => $companyId, 'isProcess' => 0])->update(['isProcess' => 1, 'updateTime' => time()]);
|
||||
ToDo::where(['userId' => $userId, 'companyId' => $companyId, 'isProcess' => 0])->update(['isProcess' => 1, 'updateTime' => time()]);
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ', '全部已读');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('处理失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
233
application/chukebao/controller/QuestionsController.php
Normal file
233
application/chukebao/controller/QuestionsController.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\Questions;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class QuestionsController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
$query = Questions::where(['userId' => $userId,'companyId' => $companyId,'isDel' => 0])
|
||||
->order('id desc');
|
||||
if (!empty($keyword)){
|
||||
$query->where('questions|answers', 'like', '%'.$keyword.'%');
|
||||
}
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select()->toArray();
|
||||
|
||||
|
||||
foreach ($list as $k => &$v){
|
||||
$user = Db::name('users')->where(['id' => $v['userId']])->field('username,account')->find();
|
||||
if (!empty($user)){
|
||||
$v['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
|
||||
}else{
|
||||
$v['userName'] = '';
|
||||
}
|
||||
$v['answers'] = json_decode($v['answers'],true);
|
||||
}
|
||||
unset($v);
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(){
|
||||
|
||||
$type = $this->request->param('type', 0);
|
||||
$questions = $this->request->param('questions', '');
|
||||
$answers = $this->request->param('answers', []);
|
||||
$status = $this->request->param('status', 0);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($questions) || empty($answers)){
|
||||
return ResponseHelper::error('问题和答案不能为空');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$questionsModel = new Questions();
|
||||
$questionsModel->type = $type;
|
||||
$questionsModel->questions = $questions;
|
||||
$questionsModel->answers = !empty($answers) ? json_encode($answers,256) : json_encode([],256);
|
||||
$questionsModel->status = $status;
|
||||
$questionsModel->accountId = $accountId;
|
||||
$questionsModel->userId = $userId;
|
||||
$questionsModel->companyId = $companyId;
|
||||
$questionsModel->createTime = time();
|
||||
$questionsModel->updateTime = time();
|
||||
$questionsModel->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update(){
|
||||
|
||||
$id = $this->request->param('id', 0);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$type = $this->request->param('type', 0);
|
||||
$questions = $this->request->param('questions', '');
|
||||
$answers = $this->request->param('answers', []);
|
||||
$status = $this->request->param('status', 0);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (empty($questions) || empty($answers)){
|
||||
return ResponseHelper::error('问题和答案不能为空');
|
||||
}
|
||||
Db::startTrans();
|
||||
try {
|
||||
$questionsData = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
|
||||
$questionsData->type = $type;
|
||||
$questionsData->questions = $questions;
|
||||
$questionsData->answers = !empty($answers) ? json_encode($answers,256) : json_encode([],256);
|
||||
$questionsData->status = $status;
|
||||
$questionsData->accountId = $accountId;
|
||||
$questionsData->userId = $userId;
|
||||
$questionsData->companyId = $companyId;
|
||||
$questionsData->updateTime = time();
|
||||
$questionsData->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','更新成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('更新失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete(){
|
||||
|
||||
$id = $this->request->param('id', 0);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$questions = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
|
||||
|
||||
if (empty($questions)){
|
||||
return ResponseHelper::error('该问题不存在或者已删除');
|
||||
}
|
||||
$res = Questions::where(['id' => $id])->update(['isDel' => 1,'deleteTime' => time()]);
|
||||
|
||||
if (!empty($res)){
|
||||
return ResponseHelper::success('','已删除');
|
||||
}else{
|
||||
return ResponseHelper::error('删除失败');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 详情
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function detail(){
|
||||
|
||||
$id = $this->request->param('id', 0);
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$questions = Questions::where(['id' => $id,'userId' => $userId,'companyId' => $companyId,'isDel' => 0])->find();
|
||||
|
||||
if (empty($questions)){
|
||||
return ResponseHelper::error('该问题不存在或者已删除');
|
||||
}
|
||||
|
||||
$questions['answers'] = json_decode($questions['answers'],true);
|
||||
$user = Db::name('users')->where(['id' => $questions['userId']])->field('username,account')->find();
|
||||
if (!empty($user)){
|
||||
$questions['userName'] = !empty($user['username']) ? $user['username'] : $user['account'];
|
||||
}else{
|
||||
$questions['userName'] = '';
|
||||
}
|
||||
|
||||
unset(
|
||||
$questions['isDel'],
|
||||
$questions['deleteTime'],
|
||||
$questions['createTime'],
|
||||
$questions['updateTime']
|
||||
);
|
||||
|
||||
return ResponseHelper::success($questions,'获取成功');
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
371
application/chukebao/controller/ReplyController.php
Normal file
371
application/chukebao/controller/ReplyController.php
Normal file
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\Reply;
|
||||
use app\chukebao\model\ReplyGroup;
|
||||
use library\ResponseHelper;
|
||||
|
||||
class ReplyController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$replyType = $this->request->param('replyType', 0);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
try {
|
||||
// 构建分组查询条件
|
||||
$groupWhere = [
|
||||
['isDel','=',0]
|
||||
];
|
||||
switch ($replyType) {
|
||||
case 0:
|
||||
//公共快捷语
|
||||
$groupWhere[] = ['replyType', '=', 0];
|
||||
break;
|
||||
case 1:
|
||||
//私有快捷语
|
||||
$groupWhere[] = ['companyId', '=', $companyId];
|
||||
$groupWhere[] = ['userId', '=', $userId];
|
||||
$groupWhere[] = ['replyType', '=', 1];
|
||||
break;
|
||||
case 2:
|
||||
//公司快捷语
|
||||
$groupWhere[] = ['companyId', '=', $companyId];
|
||||
$groupWhere[] = ['replyType', '=', 2];
|
||||
break;
|
||||
default:
|
||||
$groupWhere[] = ['replyType', '=', 0];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$groupWhere[] = ['groupName','like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 获取所有分组
|
||||
$allGroups = ReplyGroup::where($groupWhere)
|
||||
->order('sortIndex asc,id DESC')
|
||||
->select();
|
||||
// 构建树形结构
|
||||
$result = $this->buildGroupTree($allGroups, $keyword);
|
||||
|
||||
return ResponseHelper::success($result, '获取成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增快捷语分组
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addGroup()
|
||||
{
|
||||
$groupName = $this->request->param('groupName', '');
|
||||
$parentId = (int)$this->request->param('parentId', 0);
|
||||
$replyType = (int)$this->request->param('replyType', 0); // 0公共 1私有 2公司
|
||||
$sortIndex = (string)$this->request->param('sortIndex', 50);
|
||||
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if ($groupName === '') {
|
||||
return ResponseHelper::error('分组名称不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
$data = [
|
||||
'groupName' => $groupName,
|
||||
'parentId' => $parentId,
|
||||
'replyType' => $replyType,
|
||||
'sortIndex' => $sortIndex,
|
||||
// 兼容现有程序中使用到的字段
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
];
|
||||
|
||||
/** @var ReplyGroup $group */
|
||||
$group = new ReplyGroup();
|
||||
$group->save($data);
|
||||
|
||||
return ResponseHelper::success($group->toArray(), '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增快捷语
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addReply()
|
||||
{
|
||||
$groupId = (int)$this->request->param('groupId', 0);
|
||||
$title = $this->request->param('title', '');
|
||||
$msgType = (int)$this->request->param('msgType', 1); // 1文本 3图片 43视频 49链接 等
|
||||
$content = $this->request->param('content', '');
|
||||
$sortIndex = (string)$this->request->param('sortIndex', 50);
|
||||
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
|
||||
if ($groupId <= 0) {
|
||||
return ResponseHelper::error('分组ID不合法');
|
||||
}
|
||||
if ($title === '') {
|
||||
return ResponseHelper::error('标题不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
$now = time();
|
||||
$data = [
|
||||
'tenantId' => $companyId,
|
||||
'groupId' => $groupId,
|
||||
'accountId' => $accountId,
|
||||
'title' => $title,
|
||||
'msgType' => $msgType,
|
||||
'content' => $content,
|
||||
'sortIndex' => $sortIndex,
|
||||
'createTime' => $now,
|
||||
'lastUpdateTime' => $now,
|
||||
'userId' => $userId,
|
||||
];
|
||||
|
||||
/** @var Reply $reply */
|
||||
$reply = new Reply();
|
||||
$reply->save($data);
|
||||
|
||||
return ResponseHelper::success($reply->toArray(), '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑快捷语分组
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateGroup()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('分组ID不合法');
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$groupName = $this->request->param('groupName', null);
|
||||
$parentId = $this->request->param('parentId', null);
|
||||
$replyType = $this->request->param('replyType', null);
|
||||
$sortIndex = $this->request->param('sortIndex', null);
|
||||
|
||||
if ($groupName !== null) $data['groupName'] = $groupName;
|
||||
if ($parentId !== null) $data['parentId'] = (int)$parentId;
|
||||
if ($replyType !== null) $data['replyType'] = (int)$replyType;
|
||||
if ($sortIndex !== null) $data['sortIndex'] = (string)$sortIndex;
|
||||
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('无可更新字段');
|
||||
}
|
||||
|
||||
try {
|
||||
$group = ReplyGroup::where(['id' => $id,'isDel' => 0])->find();
|
||||
if (empty($group)) {
|
||||
return ResponseHelper::error('分组不存在');
|
||||
}
|
||||
$group->save($data);
|
||||
return ResponseHelper::success($group->toArray(), '更新成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 假删除快捷语分组
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteGroup()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('分组ID不合法');
|
||||
}
|
||||
try {
|
||||
$group = ReplyGroup::where(['id' => $id,'isDel' => 0])->find();
|
||||
if (empty($group)) {
|
||||
return ResponseHelper::error('分组不存在');
|
||||
}
|
||||
$group->save(['isDel' => 1,'delTime' => time()]);
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑快捷语
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateReply()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('快捷语ID不合法');
|
||||
}
|
||||
|
||||
$data = [];
|
||||
$groupId = $this->request->param('groupId', null);
|
||||
$title = $this->request->param('title', null);
|
||||
$msgType = $this->request->param('msgType', null);
|
||||
$content = $this->request->param('content', null);
|
||||
$sortIndex = $this->request->param('sortIndex', null);
|
||||
|
||||
if ($groupId !== null) $data['groupId'] = (int)$groupId;
|
||||
if ($title !== null) $data['title'] = $title;
|
||||
if ($msgType !== null) $data['msgType'] = (int)$msgType;
|
||||
if ($content !== null) $data['content'] = $content;
|
||||
if ($sortIndex !== null) $data['sortIndex'] = (string)$sortIndex;
|
||||
if (!empty($data)) {
|
||||
$data['lastUpdateTime'] = time();
|
||||
}
|
||||
|
||||
if (empty($data)) {
|
||||
return ResponseHelper::error('无可更新字段');
|
||||
}
|
||||
|
||||
try {
|
||||
$reply = Reply::where(['id' => $id,'isDel' => 0])->find();
|
||||
if (empty($reply)) {
|
||||
return ResponseHelper::error('快捷语不存在');
|
||||
}
|
||||
$reply->save($data);
|
||||
return ResponseHelper::success($reply->toArray(), '更新成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 假删除快捷语
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteReply()
|
||||
{
|
||||
$id = (int)$this->request->param('id', 0);
|
||||
if ($id <= 0) {
|
||||
return ResponseHelper::error('快捷语ID不合法');
|
||||
}
|
||||
try {
|
||||
$reply = Reply::where(['id' => $id,'isDel' => 0])->find();
|
||||
if (empty($reply)) {
|
||||
return ResponseHelper::error('快捷语不存在');
|
||||
}
|
||||
$reply->save(['isDel' => 1, 'delTime' => time()]);
|
||||
return ResponseHelper::success([], '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分组树形结构
|
||||
* @param array $groups 所有分组数据
|
||||
* @param string $keyword 搜索关键词
|
||||
* @return array
|
||||
*/
|
||||
private function buildGroupTree($groups, $keyword = '')
|
||||
{
|
||||
$tree = [];
|
||||
$groupMap = [];
|
||||
|
||||
// 先构建分组映射
|
||||
foreach ($groups as $group) {
|
||||
$groupMap[$group->id] = $group->toArray();
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
foreach ($groups as $group) {
|
||||
$groupData = $this->buildGroupData($group, $keyword);
|
||||
|
||||
if ($group->parentId == null || $group->parentId == 0) {
|
||||
// 顶级分组
|
||||
$tree[] = $groupData;
|
||||
} else {
|
||||
// 子分组,需要找到父分组并添加到其children中
|
||||
$this->addToParentGroup($tree, $group->parentId, $groupData);
|
||||
}
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建单个分组数据
|
||||
* @param object $group 分组对象
|
||||
* @param string $keyword 搜索关键词
|
||||
* @return array
|
||||
*/
|
||||
private function buildGroupData($group, $keyword = '')
|
||||
{
|
||||
// 构建快捷回复查询条件
|
||||
$replyWhere[] =[
|
||||
['groupId' ,'=', $group->id],
|
||||
['isDel','=',0]
|
||||
];
|
||||
if (!empty($keyword)) {
|
||||
$replyWhere[] = ['title','like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 获取该分组下的快捷回复
|
||||
$replies = Reply::where($replyWhere)
|
||||
->order('sortIndex asc, id desc
|
||||
')
|
||||
->select();
|
||||
|
||||
return [
|
||||
'id' => $group->id,
|
||||
'groupName' => $group->groupName,
|
||||
'sortIndex' => $group->sortIndex,
|
||||
'parentId' => $group->parentId,
|
||||
'replyType' => $group->replyType,
|
||||
'replys' => $group->replys,
|
||||
'companyId' => $group->companyId,
|
||||
'userId' => $group->userId,
|
||||
'replies' => $replies->toArray(),
|
||||
'children' => [] // 子分组
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将子分组添加到父分组中
|
||||
* @param array $tree 树形结构
|
||||
* @param int $parentId 父分组ID
|
||||
* @param array $childGroup 子分组数据
|
||||
*/
|
||||
private function addToParentGroup(&$tree, $parentId, $childGroup)
|
||||
{
|
||||
foreach ($tree as &$group) {
|
||||
if ($group['id'] == $parentId) {
|
||||
$group['children'][] = $childGroup;
|
||||
return;
|
||||
}
|
||||
|
||||
// 递归查找子分组
|
||||
if (!empty($group['children'])) {
|
||||
$this->addToParentGroup($group['children'], $parentId, $childGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
143
application/chukebao/controller/ToDoController.php
Normal file
143
application/chukebao/controller/ToDoController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\ToDo;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class ToDoController extends BaseController
|
||||
{
|
||||
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$isRemind = $this->request->param('isRemind', '');
|
||||
$isProcess = $this->request->param('isProcess', '');
|
||||
$level = $this->request->param('level', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId','=',$companyId],
|
||||
['userId' ,'=', $userId]
|
||||
];
|
||||
|
||||
if ($isRemind != '') {
|
||||
$where[] = ['isRemind','=',$isRemind];
|
||||
}
|
||||
if ($level != '') {
|
||||
$where[] = ['level','=',$level];
|
||||
}
|
||||
if ($isProcess != '') {
|
||||
$where[] = ['isProcess','=',$isProcess];
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['title|description','like','%'.$keyword.'%'];
|
||||
}
|
||||
|
||||
$query = ToDo::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
|
||||
|
||||
|
||||
foreach ($list as &$item) {
|
||||
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendId']])->value('nickname');
|
||||
$item['nickname'] = !empty($nickname) ? $nickname : '-';
|
||||
$item['reminderTime'] = date('Y-m-d H:i:s',$item['reminderTime']);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create(){
|
||||
$level = $this->request->param('level', 0);
|
||||
$title = $this->request->param('title', '');
|
||||
$reminderTime = $this->request->param('reminderTime', '');
|
||||
$description = $this->request->param('description', '');
|
||||
$friendId = $this->request->param('friendId', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($title) || empty($reminderTime) || empty($description) || empty($friendId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$friend = Db::table('s2_wechat_friend')->where(['id' => $friendId])->find();
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$todo = new ToDo();
|
||||
$todo->level = $level;
|
||||
$todo->title = $title;
|
||||
$todo->friendId = $friendId;
|
||||
$todo->reminderTime = !empty($reminderTime) ? strtotime($reminderTime) : time();
|
||||
$todo->description = $description;
|
||||
$todo->userId = $userId;
|
||||
$todo->companyId = $companyId;
|
||||
$todo->updateTime = time();
|
||||
$todo->createTime = time();
|
||||
$todo->save();
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:'.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理代办事项
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function process(){
|
||||
$ids = $this->request->param('ids','');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($ids)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
$ids = explode(',',$ids);
|
||||
|
||||
if (!is_array($ids)){
|
||||
return ResponseHelper::error('格式错误');
|
||||
}
|
||||
|
||||
$todoIds = ToDo::where(['userId' => $userId,'companyId' => $companyId,'isProcess' => 0])->whereIn('id',$ids)->column('id');
|
||||
if (empty($todoIds)){
|
||||
return ResponseHelper::error('代办事项不存在');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
ToDo::whereIn('id',$todoIds)->update(['isProcess' => 1,'isRemind' => 1,'updateTime' => time()]);
|
||||
Db::commit();
|
||||
return ResponseHelper::success(' ','已处理');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('处理失败:'.$e->getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
192
application/chukebao/controller/TokensRecordController.php
Normal file
192
application/chukebao/controller/TokensRecordController.php
Normal file
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\TokensCompany;
|
||||
use app\chukebao\model\TokensRecord;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class TokensRecordController extends BaseController
|
||||
{
|
||||
|
||||
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$type = $this->request->param('type', '');
|
||||
$form = $this->request->param('form', '');
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId','=',$companyId],
|
||||
['userId' ,'=', $userId]
|
||||
];
|
||||
|
||||
if ($type != '') {
|
||||
$where[] = ['type','=',$type];
|
||||
}
|
||||
if ($form != '') {
|
||||
$where[] = ['form','=',$form];
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
if (!empty($startTime)) {
|
||||
// 支持时间戳或日期字符串格式
|
||||
$startTimestamp = is_numeric($startTime) ? intval($startTime) : strtotime($startTime);
|
||||
if ($startTimestamp !== false) {
|
||||
$where[] = ['createTime', '>=', $startTimestamp];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($endTime)) {
|
||||
// 支持时间戳或日期字符串格式
|
||||
$endTimestamp = is_numeric($endTime) ? intval($endTime) : strtotime($endTime);
|
||||
if ($endTimestamp !== false) {
|
||||
// 如果是日期字符串,自动设置为当天的23:59:59
|
||||
if (!is_numeric($endTime)) {
|
||||
$endTimestamp = strtotime(date('Y-m-d 23:59:59', $endTimestamp));
|
||||
}
|
||||
$where[] = ['createTime', '<=', $endTimestamp];
|
||||
}
|
||||
}
|
||||
|
||||
$query = TokensRecord::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->where($where)->page($page,$limit)->order('id desc')->select();
|
||||
|
||||
|
||||
foreach ($list as &$item) {
|
||||
if (in_array($item['type'],[1])){
|
||||
$nickname = Db::table('s2_wechat_friend')->where(['id' => $item['friendIdOrGroupId']])->value('nickname');
|
||||
$item['nickname'] = !empty($nickname) ? $nickname : '-';
|
||||
}
|
||||
if (in_array($item['type'],[2,3])){
|
||||
$nickname = Db::table('s2_wechat_chatroom')->where(['id' => $item['friendIdOrGroupId']])->value('nickname');
|
||||
$item['nickname'] = !empty($nickname) ? $nickname : '-';
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
|
||||
public function consumeTokens($data = [])
|
||||
{
|
||||
if (empty($data)){
|
||||
return ResponseHelper::error('数据缺失');
|
||||
}
|
||||
|
||||
$tokens = isset($data['tokens']) ? intval($data['tokens']) : 0;
|
||||
$type = isset($data['type']) ? intval($data['type']) : 0;
|
||||
$form = isset($data['form']) ? intval($data['form']) : 0;
|
||||
$wechatAccountId = isset($data['wechatAccountId']) ? intval($data['wechatAccountId']) : 0;
|
||||
$friendIdOrGroupId = isset($data['friendIdOrGroupId']) ? intval($data['friendIdOrGroupId']) : 0;
|
||||
$remarks = isset($data['remarks']) ? $data['remarks'] : '';
|
||||
$companyId = isset($data['companyId']) ? intval($data['companyId']) : $this->getUserInfo('companyId');
|
||||
$userId = isset($data['userId']) ? intval($data['userId']) : $this->getUserInfo('id');
|
||||
|
||||
// 验证必要参数
|
||||
if ($tokens <= 0) {
|
||||
return ResponseHelper::error('tokens数量必须大于0');
|
||||
}
|
||||
|
||||
if (!in_array($type, [0, 1])) {
|
||||
return ResponseHelper::error('类型参数错误,0为减少,1为增加');
|
||||
}
|
||||
|
||||
|
||||
// 重试机制,最多重试3次
|
||||
$maxRetries = 3;
|
||||
$retryCount = 0;
|
||||
while ($retryCount < $maxRetries) {
|
||||
try {
|
||||
return $this->doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks);
|
||||
} catch (\Exception $e) {
|
||||
$retryCount++;
|
||||
if ($retryCount >= $maxRetries) {
|
||||
return ResponseHelper::error('操作失败,请稍后重试:' . $e->getMessage());
|
||||
}
|
||||
// 短暂延迟后重试
|
||||
usleep(100000); // 100ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行tokens消费的核心方法
|
||||
*/
|
||||
private function doConsumeTokens($userId, $companyId, $tokens, $type, $form, $wechatAccountId, $friendIdOrGroupId, $remarks)
|
||||
{
|
||||
// 开启数据库事务
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 使用悲观锁获取用户当前tokens余额,确保并发安全
|
||||
$userInfo = TokensCompany::where(['companyId'=> $companyId,'userId' => $userId])->lock(true)->find();
|
||||
if (!$userInfo) {
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
|
||||
$currentTokens = intval($userInfo['tokens']);
|
||||
|
||||
// 计算新的余额
|
||||
$newBalance = $type == 1 ? ($currentTokens + $tokens) : ($currentTokens - $tokens);
|
||||
|
||||
// 使用原子更新操作,基于当前值进行更新,防止并发覆盖
|
||||
$updateResult = TokensCompany::where('companyId', $companyId)
|
||||
->where('companyId', $companyId)
|
||||
->update([
|
||||
'tokens' => $newBalance,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
if (!$updateResult) {
|
||||
// 如果更新失败,说明tokens值已被其他事务修改,需要重新获取
|
||||
throw new \Exception('tokens余额已被其他操作修改,请重试');
|
||||
}
|
||||
|
||||
// 记录tokens变动
|
||||
$recordData = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $friendIdOrGroupId,
|
||||
'form' => $form,
|
||||
'type' => $type,
|
||||
'tokens' => $tokens,
|
||||
'balanceTokens' => $newBalance,
|
||||
'remarks' => $remarks,
|
||||
'createTime' => time()
|
||||
];
|
||||
|
||||
$recordId = Db::name('tokens_record')->insertGetId($recordData);
|
||||
|
||||
if (!$recordId) {
|
||||
throw new \Exception('记录tokens变动失败');
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success([
|
||||
'recordId' => $recordId,
|
||||
'oldBalance' => $currentTokens,
|
||||
'newBalance' => $newBalance,
|
||||
'changeAmount' => $type == 1 ? $tokens : -$tokens
|
||||
], 'tokens变动记录成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e; // 重新抛出异常,让重试机制处理
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
269
application/chukebao/controller/WechatChatroomController.php
Normal file
269
application/chukebao/controller/WechatChatroomController.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\ai\controller\DouBaoAI;
|
||||
use app\chukebao\controller\TokensRecordController as tokensRecord;
|
||||
use app\chukebao\model\TokensCompany;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class WechatChatroomController extends BaseController
|
||||
{
|
||||
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$groupIds = $this->request->param('groupId', '');
|
||||
$ownerWechatId = $this->request->param('ownerWechatId', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
$query = Db::table('s2_wechat_chatroom')
|
||||
->where(['accountId' => $accountId,'isDeleted' => 0]);
|
||||
|
||||
// 关键字搜索:群昵称、微信号(这里使用chatroomId作为群标识)
|
||||
if ($keyword !== '' && $keyword !== null) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('nickname', $like)
|
||||
->whereOr('conRemark', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
// 分组筛选:groupIds(单个分组ID)
|
||||
if ($groupIds !== '' && $groupIds !== null) {
|
||||
$query->where('groupIds', $groupIds);
|
||||
}
|
||||
|
||||
if (!empty($ownerWechatId)) {
|
||||
$query->where('ownerWechatId', $ownerWechatId);
|
||||
}
|
||||
|
||||
$query->order('id desc');
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
|
||||
|
||||
// 提取所有聊天室ID,用于批量查询
|
||||
$chatroomIds = array_column($list, 'id');
|
||||
|
||||
|
||||
// 一次性查询所有聊天室的未读消息数量
|
||||
$unreadCounts = [];
|
||||
if (!empty($chatroomIds)) {
|
||||
$unreadResults = Db::table('s2_wechat_message')
|
||||
->field('wechatChatroomId, COUNT(*) as count')
|
||||
->where('wechatChatroomId', 'in', $chatroomIds)
|
||||
->where('isRead', 0)
|
||||
->group('wechatChatroomId')
|
||||
->select();
|
||||
|
||||
foreach ($unreadResults as $result) {
|
||||
$unreadCounts[$result['wechatChatroomId']] = $result['count'];
|
||||
}
|
||||
}
|
||||
// 一次性查询所有聊天室的最新消息
|
||||
$latestMessages = [];
|
||||
if (!empty($chatroomIds)) {
|
||||
// 使用子查询获取每个聊天室的最新消息ID
|
||||
$subQuery = Db::table('s2_wechat_message')
|
||||
->field('MAX(id) as max_id, wechatChatroomId')
|
||||
->where('wechatChatroomId', 'in', $chatroomIds)
|
||||
->group('wechatChatroomId')
|
||||
->buildSql();
|
||||
|
||||
// 查询最新消息的详细信息
|
||||
$messageResults = Db::table('s2_wechat_message')
|
||||
->alias('m')
|
||||
->join([$subQuery => 'sub'], 'm.id = sub.max_id')
|
||||
->field('m.*, sub.wechatChatroomId')
|
||||
->select();
|
||||
|
||||
foreach ($messageResults as $message) {
|
||||
$latestMessages[$message['wechatChatroomId']] = $message;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理每个聊天室的数据
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
|
||||
|
||||
$config = [
|
||||
'unreadCount' => isset($unreadCounts[$v['id']]) ? $unreadCounts[$v['id']] : 0,
|
||||
'chat' => isset($latestMessages[$v['id']]),
|
||||
'msgTime' => isset($latestMessages[$v['id']]) ? $latestMessages[$v['id']]['wechatTime'] : 0
|
||||
];
|
||||
$v['config'] = $config;
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
public function getDetail(){
|
||||
$id = input('id', 0);
|
||||
|
||||
if (!$id) {
|
||||
return ResponseHelper::error('聊天室ID不能为空');
|
||||
}
|
||||
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
$detail = Db::table('s2_wechat_chatroom')
|
||||
->where(['accountId' => $accountId, 'id' => $id, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
if (!$detail) {
|
||||
return ResponseHelper::error('聊天室不存在或无权限访问');
|
||||
}
|
||||
|
||||
// 处理时间格式
|
||||
$detail['createTime'] = !empty($detail['createTime']) ? date('Y-m-d H:i:s', $detail['createTime']) : '';
|
||||
$detail['updateTime'] = !empty($detail['updateTime']) ? date('Y-m-d H:i:s', $detail['updateTime']) : '';
|
||||
|
||||
// 查询未读消息数量
|
||||
$unreadCount = Db::table('s2_wechat_message')
|
||||
->where('wechatChatroomId', $id)
|
||||
->where('isRead', 0)
|
||||
->count();
|
||||
|
||||
// 查询最新消息
|
||||
$latestMessage = Db::table('s2_wechat_message')
|
||||
->where('wechatChatroomId', $id)
|
||||
->order('id desc')
|
||||
->find();
|
||||
|
||||
$config = [
|
||||
'unreadCount' => $unreadCount,
|
||||
'chat' => !empty($latestMessage),
|
||||
'msgTime' => isset($latestMessage['wechatTime']) ? $latestMessage['wechatTime'] : 0
|
||||
];
|
||||
$detail['config'] = $config;
|
||||
|
||||
return ResponseHelper::success($detail);
|
||||
}
|
||||
|
||||
public function getMembers()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$groupId = $this->request->param('groupId', '');
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
// 验证群组ID必填
|
||||
if (empty($groupId)) {
|
||||
return ResponseHelper::error('群组ID不能为空');
|
||||
}
|
||||
|
||||
// 验证群组是否属于当前账号
|
||||
$chatroom = Db::table('s2_wechat_chatroom')
|
||||
->where(['id' => $groupId, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
if (!$chatroom) {
|
||||
return ResponseHelper::error('群组不存在或无权限访问');
|
||||
}
|
||||
|
||||
// 获取群组的chatroomId(微信群聊ID)
|
||||
$chatroomId = $chatroom['chatroomId'] ?? $chatroom['id'];
|
||||
|
||||
// 如果chatroomId为空,使用id作为chatroomId
|
||||
if (empty($chatroomId)) {
|
||||
$chatroomId = $chatroom['id'];
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
$query = Db::table('s2_wechat_chatroom_member')
|
||||
->where('chatroomId', $chatroomId);
|
||||
|
||||
// 关键字搜索:昵称、备注、别名
|
||||
if ($keyword !== '' && $keyword !== null) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('nickname', $like)
|
||||
->whereOr('conRemark', 'like', $like)
|
||||
->whereOr('alias', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
$query->order('id desc');
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
// 处理时间格式
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
public function aiAnnouncement()
|
||||
{
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$groupId = $this->request->param('groupId', '');
|
||||
$content = $this->request->param('content', '');
|
||||
|
||||
if (empty($groupId) || empty($content)|| empty($wechatAccountId)){
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$tokens = TokensCompany::where(['companyId' => $companyId])->value('tokens');
|
||||
if (empty($tokens) || $tokens <= 0){
|
||||
return ResponseHelper::error('用户Tokens余额不足');
|
||||
}
|
||||
|
||||
$params = [
|
||||
'model' => 'doubao-1-5-pro-32k-250115',
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => '你现在是存客宝的AI助理,你精通中国大陆的法律'],
|
||||
['role' => 'user', 'content' => $content],
|
||||
],
|
||||
];
|
||||
|
||||
//AI处理
|
||||
$ai = new DouBaoAI();
|
||||
$res = $ai->text($params);
|
||||
$res = json_decode($res,true);
|
||||
|
||||
|
||||
if ($res['code'] == 200) {
|
||||
//扣除Tokens
|
||||
$tokensRecord = new tokensRecord();
|
||||
$nickname = Db::table('s2_wechat_chatroom')->where(['id' => $groupId])->value('nickname');
|
||||
$remarks = !empty($nickname) ? '生成【'.$nickname.'】群公告' : '生成群公告';
|
||||
$data = [
|
||||
'tokens' => $res['data']['token'],
|
||||
'type' => 0,
|
||||
'form' => 14,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $groupId,
|
||||
'remarks' => $remarks,
|
||||
];
|
||||
$tokensRecord->consumeTokens($data);
|
||||
return ResponseHelper::success($res['data']['content']);
|
||||
}else{
|
||||
return ResponseHelper::error($res['msg']);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
302
application/chukebao/controller/WechatFriendController.php
Normal file
302
application/chukebao/controller/WechatFriendController.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
class WechatFriendController extends BaseController
|
||||
{
|
||||
|
||||
public function getList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$groupIds = $this->request->param('groupId', '');
|
||||
$ownerWechatId = $this->request->param('ownerWechatId', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
$query = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0]);
|
||||
|
||||
// 关键字搜索:昵称、备注、微信号
|
||||
if ($keyword !== '' && $keyword !== null) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('nickname', $like)
|
||||
->whereOr('conRemark', 'like', $like)
|
||||
->whereOr('alias', 'like', $like)
|
||||
->whereOr('wechatId', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
// 分组筛选:groupIds(单个分组ID)
|
||||
if ($groupIds !== '' && $groupIds !== null) {
|
||||
$query->where('groupIds', $groupIds);
|
||||
}
|
||||
|
||||
if (!empty($ownerWechatId)) {
|
||||
$query->where('ownerWechatId', $ownerWechatId);
|
||||
}
|
||||
|
||||
$query->order('id desc');
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
// 提取所有好友ID
|
||||
$friendIds = array_column($list, 'id');
|
||||
|
||||
$aiTypeData = [];
|
||||
if (!empty($friendIds)) {
|
||||
$aiTypeData = FriendSettings::where('friendId', 'in', $friendIds)->column('friendId,type');
|
||||
}
|
||||
|
||||
|
||||
// 处理每个好友的数据
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['labels'] = json_decode($v['labels'], true);
|
||||
$v['siteLabels'] = json_decode($v['siteLabels'], true);
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
$v['updateTime'] = !empty($v['updateTime']) ? date('Y-m-d H:i:s', $v['updateTime']) : '';
|
||||
$v['passTime'] = !empty($v['passTime']) ? date('Y-m-d H:i:s', $v['passTime']) : '';
|
||||
$v['aiType'] = isset($aiTypeData[$v['id']]) ? $aiTypeData[$v['id']] : 0;
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个好友详情
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getDetail()
|
||||
{
|
||||
$friendId = $this->request->param('id');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($friendId)) {
|
||||
return ResponseHelper::error('好友ID不能为空');
|
||||
}
|
||||
|
||||
// 查询好友详情
|
||||
$friend = Db::table('s2_wechat_friend')
|
||||
->where(['id' => $friendId, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
|
||||
// 处理好友数据
|
||||
$friend['labels'] = json_decode($friend['labels'], true);
|
||||
$friend['siteLabels'] = json_decode($friend['siteLabels'], true);
|
||||
$friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : '';
|
||||
$friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : '';
|
||||
$friend['passTime'] = !empty($friend['passTime']) ? date('Y-m-d H:i:s', $friend['passTime']) : '';
|
||||
|
||||
// 获取AI类型设置
|
||||
$aiTypeSetting = FriendSettings::where('friendId', $friendId)->find();
|
||||
$friend['aiType'] = $aiTypeSetting ? $aiTypeSetting['type'] : 0;
|
||||
|
||||
return ResponseHelper::success(['detail' => $friend]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新好友资料(公司、姓名、手机号等字段可单独更新)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateFriendInfo()
|
||||
{
|
||||
$friendId = $this->request->param('id');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($friendId)) {
|
||||
return ResponseHelper::error('好友ID不能为空');
|
||||
}
|
||||
|
||||
$friend = Db::table('s2_wechat_friend')
|
||||
->where(['id' => $friendId, 'accountId' => $accountId, 'isDeleted' => 0])
|
||||
->find();
|
||||
|
||||
if (empty($friend)) {
|
||||
return ResponseHelper::error('好友不存在或无权限操作');
|
||||
}
|
||||
|
||||
$requestData = $this->request->param();
|
||||
$updatableColumns = [
|
||||
'phone',
|
||||
'conRemark',
|
||||
];
|
||||
$columnUpdates = [];
|
||||
|
||||
foreach ($updatableColumns as $field) {
|
||||
if (array_key_exists($field, $requestData)) {
|
||||
$columnUpdates[$field] = $requestData[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$extendFieldsData = [];
|
||||
if (!empty($friend['extendFields'])) {
|
||||
$decodedExtend = json_decode($friend['extendFields'], true);
|
||||
$extendFieldsData = is_array($decodedExtend) ? $decodedExtend : [];
|
||||
}
|
||||
|
||||
$extendFieldKeys = [
|
||||
'company',
|
||||
'name',
|
||||
'position',
|
||||
'email',
|
||||
'address',
|
||||
'wechat',
|
||||
'qq',
|
||||
'remark'
|
||||
];
|
||||
$extendFieldsUpdated = false;
|
||||
|
||||
foreach ($extendFieldKeys as $key) {
|
||||
if (array_key_exists($key, $requestData)) {
|
||||
$extendFieldsData[$key] = $requestData[$key];
|
||||
$extendFieldsUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($extendFieldsUpdated) {
|
||||
$columnUpdates['extendFields'] = json_encode($extendFieldsData, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
if (empty($columnUpdates)) {
|
||||
return ResponseHelper::error('没有可更新的字段');
|
||||
}
|
||||
|
||||
$columnUpdates['updateTime'] = time();
|
||||
|
||||
try {
|
||||
Db::table('s2_wechat_friend')->where('id', $friendId)->update($columnUpdates);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['id' => $friendId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取添加好友任务记录列表(全新功能)
|
||||
* 返回当前账号的所有添加好友任务记录,无论是否通过都展示
|
||||
* 包含:添加者头像、昵称、微信号、添加状态、添加时间、通过时间等信息
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getAddTaskList()
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$status = $this->request->param('status', ''); // 可选:筛选状态 0执行中,1执行成功,2执行失败
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
// 直接使用operatorAccountId查询添加好友任务记录
|
||||
$query = Db::table('s2_friend_task')
|
||||
->where('operatorAccountId', $accountId)
|
||||
->order('createTime desc');
|
||||
|
||||
// 如果指定了状态筛选
|
||||
if ($status !== '' && $status !== null) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$tasks = $query->page($page, $limit)->select();
|
||||
|
||||
|
||||
// 处理任务数据
|
||||
$list = [];
|
||||
foreach ($tasks as $task) {
|
||||
// 提取所有任务的phone、wechatId,用于查询好友信息(获取通过时间)
|
||||
$friendInfo = Db::table('s2_wechat_friend')
|
||||
->where(['isDeleted' => 0, 'ownerWechatId' => $task['wechatId']])
|
||||
->where(function ($query) use ($task) {
|
||||
$query->whereLike('phone', '%'.$task['phone'].'%')->whereOr('alias', $task['phone'])->whereOr('wechatId', $task['phone']);
|
||||
})->field('phone,wechatId,alias,passTime,nickname')->find();
|
||||
|
||||
|
||||
|
||||
$item = [
|
||||
'taskId' => $task['id'] ?? 0,
|
||||
'phone' => $task['phone'] ?? '',
|
||||
'wechatId' => $task['wechatId'] ?? '',
|
||||
'alias' => $task['alias'] ?? '',
|
||||
// 添加者信息
|
||||
'adder' => [
|
||||
'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像
|
||||
'nickname' => $task['wechatNickname'] ?? '', // 添加者昵称
|
||||
'username' => $task['accountUsername'] ?? '', // 添加者微信号
|
||||
'accountNickname' => $task['accountNickname'] ?? '', // 账号昵称
|
||||
'accountRealName' => $task['accountRealName'] ?? '', // 账号真实姓名
|
||||
],
|
||||
// 添加状态
|
||||
'status' => [
|
||||
'code' => $task['status'] ?? 0, // 状态码:0执行中,1执行成功,2执行失败
|
||||
'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本
|
||||
'extra' => ''
|
||||
],
|
||||
// 时间信息
|
||||
'time' => [
|
||||
'addTime' => !empty($task['createTime']) ? date('Y-m-d H:i:s', $task['createTime']) : '', // 添加时间
|
||||
'addTimeStamp' => $task['createTime'] ?? 0, // 添加时间戳
|
||||
'updateTime' => !empty($task['updateTime']) ? date('Y-m-d H:i:s', $task['updateTime']) : '', // 更新时间
|
||||
'updateTimeStamp' => $task['updateTime'] ?? 0, // 更新时间戳
|
||||
'passTime' => !empty($friendInfo['passTime']) ? date('Y-m-d H:i:s', $friendInfo['passTime']) : '', // 通过时间
|
||||
'passTimeStamp' => $friendInfo['passTime'] ?? 0, // 通过时间戳
|
||||
],
|
||||
// 好友信息(如果已通过)
|
||||
'friend' => [
|
||||
'nickname' => $friendInfo['nickname'] ?? '', // 好友昵称
|
||||
'isPassed' => !empty($friendInfo['passTime']), // 是否已通过
|
||||
],
|
||||
// 其他信息
|
||||
'other' => [
|
||||
'msgContent' => $task['msgContent'] ?? '', // 验证消息
|
||||
'remark' => $task['remark'] ?? '', // 备注
|
||||
'from' => $task['from'] ?? '', // 来源
|
||||
'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签
|
||||
]
|
||||
];
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['list' => $list, 'total' => $total]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取任务状态文本
|
||||
* @param int $status 状态码
|
||||
* @return string 状态文本
|
||||
*/
|
||||
private function getTaskStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '执行中',
|
||||
1 => '执行成功',
|
||||
2 => '执行失败',
|
||||
];
|
||||
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态';
|
||||
}
|
||||
}
|
||||
273
application/chukebao/controller/WechatGroupController.php
Normal file
273
application/chukebao/controller/WechatGroupController.php
Normal file
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\chukebao\model\ChatGroups;
|
||||
|
||||
class WechatGroupController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取分组列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
// 公司维度分组,不强制校验 userId
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = ChatGroups::where([
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])
|
||||
->order('groupType desc,sort desc,id desc');
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->select();
|
||||
|
||||
// 处理每个分组的数据
|
||||
$list = is_array($list) ? $list : $list->toArray();
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
}
|
||||
unset($v);
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分组
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$groupName = $this->request->param('groupName', '');
|
||||
$groupMemo = $this->request->param('groupMemo', '');
|
||||
$groupType = $this->request->param('groupType', 1);
|
||||
$sort = $this->request->param('sort', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 只校验公司维度
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($groupName)) {
|
||||
return ResponseHelper::error('分组名称不能为空');
|
||||
}
|
||||
|
||||
// 验证分组类型
|
||||
if (!in_array($groupType, [1, 2])) {
|
||||
return ResponseHelper::error('无效的分组类型');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$chatGroup = new ChatGroups();
|
||||
$chatGroup->groupName = $groupName;
|
||||
$chatGroup->groupMemo = $groupMemo;
|
||||
$chatGroup->groupType = $groupType;
|
||||
$chatGroup->sort = $sort;
|
||||
$chatGroup->userId = $this->getUserInfo('id');
|
||||
$chatGroup->companyId = $companyId;
|
||||
$chatGroup->createTime = time();
|
||||
$chatGroup->isDel = 0;
|
||||
$chatGroup->save();
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success(['id' => $chatGroup->id], '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分组
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$id = $this->request->param('id', 0);
|
||||
$groupName = $this->request->param('groupName', '');
|
||||
$groupMemo = $this->request->param('groupMemo', '');
|
||||
$groupType = $this->request->param('groupType', 1);
|
||||
$sort = $this->request->param('sort', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (empty($groupName)) {
|
||||
return ResponseHelper::error('分组名称不能为空');
|
||||
}
|
||||
|
||||
// 验证分组类型
|
||||
if (!in_array($groupType, [1, 2])) {
|
||||
return ResponseHelper::error('无效的分组类型');
|
||||
}
|
||||
|
||||
// 检查分组是否存在
|
||||
$chatGroup = ChatGroups::where([
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($chatGroup)) {
|
||||
return ResponseHelper::error('该分组不存在或已删除');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$chatGroup->groupName = $groupName;
|
||||
$chatGroup->groupMemo = $groupMemo;
|
||||
$chatGroup->groupType = $groupType;
|
||||
$chatGroup->sort = $sort;
|
||||
$chatGroup->save();
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '更新成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分组(假删除)
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$id = $this->request->param('id', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 检查分组是否存在
|
||||
$chatGroup = ChatGroups::where([
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($chatGroup)) {
|
||||
return ResponseHelper::error('该分组不存在或已删除');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 1. 假删除当前分组
|
||||
$chatGroup->isDel = 1;
|
||||
$chatGroup->deleteTime = time();
|
||||
$chatGroup->save();
|
||||
|
||||
// 2. 重置该分组下所有好友的分组ID(s2_wechat_friend.groupIds -> 0)
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('groupIds', $id)
|
||||
->update(['groupIds' => 0]);
|
||||
|
||||
// 3. 重置该分组下所有微信群的分组ID(s2_wechat_chatroom.groupIds -> 0)
|
||||
Db::table('s2_wechat_chatroom')
|
||||
->where('groupIds', $id)
|
||||
->update(['groupIds' => 0]);
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动分组(将好友或群移动到指定分组)
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
// type: friend 好友, chatroom 群
|
||||
$type = $this->request->param('type', 'friend');
|
||||
$targetId = (int)$this->request->param('groupId', 0);
|
||||
// 仅支持单个ID移动
|
||||
$idParam = $this->request->param('id', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($targetId)) {
|
||||
return ResponseHelper::error('目标分组ID不能为空');
|
||||
}
|
||||
|
||||
// 仅允许单个 ID,禁止批量
|
||||
$moveId = (int)$idParam;
|
||||
if (empty($moveId)) {
|
||||
return ResponseHelper::error('需要移动的ID不能为空');
|
||||
}
|
||||
|
||||
// 校验目标分组是否存在且属于当前公司
|
||||
$targetGroup = ChatGroups::where([
|
||||
'id' => $targetId,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($targetGroup)) {
|
||||
return ResponseHelper::error('目标分组不存在或已删除');
|
||||
}
|
||||
|
||||
// 校验分组类型与移动对象类型是否匹配
|
||||
// groupType: 1=好友分组, 2=群分组
|
||||
if ($type === 'friend' && (int)$targetGroup->groupType !== 1) {
|
||||
return ResponseHelper::error('目标分组类型错误(需要好友分组)');
|
||||
}
|
||||
if ($type === 'chatroom' && (int)$targetGroup->groupType !== 2) {
|
||||
return ResponseHelper::error('目标分组类型错误(需要群分组)');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
if ($type === 'friend') {
|
||||
// 移动单个好友到指定分组:更新 s2_wechat_friend.groupIds
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('id', $moveId)
|
||||
->update(['groupIds' => $targetId]);
|
||||
} elseif ($type === 'chatroom') {
|
||||
// 移动单个群到指定分组:更新 s2_wechat_chatroom.groupIds
|
||||
Db::table('s2_wechat_chatroom')
|
||||
->where('id', $moveId)
|
||||
->update(['groupIds' => $targetId]);
|
||||
} else {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('无效的类型参数');
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '移动成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('移动失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
52
application/chukebao/model/AiKnowledgeBase.php
Normal file
52
application/chukebao/model/AiKnowledgeBase.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* AI知识库模型
|
||||
*/
|
||||
class AiKnowledgeBase extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 设置JSON字段
|
||||
protected $json = ['label'];
|
||||
|
||||
// 设置JSON字段自动转换为数组
|
||||
protected $jsonAssoc = true;
|
||||
|
||||
/**
|
||||
* 关联知识库类型
|
||||
*/
|
||||
public function type()
|
||||
{
|
||||
return $this->belongsTo(AiKnowledgeBaseType::class, 'typeId', 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效的知识库列表(未删除)
|
||||
*/
|
||||
public static function getValidList($companyId, $typeId = null)
|
||||
{
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
if ($typeId !== null) {
|
||||
$where[] = ['typeId', '=', $typeId];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->with(['type'])
|
||||
->order('createTime', 'desc')
|
||||
->select();
|
||||
}
|
||||
}
|
||||
|
||||
57
application/chukebao/model/AiKnowledgeBaseType.php
Normal file
57
application/chukebao/model/AiKnowledgeBaseType.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* AI知识库类型模型
|
||||
*/
|
||||
class AiKnowledgeBaseType extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'ai_knowledge_base_type';
|
||||
|
||||
// 设置主键
|
||||
protected $pk = 'id';
|
||||
|
||||
// 设置JSON字段
|
||||
protected $json = ['label'];
|
||||
|
||||
// 设置JSON字段自动转换为数组
|
||||
protected $jsonAssoc = true;
|
||||
|
||||
// 类型常量
|
||||
const TYPE_SYSTEM = 0; // 系统类型
|
||||
const TYPE_USER = 1; // 用户创建类型
|
||||
|
||||
/**
|
||||
* 获取有效的类型列表(未删除)
|
||||
*/
|
||||
public static function getValidList($companyId, $includeSystem = true)
|
||||
{
|
||||
$where = [
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
|
||||
if ($includeSystem) {
|
||||
$where[] = ['type|companyId', 'in', [0, $companyId]];
|
||||
} else {
|
||||
$where[] = ['companyId', '=', $companyId];
|
||||
$where[] = ['type', '=', self::TYPE_USER];
|
||||
}
|
||||
|
||||
return self::where($where)
|
||||
->order('createTime', 'desc')
|
||||
->select();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为系统类型
|
||||
*/
|
||||
public function isSystemType()
|
||||
{
|
||||
return $this->type == self::TYPE_SYSTEM;
|
||||
}
|
||||
}
|
||||
|
||||
17
application/chukebao/model/AiPush.php
Normal file
17
application/chukebao/model/AiPush.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AiPush extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_ai_push';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
}
|
||||
|
||||
16
application/chukebao/model/AiPushRecord.php
Normal file
16
application/chukebao/model/AiPushRecord.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class AiPushRecord extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_ai_push_record';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
}
|
||||
|
||||
17
application/chukebao/model/AiSettings.php
Normal file
17
application/chukebao/model/AiSettings.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class AiSettings extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'ai_settings';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
17
application/chukebao/model/AutoGreetings.php
Normal file
17
application/chukebao/model/AutoGreetings.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class AutoGreetings extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_auto_greetings';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
16
application/chukebao/model/ChatGroups.php
Normal file
16
application/chukebao/model/ChatGroups.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ChatGroups extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'chat_groups';
|
||||
|
||||
// 不开启自动时间戳,手动维护 createTime / deleteTime
|
||||
protected $autoWriteTimestamp = false;
|
||||
}
|
||||
|
||||
|
||||
17
application/chukebao/model/FollowUp.php
Normal file
17
application/chukebao/model/FollowUp.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class FollowUp extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_follow_up';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
17
application/chukebao/model/FriendSettings.php
Normal file
17
application/chukebao/model/FriendSettings.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class FriendSettings extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_friend_settings';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
17
application/chukebao/model/Keywords.php
Normal file
17
application/chukebao/model/Keywords.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class Keywords extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_keywords';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
11
application/chukebao/model/KfMoments.php
Normal file
11
application/chukebao/model/KfMoments.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class KfMoments extends Model
|
||||
{
|
||||
protected $table = 'ck_kf_moments';
|
||||
|
||||
}
|
||||
17
application/chukebao/model/Material.php
Normal file
17
application/chukebao/model/Material.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class Material extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_material';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
16
application/chukebao/model/NoticeModel.php
Normal file
16
application/chukebao/model/NoticeModel.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class NoticeModel extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_notice';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
}
|
||||
17
application/chukebao/model/Questions.php
Normal file
17
application/chukebao/model/Questions.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class Questions extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_questions';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
17
application/chukebao/model/Reply.php
Normal file
17
application/chukebao/model/Reply.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class Reply extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_reply';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'lastUpdateTime';
|
||||
|
||||
|
||||
}
|
||||
10
application/chukebao/model/ReplyGroup.php
Normal file
10
application/chukebao/model/ReplyGroup.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class ReplyGroup extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_reply_group';
|
||||
}
|
||||
17
application/chukebao/model/SensitiveWord.php
Normal file
17
application/chukebao/model/SensitiveWord.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class SensitiveWord extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_sensitive_word';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
17
application/chukebao/model/ToDo.php
Normal file
17
application/chukebao/model/ToDo.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class ToDo extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'kf_to_do';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
protected $updateTime = 'updateTime';
|
||||
|
||||
|
||||
}
|
||||
16
application/chukebao/model/TokensCompany.php
Normal file
16
application/chukebao/model/TokensCompany.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class TokensCompany extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'tokens_company';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
|
||||
|
||||
}
|
||||
16
application/chukebao/model/TokensRecord.php
Normal file
16
application/chukebao/model/TokensRecord.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
class TokensRecord extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'tokens_record';
|
||||
|
||||
// 自动写入时间戳
|
||||
protected $autoWriteTimestamp = true;
|
||||
protected $createTime = 'createTime';
|
||||
|
||||
|
||||
}
|
||||
49
application/command.php
Normal file
49
application/command.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'device:list' => 'app\command\DeviceListCommand', // 设备列表 √
|
||||
'wechatFriends:list' => 'app\command\WechatFriendCommand', // 微信好友列表 √
|
||||
'wechatChatroom:list' => 'app\command\WechatChatroomCommand', // 微信群列表 √
|
||||
'friendTask:list' => 'app\command\FriendTaskCommand', // 添加好友任务列表 √
|
||||
'wechatList:list' => 'app\command\WechatListCommand', // 微信客服列表 √
|
||||
'account:list' => 'app\command\AccountListCommand', // 公司账号列表 √
|
||||
'message:friendsList' => 'app\command\MessageFriendsListCommand', // 微信好友消息列表 √
|
||||
'message:chatroomList' => 'app\command\MessageChatroomListCommand', // 微信群聊消息列表 √
|
||||
'department:list' => 'app\command\DepartmentListCommand', // 部门列表 √
|
||||
'content:sync' => 'app\command\SyncContentCommand', // 同步内容库 XXXXXXXX
|
||||
'groupFriends:list' => 'app\command\GroupFriendsCommand', // 微信群好友列表
|
||||
// 'allotFriends:run' => 'app\command\AllotFriendCommand', // 自动分配微信好友
|
||||
// 'allotChatroom:run' => 'app\command\AllotChatroomCommand', // 自动分配微信群聊
|
||||
'allotrule:list' => 'app\command\AllotRuleListCommand', // 分配规则列表 √
|
||||
'allotrule:autocreate' => 'app\command\AutoCreateAllotRulesCommand', // 自动创建分配规则 √
|
||||
'content:collect' => 'app\command\ContentCollectCommand', // 内容采集任务 √
|
||||
'moments:collect' => 'app\command\WechatMomentsCommand', // 朋友圈采集任务
|
||||
'own:moments:collect' => 'app\command\OwnMomentsCollectCommand', // 采集在线微信账号自己的朋友圈
|
||||
'switch:friends' => 'app\command\SwitchFriendsCommand',
|
||||
'call-recording:list' => 'app\command\CallRecordingListCommand', // 通话记录列表 √
|
||||
'sync:wechatData' => 'app\command\SyncWechatDataToCkbTask', // 同步微信数据到存客宝
|
||||
'sync:allFriends' => 'app\command\SyncAllFriendsCommand', // 同步所有在线好友
|
||||
|
||||
'workbench:autoLike' => 'app\command\WorkbenchAutoLikeCommand', // 工作台自动点赞任务
|
||||
'workbench:moments' => 'app\command\WorkbenchMomentsCommand', // 工作台朋友圈同步任务
|
||||
'workbench:trafficDistribute' => 'app\command\WorkbenchTrafficDistributeCommand', // 工作台流量分发任务
|
||||
'workbench:groupPush' => 'app\command\WorkbenchGroupPushCommand', // 工作台群推送任务
|
||||
'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务
|
||||
'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务
|
||||
'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知
|
||||
|
||||
'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分
|
||||
'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录
|
||||
|
||||
// 统一任务调度器
|
||||
'scheduler:run' => 'app\command\TaskSchedulerCommand', // 统一任务调度器,支持多进程并发执行
|
||||
];
|
||||
57
application/command/AccountListCommand.php
Normal file
57
application/command/AccountListCommand.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\AccountListJob;
|
||||
|
||||
class AccountListCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('account:list')
|
||||
->setDescription('获取公司账号列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理公司账号列表任务...');
|
||||
|
||||
try {
|
||||
// 初始页码
|
||||
$pageIndex = 0;
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将第一页任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('公司账号列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('公司账号列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('公司账号列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 account_list
|
||||
Queue::push(AccountListJob::class, $data, 'account_list');
|
||||
}
|
||||
}
|
||||
102
application/command/AllotChatroomCommand.php
Normal file
102
application/command/AllotChatroomCommand.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\AllotChatroomJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class AllotChatroomCommand extends Command
|
||||
{
|
||||
// 队列名称
|
||||
protected $queueName = 'allot_chatroom';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('allotChatroom:run')
|
||||
->setDescription('自动分配微信群聊')
|
||||
->addOption('toAccountId', null, Option::VALUE_REQUIRED, '目标账号ID')
|
||||
->addOption('wechatAccountKeyword', null, Option::VALUE_REQUIRED, '微信账号关键字')
|
||||
->addOption('isDeleted', null, Option::VALUE_OPTIONAL, '是否已删除状态: 0=未删除(false), 1=已删除(true)', 0)
|
||||
->addOption('jobId', null, Option::VALUE_OPTIONAL, '任务ID,用于区分不同实例', date('YmdHis') . rand(1000, 9999));
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理微信群聊自动分配任务...');
|
||||
|
||||
try {
|
||||
// 获取命令参数
|
||||
$toAccountId = $input->getOption('toAccountId');
|
||||
$wechatAccountKeyword = $input->getOption('wechatAccountKeyword');
|
||||
$isDeleted = $input->getOption('isDeleted');
|
||||
$jobId = $input->getOption('jobId');
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($toAccountId)) {
|
||||
$output->writeln('错误: 目标账号ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($wechatAccountKeyword)) {
|
||||
$output->writeln('错误: 微信账号关键字不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->writeln('目标账号ID: ' . $toAccountId);
|
||||
$output->writeln('微信账号关键字: ' . $wechatAccountKeyword);
|
||||
$output->writeln('删除状态: ' . ($isDeleted ? '已删除' : '未删除'));
|
||||
$output->writeln('任务ID: ' . $jobId);
|
||||
|
||||
// 检查队列是否已经在运行
|
||||
$queueLockKey = "queue_lock:{$this->queueName}:{$wechatAccountKeyword}";
|
||||
if (Cache::get($queueLockKey)) {
|
||||
$output->writeln("队列 {$this->queueName} 已经在运行中,wechatAccountKeyword:{$wechatAccountKeyword},跳过执行");
|
||||
Log::warning("队列 {$this->queueName} 已经在运行中,wechatAccountKeyword:{$wechatAccountKeyword},跳过执行");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置队列运行锁,有效期1小时
|
||||
Cache::set($queueLockKey, $jobId, 3600);
|
||||
$output->writeln("已设置队列运行锁,键名:{$queueLockKey},值:{$jobId},有效期:1小时");
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($toAccountId, $wechatAccountKeyword, $isDeleted, $jobId, $queueLockKey);
|
||||
|
||||
$output->writeln('微信群聊自动分配任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('微信群聊自动分配任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('微信群聊自动分配任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param string $toAccountId 目标账号ID
|
||||
* @param string $wechatAccountKeyword 微信账号关键字
|
||||
* @param bool $isDeleted 是否已删除状态
|
||||
* @param string $jobId 任务ID
|
||||
* @param string $queueLockKey 队列锁键名
|
||||
*/
|
||||
public function addToQueue($toAccountId, $wechatAccountKeyword, $isDeleted = false, $jobId = '', $queueLockKey = '')
|
||||
{
|
||||
$data = [
|
||||
'toAccountId' => $toAccountId,
|
||||
'wechatAccountKeyword' => $wechatAccountKeyword,
|
||||
'isDeleted' => $isDeleted,
|
||||
'jobId' => $jobId,
|
||||
'queueLockKey' => $queueLockKey
|
||||
];
|
||||
|
||||
// 添加到队列
|
||||
Queue::push(AllotChatroomJob::class, $data, $this->queueName);
|
||||
}
|
||||
}
|
||||
102
application/command/AllotFriendCommand.php
Normal file
102
application/command/AllotFriendCommand.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\AllotFriendJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class AllotFriendCommand extends Command
|
||||
{
|
||||
// 队列名称
|
||||
protected $queueName = 'allot_friends';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('allotFriends:run')
|
||||
->setDescription('自动分配微信好友')
|
||||
->addOption('toAccountId', null, Option::VALUE_REQUIRED, '目标账号ID')
|
||||
->addOption('wechatAccountKeyword', null, Option::VALUE_REQUIRED, '微信账号关键字')
|
||||
->addOption('isDeleted', null, Option::VALUE_OPTIONAL, '是否已删除状态: 0=未删除(false), 1=已删除(true)', 0)
|
||||
->addOption('jobId', null, Option::VALUE_OPTIONAL, '任务ID,用于区分不同实例', date('YmdHis') . rand(1000, 9999));
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理微信好友自动分配任务...');
|
||||
|
||||
try {
|
||||
// 获取命令参数
|
||||
$toAccountId = $input->getOption('toAccountId');
|
||||
$wechatAccountKeyword = $input->getOption('wechatAccountKeyword');
|
||||
$isDeleted = $input->getOption('isDeleted');
|
||||
$jobId = $input->getOption('jobId');
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($toAccountId)) {
|
||||
$output->writeln('错误: 目标账号ID不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($wechatAccountKeyword)) {
|
||||
$output->writeln('错误: 微信账号关键字不能为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
$output->writeln('目标账号ID: ' . $toAccountId);
|
||||
$output->writeln('微信账号关键字: ' . $wechatAccountKeyword);
|
||||
$output->writeln('删除状态: ' . ($isDeleted ? '已删除' : '未删除'));
|
||||
$output->writeln('任务ID: ' . $jobId);
|
||||
|
||||
// 检查队列是否已经在运行
|
||||
$queueLockKey = "queue_lock:{$this->queueName}:{$wechatAccountKeyword}";
|
||||
if (Cache::get($queueLockKey)) {
|
||||
$output->writeln("队列 {$this->queueName} 已经在运行中,wechatAccountKeyword:{$wechatAccountKeyword},跳过执行");
|
||||
Log::warning("队列 {$this->queueName} 已经在运行中,wechatAccountKeyword:{$wechatAccountKeyword},跳过执行");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置队列运行锁,有效期1小时
|
||||
Cache::set($queueLockKey, $jobId, 3600);
|
||||
$output->writeln("已设置队列运行锁,键名:{$queueLockKey},值:{$jobId},有效期:1小时");
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($toAccountId, $wechatAccountKeyword, $isDeleted, $jobId, $queueLockKey);
|
||||
|
||||
$output->writeln('微信好友自动分配任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('微信好友自动分配任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('微信好友自动分配任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param string $toAccountId 目标账号ID
|
||||
* @param string $wechatAccountKeyword 微信账号关键字
|
||||
* @param bool $isDeleted 是否已删除状态
|
||||
* @param string $jobId 任务ID
|
||||
* @param string $queueLockKey 队列锁键名
|
||||
*/
|
||||
public function addToQueue($toAccountId, $wechatAccountKeyword, $isDeleted = false, $jobId = '', $queueLockKey = '')
|
||||
{
|
||||
$data = [
|
||||
'toAccountId' => $toAccountId,
|
||||
'wechatAccountKeyword' => $wechatAccountKeyword,
|
||||
'isDeleted' => $isDeleted,
|
||||
'jobId' => $jobId,
|
||||
'queueLockKey' => $queueLockKey
|
||||
];
|
||||
|
||||
// 添加到队列
|
||||
Queue::push(AllotFriendJob::class, $data, $this->queueName);
|
||||
}
|
||||
}
|
||||
50
application/command/AllotRuleListCommand.php
Normal file
50
application/command/AllotRuleListCommand.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\AllotRuleListJob;
|
||||
|
||||
class AllotRuleListCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('allotrule:list')
|
||||
->setDescription('获取分配规则列表,自动同步到数据库');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理分配规则列表任务...');
|
||||
|
||||
try {
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue();
|
||||
|
||||
$output->writeln('分配规则列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('分配规则列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('分配规则列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
*/
|
||||
protected function addToQueue()
|
||||
{
|
||||
$data = [
|
||||
'time' => time()
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 allotrule_list
|
||||
Queue::push(AllotRuleListJob::class, $data, 'allotrule_list');
|
||||
}
|
||||
}
|
||||
50
application/command/AutoCreateAllotRulesCommand.php
Normal file
50
application/command/AutoCreateAllotRulesCommand.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\AutoCreateAllotRulesJob;
|
||||
|
||||
class AutoCreateAllotRulesCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('allotrule:autocreate')
|
||||
->setDescription('自动创建微信分配规则');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理自动创建分配规则任务...');
|
||||
|
||||
try {
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue();
|
||||
|
||||
$output->writeln('自动创建分配规则任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('自动创建分配规则任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('自动创建分配规则任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
*/
|
||||
protected function addToQueue()
|
||||
{
|
||||
$data = [
|
||||
'time' => time()
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 autocreate_allotrule
|
||||
Queue::push(AutoCreateAllotRulesJob::class, $data, 'autocreate_allotrule');
|
||||
}
|
||||
}
|
||||
554
application/command/CalculateWechatAccountScoreCommand.php
Normal file
554
application/command/CalculateWechatAccountScoreCommand.php
Normal file
@@ -0,0 +1,554 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
|
||||
/**
|
||||
* 统一计算微信账号健康分命令
|
||||
* 一个命令完成所有评分工作:
|
||||
* 1. 初始化未计算的账号(基础分只计算一次)
|
||||
* 2. 更新评分记录(根据wechatId和alias不一致情况)
|
||||
* 3. 批量更新健康分(只更新动态分)
|
||||
*/
|
||||
class CalculateWechatAccountScoreCommand extends Command
|
||||
{
|
||||
/**
|
||||
* 数据库表名
|
||||
*/
|
||||
const TABLE_WECHAT_ACCOUNT = 's2_wechat_account';
|
||||
const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score';
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('wechat:calculate-score')
|
||||
->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)')
|
||||
->addOption('only-init', null, \think\console\input\Option::VALUE_NONE, '仅执行初始化步骤')
|
||||
->addOption('only-update', null, \think\console\input\Option::VALUE_NONE, '仅执行更新评分记录步骤')
|
||||
->addOption('only-batch', null, \think\console\input\Option::VALUE_NONE, '仅执行批量更新健康分步骤')
|
||||
->addOption('account-id', 'a', \think\console\input\Option::VALUE_OPTIONAL, '指定账号ID,仅处理该账号')
|
||||
->addOption('batch-size', 'b', \think\console\input\Option::VALUE_OPTIONAL, '批处理大小', 50)
|
||||
->addOption('force-recalculate', 'f', \think\console\input\Option::VALUE_NONE, '强制重新计算基础分');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param Input $input 输入对象
|
||||
* @param Output $output 输出对象
|
||||
* @return int 命令执行状态码(0表示成功)
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 解析命令行参数
|
||||
$onlyInit = $input->getOption('only-init');
|
||||
$onlyUpdate = $input->getOption('only-update');
|
||||
$onlyBatch = $input->getOption('only-batch');
|
||||
$accountId = $input->getOption('account-id');
|
||||
$batchSize = (int)$input->getOption('batch-size');
|
||||
$forceRecalculate = $input->getOption('force-recalculate');
|
||||
|
||||
// 参数验证
|
||||
if ($batchSize <= 0) {
|
||||
$batchSize = 50; // 默认批处理大小
|
||||
}
|
||||
|
||||
// 显示执行参数
|
||||
$output->writeln("==========================================");
|
||||
$output->writeln("开始统一计算微信账号健康分...");
|
||||
$output->writeln("==========================================");
|
||||
|
||||
if ($accountId) {
|
||||
$output->writeln("指定账号ID: {$accountId}");
|
||||
}
|
||||
|
||||
if ($onlyInit) {
|
||||
$output->writeln("仅执行初始化步骤");
|
||||
} elseif ($onlyUpdate) {
|
||||
$output->writeln("仅执行更新评分记录步骤");
|
||||
} elseif ($onlyBatch) {
|
||||
$output->writeln("仅执行批量更新健康分步骤");
|
||||
}
|
||||
|
||||
if ($forceRecalculate) {
|
||||
$output->writeln("强制重新计算基础分");
|
||||
}
|
||||
|
||||
$output->writeln("批处理大小: {$batchSize}");
|
||||
|
||||
// 记录命令开始执行的日志(仅在非交互模式下记录)
|
||||
if (!$output->isVerbose()) {
|
||||
Log::info('开始执行微信账号健康分计算命令', [
|
||||
'accountId' => $accountId,
|
||||
'onlyInit' => $onlyInit ? 'true' : 'false',
|
||||
'onlyUpdate' => $onlyUpdate ? 'true' : 'false',
|
||||
'onlyBatch' => $onlyBatch ? 'true' : 'false',
|
||||
'batchSize' => $batchSize,
|
||||
'forceRecalculate' => $forceRecalculate ? 'true' : 'false'
|
||||
]);
|
||||
|
||||
$startTime = time();
|
||||
|
||||
try {
|
||||
// 实例化服务
|
||||
$service = new WechatAccountHealthScoreService();
|
||||
} catch (\Exception $e) {
|
||||
$errorMsg = "实例化WechatAccountHealthScoreService失败: " . $e->getMessage();
|
||||
$output->writeln("<error>{$errorMsg}</error>");
|
||||
Log::error($errorMsg);
|
||||
return 1; // 返回非零状态码表示失败
|
||||
}
|
||||
|
||||
// 初始化统计数据
|
||||
$initStats = ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
$updateStats = ['total' => 0];
|
||||
$batchStats = ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
|
||||
try {
|
||||
// 步骤1: 初始化未计算基础分的账号
|
||||
if (!$onlyUpdate && !$onlyBatch) {
|
||||
$output->writeln("\n[步骤1] 初始化未计算基础分的账号...");
|
||||
$initStats = $this->initUncalculatedAccounts($service, $output, $accountId, $batchSize);
|
||||
$output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
|
||||
}
|
||||
|
||||
// 步骤2: 更新评分记录(根据wechatId和alias不一致情况)
|
||||
if (!$onlyInit && !$onlyBatch) {
|
||||
$output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)...");
|
||||
$updateStats = $this->updateScoreRecords($service, $output, $accountId, $batchSize);
|
||||
$output->writeln("更新完成:处理了 {$updateStats['total']} 条记录");
|
||||
}
|
||||
|
||||
// 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分)
|
||||
if (!$onlyInit && !$onlyUpdate) {
|
||||
$output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)...");
|
||||
$batchStats = $this->batchUpdateHealthScore($service, $output, $accountId, $batchSize, $forceRecalculate);
|
||||
$output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
$endTime = time();
|
||||
$duration = $endTime - $startTime;
|
||||
|
||||
$output->writeln("\n==========================================");
|
||||
$output->writeln("任务完成!");
|
||||
$output->writeln("==========================================");
|
||||
$output->writeln("总耗时: {$duration} 秒");
|
||||
$output->writeln("初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
|
||||
$output->writeln("更新评分记录: {$updateStats['total']} 条");
|
||||
$output->writeln("批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
|
||||
// 记录命令执行完成的日志
|
||||
Log::info("微信账号健康分计算命令执行完成,总耗时: {$duration} 秒," .
|
||||
"初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条," .
|
||||
"更新评分记录: {$updateStats['total']} 条," .
|
||||
"批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
|
||||
if (!empty($initStats['errors'])) {
|
||||
$output->writeln("\n初始化错误详情:");
|
||||
Log::warning("初始化阶段出现 " . count($initStats['errors']) . " 个错误");
|
||||
|
||||
foreach (array_slice($initStats['errors'], 0, 10) as $error) {
|
||||
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
|
||||
Log::error("初始化错误 - 账号ID {$error['accountId']}: {$error['error']}");
|
||||
}
|
||||
|
||||
if (count($initStats['errors']) > 10) {
|
||||
$output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误");
|
||||
Log::warning("初始化错误过多,只记录前10个,还有 " . (count($initStats['errors']) - 10) . " 个错误未显示");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($batchStats['errors'])) {
|
||||
$output->writeln("\n批量更新错误详情:");
|
||||
Log::warning("批量更新阶段出现 " . count($batchStats['errors']) . " 个错误");
|
||||
|
||||
foreach (array_slice($batchStats['errors'], 0, 10) as $error) {
|
||||
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
|
||||
Log::error("批量更新错误 - 账号ID {$error['accountId']}: {$error['error']}");
|
||||
}
|
||||
|
||||
if (count($batchStats['errors']) > 10) {
|
||||
$output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误");
|
||||
Log::warning("批量更新错误过多,只记录前10个,还有 " . (count($batchStats['errors']) - 10) . " 个错误未显示");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\PDOException $e) {
|
||||
// 数据库异常
|
||||
$errorMsg = "数据库操作失败: " . $e->getMessage();
|
||||
$output->writeln("\n<error>数据库错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录数据库错误
|
||||
Log::error("数据库错误: " . $errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 2; // 数据库错误状态码
|
||||
} catch (\Exception $e) {
|
||||
// 一般异常
|
||||
$errorMsg = "命令执行失败: " . $e->getMessage();
|
||||
$output->writeln("\n<error>错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录严重错误
|
||||
Log::error($errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 1; // 一般错误状态码
|
||||
} catch (\Throwable $e) {
|
||||
// 其他所有错误
|
||||
$errorMsg = "严重错误: " . $e->getMessage();
|
||||
$output->writeln("\n<error>严重错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录严重错误
|
||||
Log::critical($errorMsg);
|
||||
Log::critical("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 3; // 严重错误状态码
|
||||
}
|
||||
|
||||
return 0; // 成功执行
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化未计算基础分的账号
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function initUncalculatedAccounts($service, $output, $accountId = null, $batchSize = 50)
|
||||
{
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
try {
|
||||
// 获取所有未计算基础分的账号
|
||||
// 优化查询:使用索引字段,只查询必要的字段
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->alias('a')
|
||||
->leftJoin([self::TABLE_WECHAT_ACCOUNT_SCORE => 's'], 's.accountId = a.id')
|
||||
->where('a.isDeleted', 0)
|
||||
->where(function($query) {
|
||||
$query->whereNull('s.id')
|
||||
->whereOr('s.baseScoreCalculated', 0);
|
||||
});
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('a.id', $accountId);
|
||||
}
|
||||
|
||||
$accounts = $query->field('a.id, a.wechatId') // 只查询必要的字段
|
||||
->select();
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询未计算基础分的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询未计算基础分的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$stats['total'] = count($accounts);
|
||||
|
||||
if ($stats['total'] == 0) {
|
||||
$output->writeln("没有需要初始化的账号");
|
||||
Log::info("没有需要初始化的账号");
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$stats['total']} 个需要初始化的账号");
|
||||
Log::info("找到 {$stats['total']} 个需要初始化的账号");
|
||||
|
||||
// 优化批处理:使用传入的批处理大小
|
||||
$batches = array_chunk($accounts, $batchSize);
|
||||
$batchCount = count($batches);
|
||||
|
||||
Log::info("将分 {$batchCount} 批处理,每批 {$batchSize} 个账号");
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
$batchStartTime = microtime(true);
|
||||
$batchSuccessCount = 0;
|
||||
$batchFailedCount = 0;
|
||||
|
||||
foreach ($batch as $account) {
|
||||
try {
|
||||
$service->calculateAndUpdate($account['id']);
|
||||
$stats['success']++;
|
||||
$batchSuccessCount++;
|
||||
|
||||
if ($stats['success'] % 20 == 0) { // 更频繁地显示进度
|
||||
$output->write(".");
|
||||
Log::debug("已成功初始化 {$stats['success']} 个账号");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$stats['failed']++;
|
||||
$batchFailedCount++;
|
||||
$errorMsg = "初始化账号 {$account['id']} 失败: " . $e->getMessage();
|
||||
Log::error($errorMsg);
|
||||
$stats['errors'][] = [
|
||||
'accountId' => $account['id'],
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$batchEndTime = microtime(true);
|
||||
$batchDuration = round($batchEndTime - $batchStartTime, 2);
|
||||
|
||||
// 每批次完成后输出进度信息
|
||||
$output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
|
||||
Log::info("初始化批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新评分记录(根据wechatId和alias不一致情况)
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function updateScoreRecords($service, $output, $accountId = null, $batchSize = 50)
|
||||
{
|
||||
$stats = ['total' => 0];
|
||||
|
||||
try {
|
||||
// 优化查询:合并两次查询为一次,减少数据库访问次数
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->where('isDeleted', 0)
|
||||
->where('wechatId', '<>', '')
|
||||
->where('alias', '<>', '');
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('id', $accountId);
|
||||
}
|
||||
|
||||
$accounts = $query->field('id, wechatId, alias, IF(wechatId = alias, 0, 1) as isModifiedAlias')
|
||||
->select();
|
||||
|
||||
// 分类处理查询结果
|
||||
$inconsistentAccounts = [];
|
||||
$consistentAccounts = [];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if ($account['isModifiedAlias'] == 1) {
|
||||
$inconsistentAccounts[] = $account;
|
||||
} else {
|
||||
$consistentAccounts[] = $account;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询需要更新评分记录的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询需要更新评分记录的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$allAccounts = array_merge($inconsistentAccounts, $consistentAccounts);
|
||||
$stats['total'] = count($allAccounts);
|
||||
|
||||
if ($stats['total'] == 0) {
|
||||
$output->writeln("没有需要更新的账号");
|
||||
Log::info("没有需要更新的评分记录");
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
|
||||
Log::info("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
|
||||
|
||||
$updatedCount = 0;
|
||||
|
||||
// 优化批处理:使用传入的批处理大小
|
||||
$batches = array_chunk($allAccounts, $batchSize);
|
||||
$batchCount = count($batches);
|
||||
|
||||
Log::info("将分 {$batchCount} 批更新评分记录,每批 {$batchSize} 个账号");
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
$batchStartTime = microtime(true);
|
||||
$batchUpdatedCount = 0;
|
||||
|
||||
foreach ($batch as $account) {
|
||||
$isModifiedAlias = isset($account['isModifiedAlias']) ?
|
||||
($account['isModifiedAlias'] == 1) :
|
||||
in_array($account['id'], array_column($inconsistentAccounts, 'id'));
|
||||
|
||||
$this->updateScoreRecord($account['id'], $isModifiedAlias, $service);
|
||||
$updatedCount++;
|
||||
$batchUpdatedCount++;
|
||||
|
||||
if ($batchUpdatedCount % 20 == 0) {
|
||||
$output->write(".");
|
||||
}
|
||||
}
|
||||
|
||||
$batchEndTime = microtime(true);
|
||||
$batchDuration = round($batchEndTime - $batchStartTime, 2);
|
||||
|
||||
// 每批次完成后输出进度信息
|
||||
$output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
|
||||
Log::info("更新评分记录批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
|
||||
}
|
||||
|
||||
if ($updatedCount > 0 && $updatedCount % 100 == 0) {
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新健康分(只更新动态分)
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function batchUpdateHealthScore($service, $output, $accountId = null, $batchSize = 50, $forceRecalculate = false)
|
||||
{
|
||||
try {
|
||||
// 获取所有已计算基础分的账号
|
||||
// 优化查询:只查询必要的字段,使用索引字段
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('baseScoreCalculated', 1);
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('accountId', $accountId);
|
||||
}
|
||||
|
||||
$accountIds = $query->column('accountId');
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询需要批量更新健康分的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询需要批量更新健康分的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$total = count($accountIds);
|
||||
|
||||
if ($total == 0) {
|
||||
$output->writeln("没有需要更新的账号");
|
||||
Log::info("没有需要批量更新健康分的账号");
|
||||
return ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$total} 个需要更新动态分的账号");
|
||||
Log::info("找到 {$total} 个需要更新动态分的账号");
|
||||
|
||||
// 使用传入的批处理大小和强制重新计算标志
|
||||
Log::info("使用批量大小 {$batchSize} 进行批量更新健康分,强制重新计算基础分: " . ($forceRecalculate ? 'true' : 'false'));
|
||||
$stats = $service->batchCalculateAndUpdate($accountIds, $batchSize, $forceRecalculate);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新评分记录
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param bool $isModifiedAlias 是否已修改微信号
|
||||
* @param WechatAccountHealthScoreService $service 评分服务
|
||||
*/
|
||||
/**
|
||||
* 更新评分记录
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param bool $isModifiedAlias 是否已修改微信号
|
||||
* @param WechatAccountHealthScoreService $service 评分服务
|
||||
* @return bool 是否成功更新
|
||||
*/
|
||||
private function updateScoreRecord($accountId, $isModifiedAlias, $service)
|
||||
{
|
||||
Log::debug("开始更新账号 {$accountId} 的评分记录,isModifiedAlias: " . ($isModifiedAlias ? 'true' : 'false'));
|
||||
|
||||
try {
|
||||
// 获取账号数据 - 只查询必要的字段
|
||||
$accountData = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->where('id', $accountId)
|
||||
->field('id, wechatId, alias') // 只查询必要的字段
|
||||
->find();
|
||||
|
||||
if (empty($accountData)) {
|
||||
Log::warning("账号 {$accountId} 不存在,跳过更新评分记录");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保评分记录存在 - 只查询必要的字段
|
||||
$scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->field('accountId, baseScore, baseScoreCalculated, baseInfoScore, dynamicScore') // 只查询必要的字段
|
||||
->find();
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
// 如果记录不存在,创建并计算基础分
|
||||
Log::info("账号 {$accountId} 的评分记录不存在,创建并计算基础分");
|
||||
$service->calculateAndUpdate($accountId);
|
||||
$scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->find();
|
||||
}
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
Log::warning("账号 {$accountId} 的评分记录创建失败,跳过更新");
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新isModifiedAlias字段
|
||||
$updateData = [
|
||||
'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 如果基础分已计算,需要更新基础信息分和基础分
|
||||
if ($scoreRecord['baseScoreCalculated']) {
|
||||
$oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
|
||||
$newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
|
||||
|
||||
if ($oldBaseInfoScore != $newBaseInfoScore) {
|
||||
$oldBaseScore = $scoreRecord['baseScore'] ?? 60;
|
||||
$newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
|
||||
|
||||
$updateData['baseInfoScore'] = $newBaseInfoScore;
|
||||
$updateData['baseScore'] = $newBaseScore;
|
||||
|
||||
// 重新计算健康分
|
||||
$dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
|
||||
$healthScore = $newBaseScore + $dynamicScore;
|
||||
$healthScore = max(0, min(100, $healthScore));
|
||||
$updateData['healthScore'] = $healthScore;
|
||||
$updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
|
||||
|
||||
Log::info("账号 {$accountId} 的基础信息分从 {$oldBaseInfoScore} 更新为 {$newBaseInfoScore}," .
|
||||
"基础分从 {$oldBaseScore} 更新为 {$newBaseScore},健康分更新为 {$healthScore}");
|
||||
}
|
||||
} else {
|
||||
// 基础分未计算,只更新标记和基础信息分
|
||||
$updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
|
||||
}
|
||||
|
||||
$result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->update($updateData);
|
||||
|
||||
Log::debug("账号 {$accountId} 的评分记录更新" . ($result !== false ? "成功" : "失败"));
|
||||
|
||||
return $result !== false;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("更新账号 {$accountId} 的评分记录失败: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
57
application/command/CallRecordingListCommand.php
Normal file
57
application/command/CallRecordingListCommand.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\CallRecordingListJob;
|
||||
|
||||
class CallRecordingListCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('call-recording:list')
|
||||
->setDescription('获取通话记录列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理通话记录列表任务...');
|
||||
|
||||
try {
|
||||
// 初始页码
|
||||
$pageIndex = 0;
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将第一页任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('通话记录列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('通话记录列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('通话记录列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 call_recording_list
|
||||
Queue::push(CallRecordingListJob::class, $data, 'call_recording_list');
|
||||
}
|
||||
}
|
||||
100
application/command/CleanExpiredGroupMessages.php
Normal file
100
application/command/CleanExpiredGroupMessages.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\Db;
|
||||
|
||||
class CleanExpiredGroupMessages extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('clean:expired_group_messages')
|
||||
->setDescription('Clean expired group messages from the database')
|
||||
->addOption('days', 'd', Option::VALUE_OPTIONAL, 'Number of days to keep messages (default: 90)', 90)
|
||||
->addOption('dry-run', null, Option::VALUE_NONE, 'Perform a dry run without deleting any data')
|
||||
->addOption('batch-size', 'b', Option::VALUE_OPTIONAL, 'Batch size for deletion (default: 1000)', 1000);
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$days = (int)$input->getOption('days');
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$batchSize = (int)$input->getOption('batch-size');
|
||||
|
||||
if ($dryRun) {
|
||||
$output->writeln("<info>Running in dry-run mode. No data will be deleted.</info>");
|
||||
}
|
||||
|
||||
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
$output->writeln("<info>Cleaning group messages older than {$cutoffDate} (keeping last {$days} days)</info>");
|
||||
|
||||
// 清理微信群组消息
|
||||
$this->cleanWechatGroupMessages($cutoffDate, $dryRun, $batchSize, $output);
|
||||
|
||||
$output->writeln("<info>Group message cleanup completed successfully.</info>");
|
||||
}
|
||||
|
||||
protected function cleanWechatGroupMessages($cutoffDate, $dryRun, $batchSize, Output $output)
|
||||
{
|
||||
$output->writeln("\nCleaning s2_wechat_group_message table...");
|
||||
|
||||
// 获取符合条件的消息总数
|
||||
$totalCount = Db::table('s2_wechat_group_message')
|
||||
->where('createTime', '<', $cutoffDate)
|
||||
->count();
|
||||
|
||||
if ($totalCount === 0) {
|
||||
$output->writeln(" <comment>No expired group messages found.</comment>");
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln(" Found {$totalCount} group messages to clean up.");
|
||||
|
||||
if ($dryRun) {
|
||||
$output->writeln(" <comment>Dry run mode: would delete {$totalCount} group messages.</comment>");
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算需要执行的批次数
|
||||
$batches = ceil($totalCount / $batchSize);
|
||||
$deletedCount = 0;
|
||||
|
||||
$output->writeln(" Deleting in {$batches} batches of {$batchSize} records...");
|
||||
|
||||
// 分批删除数据
|
||||
for ($i = 0; $i < $batches; $i++) {
|
||||
// 获取一批要删除的ID
|
||||
$ids = Db::table('s2_wechat_group_message')
|
||||
->where('createTime', '<', $cutoffDate)
|
||||
->limit($batchSize)
|
||||
->column('id');
|
||||
|
||||
if (empty($ids)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 删除这批数据
|
||||
$count = Db::table('s2_wechat_group_message')
|
||||
->whereIn('id', $ids)
|
||||
->delete();
|
||||
|
||||
$deletedCount += $count;
|
||||
$progress = round(($deletedCount / $totalCount) * 100, 2);
|
||||
$output->write(" Progress: {$progress}% ({$deletedCount}/{$totalCount})\r");
|
||||
|
||||
// 短暂暂停,减轻数据库负担
|
||||
usleep(500000); // 暂停0.5秒
|
||||
}
|
||||
|
||||
$output->writeln("");
|
||||
$output->writeln(" <info>Successfully deleted {$deletedCount} expired group messages.</info>");
|
||||
|
||||
// 优化表
|
||||
$output->writeln(" Optimizing table...");
|
||||
Db::execute("OPTIMIZE TABLE s2_wechat_group_message");
|
||||
$output->writeln(" <info>Table optimization completed.</info>");
|
||||
}
|
||||
}
|
||||
100
application/command/CleanExpiredMessages.php
Normal file
100
application/command/CleanExpiredMessages.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\Db;
|
||||
|
||||
class CleanExpiredMessages extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('clean:expired_messages')
|
||||
->setDescription('Clean expired messages from the database')
|
||||
->addOption('days', 'd', Option::VALUE_OPTIONAL, 'Number of days to keep messages (default: 90)', 90)
|
||||
->addOption('dry-run', null, Option::VALUE_NONE, 'Perform a dry run without deleting any data')
|
||||
->addOption('batch-size', 'b', Option::VALUE_OPTIONAL, 'Batch size for deletion (default: 1000)', 1000);
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$days = (int)$input->getOption('days');
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$batchSize = (int)$input->getOption('batch-size');
|
||||
|
||||
if ($dryRun) {
|
||||
$output->writeln("<info>Running in dry-run mode. No data will be deleted.</info>");
|
||||
}
|
||||
|
||||
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
$output->writeln("<info>Cleaning messages older than {$cutoffDate} (keeping last {$days} days)</info>");
|
||||
|
||||
// 清理微信消息
|
||||
$this->cleanWechatMessages($cutoffDate, $dryRun, $batchSize, $output);
|
||||
|
||||
$output->writeln("<info>Message cleanup completed successfully.</info>");
|
||||
}
|
||||
|
||||
protected function cleanWechatMessages($cutoffDate, $dryRun, $batchSize, Output $output)
|
||||
{
|
||||
$output->writeln("\nCleaning s2_wechat_message table...");
|
||||
|
||||
// 获取符合条件的消息总数
|
||||
$totalCount = Db::table('s2_wechat_message')
|
||||
->where('createTime', '<', $cutoffDate)
|
||||
->count();
|
||||
|
||||
if ($totalCount === 0) {
|
||||
$output->writeln(" <comment>No expired messages found.</comment>");
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln(" Found {$totalCount} messages to clean up.");
|
||||
|
||||
if ($dryRun) {
|
||||
$output->writeln(" <comment>Dry run mode: would delete {$totalCount} messages.</comment>");
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算需要执行的批次数
|
||||
$batches = ceil($totalCount / $batchSize);
|
||||
$deletedCount = 0;
|
||||
|
||||
$output->writeln(" Deleting in {$batches} batches of {$batchSize} records...");
|
||||
|
||||
// 分批删除数据
|
||||
for ($i = 0; $i < $batches; $i++) {
|
||||
// 获取一批要删除的ID
|
||||
$ids = Db::table('s2_wechat_message')
|
||||
->where('createTime', '<', $cutoffDate)
|
||||
->limit($batchSize)
|
||||
->column('id');
|
||||
|
||||
if (empty($ids)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 删除这批数据
|
||||
$count = Db::table('s2_wechat_message')
|
||||
->whereIn('id', $ids)
|
||||
->delete();
|
||||
|
||||
$deletedCount += $count;
|
||||
$progress = round(($deletedCount / $totalCount) * 100, 2);
|
||||
$output->write(" Progress: {$progress}% ({$deletedCount}/{$totalCount})\r");
|
||||
|
||||
// 短暂暂停,减轻数据库负担
|
||||
usleep(500000); // 暂停0.5秒
|
||||
}
|
||||
|
||||
$output->writeln("");
|
||||
$output->writeln(" <info>Successfully deleted {$deletedCount} expired messages.</info>");
|
||||
|
||||
// 优化表
|
||||
$output->writeln(" Optimizing table...");
|
||||
Db::execute("OPTIMIZE TABLE s2_wechat_message");
|
||||
$output->writeln(" <info>Table optimization completed.</info>");
|
||||
}
|
||||
}
|
||||
51
application/command/ContentCollectCommand.php
Normal file
51
application/command/ContentCollectCommand.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\ContentCollectJob;
|
||||
|
||||
class ContentCollectCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('content:collect')
|
||||
->setDescription('执行内容采集任务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理内容采集任务...');
|
||||
|
||||
try {
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue();
|
||||
|
||||
$output->writeln('内容采集任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('内容采集任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('内容采集任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
*/
|
||||
protected function addToQueue()
|
||||
{
|
||||
$data = [
|
||||
'libraryId' => 0, // 0表示采集所有内容库
|
||||
'timestamp' => time()
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 content_collect
|
||||
Queue::push(ContentCollectJob::class, $data, 'content_collect');
|
||||
}
|
||||
}
|
||||
57
application/command/DepartmentListCommand.php
Normal file
57
application/command/DepartmentListCommand.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\DepartmentListJob;
|
||||
|
||||
class DepartmentListCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('department:list')
|
||||
->setDescription('获取部门列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理部门列表任务...');
|
||||
|
||||
try {
|
||||
// 初始页码
|
||||
$pageIndex = 0;
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将第一页任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('部门列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('部门列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('部门列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 account_list
|
||||
Queue::push(DepartmentListJob::class, $data, 'department_list');
|
||||
}
|
||||
}
|
||||
98
application/command/DeviceListCommand.php
Normal file
98
application/command/DeviceListCommand.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\console\input\Option;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\DeviceListJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class DeviceListCommand extends Command
|
||||
{
|
||||
// 队列名称
|
||||
protected $queueName = 'device_list';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('device:list')
|
||||
->setDescription('获取设备列表,并根据分页自动处理下一页')
|
||||
->addOption('isDel', null, Option::VALUE_OPTIONAL, '删除状态: 0=未删除(unDeleted), 1=已删除(deleted), 2=已停用(deletedAndStop)', '')
|
||||
->addOption('jobId', null, Option::VALUE_OPTIONAL, '任务ID,用于区分不同实例', date('YmdHis') . rand(1000, 9999));
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理设备列表任务...');
|
||||
|
||||
try {
|
||||
// 获取是否删除参数和任务ID
|
||||
$isDel = $input->getOption('isDel');
|
||||
$jobId = $input->getOption('jobId');
|
||||
|
||||
$output->writeln('删除状态参数: ' . ($isDel === '' ? '全部' : ($isDel == 0 ? '未删除' : ($isDel == 1 ? '已删除' : '已停用'))));
|
||||
$output->writeln('任务ID: ' . $jobId);
|
||||
|
||||
// 检查队列是否已经在运行
|
||||
$queueLockKey = "queue_lock:{$this->queueName}:{$isDel}";
|
||||
if (Cache::get($queueLockKey)) {
|
||||
$output->writeln("队列 {$this->queueName} 已经在运行中,删除状态:{$isDel},跳过执行");
|
||||
Log::warning("队列 {$this->queueName} 已经在运行中,删除状态:{$isDel},跳过执行");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置队列运行锁,有效期1小时
|
||||
Cache::set($queueLockKey, $jobId, 3600);
|
||||
$output->writeln("已设置队列运行锁,键名:{$queueLockKey},值:{$jobId},有效期:1小时");
|
||||
|
||||
// 为不同的删除状态和任务ID使用不同的缓存键名
|
||||
$cacheKeyPrefix = "devicePage:{$jobId}";
|
||||
$cacheKeySuffix = $isDel === '' ? '' : ":{$isDel}";
|
||||
$cacheKey = $cacheKeyPrefix . $cacheKeySuffix;
|
||||
|
||||
// 从缓存获取初始页码,缓存有效期1天
|
||||
$pageIndex = Cache::get($cacheKey, 0);
|
||||
$output->writeln("从缓存获取页码: {$pageIndex}, 缓存键: {$cacheKey}");
|
||||
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize, $isDel, $jobId, $cacheKey, $queueLockKey);
|
||||
|
||||
$output->writeln('设备列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('设备列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('设备列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
* @param string $isDel 删除状态
|
||||
* @param string $jobId 任务ID
|
||||
* @param string $cacheKey 缓存键名
|
||||
* @param string $queueLockKey 队列锁键名
|
||||
*/
|
||||
public function addToQueue($pageIndex, $pageSize, $isDel = '', $jobId = '', $cacheKey = '', $queueLockKey = '')
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize,
|
||||
'isDel' => $isDel,
|
||||
'jobId' => $jobId,
|
||||
'cacheKey' => $cacheKey,
|
||||
'queueLockKey' => $queueLockKey
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 device_list
|
||||
Queue::push(DeviceListJob::class, $data, $this->queueName);
|
||||
}
|
||||
}
|
||||
60
application/command/FriendTaskCommand.php
Normal file
60
application/command/FriendTaskCommand.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\FriendTaskJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class FriendTaskCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('friend:task')
|
||||
->setDescription('获取添加好友认为列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理添加好友任务...');
|
||||
|
||||
try {
|
||||
// 从缓存获取初始页码,缓存10分钟有效
|
||||
$pageIndex = Cache::get('friendTaskPage', 0);
|
||||
$output->writeln('从缓存获取页码:' . $pageIndex);
|
||||
|
||||
$pageSize = 1000; // 每页获取1000条记录
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('添加好友任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('添加好友任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('添加好友任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 friend_task
|
||||
Queue::push(FriendTaskJob::class, $data, 'friend_task');
|
||||
}
|
||||
}
|
||||
60
application/command/GroupFriendsCommand.php
Normal file
60
application/command/GroupFriendsCommand.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\GroupFriendsJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class GroupFriendsCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('groupFriends:list')
|
||||
->setDescription('获取微信群好友列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理微信群好友列表任务...');
|
||||
|
||||
try {
|
||||
// 从缓存获取初始页码,缓存有效期一天
|
||||
$pageIndex = Cache::get('groupFriendsPage', 0);
|
||||
$output->writeln('从缓存获取页码:' . $pageIndex);
|
||||
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('微信群好友列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('微信群好友列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('微信群好友列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 group_friends
|
||||
Queue::push(GroupFriendsJob::class, $data, 'group_friends');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user