代码提交

This commit is contained in:
wong
2025-09-04 10:49:22 +08:00
parent d3ae45a360
commit 3c68a603af
5 changed files with 661 additions and 162 deletions

View File

@@ -8,6 +8,7 @@ import {
Card, Card,
Tag, Tag,
message, message,
Modal,
} from "antd"; } from "antd";
import { import {
PhoneOutlined, PhoneOutlined,
@@ -22,9 +23,12 @@ import {
StarOutlined, StarOutlined,
EditOutlined, EditOutlined,
CheckOutlined, CheckOutlined,
PlusOutlined,
NotificationOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data";
import { useCkChatStore } from "@/store/module/ckchat/ckchat"; import { useCkChatStore } from "@/store/module/ckchat/ckchat";
import { useWebSocketStore } from "@/store/module/websocket/websocket";
import styles from "./Person.module.scss"; import styles from "./Person.module.scss";
const { Sider } = Layout; const { Sider } = Layout;
@@ -47,10 +51,27 @@ const Person: React.FC<PersonProps> = ({
contract.labels || [], contract.labels || [],
); );
const [allAvailableTags, setAllAvailableTags] = useState<string[]>([]); const [allAvailableTags, setAllAvailableTags] = useState<string[]>([]);
const [isAddingTag, setIsAddingTag] = useState(false);
const [newTagValue, setNewTagValue] = useState("");
// 判断是否为群聊
const isGroup = 'chatroomId' in contract;
// 群聊相关状态
const [isEditingGroupName, setIsEditingGroupName] = useState(false);
const [groupNameValue, setGroupNameValue] = useState(contract.name || '');
const [isEditingGroupNotice, setIsEditingGroupNotice] = useState(false);
const [groupNoticeValue, setGroupNoticeValue] = useState(contract.notice || '');
const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = useState(false);
const [selfDisplayNameValue, setSelfDisplayNameValue] = useState(contract.selfDisplyName || '');
const [isGroupNoticeModalVisible, setIsGroupNoticeModalVisible] = useState(false);
// 构建联系人或群聊详细信息
const kfSelectedUser = useCkChatStore(state => const kfSelectedUser = useCkChatStore(state =>
state.getKfUserInfo(contract.wechatAccountId || 0), state.getKfUserInfo(contract.wechatAccountId || 0),
); );
const { sendCommand } = useWebSocketStore();
// 获取所有可用标签 // 获取所有可用标签
useEffect(() => { useEffect(() => {
@@ -68,22 +89,102 @@ const Person: React.FC<PersonProps> = ({
fetchAvailableTags(); fetchAvailableTags();
}, [kfSelectedUser, contract.labels]); }, [kfSelectedUser, contract.labels]);
// 当contract变化时更新备注值和标签 // 当contract变化时更新各种值
useEffect(() => { useEffect(() => {
setRemarkValue(contract.conRemark || ""); setRemarkValue(contract.conRemark || "");
setIsEditingRemark(false); setIsEditingRemark(false);
setSelectedTags(contract.labels || []); setSelectedTags(contract.labels || []);
}, [contract.conRemark, contract.labels]);
if (isGroup) {
setGroupNameValue(contract.name || '');
setIsEditingGroupName(false);
setGroupNoticeValue(contract.notice || '');
setIsEditingGroupNotice(false);
setSelfDisplayNameValue(contract.selfDisplyName || '');
setIsEditingSelfDisplayName(false);
}
}, [contract.conRemark, contract.labels, contract.name, contract.notice, contract.selfDisplyName, isGroup]);
// 处理备注保存 // 处理备注保存
const handleSaveRemark = () => { const handleSaveRemark = () => {
// 这里应该调用API保存备注到后端 if (isGroup) {
// 暂时只更新本地状态 // 群聊备注修改
sendCommand("CmdModifyGroupRemark", {
wechatAccountId: contract.wechatAccountId,
chatroomId: contract.chatroomId,
newRemark: remarkValue,
});
} else {
// 好友备注修改
sendCommand("CmdModifyFriendRemark", {
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract.id,
newRemark: remarkValue,
});
}
messageApi.success("备注保存成功"); messageApi.success("备注保存成功");
setIsEditingRemark(false); setIsEditingRemark(false);
// 更新contract对象中的备注实际项目中应该通过props回调或状态管理 // 更新contract对象中的备注实际项目中应该通过props回调或状态管理
}; };
// 处理群名称保存
const handleSaveGroupName = () => {
sendCommand("CmdChatroomOperate", {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract.id,
chatroomOperateType: 6,
extra: `{\"chatroomName\":\"${groupNameValue}\"}`,
});
messageApi.success("群名称修改成功");
setIsEditingGroupName(false);
};
// 点击编辑群名称按钮
const handleEditGroupName = () => {
setGroupNameValue(contract.name || '');
setIsEditingGroupName(true);
};
// 处理群公告保存
const handleSaveGroupNotice = () => {
sendCommand("CmdChatroomOperate", {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract.id,
chatroomOperateType: 5,
extra: `{\"announce\":\"${groupNoticeValue}\"}`,
});
messageApi.success("群公告修改成功");
setIsEditingGroupNotice(false);
};
// 点击编辑群公告按钮
const handleEditGroupNotice = () => {
setGroupNoticeValue(contract.notice || '');
setIsGroupNoticeModalVisible(true);
};
// 处理我在本群中的昵称保存
const handleSaveSelfDisplayName = () => {
sendCommand("CmdChatroomOperate", {
wechatAccountId: contract.wechatAccountId,
wechatChatroomId: contract.id,
chatroomOperateType: 8,
extra: `${selfDisplayNameValue}`,
});
messageApi.success("群昵称修改成功");
setIsEditingSelfDisplayName(false);
};
// 点击编辑群昵称按钮
const handleEditSelfDisplayName = () => {
setSelfDisplayNameValue(contract.selfDisplyName || '');
setIsEditingSelfDisplayName(true);
};
// 处理取消编辑 // 处理取消编辑
const handleCancelEdit = () => { const handleCancelEdit = () => {
setRemarkValue(contract.conRemark || ""); setRemarkValue(contract.conRemark || "");
@@ -98,20 +199,90 @@ const Person: React.FC<PersonProps> = ({
setSelectedTags(newSelectedTags); setSelectedTags(newSelectedTags);
// 这里应该调用API保存标签到后端 // 使用WebSocket发送修改标签命令
if (isGroup) {
// 群聊标签修改
sendCommand("CmdModifyGroupLabel", {
labels: newSelectedTags,
seq: +new Date(),
wechatAccountId: contract.wechatAccountId,
chatroomId: contract.chatroomId,
});
} else {
// 好友标签修改
sendCommand("CmdModifyFriendLabel", {
labels: newSelectedTags,
seq: +new Date(),
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract.id,
});
}
messageApi.success( messageApi.success(
`标签"${tagName}"${selectedTags.includes(tagName) ? "已取消" : "已选中"}`, `标签"${tagName}"${selectedTags.includes(tagName) ? "已取消" : "已选中"}`,
); );
}; };
// 模拟联系人详细信息 // 处理新增标签
const handleAddTag = () => {
if (!newTagValue.trim()) {
messageApi.error("请输入标签名称");
return;
}
if (allAvailableTags.includes(newTagValue.trim())) {
messageApi.error("标签已存在");
return;
}
const newTag = newTagValue.trim();
// 添加到可用标签列表
setAllAvailableTags(prev => [...prev, newTag]);
// 自动选中新添加的标签
setSelectedTags(prev => [...prev, newTag]);
// 使用WebSocket发送新增标签命令
if (isGroup) {
// 群聊标签修改
sendCommand("CmdModifyGroupLabel", {
labels: [...selectedTags, newTag],
seq: +new Date(),
wechatAccountId: contract.wechatAccountId,
chatroomId: contract.chatroomId,
});
} else {
// 好友标签修改
sendCommand("CmdModifyFriendLabel", {
labels: [...selectedTags, newTag],
seq: +new Date(),
wechatAccountId: contract.wechatAccountId,
wechatFriendId: contract.id || contract.wechatId,
});
}
messageApi.success(`标签"${newTag}"添加成功`);
setNewTagValue("");
setIsAddingTag(false);
};
// 处理取消新增标签
const handleCancelAddTag = () => {
setNewTagValue("");
setIsAddingTag(false);
};
// 构建联系人或群聊详细信息
const contractInfo = { const contractInfo = {
name: contract.name, name: contract.name || contract.nickname,
nickname: contract.nickname, nickname: contract.nickname,
conRemark: remarkValue, // 使用当前编辑的备注值 conRemark: remarkValue, // 使用当前编辑的备注值
alias: contract.alias, alias: contract.alias,
wechatId: contract.wechatId, wechatId: contract.wechatId,
avatar: contract.avatar, chatroomId: isGroup ? contract.chatroomId : undefined,
chatroomOwner: isGroup ? contract.chatroomOwner : undefined,
avatar: contract.avatar || contract.chatroomAvatar,
phone: contract.phone || "-", phone: contract.phone || "-",
email: contract.email || "-", email: contract.email || "-",
department: contract.department || "-", department: contract.department || "-",
@@ -119,9 +290,11 @@ const Person: React.FC<PersonProps> = ({
company: contract.company || "-", company: contract.company || "-",
region: contract.region || "-", region: contract.region || "-",
joinDate: contract.joinDate || "-", joinDate: contract.joinDate || "-",
notice: isGroup ? contract.notice : undefined,
selfDisplyName: isGroup ? contract.selfDisplyName : undefined,
status: "在线", status: "在线",
tags: selectedTags, tags: selectedTags,
bio: contract.bio || "-", bio: contract.bio || contract.signature || "-",
}; };
if (!showProfile) { if (!showProfile) {
@@ -151,14 +324,59 @@ const Person: React.FC<PersonProps> = ({
icon={<UserOutlined />} icon={<UserOutlined />}
/> />
<div className={styles.profileInfo}> <div className={styles.profileInfo}>
<Tooltip {isGroup && isEditingGroupName ? (
title={contractInfo.nickname || contractInfo.name} <div
placement="top" style={{
> display: "flex",
<h4 className={styles.profileNickname}> alignItems: "center",
{contractInfo.nickname || contractInfo.name} gap: "8px",
</h4> }}
</Tooltip> >
<Input
value={groupNameValue}
onChange={e => setGroupNameValue(e.target.value)}
placeholder="请输入群名称"
size="small"
style={{ flex: 1 }}
/>
<Button
type="text"
size="small"
icon={<CheckOutlined />}
onClick={handleSaveGroupName}
style={{ color: "#52c41a" }}
/>
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={() => {
setGroupNameValue(contract.name || '');
setIsEditingGroupName(false);
}}
style={{ color: "#ff4d4f" }}
/>
</div>
) : (
<Tooltip
title={contractInfo.nickname || contractInfo.name}
placement="top"
>
<div style={{ display: "flex", alignItems: "center" }}>
<h4 className={styles.profileNickname}>
{contractInfo.nickname || contractInfo.name}
</h4>
{isGroup && (
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={() => setIsEditingGroupName(true)}
/>
)}
</div>
</Tooltip>
)}
<div className={styles.profileStatus}> <div className={styles.profileStatus}>
<span className={styles.statusDot}></span> <span className={styles.statusDot}></span>
@@ -169,58 +387,245 @@ const Person: React.FC<PersonProps> = ({
{/* 详细信息卡片 */} {/* 详细信息卡片 */}
<Card title="详细信息" className={styles.profileCard}> <Card title="详细信息" className={styles.profileCard}>
<div className={styles.infoItem}> {isGroup ? (
<TeamOutlined className={styles.infoIcon} /> // 群聊信息
<span className={styles.infoLabel}>:</span> <>
<span className={styles.infoValue}> <div className={styles.infoItem}>
{contractInfo.alias || contractInfo.wechatId} <TeamOutlined className={styles.infoIcon} />
</span> <span className={styles.infoLabel}>ID:</span>
</div> <span className={styles.infoValue}>
<div className={styles.infoItem}> {contractInfo.chatroomId}
<PhoneOutlined className={styles.infoIcon} /> </span>
<span className={styles.infoLabel}>:</span> </div>
<span className={styles.infoValue}>{contractInfo.phone}</span> <div className={styles.infoItem}>
</div> <UserOutlined className={styles.infoIcon} />
<div className={styles.infoItem}> <span className={styles.infoLabel}>:</span>
<EnvironmentOutlined className={styles.infoIcon} /> <span className={styles.infoValue}>{contractInfo.chatroomOwner}</span>
<span className={styles.infoLabel}>:</span> </div>
<span className={styles.infoValue}>{contractInfo.region}</span> <div className={styles.infoItem}>
</div> <UserOutlined className={styles.infoIcon} />
<div className={styles.infoItem}> <span className={styles.infoLabel}>:</span>
<EditOutlined className={styles.infoIcon} /> <div className={styles.infoValue}>
<span className={styles.infoLabel}>:</span> {isEditingSelfDisplayName ? (
<div className={styles.infoValue}> <div
{isEditingRemark ? ( style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<Input
value={selfDisplayNameValue}
onChange={e => setSelfDisplayNameValue(e.target.value)}
placeholder="请输入群昵称"
size="small"
style={{ flex: 1 }}
/>
<Button
type="text"
size="small"
icon={<CheckOutlined />}
onClick={handleSaveSelfDisplayName}
style={{ color: "#52c41a" }}
/>
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={() => {
setSelfDisplayNameValue(contract.selfDisplyName || '');
setIsEditingSelfDisplayName(false);
}}
style={{ color: "#ff4d4f" }}
/>
</div>
) : (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<span>{contractInfo.selfDisplyName || "点击添加群昵称"}</span>
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={() => setIsEditingSelfDisplayName(true)}
/>
</div>
)}
</div>
</div>
</>
) : (
// 好友信息
<>
<div className={styles.infoItem}>
<TeamOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>
{contractInfo.alias || contractInfo.wechatId}
</span>
</div>
<div className={styles.infoItem}>
<PhoneOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.phone}</span>
</div>
<div className={styles.infoItem}>
<EnvironmentOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.region}</span>
</div>
</>
)}
{!isGroup && (
<div className={styles.infoItem}>
<EditOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<div className={styles.infoValue}>
{isEditingRemark ? (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<Input
value={remarkValue}
onChange={e => setRemarkValue(e.target.value)}
placeholder="请输入备注"
size="small"
style={{ flex: 1 }}
/>
<Button
type="text"
size="small"
icon={<CheckOutlined />}
onClick={handleSaveRemark}
style={{ color: "#52c41a" }}
/>
<Button
type="text"
size="small"
icon={<CloseOutlined />}
onClick={handleCancelEdit}
style={{ color: "#ff4d4f" }}
/>
</div>
) : (
<div
style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}
>
<span>{contractInfo.conRemark || "点击添加备注"}</span>
<Button
type="text"
size="small"
icon={<EditOutlined />}
onClick={() => setIsEditingRemark(true)}
/>
</div>
)}
</div>
</div>
)}
</Card>
{/* 标签 - 仅在非群聊时显示 */}
{!isGroup && (
<Card title="标签" className={styles.profileCard}>
<div className={styles.tagsContainer}>
{/* 渲染所有可用标签,选中的排在前面 */}
{[...new Set([...selectedTags, ...allAvailableTags])].map(
(tag, index) => {
const isSelected = selectedTags.includes(tag);
return (
<Tag
key={index}
color={isSelected ? "blue" : "default"}
style={{
cursor: "pointer",
border: isSelected
? "1px solid #1890ff"
: "1px solid #d9d9d9",
backgroundColor: isSelected ? "#e6f7ff" : "#fafafa",
}}
onClick={() => handleTagToggle(tag)}
>
{tag}
</Tag>
);
},
)}
{/* 新增标签区域 */}
{isAddingTag ? (
<div <div
style={{ style={{
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
gap: "8px", gap: "8px",
marginTop: "8px",
}} }}
> >
<Input <Input
value={remarkValue} value={newTagValue}
onChange={e => setRemarkValue(e.target.value)} onChange={e => setNewTagValue(e.target.value)}
placeholder="请输入备注" placeholder="请输入标签名称"
size="small" size="small"
style={{ flex: 1 }} style={{ width: "120px" }}
onPressEnter={handleAddTag}
/> />
<Button <Button
type="text" type="text"
size="small" size="small"
icon={<CheckOutlined />} icon={<CheckOutlined />}
onClick={handleSaveRemark} onClick={handleAddTag}
style={{ color: "#52c41a" }} style={{ color: "#52c41a" }}
/> />
<Button <Button
type="text" type="text"
size="small" size="small"
icon={<CloseOutlined />} icon={<CloseOutlined />}
onClick={handleCancelEdit} onClick={handleCancelAddTag}
style={{ color: "#ff4d4f" }} style={{ color: "#ff4d4f" }}
/> />
</div> </div>
) : ( ) : (
<Tag
style={{
cursor: "pointer",
border: "1px dashed #d9d9d9",
backgroundColor: "#fafafa",
}}
onClick={() => setIsAddingTag(true)}
>
<PlusOutlined />
</Tag>
)}
{allAvailableTags.length === 0 && !isAddingTag && (
<span style={{ color: "#999", fontSize: "12px" }}>
</span>
)}
</div>
</Card>
)}
{/* 个人简介或群公告 */}
<Card title={isGroup ? "群公告" : "个人简介"} className={styles.profileCard}>
{isGroup ? (
// 群聊简介(原群公告)
<div className={styles.infoValue}>
{isEditingGroupNotice ? (
<div <div
style={{ style={{
display: "flex", display: "flex",
@@ -228,55 +633,45 @@ const Person: React.FC<PersonProps> = ({
gap: "8px", gap: "8px",
}} }}
> >
<span>{contractInfo.conRemark || "点击添加备注"}</span> <Input.TextArea
value={groupNoticeValue}
onChange={e => setGroupNoticeValue(e.target.value)}
placeholder="请输入内容"
rows={6}
style={{ width: '100%' }}
/>
</div>
) : (
<div
style={{
display: "flex",
alignItems: "flex-start",
gap: "8px",
}}
>
<div
className={styles.bioText}
style={{
maxHeight: "120px", // 约5行文本的高度
overflowY: "auto", // 添加垂直滚动条
paddingRight: "5px", // 为滚动条留出空间
}}
>
{contractInfo.notice || "点击添加群公告"}
</div>
<Button <Button
type="text" type="text"
size="small" size="small"
icon={<EditOutlined />} icon={<EditOutlined />}
onClick={() => setIsEditingRemark(true)} onClick={() => setIsEditingGroupNotice(true)}
/> />
</div> </div>
)} )}
</div> </div>
</div> ) : (
</Card> // 个人简介
<p className={styles.bioText}>{contractInfo.bio}</p>
{/* 标签 */} )}
<Card title="标签" className={styles.profileCard}>
<div className={styles.tagsContainer}>
{/* 渲染所有可用标签,选中的排在前面 */}
{[...new Set([...selectedTags, ...allAvailableTags])].map(
(tag, index) => {
const isSelected = selectedTags.includes(tag);
return (
<Tag
key={index}
color={isSelected ? "blue" : "default"}
style={{
cursor: "pointer",
border: isSelected
? "1px solid #1890ff"
: "1px solid #d9d9d9",
backgroundColor: isSelected ? "#e6f7ff" : "#fafafa",
}}
onClick={() => handleTagToggle(tag)}
>
{tag}
</Tag>
);
},
)}
{allAvailableTags.length === 0 && (
<span style={{ color: "#999", fontSize: "12px" }}>
</span>
)}
</div>
</Card>
{/* 个人简介 */}
<Card title="个人简介" className={styles.profileCard}>
<p className={styles.bioText}>{contractInfo.bio}</p>
</Card> </Card>
{/* 操作按钮 */} {/* 操作按钮 */}
@@ -294,6 +689,36 @@ const Person: React.FC<PersonProps> = ({
</div> </div>
</div> </div>
</Sider> </Sider>
{/* 群公告编辑弹窗 */}
<Modal
title="发布群公告"
open={isGroupNoticeModalVisible}
onCancel={() => setIsGroupNoticeModalVisible(false)}
footer={[
<Button key="cancel" onClick={() => setIsGroupNoticeModalVisible(false)}>
</Button>,
<Button
key="submit"
type="primary"
onClick={() => {
handleSaveGroupNotice();
setIsGroupNoticeModalVisible(false);
}}
>
</Button>,
]}
>
<Input.TextArea
value={groupNoticeValue}
onChange={e => setGroupNoticeValue(e.target.value)}
placeholder="请输入内容"
rows={6}
style={{ width: '100%' }}
/>
</Modal>
</> </>
); );
}; };

View File

@@ -266,7 +266,7 @@ class UserController extends BaseController
* 获取验证码 * 获取验证码
* @return \think\response\Json * @return \think\response\Json
*/ */
public function getVerifyCode() public function getVerifyCode($isJson = false)
{ {
$headerData = ['client:' . self::CLIENT_TYPE]; $headerData = ['client:' . self::CLIENT_TYPE];
$header = setHeader($headerData, '', 'plain'); $header = setHeader($headerData, '', 'plain');
@@ -279,17 +279,19 @@ class UserController extends BaseController
if (is_array($response)) { if (is_array($response)) {
// 如果verifyCodeImage和verifySessionId都不为null返回它们 // 如果verifyCodeImage和verifySessionId都不为null返回它们
if (!empty($response['verifyCodeImage']) && !empty($response['verifySessionId'])) { if (!empty($response['verifyCodeImage']) && !empty($response['verifySessionId'])) {
return successJson([ $returnData = [
'verifyCodeImage' => $response['verifyCodeImage'], 'verifyCodeImage' => $response['verifyCodeImage'],
'verifySessionId' => $response['verifySessionId'] 'verifySessionId' => $response['verifySessionId']
]); ];
return !empty($isJson) ? json_encode(['code' => 200,'data' => $returnData]) : successJson($returnData);
} }
} }
// 如果不是预期的格式,返回原始数据 // 如果不是预期的格式,返回原始数据
return successJson($response); return !empty($isJson) ? json_encode(['code' => 500,'data' => $response]) : errorJson('无需验证码');
} catch (\Exception $e) { } catch (\Exception $e) {
return errorJson('获取验证码失败' . $e->getMessage()); $msg = '获取验证码失败'. $e->getMessage();
return !empty($isJson) ? json_encode(['code' => 400,'msg' => $msg]) : errorJson($msg);
} }
} }

View File

@@ -141,11 +141,16 @@ Route::group('v1/', function () {
Route::get('friendRequestTaskStats', 'app\cunkebao\controller\StatsController@getFriendRequestTaskStats'); Route::get('friendRequestTaskStats', 'app\cunkebao\controller\StatsController@getFriendRequestTaskStats');
Route::get('userInfoStats', 'app\cunkebao\controller\StatsController@userInfoStats'); Route::get('userInfoStats', 'app\cunkebao\controller\StatsController@userInfoStats');
}); });
})->middleware(['jwt']); })->middleware(['jwt']);
// 客服登录
Route::group('v1/kefu', function () {
Route::post('login', 'app\cunkebao\controller\KeFuLoginController@index'); // 获取好友列表
});
Route::group('v1/api/scenarios', function () { Route::group('v1/api/scenarios', function () {
Route::any('', 'app\cunkebao\controller\plan\PostExternalApiV1Controller@index'); Route::any('', 'app\cunkebao\controller\plan\PostExternalApiV1Controller@index');

View File

@@ -0,0 +1,78 @@
<?php
namespace app\cunkebao\controller;
use app\common\controller\BaseController;
use Exception;
use library\ResponseHelper;
use app\api\controller\UserController;
/**
* 认证控制器
* 处理用户登录和身份验证
*/
class KeFuLoginController extends BaseController
{
/**
* 用户登录
*
* @return \think\response\Json
*/
public function index($username = '', $password = '',$verifySessionId = '',$verifyCode = '')
{
$username = !empty($username) ? $username : $this->request->param('username', '');
$password = !empty($password) ? $password : $this->request->param('password', '');
$verifySessionId =!empty($verifySessionId) ? $verifySessionId : $this->request->param('verifySessionId', '');
$verifyCode = !empty($verifyCode) ? $verifyCode : $this->request->param('verifyCode', '');
if (empty($username) || empty($password)) {
return ResponseHelper::error('请输入账号密码');
}
//登录参数
$params = [
'grant_type' => 'password',
'username' => $username,
'password' => $password
];
if (!empty($verifySessionId) && !empty($verifyCode)){
$params[] = 'verifysessionid:' . $verifySessionId;
$params[] = 'verifycode:' . $verifyCode;
}
//获取验证码
// $UserController = new UserController();
// $verifyCode = $UserController->getVerifyCode(true);
// $verifyCode = json_decode($verifyCode, true);
// if ($verifyCode['code'] != 200) {
// exit_data($verifyCode);
// }
try {
// 调用登录接口获取token
$headerData = ['client:kefu-client'];
$header = setHeader($headerData, '', 'plain');
$result = requestCurl('https://s2.siyuguanli.com:9991/token', $params, 'POST', $header);
$token = handleApiResponse($result);
$userData['kefuData']['token'] = $token;
if (isset($token['access_token']) && !empty($token['access_token'])) {
$headerData = ['client:kefu-client'];
$header = setHeader($headerData, $token['access_token']);
$result = requestCurl('https://s2.siyuguanli.com:9991/api/account/self', [], 'GET', $header, 'json');
$self = handleApiResponse($result);
$userData['kefuData']['self'] = $self;
}
return ResponseHelper::success($userData, '登录成功');
} catch (Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode());
}
}
}

View File

@@ -58,7 +58,7 @@ class WorkbenchGroupPushJob
{ {
try { try {
// 获取所有工作台 // 获取所有工作台
$workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0])->order('id desc')->select(); $workbenches = Workbench::where(['status' => 1, 'type' => 3, 'isDel' => 0,'id' => 178])->order('id desc')->select();
foreach ($workbenches as $workbench) { foreach ($workbenches as $workbench) {
// 获取工作台配置 // 获取工作台配置
$config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find(); $config = WorkbenchGroupPush::where('workbenchId', $workbench->id)->find();
@@ -87,7 +87,7 @@ class WorkbenchGroupPushJob
} }
// 发微信消息 // 发微信个人消息
public function sendMsgToGroup($workbench, $config, $msgConf) public function sendMsgToGroup($workbench, $config, $msgConf)
{ {
// 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包gif、其他表情包 49:小程序/其他:图文、文件) // 消息拼接 msgType(1:文本 3:图片 43:视频 47:动图表情包gif、其他表情包 49:小程序/其他:图文、文件)
@@ -117,7 +117,6 @@ class WorkbenchGroupPushJob
} }
// 建立WebSocket // 建立WebSocket
$wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); $wsController = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
foreach ($msgConf as $content) { foreach ($msgConf as $content) {
$sendData = []; $sendData = [];
$sqlData = []; $sqlData = [];
@@ -294,82 +293,72 @@ class WorkbenchGroupPushJob
return false; return false;
} }
$limit = ($config['pushType'] == 1) ? 10 : 1; if ($config['pushType'] == 1) {
$order = ($config['pushOrder'] == 1) ? 'ci.sendTime desc, ci.id asc' : 'ci.sendTime desc, ci.id desc'; $limit = 10;
} else {
// 基础查询构建器 $limit = 1;
$baseQuery = function() use ($workbench, $contentids) {
return Db::name('content_library')->alias('cl')
->join('content_item ci', 'ci.libraryId = cl.id')
->where(['cl.isDel' => 0, 'ci.isDel' => 0])
->where('ci.sendTime <= ' . (time() + 60))
->whereIn('cl.id', $contentids)
->field('ci.id,ci.libraryId,ci.contentType,ci.title,ci.content,ci.resUrls,ci.urls,ci.comment,ci.sendTime');
};
// 获取未发送的内容
$unsentContent = $baseQuery()
->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left')
->where('wgpi.id', 'null')
->order($order)
->limit($limit)
->select();
if (!empty($unsentContent)) {
return $unsentContent;
} }
// 如果不允许循环发送,直接返回空
if ($config['isLoop'] != 1) { //推送顺序
return []; if ($config['pushOrder'] == 1) {
$order = 'ci.sendTime desc, ci.id asc';
} else {
$order = 'ci.sendTime desc, ci.id desc';
} }
// 循环发送逻辑:检查是否需要标记循环完成
$this->checkAndResetLoop($workbench->id, $contentids);
// 获取下一个要发送的内容从内容库中查询排除isLoop为0的数据 // 基础查询
$isPushIds = Db::name('workbench_group_push_item') $query = Db::name('content_library')->alias('cl')
->where(['workbenchId' => $workbench->id,'isLoop' => 0])
->column('contentId');
$nextContent = $baseQuery()
->whereNotIn('ci.id', $isPushIds)
->group('ci.id')
->order('ci.id asc')
->limit($limit)
->select();
return $nextContent;
}
/**
* 检查循环状态
* @param int $workbenchId
* @param array $contentids
*/
private function checkAndResetLoop($workbenchId, $contentids)
{
// 统计总内容数
$totalCount = Db::name('content_library')->alias('cl')
->join('content_item ci', 'ci.libraryId = cl.id') ->join('content_item ci', 'ci.libraryId = cl.id')
->join('workbench_group_push_item wgpi', 'wgpi.contentId = ci.id and wgpi.workbenchId = ' . $workbench->id, 'left')
->where(['cl.isDel' => 0, 'ci.isDel' => 0]) ->where(['cl.isDel' => 0, 'ci.isDel' => 0])
->where('ci.sendTime <= ' . (time() + 60)) ->where('ci.sendTime <= ' . (time() + 60))
->whereIn('cl.id', $contentids) ->whereIn('cl.id', $contentids)
->count(); ->field([
'ci.id',
'ci.libraryId',
'ci.contentType',
'ci.title',
'ci.content',
'ci.resUrls',
'ci.urls',
'ci.comment',
'ci.sendTime'
]);
// 复制 query
$query2 = clone $query;
$query3 = clone $query;
// 根据accountType处理不同的发送逻辑
if ($config['isLoop'] == 1) {
// 可以循环发送
// 1. 优先获取未发送的内容
$unsentContent = $query->where('wgpi.id', 'null')
->order($order)
->limit(0, $limit)
->select();
exit_data($unsentContent);
if (!empty($unsentContent)) {
return $unsentContent;
}
$lastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id desc')->find();
$fastSendData = Db::name('workbench_group_push_item')->where('workbenchId', $workbench->id)->order('id asc')->find();
// 统计已发送内容数排除isLoop为0的数据 $sentContent = $query2->where('wgpi.contentId', '<', $lastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select();
$sentCount = Db::name('workbench_group_push_item')
->alias('wgpi')
->join('content_item ci', 'ci.id = wgpi.contentId')
->join('content_library cl', 'cl.id = ci.libraryId')
->where('wgpi.workbenchId', $workbenchId)
->where('wgpi.isLoop', 0)
->whereIn('cl.id', $contentids)
->count('DISTINCT wgpi.contentId');
// 记录循环状态 if (empty($sentContent)) {
if ($sentCount >= $totalCount) { $sentContent = $query3->where('wgpi.contentId', '=', $fastSendData['contentId'])->order('wgpi.id ASC')->group('wgpi.contentId')->limit(0, $limit)->select();
Db::name('workbench_group_push_item') }
->where(['workbenchId' => $workbenchId, 'isLoop' => 0]) return $sentContent;
->update(['isLoop' => 1]); } else {
// 不能循环发送,只获取未发送的内容
$list = $query->where('wgpi.id', 'null')
->order($order)
->limit(0, $limit)
->select();
exit_data($list);
return $list;
} }
} }
@@ -422,4 +411,4 @@ class WorkbenchGroupPushJob
return false; return false;
} }
} }