Merge branch 'develop' into yongpxu-dev

# Conflicts:
#	Cunkebao/src/pages/mobile/mine/wechat-accounts/detail/index.tsx   resolved by yongpxu-dev version
This commit is contained in:
超级老白兔
2025-11-28 16:52:00 +08:00
23 changed files with 3293 additions and 441 deletions

View File

@@ -1,10 +1,26 @@
import request from "@/api/request";
import axios from "axios";
import { useUserStore } from "@/store/module/user";
// 获取微信号详情
export function getWechatAccountDetail(id: string) {
return request("/v1/wechats/getWechatInfo", { wechatId: id }, "GET");
}
// 获取微信号概览数据
export function getWechatAccountOverview(id: string) {
return request("/v1/wechats/overview", { wechatId: id }, "GET");
}
// 获取微信号朋友圈列表
export function getWechatMoments(params: {
wechatId: string;
page?: number;
limit?: number;
}) {
return request("/v1/wechats/moments", params, "GET");
}
// 获取微信号好友列表
export function getWechatFriends(params: {
wechatAccount: string;
@@ -36,3 +52,68 @@ export function transferWechatFriends(params: {
}) {
return request("/v1/wechats/transfer-friends", params, "POST");
}
// 导出朋友圈接口(直接下载文件)
export async function exportWechatMoments(params: {
wechatId: string;
keyword?: string;
type?: number;
startTime?: string;
endTime?: string;
}): Promise<void> {
const { token } = useUserStore.getState();
const baseURL =
(import.meta as any).env?.VITE_API_BASE_URL || "/api";
// 构建查询参数
const queryParams = new URLSearchParams();
queryParams.append("wechatId", params.wechatId);
if (params.keyword) {
queryParams.append("keyword", params.keyword);
}
if (params.type !== undefined) {
queryParams.append("type", params.type.toString());
}
if (params.startTime) {
queryParams.append("startTime", params.startTime);
}
if (params.endTime) {
queryParams.append("endTime", params.endTime);
}
try {
const response = await axios.get(
`${baseURL}/v1/wechats/moments/export?${queryParams.toString()}`,
{
responseType: "blob",
headers: {
Authorization: token ? `Bearer ${token}` : undefined,
},
}
);
// 创建下载链接
const blob = new Blob([response.data]);
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
// 从响应头获取文件名,如果没有则使用默认文件名
const contentDisposition = response.headers["content-disposition"];
let fileName = "朋友圈导出.xlsx";
if (contentDisposition) {
const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (fileNameMatch && fileNameMatch[1]) {
fileName = decodeURIComponent(fileNameMatch[1].replace(/['"]/g, ""));
}
}
link.download = fileName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error: any) {
throw new Error(error.response?.data?.message || error.message || "导出失败");
}
}

View File

@@ -1,3 +1,41 @@
// 概览数据接口
export interface WechatAccountOverview {
healthScoreAssessment: {
score: number;
dailyLimit: number;
todayAdded: number;
lastAddTime: string;
statusTag: string;
baseComposition?: Array<{
name: string;
score: number;
formatted: string;
friendCount?: number;
}>;
dynamicRecords?: Array<{
title?: string;
description?: string;
time?: string;
score?: number;
formatted?: string;
statusTag?: string;
}>;
};
accountValue: {
value: number;
formatted: string;
};
todayValueChange: {
change: number;
formatted: string;
isPositive: boolean;
};
totalFriends: number;
todayNewFriends: number;
highValueChatrooms: number;
todayNewChatrooms: number;
}
export interface WechatAccountSummary {
accountAge: string;
activityLevel: {
@@ -15,12 +53,51 @@ export interface WechatAccountSummary {
todayAdded: number;
addLimit: number;
};
healthScore?: {
score: number;
lastUpdate?: string;
lastAddTime?: string;
baseScore?: number;
verifiedScore?: number;
friendsScore?: number;
activities?: {
type: string;
time?: string;
score: number;
description?: string;
status?: string;
}[];
};
moments?: {
id: string;
date: string;
month: string;
day: string;
content: string;
images?: string[];
timeAgo?: string;
hasEmoji?: boolean;
}[];
accountValue?: {
value: number;
todayChange?: number;
};
friendsCount?: {
total: number;
todayAdded?: number;
};
groupsCount?: {
total: number;
todayAdded?: number;
};
restrictions: {
id: number;
level: number;
reason: string;
date: string;
}[];
// 新增概览数据
overview?: WechatAccountOverview;
}
export interface Friend {
@@ -39,6 +116,27 @@ export interface Friend {
region: string;
source: string;
notes: string;
value?: number;
valueFormatted?: string;
statusTags?: string[];
}
export interface MomentItem {
id: string;
snsId: string;
type: number;
content: string;
resUrls: string[];
commentList?: any[];
likeList?: any[];
createTime: string;
momentEntity?: {
lat?: string;
lng?: string;
location?: string;
picSize?: number;
userName?: string;
};
}
export interface WechatFriendDetail {

View File

@@ -143,67 +143,235 @@
}
.overview-content {
.info-grid {
// 健康分评估区域
.health-score-section {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.health-score-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.health-score-info {
.health-score-status {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.status-tag {
background: #ffebeb;
color: #ff4d4f;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
.status-time {
font-size: 12px;
color: #999;
}
}
.health-score-display {
display: flex;
align-items: center;
.score-circle-wrapper {
width: 100px;
height: 100px;
margin-right: 24px;
position: relative;
.score-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background: #fff;
border: 8px solid #ff4d4f;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.score-number {
font-size: 28px;
font-weight: 700;
color: #ff4d4f;
line-height: 1;
}
.score-label {
font-size: 12px;
color: #999;
margin-top: 4px;
}
}
}
.health-score-stats {
flex: 1;
.stats-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.stats-label {
font-size: 14px;
color: #666;
}
.stats-value {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
}
}
}
}
// 账号统计卡片网格
.account-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
.info-card {
background: linear-gradient(135deg, #e6f7ff, #f0f8ff);
.stat-card {
background: #ffffff;
padding: 16px;
border-radius: 12px;
border: 1px solid #bae7ff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
&:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.info-header {
.stat-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-bottom: 8px;
.info-icon {
font-size: 16px;
color: #1677ff;
padding: 6px;
background: #e6f7ff;
border-radius: 8px;
.stat-title {
font-size: 14px;
color: #666;
}
.info-title {
flex: 1;
.stat-icon-up {
width: 20px;
height: 20px;
background: #f0f0f0;
border-radius: 50%;
position: relative;
.title-text {
font-size: 12px;
font-weight: 600;
color: #1677ff;
margin-bottom: 2px;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
width: 8px;
height: 8px;
border-top: 2px solid #722ed1;
border-right: 2px solid #722ed1;
}
}
.stat-icon-plus {
width: 20px;
height: 20px;
background: #f0f0f0;
border-radius: 50%;
position: relative;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 10px;
height: 2px;
background: #52c41a;
}
.title-sub {
font-size: 10px;
color: #666;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 10px;
background: #52c41a;
}
}
.stat-icon-people {
width: 20px;
height: 20px;
background: #f0f0f0;
border-radius: 50%;
position: relative;
&::before {
content: '';
position: absolute;
top: 6px;
left: 7px;
width: 6px;
height: 6px;
border-radius: 50%;
background: #1677ff;
}
&::after {
content: '';
position: absolute;
top: 13px;
left: 5px;
width: 10px;
height: 5px;
border-radius: 10px 10px 0 0;
background: #1677ff;
}
}
.stat-icon-chat {
width: 20px;
height: 20px;
background: #f0f0f0;
border-radius: 50%;
position: relative;
&::before {
content: '';
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
border-radius: 2px;
background: #fa8c16;
}
}
}
.info-value {
text-align: right;
font-size: 18px;
font-weight: 700;
color: #1677ff;
.stat-value {
font-size: 20px;
font-weight: 600;
color: #333;
}
.value-unit {
font-size: 12px;
color: #666;
margin-left: 4px;
}
.stat-value-positive {
font-size: 20px;
font-weight: 600;
color: #52c41a;
}
}
}
@@ -449,6 +617,47 @@
}
}
.friends-summary {
display: flex;
align-items: center;
justify-content: space-between;
background: #f5f9ff;
border: 1px solid #e0edff;
border-radius: 10px;
padding: 12px 16px;
margin-bottom: 16px;
.summary-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.summary-label {
font-size: 12px;
color: #666;
}
.summary-value {
font-size: 20px;
font-weight: 600;
color: #111;
}
.summary-value-highlight {
font-size: 20px;
font-weight: 600;
color: #fa541c;
}
.summary-divider {
width: 1px;
height: 32px;
background: #e6e6e6;
margin: 0 12px;
}
}
.friends-list {
.empty {
text-align: center;
@@ -467,83 +676,100 @@
}
}
.friend-item {
.friend-card {
display: flex;
align-items: center;
padding: 12px;
padding: 14px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 8px;
margin-bottom: 8px;
}
border: 1px solid #f0f0f0;
border-radius: 12px;
margin-bottom: 10px;
gap: 12px;
transition: box-shadow 0.2s, border-color 0.2s;
.friend-item-static {
display: flex;
align-items: center;
padding: 12px;
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 8px;
margin-bottom: 8px;
&:hover {
border-color: #cfe2ff;
box-shadow: 0 6px 16px rgba(24, 144, 255, 0.15);
}
}
.friend-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin-right: 12px;
width: 48px;
height: 48px;
.adm-avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
}
.friend-info {
.friend-main {
flex: 1;
min-width: 0;
}
.friend-header {
display: flex;
align-items: center;
justify-content: space-between;
.friend-name-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 4px;
}
.friend-name {
font-size: 15px;
font-weight: 600;
color: #111;
flex-shrink: 0;
}
.friend-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.friend-tag {
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
background: #f5f5f5;
color: #666;
}
.friend-id-row {
font-size: 12px;
color: #999;
margin-bottom: 6px;
}
.friend-status-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.friend-status-chip {
background: #f0f7ff;
color: #1677ff;
font-size: 11px;
padding: 2px 8px;
border-radius: 8px;
}
.friend-value {
text-align: right;
.value-label {
font-size: 11px;
color: #999;
margin-bottom: 4px;
.friend-name {
font-size: 14px;
font-weight: 500;
color: #333;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.friend-remark {
color: #666;
margin-left: 4px;
}
}
.friend-arrow {
font-size: 12px;
color: #ccc;
}
}
.friend-wechat-id {
font-size: 12px;
color: #666;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.friend-tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
.friend-tag {
font-size: 10px;
padding: 2px 6px;
border-radius: 6px;
}
.value-amount {
font-size: 14px;
font-weight: 600;
color: #fa541c;
}
}
}
@@ -619,6 +845,56 @@
margin-top: 20px;
}
.popup-footer {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
}
.export-form {
margin-top: 20px;
.form-item {
margin-bottom: 20px;
label {
display: block;
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.type-selector {
display: flex;
gap: 8px;
flex-wrap: wrap;
.type-option {
padding: 8px 16px;
border: 1px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
color: #666;
cursor: pointer;
transition: all 0.2s;
background: white;
&:hover {
border-color: #1677ff;
color: #1677ff;
}
&.active {
background: #1677ff;
border-color: #1677ff;
color: white;
}
}
}
}
}
.restrictions-detail {
.restriction-detail-item {
display: flex;
@@ -769,6 +1045,416 @@
margin-right: 8px;
}
.health-content {
padding: 16px 0;
height: 500px;
overflow-y: auto;
.health-score-card {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.health-score-status {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
.status-tag {
background: #ffebeb;
color: #ff4d4f;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
.status-time {
font-size: 12px;
color: #999;
}
}
.health-score-display {
display: flex;
align-items: center;
.score-circle-wrapper {
width: 100px;
height: 100px;
margin-right: 24px;
position: relative;
.score-circle {
width: 100%;
height: 100%;
border-radius: 50%;
background: #fff;
border: 8px solid #ff4d4f;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.score-number {
font-size: 28px;
font-weight: 700;
color: #ff4d4f;
line-height: 1;
}
.score-label {
font-size: 12px;
color: #999;
margin-top: 4px;
}
}
}
.health-score-stats {
flex: 1;
.stats-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
.stats-label {
font-size: 14px;
color: #666;
}
.stats-value {
font-size: 14px;
color: #333;
font-weight: 500;
}
}
}
}
}
.health-section {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.health-section-title {
font-size: 16px;
font-weight: 600;
color: #ff8800;
margin-bottom: 12px;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: #ff8800;
border-radius: 2px;
}
}
.health-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child {
border-bottom: none;
}
.health-item-label {
font-size: 14px;
color: #333;
display: flex;
align-items: center;
.health-item-icon-warning {
width: 16px;
height: 16px;
border-radius: 50%;
background: #ffebeb;
margin-right: 8px;
position: relative;
&::before {
content: '!';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #ff4d4f;
font-size: 12px;
font-weight: bold;
}
}
.health-item-tag {
background: #fff7e6;
color: #fa8c16;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
margin-left: 8px;
}
}
.health-item-value-positive {
font-size: 14px;
font-weight: 600;
color: #52c41a;
}
.health-item-value-negative {
font-size: 14px;
font-weight: 600;
color: #ff4d4f;
}
.health-item-value-empty {
width: 20px;
}
}
.health-empty {
text-align: center;
color: #999;
font-size: 14px;
padding: 20px 0;
}
}
}
.moments-content {
padding: 16px 0;
height: 500px;
overflow-y: auto;
background: #f5f5f5;
.moments-action-bar {
display: flex;
justify-content: space-between;
padding: 0 16px 16px;
.action-button, .action-button-dark {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 70px;
height: 40px;
border-radius: 8px;
background: #1677ff;
.action-icon-text, .action-icon-image, .action-icon-video, .action-icon-export {
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
margin-bottom: 2px;
position: relative;
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 2px;
background: white;
}
}
.action-icon-image::after {
content: '';
position: absolute;
top: 6px;
left: 6px;
width: 8px;
height: 8px;
border-radius: 2px;
background: white;
}
.action-icon-video::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0;
height: 0;
border-style: solid;
border-width: 5px 0 5px 8px;
border-color: transparent transparent transparent white;
}
.action-text, .action-text-light {
font-size: 12px;
color: white;
}
}
.action-button-dark {
background: #333;
}
}
.moments-list {
padding: 0 16px;
.moment-item {
display: flex;
margin-bottom: 16px;
background: white;
border-radius: 8px;
padding: 16px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
.moment-date {
margin-right: 12px;
text-align: center;
.date-day {
font-size: 20px;
font-weight: 600;
color: #333;
line-height: 1;
}
.date-month {
font-size: 12px;
color: #999;
margin-top: 2px;
}
}
.moment-content {
flex: 1;
.moment-text {
font-size: 14px;
line-height: 1.5;
color: #333;
margin-bottom: 8px;
white-space: pre-wrap; // 保留换行和空格,确保文本完整显示
word-wrap: break-word; // 长单词自动换行
.moment-emoji {
display: inline;
font-size: 16px;
vertical-align: middle;
}
}
.moment-images {
margin-bottom: 8px;
.image-grid {
display: grid;
gap: 8px;
width: 100%;
// 1张图片宽度拉伸高度自适应
&.single {
grid-template-columns: 1fr;
img {
width: 100%;
height: auto;
object-fit: cover;
border-radius: 8px;
}
}
// 2张图片左右并列
&.double {
grid-template-columns: 1fr 1fr;
img {
width: 100%;
height: 120px;
object-fit: cover;
border-radius: 8px;
}
}
// 3张图片三张并列
&.triple {
grid-template-columns: 1fr 1fr 1fr;
img {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 8px;
}
}
// 4张图片2x2网格布局
&.quad {
grid-template-columns: repeat(2, 1fr);
img {
width: 100%;
height: 140px;
object-fit: cover;
border-radius: 8px;
}
}
// 5张及以上网格布局9宫格
&.grid {
grid-template-columns: repeat(3, 1fr);
img {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 8px;
}
.image-more {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
border-radius: 8px;
color: white;
font-size: 12px;
font-weight: 500;
height: 100px;
}
}
}
}
.moment-footer {
display: flex;
justify-content: flex-end;
.moment-time {
font-size: 12px;
color: #999;
}
}
}
}
}
}
.risk-content {
padding: 16px 0;
height: 500px;

View File

@@ -2,6 +2,39 @@
padding: 0 12px;
}
.filter-bar {
padding: 12px;
background: #fff;
border-bottom: 1px solid #f0f0f0;
.filter-buttons {
display: flex;
gap: 8px;
.filter-button {
flex: 1;
height: 32px;
border-radius: 6px;
border: 1px solid #d9d9d9;
background: #fff;
color: #666;
font-size: 14px;
transition: all 0.2s;
&:hover {
border-color: #1677ff;
color: #1677ff;
}
&.filter-button-active {
background: #1677ff;
border-color: #1677ff;
color: #fff;
}
}
}
}
.nav-title {
font-size: 18px;
font-weight: 600;

View File

@@ -33,11 +33,12 @@ const WechatAccounts: React.FC = () => {
const [totalAccounts, setTotalAccounts] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [isRefreshing, setIsRefreshing] = useState(false);
const [statusFilter, setStatusFilter] = useState<"all" | "online" | "offline">("all");
// 获取路由参数 wechatStatus
const wechatStatus = searchParams.get("wechatStatus");
const fetchAccounts = async (page = 1, keyword = "") => {
const fetchAccounts = async (page = 1, keyword = "", status?: "all" | "online" | "offline") => {
setIsLoading(true);
try {
const params: any = {
@@ -46,8 +47,12 @@ const WechatAccounts: React.FC = () => {
keyword,
};
// 如果有 wechatStatus 参数,添加到请求参数中
if (wechatStatus) {
// 优先使用传入的status参数否则使用路由参数,最后使用状态中的筛选
const filterStatus = status || wechatStatus || statusFilter;
if (filterStatus && filterStatus !== "all") {
params.wechatStatus = filterStatus === "online" ? "1" : "0";
} else if (wechatStatus) {
params.wechatStatus = wechatStatus;
}
@@ -60,7 +65,7 @@ const WechatAccounts: React.FC = () => {
setTotalAccounts(0);
}
} catch (e) {
Toast.show({ content: "获取微信号失败", position: "top" });
setAccounts([]);
setTotalAccounts(0);
} finally {
@@ -69,18 +74,24 @@ const WechatAccounts: React.FC = () => {
};
useEffect(() => {
fetchAccounts(currentPage, searchTerm);
fetchAccounts(currentPage, searchTerm, statusFilter);
// eslint-disable-next-line
}, [currentPage]);
}, [currentPage, statusFilter]);
const handleSearch = () => {
setCurrentPage(1);
fetchAccounts(1, searchTerm);
fetchAccounts(1, searchTerm, statusFilter);
};
const handleStatusFilterChange = (status: "all" | "online" | "offline") => {
setStatusFilter(status);
setCurrentPage(1);
fetchAccounts(1, searchTerm, status);
};
const handleRefresh = async () => {
setIsRefreshing(true);
await fetchAccounts(currentPage, searchTerm);
await fetchAccounts(currentPage, searchTerm, statusFilter);
setIsRefreshing(false);
Toast.show({ content: "刷新成功", position: "top" });
};
@@ -122,6 +133,31 @@ const WechatAccounts: React.FC = () => {
<ReloadOutlined />
</Button>
</div>
<div className={style["filter-bar"]}>
<div className={style["filter-buttons"]}>
<Button
size="small"
className={`${style["filter-button"]} ${statusFilter === "all" ? style["filter-button-active"] : ""}`}
onClick={() => handleStatusFilterChange("all")}
>
</Button>
<Button
size="small"
className={`${style["filter-button"]} ${statusFilter === "online" ? style["filter-button-active"] : ""}`}
onClick={() => handleStatusFilterChange("online")}
>
线
</Button>
<Button
size="small"
className={`${style["filter-button"]} ${statusFilter === "offline" ? style["filter-button-active"] : ""}`}
onClick={() => handleStatusFilterChange("offline")}
>
线
</Button>
</div>
</div>
</>
}
>

View File

@@ -369,13 +369,15 @@ const ScenarioList: React.FC = () => {
backFn={() => navigate("/scenarios")}
title={scenarioName || ""}
right={
<Button
size="small"
color="primary"
onClick={handleCreateNewPlan}
>
<PlusOutlined />
</Button>
scenarioId !== "10" ? (
<Button
size="small"
color="primary"
onClick={handleCreateNewPlan}
>
<PlusOutlined />
</Button>
) : null
}
/>
@@ -424,13 +426,15 @@ const ScenarioList: React.FC = () => {
<div className={style["empty-text"]}>
{searchTerm ? "没有找到匹配的计划" : "暂无计划"}
</div>
<Button
color="primary"
onClick={handleCreateNewPlan}
className={style["create-first-btn"]}
>
<PlusOutlined />
</Button>
{scenarioId !== "10" && (
<Button
color="primary"
onClick={handleCreateNewPlan}
className={style["create-first-btn"]}
>
<PlusOutlined />
</Button>
)}
</div>
) : (
<>

View File

@@ -242,7 +242,9 @@ const BasicSettings: React.FC<BasicSettingsProps> = ({
</div>
) : (
<div className={styles["basic-scene-grid"]}>
{sceneList.map(scene => {
{sceneList
.filter(scene => scene.id !== 10)
.map(scene => {
const selected = formData.scenario === scene.id;
return (
<button

View File

@@ -201,40 +201,38 @@ class UserController extends BaseController
* 修改密码
* @return \think\response\Json
*/
public function modifyPwd()
public function modifyPwd($data = [])
{
// 获取并验证参数
$params = $this->validateModifyPwdParams();
if (!is_array($params)) {
return $params;
if (empty($data)) {
return json_encode(['code' => 400,'msg' => '参数缺失']);
}
$authorization = trim($this->request->header('authorization', $this->authorization));
if (!isset($data['id']) || !isset($data['pwd'])) {
return json_encode(['code' => 401,'msg' => '参数缺失']);
}
$authorization = $this->authorization;
if (empty($authorization)) {
return errorJson('缺少授权信息');
return json_encode(['code' => 400,'msg' => '缺少授权信息']);
}
$headerData = ['client:' . self::CLIENT_TYPE];
$header = setHeader($headerData, $authorization, 'plain');
$headerData = ['client:system'];
$header = setHeader($headerData, $authorization, 'json');
$params = [
'id' => $data['id'],
'newPw' => $data['pwd'],
];
try {
$result = requestCurl($this->baseUrl . 'api/Account/self', $params, 'PUT', $header);
$result = requestCurl($this->baseUrl . 'api/Account/modifypw', $params, 'PUT', $header,'json');
$response = handleApiResponse($result);
if (empty($response)) {
// 获取当前用户信息
$currentUser = CompanyAccountModel::where('token', $authorization)->find();
if ($currentUser) {
recordUserLog($currentUser['id'], $currentUser['userName'], 'MODIFY_PASSWORD', '修改密码成功', [], 200, '修改成功');
}
return successJson(['message' => '修改成功']);
return json_encode(['code' => 200,'msg' => '修改成功']);
}
recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码失败', $params, 500, $response);
return errorJson($response);
return json_encode(['code' => 400,'msg' => $response]);
} catch (\Exception $e) {
recordUserLog(0, '', 'MODIFY_PASSWORD', '修改密码异常', $params, 500, $e->getMessage());
return errorJson('修改密码失败:' . $e->getMessage());
return json_encode(['code' => 400,'msg' => '修改密码失败:' . $e->getMessage()]);
}
}

View File

@@ -8,26 +8,4 @@ class WechatAccountModel extends Model
{
// 设置表名
protected $table = 's2_wechat_account';
// 定义字段类型
protected $type = [
'healthScore' => 'integer',
'baseScore' => 'integer',
'dynamicScore' => 'integer',
'isModifiedAlias' => 'integer',
'frequentCount' => 'integer',
'consecutiveNoFrequentDays' => 'integer',
'lastFrequentTime' => 'integer',
'lastNoFrequentTime' => 'integer',
'scoreUpdateTime' => 'integer',
];
// 允许批量赋值的字段
protected $field = [
'id', 'wechatId', 'alias', 'nickname', 'avatar', 'gender', 'region', 'signature',
'healthScore', 'baseScore', 'dynamicScore', 'isModifiedAlias',
'lastFrequentTime', 'frequentCount', 'lastNoFrequentTime',
'consecutiveNoFrequentDays', 'scoreUpdateTime',
'createTime', 'updateTime', 'status', 'isDeleted'
];
}

View File

@@ -14,6 +14,8 @@ Route::group('v1/', function () {
Route::get('list', 'app\chukebao\controller\WechatFriendController@getList'); // 获取好友列表
Route::get('detail', 'app\chukebao\controller\WechatFriendController@getDetail'); // 获取好友详情
Route::post('updateInfo', 'app\chukebao\controller\WechatFriendController@updateFriendInfo'); // 更新好友资料
// 添加好友任务记录相关接口
Route::get('addTaskList', 'app\chukebao\controller\WechatFriendController@getAddTaskList'); // 获取添加好友任务记录列表包含添加者信息、状态、时间等支持状态筛选无需传好友ID
});
//群相关
Route::group('wechatChatroom/', function () {

View File

@@ -2,8 +2,10 @@
namespace app\chukebao\controller;
use app\api\model\WechatChatroomModel;
use library\ResponseHelper;
use app\api\model\WechatFriendModel;
use app\api\model\WechatMessageModel;
use app\api\controller\MessageController;
@@ -33,6 +35,7 @@ class DataProcessing extends BaseController
'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId}
'CmdChatroomOperate', //修改群信息 {chatroomName群名、announce公告、extra公告、wechatAccountId、wechatChatroomId}
'CmdNewMessage', //接收消息
'CmdSendMessageResult', //更新消息状态
];
if (empty($type) || empty($wechatAccountId)) {
@@ -75,15 +78,31 @@ class DataProcessing extends BaseController
if(empty($toAccountId)){
return ResponseHelper::error('参数缺失');
}
$friend = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
if(empty($friend)){
return ResponseHelper::error('好友不存在');
if(empty($wechatFriendId) && empty($wechatChatroomId)){
return ResponseHelper::error('参数缺失');
}
$friend->accountId = $toAccountId;
$friend->updateTime = time();
$friend->save();
$msg = '好友转移成功';
if (!empty($wechatFriendId)){
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
$msg = '好友转移成功';
if(empty($data)){
return ResponseHelper::error('好友不存在');
}
}
if (!empty($wechatChatroomId)){
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
$msg = '群聊转移成功';
if(empty($data)){
return ResponseHelper::error('群聊不存在');
}
}
$data->accountId = $toAccountId;
$data->updateTime = time();
$data->save();
break;
case 'CmdNewMessage':
if(empty($friendMessage) && empty($chatroomMessage)){
@@ -105,8 +124,46 @@ class DataProcessing extends BaseController
$msg = '消息记录成功';
}else{
$msg = '消息记录失败';
$codee = 400;
$codee = 200;
}
break;
case 'CmdSendMessageResult':
$friendMessageId = $this->request->param('friendMessageId', 0);
$chatroomMessageId = $this->request->param('chatroomMessageId', 0);
$sendStatus = $this->request->param('sendStatus', null);
$wechatTime = $this->request->param('wechatTime', 0);
if ($sendStatus === null) {
return ResponseHelper::error('sendStatus不能为空');
}
if (empty($friendMessageId) && empty($chatroomMessageId)) {
return ResponseHelper::error('friendMessageId或chatroomMessageId至少提供一个');
}
$messageId = $friendMessageId ?: $chatroomMessageId;
$update = [
'sendStatus' => (int)$sendStatus,
];
if (!empty($wechatTime)) {
$update['wechatTime'] = strlen((string)$wechatTime) > 10
? intval($wechatTime / 1000)
: (int)$wechatTime;
}
$affected = WechatMessageModel::where('id', $messageId)->update($update);
if ($affected === false) {
return ResponseHelper::success('','更新消息状态失败');
}
if ($affected === 0) {
return ResponseHelper::success('','消息不存在');
}
$msg = '更新消息状态成功';
break;
}
return ResponseHelper::success('',$msg,$codee);
}

View File

@@ -23,7 +23,7 @@ class WechatFriendController extends BaseController
$total = $query->count();
$list = $query->page($page, $limit)->select();
// 提取所有好友ID
// 提取所有好友ID
$friendIds = array_column($list, 'id');
$aiTypeData = [];
@@ -67,7 +67,7 @@ class WechatFriendController extends BaseController
$friend = Db::table('s2_wechat_friend')
->where(['id' => $friendId, 'isDeleted' => 0])
->find();
if (empty($friend)) {
return ResponseHelper::error('好友不存在');
}
@@ -78,11 +78,11 @@ class WechatFriendController extends BaseController
$friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : '';
$friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : '';
$friend['passTime'] = !empty($friend['passTime']) ? date('Y-m-d H:i:s', $friend['passTime']) : '';
// 获取AI类型设置
$aiTypeSetting = FriendSettings::where('friendId', $friendId)->find();
$friend['aiType'] = $aiTypeSetting ? $aiTypeSetting['type'] : 0;
return ResponseHelper::success(['detail' => $friend]);
}
@@ -166,4 +166,112 @@ class WechatFriendController extends BaseController
return ResponseHelper::success(['id' => $friendId]);
}
/**
* 获取添加好友任务记录列表(全新功能)
* 返回当前账号的所有添加好友任务记录,无论是否通过都展示
* 包含:添加者头像、昵称、微信号、添加状态、添加时间、通过时间等信息
* @return \think\response\Json
*/
public function getAddTaskList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$status = $this->request->param('status', ''); // 可选:筛选状态 0执行中1执行成功2执行失败
$accountId = $this->getUserInfo('s2_accountId');
if (empty($accountId)) {
return ResponseHelper::error('请先登录');
}
// 直接使用operatorAccountId查询添加好友任务记录
$query = Db::table('s2_friend_task')
->where('operatorAccountId', $accountId)
->order('createTime desc');
// 如果指定了状态筛选
if ($status !== '' && $status !== null) {
$query->where('status', $status);
}
$total = $query->count();
$tasks = $query->page($page, $limit)->select();
// 处理任务数据
$list = [];
foreach ($tasks as $task) {
// 提取所有任务的phone、wechatId用于查询好友信息获取通过时间
$friendInfo = Db::table('s2_wechat_friend')
->where(['isDeleted' => 0, 'ownerWechatId' => $task['wechatId']])
->where(function ($query) use ($task) {
$query->whereLike('phone', '%'.$task['phone'].'%')->whereOr('alias', $task['phone'])->whereOr('wechatId', $task['phone']);
})->field('phone,wechatId,alias,passTime,nickname')->find();
$item = [
'taskId' => $task['id'] ?? 0,
'phone' => $task['phone'] ?? '',
'wechatId' => $task['wechatId'] ?? '',
'alias' => $task['alias'] ?? '',
// 添加者信息
'adder' => [
'avatar' => $task['wechatAvatar'] ?? '', // 添加者头像
'nickname' => $task['wechatNickname'] ?? '', // 添加者昵称
'username' => $task['accountUsername'] ?? '', // 添加者微信号
'accountNickname' => $task['accountNickname'] ?? '', // 账号昵称
'accountRealName' => $task['accountRealName'] ?? '', // 账号真实姓名
],
// 添加状态
'status' => [
'code' => $task['status'] ?? 0, // 状态码0执行中1执行成功2执行失败
'text' => $this->getTaskStatusText($task['status'] ?? 0), // 状态文本
'extra' => ''
],
// 时间信息
'time' => [
'addTime' => !empty($task['createTime']) ? date('Y-m-d H:i:s', $task['createTime']) : '', // 添加时间
'addTimeStamp' => $task['createTime'] ?? 0, // 添加时间戳
'updateTime' => !empty($task['updateTime']) ? date('Y-m-d H:i:s', $task['updateTime']) : '', // 更新时间
'updateTimeStamp' => $task['updateTime'] ?? 0, // 更新时间戳
'passTime' => !empty($friendInfo['passTime']) ? date('Y-m-d H:i:s', $friendInfo['passTime']) : '', // 通过时间
'passTimeStamp' => $friendInfo['passTime'] ?? 0, // 通过时间戳
],
// 好友信息(如果已通过)
'friend' => [
'nickname' => $friendInfo['nickname'] ?? '', // 好友昵称
'isPassed' => !empty($friendInfo['passTime']), // 是否已通过
],
// 其他信息
'other' => [
'msgContent' => $task['msgContent'] ?? '', // 验证消息
'remark' => $task['remark'] ?? '', // 备注
'from' => $task['from'] ?? '', // 来源
'labels' => !empty($task['labels']) ? explode(',', $task['labels']) : [], // 标签
]
];
$list[] = $item;
}
return ResponseHelper::success(['list' => $list, 'total' => $total]);
}
/**
* 获取任务状态文本
* @param int $status 状态码
* @return string 状态文本
*/
private function getTaskStatusText($status)
{
$statusMap = [
0 => '执行中',
1 => '执行成功',
2 => '执行失败',
];
return isset($statusMap[$status]) ? $statusMap[$status] : '未知状态';
}
}

View File

@@ -82,7 +82,7 @@ class TaskServer extends Server
if ($current_worker_id == 1) {
// 每60秒检查一次自动问候规则
Timer::add(60, function () use ($adapter) {
$adapter->handleAutoGreetings();
//$adapter->handleAutoGreetings();
});
}

View File

@@ -0,0 +1,333 @@
<?php
namespace app\common\controller;
use PHPExcel;
use PHPExcel_IOFactory;
use PHPExcel_Worksheet_Drawing;
use think\Controller;
use think\Exception;
/**
* 通用导出控制器,提供 Excel 导出与图片插入能力
*/
class ExportController extends Controller
{
/**
* @var array<string> 需要在请求结束时清理的临时文件
*/
protected static $tempFiles = [];
/**
* 导出 Excel支持指定列插入图片
*
* @param string $fileName 输出文件名(可不带扩展名)
* @param array $headers 列定义,例如 ['name' => '姓名', 'phone' => '电话']
* @param array $rows 数据行,需与 $headers 的 key 对应
* @param array $imageColumns 需要渲染为图片的列 key 列表
* @param string $sheetName 工作表名称
* @param array $options 额外选项:
* - imageWidth(图片宽度默认100)
* - imageHeight(图片高度默认100)
* - imageColumnWidth(图片列宽默认15)
* - titleRow(标题行内容,支持多行文本数组)
*
* @throws Exception
*/
public static function exportExcelWithImages(
$fileName,
array $headers,
array $rows,
array $imageColumns = [],
$sheetName = 'Sheet1',
array $options = []
) {
if (empty($headers)) {
throw new Exception('导出列定义不能为空');
}
if (empty($rows)) {
throw new Exception('导出数据不能为空');
}
// 抑制 PHPExcel 库中已废弃的大括号语法警告PHP 7.4+
$oldErrorReporting = error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
// 默认选项
$imageWidth = isset($options['imageWidth']) ? (int)$options['imageWidth'] : 100;
$imageHeight = isset($options['imageHeight']) ? (int)$options['imageHeight'] : 100;
$imageColumnWidth = isset($options['imageColumnWidth']) ? (float)$options['imageColumnWidth'] : 15;
$rowHeight = isset($options['rowHeight']) ? (int)$options['rowHeight'] : ($imageHeight + 10);
$excel = new PHPExcel();
$sheet = $excel->getActiveSheet();
$sheet->setTitle($sheetName);
$columnKeys = array_keys($headers);
$totalColumns = count($columnKeys);
$lastColumnLetter = self::columnLetter($totalColumns - 1);
// 定义特定列的固定宽度(如果未指定则使用默认值)
$columnWidths = isset($options['columnWidths']) ? $options['columnWidths'] : [];
// 检查是否有标题行
$titleRow = isset($options['titleRow']) ? $options['titleRow'] : null;
$dataStartRow = 1; // 数据开始行(表头行)
// 如果有标题行,先写入标题行(支持数组或字符串)
if (!empty($titleRow)) {
$dataStartRow = 2; // 数据从第2行开始第1行是标题第2行是表头
// 合并标题行单元格(从第一列到最后一列)
$titleRange = 'A1:' . $lastColumnLetter . '1';
$sheet->mergeCells($titleRange);
// 构建标题内容(支持多行数组或字符串)
$titleContent = '';
if (is_array($titleRow)) {
$titleContent = implode("\n", $titleRow);
} else {
$titleContent = (string)$titleRow;
}
// 写入标题
$sheet->setCellValue('A1', $titleContent);
// 设置标题行样式
$sheet->getStyle('A1')->applyFromArray([
'font' => ['bold' => true, 'size' => 16],
'alignment' => [
'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER,
'wrap' => true
],
'fill' => [
'type' => \PHPExcel_Style_Fill::FILL_SOLID,
'color' => ['rgb' => 'FFF8DC'] // 浅黄色背景
],
'borders' => [
'allborders' => [
'style' => \PHPExcel_Style_Border::BORDER_THIN,
'color' => ['rgb' => '000000']
]
]
]);
$sheet->getRowDimension(1)->setRowHeight(80); // 标题行高度
}
// 写入表头并设置列宽
$headerRow = $dataStartRow;
foreach ($columnKeys as $index => $key) {
$columnLetter = self::columnLetter($index);
$sheet->setCellValue($columnLetter . $headerRow, $headers[$key]);
// 如果是图片列,设置固定列宽
if (in_array($key, $imageColumns, true)) {
$sheet->getColumnDimension($columnLetter)->setWidth($imageColumnWidth);
} elseif (isset($columnWidths[$key])) {
// 如果指定了该列的宽度,使用指定宽度
$sheet->getColumnDimension($columnLetter)->setWidth($columnWidths[$key]);
} else {
// 否则自动调整
$sheet->getColumnDimension($columnLetter)->setAutoSize(true);
}
}
// 设置表头样式
$headerRange = 'A' . $headerRow . ':' . $lastColumnLetter . $headerRow;
$sheet->getStyle($headerRange)->applyFromArray([
'font' => ['bold' => true, 'size' => 11],
'alignment' => [
'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER,
'wrap' => true
],
'fill' => [
'type' => \PHPExcel_Style_Fill::FILL_SOLID,
'color' => ['rgb' => 'FFF8DC']
],
'borders' => [
'allborders' => [
'style' => \PHPExcel_Style_Border::BORDER_THIN,
'color' => ['rgb' => '000000']
]
]
]);
$sheet->getRowDimension($headerRow)->setRowHeight(30); // 增加表头行高以确保文本完整显示
// 写入数据与图片
$dataRowStart = $dataStartRow + 1; // 数据从表头行下一行开始
foreach ($rows as $rowIndex => $rowData) {
$excelRow = $dataRowStart + $rowIndex; // 数据行
$maxRowHeight = $rowHeight; // 记录当前行的最大高度
foreach ($columnKeys as $colIndex => $key) {
$columnLetter = self::columnLetter($colIndex);
$cell = $columnLetter . $excelRow;
$value = isset($rowData[$key]) ? $rowData[$key] : '';
if (in_array($key, $imageColumns, true) && !empty($value)) {
$imagePath = self::resolveImagePath($value);
if ($imagePath) {
// 获取图片实际尺寸并等比例缩放
$imageSize = @getimagesize($imagePath);
if ($imageSize) {
$originalWidth = $imageSize[0];
$originalHeight = $imageSize[1];
// 计算等比例缩放后的尺寸
$ratio = min($imageWidth / $originalWidth, $imageHeight / $originalHeight);
$scaledWidth = $originalWidth * $ratio;
$scaledHeight = $originalHeight * $ratio;
// 确保不超过最大尺寸
if ($scaledWidth > $imageWidth) {
$scaledWidth = $imageWidth;
$scaledHeight = $originalHeight * ($imageWidth / $originalWidth);
}
if ($scaledHeight > $imageHeight) {
$scaledHeight = $imageHeight;
$scaledWidth = $originalWidth * ($imageHeight / $originalHeight);
}
$drawing = new PHPExcel_Worksheet_Drawing();
$drawing->setPath($imagePath);
$drawing->setCoordinates($cell);
// 居中显示图片Excel列宽1单位≈7像素行高1单位≈0.75像素)
$cellWidthPx = $imageColumnWidth * 7;
$cellHeightPx = $maxRowHeight * 0.75;
$offsetX = max(2, ($cellWidthPx - $scaledWidth) / 2);
$offsetY = max(2, ($cellHeightPx - $scaledHeight) / 2);
$drawing->setOffsetX((int)$offsetX);
$drawing->setOffsetY((int)$offsetY);
$drawing->setWidth((int)$scaledWidth);
$drawing->setHeight((int)$scaledHeight);
$drawing->setWorksheet($sheet);
// 更新行高以适应图片(留出一些边距)
$neededHeight = (int)($scaledHeight / 0.75) + 10;
if ($neededHeight > $maxRowHeight) {
$maxRowHeight = $neededHeight;
}
} else {
// 如果无法获取图片尺寸,使用默认尺寸
$drawing = new PHPExcel_Worksheet_Drawing();
$drawing->setPath($imagePath);
$drawing->setCoordinates($cell);
$drawing->setOffsetX(5);
$drawing->setOffsetY(5);
$drawing->setWidth($imageWidth);
$drawing->setHeight($imageHeight);
$drawing->setWorksheet($sheet);
}
} else {
$sheet->setCellValue($cell, '');
}
} else {
$sheet->setCellValue($cell, $value);
// 设置文本对齐和换行
$style = $sheet->getStyle($cell);
$style->getAlignment()->setVertical(\PHPExcel_Style_Alignment::VERTICAL_CENTER);
$style->getAlignment()->setWrapText(true);
// 根据列类型设置水平对齐
if (in_array($key, ['date', 'postTime'])) {
$style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_CENTER);
} else {
$style->getAlignment()->setHorizontal(\PHPExcel_Style_Alignment::HORIZONTAL_LEFT);
}
}
}
// 设置行高
$sheet->getRowDimension($excelRow)->setRowHeight($maxRowHeight);
}
$safeName = preg_replace('/[^\w\-]/', '_', $fileName ?: 'export_' . date('Ymd_His'));
if (stripos($safeName, '.xlsx') === false) {
$safeName .= '.xlsx';
}
if (ob_get_length()) {
ob_end_clean();
}
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Cache-Control: max-age=0');
header('Content-Disposition: attachment;filename="' . $safeName . '"');
try {
$writer = PHPExcel_IOFactory::createWriter($excel, 'Excel2007');
$writer->save('php://output');
} catch (\Exception $e) {
// 恢复错误报告级别
error_reporting($oldErrorReporting);
throw $e;
}
// 恢复错误报告级别
error_reporting($oldErrorReporting);
self::cleanupTempFiles();
exit;
}
/**
* 根据列序号生成 Excel 列字母
*
* @param int $index
* @return string
*/
protected static function columnLetter($index)
{
$letters = '';
do {
$letters = chr($index % 26 + 65) . $letters;
$index = intval($index / 26) - 1;
} while ($index >= 0);
return $letters;
}
/**
* 将远程或本地图片路径转换为可用的本地文件路径
*
* @param string $path
* @return string|null
*/
protected static function resolveImagePath($path)
{
if (empty($path)) {
return null;
}
if (preg_match('/^https?:\/\//i', $path)) {
$tempFile = tempnam(sys_get_temp_dir(), 'export_img_');
$stream = @file_get_contents($path);
if ($stream === false) {
return null;
}
file_put_contents($tempFile, $stream);
self::$tempFiles[] = $tempFile;
return $tempFile;
}
if (file_exists($path)) {
return $path;
}
return null;
}
/**
* 清理所有临时文件
*/
protected static function cleanupTempFiles()
{
foreach (self::$tempFiles as $file) {
if (file_exists($file)) {
@unlink($file);
}
}
self::$tempFiles = [];
}
}

View File

@@ -36,13 +36,14 @@ Route::group('v1/', function () {
Route::get(':id/summary', 'app\cunkebao\controller\wechat\GetWechatOnDeviceSummarizeV1Controller@index');
Route::get(':id/friends', 'app\cunkebao\controller\wechat\GetWechatOnDeviceFriendsV1Controller@index');
Route::get('getWechatInfo', 'app\cunkebao\controller\wechat\GetWechatController@getWechatInfo');
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
Route::get('overview', 'app\cunkebao\controller\wechat\GetWechatOverviewV1Controller@index'); // 获取微信账号概览数据
Route::get('moments', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@index'); // 获取微信朋友圈
Route::get('moments/export', 'app\cunkebao\controller\wechat\GetWechatMomentsV1Controller@export'); // 导出微信朋友圈
Route::get('count', 'app\cunkebao\controller\DeviceWechat@count');
Route::get('device-count', 'app\cunkebao\controller\DeviceWechat@deviceCount'); // 获取有登录微信的设备数量
Route::put('refresh', 'app\cunkebao\controller\DeviceWechat@refresh'); // 刷新设备微信状态
Route::post('transfer-friends', 'app\cunkebao\controller\wechat\PostTransferFriends@index'); // 微信好友转移
Route::get(':wechatId', 'app\cunkebao\controller\wechat\GetWechatProfileV1Controller@index');
});
// 获客场景相关

View File

@@ -3,6 +3,7 @@
namespace app\cunkebao\controller;
use app\api\controller\AccountController;
use app\api\controller\UserController;
use app\common\service\ClassTableService;
use library\ResponseHelper;
use think\Controller;
@@ -148,6 +149,12 @@ class BaseController extends Controller
$res = Db::name('users')->where(['id' => $userId, 'companyId' => $companyId])->update($data);
if (!empty($res)) {
if ($user['typeId'] == 1 && !empty($user['s2_accountId'])) {
$UserController = new UserController();
$UserController->modifyPwd(['id' => $user['s2_accountId'],'pwd' => $passWord]);
}
return ResponseHelper::success('密码修改成功');
} else {
return ResponseHelper::error('密码修改失败');

View File

@@ -1061,6 +1061,7 @@ class ContentLibraryController extends Controller
$where = [
['isDel', '=', 0], // 未删除
['status', '=', 1], // 已开启
['id', '=', 99], // 已开启
];
// 查询符合条件的内容库
@@ -1225,7 +1226,7 @@ class ContentLibraryController extends Controller
foreach ($friends as $friend) {
$processedFriends++;
// 如果配置了API并且需要主动获取朋友圈
if ($needFetch) {
try {
@@ -1264,9 +1265,9 @@ class ContentLibraryController extends Controller
}
// 如果指定了采集类型,进行过滤
if (!empty($catchTypes)) {
/*if (!empty($catchTypes)) {
$query->whereIn('type', $catchTypes);
}
}*/
// 获取最近20条朋友圈
$moments = $query->page(1, 20)->select();
@@ -1289,7 +1290,7 @@ class ContentLibraryController extends Controller
continue;
}
// 如果启用了AI处理
/* // 如果启用了AI处理
if (!empty($library['aiEnabled']) && !empty($content)) {
try {
$contentAi = $this->aiRewrite($library, $content);
@@ -1300,7 +1301,7 @@ class ContentLibraryController extends Controller
\think\facade\Log::error('AI处理失败: ' . $e->getMessage() . ' [朋友圈ID: ' . ($moment['id'] ?? 'unknown') . ']');
$moment['contentAi'] = '';
}
}
}*/
// 保存到内容库的content_item表
if ($this->saveMomentToContentItem($moment, $library['id'], $friend, $nickname)) {

View File

@@ -0,0 +1,363 @@
<?php
namespace app\cunkebao\controller\wechat;
use app\common\controller\ExportController;
use app\common\model\Device as DeviceModel;
use app\common\model\DeviceUser as DeviceUserModel;
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
use app\common\model\User as UserModel;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
use think\Db;
/**
* 查看微信朋友圈列表(仅限当前操盘手可访问的微信)
*/
class GetWechatMomentsV1Controller extends BaseController
{
/**
* 主操盘手获取项目下所有设备ID
*
* @return array
*/
protected function getCompanyDevicesId(): array
{
return DeviceModel::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');
}
/**
* 获取当前用户可访问的设备ID
*
* @return array
*/
protected function getDevicesId(): array
{
return ($this->getUserInfo('isAdmin') == UserModel::ADMIN_STP)
? $this->getCompanyDevicesId()
: $this->getUserDevicesId();
}
/**
* 获取用户可访问的微信ID集合
*
* @return array
* @throws \Exception
*/
protected function getAccessibleWechatIds(): array
{
$deviceIds = $this->getDevicesId();
if (empty($deviceIds)) {
throw new \Exception('暂无可用设备', 200);
}
return DeviceWechatLoginModel::distinct(true)
->where('companyId', $this->getUserInfo('companyId'))
->whereIn('deviceId', $deviceIds)
->column('wechatId');
}
/**
* 查看朋友圈列表
*
* @return \think\response\Json
*/
public function index()
{
try {
$wechatId = $this->request->param('wechatId/s', '');
if (empty($wechatId)) {
return ResponseHelper::error('wechatId不能为空');
}
// 权限校验:只能查看当前账号可访问的微信
$accessibleWechatIds = $this->getAccessibleWechatIds();
if (!in_array($wechatId, $accessibleWechatIds, true)) {
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
}
// 获取对应的微信账号ID
$accountId = Db::table('s2_wechat_account')
->where('wechatId', $wechatId)
->value('id');
if (empty($accountId)) {
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
}
$query = Db::table('s2_wechat_moments')
->where('wechatAccountId', $accountId);
// 关键词搜索
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
$query->whereLike('content', '%' . $keyword . '%');
}
// 类型筛选
$type = $this->request->param('type', '');
if ($type !== '' && $type !== null) {
$query->where('type', (int)$type);
}
// 时间筛选
$startTime = $this->request->param('startTime', '');
$endTime = $this->request->param('endTime', '');
if ($startTime || $endTime) {
$start = $startTime ? strtotime($startTime) : 0;
$end = $endTime ? strtotime($endTime) : time();
if ($start && $end && $end < $start) {
return ResponseHelper::error('结束时间不能早于开始时间');
}
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
}
$page = (int)$this->request->param('page', 1);
$limit = (int)$this->request->param('limit', 10);
$paginator = $query->order('createTime', 'desc')
->paginate($limit, false, ['page' => $page]);
$list = array_map(function ($item) {
return $this->formatMomentRow($item);
}, $paginator->items());
return ResponseHelper::success([
'list' => $list,
'total' => $paginator->total(),
'page' => $page,
'limit' => $limit,
]);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
}
}
/**
* 导出朋友圈数据到Excel
*
* @return void
*/
public function export()
{
try {
$wechatId = $this->request->param('wechatId/s', '');
if (empty($wechatId)) {
return ResponseHelper::error('wechatId不能为空');
}
// 权限校验:只能查看当前账号可访问的微信
$accessibleWechatIds = $this->getAccessibleWechatIds();
if (!in_array($wechatId, $accessibleWechatIds, true)) {
return ResponseHelper::error('无权查看该微信的朋友圈', 403);
}
// 获取对应的微信账号ID
$accountId = Db::table('s2_wechat_account')
->where('wechatId', $wechatId)
->value('id');
if (empty($accountId)) {
return ResponseHelper::error('微信账号不存在或尚未同步', 404);
}
$query = Db::table('s2_wechat_moments')
->where('wechatAccountId', $accountId);
// 关键词搜索
if ($keyword = trim((string)$this->request->param('keyword', ''))) {
$query->whereLike('content', '%' . $keyword . '%');
}
// 类型筛选
$type = $this->request->param('type', '');
if ($type !== '' && $type !== null) {
$query->where('type', (int)$type);
}
// 时间筛选
$startTime = $this->request->param('startTime', '');
$endTime = $this->request->param('endTime', '');
if ($startTime || $endTime) {
$start = $startTime ? strtotime($startTime) : 0;
$end = $endTime ? strtotime($endTime) : time();
if ($start && $end && $end < $start) {
return ResponseHelper::error('结束时间不能早于开始时间');
}
$query->whereBetween('createTime', [$start ?: 0, $end ?: time()]);
}
// 获取所有数据(不分页)
$moments = $query->order('createTime', 'desc')->select();
if (empty($moments)) {
return ResponseHelper::error('暂无数据可导出');
}
// 定义表头
$headers = [
'date' => '日期',
'postTime' => '投放时间',
'functionCategory' => '作用分类',
'content' => '朋友圈文案',
'selfReply' => '自回评内容',
'displayForm' => '朋友圈展示形式',
'image1' => '配图1',
'image2' => '配图2',
'image3' => '配图3',
'image4' => '配图4',
'image5' => '配图5',
'image6' => '配图6',
'image7' => '配图7',
'image8' => '配图8',
'image9' => '配图9',
];
// 格式化数据
$rows = [];
foreach ($moments as $moment) {
$resUrls = $this->decodeJson($moment['resUrls'] ?? null);
$imageUrls = is_array($resUrls) ? $resUrls : [];
// 格式化日期和时间
$createTime = !empty($moment['createTime'])
? (is_numeric($moment['createTime']) ? $moment['createTime'] : strtotime($moment['createTime']))
: 0;
$date = $createTime ? date('Y年m月d日', $createTime) : '';
$postTime = $createTime ? date('H:i', $createTime) : '';
// 判断展示形式
$displayForm = '';
if (!empty($moment['content']) && !empty($imageUrls)) {
$displayForm = '文字+图片';
} elseif (!empty($moment['content'])) {
$displayForm = '文字';
} elseif (!empty($imageUrls)) {
$displayForm = '图片';
}
$row = [
'date' => $date,
'postTime' => $postTime,
'functionCategory' => '', // 暂时放空
'content' => $moment['content'] ?? '',
'selfReply' => '', // 暂时放空
'displayForm' => $displayForm,
];
// 分配图片到配图1-9列
for ($i = 1; $i <= 9; $i++) {
$imageKey = 'image' . $i;
$row[$imageKey] = isset($imageUrls[$i - 1]) ? $imageUrls[$i - 1] : '';
}
$rows[] = $row;
}
// 定义图片列配图1-9
$imageColumns = ['image1', 'image2', 'image3', 'image4', 'image5', 'image6', 'image7', 'image8', 'image9'];
// 生成文件名
$fileName = '朋友圈投放_' . date('Ymd_His');
// 调用导出方法,优化图片显示效果
ExportController::exportExcelWithImages(
$fileName,
$headers,
$rows,
$imageColumns,
'朋友圈投放',
[
'imageWidth' => 120, // 图片宽度(像素)
'imageHeight' => 120, // 图片高度(像素)
'imageColumnWidth' => 18, // 图片列宽Excel单位
'rowHeight' => 130, // 行高(像素)
'columnWidths' => [ // 特定列的固定宽度
'date' => 15, // 日期列宽
'postTime' => 12, // 投放时间列宽
'functionCategory' => 15, // 作用分类列宽
'content' => 40, // 朋友圈文案列宽(自动调整可能不够)
'selfReply' => 30, // 自回评内容列宽
'displayForm' => 18, // 朋友圈展示形式列宽
],
'titleRow' => [ // 标题行内容(第一行)
'朋友圈投放',
'我能提供什么价值? (40%) 有谁正在和我合作 (20%) 如何和我合作? (20%) 你找我合作需要付多少钱? (20%)'
]
]
);
} catch (\Exception $e) {
return ResponseHelper::error('导出失败:' . $e->getMessage(), 500);
}
}
/**
* 格式化朋友圈数据
*
* @param array $row
* @return array
*/
protected function formatMomentRow(array $row): array
{
$formatTime = function ($timestamp) {
if (empty($timestamp)) {
return '';
}
return is_numeric($timestamp)
? date('Y-m-d H:i:s', $timestamp)
: date('Y-m-d H:i:s', strtotime($timestamp));
};
return [
'id' => (int)$row['id'],
'snsId' => $row['snsId'] ?? '',
'type' => (int)($row['type'] ?? 0),
'content' => $row['content'] ?? '',
'commentList' => $this->decodeJson($row['commentList'] ?? null),
'likeList' => $this->decodeJson($row['likeList'] ?? null),
'resUrls' => $this->decodeJson($row['resUrls'] ?? null),
'createTime' => $formatTime($row['createTime'] ?? null),
'momentEntity' => [
'lat' => $row['lat'] ?? 0,
'lng' => $row['lng'] ?? 0,
'location' => $row['location'] ?? '',
'picSize' => $row['picSize'] ?? 0,
'userName' => $row['userName'] ?? '',
],
];
}
/**
* JSON字段解析
*
* @param mixed $value
* @return array
*/
protected function decodeJson($value): array
{
if (empty($value)) {
return [];
}
if (is_array($value)) {
return $value;
}
$decoded = json_decode($value, true);
return $decoded ?: [];
}
}

View File

@@ -0,0 +1,411 @@
<?php
namespace app\cunkebao\controller\wechat;
use app\common\service\WechatAccountHealthScoreService;
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
use think\Db;
/**
* 微信账号概览控制器
* 提供账号概览页面的所有数据接口
*/
class GetWechatOverviewV1Controller extends BaseController
{
/**
* 获取微信账号概览数据
*
* @return \think\response\Json
*/
public function index()
{
try {
$wechatId = $this->request->param('wechatId', '');
if (empty($wechatId)) {
return ResponseHelper::error('微信ID不能为空');
}
$companyId = $this->getUserInfo('companyId');
// 获取微信账号IDaccountId
$account = Db::table('s2_wechat_account')
->where('wechatId', $wechatId)
->find();
if (empty($account)) {
return ResponseHelper::error('微信账号不存在');
}
$accountId = $account['id'];
// 1. 健康分评估
$healthScoreData = $this->getHealthScoreAssessment($accountId, $wechatId);
// 2. 账号价值(模拟数据)
$accountValue = $this->getAccountValue($accountId);
// 3. 今日价值变化(模拟数据)
$todayValueChange = $this->getTodayValueChange($accountId);
// 4. 好友总数
$totalFriends = $this->getTotalFriends($wechatId, $companyId);
// 5. 今日新增好友
$todayNewFriends = $this->getTodayNewFriends($wechatId);
// 6. 高价群聊
$highValueChatrooms = $this->getHighValueChatrooms($wechatId, $companyId);
// 7. 今日新增群聊
$todayNewChatrooms = $this->getTodayNewChatrooms($wechatId, $companyId);
$result = [
'healthScoreAssessment' => $healthScoreData,
'accountValue' => $accountValue,
'todayValueChange' => $todayValueChange,
'totalFriends' => $totalFriends,
'todayNewFriends' => $todayNewFriends,
'highValueChatrooms' => $highValueChatrooms,
'todayNewChatrooms' => $todayNewChatrooms,
];
return ResponseHelper::success($result);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode() ?: 500);
}
}
/**
* 获取健康分评估数据
*
* @param int $accountId 账号ID
* @param string $wechatId 微信ID
* @return array
*/
protected function getHealthScoreAssessment($accountId, $wechatId)
{
// 获取健康分信息
$healthScoreService = new WechatAccountHealthScoreService();
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
$healthScore = $healthScoreInfo['healthScore'] ?? 0;
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0;
// 获取今日已加好友数
$todayAdded = $this->getTodayAddedCount($wechatId);
// 获取最后添加时间
$lastAddTime = $this->getLastAddTime($wechatId);
// 判断状态标签
$statusTag = $todayAdded > 0 ? '已添加加人' : '';
// 获取基础构成
$baseComposition = $this->getBaseComposition($healthScoreInfo);
// 获取动态记录
$dynamicRecords = $this->getDynamicRecords($healthScoreInfo);
return [
'score' => $healthScore,
'dailyLimit' => $maxAddFriendPerDay,
'todayAdded' => $todayAdded,
'lastAddTime' => $lastAddTime,
'statusTag' => $statusTag,
'baseComposition' => $baseComposition,
'dynamicRecords' => $dynamicRecords,
];
}
/**
* 获取基础构成数据
*
* @param array $healthScoreInfo 健康分信息
* @return array
*/
protected function getBaseComposition($healthScoreInfo)
{
$baseScore = $healthScoreInfo['baseScore'] ?? 0;
$baseInfoScore = $healthScoreInfo['baseInfoScore'] ?? 0;
$friendCountScore = $healthScoreInfo['friendCountScore'] ?? 0;
$friendCount = $healthScoreInfo['friendCount'] ?? 0;
// 账号基础分默认60分
$accountBaseScore = 60;
// 已修改微信号如果baseInfoScore > 0说明已修改
$isModifiedAlias = $baseInfoScore > 0;
$composition = [
[
'name' => '账号基础分',
'score' => $accountBaseScore,
'formatted' => '+' . $accountBaseScore,
]
];
// 如果已修改微信号,添加基础信息分
if ($isModifiedAlias) {
$composition[] = [
'name' => '已修改微信号',
'score' => $baseInfoScore,
'formatted' => '+' . $baseInfoScore,
];
}
// 好友数量加成
if ($friendCountScore > 0) {
$composition[] = [
'name' => '好友数量加成',
'score' => $friendCountScore,
'formatted' => '+' . $friendCountScore,
'friendCount' => $friendCount, // 显示好友总数
];
}
return $composition;
}
/**
* 获取动态记录数据
*
* @param array $healthScoreInfo 健康分信息
* @return array
*/
protected function getDynamicRecords($healthScoreInfo)
{
$records = [];
$frequentPenalty = $healthScoreInfo['frequentPenalty'] ?? 0;
$frequentCount = $healthScoreInfo['frequentCount'] ?? 0;
$banPenalty = $healthScoreInfo['banPenalty'] ?? 0;
$isBanned = $healthScoreInfo['isBanned'] ?? 0;
$noFrequentBonus = $healthScoreInfo['noFrequentBonus'] ?? 0;
$consecutiveNoFrequentDays = $healthScoreInfo['consecutiveNoFrequentDays'] ?? 0;
$lastFrequentTime = $healthScoreInfo['lastFrequentTime'] ?? null;
// 频繁扣分记录
// 根据frequentCount判断是首次还是再次
// frequentPenalty存储的是当前状态的扣分-15或-25不是累计值
if ($frequentCount > 0 && $frequentPenalty < 0) {
if ($frequentCount == 1) {
// 首次频繁:-15分
$records[] = [
'name' => '首次触发限额',
'score' => $frequentPenalty,
'formatted' => (string)$frequentPenalty,
'type' => 'penalty',
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
];
} else {
// 再次频繁:-25分
$records[] = [
'name' => '再次触发限额',
'score' => $frequentPenalty,
'formatted' => (string)$frequentPenalty,
'type' => 'penalty',
'time' => $lastFrequentTime ? date('Y-m-d H:i:s', $lastFrequentTime) : null,
];
}
}
// 封号扣分记录
if ($isBanned && $banPenalty < 0) {
$lastBanTime = $healthScoreInfo['lastBanTime'] ?? null;
$records[] = [
'name' => '封号',
'score' => $banPenalty,
'formatted' => (string)$banPenalty,
'type' => 'penalty',
'time' => $lastBanTime ? date('Y-m-d H:i:s', $lastBanTime) : null,
];
}
// 不频繁加分记录
if ($noFrequentBonus > 0 && $consecutiveNoFrequentDays >= 3) {
$lastNoFrequentTime = $healthScoreInfo['lastNoFrequentTime'] ?? null;
$records[] = [
'name' => '连续' . $consecutiveNoFrequentDays . '天不触发频繁',
'score' => $noFrequentBonus,
'formatted' => '+' . $noFrequentBonus,
'type' => 'bonus',
'time' => $lastNoFrequentTime ? date('Y-m-d H:i:s', $lastNoFrequentTime) : null,
];
}
return $records;
}
/**
* 获取今日已加好友数
*
* @param string $wechatId 微信ID
* @return int
*/
protected function getTodayAddedCount($wechatId)
{
$start = strtotime(date('Y-m-d 00:00:00'));
$end = strtotime(date('Y-m-d 23:59:59'));
return Db::table('s2_friend_task')
->where('wechatId', $wechatId)
->whereBetween('createTime', [$start, $end])
->count();
}
/**
* 获取最后添加时间
*
* @param string $wechatId 微信ID
* @return string
*/
protected function getLastAddTime($wechatId)
{
$lastTask = Db::table('s2_friend_task')
->where('wechatId', $wechatId)
->order('createTime', 'desc')
->find();
if (empty($lastTask) || empty($lastTask['createTime'])) {
return '';
}
return date('H:i:s', $lastTask['createTime']);
}
/**
* 获取账号价值(模拟数据)
*
* @param int $accountId 账号ID
* @return array
*/
protected function getAccountValue($accountId)
{
// TODO: 后续替换为真实计算逻辑
// 模拟数据¥29,800
$value = 29800;
return [
'value' => $value,
'formatted' => '¥' . number_format($value, 0, '.', ','),
];
}
/**
* 获取今日价值变化(模拟数据)
*
* @param int $accountId 账号ID
* @return array
*/
protected function getTodayValueChange($accountId)
{
// TODO: 后续替换为真实计算逻辑
// 模拟数据:+500
$change = 500;
return [
'change' => $change,
'formatted' => $change > 0 ? '+' . $change : (string)$change,
'isPositive' => $change > 0,
];
}
/**
* 获取好友总数
*
* @param string $wechatId 微信ID
* @param int $companyId 公司ID
* @return int
*/
protected function getTotalFriends($wechatId, $companyId)
{
// 优先从 s2_wechat_account 表获取
$account = Db::table('s2_wechat_account')
->where('wechatId', $wechatId)
->field('totalFriend')
->find();
if (!empty($account) && isset($account['totalFriend'])) {
return (int)$account['totalFriend'];
}
// 如果 totalFriend 为空,则从 s2_wechat_friend 表统计
return Db::table('s2_wechat_friend')
->where('ownerWechatId', $wechatId)
->where('isDeleted', 0)
->count();
}
/**
* 获取今日新增好友数
*
* @param string $wechatId 微信ID
* @return int
*/
protected function getTodayNewFriends($wechatId)
{
$start = strtotime(date('Y-m-d 00:00:00'));
$end = strtotime(date('Y-m-d 23:59:59'));
// 从 s2_wechat_friend 表统计今日新增
return Db::table('s2_wechat_friend')
->where('ownerWechatId', $wechatId)
->whereBetween('createTime', [$start, $end])
->where('isDeleted', 0)
->count();
}
/**
* 获取高价群聊数量
* 高价群聊定义:群成员数 >= 50 的群聊
*
* @param string $wechatId 微信ID
* @param int $companyId 公司ID
* @return int
*/
protected function getHighValueChatrooms($wechatId, $companyId)
{
// 高价群聊定义:群成员数 >= 50
$minMemberCount = 50;
// 查询该微信账号下的高价群聊
// 使用子查询统计每个群的成员数
$result = Db::query("
SELECT COUNT(DISTINCT c.chatroomId) as count
FROM s2_wechat_chatroom c
INNER JOIN (
SELECT chatroomId, COUNT(*) as memberCount
FROM s2_wechat_chatroom_member
GROUP BY chatroomId
HAVING memberCount >= ?
) m ON c.chatroomId = m.chatroomId
WHERE c.wechatAccountWechatId = ?
AND c.isDeleted = 0
", [$minMemberCount, $wechatId]);
return !empty($result) ? (int)$result[0]['count'] : 0;
}
/**
* 获取今日新增群聊数
*
* @param string $wechatId 微信ID
* @param int $companyId 公司ID
* @return int
*/
protected function getTodayNewChatrooms($wechatId, $companyId)
{
$start = strtotime(date('Y-m-d 00:00:00'));
$end = strtotime(date('Y-m-d 23:59:59'));
return Db::table('s2_wechat_chatroom')
->where('wechatAccountWechatId', $wechatId)
->whereBetween('createTime', [$start, $end])
->where('isDeleted', 0)
->count();
}
}

View File

@@ -8,14 +8,25 @@ 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\WechatCustomer as WechatCustomerModel;
use app\common\model\WechatFriendShip as WechatFriendShipModel;
// 不再使用WechatFriendShipModelWechatCustomerModel改为直接查询s2_wechat_friend和s2_wechat_account_score表
use app\cunkebao\controller\BaseController;
use library\ResponseHelper;
use think\Db;
/**
* 微信控制器
*
* 性能优化建议:
* 1. 为以下字段添加索引以提高查询性能:
* - device_wechat_login表: (companyId, wechatId), (deviceId)
* - wechat_account表: (wechatId)
* - wechat_customer表: (companyId, wechatId)
* - wechat_friend_ship表: (ownerWechatId), (createTime)
* - s2_wechat_message表: (wechatAccountId, wechatTime)
*
* 2. 考虑创建以下复合索引:
* - device_wechat_login表: (companyId, deviceId, wechatId)
* - wechat_friend_ship表: (ownerWechatId, createTime)
*/
class GetWechatsOnDevicesV1Controller extends BaseController
{
@@ -66,6 +77,7 @@ class GetWechatsOnDevicesV1Controller extends BaseController
/**
* 获取有登录设备的微信id
* 优化:使用索引字段,减少数据查询量
*
* @return array
*/
@@ -76,12 +88,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
throw new \Exception('暂无设备数据', 200);
}
return DeviceWechatLoginModel::where(
[
// 优化直接使用DISTINCT减少数据传输量
return DeviceWechatLoginModel::distinct(true)
->where([
'companyId' => $this->getUserInfo('companyId'),
// 'alive' => DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE,
]
)
])
->where('deviceId', 'in', $deviceIds)
->column('wechatId');
}
@@ -110,24 +122,50 @@ class GetWechatsOnDevicesV1Controller extends BaseController
/**
* 获取在线微信账号列表
* 优化减少查询字段使用索引优化JOIN条件
*
* @param array $where
* @return \think\Paginator 分页对象
*/
protected function getOnlineWechatList(array $where): \think\Paginator
{
// 获取微信在线状态筛选参数1=在线0=离线,不传=全部)
$wechatStatus = $this->request->param('wechatStatus');
// 优化只查询必要字段使用FORCE INDEX提示数据库使用索引
$query = WechatAccountModel::alias('w')
->field(
[
'w.id', 'w.nickname', 'w.avatar', 'w.wechatId',
'CASE WHEN w.alias IS NULL OR w.alias = "" THEN w.wechatId ELSE w.alias END AS wechatAccount',
'l.deviceId','l.alive'
'MAX(l.deviceId) as deviceId', 'MAX(l.alive) as alive' // 使用MAX确保GROUP BY时获取正确的在线状态
]
)
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'))
->order('w.id desc')
->group('w.wechatId');
// 优化使用INNER JOIN代替LEFT JOIN并添加索引提示
->join('device_wechat_login l', 'w.wechatId = l.wechatId AND l.companyId = '. $this->getUserInfo('companyId'), 'INNER')
// 添加s2_wechat_account表的LEFT JOIN用于筛选微信在线状态
->join(['s2_wechat_account' => 'sa'], 'w.wechatId = sa.wechatId', 'LEFT')
->group('w.wechatId')
// 优化在线状态优先排序alive=1的排在前面然后按wechatId排序
// 注意ORDER BY使用SELECT中定义的别名alive而不是聚合函数
->order('alive desc, w.wechatId desc');
// 根据wechatStatus参数筛选1=在线0=离线,不传=全部)
if ($wechatStatus !== null && $wechatStatus !== '') {
$wechatStatus = (int)$wechatStatus;
if ($wechatStatus === 1) {
// 筛选在线wechatAlive = 1
$query->where('sa.wechatAlive', 1);
} elseif ($wechatStatus === 0) {
// 筛选离线wechatAlive = 0 或 NULL
$query->where(function($query) {
$query->where('sa.wechatAlive', 0)
->whereOr('sa.wechatAlive', 'exp', 'IS NULL');
});
}
}
// 应用查询条件
foreach ($where as $key => $value) {
if (is_numeric($key) && is_array($value) && isset($value[0]) && $value[0] === 'exp') {
$query->whereExp('', $value[1]);
@@ -142,7 +180,12 @@ class GetWechatsOnDevicesV1Controller extends BaseController
$query->where($key, $value);
}
return $query->paginate($this->request->param('limit/d', 10), false, ['page' => $this->request->param('page/d', 1)]);
// 优化:使用简单计数查询
return $query->paginate(
$this->request->param('limit/d', 10),
false,
['page' => $this->request->param('page/d', 1)]
);
}
/**
@@ -167,9 +210,15 @@ class GetWechatsOnDevicesV1Controller extends BaseController
$metrics = $this->collectWechatMetrics($wechatIds);
foreach ($items as $item) {
$addLimit = $metrics['addLimit'][$item->wechatId] ?? 0;
$todayAdded = $metrics['todayAdded'][$item->wechatId] ?? 0;
// 计算今日可添加数量 = 可添加额度 - 今日已添加
$todayCanAdd = max(0, $addLimit - $todayAdded);
$sections = $item->toArray() + [
'times' => $metrics['addLimit'][$item->wechatId] ?? 0,
'addedCount' => $metrics['todayAdded'][$item->wechatId] ?? 0,
'times' => $addLimit,
'addedCount' => $todayAdded,
'todayCanAdd' => $todayCanAdd, // 今日可添加数量
'wechatStatus' => $metrics['wechatStatus'][$item->wechatId] ?? 0,
'totalFriend' => $metrics['totalFriend'][$item->wechatId] ?? 0,
'deviceMemo' => $metrics['deviceMemo'][$item->wechatId] ?? '',
@@ -184,6 +233,8 @@ class GetWechatsOnDevicesV1Controller extends BaseController
/**
* 批量收集微信账号的统计信息
* 优化:合并查询,减少数据库访问次数,使用缓存
*
* @param array $wechatIds
* @return array
*/
@@ -203,106 +254,167 @@ class GetWechatsOnDevicesV1Controller extends BaseController
}
$companyId = $this->getUserInfo('companyId');
// 可添加好友额度
$weightRows = WechatCustomerModel::where('companyId', $companyId)
// 使用缓存键,避免短时间内重复查询
$cacheKey = 'wechat_metrics_' . md5(implode(',', $wechatIds) . '_' . $companyId);
// 尝试从缓存获取数据缓存5分钟
$cachedMetrics = cache($cacheKey);
if ($cachedMetrics) {
return $cachedMetrics;
}
// 优化1可添加好友额度 - 从s2_wechat_account_score表获取maxAddFriendPerDay
$scoreRows = Db::table('s2_wechat_account_score')
->whereIn('wechatId', $wechatIds)
->column('weight', 'wechatId');
foreach ($weightRows as $wechatId => $weight) {
$decoded = json_decode($weight, true);
$metrics['addLimit'][$wechatId] = $decoded['addLimit'] ?? 0;
->column('maxAddFriendPerDay', 'wechatId');
foreach ($scoreRows as $wechatId => $maxAddFriendPerDay) {
$metrics['addLimit'][$wechatId] = (int)($maxAddFriendPerDay ?? 0);
}
// 今日新增好友
// 优化2今日新增好友 - 使用索引字段和预计算
$start = strtotime(date('Y-m-d 00:00:00'));
$end = strtotime(date('Y-m-d 23:59:59'));
$todayRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
->whereBetween('createTime', [$start, $end])
->field('ownerWechatId, COUNT(*) as total')
->group('ownerWechatId')
->select();
foreach ($todayRows as $row) {
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
// 使用单次查询获取所有wechatIds的今日新增和总好友数
// 根据数据库结构使用s2_wechat_friend表而不是wechat_friend_ship
$friendshipStats = Db::query("
SELECT
ownerWechatId,
SUM(IF(createTime BETWEEN {$start} AND {$end}, 1, 0)) as today_added,
COUNT(*) as total_friend
FROM
s2_wechat_friend
WHERE
ownerWechatId IN ('" . implode("','", $wechatIds) . "')
AND isDeleted = 0
GROUP BY
ownerWechatId
");
// 处理结果
foreach ($friendshipStats as $row) {
$wechatId = $row['ownerWechatId'] ?? '';
if ($wechatId) {
$metrics['todayAdded'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
$metrics['todayAdded'][$wechatId] = (int)($row['today_added'] ?? 0);
$metrics['totalFriend'][$wechatId] = (int)($row['total_friend'] ?? 0);
}
}
// 总好友
$friendRows = WechatFriendShipModel::whereIn('ownerWechatId', $wechatIds)
->field('ownerWechatId, COUNT(*) as total')
->group('ownerWechatId')
// 优化3微信在线状态 - 从s2_wechat_account表获取wechatAlive
$wechatAccountRows = Db::table('s2_wechat_account')
->whereIn('wechatId', $wechatIds)
->field('wechatId, wechatAlive')
->select();
foreach ($friendRows as $row) {
$wechatId = is_array($row) ? ($row['ownerWechatId'] ?? '') : ($row->ownerWechatId ?? '');
if ($wechatId) {
$metrics['totalFriend'][$wechatId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
foreach ($wechatAccountRows as $row) {
$wechatId = $row['wechatId'] ?? '';
if (!empty($wechatId)) {
$metrics['wechatStatus'][$wechatId] = (int)($row['wechatAlive'] ?? 0);
}
}
// 设备状态与备注
// 优化4设备状态与备注 - 使用INNER JOIN和索引
$loginRows = Db::name('device_wechat_login')
->alias('l')
->leftJoin('device d', 'd.id = l.deviceId')
->field('l.wechatId,l.alive,d.memo')
->join('device d', 'd.id = l.deviceId', 'LEFT')
->field('l.wechatId, l.alive, d.memo')
->where('l.companyId', $companyId)
->whereIn('l.wechatId', $wechatIds)
->order('l.id', 'desc')
->select();
// 使用临时数组避免重复处理
$processedWechatIds = [];
foreach ($loginRows as $row) {
$wechatId = is_array($row) ? ($row['wechatId'] ?? '') : ($row->wechatId ?? '');
if (empty($wechatId) || isset($metrics['wechatStatus'][$wechatId])) {
continue;
$wechatId = $row['wechatId'] ?? '';
// 只处理每个wechatId的第一条记录最新的
if (!empty($wechatId) && !in_array($wechatId, $processedWechatIds)) {
// 如果s2_wechat_account表中没有wechatAlive则使用device_wechat_login的alive作为备用
if (!isset($metrics['wechatStatus'][$wechatId])) {
$metrics['wechatStatus'][$wechatId] = (int)($row['alive'] ?? 0);
}
$metrics['deviceMemo'][$wechatId] = $row['memo'] ?? '';
$processedWechatIds[] = $wechatId;
}
$metrics['wechatStatus'][$wechatId] = (int)(is_array($row) ? ($row['alive'] ?? 0) : ($row->alive ?? 0));
$metrics['deviceMemo'][$wechatId] = is_array($row) ? ($row['memo'] ?? '') : ($row->memo ?? '');
}
// 活跃时间
$accountMap = Db::table('s2_wechat_account')
->whereIn('wechatId', $wechatIds)
->column('id', 'wechatId');
if (!empty($accountMap)) {
$accountRows = Db::table('s2_wechat_message')
->whereIn('wechatAccountId', array_values($accountMap))
->field('wechatAccountId, MAX(wechatTime) as lastTime')
->group('wechatAccountId')
->select();
$accountLastTime = [];
foreach ($accountRows as $row) {
$accountId = is_array($row) ? ($row['wechatAccountId'] ?? 0) : ($row->wechatAccountId ?? 0);
if ($accountId) {
$accountLastTime[$accountId] = (int)(is_array($row) ? ($row['lastTime'] ?? 0) : ($row->lastTime ?? 0));
}
}
foreach ($accountMap as $wechatId => $accountId) {
if (isset($accountLastTime[$accountId]) && $accountLastTime[$accountId] > 0) {
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $accountLastTime[$accountId]);
}
// 优化5活跃时间 - 使用JOIN减少查询次数
$activeTimeResults = Db::query("
SELECT
a.wechatId,
MAX(m.wechatTime) as lastTime
FROM
s2_wechat_account a
LEFT JOIN
s2_wechat_message m ON a.id = m.wechatAccountId
WHERE
a.wechatId IN ('" . implode("','", $wechatIds) . "')
GROUP BY
a.wechatId
");
foreach ($activeTimeResults as $row) {
$wechatId = $row['wechatId'] ?? '';
$lastTime = (int)($row['lastTime'] ?? 0);
if (!empty($wechatId) && $lastTime > 0) {
$metrics['activeTime'][$wechatId] = date('Y-m-d H:i:s', $lastTime);
} else {
$metrics['activeTime'][$wechatId] = '-';
}
}
// 确保所有wechatId都有wechatStatus值默认0
foreach ($wechatIds as $wechatId) {
if (!isset($metrics['wechatStatus'][$wechatId])) {
$metrics['wechatStatus'][$wechatId] = 0;
}
}
// 存入缓存有效期5分钟
cache($cacheKey, $metrics, 300);
return $metrics;
}
/**
* 获取在线微信账号列表
* 优化:添加缓存,优化分页逻辑
*
* @return \think\response\Json
*/
public function index()
{
try {
// 获取分页参数
$page = $this->request->param('page/d', 1);
$limit = $this->request->param('limit/d', 10);
$keyword = $this->request->param('keyword');
$wechatStatus = $this->request->param('wechatStatus');
// 创建缓存键(基于用户、分页、搜索条件和在线状态筛选)
$cacheKey = 'wechat_list_' . $this->getUserInfo('id') . '_' . $page . '_' . $limit . '_' . md5($keyword ?? '') . '_' . ($wechatStatus ?? 'all');
// 尝试从缓存获取数据缓存2分钟
$cachedData = cache($cacheKey);
if ($cachedData) {
return ResponseHelper::success($cachedData);
}
// 如果没有缓存,执行查询
$result = $this->getOnlineWechatList(
$this->makeWhere()
);
$responseData = [
'list' => $this->makeResultedSet($result),
'total' => $result->total(),
];
// 存入缓存有效期2分钟
cache($cacheKey, $responseData, 120);
return ResponseHelper::success(
[
'list' => $this->makeResultedSet($result),
'total' => $result->total(),
]
);
return ResponseHelper::success($responseData);
} catch (\Exception $e) {
return ResponseHelper::error($e->getMessage(), $e->getCode());
}

View File

@@ -307,7 +307,7 @@ class Adapter implements WeChatServiceInterface
$conf = array_merge($task_info['reqConf'], ['task_name' => $task_info['name'], 'tags' => $tags]);
$this->createFriendAddTask($accountId, $task['phone'], $conf);
$this->createFriendAddTask($accountId, $task['phone'], $conf, $task['remark']);
$friendAddTaskCreated = true;
$task['processed_wechat_ids'] = $task['processed_wechat_ids'] . ',' . $wechatId; // 处理失败任务用,用于过滤已处理的微信号
break;
@@ -951,28 +951,29 @@ class Adapter implements WeChatServiceInterface
}
// 创建添加好友任务/执行添加
public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf)
public function createFriendAddTask(int $wechatAccountId, string $phone, array $conf, $remark = '')
{
if (empty($wechatAccountId) || empty($phone) || empty($conf)) {
return;
}
switch ($conf['remarkType']) {
case 'phone':
$remark = $phone . '-' . $conf['task_name'];
break;
case 'nickname':
$remark = '';
break;
case 'source':
$remark = $conf['task_name'];
break;
default:
$remark = '';
break;
if (empty($remark)){
switch ($conf['remarkType']) {
case 'phone':
$remark = $phone . '-' . $conf['task_name'];
break;
case 'nickname':
$remark = '';
break;
case 'source':
$remark = $conf['task_name'];
break;
default:
$remark = '';
break;
}
}
$tags = [];
if (!empty($conf['tags'])) {
if (is_array($conf['tags'])) {

View File

@@ -11,7 +11,7 @@
Target Server Version : 50736
File Encoding : 65001
Date: 12/11/2025 11:05:39
Date: 24/11/2025 16:50:43
*/
SET NAMES utf8mb4;
@@ -123,7 +123,7 @@ CREATE TABLE `ck_app_version` (
`updateContent` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`createTime` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_attachments
@@ -145,7 +145,7 @@ CREATE TABLE `ck_attachments` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_hash_key`(`hash_key`) USING BTREE,
INDEX `idx_server`(`server`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 481 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 505 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '附件表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_call_recording
@@ -222,7 +222,7 @@ CREATE TABLE `ck_content_item` (
INDEX `idx_wechatid`(`wechatId`) USING BTREE,
INDEX `idx_friendid`(`friendId`) USING BTREE,
INDEX `idx_create_time`(`createTime`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5876 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 5993 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容项目表-存储朋友圈采集数据' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_content_library
@@ -252,7 +252,7 @@ CREATE TABLE `ck_content_library` (
`isDel` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除',
`deleteTime` int(11) NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 87 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 100 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '内容库表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_coze_conversation
@@ -272,7 +272,7 @@ CREATE TABLE `ck_coze_conversation` (
UNIQUE INDEX `idx_conversation_id`(`conversation_id`) USING BTREE,
INDEX `idx_bot_id`(`bot_id`) USING BTREE,
INDEX `idx_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 56 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'Coze AI 会话表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_coze_message
@@ -331,7 +331,7 @@ CREATE TABLE `ck_customer_acquisition_task` (
`deleteTime` int(11) NULL DEFAULT 0 COMMENT '删除时间',
`apiKey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 162 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 168 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '获客计划表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_device
@@ -372,7 +372,7 @@ CREATE TABLE `ck_device_handle_log` (
`companyId` int(11) NULL DEFAULT NULL COMMENT '租户id',
`createTime` int(11) NULL DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 304 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 339 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_device_taskconf
@@ -395,7 +395,7 @@ CREATE TABLE `ck_device_taskconf` (
`updateTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '更新时间',
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 29 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备任务配置表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_device_user
@@ -425,7 +425,7 @@ CREATE TABLE `ck_device_wechat_login` (
`isTips` tinyint(2) NOT NULL DEFAULT 0 COMMENT '是否提示迁移',
PRIMARY KEY (`id`) USING BTREE,
INDEX `wechatId`(`wechatId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 309 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 312 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '设备登录微信记录表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_flow_package
@@ -653,7 +653,7 @@ CREATE TABLE `ck_kf_follow_up` (
INDEX `idx_level`(`type`) USING BTREE,
INDEX `idx_isRemind`(`isRemind`) USING BTREE,
INDEX `idx_isProcess`(`isProcess`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '跟进提醒' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_kf_friend_settings
@@ -675,7 +675,7 @@ CREATE TABLE `ck_kf_friend_settings` (
INDEX `idx_userId`(`userId`) USING BTREE,
INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
INDEX `idx_friendId`(`friendId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 37 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '好友AI配置' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_kf_keywords
@@ -769,7 +769,7 @@ CREATE TABLE `ck_kf_notice` (
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
`readTime` int(12) NULL DEFAULT NULL COMMENT '读取时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 246 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 247 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '通知消息' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_kf_questions
@@ -810,7 +810,7 @@ CREATE TABLE `ck_kf_reply` (
`isDel` tinyint(2) NULL DEFAULT 0 COMMENT '是否删除',
`delTime` int(12) NULL DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 130746 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 130751 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '快捷回复' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_kf_reply_group
@@ -977,7 +977,7 @@ CREATE TABLE `ck_task_customer` (
INDEX `addTime`(`addTime`) USING BTREE,
INDEX `passTime`(`passTime`) USING BTREE,
INDEX `updateTime`(`updateTime`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 24192 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 28204 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_tokens_company
@@ -990,7 +990,7 @@ CREATE TABLE `ck_tokens_company` (
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '公司算力账户' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_tokens_package
@@ -1033,7 +1033,7 @@ CREATE TABLE `ck_tokens_record` (
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`createTime` int(12) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 236 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 273 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '算力明细记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_traffic_order
@@ -1070,8 +1070,9 @@ CREATE TABLE `ck_traffic_pool` (
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uni_identifier`(`identifier`) USING BTREE,
INDEX `idx_wechatId`(`wechatId`) USING BTREE,
INDEX `idx_mobile`(`mobile`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 959687 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic;
INDEX `idx_mobile`(`mobile`) USING BTREE,
INDEX `idx_create_time`(`createTime`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1063510 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量池' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_traffic_profile
@@ -1114,8 +1115,9 @@ CREATE TABLE `ck_traffic_source` (
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_identifier_sourceId_sceneId`(`identifier`, `sourceId`, `sceneId`) USING BTREE,
INDEX `idx_identifier`(`identifier`) USING BTREE,
INDEX `idx_companyId`(`companyId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 564508 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic;
INDEX `idx_companyId`(`companyId`) USING BTREE,
INDEX `idx_company_status_time`(`companyId`, `status`, `updateTime`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 573831 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量来源' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_traffic_source_package
@@ -1242,7 +1244,7 @@ CREATE TABLE `ck_user_portrait` (
`createTime` int(11) UNSIGNED NULL DEFAULT NULL COMMENT '创建时间',
`updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 17718 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 19014 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户画像' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_users
@@ -1267,7 +1269,7 @@ CREATE TABLE `ck_users` (
`updateTime` int(11) NULL DEFAULT NULL COMMENT '修改时间',
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '删除时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1652 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 1658 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_vendor_order
@@ -1360,7 +1362,7 @@ CREATE TABLE `ck_wechat_account` (
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3097959 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 3614968 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_wechat_customer
@@ -1378,7 +1380,7 @@ CREATE TABLE `ck_wechat_customer` (
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uni_wechatId`(`wechatId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 153 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 154 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信客服信息' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_wechat_friendship
@@ -1434,7 +1436,7 @@ CREATE TABLE `ck_wechat_group_member` (
`deleteTime` int(11) UNSIGNED NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_identifier_chatroomId_groupId`(`identifier`, `chatroomId`, `groupId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 549847 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 554147 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_wechat_restricts
@@ -1451,7 +1453,7 @@ CREATE TABLE `ck_wechat_restricts` (
`restrictTime` int(11) NULL DEFAULT NULL COMMENT '限制日期',
`recoveryTime` int(11) NULL DEFAULT NULL COMMENT '恢复日期',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 1319 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信风险受限记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_wechat_tag
@@ -1489,7 +1491,7 @@ CREATE TABLE `ck_workbench` (
INDEX `idx_user_id`(`userId`) USING BTREE,
INDEX `idx_type`(`type`) USING BTREE,
INDEX `idx_status`(`status`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 275 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 282 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台主表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_auto_like
@@ -1534,7 +1536,7 @@ CREATE TABLE `ck_workbench_auto_like_item` (
INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE,
INDEX `momentsId`(`momentsId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4639 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 4653 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '工作台-自动点赞记录' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_group_create
@@ -1604,9 +1606,14 @@ CREATE TABLE `ck_workbench_group_push` (
`promotionSiteId` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '京东广告位',
`trafficPools` json NULL COMMENT '流量池',
`devices` json NULL,
`groupPushSubType` tinyint(2) NULL DEFAULT 1 COMMENT '群推送子类型 1=群群发2=群公告',
`announcementContent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`enableAiRewrite` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`aiRewritePrompt` tinyint(2) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic;
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE,
INDEX `idx_status_targetType`(`status`, `targetType`, `workbenchId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '群消息推送扩展表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_group_push_item
@@ -1622,7 +1629,9 @@ CREATE TABLE `ck_workbench_group_push_item` (
`wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
`isLoop` tinyint(2) NULL DEFAULT 0 COMMENT '是否循环完成',
`createTime` int(11) NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_workbench_target_time`(`workbenchId`, `targetType`, `createTime`) USING BTREE,
INDEX `idx_workbench_target_friend`(`workbenchId`, `targetType`, `friendId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
@@ -1677,7 +1686,7 @@ CREATE TABLE `ck_workbench_moments_sync` (
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_workbench_id`(`workbenchId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 47 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 51 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_moments_sync_item
@@ -1691,8 +1700,10 @@ CREATE TABLE `ck_workbench_moments_sync_item` (
`wechatAccountId` int(11) NULL DEFAULT NULL COMMENT '客服id',
`createTime` int(11) NOT NULL COMMENT '创建时间',
`isLoop` tinyint(2) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1650 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_workbench_time`(`workbenchId`, `createTime`) USING BTREE,
INDEX `idx_workbench_content`(`workbenchId`, `contentId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1785 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '朋友圈同步配置' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_traffic_config
@@ -1714,7 +1725,7 @@ CREATE TABLE `ck_workbench_traffic_config` (
`updateTime` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uniq_workbench`(`workbenchId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for ck_workbench_traffic_config_item
@@ -1736,7 +1747,7 @@ CREATE TABLE `ck_workbench_traffic_config_item` (
INDEX `deviceId`(`deviceId`) USING BTREE,
INDEX `wechatFriendId`(`wechatFriendId`) USING BTREE,
INDEX `wechatAccountId`(`wechatAccountId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 49898 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 54241 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '流量分发计划扩展表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for s2_allot_rule
@@ -1869,6 +1880,7 @@ CREATE TABLE `s2_device` (
`groupName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分组名称',
`wechatAccounts` json NULL COMMENT '微信账号列表JSON',
`alive` tinyint(1) NULL DEFAULT 0 COMMENT '是否在线',
`aliveTime` int(11) NULL DEFAULT 0,
`lastAliveTime` int(11) NULL DEFAULT NULL COMMENT '最后在线时间',
`tenantId` int(11) NULL DEFAULT NULL COMMENT '租户ID',
`groupId` int(11) NULL DEFAULT NULL COMMENT '分组ID',
@@ -1938,6 +1950,7 @@ CREATE TABLE `s2_friend_task` (
`accountRealName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号真实姓名',
`accountUsername` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '账号用户名',
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间戳',
`is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计0=未统计1=已统计)',
UNIQUE INDEX `uk_task_id`(`id`) USING BTREE,
INDEX `idx_tenant_id`(`tenantId`) USING BTREE,
INDEX `idx_operator_account_id`(`operatorAccountId`) USING BTREE,
@@ -2064,6 +2077,7 @@ CREATE TABLE `s2_wechat_account` (
`keFuAlive` tinyint(1) NULL DEFAULT 0 COMMENT '客服是否在线',
`deviceAlive` tinyint(1) NULL DEFAULT 0 COMMENT '设备是否在线',
`wechatAlive` tinyint(1) NULL DEFAULT 0 COMMENT '微信是否在线',
`wechatAliveTime` int(11) NULL DEFAULT 0 COMMENT '在线时间',
`yesterdayMsgCount` int(11) NULL DEFAULT 0 COMMENT '昨日消息数',
`sevenDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '7天消息数',
`thirtyDayMsgCount` int(11) NULL DEFAULT 0 COMMENT '30天消息数',
@@ -2092,9 +2106,82 @@ CREATE TABLE `s2_wechat_account` (
`createTime` int(11) NULL DEFAULT NULL COMMENT '创建时间',
`updateTime` int(11) NULL DEFAULT NULL COMMENT '更新时间',
`status` tinyint(3) NULL DEFAULT 1 COMMENT '状态值',
INDEX `idx_wechat_id`(`wechatId`) USING BTREE
`healthScore` int(11) NULL DEFAULT 60 COMMENT '健康分总分(基础分+动态分)',
`baseScore` int(11) NULL DEFAULT 60 COMMENT '基础分60-100分',
`dynamicScore` int(11) NULL DEFAULT 0 COMMENT '动态分(扣分和加分)',
`isModifiedAlias` tinyint(1) NULL DEFAULT 0 COMMENT '是否已修改微信号0=未修改1=已修改)',
`lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)',
`frequentCount` int(11) NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)',
`lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)',
`consecutiveNoFrequentDays` int(11) NULL DEFAULT 0 COMMENT '连续不频繁天数(用于加分)',
`scoreUpdateTime` int(11) NULL DEFAULT NULL COMMENT '评分更新时间',
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
INDEX `idx_health_score`(`healthScore`) USING BTREE,
INDEX `idx_is_modified_alias`(`isModifiedAlias`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for s2_wechat_account_score
-- ----------------------------
DROP TABLE IF EXISTS `s2_wechat_account_score`;
CREATE TABLE `s2_wechat_account_score` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`accountId` int(11) NOT NULL COMMENT '微信账号IDs2_wechat_account.id',
`wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
`baseScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础分60-100分',
`baseScoreCalculated` tinyint(1) NOT NULL DEFAULT 0 COMMENT '基础分是否已计算0=未计算1=已计算)',
`baseScoreCalcTime` int(11) NULL DEFAULT NULL COMMENT '基础分计算时间',
`baseInfoScore` int(11) NOT NULL DEFAULT 0 COMMENT '基础信息分0-10分',
`isModifiedAlias` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已修改微信号0=未修改1=已修改)',
`friendCountScore` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量分0-30分',
`friendCount` int(11) NOT NULL DEFAULT 0 COMMENT '好友数量(评分时的快照)',
`friendCountSource` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '好友数量来源manual=手动sync=同步)',
`dynamicScore` int(11) NOT NULL DEFAULT 0 COMMENT '动态分(扣分和加分)',
`lastFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后频繁时间(时间戳)',
`frequentCount` int(11) NOT NULL DEFAULT 0 COMMENT '频繁次数(用于判断首次/再次频繁)',
`frequentPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '频繁扣分(累计)',
`lastNoFrequentTime` int(11) NULL DEFAULT NULL COMMENT '最后不频繁时间(时间戳)',
`consecutiveNoFrequentDays` int(11) NOT NULL DEFAULT 0 COMMENT '连续不频繁天数',
`noFrequentBonus` int(11) NOT NULL DEFAULT 0 COMMENT '不频繁加分(累计)',
`isBanned` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否封号0=否1=是)',
`banPenalty` int(11) NOT NULL DEFAULT 0 COMMENT '封号扣分',
`healthScore` int(11) NOT NULL DEFAULT 0 COMMENT '健康分总分(基础分+动态分)',
`maxAddFriendPerDay` int(11) NOT NULL DEFAULT 0 COMMENT '每日最大加人次数',
`createTime` int(11) NOT NULL DEFAULT 0 COMMENT '创建时间',
`updateTime` int(11) NOT NULL DEFAULT 0 COMMENT '更新时间',
`lastBanTime` int(11) NULL DEFAULT NULL COMMENT '最后一次封号时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_account_id`(`accountId`) USING BTREE,
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
INDEX `idx_health_score`(`healthScore`) USING BTREE,
INDEX `idx_base_score_calculated`(`baseScoreCalculated`) USING BTREE,
INDEX `idx_update_time`(`updateTime`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 363 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号评分记录表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for s2_wechat_account_score_log
-- ----------------------------
DROP TABLE IF EXISTS `s2_wechat_account_score_log`;
CREATE TABLE `s2_wechat_account_score_log` (
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`accountId` int(11) NOT NULL COMMENT '微信账号ID',
`wechatId` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信ID',
`field` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '变动字段如frequentPenalty',
`changeValue` int(11) NOT NULL DEFAULT 0 COMMENT '变动值(正加负减)',
`valueBefore` int(11) NULL DEFAULT NULL COMMENT '变更前的字段值',
`valueAfter` int(11) NULL DEFAULT NULL COMMENT '变更后的字段值',
`category` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类penalty/bonus/dynamic_total/health_total等',
`source` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '触发来源 friend_task/wechat_message/system',
`sourceId` bigint(20) NULL DEFAULT NULL COMMENT '关联记录ID如任务/消息ID',
`extra` json NULL COMMENT '附加信息JSON',
`totalScoreBefore` int(11) NULL DEFAULT NULL COMMENT '变更前健康总分',
`totalScoreAfter` int(11) NULL DEFAULT NULL COMMENT '变更后健康总分',
`createTime` int(11) NOT NULL COMMENT '记录时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_account_field`(`accountId`, `field`) USING BTREE,
INDEX `idx_wechat_id`(`wechatId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信账号健康分加减分日志' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for s2_wechat_chatroom
-- ----------------------------
@@ -2150,7 +2237,7 @@ CREATE TABLE `s2_wechat_chatroom_member` (
UNIQUE INDEX `uk_chatroom_wechat`(`chatroomId`, `wechatId`) USING BTREE,
INDEX `chatroomId`(`chatroomId`) USING BTREE,
INDEX `wechatId`(`wechatId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 495043 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 495174 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信群成员表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for s2_wechat_friend
@@ -2198,6 +2285,9 @@ CREATE TABLE `s2_wechat_friend` (
`R` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
`F` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
`M` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0',
`realName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
`company` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '公司',
`position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职位',
UNIQUE INDEX `uk_owner_wechat_account`(`ownerWechatId`, `wechatId`, `wechatAccountId`) USING BTREE,
INDEX `idx_wechat_account_id`(`wechatAccountId`) USING BTREE,
INDEX `idx_wechat_id`(`wechatId`) USING BTREE,
@@ -2257,6 +2347,7 @@ CREATE TABLE `s2_wechat_message` (
`msgId` bigint(20) NULL DEFAULT NULL COMMENT '消息ID',
`recallId` tinyint(1) NULL DEFAULT 0 COMMENT '撤回ID',
`isRead` tinyint(1) NULL DEFAULT 0 COMMENT '是否读取',
`is_counted` tinyint(1) NULL DEFAULT 0 COMMENT '是否已统计0=未统计1=已统计)',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_wechatChatroomId`(`wechatChatroomId`) USING BTREE,
INDEX `idx_wechatAccountId`(`wechatAccountId`) USING BTREE,
@@ -2296,6 +2387,6 @@ CREATE TABLE `s2_wechat_moments` (
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_sns_account`(`snsId`, `wechatAccountId`) USING BTREE,
INDEX `idx_account_friend`(`wechatAccountId`, `wechatFriendId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 39669 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic;
) ENGINE = InnoDB AUTO_INCREMENT = 40130 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '微信朋友圈数据表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;