群列表+群成员接口外加对接
This commit is contained in:
@@ -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<string>("")
|
||||
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
|
||||
/>
|
||||
<span>{group.name}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleSelectGroupMembers(group.id)}
|
||||
className="mr-1"
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.selectedGroupMembers.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label className="text-sm font-medium">已选择群成员 ({formData.selectedGroupMembers.length})</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setFormData({...formData, selectedGroupMembers: []})}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 p-2 bg-gray-50 rounded-md max-h-[150px] overflow-y-auto">
|
||||
{formData.selectedGroupMembers.map((member) => (
|
||||
<div key={member.id} className="flex items-center bg-white px-2 py-1 rounded border">
|
||||
<Avatar className="h-6 w-6 mr-2">
|
||||
<AvatarImage src={member.avatar} />
|
||||
<AvatarFallback>{member.nickname?.[0] || '?'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-sm">{member.nickname}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 ml-1"
|
||||
onClick={() => setFormData({
|
||||
...formData,
|
||||
selectedGroupMembers: formData.selectedGroupMembers.filter(m => m.id !== member.id)
|
||||
})}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
@@ -425,6 +468,14 @@ export default function EditContentLibraryPage({ params }: { params: Promise<{ i
|
||||
selectedGroups={formData.selectedGroups}
|
||||
onSelect={(groups) => setFormData({ ...formData, selectedGroups: groups })}
|
||||
/>
|
||||
|
||||
<WechatGroupMemberSelector
|
||||
open={isWechatGroupMemberSelectorOpen}
|
||||
onOpenChange={setIsWechatGroupMemberSelectorOpen}
|
||||
groupId={currentGroupId}
|
||||
selectedMembers={formData.selectedGroupMembers}
|
||||
onSelect={(members) => setFormData({ ...formData, selectedGroupMembers: members })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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<T = any> {
|
||||
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<string>("")
|
||||
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() {
|
||||
/>
|
||||
<span>{group.name}</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleSelectGroupMembers(group.id)}
|
||||
className="mr-1"
|
||||
>
|
||||
<Users className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" onClick={() => removeGroup(group.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formData.selectedGroupMembers.length > 0 && (
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Label className="text-sm font-medium">已选择群成员 ({formData.selectedGroupMembers.length})</Label>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setFormData({...formData, selectedGroupMembers: []})}
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 p-2 bg-gray-50 rounded-md max-h-[150px] overflow-y-auto">
|
||||
{formData.selectedGroupMembers.map((member) => (
|
||||
<div key={member.id} className="flex items-center bg-white px-2 py-1 rounded border">
|
||||
<Avatar className="h-6 w-6 mr-2">
|
||||
<AvatarImage src={member.avatar} />
|
||||
<AvatarFallback>{member.nickname?.[0] || '?'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-sm">{member.nickname}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 ml-1"
|
||||
onClick={() => setFormData({
|
||||
...formData,
|
||||
selectedGroupMembers: formData.selectedGroupMembers.filter(m => m.id !== member.id)
|
||||
})}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
@@ -340,6 +381,14 @@ export default function NewContentLibraryPage() {
|
||||
selectedGroups={formData.selectedGroups}
|
||||
onSelect={(groups) => setFormData({ ...formData, selectedGroups: groups })}
|
||||
/>
|
||||
|
||||
<WechatGroupMemberSelector
|
||||
open={isWechatGroupMemberSelectorOpen}
|
||||
onOpenChange={setIsWechatGroupMemberSelectorOpen}
|
||||
groupId={currentGroupId}
|
||||
selectedMembers={formData.selectedGroupMembers}
|
||||
onSelect={(members) => setFormData({ ...formData, selectedGroupMembers: members })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<T = any> {
|
||||
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<string>("")
|
||||
const [selectedGroupMembers, setSelectedGroupMembers] = useState<WechatGroupMember[]>([])
|
||||
|
||||
// 获取内容库列表
|
||||
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() {
|
||||
<div className="text-sm text-gray-500 space-y-1">
|
||||
<div className="flex items-center space-x-1">
|
||||
<span>来源:</span>
|
||||
<div className="w-4 h-4 bg-gray-200 rounded-full"></div>
|
||||
{library.sourceType === 1 && library.sourceFriends?.length > 0 ? (
|
||||
<div className="flex -space-x-1 overflow-hidden">
|
||||
{(library.friendsData || []).slice(0, 3).map((friend) => (
|
||||
<img
|
||||
key={friend.id}
|
||||
src={friend.avatar || "/placeholder.svg"}
|
||||
alt={friend.nickname || `好友${friend.id}`}
|
||||
className="inline-block h-6 w-6 rounded-full ring-2 ring-white"
|
||||
/>
|
||||
))}
|
||||
{library.sourceFriends.length > 3 && (
|
||||
<span className="flex items-center justify-center h-6 w-6 rounded-full bg-gray-200 text-xs font-medium text-gray-800">
|
||||
+{library.sourceFriends.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : library.sourceType === 2 && library.sourceGroups?.length > 0 ? (
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex -space-x-1 overflow-hidden">
|
||||
{(library.groupsData || []).slice(0, 3).map((group) => (
|
||||
<img
|
||||
key={group.id}
|
||||
src={group.avatar || "/placeholder.svg"}
|
||||
alt={group.name || `群组${group.id}`}
|
||||
className="inline-block h-6 w-6 rounded-full ring-2 ring-white"
|
||||
/>
|
||||
))}
|
||||
{library.sourceGroups.length > 3 && (
|
||||
<span className="flex items-center justify-center h-6 w-6 rounded-full bg-gray-200 text-xs font-medium text-gray-800">
|
||||
+{library.sourceGroups.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full"></div>
|
||||
)}
|
||||
</div>
|
||||
<div>创建人:{library.creator}</div>
|
||||
<div>内容数量:{library.itemCount}</div>
|
||||
@@ -287,6 +366,14 @@ export default function ContentLibraryPage() {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<WechatGroupMemberSelector
|
||||
open={isGroupMemberSelectorOpen}
|
||||
onOpenChange={setIsGroupMemberSelectorOpen}
|
||||
groupId={currentGroupId}
|
||||
selectedMembers={selectedGroupMembers}
|
||||
onSelect={handleSaveSelectedMembers}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
211
Cunkebao/components/WechatGroupMemberSelector.tsx
Normal file
211
Cunkebao/components/WechatGroupMemberSelector.tsx
Normal file
@@ -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<T = any> {
|
||||
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<WechatGroupMember[]>([])
|
||||
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<ApiResponse<GroupMemberListResponse>>(`/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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>选择群成员</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索群成员"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleSearch}
|
||||
disabled={loading}
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2 max-h-[400px] overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="text-center py-4">加载中...</div>
|
||||
) : members.length === 0 ? (
|
||||
<div className="text-center py-4">未找到匹配的群成员</div>
|
||||
) : (
|
||||
members.map((member) => (
|
||||
<div key={member.id} className="flex items-center space-x-3 p-2 hover:bg-gray-100 rounded-lg">
|
||||
<Checkbox
|
||||
checked={selectedMembers.some((m) => m.id === member.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked) {
|
||||
onSelect([...selectedMembers, member])
|
||||
} else {
|
||||
onSelect(selectedMembers.filter((m) => m.id !== member.id))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Avatar>
|
||||
<AvatarImage src={member.avatar} />
|
||||
<AvatarFallback>{member.nickname?.[0] || '?'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium truncate">
|
||||
{member.nickname}
|
||||
{member.role === 'owner' && <span className="ml-1 text-xs text-red-500">(群主)</span>}
|
||||
{member.role === 'admin' && <span className="ml-1 text-xs text-blue-500">(管理员)</span>}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{member.wechatId && <div className="truncate">微信ID:{member.wechatId}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分页控制 */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between border-t pt-4 mt-4">
|
||||
<div className="text-sm text-gray-500">
|
||||
总计 {totalItems} 个成员
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handlePrevPage}
|
||||
disabled={page === 1 || loading}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-sm">
|
||||
{page} / {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleNextPage}
|
||||
disabled={page === totalPages || loading}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-2 mt-4">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => onOpenChange(false)}>
|
||||
确定 ({selectedMembers.length})
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -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<T = any> {
|
||||
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<WechatGroup[]>([])
|
||||
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<ApiResponse<GroupListResponse>>(`/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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
@@ -58,22 +101,33 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele
|
||||
<DialogHeader>
|
||||
<DialogTitle>选择聊天群</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索群聊"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜索群聊"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleSearch}
|
||||
disabled={loading}
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2 max-h-[400px] overflow-y-auto">
|
||||
{loading ? (
|
||||
<div className="text-center py-4">加载中...</div>
|
||||
) : filteredGroups.length === 0 ? (
|
||||
) : groups.length === 0 ? (
|
||||
<div className="text-center py-4">未找到匹配的群聊</div>
|
||||
) : (
|
||||
filteredGroups.map((group) => (
|
||||
groups.map((group) => (
|
||||
<div key={group.id} className="flex items-center space-x-3 p-2 hover:bg-gray-100 rounded-lg">
|
||||
<Checkbox
|
||||
checked={selectedGroups.some((g) => g.id === group.id)}
|
||||
@@ -85,24 +139,56 @@ export function WechatGroupSelector({ open, onOpenChange, selectedGroups, onSele
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<img src={group.avatar || "/placeholder.svg"} alt={group.name} className="w-10 h-10 rounded-lg" />
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{group.name}</div>
|
||||
<Avatar className="h-10 w-10 rounded-lg">
|
||||
<AvatarImage src={group.avatar} />
|
||||
<AvatarFallback className="rounded-lg">{group.name?.[0] || '群'}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium truncate">{group.name}</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<div>群主:{group.owner}</div>
|
||||
<div>归属客户:{group.customer}</div>
|
||||
<div>{group.memberCount}人</div>
|
||||
<div className="truncate">归属客户:{group.customer}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 分页控制 */}
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center justify-between border-t pt-4 mt-4">
|
||||
<div className="text-sm text-gray-500">
|
||||
总计 {totalItems} 个群聊
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handlePrevPage}
|
||||
disabled={page === 1 || loading}
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span className="text-sm">
|
||||
{page} / {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={handleNextPage}
|
||||
disabled={page === totalPages || loading}
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end space-x-2 mt-4">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={() => onOpenChange(false)}>确定</Button>
|
||||
<Button onClick={() => onOpenChange(false)}>确定 ({selectedGroups.length})</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
34
Cunkebao/types/wechat.ts
Normal file
34
Cunkebao/types/wechat.ts
Normal file
@@ -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
|
||||
}
|
||||
@@ -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'); // 同步微信群聊数据 √
|
||||
});
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
];
|
||||
|
||||
60
Server/application/command/GroupFriendsCommand.php
Normal file
60
Server/application/command/GroupFriendsCommand.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use app\job\GroupFriendsJob;
|
||||
use think\facade\Cache;
|
||||
|
||||
class GroupFriendsCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('groupFriends:list')
|
||||
->setDescription('获取微信群好友列表,并根据分页自动处理下一页');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('开始处理微信群好友列表任务...');
|
||||
|
||||
try {
|
||||
// 从缓存获取初始页码,缓存有效期一天
|
||||
$pageIndex = Cache::get('groupFriendsPage', 0);
|
||||
$output->writeln('从缓存获取页码:' . $pageIndex);
|
||||
|
||||
$pageSize = 100; // 每页获取100条记录
|
||||
|
||||
// 将任务添加到队列
|
||||
$this->addToQueue($pageIndex, $pageSize);
|
||||
|
||||
$output->writeln('微信群好友列表任务已添加到队列');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('微信群好友列表任务添加失败:' . $e->getMessage());
|
||||
$output->writeln('微信群好友列表任务添加失败:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加任务到队列
|
||||
* @param int $pageIndex 页码
|
||||
* @param int $pageSize 每页大小
|
||||
*/
|
||||
protected function addToQueue($pageIndex, $pageSize)
|
||||
{
|
||||
$data = [
|
||||
'pageIndex' => $pageIndex,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
// 添加到队列,设置任务名为 group_friends
|
||||
Queue::push(GroupFriendsJob::class, $data, 'group_friends');
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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']);
|
||||
@@ -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();
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
namespace app\cunkebao\controller\chatroom;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\model\WechatChatroom;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 群聊管理控制器
|
||||
*/
|
||||
class GetChatroomListV1Controller extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* 获取群聊列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$page = $this->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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
|
||||
|
||||
|
||||
15
Server/application/cunkebao/model/WechatChatroom.php
Normal file
15
Server/application/cunkebao/model/WechatChatroom.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace app\cunkebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 微信好友模型类
|
||||
*/
|
||||
class WechatChatroom extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $name = 'wechat_group';
|
||||
|
||||
|
||||
}
|
||||
145
Server/application/job/GroupFriendsJob.php
Normal file
145
Server/application/job/GroupFriendsJob.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace app\job;
|
||||
|
||||
use think\queue\Job;
|
||||
use think\facade\Log;
|
||||
use think\Queue;
|
||||
use think\facade\Config;
|
||||
use think\facade\Cache;
|
||||
use app\api\controller\WechatChatroomController;
|
||||
use app\api\model\WechatChatroomModel;
|
||||
use think\Db;
|
||||
|
||||
class GroupFriendsJob
|
||||
{
|
||||
/**
|
||||
* 队列任务处理
|
||||
* @param Job $job 队列任务
|
||||
* @param array $data 任务数据
|
||||
* @return void
|
||||
*/
|
||||
public function fire(Job $job, $data)
|
||||
{
|
||||
try {
|
||||
// 如果任务执行成功后删除任务
|
||||
if ($this->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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user