From c05e77462e88831167330830ee4a17e6309e2c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Tue, 6 May 2025 15:57:22 +0800 Subject: [PATCH 01/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8F=82=E6=95=B0=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/api/devices.ts | 3 +- Cunkebao/app/devices/page.tsx | 210 +++++++++++++++++-- Server/application/cunkebao/config/route.php | 2 +- 3 files changed, 199 insertions(+), 16 deletions(-) diff --git a/Cunkebao/api/devices.ts b/Cunkebao/api/devices.ts index 8f13f92c..eb240e23 100644 --- a/Cunkebao/api/devices.ts +++ b/Cunkebao/api/devices.ts @@ -45,8 +45,8 @@ export const fetchDeviceHandleLogs = async (id: string | number, page: number = // 更新设备任务配置 export const updateDeviceTaskConfig = async ( - id: string | number, config: { + deviceId: string | number; autoAddFriend?: boolean; autoReply?: boolean; momentsSync?: boolean; @@ -54,7 +54,6 @@ export const updateDeviceTaskConfig = async ( } ): Promise> => { return api.post>(`/v1/devices/task-config`, { - id, ...config }); }; diff --git a/Cunkebao/app/devices/page.tsx b/Cunkebao/app/devices/page.tsx index 2aa2e5ab..6a8eaa96 100644 --- a/Cunkebao/app/devices/page.tsx +++ b/Cunkebao/app/devices/page.tsx @@ -47,6 +47,20 @@ export default function DevicesPage() { const [isLoadingQRCode, setIsLoadingQRCode] = useState(false) const [isSubmittingImei, setIsSubmittingImei] = useState(false) const [activeTab, setActiveTab] = useState("scan") + const [pollingStatus, setPollingStatus] = useState<{ + isPolling: boolean; + message: string; + messageType: 'default' | 'success' | 'error'; + showAnimation: boolean; + }>({ + isPolling: false, + message: '', + messageType: 'default', + showAnimation: false + }); + + // 添加轮询定时器引用 + const pollingTimerRef = useRef(null); const devicesPerPage = 20 // 每页显示20条记录 @@ -315,12 +329,127 @@ export default function DevicesPage() { } } - // 打开添加设备模态框时获取二维码 + // 清理轮询函数 + const cleanupPolling = useCallback(() => { + if (pollingTimerRef.current) { + clearTimeout(pollingTimerRef.current); + pollingTimerRef.current = null; + } + setPollingStatus({ + isPolling: false, + message: '', + messageType: 'default', + showAnimation: false + }); + }, []); + + // 轮询检测设备添加状态 + const startPolling = useCallback(() => { + let pollCount = 0; + const maxPolls = 60; + const pollInterval = 1000; // 1秒 + const initialDelay = 5000; // 5秒后开始轮询 + + // 初始提示 + setPollingStatus({ + isPolling: false, + message: '请扫描二维码添加设备,5秒后将开始检测添加结果', + messageType: 'default', + showAnimation: false + }); + + const poll = async () => { + try { + const accountId = localStorage.getItem('s2_accountId'); + if (!accountId) { + setPollingStatus({ + isPolling: false, + message: '未获取到用户信息,请重新登录', + messageType: 'error', + showAnimation: false + }); + return; + } + + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/devices/add-results?accountId=${accountId}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + }); + + const data = await response.json(); + + if (data.code === 200 && data.data) { + if (data.data.added) { + setPollingStatus({ + isPolling: false, + message: '设备添加成功。关闭后可继续', + messageType: 'success', + showAnimation: false + }); + setQrCodeImage('/broken-qr.png'); // 显示损坏的二维码 + cleanupPolling(); + return; + } + } + + pollCount++; + if (pollCount >= maxPolls) { + setPollingStatus({ + isPolling: false, + message: '未检测到设备添加,请关闭后重试', + messageType: 'error', + showAnimation: false + }); + cleanupPolling(); + return; + } + + pollingTimerRef.current = setTimeout(poll, pollInterval); + } catch (error) { + setPollingStatus({ + isPolling: false, + message: '检测过程中发生错误,请重试', + messageType: 'error', + showAnimation: false + }); + cleanupPolling(); + } + }; + + // 5秒后开始轮询 + pollingTimerRef.current = setTimeout(() => { + setPollingStatus({ + isPolling: true, + message: '正在检测添加结果', + messageType: 'default', + showAnimation: true + }); + poll(); + }, initialDelay); + }, [cleanupPolling]); + + // 修改打开添加设备模态框的处理函数 const handleOpenAddDeviceModal = () => { - setIsAddDeviceOpen(true) - setDeviceImei("") - setDeviceName("") - fetchDeviceQRCode() + setIsAddDeviceOpen(true); + setDeviceImei(""); + setDeviceName(""); + setQrCodeImage(""); + setPollingStatus({ + isPolling: false, + message: '', + messageType: 'default', + showAnimation: false + }); + fetchDeviceQRCode(); + startPolling(); + } + + // 修改关闭模态框的处理函数 + const handleCloseAddDeviceModal = () => { + cleanupPolling(); + setIsAddDeviceOpen(false); } // 通过IMEI添加设备 @@ -484,7 +613,10 @@ export default function DevicesPage() { try { const s2_accountId = localStorage.getItem('s2_accountId'); if (!s2_accountId) { - toast.error('未获取到用户信息,请重新登录'); + toast({ + title: '未获取到用户信息,请重新登录', + variant: 'destructive', + }); return; } @@ -503,16 +635,25 @@ export default function DevicesPage() { const data = await response.json(); if (data.code === 200) { - toast.success('添加设备成功'); + toast({ + title: '添加设备成功', + variant: 'default', + }); setIsAddDeviceOpen(false); // 刷新设备列表 loadDevices(1, true); } else { - toast.error(data.msg || '添加设备失败'); + toast({ + title: data.msg || '添加设备失败', + variant: 'destructive', + }); } } catch (error) { console.error('添加设备失败:', error); - toast.error('添加设备失败,请稍后重试'); + toast({ + title: '添加设备失败,请稍后重试', + variant: 'destructive', + }); } }; @@ -651,8 +792,12 @@ export default function DevicesPage() { - {/* 添加设备对话框 */} - + {/* 修改添加设备对话框 */} + { + if (!open) { + handleCloseAddDeviceModal(); + } + }}> 添加设备 @@ -671,7 +816,24 @@ export default function DevicesPage() { */} -
+
+ {/* 悬浮提示区域,上移50% */} +
+
+ {pollingStatus.isPolling || pollingStatus.showAnimation ? ( + <> + 正在检测添加结果 +
+
+
+
+
+ + ) : ( + 5秒后将开始检测添加结果 + )} +
+
{isLoadingQRCode ? (
@@ -719,7 +881,7 @@ export default function DevicesPage() { type="button" onClick={fetchDeviceQRCode} disabled={isLoadingQRCode} - className="w-48" + className="w-48 mt-8" > {isLoadingQRCode ? ( <> @@ -776,6 +938,28 @@ export default function DevicesPage() {
+ + {/* 在二维码显示区域下方添加状态提示 */} +
+ {pollingStatus.message && ( +
+

+ {pollingStatus.message} +

+ {pollingStatus.showAnimation && ( +
+
+
+
+
+ )} +
+ )} +
) } diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 309a6dfd..744fdba2 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -10,6 +10,7 @@ Route::group('v1/', function () { // 设备管理相关 Route::group('devices', function () { + Route::get('add-results', 'app\cunkebao\controller\device\GetAddResultedDevicesController@index'); // 更新设备任务配置 Route::get(':id/related-accounts', 'app\cunkebao\controller\device\GetRelatedAccountsV1Controller@index'); // 设备关联微信账号路由 Route::get(':id/handle-logs', 'app\cunkebao\controller\device\GetDeviceHandleLogsV1Controller@index'); // 获取设备操作记录 Route::get('', 'app\cunkebao\controller\device\GetDeviceListV1Controller@index'); // 获取设备列表 @@ -18,7 +19,6 @@ Route::group('v1/', function () { Route::put('refresh', 'app\cunkebao\controller\device\RefreshDeviceDetailV1Controller@index'); // 刷新设备状态 Route::delete(':id', 'app\cunkebao\controller\Device@delete'); // 删除设备 Route::post('task-config', 'app\cunkebao\controller\device\UpdateDeviceTaskConfigV1Controller@index'); - Route::get('add-results', 'app\cunkebao\controller\device\GetAddResultedDevicesController@index'); // 更新设备任务配置 }); // 设备微信相关 From e770d668d78bf2c84f800bd1a21df24713b9f6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Tue, 6 May 2025 16:11:48 +0800 Subject: [PATCH 02/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E5=A4=8D=E6=A0=87=E7=AD=BE=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B5=E9=9D=A2=E6=89=93=E5=BC=80=E6=8A=A5=E9=94=99?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/plans/new/steps/TagSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cunkebao/app/plans/new/steps/TagSettings.tsx b/Cunkebao/app/plans/new/steps/TagSettings.tsx index e02294a9..54c00610 100644 --- a/Cunkebao/app/plans/new/steps/TagSettings.tsx +++ b/Cunkebao/app/plans/new/steps/TagSettings.tsx @@ -35,7 +35,7 @@ export function TagSettings({ formData, onChange, onNext, onPrev }: TagSettingsP // 当标签更新时,更新formData useEffect(() => { onChange({ ...formData, tags }) - }, [tags, onChange, formData]) + }, [tags, onChange]) // 检查是否有标签 useEffect(() => { @@ -145,7 +145,7 @@ export function TagSettings({ formData, onChange, onNext, onPrev }: TagSettingsP )} {hasWarnings && ( - + 建议添加至少一个标签,以便更好地管理客户。 From 8c4a899b4346398659ea5e4c5be157e3c15ae2a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Tue, 6 May 2025 17:47:30 +0800 Subject: [PATCH 03/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E6=94=B9=E6=A8=A1=E5=9D=97=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=99=A8=E5=85=A8=E9=83=A8=E7=BB=A7=E6=89=BF=E5=9F=BA?= =?UTF-8?q?=E7=B1=BB=E4=BB=A5=E5=AE=9E=E7=8E=B0=E6=9B=B4=E5=8A=A0=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E7=9A=84=E6=93=8D=E4=BD=9C=E8=A7=84=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/cunkebao/config/route.php | 1 + .../application/cunkebao/controller/Plan.php | 21 +++++++++++++++++++ .../GetAddResultedDevicesController.php | 4 ++-- .../device/GetDeviceListV1Controller.php | 3 ++- .../device/PostAddDeviceV1Controller.php | 3 ++- .../UpdateDeviceTaskConfigV1Controller.php | 14 ++++++------- 6 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 Server/application/cunkebao/controller/Plan.php diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 744fdba2..cc691213 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -35,6 +35,7 @@ Route::group('v1/', function () { // 获客场景相关 Route::group('plan/scenes', function () { Route::get('', 'app\cunkebao\controller\Scene@index'); // 获取场景列表 + Route::post('create', 'app\cunkebao\controller\Plan@index'); // 获取场景列表 }); // 流量标签相关 diff --git a/Server/application/cunkebao/controller/Plan.php b/Server/application/cunkebao/controller/Plan.php new file mode 100644 index 00000000..42de7dfd --- /dev/null +++ b/Server/application/cunkebao/controller/Plan.php @@ -0,0 +1,21 @@ +field(['d.id', 'd.imei', 'd.memo', 'l.wechatId', 'd.alive','wa.nickname','wa.alias', '0 totalFriend']) + ->field(['d.id', 'd.imei', 'd.memo', 'l.wechatId', 'd.alive', 'wa.nickname', 'wa.alias', '0 totalFriend']) ->leftJoin('device_wechat_login l', 'd.id = l.deviceId') ->leftJoin('wechat_account wa', 'l.wechatId = wa.wechatId') ->order('d.id desc'); diff --git a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php index f8232194..cc44d91e 100644 --- a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php +++ b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php @@ -1,4 +1,5 @@ setMethod() + // $curl->setMethod() } /** diff --git a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php index 0b1fb4c7..e1da2e00 100644 --- a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php +++ b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php @@ -1,13 +1,13 @@ request->post(); $content = null; - if (isset($data['autoAddFriend']))/**/$content = $data['autoAddFriend'] ? '开启自动添加好友' : '关闭自动添加好友'; - if (isset($data['autoReply']))/* */$content = $data['autoReply'] ? '开启自动回复' : '关闭自动回复'; - if (isset($data['momentsSync']))/* */$content = $data['momentsSync'] ? '开启朋友圈同步' : '关闭朋友圈同步'; - if (isset($data['aiChat']))/* */$content = $data['aiChat'] ? '开启AI会话' : '关闭AI会话'; + if (isset($data['autoAddFriend']))/**/ $content = $data['autoAddFriend'] ? '开启自动添加好友' : '关闭自动添加好友'; + if (isset($data['autoReply']))/* */ $content = $data['autoReply'] ? '开启自动回复' : '关闭自动回复'; + if (isset($data['momentsSync']))/* */ $content = $data['momentsSync'] ? '开启朋友圈同步' : '关闭朋友圈同步'; + if (isset($data['aiChat']))/* */ $content = $data['aiChat'] ? '开启AI会话' : '关闭AI会话'; if (empty($content)) { throw new \Exception('参数错误', '400'); From 21a6907e528de9154165b27cca417fa77fc97a21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Wed, 7 May 2025 10:49:28 +0800 Subject: [PATCH 04/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E8=AE=A1=E5=88=92=E4=BB=BB=E5=8A=A1=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/page.tsx | 24 +- Cunkebao/app/plans/new/page.tsx | 84 ++++++- Cunkebao/app/scenarios/[channel]/page.tsx | 150 ++++++++++- Server/application/cunkebao/config/route.php | 12 +- .../application/cunkebao/controller/Plan.php | 235 +++++++++++++++++- 5 files changed, 485 insertions(+), 20 deletions(-) diff --git a/Cunkebao/app/page.tsx b/Cunkebao/app/page.tsx index a14c56be..07f2a189 100644 --- a/Cunkebao/app/page.tsx +++ b/Cunkebao/app/page.tsx @@ -154,6 +154,7 @@ export default function Home() { color: "bg-orange-100 text-orange-600", value: 167, growth: 10, + sceneId: 7, }, ] @@ -209,7 +210,11 @@ export default function Home() { {scenarioFeatures .sort((a, b) => b.value - a.value) .map((scenario) => ( - +
router.push(`/scenarios/${scenario.id}?id=${scenario.sceneId || getSceneIdFromType(scenario.id)}`)} + >
{scenario.name} @@ -219,7 +224,7 @@ export default function Home() { {scenario.name}
- +
))} @@ -236,3 +241,18 @@ export default function Home() { ) } +function getSceneIdFromType(type: string): number { + const typeToIdMap: Record = { + 'douyin': 1, + 'xiaohongshu': 2, + 'weixinqun': 3, + 'gongzhonghao': 4, + 'kuaishou': 5, + 'weibo': 6, + 'haibao': 7, + 'phone': 8, + 'api': 9 + }; + return typeToIdMap[type] || 1; +} + diff --git a/Cunkebao/app/plans/new/page.tsx b/Cunkebao/app/plans/new/page.tsx index 99f4cf8c..16c75ac4 100644 --- a/Cunkebao/app/plans/new/page.tsx +++ b/Cunkebao/app/plans/new/page.tsx @@ -123,14 +123,82 @@ export default function NewAcquisitionPlan() { // 根据标签和计划名称自动判断场景 const scenario = formData.scenario || determineScenario(formData.planName, formData.tags) - console.log("计划已创建:", { ...formData, scenario }) - toast({ - title: "创建成功", - description: "获客计划已创建完成", - }) + // 准备请求数据 + const requestData = { + sceneId: getSceneIdFromScenario(scenario), // 获取场景ID + name: formData.planName, + status: formData.enabled ? 1 : 0, + reqConf: JSON.stringify({ + remarkType: formData.remarkType, + remarkKeyword: formData.remarkKeyword, + greeting: formData.greeting, + addFriendTimeStart: formData.addFriendTimeStart, + addFriendTimeEnd: formData.addFriendTimeEnd, + addFriendInterval: formData.addFriendInterval, + maxDailyFriends: formData.maxDailyFriends, + selectedDevices: formData.selectedDevices, + }), + msgConf: JSON.stringify({ + messageInterval: formData.messageInterval, + messagePlans: formData.messagePlans, + }), + tagConf: JSON.stringify({ + tags: formData.tags, + importedTags: formData.importedTags, + }) + } - // 跳转到首页 - router.push("/") + // 调用API创建获客计划 + fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/plan/scenes/create`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + }, + body: JSON.stringify(requestData) + }) + .then(response => response.json()) + .then(data => { + if (data.code === 200) { + toast({ + title: "创建成功", + description: "获客计划已创建完成", + }) + // 跳转到首页 + router.push("/") + } else { + toast({ + title: "创建失败", + description: data.msg || "服务器错误,请稍后重试", + variant: "destructive" + }) + } + }) + .catch(error => { + console.error("创建获客计划失败:", error); + toast({ + title: "创建失败", + description: "网络错误,请稍后重试", + variant: "destructive" + }) + }) + } + + // 将场景类型转换为场景ID + const getSceneIdFromScenario = (scenario: string): number => { + const scenarioMap: Record = { + 'douyin': 1, + 'xiaohongshu': 2, + 'weixinqun': 3, + 'gongzhonghao': 4, + 'kuaishou': 5, + 'weibo': 6, + 'haibao': 7, + 'phone': 8, + 'order': 9, + 'other': 10 + } + return scenarioMap[scenario] || 1 // 默认返回1 } const handlePrev = () => { @@ -206,7 +274,7 @@ export default function NewAcquisitionPlan() { case 3: return case 4: - return + return default: return null } diff --git a/Cunkebao/app/scenarios/[channel]/page.tsx b/Cunkebao/app/scenarios/[channel]/page.tsx index 072e8266..18004ce8 100644 --- a/Cunkebao/app/scenarios/[channel]/page.tsx +++ b/Cunkebao/app/scenarios/[channel]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { ChevronLeft, Copy, Link, HelpCircle } from "lucide-react" import { Button } from "@/components/ui/button" import { useRouter } from "next/navigation" @@ -35,6 +35,7 @@ const getChannelName = (channel: string) => { return channelMap[channel] || channel } +// 恢复Task接口定义 interface Task { id: string name: string @@ -50,6 +51,21 @@ interface Task { trend: { date: string; customers: number }[] } +interface PlanItem { + id: number; + name: string; + status: number; + statusText: string; + createTime: number; + createTimeFormat: string; + deviceCount: number; + customerCount: number; + addedCount: number; + passRate: number; + lastExecutionTime: string; + nextExecutionTime: string; +} + interface DeviceStats { active: number } @@ -76,6 +92,23 @@ export default function ChannelPage({ params }: { params: { channel: string } }) const router = useRouter() const channel = params.channel const channelName = getChannelName(params.channel) + + // 从URL query参数获取场景ID + const [sceneId, setSceneId] = useState(null); + + // 获取URL中的查询参数 + useEffect(() => { + // 从URL获取id参数 + const urlParams = new URLSearchParams(window.location.search); + const idParam = urlParams.get('id'); + + if (idParam && !isNaN(Number(idParam))) { + setSceneId(Number(idParam)); + } else { + // 如果没有传递有效的ID,使用函数获取默认ID + setSceneId(getSceneIdFromChannel(channel)); + } + }, [channel]); const initialTasks = [ { @@ -114,7 +147,9 @@ export default function ChannelPage({ params }: { params: { channel: string } }) }, ] - const [tasks, setTasks] = useState(initialTasks) + const [tasks, setTasks] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) const [deviceStats, setDeviceStats] = useState({ active: 5, @@ -144,7 +179,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) toast({ title: "计划已复制", description: `已成功复制"${taskToCopy.name}"`, - variant: "success", + variant: "default", }) } } @@ -156,7 +191,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) toast({ title: "计划已删除", description: `已成功删除"${taskToDelete.name}"`, - variant: "success", + variant: "default", }) } } @@ -167,7 +202,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) toast({ title: newStatus === "running" ? "计划已启动" : "计划已暂停", description: `已${newStatus === "running" ? "启动" : "暂停"}获客计划`, - variant: "success", + variant: "default", }) } @@ -192,10 +227,95 @@ export default function ChannelPage({ params }: { params: { channel: string } }) toast({ title: "已复制", description: withParams ? "接口地址(含示例参数)已复制到剪贴板" : "接口地址已复制到剪贴板", - variant: "success", + variant: "default", }) } + // 修改API数据处理部分 + useEffect(() => { + const fetchPlanList = async () => { + try { + setLoading(true); + // 如果sceneId还未确定,则等待 + if (sceneId === null) return; + + // 调用API获取计划列表 + const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/plan/list?id=${sceneId}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${localStorage.getItem('token')}` + } + } + ); + + const data = await response.json(); + + if (data.code === 1 && data.data && data.data.list) { + // 将API返回的数据转换为前端展示格式 + const transformedTasks = data.data.list.map((item: PlanItem) => { + // 确保返回的对象完全符合Task类型 + const status: "running" | "paused" | "completed" = + item.status === 1 ? "running" : "paused"; + + return { + id: item.id.toString(), + name: item.name, + status: status, + stats: { + devices: item.deviceCount, + acquired: item.customerCount, + added: item.addedCount, + }, + lastUpdated: item.createTimeFormat, + executionTime: item.lastExecutionTime || "--", + nextExecutionTime: item.nextExecutionTime || "--", + trend: Array.from({ length: 7 }, (_, i) => ({ + date: `2月${String(i + 1)}日`, + customers: Math.floor(Math.random() * 20) + 10, // 模拟数据 + })), + }; + }); + + // 使用类型断言解决类型冲突 + setTasks(transformedTasks as Task[]); + setError(null); + } else { + setError(data.msg || "获取计划列表失败"); + // 如果API返回错误,使用初始数据 + setTasks(initialTasks); + } + } catch (err) { + console.error("获取计划列表失败:", err); + setError("网络错误,无法获取计划列表"); + // 出错时使用初始数据 + setTasks(initialTasks); + } finally { + setLoading(false); + } + }; + + fetchPlanList(); + }, [channel, initialTasks, sceneId]); + + // 辅助函数:根据渠道获取场景ID + const getSceneIdFromChannel = (channel: string): number => { + const channelMap: Record = { + 'douyin': 1, + 'xiaohongshu': 2, + 'weixinqun': 3, + 'gongzhonghao': 4, + 'kuaishou': 5, + 'weibo': 6, + 'haibao': 7, + 'phone': 8, + 'api': 9 + }; + return channelMap[channel] || 6; + }; + return (
@@ -210,7 +330,21 @@ export default function ChannelPage({ params }: { params: { channel: string } })
- {tasks.length > 0 ? ( + {loading ? ( + // 添加加载状态 +
+
+
加载计划中...
+
+ ) : error ? ( + // 添加错误提示 +
+
{error}
+ +
+ ) : tasks.length > 0 ? ( tasks.map((task) => (
diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index cc691213..692e55d0 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -81,4 +81,14 @@ Route::group('v1/', function () { }); -})->middleware(['jwt']); \ No newline at end of file + // 计划任务相关路由 + Route::group('plan', function () { + // 添加计划任务 + Route::post('add', 'app\cunkebao\controller\Plan@index'); + // 获取计划任务列表 + Route::get('list', 'app\cunkebao\controller\Plan@getList'); + }); + +})->middleware(['jwt']); + +return []; \ No newline at end of file diff --git a/Server/application/cunkebao/controller/Plan.php b/Server/application/cunkebao/controller/Plan.php index 42de7dfd..2c5f1478 100644 --- a/Server/application/cunkebao/controller/Plan.php +++ b/Server/application/cunkebao/controller/Plan.php @@ -3,6 +3,9 @@ namespace app\cunkebao\controller; use think\Controller; +use think\Db; +use think\facade\Request; +use library\ResponseHelper; /** * 获客场景控制器 @@ -10,12 +13,242 @@ use think\Controller; class Plan extends Controller { /** - * 计划任务 + * 添加计划任务 * * @return \think\response\Json */ public function index() { + try { + // 获取表单数据 + $data = [ + 'name' => Request::post('name', ''), + 'sceneId' => Request::post('sceneId', 0), + 'status' => Request::post('status', 0), + 'reqConf' => Request::post('reqConf', ''), + 'msgConf' => Request::post('msgConf', ''), + 'tagConf' => Request::post('tagConf', ''), + 'createTime' => time(), + 'updateTime' => time() + ]; + // 验证必填字段 + if (empty($data['name'])) { + return ResponseHelper::error('计划名称不能为空', 400); + } + + if (empty($data['sceneId'])) { + return ResponseHelper::error('场景ID不能为空', 400); + } + + // 验证数据格式 + if (!$this->validateJson($data['reqConf'])) { + return ResponseHelper::error('好友申请设置格式不正确', 400); + } + + if (!$this->validateJson($data['msgConf'])) { + return ResponseHelper::error('消息设置格式不正确', 400); + } + + if (!$this->validateJson($data['tagConf'])) { + return ResponseHelper::error('标签设置格式不正确', 400); + } + + // 插入数据库 + $result = Db::name('friend_plan')->insert($data); + + if ($result) { + return ResponseHelper::success([], '添加计划任务成功'); + } else { + return ResponseHelper::error('添加计划任务失败', 500); + } + } catch (\Exception $e) { + return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500); + } + } + + /** + * 获取计划任务列表 + * + * @return \think\response\Json + */ + public function getList() + { + try { + // 获取分页参数 + $id = Request::param('id', 1); + $page = Request::param('page', 1); + $pageSize = Request::param('pageSize', 10); + + // 构建查询条件 + $where = []; + + // 过滤已删除的记录 + $where[] = ['deleteTime', 'null']; + + // 查询总数 + $total = Db::name('friend_plan')->where('sceneId', $id)->count(); + + // 查询列表数据 + $list = Db::name('friend_plan') + ->where('sceneId', $id) + ->field('id, name, status, createTime, updateTime, sceneId') + ->order('createTime desc') + ->page($page, $pageSize) + ->select(); + // 遍历列表,获取每个计划的统计信息 + foreach ($list as &$item) { + // 获取计划的统计信息 + $stats = $this->getPlanStats($item['id']); + + // 合并统计信息到结果中 + $item = array_merge($item, $stats); + + // 格式化状态为文字描述 + $item['statusText'] = $item['status'] == 1 ? '进行中' : '已暂停'; + + // 格式化时间 + $item['createTimeFormat'] = date('Y-m-d H:i', $item['createTime']); + + // 获取最近一次执行时间 + $lastExecution = $this->getLastExecution($item['id']); + $item['lastExecutionTime'] = $lastExecution['lastTime'] ?? ''; + $item['nextExecutionTime'] = $lastExecution['nextTime'] ?? ''; + } + + // 返回结果 + $result = [ + 'total' => $total, + 'list' => $list, + 'page' => $page, + 'pageSize' => $pageSize + ]; + + return ResponseHelper::success($result); + } catch (\Exception $e) { + return ResponseHelper::error('获取数据失败: ' . $e->getMessage(), 500); + } + } + + /** + * 获取计划的统计信息 + * + * @param int $planId 计划ID + * @return array + */ + private function getPlanStats($planId) + { + try { + // 获取设备数 + $deviceCount = $this->getDeviceCount($planId); + + // 获取已获客数 + $customerCount = $this->getCustomerCount($planId); + + // 获取已添加数 + $addedCount = 1; //$this->getAddedCount($planId); + + // 计算通过率 + $passRate = $customerCount > 0 ? round(($addedCount / $customerCount) * 100) : 0; + + return [ + 'deviceCount' => $deviceCount, + 'customerCount' => $customerCount, + 'addedCount' => $addedCount, + 'passRate' => $passRate + ]; + } catch (\Exception $e) { + return [ + 'deviceCount' => 0, + 'customerCount' => 0, + 'addedCount' => 0, + 'passRate' => 0 + ]; + } + } + + /** + * 获取计划使用的设备数 + * + * @param int $planId 计划ID + * @return int + */ + private function getDeviceCount($planId) + { + try { + // 获取计划 + $plan = Db::name('friend_plan')->where('id', $planId)->find(); + if (!$plan) { + return 0; + } + + // 解析reqConf + $reqConf = json_decode($plan['reqConf'], true); + + // 返回设备数量 + return isset($reqConf['selectedDevices']) ? count($reqConf['selectedDevices']) : 0; + } catch (\Exception $e) { + return 0; + } + } + + /** + * 获取计划的已获客数 + * + * @param int $planId 计划ID + * @return int + */ + private function getCustomerCount($planId) + { + // 模拟数据,实际应从相关表获取 + return rand(10, 50); + } + + /** + * 获取计划的已添加数 + * + * @param int $planId 计划ID + * @return int + */ + private function getAddedCount($planId) + { + // 模拟数据,实际应从相关表获取 + $customerCount = $this->getCustomerCount($planId); + return rand(5, $customerCount); + } + + /** + * 获取计划的最近一次执行时间 + * + * @param int $planId 计划ID + * @return array + */ + private function getLastExecution($planId) + { + // 模拟数据,实际应从执行记录表获取 + $now = time(); + $lastTime = $now - rand(3600, 86400); + $nextTime = $now + rand(3600, 86400); + + return [ + 'lastTime' => date('Y-m-d H:i', $lastTime), + 'nextTime' => date('Y-m-d H:i:s', $nextTime) + ]; + } + + /** + * 验证JSON格式是否正确 + * + * @param string $string + * @return bool + */ + private function validateJson($string) + { + if (empty($string)) { + return true; + } + + json_decode($string); + return (json_last_error() == JSON_ERROR_NONE); } } \ No newline at end of file From 6fa41e3f4a43a347905ef0ac266ab3b5f01bad55 Mon Sep 17 00:00:00 2001 From: xavier Date: Wed, 7 May 2025 17:43:39 +0800 Subject: [PATCH 05/16] submit cursor rule --- Server/.cursor/rules/01-project-overview.mdc | 31 + .../.cursor/rules/02-directory-structure.mdc | 50 + Server/.cursor/rules/03-main-modules.mdc | 66 + .../.cursor/rules/04-development-workflow.mdc | 71 + Server/.cursor/rules/05-api-reference.mdc | 85 + Server/.cursor/rules/06-data-models.mdc | 96 + .../rules/07-websocket-communication.mdc | 104 + Server/composer.json | 2 +- Server/config/wechat_device_api.php | 19 + .../Adapters/ChuKeBao/Adapter.php | 98 + .../Contracts/WeChatServiceInterface.php | 54 + .../Exceptions/ApiException.php | 7 + Server/extend/WeChatDeviceApi/Manager.php | 94 + Server/thinkphp/library/think/App.php | 1958 ++++++++--------- 14 files changed, 1755 insertions(+), 980 deletions(-) create mode 100644 Server/.cursor/rules/01-project-overview.mdc create mode 100644 Server/.cursor/rules/02-directory-structure.mdc create mode 100644 Server/.cursor/rules/03-main-modules.mdc create mode 100644 Server/.cursor/rules/04-development-workflow.mdc create mode 100644 Server/.cursor/rules/05-api-reference.mdc create mode 100644 Server/.cursor/rules/06-data-models.mdc create mode 100644 Server/.cursor/rules/07-websocket-communication.mdc create mode 100644 Server/config/wechat_device_api.php create mode 100644 Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php create mode 100644 Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php create mode 100644 Server/extend/WeChatDeviceApi/Exceptions/ApiException.php create mode 100644 Server/extend/WeChatDeviceApi/Manager.php diff --git a/Server/.cursor/rules/01-project-overview.mdc b/Server/.cursor/rules/01-project-overview.mdc new file mode 100644 index 00000000..76fb1db1 --- /dev/null +++ b/Server/.cursor/rules/01-project-overview.mdc @@ -0,0 +1,31 @@ +--- +description: +globs: +alwaysApply: false +--- +# Project Overview + +存客宝微信管理系统是基于ThinkPHP 5.1和PHP 7.4的微信账号管理和营销自动化平台。 + +## 主要功能 + +- **设备管理**: 设备添加、删除、状态监控和任务配置 +- **微信账号管理**: 微信账号列表、微信好友管理和好友转移功能 +- **内容库**: 内容创建与管理、朋友圈内容采集和内容分类管理 +- **工作台功能**: 自动点赞、朋友圈同步、群消息推送和自动建群 +- **流量管理**: 流量标签、流量池管理和订单导入 + +## 核心文件 + +- 入口文件: [public/index.php](mdc:public/index.php) +- 应用配置: [config/app.php](mdc:config/app.php) +- 数据库配置: [config/database.php](mdc:config/database.php) +- 路由配置: [route/route.php](mdc:route/route.php) +- 公共函数: [application/common.php](mdc:application/common.php) + +## 环境要求 + +- PHP >= 7.0 (推荐 PHP 7.4) +- MySQL 5.6+ +- Composer +- ThinkPHP 5.1所需的PHP扩展 diff --git a/Server/.cursor/rules/02-directory-structure.mdc b/Server/.cursor/rules/02-directory-structure.mdc new file mode 100644 index 00000000..2ab8a4e7 --- /dev/null +++ b/Server/.cursor/rules/02-directory-structure.mdc @@ -0,0 +1,50 @@ +--- +description: +globs: +alwaysApply: false +--- +# 目录结构 + +整个项目遵循ThinkPHP 5.1的目录结构规范,主要包含以下目录: + +## 核心目录 + +- **application/** - 应用目录,包含所有模块和业务逻辑 + - [api/](mdc:application/api) - API模块,提供RESTful API接口 + - [command/](mdc:application/command) - 命令行脚本 + - [common/](mdc:application/common) - 公共模块,包含共享功能 + - [cunkebao/](mdc:application/cunkebao) - 村客宝主模块 + - [store/](mdc:application/store) - 商店模块 + - [superadmin/](mdc:application/superadmin) - 超级管理员模块 + - [cozeai/](mdc:application/cozeai) - 智能AI模块 + - [job/](mdc:application/job) - 后台任务模块 + +- **config/** - 配置目录 + - [app.php](mdc:config/app.php) - 应用配置 + - [database.php](mdc:config/database.php) - 数据库配置 + - [queue.php](mdc:config/queue.php) - 队列配置 + - [worker.php](mdc:config/worker.php) - Worker配置 + +- **extend/** - 扩展目录,存放第三方扩展类库 + +- **public/** - 公共资源目录,Web访问入口 + - [index.php](mdc:public/index.php) - 入口文件 + +- **route/** - 路由目录 + - [route.php](mdc:route/route.php) - 路由配置 + +- **runtime/** - 运行时目录,存放日志和缓存文件 + +- **thinkphp/** - ThinkPHP框架核心目录 + +- **vendor/** - Composer依赖目录 + +## 模块结构 + +每个模块通常包含以下目录结构: + +- **controller/** - 控制器 +- **model/** - 数据模型 +- **service/** - 服务层 +- **validate/** - 验证器 +- **config/** - 模块配置,包括独立的路由配置 diff --git a/Server/.cursor/rules/03-main-modules.mdc b/Server/.cursor/rules/03-main-modules.mdc new file mode 100644 index 00000000..107c6a76 --- /dev/null +++ b/Server/.cursor/rules/03-main-modules.mdc @@ -0,0 +1,66 @@ +--- +description: +globs: +alwaysApply: false +--- +# 主要模块 + +村客宝系统由多个功能模块组成,各模块负责不同的业务功能: + +## API模块 + +[application/api/](mdc:application/api) 提供RESTful API服务,主要包括: + +- [DeviceController.php](mdc:application/api/controller/DeviceController.php) - 设备相关API +- [WechatController.php](mdc:application/api/controller/WechatController.php) - 微信账号相关API +- [WechatFriendController.php](mdc:application/api/controller/WechatFriendController.php) - 微信好友相关API +- [WechatChatroomController.php](mdc:application/api/controller/WechatChatroomController.php) - 微信群相关API +- [MessageController.php](mdc:application/api/controller/MessageController.php) - 消息相关API +- [MomentsController.php](mdc:application/api/controller/MomentsController.php) - 朋友圈相关API +- [WebSocketController.php](mdc:application/api/controller/WebSocketController.php) - WebSocket通信API + +## 村客宝主模块 + +[application/cunkebao/](mdc:application/cunkebao) 是系统的核心模块,包含主要业务逻辑: + +- [Device.php](mdc:application/cunkebao/controller/Device.php) - 设备管理控制器 +- [DeviceWechat.php](mdc:application/cunkebao/controller/DeviceWechat.php) - 设备微信管理 +- [ContentLibraryController.php](mdc:application/cunkebao/controller/ContentLibraryController.php) - 内容库管理 +- [WorkbenchController.php](mdc:application/cunkebao/controller/WorkbenchController.php) - 工作台功能 +- [TrafficPool.php](mdc:application/cunkebao/controller/TrafficPool.php) - 流量池管理 +- [TrafficTag.php](mdc:application/cunkebao/controller/TrafficTag.php) - 流量标签管理 + +## 公共模块 + +[application/common/](mdc:application/common) 包含系统共享的功能和工具: + +- [service/](mdc:application/common/service) - 公共服务 +- [model/](mdc:application/common/model) - 核心数据模型 +- [util/](mdc:application/common/util) - 工具类 +- [socket/](mdc:application/common/socket) - WebSocket通信 +- [middleware/](mdc:application/common/middleware) - 中间件 + +## 命令行模块 + +[application/command/](mdc:application/command) 包含系统的命令行工具: + +- 定时任务 +- 队列处理 +- 数据迁移 +- 系统维护 + +## 存储模块 + +[application/store/](mdc:application/store) 提供资源存储相关的功能: + +- 图片存储 +- 文件上传 +- 资源管理 + +## 超级管理员模块 + +[application/superadmin/](mdc:application/superadmin) 提供系统管理功能: + +- 用户管理 +- 权限管理 +- 系统配置 diff --git a/Server/.cursor/rules/04-development-workflow.mdc b/Server/.cursor/rules/04-development-workflow.mdc new file mode 100644 index 00000000..8e177beb --- /dev/null +++ b/Server/.cursor/rules/04-development-workflow.mdc @@ -0,0 +1,71 @@ +--- +description: +globs: +alwaysApply: false +--- +# 开发工作流程 + +本项目基于ThinkPHP 5.1框架开发,遵循MVC设计模式。开发时请参考以下工作流程和规范: + +## 框架特性 + +ThinkPHP 5.1使用以下特性: +- 命名空间和自动加载 +- 依赖注入和容器 +- 门面(Facade)模式 +- 中间件机制 +- 路由定义 + +## 开发流程 + +1. **理解需求** - 明确功能需求和业务逻辑 +2. **设计数据库** - 设计相关数据表和字段 +3. **创建模型** - 在对应模块的`model`目录下创建数据模型 +4. **编写服务层** - 在`service`目录下实现业务逻辑 +5. **创建控制器** - 在`controller`目录下创建控制器处理请求 +6. **定义路由** - 在模块的`config/route.php`中定义路由规则 +7. **编写前端代码** - 实现页面交互和UI + +## 关键文件和位置 + +- **模型定义**: `application/模块名/model/` +- **控制器**: `application/模块名/controller/` +- **服务层**: `application/模块名/service/` +- **路由配置**: `application/模块名/config/route.php` +- **公共函数**: `application/common.php` +- **配置文件**: `config/` 和 `application/模块名/config/` + +## 命名规范 + +- **类名**: 使用Pascal命名法(如`DeviceController`) +- **方法名**: 使用camel命名法(如`getUserInfo`) +- **变量名**: 使用camel命名法(如`$deviceInfo`) +- **常量**: 使用大写下划线(如`APP_DEBUG`) +- **配置参数**: 使用小写下划线(如`app_debug`) + +## 使用命令行 + +ThinkPHP提供了命令行工具,可用于执行各种任务: + +```bash +# 查看可用命令 +php think + +# 启动内置服务器 +php think run + +# 清除缓存 +php think clear + +# 执行数据库迁移 +php think migrate:run +``` + +## 扩展包依赖 + +项目使用Composer管理依赖,主要依赖见[composer.json](mdc:composer.json): + +- topthink/framework: 5.1.* +- phpoffice/phpexcel +- guzzlehttp/guzzle +- 等其他扩展包 diff --git a/Server/.cursor/rules/05-api-reference.mdc b/Server/.cursor/rules/05-api-reference.mdc new file mode 100644 index 00000000..9e27a73d --- /dev/null +++ b/Server/.cursor/rules/05-api-reference.mdc @@ -0,0 +1,85 @@ +--- +description: +globs: +alwaysApply: false +--- +# API 参考 + +村客宝系统提供RESTful风格的API,主要通过`application/api`模块实现。 + +## API路由结构 + +系统API遵循RESTful设计,主要路由前缀为`/v1`,详细路由定义见[application/api/config/route.php](mdc:application/api/config/route.php) + +## 主要API控制器 + +- [DeviceController.php](mdc:application/api/controller/DeviceController.php) - 设备管理API +- [WechatController.php](mdc:application/api/controller/WechatController.php) - 微信账号API +- [WechatFriendController.php](mdc:application/api/controller/WechatFriendController.php) - 微信好友API +- [WechatChatroomController.php](mdc:application/api/controller/WechatChatroomController.php) - 微信群API +- [MessageController.php](mdc:application/api/controller/MessageController.php) - 消息API +- [MomentsController.php](mdc:application/api/controller/MomentsController.php) - 朋友圈API +- [UserController.php](mdc:application/api/controller/UserController.php) - 用户API + +## 常用API端点 + +### 设备管理 + +- `GET /v1/devices` - 获取设备列表 +- `GET /v1/devices/{id}` - 获取设备详情 +- `POST /v1/devices` - 添加设备 +- `PUT /v1/devices/{id}` - 更新设备信息 +- `DELETE /v1/devices/{id}` - 删除设备 + +### 微信账号管理 + +- `GET /v1/wechats` - 获取微信账号列表 +- `GET /v1/wechats/{id}` - 获取微信账号详情 +- `PUT /v1/wechats/{id}` - 更新微信账号信息 +- `DELETE /v1/wechats/{id}` - 删除微信账号 + +### 微信好友管理 + +- `GET /v1/wechats/{id}/friends` - 获取好友列表 +- `POST /v1/wechats/{id}/friends` - 添加好友 +- `PUT /v1/wechats/{id}/friends/{friendId}` - 更新好友信息 + +### 工作台功能 + +- `POST /v1/workbench/auto-like` - 自动点赞 +- `POST /v1/workbench/sync-moments` - 同步朋友圈 +- `POST /v1/workbench/send-message` - 发送消息 + +## API鉴权 + +API使用Token认证机制,每个请求需要在Header中包含授权信息: + +``` +Authorization: Bearer {token} +``` + +获取Token的方法: +- `POST /v1/auth/login` - 登录获取Token +- `POST /v1/auth/refresh` - 刷新Token + +## 响应格式 + +API统一返回JSON格式数据,基本结构: + +```json +{ + "code": 200, // 状态码 + "message": "success", // 消息 + "data": {}, // 数据(可选) + "time": 1628160000 // 时间戳 +} +``` + +## WebSocket通信 + +实时通信通过WebSocket实现,在[WebSocketController.php](mdc:application/api/controller/WebSocketController.php)中定义: + +- 连接地址:`ws://{host}/ws` +- 支持设备状态实时推送 +- 支持消息实时通知 +- 支持任务执行状态更新 diff --git a/Server/.cursor/rules/06-data-models.mdc b/Server/.cursor/rules/06-data-models.mdc new file mode 100644 index 00000000..e41ff5f8 --- /dev/null +++ b/Server/.cursor/rules/06-data-models.mdc @@ -0,0 +1,96 @@ +--- +description: +globs: +alwaysApply: false +--- +# 数据模型 + +系统使用ThinkPHP的ORM框架进行数据库操作,主要模型如下: + +## 核心模型 + +### 设备相关 + +- **Device模型** - 设备信息 + - 位置:[application/common/model/Device.php](mdc:application/common/model/Device.php) + - 主要字段:id, name, device_id, status, online_status, created_at, updated_at + +- **DeviceWechat模型** - 设备上的微信账号 + - 位置:[application/common/model/DeviceWechat.php](mdc:application/common/model/DeviceWechat.php) + - 主要字段:id, device_id, wechat_id, status, login_status, created_at, updated_at + +### 微信相关 + +- **Wechat模型** - 微信账号信息 + - 位置:[application/common/model/Wechat.php](mdc:application/common/model/Wechat.php) + - 主要字段:id, wxid, nickname, avatar, gender, country, province, city, created_at, updated_at + +- **WechatFriend模型** - 微信好友 + - 位置:[application/common/model/WechatFriend.php](mdc:application/common/model/WechatFriend.php) + - 主要字段:id, wechat_id, wxid, nickname, remark, avatar, gender, created_at, updated_at + +- **WechatChatroom模型** - 微信群 + - 位置:[application/common/model/WechatChatroom.php](mdc:application/common/model/WechatChatroom.php) + - 主要字段:id, chatroom_id, name, owner_wxid, member_count, created_at, updated_at + +### 内容相关 + +- **ContentLibrary模型** - 内容库 + - 位置:[application/common/model/ContentLibrary.php](mdc:application/common/model/ContentLibrary.php) + - 主要字段:id, title, content, type, category_id, tags, created_at, updated_at + +- **ContentCategory模型** - 内容分类 + - 位置:[application/common/model/ContentCategory.php](mdc:application/common/model/ContentCategory.php) + - 主要字段:id, name, parent_id, sort, created_at, updated_at + +### 工作台相关 + +- **Task模型** - 任务 + - 位置:[application/common/model/Task.php](mdc:application/common/model/Task.php) + - 主要字段:id, name, type, status, config, created_at, updated_at + +- **Plan模型** - 计划 + - 位置:[application/common/model/Plan.php](mdc:application/common/model/Plan.php) + - 主要字段:id, name, type, status, config, schedule, created_at, updated_at + +### 流量相关 + +- **TrafficPool模型** - 流量池 + - 位置:[application/common/model/TrafficPool.php](mdc:application/common/model/TrafficPool.php) + - 主要字段:id, name, description, created_at, updated_at + +- **TrafficTag模型** - 流量标签 + - 位置:[application/common/model/TrafficTag.php](mdc:application/common/model/TrafficTag.php) + - 主要字段:id, name, color, created_at, updated_at + +## 模型关联 + +系统中的模型通过ThinkPHP的关联关系进行关联: + +- Device模型 hasMany DeviceWechat模型 +- DeviceWechat模型 belongsTo Device模型 +- DeviceWechat模型 belongsTo Wechat模型 +- Wechat模型 hasMany WechatFriend模型 +- Wechat模型 hasMany WechatChatroom模型 +- ContentLibrary模型 belongsTo ContentCategory模型 + +## 模型使用示例 + +```php +// 查询设备列表 +$devices = Device::where('status', 1)->order('id', 'desc')->paginate(10); + +// 关联查询设备上的微信账号 +$device = Device::with('wechats')->find($deviceId); + +// 创建新设备 +$device = new Device; +$device->name = '设备名称'; +$device->device_id = 'DEVICE_123456'; +$device->status = 1; +$device->save(); +``` + +## 数据验证 + +模型验证规则定义在对应的验证器类中,位于`application/模块名/validate/`目录下。 diff --git a/Server/.cursor/rules/07-websocket-communication.mdc b/Server/.cursor/rules/07-websocket-communication.mdc new file mode 100644 index 00000000..0f7914ac --- /dev/null +++ b/Server/.cursor/rules/07-websocket-communication.mdc @@ -0,0 +1,104 @@ +--- +description: +globs: +alwaysApply: false +--- +# WebSocket通信 + +村客宝系统使用WebSocket实现实时通信功能,主要用于设备状态监控和消息实时推送。 + +## WebSocket服务 + +系统使用ThinkPHP的think-worker扩展实现WebSocket服务: + +- 服务配置:[config/worker.php](mdc:config/worker.php)和[config/worker_server.php](mdc:config/worker_server.php) +- Gateway配置:[config/gateway_worker.php](mdc:config/gateway_worker.php) + +## WebSocket控制器 + +WebSocket服务主要通过以下文件实现: + +- [application/api/controller/WebSocketController.php](mdc:application/api/controller/WebSocketController.php) - WebSocket控制器 +- [application/common/socket/](mdc:application/common/socket) - WebSocket核心实现 + +## 消息格式 + +WebSocket消息使用JSON格式,基本结构如下: + +```json +{ + "type": "message_type", // 消息类型 + "data": {}, // 消息数据 + "time": 1628160000 // 时间戳 +} +``` + +## 常用消息类型 + +- `device_status` - 设备状态更新 +- `wechat_login` - 微信登录状态更新 +- `new_message` - 新消息通知 +- `task_status` - 任务状态更新 +- `error` - 错误消息 + +## 客户端连接 + +客户端可以通过以下方式连接WebSocket服务: + +```javascript +const ws = new WebSocket('ws://{host}/ws'); + +ws.onopen = function() { + console.log('Connected to WebSocket server'); + // 发送认证消息 + ws.send(JSON.stringify({ + type: 'auth', + data: { + token: 'YOUR_AUTH_TOKEN' + } + })); +}; + +ws.onmessage = function(event) { + const message = JSON.parse(event.data); + console.log('Received message:', message); + + // 根据消息类型处理 + switch(message.type) { + case 'device_status': + // 处理设备状态更新 + break; + case 'new_message': + // 处理新消息 + break; + // ...其他消息类型 + } +}; +``` + +## WebSocket命令 + +系统提供命令行工具管理WebSocket服务: + +```bash +# 启动WebSocket服务 +php think worker:server start + +# 停止WebSocket服务 +php think worker:server stop + +# 重启WebSocket服务 +php think worker:server restart + +# 查看WebSocket服务状态 +php think worker:server status +``` + +## 消息推送服务 + +系统使用队列实现消息的异步推送: + +- 队列配置:[config/queue.php](mdc:config/queue.php) +- 消息推送任务:[application/job/](mdc:application/job) + +通过队列可以实现高效的消息推送,避免阻塞主进程。 diff --git a/Server/composer.json b/Server/composer.json index 093debe1..5f38cd4a 100644 --- a/Server/composer.json +++ b/Server/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=5.6.0", + "php": ">=7.0", "topthink/framework": "5.1.*", "phpoffice/phpexcel": "^1.8", "endroid/qr-code": "^2.5", diff --git a/Server/config/wechat_device_api.php b/Server/config/wechat_device_api.php new file mode 100644 index 00000000..24c57cac --- /dev/null +++ b/Server/config/wechat_device_api.php @@ -0,0 +1,19 @@ + 'ChuKeBao', + + // 各个供应商适配器的配置 + 'adapters' => [ + 'ChuKeBao' => [ + 'driver' => \WeChatDeviceApi\Adapters\ChuKebao\Adapter::class, +// 'api_key' => env('ChuKebao_API_KEY', ''), +// 'api_secret' => env('ChuKebao_API_SECRET', ''), + 'base_url' => env('api.wechat_url'), + 'username' => env('api.username', ''), + 'password' => env('api.password', ''), + ], + // ... 更多供应商 + ], +]; \ No newline at end of file diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php new file mode 100644 index 00000000..5720ce68 --- /dev/null +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -0,0 +1,98 @@ +config = $config; + // $this->apiClient = new VendorAApiClient($config['api_key'], $config['api_secret'], $config['base_url']); + // 校验配置等... + if (empty($config['api_key']) || empty($config['base_url'])) { + throw new \InvalidArgumentException("VendorA API key and base_url are required."); + } + } + + public function addFriend(string $deviceId, string $targetWxId): bool + { + // 1. 构建请求参数 (VendorA 特定的格式) + $params = [ + 'device_identifier' => $deviceId, + 'wechat_user_to_add' => $targetWxId, + 'apiKey' => $this->config['api_key'], + // ... 其他 VendorA 特定参数 + ]; + + // 2. 调用 VendorA 的 API (例如使用 GuzzleHttp 或 cURL) + // $response = $this->apiClient->post('/friend/add', $params); + // 伪代码: + $url = $this->config['base_url'] . '/friend/add'; + // $httpClient = new \GuzzleHttp\Client(); + // $response = $httpClient->request('POST', $url, ['form_params' => $params]); + // $responseData = json_decode($response->getBody()->getContents(), true); + + // 模拟API调用 + echo "VendorA: Adding friend {$targetWxId} using device {$deviceId}\n"; + $responseData = ['code' => 0, 'message' => 'Success']; // 假设的响应 + + // 3. 处理响应,转换为标准结果 + if (!isset($responseData['code'])) { + throw new ApiException("VendorA: Invalid API response for addFriend."); + } +// if ($responseData['code'] === 1001) { // 假设1001是设备离线 +// throw new DeviceOfflineException("VendorA: Device {$deviceId} is offline."); +// } + if ($responseData['code'] !== 0) { + throw new ApiException("VendorA: Failed to add friend - " . ($responseData['message'] ?? 'Unknown error')); + } + + return true; + } + + public function likeMoment(string $deviceId, string $momentId): bool + { + echo "VendorA: Liking moment {$momentId} using device {$deviceId}\n"; + // 实现 VendorA 的点赞逻辑 + return true; + } + + public function getGroupList(string $deviceId): array + { + echo "VendorA: Getting group list for device {$deviceId}\n"; + // 实现 VendorA 的获取群列表逻辑,并转换数据格式 + return [ + ['id' => 'group1_va', 'name' => 'VendorA Group 1', 'member_count' => 10], + ]; + } + + public function getFriendList(string $deviceId): array + { + echo "VendorA: Getting friend list for device {$deviceId}\n"; + return [ + ['id' => 'friend1_va', 'nickname' => 'VendorA Friend 1', 'remark' => 'VA-F1'], + ]; + } + + public function getDeviceInfo(string $deviceId): array + { + echo "VendorA: Getting device info for device {$deviceId}\n"; + return ['id' => $deviceId, 'status' => 'online_va', 'battery' => '80%']; + } + + public function bindDeviceToCompany(string $deviceId, string $companyId): bool + { + echo "VendorA: Binding device {$deviceId} to company {$companyId}\n"; + return true; + } + + // ... 实现接口中的其他方法 +} \ No newline at end of file diff --git a/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php b/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php new file mode 100644 index 00000000..e35cc496 --- /dev/null +++ b/Server/extend/WeChatDeviceApi/Contracts/WeChatServiceInterface.php @@ -0,0 +1,54 @@ +config = $config ?: Config::get('wechat_device_api.'); + if (empty($this->config)) { + throw new \InvalidArgumentException("WeChat Device API configuration not found."); + } + } + + /** + * 获取指定的适配器实例 + * + * @param string|null $name 适配器名称 (例如 'vendor_a', 'vendor_b'),null 则使用默认 + * @return WeChatServiceInterface + * @throws \InvalidArgumentException + */ + public function adapter(string $name = null): WeChatServiceInterface + { + $name = $name ?: $this->getDefaultAdapterName(); + + if (!isset($this->config['adapters'][$name])) { + throw new \InvalidArgumentException("Adapter [{$name}] configuration not found."); + } + + if (!isset($this->adapters[$name])) { + $this->adapters[$name] = $this->createAdapter($name); + } + + return $this->adapters[$name]; + } + + /** + * 创建适配器实例 + * + * @param string $name + * @return WeChatServiceInterface + */ + protected function createAdapter(string $name): WeChatServiceInterface + { + $adapterConfig = $this->config['adapters'][$name]; + $driverClass = $adapterConfig['driver'] ?? null; + + if (!$driverClass || !class_exists($driverClass)) { + throw new \InvalidArgumentException("Driver class for adapter [{$name}] not found or not specified."); + } + + $adapterInstance = new $driverClass($adapterConfig); + + if (!$adapterInstance instanceof WeChatServiceInterface) { + throw new \LogicException("Driver class [{$driverClass}] must implement WeChatServiceInterface."); + } + + return $adapterInstance; + } + + /** + * 获取默认适配器名称 + * + * @return string + */ + public function getDefaultAdapterName(): string + { + return $this->config['default_adapter'] ?? ''; + } + + /** + * 动态调用默认适配器的方法 + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call(string $method, array $parameters) + { + return $this->adapter()->{$method}(...$parameters); + } +} \ No newline at end of file diff --git a/Server/thinkphp/library/think/App.php b/Server/thinkphp/library/think/App.php index 35924a5b..692b4227 100644 --- a/Server/thinkphp/library/think/App.php +++ b/Server/thinkphp/library/think/App.php @@ -1,979 +1,979 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; -use think\exception\HttpResponseException; -use think\route\Dispatch; - -/** - * App 应用管理 - */ -class App extends Container -{ - const VERSION = '5.1.41 LTS'; - - /** - * 当前模块路径 - * @var string - */ - protected $modulePath; - - /** - * 应用调试模式 - * @var bool - */ - protected $appDebug = true; - - /** - * 应用开始时间 - * @var float - */ - protected $beginTime; - - /** - * 应用内存初始占用 - * @var integer - */ - protected $beginMem; - - /** - * 应用类库命名空间 - * @var string - */ - protected $namespace = 'app'; - - /** - * 应用类库后缀 - * @var bool - */ - protected $suffix = false; - - /** - * 严格路由检测 - * @var bool - */ - protected $routeMust; - - /** - * 应用类库目录 - * @var string - */ - protected $appPath; - - /** - * 框架目录 - * @var string - */ - protected $thinkPath; - - /** - * 应用根目录 - * @var string - */ - protected $rootPath; - - /** - * 运行时目录 - * @var string - */ - protected $runtimePath; - - /** - * 配置目录 - * @var string - */ - protected $configPath; - - /** - * 路由目录 - * @var string - */ - protected $routePath; - - /** - * 配置后缀 - * @var string - */ - protected $configExt; - - /** - * 应用调度实例 - * @var Dispatch - */ - protected $dispatch; - - /** - * 绑定模块(控制器) - * @var string - */ - protected $bindModule; - - /** - * 初始化 - * @var bool - */ - protected $initialized = false; - - public function __construct($appPath = '') - { - $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; - $this->path($appPath); - } - - /** - * 绑定模块或者控制器 - * @access public - * @param string $bind - * @return $this - */ - public function bind($bind) - { - $this->bindModule = $bind; - return $this; - } - - /** - * 设置应用类库目录 - * @access public - * @param string $path 路径 - * @return $this - */ - public function path($path) - { - $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); - - return $this; - } - - /** - * 初始化应用 - * @access public - * @return void - */ - public function initialize() - { - if ($this->initialized) { - return; - } - - $this->initialized = true; - $this->beginTime = microtime(true); - $this->beginMem = memory_get_usage(); - - $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; - $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; - $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; - $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; - - static::setInstance($this); - - $this->instance('app', $this); - - // 加载环境变量配置文件 - if (is_file($this->rootPath . '.env')) { - $this->env->load($this->rootPath . '.env'); - } - - $this->configExt = $this->env->get('config_ext', '.php'); - - // 加载惯例配置文件 - $this->config->set(include $this->thinkPath . 'convention.php'); - - // 设置路径环境变量 - $this->env->set([ - 'think_path' => $this->thinkPath, - 'root_path' => $this->rootPath, - 'app_path' => $this->appPath, - 'config_path' => $this->configPath, - 'route_path' => $this->routePath, - 'runtime_path' => $this->runtimePath, - 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, - 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, - ]); - - $this->namespace = $this->env->get('app_namespace', $this->namespace); - $this->env->set('app_namespace', $this->namespace); - - // 注册应用命名空间 - Loader::addNamespace($this->namespace, $this->appPath); - - // 初始化应用 - $this->init(); - - // 开启类名后缀 - $this->suffix = $this->config('app.class_suffix'); - - // 应用调试模式 - $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); - $this->env->set('app_debug', $this->appDebug); - - if (!$this->appDebug) { - ini_set('display_errors', 'Off'); - } elseif (PHP_SAPI != 'cli') { - //重新申请一块比较大的buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - ob_start(); - if (!empty($output)) { - echo $output; - } - } - - // 注册异常处理类 - if ($this->config('app.exception_handle')) { - Error::setExceptionHandler($this->config('app.exception_handle')); - } - - // 注册根命名空间 - if (!empty($this->config('app.root_namespace'))) { - Loader::addNamespace($this->config('app.root_namespace')); - } - - // 加载composer autofile文件 - Loader::loadComposerAutoloadFiles(); - - // 注册类库别名 - Loader::addClassAlias($this->config->pull('alias')); - - // 数据库配置初始化 - Db::init($this->config->pull('database')); - - // 设置系统时区 - date_default_timezone_set($this->config('app.default_timezone')); - - // 读取语言包 - $this->loadLangPack(); - - // 路由初始化 - $this->routeInit(); - } - - /** - * 初始化应用或模块 - * @access public - * @param string $module 模块名 - * @return void - */ - public function init($module = '') - { - // 定位模块目录 - $module = $module ? $module . DIRECTORY_SEPARATOR : ''; - $path = $this->appPath . $module; - - // 加载初始化文件 - if (is_file($path . 'init.php')) { - include $path . 'init.php'; - } elseif (is_file($this->runtimePath . $module . 'init.php')) { - include $this->runtimePath . $module . 'init.php'; - } else { - // 加载行为扩展文件 - if (is_file($path . 'tags.php')) { - $tags = include $path . 'tags.php'; - if (is_array($tags)) { - $this->hook->import($tags); - } - } - - // 加载公共文件 - if (is_file($path . 'common.php')) { - include_once $path . 'common.php'; - } - - if ('' == $module) { - // 加载系统助手函数 - include $this->thinkPath . 'helper.php'; - } - - // 加载中间件 - if (is_file($path . 'middleware.php')) { - $middleware = include $path . 'middleware.php'; - if (is_array($middleware)) { - $this->middleware->import($middleware); - } - } - - // 注册服务的容器对象实例 - if (is_file($path . 'provider.php')) { - $provider = include $path . 'provider.php'; - if (is_array($provider)) { - $this->bindTo($provider); - } - } - - // 自动读取配置文件 - if (is_dir($path . 'config')) { - $dir = $path . 'config' . DIRECTORY_SEPARATOR; - } elseif (is_dir($this->configPath . $module)) { - $dir = $this->configPath . $module; - } - - $files = isset($dir) ? scandir($dir) : []; - - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { - $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - $this->setModulePath($path); - - if ($module) { - // 对容器中的对象实例进行配置更新 - $this->containerConfigUpdate($module); - } - } - - protected function containerConfigUpdate($module) - { - $config = $this->config->get(); - - // 注册异常处理类 - if ($config['app']['exception_handle']) { - Error::setExceptionHandler($config['app']['exception_handle']); - } - - Db::init($config['database']); - $this->middleware->setConfig($config['middleware']); - $this->route->setConfig($config['app']); - $this->request->init($config['app']); - $this->cookie->init($config['cookie']); - $this->view->init($config['template']); - $this->log->init($config['log']); - $this->session->setConfig($config['session']); - $this->debug->setConfig($config['trace']); - $this->cache->init($config['cache'], true); - - // 加载当前模块语言包 - $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); - - // 模块请求缓存检查 - $this->checkRequestCache( - $config['app']['request_cache'], - $config['app']['request_cache_expire'], - $config['app']['request_cache_except'] - ); - } - - /** - * 执行应用程序 - * @access public - * @return Response - * @throws Exception - */ - public function run() - { - try { - // 初始化应用 - $this->initialize(); - - // 监听app_init - $this->hook->listen('app_init'); - - if ($this->bindModule) { - // 模块/控制器绑定 - $this->route->bind($this->bindModule); - } elseif ($this->config('app.auto_bind_module')) { - // 入口自动绑定 - $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir($this->appPath . $name)) { - $this->route->bind($name); - } - } - - // 监听app_dispatch - $this->hook->listen('app_dispatch'); - - $dispatch = $this->dispatch; - - if (empty($dispatch)) { - // 路由检测 - $dispatch = $this->routeCheck()->init(); - } - - // 记录当前调度信息 - $this->request->dispatch($dispatch); - - // 记录路由和请求信息 - if ($this->appDebug) { - $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); - $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); - $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); - } - - // 监听app_begin - $this->hook->listen('app_begin'); - - // 请求缓存检查 - $this->checkRequestCache( - $this->config('request_cache'), - $this->config('request_cache_expire'), - $this->config('request_cache_except') - ); - - $data = null; - } catch (HttpResponseException $exception) { - $dispatch = null; - $data = $exception->getResponse(); - } - - $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { - return is_null($data) ? $dispatch->run() : $data; - }); - - $response = $this->middleware->dispatch($this->request); - - // 监听app_end - $this->hook->listen('app_end', $response); - - return $response; - } - - protected function getRouteCacheKey() - { - if ($this->config->get('route_check_cache_key')) { - $closure = $this->config->get('route_check_cache_key'); - $routeKey = $closure($this->request); - } else { - $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); - } - - return $routeKey; - } - - protected function loadLangPack() - { - // 读取默认语言 - $this->lang->range($this->config('app.default_lang')); - - if ($this->config('app.lang_switch_on')) { - // 开启多语言机制 检测当前语言 - $this->lang->detect(); - } - - $this->request->setLangset($this->lang->range()); - - // 加载系统语言包 - $this->lang->load([ - $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', - $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', - ]); - } - - /** - * 设置当前地址的请求缓存 - * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @param string $tag 缓存标签 - * @return void - */ - public function checkRequestCache($key, $expire = null, $except = [], $tag = null) - { - $cache = $this->request->cache($key, $expire, $except, $tag); - - if ($cache) { - $this->setResponseCache($cache); - } - } - - public function setResponseCache($cache) - { - list($key, $expire, $tag) = $cache; - - if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { - // 读取缓存 - $response = Response::create()->code(304); - throw new HttpResponseException($response); - } elseif ($this->cache->has($key)) { - list($content, $header) = $this->cache->get($key); - - $response = Response::create($content)->header($header); - throw new HttpResponseException($response); - } - } - - /** - * 设置当前请求的调度信息 - * @access public - * @param Dispatch $dispatch 调度信息 - * @return $this - */ - public function dispatch(Dispatch $dispatch) - { - $this->dispatch = $dispatch; - return $this; - } - - /** - * 记录调试信息 - * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @return void - */ - public function log($msg, $type = 'info') - { - $this->appDebug && $this->log->record($msg, $type); - } - - /** - * 获取配置参数 为空则获取所有配置 - * @access public - * @param string $name 配置参数名(支持二级配置 .号分割) - * @return mixed - */ - public function config($name = '') - { - return $this->config->get($name); - } - - /** - * 路由初始化 导入路由定义规则 - * @access public - * @return void - */ - public function routeInit() - { - // 路由检测 - if (is_dir($this->routePath)) { - $files = glob($this->routePath . '*.php'); - foreach ($files as $file) { - $rules = include $file; - if (is_array($rules)) { - $this->route->import($rules); - } - } - } - - if ($this->route->config('route_annotation')) { - // 自动生成路由定义 - if ($this->appDebug) { - $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); - $this->build->buildRoute($suffix); - } - - $filename = $this->runtimePath . 'build_route.php'; - - if (is_file($filename)) { - include $filename; - } - } - } - - /** - * URL路由检测(根据PATH_INFO) - * @access public - * @return Dispatch - */ - public function routeCheck() - { - // 检测路由缓存 - if (!$this->appDebug && $this->config->get('route_check_cache')) { - $routeKey = $this->getRouteCacheKey(); - $option = $this->config->get('route_cache_option'); - - if ($option && $this->cache->connect($option)->has($routeKey)) { - return $this->cache->connect($option)->get($routeKey); - } elseif ($this->cache->has($routeKey)) { - return $this->cache->get($routeKey); - } - } - - // 获取应用调度信息 - $path = $this->request->path(); - - // 是否强制路由模式 - $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); - - // 路由检测 返回一个Dispatch对象 - $dispatch = $this->route->check($path, $must); - - if (!empty($routeKey)) { - try { - if ($option) { - $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); - } else { - $this->cache->tag('route_cache')->set($routeKey, $dispatch); - } - } catch (\Exception $e) { - // 存在闭包的时候缓存无效 - } - } - - return $dispatch; - } - - /** - * 设置应用的路由检测机制 - * @access public - * @param bool $must 是否强制检测路由 - * @return $this - */ - public function routeMust($must = false) - { - $this->routeMust = $must; - return $this; - } - - /** - * 解析模块和类名 - * @access protected - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return array - */ - protected function parseModuleAndClass($name, $layer, $appendSuffix) - { - if (false !== strpos($name, '\\')) { - $class = $name; - $module = $this->request->module(); - } else { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = $this->request->module(); - } - - $class = $this->parseClass($module, $layer, $name, $appendSuffix); - } - - return [$module, $class]; - } - - /** - * 实例化应用类库 - * @access public - * @param string $name 类名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object - * @throws ClassNotFoundException - */ - public function create($name, $layer, $appendSuffix = false, $common = 'common') - { - $guid = $name . $layer; - - if ($this->__isset($guid)) { - return $this->__get($guid); - } - - list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $object = $this->__get($class); - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - if (class_exists($class)) { - $object = $this->__get($class); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - $this->__set($guid, $class); - - return $object; - } - - /** - * 实例化(分层)模型 - * @access public - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return Model - * @throws ClassNotFoundException - */ - public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') - { - return $this->create($name, $layer, $appendSuffix, $common); - } - - /** - * 实例化(分层)控制器 格式:[模块名/]控制器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $empty 空控制器名称 - * @return object - * @throws ClassNotFoundException - */ - public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') - { - list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - return $this->make($class, true); - } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { - return $this->make($emptyClass, true); - } - - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** - * 实例化验证类 格式:[模块名/]验证器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return Validate - * @throws ClassNotFoundException - */ - public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') - { - $name = $name ?: $this->config('default_validate'); - - if (empty($name)) { - return new Validate; - } - - return $this->create($name, $layer, $appendSuffix, $common); - } - - /** - * 数据库初始化 - * @access public - * @param mixed $config 数据库配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Query - */ - public function db($config = [], $name = false) - { - return Db::connect($config, $name); - } - - /** - * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @access public - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - * @throws ClassNotFoundException - */ - public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - $info = pathinfo($url); - $action = $info['basename']; - $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); - $class = $this->controller($module, $layer, $appendSuffix); - - if (is_scalar($vars)) { - if (strpos($vars, '=')) { - parse_str($vars, $vars); - } else { - $vars = [$vars]; - } - } - - return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); - } - - /** - * 解析应用类的类名 - * @access public - * @param string $module 模块名 - * @param string $layer 层名 controller model ... - * @param string $name 类名 - * @param bool $appendSuffix - * @return string - */ - public function parseClass($module, $layer, $name, $appendSuffix = false) - { - $name = str_replace(['/', '.'], '\\', $name); - $array = explode('\\', $name); - $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); - $path = $array ? implode('\\', $array) . '\\' : ''; - - return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; - } - - /** - * 获取框架版本 - * @access public - * @return string - */ - public function version() - { - return static::VERSION; - } - - /** - * 是否为调试模式 - * @access public - * @return bool - */ - public function isDebug() - { - return $this->appDebug; - } - - /** - * 获取模块路径 - * @access public - * @return string - */ - public function getModulePath() - { - return $this->modulePath; - } - - /** - * 设置模块路径 - * @access public - * @param string $path 路径 - * @return void - */ - public function setModulePath($path) - { - $this->modulePath = $path; - $this->env->set('module_path', $path); - } - - /** - * 获取应用根目录 - * @access public - * @return string - */ - public function getRootPath() - { - return $this->rootPath; - } - - /** - * 获取应用类库目录 - * @access public - * @return string - */ - public function getAppPath() - { - if (is_null($this->appPath)) { - $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; - } - - return $this->appPath; - } - - /** - * 获取应用运行时目录 - * @access public - * @return string - */ - public function getRuntimePath() - { - return $this->runtimePath; - } - - /** - * 获取核心框架目录 - * @access public - * @return string - */ - public function getThinkPath() - { - return $this->thinkPath; - } - - /** - * 获取路由目录 - * @access public - * @return string - */ - public function getRoutePath() - { - return $this->routePath; - } - - /** - * 获取应用配置目录 - * @access public - * @return string - */ - public function getConfigPath() - { - return $this->configPath; - } - - /** - * 获取配置后缀 - * @access public - * @return string - */ - public function getConfigExt() - { - return $this->configExt; - } - - /** - * 获取应用类库命名空间 - * @access public - * @return string - */ - public function getNamespace() - { - return $this->namespace; - } - - /** - * 设置应用类库命名空间 - * @access public - * @param string $namespace 命名空间名称 - * @return $this - */ - public function setNamespace($namespace) - { - $this->namespace = $namespace; - return $this; - } - - /** - * 是否启用类库后缀 - * @access public - * @return bool - */ - public function getSuffix() - { - return $this->suffix; - } - - /** - * 获取应用开启时间 - * @access public - * @return float - */ - public function getBeginTime() - { - return $this->beginTime; - } - - /** - * 获取应用初始内存占用 - * @access public - * @return integer - */ - public function getBeginMem() - { - return $this->beginMem; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpResponseException; +use think\route\Dispatch; + +/** + * App 应用管理 + */ +class App extends Container +{ + const VERSION = '5.1.41 LTS'; + + /** + * 当前模块路径 + * @var string + */ + protected $modulePath; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = true; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用类库后缀 + * @var bool + */ + protected $suffix = false; + + /** + * 严格路由检测 + * @var bool + */ + protected $routeMust; + + /** + * 应用类库目录 + * @var string + */ + protected $appPath; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bindModule; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + public function __construct($appPath = '') + { + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->path($appPath); + } + + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bindModule = $bind; + return $this; + } + + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); + + return $this; + } + + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + + static::setInstance($this); + + $this->instance('app', $this); + + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, + ]); + + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); + + if (!$this->appDebug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 数据库配置初始化 + Db::init($this->config->pull('database')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 读取语言包 + $this->loadLangPack(); + + // 路由初始化 + $this->routeInit(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return void + */ + public function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; + + // 加载初始化文件 + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; + } else { + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + include_once $path . 'common.php'; + } + + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->bindTo($provider); + } + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config' . DIRECTORY_SEPARATOR; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + $this->setModulePath($path); + + if ($module) { + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); + } + } + + protected function containerConfigUpdate($module) + { + $config = $this->config->get(); + + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + + Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); + $this->route->setConfig($config['app']); + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->checkRequestCache( + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] + ); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + try { + // 初始化应用 + $this->initialize(); + + // 监听app_init + $this->hook->listen('app_init'); + + if ($this->bindModule) { + // 模块/控制器绑定 + $this->route->bind($this->bindModule); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + $dispatch = $this->dispatch; + + if (empty($dispatch)) { + // 路由检测 + $dispatch = $this->routeCheck()->init(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->appDebug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->checkRequestCache( + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') + ); + + $data = null; + } catch (HttpResponseException $exception) { + $dispatch = null; + $data = $exception->getResponse(); + } + + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { + return is_null($data) ? $dispatch->run() : $data; + }); + + $response = $this->middleware->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; + } + + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->setLangset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + $this->setResponseCache($cache); + } + } + + public function setResponseCache($cache) + { + list($key, $expire, $tag) = $cache; + + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public function log($msg, $type = 'info') + { + $this->appDebug && $this->log->record($msg, $type); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 .号分割) + * @return mixed + */ + public function config($name = '') + { + return $this->config->get($name); + } + + /** + * 路由初始化 导入路由定义规则 + * @access public + * @return void + */ + public function routeInit() + { + // 路由检测 + if (is_dir($this->routePath)) { + $files = glob($this->routePath . '*.php'); + foreach ($files as $file) { + $rules = include $file; + if (is_array($rules)) { + $this->route->import($rules); + } + } + } + + if ($this->route->config('route_annotation')) { + // 自动生成路由定义 + if ($this->appDebug) { + $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); + $this->build->buildRoute($suffix); + } + + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; + } + } + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @return Dispatch + */ + public function routeCheck() + { + // 检测路由缓存 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option'); + + if ($option && $this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); + } elseif ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + + // 获取应用调度信息 + $path = $this->request->path(); + + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); + + // 路由检测 返回一个Dispatch对象 + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + if ($option) { + $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); + } else { + $this->cache->tag('route_cache')->set($routeKey, $dispatch); + } + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->make($class, true); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->make($emptyClass, true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->appDebug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 + * @return void + */ + public function setModulePath($path) + { + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + if (is_null($this->appPath)) { + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; + } + + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + +} From b5939b36b39519a78ee2164c27043a05ce0791e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Wed, 7 May 2025 18:16:02 +0800 Subject: [PATCH 06/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E6=89=80=E6=9C=89API=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E5=93=8D=E5=BA=94=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/GetDeviceDetailV1Controller.php | 15 ++++----------- .../device/GetDeviceHandleLogsV1Controller.php | 16 ++++++---------- .../device/GetDeviceListV1Controller.php | 14 +++++--------- .../device/GetRelatedAccountsV1Controller.php | 14 +++++--------- .../device/PostAddDeviceV1Controller.php | 12 +++--------- .../device/RefreshDeviceDetailV1Controller.php | 12 +++--------- .../UpdateDeviceTaskConfigV1Controller.php | 12 +++--------- .../administrator/AddAdministratorController.php | 1 - 8 files changed, 29 insertions(+), 67 deletions(-) diff --git a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php index 05bf1b93..f7e84799 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php @@ -8,6 +8,7 @@ use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; /** * 设备管理控制器 @@ -152,25 +153,17 @@ class GetDeviceDetailV1Controller extends BaseController public function index() { try { - // 获取设备ID $id = $this->request->param('id/d'); if ($this->getUserInfo('isAdmin') != 1) { $this->checkUserDevicePermission($id); } - $info = $this->getDeviceInfo($id); + $resultSet = $this->getDeviceInfo($id); - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $info - ]); + return ResponseHelper::success($resultSet); } catch (\Exception $e) { - return json([ - 'code' => $e->getMessage(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php index cec957e1..25eacd47 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php @@ -5,6 +5,7 @@ namespace app\cunkebao\controller\device; use app\common\model\DeviceHandleLog; use app\common\model\DeviceUser as DeviceUserModel; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; /** * 设备管理控制器 @@ -69,19 +70,14 @@ class GetDeviceHandleLogsV1Controller extends BaseController $logs = $this->getHandleLogs($deviceId); - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ + return ResponseHelper::success( + [ 'total' => $logs->total(), - 'list' => $logs->items() + 'list' => $logs->items() ] - ]); + ); } catch (\Exception $e) { - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php index a4767003..e08d91b9 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php @@ -6,6 +6,7 @@ use app\common\model\Device as DeviceModel; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; /** * 设备管理控制器 @@ -126,19 +127,14 @@ class GetDeviceListV1Controller extends BaseController $result = $this->getDeviceList($where); } - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ + return ResponseHelper::success( + [ 'list' => $this->countFriend($result), 'total' => $result->total(), ] - ]); + ); } catch (\Exception $e) { - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php index 2891db7e..3677c22a 100644 --- a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php @@ -7,6 +7,7 @@ use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; use app\common\model\WechatAccount as WechatAccountModel; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; /** * 设备管理控制器 @@ -154,20 +155,15 @@ class GetRelatedAccountsV1Controller extends BaseController // 获取设备关联的微信账号 $wechatAccounts = $this->getDeviceRelatedAccounts($deviceId); - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ + return ResponseHelper::success( + [ 'deviceId' => $deviceId, 'accounts' => $wechatAccounts, 'total' => count($wechatAccounts) ] - ]); + ); } catch (\Exception $e) { - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php index cc44d91e..dbb069dd 100644 --- a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php +++ b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php @@ -5,6 +5,7 @@ namespace app\cunkebao\controller\device; use app\common\model\Device as DeviceModel; use app\common\model\DeviceHandleLog as DeviceHandleLogModel; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; use library\s2\CurlHandle; use think\Db; use think\Validate; @@ -115,17 +116,10 @@ class PostAddDeviceV1Controller extends BaseController Db::commit(); - return json([ - 'code' => 200, - 'msg' => '添加成功' - ]); + return ResponseHelper::success(); } catch (\Exception $e) { Db::rollback(); - - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/RefreshDeviceDetailV1Controller.php b/Server/application/cunkebao/controller/device/RefreshDeviceDetailV1Controller.php index db6adaad..5ea260a0 100644 --- a/Server/application/cunkebao/controller/device/RefreshDeviceDetailV1Controller.php +++ b/Server/application/cunkebao/controller/device/RefreshDeviceDetailV1Controller.php @@ -3,6 +3,7 @@ namespace app\cunkebao\controller\device; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; /** * 设备管理控制器 @@ -17,16 +18,9 @@ class RefreshDeviceDetailV1Controller extends BaseController { try { // TODO: 实现实际刷新设备状态的功能 - - return json([ - 'code' => 200, - 'msg' => '刷新成功', - ]); + return ResponseHelper::success(); } catch (\Exception $e) { - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php index e1da2e00..b6182220 100644 --- a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php +++ b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php @@ -7,6 +7,7 @@ use app\common\model\DeviceHandleLog as DeviceHandleLogModel; use app\common\model\DeviceTaskconf; use app\common\model\DeviceUser as DeviceUserModel; use app\cunkebao\controller\BaseController; +use library\ResponseHelper; use think\Db; /** @@ -130,17 +131,10 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController Db::commit(); - return json([ - 'code' => 200, - 'msg' => '更新任务配置成功' - ]); + return ResponseHelper::success(); } catch (\Exception $e) { Db::rollback(); - - return json([ - 'code' => $e->getCode(), - 'msg' => $e->getMessage() - ]); + return ResponseHelper::error($e->getMessage(), $e->getCode()); } } } \ No newline at end of file diff --git a/Server/application/superadmin/controller/administrator/AddAdministratorController.php b/Server/application/superadmin/controller/administrator/AddAdministratorController.php index dc9551e3..f0f6e19f 100644 --- a/Server/application/superadmin/controller/administrator/AddAdministratorController.php +++ b/Server/application/superadmin/controller/administrator/AddAdministratorController.php @@ -141,7 +141,6 @@ class AddAdministratorController extends BaseController } Db::commit(); - return ResponseHelper::success(); } catch (\Exception $e) { Db::rollback(); From 0426485177fe36a99de2d6c1fdc84fae2d35efa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 10:39:53 +0800 Subject: [PATCH 07/16] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8F=8A=E4=BD=BF=E7=94=A8=E5=B8=B8=E9=87=8F?= =?UTF-8?q?=E5=8E=BB=E6=9B=BF=E6=8D=A2=E5=B8=B8=E7=94=A8=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/model/Administrator.php | 2 + .../common/model/DeviceTaskconf.php | 4 + Server/application/common/model/Menu.php | 3 + Server/application/common/model/User.php | 8 ++ Server/application/cunkebao/config/route.php | 2 +- .../GetAddResultedDevicesController.php | 2 +- .../device/GetDeviceDetailV1Controller.php | 32 ++++---- .../GetDeviceHandleLogsV1Controller.php | 13 ++- .../device/GetDeviceListV1Controller.php | 12 ++- .../device/GetRelatedAccountsV1Controller.php | 11 +-- .../device/PostAddDeviceV1Controller.php | 9 +-- .../UpdateDeviceTaskConfigV1Controller.php | 18 ++--- .../controller/Menu/GetMenuTreeController.php | 11 +-- .../GetTopLevelForPermissionController.php | 4 +- .../AddAdministratorController.php | 18 ++--- .../DeleteAdministratorController.php | 6 +- .../GetAdministratorDetailController.php | 13 +-- .../GetAdministratorListController.php | 22 ++--- .../UpdateAdministratorController.php | 24 +++--- .../controller/auth/AuthLoginController.php | 27 ++++--- .../company/CreateCompanyController.php | 80 ++++++++++--------- .../company/DeleteCompanyController.php | 2 +- .../GetCompanyDetailForProfileController.php | 10 +-- .../GetCompanyDetailForUpdateController.php | 3 +- .../GetCompanyDevicesForProfileController.php | 16 +++- .../company/GetCompanyListController.php | 8 +- ...GetCompanySubusersForProfileController.php | 10 ++- .../company/UpdateCompanyController.php | 30 +++---- .../dashboard/GetBasestatisticsController.php | 2 +- .../GetAddResultedDevicesController.php | 2 +- .../traffic/GetPoolListController.php | 17 ++-- 31 files changed, 230 insertions(+), 191 deletions(-) diff --git a/Server/application/common/model/Administrator.php b/Server/application/common/model/Administrator.php index 28a6577d..9a2ef174 100644 --- a/Server/application/common/model/Administrator.php +++ b/Server/application/common/model/Administrator.php @@ -12,6 +12,8 @@ class Administrator extends Model { use SoftDelete; + const MASTER_ID = 1; + // 设置数据表名 protected $name = 'administrators'; diff --git a/Server/application/common/model/DeviceTaskconf.php b/Server/application/common/model/DeviceTaskconf.php index 2905b023..2b3d98b3 100644 --- a/Server/application/common/model/DeviceTaskconf.php +++ b/Server/application/common/model/DeviceTaskconf.php @@ -3,12 +3,15 @@ namespace app\common\model; use think\Model; +use think\model\concern\SoftDelete; /** * 设备任务配置模型类 */ class DeviceTaskconf extends Model { + use SoftDelete; + // 设置表名 protected $name = 'device_taskconf'; @@ -16,4 +19,5 @@ class DeviceTaskconf extends Model protected $autoWriteTimestamp = true; protected $createTime = 'createTime'; protected $updateTime = 'updateTime'; + protected $defaultSoftDelete = 0; } \ No newline at end of file diff --git a/Server/application/common/model/Menu.php b/Server/application/common/model/Menu.php index 72597fd3..acd26dc0 100644 --- a/Server/application/common/model/Menu.php +++ b/Server/application/common/model/Menu.php @@ -9,6 +9,9 @@ use think\Model; */ class Menu extends Model { + const STATUS_ACTIVE = 1; + const TOP_LEVEL = 0; + // 设置数据表名 protected $name = 'menus'; } \ No newline at end of file diff --git a/Server/application/common/model/User.php b/Server/application/common/model/User.php index c3535d5c..f691a82c 100644 --- a/Server/application/common/model/User.php +++ b/Server/application/common/model/User.php @@ -9,6 +9,14 @@ class User extends Model { use SoftDelete; + const ADMIN_STP = 1; + const ADMIN_OTP = 0; + const NOT_USER = -1; + const MASTER_USER = 1; // 操盘手 + const CUSTOMER_USER = 2; // 门店接待 + const STATUS_STOP = 0; // 禁用状态 + const STATUS_ACTIVE = 1; // 活动状态 + /** * 数据表名 * @var string diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 692e55d0..49d7af83 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -35,7 +35,7 @@ Route::group('v1/', function () { // 获客场景相关 Route::group('plan/scenes', function () { Route::get('', 'app\cunkebao\controller\Scene@index'); // 获取场景列表 - Route::post('create', 'app\cunkebao\controller\Plan@index'); // 获取场景列表 + Route::post('create', 'app\cunkebao\controller\Plan@index'); // 获取场景列表 }); // 流量标签相关 diff --git a/Server/application/cunkebao/controller/device/GetAddResultedDevicesController.php b/Server/application/cunkebao/controller/device/GetAddResultedDevicesController.php index ba699571..86a166a0 100644 --- a/Server/application/cunkebao/controller/device/GetAddResultedDevicesController.php +++ b/Server/application/cunkebao/controller/device/GetAddResultedDevicesController.php @@ -85,7 +85,7 @@ class GetAddResultedDevicesController extends BaseController [ 'accountId' => $accountId, 'pageIndex' => 0, - 'pageSize' => 1 + 'pageSize' => 1 ], true ); diff --git a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php index f7e84799..0cb0cd58 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php @@ -6,8 +6,10 @@ use app\common\model\Device as DeviceModel; use app\common\model\DeviceTaskconf as DeviceTaskconfModel; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin; +use app\common\model\User as UserModel; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; +use Eison\Utils\Helper\ArrHelper; use library\ResponseHelper; /** @@ -24,8 +26,8 @@ class GetDeviceDetailV1Controller extends BaseController protected function checkUserDevicePermission(int $deviceId): void { $where = [ - 'deviceId' => $deviceId, - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId') ]; @@ -65,19 +67,20 @@ class GetDeviceDetailV1Controller extends BaseController protected function getTaskConfig(int $deviceId): array { $where = [ - 'deviceId' => $deviceId, - 'companyId' => $this->getUserInfo('companyId'), - 'deleteTime' => 0 + 'deviceId' => $deviceId, + 'companyId' => $this->getUserInfo('companyId'), ]; - $conf = DeviceTaskconfModel::where($where)->field('autoAddFriend,autoReply,contentSync,aiChat')->find(); + $conf = DeviceTaskconfModel::alias('c') + ->field([ + 'c.autoAddFriend', 'c.autoReply', 'c.contentSync', 'c.aiChat' + ]) + ->where($where) + ->find(); - return $conf ? $conf->toArray() : [ - 'autoAddFriend' => 0, - 'autoReply' => 0, - 'contentSync' => 0, - 'aiChat' => 0 - ]; + return $conf + ? $conf->toArray() + : ArrHelper::getValue([], 'autoAddFriend,autoReply,contentSync,aiChat', 0); } /** @@ -90,7 +93,7 @@ class GetDeviceDetailV1Controller extends BaseController protected function getTotalFriend(int $deviceId): int { $where = [ - 'deviceId' => $deviceId, + 'deviceId' => $deviceId, 'companyId' => $this->getUserInfo('companyId'), ]; @@ -125,7 +128,6 @@ class GetDeviceDetailV1Controller extends BaseController ->field([ 'd.id', 'd.imei', 'd.memo', 'd.alive', 'd.updateTime as lastUpdateTime', 'd.extra' ]) - ->where('d.deleteTime', 0) ->find($id); if (empty($device)) { @@ -155,7 +157,7 @@ class GetDeviceDetailV1Controller extends BaseController try { $id = $this->request->param('id/d'); - if ($this->getUserInfo('isAdmin') != 1) { + if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) { $this->checkUserDevicePermission($id); } diff --git a/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php index 25eacd47..ccd031c4 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceHandleLogsV1Controller.php @@ -4,6 +4,7 @@ namespace app\cunkebao\controller\device; use app\common\model\DeviceHandleLog; use app\common\model\DeviceUser as DeviceUserModel; +use app\common\model\User as UserModel; use app\cunkebao\controller\BaseController; use library\ResponseHelper; @@ -21,15 +22,15 @@ class GetDeviceHandleLogsV1Controller extends BaseController protected function checkUserDevicePermission(int $deviceId): void { $where = [ - 'deviceId' => $deviceId, - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId') ]; $hasPermission = DeviceUserModel::where($where)->count() > 0; if (!$hasPermission) { - throw new \Exception('您没有权限查看该设备', '403'); + throw new \Exception('您没有权限查看该设备', 403); } } @@ -43,9 +44,7 @@ class GetDeviceHandleLogsV1Controller extends BaseController { return DeviceHandleLog::alias('l') ->field([ - 'l.id', - 'l.content', - 'l.createTime', + 'l.id', 'l.content', 'l.createTime', 'u.username' ]) ->leftJoin('users u', 'l.userId = u.id') @@ -64,7 +63,7 @@ class GetDeviceHandleLogsV1Controller extends BaseController try { $deviceId = $this->request->param('id/d'); - if ($this->getUserInfo('isAdmin') != 1) { + if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) { $this->checkUserDevicePermission($deviceId); } diff --git a/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php index e08d91b9..aa7ef9a9 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceListV1Controller.php @@ -4,6 +4,7 @@ namespace app\cunkebao\controller\device; use app\common\model\Device as DeviceModel; use app\common\model\DeviceUser as DeviceUserModel; +use app\common\model\User as UserModel; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; use library\ResponseHelper; @@ -34,7 +35,6 @@ class GetDeviceListV1Controller extends BaseController } $where['d.companyId'] = $this->getUserInfo('companyId'); - $where['d.deleteTime'] = 0; return array_merge($params, $where); } @@ -72,7 +72,11 @@ class GetDeviceListV1Controller extends BaseController protected function getDeviceList(array $where, int $page = 1, int $limit = 10): \think\Paginator { $query = DeviceModel::alias('d') - ->field(['d.id', 'd.imei', 'd.memo', 'l.wechatId', 'd.alive', 'wa.nickname', 'wa.alias', '0 totalFriend']) + ->field([ + 'd.id', 'd.imei', 'd.memo', 'd.alive', + 'l.wechatId', + 'wa.nickname', 'wa.alias', '0 totalFriend' + ]) ->leftJoin('device_wechat_login l', 'd.id = l.deviceId') ->leftJoin('wechat_account wa', 'l.wechatId = wa.wechatId') ->order('d.id desc'); @@ -119,7 +123,7 @@ class GetDeviceListV1Controller extends BaseController public function index() { try { - if ($this->getUserInfo('isAdmin') == 1) { + if ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP) { $where = $this->makeWhere(); $result = $this->getDeviceList($where); } else { @@ -129,7 +133,7 @@ class GetDeviceListV1Controller extends BaseController return ResponseHelper::success( [ - 'list' => $this->countFriend($result), + 'list' => $this->countFriend($result), 'total' => $result->total(), ] ); diff --git a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php index 3677c22a..346546f1 100644 --- a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php @@ -4,6 +4,7 @@ namespace app\cunkebao\controller\device; use app\common\model\DeviceUser as DeviceUserModel; use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; +use app\common\model\User as UserModel; use app\common\model\WechatAccount as WechatAccountModel; use app\common\model\WechatFriend; use app\cunkebao\controller\BaseController; @@ -23,8 +24,8 @@ class GetRelatedAccountsV1Controller extends BaseController protected function checkUserDevicePermission(int $deviceId): void { $where = [ - 'deviceId' => $deviceId, - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId') ]; @@ -44,7 +45,7 @@ class GetRelatedAccountsV1Controller extends BaseController protected function getDeviceWechatIds(int $deviceId): array { $where = [ - 'deviceId' => $deviceId, + 'deviceId' => $deviceId, 'companyId' => $this->getUserInfo('companyId') ]; @@ -148,7 +149,7 @@ class GetRelatedAccountsV1Controller extends BaseController try { $deviceId = $this->request->param('id/d'); - if ($this->getUserInfo('isAdmin') != 1) { + if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) { $this->checkUserDevicePermission($deviceId); } @@ -159,7 +160,7 @@ class GetRelatedAccountsV1Controller extends BaseController [ 'deviceId' => $deviceId, 'accounts' => $wechatAccounts, - 'total' => count($wechatAccounts) + 'total' => count($wechatAccounts) ] ); } catch (\Exception $e) { diff --git a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php index dbb069dd..7f269c3c 100644 --- a/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php +++ b/Server/application/cunkebao/controller/device/PostAddDeviceV1Controller.php @@ -25,9 +25,8 @@ class PostAddDeviceV1Controller extends BaseController { if ($this->request->param('imei')) { $where = [ - 'imei' => $this->request->param('imei'), + 'imei' => $this->request->param('imei'), 'companyId' => $this->getUserInfo('companyId'), - 'deleteTime' => 0 ]; $exist = DeviceModel::where($where)->count() > 0; @@ -72,9 +71,9 @@ class PostAddDeviceV1Controller extends BaseController { DeviceHandleLogModel::addLog( [ - 'deviceId' => $deviceId, - 'content' => '添加设备', - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'content' => '添加设备', + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId'), ] ); diff --git a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php index b6182220..fb747ea4 100644 --- a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php +++ b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php @@ -6,6 +6,7 @@ use app\common\model\Device as DeviceModel; use app\common\model\DeviceHandleLog as DeviceHandleLogModel; use app\common\model\DeviceTaskconf; use app\common\model\DeviceUser as DeviceUserModel; +use app\common\model\User as UserModel; use app\cunkebao\controller\BaseController; use library\ResponseHelper; use think\Db; @@ -25,9 +26,8 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController protected function checkDeviceExists(int $deviceId) { $where = [ - 'deviceId' => $deviceId, - 'companyId' => $this->getUserInfo('companyId'), - 'deleteTime' => 0 + 'deviceId' => $deviceId, + 'companyId' => $this->getUserInfo('companyId'), ]; $device = DeviceModel::find($where); @@ -46,8 +46,8 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController protected function checkUserDevicePermission(int $deviceId): void { $where = [ - 'deviceId' => $deviceId, - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId') ]; @@ -81,9 +81,9 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController DeviceHandleLogModel::addLog( [ - 'deviceId' => $deviceId, - 'content' => $content, - 'userId' => $this->getUserInfo('id'), + 'deviceId' => $deviceId, + 'content' => $content, + 'userId' => $this->getUserInfo('id'), 'companyId' => $this->getUserInfo('companyId'), ] ); @@ -119,7 +119,7 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController $this->checkDeviceExists($id); - if ($this->getUserInfo('isAdmin') != 1) { + if ($this->getUserInfo('isAdmin') != UserModel::ADMIN_STP) { $this->checkUserDevicePermission($id); } diff --git a/Server/application/superadmin/controller/Menu/GetMenuTreeController.php b/Server/application/superadmin/controller/Menu/GetMenuTreeController.php index 6e3d029f..1a621838 100644 --- a/Server/application/superadmin/controller/Menu/GetMenuTreeController.php +++ b/Server/application/superadmin/controller/Menu/GetMenuTreeController.php @@ -2,6 +2,7 @@ namespace app\superadmin\controller\Menu; +use app\common\model\Administrator as AdministratorModel; use app\common\model\Menu as MenuModel; use app\common\model\AdministratorPermissions as AdministratorPermissionsModel; use app\superadmin\controller\BaseController; @@ -69,7 +70,7 @@ class GetMenuTreeController extends BaseController protected function getMenuTree(): array { // 获取所有菜单 - $allMenus = MenuModel::where('status', 1)->order('sort', 'asc')->select()->toArray(); + $allMenus = MenuModel::where('status', MenuModel::STATUS_ACTIVE)->order('sort', 'asc')->select()->toArray(); // 组织成树状结构 return $allMenus ? $this->buildMenuTree($allMenus) : []; @@ -84,8 +85,8 @@ class GetMenuTreeController extends BaseController protected function getTopMenusInPermissionIds(array $permissionIds): array { $where = [ - 'parentId' => 0, - 'status' => 1, + 'parentId' => MenuModel::TOP_LEVEL, + 'status' => MenuModel::STATUS_ACTIVE, ]; return MenuModel::where($where)->whereIn('id', $permissionIds)->order('sort', 'asc')->select()->toArray(); @@ -99,7 +100,7 @@ class GetMenuTreeController extends BaseController */ protected function getAllChildrenInPermissionIds(array $topMenuIds): array { - return MenuModel::where('status', 1)->whereIn('parentId', $topMenuIds)->order('sort', 'asc')->select()->toArray(); + return MenuModel::where('status', MenuModel::STATUS_ACTIVE)->whereIn('parentId', $topMenuIds)->order('sort', 'asc')->select()->toArray(); } /** @@ -165,7 +166,7 @@ class GetMenuTreeController extends BaseController */ public function index() { - if ($this->getAdminInfo('id') == 1) { + if ($this->getAdminInfo('id') == AdministratorModel::MASTER_ID) { $menuTree = $this->getMenuTree(); } else { $menuTree = $this->getMenuTreeByPermissions( diff --git a/Server/application/superadmin/controller/Menu/GetTopLevelForPermissionController.php b/Server/application/superadmin/controller/Menu/GetTopLevelForPermissionController.php index 0bf1d1c0..20c06321 100644 --- a/Server/application/superadmin/controller/Menu/GetTopLevelForPermissionController.php +++ b/Server/application/superadmin/controller/Menu/GetTopLevelForPermissionController.php @@ -19,8 +19,8 @@ class GetTopLevelForPermissionController extends BaseController protected function getTopLevelMenus(): array { $where = [ - 'parentId' => 0, - 'status' => 1 + 'parentId' => MenuModel::TOP_LEVEL, + 'status' => MenuModel::STATUS_NORMAL ]; return MenuModel::where($where)->field('id, title')->order('sort', 'asc')->select()->toArray(); diff --git a/Server/application/superadmin/controller/administrator/AddAdministratorController.php b/Server/application/superadmin/controller/administrator/AddAdministratorController.php index f0f6e19f..22a6a756 100644 --- a/Server/application/superadmin/controller/administrator/AddAdministratorController.php +++ b/Server/application/superadmin/controller/administrator/AddAdministratorController.php @@ -41,15 +41,15 @@ class AddAdministratorController extends BaseController protected function dataValidate(array $params): self { $validate = Validate::make([ - 'account' => 'require|regex:^[a-zA-Z0-9]+$|/\S+/', - 'username' => 'require|/\S+/', - 'password' => 'require|/\S+/', + 'account' => 'require|regex:^[a-zA-Z0-9]+$|/\S+/', + 'username' => 'require|/\S+/', + 'password' => 'require|/\S+/', 'permissionIds' => 'require|array', ], [ - 'account.require' => '账号不能为空', - 'account.regex' => '账号只能用数字或者字母或者数字字母组合', - 'username.require' => '用户名不能为空', - 'password.require' => '密码不能为空', + 'account.require' => '账号不能为空', + 'account.regex' => '账号只能用数字或者字母或者数字字母组合', + 'username.require' => '用户名不能为空', + 'password.require' => '密码不能为空', 'permissionIds.require' => '请至少分配一种权限', ]); @@ -67,7 +67,7 @@ class AddAdministratorController extends BaseController */ protected function checkPermission(): self { - if ($this->getAdminInfo('id') != 1) { + if ($this->getAdminInfo('id') != AdministratorModel::MASTER_ID) { throw new \Exception('您没有权限添加管理员', 403); } @@ -95,7 +95,7 @@ class AddAdministratorController extends BaseController ]); } else { return AdministratorPermissionsModel::create([ - 'adminId' => $adminId, + 'adminId' => $adminId, 'permissions' => json_encode($permissionData), ]); } diff --git a/Server/application/superadmin/controller/administrator/DeleteAdministratorController.php b/Server/application/superadmin/controller/administrator/DeleteAdministratorController.php index c8233053..0f7dea2d 100644 --- a/Server/application/superadmin/controller/administrator/DeleteAdministratorController.php +++ b/Server/application/superadmin/controller/administrator/DeleteAdministratorController.php @@ -66,12 +66,12 @@ class DeleteAdministratorController extends BaseController } // 只有超级管理员(ID为1)可以删除管理员 - if ($this->getAdminInfo('id') != 1) { + if ($this->getAdminInfo('id') != AdministratorModel::MASTER_ID) { throw new \Exception('您没有权限删除管理员', 403); } // 不能删除超级管理员账号 - if ($adminId == 1) { + if ($adminId == AdministratorModel::MASTER_ID) { throw new \Exception('不能删除超级管理员账号', 403); } } @@ -88,7 +88,7 @@ class DeleteAdministratorController extends BaseController $validate = Validate::make([ 'id' => 'require|regex:/^[1-9]\d*$/', ], [ - 'id.regex' => '非法请求', + 'id.regex' => '非法请求', 'id.require' => '非法请求', ]); diff --git a/Server/application/superadmin/controller/administrator/GetAdministratorDetailController.php b/Server/application/superadmin/controller/administrator/GetAdministratorDetailController.php index 4c053e38..25200dd9 100644 --- a/Server/application/superadmin/controller/administrator/GetAdministratorDetailController.php +++ b/Server/application/superadmin/controller/administrator/GetAdministratorDetailController.php @@ -22,9 +22,10 @@ class GetAdministratorDetailController extends BaseController protected function getAdministrator(int $adminId): AdministratorModel { $admin = AdministratorModel::alias('a') - ->field( - 'a.id, a.account, a.username, a.status, a.authId, a.createTime createdAt, a.lastLoginTime lastLogin, p.permissions' - ) + ->field([ + 'a.id', 'a.account', 'a.username', 'a.status', 'a.authId', 'a.createTime createdAt', 'a.lastLoginTime lastLogin', + 'p.permissions' + ]) ->leftJoin('administrator_permissions p', 'a.id = p.adminId') ->where('a.id', $adminId) ->find(); @@ -94,10 +95,10 @@ class GetAdministratorDetailController extends BaseController return ResponseHelper::success( array_merge($admin->toArray(), [ - 'roleName' => $roleName, + 'roleName' => $roleName, 'permissions' => $permissionIds, - 'lastLogin' => $admin->lastLogin ? date('Y-m-d H:i', $admin->lastLogin) : '从未登录', - 'createdAt' => date('Y-m-d H:i', $admin->createdAt), + 'lastLogin' => $admin->lastLogin ? date('Y-m-d H:i', $admin->lastLogin) : '从未登录', + 'createdAt' => date('Y-m-d H:i', $admin->createdAt), ]) ); } catch (\Exception $e) { diff --git a/Server/application/superadmin/controller/administrator/GetAdministratorListController.php b/Server/application/superadmin/controller/administrator/GetAdministratorListController.php index c3b7d28b..6bf4e7ab 100644 --- a/Server/application/superadmin/controller/administrator/GetAdministratorListController.php +++ b/Server/application/superadmin/controller/administrator/GetAdministratorListController.php @@ -40,9 +40,9 @@ class GetAdministratorListController extends Controller protected function getAdministratorList(array $where): \think\Paginator { $query = AdministratorModel::alias('a') - ->field( - 'id, account, username, status, authId, createTime createdAt, lastLoginTime, lastLoginIp' - ); + ->field([ + 'a.id', 'a.account', 'a.username', 'a.status', 'a.authId', 'a.createTime createdAt', 'a.lastLoginTime', 'a.lastLoginIp' + ]); foreach ($where as $key => $value) { if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') { @@ -139,13 +139,13 @@ class GetAdministratorListController extends Controller foreach ($list->items() as $item) { $section = [ - 'id' => $item->id, - 'account' => $item->account, - 'username' => $item->username, - 'status' => $item->status, - 'createdAt' => date('Y-m-d H:i:s', $item->createdAt), - 'lastLogin' => !empty($item->lastLoginTime) ? date('Y-m-d H:i:s', $item->lastLoginTime) : '从未登录', - 'role' => $this->getRoleName($item->authId), + 'id' => $item->id, + 'account' => $item->account, + 'username' => $item->username, + 'status' => $item->status, + 'createdAt' => date('Y-m-d H:i:s', $item->createdAt), + 'lastLogin' => !empty($item->lastLoginTime) ? date('Y-m-d H:i:s', $item->lastLoginTime) : '从未登录', + 'role' => $this->getRoleName($item->authId), 'permissions' => $this->getPermissions($item->id), ]; @@ -167,7 +167,7 @@ class GetAdministratorListController extends Controller return ResponseHelper::success( [ - 'list' => $this->makeReturnedResult($result), + 'list' => $this->makeReturnedResult($result), 'total' => $result->total(), ] ); diff --git a/Server/application/superadmin/controller/administrator/UpdateAdministratorController.php b/Server/application/superadmin/controller/administrator/UpdateAdministratorController.php index 5cb8ba17..9e886c09 100644 --- a/Server/application/superadmin/controller/administrator/UpdateAdministratorController.php +++ b/Server/application/superadmin/controller/administrator/UpdateAdministratorController.php @@ -48,16 +48,16 @@ class UpdateAdministratorController extends BaseController protected function dataValidate(array $params): self { $validate = Validate::make([ - 'id' => 'require|regex:/^[1-9]\d*$/', - 'account' => 'require|regex:^[a-zA-Z0-9]+$|/\S+/', - 'username' => 'require|/\S+/', - 'password' => '/\S+/', + 'id' => 'require|regex:/^[1-9]\d*$/', + 'account' => 'require|regex:^[a-zA-Z0-9]+$|/\S+/', + 'username' => 'require|/\S+/', + 'password' => '/\S+/', 'permissionIds' => 'array', ], [ - 'id.require' => '缺少必要参数', - 'account.require' => '账号不能为空', - 'account.regex' => '账号只能用数字或者字母或者数字字母组合', - 'username.require' => '用户名不能为空', + 'id.require' => '缺少必要参数', + 'account.require' => '账号不能为空', + 'account.regex' => '账号只能用数字或者字母或者数字字母组合', + 'username.require' => '用户名不能为空', 'permissionIds.array' => '请至少分配一种权限', ]); @@ -79,11 +79,11 @@ class UpdateAdministratorController extends BaseController { $currentAdminId = $this->getAdminInfo('id'); - if ($currentAdminId != 1 && $currentAdminId != $adminId) { + if ($currentAdminId != AdministratorModel::MASTER_ID && $currentAdminId != $adminId) { throw new \Exception('您没有权限修改其他管理员', 403); } - if ($params['id'] != 1 && empty($params['permissionIds'])) { + if ($params['id'] != AdministratorModel::MASTER_ID && empty($params['permissionIds'])) { throw new \Exception('请至少分配一种权限', 403); } @@ -111,7 +111,7 @@ class UpdateAdministratorController extends BaseController ]); } else { return AdministratorPermissionsModel::create([ - 'adminId' => $adminId, + 'adminId' => $adminId, 'permissions' => json_encode($permissionData), ]); } @@ -137,7 +137,7 @@ class UpdateAdministratorController extends BaseController $this->udpateAdministrator($params); // 如果当前是超级管理员(ID为1),并且修改的不是自己,则更新权限 - if ($this->getAdminInfo('id') == 1 + if ($this->getAdminInfo('id') == AdministratorModel::MASTER_ID && $this->getAdminInfo('id') != $adminId && !empty($params['permissionIds']) ) { diff --git a/Server/application/superadmin/controller/auth/AuthLoginController.php b/Server/application/superadmin/controller/auth/AuthLoginController.php index 2af1a59f..0c52e295 100644 --- a/Server/application/superadmin/controller/auth/AuthLoginController.php +++ b/Server/application/superadmin/controller/auth/AuthLoginController.php @@ -7,6 +7,7 @@ use app\superadmin\controller\administrator\DeleteAdministratorController; use library\ResponseHelper; use think\Controller; use think\Validate; +use think\facade\Cookie; class AuthLoginController extends Controller { @@ -30,7 +31,7 @@ class AuthLoginController extends Controller protected function dataValidate(array $params): self { $validate = Validate::make([ - 'account' => 'require|/\S+/', + 'account' => 'require|/\S+/', 'password' => 'require|/\S+/', ]); @@ -96,11 +97,11 @@ class AuthLoginController extends Controller { // 获取当前环境 $env = app()->env->get('APP_ENV', 'production'); - + // 获取请求的域名 $origin = $this->request->header('origin'); $domain = ''; - + if ($origin) { // 解析域名 $parsedUrl = parse_url($origin); @@ -112,7 +113,7 @@ class AuthLoginController extends Controller // 生产环境使用顶级域名 $parts = explode('.', $parsedUrl['host']); if (count($parts) > 1) { - $domain = '.' . $parts[count($parts)-2] . '.' . $parts[count($parts)-1]; + $domain = '.' . $parts[count($parts) - 2] . '.' . $parts[count($parts) - 1]; } } } @@ -120,11 +121,11 @@ class AuthLoginController extends Controller // 设置cookie选项 $options = [ - 'expire' => 86400, - 'path' => '/', + 'expire' => 86400, + 'path' => '/', 'httponly' => true, - 'samesite' => 'None', // 允许跨域 - 'secure' => true // 仅 HTTPS 下有效 + 'samesite' => 'None', // 允许跨域 + 'secure' => true // 仅 HTTPS 下有效 ]; // 如果有域名,添加到选项 @@ -133,8 +134,8 @@ class AuthLoginController extends Controller } // 设置cookies - \think\facade\Cookie::set('admin_id', $admin->id, $options); - \think\facade\Cookie::set('admin_token', $this->createToken($admin), $options); + Cookie::set('admin_id', $admin->id, $options); + Cookie::set('admin_token', $this->createToken($admin), $options); } /** @@ -152,10 +153,10 @@ class AuthLoginController extends Controller return ResponseHelper::success( [ - 'id' => $admin->id, - 'name' => $admin->username, + 'id' => $admin->id, + 'name' => $admin->username, 'account' => $admin->account, - 'token' => cookie('admin_token') + 'token' => Cookie::get('admin_token') ] ); } catch (\Exception $e) { diff --git a/Server/application/superadmin/controller/company/CreateCompanyController.php b/Server/application/superadmin/controller/company/CreateCompanyController.php index bc49921b..19dd4141 100644 --- a/Server/application/superadmin/controller/company/CreateCompanyController.php +++ b/Server/application/superadmin/controller/company/CreateCompanyController.php @@ -7,10 +7,12 @@ use app\common\model\Company as CompanyModel; use app\common\model\User as UsersModel; use app\superadmin\controller\BaseController; use Eison\Utils\Helper\ArrHelper; +use Exception; use library\ResponseHelper; use library\s2\CurlHandle; use think\Db; use think\facade\Env; +use think\response\Json; use think\Validate; /** @@ -23,7 +25,7 @@ class CreateCompanyController extends BaseController * * @param array $params * @return mixed|null - * @throws \Exception + * @throws Exception */ protected function s2CreateUser(array $params): ?array { @@ -38,7 +40,7 @@ class CreateCompanyController extends BaseController $result = json_decode($response, true); if ($result['code'] != 200) { - throw new \Exception($result['msg'], 210 . $result['code']); + throw new Exception($result['msg'], 210 . $result['code']); } return $result['data'] ?: null; @@ -63,7 +65,7 @@ class CreateCompanyController extends BaseController $result = json_decode($response, true); if ($result['code'] != 200) { - throw new \Exception($result['msg'], 210 . $result['code']); + throw new Exception($result['msg'], 210 . $result['code']); } return $result['data'] ?: null; @@ -74,33 +76,33 @@ class CreateCompanyController extends BaseController * * @param array $params * @return $this - * @throws \Exception + * @throws Exception */ protected function dataValidate(array $params): self { $validate = Validate::make([ - 'name' => 'require|max:50|/\S+/', - 'account' => 'require|regex:^[a-zA-Z0-9]+$|max:20|/\S+/', + 'name' => 'require|max:50|/\S+/', + 'account' => 'require|regex:^[a-zA-Z0-9]+$|max:20|/\S+/', 'username' => 'require|max:20|/\S+/', - 'phone' => 'require|regex:/^1[3-9]\d{9}$/', - 'status' => 'require|in:0,1', + 'phone' => 'require|regex:/^1[3-9]\d{9}$/', + 'status' => 'require|in:0,1', 'password' => 'require|/\S+/', - 'memo' => '/\S+/', + 'memo' => '/\S+/', ], [ - 'name.require' => '请输入项目名称', - 'account.require' => '请输入账号', - 'account.max' => '账号长度受限', - 'account.regex' => '账号只能用数字或者字母或者数字字母组合', + 'name.require' => '请输入项目名称', + 'account.require' => '请输入账号', + 'account.max' => '账号长度受限', + 'account.regex' => '账号只能用数字或者字母或者数字字母组合', 'username.require' => '请输入用户昵称', - 'phone.require' => '请输入手机号', - 'phone.regex' => '手机号格式错误', - 'status.require' => '缺少重要参数', - 'status.in' => '非法参数', + 'phone.require' => '请输入手机号', + 'phone.regex' => '手机号格式错误', + 'status.require' => '缺少重要参数', + 'status.in' => '非法参数', 'password.require' => '请输入密码', ]); if (!$validate->check($params)) { - throw new \Exception($validate->getError(), 400); + throw new Exception($validate->getError(), 400); } return $this; @@ -111,7 +113,7 @@ class CreateCompanyController extends BaseController * * @param array $params * @return void - * @throws \Exception + * @throws Exception */ protected function s2CreateDeviceGroup(array $params): void { @@ -119,7 +121,7 @@ class CreateCompanyController extends BaseController $respon = json_decode($respon, true); if ($respon['code'] != 200) { - throw new \Exception('设备分组添加错误', 210 . $respon['code']); + throw new Exception('设备分组添加错误', 210 . $respon['code']); } } @@ -128,14 +130,14 @@ class CreateCompanyController extends BaseController * * @param array $params * @return array - * @throws \Exception + * @throws Exception */ protected function creatS2About(array $params): array { $department = $this->s2CreateDepartmentAndUser($params); if (!$department || !isset($department['id']) || !isset($department['departmentId'])) { - throw new \Exception('S2返参异常', 210402); + throw new Exception('S2返参异常', 210402); } // 设备创建分组 @@ -152,7 +154,7 @@ class CreateCompanyController extends BaseController * * @param array $params * @return void - * @throws \Exception + * @throws Exception */ protected function ckbCreateCompany(array $params): void { @@ -160,7 +162,7 @@ class CreateCompanyController extends BaseController $result = CompanyModel::create($params); if (!$result) { - throw new \Exception('创建公司记录失败', 402); + throw new Exception('创建公司记录失败', 402); } } @@ -169,17 +171,17 @@ class CreateCompanyController extends BaseController * * @param array $params * @return void - * @throws \Exception + * @throws Exception */ protected function createFuncUsers(array $params): void { $seedCols = [ - ['account' => $params['account'] . '_offline', 'username' => '处理离线专用', 'status' => 0, 'isAdmin' => 0, 'typeId' => -1], - ['account' => $params['account'] . '_delete' , 'username' => '处理删除专用', 'status' => 0, 'isAdmin' => 0, 'typeId' => -1], + ['account' => $params['account'] . '_offline', 'username' => '处理离线专用', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER], + ['account' => $params['account'] . '_delete', 'username' => '处理删除专用', 'status' => UsersModel::STATUS_STOP, 'isAdmin' => UsersModel::ADMIN_OTP, 'typeId' => UsersModel::NOT_USER], ]; foreach ($seedCols as $seeds) { - $this->s2CreateUser (array_merge($params, ArrHelper::getValue('account,username', $seeds))); + $this->s2CreateUser(array_merge($params, ArrHelper::getValue('account,username', $seeds))); $this->ckbCreateUser(array_merge($params, $seeds)); } } @@ -189,7 +191,7 @@ class CreateCompanyController extends BaseController * * @param array $params * @return void - * @throws \Exception + * @throws Exception */ protected function ckbCreateUser(array $params): void { @@ -201,14 +203,14 @@ class CreateCompanyController extends BaseController ]); if (!UsersModel::create($params)) { - throw new \Exception('创建用户记录失败', 402); + throw new Exception('创建用户记录失败', 402); } } /** * @param array $params * @return void - * @throws \Exception + * @throws Exception */ protected function createCkbAbout(array $params) { @@ -217,8 +219,8 @@ class CreateCompanyController extends BaseController // 2. 存客宝创建操盘手总账号 $this->ckbCreateUser(array_merge($params, [ - 'isAdmin' => 1, // 主要账号默认1 - 'typeId' => 1, // 类型:运营后台/操盘手传1、 门店传2 + 'isAdmin' => UsersModel::ADMIN_STP, // 主要账号默认1 + 'typeId' => UsersModel::MASTER_USER, // 类型:运营后台/操盘手传1、 门店传2 ])); } @@ -227,7 +229,7 @@ class CreateCompanyController extends BaseController * * @param array $where * @return void - * @throws \Exception + * @throws Exception */ protected function checkCompanyNameOrAccountOrPhoneExists(array $where): void { @@ -236,26 +238,26 @@ class CreateCompanyController extends BaseController // 项目名称尽量不重名 $exists = CompanyModel::where(compact('name'))->count() > 0; if ($exists) { - throw new \Exception('项目名称已存在', 403); + throw new Exception('项目名称已存在', 403); } // 账号不重名 $exists = UsersModel::where(compact('account'))->count() > 0; if ($exists) { - throw new \Exception('用户账号已存在', 403); + throw new Exception('用户账号已存在', 403); } // 手机号不重名 $exists = UsersModel::where(compact('phone'))->count() > 0; if ($exists) { - throw new \Exception('手机号已存在', 403); + throw new Exception('手机号已存在', 403); } } /** * 创建新项目 * - * @return \think\response\Json + * @return Json */ public function index() { @@ -273,7 +275,7 @@ class CreateCompanyController extends BaseController Db::commit(); return ResponseHelper::success(); - } catch (\Exception $e) { + } catch (Exception $e) { Db::rollback(); return ResponseHelper::error($e->getMessage(), $e->getCode()); } diff --git a/Server/application/superadmin/controller/company/DeleteCompanyController.php b/Server/application/superadmin/controller/company/DeleteCompanyController.php index 60d66ea7..5334d4c6 100644 --- a/Server/application/superadmin/controller/company/DeleteCompanyController.php +++ b/Server/application/superadmin/controller/company/DeleteCompanyController.php @@ -26,7 +26,7 @@ class DeleteCompanyController extends BaseController $validate = Validate::make([ 'id' => 'require|regex:/^[1-9]\d*$/', ], [ - 'id.regex' => '非法请求', + 'id.regex' => '非法请求', 'id.require' => '非法请求', ]); diff --git a/Server/application/superadmin/controller/company/GetCompanyDetailForProfileController.php b/Server/application/superadmin/controller/company/GetCompanyDetailForProfileController.php index b1064530..e878bb0d 100644 --- a/Server/application/superadmin/controller/company/GetCompanyDetailForProfileController.php +++ b/Server/application/superadmin/controller/company/GetCompanyDetailForProfileController.php @@ -23,9 +23,9 @@ class GetCompanyDetailForProfileController extends BaseController */ protected function getDeveiceWechats(int $companyId): array { - $wechatId = DeviceWechatLoginModel::where('companyId', $companyId)->column('wechatId'); + $wechatIds = DeviceWechatLoginModel::where('companyId', $companyId)->column('wechatId'); - return array_unique($wechatId); + return array_unique($wechatIds); } /** @@ -60,7 +60,7 @@ class GetCompanyDetailForProfileController extends BaseController */ protected function getUsersCountByCompanyId(int $companyId): int { - $where = array_merge(compact('companyId'), array('isAdmin' => 0)); + $where = array_merge(compact('companyId'), array('isAdmin' => UserModel::ADMIN_OTP)); return UserModel::where($where)->count(); } @@ -79,7 +79,7 @@ class GetCompanyDetailForProfileController extends BaseController 'c.id', 'c.name', 'c.memo', 'c.companyId', 'c.createTime', 'u.account', 'u.phone' ]) - ->leftJoin('users u', 'c.companyId = u.companyId and u.isAdmin = 1') + ->leftJoin('users u', 'c.companyId = u.companyId and u.isAdmin = ' . UserModel::ADMIN_STP) ->find($id); if (!$detail) { @@ -100,7 +100,7 @@ class GetCompanyDetailForProfileController extends BaseController try { $data = $this->getCompanyDetail($id); - $userCount = $this->getUsersCountByCompanyId($id); + $userCount = $this->getUsersCountByCompanyId($id); $deviceCount = $this->getDeviceCountByCompanyId($id); $friendCount = $this->getFriendCountByCompanyId($id); diff --git a/Server/application/superadmin/controller/company/GetCompanyDetailForUpdateController.php b/Server/application/superadmin/controller/company/GetCompanyDetailForUpdateController.php index 5a03f582..ddf63ae0 100644 --- a/Server/application/superadmin/controller/company/GetCompanyDetailForUpdateController.php +++ b/Server/application/superadmin/controller/company/GetCompanyDetailForUpdateController.php @@ -4,6 +4,7 @@ namespace app\superadmin\controller\company; use app\common\model\Company as CompanyModel; use app\common\model\Device as DeviceModel; +use app\common\model\User as UserModel; use app\superadmin\controller\BaseController; use library\ResponseHelper; @@ -43,7 +44,7 @@ class GetCompanyDetailForUpdateController extends BaseController 'c.id', 'c.name', 'c.status', 'c.memo', 'c.companyId', 'u.account', 'u.username', 'u.phone', 'u.s2_accountId' ]) - ->leftJoin('users u', 'c.companyId = u.companyId and u.isAdmin = 1') + ->leftJoin('users u', 'c.companyId = u.companyId and u.isAdmin = ' . UserModel::ADMIN_STP) ->find($id); if (!$detail) { diff --git a/Server/application/superadmin/controller/company/GetCompanyDevicesForProfileController.php b/Server/application/superadmin/controller/company/GetCompanyDevicesForProfileController.php index 3b4b514d..810a6356 100644 --- a/Server/application/superadmin/controller/company/GetCompanyDevicesForProfileController.php +++ b/Server/application/superadmin/controller/company/GetCompanyDevicesForProfileController.php @@ -23,7 +23,11 @@ class GetCompanyDevicesForProfileController extends Controller { $companyId = $this->request->param('companyId/d', 0); - $devices = DeviceModel::where(compact('companyId'))->field('id,memo,imei,phone,model,brand,alive,id deviceId') + $devices = DeviceModel::alias('d') + ->field([ + 'd.id', 'd.memo', 'd.imei', 'd.phone', 'd.model', 'd.brand', 'd.alive', 'd.id deviceId' + ]) + ->where(compact('companyId')) ->select() ->toArray(); @@ -47,7 +51,10 @@ class GetCompanyDevicesForProfileController extends Controller // 获取最新登录记录id $latestIds = array_column($latestLogs, 'lastedId'); - return DeviceWechatLoginModel::field('deviceId,wechatId,alive wAlive') + return DeviceWechatLoginModel::alias('d') + ->field([ + 'd.deviceId', 'd.wechatId', 'd.alive wAlive' + ]) ->whereIn('id', $latestIds) ->select() ->toArray(); @@ -65,7 +72,10 @@ class GetCompanyDevicesForProfileController extends Controller $relations = $this->getDeviceWechatRelationsByDeviceIds($deviceIds); // 统计微信好友数量 - $friendCounts = WechatFriendModel::alias('f')->field('ownerWechatId wechatId,count(*) friendCount') + $friendCounts = WechatFriendModel::alias('f') + ->field([ + 'f.ownerWechatId wechatId', 'count(*) friendCount' + ]) ->whereIn('ownerWechatId', array_column($relations, 'wechatId')) ->group('ownerWechatId') ->select() diff --git a/Server/application/superadmin/controller/company/GetCompanyListController.php b/Server/application/superadmin/controller/company/GetCompanyListController.php index e53d9990..74c8abc6 100644 --- a/Server/application/superadmin/controller/company/GetCompanyListController.php +++ b/Server/application/superadmin/controller/company/GetCompanyListController.php @@ -57,9 +57,9 @@ class GetCompanyListController extends BaseController protected function getCompanyList(array $where): \think\Paginator { $query = CompanyModel::alias('c') - ->field( - 'id, name, status, companyId, memo, createTime' - ); + ->field([ + 'c.id', 'c.name', 'c.status', 'c.companyId', 'c.memo', 'c.createTime' + ]); foreach ($where as $key => $value) { if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') { @@ -118,7 +118,7 @@ class GetCompanyListController extends BaseController return ResponseHelper::success( [ - 'list' => $this->makeReturnedResult($result), + 'list' => $this->makeReturnedResult($result), 'total' => $result->total(), ] ); diff --git a/Server/application/superadmin/controller/company/GetCompanySubusersForProfileController.php b/Server/application/superadmin/controller/company/GetCompanySubusersForProfileController.php index ad81e182..d9a02f8e 100644 --- a/Server/application/superadmin/controller/company/GetCompanySubusersForProfileController.php +++ b/Server/application/superadmin/controller/company/GetCompanySubusersForProfileController.php @@ -21,10 +21,16 @@ class GetCompanySubusersForProfileController extends Controller { $where = [ 'companyId' => $this->request->param('companyId/d', 0), - 'isAdmin' => 0 + 'isAdmin' => UserModel::ADMIN_OTP ]; - return UserModel::field('id,account,phone,username,avatar,status,createTime,typeId')->where($where)->select()->toArray(); + return UserModel::alias('u') + ->field([ + 'u.id', 'u.account', 'u.phone', 'u.username', 'u.avatar', 'u.status', 'u.createTime', 'u.typeId' + ]) + ->where($where) + ->select() + ->toArray(); } /** diff --git a/Server/application/superadmin/controller/company/UpdateCompanyController.php b/Server/application/superadmin/controller/company/UpdateCompanyController.php index c7d63bb6..aa6fac08 100644 --- a/Server/application/superadmin/controller/company/UpdateCompanyController.php +++ b/Server/application/superadmin/controller/company/UpdateCompanyController.php @@ -46,7 +46,7 @@ class UpdateCompanyController extends BaseController protected function getUserDetailByCompanyId(): ?UsersModel { $where = [ - 'isAdmin' => 1, // 必须保证 isAdmin 有且只有一个 + 'isAdmin' => UsersModel::MASTER_USER, // 必须保证 isAdmin 有且只有一个 'companyId' => $this->companyId, ]; @@ -175,23 +175,23 @@ class UpdateCompanyController extends BaseController protected function dataValidate(array $params): self { $validate = Validate::make([ - 'id' => 'require', - 'name' => 'require|max:50|/\S+/', + 'id' => 'require', + 'name' => 'require|max:50|/\S+/', 'username' => 'require|max:20|/\S+/', - 'account' => 'require|regex:^[a-zA-Z0-9]+$|max:20|/\S+/', - 'phone' => 'require|regex:/^1[3-9]\d{9}$/', - 'status' => 'require|in:0,1' + 'account' => 'require|regex:^[a-zA-Z0-9]+$|max:20|/\S+/', + 'phone' => 'require|regex:/^1[3-9]\d{9}$/', + 'status' => 'require|in:0,1' ], [ - 'id.require' => '非法请求', - 'name.require' => '请输入项目名称', + 'id.require' => '非法请求', + 'name.require' => '请输入项目名称', 'username.require' => '请输入用户昵称', - 'account.require' => '请输入账号', - 'account.regex' => '账号只能用数字或者字母或者数字字母组合', - 'account.max' => '账号长度受限', - 'phone.require' => '请输入手机号', - 'phone.regex' => '手机号格式错误', - 'status.require' => '缺少重要参数', - 'status.in' => '非法参数', + 'account.require' => '请输入账号', + 'account.regex' => '账号只能用数字或者字母或者数字字母组合', + 'account.max' => '账号长度受限', + 'phone.require' => '请输入手机号', + 'phone.regex' => '手机号格式错误', + 'status.require' => '缺少重要参数', + 'status.in' => '非法参数', ]); if (!$validate->check($params)) { diff --git a/Server/application/superadmin/controller/dashboard/GetBasestatisticsController.php b/Server/application/superadmin/controller/dashboard/GetBasestatisticsController.php index 01278b8c..e4dfc371 100644 --- a/Server/application/superadmin/controller/dashboard/GetBasestatisticsController.php +++ b/Server/application/superadmin/controller/dashboard/GetBasestatisticsController.php @@ -52,7 +52,7 @@ class GetBasestatisticsController extends Controller { return ResponseHelper::success( [ - 'companyCount' => $this->getCompanyCount(), + 'companyCount' => $this->getCompanyCount(), 'adminCount' => $this->getAdminCount(), 'customerCount' => $this->getDeviceCount(), ] diff --git a/Server/application/superadmin/controller/devices/GetAddResultedDevicesController.php b/Server/application/superadmin/controller/devices/GetAddResultedDevicesController.php index 9232ebf5..f5083380 100644 --- a/Server/application/superadmin/controller/devices/GetAddResultedDevicesController.php +++ b/Server/application/superadmin/controller/devices/GetAddResultedDevicesController.php @@ -85,7 +85,7 @@ class GetAddResultedDevicesController extends Controller [ 'accountId' => $accountId, 'pageIndex' => 0, - 'pageSize' => 1 + 'pageSize' => 1 ], true ); diff --git a/Server/application/superadmin/controller/traffic/GetPoolListController.php b/Server/application/superadmin/controller/traffic/GetPoolListController.php index 67ee2d0b..d19ce5d3 100644 --- a/Server/application/superadmin/controller/traffic/GetPoolListController.php +++ b/Server/application/superadmin/controller/traffic/GetPoolListController.php @@ -61,9 +61,9 @@ class GetPoolListController extends BaseController protected function makeReturnedValue(\think\Paginator $list): \think\Paginator { $list->each(function ($item) { - $item->gender = $this->formatGender($item->gender); + $item->gender = $this->formatGender($item->gender); $item->addTime = $this->formatDate($item->addTime); - $item->tags = $this->handlTags($item->tags); + $item->tags = $this->handlTags($item->tags); }); return $list; @@ -78,15 +78,10 @@ class GetPoolListController extends BaseController { $query = TrafficPoolModel::alias('tp') ->field([ - 'ts.id', 'tp.wechatId', - 'ts.createTime as addTime', - 'ts.fromd as source', + 'ts.id', 'ts.createTime as addTime', 'ts.fromd as source', 'c.name as projectName', - 'wa.avatar', - 'wa.gender', - 'wa.nickname', - 'wa.region', + 'wa.avatar', 'wa.gender', 'wa.nickname', 'wa.region', 'wt.tags' ]) ->join('traffic_source ts', 'tp.identifier = ts.identifier', 'RIGHT') @@ -108,9 +103,9 @@ class GetPoolListController extends BaseController return ResponseHelper::success( [ - 'list' => $this->makeReturnedValue($list)->items(), + 'list' => $this->makeReturnedValue($list)->items(), 'total' => $list->total(), - 'page' => $list->currentPage(), + 'page' => $list->currentPage(), 'limit' => $list->listRows() ] ); From 1ec9863a8a58ab0a468d2c2c6992bfb67c1fe37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 14:23:24 +0800 Subject: [PATCH 08/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=E7=AB=AF=20-=20=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=A1=B5=E9=9D=A2=E4=B8=ADapi=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E8=B0=83=E7=94=A8=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/devices/page.tsx | 20 +++++-- Cunkebao/app/scenarios/[channel]/page.tsx | 37 ++++++++++-- Cunkebao/app/scenarios/page.tsx | 38 +++++++++++-- Cunkebao/hooks/useDeviceStatusPolling.ts | 69 +++++++++++++++++++++-- 4 files changed, 143 insertions(+), 21 deletions(-) diff --git a/Cunkebao/app/devices/page.tsx b/Cunkebao/app/devices/page.tsx index 6a8eaa96..3d6fe02e 100644 --- a/Cunkebao/app/devices/page.tsx +++ b/Cunkebao/app/devices/page.tsx @@ -124,7 +124,7 @@ export default function DevicesPage() { setIsLoading(false) } // 移除isLoading依赖,只保留真正需要的依赖 - }, [searchQuery, devicesPerPage]) + }, [searchQuery]) // devicesPerPage是常量,不需要加入依赖 // 加载下一页数据的函数,使用ref来追踪页码,避免依赖循环 const loadNextPage = useCallback(() => { @@ -140,8 +140,21 @@ export default function DevicesPage() { // 只依赖必要的状态 }, [hasMore, isLoading, loadDevices]); + // 追踪组件是否已挂载 + const isMounted = useRef(true); + + // 组件卸载时更新挂载状态 + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); + // 初始加载和搜索时刷新列表 useEffect(() => { + // 组件未挂载,不执行操作 + if (!isMounted.current) return; + // 重置页码 setCurrentPage(1) pageRef.current = 1 @@ -154,13 +167,11 @@ export default function DevicesPage() { // 如果没有更多数据或者正在加载,不创建observer if (!hasMore || isLoading) return; - let isMounted = true; // 追踪组件是否已挂载 - // 创建观察器观察加载点 const observer = new IntersectionObserver( entries => { // 如果交叉了,且有更多数据,且当前不在加载状态,且组件仍然挂载 - if (entries[0].isIntersecting && hasMore && !isLoading && isMounted) { + if (entries[0].isIntersecting && hasMore && !isLoading && isMounted.current) { loadNextPage(); } }, @@ -174,7 +185,6 @@ export default function DevicesPage() { // 清理观察器 return () => { - isMounted = false; observer.disconnect(); } }, [hasMore, isLoading, loadNextPage]) diff --git a/Cunkebao/app/scenarios/[channel]/page.tsx b/Cunkebao/app/scenarios/[channel]/page.tsx index 18004ce8..5126be5b 100644 --- a/Cunkebao/app/scenarios/[channel]/page.tsx +++ b/Cunkebao/app/scenarios/[channel]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import { ChevronLeft, Copy, Link, HelpCircle } from "lucide-react" import { Button } from "@/components/ui/button" import { useRouter } from "next/navigation" @@ -95,18 +95,35 @@ export default function ChannelPage({ params }: { params: { channel: string } }) // 从URL query参数获取场景ID const [sceneId, setSceneId] = useState(null); + // 使用ref追踪sceneId值,避免重复请求 + const sceneIdRef = useRef(null); + // 追踪组件是否已挂载 + const isMounted = useRef(true); + + // 组件卸载时更新挂载状态 + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); // 获取URL中的查询参数 useEffect(() => { + // 组件未挂载,不执行操作 + if (!isMounted.current) return; + // 从URL获取id参数 const urlParams = new URLSearchParams(window.location.search); const idParam = urlParams.get('id'); if (idParam && !isNaN(Number(idParam))) { setSceneId(Number(idParam)); + sceneIdRef.current = Number(idParam); } else { // 如果没有传递有效的ID,使用函数获取默认ID - setSceneId(getSceneIdFromChannel(channel)); + const defaultId = getSceneIdFromChannel(channel); + setSceneId(defaultId); + sceneIdRef.current = defaultId; } }, [channel]); @@ -114,7 +131,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) { id: "1", name: `${channelName}直播获客计划`, - status: "running", + status: "running" as const, stats: { devices: 5, acquired: 31, @@ -131,7 +148,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) { id: "2", name: `${channelName}评论区获客计划`, - status: "paused", + status: "paused" as const, stats: { devices: 3, acquired: 15, @@ -145,7 +162,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) customers: Math.floor(Math.random() * 20) + 20, })), }, - ] + ] as Task[]; const [tasks, setTasks] = useState([]) const [loading, setLoading] = useState(true) @@ -233,6 +250,14 @@ export default function ChannelPage({ params }: { params: { channel: string } }) // 修改API数据处理部分 useEffect(() => { + // 组件未挂载,不执行操作 + if (!isMounted.current) return; + + // 防止重复请求:如果sceneId没有变化且已经加载过数据,则不重新请求 + if (sceneId === sceneIdRef.current && tasks.length > 0 && !loading) { + return; + } + const fetchPlanList = async () => { try { setLoading(true); @@ -298,7 +323,7 @@ export default function ChannelPage({ params }: { params: { channel: string } }) }; fetchPlanList(); - }, [channel, initialTasks, sceneId]); + }, [sceneId]); // 只依赖sceneId变化触发请求 // 辅助函数:根据渠道获取场景ID const getSceneIdFromChannel = (channel: string): number => { diff --git a/Cunkebao/app/scenarios/page.tsx b/Cunkebao/app/scenarios/page.tsx index a598b978..11ecaea0 100644 --- a/Cunkebao/app/scenarios/page.tsx +++ b/Cunkebao/app/scenarios/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import type React from "react" import { TrendingUp, Users, ChevronLeft, Bot, Sparkles, Plus, Phone } from "lucide-react" import { Card } from "@/components/ui/card" @@ -97,8 +97,25 @@ export default function ScenariosPage() { const [channels, setChannels] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + // 使用ref跟踪组件挂载状态 + const isMounted = useRef(true); + // 使用ref跟踪是否已经加载过数据 + const hasLoadedRef = useRef(false); + + // 组件卸载时更新挂载状态 + useEffect(() => { + return () => { + isMounted.current = false; + }; + }, []); useEffect(() => { + // 组件未挂载,不执行操作 + if (!isMounted.current) return; + + // 如果已经加载过数据,不再重复请求 + if (hasLoadedRef.current && channels.length > 0) return; + const loadScenes = async () => { try { setLoading(true) @@ -116,15 +133,26 @@ export default function ScenariosPage() { } }) - setChannels(transformedScenes) + // 只有在组件仍然挂载的情况下才更新状态 + if (isMounted.current) { + setChannels(transformedScenes) + // 标记已加载过数据 + hasLoadedRef.current = true; + } } else { - setError(response.msg || "获取场景列表失败") + if (isMounted.current) { + setError(response.msg || "获取场景列表失败") + } } } catch (err) { console.error("Failed to fetch scenes:", err) - setError("获取场景列表失败") + if (isMounted.current) { + setError("获取场景列表失败") + } } finally { - setLoading(false) + if (isMounted.current) { + setLoading(false) + } } } diff --git a/Cunkebao/hooks/useDeviceStatusPolling.ts b/Cunkebao/hooks/useDeviceStatusPolling.ts index 8863e592..dfb82ab9 100644 --- a/Cunkebao/hooks/useDeviceStatusPolling.ts +++ b/Cunkebao/hooks/useDeviceStatusPolling.ts @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import type { Device } from "@/components/device-grid" interface DeviceStatus { @@ -25,21 +25,80 @@ async function fetchDeviceStatuses(deviceIds: string[]): Promise>({}) + // 使用ref跟踪上一次的设备ID列表 + const prevDeviceIdsRef = useRef([]); + // 使用ref跟踪组件挂载状态 + const isMounted = useRef(true); + // 记录轮询错误次数,用于实现退避策略 + const errorCountRef = useRef(0); + + // 检查设备列表是否有实质性变化 + const hasDevicesChanged = (prevIds: string[], currentIds: string[]): boolean => { + if (prevIds.length !== currentIds.length) return true; + + // 使用Set检查两个数组是否包含相同的元素 + const prevSet = new Set(prevIds); + return currentIds.some(id => !prevSet.has(id)); + }; useEffect(() => { + // 重置组件挂载状态 + isMounted.current = true; + + // 获取当前设备ID列表 + const deviceIds = devices.map(d => d.id); + + // 检查设备列表是否有变化 + const deviceListChanged = hasDevicesChanged(prevDeviceIdsRef.current, deviceIds); + + // 更新设备ID引用 + prevDeviceIdsRef.current = deviceIds; + const pollStatus = async () => { try { const newStatuses = await fetchDeviceStatuses(devices.map((d) => d.id)) - setStatuses((prevStatuses) => ({ ...prevStatuses, ...newStatuses })) + // 确保组件仍然挂载 + if (isMounted.current) { + setStatuses((prevStatuses) => ({ ...prevStatuses, ...newStatuses })) + // 重置错误计数 + errorCountRef.current = 0; + } } catch (error) { console.error("Failed to fetch device statuses:", error) + // 增加错误计数 + errorCountRef.current += 1; } } - pollStatus() // 立即执行一次 - const intervalId = setInterval(pollStatus, 30000) // 每30秒更新一次 + // 仅当设备列表有变化或初始加载时才立即执行一次 + if (deviceListChanged || Object.keys(statuses).length === 0) { + pollStatus(); + } + + // 使用基于错误次数的指数退避策略 + const getPollingInterval = () => { + const baseInterval = 30000; // 基础间隔 30 秒 + const maxInterval = 2 * 60 * 1000; // 最大间隔 2 分钟 + + if (errorCountRef.current === 0) return baseInterval; + + // 计算指数退避间隔,但不超过最大间隔 + const backoffInterval = Math.min( + baseInterval * Math.pow(1.5, Math.min(errorCountRef.current, 5)), + maxInterval + ); + + return backoffInterval; + }; + + // 设置轮询间隔 + const intervalId = setInterval(pollStatus, getPollingInterval()); - return () => clearInterval(intervalId) + // 清理函数 + return () => { + isMounted.current = false; + clearInterval(intervalId) + } }, [devices]) return statuses From 5d83938c248b3f4b5e03675e15cb7ebb58dfd38f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 14:51:22 +0800 Subject: [PATCH 09/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E5=A4=8D=20http-interceptors.ts=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=AD=E6=9C=AA=E5=87=86=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D401=E9=94=99=E8=AF=AF=E6=97=B6=EF=BC=8C=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=AF=BC=E8=87=B4=E6=84=8F=E5=A4=96=E5=88=A0=E9=99=A4?= =?UTF-8?q?=20localStorage=20=E4=B8=AD=20token=20=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/components/AuthProvider.tsx | 64 +++++++++--- Cunkebao/app/devices/[id]/page.tsx | 117 +++++++++++++++++++-- Cunkebao/lib/api.ts | 126 +++++++++++++++++++++-- Cunkebao/lib/http-interceptors.ts | 56 ++++++---- 4 files changed, 313 insertions(+), 50 deletions(-) diff --git a/Cunkebao/app/components/AuthProvider.tsx b/Cunkebao/app/components/AuthProvider.tsx index 9a3e0da3..ca09cfa5 100644 --- a/Cunkebao/app/components/AuthProvider.tsx +++ b/Cunkebao/app/components/AuthProvider.tsx @@ -76,29 +76,67 @@ export function AuthProvider({ children }: AuthProviderProps) { const storedToken = safeLocalStorage.getItem("token") if (storedToken) { - // 验证token是否有效 - const isValid = await validateToken() - - if (isValid) { - // 从localStorage获取用户信息 - const userDataStr = safeLocalStorage.getItem("user") - if (userDataStr) { + // 首先尝试从localStorage获取用户信息 + const userDataStr = safeLocalStorage.getItem("user") + if (userDataStr) { + try { + // 如果能解析用户数据,先设置登录状态 const userData = JSON.parse(userDataStr) as User setToken(storedToken) setUser(userData) setIsAuthenticated(true) - } else { - // token有效但没有用户信息,清除token + + // 然后在后台尝试验证token,但不影响当前登录状态 + validateToken().then(isValid => { + // 只有在确认token绝对无效时才登出 + // 网络错误等情况默认保持登录状态 + if (isValid === false) { + console.warn('验证token失败,但仍允许用户保持登录状态') + } + }).catch(error => { + // 捕获所有验证过程中的错误,并记录日志 + console.error('验证token过程中出错:', error) + // 网络错误等不会导致登出 + }) + } catch (parseError) { + // 用户数据无法解析,需要清除 + console.error('解析用户数据失败:', parseError) handleLogout() } } else { - // token无效,清除 - handleLogout() + // 有token但没有用户信息,可能是部分数据丢失 + console.warn('找到token但没有用户信息,尝试保持登录状态') + + // 尝试验证token并获取用户信息 + try { + const isValid = await validateToken() + if (isValid) { + // 如果token有效,尝试从API获取用户信息 + // 这里简化处理,直接使用token + setToken(storedToken) + setIsAuthenticated(true) + } else { + // token确认无效,清除 + handleLogout() + } + } catch (error) { + // 验证过程出错,记录日志但不登出 + console.error('验证token过程中出错:', error) + // 保留token,允许用户继续使用 + setToken(storedToken) + setIsAuthenticated(true) + } } } } catch (error) { - console.error("验证token时出错:", error) - handleLogout() + console.error("初始化认证状态时出错:", error) + // 非401错误不应强制登出 + if (error instanceof Error && + (error.message.includes('401') || + error.message.includes('未授权') || + error.message.includes('token'))) { + handleLogout() + } } finally { setIsLoading(false) setIsInitialized(true) diff --git a/Cunkebao/app/devices/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index 867a0947..f532dfdf 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -361,22 +361,117 @@ export default function DeviceDetailPage() { features: updatedFeatures } : null) - // 调用API更新服务器配置 - const response = await updateDeviceTaskConfig(configUpdate) - - if (response && response.code === 200) { - toast.success(`${getFeatureName(feature)}${checked ? '已启用' : '已禁用'}`) - } else { - // 如果请求失败,回滚UI变更 + // 使用更安全的API调用方式,避免自动重定向 + try { + // 获取token + const token = localStorage.getItem('token'); + if (!token) { + throw new Error('未找到授权信息'); + } + + // 直接使用fetch,而不是通过API工具 + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/task-config`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(configUpdate) + }); + + // 检查是否是401错误(未授权),这是唯一应该处理token的情况 + if (response.status === 401) { + // 此处我们不立即跳转,而是给出错误提示 + toast.error('认证已过期,请重新登录后再尝试操作'); + console.error('API请求返回401未授权错误'); + // 可以选择是否重定向到登录页面 + // window.location.href = '/login'; + throw new Error('认证已过期'); + } + + // 检查响应是否正常 + if (!response.ok) { + // 所有非401的HTTP错误 + console.warn(`API返回HTTP错误: ${response.status} ${response.statusText}`); + + // 尝试解析错误详情 + try { + const errorResult = await response.json(); + // 显示详细错误信息,但保持本地token + const errorMsg = errorResult?.msg || `服务器错误 (${response.status})`; + toast.error(`更新失败: ${errorMsg}`); + + // 回滚UI更改 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null); + } catch (parseError) { + // 无法解析响应,可能是网络问题 + console.error('无法解析错误响应:', parseError); + toast.error(`更新失败: 服务器无响应 (${response.status})`); + + // 回滚UI更改 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null); + } + + return; // 提前返回,避免继续处理 + } + + // 响应正常,尝试解析 + try { + const result = await response.json(); + + // 检查API响应码 + if (result && result.code === 200) { + toast.success(`${getFeatureName(feature)}${checked ? '已启用' : '已禁用'}`); + } else if (result && result.code === 401) { + // API明确返回401,提示用户但不自动登出 + toast.error('认证已过期,请重新登录后再尝试操作'); + console.error('API请求返回401未授权状态码'); + + // 回滚UI更改 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null); + } else { + // 其他API错误 + const errorMsg = result?.msg || '未知错误'; + console.warn(`API返回业务错误: ${result?.code} - ${errorMsg}`); + toast.error(`更新失败: ${errorMsg}`); + + // 回滚UI更改 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null); + } + } catch (parseError) { + // 无法解析响应JSON + console.error('无法解析API响应:', parseError); + toast.error('更新失败: 无法解析服务器响应'); + + // 回滚UI更改 + setDevice(prev => prev ? { + ...prev, + features: { ...prev.features, [feature]: !checked } + } : null); + } + } catch (fetchError) { + console.error('请求错误:', fetchError) + + // 回滚UI更改 setDevice(prev => prev ? { ...prev, features: { ...prev.features, [feature]: !checked } } : null) - // 处理错误信息,使用类型断言解决字段不一致问题 - const anyResponse = response as any; - const errorMsg = anyResponse ? (anyResponse.message || anyResponse.msg || '未知错误') : '未知错误'; - toast.error(`更新失败: ${errorMsg}`) + // 显示友好的错误提示 + toast.error('网络请求失败,请稍后重试') } } catch (error) { console.error(`更新${getFeatureName(feature)}失败:`, error) diff --git a/Cunkebao/lib/api.ts b/Cunkebao/lib/api.ts index a11e1a7b..71932b0b 100644 --- a/Cunkebao/lib/api.ts +++ b/Cunkebao/lib/api.ts @@ -51,13 +51,79 @@ export const request = async ( try { const response = await fetch(`${API_BASE_URL}${url}`, options); - const result = await response.json(); + + // 检查网络响应状态 + if (!response.ok) { + // 只有当响应状态码为401时才特殊处理为认证错误 + if (response.status === 401) { + if (typeof window !== 'undefined') { + // 直接调用handleTokenExpired而不是handleApiError,以处理401错误 + // 因为有响应体的情况下,我们应该让handleApiResponse处理401 + // 在这里只处理没有响应体的401网络错误 + const errorMessage = `Unauthorized: ${response.statusText}`; + console.error('授权错误:', errorMessage); + + // 尝试解析响应,如果无法解析才直接处理token过期 + try { + await response.json(); + // 如果能够解析,让后续代码处理 + } catch (e) { + // 如果无法解析,说明是纯网络层401错误,直接处理token过期 + if (typeof window !== 'undefined') { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + window.location.href = '/login'; + throw new Error(errorMessage); + } + } + } + } + // 其他HTTP错误正常返回,让上层组件自行处理 + } + + let result; + try { + result = await response.json(); + } catch (parseError) { + // 处理JSON解析错误 + console.error('无法解析响应JSON:', parseError); + throw new Error('服务器响应格式错误'); + } // 使用响应拦截器处理响应 - return handleApiResponse(response, result); + if (result && result.code === 401) { + if (typeof window !== 'undefined') { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + // 使用客户端导航而不是直接修改window.location + setTimeout(() => { + window.location.href = '/login'; + }, 0); + } + throw new Error(result.msg || '登录已过期,请重新登录'); + } + + // 返回结果而不调用可能会清除token的handleApiResponse + return result; } catch (error) { // 使用错误拦截器处理错误 - return handleApiError(error); + // 只有在确认是401错误时才清除token + if (error instanceof Error && + (error.message.includes('401') || + (error.message.toLowerCase().includes('unauthorized') && + error.message.toLowerCase().includes('token')))) { + if (typeof window !== 'undefined') { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + setTimeout(() => { + window.location.href = '/login'; + }, 0); + } + } + + // 其他错误直接抛出但不调用handleApiError + console.error('API请求错误:', error); + throw error; } }; @@ -103,10 +169,58 @@ export const validateToken = async (): Promise => { } try { - const response = await loginApi.getUserInfo(); - return response.code === 200; + // 直接使用fetch而不是通过api调用,以便捕获具体错误 + const token = getToken(); + if (!token) return false; + + const response = await fetch(`${API_BASE_URL}/v1/auth/info`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + + // 如果状态码是401,明确是认证问题 + if (response.status === 401) { + return false; + } + + // 如果是其他HTTP错误,我们不确定是否是认证问题 + if (!response.ok) { + // 对于非401错误,不要立即判定token无效 + console.warn(`验证token时收到HTTP错误: ${response.status} ${response.statusText}`); + + // 尝试读取响应内容 + try { + const result = await response.json(); + // 只有明确返回code为401才判断为token无效 + if (result && result.code === 401) { + return false; + } + // 其他错误代码视为服务端问题,不影响token有效性 + return true; + } catch (parseError) { + // 无法解析响应,视为网络或服务器问题,不影响token + console.error('无法解析验证token的响应:', parseError); + return true; + } + } + + // 正常情况下,尝试解析响应并检查code + try { + const result = await response.json(); + return result.code === 200; + } catch (parseError) { + // 无法解析响应,视为网络或服务器问题,不影响token + console.error('无法解析验证token的响应:', parseError); + return true; + } } catch (error) { - return false; + // 网络错误或其他异常不应该导致token被视为无效 + console.error('验证token时发生异常:', error); + // 对于网络连接问题,不直接判定为token无效 + return true; } }; diff --git a/Cunkebao/lib/http-interceptors.ts b/Cunkebao/lib/http-interceptors.ts index 439802a7..3965feab 100644 --- a/Cunkebao/lib/http-interceptors.ts +++ b/Cunkebao/lib/http-interceptors.ts @@ -1,4 +1,5 @@ import { useRouter } from "next/navigation"; +import { toast } from "@/components/ui/use-toast"; // Token过期处理 export const handleTokenExpired = () => { @@ -15,34 +16,49 @@ export const handleTokenExpired = () => { } }; -// 响应拦截器 -export const handleApiResponse = (response: Response, result: any): T => { - // 处理token过期情况 - if (result && (result.code === 401 || result.msg?.includes('token'))) { - // 仅在客户端处理token过期 - if (typeof window !== 'undefined') { - handleTokenExpired(); +// 显示API错误,但不会重定向 +export const showApiError = (error: any, defaultMessage: string = '请求失败') => { + if (typeof window === 'undefined') return; // 服务端不处理 + + let errorMessage = defaultMessage; + + // 尝试从各种可能的错误格式中获取消息 + if (error) { + if (typeof error === 'string') { + errorMessage = error; + } else if (error instanceof Error) { + errorMessage = error.message || defaultMessage; + } else if (typeof error === 'object') { + // 尝试从API响应中获取错误消息 + errorMessage = error.msg || error.message || error.error || defaultMessage; } - throw new Error(result.msg || '登录已过期,请重新登录'); } + // 显示错误消息 + if (typeof toast !== 'undefined') { + toast({ + title: "请求错误", + description: errorMessage, + variant: "destructive" + }); + } else { + console.error('API错误:', errorMessage); + } +}; + +// 响应拦截器 - 此函数已不再使用,保留仅用于兼容性 +export const handleApiResponse = (response: Response, result: any): T => { + // 不再处理401响应,而是直接返回结果 + // 401的处理已移至api.ts中直接处理 + console.warn('handleApiResponse已弃用,请直接在api.ts中处理响应'); + return result; }; -// 处理API错误 +// 处理API错误 - 此函数已不再使用,保留仅用于兼容性 export const handleApiError = (error: unknown): never => { console.error('API请求错误:', error); - - if (error instanceof Error) { - // 如果是未授权错误,可能是token过期 - if (error.message.includes('401') || error.message.includes('token') || error.message.includes('授权')) { - // 仅在客户端处理token过期 - if (typeof window !== 'undefined') { - handleTokenExpired(); - } - } - throw error; - } + console.warn('handleApiError已弃用,请直接在api.ts中处理错误'); throw new Error('未知错误,请稍后重试'); }; \ No newline at end of file From 73dd0f51dc91f69d712d80f680a82ef61e03cf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 14:53:12 +0800 Subject: [PATCH 10/16] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E9=85=8D=E7=BD=AE=E6=A8=A1=E5=9E=8B=E7=B1=BB?= =?UTF-8?q?=E4=B8=AD=E5=9B=A0=E5=BC=95=E5=85=A5=E8=BD=AF=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E8=80=8C=E6=9C=AA=E9=85=8D=E7=BD=AE=E8=BD=AF?= =?UTF-8?q?=E5=88=A0=E5=AD=97=E6=AE=B5=E5=AF=BC=E8=87=B4=E9=83=A8=E5=88=86?= =?UTF-8?q?=E4=B8=9A=E5=8A=A1=E9=80=BB=E8=BE=91=E6=8A=A5=E9=94=99=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Server/application/common/model/DeviceTaskconf.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/application/common/model/DeviceTaskconf.php b/Server/application/common/model/DeviceTaskconf.php index 2b3d98b3..a4f2c682 100644 --- a/Server/application/common/model/DeviceTaskconf.php +++ b/Server/application/common/model/DeviceTaskconf.php @@ -19,5 +19,6 @@ class DeviceTaskconf extends Model protected $autoWriteTimestamp = true; protected $createTime = 'createTime'; protected $updateTime = 'updateTime'; + protected $deleteTime = 'deleteTime'; protected $defaultSoftDelete = 0; } \ No newline at end of file From 9465f19479d19ffaed6e90b0f777cee0b75183e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 14:58:41 +0800 Subject: [PATCH 11/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E9=9D=A2=E5=9B=A0=E4=B8=BA=20ArrHelper::getv?= =?UTF-8?q?alue()=20=E4=BC=A0=E5=8F=82=E4=B8=8D=E4=B8=A5=E8=B0=A8=E5=88=B0?= =?UTF-8?q?=E7=9A=84500=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../device/GetDeviceDetailV1Controller.php | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php index 0cb0cd58..e09bdbae 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php @@ -34,7 +34,7 @@ class GetDeviceDetailV1Controller extends BaseController $hasPermission = DeviceUserModel::where($where)->count() > 0; if (!$hasPermission) { - throw new \Exception('您没有权限查看该设备', '403'); + throw new \Exception('您没有权限查看该设备', 403); } } @@ -67,8 +67,8 @@ class GetDeviceDetailV1Controller extends BaseController protected function getTaskConfig(int $deviceId): array { $where = [ - 'deviceId' => $deviceId, - 'companyId' => $this->getUserInfo('companyId'), + 'deviceId' => $deviceId, + 'companyId' => $this->getUserInfo('companyId'), ]; $conf = DeviceTaskconfModel::alias('c') @@ -78,9 +78,12 @@ class GetDeviceDetailV1Controller extends BaseController ->where($where) ->find(); - return $conf - ? $conf->toArray() - : ArrHelper::getValue([], 'autoAddFriend,autoReply,contentSync,aiChat', 0); + if (!is_null($conf)) { + return $conf->toArray(); + } + + // 未配置时赋予默认关闭的状态 + return ArrHelper::getValue('autoAddFriend,autoReply,contentSync,aiChat', [], 0); } /** @@ -131,7 +134,7 @@ class GetDeviceDetailV1Controller extends BaseController ->find($id); if (empty($device)) { - throw new \Exception('设备不存在', '404'); + throw new \Exception('设备不存在', 404); } $device['battery'] = $this->parseExtraForBattery($device['extra']); From 40000b397b2884417270c06ef7cbfad7acb9fa10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 15:02:53 +0800 Subject: [PATCH 12/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E8=AE=BE=E5=A4=87=E8=AF=A6=E6=83=85=E9=A1=B5?= =?UTF-8?q?=E5=8E=BB=E9=99=A4=20max-width=20=E9=99=90=E5=88=B6=EF=BC=88?= =?UTF-8?q?=E7=99=BD=E8=BE=B9=E5=87=BA=E8=A1=80=E9=97=AE=E9=A2=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/devices/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cunkebao/app/devices/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index f532dfdf..5d050a47 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -506,7 +506,7 @@ export default function DeviceDetailPage() { return (
-
+
From 6354dd8565056712ecc5b486a68a7c8d00c8bf8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 15:16:29 +0800 Subject: [PATCH 13/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E5=A4=8D=E8=AE=BE=E5=A4=87=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/devices/[id]/page.tsx | 2 +- Cunkebao/lib/api/devices.ts | 2 +- .../device/GetDeviceDetailV1Controller.php | 4 ++-- .../device/GetRelatedAccountsV1Controller.php | 2 +- .../device/UpdateDeviceTaskConfigV1Controller.php | 12 ++++++------ 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cunkebao/app/devices/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index 5d050a47..79e16959 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -370,7 +370,7 @@ export default function DeviceDetailPage() { } // 直接使用fetch,而不是通过API工具 - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/task-config`, { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/devices/task-config`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/Cunkebao/lib/api/devices.ts b/Cunkebao/lib/api/devices.ts index 9facd1fc..c01be6f9 100644 --- a/Cunkebao/lib/api/devices.ts +++ b/Cunkebao/lib/api/devices.ts @@ -136,7 +136,7 @@ export const deviceApi = { momentsSync?: number; aiChat?: number; }): Promise> { - const response = await fetch(`${API_BASE}/task-config`, { + const response = await fetch(`${API_BASE}/devices/task-config`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php index e09bdbae..6e5a454b 100644 --- a/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetDeviceDetailV1Controller.php @@ -73,7 +73,7 @@ class GetDeviceDetailV1Controller extends BaseController $conf = DeviceTaskconfModel::alias('c') ->field([ - 'c.autoAddFriend', 'c.autoReply', 'c.contentSync', 'c.aiChat' + 'c.autoAddFriend', 'c.autoReply', 'c.momentsSync', 'c.aiChat' ]) ->where($where) ->find(); @@ -83,7 +83,7 @@ class GetDeviceDetailV1Controller extends BaseController } // 未配置时赋予默认关闭的状态 - return ArrHelper::getValue('autoAddFriend,autoReply,contentSync,aiChat', [], 0); + return ArrHelper::getValue('autoAddFriend,autoReply,momentsSync,aiChat', [], 0); } /** diff --git a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php index 346546f1..f46e91e4 100644 --- a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php @@ -32,7 +32,7 @@ class GetRelatedAccountsV1Controller extends BaseController $hasPermission = DeviceUserModel::where($where)->count() > 0; if (!$hasPermission) { - throw new \Exception('您没有权限查看该设备', '403'); + throw new \Exception('您没有权限查看该设备', 403); } } diff --git a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php index fb747ea4..97c326a9 100644 --- a/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php +++ b/Server/application/cunkebao/controller/device/UpdateDeviceTaskConfigV1Controller.php @@ -33,7 +33,7 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController $device = DeviceModel::find($where); if (!$device) { - throw new \Exception('设备不存在或已删除', '404'); + throw new \Exception('设备不存在或已删除', 404); } } @@ -54,7 +54,7 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController $hasPermission = DeviceUserModel::where($where)->count() > 0; if (!$hasPermission) { - throw new \Exception('您没有权限操作该设备', '403'); + throw new \Exception('您没有权限操作该设备', 403); } } @@ -71,12 +71,12 @@ class UpdateDeviceTaskConfigV1Controller extends BaseController $content = null; if (isset($data['autoAddFriend']))/**/ $content = $data['autoAddFriend'] ? '开启自动添加好友' : '关闭自动添加好友'; - if (isset($data['autoReply']))/* */ $content = $data['autoReply'] ? '开启自动回复' : '关闭自动回复'; - if (isset($data['momentsSync']))/* */ $content = $data['momentsSync'] ? '开启朋友圈同步' : '关闭朋友圈同步'; - if (isset($data['aiChat']))/* */ $content = $data['aiChat'] ? '开启AI会话' : '关闭AI会话'; + if (isset($data['autoReply']))/* */ $content = $data['autoReply'] ? '开启自动回复' : '关闭自动回复'; + if (isset($data['momentsSync']))/* */ $content = $data['momentsSync'] ? '开启朋友圈同步' : '关闭朋友圈同步'; + if (isset($data['aiChat']))/* */ $content = $data['aiChat'] ? '开启AI会话' : '关闭AI会话'; if (empty($content)) { - throw new \Exception('参数错误', '400'); + throw new \Exception('参数错误', 400); } DeviceHandleLogModel::addLog( From 250464de39be8214c07862a894a7ce9482a2dc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 15:32:33 +0800 Subject: [PATCH 14/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E6=93=8D=E4=BD=9C=E8=AE=B0=E5=BD=95=E6=87=92=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E5=88=86=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/devices/[id]/page.tsx | 111 +++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/Cunkebao/app/devices/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index 79e16959..eb203a8d 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect } from "react" +import { useState, useEffect, useRef } from "react" import { useParams, useRouter } from "next/navigation" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" @@ -83,6 +83,10 @@ export default function DeviceDetailPage() { const [accountsLoading, setAccountsLoading] = useState(false) const [logsLoading, setLogsLoading] = useState(false) const [handleLogs, setHandleLogs] = useState([]) + const [logPage, setLogPage] = useState(1) + const [hasMoreLogs, setHasMoreLogs] = useState(true) + const logsPerPage = 10 + const logsEndRef = useRef(null) const [savingFeatures, setSavingFeatures] = useState({ autoAddFriend: false, autoReply: false, @@ -297,11 +301,24 @@ export default function DeviceDetailPage() { try { setLogsLoading(true) - const response = await fetchDeviceHandleLogs(params.id as string) + const response = await fetchDeviceHandleLogs( + params.id as string, + logPage, + logsPerPage + ) if (response && response.code === 200 && response.data) { const logs = response.data.list || [] - setHandleLogs(logs) + + // 如果是第一页,替换数据;否则追加数据 + if (logPage === 1) { + setHandleLogs(logs) + } else { + setHandleLogs(prev => [...prev, ...logs]) + } + + // 判断是否还有更多数据 + setHasMoreLogs(logs.length === logsPerPage) if (logs.length > 0) { console.log('获取到操作记录:', logs.length) @@ -319,6 +336,58 @@ export default function DeviceDetailPage() { } } + // 加载更多日志 + const loadMoreLogs = () => { + if (logsLoading || !hasMoreLogs) return + + setLogPage(prevPage => prevPage + 1) + } + + // 监听滚动加载更多 + useEffect(() => { + if (activeTab !== "history") return + + const observerOptions = { + root: null, + rootMargin: '0px', + threshold: 0.1 + } + + const handleIntersect = (entries: IntersectionObserverEntry[]) => { + const [entry] = entries + if (entry.isIntersecting && hasMoreLogs && !logsLoading) { + loadMoreLogs() + } + } + + const observer = new IntersectionObserver(handleIntersect, observerOptions) + + if (logsEndRef.current) { + observer.observe(logsEndRef.current) + } + + return () => { + if (logsEndRef.current) { + observer.unobserve(logsEndRef.current) + } + } + }, [activeTab, hasMoreLogs, logsLoading]) + + // 当切换到日志标签时重置页码 + useEffect(() => { + if (activeTab === "history") { + setLogPage(1) + setHasMoreLogs(true) + } + }, [activeTab]) + + // 观察logPage变化加载数据 + useEffect(() => { + if (activeTab === "history") { + fetchHandleLogs() + } + }, [logPage, activeTab]) + // 处理标签页切换 const handleTabChange = (value: string) => { setActiveTab(value) @@ -726,7 +795,10 @@ export default function DeviceDetailPage() {
- + {logsLoading && handleLogs.length === 0 ? (
@@ -764,6 +836,30 @@ export default function DeviceDetailPage() {
))} + + {/* 加载更多区域 - 用于懒加载触发点 */} +
+ {logsLoading && hasMoreLogs ? ( +
+
+ 加载更多... +
+ ) : hasMoreLogs ? ( + + ) : ( + - 已加载全部记录 - + )} +
) : (
@@ -772,7 +868,10 @@ export default function DeviceDetailPage() { variant="outline" size="sm" className="mt-2" - onClick={fetchHandleLogs} + onClick={() => { + setLogPage(1) + setHasMoreLogs(true) + }} > 刷新 From 8effb037dd12eac653894af873fdcbcf4e1ef27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 15:56:07 +0800 Subject: [PATCH 15/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E4=BF=AE=E6=94=B9=E9=9A=90=E8=97=8F=E9=94=99?= =?UTF-8?q?=E8=AF=AF=20-=20=E6=89=BE=E5=88=B0token=E4=BD=86=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E4=BF=9D=E6=8C=81=E7=99=BB=E5=BD=95=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/components/AuthProvider.tsx | 2 +- .../device/GetRelatedAccountsV1Controller.php | 6 +-- Server/extend/library/ResponseHelper.php | 38 ++++++++++--------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/Cunkebao/app/components/AuthProvider.tsx b/Cunkebao/app/components/AuthProvider.tsx index ca09cfa5..fe5f9c4b 100644 --- a/Cunkebao/app/components/AuthProvider.tsx +++ b/Cunkebao/app/components/AuthProvider.tsx @@ -77,7 +77,7 @@ export function AuthProvider({ children }: AuthProviderProps) { if (storedToken) { // 首先尝试从localStorage获取用户信息 - const userDataStr = safeLocalStorage.getItem("user") + const userDataStr = safeLocalStorage.getItem("userInfo") if (userDataStr) { try { // 如果能解析用户数据,先设置登录状态 diff --git a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php index f46e91e4..4bdd6e0f 100644 --- a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php @@ -72,12 +72,12 @@ class GetRelatedAccountsV1Controller extends BaseController /** * 通过微信id获取微信最后活跃时间 * - * @param int $wechatId + * @param int $time * @return string */ - protected function getWechatLastActiveTime($wechatId): string + protected function getWechatLastActiveTime(int $time): string { - return date('Y-m-d H:i:s', $wechatId ?: time()); + return date('Y-m-d H:i:s', $time ?: time()); } /** diff --git a/Server/extend/library/ResponseHelper.php b/Server/extend/library/ResponseHelper.php index ab17eb45..256bdd9b 100644 --- a/Server/extend/library/ResponseHelper.php +++ b/Server/extend/library/ResponseHelper.php @@ -2,21 +2,23 @@ namespace library; +use think\response\Json as JsonResponse; + class ResponseHelper { /** * 成功响应 * - * @param mixed $data 响应数据 - * @param string $msg 响应消息 - * @param int $code 响应代码 - * @return \think\response\Json + * @param mixed $data + * @param string $msg + * @param int $code + * @return JsonResponse */ - public static function success($data = null, $msg = '操作成功', $code = 200) + public static function success($data = null, string $msg = 'success', int $code = 200): JsonResponse { return json([ 'code' => $code, - 'msg' => $msg, + 'msg' => $msg, 'data' => $data ]); } @@ -24,16 +26,16 @@ class ResponseHelper /** * 错误响应 * - * @param string $msg 错误消息 - * @param int $code 错误代码 - * @param mixed $data 错误数据 - * @return \think\response\Json + * @param string $msg + * @param int $code + * @param mixed $data + * @return JsonResponse */ - public static function error($msg = '操作失败', $code = 400, $data = []) + public static function error(string $msg = 'fail', int $code = 400, $data = []): JsonResponse { return json([ 'code' => $code, - 'msg' => $msg, + 'msg' => $msg, 'data' => $data ]); } @@ -42,21 +44,21 @@ class ResponseHelper * 未授权响应 * * @param string $msg 错误消息 - * @return \think\response\Json + * @return JsonResponse */ - public static function unauthorized($msg = '未授权访问') + public static function unauthorized(string $msg = 'unauthorized access'): JsonResponse { - return self::error($msg, 401); + return static::error($msg, 401); } /** * 禁止访问响应 * * @param string $msg 错误消息 - * @return \think\response\Json + * @return JsonResponse */ - public static function forbidden($msg = '禁止访问') + public static function forbidden(string $msg = 'access denied'): JsonResponse { - return self::error($msg, 403); + return static::error($msg, 403); } } \ No newline at end of file From b90663c9d3ba542b0dc838e3b3b92bf2249a6c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9F=B3=E6=B8=85=E7=88=BD?= Date: Thu, 8 May 2025 16:02:07 +0800 Subject: [PATCH 16/16] =?UTF-8?q?=E7=A7=81=E5=9F=9F=E6=93=8D=E7=9B=98?= =?UTF-8?q?=E6=89=8B=20-=20=E6=B8=85=E9=99=A4=E8=AE=BE=E5=A4=87=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E9=A1=B5=E9=9D=A2=E7=9A=84=20failMockup=20=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=EF=BC=8C=E7=98=A6=E8=BA=AB=E9=A1=B9=E7=9B=AE=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/devices/[id]/page.tsx | 105 ++++++------------ .../device/GetRelatedAccountsV1Controller.php | 6 +- 2 files changed, 38 insertions(+), 73 deletions(-) diff --git a/Cunkebao/app/devices/[id]/page.tsx b/Cunkebao/app/devices/[id]/page.tsx index eb203a8d..1989896a 100644 --- a/Cunkebao/app/devices/[id]/page.tsx +++ b/Cunkebao/app/devices/[id]/page.tsx @@ -180,82 +180,20 @@ export default function DeviceDetailPage() { fetchRelatedAccounts() } } else { - // 如果API返回错误,则使用备用模拟数据 - toast.error("获取设备信息失败,显示备用数据") - fallbackToMockDevice() + // 如果API返回错误,显示错误提示 + toast.error("获取设备信息失败: " + ((response as any)?.msg || "未知错误")) + setLoading(false) } } catch (error) { console.error("获取设备信息失败:", error) - toast.error("获取设备信息出错,显示备用数据") - fallbackToMockDevice() + toast.error("获取设备信息出错,请稍后重试") + setLoading(false) } finally { + // 确保loading状态被关闭 setLoading(false) } } - const fallbackToMockDevice = () => { - const mockDevice: Device = { - id: params.id as string, - imei: "sd123123", - name: "设备 1", - status: "online", - battery: 85, - lastActive: "2024-02-09 15:30:45", - historicalIds: ["vx412321", "vfbadasd"], - wechatAccounts: [ - { - id: "1", - avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", - nickname: "老张", - wechatId: "wxid_abc123", - gender: 1, - status: 1, - statusText: "可加友", - wechatAlive: 1, - wechatAliveText: "正常", - addFriendStatus: 1, - totalFriend: 523, - lastActive: "2024-02-09 15:20:33", - }, - { - id: "2", - avatar: "https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-q2rVrFbfDdAbSnT3ZTNE7gfn3QCbvr.png", - nickname: "老李", - wechatId: "wxid_xyz789", - gender: 1, - status: 0, - statusText: "已停用", - wechatAlive: 0, - wechatAliveText: "异常", - addFriendStatus: 0, - totalFriend: 245, - lastActive: "2024-02-09 14:15:22", - }, - ], - features: { - autoAddFriend: true, - autoReply: true, - momentsSync: false, - aiChat: true, - }, - history: [ - { - time: "2024-02-09 15:30:45", - action: "开启自动加好友", - operator: "系统", - }, - { - time: "2024-02-09 14:20:33", - action: "添加微信号", - operator: "管理员", - }, - ], - totalFriend: 768, - thirtyDayMsgCount: 5678 - } - setDevice(mockDevice) - } - fetchDevice() }, [params.id, activeTab]) @@ -569,8 +507,35 @@ export default function DeviceDetailPage() { return nameMap[feature] || feature } - if (loading || !device) { - return
加载中...
+ if (loading) { + return ( +
+
+
+
正在加载设备详情...
+
+
+ ) + } + + if (!device) { + return ( +
+
+
+ +
+
设备不存在或已被删除
+
+ 无法加载ID为 "{params.id}" 的设备信息,请检查设备是否存在。 +
+ +
+
+ ) } return ( diff --git a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php index 4bdd6e0f..c2d09b16 100644 --- a/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php +++ b/Server/application/cunkebao/controller/device/GetRelatedAccountsV1Controller.php @@ -70,7 +70,7 @@ class GetRelatedAccountsV1Controller extends BaseController } /** - * 通过微信id获取微信最后活跃时间 + * TODO 通过微信id获取微信最后活跃时间 * * @param int $time * @return string @@ -81,7 +81,7 @@ class GetRelatedAccountsV1Controller extends BaseController } /** - * 加友状态 + * TODO 加友状态 * * @param string $wechatId * @return string @@ -92,7 +92,7 @@ class GetRelatedAccountsV1Controller extends BaseController } /** - * 账号状态 + * TODO 账号状态 * * @param string $wechatId * @return string