Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c33c62aa6d | ||
|
|
581e09d45b | ||
|
|
688a200999 | ||
|
|
cc89df7957 | ||
|
|
198e0434b3 | ||
|
|
55fe2b46df | ||
|
|
c7e934f23d | ||
|
|
7752dd136f | ||
|
|
af2f32da4a | ||
|
|
81859dfacb | ||
|
|
3e145ca123 | ||
|
|
c9a5d3091f | ||
|
|
4374ad5036 | ||
|
|
1ca2ca7058 | ||
|
|
9fe15b1bec | ||
|
|
a557319b3e | ||
|
|
f06eb0e9d7 | ||
|
|
1be091522e | ||
|
|
63df35b79c | ||
|
|
2697da15aa | ||
|
|
ced0f993de | ||
|
|
55629a8ce4 | ||
|
|
923c6a5904 | ||
|
|
b63e87d0cf | ||
|
|
96caa71f03 | ||
|
|
b9b1877e04 | ||
|
|
81daa8cfdc | ||
|
|
903ea005e3 | ||
|
|
580e9f4f73 | ||
|
|
51e08d1b2a | ||
|
|
3708f5a61e | ||
|
|
58377b4670 | ||
|
|
5bb5aa3d5a | ||
|
|
71def21369 | ||
|
|
1ea565fbdf | ||
|
|
56a9a015f1 | ||
|
|
0708b5a134 | ||
|
|
242065d298 | ||
|
|
b5d6214521 | ||
|
|
9c5bccb23c | ||
|
|
5f5b567749 | ||
|
|
354d6c4f62 | ||
|
|
2c37314c4f | ||
|
|
1dbdee155e | ||
|
|
2939b63998 | ||
|
|
ff7097f2d9 | ||
|
|
33cd4de3c5 | ||
|
|
0bc6c3a22a | ||
|
|
44edfe7a81 | ||
|
|
d3fae3bbd0 | ||
|
|
11a4ec8c67 | ||
|
|
75aab08a72 | ||
|
|
2cc2184002 | ||
|
|
57c398f537 | ||
|
|
7d9f94984c | ||
|
|
8659049ae9 | ||
|
|
1ca5b30c15 | ||
|
|
b9bc0a71a9 | ||
|
|
13ae8bd76f | ||
|
|
31fdabaa15 | ||
|
|
43e9930b45 | ||
|
|
a77192ebc2 | ||
|
|
181a792d0a | ||
|
|
43f6a061e1 | ||
|
|
05bba1a461 | ||
|
|
97fc9959bb | ||
|
|
ad2d1c27ab | ||
|
|
97144e4ebe | ||
|
|
0cc30e5cb1 | ||
|
|
eebb73f49e | ||
|
|
72626e3367 | ||
|
|
07a44e104d | ||
|
|
e4a8975baa | ||
|
|
23de3cac7e | ||
|
|
0244871fc1 | ||
|
|
5691d78004 | ||
|
|
9e76d39707 | ||
|
|
5087190816 | ||
|
|
ce47856c81 | ||
|
|
a03c162962 | ||
|
|
e658a32244 | ||
|
|
637dcddee2 | ||
|
|
5c7940538d | ||
|
|
499b36b03b | ||
|
|
9e101e924e | ||
|
|
10f876f2f6 | ||
|
|
ed4d305165 | ||
|
|
a5cc4e5e18 | ||
|
|
65da14032b | ||
|
|
7e2dd2914d | ||
|
|
644dd8912d | ||
|
|
d7e08fe420 | ||
|
|
cd27506388 | ||
|
|
78de6c92b7 | ||
|
|
cd41190663 | ||
|
|
bbdfdc136b | ||
|
|
5a52b56d89 | ||
|
|
d8d1003d7a | ||
|
|
c801490c2f | ||
|
|
30b33ec974 | ||
|
|
4140e6d3d7 | ||
|
|
1786dec900 | ||
|
|
bda57a84e8 | ||
|
|
deac306f69 | ||
|
|
43b60a0049 | ||
|
|
ab2ee050de | ||
|
|
21de87ca32 | ||
|
|
782031cbc6 | ||
|
|
6c9551be05 | ||
|
|
36eb67a217 | ||
|
|
7dcbcfacc1 | ||
|
|
2c9bda734e | ||
|
|
d296d019e5 | ||
|
|
ccf947c64d | ||
|
|
3faee0d8be | ||
|
|
fe12f78c1d | ||
|
|
a2b0041490 | ||
|
|
235e3ed994 | ||
|
|
4baa82449c | ||
|
|
84ced0c32a | ||
|
|
beabe1c191 | ||
|
|
719ed98996 | ||
|
|
b0a60e428c | ||
|
|
16a8656b49 | ||
|
|
7fdb993f57 | ||
|
|
0e56f0808c | ||
|
|
bd45a9252a | ||
|
|
5a5160b92d | ||
|
|
3e78122659 | ||
|
|
57d85765c3 | ||
|
|
067ae0fd10 | ||
|
|
bf421e1a49 | ||
|
|
339f06e630 | ||
|
|
367a250fba | ||
|
|
73110acb37 | ||
|
|
0912a0067e | ||
|
|
5faf83e437 | ||
|
|
3f76f37a4c | ||
|
|
f0574ef555 | ||
|
|
273ea73b1b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,9 +5,11 @@ Store_vue/node_modules/
|
||||
Cunkebao/.specstory/
|
||||
*.cursorindexingignore
|
||||
Server/.specstory/
|
||||
Server/thinkphp/
|
||||
Store_vue/.specstory/
|
||||
Store_vue/unpackage/
|
||||
Store_vue/.vscode/
|
||||
SuperAdmin/.specstory/
|
||||
Cunkebao/dist
|
||||
Touchkebao/.specstory/
|
||||
Serverruntime/
|
||||
|
||||
8
Cunkebao/pnpm-lock.yaml
generated
8
Cunkebao/pnpm-lock.yaml
generated
@@ -26,9 +26,6 @@ importers:
|
||||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
dexie:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
echarts:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
@@ -1070,9 +1067,6 @@ packages:
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
dexie@4.2.0:
|
||||
resolution: {integrity: sha512-OSeyyWOUetDy9oFWeddJgi83OnRA3hSFh3RrbltmPgqHszE9f24eUCVLI4mPg0ifsWk0lQTdnS+jyGNrPMvhDA==}
|
||||
|
||||
dir-glob@3.0.1:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3410,8 +3404,6 @@ snapshots:
|
||||
detect-libc@1.0.3:
|
||||
optional: true
|
||||
|
||||
dexie@4.2.0: {}
|
||||
|
||||
dir-glob@3.0.1:
|
||||
dependencies:
|
||||
path-type: 4.0.0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import request from "@/api/request";
|
||||
|
||||
export function getContentLibraryList(params: any) {
|
||||
return request("/v1/content/library/list", params, "GET");
|
||||
return request("/v1/content/library/list", { ...params, formType: 0 }, "GET");
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ export interface DeviceSelectionProps {
|
||||
showSelectedList?: boolean; // 新增
|
||||
readonly?: boolean; // 新增
|
||||
deviceGroups?: any[]; // 传递设备组数据
|
||||
singleSelect?: boolean; // 新增,是否单选模式
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
||||
showInput = true,
|
||||
showSelectedList = true,
|
||||
readonly = false,
|
||||
singleSelect = false,
|
||||
}) => {
|
||||
// 弹窗控制
|
||||
const [popupVisible, setPopupVisible] = useState(false);
|
||||
@@ -37,6 +38,9 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
||||
// 获取显示文本
|
||||
const getDisplayText = () => {
|
||||
if (selectedOptions.length === 0) return "";
|
||||
if (singleSelect && selectedOptions.length > 0) {
|
||||
return selectedOptions[0].memo || selectedOptions[0].wechatId || "已选择设备";
|
||||
}
|
||||
return `已选择 ${selectedOptions.length} 个设备`;
|
||||
};
|
||||
|
||||
@@ -179,6 +183,7 @@ const DeviceSelection: React.FC<DeviceSelectionProps> = ({
|
||||
onClose={() => setRealVisible(false)}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onSelect}
|
||||
singleSelect={singleSelect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ interface SelectionPopupProps {
|
||||
onClose: () => void;
|
||||
selectedOptions: DeviceSelectionItem[];
|
||||
onSelect: (devices: DeviceSelectionItem[]) => void;
|
||||
singleSelect?: boolean;
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
@@ -21,6 +22,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||||
onClose,
|
||||
selectedOptions,
|
||||
onSelect,
|
||||
singleSelect = false,
|
||||
}) => {
|
||||
// 设备数据
|
||||
const [devices, setDevices] = useState<DeviceSelectionItem[]>([]);
|
||||
@@ -110,6 +112,15 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||||
|
||||
// 处理设备选择
|
||||
const handleDeviceToggle = (device: DeviceSelectionItem) => {
|
||||
if (singleSelect) {
|
||||
// 单选模式:如果已选中,则取消选择;否则替换为当前设备
|
||||
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||||
setTempSelectedOptions([]);
|
||||
} else {
|
||||
setTempSelectedOptions([device]);
|
||||
}
|
||||
} else {
|
||||
// 多选模式:原有的逻辑
|
||||
if (tempSelectedOptions.some(v => v.id === device.id)) {
|
||||
setTempSelectedOptions(
|
||||
tempSelectedOptions.filter(v => v.id !== device.id),
|
||||
@@ -117,6 +128,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||||
} else {
|
||||
const newSelectedOptions = [...tempSelectedOptions, device];
|
||||
setTempSelectedOptions(newSelectedOptions);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -179,6 +191,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||||
totalPages={totalPages}
|
||||
loading={loading}
|
||||
selectedCount={tempSelectedOptions.length}
|
||||
singleSelect={singleSelect}
|
||||
onPageChange={setCurrentPage}
|
||||
onCancel={onClose}
|
||||
onConfirm={() => {
|
||||
@@ -187,7 +200,7 @@ const SelectionPopup: React.FC<SelectionPopupProps> = ({
|
||||
onClose();
|
||||
}}
|
||||
isAllSelected={isCurrentPageAllSelected}
|
||||
onSelectAll={handleSelectAllCurrentPage}
|
||||
onSelectAll={singleSelect ? undefined : handleSelectAllCurrentPage}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -95,9 +95,9 @@ export default function FriendSelection({
|
||||
{(selectedOptions || []).map(friend => (
|
||||
<div key={friend.id} className={style.selectedListRow}>
|
||||
<div className={style.selectedListRowContent}>
|
||||
<Avatar src={friend.avatar} />
|
||||
<Avatar src={friend.avatar || friend.friendAvatar} />
|
||||
<div className={style.selectedListRowContentText}>
|
||||
<div>{friend.nickname}</div>
|
||||
<div>{friend.nickname || friend.friendName}</div>
|
||||
<div>{friend.wechatId}</div>
|
||||
</div>
|
||||
{!readonly && (
|
||||
|
||||
@@ -14,6 +14,7 @@ interface PopupFooterProps {
|
||||
// 全选功能相关
|
||||
isAllSelected?: boolean;
|
||||
onSelectAll?: (checked: boolean) => void;
|
||||
singleSelect?: boolean;
|
||||
}
|
||||
|
||||
const PopupFooter: React.FC<PopupFooterProps> = ({
|
||||
@@ -26,11 +27,13 @@ const PopupFooter: React.FC<PopupFooterProps> = ({
|
||||
onConfirm,
|
||||
isAllSelected = false,
|
||||
onSelectAll,
|
||||
singleSelect = false,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{/* 分页栏 */}
|
||||
<div className={style.paginationRow}>
|
||||
{onSelectAll && (
|
||||
<div className={style.totalCount}>
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
@@ -40,6 +43,7 @@ const PopupFooter: React.FC<PopupFooterProps> = ({
|
||||
全选当前页
|
||||
</Checkbox>
|
||||
</div>
|
||||
)}
|
||||
<div className={style.paginationControls}>
|
||||
<Button
|
||||
onClick={() => onPageChange(Math.max(1, currentPage - 1))}
|
||||
@@ -61,7 +65,13 @@ const PopupFooter: React.FC<PopupFooterProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className={style.popupFooter}>
|
||||
<div className={style.selectedCount}>已选择 {selectedCount} 条记录</div>
|
||||
<div className={style.selectedCount}>
|
||||
{singleSelect
|
||||
? selectedCount > 0
|
||||
? "已选择设备"
|
||||
: "未选择设备"
|
||||
: `已选择 ${selectedCount} 条记录`}
|
||||
</div>
|
||||
<div className={style.footerBtnGroup}>
|
||||
<Button color="primary" variant="filled" onClick={onCancel}>
|
||||
取消
|
||||
|
||||
@@ -149,14 +149,14 @@ const Home: React.FC = () => {
|
||||
<div className={style["stat-card"]} onClick={handleDevicesClick}>
|
||||
<div className={style["stat-label"]}>设备数量</div>
|
||||
<div className={style["stat-value"]}>
|
||||
<span>{dashboard.deviceNum || 42}</span>
|
||||
<span>{dashboard.deviceNum || 0}</span>
|
||||
<MobileOutlined style={{ fontSize: 20, color: "#3b82f6" }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["stat-card"]} onClick={handleWechatClick}>
|
||||
<div className={style["stat-label"]}>微信号数量</div>
|
||||
<div className={style["stat-value"]}>
|
||||
<span>{dashboard.wechatNum || 42}</span>
|
||||
<span>{dashboard.wechatNum || 0}</span>
|
||||
<TeamOutlined style={{ fontSize: 20, color: "#3b82f6" }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +166,7 @@ const Home: React.FC = () => {
|
||||
>
|
||||
<div className={style["stat-label"]}>在线微信号</div>
|
||||
<div className={style["stat-value"]}>
|
||||
<span>{dashboard.aliveWechatNum || 35}</span>
|
||||
<span>{dashboard.aliveWechatNum || 0}</span>
|
||||
<LineChartOutlined style={{ fontSize: 20, color: "#3b82f6" }} />
|
||||
</div>
|
||||
<div className={style["progress-bar"]}>
|
||||
|
||||
@@ -14,7 +14,7 @@ export function getContentLibraryDetail(id: string): Promise<any> {
|
||||
export function createContentLibrary(
|
||||
params: CreateContentLibraryParams,
|
||||
): Promise<any> {
|
||||
return request("/v1/content/library/create", params, "POST");
|
||||
return request("/v1/content/library/create", { ...params, formType: 0 }, "POST");
|
||||
}
|
||||
|
||||
// 更新内容库
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
.form-page {
|
||||
background: #f7f8fa;
|
||||
padding: 16px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.form-main {
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
padding: 16px 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-card {
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
padding: 24px 18px 18px 18px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.source-tag {
|
||||
font-size: 14px;
|
||||
padding: 4px 16px;
|
||||
border-radius: 16px;
|
||||
margin-top: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
@@ -32,9 +46,23 @@
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-top: 28px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
padding-left: 8px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: #1890ff;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.section-block {
|
||||
@@ -47,40 +75,78 @@
|
||||
.adm-tabs-header {
|
||||
background: #f7f8fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
.adm-tabs-tab {
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 8px 0;
|
||||
padding: 8px 20px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&.adm-tabs-tab-active {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.adm-tabs-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse {
|
||||
margin-top: 12px;
|
||||
.keyword-collapse {
|
||||
.adm-collapse-panel-header {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.adm-collapse-panel-content {
|
||||
padding-bottom: 8px;
|
||||
background: #f8fafc;
|
||||
border-radius: 10px;
|
||||
padding: 18px 14px 10px 14px;
|
||||
margin-top: 2px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.03);
|
||||
padding: 16px 0 0 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 22px;
|
||||
margin-bottom: 18px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
display: block;
|
||||
}
|
||||
.adm-input {
|
||||
min-height: 42px;
|
||||
font-size: 15px;
|
||||
border-radius: 7px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.keyword-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.keyword-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.keyword-arrow {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.keyword-collapse .adm-collapse-panel-active .keyword-arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.ai-row,
|
||||
@@ -91,9 +157,53 @@
|
||||
}
|
||||
|
||||
.ai-desc {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.content-type-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.content-type-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.content-type-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.content-type-btn {
|
||||
padding: 10px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
outline: none;
|
||||
|
||||
&:not(.active) {
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.date-row,
|
||||
@@ -109,6 +219,179 @@
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.time-limit-header {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.time-limit-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.date-inputs {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.date-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.date-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.date-input {
|
||||
width: 100%;
|
||||
|
||||
.ant-input {
|
||||
background: #f5f5f5;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
color: #222;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.enable-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.enable-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.device-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
}
|
||||
}
|
||||
|
||||
.device-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.device-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.device-name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.device-name {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.device-tag {
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
background: #f5f5f5;
|
||||
color: #666;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.online {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
&.offline {
|
||||
background: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.device-wechat-id {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.device-arrow {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.device-input-wrapper {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.device-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.keyword-select {
|
||||
width: 100%;
|
||||
|
||||
.ant-select-selector {
|
||||
min-height: 44px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 32px;
|
||||
height: 48px !important;
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Input as AntdInput, Switch } from "antd";
|
||||
import { Input as AntdInput, Switch, Tag } from "antd";
|
||||
import { Button, Collapse, Toast, DatePicker, Tabs } from "antd-mobile";
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import FriendSelection from "@/components/FriendSelection";
|
||||
import GroupSelection from "@/components/GroupSelection";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import style from "./index.module.scss";
|
||||
import request from "@/api/request";
|
||||
import { getContentLibraryDetail, updateContentLibrary } from "./api";
|
||||
import { GroupSelectionItem } from "@/components/GroupSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
|
||||
const { TextArea } = AntdInput;
|
||||
|
||||
@@ -29,6 +32,15 @@ export default function ContentForm() {
|
||||
const isEdit = !!id;
|
||||
const [sourceType, setSourceType] = useState<"friends" | "groups">("friends");
|
||||
const [name, setName] = useState("");
|
||||
const [selectedDevices, setSelectedDevices] = useState<DeviceSelectionItem[]>([]);
|
||||
const [deviceSelectionVisible, setDeviceSelectionVisible] = useState(false);
|
||||
|
||||
// 处理设备选择(单选模式)
|
||||
const handleDeviceSelect = (devices: DeviceSelectionItem[]) => {
|
||||
// 单选模式:只保留最后一个选中的设备
|
||||
setSelectedDevices(devices.length > 0 ? [devices[devices.length - 1]] : []);
|
||||
setDeviceSelectionVisible(false);
|
||||
};
|
||||
const [friendsGroups, setSelectedFriends] = useState<string[]>([]);
|
||||
const [friendsGroupsOptions, setSelectedFriendsOptions] = useState<
|
||||
FriendSelectionItem[]
|
||||
@@ -64,6 +76,23 @@ export default function ContentForm() {
|
||||
.then(data => {
|
||||
setName(data.name || "");
|
||||
setSourceType(data.sourceType === 1 ? "friends" : "groups");
|
||||
// 详情接口中的采集设备数据可能在 devices / selectedDevices / deviceGroupsOptions 中
|
||||
const deviceOptions: DeviceSelectionItem[] =
|
||||
data.devices ||
|
||||
data.selectedDevices ||
|
||||
(data.deviceGroupsOptions
|
||||
? (data.deviceGroupsOptions as any[]).map(item => ({
|
||||
id: item.id,
|
||||
memo: item.memo,
|
||||
imei: item.imei,
|
||||
wechatId: item.wechatId,
|
||||
status: item.alive === 1 ? "online" : "offline",
|
||||
nickname: item.nickname,
|
||||
avatar: item.avatar,
|
||||
totalFriend: item.totalFriend,
|
||||
}))
|
||||
: []);
|
||||
setSelectedDevices(deviceOptions || []);
|
||||
setSelectedFriends(data.sourceFriends || []);
|
||||
setSelectedGroups(data.selectedGroups || []);
|
||||
setSelectedGroupsOptions(data.selectedGroupsOptions || []);
|
||||
@@ -72,7 +101,13 @@ export default function ContentForm() {
|
||||
setKeywordsExclude((data.keywordExclude || []).join(","));
|
||||
setCatchType(data.catchType || ["text", "image", "video"]);
|
||||
setAIPrompt(data.aiPrompt || "");
|
||||
// aiEnabled 为 AI 提示词开关,1 开启 0 关闭
|
||||
if (typeof data.aiEnabled !== "undefined") {
|
||||
setUseAI(data.aiEnabled === 1);
|
||||
} else {
|
||||
// 兼容旧数据,默认根据是否有 aiPrompt 判断
|
||||
setUseAI(!!data.aiPrompt);
|
||||
}
|
||||
setEnabled(data.status === 1);
|
||||
// 时间范围
|
||||
const start = data.timeStart || data.startTime;
|
||||
@@ -103,6 +138,7 @@ export default function ContentForm() {
|
||||
const payload = {
|
||||
name,
|
||||
sourceType: sourceType === "friends" ? 1 : 2,
|
||||
devices: selectedDevices.map(d => d.id),
|
||||
friendsGroups: friendsGroups,
|
||||
wechatGroups: selectedGroups,
|
||||
groupMembers: {},
|
||||
@@ -116,6 +152,7 @@ export default function ContentForm() {
|
||||
.filter(Boolean),
|
||||
catchType,
|
||||
aiPrompt,
|
||||
aiEnabled: useAI ? 1 : 0,
|
||||
timeEnabled: dateRange[0] || dateRange[1] ? 1 : 0,
|
||||
startTime: dateRange[0] ? formatDate(dateRange[0]) : "",
|
||||
endTime: dateRange[1] ? formatDate(dateRange[1]) : "",
|
||||
@@ -125,7 +162,7 @@ export default function ContentForm() {
|
||||
await updateContentLibrary({ id, ...payload });
|
||||
Toast.show({ content: "保存成功", position: "top" });
|
||||
} else {
|
||||
await request("/v1/content/library/create", payload, "POST");
|
||||
await request("/v1/content/library/create", { ...payload, formType: 0 }, "POST");
|
||||
Toast.show({ content: "创建成功", position: "top" });
|
||||
}
|
||||
navigate("/mine/content");
|
||||
@@ -178,7 +215,7 @@ export default function ContentForm() {
|
||||
onSubmit={e => e.preventDefault()}
|
||||
autoComplete="off"
|
||||
>
|
||||
<div className={style["form-section"]}>
|
||||
<div className={style["form-card"]}>
|
||||
<label className={style["form-label"]}>
|
||||
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||
内容库名称
|
||||
@@ -191,8 +228,100 @@ export default function ContentForm() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={style["section-title"]}>数据来源配置</div>
|
||||
<div className={style["form-section"]}>
|
||||
<div className={style["form-card"]}>
|
||||
<label className={style["form-label"]}>
|
||||
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||
来源渠道
|
||||
</label>
|
||||
<Tag color="blue" className={style["source-tag"]}>
|
||||
微信朋友圈
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
<div className={style["form-card"]}>
|
||||
<label className={style["form-label"]}>
|
||||
<span style={{ color: "#ff4d4f", marginRight: 4 }}>*</span>
|
||||
选择采集设备
|
||||
</label>
|
||||
{selectedDevices.length > 0 ? (
|
||||
<div
|
||||
className={style["device-card"]}
|
||||
onClick={() => setDeviceSelectionVisible(true)}
|
||||
>
|
||||
<div className={style["device-avatar"]}>
|
||||
{selectedDevices[0].avatar ? (
|
||||
<img
|
||||
src={selectedDevices[0].avatar}
|
||||
alt="头像"
|
||||
className={style["avatar-img"]}
|
||||
/>
|
||||
) : (
|
||||
<span className={style["avatar-text"]}>
|
||||
{(selectedDevices[0].memo ||
|
||||
selectedDevices[0].wechatId ||
|
||||
"设")[0]}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={style["device-info"]}>
|
||||
<div className={style["device-name-row"]}>
|
||||
<span className={style["device-name"]}>
|
||||
{selectedDevices[0].nickname || selectedDevices[0].memo}
|
||||
</span>
|
||||
{selectedDevices[0].memo &&
|
||||
selectedDevices[0].nickname &&
|
||||
selectedDevices[0].memo !== selectedDevices[0].nickname && (
|
||||
<span className={style["device-tag"]}>
|
||||
{selectedDevices[0].memo}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className={`${style["status-dot"]} ${
|
||||
selectedDevices[0].status === "online"
|
||||
? style["online"]
|
||||
: style["offline"]
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className={style["device-wechat-id"]}>
|
||||
微信ID: {selectedDevices[0].wechatId}
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["device-arrow"]}>
|
||||
<DownOutlined />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className={style["device-input-wrapper"]}>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
placeholder="选择采集设备"
|
||||
showInput={true}
|
||||
showSelectedList={false}
|
||||
singleSelect={true}
|
||||
className={style["device-input"]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* 隐藏的设备选择组件,用于打开弹窗 */}
|
||||
<div style={{ display: "none" }}>
|
||||
<DeviceSelection
|
||||
selectedOptions={selectedDevices}
|
||||
onSelect={handleDeviceSelect}
|
||||
placeholder="选择采集设备"
|
||||
showInput={false}
|
||||
showSelectedList={false}
|
||||
singleSelect={true}
|
||||
mode="dialog"
|
||||
open={deviceSelectionVisible}
|
||||
onOpenChange={setDeviceSelectionVisible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["section-title"]}>采集内容配置</div>
|
||||
<div className={style["form-card"]}>
|
||||
<Tabs
|
||||
activeKey={sourceType}
|
||||
onChange={key => setSourceType(key as "friends" | "groups")}
|
||||
@@ -203,6 +332,7 @@ export default function ContentForm() {
|
||||
selectedOptions={friendsGroupsOptions}
|
||||
onSelect={handleFriendsChange}
|
||||
placeholder="选择微信好友"
|
||||
deviceIds={selectedDevices.map(d => Number(d.id))}
|
||||
/>
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab title="选择聊天群" key="groups">
|
||||
@@ -215,10 +345,16 @@ export default function ContentForm() {
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<Collapse className={style["collapse"]}>
|
||||
<div className={style["form-card"]}>
|
||||
<Collapse className={style["keyword-collapse"]}>
|
||||
<Collapse.Panel
|
||||
key="keywords"
|
||||
title={<span className={style["form-label"]}>关键词设置</span>}
|
||||
title={
|
||||
<div className={style["keyword-header"]}>
|
||||
<span className={style["keyword-title"]}>关键词设置</span>
|
||||
<DownOutlined className={style["keyword-arrow"]} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={style["form-section"]}>
|
||||
<label className={style["form-label"]}>包含关键词</label>
|
||||
@@ -242,27 +378,20 @@ export default function ContentForm() {
|
||||
</div>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</div>
|
||||
|
||||
{/* 采集内容类型 */}
|
||||
<div className={style["section-title"]}>采集内容类型</div>
|
||||
<div className={style["form-section"]}>
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}>
|
||||
<div className={style["form-card"]}>
|
||||
<div className={style["content-type-header"]}>
|
||||
<span className={style["content-type-title"]}>采集内容类型</span>
|
||||
</div>
|
||||
<div className={style["content-type-buttons"]}>
|
||||
{["text", "image", "video"].map(type => (
|
||||
<div
|
||||
<button
|
||||
key={type}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
padding: "8px 12px",
|
||||
border: "1px solid #d9d9d9",
|
||||
borderRadius: "6px",
|
||||
backgroundColor: catchType.includes(type)
|
||||
? "#1890ff"
|
||||
: "#fff",
|
||||
color: catchType.includes(type) ? "#fff" : "#333",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className={`${style["content-type-btn"]} ${
|
||||
catchType.includes(type) ? style["active"] : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCatchType(prev =>
|
||||
prev.includes(type)
|
||||
@@ -271,26 +400,24 @@ export default function ContentForm() {
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{type === "text"
|
||||
? "文本"
|
||||
: type === "image"
|
||||
? "图片"
|
||||
: "视频"}
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["section-title"]}>是否启用AI</div>
|
||||
<div className={style["form-card"]}>
|
||||
<div
|
||||
className={style["form-section"]}
|
||||
style={{ display: "flex", alignItems: "center", gap: 12 }}
|
||||
>
|
||||
<Switch checked={useAI} onChange={setUseAI} />
|
||||
<span className={style["ai-desc"]}>
|
||||
启用AI后,该内容库下的所有内容都会通过AI生成
|
||||
启用后,该内容库下的内容会通过AI生成
|
||||
</span>
|
||||
</div>
|
||||
{useAI && (
|
||||
@@ -305,19 +432,24 @@ export default function ContentForm() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={style["section-title"]}>时间限制</div>
|
||||
<div
|
||||
className={style["form-section"]}
|
||||
style={{ display: "flex", gap: 12 }}
|
||||
>
|
||||
<label>开始时间</label>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className={style["form-card"]}>
|
||||
<div className={style["time-limit-header"]}>
|
||||
<span className={style["time-limit-title"]}>时间限制</span>
|
||||
</div>
|
||||
<div className={style["date-inputs"]}>
|
||||
<div className={style["date-item"]}>
|
||||
<label className={style["date-label"]}>开始时间</label>
|
||||
<AntdInput
|
||||
readOnly
|
||||
value={dateRange[0] ? dateRange[0].toLocaleDateString() : ""}
|
||||
value={
|
||||
dateRange[0]
|
||||
? `${dateRange[0].getFullYear()}/${String(dateRange[0].getMonth() + 1).padStart(2, "0")}/${String(dateRange[0].getDate()).padStart(2, "0")}`
|
||||
: ""
|
||||
}
|
||||
placeholder="年/月/日"
|
||||
className={style["input"]}
|
||||
className={style["date-input"]}
|
||||
onClick={() => setShowStartPicker(true)}
|
||||
/>
|
||||
<DatePicker
|
||||
@@ -331,13 +463,17 @@ export default function ContentForm() {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<label>结束时间</label>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className={style["date-item"]}>
|
||||
<label className={style["date-label"]}>结束时间</label>
|
||||
<AntdInput
|
||||
readOnly
|
||||
value={dateRange[1] ? dateRange[1].toLocaleDateString() : ""}
|
||||
value={
|
||||
dateRange[1]
|
||||
? `${dateRange[1].getFullYear()}/${String(dateRange[1].getMonth() + 1).padStart(2, "0")}/${String(dateRange[1].getDate()).padStart(2, "0")}`
|
||||
: ""
|
||||
}
|
||||
placeholder="年/月/日"
|
||||
className={style["input"]}
|
||||
className={style["date-input"]}
|
||||
onClick={() => setShowEndPicker(true)}
|
||||
/>
|
||||
<DatePicker
|
||||
@@ -350,21 +486,15 @@ export default function ContentForm() {
|
||||
setShowEndPicker(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={style["section-title"]}
|
||||
style={{
|
||||
marginTop: 24,
|
||||
marginBottom: 8,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<span>是否启用</span>
|
||||
<div className={style["form-card"]}>
|
||||
<div className={style["enable-section"]}>
|
||||
<span className={style["enable-label"]}>是否启用</span>
|
||||
<Switch checked={enabled} onChange={setEnabled} />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function getContentLibraryList(params: {
|
||||
keyword?: string;
|
||||
sourceType?: number;
|
||||
}): Promise<any> {
|
||||
return request("/v1/content/library/list", params, "GET");
|
||||
return request("/v1/content/library/list", { ...params, formType: 0 }, "GET");
|
||||
}
|
||||
|
||||
// 获取内容库详情
|
||||
@@ -24,7 +24,7 @@ export function getContentLibraryDetail(id: string): Promise<any> {
|
||||
export function createContentLibrary(
|
||||
params: CreateContentLibraryParams,
|
||||
): Promise<any> {
|
||||
return request("/v1/content/library/create", params, "POST");
|
||||
return request("/v1/content/library/create", { ...params, formType: 0 }, "POST");
|
||||
}
|
||||
|
||||
// 更新内容库
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
GetContentItemListParams,
|
||||
CreateContentItemParams,
|
||||
UpdateContentItemParams,
|
||||
AIRewriteParams,
|
||||
ReplaceContentParams,
|
||||
} from "./data";
|
||||
|
||||
// 获取素材列表
|
||||
@@ -35,3 +37,21 @@ export function deleteContentItem(id: string) {
|
||||
export function getContentLibraryDetail(id: string) {
|
||||
return request("/v1/content/library/detail", { id }, "GET");
|
||||
}
|
||||
|
||||
// AI改写内容
|
||||
export function aiRewriteContent(params: AIRewriteParams) {
|
||||
return request("/v1/content/library/aiEditContent", params, "GET");
|
||||
}
|
||||
|
||||
// 替换原内容
|
||||
export function replaceContent(params: ReplaceContentParams) {
|
||||
return request("/v1/content/library/aiEditContent", params, "POST");
|
||||
}
|
||||
|
||||
// 导入Excel素材
|
||||
export function importMaterialsFromExcel(params: {
|
||||
id: string;
|
||||
fileUrl: string;
|
||||
}) {
|
||||
return request("/v1/content/library/import-excel", params, "POST");
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface ContentItem {
|
||||
delTime: number;
|
||||
wechatChatroomId?: string | null;
|
||||
senderNickname: string;
|
||||
senderAvatar?: string | null;
|
||||
createMessageTime?: string | null;
|
||||
comment: string;
|
||||
sendTime: number;
|
||||
@@ -104,3 +105,15 @@ export interface UpdateContentItemParams
|
||||
extends Partial<CreateContentItemParams> {
|
||||
id: string;
|
||||
}
|
||||
|
||||
// AI改写参数
|
||||
export interface AIRewriteParams {
|
||||
id: string;
|
||||
aiPrompt: string;
|
||||
}
|
||||
|
||||
// 替换内容参数
|
||||
export interface ReplaceContentParams {
|
||||
id: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,25 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-icon-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.avatar-icon {
|
||||
@@ -613,3 +632,231 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
|
||||
// AI改写弹框样式
|
||||
.ai-popup-content {
|
||||
padding: 20px;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
background: #f9fbfd;
|
||||
|
||||
.ai-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e8f4ff;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #1677ff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-form {
|
||||
.ai-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.ai-form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #1677ff;
|
||||
margin-right: 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-result-description {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-submit {
|
||||
margin: 24px 0;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.ai-result-box {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e0f0ff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
min-height: 100px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.ai-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.ai-result-content {
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.ai-result-placeholder {
|
||||
color: #999;
|
||||
text-align: center;
|
||||
padding: 30px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.placeholder-icon {
|
||||
font-size: 28px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.placeholder-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-replace-action {
|
||||
margin-top: 20px;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
background: #1677ff;
|
||||
border-color: #1677ff;
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
color: #ffffff;
|
||||
|
||||
&:hover, &:focus {
|
||||
background: #4096ff;
|
||||
border-color: #4096ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导入弹窗样式
|
||||
.import-popup-content {
|
||||
padding: 20px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
background: #ffffff;
|
||||
|
||||
.import-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e8f4ff;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1677ff;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 18px;
|
||||
background: #1677ff;
|
||||
margin-right: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-form {
|
||||
.import-form-item {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.import-form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #1677ff;
|
||||
margin-right: 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-form-control {
|
||||
.import-tip {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.import-actions {
|
||||
margin-top: 24px;
|
||||
|
||||
button {
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
|
||||
&:first-child {
|
||||
box-shadow: 0 2px 6px rgba(22, 119, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { Toast, SpinLoading, Dialog, Card } from "antd-mobile";
|
||||
import { Input, Pagination, Button } from "antd";
|
||||
import { Toast, SpinLoading, Dialog, Card, Popup, TextArea } from "antd-mobile";
|
||||
import { Input, Pagination, Button, Spin } from "antd";
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
VideoCameraOutlined,
|
||||
FileTextOutlined,
|
||||
AppstoreOutlined,
|
||||
UploadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { getContentItemList, deleteContentItem } from "./api";
|
||||
import { getContentItemList, deleteContentItem, aiRewriteContent, replaceContent, importMaterialsFromExcel } from "./api";
|
||||
import FileUpload from "@/components/Upload/FileUpload";
|
||||
import { ContentItem } from "./data";
|
||||
import style from "./index.module.scss";
|
||||
|
||||
@@ -42,6 +44,19 @@ const MaterialsList: React.FC = () => {
|
||||
const [total, setTotal] = useState(0);
|
||||
const pageSize = 20;
|
||||
|
||||
// AI改写相关状态
|
||||
const [showAIRewritePopup, setShowAIRewritePopup] = useState(false);
|
||||
const [currentMaterial, setCurrentMaterial] = useState<ContentItem | null>(null);
|
||||
const [aiPrompt, setAiPrompt] = useState("");
|
||||
const [aiResult, setAiResult] = useState("");
|
||||
const [aiLoading, setAiLoading] = useState(false);
|
||||
const [replaceLoading, setReplaceLoading] = useState(false);
|
||||
|
||||
// 导入相关状态
|
||||
const [showImportPopup, setShowImportPopup] = useState(false);
|
||||
const [importFileUrl, setImportFileUrl] = useState<string>("");
|
||||
const [importLoading, setImportLoading] = useState(false);
|
||||
|
||||
// 获取素材列表
|
||||
const fetchMaterials = useCallback(async () => {
|
||||
if (!id) return;
|
||||
@@ -104,15 +119,139 @@ const MaterialsList: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleView = (materialId: number) => {
|
||||
// 可以跳转到素材详情页面或显示弹窗
|
||||
console.log("查看素材:", materialId);
|
||||
const handleAIRewrite = (material: ContentItem) => {
|
||||
setCurrentMaterial(material);
|
||||
setAiPrompt("重写这条朋友圈 要求: 1、原本的字数和意思不要修改超过10% 2、出现品牌名或个人名字就去除 3、适当的换行及加些表情点缀");
|
||||
setAiResult("");
|
||||
setShowAIRewritePopup(true);
|
||||
};
|
||||
|
||||
const handleSubmitAIRewrite = async () => {
|
||||
if (!currentMaterial) return;
|
||||
|
||||
try {
|
||||
setAiLoading(true);
|
||||
const response = await aiRewriteContent({
|
||||
id: currentMaterial.id.toString(),
|
||||
aiPrompt: aiPrompt
|
||||
});
|
||||
|
||||
setAiResult(response.contentAfter || "暂无改写结果");
|
||||
|
||||
// 可以在这里显示原内容和改写后内容的对比
|
||||
console.log("原内容:", response.contentFront);
|
||||
console.log("改写后内容:", response.contentAfter);
|
||||
} catch (error) {
|
||||
console.error("AI改写失败:", error);
|
||||
Toast.show({
|
||||
content: "AI改写失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setAiLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReplaceContent = async () => {
|
||||
if (!currentMaterial || !aiResult) return;
|
||||
|
||||
try {
|
||||
setReplaceLoading(true);
|
||||
await replaceContent({
|
||||
id: currentMaterial.id.toString(),
|
||||
content: aiResult
|
||||
});
|
||||
|
||||
Toast.show({
|
||||
content: "内容已成功替换",
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 刷新素材列表
|
||||
fetchMaterials();
|
||||
|
||||
// 关闭弹窗
|
||||
closeAIRewritePopup();
|
||||
} catch (error) {
|
||||
console.error("替换内容失败:", error);
|
||||
Toast.show({
|
||||
content: "替换内容失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setReplaceLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const closeAIRewritePopup = () => {
|
||||
setShowAIRewritePopup(false);
|
||||
setCurrentMaterial(null);
|
||||
setAiPrompt("");
|
||||
setAiResult("");
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchMaterials();
|
||||
};
|
||||
|
||||
// 处理导入文件上传
|
||||
const handleImportFileChange = (fileInfo: { fileName: string; fileUrl: string }) => {
|
||||
setImportFileUrl(fileInfo.fileUrl);
|
||||
};
|
||||
|
||||
// 执行导入
|
||||
const handleImport = async () => {
|
||||
if (!id) {
|
||||
Toast.show({
|
||||
content: "内容库ID不存在",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!importFileUrl) {
|
||||
Toast.show({
|
||||
content: "请先上传Excel文件",
|
||||
position: "top",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setImportLoading(true);
|
||||
await importMaterialsFromExcel({
|
||||
id: id,
|
||||
fileUrl: importFileUrl,
|
||||
});
|
||||
|
||||
Toast.show({
|
||||
content: "导入成功",
|
||||
position: "top",
|
||||
});
|
||||
|
||||
// 关闭弹窗并重置状态
|
||||
setShowImportPopup(false);
|
||||
setImportFileUrl("");
|
||||
|
||||
// 刷新素材列表
|
||||
fetchMaterials();
|
||||
} catch (error: unknown) {
|
||||
console.error("导入失败:", error);
|
||||
Toast.show({
|
||||
content: error instanceof Error ? error.message : "导入失败,请重试",
|
||||
position: "top",
|
||||
});
|
||||
} finally {
|
||||
setImportLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭导入弹窗
|
||||
const closeImportPopup = () => {
|
||||
setShowImportPopup(false);
|
||||
setImportFileUrl("");
|
||||
};
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
@@ -280,9 +419,18 @@ const MaterialsList: React.FC = () => {
|
||||
title="素材管理"
|
||||
backFn={() => navigate("/mine/content")}
|
||||
right={
|
||||
<Button type="primary" onClick={handleCreateNew}>
|
||||
<PlusOutlined /> 新建素材
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => setShowImportPopup(true)}
|
||||
style={{ marginRight: 8 }}
|
||||
>
|
||||
<UploadOutlined /> 导入
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleCreateNew}>
|
||||
<PlusOutlined /> 新建素材
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{/* 搜索栏 */}
|
||||
@@ -348,7 +496,23 @@ const MaterialsList: React.FC = () => {
|
||||
<div className={style["card-header"]}>
|
||||
<div className={style["avatar-section"]}>
|
||||
<div className={style["avatar"]}>
|
||||
<UserOutlined className={style["avatar-icon"]} />
|
||||
{material.senderAvatar ? (
|
||||
<img
|
||||
src={material.senderAvatar}
|
||||
alt="头像"
|
||||
className={style["avatar-img"]}
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = 'none';
|
||||
const nextElement = e.currentTarget.nextSibling as HTMLElement;
|
||||
if (nextElement) {
|
||||
nextElement.style.display = 'flex';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<div className={style["avatar-icon-wrapper"]} style={{display: material.senderAvatar ? 'none' : 'flex'}}>
|
||||
<UserOutlined className={style["avatar-icon"]}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["header-info"]}>
|
||||
<span className={style["creator-name"]}>
|
||||
@@ -381,7 +545,7 @@ const MaterialsList: React.FC = () => {
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleView(material.id)}
|
||||
onClick={() => handleAIRewrite(material)}
|
||||
className={style["action-btn"]}
|
||||
>
|
||||
<BarChartOutlined />
|
||||
@@ -402,6 +566,165 @@ const MaterialsList: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* AI改写弹框 */}
|
||||
<Popup
|
||||
visible={showAIRewritePopup}
|
||||
onMaskClick={closeAIRewritePopup}
|
||||
bodyStyle={{
|
||||
borderRadius: "16px 16px 0 0",
|
||||
maxHeight: "90vh",
|
||||
boxShadow: "0 -4px 12px rgba(0, 0, 0, 0.1)"
|
||||
}}
|
||||
>
|
||||
<div className={style["ai-popup-content"]}>
|
||||
<div className={style["ai-popup-header"]}>
|
||||
<h3>AI内容改写</h3>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={closeAIRewritePopup}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["ai-form"]}>
|
||||
{/* 提示词输入区 */}
|
||||
<div className={style["ai-form-item"]}>
|
||||
<div className={style["ai-form-label"]}>提示词</div>
|
||||
<div className={style["ai-form-control"]}>
|
||||
<TextArea
|
||||
placeholder="请输入提示词指导AI如何改写内容"
|
||||
value={aiPrompt}
|
||||
onChange={val => setAiPrompt(val)}
|
||||
rows={4}
|
||||
showCount
|
||||
maxLength={500}
|
||||
style={{
|
||||
border: "1px solid #d9e8ff",
|
||||
borderRadius: "8px",
|
||||
padding: "12px",
|
||||
fontSize: "14px"
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["ai-submit"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleSubmitAIRewrite}
|
||||
loading={aiLoading}
|
||||
disabled={aiLoading || !aiPrompt.trim()}
|
||||
>
|
||||
{aiLoading ? "生成中..." : "生成内容"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 改写结果区 */}
|
||||
<div className={style["ai-form-item"]}>
|
||||
<div className={style["ai-form-label"]}>改写结果</div>
|
||||
<div className={style["ai-result-description"]}>AI生成的内容将显示在下方区域</div>
|
||||
<div className={style["ai-result-box"]}>
|
||||
{aiLoading ? (
|
||||
<div className={style["ai-loading"]}>
|
||||
<Spin tip="AI正在思考中..." />
|
||||
</div>
|
||||
) : aiResult ? (
|
||||
<div className={style["ai-result-content"]}>
|
||||
{aiResult}
|
||||
</div>
|
||||
) : (
|
||||
<div className={style["ai-result-placeholder"]}>
|
||||
<div className={style["placeholder-icon"]}>✨</div>
|
||||
<div className={style["placeholder-text"]}>点击"生成内容"按钮获取AI改写结果</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 替换按钮 */}
|
||||
{aiResult && (
|
||||
<div className={style["ai-replace-action"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleReplaceContent}
|
||||
loading={replaceLoading}
|
||||
disabled={replaceLoading || !aiResult}
|
||||
>
|
||||
{replaceLoading ? "替换中..." : "替换原内容"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
|
||||
{/* 导入弹窗 */}
|
||||
<Popup
|
||||
visible={showImportPopup}
|
||||
onMaskClick={closeImportPopup}
|
||||
bodyStyle={{
|
||||
borderRadius: "16px 16px 0 0",
|
||||
maxHeight: "90vh",
|
||||
}}
|
||||
>
|
||||
<div className={style["import-popup-content"]}>
|
||||
<div className={style["import-popup-header"]}>
|
||||
<h3>导入素材</h3>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={closeImportPopup}
|
||||
>
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={style["import-form"]}>
|
||||
<div className={style["import-form-item"]}>
|
||||
<div className={style["import-form-label"]}>选择Excel文件</div>
|
||||
<div className={style["import-form-control"]}>
|
||||
<FileUpload
|
||||
value={importFileUrl}
|
||||
onChange={(url) => {
|
||||
const fileUrl = Array.isArray(url) ? url[0] : url;
|
||||
setImportFileUrl(fileUrl || "");
|
||||
}}
|
||||
acceptTypes={["excel"]}
|
||||
maxSize={50}
|
||||
maxCount={1}
|
||||
showPreview={false}
|
||||
/>
|
||||
<div className={style["import-tip"]}>
|
||||
请上传Excel格式的文件,文件大小不超过50MB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style["import-actions"]}>
|
||||
<Button
|
||||
block
|
||||
color="primary"
|
||||
onClick={handleImport}
|
||||
loading={importLoading}
|
||||
disabled={importLoading || !importFileUrl}
|
||||
>
|
||||
{importLoading ? "导入中..." : "确认导入"}
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
color="danger"
|
||||
fill="outline"
|
||||
onClick={closeImportPopup}
|
||||
style={{ marginTop: 12 }}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -42,3 +42,7 @@ export const fetchDeviceQRCode = (accountId: string) =>
|
||||
// 通过IMEI添加设备
|
||||
export const addDeviceByImei = (imei: string, name: string) =>
|
||||
request("/v1/api/device/add-by-imei", { imei, name }, "POST");
|
||||
|
||||
// 获取设备添加结果(用于轮询检查)
|
||||
export const fetchAddResults = (params: { accountId?: string }) =>
|
||||
request("/v1/devices/add-results", params, "GET");
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
fetchDeviceQRCode,
|
||||
addDeviceByImei,
|
||||
deleteDevice,
|
||||
fetchAddResults,
|
||||
} from "./api";
|
||||
import type { Device } from "@/types/device";
|
||||
import { comfirm } from "@/utils/common";
|
||||
@@ -44,12 +45,18 @@ const Devices: React.FC = () => {
|
||||
const [name, setName] = useState("");
|
||||
const [addLoading, setAddLoading] = useState(false);
|
||||
|
||||
// 轮询监听相关
|
||||
const [isPolling, setIsPolling] = useState(false);
|
||||
const pollingRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const loadDevicesRef = useRef<((reset?: boolean) => Promise<void>) | null>(null);
|
||||
|
||||
// 删除弹窗
|
||||
const [delVisible, setDelVisible] = useState(false);
|
||||
const [delLoading, setDelLoading] = useState(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { user } = useUserStore();
|
||||
|
||||
// 加载设备列表
|
||||
const loadDevices = useCallback(
|
||||
async (reset = false) => {
|
||||
@@ -74,6 +81,11 @@ const Devices: React.FC = () => {
|
||||
[loading, search, page],
|
||||
);
|
||||
|
||||
// 更新 loadDevices 的 ref
|
||||
useEffect(() => {
|
||||
loadDevicesRef.current = loadDevices;
|
||||
}, [loadDevices]);
|
||||
|
||||
// 首次加载和搜索
|
||||
useEffect(() => {
|
||||
loadDevices(true);
|
||||
@@ -110,6 +122,56 @@ const Devices: React.FC = () => {
|
||||
return true;
|
||||
});
|
||||
|
||||
// 开始轮询监听设备状态
|
||||
const startPolling = useCallback(() => {
|
||||
if (isPolling) return;
|
||||
|
||||
setIsPolling(true);
|
||||
|
||||
const pollDeviceStatus = async () => {
|
||||
try {
|
||||
const res = await fetchAddResults({ accountId: user?.s2_accountId });
|
||||
if (res.added) {
|
||||
Toast.show({ content: "设备添加成功!", position: "top" });
|
||||
setAddVisible(false);
|
||||
setIsPolling(false);
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current);
|
||||
pollingRef.current = null;
|
||||
}
|
||||
// 刷新设备列表
|
||||
if (loadDevicesRef.current) {
|
||||
await loadDevicesRef.current(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("轮询检查设备状态失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 每3秒检查一次设备状态
|
||||
pollingRef.current = setInterval(pollDeviceStatus, 3000);
|
||||
}, [isPolling, user?.s2_accountId]);
|
||||
|
||||
// 停止轮询
|
||||
const stopPolling = useCallback(() => {
|
||||
setIsPolling(false);
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current);
|
||||
pollingRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 组件卸载时清理轮询
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (pollingRef.current) {
|
||||
clearInterval(pollingRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 获取二维码
|
||||
const handleGetQr = async () => {
|
||||
setQrLoading(true);
|
||||
@@ -119,6 +181,8 @@ const Devices: React.FC = () => {
|
||||
if (!accountId) throw new Error("未获取到用户信息");
|
||||
const res = await fetchDeviceQRCode(accountId);
|
||||
setQrCode(res.qrCode);
|
||||
// 获取二维码后开始轮询监听
|
||||
startPolling();
|
||||
} catch (e: any) {
|
||||
Toast.show({ content: e.message || "获取二维码失败", position: "top" });
|
||||
} finally {
|
||||
@@ -362,7 +426,11 @@ const Devices: React.FC = () => {
|
||||
{/* 添加设备弹窗 */}
|
||||
<Popup
|
||||
visible={addVisible}
|
||||
onMaskClick={() => setAddVisible(false)}
|
||||
onMaskClick={() => {
|
||||
setAddVisible(false);
|
||||
stopPolling();
|
||||
setQrCode(null);
|
||||
}}
|
||||
bodyStyle={{
|
||||
borderTopLeftRadius: 16,
|
||||
borderTopRightRadius: 16,
|
||||
@@ -403,6 +471,13 @@ const Devices: React.FC = () => {
|
||||
<div style={{ color: "#888", fontSize: 12, marginTop: 8 }}>
|
||||
请用手机扫码添加设备
|
||||
</div>
|
||||
{isPolling && (
|
||||
<div
|
||||
style={{ color: "#1890ff", fontSize: 12, marginTop: 8 }}
|
||||
>
|
||||
正在监听设备添加状态...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import request from "@/api/request";
|
||||
export interface PowerPackage {
|
||||
id: number;
|
||||
name: string;
|
||||
tokens: number; // 算力点数
|
||||
tokens: number | string; // 算力点数(可能是字符串,如"2,800")
|
||||
price: number; // 价格(分)
|
||||
originalPrice: number; // 原价(分)
|
||||
unitPrice: number; // 单价
|
||||
@@ -13,7 +13,7 @@ export interface PowerPackage {
|
||||
isRecommend: number; // 是否推荐
|
||||
isHot: number; // 是否热门
|
||||
isVip: number; // 是否VIP
|
||||
features: string[]; // 功能特性
|
||||
features?: string[]; // 功能特性(可选)
|
||||
description: string[]; // 描述关键词
|
||||
status: number;
|
||||
createTime: string;
|
||||
|
||||
@@ -6,6 +6,9 @@ export interface Statistics {
|
||||
monthUsed: number; // 本月使用
|
||||
remainingTokens: number; // 剩余算力
|
||||
totalConsumed: number; // 总消耗
|
||||
yesterdayUsed?: number; // 昨日消耗
|
||||
historyConsumed?: number; // 历史消耗
|
||||
estimatedDays?: number; // 预计可用天数
|
||||
}
|
||||
// 算力统计接口
|
||||
export function getStatistics(): Promise<Statistics> {
|
||||
@@ -143,3 +146,56 @@ export function buyPackage(params: { id: number; price: number }) {
|
||||
export function buyCustomPower(params: { amount: number }) {
|
||||
return request("/v1/power/buy-custom", params, "POST");
|
||||
}
|
||||
|
||||
// 查询订单状态
|
||||
export interface QueryOrderResponse {
|
||||
id: number;
|
||||
mchId: number;
|
||||
companyId: number;
|
||||
userId: number;
|
||||
orderType: number;
|
||||
status: number; // 0: 待支付, 1: 已支付
|
||||
goodsId: number;
|
||||
goodsName: string;
|
||||
goodsSpecs: string;
|
||||
money: number;
|
||||
orderNo: string;
|
||||
payType: number | null;
|
||||
payTime: number | null;
|
||||
payInfo: any;
|
||||
createTime: number;
|
||||
}
|
||||
|
||||
export function queryOrder(orderNo: string): Promise<QueryOrderResponse> {
|
||||
return request("/v1/tokens/queryOrder", { orderNo }, "GET");
|
||||
}
|
||||
|
||||
// 账号信息
|
||||
export interface Account {
|
||||
id: number;
|
||||
uid?: number; // 用户ID(用于分配算力)
|
||||
userId?: number; // 用户ID(别名)
|
||||
userName: string;
|
||||
realName: string;
|
||||
nickname: string;
|
||||
departmentId: number;
|
||||
departmentName: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
// 获取账号列表
|
||||
export function getAccountList(): Promise<{ list: Account[]; total: number }> {
|
||||
return request("/v1/kefu/accounts/list", undefined, "GET");
|
||||
}
|
||||
|
||||
// 分配算力接口参数
|
||||
export interface AllocateTokensParams {
|
||||
targetUserId: number; // 目标用户ID
|
||||
tokens: number; // 分配的算力数量
|
||||
remarks?: string; // 备注
|
||||
}
|
||||
|
||||
// 分配算力
|
||||
export function allocateTokens(params: AllocateTokensParams): Promise<any> {
|
||||
return request("/v1/tokens/allocate", params, "POST");
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ export interface TokensUseRecordItem {
|
||||
|
||||
export interface TokensUseRecordList {
|
||||
list: TokensUseRecordItem[];
|
||||
total?: number;
|
||||
}
|
||||
|
||||
//算力使用明细
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
LogoutOutlined,
|
||||
SettingOutlined,
|
||||
LockOutlined,
|
||||
ReloadOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import Layout from "@/components/Layout/Layout";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
@@ -16,7 +15,7 @@ import { useSettingsStore } from "@/store/module/settings";
|
||||
import style from "./index.module.scss";
|
||||
import NavCommon from "@/components/NavCommon";
|
||||
import { sendMessageToParent, TYPE_EMUE } from "@/utils/postApp";
|
||||
import { updateChecker } from "@/utils/updateChecker";
|
||||
import { clearApplicationCache } from "@/utils/cacheCleaner";
|
||||
|
||||
interface SettingItem {
|
||||
id: string;
|
||||
@@ -58,13 +57,35 @@ const Setting: React.FC = () => {
|
||||
const handleClearCache = () => {
|
||||
Dialog.confirm({
|
||||
content: "确定要清除缓存吗?这将清除所有本地数据。",
|
||||
onConfirm: () => {
|
||||
sendMessageToParent(
|
||||
{
|
||||
action: "clearCache",
|
||||
},
|
||||
TYPE_EMUE.FUNCTION,
|
||||
);
|
||||
onConfirm: async () => {
|
||||
const handler = Toast.show({
|
||||
icon: "loading",
|
||||
content: "正在清理缓存...",
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await clearApplicationCache();
|
||||
sendMessageToParent(
|
||||
{
|
||||
action: "clearCache",
|
||||
},
|
||||
TYPE_EMUE.FUNCTION,
|
||||
);
|
||||
handler.close();
|
||||
Toast.show({
|
||||
icon: "success",
|
||||
content: "缓存清理完成",
|
||||
position: "top",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("clear cache failed", error);
|
||||
handler.close();
|
||||
Toast.show({
|
||||
icon: "fail",
|
||||
content: "缓存清理失败,请稍后再试",
|
||||
position: "top",
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -33,6 +49,113 @@ export function transferWechatFriends(params: {
|
||||
wechatId: string;
|
||||
devices: number[];
|
||||
inherit: boolean;
|
||||
greeting?: string;
|
||||
firstMessage?: string;
|
||||
}) {
|
||||
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,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// 检查响应类型,如果是JSON错误响应,需要解析错误信息
|
||||
const contentType = response.headers["content-type"] || "";
|
||||
if (contentType.includes("application/json")) {
|
||||
// 如果是JSON响应,说明可能是错误信息
|
||||
const text = await response.data.text();
|
||||
const errorData = JSON.parse(text);
|
||||
throw new Error(errorData.message || errorData.msg || "导出失败");
|
||||
}
|
||||
|
||||
// 检查响应状态
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`导出失败,状态码: ${response.status}`);
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
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) {
|
||||
// 如果是我们抛出的错误,直接抛出
|
||||
if (error.message && error.message !== "导出失败") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 处理axios错误响应
|
||||
if (error.response) {
|
||||
// 如果响应是blob类型,尝试读取为文本
|
||||
if (error.response.data instanceof Blob) {
|
||||
try {
|
||||
const text = await error.response.data.text();
|
||||
const errorData = JSON.parse(text);
|
||||
throw new Error(errorData.message || errorData.msg || "导出失败");
|
||||
} catch (parseError) {
|
||||
throw new Error("导出失败,请重试");
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
error.response.data?.message ||
|
||||
error.response.data?.msg ||
|
||||
error.message ||
|
||||
"导出失败"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw new Error(error.message || "导出失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -369,6 +369,7 @@ const ScenarioList: React.FC = () => {
|
||||
backFn={() => navigate("/scenarios")}
|
||||
title={scenarioName || ""}
|
||||
right={
|
||||
scenarioId !== "10" ? (
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
@@ -376,6 +377,7 @@ const ScenarioList: React.FC = () => {
|
||||
>
|
||||
<PlusOutlined /> 新建计划
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -424,6 +426,7 @@ const ScenarioList: React.FC = () => {
|
||||
<div className={style["empty-text"]}>
|
||||
{searchTerm ? "没有找到匹配的计划" : "暂无计划"}
|
||||
</div>
|
||||
{scenarioId !== "10" && (
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleCreateNewPlan}
|
||||
@@ -431,6 +434,7 @@ const ScenarioList: React.FC = () => {
|
||||
>
|
||||
<PlusOutlined /> 创建第一个计划
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -70,24 +70,72 @@ const PlanApi: React.FC<PlanApiProps> = ({
|
||||
|
||||
// 处理webhook URL,确保包含完整的API地址
|
||||
const fullWebhookUrl = useMemo(() => {
|
||||
return buildApiUrl(webhookUrl);
|
||||
return buildApiUrl('');
|
||||
}, [webhookUrl]);
|
||||
|
||||
// 生成测试URL
|
||||
// 快速测试使用的 GET 地址(携带示例查询参数,方便在浏览器中直接访问)
|
||||
const testUrl = useMemo(() => {
|
||||
if (!fullWebhookUrl) return "";
|
||||
return `${fullWebhookUrl}?name=测试客户&phone=13800138000&source=API测试`;
|
||||
}, [fullWebhookUrl]);
|
||||
return buildApiUrl(webhookUrl);
|
||||
}, [webhookUrl]);
|
||||
|
||||
// 检测是否为移动端
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
const handleCopy = (text: string, type: string) => {
|
||||
navigator.clipboard.writeText(text);
|
||||
Toast.show({
|
||||
content: `${type}已复制到剪贴板`,
|
||||
position: "top",
|
||||
});
|
||||
// 先尝试使用 Clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.then(() => {
|
||||
Toast.show({
|
||||
content: `${type}已复制到剪贴板`,
|
||||
position: "top",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 回退到传统的 textarea 复制方式
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-9999px";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
Toast.show({
|
||||
content: `${type}已复制到剪贴板`,
|
||||
position: "top",
|
||||
});
|
||||
} catch {
|
||||
Toast.show({
|
||||
content: `${type}复制失败,请手动复制`,
|
||||
position: "top",
|
||||
});
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
});
|
||||
} else {
|
||||
// 不支持 Clipboard API 时直接使用回退方案
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.left = "-9999px";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
Toast.show({
|
||||
content: `${type}已复制到剪贴板`,
|
||||
position: "top",
|
||||
});
|
||||
} catch {
|
||||
Toast.show({
|
||||
content: `${type}复制失败,请手动复制`,
|
||||
position: "top",
|
||||
});
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestInBrowser = () => {
|
||||
@@ -96,7 +144,7 @@ const PlanApi: React.FC<PlanApiProps> = ({
|
||||
|
||||
const renderConfigTab = () => (
|
||||
<div className={style["config-content"]}>
|
||||
{/* API密钥配置 */}
|
||||
{/* 鉴权参数配置 */}
|
||||
<div className={style["config-section"]}>
|
||||
<div className={style["section-header"]}>
|
||||
<div className={style["section-title"]}>
|
||||
@@ -122,7 +170,7 @@ const PlanApi: React.FC<PlanApiProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 接口地址配置 */}
|
||||
{/* 接口地址与参数说明 */}
|
||||
<div className={style["config-section"]}>
|
||||
<div className={style["section-header"]}>
|
||||
<div className={style["section-title"]}>
|
||||
@@ -150,27 +198,42 @@ const PlanApi: React.FC<PlanApiProps> = ({
|
||||
{/* 参数说明 */}
|
||||
<div className={style["params-grid"]}>
|
||||
<div className={style["param-section"]}>
|
||||
<h4>必要参数</h4>
|
||||
<h4>鉴权参数(必填)</h4>
|
||||
<div className={style["param-list"]}>
|
||||
<div>
|
||||
<code>name</code> - 客户姓名
|
||||
<code>apiKey</code> - 分配给第三方的接口密钥(每个任务唯一)
|
||||
</div>
|
||||
<div>
|
||||
<code>phone</code> - 手机号码
|
||||
<code>sign</code> - 签名值,按文档的签名规则生成
|
||||
</div>
|
||||
<div>
|
||||
<code>timestamp</code> - 秒级时间戳(与服务器时间差不超过 5 分钟)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={style["param-section"]}>
|
||||
<h4>可选参数</h4>
|
||||
<h4>业务参数</h4>
|
||||
<div className={style["param-list"]}>
|
||||
<div>
|
||||
<code>source</code> - 来源标识
|
||||
<code>wechatId</code> - 微信号,存在时优先作为主标识
|
||||
</div>
|
||||
<div>
|
||||
<code>phone</code> - 手机号,当 <code>wechatId</code> 为空时用作主标识
|
||||
</div>
|
||||
<div>
|
||||
<code>name</code> - 客户姓名
|
||||
</div>
|
||||
<div>
|
||||
<code>source</code> - 线索来源描述,如“百度推广”、“抖音直播间”
|
||||
</div>
|
||||
<div>
|
||||
<code>remark</code> - 备注信息
|
||||
</div>
|
||||
<div>
|
||||
<code>tags</code> - 客户标签
|
||||
<code>tags</code> - 微信标签,逗号分隔,如 <code>"高意向,电商,女装"</code>
|
||||
</div>
|
||||
<div>
|
||||
<code>siteTags</code> - 站内标签,逗号分隔,用于站内进一步细分
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -179,93 +242,131 @@ const PlanApi: React.FC<PlanApiProps> = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderQuickTestTab = () => (
|
||||
<div className={style["test-content"]}>
|
||||
<div className={style["test-section"]}>
|
||||
<h3>快速测试URL</h3>
|
||||
<div className={style["input-group"]}>
|
||||
<Input value={testUrl} disabled className={style["test-input"]} />
|
||||
</div>
|
||||
<div className={style["test-buttons"]}>
|
||||
<Button
|
||||
onClick={() => handleCopy(testUrl, "测试URL")}
|
||||
className={style["test-btn"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制测试URL
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleTestInBrowser}
|
||||
className={style["test-btn"]}
|
||||
>
|
||||
在浏览器中测试
|
||||
</Button>
|
||||
const renderQuickTestTab = () => {
|
||||
return (
|
||||
<div className={style["test-content"]}>
|
||||
<div className={style["test-section"]}>
|
||||
<h3>快速测试 URL(GET 示例)</h3>
|
||||
<div className={style["input-group"]}>
|
||||
<Input value={testUrl} disabled className={style["test-input"]} />
|
||||
</div>
|
||||
<div className={style["test-buttons"]}>
|
||||
<Button
|
||||
onClick={() => handleCopy(testUrl, "测试URL")}
|
||||
className={style["test-btn"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制测试URL
|
||||
</Button>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={handleTestInBrowser}
|
||||
className={style["test-btn"]}
|
||||
>
|
||||
在浏览器中测试
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const renderDocsTab = () => (
|
||||
<div className={style["docs-content"]}>
|
||||
<div className={style["docs-grid"]}>
|
||||
<Card className={style["doc-card"]}>
|
||||
<div className={style["doc-icon"]}>
|
||||
<BookOutlined />
|
||||
</div>
|
||||
<h4>完整API文档</h4>
|
||||
<p>详细的接口说明和参数文档</p>
|
||||
</Card>
|
||||
<Card className={style["doc-card"]}>
|
||||
<div className={style["doc-icon"]}>
|
||||
<LinkOutlined />
|
||||
</div>
|
||||
<h4>集成指南</h4>
|
||||
<p>第三方平台集成教程</p>
|
||||
</Card>
|
||||
const renderDocsTab = () => {
|
||||
const docUrl = `${import.meta.env.VITE_API_BASE_URL}/doc/api_v1.md`;
|
||||
|
||||
return (
|
||||
<div className={style["docs-content"]}>
|
||||
<div className={style["docs-grid"]}>
|
||||
<Card className={style["doc-card"]}>
|
||||
<div className={style["doc-icon"]}>
|
||||
<BookOutlined />
|
||||
</div>
|
||||
<h4>对外获客线索上报接口文档(V1)</h4>
|
||||
<p>点击下方按钮可直接在浏览器中打开最新的远程文档。</p>
|
||||
<div className={style["doc-actions"]}>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
window.open(docUrl, "_blank");
|
||||
}}
|
||||
className={style["doc-open-btn"]}
|
||||
>
|
||||
在浏览器中打开文档
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => handleCopy(docUrl, "文档链接")}
|
||||
className={style["doc-copy-btn"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制文档链接
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const renderCodeTab = () => {
|
||||
const codeExamples = {
|
||||
javascript: `fetch('${fullWebhookUrl}', {
|
||||
javascript: `// 参考 api_v1 文档示例,使用 JSON 方式 POST
|
||||
fetch('${fullWebhookUrl}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ${apiKey}'
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: '张三',
|
||||
apiKey: '${apiKey}',
|
||||
timestamp: 1710000000, // 秒级时间戳
|
||||
phone: '13800138000',
|
||||
name: '张三',
|
||||
source: '官网表单',
|
||||
remark: '通过H5表单提交',
|
||||
tags: '高意向,电商',
|
||||
siteTags: '新客,女装',
|
||||
// sign 需要根据签名规则生成
|
||||
sign: '根据签名规则生成的MD5字符串'
|
||||
})
|
||||
})`,
|
||||
python: `import requests
|
||||
|
||||
url = '${fullWebhookUrl}'
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ${apiKey}'
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
data = {
|
||||
'name': '张三',
|
||||
'phone': '13800138000',
|
||||
'source': '官网表单'
|
||||
"apiKey": "${apiKey}",
|
||||
"timestamp": 1710000000,
|
||||
"phone": "13800138000",
|
||||
"name": "张三",
|
||||
"source": "官网表单",
|
||||
"remark": "通过H5表单提交",
|
||||
"tags": "高意向,电商",
|
||||
"siteTags": "新客,女装",
|
||||
# sign 需要根据签名规则生成
|
||||
"sign": "根据签名规则生成的MD5字符串"
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)`,
|
||||
php: `<?php
|
||||
$url = '${fullWebhookUrl}';
|
||||
$data = array(
|
||||
'name' => '张三',
|
||||
'apiKey' => '${apiKey}',
|
||||
'timestamp' => 1710000000,
|
||||
'phone' => '13800138000',
|
||||
'source' => '官网表单'
|
||||
'name' => '张三',
|
||||
'source' => '官网表单',
|
||||
'remark' => '通过H5表单提交',
|
||||
'tags' => '高意向,电商',
|
||||
'siteTags' => '新客,女装',
|
||||
// sign 需要根据签名规则生成
|
||||
'sign' => '根据签名规则生成的MD5字符串'
|
||||
);
|
||||
|
||||
$options = array(
|
||||
'http' => array(
|
||||
'header' => "Content-type: application/json\\r\\nAuthorization: Bearer ${apiKey}\\r\\n",
|
||||
'header' => "Content-type: application/json\\r\\n",
|
||||
'method' => 'POST',
|
||||
'content' => json_encode($data)
|
||||
)
|
||||
@@ -279,12 +380,11 @@ import java.net.http.HttpResponse;
|
||||
import java.net.URI;
|
||||
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
String json = "{\\"name\\":\\"张三\\",\\"phone\\":\\"13800138000\\",\\"source\\":\\"官网表单\\"}";
|
||||
String json = "{\\"apiKey\\":\\"${apiKey}\\",\\"timestamp\\":1710000000,\\"phone\\":\\"13800138000\\",\\"name\\":\\"张三\\",\\"source\\":\\"官网表单\\",\\"remark\\":\\"通过H5表单提交\\",\\"tags\\":\\"高意向,电商\\",\\"siteTags\\":\\"新客,女装\\",\\"sign\\":\\"根据签名规则生成的MD5字符串\\"}";
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("${fullWebhookUrl}"))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer ${apiKey}")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(json))
|
||||
.build();
|
||||
|
||||
@@ -394,11 +494,7 @@ HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.o
|
||||
<SafetyOutlined />
|
||||
所有数据传输均采用HTTPS加密
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={onClose}
|
||||
className={style["complete-btn"]}
|
||||
>
|
||||
<Button color="primary" onClick={onClose} className={style["complete-btn"]}>
|
||||
完成配置
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
import React, { useImperativeHandle, forwardRef } from "react";
|
||||
import { Form, Card, Tabs } from "antd";
|
||||
import DeviceSelection from "@/components/DeviceSelection";
|
||||
import FriendSelection from "@/components/FriendSelection";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
|
||||
interface OwnerAdminSelectorProps {
|
||||
selectedOwners: DeviceSelectionItem[];
|
||||
selectedAdmins: FriendSelectionItem[];
|
||||
onNext: (data: {
|
||||
devices: string[];
|
||||
devicesOptions: DeviceSelectionItem[];
|
||||
admins: string[];
|
||||
adminsOptions: FriendSelectionItem[];
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface OwnerAdminSelectorRef {
|
||||
validate: () => Promise<boolean>;
|
||||
getValues: () => any;
|
||||
}
|
||||
|
||||
const OwnerAdminSelector = forwardRef<
|
||||
OwnerAdminSelectorRef,
|
||||
OwnerAdminSelectorProps
|
||||
>(({ selectedOwners, selectedAdmins, onNext }, ref) => {
|
||||
const [form] = Form.useForm();
|
||||
const [owners, setOwners] = React.useState<DeviceSelectionItem[]>(
|
||||
selectedOwners || []
|
||||
);
|
||||
const [admins, setAdmins] = React.useState<FriendSelectionItem[]>(
|
||||
selectedAdmins || []
|
||||
);
|
||||
|
||||
// 当外部传入的 selectedOwners 或 selectedAdmins 变化时,同步内部状态
|
||||
React.useEffect(() => {
|
||||
setOwners(selectedOwners || []);
|
||||
}, [selectedOwners]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setAdmins(selectedAdmins || []);
|
||||
}, [selectedAdmins]);
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
validate: async () => {
|
||||
// 验证群主和管理员
|
||||
if (owners.length === 0) {
|
||||
form.setFields([
|
||||
{
|
||||
name: "devices",
|
||||
errors: ["请选择一个群主"],
|
||||
},
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
if (owners.length > 1) {
|
||||
form.setFields([
|
||||
{
|
||||
name: "devices",
|
||||
errors: ["群主只能选择一个设备"],
|
||||
},
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
if (admins.length === 0) {
|
||||
form.setFields([
|
||||
{
|
||||
name: "admins",
|
||||
errors: ["请至少选择一个管理员"],
|
||||
},
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
// 清除错误
|
||||
form.setFields([
|
||||
{
|
||||
name: "devices",
|
||||
errors: [],
|
||||
},
|
||||
{
|
||||
name: "admins",
|
||||
errors: [],
|
||||
},
|
||||
]);
|
||||
return true;
|
||||
},
|
||||
getValues: () => {
|
||||
return {
|
||||
devices: owners.map(o => o.id.toString()),
|
||||
admins: admins.map(a => a.id.toString()),
|
||||
devicesOptions: owners,
|
||||
adminsOptions: admins,
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
// 群主选择(设备选择)
|
||||
const handleOwnersSelect = (selectedDevices: DeviceSelectionItem[]) => {
|
||||
const previousOwnerId = owners.length > 0 ? owners[0]?.id : null;
|
||||
const newOwnerId = selectedDevices.length > 0 ? selectedDevices[0]?.id : null;
|
||||
|
||||
// 当群主改变时,清空已选的管理员(因为筛选条件变了)
|
||||
const shouldClearAdmins = previousOwnerId !== newOwnerId;
|
||||
|
||||
setOwners(selectedDevices);
|
||||
const ownerIds = selectedDevices.map(d => d.id.toString());
|
||||
form.setFieldValue("devices", ownerIds);
|
||||
|
||||
if (shouldClearAdmins) {
|
||||
setAdmins([]);
|
||||
form.setFieldValue("admins", []);
|
||||
}
|
||||
|
||||
// 通知父组件数据变化
|
||||
onNext({
|
||||
devices: ownerIds,
|
||||
devicesOptions: selectedDevices,
|
||||
admins: shouldClearAdmins ? [] : admins.map(a => a.id.toString()),
|
||||
adminsOptions: shouldClearAdmins ? [] : admins,
|
||||
});
|
||||
};
|
||||
|
||||
// 管理员选择
|
||||
const handleAdminsSelect = (selectedFriends: FriendSelectionItem[]) => {
|
||||
setAdmins(selectedFriends);
|
||||
const adminIds = selectedFriends.map(f => f.id.toString());
|
||||
form.setFieldValue("admins", adminIds);
|
||||
// 通知父组件数据变化
|
||||
onNext({
|
||||
devices: owners.map(o => o.id.toString()),
|
||||
devicesOptions: owners,
|
||||
admins: adminIds,
|
||||
adminsOptions: selectedFriends,
|
||||
});
|
||||
};
|
||||
|
||||
const tabItems = [
|
||||
{
|
||||
key: "devices",
|
||||
label: `群主 (${owners.length})`,
|
||||
children: (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<p style={{ margin: "0 0 8px 0", color: "#666", fontSize: 14 }}>
|
||||
请选择一个群主(设备),该设备将作为新建群聊的群主
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="devices"
|
||||
validateStatus={owners.length === 0 || owners.length > 1 ? "error" : ""}
|
||||
help={
|
||||
owners.length === 0
|
||||
? "请选择一个群主(设备)"
|
||||
: owners.length > 1
|
||||
? "群主只能选择一个设备"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<DeviceSelection
|
||||
selectedOptions={owners}
|
||||
onSelect={handleOwnersSelect}
|
||||
placeholder="选择群主(设备)"
|
||||
singleSelect={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "admins",
|
||||
label: `管理员 (${admins.length})`,
|
||||
children: (
|
||||
<div>
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<p style={{ margin: "0 0 8px 0", color: "#666", fontSize: 14 }}>
|
||||
{owners.length === 0
|
||||
? "请先选择群主(设备),然后选择该设备下的好友作为管理员"
|
||||
: "请选择管理员,管理员将协助管理新建的群聊(仅显示所选设备下的好友)"}
|
||||
</p>
|
||||
</div>
|
||||
<Form.Item
|
||||
name="admins"
|
||||
validateStatus={admins.length === 0 ? "error" : ""}
|
||||
help={
|
||||
owners.length === 0
|
||||
? "请先选择群主(设备)"
|
||||
: admins.length === 0
|
||||
? "请至少选择一个管理员"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<FriendSelection
|
||||
selectedOptions={admins}
|
||||
onSelect={handleAdminsSelect}
|
||||
placeholder={owners.length === 0 ? "请先选择群主" : "选择管理员"}
|
||||
deviceIds={owners.length > 0 ? owners.map(d => d.id) : []}
|
||||
enableDeviceFilter={true}
|
||||
readonly={owners.length === 0}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
devices: (selectedOwners || []).map(item => item.id.toString()),
|
||||
admins: (selectedAdmins || []).map(item => item.id.toString()),
|
||||
}}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<h2 style={{ margin: 0, fontSize: 18, fontWeight: 600 }}>
|
||||
选择群主和管理员
|
||||
</h2>
|
||||
<p style={{ margin: "8px 0 0 0", color: "#666", fontSize: 14 }}>
|
||||
请选择一个群主(设备)和管理员(好友),他们将负责管理新建的群聊
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs items={tabItems} />
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
OwnerAdminSelector.displayName = "OwnerAdminSelector";
|
||||
|
||||
export default OwnerAdminSelector;
|
||||
@@ -7,24 +7,29 @@ import { createAutoGroup, updateAutoGroup, getAutoGroupDetail } from "./api";
|
||||
import { AutoGroupFormData, StepItem } from "./types";
|
||||
import StepIndicator from "@/components/StepIndicator";
|
||||
import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings";
|
||||
import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector";
|
||||
import OwnerAdminSelector, {
|
||||
OwnerAdminSelectorRef,
|
||||
} from "./components/OwnerAdminSelector";
|
||||
import PoolSelector, { PoolSelectorRef } from "./components/PoolSelector";
|
||||
import NavCommon from "@/components/NavCommon/index";
|
||||
import dayjs from "dayjs";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
|
||||
const steps: StepItem[] = [
|
||||
{ id: 1, title: "步骤 1", subtitle: "基础设置" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择设备" },
|
||||
{ id: 2, title: "步骤 2", subtitle: "选择群主和管理员" },
|
||||
{ id: 3, title: "步骤 3", subtitle: "选择流量池包" },
|
||||
];
|
||||
|
||||
const defaultForm: AutoGroupFormData = {
|
||||
name: "",
|
||||
type: 4,
|
||||
deviceGroups: [], // 设备组
|
||||
deviceGroupsOptions: [], // 设备组选项
|
||||
devices: [], // 群主ID列表
|
||||
devicesOptions: [], // 群主选项
|
||||
admins: [], // 管理员ID列表
|
||||
adminsOptions: [], // 管理员选项
|
||||
poolGroups: [], // 内容库
|
||||
poolGroupsOptions: [], // 内容库选项
|
||||
startTime: dayjs().format("HH:mm"), // 开始时间 (HH:mm)
|
||||
@@ -45,16 +50,15 @@ const AutoGroupForm: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [dataLoaded, setDataLoaded] = useState(!isEdit); // 非编辑模式直接标记为已加载
|
||||
const [formData, setFormData] = useState<AutoGroupFormData>(defaultForm);
|
||||
const [deviceGroupsOptions, setDeviceGroupsOptions] = useState<
|
||||
DeviceSelectionItem[]
|
||||
>([]);
|
||||
const [devicesOptions, setDevicesOptions] = useState<DeviceSelectionItem[]>([]);
|
||||
const [adminsOptions, setAdminsOptions] = useState<FriendSelectionItem[]>([]);
|
||||
const [poolGroupsOptions, setpoolGroupsOptions] = useState<
|
||||
PoolSelectionItem[]
|
||||
>([]);
|
||||
|
||||
// 创建子组件的ref
|
||||
const basicSettingsRef = useRef<BasicSettingsRef>(null);
|
||||
const deviceSelectorRef = useRef<DeviceSelectorRef>(null);
|
||||
const ownerAdminSelectorRef = useRef<OwnerAdminSelectorRef>(null);
|
||||
const poolSelectorRef = useRef<PoolSelectorRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,8 +68,10 @@ const AutoGroupForm: React.FC = () => {
|
||||
const updatedForm = {
|
||||
...defaultForm,
|
||||
name: res.name,
|
||||
deviceGroups: res.config.deviceGroups || [],
|
||||
deviceGroupsOptions: res.config.deviceGroupsOptions || [],
|
||||
devices: res.config.deviceGroups || res.config.devices || [], // 兼容deviceGroups和devices
|
||||
devicesOptions: res.config.deviceGroupsOptions || res.config.devicesOptions || [], // 兼容deviceGroupsOptions和devicesOptions
|
||||
admins: res.config.admins || [],
|
||||
adminsOptions: res.config.adminsOptions || [],
|
||||
poolGroups: res.config.poolGroups || [],
|
||||
poolGroupsOptions: res.config.poolGroupsOptions || [],
|
||||
startTime: res.config.startTime,
|
||||
@@ -80,7 +86,8 @@ const AutoGroupForm: React.FC = () => {
|
||||
id: res.id,
|
||||
};
|
||||
setFormData(updatedForm);
|
||||
setDeviceGroupsOptions(res.config.deviceGroupsOptions || []);
|
||||
setDevicesOptions(res.config.deviceGroupsOptions || res.config.devicesOptions || []); // 兼容deviceGroupsOptions和devicesOptions
|
||||
setAdminsOptions(res.config.adminsOptions || []);
|
||||
setpoolGroupsOptions(res.config.poolGroupsOptions || []);
|
||||
setDataLoaded(true); // 标记数据已加载
|
||||
});
|
||||
@@ -90,16 +97,20 @@ const AutoGroupForm: React.FC = () => {
|
||||
setFormData(prev => ({ ...prev, ...values }));
|
||||
};
|
||||
|
||||
// 设备组选择
|
||||
const handleDevicesChange = (data: {
|
||||
deviceGroups: string[];
|
||||
deviceGroupsOptions: DeviceSelectionItem[];
|
||||
// 群主和管理员选择
|
||||
const handleOwnerAdminChange = (data: {
|
||||
devices: string[];
|
||||
devicesOptions: DeviceSelectionItem[];
|
||||
admins: string[];
|
||||
adminsOptions: FriendSelectionItem[];
|
||||
}) => {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
deviceGroups: data.deviceGroups,
|
||||
devices: data.devices,
|
||||
admins: data.admins,
|
||||
}));
|
||||
setDeviceGroupsOptions(data.deviceGroupsOptions);
|
||||
setDevicesOptions(data.devicesOptions);
|
||||
setAdminsOptions(data.adminsOptions);
|
||||
};
|
||||
|
||||
// 流量池包选择
|
||||
@@ -116,8 +127,16 @@ const AutoGroupForm: React.FC = () => {
|
||||
Toast.show({ content: "请输入任务名称" });
|
||||
return;
|
||||
}
|
||||
if (formData.deviceGroups.length === 0) {
|
||||
Toast.show({ content: "请选择至少一个设备组" });
|
||||
if (formData.devices.length === 0) {
|
||||
Toast.show({ content: "请选择一个群主" });
|
||||
return;
|
||||
}
|
||||
if (formData.devices.length > 1) {
|
||||
Toast.show({ content: "群主只能选择一个设备" });
|
||||
return;
|
||||
}
|
||||
if (formData.admins.length === 0) {
|
||||
Toast.show({ content: "请至少选择一个管理员" });
|
||||
return;
|
||||
}
|
||||
if (formData.poolGroups.length === 0) {
|
||||
@@ -127,9 +146,13 @@ const AutoGroupForm: React.FC = () => {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// 构建提交数据,将devices映射为deviceGroups
|
||||
const { devices, devicesOptions, ...restFormData } = formData;
|
||||
const submitData = {
|
||||
...formData,
|
||||
deviceGroupsOptions: deviceGroupsOptions,
|
||||
...restFormData,
|
||||
deviceGroups: devices, // 设备ID数组,传输字段名为deviceGroups
|
||||
deviceGroupsOptions: devicesOptions, // 设备完整信息,传输字段名为deviceGroupsOptions
|
||||
adminsOptions: adminsOptions,
|
||||
poolGroupsOptions: poolGroupsOptions,
|
||||
};
|
||||
|
||||
@@ -173,8 +196,9 @@ const AutoGroupForm: React.FC = () => {
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// 调用 DeviceSelector 的表单校验
|
||||
isValid = (await deviceSelectorRef.current?.validate()) || false;
|
||||
// 调用 OwnerAdminSelector 的表单校验
|
||||
isValid =
|
||||
(await ownerAdminSelectorRef.current?.validate()) || false;
|
||||
if (isValid) {
|
||||
setCurrentStep(3);
|
||||
}
|
||||
@@ -217,10 +241,11 @@ const AutoGroupForm: React.FC = () => {
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<DeviceSelector
|
||||
ref={deviceSelectorRef}
|
||||
selectedDevices={deviceGroupsOptions}
|
||||
onNext={handleDevicesChange}
|
||||
<OwnerAdminSelector
|
||||
ref={ownerAdminSelectorRef}
|
||||
selectedOwners={devicesOptions}
|
||||
selectedAdmins={adminsOptions}
|
||||
onNext={handleOwnerAdminChange}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { PoolSelectionItem } from "@/components/PoolSelection/data";
|
||||
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
|
||||
import { FriendSelectionItem } from "@/components/FriendSelection/data";
|
||||
|
||||
// 自动建群表单数据类型定义
|
||||
export interface AutoGroupFormData {
|
||||
id?: string; // 任务ID
|
||||
type: number; // 任务类型
|
||||
name: string; // 任务名称
|
||||
deviceGroups: string[]; // 设备组
|
||||
deviceGroupsOptions: DeviceSelectionItem[]; // 设备组选项
|
||||
devices: string[]; // 群主ID列表(设备ID)
|
||||
devicesOptions: DeviceSelectionItem[]; // 群主选项(设备)
|
||||
admins: string[]; // 管理员ID列表(好友ID)
|
||||
adminsOptions: FriendSelectionItem[]; // 管理员选项(好友)
|
||||
poolGroups: string[]; // 流量池
|
||||
poolGroupsOptions: PoolSelectionItem[]; // 流量池选项
|
||||
startTime: string; // 开始时间 (YYYY-MM-DD HH:mm:ss)
|
||||
@@ -34,9 +37,13 @@ export const formValidationRules = {
|
||||
{ required: true, message: "请输入任务名称" },
|
||||
{ min: 2, max: 50, message: "任务名称长度应在2-50个字符之间" },
|
||||
],
|
||||
deviceGroups: [
|
||||
{ required: true, message: "请选择设备组" },
|
||||
{ type: "array", min: 1, message: "至少选择一个设备组" },
|
||||
devices: [
|
||||
{ required: true, message: "请选择群主" },
|
||||
{ type: "array", min: 1, max: 1, message: "群主只能选择一个设备" },
|
||||
],
|
||||
admins: [
|
||||
{ required: true, message: "请选择管理员" },
|
||||
{ type: "array", min: 1, message: "至少选择一个管理员" },
|
||||
],
|
||||
poolGroups: [
|
||||
{ required: true, message: "请选择内容库" },
|
||||
|
||||
@@ -116,20 +116,38 @@ const AutoLike: React.FC = () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const Res: any = await fetchAutoLikeTasks();
|
||||
// 直接就是任务数组,无需再解包
|
||||
const mappedTasks = Res?.list?.map((task: any) => ({
|
||||
...task,
|
||||
status: task.status || 2, // 默认为关闭状态
|
||||
deviceCount: task.deviceCount || 0,
|
||||
targetGroup: task.targetGroup || "全部好友",
|
||||
likeInterval: task.likeInterval || 60,
|
||||
maxLikesPerDay: task.maxLikesPerDay || 100,
|
||||
lastLikeTime: task.lastLikeTime || "暂无",
|
||||
createTime: task.createTime || "",
|
||||
updateTime: task.updateTime || "",
|
||||
todayLikeCount: task.todayLikeCount || 0,
|
||||
totalLikeCount: task.totalLikeCount || 0,
|
||||
}));
|
||||
// 数据在 data.list 中
|
||||
const taskList = Res?.data?.list || Res?.list || [];
|
||||
const mappedTasks = taskList.map((task: any) => {
|
||||
const config = task.config || {};
|
||||
const friends = config.friends || [];
|
||||
const devices = config.devices || [];
|
||||
|
||||
// 判断目标人群:如果 friends 为空或未设置,表示选择全部好友
|
||||
let targetGroup = "全部好友";
|
||||
if (friends.length > 0) {
|
||||
targetGroup = `${friends.length} 个好友`;
|
||||
}
|
||||
|
||||
return {
|
||||
id: task.id?.toString() || "",
|
||||
name: task.name || "",
|
||||
status: task.status === 1 ? 1 : 2, // 1: 开启, 2: 关闭
|
||||
deviceCount: devices.length,
|
||||
targetGroup: targetGroup,
|
||||
likeInterval: config.interval || 60,
|
||||
maxLikesPerDay: config.maxLikes || 100,
|
||||
lastLikeTime: task.lastLikeTime || "暂无",
|
||||
createTime: task.createTime || "",
|
||||
updateTime: task.updateTime || "",
|
||||
todayLikeCount: config.todayLikeCount || 0,
|
||||
totalLikeCount: config.totalLikeCount || 0,
|
||||
// 保留原始数据
|
||||
config: config,
|
||||
devices: devices,
|
||||
friends: friends,
|
||||
};
|
||||
});
|
||||
setTasks(mappedTasks);
|
||||
} catch (error) {
|
||||
console.error("获取自动点赞任务失败:", error);
|
||||
@@ -355,7 +373,7 @@ const AutoLike: React.FC = () => {
|
||||
/>
|
||||
<span className={style["stats-label"]}>今日点赞:</span>
|
||||
<span className={style["stats-value"]}>
|
||||
{task.lastLikeTime}
|
||||
{task.todayLikeCount || 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style["stats-item"]}>
|
||||
|
||||
@@ -36,6 +36,7 @@ const NewAutoLike: React.FC = () => {
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(isEditMode);
|
||||
const [autoEnabled, setAutoEnabled] = useState(false);
|
||||
const [selectAllFriends, setSelectAllFriends] = useState(false);
|
||||
const [formData, setFormData] = useState<CreateLikeTaskData>({
|
||||
name: "",
|
||||
interval: 5,
|
||||
@@ -45,8 +46,8 @@ const NewAutoLike: React.FC = () => {
|
||||
contentTypes: ["text", "image", "video"],
|
||||
deviceGroups: [],
|
||||
deviceGroupsOptions: [],
|
||||
friendsGroups: [],
|
||||
friendsGroupsOptions: [],
|
||||
wechatFriends: [],
|
||||
wechatFriendsOptions: [],
|
||||
targetTags: [],
|
||||
friendMaxLikes: 10,
|
||||
enableFriendTags: false,
|
||||
@@ -74,8 +75,8 @@ const NewAutoLike: React.FC = () => {
|
||||
contentTypes: config.contentTypes || ["text", "image", "video"],
|
||||
deviceGroups: config.deviceGroups || [],
|
||||
deviceGroupsOptions: config.deviceGroupsOptions || [],
|
||||
friendsGroups: config.friendsgroups || [],
|
||||
friendsGroupsOptions: config.friendsGroupsOptions || [],
|
||||
wechatFriends: config.wechatFriends || [],
|
||||
wechatFriendsOptions: config.wechatFriendsOptions || [],
|
||||
targetTags: config.targetTags || [],
|
||||
friendMaxLikes: config.friendMaxLikes || 10,
|
||||
enableFriendTags: config.enableFriendTags || false,
|
||||
@@ -85,6 +86,10 @@ const NewAutoLike: React.FC = () => {
|
||||
(taskDetail as any).status === 1 ||
|
||||
(taskDetail as any).status === "running",
|
||||
);
|
||||
// 如果 wechatFriends 为空或未设置,可能表示选择了全部好友
|
||||
setSelectAllFriends(
|
||||
!config.wechatFriends || config.wechatFriends.length === 0
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error("获取任务详情失败");
|
||||
@@ -127,11 +132,19 @@ const NewAutoLike: React.FC = () => {
|
||||
}
|
||||
setIsSubmitting(true);
|
||||
try {
|
||||
// 如果选择了全部好友,提交时传空数组或特殊标识
|
||||
const submitData = {
|
||||
...formData,
|
||||
wechatFriends: selectAllFriends ? [] : formData.wechatFriends,
|
||||
wechatFriendsOptions: selectAllFriends ? [] : formData.wechatFriendsOptions,
|
||||
selectAllFriends: selectAllFriends, // 添加标识字段
|
||||
};
|
||||
|
||||
if (isEditMode) {
|
||||
await updateAutoLikeTask({ ...formData, id });
|
||||
await updateAutoLikeTask({ ...submitData, id });
|
||||
message.success("更新成功");
|
||||
} else {
|
||||
await createAutoLikeTask(formData);
|
||||
await createAutoLikeTask(submitData);
|
||||
message.success("创建成功");
|
||||
}
|
||||
navigate("/workspace/auto-like");
|
||||
@@ -142,6 +155,28 @@ const NewAutoLike: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 选择全部好友(仅设置标识)
|
||||
const handleSelectAllFriends = () => {
|
||||
if (!formData.deviceGroups || formData.deviceGroups.length === 0) {
|
||||
message.warning("请先选择执行设备");
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectAllFriends) {
|
||||
// 取消全选标识
|
||||
setSelectAllFriends(false);
|
||||
// 清空已选好友
|
||||
handleUpdateFormData({
|
||||
wechatFriends: [],
|
||||
wechatFriendsOptions: [],
|
||||
});
|
||||
} else {
|
||||
// 设置全选标识
|
||||
setSelectAllFriends(true);
|
||||
message.success("已标记为选择全部好友");
|
||||
}
|
||||
};
|
||||
|
||||
// 步骤器
|
||||
const renderStepIndicator = () => (
|
||||
<StepIndicator steps={steps} currentStep={currentStep} />
|
||||
@@ -364,16 +399,39 @@ const NewAutoLike: React.FC = () => {
|
||||
const renderFriendSettings = () => (
|
||||
<div className={style.basicSection}>
|
||||
<div className={style.formItem}>
|
||||
<FriendSelection
|
||||
selectedOptions={formData.friendsGroupsOptions || []}
|
||||
onSelect={friends =>
|
||||
handleUpdateFormData({
|
||||
friendsGroups: friends.map(f => f.id),
|
||||
friendsGroupsOptions: friends,
|
||||
})
|
||||
}
|
||||
deviceIds={formData.deviceGroups}
|
||||
/>
|
||||
<div className={style.friendSelectionHeader}>
|
||||
<div className={style.formLabel}>选择好友</div>
|
||||
<Button
|
||||
type={selectAllFriends ? "primary" : "default"}
|
||||
size="small"
|
||||
onClick={handleSelectAllFriends}
|
||||
disabled={!formData.deviceGroups || formData.deviceGroups.length === 0}
|
||||
className={style.selectAllBtn}
|
||||
>
|
||||
{selectAllFriends ? "已选择全部" : "选择全部好友"}
|
||||
</Button>
|
||||
</div>
|
||||
{selectAllFriends ? (
|
||||
<div className={style.selectAllTip}>
|
||||
<span className={style.selectAllIcon}>✓</span>
|
||||
已标记为选择全部好友
|
||||
</div>
|
||||
) : (
|
||||
<FriendSelection
|
||||
selectedOptions={formData.wechatFriendsOptions || []}
|
||||
onSelect={friends => {
|
||||
handleUpdateFormData({
|
||||
wechatFriends: friends.map(f => f.id),
|
||||
wechatFriendsOptions: friends,
|
||||
});
|
||||
// 如果手动选择了好友,取消全选标识
|
||||
if (selectAllFriends) {
|
||||
setSelectAllFriends(false);
|
||||
}
|
||||
}}
|
||||
deviceIds={formData.deviceGroups}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handlePrev}
|
||||
@@ -390,7 +448,8 @@ const NewAutoLike: React.FC = () => {
|
||||
size="large"
|
||||
loading={isSubmitting}
|
||||
disabled={
|
||||
!formData.friendsGroups || formData.friendsGroups.length === 0
|
||||
!selectAllFriends &&
|
||||
(!formData.wechatFriends || formData.wechatFriends.length === 0)
|
||||
}
|
||||
>
|
||||
{isEditMode ? "更新任务" : "创建任务"}
|
||||
|
||||
@@ -126,6 +126,13 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.contentTypeBtn {
|
||||
flex: 1;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.contentTypeTag {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
@@ -230,3 +237,50 @@
|
||||
font-size: 15px;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.friendSelectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.selectAllBtn {
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.selectAllTip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: #f0f9ff;
|
||||
border: 1px solid #91d5ff;
|
||||
border-radius: 8px;
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.selectAllIcon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.mainBtn {
|
||||
height: 44px;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ const NewMomentsSync: React.FC = () => {
|
||||
if (res) {
|
||||
setFormData({
|
||||
taskName: res.name,
|
||||
startTime: res.timeRange?.start || "06:00",
|
||||
endTime: res.timeRange?.end || "23:59",
|
||||
startTime: res.config?.startTime || "06:00",
|
||||
endTime: res.config?.endTime || "23:59",
|
||||
syncCount: res.config?.syncCount || res.syncCount || 5,
|
||||
syncInterval: res.config?.syncInterval || res.syncInterval || 30,
|
||||
syncType: res.config?.syncType,
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
export * from "./module/user";
|
||||
export * from "./module/app";
|
||||
export * from "./module/settings";
|
||||
export * from "./module/websocket/websocket";
|
||||
|
||||
// 导入store实例
|
||||
import { useUserStore } from "./module/user";
|
||||
import { useAppStore } from "./module/app";
|
||||
import { useSettingsStore } from "./module/settings";
|
||||
import { useWebSocketStore } from "./module/websocket/websocket";
|
||||
|
||||
// 导出持久化store创建函数
|
||||
export {
|
||||
@@ -34,7 +32,6 @@ export interface StoreState {
|
||||
user: ReturnType<typeof useUserStore.getState>;
|
||||
app: ReturnType<typeof useAppStore.getState>;
|
||||
settings: ReturnType<typeof useSettingsStore.getState>;
|
||||
websocket: ReturnType<typeof useWebSocketStore.getState>;
|
||||
}
|
||||
|
||||
// 便利的store访问函数
|
||||
@@ -42,14 +39,12 @@ export const getStores = (): StoreState => ({
|
||||
user: useUserStore.getState(),
|
||||
app: useAppStore.getState(),
|
||||
settings: useSettingsStore.getState(),
|
||||
websocket: useWebSocketStore.getState(),
|
||||
});
|
||||
|
||||
// 获取特定store状态
|
||||
export const getUserStore = () => useUserStore.getState();
|
||||
export const getAppStore = () => useAppStore.getState();
|
||||
export const getSettingsStore = () => useSettingsStore.getState();
|
||||
export const getWebSocketStore = () => useWebSocketStore.getState();
|
||||
|
||||
// 清除所有持久化数据(使用工具函数)
|
||||
export const clearAllPersistedData = clearAllData;
|
||||
@@ -61,7 +56,6 @@ export const getPersistKeys = () => Object.values(PERSIST_KEYS);
|
||||
export const subscribeToUserStore = useUserStore.subscribe;
|
||||
export const subscribeToAppStore = useAppStore.subscribe;
|
||||
export const subscribeToSettingsStore = useSettingsStore.subscribe;
|
||||
export const subscribeToWebSocketStore = useWebSocketStore.subscribe;
|
||||
|
||||
// 组合订阅函数
|
||||
export const subscribeToAllStores = (callback: (state: StoreState) => void) => {
|
||||
@@ -74,14 +68,10 @@ export const subscribeToAllStores = (callback: (state: StoreState) => void) => {
|
||||
const unsubscribeSettings = useSettingsStore.subscribe(() => {
|
||||
callback(getStores());
|
||||
});
|
||||
const unsubscribeWebSocket = useWebSocketStore.subscribe(() => {
|
||||
callback(getStores());
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribeUser();
|
||||
unsubscribeApp();
|
||||
unsubscribeSettings();
|
||||
unsubscribeWebSocket();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -33,14 +33,15 @@ export const getFullApiPath = (): string => {
|
||||
* - buildApiUrl('https://api.example.com/webhook/123') → 'https://api.example.com/webhook/123'
|
||||
*/
|
||||
export const buildApiUrl = (path: string): string => {
|
||||
if (!path) return "";
|
||||
const fullApiPath = getFullApiPath();
|
||||
|
||||
if (!path) return `${fullApiPath}`;
|
||||
|
||||
// 如果已经是完整的URL(包含http或https),直接返回
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
||||
return path;
|
||||
}
|
||||
|
||||
const fullApiPath = getFullApiPath();
|
||||
|
||||
// 如果是相对路径,拼接完整API路径
|
||||
if (path.startsWith("/")) {
|
||||
|
||||
70
Cunkebao/src/utils/cacheCleaner.ts
Normal file
70
Cunkebao/src/utils/cacheCleaner.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// 全局缓存清理工具:浏览器存储 + IndexedDB + Zustand store
|
||||
import { clearAllPersistedData } from "@/store";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useAppStore } from "@/store/module/app";
|
||||
import { useSettingsStore } from "@/store/module/settings";
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
const safeStorageClear = (storage?: Storage) => {
|
||||
if (!storage) return;
|
||||
try {
|
||||
storage.clear();
|
||||
} catch (error) {
|
||||
console.warn("清理存储失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearBrowserStorage = () => {
|
||||
if (!isBrowser) return;
|
||||
safeStorageClear(window.localStorage);
|
||||
safeStorageClear(window.sessionStorage);
|
||||
try {
|
||||
clearAllPersistedData();
|
||||
} catch (error) {
|
||||
console.warn("清理持久化 store 失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearAllIndexedDB = async (): Promise<void> => {
|
||||
if (!isBrowser || !window.indexedDB || !indexedDB.databases) return;
|
||||
|
||||
const databases = await indexedDB.databases();
|
||||
const deleteJobs = databases
|
||||
.map(db => db.name)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
.map(
|
||||
name =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(name);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(new Error(`删除数据库 ${name} 失败`));
|
||||
request.onblocked = () => {
|
||||
setTimeout(() => {
|
||||
const retry = indexedDB.deleteDatabase(name);
|
||||
retry.onsuccess = () => resolve();
|
||||
retry.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
}, 100);
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.allSettled(deleteJobs);
|
||||
};
|
||||
|
||||
export const resetAllStores = () => {
|
||||
const userStore = useUserStore.getState();
|
||||
const appStore = useAppStore.getState();
|
||||
const settingsStore = useSettingsStore.getState();
|
||||
|
||||
userStore?.clearUser?.();
|
||||
appStore?.resetAppState?.();
|
||||
settingsStore?.resetSettings?.();
|
||||
};
|
||||
|
||||
export const clearApplicationCache = async () => {
|
||||
clearBrowserStorage();
|
||||
await clearAllIndexedDB();
|
||||
resetAllStores();
|
||||
};
|
||||
11
Moncter/MCP/mcp.json
Normal file
11
Moncter/MCP/mcp.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"MongoDB": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mongodb-mcp-server@latest", "--readOnly"],
|
||||
"env": {
|
||||
"MDB_MCP_CONNECTION_STRING": "mongodb://ckb:123456@192.168.1.106:27017/ckb"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Moncter/MCP/数据库账户密码.md
Normal file
15
Moncter/MCP/数据库账户密码.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# 账户密码
|
||||
```
|
||||
HOST 192.168.1.106
|
||||
PORT 27017
|
||||
ACCOUNT:ckb
|
||||
DBNAME:ckb
|
||||
PASSWROD:123456
|
||||
```
|
||||
|
||||
==================
|
||||
# 环境准备
|
||||
需要安装一个全局环境否则无法运行
|
||||
node版本为22.12.0
|
||||
|
||||
npm i mongodb-mcp-server -g
|
||||
@@ -1,147 +0,0 @@
|
||||
.chatFooter {
|
||||
background: #f7f7f7;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.inputContainer {
|
||||
padding: 8px 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.inputToolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.leftTool {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbarButton {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
transition: all 0.15s;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: #e6e6e6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
}
|
||||
|
||||
.inputArea {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.inputWrapper {
|
||||
border: 1px solid #d1d1d1;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus-within {
|
||||
border-color: #07c160;
|
||||
}
|
||||
}
|
||||
|
||||
.messageInput {
|
||||
width: 100%;
|
||||
border: none;
|
||||
resize: none;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
padding: 8px 10px;
|
||||
background: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: #b3b3b3;
|
||||
}
|
||||
}
|
||||
|
||||
.sendButtonArea {
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sendButton {
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
font-weight: normal;
|
||||
min-width: 60px;
|
||||
font-size: 13px;
|
||||
background: #07c160;
|
||||
border-color: #07c160;
|
||||
|
||||
&:hover {
|
||||
background: #06ad56;
|
||||
border-color: #06ad56;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #059748;
|
||||
border-color: #059748;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: #b3b3b3;
|
||||
border-color: #b3b3b3;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.hintButton {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.inputHint {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
text-align: right;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.inputToolbar {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sendButtonArea {
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
.stepContent {
|
||||
.stepHeader {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step3Content {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
align-items: flex-start;
|
||||
|
||||
.leftColumn {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
width: 400px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.messagePreview {
|
||||
border: 2px dashed #52c41a;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #f6ffed;
|
||||
|
||||
.previewTitle {
|
||||
font-size: 14px;
|
||||
color: #52c41a;
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.messageBubble {
|
||||
min-height: 60px;
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
|
||||
.currentEditingLabel {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.messageText {
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.savedScriptGroups {
|
||||
.scriptGroupTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.scriptGroupItem {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
background: #fff;
|
||||
|
||||
.scriptGroupHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.scriptGroupLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
|
||||
:global(.ant-radio) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.scriptGroupName {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.messageCount {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.scriptGroupActions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
|
||||
.actionButton {
|
||||
padding: 4px;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scriptGroupContent {
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.messageInputArea {
|
||||
.messageInput {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.attachmentButtons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aiRewriteSection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.messageHint {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.settingsPanel {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #fafafa;
|
||||
|
||||
.settingsTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.settingItem {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.settingLabel {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settingControl {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tagSection {
|
||||
.settingLabel {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.pushPreview {
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
background: #f0f7ff;
|
||||
|
||||
.previewTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.step3Content {
|
||||
.rightColumn {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.step3Content {
|
||||
flex-direction: column;
|
||||
|
||||
.leftColumn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rightColumn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import ContentSelection from "@/components/ContentSelection";
|
||||
import { ContentItem } from "@/components/ContentSelection/data";
|
||||
import InputMessage from "./InputMessage/InputMessage";
|
||||
import styles from "./index.module.scss";
|
||||
|
||||
interface StepSendMessageProps {
|
||||
73
Moncter/src/utils/cacheCleaner.ts
Normal file
73
Moncter/src/utils/cacheCleaner.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// 缓存清理工具,统一处理浏览器存储与 Zustand store
|
||||
import { clearAllPersistedData } from "@/store";
|
||||
import { useUserStore } from "@/store/module/user";
|
||||
import { useAppStore } from "@/store/module/app";
|
||||
import { useSettingsStore } from "@/store/module/settings";
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
const safeStorageClear = (storage?: Storage) => {
|
||||
if (!storage) return;
|
||||
try {
|
||||
storage.clear();
|
||||
} catch (error) {
|
||||
console.warn("清理存储失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearBrowserStorage = () => {
|
||||
if (!isBrowser) return;
|
||||
safeStorageClear(window.localStorage);
|
||||
safeStorageClear(window.sessionStorage);
|
||||
// 清理自定义持久化数据
|
||||
try {
|
||||
clearAllPersistedData();
|
||||
} catch (error) {
|
||||
console.warn("清理持久化 store 失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const clearAllIndexedDB = async (): Promise<void> => {
|
||||
if (!isBrowser || !window.indexedDB || !indexedDB.databases) return;
|
||||
|
||||
const databases = await indexedDB.databases();
|
||||
const deleteJobs = databases
|
||||
.map(db => db.name)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
.map(
|
||||
name =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
const request = indexedDB.deleteDatabase(name);
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
request.onblocked = () => {
|
||||
setTimeout(() => {
|
||||
const retry = indexedDB.deleteDatabase(name);
|
||||
retry.onsuccess = () => resolve();
|
||||
retry.onerror = () =>
|
||||
reject(new Error(`删除数据库 ${name} 失败`));
|
||||
}, 100);
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
await Promise.allSettled(deleteJobs);
|
||||
};
|
||||
|
||||
export const resetAllStores = () => {
|
||||
const userStore = useUserStore.getState();
|
||||
const appStore = useAppStore.getState();
|
||||
const settingsStore = useSettingsStore.getState();
|
||||
|
||||
userStore?.clearUser?.();
|
||||
appStore?.resetAppState?.();
|
||||
settingsStore?.resetSettings?.();
|
||||
};
|
||||
|
||||
export const clearApplicationCache = async () => {
|
||||
clearBrowserStorage();
|
||||
await clearAllIndexedDB();
|
||||
resetAllStores();
|
||||
};
|
||||
|
||||
3
Moncter/weChat.ts
Normal file
3
Moncter/weChat.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
|
||||
374
Moncter/数据库/KR.js
Normal file
374
Moncter/数据库/KR.js
Normal file
@@ -0,0 +1,374 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:11
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门京东内
|
||||
// ----------------------------
|
||||
db.getCollection("厦门京东内").drop();
|
||||
db.createCollection("厦门京东内");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门用户资产2025年9月
|
||||
// ----------------------------
|
||||
db.getCollection("厦门用户资产2025年9月").drop();
|
||||
db.createCollection("厦门用户资产2025年9月");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 厦门用户资产2025年9月_优化版
|
||||
// ----------------------------
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").drop();
|
||||
db.createCollection("厦门用户资产2025年9月_优化版");
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
user_level: NumberInt("1")
|
||||
}, {
|
||||
name: "user_level_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
total_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "total_score_-1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("厦门用户资产2025年9月_优化版").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 游戏_深度用户有过语音聊天、魔兽等
|
||||
// ----------------------------
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").drop();
|
||||
db.createCollection("游戏_深度用户有过语音聊天、魔兽等");
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
user_id: NumberInt("1")
|
||||
}, {
|
||||
name: "user_id_1"
|
||||
});
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
user_value: NumberInt("1")
|
||||
}, {
|
||||
name: "user_value_1"
|
||||
});
|
||||
db.getCollection("游戏_深度用户有过语音聊天、魔兽等").createIndex({
|
||||
overlap_count: NumberInt("1")
|
||||
}, {
|
||||
name: "overlap_count_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户估值
|
||||
// ----------------------------
|
||||
db.getCollection("用户估值").drop();
|
||||
db.createCollection("用户估值");
|
||||
db.getCollection("用户估值").createIndex({
|
||||
user_key: NumberInt("1")
|
||||
}, {
|
||||
name: "user_key_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
phone_masked: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_masked_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
computed_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "computed_at_-1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
source_channels: NumberInt("1")
|
||||
}, {
|
||||
name: "source_channels_1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
"data_quality.completeness": NumberInt("-1")
|
||||
}, {
|
||||
name: "data_quality.completeness_-1"
|
||||
});
|
||||
db.getCollection("用户估值").createIndex({
|
||||
user_evaluation_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_evaluation_score_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户资产整合
|
||||
// ----------------------------
|
||||
db.getCollection("用户资产整合").drop();
|
||||
db.createCollection("用户资产整合");
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
user_key: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_user_key"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_phone"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_level: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_consumption_level"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_consumption_score_desc"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_city"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
age_range: NumberInt("1")
|
||||
}, {
|
||||
name: "idx_age_range"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
user_evaluation_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_user_evaluation_score_desc"
|
||||
});
|
||||
db.getCollection("用户资产整合").createIndex({
|
||||
consumption_level: NumberInt("1"),
|
||||
consumption_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "idx_level_score"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_A级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_A级用户").drop();
|
||||
db.createCollection("金融客户_厦门_A级用户");
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_A级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_B级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_B级用户").drop();
|
||||
db.createCollection("金融客户_厦门_B级用户");
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_B级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_C级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_C级用户").drop();
|
||||
db.createCollection("金融客户_厦门_C级用户");
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_C级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_D级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_D级用户").drop();
|
||||
db.createCollection("金融客户_厦门_D级用户");
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_D级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_E级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_E级用户").drop();
|
||||
db.createCollection("金融客户_厦门_E级用户");
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_E级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融客户_厦门_S级用户
|
||||
// ----------------------------
|
||||
db.getCollection("金融客户_厦门_S级用户").drop();
|
||||
db.createCollection("金融客户_厦门_S级用户");
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
total_score: NumberInt("1")
|
||||
}, {
|
||||
name: "total_score_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("金融客户_厦门_S级用户").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 金融组02250905
|
||||
// ----------------------------
|
||||
db.getCollection("金融组02250905").drop();
|
||||
db.createCollection("金融组02250905");
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"身份证": NumberInt("1")
|
||||
}, {
|
||||
name: "身份证_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"综合评分": NumberInt("-1")
|
||||
}, {
|
||||
name: "综合评分_-1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"用户等级": NumberInt("1")
|
||||
}, {
|
||||
name: "用户等级_1"
|
||||
});
|
||||
db.getCollection("金融组02250905").createIndex({
|
||||
"城市": NumberInt("1")
|
||||
}, {
|
||||
name: "城市_1"
|
||||
});
|
||||
509
Moncter/数据库/KR_KR.js
Normal file
509
Moncter/数据库/KR_KR.js
Normal file
@@ -0,0 +1,509 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_KR
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:19
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业1
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业1").drop();
|
||||
db.createCollection("34万HR人力资源经理企业1");
|
||||
db.getCollection("34万HR人力资源经理企业1").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("34万HR人力资源经理企业1").createIndex({
|
||||
"电话": NumberInt("1")
|
||||
}, {
|
||||
name: "电话_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业3
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业3").drop();
|
||||
db.createCollection("34万HR人力资源经理企业3");
|
||||
db.getCollection("34万HR人力资源经理企业3").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业4
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业4").drop();
|
||||
db.createCollection("34万HR人力资源经理企业4");
|
||||
db.getCollection("34万HR人力资源经理企业4").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 34万HR人力资源经理企业5
|
||||
// ----------------------------
|
||||
db.getCollection("34万HR人力资源经理企业5").drop();
|
||||
db.createCollection("34万HR人力资源经理企业5");
|
||||
db.getCollection("34万HR人力资源经理企业5").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 51JOB
|
||||
// ----------------------------
|
||||
db.getCollection("51JOB").drop();
|
||||
db.createCollection("51JOB");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 8万条借款数据
|
||||
// ----------------------------
|
||||
db.getCollection("8万条借款数据").drop();
|
||||
db.createCollection("8万条借款数据");
|
||||
db.getCollection("8万条借款数据").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for CSDN用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("CSDN用户数据").drop();
|
||||
db.createCollection("CSDN用户数据");
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("CSDN用户数据").createIndex({
|
||||
username: NumberInt("1"),
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1_email_1",
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Sheet2
|
||||
// ----------------------------
|
||||
db.getCollection("Sheet2").drop();
|
||||
db.createCollection("Sheet2");
|
||||
db.getCollection("Sheet2").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for pw_members
|
||||
// ----------------------------
|
||||
db.getCollection("pw_members").drop();
|
||||
db.createCollection("pw_members");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.jfclub.com
|
||||
// ----------------------------
|
||||
db.getCollection("www.jfclub.com").drop();
|
||||
db.createCollection("www.jfclub.com");
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
u_id: NumberInt("1")
|
||||
}, {
|
||||
name: "u_id_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
email: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1_name_1"
|
||||
});
|
||||
db.getCollection("www.jfclub.com").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 人人网用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("人人网用户数据").drop();
|
||||
db.createCollection("人人网用户数据");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 借贷
|
||||
// ----------------------------
|
||||
db.getCollection("借贷").drop();
|
||||
db.createCollection("借贷");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 全国车主数据集_2020年
|
||||
// ----------------------------
|
||||
db.getCollection("全国车主数据集_2020年").drop();
|
||||
db.createCollection("全国车主数据集_2020年");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京本科
|
||||
// ----------------------------
|
||||
db.getCollection("北京本科").drop();
|
||||
db.createCollection("北京本科");
|
||||
db.getCollection("北京本科").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 千脑云电脑_qiannao_com
|
||||
// ----------------------------
|
||||
db.getCollection("千脑云电脑_qiannao_com").drop();
|
||||
db.createCollection("千脑云电脑_qiannao_com");
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"用户名": NumberInt("1")
|
||||
}, {
|
||||
name: "用户名_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"邮箱": NumberInt("1")
|
||||
}, {
|
||||
name: "邮箱_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"用户ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户ID_1"
|
||||
});
|
||||
db.getCollection("千脑云电脑_qiannao_com").createIndex({
|
||||
"创建时间": NumberInt("1")
|
||||
}, {
|
||||
name: "创建时间_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天涯论坛tianya.cn
|
||||
// ----------------------------
|
||||
db.getCollection("天涯论坛tianya.cn").drop();
|
||||
db.createCollection("天涯论坛tianya.cn");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 情感
|
||||
// ----------------------------
|
||||
db.getCollection("情感").drop();
|
||||
db.createCollection("情感");
|
||||
db.getCollection("情感").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("情感").createIndex({
|
||||
"手机": NumberInt("1")
|
||||
}, {
|
||||
name: "手机_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 慈溪人才网
|
||||
// ----------------------------
|
||||
db.getCollection("慈溪人才网").drop();
|
||||
db.createCollection("慈溪人才网");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 房产网
|
||||
// ----------------------------
|
||||
db.getCollection("房产网").drop();
|
||||
db.createCollection("房产网");
|
||||
db.getCollection("房产网").createIndex({
|
||||
uid: NumberInt("1")
|
||||
}, {
|
||||
name: "uid_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("房产网").createIndex({
|
||||
regdate: NumberInt("1")
|
||||
}, {
|
||||
name: "regdate_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 抖音直播粉
|
||||
// ----------------------------
|
||||
db.getCollection("抖音直播粉").drop();
|
||||
db.createCollection("抖音直播粉");
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
Uid: NumberInt("1")
|
||||
}, {
|
||||
name: "Uid_1"
|
||||
});
|
||||
db.getCollection("抖音直播粉").createIndex({
|
||||
sec_uid: NumberInt("1")
|
||||
}, {
|
||||
name: "sec_uid_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 收藏品微信好友
|
||||
// ----------------------------
|
||||
db.getCollection("收藏品微信好友").drop();
|
||||
db.createCollection("收藏品微信好友");
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
WechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_WechatId"
|
||||
});
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
OwnerWechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_OwnerWechatId"
|
||||
});
|
||||
db.getCollection("收藏品微信好友").createIndex({
|
||||
Phone: NumberInt("1")
|
||||
}, {
|
||||
name: "Phone_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 收藏品用户
|
||||
// ----------------------------
|
||||
db.getCollection("收藏品用户").drop();
|
||||
db.createCollection("收藏品用户");
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
Id: NumberInt("1")
|
||||
}, {
|
||||
name: "Id_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
WechatAccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatAccountId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
WechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
AccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "AccountId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
OwnerWechatId: NumberInt("1")
|
||||
}, {
|
||||
name: "OwnerWechatId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
GroupId: NumberInt("1")
|
||||
}, {
|
||||
name: "GroupId_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
Nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "Nickname_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
AccountNickname: NumberInt("1")
|
||||
}, {
|
||||
name: "AccountNickname_1"
|
||||
});
|
||||
db.getCollection("收藏品用户").createIndex({
|
||||
OwnerNickname: NumberInt("1")
|
||||
}, {
|
||||
name: "OwnerNickname_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 木蚂蚁munayi_com
|
||||
// ----------------------------
|
||||
db.getCollection("木蚂蚁munayi_com").drop();
|
||||
db.createCollection("木蚂蚁munayi_com");
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1",
|
||||
unique: true,
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1",
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
uid: NumberInt("1")
|
||||
}, {
|
||||
name: "uid_1",
|
||||
sparse: true
|
||||
});
|
||||
db.getCollection("木蚂蚁munayi_com").createIndex({
|
||||
data_hash: NumberInt("1")
|
||||
}, {
|
||||
name: "data_hash_1",
|
||||
unique: true,
|
||||
sparse: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 狂人影子库user
|
||||
// ----------------------------
|
||||
db.getCollection("狂人影子库user").drop();
|
||||
db.createCollection("狂人影子库user");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 珍爱网
|
||||
// ----------------------------
|
||||
db.getCollection("珍爱网").drop();
|
||||
db.createCollection("珍爱网");
|
||||
db.getCollection("珍爱网").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 用户身份凭证数据集
|
||||
// ----------------------------
|
||||
db.getCollection("用户身份凭证数据集").drop();
|
||||
db.createCollection("用户身份凭证数据集");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网红库
|
||||
// ----------------------------
|
||||
db.getCollection("网红库").drop();
|
||||
db.createCollection("网红库");
|
||||
db.getCollection("网红库").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
sec_uid: NumberInt("1")
|
||||
}, {
|
||||
name: "sec_uid_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
idcard: NumberInt("1")
|
||||
}, {
|
||||
name: "idcard_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
collectId: NumberInt("1")
|
||||
}, {
|
||||
name: "collectId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
cateId: NumberInt("1")
|
||||
}, {
|
||||
name: "cateId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
authorId: NumberInt("1")
|
||||
}, {
|
||||
name: "authorId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
uniqueId: NumberInt("1")
|
||||
}, {
|
||||
name: "uniqueId_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "phoneNumber_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "nickname_1"
|
||||
});
|
||||
db.getCollection("网红库").createIndex({
|
||||
updatedAt: NumberInt("1")
|
||||
}, {
|
||||
name: "updatedAt_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 雅虎用户
|
||||
// ----------------------------
|
||||
db.getCollection("雅虎用户").drop();
|
||||
db.createCollection("雅虎用户");
|
||||
31
Moncter/数据库/KR_Linkedln.js
Normal file
31
Moncter/数据库/KR_Linkedln.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_Linkedln
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:28
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for linkedin
|
||||
// ----------------------------
|
||||
db.getCollection("linkedin").drop();
|
||||
db.createCollection("linkedin");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
31
Moncter/数据库/KR_京东.js
Normal file
31
Moncter/数据库/KR_京东.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_京东
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:11
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for jd_com
|
||||
// ----------------------------
|
||||
db.getCollection("jd_com").drop();
|
||||
db.createCollection("jd_com");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
37
Moncter/数据库/KR_人才库.js
Normal file
37
Moncter/数据库/KR_人才库.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_人才库
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:59
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 51JOB
|
||||
// ----------------------------
|
||||
db.getCollection("51JOB").drop();
|
||||
db.createCollection("51JOB");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 慈溪人才网
|
||||
// ----------------------------
|
||||
db.getCollection("慈溪人才网").drop();
|
||||
db.createCollection("慈溪人才网");
|
||||
142
Moncter/数据库/KR_企业.js
Normal file
142
Moncter/数据库/KR_企业.js
Normal file
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_企业
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:44
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业400号段数据
|
||||
// ----------------------------
|
||||
db.getCollection("企业400号段数据").drop();
|
||||
db.createCollection("企业400号段数据");
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"企业名称": NumberInt("1")
|
||||
}, {
|
||||
name: "企业名称_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"400号码": NumberInt("1")
|
||||
}, {
|
||||
name: "400号码_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"联系人": NumberInt("1")
|
||||
}, {
|
||||
name: "联系人_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"手机号码": NumberInt("1")
|
||||
}, {
|
||||
name: "手机号码_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"表名": NumberInt("1")
|
||||
}, {
|
||||
name: "表名_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
"导入时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "导入时间_-1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
nid: NumberInt("1")
|
||||
}, {
|
||||
name: "nid_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
EnterpriseName: NumberInt("1")
|
||||
}, {
|
||||
name: "EnterpriseName_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
Number400: NumberInt("1")
|
||||
}, {
|
||||
name: "Number400_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
LinkMan: NumberInt("1")
|
||||
}, {
|
||||
name: "LinkMan_1"
|
||||
});
|
||||
db.getCollection("企业400号段数据").createIndex({
|
||||
Mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "Mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 统一用户资产表
|
||||
// ----------------------------
|
||||
db.getCollection("统一用户资产表").drop();
|
||||
db.createCollection("统一用户资产表");
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"用户唯一ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户唯一ID_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"基础信息.手机号": NumberInt("1")
|
||||
}, {
|
||||
name: "基础信息.手机号_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"基础信息.邮箱": NumberInt("1")
|
||||
}, {
|
||||
name: "基础信息.邮箱_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"企业信息.企业ID": NumberInt("1")
|
||||
}, {
|
||||
name: "企业信息.企业ID_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"企业信息.400号码": NumberInt("1")
|
||||
}, {
|
||||
name: "企业信息.400号码_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"资产概览.总资产价值": NumberInt("-1")
|
||||
}, {
|
||||
name: "资产概览.总资产价值_-1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"资产概览.资产等级": NumberInt("1")
|
||||
}, {
|
||||
name: "资产概览.资产等级_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"用户类型": NumberInt("1")
|
||||
}, {
|
||||
name: "用户类型_1"
|
||||
});
|
||||
db.getCollection("统一用户资产表").createIndex({
|
||||
"创建时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "创建时间_-1"
|
||||
});
|
||||
115
Moncter/数据库/KR_企业名录.js
Normal file
115
Moncter/数据库/KR_企业名录.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:51
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 中国医院名录_copy1
|
||||
// ----------------------------
|
||||
db.getCollection("中国医院名录_copy1").drop();
|
||||
db.createCollection("中国医院名录_copy1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业及个体工商目录
|
||||
// ----------------------------
|
||||
db.getCollection("企业及个体工商目录").drop();
|
||||
db.createCollection("企业及个体工商目录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("企业名录").drop();
|
||||
db.createCollection("企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川企业老板
|
||||
// ----------------------------
|
||||
db.getCollection("四川企业老板").drop();
|
||||
db.createCollection("四川企业老板");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库1
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库1").drop();
|
||||
db.createCollection("广东工商企业库1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库2
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库2").drop();
|
||||
db.createCollection("广东工商企业库2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库3
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库3").drop();
|
||||
db.createCollection("广东工商企业库3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库4
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库4").drop();
|
||||
db.createCollection("广东工商企业库4");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库5
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库5").drop();
|
||||
db.createCollection("广东工商企业库5");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库6
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库6").drop();
|
||||
db.createCollection("广东工商企业库6");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东工商企业库7
|
||||
// ----------------------------
|
||||
db.getCollection("广东工商企业库7").drop();
|
||||
db.createCollection("广东工商企业库7");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行政区划代码
|
||||
// ----------------------------
|
||||
db.getCollection("行政区划代码").drop();
|
||||
db.createCollection("行政区划代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 表1
|
||||
// ----------------------------
|
||||
db.getCollection("表1").drop();
|
||||
db.createCollection("表1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 表2
|
||||
// ----------------------------
|
||||
db.getCollection("表2").drop();
|
||||
db.createCollection("表2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 销售额3000万元-5000万元企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("销售额3000万元-5000万元企业名录").drop();
|
||||
db.createCollection("销售额3000万元-5000万元企业名录");
|
||||
228
Moncter/数据库/KR_卡若私域.js
Normal file
228
Moncter/数据库/KR_卡若私域.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_卡若私域
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:24
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝流量池分类
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝流量池分类").drop();
|
||||
db.createCollection("存客宝流量池分类");
|
||||
db.getCollection("存客宝流量池分类").createIndex({
|
||||
province: NumberInt("1"),
|
||||
wechat: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1"),
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province_wechat_phone_number_created_at"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类").createIndex({
|
||||
province: NumberInt("1"),
|
||||
wechat: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1"),
|
||||
wechatIsOpen: NumberInt("1"),
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province_wechat_phone_number_wechat_is_open_created_at"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝流量池分类2
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝流量池分类2").drop();
|
||||
db.createCollection("存客宝流量池分类2");
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
source: NumberInt("1"),
|
||||
wechat: NumberInt("1")
|
||||
}, {
|
||||
name: "user_source_wechat",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
source: NumberInt("1"),
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "user_source_phone_number",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "user_province"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
wechat: NumberInt("1")
|
||||
}, {
|
||||
name: "user_wechat"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
phoneNumber: NumberInt("1")
|
||||
}, {
|
||||
name: "user_phone_number"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
wechatIsOpen: NumberInt("1")
|
||||
}, {
|
||||
name: "user_wechat_is_open"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
createdAt: NumberInt("1")
|
||||
}, {
|
||||
name: "user_created_at"
|
||||
});
|
||||
db.getCollection("存客宝流量池分类2").createIndex({
|
||||
jdIsOpen: NumberInt("1")
|
||||
}, {
|
||||
name: "user_jd_is_open"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝采集网红
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝采集网红").drop();
|
||||
db.createCollection("存客宝采集网红");
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
OwnerWechatId: NumberInt("1"),
|
||||
Phone: NumberInt("1"),
|
||||
Alias: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_OwnerWechatId"
|
||||
});
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
WechatId: NumberInt("1"),
|
||||
IsSend: NumberInt("1"),
|
||||
IsDeleted: NumberInt("1"),
|
||||
Gender: NumberInt("1"),
|
||||
Nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "IX_Kefure_WechatFriend_WechatId_IsSend"
|
||||
});
|
||||
db.getCollection("存客宝采集网红").createIndex({
|
||||
WechatAccountId: NumberInt("1")
|
||||
}, {
|
||||
name: "WechatAccountId"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 存客宝采集网红有微信
|
||||
// ----------------------------
|
||||
db.getCollection("存客宝采集网红有微信").drop();
|
||||
db.createCollection("存客宝采集网红有微信");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 老坑爹商店 shop.lkdie.com
|
||||
// ----------------------------
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").drop();
|
||||
db.createCollection("老坑爹商店 shop.lkdie.com");
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
nickname: NumberInt("1")
|
||||
}, {
|
||||
name: "nickname",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹商店 shop.lkdie.com").createIndex({
|
||||
updatedTime: NumberInt("1")
|
||||
}, {
|
||||
name: "updatedTime"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 老坑爹论坛www.lkdie.com 会员
|
||||
// ----------------------------
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").drop();
|
||||
db.createCollection("老坑爹论坛www.lkdie.com 会员");
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("老坑爹论坛www.lkdie.com 会员").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑科技www.quwanzhi.com 付款邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").drop();
|
||||
db.createCollection("黑科技www.quwanzhi.com 付款邮箱");
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
trade_no: NumberInt("1")
|
||||
}, {
|
||||
name: "tradeNo"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
zid: NumberInt("1")
|
||||
}, {
|
||||
name: "zid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 付款邮箱").createIndex({
|
||||
tid: NumberInt("1")
|
||||
}, {
|
||||
name: "tid"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑科技www.quwanzhi.com 发卡邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").drop();
|
||||
db.createCollection("黑科技www.quwanzhi.com 发卡邮箱");
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
zid: NumberInt("1")
|
||||
}, {
|
||||
name: "zid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
input: NumberInt("1")
|
||||
}, {
|
||||
name: "input"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
userid: NumberInt("1")
|
||||
}, {
|
||||
name: "userid"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
tradeno: NumberInt("1")
|
||||
}, {
|
||||
name: "tradeno"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id"
|
||||
});
|
||||
db.getCollection("黑科技www.quwanzhi.com 发卡邮箱").createIndex({
|
||||
tid: NumberInt("1")
|
||||
}, {
|
||||
name: "tid"
|
||||
});
|
||||
122
Moncter/数据库/KR_商城.js
Normal file
122
Moncter/数据库/KR_商城.js
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_商城
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:13
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 21年贝蒂喜订单整合
|
||||
// ----------------------------
|
||||
db.getCollection("21年贝蒂喜订单整合").drop();
|
||||
db.createCollection("21年贝蒂喜订单整合");
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"特权订金订单id": NumberInt("1")
|
||||
}, {
|
||||
name: "特权订金订单id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售导购门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售导购门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售发货门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售发货门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售成交门店id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售成交门店id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"新零售成交经销商id": NumberInt("1")
|
||||
}, {
|
||||
name: "新零售成交经销商id_1"
|
||||
});
|
||||
db.getCollection("21年贝蒂喜订单整合").createIndex({
|
||||
"店铺Id": NumberInt("1")
|
||||
}, {
|
||||
name: "店铺Id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 凡客诚品_vancl.com
|
||||
// ----------------------------
|
||||
db.getCollection("凡客诚品_vancl.com").drop();
|
||||
db.createCollection("凡客诚品_vancl.com");
|
||||
db.getCollection("凡客诚品_vancl.com").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("凡客诚品_vancl.com").createIndex({
|
||||
"数据来源": NumberInt("1"),
|
||||
"导入时间": NumberInt("-1")
|
||||
}, {
|
||||
name: "数据来源_1_导入时间_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 嘟嘟牛
|
||||
// ----------------------------
|
||||
db.getCollection("嘟嘟牛").drop();
|
||||
db.createCollection("嘟嘟牛");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 嘟嘟牛 本地同城
|
||||
// ----------------------------
|
||||
db.getCollection("嘟嘟牛 本地同城").drop();
|
||||
db.createCollection("嘟嘟牛 本地同城");
|
||||
db.getCollection("嘟嘟牛 本地同城").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("嘟嘟牛 本地同城").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 小米 xiaomi_com
|
||||
// ----------------------------
|
||||
db.getCollection("小米 xiaomi_com").drop();
|
||||
db.createCollection("小米 xiaomi_com");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 购物-北京一电购公司2月整理版30万
|
||||
// ----------------------------
|
||||
db.getCollection("购物-北京一电购公司2月整理版30万").drop();
|
||||
db.createCollection("购物-北京一电购公司2月整理版30万");
|
||||
db.getCollection("购物-北京一电购公司2月整理版30万").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
359
Moncter/数据库/KR_国外.js
Normal file
359
Moncter/数据库/KR_国外.js
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_国外
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:56
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Yandex俄罗斯百度用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").drop();
|
||||
db.createCollection("Yandex俄罗斯百度用户数据");
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
password_strength: NumberInt("1")
|
||||
}, {
|
||||
name: "password_strength_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
country: NumberInt("1")
|
||||
}, {
|
||||
name: "country_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
service_provider: NumberInt("1")
|
||||
}, {
|
||||
name: "service_provider_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
email: NumberInt("1"),
|
||||
password: NumberInt("1")
|
||||
}, {
|
||||
name: "email_password_unique",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯百度用户数据").createIndex({
|
||||
"$**": "text"
|
||||
}, {
|
||||
name: "text_search",
|
||||
weights: {
|
||||
email: NumberInt("1"),
|
||||
username: NumberInt("1")
|
||||
},
|
||||
default_language: "english",
|
||||
language_override: "language",
|
||||
textIndexVersion: NumberInt("3")
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for Yandex俄罗斯谷歌用户数据
|
||||
// ----------------------------
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").drop();
|
||||
db.createCollection("Yandex俄罗斯谷歌用户数据");
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
password_strength: NumberInt("1")
|
||||
}, {
|
||||
name: "password_strength_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
country: NumberInt("1")
|
||||
}, {
|
||||
name: "country_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
service_provider: NumberInt("1")
|
||||
}, {
|
||||
name: "service_provider_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
created_at: NumberInt("-1")
|
||||
}, {
|
||||
name: "created_at_-1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
data_source: NumberInt("1")
|
||||
}, {
|
||||
name: "data_source_1"
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
email: NumberInt("1"),
|
||||
password: NumberInt("1")
|
||||
}, {
|
||||
name: "email_password_unique",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("Yandex俄罗斯谷歌用户数据").createIndex({
|
||||
"$**": "text"
|
||||
}, {
|
||||
name: "text_search",
|
||||
weights: {
|
||||
email: NumberInt("1"),
|
||||
username: NumberInt("1")
|
||||
},
|
||||
default_language: "english",
|
||||
language_override: "language",
|
||||
textIndexVersion: NumberInt("3")
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.angeldiet.co.kr 1
|
||||
// ----------------------------
|
||||
db.getCollection("www.angeldiet.co.kr 1").drop();
|
||||
db.createCollection("www.angeldiet.co.kr 1");
|
||||
db.getCollection("www.angeldiet.co.kr 1").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.angeldiet.co.kr 2
|
||||
// ----------------------------
|
||||
db.getCollection("www.angeldiet.co.kr 2").drop();
|
||||
db.createCollection("www.angeldiet.co.kr 2");
|
||||
db.getCollection("www.angeldiet.co.kr 2").createIndex({
|
||||
ID: NumberInt("1")
|
||||
}, {
|
||||
name: "ID_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for www.bookoa.com(韩国学术出版)
|
||||
// ----------------------------
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").drop();
|
||||
db.createCollection("www.bookoa.com(韩国学术出版)");
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
hphone: NumberInt("1")
|
||||
}, {
|
||||
name: "hphone_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("www.bookoa.com(韩国学术出版)").createIndex({
|
||||
email: NumberInt("1"),
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1_name_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_APP数据
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_APP数据").drop();
|
||||
db.createCollection("卡塔卡银行_APP数据");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_交易明细
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_交易明细").drop();
|
||||
db.createCollection("卡塔卡银行_交易明细");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_交易明细临时
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_交易明细临时").drop();
|
||||
db.createCollection("卡塔卡银行_交易明细临时");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_卡主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_卡主表").drop();
|
||||
db.createCollection("卡塔卡银行_卡主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_卡列表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_卡列表").drop();
|
||||
db.createCollection("卡塔卡银行_卡列表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_审计主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_审计主表").drop();
|
||||
db.createCollection("卡塔卡银行_审计主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_客户
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_客户").drop();
|
||||
db.createCollection("卡塔卡银行_客户");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_客户主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_客户主表").drop();
|
||||
db.createCollection("卡塔卡银行_客户主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_收款人主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_收款人主表").drop();
|
||||
db.createCollection("卡塔卡银行_收款人主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_用户档案
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_用户档案").drop();
|
||||
db.createCollection("卡塔卡银行_用户档案");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_申请记录
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_申请记录").drop();
|
||||
db.createCollection("卡塔卡银行_申请记录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_电子对账单邮箱
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_电子对账单邮箱").drop();
|
||||
db.createCollection("卡塔卡银行_电子对账单邮箱");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_订单客户明细
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_订单客户明细").drop();
|
||||
db.createCollection("卡塔卡银行_订单客户明细");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_账户主表
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_账户主表").drop();
|
||||
db.createCollection("卡塔卡银行_账户主表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_身份证变更审计
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_身份证变更审计").drop();
|
||||
db.createCollection("卡塔卡银行_身份证变更审计");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卡塔卡银行_黑名单用户
|
||||
// ----------------------------
|
||||
db.getCollection("卡塔卡银行_黑名单用户").drop();
|
||||
db.createCollection("卡塔卡银行_黑名单用户");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 台湾财经电视台
|
||||
// ----------------------------
|
||||
db.getCollection("台湾财经电视台").drop();
|
||||
db.createCollection("台湾财经电视台");
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("台湾财经电视台").createIndex({
|
||||
source: NumberInt("1")
|
||||
}, {
|
||||
name: "source_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 土耳其公民
|
||||
// ----------------------------
|
||||
db.getCollection("土耳其公民").drop();
|
||||
db.createCollection("土耳其公民");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 社工库_1.7G_www.comicstorm.co.kr 85w_1
|
||||
// ----------------------------
|
||||
db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").drop();
|
||||
db.createCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1");
|
||||
db.getCollection("社工库_1.7G_www.comicstorm.co.kr 85w_1").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 社工库_1.7G_www.hacker.co.kr 140W_140W
|
||||
// ----------------------------
|
||||
db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").drop();
|
||||
db.createCollection("社工库_1.7G_www.hacker.co.kr 140W_140W");
|
||||
db.getCollection("社工库_1.7G_www.hacker.co.kr 140W_140W").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
618
Moncter/数据库/KR_存客宝.js
Normal file
618
Moncter/数据库/KR_存客宝.js
Normal file
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_存客宝
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:41
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_AccountWechat
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_AccountWechat").drop();
|
||||
db.createCollection("cunkebao_AccountWechat");
|
||||
db.getCollection("cunkebao_AccountWechat").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_DownloadVideoSchedule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_DownloadVideoSchedule").drop();
|
||||
db.createCollection("cunkebao_DownloadVideoSchedule");
|
||||
db.getCollection("cunkebao_DownloadVideoSchedule").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendMessageTask
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendMessageTask").drop();
|
||||
db.createCollection("cunkebao_FriendMessageTask");
|
||||
db.getCollection("cunkebao_FriendMessageTask").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendMessageTpl
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendMessageTpl").drop();
|
||||
db.createCollection("cunkebao_FriendMessageTpl");
|
||||
db.getCollection("cunkebao_FriendMessageTpl").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendRequestConfig
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendRequestConfig").drop();
|
||||
db.createCollection("cunkebao_FriendRequestConfig");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FriendRequestTaskScan
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FriendRequestTaskScan").drop();
|
||||
db.createCollection("cunkebao_FriendRequestTaskScan");
|
||||
db.getCollection("cunkebao_FriendRequestTaskScan").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_FrontSession
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_FrontSession").drop();
|
||||
db.createCollection("cunkebao_FrontSession");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_JdPromotionSite
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_JdPromotionSite").drop();
|
||||
db.createCollection("cunkebao_JdPromotionSite");
|
||||
db.getCollection("cunkebao_JdPromotionSite").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_JdSocialMedia
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_JdSocialMedia").drop();
|
||||
db.createCollection("cunkebao_JdSocialMedia");
|
||||
db.getCollection("cunkebao_JdSocialMedia").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MaterialLib
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MaterialLib").drop();
|
||||
db.createCollection("cunkebao_MaterialLib");
|
||||
db.getCollection("cunkebao_MaterialLib").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MaterialPushLog
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MaterialPushLog").drop();
|
||||
db.createCollection("cunkebao_MaterialPushLog");
|
||||
db.getCollection("cunkebao_MaterialPushLog").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_MiniProgrameLib
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_MiniProgrameLib").drop();
|
||||
db.createCollection("cunkebao_MiniProgrameLib");
|
||||
db.getCollection("cunkebao_MiniProgrameLib").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_PartTimer
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_PartTimer").drop();
|
||||
db.createCollection("cunkebao_PartTimer");
|
||||
db.getCollection("cunkebao_PartTimer").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_Poster
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_Poster").drop();
|
||||
db.createCollection("cunkebao_Poster");
|
||||
db.getCollection("cunkebao_Poster").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach").drop();
|
||||
db.createCollection("cunkebao_SysAttach");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach_20250228
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach_20250228").drop();
|
||||
db.createCollection("cunkebao_SysAttach_20250228");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysAttach_copy
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysAttach_copy").drop();
|
||||
db.createCollection("cunkebao_SysAttach_copy");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysDataSource
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysDataSource").drop();
|
||||
db.createCollection("cunkebao_SysDataSource");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysModule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysModule").drop();
|
||||
db.createCollection("cunkebao_SysModule");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysNavItem
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysNavItem").drop();
|
||||
db.createCollection("cunkebao_SysNavItem");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysObject
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysObject").drop();
|
||||
db.createCollection("cunkebao_SysObject");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysOperatorPermission
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysOperatorPermission").drop();
|
||||
db.createCollection("cunkebao_SysOperatorPermission");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysRelatedList
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysRelatedList").drop();
|
||||
db.createCollection("cunkebao_SysRelatedList");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysShare
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysShare").drop();
|
||||
db.createCollection("cunkebao_SysShare");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysSolution
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysSolution").drop();
|
||||
db.createCollection("cunkebao_SysSolution");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_SysUI
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_SysUI").drop();
|
||||
db.createCollection("cunkebao_SysUI");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_UserIncome
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_UserIncome").drop();
|
||||
db.createCollection("cunkebao_UserIncome");
|
||||
db.getCollection("cunkebao_UserIncome").createIndex({
|
||||
lId: NumberInt("1")
|
||||
}, {
|
||||
name: "lId_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_administrator_permissions
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_administrator_permissions").drop();
|
||||
db.createCollection("cunkebao_v3_ck_administrator_permissions");
|
||||
db.getCollection("cunkebao_v3_ck_administrator_permissions").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_administrators
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_administrators").drop();
|
||||
db.createCollection("cunkebao_v3_ck_administrators");
|
||||
db.getCollection("cunkebao_v3_ck_administrators").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_call_recording
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_call_recording").drop();
|
||||
db.createCollection("cunkebao_v3_ck_call_recording");
|
||||
db.getCollection("cunkebao_v3_ck_call_recording").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_company
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_company").drop();
|
||||
db.createCollection("cunkebao_v3_ck_company");
|
||||
db.getCollection("cunkebao_v3_ck_company").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_customer_acquisition_task
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_customer_acquisition_task").drop();
|
||||
db.createCollection("cunkebao_v3_ck_customer_acquisition_task");
|
||||
db.getCollection("cunkebao_v3_ck_customer_acquisition_task").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_flow_package_order
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_flow_package_order").drop();
|
||||
db.createCollection("cunkebao_v3_ck_flow_package_order");
|
||||
db.getCollection("cunkebao_v3_ck_flow_package_order").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_flow_usage_record
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_flow_usage_record").drop();
|
||||
db.createCollection("cunkebao_v3_ck_flow_usage_record");
|
||||
db.getCollection("cunkebao_v3_ck_flow_usage_record").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_jd_promotion_site
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_jd_promotion_site").drop();
|
||||
db.createCollection("cunkebao_v3_ck_jd_promotion_site");
|
||||
db.getCollection("cunkebao_v3_ck_jd_promotion_site").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_jd_social_media
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_jd_social_media").drop();
|
||||
db.createCollection("cunkebao_v3_ck_jd_social_media");
|
||||
db.getCollection("cunkebao_v3_ck_jd_social_media").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_menus
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_menus").drop();
|
||||
db.createCollection("cunkebao_v3_ck_menus");
|
||||
db.getCollection("cunkebao_v3_ck_menus").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_plan_scene
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_plan_scene").drop();
|
||||
db.createCollection("cunkebao_v3_ck_plan_scene");
|
||||
db.getCollection("cunkebao_v3_ck_plan_scene").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_plan_tags
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_plan_tags").drop();
|
||||
db.createCollection("cunkebao_v3_ck_plan_tags");
|
||||
db.getCollection("cunkebao_v3_ck_plan_tags").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_task_customer
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_task_customer").drop();
|
||||
db.createCollection("cunkebao_v3_ck_task_customer");
|
||||
db.getCollection("cunkebao_v3_ck_task_customer").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_pool
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_pool").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_pool");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_pool").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source_package
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source_package");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_source_package_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_source_package_item");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_source_package_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_traffic_tag
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_traffic_tag").drop();
|
||||
db.createCollection("cunkebao_v3_ck_traffic_tag");
|
||||
db.getCollection("cunkebao_v3_ck_traffic_tag").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_user_flow_package
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_user_flow_package").drop();
|
||||
db.createCollection("cunkebao_v3_ck_user_flow_package");
|
||||
db.getCollection("cunkebao_v3_ck_user_flow_package").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_user_portrait
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_user_portrait").drop();
|
||||
db.createCollection("cunkebao_v3_ck_user_portrait");
|
||||
db.getCollection("cunkebao_v3_ck_user_portrait").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_vendor_project
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_vendor_project").drop();
|
||||
db.createCollection("cunkebao_v3_ck_vendor_project");
|
||||
db.getCollection("cunkebao_v3_ck_vendor_project").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_friendship
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_friendship").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_friendship");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_friendship").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_group_member
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_group_member").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_group_member");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_group_member").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_wechat_tag
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_wechat_tag").drop();
|
||||
db.createCollection("cunkebao_v3_ck_wechat_tag");
|
||||
db.getCollection("cunkebao_v3_ck_wechat_tag").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench");
|
||||
db.getCollection("cunkebao_v3_ck_workbench").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_create_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_create_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_create_item");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_create_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_push
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_push");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_ck_workbench_group_push_item
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push_item").drop();
|
||||
db.createCollection("cunkebao_v3_ck_workbench_group_push_item");
|
||||
db.getCollection("cunkebao_v3_ck_workbench_group_push_item").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_s2_allot_rule
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_s2_allot_rule").drop();
|
||||
db.createCollection("cunkebao_v3_s2_allot_rule");
|
||||
db.getCollection("cunkebao_v3_s2_allot_rule").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for cunkebao_v3_s2_device_group
|
||||
// ----------------------------
|
||||
db.getCollection("cunkebao_v3_s2_device_group").drop();
|
||||
db.createCollection("cunkebao_v3_s2_device_group");
|
||||
db.getCollection("cunkebao_v3_s2_device_group").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 好友关系网络汇总表
|
||||
// ----------------------------
|
||||
db.getCollection("好友关系网络汇总表").drop();
|
||||
db.createCollection("好友关系网络汇总表");
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"用户微信ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户微信ID_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"好友微信ID": NumberInt("1")
|
||||
}, {
|
||||
name: "好友微信ID_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"关系等级": NumberInt("1")
|
||||
}, {
|
||||
name: "关系等级_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"RFM评分.总分": NumberInt("1")
|
||||
}, {
|
||||
name: "RFM评分.总分_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"关系强度评分": NumberInt("1")
|
||||
}, {
|
||||
name: "关系强度评分_1"
|
||||
});
|
||||
db.getCollection("好友关系网络汇总表").createIndex({
|
||||
"公司ID": NumberInt("1")
|
||||
}, {
|
||||
name: "公司ID_1"
|
||||
});
|
||||
65
Moncter/数据库/KR_存客宝_四表重构KR_KR版.js
Normal file
65
Moncter/数据库/KR_存客宝_四表重构KR_KR版.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_存客宝_四表重构KR_KR版
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:18:48
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 估值模式表
|
||||
// ----------------------------
|
||||
db.getCollection("估值模式表").drop();
|
||||
db.createCollection("估值模式表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微信号管理表
|
||||
// ----------------------------
|
||||
db.getCollection("微信号管理表").drop();
|
||||
db.createCollection("微信号管理表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微信好友表
|
||||
// ----------------------------
|
||||
db.getCollection("微信好友表").drop();
|
||||
db.createCollection("微信好友表");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 设备管理表
|
||||
// ----------------------------
|
||||
db.getCollection("设备管理表").drop();
|
||||
db.createCollection("设备管理表");
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"设备ID": NumberInt("1")
|
||||
}, {
|
||||
name: "设备ID_1",
|
||||
unique: true
|
||||
});
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"用户ID": NumberInt("1")
|
||||
}, {
|
||||
name: "用户ID_1"
|
||||
});
|
||||
db.getCollection("设备管理表").createIndex({
|
||||
"设备类型": NumberInt("1")
|
||||
}, {
|
||||
name: "设备类型_1"
|
||||
});
|
||||
36
Moncter/数据库/KR_微博.js
Normal file
36
Moncter/数据库/KR_微博.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_微博
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:57
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 微博uid+手机
|
||||
// ----------------------------
|
||||
db.getCollection("微博uid+手机").drop();
|
||||
db.createCollection("微博uid+手机");
|
||||
db.getCollection("微博uid+手机").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
90
Moncter/数据库/KR_快递.js
Normal file
90
Moncter/数据库/KR_快递.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_快递
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:31
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for wlzhwu_jiezhang
|
||||
// ----------------------------
|
||||
db.getCollection("wlzhwu_jiezhang").drop();
|
||||
db.createCollection("wlzhwu_jiezhang");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for wlzhwu_wlzhangwu
|
||||
// ----------------------------
|
||||
db.getCollection("wlzhwu_wlzhangwu").drop();
|
||||
db.createCollection("wlzhwu_wlzhangwu");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 可行路由
|
||||
// ----------------------------
|
||||
db.getCollection("可行路由").drop();
|
||||
db.createCollection("可行路由");
|
||||
db.getCollection("可行路由").createIndex({
|
||||
_import_time: NumberInt("1")
|
||||
}, {
|
||||
name: "_import_time_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 圆通_普通客户
|
||||
// ----------------------------
|
||||
db.getCollection("圆通_普通客户").drop();
|
||||
db.createCollection("圆通_普通客户");
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("圆通_普通客户").createIndex({
|
||||
user_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_score_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 圆通_潜在客户
|
||||
// ----------------------------
|
||||
db.getCollection("圆通_潜在客户").drop();
|
||||
db.createCollection("圆通_潜在客户");
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("圆通_潜在客户").createIndex({
|
||||
user_score: NumberInt("-1")
|
||||
}, {
|
||||
name: "user_score_-1"
|
||||
});
|
||||
46
Moncter/数据库/KR_户口.js
Normal file
46
Moncter/数据库/KR_户口.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_户口
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:03
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 户籍数据
|
||||
// ----------------------------
|
||||
db.getCollection("户籍数据").drop();
|
||||
db.createCollection("户籍数据");
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"姓名": NumberInt("1")
|
||||
}, {
|
||||
name: "姓名_1"
|
||||
});
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"身份证号": NumberInt("1")
|
||||
}, {
|
||||
name: "身份证号_1"
|
||||
});
|
||||
db.getCollection("户籍数据").createIndex({
|
||||
"户籍地址": NumberInt("1")
|
||||
}, {
|
||||
name: "户籍地址_1"
|
||||
});
|
||||
186
Moncter/数据库/KR_手机.js
Normal file
186
Moncter/数据库/KR_手机.js
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_手机
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:20
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for tmpgSyh4.renameCollection
|
||||
// ----------------------------
|
||||
db.getCollection("tmpgSyh4.renameCollection").drop();
|
||||
db.createCollection("tmpgSyh4.renameCollection");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东gd5800万神州行动感地带=广州GZBran2_copy2
|
||||
// ----------------------------
|
||||
db.getCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2").drop();
|
||||
db.createCollection("广东gd5800万神州行动感地带=广州GZBran2_copy2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据1
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据1").drop();
|
||||
db.createCollection("广东广州航空数据1");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据2
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据2").drop();
|
||||
db.createCollection("广东广州航空数据2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据3
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据3").drop();
|
||||
db.createCollection("广东广州航空数据3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东广州航空数据4
|
||||
// ----------------------------
|
||||
db.getCollection("广东广州航空数据4").drop();
|
||||
db.createCollection("广东广州航空数据4");
|
||||
db.getCollection("广东广州航空数据4").createIndex({
|
||||
"电话号码": NumberInt("1")
|
||||
}, {
|
||||
name: "电话号码_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东深圳全球通女
|
||||
// ----------------------------
|
||||
db.getCollection("广东深圳全球通女").drop();
|
||||
db.createCollection("广东深圳全球通女");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_701鹰潭
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_701鹰潭").drop();
|
||||
db.createCollection("江西移动_701鹰潭");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_792九江
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_792九江").drop();
|
||||
db.createCollection("江西移动_792九江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_793上饶
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_793上饶").drop();
|
||||
db.createCollection("江西移动_793上饶");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_798景德镇
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_798景德镇").drop();
|
||||
db.createCollection("江西移动_798景德镇");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_799萍乡
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_799萍乡").drop();
|
||||
db.createCollection("江西移动_799萍乡");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_上绕262万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_上绕262万").drop();
|
||||
db.createCollection("江西移动_上绕262万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_九江222万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_九江222万").drop();
|
||||
db.createCollection("江西移动_九江222万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_南昌368万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_南昌368万").drop();
|
||||
db.createCollection("江西移动_南昌368万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_吉安206万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_吉安206万").drop();
|
||||
db.createCollection("江西移动_吉安206万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_宜春230万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_宜春230万").drop();
|
||||
db.createCollection("江西移动_宜春230万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_抚州143万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_抚州143万").drop();
|
||||
db.createCollection("江西移动_抚州143万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_新余67万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_新余67万").drop();
|
||||
db.createCollection("江西移动_新余67万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_景德镇76万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_景德镇76万").drop();
|
||||
db.createCollection("江西移动_景德镇76万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通带身份证
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通带身份证").drop();
|
||||
db.createCollection("江西移动_江西全球通带身份证");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通身份证
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通身份证").drop();
|
||||
db.createCollection("江西移动_江西全球通身份证");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_江西全球通身份证姓名1226056
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_江西全球通身份证姓名1226056").drop();
|
||||
db.createCollection("江西移动_江西全球通身份证姓名1226056");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_萍乡86万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_萍乡86万").drop();
|
||||
db.createCollection("江西移动_萍乡86万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_赣州352万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_赣州352万").drop();
|
||||
db.createCollection("江西移动_赣州352万");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西移动_鹰潭53万
|
||||
// ----------------------------
|
||||
db.getCollection("江西移动_鹰潭53万").drop();
|
||||
db.createCollection("江西移动_鹰潭53万");
|
||||
223
Moncter/数据库/KR_投资.js
Normal file
223
Moncter/数据库/KR_投资.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_投资
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:51
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 上海
|
||||
// ----------------------------
|
||||
db.getCollection("上海").drop();
|
||||
db.createCollection("上海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 云南
|
||||
// ----------------------------
|
||||
db.getCollection("云南").drop();
|
||||
db.createCollection("云南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 内蒙古
|
||||
// ----------------------------
|
||||
db.getCollection("内蒙古").drop();
|
||||
db.createCollection("内蒙古");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京
|
||||
// ----------------------------
|
||||
db.getCollection("北京").drop();
|
||||
db.createCollection("北京");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 吉林
|
||||
// ----------------------------
|
||||
db.getCollection("吉林").drop();
|
||||
db.createCollection("吉林");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 商务部世界买家数据库 此数据由www_10195_com提供
|
||||
// ----------------------------
|
||||
db.getCollection("商务部世界买家数据库 此数据由www_10195_com提供").drop();
|
||||
db.createCollection("商务部世界买家数据库 此数据由www_10195_com提供");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川
|
||||
// ----------------------------
|
||||
db.getCollection("四川").drop();
|
||||
db.createCollection("四川");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天津
|
||||
// ----------------------------
|
||||
db.getCollection("天津").drop();
|
||||
db.createCollection("天津");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 宁夏
|
||||
// ----------------------------
|
||||
db.getCollection("宁夏").drop();
|
||||
db.createCollection("宁夏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 安徽
|
||||
// ----------------------------
|
||||
db.getCollection("安徽").drop();
|
||||
db.createCollection("安徽");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山东
|
||||
// ----------------------------
|
||||
db.getCollection("山东").drop();
|
||||
db.createCollection("山东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山西
|
||||
// ----------------------------
|
||||
db.getCollection("山西").drop();
|
||||
db.createCollection("山西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东
|
||||
// ----------------------------
|
||||
db.getCollection("广东").drop();
|
||||
db.createCollection("广东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广西
|
||||
// ----------------------------
|
||||
db.getCollection("广西").drop();
|
||||
db.createCollection("广西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 新疆
|
||||
// ----------------------------
|
||||
db.getCollection("新疆").drop();
|
||||
db.createCollection("新疆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江苏
|
||||
// ----------------------------
|
||||
db.getCollection("江苏").drop();
|
||||
db.createCollection("江苏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西
|
||||
// ----------------------------
|
||||
db.getCollection("江西").drop();
|
||||
db.createCollection("江西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河北
|
||||
// ----------------------------
|
||||
db.getCollection("河北").drop();
|
||||
db.createCollection("河北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河南
|
||||
// ----------------------------
|
||||
db.getCollection("河南").drop();
|
||||
db.createCollection("河南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 浙江
|
||||
// ----------------------------
|
||||
db.getCollection("浙江").drop();
|
||||
db.createCollection("浙江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 海南
|
||||
// ----------------------------
|
||||
db.getCollection("海南").drop();
|
||||
db.createCollection("海南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖北
|
||||
// ----------------------------
|
||||
db.getCollection("湖北").drop();
|
||||
db.createCollection("湖北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖南
|
||||
// ----------------------------
|
||||
db.getCollection("湖南").drop();
|
||||
db.createCollection("湖南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 甘肃
|
||||
// ----------------------------
|
||||
db.getCollection("甘肃").drop();
|
||||
db.createCollection("甘肃");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 省份
|
||||
// ----------------------------
|
||||
db.getCollection("省份").drop();
|
||||
db.createCollection("省份");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 福建
|
||||
// ----------------------------
|
||||
db.getCollection("福建").drop();
|
||||
db.createCollection("福建");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 西藏
|
||||
// ----------------------------
|
||||
db.getCollection("西藏").drop();
|
||||
db.createCollection("西藏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 贵州
|
||||
// ----------------------------
|
||||
db.getCollection("贵州").drop();
|
||||
db.createCollection("贵州");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 辽宁
|
||||
// ----------------------------
|
||||
db.getCollection("辽宁").drop();
|
||||
db.createCollection("辽宁");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 重庆
|
||||
// ----------------------------
|
||||
db.getCollection("重庆").drop();
|
||||
db.createCollection("重庆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 陕西
|
||||
// ----------------------------
|
||||
db.getCollection("陕西").drop();
|
||||
db.createCollection("陕西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 青海
|
||||
// ----------------------------
|
||||
db.getCollection("青海").drop();
|
||||
db.createCollection("青海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑龙江
|
||||
// ----------------------------
|
||||
db.getCollection("黑龙江").drop();
|
||||
db.createCollection("黑龙江");
|
||||
223
Moncter/数据库/KR_淘宝.js
Normal file
223
Moncter/数据库/KR_淘宝.js
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_淘宝
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:35
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo1(去重复后33万)
|
||||
// ----------------------------
|
||||
db.getCollection("zippo1(去重复后33万)").drop();
|
||||
db.createCollection("zippo1(去重复后33万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo2
|
||||
// ----------------------------
|
||||
db.getCollection("zippo2").drop();
|
||||
db.createCollection("zippo2");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo3
|
||||
// ----------------------------
|
||||
db.getCollection("zippo3").drop();
|
||||
db.createCollection("zippo3");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo4
|
||||
// ----------------------------
|
||||
db.getCollection("zippo4").drop();
|
||||
db.createCollection("zippo4");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for zippo5
|
||||
// ----------------------------
|
||||
db.getCollection("zippo5").drop();
|
||||
db.createCollection("zippo5");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 办公设备文具(去重复64.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("办公设备文具(去重复64.5万)").drop();
|
||||
db.createCollection("办公设备文具(去重复64.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 包(去重复后71万)
|
||||
// ----------------------------
|
||||
db.getCollection("包(去重复后71万)").drop();
|
||||
db.createCollection("包(去重复后71万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 卖家邮箱(去重复后300万)
|
||||
// ----------------------------
|
||||
db.getCollection("卖家邮箱(去重复后300万)").drop();
|
||||
db.createCollection("卖家邮箱(去重复后300万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女士内衣(去重复后132.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女士内衣(去重复后132.6万)").drop();
|
||||
db.createCollection("女士内衣(去重复后132.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女士箱包配件(去重复后34.8万)
|
||||
// ----------------------------
|
||||
db.getCollection("女士箱包配件(去重复后34.8万)").drop();
|
||||
db.createCollection("女士箱包配件(去重复后34.8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女装1(去重复后388.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女装1(去重复后388.6万)").drop();
|
||||
db.createCollection("女装1(去重复后388.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女装2(去重复后83万,带旺旺号及支付宝)
|
||||
// ----------------------------
|
||||
db.getCollection("女装2(去重复后83万,带旺旺号及支付宝)").drop();
|
||||
db.createCollection("女装2(去重复后83万,带旺旺号及支付宝)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 女鞋(去重复后118.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("女鞋(去重复后118.6万)").drop();
|
||||
db.createCollection("女鞋(去重复后118.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 家具定制代购(去重复后20万)
|
||||
// ----------------------------
|
||||
db.getCollection("家具定制代购(去重复后20万)").drop();
|
||||
db.createCollection("家具定制代购(去重复后20万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 家用电器(去重复后93.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("家用电器(去重复后93.5万)").drop();
|
||||
db.createCollection("家用电器(去重复后93.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 床上用品(去重复后19万)
|
||||
// ----------------------------
|
||||
db.getCollection("床上用品(去重复后19万)").drop();
|
||||
db.createCollection("床上用品(去重复后19万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 彩妆护肤(去重复后138万)
|
||||
// ----------------------------
|
||||
db.getCollection("彩妆护肤(去重复后138万)").drop();
|
||||
db.createCollection("彩妆护肤(去重复后138万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 成人用品避孕用品情趣内衣(去重复后8万)
|
||||
// ----------------------------
|
||||
db.getCollection("成人用品避孕用品情趣内衣(去重复后8万)").drop();
|
||||
db.createCollection("成人用品避孕用品情趣内衣(去重复后8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 户外军品旅游机票(去重复后64万)
|
||||
// ----------------------------
|
||||
db.getCollection("户外军品旅游机票(去重复后64万)").drop();
|
||||
db.createCollection("户外军品旅游机票(去重复后64万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 手机(去重复后80万)
|
||||
// ----------------------------
|
||||
db.getCollection("手机(去重复后80万)").drop();
|
||||
db.createCollection("手机(去重复后80万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 数码相机图形冲印(去重复后23.8万)
|
||||
// ----------------------------
|
||||
db.getCollection("数码相机图形冲印(去重复后23.8万)").drop();
|
||||
db.createCollection("数码相机图形冲印(去重复后23.8万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 数码配件电子元件(去重复后51.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("数码配件电子元件(去重复后51.6万)").drop();
|
||||
db.createCollection("数码配件电子元件(去重复后51.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 时尚家饰工艺品(去重复后41万)
|
||||
// ----------------------------
|
||||
db.getCollection("时尚家饰工艺品(去重复后41万)").drop();
|
||||
db.createCollection("时尚家饰工艺品(去重复后41万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 汽车配件(去重复后38.6万)
|
||||
// ----------------------------
|
||||
db.getCollection("汽车配件(去重复后38.6万)").drop();
|
||||
db.createCollection("汽车配件(去重复后38.6万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 珠宝钻石翡翠(去重复后44.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("珠宝钻石翡翠(去重复后44.5万)").drop();
|
||||
db.createCollection("珠宝钻石翡翠(去重复后44.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 电脑硬件台式机网络设备(去重复后20万)
|
||||
// ----------------------------
|
||||
db.getCollection("电脑硬件台式机网络设备(去重复后20万)").drop();
|
||||
db.createCollection("电脑硬件台式机网络设备(去重复后20万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 男装(去重复后216万)
|
||||
// ----------------------------
|
||||
db.getCollection("男装(去重复后216万)").drop();
|
||||
db.createCollection("男装(去重复后216万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 童装婴儿服鞋帽(去重复后52万)
|
||||
// ----------------------------
|
||||
db.getCollection("童装婴儿服鞋帽(去重复后52万)").drop();
|
||||
db.createCollection("童装婴儿服鞋帽(去重复后52万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 笔记本(去重复后32万)
|
||||
// ----------------------------
|
||||
db.getCollection("笔记本(去重复后32万)").drop();
|
||||
db.createCollection("笔记本(去重复后32万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网络服务电脑软件(去重复后17万)
|
||||
// ----------------------------
|
||||
db.getCollection("网络服务电脑软件(去重复后17万)").drop();
|
||||
db.createCollection("网络服务电脑软件(去重复后17万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 装潢灯具五金(去重复后107万)
|
||||
// ----------------------------
|
||||
db.getCollection("装潢灯具五金(去重复后107万)").drop();
|
||||
db.createCollection("装潢灯具五金(去重复后107万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 食品保健(去重复后84.5万)
|
||||
// ----------------------------
|
||||
db.getCollection("食品保健(去重复后84.5万)").drop();
|
||||
db.createCollection("食品保健(去重复后84.5万)");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 饰品(去重复14.7万)
|
||||
// ----------------------------
|
||||
db.getCollection("饰品(去重复14.7万)").drop();
|
||||
db.createCollection("饰品(去重复14.7万)");
|
||||
156
Moncter/数据库/KR_游戏.js
Normal file
156
Moncter/数据库/KR_游戏.js
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_游戏
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:24
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 178游戏网
|
||||
// ----------------------------
|
||||
db.getCollection("178游戏网").drop();
|
||||
db.createCollection("178游戏网");
|
||||
db.getCollection("178游戏网").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 766游戏网
|
||||
// ----------------------------
|
||||
db.getCollection("766游戏网").drop();
|
||||
db.createCollection("766游戏网");
|
||||
db.getCollection("766游戏网").createIndex({
|
||||
"data.0": NumberInt("1")
|
||||
}, {
|
||||
name: "data.0_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 7k7k小游戏
|
||||
// ----------------------------
|
||||
db.getCollection("7k7k小游戏").drop();
|
||||
db.createCollection("7k7k小游戏");
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
account: NumberInt("1")
|
||||
}, {
|
||||
name: "account_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
domain: NumberInt("1")
|
||||
}, {
|
||||
name: "domain_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
account_type: NumberInt("1")
|
||||
}, {
|
||||
name: "account_type_1"
|
||||
});
|
||||
db.getCollection("7k7k小游戏").createIndex({
|
||||
is_email: NumberInt("1")
|
||||
}, {
|
||||
name: "is_email_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for DNF
|
||||
// ----------------------------
|
||||
db.getCollection("DNF").drop();
|
||||
db.createCollection("DNF");
|
||||
db.getCollection("DNF").createIndex({
|
||||
"data.0": NumberInt("1")
|
||||
}, {
|
||||
name: "data.0_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
id: NumberInt("1")
|
||||
}, {
|
||||
name: "id_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
character_name: NumberInt("1")
|
||||
}, {
|
||||
name: "character_name_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
server_name: NumberInt("1")
|
||||
}, {
|
||||
name: "server_name_1"
|
||||
});
|
||||
db.getCollection("DNF").createIndex({
|
||||
level: NumberInt("-1")
|
||||
}, {
|
||||
name: "level_-1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for KR_游戏_重叠用户
|
||||
// ----------------------------
|
||||
db.getCollection("KR_游戏_重叠用户").drop();
|
||||
db.createCollection("KR_游戏_重叠用户");
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
user_id: NumberInt("1")
|
||||
}, {
|
||||
name: "user_id_1"
|
||||
});
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
user_value: NumberInt("1")
|
||||
}, {
|
||||
name: "user_value_1"
|
||||
});
|
||||
db.getCollection("KR_游戏_重叠用户").createIndex({
|
||||
overlap_count: NumberInt("1")
|
||||
}, {
|
||||
name: "overlap_count_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 游戏语音ispeak_cn
|
||||
// ----------------------------
|
||||
db.getCollection("游戏语音ispeak_cn").drop();
|
||||
db.createCollection("游戏语音ispeak_cn");
|
||||
db.getCollection("游戏语音ispeak_cn").createIndex({
|
||||
username: NumberInt("1")
|
||||
}, {
|
||||
name: "username_1"
|
||||
});
|
||||
db.getCollection("游戏语音ispeak_cn").createIndex({
|
||||
email: NumberInt("1")
|
||||
}, {
|
||||
name: "email_1"
|
||||
});
|
||||
1136
Moncter/数据库/KR_腾讯.js
Normal file
1136
Moncter/数据库/KR_腾讯.js
Normal file
File diff suppressed because it is too large
Load Diff
79
Moncter/数据库/KR_酒店.js
Normal file
79
Moncter/数据库/KR_酒店.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_酒店
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:17
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 如家汉庭
|
||||
// ----------------------------
|
||||
db.getCollection("如家汉庭").drop();
|
||||
db.createCollection("如家汉庭");
|
||||
db.getCollection("如家汉庭").createIndex({
|
||||
Mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "Mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万");
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
name: NumberInt("1")
|
||||
}, {
|
||||
name: "name_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
id_card: NumberInt("1")
|
||||
}, {
|
||||
name: "id_card_1"
|
||||
});
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万内
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万内");
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内").createIndex({
|
||||
mobile: NumberInt("1")
|
||||
}, {
|
||||
name: "mobile_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 酒店开房记录_2013年8月_2000万内_备份
|
||||
// ----------------------------
|
||||
db.getCollection("酒店开房记录_2013年8月_2000万内_备份").drop();
|
||||
db.createCollection("酒店开房记录_2013年8月_2000万内_备份");
|
||||
85
Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js
Normal file
85
Moncter/数据库/KR_销售额3000万元-5000万元企业名录.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_销售额3000万元-5000万元企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:17
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业名录 的副本
|
||||
// ----------------------------
|
||||
db.getCollection("企业名录 的副本").drop();
|
||||
db.createCollection("企业名录 的副本");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 企业规模代码
|
||||
// ----------------------------
|
||||
db.getCollection("企业规模代码").drop();
|
||||
db.createCollection("企业规模代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 控股情况代码
|
||||
// ----------------------------
|
||||
db.getCollection("控股情况代码").drop();
|
||||
db.createCollection("控股情况代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 机构类型代码
|
||||
// ----------------------------
|
||||
db.getCollection("机构类型代码").drop();
|
||||
db.createCollection("机构类型代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 经济类型代码
|
||||
// ----------------------------
|
||||
db.getCollection("经济类型代码").drop();
|
||||
db.createCollection("经济类型代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 营业收入代码
|
||||
// ----------------------------
|
||||
db.getCollection("营业收入代码").drop();
|
||||
db.createCollection("营业收入代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行业代码
|
||||
// ----------------------------
|
||||
db.getCollection("行业代码").drop();
|
||||
db.createCollection("行业代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 行政区划代码
|
||||
// ----------------------------
|
||||
db.getCollection("行政区划代码").drop();
|
||||
db.createCollection("行政区划代码");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 销售额3000万元-5000万元企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("销售额3000万元-5000万元企业名录").drop();
|
||||
db.createCollection("销售额3000万元-5000万元企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 隶属关系代码
|
||||
// ----------------------------
|
||||
db.getCollection("隶属关系代码").drop();
|
||||
db.createCollection("隶属关系代码");
|
||||
91
Moncter/数据库/KR_顺丰.js
Normal file
91
Moncter/数据库/KR_顺丰.js
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_顺丰
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:20:28
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 顺丰快递数据
|
||||
// ----------------------------
|
||||
db.getCollection("顺丰快递数据").drop();
|
||||
db.createCollection("顺丰快递数据");
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_province_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 顺丰快递数据_tmpForCopy1
|
||||
// ----------------------------
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").drop();
|
||||
db.createCollection("顺丰快递数据_tmpForCopy1");
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
phone: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
province: NumberInt("1"),
|
||||
city: NumberInt("1")
|
||||
}, {
|
||||
name: "province_1_city_1"
|
||||
});
|
||||
db.getCollection("顺丰快递数据_tmpForCopy1").createIndex({
|
||||
phone: NumberInt("1"),
|
||||
province: NumberInt("1")
|
||||
}, {
|
||||
name: "phone_1_province_1"
|
||||
});
|
||||
217
Moncter/数据库/KR_香港在大陆投资企业名录.js
Normal file
217
Moncter/数据库/KR_香港在大陆投资企业名录.js
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_香港在大陆投资企业名录
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:21:07
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 上海
|
||||
// ----------------------------
|
||||
db.getCollection("上海").drop();
|
||||
db.createCollection("上海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 云南
|
||||
// ----------------------------
|
||||
db.getCollection("云南").drop();
|
||||
db.createCollection("云南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 内蒙
|
||||
// ----------------------------
|
||||
db.getCollection("内蒙").drop();
|
||||
db.createCollection("内蒙");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 北京
|
||||
// ----------------------------
|
||||
db.getCollection("北京").drop();
|
||||
db.createCollection("北京");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 吉林
|
||||
// ----------------------------
|
||||
db.getCollection("吉林").drop();
|
||||
db.createCollection("吉林");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 四川
|
||||
// ----------------------------
|
||||
db.getCollection("四川").drop();
|
||||
db.createCollection("四川");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 天津
|
||||
// ----------------------------
|
||||
db.getCollection("天津").drop();
|
||||
db.createCollection("天津");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 宁夏
|
||||
// ----------------------------
|
||||
db.getCollection("宁夏").drop();
|
||||
db.createCollection("宁夏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 安徽
|
||||
// ----------------------------
|
||||
db.getCollection("安徽").drop();
|
||||
db.createCollection("安徽");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山东
|
||||
// ----------------------------
|
||||
db.getCollection("山东").drop();
|
||||
db.createCollection("山东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 山西
|
||||
// ----------------------------
|
||||
db.getCollection("山西").drop();
|
||||
db.createCollection("山西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广东
|
||||
// ----------------------------
|
||||
db.getCollection("广东").drop();
|
||||
db.createCollection("广东");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 广西
|
||||
// ----------------------------
|
||||
db.getCollection("广西").drop();
|
||||
db.createCollection("广西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 新疆
|
||||
// ----------------------------
|
||||
db.getCollection("新疆").drop();
|
||||
db.createCollection("新疆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江苏
|
||||
// ----------------------------
|
||||
db.getCollection("江苏").drop();
|
||||
db.createCollection("江苏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 江西
|
||||
// ----------------------------
|
||||
db.getCollection("江西").drop();
|
||||
db.createCollection("江西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河北
|
||||
// ----------------------------
|
||||
db.getCollection("河北").drop();
|
||||
db.createCollection("河北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 河南
|
||||
// ----------------------------
|
||||
db.getCollection("河南").drop();
|
||||
db.createCollection("河南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 浙江
|
||||
// ----------------------------
|
||||
db.getCollection("浙江").drop();
|
||||
db.createCollection("浙江");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 海南
|
||||
// ----------------------------
|
||||
db.getCollection("海南").drop();
|
||||
db.createCollection("海南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖北
|
||||
// ----------------------------
|
||||
db.getCollection("湖北").drop();
|
||||
db.createCollection("湖北");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 湖南
|
||||
// ----------------------------
|
||||
db.getCollection("湖南").drop();
|
||||
db.createCollection("湖南");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 甘肃
|
||||
// ----------------------------
|
||||
db.getCollection("甘肃").drop();
|
||||
db.createCollection("甘肃");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 福建
|
||||
// ----------------------------
|
||||
db.getCollection("福建").drop();
|
||||
db.createCollection("福建");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 西藏
|
||||
// ----------------------------
|
||||
db.getCollection("西藏").drop();
|
||||
db.createCollection("西藏");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 贵州
|
||||
// ----------------------------
|
||||
db.getCollection("贵州").drop();
|
||||
db.createCollection("贵州");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 辽宁
|
||||
// ----------------------------
|
||||
db.getCollection("辽宁").drop();
|
||||
db.createCollection("辽宁");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 重庆
|
||||
// ----------------------------
|
||||
db.getCollection("重庆").drop();
|
||||
db.createCollection("重庆");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 陕西
|
||||
// ----------------------------
|
||||
db.getCollection("陕西").drop();
|
||||
db.createCollection("陕西");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 青海
|
||||
// ----------------------------
|
||||
db.getCollection("青海").drop();
|
||||
db.createCollection("青海");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 香港投资企业名录
|
||||
// ----------------------------
|
||||
db.getCollection("香港投资企业名录").drop();
|
||||
db.createCollection("香港投资企业名录");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 黑龙江
|
||||
// ----------------------------
|
||||
db.getCollection("黑龙江").drop();
|
||||
db.createCollection("黑龙江");
|
||||
103
Moncter/数据库/KR_魔兽世界.js
Normal file
103
Moncter/数据库/KR_魔兽世界.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Navicat Premium Data Transfer
|
||||
|
||||
Source Server : 标签数据库
|
||||
Source Server Type : MongoDB
|
||||
Source Server Version : 60025 (6.0.25)
|
||||
Source Host : 192.168.2.6:27017
|
||||
Source Schema : KR_魔兽世界
|
||||
|
||||
Target Server Type : MongoDB
|
||||
Target Server Version : 60025 (6.0.25)
|
||||
File Encoding : 65001
|
||||
|
||||
Date: 19/11/2025 15:19:37
|
||||
*/
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for elysn欧美_魔兽世界主题库
|
||||
// ----------------------------
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").drop();
|
||||
db.createCollection("elysn欧美_魔兽世界主题库");
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1"
|
||||
});
|
||||
db.getCollection("elysn欧美_魔兽世界主题库").createIndex({
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "密码_1"
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for system.profile
|
||||
// ----------------------------
|
||||
db.getCollection("system.profile").drop();
|
||||
db.createCollection("system.profile",{
|
||||
capped: true,
|
||||
size: 1048576
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易2500W密正数据
|
||||
// ----------------------------
|
||||
db.getCollection("网易2500W密正数据").drop();
|
||||
db.createCollection("网易2500W密正数据");
|
||||
db.getCollection("网易2500W密正数据").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1",
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易IS_game.sohu.com邮箱占90%
|
||||
// ----------------------------
|
||||
db.getCollection("网易IS_game.sohu.com邮箱占90%").drop();
|
||||
db.createCollection("网易IS_game.sohu.com邮箱占90%");
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易正确数据_已确认
|
||||
// ----------------------------
|
||||
db.getCollection("网易正确数据_已确认").drop();
|
||||
db.createCollection("网易正确数据_已确认");
|
||||
db.getCollection("网易正确数据_已确认").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "账号_1",
|
||||
background: true,
|
||||
unique: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 网易正确数据_空白数据
|
||||
// ----------------------------
|
||||
db.getCollection("网易正确数据_空白数据").drop();
|
||||
db.createCollection("网易正确数据_空白数据");
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"账号": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_account",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_password",
|
||||
background: true
|
||||
});
|
||||
db.getCollection("网易正确数据_空白数据").createIndex({
|
||||
"账号": NumberInt("1"),
|
||||
"密码": NumberInt("1")
|
||||
}, {
|
||||
name: "idx_account_password",
|
||||
background: true
|
||||
});
|
||||
|
||||
// ----------------------------
|
||||
// Collection structure for 魔兽世界_3月库
|
||||
// ----------------------------
|
||||
db.getCollection("魔兽世界_3月库").drop();
|
||||
db.createCollection("魔兽世界_3月库");
|
||||
254
Server/README_scheduler.md
Normal file
254
Server/README_scheduler.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 统一任务调度器使用说明
|
||||
|
||||
## 概述
|
||||
|
||||
统一任务调度器(TaskSchedulerCommand)是一个集中管理所有定时任务的调度系统,支持:
|
||||
- ✅ 单条 crontab 配置管理所有任务
|
||||
- ✅ 多进程并发执行任务
|
||||
- ✅ 自动根据 cron 表达式判断任务执行时间
|
||||
- ✅ 任务锁机制,防止重复执行
|
||||
- ✅ 完善的日志记录
|
||||
|
||||
## 安装配置
|
||||
|
||||
### 1. 配置文件
|
||||
|
||||
任务配置位于 `config/task_scheduler.php`,格式如下:
|
||||
|
||||
```php
|
||||
'任务标识' => [
|
||||
'command' => '命令名称', // 必填:执行的命令
|
||||
'schedule' => 'cron表达式', // 必填:cron表达式
|
||||
'options' => ['--option=value'], // 可选:命令参数
|
||||
'enabled' => true, // 可选:是否启用
|
||||
'max_concurrent' => 1, // 可选:最大并发数
|
||||
'timeout' => 3600, // 可选:超时时间(秒)
|
||||
'log_file' => 'custom.log', // 可选:自定义日志文件
|
||||
]
|
||||
```
|
||||
|
||||
### 2. Cron 表达式格式
|
||||
|
||||
标准 cron 格式:`分钟 小时 日 月 星期`
|
||||
|
||||
示例:
|
||||
- `*/1 * * * *` - 每分钟执行
|
||||
- `*/5 * * * *` - 每5分钟执行
|
||||
- `*/30 * * * *` - 每30分钟执行
|
||||
- `0 2 * * *` - 每天凌晨2点执行
|
||||
- `0 3 */3 * *` - 每3天的3点执行
|
||||
|
||||
### 3. Crontab 配置
|
||||
|
||||
**只需要在 crontab 中添加一条任务:**
|
||||
|
||||
```bash
|
||||
# 每分钟执行一次调度器(调度器内部会根据 cron 表达式判断哪些任务需要执行)
|
||||
* * * * * cd /www/wwwroot/mckb_quwanzhi_com/Server && php think scheduler:run >> /www/wwwroot/mckb_quwanzhi_com/Server/runtime/log/scheduler.log 2>&1
|
||||
```
|
||||
|
||||
### 4. 系统要求
|
||||
|
||||
- PHP >= 5.6.0
|
||||
- 推荐启用 `pcntl` 扩展以支持多进程并发(非必需,未启用时使用单进程顺序执行)
|
||||
|
||||
检查 pcntl 扩展:
|
||||
```bash
|
||||
php -m | grep pcntl
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 手动执行调度器
|
||||
|
||||
```bash
|
||||
# 执行调度器(会自动判断当前时间需要执行的任务)
|
||||
php think scheduler:run
|
||||
```
|
||||
|
||||
### 查看任务配置
|
||||
|
||||
```bash
|
||||
# 查看所有已注册的命令
|
||||
php think list
|
||||
```
|
||||
|
||||
### 启用/禁用任务
|
||||
|
||||
编辑 `config/task_scheduler.php`,设置 `'enabled' => false` 即可禁用任务。
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 多进程并发执行
|
||||
|
||||
- 默认最大并发数:10 个进程
|
||||
- 自动管理进程池
|
||||
- 自动清理僵尸进程
|
||||
|
||||
### 2. 任务锁机制
|
||||
|
||||
- 每个任务在执行时会设置锁(5分钟内不重复执行)
|
||||
- 防止任务重复执行
|
||||
- 锁存储在缓存中,自动过期
|
||||
|
||||
### 3. 日志记录
|
||||
|
||||
- 调度器日志:`runtime/log/scheduler.log`
|
||||
- 每个任务的日志:`runtime/log/{log_file}`
|
||||
- 任务执行开始和结束都有标记
|
||||
|
||||
### 4. 超时控制
|
||||
|
||||
- 默认超时时间:3600 秒(1小时)
|
||||
- 可在配置中为每个任务单独设置超时时间
|
||||
- 超时后自动终止任务
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 高频任务(每分钟)
|
||||
|
||||
```php
|
||||
'wechat_friends_active' => [
|
||||
'command' => 'wechatFriends:list',
|
||||
'schedule' => '*/1 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 中频任务(每5分钟)
|
||||
|
||||
```php
|
||||
'device_active' => [
|
||||
'command' => 'device:list',
|
||||
'schedule' => '*/5 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 每日任务
|
||||
|
||||
```php
|
||||
'wechat_calculate_score' => [
|
||||
'command' => 'wechat:calculate-score',
|
||||
'schedule' => '0 2 * * *', // 每天凌晨2点
|
||||
'options' => [],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
### 定期任务(每3天)
|
||||
|
||||
```php
|
||||
'sync_all_friends' => [
|
||||
'command' => 'sync:allFriends',
|
||||
'schedule' => '0 3 */3 * *', // 每3天的3点
|
||||
'options' => [],
|
||||
'enabled' => true,
|
||||
],
|
||||
```
|
||||
|
||||
## 从旧配置迁移
|
||||
|
||||
### 旧配置(多条 crontab)
|
||||
|
||||
```bash
|
||||
*/5 * * * * cd /path && php think device:list --isDel=0 >> log1.log 2>&1
|
||||
*/1 * * * * cd /path && php think wechatFriends:list >> log2.log 2>&1
|
||||
```
|
||||
|
||||
### 新配置(单条 crontab + 配置文件)
|
||||
|
||||
**Crontab:**
|
||||
```bash
|
||||
* * * * * cd /path && php think scheduler:run >> scheduler.log 2>&1
|
||||
```
|
||||
|
||||
**config/task_scheduler.php:**
|
||||
```php
|
||||
'device_active' => [
|
||||
'command' => 'device:list',
|
||||
'schedule' => '*/5 * * * *',
|
||||
'options' => ['--isDel=0'],
|
||||
'log_file' => 'log1.log',
|
||||
],
|
||||
'wechat_friends' => [
|
||||
'command' => 'wechatFriends:list',
|
||||
'schedule' => '*/1 * * * *',
|
||||
'log_file' => 'log2.log',
|
||||
],
|
||||
```
|
||||
|
||||
## 监控和调试
|
||||
|
||||
### 查看调度器日志
|
||||
|
||||
```bash
|
||||
tail -f runtime/log/scheduler.log
|
||||
```
|
||||
|
||||
### 查看任务执行日志
|
||||
|
||||
```bash
|
||||
tail -f runtime/log/crontab_device_active.log
|
||||
```
|
||||
|
||||
### 检查任务是否在执行
|
||||
|
||||
```bash
|
||||
# 查看进程
|
||||
ps aux | grep "php think"
|
||||
```
|
||||
|
||||
### 手动测试任务
|
||||
|
||||
```bash
|
||||
# 直接执行某个任务
|
||||
php think device:list --isDel=0
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **时间同步**:确保服务器时间准确,调度器依赖系统时间判断任务执行时间
|
||||
2. **资源限制**:根据服务器性能调整 `maxConcurrent` 参数
|
||||
3. **日志清理**:定期清理日志文件,避免占用过多磁盘空间
|
||||
4. **任务冲突**:如果任务执行时间较长,建议调整执行频率或增加并发数
|
||||
5. **缓存依赖**:任务锁使用缓存,确保缓存服务正常运行
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 任务未执行
|
||||
|
||||
1. 检查任务是否启用:`'enabled' => true`
|
||||
2. 检查 cron 表达式是否正确
|
||||
3. 检查调度器是否正常运行:查看 `scheduler.log`
|
||||
4. 检查任务锁:任务可能在5分钟内重复执行被跳过
|
||||
|
||||
### 任务执行失败
|
||||
|
||||
1. 查看任务日志:`runtime/log/{log_file}`
|
||||
2. 检查命令是否正确:手动执行命令测试
|
||||
3. 检查权限:确保有执行权限和日志写入权限
|
||||
|
||||
### 多进程不工作
|
||||
|
||||
1. 检查 pcntl 扩展:`php -m | grep pcntl`
|
||||
2. 检查系统限制:`ulimit -u` 查看最大进程数
|
||||
3. 查看调度器日志中的错误信息
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **合理设置并发数**:根据服务器 CPU 核心数和内存大小调整
|
||||
2. **错开高频任务**:避免所有任务在同一分钟执行
|
||||
3. **优化任务执行时间**:减少任务执行时长
|
||||
4. **使用队列**:对于耗时任务,建议使用队列异步处理
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.0 (2024-01-XX)
|
||||
- 初始版本
|
||||
- 支持多进程并发执行
|
||||
- 支持 cron 表达式调度
|
||||
- 支持任务锁机制
|
||||
|
||||
@@ -61,10 +61,8 @@ class MessageController extends BaseController
|
||||
// 发送请求获取好友列表
|
||||
$result = requestCurl($this->baseUrl . 'api/WechatFriend/listWechatFriendForMsgPagination', $params, 'POST', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 获取同步消息标志
|
||||
$syncMessages = $this->request->param('syncMessages', true);
|
||||
|
||||
// 如果需要同步消息,则获取每个好友的消息
|
||||
if ($syncMessages && !empty($response['results'])) {
|
||||
$from = strtotime($fromTime) * 1000;
|
||||
@@ -77,7 +75,7 @@ class MessageController extends BaseController
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 100,
|
||||
'count' => 20,
|
||||
'messageId' => '',
|
||||
'olderData' => true,
|
||||
'wechatAccountId' => $friend['wechatAccountId'],
|
||||
@@ -90,7 +88,6 @@ class MessageController extends BaseController
|
||||
// 调用获取消息的接口
|
||||
$messageResult = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $messageParams, 'GET', $header, 'json');
|
||||
$messageResponse = handleApiResponse($messageResult);
|
||||
|
||||
// 保存消息到数据库
|
||||
if (!empty($messageResponse)) {
|
||||
foreach ($messageResponse as $item) {
|
||||
@@ -241,7 +238,7 @@ class MessageController extends BaseController
|
||||
'keyword' => '',
|
||||
'msgType' =>'',
|
||||
'accountId' => '',
|
||||
'count' => 100,
|
||||
'count' => 20,
|
||||
'messageId' => '',
|
||||
'olderData' => true,
|
||||
'wechatId' => '',
|
||||
@@ -353,12 +350,12 @@ class MessageController extends BaseController
|
||||
{
|
||||
// 检查消息是否已存在
|
||||
$exists = WechatMessageModel::where('id', $item['id']) ->find();
|
||||
|
||||
// 如果消息已存在,直接返回
|
||||
if ($exists) {
|
||||
return;
|
||||
|
||||
if (!empty($exists) && $exists['sendStatus'] == 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// 将毫秒时间戳转换为秒级时间戳
|
||||
$createTime = isset($item['createTime']) ? strtotime($item['createTime']) : null;
|
||||
$deleteTime = !empty($item['isDeleted']) ? strtotime($item['deleteTime']) : null;
|
||||
@@ -387,7 +384,8 @@ class MessageController extends BaseController
|
||||
'wechatTime' => $wechatTime
|
||||
];
|
||||
|
||||
//已被删除
|
||||
|
||||
//已被删除
|
||||
if ($item['msgType'] == 10000 && strpos($item['content'],'开启了朋友验证') !== false) {
|
||||
Db::table('s2_wechat_friend')->where('id',$item['wechatFriendId'])->update(['isDeleted'=> 1,'deleteTime' => $wechatTime]);
|
||||
}else{
|
||||
@@ -399,9 +397,9 @@ class MessageController extends BaseController
|
||||
if (!empty($accountData)){
|
||||
$account = new AccountController();
|
||||
$account->getlist(['pageIndex' => 0,'pageSize' => 100,'departmentId' => $accountData['departmentId']]);
|
||||
$accountIds = Db::table('s2_company_account')->where(['id' => $accountId,'alive' => 1])->column('id');
|
||||
$accountIds = Db::table('s2_company_account')->where(['departmentId' => $accountData['departmentId'],'alive' => 1])->column('id');
|
||||
if (!empty($accountIds)){
|
||||
if (!in_array($friend['accountId'],$accountData)){
|
||||
if (!in_array($friend['accountId'],$accountIds)){
|
||||
// 执行切换好友命令
|
||||
$randomKey = array_rand($accountIds, 1);
|
||||
$toAccountId = $accountIds[$randomKey];
|
||||
@@ -425,8 +423,19 @@ class MessageController extends BaseController
|
||||
}
|
||||
}
|
||||
}
|
||||
// 创建新记录
|
||||
$res = WechatMessageModel::create($data);
|
||||
|
||||
$id = '';
|
||||
if (empty($exists)){
|
||||
// 创建新记录
|
||||
$res = WechatMessageModel::create($data);
|
||||
$id= $res['id'];
|
||||
}else{
|
||||
$id = $data['id'];
|
||||
unset($data['id']);
|
||||
$res = $exists->save($data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 1 文字 3图片 47动态图片 34语言 43视频 42名片 40/20链接 49文件
|
||||
if (!empty($res) && empty($item['isSend']) && in_array($item['msgType'],[1,3,20,34,40,42,43,47,49])){
|
||||
@@ -439,13 +448,14 @@ class MessageController extends BaseController
|
||||
'companyId' => $friend['companyId'],
|
||||
'trafficPoolId' => $trafficPoolId,
|
||||
'source' => 0,
|
||||
'uniqueId' => $res['id'],
|
||||
'uniqueId' => $id,
|
||||
'sourceData' => json_encode([]),
|
||||
'remark' => '用户发送了消息',
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
Db::name('user_portrait')->insert($data);
|
||||
Db::name('user_portrait')->insert($data);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -461,9 +471,8 @@ class MessageController extends BaseController
|
||||
{
|
||||
// 检查消息是否已存在
|
||||
$exists = WechatMessageModel::where('id', $item['id'])->find();
|
||||
|
||||
// 如果消息已存在,直接返回
|
||||
if ($exists) {
|
||||
|
||||
if (!empty($exists) && $exists['sendStatus'] == 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -515,7 +524,12 @@ class MessageController extends BaseController
|
||||
|
||||
// 创建新记录
|
||||
try {
|
||||
WechatMessageModel::create($data);
|
||||
if(empty($exists)){
|
||||
WechatMessageModel::create($data);
|
||||
}else{
|
||||
unset($data['id']);
|
||||
$exists->save($data);
|
||||
}
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
|
||||
@@ -17,7 +17,7 @@ class MomentsController extends BaseController
|
||||
// 获取授权token
|
||||
$authorization = $this->authorization;
|
||||
if (empty($authorization)) {
|
||||
return errorJson('缺少授权信息');
|
||||
return json_encode(['msg' => '缺少授权信息','code' => 400]);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -42,18 +42,18 @@ class MomentsController extends BaseController
|
||||
|
||||
// 必填参数验证
|
||||
if (empty($jobPublishWechatMomentsItems) || !is_array($jobPublishWechatMomentsItems)) {
|
||||
return errorJson('至少需要选择一个发布账号');
|
||||
return json_encode(['msg' => '至少需要选择一个发布账号','code' => 400]);
|
||||
}
|
||||
|
||||
// 根据朋友圈类型验证必填字段
|
||||
if ($momentContentType == 1 && empty($text)) { // 纯文本
|
||||
return errorJson('朋友圈内容不能为空');
|
||||
return json_encode(['msg' => '朋友圈内容不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 2 && (empty($picUrlList) || empty($text))) { // 图片+文字
|
||||
return errorJson('朋友圈内容和图片不能为空');
|
||||
return json_encode(['msg' => '朋友圈内容和图片不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 3 && (empty($videoUrl) || empty($text))) { // 视频+文字
|
||||
return errorJson('朋友圈内容和视频不能为空');
|
||||
return json_encode(['msg' => '朋友圈内容和视频不能为空','code' => 400]);
|
||||
} else if ($momentContentType == 4 && (empty($link) || empty($text))) { // 链接+文字
|
||||
return errorJson('朋友圈内容和链接不能为空');
|
||||
return json_encode(['msg' => '朋友圈内容和链接不能为空','code' => 400]);
|
||||
}
|
||||
|
||||
// 构建请求参数
|
||||
@@ -84,13 +84,13 @@ class MomentsController extends BaseController
|
||||
$result = requestCurl($this->baseUrl . 'api/JobPublishWechatMoments/addJob', $params, 'POST', $header, 'json');
|
||||
// 处理响应
|
||||
if (empty($result)) {
|
||||
return successJson([], '朋友圈任务创建成功');
|
||||
return json_encode(['msg' => '朋友圈任务创建成功','code' => 200]);
|
||||
} else {
|
||||
// 如果返回的是错误信息
|
||||
return errorJson($result);
|
||||
return json_encode(['msg' => $result,'code' => 400]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('发布朋友圈失败:' . $e->getMessage());
|
||||
return json_encode(['msg' => '发布朋友圈失败','code' => 400]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -457,22 +457,16 @@ class WebSocketController extends BaseController
|
||||
|
||||
// 构建请求参数
|
||||
$params = [
|
||||
"cmdType" => 'CmdDownloadMomentImagesResult',
|
||||
"cmdType" => 'CmdDownloadMomentImages',
|
||||
"snsId" => $data['snsId'],
|
||||
"urls" => $data['snsUrls'],
|
||||
"wechatAccountId" => $data['wechatAccountId'],
|
||||
"seq" => time(),
|
||||
];
|
||||
|
||||
// 记录请求日志
|
||||
Log::info('获取朋友圈资源链接请求:' . json_encode($params, 256));
|
||||
|
||||
// 发送请求
|
||||
$this->client->send(json_encode($params));
|
||||
|
||||
// 接收响应
|
||||
$response = $this->client->receive();
|
||||
$message = json_decode($response, true);
|
||||
$message = $this->sendMessage($params);
|
||||
|
||||
if (empty($message)) {
|
||||
return json_encode(['code' => 500, 'msg' => '获取朋友圈资源链接失败']);
|
||||
@@ -558,15 +552,17 @@ class WebSocketController extends BaseController
|
||||
$dataToSave['create_time'] = time();
|
||||
$res = WechatMoments::create($dataToSave);
|
||||
}
|
||||
// // 获取资源链接
|
||||
// if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls'])){
|
||||
// $snsData = [
|
||||
// 'snsId' => $moment['snsId'],
|
||||
// 'snsUrls' => $momentEntity['urls'],
|
||||
// 'wechatAccountId' => $wechatAccountId,
|
||||
// ];
|
||||
// $this->getMomentSourceRealUrl($snsData);
|
||||
// }
|
||||
|
||||
|
||||
// 获取资源链接
|
||||
if(empty($momentEntity['resUrls']) && !empty($momentEntity['urls']) && $moment['type'] == 1) {
|
||||
$snsData = [
|
||||
'snsId' => $moment['snsId'],
|
||||
'snsUrls' => $momentEntity['urls'],
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
];
|
||||
$this->getMomentSourceRealUrl($snsData);
|
||||
}
|
||||
|
||||
}
|
||||
//Log::write('朋友圈数据已存入数据库,共' . count($momentList) . '条');
|
||||
|
||||
@@ -7,5 +7,5 @@ use think\Model;
|
||||
class WechatAccountModel extends Model
|
||||
{
|
||||
// 设置表名
|
||||
protected $table = 's2_wechat_account';
|
||||
protected $table = 's2_wechat_account';
|
||||
}
|
||||
@@ -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 () {
|
||||
@@ -32,15 +34,22 @@ Route::group('v1/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\AccountsController@getList'); // 获取账号列表
|
||||
});
|
||||
|
||||
//客服相关
|
||||
//消息相关
|
||||
Route::group('message/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\MessageController@getList'); // 获取好友列表
|
||||
Route::get('readMessage', 'app\chukebao\controller\MessageController@readMessage'); // 读取消息
|
||||
Route::get('details', 'app\chukebao\controller\MessageController@details'); // 消息详情
|
||||
Route::get('getMessageStatus', 'app\chukebao\controller\MessageController@getMessageStatus'); // 获取单条消息发送状态
|
||||
});
|
||||
|
||||
//微信分组
|
||||
Route::get('wechatGroup/list', 'app\chukebao\controller\WechatGroupController@getList'); // 微信分组
|
||||
Route::group('wechatGroup/', function () {
|
||||
Route::get('list', 'app\chukebao\controller\WechatGroupController@getList'); // 获取分组列表
|
||||
Route::post('add', 'app\chukebao\controller\WechatGroupController@create'); // 新增分组
|
||||
Route::post('update', 'app\chukebao\controller\WechatGroupController@update'); // 更新分组
|
||||
Route::delete('delete', 'app\chukebao\controller\WechatGroupController@delete'); // 删除分组(假删除)
|
||||
Route::post('move', 'app\chukebao\controller\WechatGroupController@move'); // 移动分组(好友/群移动到指定分组)
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ class AccountsController extends BaseController
|
||||
|
||||
$query = Db::table('s2_company_account')
|
||||
->alias('a')
|
||||
->join('users u', 'a.id = u.s2_accountId')
|
||||
->where([
|
||||
['a.departmentId', '=', $companyId],
|
||||
['a.status', '=', 0],
|
||||
@@ -48,6 +49,7 @@ class AccountsController extends BaseController
|
||||
$total = (clone $query)->count();
|
||||
$list = $query->field([
|
||||
'a.id',
|
||||
'u.id as uid',
|
||||
'a.userName',
|
||||
'a.realName',
|
||||
'a.nickname',
|
||||
|
||||
@@ -562,7 +562,7 @@ class AiChatController extends BaseController
|
||||
$data = [
|
||||
'tokens' => $tokenCount * 20,
|
||||
'type' => 0,
|
||||
'form' => 1,
|
||||
'form' => 13,
|
||||
'wechatAccountId' => $params['wechatAccountId'],
|
||||
'friendIdOrGroupId' => $params['friendId'],
|
||||
'remarks' => $remarks,
|
||||
@@ -816,7 +816,7 @@ class AiChatController extends BaseController
|
||||
$data = [
|
||||
'tokens' => $res['data']['token'],
|
||||
'type' => 0,
|
||||
'form' => 1,
|
||||
'form' => 13,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $friendId,
|
||||
'remarks' => $remarks,
|
||||
|
||||
@@ -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,8 @@ class DataProcessing extends BaseController
|
||||
'CmdAllotFriend', //转让好友 {labels、wechatAccountId、wechatFriendId}
|
||||
'CmdChatroomOperate', //修改群信息 {chatroomName(群名)、announce(公告)、extra(公告)、wechatAccountId、wechatChatroomId}
|
||||
'CmdNewMessage', //接收消息
|
||||
'CmdSendMessageResult', //更新消息状态
|
||||
'CmdPinToTop', //置顶
|
||||
];
|
||||
|
||||
if (empty($type) || empty($wechatAccountId)) {
|
||||
@@ -75,15 +79,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 +125,81 @@ 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;
|
||||
case 'CmdPinToTop': //置顶
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', 0);
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', 0);
|
||||
$isTop = $this->request->param('isTop', null);
|
||||
|
||||
if ($isTop === null) {
|
||||
return ResponseHelper::error('isTop不能为空');
|
||||
}
|
||||
|
||||
if (empty($wechatFriendId) && empty($wechatChatroomId)) {
|
||||
return ResponseHelper::error('wechatFriendId或chatroomId至少提供一个');
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatFriendId)){
|
||||
$data = WechatFriendModel::where(['id' => $wechatFriendId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('好友不存在');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!empty($wechatChatroomId)){
|
||||
$data = WechatChatroomModel::where(['id' => $wechatChatroomId,'wechatAccountId' => $wechatAccountId])->find();
|
||||
$msg = $isTop == 1 ? '已置顶' : '取消置顶';
|
||||
if(empty($data)){
|
||||
return ResponseHelper::error('群聊不存在');
|
||||
}
|
||||
}
|
||||
|
||||
$data->updateTime = time();
|
||||
$data->isTop = $isTop;
|
||||
$data->save();
|
||||
break;
|
||||
}
|
||||
return ResponseHelper::success('',$msg,$codee);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,24 @@
|
||||
|
||||
namespace app\chukebao\controller;
|
||||
|
||||
use app\api\model\WechatMessageModel;
|
||||
use app\chukebao\model\FriendSettings;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Env;
|
||||
use app\common\service\AuthService;
|
||||
|
||||
class MessageController extends BaseController
|
||||
{
|
||||
protected $baseUrl;
|
||||
protected $authorization;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->baseUrl = Env::get('api.wechat_url');
|
||||
$this->authorization = AuthService::getSystemAuthorization();
|
||||
}
|
||||
|
||||
public function getList()
|
||||
{
|
||||
@@ -20,7 +32,7 @@ class MessageController extends BaseController
|
||||
|
||||
$friends = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region');
|
||||
->column('id,nickname,avatar,conRemark,labels,groupId,wechatAccountId,wechatId,extendFields,phone,region,isTop');
|
||||
|
||||
|
||||
// 构建好友子查询
|
||||
@@ -31,7 +43,7 @@ class MessageController extends BaseController
|
||||
|
||||
// 优化后的查询:使用MySQL兼容的查询方式
|
||||
$unionQuery = "
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime,m.wechatAccountId, 2 as msgType, wc.nickname, wc.chatroomAvatar as avatar, wc.chatroomId, wc.isTop
|
||||
FROM s2_wechat_chatroom wc
|
||||
INNER JOIN s2_wechat_message m ON wc.id = m.wechatChatroomId AND m.type = 2
|
||||
INNER JOIN (
|
||||
@@ -43,7 +55,7 @@ class MessageController extends BaseController
|
||||
WHERE wc.accountId = {$accountId} AND wc.isDeleted = 0
|
||||
)
|
||||
UNION ALL
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId
|
||||
(SELECT m.id, m.content, m.wechatFriendId, m.wechatChatroomId, m.createTime, m.wechatTime, 1 as msgType, 1 as nickname, 1 as avatar, 1 as chatroomId, 1 as wechatAccountId, 0 as isTop
|
||||
FROM s2_wechat_message m
|
||||
INNER JOIN (
|
||||
SELECT wechatFriendId, MAX(wechatTime) as maxTime, MAX(id) as maxId
|
||||
@@ -64,7 +76,6 @@ class MessageController extends BaseController
|
||||
return $b['wechatTime'] <=> $a['wechatTime'];
|
||||
});
|
||||
|
||||
|
||||
// 批量统计未读数量(isRead=0),按好友/群聊分别聚合
|
||||
$friendIds = [];
|
||||
$chatroomIds = [];
|
||||
@@ -122,6 +133,7 @@ class MessageController extends BaseController
|
||||
$v['extendFields'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['extendFields'] : [];
|
||||
$v['region'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['region'] : '';
|
||||
$v['phone'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['phone'] : '';
|
||||
$v['isTop'] = !empty($friends[$v['wechatFriendId']]) ? $friends[$v['wechatFriendId']]['isTop'] : 0;
|
||||
$v['labels'] = !empty($friends[$v['wechatFriendId']]) ? json_decode($friends[$v['wechatFriendId']]['labels'], true) : [];
|
||||
|
||||
$unreadCount = isset($friendUnreadMap[$v['wechatFriendId']]) ? (int)$friendUnreadMap[$v['wechatFriendId']] : 0;
|
||||
@@ -136,7 +148,7 @@ class MessageController extends BaseController
|
||||
|
||||
$v['id'] = !empty($v['wechatFriendId']) ? $v['wechatFriendId'] : $v['wechatChatroomId'];
|
||||
$v['config'] = [
|
||||
'top' => false,
|
||||
'top' => !empty($v['isTop']) ? true : false,
|
||||
'unreadCount' => $unreadCount,
|
||||
'chat' => true,
|
||||
'msgTime' => $v['wechatTime'],
|
||||
@@ -150,7 +162,7 @@ class MessageController extends BaseController
|
||||
'wechatTime' => $wechatTime
|
||||
];
|
||||
|
||||
unset($v['wechatFriendId'], $v['wechatChatroomId']);
|
||||
unset($v['wechatFriendId'], $v['wechatChatroomId'],$v['isTop']);
|
||||
|
||||
}
|
||||
unset($v);
|
||||
@@ -184,6 +196,110 @@ class MessageController extends BaseController
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单条消息发送状态(带轮询功能)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getMessageStatus()
|
||||
{
|
||||
$messageId = $this->request->param('messageId', 0);
|
||||
$wechatAccountId = $this->request->param('wechatAccountId', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
$wechatChatroomId = $this->request->param('wechatChatroomId', '');
|
||||
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($messageId)) {
|
||||
return ResponseHelper::error('消息ID不能为空');
|
||||
}
|
||||
|
||||
if(empty($wechatFriendId) && empty($wechatChatroomId)) {
|
||||
return ResponseHelper::error('消息类型不能为空');
|
||||
}
|
||||
|
||||
// 查询单条消息的基本信息(只需要发送状态相关字段)
|
||||
$message = Db::table('s2_wechat_message')
|
||||
->where('id', $messageId)
|
||||
->field('id,wechatAccountId,wechatFriendId,wechatChatroomId,sendStatus')
|
||||
->find();
|
||||
|
||||
if (empty($message)) {
|
||||
$message = [
|
||||
'id' => $messageId,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'wechatFriendId' => $wechatFriendId,
|
||||
'wechatChatroomId' => $wechatChatroomId,
|
||||
'sendStatus' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$sendStatus = isset($message['sendStatus']) ? (int)$message['sendStatus'] : 0;
|
||||
$isUpdated = false;
|
||||
$pollCount = 0;
|
||||
$maxPollCount = 10; // 最多轮询10次
|
||||
|
||||
// 如果sendStatus不为0,开始轮询
|
||||
if ($sendStatus != 0) {
|
||||
$messageRequest = [
|
||||
'id' => $message['id'],
|
||||
'wechatAccountId' => !empty($wechatAccountId) ? $wechatAccountId : $message['wechatAccountId'],
|
||||
'wechatFriendId' => !empty($message['wechatFriendId']) ? $message['wechatFriendId'] : '',
|
||||
'wechatChatroomId' => !empty($message['wechatChatroomId']) ? $message['wechatChatroomId'] : '',
|
||||
'from' => '',
|
||||
'to' => '',
|
||||
];
|
||||
|
||||
|
||||
// 轮询逻辑:最多10次
|
||||
while ($pollCount < $maxPollCount && $sendStatus != 0) {
|
||||
$pollCount++;
|
||||
|
||||
// 请求线上接口获取最新状态
|
||||
$newData = $this->fetchLatestMessageFromApi($messageRequest);
|
||||
|
||||
if (!empty($newData)) {
|
||||
// 重新查询消息状态(可能已更新)
|
||||
$updatedMessage = Db::table('s2_wechat_message')
|
||||
->where('id', $messageId)
|
||||
->field('sendStatus')
|
||||
->find();
|
||||
|
||||
if (!empty($updatedMessage)) {
|
||||
$newSendStatus = isset($updatedMessage['sendStatus']) ? (int)$updatedMessage['sendStatus'] : 0;
|
||||
|
||||
// 如果状态已更新为0(已发送),停止轮询
|
||||
if ($newSendStatus == 0) {
|
||||
$sendStatus = 0;
|
||||
$isUpdated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果状态仍然是1,继续轮询(但需要等待一下,避免请求过快)
|
||||
if ($newSendStatus != 0 && $pollCount < $maxPollCount) {
|
||||
// 每次轮询间隔500毫秒(0.5秒)
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果请求失败,等待后继续尝试
|
||||
if ($pollCount < $maxPollCount) {
|
||||
usleep(500000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返回发送状态信息
|
||||
return ResponseHelper::success([
|
||||
'messageId' => $messageId,
|
||||
'sendStatus' => $sendStatus,
|
||||
'statusText' => $sendStatus == 0 ? '已发送' : '发送中'
|
||||
]);
|
||||
}
|
||||
|
||||
public function details()
|
||||
{
|
||||
$wechatFriendId = $this->request->param('wechatFriendId', '');
|
||||
@@ -218,14 +334,143 @@ class MessageController extends BaseController
|
||||
$total = Db::table('s2_wechat_message')->where($where)->count();
|
||||
$list = Db::table('s2_wechat_message')->where($where)->page($page, $limit)->order('id DESC')->select();
|
||||
|
||||
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['wechatTime'] = !empty($v['wechatTime']) ? date('Y-m-d H:i:s', $v['wechatTime']) : '';
|
||||
// 检查消息是否有sendStatus字段,如果有且不为0,则请求线上最新接口
|
||||
foreach ($list as $k => &$item) {
|
||||
// 检查是否存在sendStatus字段且不为0(0表示已发送成功)
|
||||
if (isset($item['sendStatus']) && $item['sendStatus'] != 0) {
|
||||
// 需要请求新的数据
|
||||
$messageRequest = [
|
||||
'id' => $item['id'],
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'wechatFriendId' => $wechatFriendId,
|
||||
'wechatChatroomId' => $wechatChatroomId,
|
||||
'from' => '',
|
||||
'to' => '',
|
||||
];
|
||||
$newData = $this->fetchLatestMessageFromApi($messageRequest);
|
||||
if (!empty($newData)){
|
||||
$item['sendStatus'] = 0;
|
||||
}
|
||||
}
|
||||
// 格式化时间
|
||||
$item['wechatTime'] = !empty($item['wechatTime']) ? date('Y-m-d H:i:s', $item['wechatTime']) : '';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
|
||||
return ResponseHelper::success(['total' => $total, 'list' => $list]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 从线上接口获取最新消息
|
||||
* @param array $messageRequest 消息项(包含wechatAccountId、wechatFriendId或wechatChatroomId、id等)
|
||||
* @return array|null 最新消息数据,失败返回null
|
||||
*/
|
||||
private function fetchLatestMessageFromApi($messageRequest)
|
||||
{
|
||||
if (empty($this->baseUrl) || empty($this->authorization)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 设置请求头
|
||||
$headerData = ['client:system'];
|
||||
$header = setHeader($headerData, $this->authorization, 'json');
|
||||
|
||||
// 判断是好友消息还是群聊消息
|
||||
if (!empty($messageRequest['wechatFriendId'])) {
|
||||
// 好友消息接口
|
||||
$params = [
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 20, // 获取多条消息以便找到对应的消息
|
||||
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
|
||||
'olderData' => true,
|
||||
'wechatAccountId' => $messageRequest['wechatAccountId'],
|
||||
'wechatFriendId' => $messageRequest['wechatFriendId'],
|
||||
'from' => $messageRequest['from'],
|
||||
'to' => $messageRequest['to'],
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
$result = requestCurl($this->baseUrl . 'api/FriendMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
// 查找对应的消息
|
||||
if (!empty($response) && is_array($response)) {
|
||||
$data = $response[0];
|
||||
if ($data['sendStatus'] == 0){
|
||||
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} elseif (!empty($messageRequest['wechatChatroomId'])) {
|
||||
// 群聊消息接口
|
||||
$params = [
|
||||
'keyword' => '',
|
||||
'msgType' => '',
|
||||
'accountId' => '',
|
||||
'count' => 20, // 获取多条消息以便找到对应的消息
|
||||
'messageId' => isset($messageRequest['id']) ? $messageRequest['id'] : '',
|
||||
'olderData' => true,
|
||||
'wechatId' => '',
|
||||
'wechatAccountId' => $messageRequest['wechatAccountId'],
|
||||
'wechatChatroomId' => $messageRequest['wechatChatroomId'],
|
||||
'from' => $messageRequest['from'],
|
||||
'to' => $messageRequest['to'],
|
||||
'searchFrom' => 'admin'
|
||||
];
|
||||
|
||||
$result = requestCurl($this->baseUrl . 'api/ChatroomMessage/searchMessage', $params, 'GET', $header, 'json');
|
||||
$response = handleApiResponse($result);
|
||||
|
||||
// 查找对应的消息
|
||||
if (!empty($response) && is_array($response)) {
|
||||
$data = $response[0];
|
||||
if ($data['sendStatus'] == 0){
|
||||
WechatMessageModel::where(['id' => $data['id']])->update(['sendStatus' => 0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('获取线上最新消息失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新数据库中的消息
|
||||
* @param array $latestMessage 线上获取的最新消息
|
||||
* @param array $oldMessage 旧消息数据
|
||||
*/
|
||||
private function updateMessageInDatabase($latestMessage, $oldMessage)
|
||||
{
|
||||
try {
|
||||
// 使用API模块的MessageController来保存消息
|
||||
$apiMessageController = new \app\api\controller\MessageController();
|
||||
|
||||
// 判断是好友消息还是群聊消息
|
||||
if (!empty($oldMessage['wechatFriendId'])) {
|
||||
// 保存好友消息
|
||||
$apiMessageController->saveMessage($latestMessage);
|
||||
} elseif (!empty($oldMessage['wechatChatroomId'])) {
|
||||
// 保存群聊消息
|
||||
$apiMessageController->saveChatroomMessage($latestMessage);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('更新数据库消息失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@ class TokensRecordController extends BaseController
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$type = $this->request->param('type', '');
|
||||
$form = $this->request->param('form', '');
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
$userId = $this->getUserInfo('id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
@@ -32,6 +34,26 @@ class TokensRecordController extends BaseController
|
||||
$where[] = ['form','=',$form];
|
||||
}
|
||||
|
||||
// 时间筛选
|
||||
if (!empty($startTime)) {
|
||||
// 支持时间戳或日期字符串格式
|
||||
$startTimestamp = is_numeric($startTime) ? intval($startTime) : strtotime($startTime);
|
||||
if ($startTimestamp !== false) {
|
||||
$where[] = ['createTime', '>=', $startTimestamp];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($endTime)) {
|
||||
// 支持时间戳或日期字符串格式
|
||||
$endTimestamp = is_numeric($endTime) ? intval($endTime) : strtotime($endTime);
|
||||
if ($endTimestamp !== false) {
|
||||
// 如果是日期字符串,自动设置为当天的23:59:59
|
||||
if (!is_numeric($endTime)) {
|
||||
$endTimestamp = strtotime(date('Y-m-d 23:59:59', $endTimestamp));
|
||||
}
|
||||
$where[] = ['createTime', '<=', $endTimestamp];
|
||||
}
|
||||
}
|
||||
|
||||
$query = TokensRecord::where($where);
|
||||
$total = $query->count();
|
||||
@@ -78,9 +100,6 @@ class TokensRecordController extends BaseController
|
||||
return ResponseHelper::error('类型参数错误,0为减少,1为增加');
|
||||
}
|
||||
|
||||
if (!in_array($form, [0, 1, 2, 3, 4, 5])) {
|
||||
return ResponseHelper::error('来源参数错误');
|
||||
}
|
||||
|
||||
// 重试机制,最多重试3次
|
||||
$maxRetries = 3;
|
||||
@@ -108,7 +127,7 @@ class TokensRecordController extends BaseController
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 使用悲观锁获取用户当前tokens余额,确保并发安全
|
||||
$userInfo = TokensCompany::where('companyId', $companyId)->lock(true)->find();
|
||||
$userInfo = TokensCompany::where(['companyId'=> $companyId,'userId' => $userId])->lock(true)->find();
|
||||
if (!$userInfo) {
|
||||
throw new \Exception('用户不存在');
|
||||
}
|
||||
|
||||
@@ -14,13 +14,30 @@ class WechatChatroomController extends BaseController
|
||||
public function getList(){
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$groupIds = $this->request->param('groupIds', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)){
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
$query = Db::table('s2_wechat_chatroom')
|
||||
->where(['accountId' => $accountId,'isDeleted' => 0])
|
||||
->order('id desc');
|
||||
->where(['accountId' => $accountId,'isDeleted' => 0]);
|
||||
|
||||
// 关键字搜索:群昵称、微信号(这里使用chatroomId作为群标识)
|
||||
if ($keyword !== '' && $keyword !== null) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('nickname', $like)
|
||||
->whereOr('conRemark', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
// 分组筛选:groupIds(单个分组ID)
|
||||
if ($groupIds !== '' && $groupIds !== null) {
|
||||
$query->where('groupIds', $groupIds);
|
||||
}
|
||||
|
||||
$query->order('id desc');
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
@@ -168,7 +185,7 @@ class WechatChatroomController extends BaseController
|
||||
$data = [
|
||||
'tokens' => $res['data']['token'],
|
||||
'type' => 0,
|
||||
'form' => 3,
|
||||
'form' => 14,
|
||||
'wechatAccountId' => $wechatAccountId,
|
||||
'friendIdOrGroupId' => $groupId,
|
||||
'remarks' => $remarks,
|
||||
|
||||
@@ -13,17 +13,36 @@ class WechatFriendController extends BaseController
|
||||
{
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$groupIds = $this->request->param('groupIds', '');
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
if (empty($accountId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
$query = Db::table('s2_wechat_friend')
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0])
|
||||
->order('id desc');
|
||||
->where(['accountId' => $accountId, 'isDeleted' => 0]);
|
||||
|
||||
// 关键字搜索:昵称、备注、微信号
|
||||
if ($keyword !== '' && $keyword !== null) {
|
||||
$query->where(function ($q) use ($keyword) {
|
||||
$like = '%' . $keyword . '%';
|
||||
$q->whereLike('nickname', $like)
|
||||
->whereOr('conRemark', 'like', $like)
|
||||
->whereOr('alias', 'like', $like)
|
||||
->whereOr('wechatId', 'like', $like);
|
||||
});
|
||||
}
|
||||
|
||||
// 分组筛选:groupIds(单个分组ID)
|
||||
if ($groupIds !== '' && $groupIds !== null) {
|
||||
$query->where('groupIds', $groupIds);
|
||||
}
|
||||
|
||||
$query->order('id desc');
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
// 提取所有好友ID
|
||||
// 提取所有好友ID
|
||||
$friendIds = array_column($list, 'id');
|
||||
|
||||
$aiTypeData = [];
|
||||
@@ -67,7 +86,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 +97,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]);
|
||||
}
|
||||
|
||||
@@ -114,6 +133,7 @@ class WechatFriendController extends BaseController
|
||||
$requestData = $this->request->param();
|
||||
$updatableColumns = [
|
||||
'phone',
|
||||
'conRemark',
|
||||
];
|
||||
$columnUpdates = [];
|
||||
|
||||
@@ -166,4 +186,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] : '未知状态';
|
||||
}
|
||||
}
|
||||
@@ -4,27 +4,32 @@ namespace app\chukebao\controller;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\chukebao\model\ChatGroups;
|
||||
|
||||
class WechatGroupController extends BaseController
|
||||
{
|
||||
|
||||
public function getList(){
|
||||
|
||||
$accountId = $this->getUserInfo('s2_accountId');
|
||||
$userId = $this->getUserInfo('id');
|
||||
/**
|
||||
* 获取分组列表
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
// 公司维度分组,不强制校验 userId
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
$query = Db::table('s2_wechat_group')
|
||||
->where(function ($query) use ($accountId,$companyId) {
|
||||
$query->where('accountId', $accountId)->whereOr('departmentId', $companyId);
|
||||
})
|
||||
->whereIn('groupType',[1,2])
|
||||
->order('groupType desc,sortIndex desc,id desc');
|
||||
$list = $query->select();
|
||||
$query = ChatGroups::where([
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])
|
||||
->order('groupType desc,sort desc,id desc');
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->select();
|
||||
|
||||
|
||||
// 处理每个好友的数据
|
||||
// 处理每个分组的数据
|
||||
$list = is_array($list) ? $list : $list->toArray();
|
||||
foreach ($list as $k => &$v) {
|
||||
$v['createTime'] = !empty($v['createTime']) ? date('Y-m-d H:i:s', $v['createTime']) : '';
|
||||
}
|
||||
@@ -32,4 +37,237 @@ class WechatGroupController extends BaseController
|
||||
|
||||
return ResponseHelper::success(['list'=>$list,'total'=>$total]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增分组
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$groupName = $this->request->param('groupName', '');
|
||||
$groupMemo = $this->request->param('groupMemo', '');
|
||||
$groupType = $this->request->param('groupType', 1);
|
||||
$sort = $this->request->param('sort', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 只校验公司维度
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($groupName)) {
|
||||
return ResponseHelper::error('分组名称不能为空');
|
||||
}
|
||||
|
||||
// 验证分组类型
|
||||
if (!in_array($groupType, [1, 2])) {
|
||||
return ResponseHelper::error('无效的分组类型');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$chatGroup = new ChatGroups();
|
||||
$chatGroup->groupName = $groupName;
|
||||
$chatGroup->groupMemo = $groupMemo;
|
||||
$chatGroup->groupType = $groupType;
|
||||
$chatGroup->sort = $sort;
|
||||
$chatGroup->userId = $this->getUserInfo('id');
|
||||
$chatGroup->companyId = $companyId;
|
||||
$chatGroup->createTime = time();
|
||||
$chatGroup->isDel = 0;
|
||||
$chatGroup->save();
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success(['id' => $chatGroup->id], '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('创建失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分组
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$id = $this->request->param('id', 0);
|
||||
$groupName = $this->request->param('groupName', '');
|
||||
$groupMemo = $this->request->param('groupMemo', '');
|
||||
$groupType = $this->request->param('groupType', 1);
|
||||
$sort = $this->request->param('sort', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
if (empty($groupName)) {
|
||||
return ResponseHelper::error('分组名称不能为空');
|
||||
}
|
||||
|
||||
// 验证分组类型
|
||||
if (!in_array($groupType, [1, 2])) {
|
||||
return ResponseHelper::error('无效的分组类型');
|
||||
}
|
||||
|
||||
// 检查分组是否存在
|
||||
$chatGroup = ChatGroups::where([
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($chatGroup)) {
|
||||
return ResponseHelper::error('该分组不存在或已删除');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
$chatGroup->groupName = $groupName;
|
||||
$chatGroup->groupMemo = $groupMemo;
|
||||
$chatGroup->groupType = $groupType;
|
||||
$chatGroup->sort = $sort;
|
||||
$chatGroup->save();
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '更新成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('更新失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分组(假删除)
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$id = $this->request->param('id', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
// 检查分组是否存在
|
||||
$chatGroup = ChatGroups::where([
|
||||
'id' => $id,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($chatGroup)) {
|
||||
return ResponseHelper::error('该分组不存在或已删除');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 1. 假删除当前分组
|
||||
$chatGroup->isDel = 1;
|
||||
$chatGroup->deleteTime = time();
|
||||
$chatGroup->save();
|
||||
|
||||
// 2. 重置该分组下所有好友的分组ID(s2_wechat_friend.groupIds -> 0)
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('groupIds', $id)
|
||||
->update(['groupIds' => 0]);
|
||||
|
||||
// 3. 重置该分组下所有微信群的分组ID(s2_wechat_chatroom.groupIds -> 0)
|
||||
Db::table('s2_wechat_chatroom')
|
||||
->where('groupIds', $id)
|
||||
->update(['groupIds' => 0]);
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('删除失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动分组(将好友或群移动到指定分组)
|
||||
* @return \think\response\Json
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function move()
|
||||
{
|
||||
// type: friend 好友, chatroom 群
|
||||
$type = $this->request->param('type', 'friend');
|
||||
$targetId = (int)$this->request->param('groupId', 0);
|
||||
// 仅支持单个ID移动
|
||||
$idParam = $this->request->param('id', 0);
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
if (empty($companyId)) {
|
||||
return ResponseHelper::error('请先登录');
|
||||
}
|
||||
|
||||
if (empty($targetId)) {
|
||||
return ResponseHelper::error('目标分组ID不能为空');
|
||||
}
|
||||
|
||||
// 仅允许单个 ID,禁止批量
|
||||
$moveId = (int)$idParam;
|
||||
if (empty($moveId)) {
|
||||
return ResponseHelper::error('需要移动的ID不能为空');
|
||||
}
|
||||
|
||||
// 校验目标分组是否存在且属于当前公司
|
||||
$targetGroup = ChatGroups::where([
|
||||
'id' => $targetId,
|
||||
'companyId' => $companyId,
|
||||
'isDel' => 0,
|
||||
])->find();
|
||||
|
||||
if (empty($targetGroup)) {
|
||||
return ResponseHelper::error('目标分组不存在或已删除');
|
||||
}
|
||||
|
||||
// 校验分组类型与移动对象类型是否匹配
|
||||
// groupType: 1=好友分组, 2=群分组
|
||||
if ($type === 'friend' && (int)$targetGroup->groupType !== 1) {
|
||||
return ResponseHelper::error('目标分组类型错误(需要好友分组)');
|
||||
}
|
||||
if ($type === 'chatroom' && (int)$targetGroup->groupType !== 2) {
|
||||
return ResponseHelper::error('目标分组类型错误(需要群分组)');
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
if ($type === 'friend') {
|
||||
// 移动单个好友到指定分组:更新 s2_wechat_friend.groupIds
|
||||
Db::table('s2_wechat_friend')
|
||||
->where('id', $moveId)
|
||||
->update(['groupIds' => $targetId]);
|
||||
} elseif ($type === 'chatroom') {
|
||||
// 移动单个群到指定分组:更新 s2_wechat_chatroom.groupIds
|
||||
Db::table('s2_wechat_chatroom')
|
||||
->where('id', $moveId)
|
||||
->update(['groupIds' => $targetId]);
|
||||
} else {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('无效的类型参数');
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return ResponseHelper::success('', '移动成功');
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
return ResponseHelper::error('移动失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Server/application/chukebao/model/ChatGroups.php
Normal file
16
Server/application/chukebao/model/ChatGroups.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace app\chukebao\model;
|
||||
|
||||
use think\Model;
|
||||
|
||||
class ChatGroups extends Model
|
||||
{
|
||||
protected $pk = 'id';
|
||||
protected $name = 'chat_groups';
|
||||
|
||||
// 不开启自动时间戳,手动维护 createTime / deleteTime
|
||||
protected $autoWriteTimestamp = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,4 +39,10 @@ return [
|
||||
'workbench:groupCreate' => 'app\command\WorkbenchGroupCreateCommand', // 工作台群创建任务
|
||||
'workbench:import-contact' => 'app\command\WorkbenchImportContactCommand', // 工作台通讯录导入任务
|
||||
'kf:notice' => 'app\command\KfNoticeCommand', // 客服端消息通知
|
||||
|
||||
'wechat:calculate-score' => 'app\command\CalculateWechatAccountScoreCommand', // 统一计算微信账号健康分
|
||||
'wechat:update-score' => 'app\command\UpdateWechatAccountScoreCommand', // 更新微信账号评分记录
|
||||
|
||||
// 统一任务调度器
|
||||
'scheduler:run' => 'app\command\TaskSchedulerCommand', // 统一任务调度器,支持多进程并发执行
|
||||
];
|
||||
|
||||
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
|
||||
/**
|
||||
* 统一计算微信账号健康分命令
|
||||
* 一个命令完成所有评分工作:
|
||||
* 1. 初始化未计算的账号(基础分只计算一次)
|
||||
* 2. 更新评分记录(根据wechatId和alias不一致情况)
|
||||
* 3. 批量更新健康分(只更新动态分)
|
||||
*/
|
||||
class CalculateWechatAccountScoreCommand extends Command
|
||||
{
|
||||
/**
|
||||
* 数据库表名
|
||||
*/
|
||||
const TABLE_WECHAT_ACCOUNT = 's2_wechat_account';
|
||||
const TABLE_WECHAT_ACCOUNT_SCORE = 's2_wechat_account_score';
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('wechat:calculate-score')
|
||||
->setDescription('统一计算微信账号健康分(包含初始化、更新评分记录、批量计算)')
|
||||
->addOption('only-init', null, \think\console\input\Option::VALUE_NONE, '仅执行初始化步骤')
|
||||
->addOption('only-update', null, \think\console\input\Option::VALUE_NONE, '仅执行更新评分记录步骤')
|
||||
->addOption('only-batch', null, \think\console\input\Option::VALUE_NONE, '仅执行批量更新健康分步骤')
|
||||
->addOption('account-id', 'a', \think\console\input\Option::VALUE_OPTIONAL, '指定账号ID,仅处理该账号')
|
||||
->addOption('batch-size', 'b', \think\console\input\Option::VALUE_OPTIONAL, '批处理大小', 50)
|
||||
->addOption('force-recalculate', 'f', \think\console\input\Option::VALUE_NONE, '强制重新计算基础分');
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param Input $input 输入对象
|
||||
* @param Output $output 输出对象
|
||||
* @return int 命令执行状态码(0表示成功)
|
||||
*/
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
// 解析命令行参数
|
||||
$onlyInit = $input->getOption('only-init');
|
||||
$onlyUpdate = $input->getOption('only-update');
|
||||
$onlyBatch = $input->getOption('only-batch');
|
||||
$accountId = $input->getOption('account-id');
|
||||
$batchSize = (int)$input->getOption('batch-size');
|
||||
$forceRecalculate = $input->getOption('force-recalculate');
|
||||
|
||||
// 参数验证
|
||||
if ($batchSize <= 0) {
|
||||
$batchSize = 50; // 默认批处理大小
|
||||
}
|
||||
|
||||
// 显示执行参数
|
||||
$output->writeln("==========================================");
|
||||
$output->writeln("开始统一计算微信账号健康分...");
|
||||
$output->writeln("==========================================");
|
||||
|
||||
if ($accountId) {
|
||||
$output->writeln("指定账号ID: {$accountId}");
|
||||
}
|
||||
|
||||
if ($onlyInit) {
|
||||
$output->writeln("仅执行初始化步骤");
|
||||
} elseif ($onlyUpdate) {
|
||||
$output->writeln("仅执行更新评分记录步骤");
|
||||
} elseif ($onlyBatch) {
|
||||
$output->writeln("仅执行批量更新健康分步骤");
|
||||
}
|
||||
|
||||
if ($forceRecalculate) {
|
||||
$output->writeln("强制重新计算基础分");
|
||||
}
|
||||
|
||||
$output->writeln("批处理大小: {$batchSize}");
|
||||
|
||||
// 记录命令开始执行的日志
|
||||
Log::info('开始执行微信账号健康分计算命令', [
|
||||
'accountId' => $accountId,
|
||||
'onlyInit' => $onlyInit ? 'true' : 'false',
|
||||
'onlyUpdate' => $onlyUpdate ? 'true' : 'false',
|
||||
'onlyBatch' => $onlyBatch ? 'true' : 'false',
|
||||
'batchSize' => $batchSize,
|
||||
'forceRecalculate' => $forceRecalculate ? 'true' : 'false'
|
||||
]);
|
||||
|
||||
$startTime = time();
|
||||
|
||||
try {
|
||||
// 实例化服务
|
||||
$service = new WechatAccountHealthScoreService();
|
||||
} catch (\Exception $e) {
|
||||
$errorMsg = "实例化WechatAccountHealthScoreService失败: " . $e->getMessage();
|
||||
$output->writeln("<error>{$errorMsg}</error>");
|
||||
Log::error($errorMsg);
|
||||
return 1; // 返回非零状态码表示失败
|
||||
}
|
||||
|
||||
// 初始化统计数据
|
||||
$initStats = ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
$updateStats = ['total' => 0];
|
||||
$batchStats = ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
|
||||
try {
|
||||
// 步骤1: 初始化未计算基础分的账号
|
||||
if (!$onlyUpdate && !$onlyBatch) {
|
||||
$output->writeln("\n[步骤1] 初始化未计算基础分的账号...");
|
||||
Log::info('[步骤1] 开始初始化未计算基础分的账号');
|
||||
$initStats = $this->initUncalculatedAccounts($service, $output, $accountId, $batchSize);
|
||||
$output->writeln("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
|
||||
Log::info("初始化完成:成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
|
||||
}
|
||||
|
||||
// 步骤2: 更新评分记录(根据wechatId和alias不一致情况)
|
||||
if (!$onlyInit && !$onlyBatch) {
|
||||
$output->writeln("\n[步骤2] 更新评分记录(根据wechatId和alias不一致情况)...");
|
||||
Log::info('[步骤2] 开始更新评分记录(根据wechatId和alias不一致情况)');
|
||||
$updateStats = $this->updateScoreRecords($service, $output, $accountId, $batchSize);
|
||||
$output->writeln("更新完成:处理了 {$updateStats['total']} 条记录");
|
||||
Log::info("更新评分记录完成:处理了 {$updateStats['total']} 条记录");
|
||||
}
|
||||
|
||||
// 步骤3: 批量更新健康分(只更新动态分,不重新计算基础分)
|
||||
if (!$onlyInit && !$onlyUpdate) {
|
||||
$output->writeln("\n[步骤3] 批量更新健康分(只更新动态分)...");
|
||||
Log::info('[步骤3] 开始批量更新健康分(只更新动态分)');
|
||||
$batchStats = $this->batchUpdateHealthScore($service, $output, $accountId, $batchSize, $forceRecalculate);
|
||||
$output->writeln("批量更新完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
Log::info("批量更新健康分完成:成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
$endTime = time();
|
||||
$duration = $endTime - $startTime;
|
||||
|
||||
$output->writeln("\n==========================================");
|
||||
$output->writeln("任务完成!");
|
||||
$output->writeln("==========================================");
|
||||
$output->writeln("总耗时: {$duration} 秒");
|
||||
$output->writeln("初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条");
|
||||
$output->writeln("更新评分记录: {$updateStats['total']} 条");
|
||||
$output->writeln("批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
|
||||
// 记录命令执行完成的日志
|
||||
Log::info("微信账号健康分计算命令执行完成,总耗时: {$duration} 秒," .
|
||||
"初始化: 成功 {$initStats['success']} 条,失败 {$initStats['failed']} 条," .
|
||||
"更新评分记录: {$updateStats['total']} 条," .
|
||||
"批量更新: 成功 {$batchStats['success']} 条,失败 {$batchStats['failed']} 条");
|
||||
|
||||
if (!empty($initStats['errors'])) {
|
||||
$output->writeln("\n初始化错误详情:");
|
||||
Log::warning("初始化阶段出现 " . count($initStats['errors']) . " 个错误");
|
||||
|
||||
foreach (array_slice($initStats['errors'], 0, 10) as $error) {
|
||||
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
|
||||
Log::error("初始化错误 - 账号ID {$error['accountId']}: {$error['error']}");
|
||||
}
|
||||
|
||||
if (count($initStats['errors']) > 10) {
|
||||
$output->writeln(" ... 还有 " . (count($initStats['errors']) - 10) . " 个错误");
|
||||
Log::warning("初始化错误过多,只记录前10个,还有 " . (count($initStats['errors']) - 10) . " 个错误未显示");
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($batchStats['errors'])) {
|
||||
$output->writeln("\n批量更新错误详情:");
|
||||
Log::warning("批量更新阶段出现 " . count($batchStats['errors']) . " 个错误");
|
||||
|
||||
foreach (array_slice($batchStats['errors'], 0, 10) as $error) {
|
||||
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
|
||||
Log::error("批量更新错误 - 账号ID {$error['accountId']}: {$error['error']}");
|
||||
}
|
||||
|
||||
if (count($batchStats['errors']) > 10) {
|
||||
$output->writeln(" ... 还有 " . (count($batchStats['errors']) - 10) . " 个错误");
|
||||
Log::warning("批量更新错误过多,只记录前10个,还有 " . (count($batchStats['errors']) - 10) . " 个错误未显示");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\PDOException $e) {
|
||||
// 数据库异常
|
||||
$errorMsg = "数据库操作失败: " . $e->getMessage();
|
||||
$output->writeln("\n<error>数据库错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录数据库错误
|
||||
Log::error("数据库错误: " . $errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 2; // 数据库错误状态码
|
||||
} catch (\Exception $e) {
|
||||
// 一般异常
|
||||
$errorMsg = "命令执行失败: " . $e->getMessage();
|
||||
$output->writeln("\n<error>错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录严重错误
|
||||
Log::error($errorMsg);
|
||||
Log::error("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 1; // 一般错误状态码
|
||||
} catch (\Throwable $e) {
|
||||
// 其他所有错误
|
||||
$errorMsg = "严重错误: " . $e->getMessage();
|
||||
$output->writeln("\n<error>严重错误: " . $errorMsg . "</error>");
|
||||
$output->writeln($e->getTraceAsString());
|
||||
|
||||
// 记录严重错误
|
||||
Log::critical($errorMsg);
|
||||
Log::critical("错误堆栈: " . $e->getTraceAsString());
|
||||
|
||||
return 3; // 严重错误状态码
|
||||
}
|
||||
|
||||
return 0; // 成功执行
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化未计算基础分的账号
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function initUncalculatedAccounts($service, $output, $accountId = null, $batchSize = 50)
|
||||
{
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
|
||||
try {
|
||||
// 获取所有未计算基础分的账号
|
||||
// 优化查询:使用索引字段,只查询必要的字段
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->alias('a')
|
||||
->leftJoin([self::TABLE_WECHAT_ACCOUNT_SCORE => 's'], 's.accountId = a.id')
|
||||
->where('a.isDeleted', 0)
|
||||
->where(function($query) {
|
||||
$query->whereNull('s.id')
|
||||
->whereOr('s.baseScoreCalculated', 0);
|
||||
});
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('a.id', $accountId);
|
||||
}
|
||||
|
||||
$accounts = $query->field('a.id, a.wechatId') // 只查询必要的字段
|
||||
->select();
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询未计算基础分的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询未计算基础分的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$stats['total'] = count($accounts);
|
||||
|
||||
if ($stats['total'] == 0) {
|
||||
$output->writeln("没有需要初始化的账号");
|
||||
Log::info("没有需要初始化的账号");
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$stats['total']} 个需要初始化的账号");
|
||||
Log::info("找到 {$stats['total']} 个需要初始化的账号");
|
||||
|
||||
// 优化批处理:使用传入的批处理大小
|
||||
$batches = array_chunk($accounts, $batchSize);
|
||||
$batchCount = count($batches);
|
||||
|
||||
Log::info("将分 {$batchCount} 批处理,每批 {$batchSize} 个账号");
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
$batchStartTime = microtime(true);
|
||||
$batchSuccessCount = 0;
|
||||
$batchFailedCount = 0;
|
||||
|
||||
foreach ($batch as $account) {
|
||||
try {
|
||||
$service->calculateAndUpdate($account['id']);
|
||||
$stats['success']++;
|
||||
$batchSuccessCount++;
|
||||
|
||||
if ($stats['success'] % 20 == 0) { // 更频繁地显示进度
|
||||
$output->write(".");
|
||||
Log::debug("已成功初始化 {$stats['success']} 个账号");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$stats['failed']++;
|
||||
$batchFailedCount++;
|
||||
$errorMsg = "初始化账号 {$account['id']} 失败: " . $e->getMessage();
|
||||
Log::error($errorMsg);
|
||||
$stats['errors'][] = [
|
||||
'accountId' => $account['id'],
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$batchEndTime = microtime(true);
|
||||
$batchDuration = round($batchEndTime - $batchStartTime, 2);
|
||||
|
||||
// 每批次完成后输出进度信息
|
||||
$output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
|
||||
Log::info("初始化批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,成功 {$batchSuccessCount},失败 {$batchFailedCount}");
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新评分记录(根据wechatId和alias不一致情况)
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function updateScoreRecords($service, $output, $accountId = null, $batchSize = 50)
|
||||
{
|
||||
$stats = ['total' => 0];
|
||||
|
||||
try {
|
||||
// 优化查询:合并两次查询为一次,减少数据库访问次数
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->where('isDeleted', 0)
|
||||
->where('wechatId', '<>', '')
|
||||
->where('alias', '<>', '');
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('id', $accountId);
|
||||
}
|
||||
|
||||
$accounts = $query->field('id, wechatId, alias, IF(wechatId = alias, 0, 1) as isModifiedAlias')
|
||||
->select();
|
||||
|
||||
// 分类处理查询结果
|
||||
$inconsistentAccounts = [];
|
||||
$consistentAccounts = [];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
if ($account['isModifiedAlias'] == 1) {
|
||||
$inconsistentAccounts[] = $account;
|
||||
} else {
|
||||
$consistentAccounts[] = $account;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询需要更新评分记录的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询需要更新评分记录的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$allAccounts = array_merge($inconsistentAccounts, $consistentAccounts);
|
||||
$stats['total'] = count($allAccounts);
|
||||
|
||||
if ($stats['total'] == 0) {
|
||||
$output->writeln("没有需要更新的账号");
|
||||
Log::info("没有需要更新的评分记录");
|
||||
return $stats;
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
|
||||
Log::info("找到 {$stats['total']} 个需要更新的账号(不一致: " . count($inconsistentAccounts) . ",一致: " . count($consistentAccounts) . ")");
|
||||
|
||||
$updatedCount = 0;
|
||||
|
||||
// 优化批处理:使用传入的批处理大小
|
||||
$batches = array_chunk($allAccounts, $batchSize);
|
||||
$batchCount = count($batches);
|
||||
|
||||
Log::info("将分 {$batchCount} 批更新评分记录,每批 {$batchSize} 个账号");
|
||||
|
||||
foreach ($batches as $batchIndex => $batch) {
|
||||
$batchStartTime = microtime(true);
|
||||
$batchUpdatedCount = 0;
|
||||
|
||||
foreach ($batch as $account) {
|
||||
$isModifiedAlias = isset($account['isModifiedAlias']) ?
|
||||
($account['isModifiedAlias'] == 1) :
|
||||
in_array($account['id'], array_column($inconsistentAccounts, 'id'));
|
||||
|
||||
$this->updateScoreRecord($account['id'], $isModifiedAlias, $service);
|
||||
$updatedCount++;
|
||||
$batchUpdatedCount++;
|
||||
|
||||
if ($batchUpdatedCount % 20 == 0) {
|
||||
$output->write(".");
|
||||
}
|
||||
}
|
||||
|
||||
$batchEndTime = microtime(true);
|
||||
$batchDuration = round($batchEndTime - $batchStartTime, 2);
|
||||
|
||||
// 每批次完成后输出进度信息
|
||||
$output->writeln(" 批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
|
||||
Log::info("更新评分记录批次 " . ($batchIndex + 1) . "/{$batchCount} 完成,耗时 {$batchDuration} 秒,更新 {$batchUpdatedCount} 条记录");
|
||||
}
|
||||
|
||||
if ($updatedCount > 0 && $updatedCount % 100 == 0) {
|
||||
$output->writeln("");
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新健康分(只更新动态分)
|
||||
*
|
||||
* @param WechatAccountHealthScoreService $service 健康分服务实例
|
||||
* @param Output $output 输出对象
|
||||
* @return array 处理结果统计
|
||||
* @throws \Exception 如果查询或处理过程中出现错误
|
||||
*/
|
||||
private function batchUpdateHealthScore($service, $output, $accountId = null, $batchSize = 50, $forceRecalculate = false)
|
||||
{
|
||||
try {
|
||||
// 获取所有已计算基础分的账号
|
||||
// 优化查询:只查询必要的字段,使用索引字段
|
||||
$query = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('baseScoreCalculated', 1);
|
||||
|
||||
// 如果指定了账号ID,则只处理该账号
|
||||
if ($accountId) {
|
||||
$query->where('accountId', $accountId);
|
||||
}
|
||||
|
||||
$accountIds = $query->column('accountId');
|
||||
} catch (\Exception $e) {
|
||||
Log::error("查询需要批量更新健康分的账号失败: " . $e->getMessage());
|
||||
throw new \Exception("查询需要批量更新健康分的账号失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$total = count($accountIds);
|
||||
|
||||
if ($total == 0) {
|
||||
$output->writeln("没有需要更新的账号");
|
||||
Log::info("没有需要批量更新健康分的账号");
|
||||
return ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
}
|
||||
|
||||
$output->writeln("找到 {$total} 个需要更新动态分的账号");
|
||||
Log::info("找到 {$total} 个需要更新动态分的账号");
|
||||
|
||||
// 使用传入的批处理大小和强制重新计算标志
|
||||
Log::info("使用批量大小 {$batchSize} 进行批量更新健康分,强制重新计算基础分: " . ($forceRecalculate ? 'true' : 'false'));
|
||||
$stats = $service->batchCalculateAndUpdate($accountIds, $batchSize, $forceRecalculate);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新评分记录
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param bool $isModifiedAlias 是否已修改微信号
|
||||
* @param WechatAccountHealthScoreService $service 评分服务
|
||||
*/
|
||||
/**
|
||||
* 更新评分记录
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param bool $isModifiedAlias 是否已修改微信号
|
||||
* @param WechatAccountHealthScoreService $service 评分服务
|
||||
* @return bool 是否成功更新
|
||||
*/
|
||||
private function updateScoreRecord($accountId, $isModifiedAlias, $service)
|
||||
{
|
||||
Log::debug("开始更新账号 {$accountId} 的评分记录,isModifiedAlias: " . ($isModifiedAlias ? 'true' : 'false'));
|
||||
|
||||
try {
|
||||
// 获取账号数据 - 只查询必要的字段
|
||||
$accountData = Db::table(self::TABLE_WECHAT_ACCOUNT)
|
||||
->where('id', $accountId)
|
||||
->field('id, wechatId, alias') // 只查询必要的字段
|
||||
->find();
|
||||
|
||||
if (empty($accountData)) {
|
||||
Log::warning("账号 {$accountId} 不存在,跳过更新评分记录");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保评分记录存在 - 只查询必要的字段
|
||||
$scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->field('accountId, baseScore, baseScoreCalculated, baseInfoScore, dynamicScore') // 只查询必要的字段
|
||||
->find();
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
// 如果记录不存在,创建并计算基础分
|
||||
Log::info("账号 {$accountId} 的评分记录不存在,创建并计算基础分");
|
||||
$service->calculateAndUpdate($accountId);
|
||||
$scoreRecord = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->find();
|
||||
}
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
Log::warning("账号 {$accountId} 的评分记录创建失败,跳过更新");
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新isModifiedAlias字段
|
||||
$updateData = [
|
||||
'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 如果基础分已计算,需要更新基础信息分和基础分
|
||||
if ($scoreRecord['baseScoreCalculated']) {
|
||||
$oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
|
||||
$newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
|
||||
|
||||
if ($oldBaseInfoScore != $newBaseInfoScore) {
|
||||
$oldBaseScore = $scoreRecord['baseScore'] ?? 60;
|
||||
$newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
|
||||
|
||||
$updateData['baseInfoScore'] = $newBaseInfoScore;
|
||||
$updateData['baseScore'] = $newBaseScore;
|
||||
|
||||
// 重新计算健康分
|
||||
$dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
|
||||
$healthScore = $newBaseScore + $dynamicScore;
|
||||
$healthScore = max(0, min(100, $healthScore));
|
||||
$updateData['healthScore'] = $healthScore;
|
||||
$updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
|
||||
|
||||
Log::info("账号 {$accountId} 的基础信息分从 {$oldBaseInfoScore} 更新为 {$newBaseInfoScore}," .
|
||||
"基础分从 {$oldBaseScore} 更新为 {$newBaseScore},健康分更新为 {$healthScore}");
|
||||
}
|
||||
} else {
|
||||
// 基础分未计算,只更新标记和基础信息分
|
||||
$updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
|
||||
}
|
||||
|
||||
$result = Db::table(self::TABLE_WECHAT_ACCOUNT_SCORE)
|
||||
->where('accountId', $accountId)
|
||||
->update($updateData);
|
||||
|
||||
Log::debug("账号 {$accountId} 的评分记录更新" . ($result !== false ? "成功" : "失败"));
|
||||
|
||||
return $result !== false;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("更新账号 {$accountId} 的评分记录失败: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
478
Server/application/command/TaskSchedulerCommand.php
Normal file
478
Server/application/command/TaskSchedulerCommand.php
Normal file
@@ -0,0 +1,478 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Config;
|
||||
use think\facade\Log;
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 统一任务调度器
|
||||
* 支持多进程并发执行任务
|
||||
*
|
||||
* 使用方法:
|
||||
* php think scheduler:run
|
||||
*
|
||||
* 在 crontab 中配置:
|
||||
* * * * * * cd /path/to/project && php think scheduler:run >> /path/to/log/scheduler.log 2>&1
|
||||
*/
|
||||
class TaskSchedulerCommand extends Command
|
||||
{
|
||||
/**
|
||||
* 任务配置
|
||||
*/
|
||||
protected $tasks = [];
|
||||
|
||||
/**
|
||||
* 最大并发进程数
|
||||
*/
|
||||
protected $maxConcurrent = 10;
|
||||
|
||||
/**
|
||||
* 当前运行的进程数
|
||||
*/
|
||||
protected $runningProcesses = [];
|
||||
|
||||
/**
|
||||
* 日志目录
|
||||
*/
|
||||
protected $logDir = '';
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('scheduler:run')
|
||||
->setDescription('统一任务调度器,支持多进程并发执行所有定时任务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln('==========================================');
|
||||
$output->writeln('任务调度器启动');
|
||||
$output->writeln('时间: ' . date('Y-m-d H:i:s'));
|
||||
$output->writeln('==========================================');
|
||||
|
||||
// 检查是否支持 pcntl 扩展
|
||||
if (!function_exists('pcntl_fork')) {
|
||||
$output->writeln('<error>错误:系统不支持 pcntl 扩展,无法使用多进程功能</error>');
|
||||
$output->writeln('<info>提示:将使用单进程顺序执行任务</info>');
|
||||
$this->maxConcurrent = 1;
|
||||
}
|
||||
|
||||
// 加载任务配置(优先使用框架配置,其次直接引入配置文件,避免加载失败)
|
||||
$this->tasks = Config::get('task_scheduler', []);
|
||||
|
||||
// 如果通过 Config 没有读到,再尝试直接 include 配置文件
|
||||
if (empty($this->tasks)) {
|
||||
// 以项目根目录为基准查找 config/task_scheduler.php
|
||||
$configFile = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'task_scheduler.php';
|
||||
if (is_file($configFile)) {
|
||||
$config = include $configFile;
|
||||
if (is_array($config) && !empty($config)) {
|
||||
$this->tasks = $config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->tasks)) {
|
||||
$output->writeln('<error>错误:未找到任务配置(task_scheduler),请检查 config/task_scheduler.php 是否存在且返回数组</error>');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置日志目录(ThinkPHP5 中无 runtime_path 辅助函数,直接使用 ROOT_PATH/runtime/log)
|
||||
if (!defined('ROOT_PATH')) {
|
||||
// CLI 下正常情况下 ROOT_PATH 已在入口脚本 define,这里兜底一次
|
||||
define('ROOT_PATH', dirname(__DIR__, 2));
|
||||
}
|
||||
$this->logDir = ROOT_PATH . DIRECTORY_SEPARATOR . 'runtime' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR;
|
||||
if (!is_dir($this->logDir)) {
|
||||
mkdir($this->logDir, 0755, true);
|
||||
}
|
||||
|
||||
// 获取当前时间
|
||||
$currentTime = time();
|
||||
$currentMinute = date('i', $currentTime);
|
||||
$currentHour = date('H', $currentTime);
|
||||
$currentDay = date('d', $currentTime);
|
||||
$currentMonth = date('m', $currentTime);
|
||||
$currentWeekday = date('w', $currentTime); // 0=Sunday, 6=Saturday
|
||||
|
||||
$output->writeln("当前时间: {$currentHour}:{$currentMinute}");
|
||||
$output->writeln("已加载 " . count($this->tasks) . " 个任务配置");
|
||||
|
||||
// 筛选需要执行的任务
|
||||
$tasksToRun = [];
|
||||
foreach ($this->tasks as $taskId => $task) {
|
||||
if (!isset($task['enabled']) || !$task['enabled']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->shouldRun($task['schedule'], $currentMinute, $currentHour, $currentDay, $currentMonth, $currentWeekday)) {
|
||||
$tasksToRun[$taskId] = $task;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tasksToRun)) {
|
||||
$output->writeln('<info>当前时间没有需要执行的任务</info>');
|
||||
return true;
|
||||
}
|
||||
|
||||
$output->writeln("找到 " . count($tasksToRun) . " 个需要执行的任务");
|
||||
|
||||
// 执行任务
|
||||
if ($this->maxConcurrent > 1 && function_exists('pcntl_fork')) {
|
||||
$this->executeConcurrent($tasksToRun, $output);
|
||||
} else {
|
||||
$this->executeSequential($tasksToRun, $output);
|
||||
}
|
||||
|
||||
// 清理僵尸进程
|
||||
$this->cleanupZombieProcesses();
|
||||
|
||||
$output->writeln('==========================================');
|
||||
$output->writeln('任务调度器执行完成');
|
||||
$output->writeln('==========================================');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断任务是否应该执行
|
||||
*
|
||||
* @param string $schedule cron表达式,格式:分钟 小时 日 月 星期
|
||||
* @param int $minute 当前分钟
|
||||
* @param int $hour 当前小时
|
||||
* @param int $day 当前日期
|
||||
* @param int $month 当前月份
|
||||
* @param int $weekday 当前星期
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldRun($schedule, $minute, $hour, $day, $month, $weekday)
|
||||
{
|
||||
$parts = preg_split('/\s+/', trim($schedule));
|
||||
if (count($parts) < 5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list($scheduleMinute, $scheduleHour, $scheduleDay, $scheduleMonth, $scheduleWeekday) = $parts;
|
||||
|
||||
// 解析分钟
|
||||
if (!$this->matchCronField($scheduleMinute, $minute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析小时
|
||||
if (!$this->matchCronField($scheduleHour, $hour)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析日期
|
||||
if (!$this->matchCronField($scheduleDay, $day)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析月份
|
||||
if (!$this->matchCronField($scheduleMonth, $month)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 解析星期(注意:cron中0和7都表示星期日)
|
||||
if ($scheduleWeekday !== '*') {
|
||||
$scheduleWeekday = str_replace('7', '0', $scheduleWeekday);
|
||||
if (!$this->matchCronField($scheduleWeekday, $weekday)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配cron字段
|
||||
*
|
||||
* @param string $field cron字段表达式
|
||||
* @param int $value 当前值
|
||||
* @return bool
|
||||
*/
|
||||
protected function matchCronField($field, $value)
|
||||
{
|
||||
// 通配符
|
||||
if ($field === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 列表(逗号分隔)
|
||||
if (strpos($field, ',') !== false) {
|
||||
$values = explode(',', $field);
|
||||
foreach ($values as $v) {
|
||||
if ($this->matchCronField(trim($v), $value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 范围(如 1-5)
|
||||
if (strpos($field, '-') !== false) {
|
||||
list($start, $end) = explode('-', $field);
|
||||
return $value >= (int)$start && $value <= (int)$end;
|
||||
}
|
||||
|
||||
// 步长(如 */5 或 0-59/5)
|
||||
if (strpos($field, '/') !== false) {
|
||||
$parts = explode('/', $field);
|
||||
$base = $parts[0];
|
||||
$step = (int)$parts[1];
|
||||
|
||||
if ($base === '*') {
|
||||
return $value % $step === 0;
|
||||
} else {
|
||||
// 处理范围步长,如 0-59/5
|
||||
if (strpos($base, '-') !== false) {
|
||||
list($start, $end) = explode('-', $base);
|
||||
if ($value >= (int)$start && $value <= (int)$end) {
|
||||
return ($value - (int)$start) % $step === 0;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return $value % $step === 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 精确匹配
|
||||
return (int)$field === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 并发执行任务(多进程)
|
||||
*
|
||||
* @param array $tasks 任务列表
|
||||
* @param Output $output 输出对象
|
||||
*/
|
||||
protected function executeConcurrent($tasks, Output $output)
|
||||
{
|
||||
$output->writeln('<info>使用多进程并发执行任务(最大并发数:' . $this->maxConcurrent . ')</info>');
|
||||
|
||||
foreach ($tasks as $taskId => $task) {
|
||||
// 等待可用进程槽
|
||||
while (count($this->runningProcesses) >= $this->maxConcurrent) {
|
||||
$this->waitForProcesses();
|
||||
usleep(100000); // 等待100ms
|
||||
}
|
||||
|
||||
// 检查任务是否已经在运行(防止重复执行)
|
||||
$lockKey = "scheduler_task_lock:{$taskId}";
|
||||
$lockTime = Cache::get($lockKey);
|
||||
if ($lockTime && (time() - $lockTime) < 300) { // 5分钟内不重复执行
|
||||
$output->writeln("<comment>任务 {$taskId} 正在运行中,跳过</comment>");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 创建子进程
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid == -1) {
|
||||
// 创建进程失败
|
||||
$output->writeln("<error>创建子进程失败:{$taskId}</error>");
|
||||
Log::error("任务调度器:创建子进程失败", ['task' => $taskId]);
|
||||
continue;
|
||||
} elseif ($pid == 0) {
|
||||
// 子进程:执行任务
|
||||
$this->runTask($taskId, $task);
|
||||
exit(0);
|
||||
} else {
|
||||
// 父进程:记录子进程PID
|
||||
$this->runningProcesses[$pid] = [
|
||||
'task_id' => $taskId,
|
||||
'start_time' => time(),
|
||||
];
|
||||
$output->writeln("<info>启动任务:{$taskId} (PID: {$pid})</info>");
|
||||
|
||||
// 设置任务锁
|
||||
Cache::set($lockKey, time(), 600); // 10分钟过期
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有子进程完成
|
||||
while (!empty($this->runningProcesses)) {
|
||||
$this->waitForProcesses();
|
||||
usleep(500000); // 等待500ms
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 顺序执行任务(单进程)
|
||||
*
|
||||
* @param array $tasks 任务列表
|
||||
* @param Output $output 输出对象
|
||||
*/
|
||||
protected function executeSequential($tasks, Output $output)
|
||||
{
|
||||
$output->writeln('<info>使用单进程顺序执行任务</info>');
|
||||
|
||||
foreach ($tasks as $taskId => $task) {
|
||||
$output->writeln("<info>执行任务:{$taskId}</info>");
|
||||
$this->runTask($taskId, $task);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单个任务
|
||||
*
|
||||
* @param string $taskId 任务ID
|
||||
* @param array $task 任务配置
|
||||
*/
|
||||
protected function runTask($taskId, $task)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
$logFile = $this->logDir . ($task['log_file'] ?? "scheduler_{$taskId}.log");
|
||||
|
||||
// 确保日志目录存在
|
||||
$logDir = dirname($logFile);
|
||||
if (!is_dir($logDir)) {
|
||||
mkdir($logDir, 0755, true);
|
||||
}
|
||||
|
||||
// 构建命令
|
||||
// 使用项目根目录下的 think 脚本(同命令行 php think)
|
||||
if (!defined('ROOT_PATH')) {
|
||||
define('ROOT_PATH', dirname(__DIR__, 2));
|
||||
}
|
||||
$thinkPath = ROOT_PATH . DIRECTORY_SEPARATOR . 'think';
|
||||
$command = "php {$thinkPath} {$task['command']}";
|
||||
if (!empty($task['options'])) {
|
||||
foreach ($task['options'] as $option) {
|
||||
$command .= ' ' . escapeshellarg($option);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加日志重定向
|
||||
$command .= " >> " . escapeshellarg($logFile) . " 2>&1";
|
||||
|
||||
// 记录任务开始
|
||||
$logMessage = "\n" . str_repeat('=', 60) . "\n";
|
||||
$logMessage .= "任务开始执行: {$taskId}\n";
|
||||
$logMessage .= "执行时间: " . date('Y-m-d H:i:s') . "\n";
|
||||
$logMessage .= "命令: {$command}\n";
|
||||
$logMessage .= str_repeat('=', 60) . "\n";
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
|
||||
// 执行命令
|
||||
$descriptorspec = [
|
||||
0 => ['file', (PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null'), 'r'], // stdin
|
||||
1 => ['file', $logFile, 'a'], // stdout
|
||||
2 => ['file', $logFile, 'a'], // stderr
|
||||
];
|
||||
|
||||
$process = @proc_open($command, $descriptorspec, $pipes, ROOT_PATH);
|
||||
|
||||
if (is_resource($process)) {
|
||||
// 关闭管道
|
||||
if (isset($pipes[0])) @fclose($pipes[0]);
|
||||
if (isset($pipes[1])) @fclose($pipes[1]);
|
||||
if (isset($pipes[2])) @fclose($pipes[2]);
|
||||
|
||||
// 设置超时
|
||||
$timeout = $task['timeout'] ?? 3600;
|
||||
$startWaitTime = time();
|
||||
|
||||
// 等待进程完成或超时
|
||||
while (true) {
|
||||
$status = proc_get_status($process);
|
||||
|
||||
if (!$status['running']) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查超时
|
||||
if ((time() - $startWaitTime) > $timeout) {
|
||||
if (function_exists('proc_terminate')) {
|
||||
proc_terminate($process, SIGTERM);
|
||||
// 等待进程终止
|
||||
sleep(2);
|
||||
$status = proc_get_status($process);
|
||||
if ($status['running']) {
|
||||
// 强制终止
|
||||
proc_terminate($process, SIGKILL);
|
||||
}
|
||||
}
|
||||
Log::warning("任务执行超时", [
|
||||
'task' => $taskId,
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
usleep(500000); // 等待500ms
|
||||
}
|
||||
|
||||
// 关闭进程
|
||||
proc_close($process);
|
||||
} else {
|
||||
// 如果 proc_open 失败,尝试直接执行(后台执行)
|
||||
if (PHP_OS_FAMILY === 'Windows') {
|
||||
pclose(popen("start /B " . $command, "r"));
|
||||
} else {
|
||||
exec($command . ' > /dev/null 2>&1 &');
|
||||
}
|
||||
}
|
||||
|
||||
$endTime = microtime(true);
|
||||
$duration = round($endTime - $startTime, 2);
|
||||
|
||||
// 记录任务完成
|
||||
$logMessage = "\n" . str_repeat('=', 60) . "\n";
|
||||
$logMessage .= "任务执行完成: {$taskId}\n";
|
||||
$logMessage .= "完成时间: " . date('Y-m-d H:i:s') . "\n";
|
||||
$logMessage .= "执行时长: {$duration} 秒\n";
|
||||
$logMessage .= str_repeat('=', 60) . "\n";
|
||||
file_put_contents($logFile, $logMessage, FILE_APPEND);
|
||||
|
||||
Log::info("任务执行完成", [
|
||||
'task' => $taskId,
|
||||
'duration' => $duration,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待进程完成
|
||||
*/
|
||||
protected function waitForProcesses()
|
||||
{
|
||||
foreach ($this->runningProcesses as $pid => $info) {
|
||||
$status = 0;
|
||||
$result = pcntl_waitpid($pid, $status, WNOHANG);
|
||||
|
||||
if ($result == $pid || $result == -1) {
|
||||
// 进程已结束
|
||||
unset($this->runningProcesses[$pid]);
|
||||
|
||||
$duration = time() - $info['start_time'];
|
||||
Log::info("子进程执行完成", [
|
||||
'pid' => $pid,
|
||||
'task' => $info['task_id'],
|
||||
'duration' => $duration,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理僵尸进程
|
||||
*/
|
||||
protected function cleanupZombieProcesses()
|
||||
{
|
||||
if (!function_exists('pcntl_waitpid')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = 0;
|
||||
while (($pid = pcntl_waitpid(-1, $status, WNOHANG)) > 0) {
|
||||
// 清理僵尸进程
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal file
168
Server/application/command/UpdateWechatAccountScoreCommand.php
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace app\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
|
||||
/**
|
||||
* 更新微信账号评分记录
|
||||
* 根据wechatId和alias是否不一致来更新isModifiedAlias字段(仅用于评分,不修复数据)
|
||||
*/
|
||||
class UpdateWechatAccountScoreCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('wechat:update-score')
|
||||
->setDescription('更新微信账号评分记录,根据wechatId和alias不一致情况更新isModifiedAlias字段(仅用于评分)');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$output->writeln("开始更新微信账号评分记录...");
|
||||
|
||||
try {
|
||||
// 1. 查找所有需要更新的账号
|
||||
$output->writeln("步骤1: 查找需要更新的账号...");
|
||||
|
||||
// 查找wechatId和alias不一致的账号
|
||||
$inconsistentAccounts = Db::table('s2_wechat_account')
|
||||
->where('isDeleted', 0)
|
||||
->where('wechatId', '<>', '')
|
||||
->where('alias', '<>', '')
|
||||
->whereRaw('wechatId != alias')
|
||||
->field('id, wechatId, alias')
|
||||
->select();
|
||||
|
||||
// 查找wechatId和alias一致的账号
|
||||
$consistentAccounts = Db::table('s2_wechat_account')
|
||||
->where('isDeleted', 0)
|
||||
->where('wechatId', '<>', '')
|
||||
->where('alias', '<>', '')
|
||||
->whereRaw('wechatId = alias')
|
||||
->field('id, wechatId, alias')
|
||||
->select();
|
||||
|
||||
$output->writeln("发现 " . count($inconsistentAccounts) . " 条不一致记录(已修改微信号)");
|
||||
$output->writeln("发现 " . count($consistentAccounts) . " 条一致记录(未修改微信号)");
|
||||
|
||||
// 2. 更新评分记录表中的isModifiedAlias字段
|
||||
$output->writeln("步骤2: 更新评分记录表...");
|
||||
$updatedCount = 0;
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
|
||||
// 更新不一致的记录
|
||||
foreach ($inconsistentAccounts as $account) {
|
||||
$this->updateScoreRecord($account['id'], true, $healthScoreService);
|
||||
$updatedCount++;
|
||||
}
|
||||
|
||||
// 更新一致的记录
|
||||
foreach ($consistentAccounts as $account) {
|
||||
$this->updateScoreRecord($account['id'], false, $healthScoreService);
|
||||
$updatedCount++;
|
||||
}
|
||||
|
||||
$output->writeln("已更新 " . $updatedCount . " 条评分记录");
|
||||
|
||||
// 3. 重新计算健康分(只更新基础信息分,不重新计算基础分)
|
||||
$output->writeln("步骤3: 重新计算健康分...");
|
||||
$allAccountIds = array_merge(
|
||||
array_column($inconsistentAccounts, 'id'),
|
||||
array_column($consistentAccounts, 'id')
|
||||
);
|
||||
|
||||
if (!empty($allAccountIds)) {
|
||||
$stats = $healthScoreService->batchCalculateAndUpdate($allAccountIds, 100, false);
|
||||
$output->writeln("健康分计算完成:成功 " . $stats['success'] . " 条,失败 " . $stats['failed'] . " 条");
|
||||
|
||||
if (!empty($stats['errors'])) {
|
||||
$output->writeln("错误详情:");
|
||||
foreach ($stats['errors'] as $error) {
|
||||
$output->writeln(" 账号ID {$error['accountId']}: {$error['error']}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln("任务完成!");
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln("错误: " . $e->getMessage());
|
||||
$output->writeln($e->getTraceAsString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新评分记录
|
||||
*
|
||||
* @param int $accountId 账号ID
|
||||
* @param bool $isModifiedAlias 是否已修改微信号
|
||||
* @param WechatAccountHealthScoreService $service 评分服务
|
||||
*/
|
||||
private function updateScoreRecord($accountId, $isModifiedAlias, $service)
|
||||
{
|
||||
// 获取或创建评分记录
|
||||
$accountData = Db::table('s2_wechat_account')
|
||||
->where('id', $accountId)
|
||||
->find();
|
||||
|
||||
if (empty($accountData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保评分记录存在
|
||||
$scoreRecord = Db::table('s2_wechat_account_score')
|
||||
->where('accountId', $accountId)
|
||||
->find();
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
// 如果记录不存在,创建并计算基础分
|
||||
$service->calculateAndUpdate($accountId);
|
||||
$scoreRecord = Db::table('s2_wechat_account_score')
|
||||
->where('accountId', $accountId)
|
||||
->find();
|
||||
}
|
||||
|
||||
if (empty($scoreRecord)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新isModifiedAlias字段
|
||||
$updateData = [
|
||||
'isModifiedAlias' => $isModifiedAlias ? 1 : 0,
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 如果基础分已计算,需要更新基础信息分和基础分
|
||||
if ($scoreRecord['baseScoreCalculated']) {
|
||||
$oldBaseInfoScore = $scoreRecord['baseInfoScore'] ?? 0;
|
||||
$newBaseInfoScore = $isModifiedAlias ? 10 : 0; // 已修改微信号得10分
|
||||
|
||||
if ($oldBaseInfoScore != $newBaseInfoScore) {
|
||||
$oldBaseScore = $scoreRecord['baseScore'] ?? 60;
|
||||
$newBaseScore = $oldBaseScore - $oldBaseInfoScore + $newBaseInfoScore;
|
||||
|
||||
$updateData['baseInfoScore'] = $newBaseInfoScore;
|
||||
$updateData['baseScore'] = $newBaseScore;
|
||||
|
||||
// 重新计算健康分
|
||||
$dynamicScore = $scoreRecord['dynamicScore'] ?? 0;
|
||||
$healthScore = $newBaseScore + $dynamicScore;
|
||||
$healthScore = max(0, min(100, $healthScore));
|
||||
$updateData['healthScore'] = $healthScore;
|
||||
$updateData['maxAddFriendPerDay'] = (int)floor($healthScore * 0.2);
|
||||
}
|
||||
} else {
|
||||
// 基础分未计算,只更新标记和基础信息分
|
||||
$updateData['baseInfoScore'] = $isModifiedAlias ? 10 : 0;
|
||||
}
|
||||
|
||||
Db::table('s2_wechat_account_score')
|
||||
->where('accountId', $accountId)
|
||||
->update($updateData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class WorkbenchGroupCreateCommand extends Command
|
||||
|
||||
// 检查队列是否已经在运行
|
||||
$queueLockKey = "queue_lock:{$this->queueName}";
|
||||
Cache::rm($queueLockKey);
|
||||
if (Cache::get($queueLockKey)) {
|
||||
$output->writeln("队列 {$this->queueName} 已经在运行中,跳过执行");
|
||||
Log::warning("队列 {$this->queueName} 已经在运行中,跳过执行");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user