From 46ba1362557a173a8bfaa9bc02d8eca124a52848 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Tue, 22 Apr 2025 19:17:14 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BE=A4=E5=88=97=E8=A1=A8+=E7=BE=A4=E6=88=90?= =?UTF-8?q?=E5=91=98=E6=8E=A5=E5=8F=A3=E5=A4=96=E5=8A=A0=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cunkebao/app/content/[id]/edit/page.tsx | 103 ++++++--- Cunkebao/app/content/new/page.tsx | 93 ++++++-- Cunkebao/app/content/page.tsx | 97 +++++++- .../components/WechatGroupMemberSelector.tsx | 211 ++++++++++++++++++ Cunkebao/components/WechatGroupSelector.tsx | 166 ++++++++++---- Cunkebao/types/content-library.ts | 27 +++ Cunkebao/types/wechat.ts | 34 +++ Server/application/api/config/route.php | 2 +- .../controller/WechatChatroomController.php | 36 ++- Server/application/command.php | 4 + .../command/GroupFriendsCommand.php | 60 +++++ .../common/service/AuthService.php | 2 +- Server/application/cunkebao/config/route.php | 8 + .../controller/ContentLibraryController.php | 102 ++++++++- .../chatroom/GetChatroomListV1Controller.php | 136 +++++++++++ .../friend/GetFriendListV1Controller.php | 19 +- .../cunkebao/model/WechatChatroom.php | 15 ++ Server/application/job/GroupFriendsJob.php | 145 ++++++++++++ 18 files changed, 1137 insertions(+), 123 deletions(-) create mode 100644 Cunkebao/components/WechatGroupMemberSelector.tsx create mode 100644 Cunkebao/types/wechat.ts create mode 100644 Server/application/command/GroupFriendsCommand.php create mode 100644 Server/application/cunkebao/controller/chatroom/GetChatroomListV1Controller.php create mode 100644 Server/application/cunkebao/model/WechatChatroom.php create mode 100644 Server/application/job/GroupFriendsJob.php diff --git a/Cunkebao/app/content/[id]/edit/page.tsx b/Cunkebao/app/content/[id]/edit/page.tsx index a7809573..5ffe7a8e 100644 --- a/Cunkebao/app/content/[id]/edit/page.tsx +++ b/Cunkebao/app/content/[id]/edit/page.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect, use } from "react" -import { ChevronLeft, X } from "lucide-react" +import { ChevronLeft, X, Users } from "lucide-react" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { Input } from "@/components/ui/input" @@ -13,31 +13,13 @@ import { useRouter } from "next/navigation" import { DateRangePicker } from "@/components/ui/date-range-picker" import { WechatFriendSelector } from "@/components/WechatFriendSelector" import { WechatGroupSelector } from "@/components/WechatGroupSelector" +import { WechatGroupMemberSelector } from "@/components/WechatGroupMemberSelector" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" import { api } from "@/lib/api" import { showToast } from "@/lib/toast" import { format, parse } from "date-fns" - -interface WechatFriend { - id: string - nickname: string - wechatId: string - avatar: string - gender?: "male" | "female" - customer?: string - alias?: string - ownerNickname?: string - ownerAlias?: string -} - -interface WechatGroup { - id: string - name: string - memberCount: number - avatar: string - owner: string - customer: string -} +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { WechatFriend, WechatGroup, WechatGroupMember } from "@/types/wechat" interface ContentLibraryDetail { id: string @@ -46,6 +28,9 @@ interface ContentLibraryDetail { status: number sourceFriends: any[] sourceGroups: any[] + selectedFriends?: WechatFriend[] + selectedGroups?: WechatGroup[] + selectedGroupMembers?: WechatGroupMember[] keywordInclude: string[] keywordExclude: string[] isEnabled: number @@ -75,6 +60,7 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i endDate: "", selectedFriends: [] as WechatFriend[], selectedGroups: [] as WechatGroup[], + selectedGroupMembers: [] as WechatGroupMember[], useAI: false, aiPrompt: "", enabled: true, @@ -82,6 +68,8 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i const [isWechatFriendSelectorOpen, setIsWechatFriendSelectorOpen] = useState(false) const [isWechatGroupSelectorOpen, setIsWechatGroupSelectorOpen] = useState(false) + const [isWechatGroupMemberSelectorOpen, setIsWechatGroupMemberSelectorOpen] = useState(false) + const [currentGroupId, setCurrentGroupId] = useState("") const [loading, setLoading] = useState(false) const [isLoadingData, setIsLoadingData] = useState(true) @@ -99,7 +87,8 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i // 直接使用API返回的好友和群组数据 const friends = data.selectedFriends || []; - const groups = data.sourceGroups || []; + const groups = data.selectedGroups || []; + const groupMembers = data.selectedGroupMembers || []; setFormData({ name: data.name, @@ -110,6 +99,7 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i endDate: data.timeEnd || "", selectedFriends: friends, selectedGroups: groups, + selectedGroupMembers: groupMembers, useAI: !!data.aiPrompt, aiPrompt: data.aiPrompt || "", enabled: data.status === 1, @@ -145,6 +135,11 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i })) } + const handleSelectGroupMembers = (groupId: string) => { + setCurrentGroupId(groupId) + setIsWechatGroupMemberSelectorOpen(true) + } + const handleSubmit = async () => { if (!formData.name) { showToast("请输入内容库名称", "error") @@ -171,6 +166,7 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i sourceType: formData.sourceType === "friends" ? 1 : 2, friends: formData.selectedFriends.map(f => f.id), groups: formData.selectedGroups.map(g => g.id), + groupMembers: formData.selectedGroupMembers.map(m => m.id), keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean), keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean), aiPrompt: formData.useAI ? formData.aiPrompt : "", @@ -289,13 +285,60 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i /> {group.name} - +
+ + +
))} )} + + {formData.selectedGroupMembers.length > 0 && ( +
+
+ + +
+
+ {formData.selectedGroupMembers.map((member) => ( +
+ + + {member.nickname?.[0] || '?'} + + {member.nickname} + +
+ ))} +
+
+ )} @@ -425,6 +468,14 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i selectedGroups={formData.selectedGroups} onSelect={(groups) => setFormData({ ...formData, selectedGroups: groups })} /> + + setFormData({ ...formData, selectedGroupMembers: members })} + /> ) } \ No newline at end of file diff --git a/Cunkebao/app/content/new/page.tsx b/Cunkebao/app/content/new/page.tsx index d39a3d68..3b2ae03d 100644 --- a/Cunkebao/app/content/new/page.tsx +++ b/Cunkebao/app/content/new/page.tsx @@ -1,7 +1,7 @@ "use client" import { useState } from "react" -import { ChevronLeft, X } from "lucide-react" +import { ChevronLeft, X, Users } from "lucide-react" import { Button } from "@/components/ui/button" import { Card } from "@/components/ui/card" import { Input } from "@/components/ui/input" @@ -13,29 +13,14 @@ import { useRouter } from "next/navigation" import { DateRangePicker } from "@/components/ui/date-range-picker" import { WechatFriendSelector } from "@/components/WechatFriendSelector" import { WechatGroupSelector } from "@/components/WechatGroupSelector" +import { WechatGroupMemberSelector } from "@/components/WechatGroupMemberSelector" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" import { api } from "@/lib/api" import { showToast } from "@/lib/toast" import { format } from "date-fns" import { zhCN } from "date-fns/locale" - -interface WechatFriend { - id: string - nickname: string - wechatId: string - avatar: string - gender: "male" | "female" - customer: string -} - -interface WechatGroup { - id: string - name: string - memberCount: number - avatar: string - owner: string - customer: string -} +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { WechatFriend, WechatGroup, WechatGroupMember } from "@/types/wechat" interface ApiResponse { code: number @@ -54,6 +39,7 @@ export default function NewContentLibraryPage() { endDate: "", selectedFriends: [] as WechatFriend[], selectedGroups: [] as WechatGroup[], + selectedGroupMembers: [] as WechatGroupMember[], useAI: false, aiPrompt: "", enabled: true, @@ -61,6 +47,8 @@ export default function NewContentLibraryPage() { const [isWechatFriendSelectorOpen, setIsWechatFriendSelectorOpen] = useState(false) const [isWechatGroupSelectorOpen, setIsWechatGroupSelectorOpen] = useState(false) + const [isWechatGroupMemberSelectorOpen, setIsWechatGroupMemberSelectorOpen] = useState(false) + const [currentGroupId, setCurrentGroupId] = useState("") const [loading, setLoading] = useState(false) const removeFriend = (friendId: string) => { @@ -77,6 +65,11 @@ export default function NewContentLibraryPage() { })) } + const handleSelectGroupMembers = (groupId: string) => { + setCurrentGroupId(groupId) + setIsWechatGroupMemberSelectorOpen(true) + } + const handleSubmit = async () => { if (!formData.name) { showToast("请输入内容库名称", "error") @@ -100,6 +93,7 @@ export default function NewContentLibraryPage() { sourceType: formData.sourceType === "friends" ? 1 : 2, friends: formData.selectedFriends.map(f => f.id), groups: formData.selectedGroups.map(g => g.id), + groupMembers: formData.selectedGroupMembers.map(m => m.id), keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean), keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean), aiPrompt: formData.useAI ? formData.aiPrompt : "", @@ -204,13 +198,60 @@ export default function NewContentLibraryPage() { /> {group.name} - +
+ + +
))} )} + + {formData.selectedGroupMembers.length > 0 && ( +
+
+ + +
+
+ {formData.selectedGroupMembers.map((member) => ( +
+ + + {member.nickname?.[0] || '?'} + + {member.nickname} + +
+ ))} +
+
+ )} @@ -340,6 +381,14 @@ export default function NewContentLibraryPage() { selectedGroups={formData.selectedGroups} onSelect={(groups) => setFormData({ ...formData, selectedGroups: groups })} /> + + setFormData({ ...formData, selectedGroupMembers: members })} + /> ) } diff --git a/Cunkebao/app/content/page.tsx b/Cunkebao/app/content/page.tsx index ef59a28d..bcfec7b5 100644 --- a/Cunkebao/app/content/page.tsx +++ b/Cunkebao/app/content/page.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect, useCallback } from "react" -import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical } from "lucide-react" +import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical, Users } from "lucide-react" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" @@ -12,6 +12,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge import Image from "next/image" import { api } from "@/lib/api" import { showToast } from "@/lib/toast" +import { WechatGroupMemberSelector } from "@/components/WechatGroupMemberSelector" interface ApiResponse { code: number @@ -24,6 +25,16 @@ interface LibraryListResponse { total: number } +interface WechatGroupMember { + id: string + nickname: string + wechatId: string + avatar: string + gender?: "male" | "female" + role?: "owner" | "admin" | "member" + joinTime?: string +} + interface ContentLibrary { id: string name: string @@ -41,6 +52,8 @@ interface ContentLibrary { // 新增字段 sourceFriends: string[] sourceGroups: string[] + friendsData?: any[] + groupsData?: any[] keywordInclude: string[] keywordExclude: string[] isEnabled: number @@ -52,6 +65,7 @@ interface ContentLibrary { createTime: string updateTime: string sourceType: number + selectedGroupMembers?: WechatGroupMember[] } export default function ContentLibraryPage() { @@ -60,6 +74,9 @@ export default function ContentLibraryPage() { const [searchQuery, setSearchQuery] = useState("") const [activeTab, setActiveTab] = useState("all") const [loading, setLoading] = useState(false) + const [isGroupMemberSelectorOpen, setIsGroupMemberSelectorOpen] = useState(false) + const [currentGroupId, setCurrentGroupId] = useState("") + const [selectedGroupMembers, setSelectedGroupMembers] = useState([]) // 获取内容库列表 const fetchLibraries = useCallback(async () => { @@ -76,13 +93,25 @@ export default function ContentLibraryPage() { if (response.code === 200 && response.data) { // 转换数据格式以匹配原有UI const transformedLibraries = response.data.list.map((item: any) => { + // 提取好友数据,确保有头像 + const friendsData = Array.isArray(item.selectedFriends) ? item.selectedFriends : []; + const groupsData = Array.isArray(item.selectedGroups) ? item.selectedGroups : []; + const transformedItem: ContentLibrary = { id: item.id, name: item.name, source: item.sourceType === 1 ? "friends" : "groups", targetAudience: [ - ...(item.sourceFriends || []).map((id: string) => ({ id, nickname: `好友${id}`, avatar: "/placeholder.svg" })), - ...(item.sourceGroups || []).map((id: string) => ({ id, nickname: `群组${id}`, avatar: "/placeholder.svg" })) + ...friendsData.map((friend: any) => ({ + id: friend.id, + nickname: friend.nickname || `好友${friend.id}`, + avatar: friend.avatar || "/placeholder.svg" + })), + ...groupsData.map((group: any) => ({ + id: group.id, + nickname: group.name || `群组${group.id}`, + avatar: group.avatar || "/placeholder.svg" + })) ], creator: item.creatorName || "系统", creatorName: item.creatorName, @@ -92,6 +121,8 @@ export default function ContentLibraryPage() { // 新增字段 sourceFriends: item.sourceFriends || [], sourceGroups: item.sourceGroups || [], + friendsData: friendsData, + groupsData: groupsData, keywordInclude: item.keywordInclude || [], keywordExclude: item.keywordExclude || [], isEnabled: item.isEnabled, @@ -102,7 +133,8 @@ export default function ContentLibraryPage() { status: item.status, createTime: item.createTime, updateTime: item.updateTime, - sourceType: item.sourceType + sourceType: item.sourceType, + selectedGroupMembers: item.selectedGroupMembers || [] } return transformedItem }) @@ -158,6 +190,17 @@ export default function ContentLibraryPage() { fetchLibraries() } + const handleSelectGroupMembers = (groupId: string) => { + setCurrentGroupId(groupId) + setSelectedGroupMembers([]) + setIsGroupMemberSelectorOpen(true) + } + + const handleSaveSelectedMembers = (members: WechatGroupMember[]) => { + setSelectedGroupMembers(members) + showToast(`已选择 ${members.length} 名群成员`, "success") + } + const filteredLibraries = libraries.filter( (library) => library.name.toLowerCase().includes(searchQuery.toLowerCase()) || @@ -245,7 +288,43 @@ export default function ContentLibraryPage() {
来源: -
+ {library.sourceType === 1 && library.sourceFriends?.length > 0 ? ( +
+ {(library.friendsData || []).slice(0, 3).map((friend) => ( + {friend.nickname + ))} + {library.sourceFriends.length > 3 && ( + + +{library.sourceFriends.length - 3} + + )} +
+ ) : library.sourceType === 2 && library.sourceGroups?.length > 0 ? ( +
+
+ {(library.groupsData || []).slice(0, 3).map((group) => ( + {group.name + ))} + {library.sourceGroups.length > 3 && ( + + +{library.sourceGroups.length - 3} + + )} +
+
+ ) : ( +
+ )}
创建人:{library.creator}
内容数量:{library.itemCount}
@@ -287,6 +366,14 @@ export default function ContentLibraryPage() {
+ + ) } diff --git a/Cunkebao/components/WechatGroupMemberSelector.tsx b/Cunkebao/components/WechatGroupMemberSelector.tsx new file mode 100644 index 00000000..95abd142 --- /dev/null +++ b/Cunkebao/components/WechatGroupMemberSelector.tsx @@ -0,0 +1,211 @@ +"use client" + +import { useState, useEffect } from "react" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Search, ChevronLeft, ChevronRight } from "lucide-react" +import { Checkbox } from "@/components/ui/checkbox" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" +import { WechatGroupMember } from "@/types/wechat" + +interface ApiResponse { + code: number + msg: string + data: T +} + +interface GroupMemberListResponse { + list: any[] + total: number +} + +interface WechatGroupMemberSelectorProps { + open: boolean + onOpenChange: (open: boolean) => void + groupId: string + selectedMembers: WechatGroupMember[] + onSelect: (members: WechatGroupMember[]) => void +} + +export function WechatGroupMemberSelector({ + open, + onOpenChange, + groupId, + selectedMembers, + onSelect +}: WechatGroupMemberSelectorProps) { + const [searchQuery, setSearchQuery] = useState("") + const [members, setMembers] = useState([]) + const [loading, setLoading] = useState(false) + const [page, setPage] = useState(1) + const [totalPages, setTotalPages] = useState(1) + const [totalItems, setTotalItems] = useState(0) + const pageSize = 20 + + useEffect(() => { + if (open && groupId) { + fetchGroupMembers(1) + } + }, [open, groupId]) + + const fetchGroupMembers = async (pageNum: number) => { + setLoading(true) + try { + const queryParams = new URLSearchParams({ + page: pageNum.toString(), + limit: pageSize.toString(), + groupId: groupId, + ...(searchQuery ? { keyword: searchQuery } : {}) + }) + + const response = await api.get>(`/v1/chatroom/getMemberList?${queryParams.toString()}`) + + if (response.code === 200 && response.data) { + const membersList = response.data.list.map((item: any) => ({ + id: item.id || `member-${Math.random()}`, + nickname: item.name || item.ownerNickname || '未知成员', + wechatId: item.ownerWechatId || '', + avatar: item.ownerAvatar || item.avatar || '/placeholder.svg', + role: item.isOwner ? 'owner' : (item.isAdmin ? 'admin' : 'member'), + joinTime: item.createTime ? new Date(item.createTime * 1000).toLocaleString() : '--' + })) + + setMembers(membersList) + setTotalItems(response.data.total) + setTotalPages(Math.ceil(response.data.total / pageSize)) + setPage(pageNum) + } else { + showToast(response.msg || "获取群成员列表失败", "error") + } + } catch (error: any) { + console.error("获取群成员列表失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } finally { + setLoading(false) + } + } + + const handleSearch = () => { + fetchGroupMembers(1) + } + + const handlePrevPage = () => { + if (page > 1) { + fetchGroupMembers(page - 1) + } + } + + const handleNextPage = () => { + if (page < totalPages) { + fetchGroupMembers(page + 1) + } + } + + return ( + + + + 选择群成员 + +
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-9" + /> +
+ +
+
+ {loading ? ( +
加载中...
+ ) : members.length === 0 ? ( +
未找到匹配的群成员
+ ) : ( + members.map((member) => ( +
+ m.id === member.id)} + onCheckedChange={(checked) => { + if (checked) { + onSelect([...selectedMembers, member]) + } else { + onSelect(selectedMembers.filter((m) => m.id !== member.id)) + } + }} + /> + + + {member.nickname?.[0] || '?'} + +
+
+ {member.nickname} + {member.role === 'owner' && (群主)} + {member.role === 'admin' && (管理员)} +
+
+ {member.wechatId &&
微信ID:{member.wechatId}
} +
+
+
+ )) + )} +
+ + {/* 分页控制 */} + {totalPages > 1 && ( +
+
+ 总计 {totalItems} 个成员 +
+
+ + + {page} / {totalPages} + + +
+
+ )} + +
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/Cunkebao/components/WechatGroupSelector.tsx b/Cunkebao/components/WechatGroupSelector.tsx index e517b415..1b34b8db 100644 --- a/Cunkebao/components/WechatGroupSelector.tsx +++ b/Cunkebao/components/WechatGroupSelector.tsx @@ -4,16 +4,22 @@ import { useState, useEffect } from "react" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" -import { Search } from "lucide-react" +import { Search, ChevronLeft, ChevronRight } from "lucide-react" import { Checkbox } from "@/components/ui/checkbox" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" +import { WechatGroup } from "@/types/wechat" -interface WechatGroup { - id: string - name: string - memberCount: number - avatar: string - owner: string - customer: string +interface ApiResponse { + code: number + msg: string + data: T +} + +interface GroupListResponse { + list: any[] + total: number } interface WechatGroupSelectorProps { @@ -27,30 +33,67 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele const [searchQuery, setSearchQuery] = useState("") const [groups, setGroups] = useState([]) const [loading, setLoading] = useState(false) + const [page, setPage] = useState(1) + const [totalPages, setTotalPages] = useState(1) + const [totalItems, setTotalItems] = useState(0) + const pageSize = 20 useEffect(() => { if (open) { - fetchGroups() + fetchGroups(1) } }, [open]) - const fetchGroups = async () => { + const fetchGroups = async (pageNum: number) => { setLoading(true) - // 模拟从API获取群聊列表 - await new Promise((resolve) => setTimeout(resolve, 1000)) - const mockGroups = Array.from({ length: 10 }, (_, i) => ({ - id: `group-${i}`, - name: `群聊${i + 1}`, - memberCount: Math.floor(Math.random() * 400) + 100, - avatar: `/placeholder.svg?height=40&width=40&text=群${i + 1}`, - owner: `群主${i + 1}`, - customer: `客户${i + 1}`, - })) - setGroups(mockGroups) - setLoading(false) + try { + const queryParams = new URLSearchParams({ + page: pageNum.toString(), + limit: pageSize.toString(), + ...(searchQuery ? { keyword: searchQuery } : {}) + }) + + const response = await api.get>(`/v1/chatroom?${queryParams.toString()}`) + + if (response.code === 200 && response.data) { + const groupsList = response.data.list.map(item => ({ + id: item.id || `group-${Math.random()}`, + name: item.name || item.chatroomName || '未知群聊', + memberCount: item.memberCount || 0, + avatar: item.avatar || '/placeholder.svg', + customer: item.ownerNickname || '--' + })) + + setGroups(groupsList) + setTotalItems(response.data.total) + setTotalPages(Math.ceil(response.data.total / pageSize)) + setPage(pageNum) + } else { + showToast(response.msg || "获取群聊列表失败", "error") + } + } catch (error: any) { + console.error("获取群聊列表失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } finally { + setLoading(false) + } } - const filteredGroups = groups.filter((group) => group.name.toLowerCase().includes(searchQuery.toLowerCase())) + const handleSearch = () => { + fetchGroups(1) + } + + const handlePrevPage = () => { + if (page > 1) { + fetchGroups(page - 1) + } + } + + const handleNextPage = () => { + if (page < totalPages) { + fetchGroups(page + 1) + } + } return ( @@ -58,22 +101,33 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele 选择聊天群 -
- - setSearchQuery(e.target.value)} - className="pl-9" - /> +
+
+ + setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="pl-9" + /> +
+
{loading ? (
加载中...
- ) : filteredGroups.length === 0 ? ( + ) : groups.length === 0 ? (
未找到匹配的群聊
) : ( - filteredGroups.map((group) => ( + groups.map((group) => (
g.id === group.id)} @@ -85,24 +139,56 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele } }} /> - {group.name} -
-
{group.name}
+ + + {group.name?.[0] || '群'} + +
+
{group.name}
-
群主:{group.owner}
-
归属客户:{group.customer}
-
{group.memberCount}人
+
归属客户:{group.customer}
)) )}
+ + {/* 分页控制 */} + {totalPages > 1 && ( +
+
+ 总计 {totalItems} 个群聊 +
+
+ + + {page} / {totalPages} + + +
+
+ )} +
- +
diff --git a/Cunkebao/types/content-library.ts b/Cunkebao/types/content-library.ts index 22ae5e9d..a00a166c 100644 --- a/Cunkebao/types/content-library.ts +++ b/Cunkebao/types/content-library.ts @@ -3,10 +3,26 @@ export interface ContentLibrary { name: string type: "moments" | "group" source: string + sourceType?: number // 1: friends, 2: groups creator: string contentCount: number lastUpdated: string status: "active" | "inactive" + + // 新增字段 + sourceFriends?: string[] + sourceGroups?: string[] + selectedFriends?: any[] + selectedGroupMembers?: any[] + keywordInclude?: string[] + keywordExclude?: string[] + isEnabled?: number + aiPrompt?: string + timeEnabled?: number + timeStart?: string + timeEnd?: string + createTime?: string + updateTime?: string } export interface ContentLibraryResponse { @@ -28,3 +44,14 @@ export interface ContentLibrarySelectResponse { } } +// 群成员类型定义 +export interface WechatGroupMember { + id: string + nickname: string + wechatId: string + avatar: string + gender?: "male" | "female" + role?: "owner" | "admin" | "member" + joinTime?: string +} + diff --git a/Cunkebao/types/wechat.ts b/Cunkebao/types/wechat.ts new file mode 100644 index 00000000..751d4504 --- /dev/null +++ b/Cunkebao/types/wechat.ts @@ -0,0 +1,34 @@ +// 微信好友类型定义 +export interface WechatFriend { + id: string + nickname: string + wechatId: string + avatar: string + gender?: "male" | "female" + customer?: string + alias?: string + ownerNickname?: string + ownerAlias?: string + createTime?: string +} + +// 微信群组类型定义 +export interface WechatGroup { + id: string + name: string + memberCount: number + avatar: string + owner: string + customer: string +} + +// 微信群成员类型定义 +export interface WechatGroupMember { + id: string + nickname: string + wechatId: string + avatar: string + gender?: "male" | "female" + role?: "owner" | "admin" | "member" + joinTime?: string +} \ No newline at end of file diff --git a/Server/application/api/config/route.php b/Server/application/api/config/route.php index 49cbe9e9..4714646e 100644 --- a/Server/application/api/config/route.php +++ b/Server/application/api/config/route.php @@ -66,7 +66,7 @@ Route::group('v1', function () { // WechatChatroom控制器路由 Route::group('chatroom', function () { Route::get('list', 'app\\api\\controller\\WechatChatroomController@getList'); // 获取微信群聊列表 √ - //Route::get('members/:wechatChatroomId', 'app\\api\\controller\\WechatChatroomController@listChatroomMember'); // 获取群成员列表 √ + Route::get('members', 'app\\api\\controller\\WechatChatroomController@listChatroomMember'); // 获取群成员列表 √ // Route::get('sync', 'app\\api\\controller\\WechatChatroomController@syncChatrooms'); // 同步微信群聊数据 √ }); diff --git a/Server/application/api/controller/WechatChatroomController.php b/Server/application/api/controller/WechatChatroomController.php index 1dd854a8..5051bafa 100644 --- a/Server/application/api/controller/WechatChatroomController.php +++ b/Server/application/api/controller/WechatChatroomController.php @@ -120,16 +120,28 @@ class WechatChatroomController extends BaseController * @param string $wechatChatroomId 微信群ID * @return \think\response\Json */ - public function listChatroomMember($wechatChatroomId = '') + public function listChatroomMember($wechatChatroomId = '',$chatroomId = '',$isJob = false) { // 获取授权token $authorization = trim($this->request->header('authorization', $this->authorization)); + $wechatChatroomId = !empty($wechatChatroomId) ? $wechatChatroomId : $this->request->param('id', ''); + $chatroomId = !empty($chatroomId) ? $chatroomId : $this->request->param('chatroomId', ''); + + if (empty($authorization)) { - return errorJson('缺少授权信息'); + if($isJob){ + return json_encode(['code'=>500,'msg'=>'缺少授权信息']); + }else{ + return errorJson('缺少授权信息'); + } } if (empty($wechatChatroomId)) { - return errorJson('群ID不能为空'); + if($isJob){ + return json_encode(['code'=>500,'msg'=>'群ID不能为空']); + }else{ + return errorJson('群ID不能为空'); + } } try { @@ -147,15 +159,23 @@ class WechatChatroomController extends BaseController $response = handleApiResponse($result); // 保存数据到数据库 - if (!empty($response['results'])) { - foreach ($response['results'] as $item) { - $this->saveChatroomMember($item, $wechatChatroomId); + if (!empty($response)) { + foreach ($response as $item) { + $this->saveChatroomMember($item, $chatroomId); } } - return successJson($response); + if($isJob){ + return json_encode(['code'=>200,'msg'=>'success','data'=>$response]); + }else{ + return successJson($response); + } } catch (\Exception $e) { - return errorJson('获取群成员列表失败:' . $e->getMessage()); + if($isJob){ + return json_encode(['code'=>500,'msg'=>'获取群成员列表失败:' . $e->getMessage()]); + }else{ + return errorJson('获取群成员列表失败:' . $e->getMessage()); + } } } diff --git a/Server/application/command.php b/Server/application/command.php index 5bb55f27..10f9c262 100644 --- a/Server/application/command.php +++ b/Server/application/command.php @@ -19,5 +19,9 @@ return [ 'message:friendsList' => 'app\command\MessageFriendsListCommand', // 微信好友消息列表 √ 'message:chatroomList' => 'app\command\MessageChatroomListCommand', // 微信群聊消息列表 √ 'department:list' => 'app\command\DepartmentListCommand', // 部门列表 √ +<<<<<<< Updated upstream 'content:sync' => 'app\command\SyncContentCommand', // 同步内容库 √ +======= + 'groupFriends:list' => 'app\command\GroupFriendsCommand', // 微信群好友列表 +>>>>>>> Stashed changes ]; diff --git a/Server/application/command/GroupFriendsCommand.php b/Server/application/command/GroupFriendsCommand.php new file mode 100644 index 00000000..fa9c3961 --- /dev/null +++ b/Server/application/command/GroupFriendsCommand.php @@ -0,0 +1,60 @@ +setName('groupFriends:list') + ->setDescription('获取微信群好友列表,并根据分页自动处理下一页'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('开始处理微信群好友列表任务...'); + + try { + // 从缓存获取初始页码,缓存有效期一天 + $pageIndex = Cache::get('groupFriendsPage', 0); + $output->writeln('从缓存获取页码:' . $pageIndex); + + $pageSize = 100; // 每页获取100条记录 + + // 将任务添加到队列 + $this->addToQueue($pageIndex, $pageSize); + + $output->writeln('微信群好友列表任务已添加到队列'); + } catch (\Exception $e) { + Log::error('微信群好友列表任务添加失败:' . $e->getMessage()); + $output->writeln('微信群好友列表任务添加失败:' . $e->getMessage()); + return false; + } + + return true; + } + + /** + * 添加任务到队列 + * @param int $pageIndex 页码 + * @param int $pageSize 每页大小 + */ + protected function addToQueue($pageIndex, $pageSize) + { + $data = [ + 'pageIndex' => $pageIndex, + 'pageSize' => $pageSize + ]; + + // 添加到队列,设置任务名为 group_friends + Queue::push(GroupFriendsJob::class, $data, 'group_friends'); + } +} \ No newline at end of file diff --git a/Server/application/common/service/AuthService.php b/Server/application/common/service/AuthService.php index 81ea6ae9..6a106103 100644 --- a/Server/application/common/service/AuthService.php +++ b/Server/application/common/service/AuthService.php @@ -168,7 +168,7 @@ class AuthService // 尝试从缓存获取授权信息 $authorization = Cache::get($cacheKey); - // $authorization = 'aXRi4R80zwTXo9V-VCXVYk4IMLl5ufKASoRtYHfaRh_uLwil_mO9U_jWfxeR1yupJIPuQCZknXGpctr9PTS1hbormw3RSrOwunNKTsvvcGzjTa0bBUz3S9W8x_PtvbY4_JpoXl8x8hm8cUa37zLlN7DQBAmj8He40FCxMTh1MC4xorM11aXoVFvYcrAkv_urHINWDmfNhH9icXzreiX9Uynw4fq7BkuP7yr6WHQ5z0NkOfKoMcesH4gPn_h_OLHC0T_ps2ky--M5HOvd5WgBmYRecNOcqbe4e0oIIO5ffANLsybyhLOEha3a03qKsyfAFWdf0A'; + $authorization = 'mYpVVhPY7PxctvYw1pn1VCTS2ck0yZG8q11gAiJrRN_D3q7KXXBPAfXoAmqs7kKHeaAx-h4GB7DiqVIQJ09HiXVhaQT6PtgLX3w8YV16erThC-lG1fyJB4DJxu-QxA3Q8ogSs1WFOa8aAXD1QQUZ7Kbjkw_VMLL4lrfe0Yjaqy3DnO7aL1xGnNjjX8P5uqCAZgHKlN8NjuDEGyYvXygW1YyoK9pNpwvq-6DYKjLWdmbHvFaAybHf-hU1XyrFavZqcZYxIoVXjfJ5ASp4XxeCWqMCzwtSoz9RAvwLAlNxGweowtuyX9389ZaXI-zbqb2T0S8llg'; // 如果缓存中没有或已过期,则重新获取 if (empty($authorization)) { try { diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index a618aa1c..963ef25e 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -70,4 +70,12 @@ Route::group('v1/', function () { Route::group('friend', function () { Route::get('', 'app\\cunkebao\\controller\\friend\\GetFriendListV1Controller@index'); // 获取好友列表 }); + + //群相关 + Route::group('chatroom', function () { + Route::get('', 'app\\cunkebao\\controller\\chatroom\\GetChatroomListV1Controller@index'); // 获取群列表 + Route::get('getMemberList', 'app\\cunkebao\\controller\\chatroom\\GetChatroomListV1Controller@getMemberList'); // 获取群详情 + + }); + })->middleware(['jwt']); \ No newline at end of file diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index 01ffed19..51619ed6 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -57,6 +57,43 @@ class ContentLibraryController extends Controller $item['keywordExclude'] = json_decode($item['keywordExclude'] ?: '[]', true); // 添加创建人名称 $item['creatorName'] = $item['user']['username'] ?? ''; + + + // 获取好友详细信息 + if (!empty($item['sourceFriends'] && $item['sourceType'] == 1)) { + $friendIds = $item['sourceFriends']; + $friendsInfo = []; + + if (!empty($friendIds)) { + // 查询好友信息,使用wechat_friend表 + $friendsInfo = Db::name('wechat_friend')->alias('wf') + ->field('wf.id,wf.wechatId, wa.nickname, wa.avatar') + ->join('wechat_account wa', 'wf.wechatId = wa.wechatId') + ->whereIn('wf.id', $friendIds) + ->select(); + } + + // 将好友信息添加到返回数据中 + $item['selectedFriends'] = $friendsInfo; + } + + // 获取群组详细信息 + if (!empty($item['sourceGroups']) && $item['sourceType'] == 2) { + $groupIds = $item['sourceGroups']; + $groupsInfo = []; + + if (!empty($groupIds)) { + // 查询群组信息 + $groupsInfo = Db::name('wechat_group')->alias('g') + ->field('g.id, g.chatroomId, g.name, g.avatar, g.ownerWechatId') + ->whereIn('g.id', $groupIds) + ->select(); + } + + // 将群组信息添加到返回数据中 + $item['selectedGroups'] = $groupsInfo; + } + unset($item['user']); // 移除关联数据 } unset($item); @@ -117,17 +154,36 @@ class ContentLibraryController extends Controller $friendsInfo = []; if (!empty($friendIds)) { - // 查询好友信息,使用wechat_account表 - $friendsInfo = Db::name('wechat_account') - ->field('wechatId, nickname, avatar') - ->whereIn('wechatId', $friendIds) - ->select(); + // 查询好友信息,使用wechat_friend表 + $friendsInfo = Db::name('wechat_friend')->alias('wf') + ->field('wf.id,wf.wechatId, wa.nickname, wa.avatar') + ->join('wechat_account wa', 'wf.wechatId = wa.wechatId') + ->whereIn('wf.id', $friendIds) + ->select(); } // 将好友信息添加到返回数据中 $library['selectedFriends'] = $friendsInfo; } + // 获取群组详细信息 + if (!empty($library['sourceGroups'])) { + $groupIds = $library['sourceGroups']; + $groupsInfo = []; + + if (!empty($groupIds)) { + // 查询群组信息 + $groupsInfo = Db::name('wechat_group')->alias('g') + ->field('g.id, g.chatroomId, g.name, g.avatar, g.ownerWechatId,wa.nickname as ownerNickname,wa.avatar as ownerAvatar,wa.alias as ownerAlias') + ->join('wechat_account wa', 'g.ownerWechatId = wa.wechatId') + ->whereIn('g.id', $groupIds) + ->select(); + } + + // 将群组信息添加到返回数据中 + $library['selectedGroups'] = $groupsInfo; + } + return json([ @@ -163,15 +219,21 @@ class ContentLibraryController extends Controller Db::startTrans(); try { + + $keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'],256) : json_encode([]); + $keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'],256) : json_encode([]); + $sourceType = isset($param['sourceType']) ? $param['sourceType'] : 1; + + // 构建数据 $data = [ 'name' => $param['name'], // 数据来源配置 - 'sourceFriends' => isset($param['friends']) ? json_encode($param['friends']) : '[]', // 选择的微信好友 - 'sourceGroups' => isset($param['groups']) ? json_encode($param['groups']) : '[]', // 选择的微信群 + 'sourceFriends' => $sourceType == 1 ? json_encode($param['friends']) : json_encode([]), // 选择的微信好友 + 'sourceGroups' => $sourceType == 2 ? json_encode($param['groups']) : json_encode([]), // 选择的微信群 // 关键词配置 - 'keywordInclude' => isset($param['keywordInclude']) ? json_encode($param['keywordInclude']) : '[]', // 包含的关键词 - 'keywordExclude' => isset($param['keywordExclude']) ? json_encode($param['keywordExclude']) : '[]', // 排除的关键词 + 'keywordInclude' => $keywordInclude, // 包含的关键词 + 'keywordExclude' => $keywordExclude, // 排除的关键词 // AI配置 'aiEnabled' => isset($param['aiEnabled']) ? $param['aiEnabled'] : 0, // 是否启用AI 'aiPrompt' => isset($param['aiPrompt']) ? $param['aiPrompt'] : '', // AI提示词 @@ -180,7 +242,7 @@ class ContentLibraryController extends Controller 'timeStart' => isset($param['startTime']) ? strtotime($param['startTime']) : 0, // 开始时间(转换为时间戳) 'timeEnd' => isset($param['endTime']) ? strtotime($param['endTime']) : 0, // 结束时间(转换为时间戳) // 来源类型 - 'sourceType' => isset($param['sourceType']) ? $param['sourceType'] : 0, // 1=好友,2=群,3=好友和群 + 'sourceType' => $sourceType, // 1=好友,2=群,3=好友和群 // 基础信息 'status' => isset($param['status']) ? $param['status'] : 0, // 状态:0=禁用,1=启用 'userId' => $this->request->userInfo['id'], @@ -240,9 +302,27 @@ class ContentLibraryController extends Controller Db::startTrans(); try { + + $keywordInclude = isset($param['keywordInclude']) ? json_encode($param['keywordInclude'],256) : json_encode([]); + $keywordExclude = isset($param['keywordExclude']) ? json_encode($param['keywordExclude'],256) : json_encode([]); + + // 更新内容库基本信息 $library->name = $param['name']; - $library->description = isset($param['description']) ? $param['description'] : ''; + $library->sourceType = isset($param['sourceType']) ? $param['sourceType'] : 1; + $library->sourceFriends = $param['sourceType'] == 1 ? json_encode($param['friends']) : json_encode([]); + $library->sourceGroups = $param['sourceType'] == 2 ? json_encode($param['groups']) : json_encode([]); + $library->keywordInclude = $keywordInclude; + $library->keywordExclude = $keywordExclude; + $library->aiEnabled = isset($param['aiEnabled']) ? $param['aiEnabled'] : 0; + $library->aiPrompt = isset($param['aiPrompt']) ? $param['aiPrompt'] : ''; + $library->timeEnabled = isset($param['timeEnabled']) ? $param['timeEnabled'] : 0; + $library->timeStart = isset($param['startTime']) ? strtotime($param['startTime']) : 0; + $library->timeEnd = isset($param['endTime']) ? strtotime($param['endTime']) : 0; + $library->status = isset($param['status']) ? $param['status'] : 0; + $library->updateTime = time(); + + $library->save(); Db::commit(); diff --git a/Server/application/cunkebao/controller/chatroom/GetChatroomListV1Controller.php b/Server/application/cunkebao/controller/chatroom/GetChatroomListV1Controller.php new file mode 100644 index 00000000..ce7facfe --- /dev/null +++ b/Server/application/cunkebao/controller/chatroom/GetChatroomListV1Controller.php @@ -0,0 +1,136 @@ +request->param('page', 1); + $limit = $this->request->param('limit', 20); + $keyword = $this->request->param('keyword', ''); + try { + + $where = []; + if ($this->getUserInfo('isAdmin') == 1) { + $where[] = ['g.companyId', '=', $this->getUserInfo('companyId')]; + $where[] = ['g.deleteTime', '=', 0]; + } else { + $where[] = ['g.companyId', '=', $this->getUserInfo('companyId')]; + $where[] = ['g.deleteTime', '=', 0]; + //$where[] = ['g.userId', '=', $this->getUserInfo('id')]; + } + + if(!empty($keyword)){ + $where[] = ['g.name', 'like', '%'.$keyword.'%']; + } + + $data = WechatChatroom::alias('g') + ->field(['g.id', 'g.chatroomId', 'g.name', 'g.avatar','g.ownerWechatId', 'g.identifier', 'g.createTime', + 'wa.nickname as ownerNickname','wa.avatar as ownerAvatar','wa.alias as ownerAlias']) + ->Join('wechat_account wa', 'g.ownerWechatId = wa.wechatId', 'LEFT') + ->where($where); + + $total = $data->count(); + $list = $data->page($page, $limit)->order('g.id DESC')->select(); + + return json([ + 'code' => 200, + 'msg' => '获取成功', + 'data' => [ + 'list' => $list, + 'total' => $total, + ] + ]); + } catch (\Exception $e) { + return json([ + 'code' => $e->getCode(), + 'msg' => $e->getMessage() + ]); + } + } + + /** + * 获取群成员列表 + * @return \think\response\Json + */ + public function getMemberList() + { + $page = $this->request->param('page', 1); + $limit = $this->request->param('limit', 20); + $keyword = $this->request->param('keyword', ''); + $groupId = $this->request->param('groupId', 0); + + if (empty($groupId)) { + return json([ + 'code' => 400, + 'msg' => '群ID不能为空' + ]); + } + + try { + $where = []; + $where[] = ['m.groupId', '=', $groupId]; + $where[] = ['m.deleteTime', '=', 0]; + + // 如果有搜索关键词 + if (!empty($keyword)) { + $where[] = ['m.nickname|m.identifier', 'like', '%'.$keyword.'%']; + } + + $data = Db::name('wechat_group_member') + ->alias('m') + ->field([ + 'm.id', + 'm.identifier', + 'm.customerIs', + 'wa.nickname', + 'wa.avatar', + 'm.groupId', + 'm.createTime', + 'g.name as groupName', + 'g.chatroomId' + ]) + ->join('wechat_group g', 'm.groupId = g.id', 'LEFT') + ->join('wechat_account wa', 'wa.wechatId = m.identifier', 'LEFT') + ->where($where); + + $total = $data->count(); + $list = $data->page($page, $limit) + ->order('m.id DESC') + ->select(); + + // 格式化时间 + foreach ($list as &$item) { + if (!empty($item['createTime'])) { + $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']); + } + } + + return json([ + 'code' => 200, + 'msg' => '获取成功', + 'data' => [ + 'list' => $list, + 'total' => $total, + ] + ]); + } catch (\Exception $e) { + return json([ + 'code' => $e->getCode() ?: 500, + 'msg' => $e->getMessage() + ]); + } + } +} diff --git a/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php b/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php index 3d0de8f1..7e3c0a64 100644 --- a/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php +++ b/Server/application/cunkebao/controller/friend/GetFriendListV1Controller.php @@ -5,6 +5,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 think\Db; /** * 设备管理控制器 @@ -26,29 +27,29 @@ class GetFriendListV1Controller extends BaseController $where = []; if ($this->getUserInfo('isAdmin') == 1) { - $where['wf.companyId'] = $this->getUserInfo('companyId'); - $where['wf.deleteTime'] = 0; + $where[] = ['wf.companyId','=',$this->getUserInfo('companyId')]; + $where[] = ['wf.deleteTime','=',0]; } else { - $where['wf.companyId'] = $this->getUserInfo('companyId'); - $where['wf.deleteTime'] = 0; - //$where['userId'] = $this->getUserInfo('id'); + $where[] = ['wf.companyId','=',$this->getUserInfo('companyId')]; + $where[] = ['wf.deleteTime','=',0]; + //$where[] = ['wf.userId','=',$this->getUserInfo('id')]; } - if($keyword){ - $where['wa1.nickname'] = ['like','%'.$keyword.'%']; + if(!empty($keyword)){ + $where[] = ['wa1.nickname','like','%'.$keyword.'%']; } $data = WechatFriend::alias('wf') - ->field(['wa1.nickname','wa1.avatar','wa1.alias','wf.wechatId','wa2.nickname as ownerNickname','wa2.alias as ownerAlias','wa2.wechatId as ownerWechatId','wf.createTime']) + ->field(['wa1.nickname','wa1.avatar','wa1.alias','wf.id','wf.wechatId','wa2.nickname as ownerNickname','wa2.alias as ownerAlias','wa2.wechatId as ownerWechatId','wf.createTime']) ->Join('wechat_account wa1','wf.wechatId = wa1.wechatId') ->Join('wechat_account wa2','wf.ownerWechatId = wa2.wechatId') ->where($where); $total = $data->count(); - $list = $data->page($page, $limit)->select(); + $list = $data->page($page, $limit)->order('wf.id DESC')->select(); diff --git a/Server/application/cunkebao/model/WechatChatroom.php b/Server/application/cunkebao/model/WechatChatroom.php new file mode 100644 index 00000000..4c07e962 --- /dev/null +++ b/Server/application/cunkebao/model/WechatChatroom.php @@ -0,0 +1,15 @@ +processGroupFriendsList($data, $job->attempts())) { + $job->delete(); + Log::info('微信群好友列表任务执行成功,页码:' . $data['pageIndex']); + } else { + if ($job->attempts() > 3) { + // 超过重试次数,删除任务 + Log::error('微信群好友列表任务执行失败,已超过重试次数,页码:' . $data['pageIndex']); + $job->delete(); + } else { + // 任务失败,重新放回队列 + Log::warning('微信群好友列表任务执行失败,重试次数:' . $job->attempts() . ',页码:' . $data['pageIndex']); + $job->release(Config::get('queue.failed_delay', 10)); + } + } + } catch (\Exception $e) { + // 出现异常,记录日志 + Log::error('微信群好友列表任务异常:' . $e->getMessage()); + if ($job->attempts() > 3) { + $job->delete(); + } else { + $job->release(Config::get('queue.failed_delay', 10)); + } + } + } + + /** + * 处理微信群好友列表获取 + * @param array $data 任务数据 + * @param int $attempts 重试次数 + * @return bool + */ + protected function processGroupFriendsList($data, $attempts) + { + // 获取参数 + $pageIndex = isset($data['pageIndex']) ? $data['pageIndex'] : 0; + $pageSize = isset($data['pageSize']) ? $data['pageSize'] : 100; + + Log::info('开始获取微信群好友列表,页码:' . $pageIndex . ',页大小:' . $pageSize); + + try { + // 从数据库获取未删除的群聊列表 + $chatrooms = WechatChatroomModel::where('isDeleted', 0) + ->page($pageIndex + 1, $pageSize) + ->order('id', 'desc') + ->select(); + + if (empty($chatrooms)) { + Log::info('未找到需要处理的群聊数据,页码:' . $pageIndex); + return true; + } + + + // 实例化控制器 + $wechatChatroomController = new WechatChatroomController(); + + // 遍历每个群聊,获取其成员 + foreach ($chatrooms as $chatroom) { + try { + // 调用获取群成员列表方法 + $result = $wechatChatroomController->listChatroomMember($chatroom['id'],$chatroom['chatroomId'],true); + $response = is_string($result) ? json_decode($result, true) : $result; + + // 判断是否成功 + if (is_array($response) && isset($response['code']) && $response['code'] == 200) { + //Log::info('成功获取群 ' . $chatroom['chatroomId'] . ' 的成员列表'); + } else { + $errorMsg = isset($response['msg']) ? $response['msg'] : '未知错误'; + Log::error('获取群 ' . $chatroom['chatroomId'] . ' 的成员列表失败:' . $errorMsg); + } + } catch (\Exception $e) { + Log::error('获取群 ' . $chatroom['chatroomId'] . ' 的成员列表异常:' . $e->getMessage()); + } + + } + + //Log::info('群成员获取完成,成功:' . $successCount . ',失败:' . $failCount); + + // 计算总数量 + $totalCount = WechatChatroomModel::where('isDeleted', 0)->count(); + $processedCount = ($pageIndex + 1) * $pageSize; + + // 判断是否有下一页 + if ($processedCount < $totalCount) { + // 更新缓存中的页码,设置一天过期 + Cache::set('groupFriendsPage', $pageIndex + 1, 86400); + //Log::info('更新缓存,下一页页码:' . ($pageIndex + 1) . ',缓存时间:1天'); + + // 有下一页,将下一页任务添加到队列 + $nextPageIndex = $pageIndex + 1; + $this->addNextPageToQueue($nextPageIndex, $pageSize); + Log::info('添加下一页任务到队列,页码:' . $nextPageIndex); + } else { + // 没有下一页,重置缓存,设置一天过期 + Cache::set('groupFriendsPage', 0, 86400); + Log::info('获取完成,重置缓存,缓存时间:1天'); + } + + return true; + } catch (\Exception $e) { + Log::error('获取微信群好友列表处理失败:' . $e->getMessage()); + return false; + } + } + + /** + * 添加下一页任务到队列 + * @param int $pageIndex 页码 + * @param int $pageSize 每页大小 + */ + protected function addNextPageToQueue($pageIndex, $pageSize) + { + $data = [ + 'pageIndex' => $pageIndex, + 'pageSize' => $pageSize + ]; + + // 添加到队列,设置任务名为 group_friends + Queue::push(self::class, $data, 'group_friends'); + } +} \ No newline at end of file