This commit is contained in:
wong
2025-05-12 09:32:51 +08:00
14 changed files with 742 additions and 243 deletions

View File

@@ -134,6 +134,29 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
const friendsLoadingRef = useRef<HTMLDivElement | null>(null)
const friendsContainerRef = useRef<HTMLDivElement | null>(null)
const [initialData, setInitialData] = useState<{
avatar: string;
nickname: string;
status: "normal" | "abnormal";
wechatId: string;
deviceName: string;
deviceId?: string | number;
} | null>(null)
useEffect(() => {
// 从 URL 参数中获取初始数据
const searchParams = new URLSearchParams(window.location.search);
const dataParam = searchParams.get('data');
if (dataParam) {
try {
const decodedData = JSON.parse(decodeURIComponent(dataParam));
setInitialData(decodedData);
} catch (error) {
console.error('解析初始数据失败:', error);
}
}
}, []);
// 计算好友列表容器高度
const getFriendsContainerHeight = () => {
// 最少显示一条记录的高度,最多显示十条记录的高度
@@ -411,6 +434,14 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
if (response && response.code === 200) {
// 转换数据格式
const transformedAccount = transformWechatAccountDetail(response)
// 使用初始数据覆盖API返回的部分字段
if (initialData) {
transformedAccount.avatar = initialData.avatar;
transformedAccount.nickname = initialData.nickname;
transformedAccount.status = initialData.status;
transformedAccount.wechatId = initialData.wechatId;
transformedAccount.deviceName = initialData.deviceName;
}
setAccount(transformedAccount)
// 如果有好友总数更新friendsTotal状态
@@ -425,6 +456,14 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
})
// 获取失败时使用模拟数据
const mockData = generateMockAccountData();
// 使用初始数据覆盖模拟数据的部分字段
if (initialData) {
mockData.avatar = initialData.avatar;
mockData.nickname = initialData.nickname;
mockData.status = initialData.status;
mockData.wechatId = initialData.wechatId;
mockData.deviceName = initialData.deviceName;
}
setAccount(mockData);
// 更新好友总数
setFriendsTotal(mockData.friendCount);
@@ -438,6 +477,14 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
})
// 请求出错时使用模拟数据
const mockData = generateMockAccountData();
// 使用初始数据覆盖模拟数据的部分字段
if (initialData) {
mockData.avatar = initialData.avatar;
mockData.nickname = initialData.nickname;
mockData.status = initialData.status;
mockData.wechatId = initialData.wechatId;
mockData.deviceName = initialData.deviceName;
}
setAccount(mockData);
// 更新好友总数
setFriendsTotal(mockData.friendCount);
@@ -447,7 +494,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
}
fetchAccount()
}, [params.id])
}, [params.id, initialData])
if (!account) {
return <div>...</div>
@@ -534,7 +581,7 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
<AvatarFallback>{account.nickname[0]}</AvatarFallback>
</Avatar>
{account.isVerified && (
<Badge variant="outline" className="absolute -top-2 -right-2 px-2 py-0.5 text-xs">
<Badge className="absolute -top-2 -right-2 px-2 py-0.5 text-xs bg-blue-500 text-white hover:bg-blue-600">
</Badge>
)}
@@ -542,15 +589,25 @@ export default function WechatAccountDetailPage({ params }: { params: { id: stri
<div className="flex-1">
<div className="flex items-center space-x-2">
<h2 className="text-xl font-semibold truncate max-w-[200px]">{account.nickname}</h2>
<Badge variant={account.status === "normal" ? "outline" : "destructive"}>
<Badge variant={account.status === "normal" ? "default" : "destructive"} className={account.status === "normal" ? "bg-green-500 hover:bg-green-600 text-white" : ""}>
{account.status === "normal" ? "正常" : "异常"}
</Badge>
</div>
<p className="text-sm text-gray-500 mt-1">{account.wechatId}</p>
<div className="flex gap-2 mt-2">
<Button variant="outline" onClick={() => router.push(`/devices/${account.deviceId}`)}>
<Button
variant="outline"
onClick={() => {
// 优先使用 initialData.deviceId
const targetDeviceId = initialData?.deviceId || account.deviceId;
if (targetDeviceId) {
// 保证 deviceId 是数字或字符串
return router.push(`/devices/${targetDeviceId}`);
}
}}
>
<Smartphone className="w-4 h-4 mr-2" />
{account.deviceName}
{account.deviceName || '未命名设备'}
</Button>
<Button variant="outline" onClick={handleTransferFriends}>
<UserPlus className="w-4 h-4 mr-2" />

View File

@@ -1,6 +1,6 @@
"use client"
import { useState, useEffect } from "react"
import { useState, useEffect, useRef, useCallback } from "react"
import { ChevronLeft, Filter, Search, RefreshCw, ArrowRightLeft, AlertCircle, Loader2 } from "lucide-react"
import { Card } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
@@ -20,9 +20,23 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
import { toast } from "@/components/ui/use-toast"
import { Progress } from "@/components/ui/progress"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { fetchWechatAccountList, refreshWechatAccounts, transferWechatFriends, transformWechatAccount } from "@/api/wechat-accounts"
import { fetchWechatAccountList, refreshWechatAccounts, transferWechatFriends } from "@/api/wechat-accounts"
import { WechatAccount } from "@/types/wechat-account"
// 定义接口以匹配新的数据结构
interface WechatAccountResponse {
id: number
wechatId: string
nickname: string
avatar: string
times: number
addedCount: number
wechatStatus: number
totalFriend: number
deviceMemo: string
activeTime: string
}
export default function WechatAccountsPage() {
const router = useRouter()
const [accounts, setAccounts] = useState<WechatAccount[]>([])
@@ -34,9 +48,10 @@ export default function WechatAccountsPage() {
const [isLoading, setIsLoading] = useState(true)
const [isRefreshing, setIsRefreshing] = useState(false)
const accountsPerPage = 10
const mounted = useRef(false)
// 获取微信账号列表
const fetchAccounts = async (page: number = 1, keyword: string = "") => {
const fetchAccounts = useCallback(async (page: number = 1, keyword: string = "") => {
try {
setIsLoading(true);
const response = await fetchWechatAccountList({
@@ -49,7 +64,23 @@ export default function WechatAccountsPage() {
if (response && response.code === 200 && response.data) {
// 转换数据格式
const wechatAccounts = response.data.list.map(transformWechatAccount);
const wechatAccounts = response.data.list.map((item: any) => {
const account: WechatAccount = {
id: item.id.toString(),
wechatId: item.wechatId,
nickname: item.nickname,
avatar: item.avatar,
remainingAdds: item.times - item.addedCount,
todayAdded: item.addedCount,
status: item.wechatStatus === 1 ? "normal" as const : "abnormal" as const,
friendCount: item.totalFriend,
deviceName: item.deviceMemo,
lastActive: item.activeTime,
maxDailyAdds: item.times,
deviceId: item.deviceId.toString(),
};
return account;
});
setAccounts(wechatAccounts);
setTotalAccounts(response.data.total);
} else {
@@ -58,7 +89,6 @@ export default function WechatAccountsPage() {
description: response?.msg || "请稍后再试",
variant: "destructive"
});
// 如果API请求失败设置空数组
setAccounts([]);
setTotalAccounts(0);
}
@@ -74,9 +104,29 @@ export default function WechatAccountsPage() {
} finally {
setIsLoading(false);
}
}, [accountsPerPage]);
// 初始化数据加载
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
fetchAccounts(currentPage, searchQuery);
}
}, []);
// 处理页码和搜索变化
useEffect(() => {
if (mounted.current) {
fetchAccounts(currentPage, searchQuery);
}
}, [currentPage, searchQuery, fetchAccounts]);
// 搜索处理
const handleSearch = () => {
setCurrentPage(1);
};
// 刷新微信账号状态
// 刷新处理
const handleRefresh = async () => {
try {
setIsRefreshing(true);
@@ -108,24 +158,6 @@ export default function WechatAccountsPage() {
}
};
// 初始加载和页码变化时获取数据
useEffect(() => {
fetchAccounts(currentPage, searchQuery);
}, [currentPage]);
// 搜索时重置页码并获取数据
const handleSearch = () => {
setCurrentPage(1);
fetchAccounts(1, searchQuery);
};
// 处理搜索框回车事件
const handleSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSearch();
}
};
const filteredAccounts = accounts;
const totalPages = Math.ceil(totalAccounts / accountsPerPage);
@@ -177,7 +209,6 @@ export default function WechatAccountsPage() {
placeholder="搜索微信号/昵称"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyDown={handleSearchKeyDown}
/>
</div>
<Button variant="outline" size="icon">
@@ -226,7 +257,18 @@ export default function WechatAccountsPage() {
<Card
key={account.id}
className="p-4 hover:shadow-lg transition-all cursor-pointer overflow-hidden"
onClick={() => router.push(`/wechat-accounts/${account.id}`)}
onClick={() => {
// 将需要的数据编码为 URL 安全的字符串
const accountData = encodeURIComponent(JSON.stringify({
avatar: account.avatar,
nickname: account.nickname,
status: account.status,
wechatId: account.wechatId,
deviceName: account.deviceName,
deviceId: account.deviceId,
}));
router.push(`/wechat-accounts/${account.id}?data=${accountData}`);
}}
>
<div className="flex items-start space-x-4">
<Avatar className="h-12 w-12 ring-2 ring-offset-2 ring-blue-500/20">
@@ -237,7 +279,7 @@ export default function WechatAccountsPage() {
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<h3 className="font-medium truncate max-w-[180px]">{account.nickname}</h3>
<Badge variant={account.status === "normal" ? "outline" : "destructive"}>
<Badge variant={account.status === "normal" ? "default" : "destructive"} className={account.status === "normal" ? "bg-green-500 hover:bg-green-600 text-white" : ""}>
{account.status === "normal" ? "正常" : "异常"}
</Badge>
</div>
@@ -279,7 +321,10 @@ export default function WechatAccountsPage() {
{account.todayAdded}/{account.maxDailyAdds}
</span>
</div>
<Progress value={(account.todayAdded / account.maxDailyAdds) * 100} className="h-2" />
<Progress
value={(account.todayAdded / account.maxDailyAdds) * 100}
className="h-2"
/>
</div>
<div className="flex items-center justify-between text-xs text-gray-500 pt-2 flex-wrap gap-1">
<div className="truncate max-w-[150px]">{account.deviceName || '未知设备'}</div>
@@ -370,5 +415,4 @@ export default function WechatAccountsPage() {
</Dialog>
</div>
)
}
}

View File

@@ -21,6 +21,7 @@ const nextConfig = {
parallelServerBuildTraces: true,
parallelServerCompiles: true,
},
reactStrictMode: false,
}
mergeConfig(nextConfig, userConfig)

View File

@@ -1,131 +0,0 @@
<?php
namespace app\common\model;
use think\Model;
/**
* 公司账户模型类
*/
class CompanyAccount extends Model
{
/**
* 数据表名
* @var string
*/
protected $table = 'tk_company_account';
/**
* 主键
* @var string
*/
protected $pk = 'id';
/**
* 自动写入时间戳
* @var bool
*/
protected $autoWriteTimestamp = true;
/**
* 创建时间字段
* @var string
*/
protected $createTime = 'createTime';
/**
* 更新时间字段
* @var string
*/
protected $updateTime = 'updateTime';
/**
* 隐藏属性
* @var array
*/
protected $hidden = ['passwordMd5', 'passwordLocal', 'secret'];
/**
* 字段类型
* @var array
*/
protected $type = [
'id' => 'integer',
'tenantId' => 'integer',
'accountType' => 'integer',
'companyId' => 'integer',
'useGoogleSecretKey' => 'boolean',
'hasVerifyGoogleSecret' => 'boolean',
'lastLoginTime' => 'integer',
'createTime' => 'integer',
'updateTime' => 'integer'
];
/**
* 获取公司账户信息
* @param string $userName 用户名
* @param string $password 密码MD5加密后的
* @return array|null
*/
public static function getAccount($userName, $password)
{
// 查询账户
$account = self::where('userName', $userName)
->find();
if (!$account) {
return null;
}
// 验证密码
if ($account->passwordMd5 !== $password) {
return null;
}
// 更新登录信息
$account->lastLoginIp = request()->ip();
$account->lastLoginTime = time();
$account->save();
return [
'id' => $account->id,
'tenantId' => $account->tenantId,
'userName' => $account->userName,
'realName' => $account->realName,
'nickname' => $account->nickname,
'avatar' => $account->avatar,
'accountType' => $account->accountType,
'companyId' => $account->companyId,
'lastLoginIp' => $account->lastLoginIp,
'lastLoginTime' => $account->lastLoginTime
];
}
/**
* 通过租户ID获取账户信息
* @param int $companyId 租户ID
* @return array|null
*/
public static function getAccountByCompanyId($companyId)
{
// 查询账户
$account = self::where('companyId', $companyId)->find();
if (!$account) {
return null;
}
return [
'id' => $account->id,
'tenantId' => $account->tenantId,
'userName' => $account->userName,
'realName' => $account->realName,
'nickname' => $account->nickname,
'avatar' => $account->avatar,
'accountType' => $account->accountType,
'companyId' => $account->companyId,
'lastLoginIp' => $account->lastLoginIp,
'lastLoginTime' => $account->lastLoginTime
];
}
}

View File

@@ -9,6 +9,9 @@ use think\Model;
*/
class DeviceWechatLogin extends Model
{
const ALIVE_WECHAT_ACTIVE = 1; // 微信在线
const ALIVE_WECHAT_DIED = 0; // 微信离线
// 登录日志最新登录 alive = 1旧数据全部设置0
protected $name = 'device_wechat_login';
@@ -25,6 +28,10 @@ class DeviceWechatLogin extends Model
*/
public static function getDevicesLatestLogin(array $deviceIds): array
{
return self::fieldRaw('max(id) as lastedId,deviceId')->whereIn('deviceId', $deviceIds)->group('deviceId')->select()->toArray();
return static::fieldRaw('max(id) as lastedId,deviceId')
->whereIn('deviceId', $deviceIds)
->group('deviceId')
->select()
->toArray();
}
}

View File

@@ -9,9 +9,9 @@ class User extends Model
{
use SoftDelete;
const ADMIN_STP = 1;
const ADMIN_STP = 1; // 主操盘手账号
const ADMIN_OTP = 0;
const NOT_USER = -1;
const NOT_USER = -1; // 非登录用户用于任务操作的S2系统专属
const MASTER_USER = 1; // 操盘手
const CUSTOMER_USER = 2; // 门店接待
const STATUS_STOP = 0; // 禁用状态

View File

@@ -16,5 +16,4 @@ class WechatAccount extends Model
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
protected $defaultSoftDelete = 0;
}

View File

@@ -3,12 +3,15 @@
namespace app\common\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 微信好友模型类
*/
class WechatFriend extends Model
{
use SoftDelete;
// 设置表名
protected $name = 'wechat_friend';
@@ -16,5 +19,6 @@ class WechatFriend extends Model
protected $autoWriteTimestamp = true;
protected $createTime = 'createTime';
protected $updateTime = 'updateTime';
protected $deleteTime = 'deleteTime';
protected $defaultSoftDelete = 0;
}

View File

@@ -7,10 +7,9 @@ use think\facade\Route;
// 定义RESTful风格的API路由
Route::group('v1/', function () {
// 设备管理相关
Route::group('devices', function () {
Route::get('add-results', 'app\cunkebao\controller\device\GetAddResultedDevicesController@index'); // 更新设备任务配置
Route::get('add-results', 'app\cunkebao\controller\device\GetAddResultedV1Controller@index'); // 更新设备任务配置
Route::get(':id/related-accounts', 'app\cunkebao\controller\device\GetRelatedAccountsV1Controller@index'); // 设备关联微信账号路由
Route::get(':id/handle-logs', 'app\cunkebao\controller\device\GetDeviceHandleLogsV1Controller@index'); // 获取设备操作记录
Route::get('', 'app\cunkebao\controller\device\GetDeviceListV1Controller@index'); // 获取设备列表
@@ -23,11 +22,14 @@ Route::group('v1/', function () {
// 设备微信相关
Route::group('device/wechats', function () {
Route::get('', 'app\cunkebao\controller\wechat\GetWechatsOnDevicesV1Controller@index'); // 获取在线微信账号列表
Route::get(':id', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index'); // 获取微信号详情
Route::get('friends', 'app\cunkebao\controller\DeviceWechat@getFriends'); // 获取微信好友列表
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count'); // 获取在线微信账号数量
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
Route::get('', 'app\cunkebao\controller\DeviceWechat@index'); // 获取在线微信账号列表
Route::get(':id', 'app\cunkebao\controller\DeviceWechat@detail'); // 获取微信号详情
Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态
Route::post('transfer-friends', 'app\cunkebao\controller\DeviceWechat@transferFriends'); // 微信好友转移
});
@@ -82,7 +84,7 @@ Route::group('v1/', function () {
Route::group('chatroom', function () {
Route::get('', 'app\cunkebao\controller\chatroom\GetChatroomListV1Controller@index'); // 获取群列表
Route::get('getMemberList', 'app\cunkebao\controller\chatroom\GetChatroomListV1Controller@getMemberList'); // 获取群详情
});
// 计划任务相关路由

View File

@@ -12,7 +12,7 @@ use think\Db;
/**
* 设备控制器
*/
class GetAddResultedDevicesController extends BaseController
class GetAddResultedV1Controller extends BaseController
{
/**
* 通过账号id 获取项目id。

View File

@@ -5,7 +5,7 @@ namespace app\cunkebao\controller\device;
use app\common\model\Device as DeviceModel;
use app\common\model\DeviceUser as DeviceUserModel;
use app\common\model\User as UserModel;
use app\common\model\WechatFriend;
use app\common\model\WechatFriend as WechatFriendModel;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
@@ -22,8 +22,6 @@ class GetDeviceListV1Controller extends BaseController
*/
protected function makeWhere(array $params = []): array
{
$where = [];
// 关键词搜索同时搜索IMEI和备注
if (!empty($keyword = $this->request->param('keyword'))) {
$where[] = ['exp', "d.imei LIKE '%{$keyword}%' OR d.memo LIKE '%{$keyword}%'"];
@@ -47,8 +45,10 @@ class GetDeviceListV1Controller extends BaseController
protected function makeDeviceIdsWhere(): array
{
$deviceIds = DeviceUserModel::where(
$this->getUserInfo('id'),
$this->getUserInfo('companyId')
[
'userId' => $this->getUserInfo('id'),
'companyId' => $this->getUserInfo('companyId')
]
)
->column('deviceId');
@@ -56,7 +56,7 @@ class GetDeviceListV1Controller extends BaseController
throw new \Exception('请联系管理员绑定设备', 403);
}
$where['d.id'] = ['in', $deviceIds];
$where['d.id'] = array('in', $deviceIds);
return $where;
}
@@ -65,11 +65,9 @@ class GetDeviceListV1Controller extends BaseController
* 获取设备列表
*
* @param array $where 查询条件
* @param int $page 页码
* @param int $limit 每页数量
* @return \think\Paginator 分页对象
*/
protected function getDeviceList(array $where, int $page = 1, int $limit = 10): \think\Paginator
protected function getDeviceList(array $where): \think\Paginator
{
$query = DeviceModel::alias('d')
->field([
@@ -101,19 +99,19 @@ class GetDeviceListV1Controller extends BaseController
*/
protected function countFriend(\think\Paginator $list): array
{
$result = [];
$resultSets = [];
foreach ($list->items() as $item) {
$section = $item->toArray();
$sections = $item->toArray();
if ($item->wechatId) {
$section['totalFriend'] = WechatFriend::where(['ownerWechatId' => $section['wechatId']])->count();
$sections['totalFriend'] = WechatFriendModel::where(['ownerWechatId' => $item->wechatId])->count();
}
array_push($result, $section);
array_push($resultSets, $sections);
}
return $result;
return $resultSets;
}
/**
@@ -126,8 +124,10 @@ class GetDeviceListV1Controller extends BaseController
if ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP) {
$where = $this->makeWhere();
$result = $this->getDeviceList($where);
} else {
$where = $this->makeWhere($this->makeDeviceIdsWhere());
}
else {
$where = $this->makeWhere( $this->makeDeviceIdsWhere() );
$result = $this->getDeviceList($where);
}

View File

@@ -0,0 +1,283 @@
<?php
namespace app\cunkebao\controller\wechat;
use app\common\model\WechatAccount as WechatAccountModel;
use app\common\model\WechatFriend as WechatFriendModel;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
/**
* 设备微信控制器
*/
class GetWechatOnDeviceSummarizeV1Controller extends BaseController
{
/**
* TODO 计算账号年龄(从创建时间到现在)
*
* @param string $wechatId
* @return string
*/
protected function getRegisterDate(string $wechatId): string
{
return date('Y-m-d H:i:s', strtotime('-15 months'));
}
/**
* TODO 获取每天聊天次数。
*
* @param string $wechatId
* @return int
*/
protected function getChatTimesPerDay(string $wechatId): int
{
return mt_rand(0, 100);
}
/**
* TODO 总聊天数量
*
* @param string $wechatId
* @return int
*/
protected function getChatTimesTotal(string $wechatId): int
{
return mt_rand(2000, 1000000);
}
/**
* 计算活跃程度(根据消息数)
*
* @param string $wechatId
* @return string
*/
protected function getActivityLevel(string $wechatId): array
{
return [
'allTimes' => $this->getChatTimesTotal($wechatId),
'dayTimes' => $this->getChatTimesPerDay($wechatId),
];
}
/**
* TODO 获取限制记录
*
* @param string $wechatId
* @return array
*/
protected function getRestrict(string $wechatId): array
{
return [
[
'id' => 1,
'type' => 'warnnig',
'reason' => '频繁添加好友',
'date' => date('Y-m-d H:i:s', strtotime('-1 day')),
],
[
'id' => 2,
'type' => 'error',
'reason' => '营销内容违规',
'date' => date('Y-m-d H:i:s', strtotime('-1 day')),
],
];
}
/**
* 计算两个时间相差几个月
*
* @param string $wechatId
* @return float|int
* @throws \DateMalformedStringException
*/
protected function getDateDiff(string $wechatId): int
{
$currentData = new \DateTime(date('Y-m-d H:i:s', time()));
$registerDate = new \DateTime($this->getRegisterDate($wechatId));
$interval = date_diff($currentData, $registerDate);
return $interval->y * 12 + $interval->m;
}
/**
* 计算账号年龄权重
*
* @param string $wechatId
* @return int
* @throws \DateMalformedStringException
*/
protected function _calculAgeWeight(string $wechatId): int
{
// 规定账号年龄五年起拥有最高权重
$cha = ceil($this->getDateDiff($wechatId) / 60) * 100;
return $cha > 100 ? 100 : $cha;
}
/**
* 计算活跃度权重
*
* @param string $wechatId
* @return int
*/
protected function _calActivityWeigth(string $wechatId): int
{
// 规定每天发送50条消息起拥有最高权重
$cha = ceil($this->getChatTimesPerDay($wechatId) / 50) * 100;
return $cha > 100 ? 100 : $cha;
}
/**
* 计算限制影响权重
*
* @param string $wechatId
* @return int
*/
protected function _calRestrictWeigth(string $wechatId): int
{
$list = $this->getRestrict($wechatId); // 2
$gtmd = 10 - count($list); // 规定没有限制记录拥有最高权重10条以上权重为0
return ($gtmd < 0 ? 0 : $gtmd) * 10;
}
/**
* 计算实名认证权重
*
* @param string $wechatId
* @return int
*/
protected function _calRealNameWeigth(string $wechatId): int
{
return 100;
}
/**
* 计算好友数量每5权重=1好友最多20个
*
* @param int $weight
* @return int
*/
protected function _calAllowedFriends(int $weight): int
{
$adjustedWeight = $weight;
$lastDigit = $weight % 10;
if ($weight < 10) {
if ($lastDigit < 5) {
$adjustedWeight = 5;
} else {
$adjustedWeight = 10;
}
}
return min(20, floor($adjustedWeight / 5));
}
/**
* 获取账号权重
*
* @param string $wechatId
* @return array
*/
protected function getAccountWeight(string $wechatId): array
{
$ageWeight = $this->_calculAgeWeight($wechatId); // 账号年龄权重
$activityWeigth = $this->_calActivityWeigth($wechatId); // 计算活跃度权重
$restrictWeight = $this->_calRestrictWeigth($wechatId); // 计算限制影响权重
$realNameWeight = $this->_calRealNameWeigth($wechatId); // 计算实名认证权重
$scope = ceil(($ageWeight + $activityWeigth + $restrictWeight + $realNameWeight) / 4); // 计算总分
return compact(
'scope',
'ageWeight',
'activityWeigth',
'restrictWeight',
'realNameWeight'
);
}
/**
* 计算今日新增好友数量
*
* @param string $ownerWechatId
* @return int
*/
protected function getTodayNewFriendCount(string $ownerWechatId): int
{
return WechatFriendModel::where( compact('ownerWechatId') )
->whereBetween('createTime',
[
strtotime(date('Y-m-d 00:00:00')),
strtotime(date('Y-m-d 23:59:59'))
]
)
->count('*');
}
/**
* 获取账号加友统计数据.
*
* @param string $wechatId
* @param array $accountWeight
* @return array
*/
protected function getStatistics(string $wechatId, array $accountWeight): array
{
return [
'addedCount' => $this->getTodayNewFriendCount($wechatId),
'addLimit' => $this->_calAllowedFriends($accountWeight['scope'])
];
}
/**
* 获取原始的64位的微信id
*
* @return string
* @throws \Exception
*/
protected function getStringWechatId(): string
{
$account = WechatAccountModel::find(333333);
if (is_null($account)) {
throw new \Exception('微信账号不存在', 404);
}
return $account->wechatId;
}
/**
* 获取微信号详情
*
* @return \think\response\Json
*/
public function index()
{
try {
// $wechatId = $this->getStringWechatId();
$wechatId = '1111111';
$accountAge = $this->getRegisterDate($wechatId);
$activityLevel = $this->getActivityLevel($wechatId);
$accountWeight = $this->getAccountWeight($wechatId);
$statistics = $this->getStatistics($wechatId, $accountWeight);
$restrictions = $this->getRestrict($wechatId);
return ResponseHelper::success(
compact(
'accountAge',
'activityLevel',
'accountWeight',
'statistics',
'restrictions'
)
);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode());
}
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace app\cunkebao\controller\wechat;
use app\common\model\Device as DeviceModel;
use app\common\model\Device as DevicesModel;
use app\common\model\DeviceUser as DeviceUserModel;
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
use app\common\model\User as UserModel;
use app\common\model\WechatAccount as WechatAccountModel;
use app\common\model\WechatFriend as WechatFriendModel;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
/**
* 微信控制器
*/
class GetWechatsOnDevicesV1Controller extends BaseController
{
/**
* 计算今日可添加好友数量
*
* @param string $wechatId
* @return int
*/
protected function getCanAddFriendCount(string $wechatId): int
{
return 20; // 最多限制 20 次
}
/**
* 计算今日新增好友数量
*
* @param string $ownerWechatId
* @return int
*/
protected function getTodayNewFriendCount(string $ownerWechatId): int
{
return WechatFriendModel::where( compact('ownerWechatId') )
->whereBetween('createTime',
[
strtotime(date('Y-m-d 00:00:00')),
strtotime(date('Y-m-d 23:59:59'))
]
)
->count('*');
}
/**
* TODO 获取微信加友状态
*
* @param string $wechatId
* @return int
*/
protected function getWechatAddFriendStatus(string $wechatId): int
{
return 1;
}
/**
* 获取微信好友数量
*
* @param string $ownerWechatId
* @return int
*/
protected function getFriendsCount(string $ownerWechatId): int
{
return WechatFriendModel::where(compact('ownerWechatId'))->count();
}
/**
* 获取微信所属设备
*
* @param string $wechatId
* @return string
*/
protected function getWhereOnDevice(string $wechatId): string
{
return (string)DeviceModel::alias('d')
->where(
[
'l.wechatId' => $wechatId,
'l.alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE
]
)
->join('device_wechat_login l', 'd.id = l.deviceId')
->value('d.memo');
}
/**
* 主操盘手获取项目下所有设备的id
*
* @return array
* @throws \Exception
*/
protected function getCompanyDevicesId(): array
{
return DevicesModel::where(
[
'companyId' => $this->getUserInfo('companyId')
]
)
->column('id');
}
/**
* 非主操盘手获取分配的设备
*
* @return array
*/
protected function getUserDevicesId(): array
{
return DeviceUserModel::where(
[
'userId' => $this->getUserInfo('id'),
'companyId' => $this->getUserInfo('companyId')
]
)
->column('deviceId');
}
/**
* 根据不同角色,显示的设备数量不同
*
* @return array
* @throws \Exception
*/
protected function getDevicesId(): array
{
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
? $this->getCompanyDevicesId() // 主操盘手获取所有的设备
: $this->getUserDevicesId(); // 非主操盘手获取分配的设备
}
/**
* 获取有登录设备的微信id
*
* @return array
*/
protected function getWechatIdsOnDevices(): array
{
// 关联设备id查询过滤掉已删除的设备
if (empty($deviceIds = $this->getDevicesId())) {
throw new \Exception('暂无设备数据', 200);
}
return DeviceWechatLoginModel::where(
[
'companyId' => $this->getUserInfo('companyId'),
'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
]
)
->where('deviceId', 'in', $deviceIds)
->column('wechatId');
}
/**
* TODO 获取设备最新活跃时间
*
* @param string $wechatId
* @return string
*/
protected function getLatestActiveTime(string $wechatId): string
{
return date('Y-m-d H:i:s', strtotime('-1 day'));
}
/**
* 构建查询条件
*
* @param array $params
* @return array
*/
protected function makeWhere(array $params = []): array
{
if (empty($wechatIds = $this->getWechatIdsOnDevices())) {
throw new \Exception('设备尚未有登录微信', 200);
}
// 关键词搜索(同时搜索微信号和昵称)
if (!empty($keyword = $this->request->param('keyword'))) {
$where[] = ['exp', "w.alias LIKE '%{$keyword}%' OR w.nickname LIKE '%{$keyword}%'"];
}
$where['w.wechatId'] = array('in', implode(',', $wechatIds));
return array_merge($params, $where);
}
/**
* 获取在线微信账号列表
*
* @param array $where
* @return \think\Paginator 分页对象
*/
protected function getOnlineWechatList(array $where): \think\Paginator
{
$query = WechatAccountModel::alias('w')
->field(
[
'w.id', 'w.nickname', 'w.avatar',
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatId',
'l.deviceId'
]
)
->join('device_wechat_login l', 'w.wechatId = l.wechatId')
->order('w.id desc');
foreach ($where as $key => $value) {
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
$query->whereExp('', $value[1]);
continue;
}
if (is_array($value)) {
$query->where($key, ...$value);
continue;
}
$query->where($key, $value);
}
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
}
/**
* 构建返回数据
*
* @param \think\Paginator $result
* @return array
*/
protected function makeResultedSet(\think\Paginator $result): array
{
$resultSets = [];
foreach ($result->items() as $item) {
$sections = $item->toArray() + [
'times' => $this->getCanAddFriendCount($item->wechatId),
'addedCount' => $this->getTodayNewFriendCount($item->wechatId),
'wechatStatus' => $this->getWechatAddFriendStatus($item->wechatId),
'totalFriend' => $this->getFriendsCount($item->wechatId),
'deviceMemo' => $this->getWhereOnDevice($item->wechatId),
'activeTime' => $this->getLatestActiveTime($item->wechatId),
];
array_push($resultSets, $sections);
}
return $resultSets;
}
/**
* 获取在线微信账号列表
* @return \think\response\Json
*/
public function index()
{
try {
$result = $this->getOnlineWechatList(
$this->makeWhere()
);
return ResponseHelper::success(
[
'list' => $this->makeResultedSet($result),
'total' => $result->total(),
]
);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode());
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
namespace app\cunkebao\model;
use think\Model;
@@ -10,10 +11,10 @@ class WechatAccount extends Model
{
// 设置表名
protected $name = 'wechat_account';
/**
* 获取在线微信账号数量
*
*
* @param array $where 额外的查询条件
* @return int 微信账号数量
*/
@@ -22,20 +23,20 @@ class WechatAccount extends Model
$condition = [
'deviceAlive' => 1,
'wechatAlive' => 1,
'isDeleted' => 0
'isDeleted' => 0
];
// 合并额外条件
if (!empty($where)) {
$condition = array_merge($condition, $where);
}
return self::where($condition)->count();
}
/**
* 获取有登录微信的设备数量
*
*
* @param array $where 额外的查询条件
* @return int 设备数量
*/
@@ -43,55 +44,14 @@ class WechatAccount extends Model
{
$condition = [
'deviceAlive' => 1,
'isDeleted' => 0
'isDeleted' => 0
];
// 合并额外条件
if (!empty($where)) {
$condition = array_merge($condition, $where);
}
return self::where($condition)->count();
}
/**
* 获取在线微信账号列表
*
* @param array $where 额外的查询条件
* @param string $order 排序方式
* @param int $page 页码
* @param int $limit 每页数量
* @return \think\Paginator 分页对象
*/
public static function getOnlineWechatList($where = [], $order = 'id desc', $page = 1, $limit = 10)
{
$condition = [
'isDeleted' => 0
];
// 合并额外条件
if (!empty($where)) {
$condition = array_merge($condition, $where);
}
return self::where($condition)
->field([
'id',
'wechatId',
'accountNickname',
'nickname',
'accountUserName',
'avatar',
'wechatAlive',
'deviceAlive',
'totalFriend',
'maleFriend',
'femaleFriend',
'imei',
'deviceMemo',
'yesterdayMsgCount'
])
->order($order)
->paginate($limit, false, ['page' => $page]);
}
}