feat-admin-ui-interaction
Build Backend JAR / build (push) Has been cancelled

This commit is contained in:
2026-08-30 22:09:22 +08:00
parent 2b613a8e5a
commit 42bbf4904e
14 changed files with 7720 additions and 592 deletions
@@ -6,6 +6,7 @@ import com.nanri.aiimage.modules.dedupe.mapper.DedupeTotalDataMapper;
import com.nanri.aiimage.modules.invalidasin.mapper.InvalidAsinDataMapper; import com.nanri.aiimage.modules.invalidasin.mapper.InvalidAsinDataMapper;
import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity; import com.nanri.aiimage.modules.invalidasin.model.entity.InvalidAsinDataEntity;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@@ -25,6 +26,7 @@ import java.util.regex.Pattern;
* - 品牌存在于无效品牌表 → invalidFiltered(增加计数) * - 品牌存在于无效品牌表 → invalidFiltered(增加计数)
* - 其余行保留 * - 其余行保留
*/ */
@Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class CollectDataBatchQuery { public class CollectDataBatchQuery {
@@ -7,7 +7,6 @@ import com.nanri.aiimage.modules.shopdatacrawl.model.vo.ShopDataCrawlResultItemV
import com.nanri.aiimage.modules.shopdatacrawl.util.BoundedImageCache; import com.nanri.aiimage.modules.shopdatacrawl.util.BoundedImageCache;
import com.nanri.aiimage.modules.shopdatacrawl.util.ShopDataCrawlPrefetchBudget; import com.nanri.aiimage.modules.shopdatacrawl.util.ShopDataCrawlPrefetchBudget;
import com.nanri.aiimage.modules.similarasin.util.SimilarAsinImageEmbedder; import com.nanri.aiimage.modules.similarasin.util.SimilarAsinImageEmbedder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellStyle;
@@ -20,6 +19,7 @@ import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -34,7 +34,6 @@ import java.util.Map;
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class ShopDataCrawlExcelAssemblyService { public class ShopDataCrawlExcelAssemblyService {
static final List<String> COUNTRIES = List.of("UK", "DE", "FR", "ES", "IT"); static final List<String> COUNTRIES = List.of("UK", "DE", "FR", "ES", "IT");
static final List<String> SHEETS = List.of("英国", "德国", "法国", "西班牙", "意大利"); static final List<String> SHEETS = List.of("英国", "德国", "法国", "西班牙", "意大利");
@@ -59,6 +58,12 @@ public class ShopDataCrawlExcelAssemblyService {
private int imageCacheMaxEntries = DEFAULT_IMAGE_CACHE_MAX_ENTRIES; private int imageCacheMaxEntries = DEFAULT_IMAGE_CACHE_MAX_ENTRIES;
private int prefetchMaxUrls = DEFAULT_PREFETCH_MAX_URLS; private int prefetchMaxUrls = DEFAULT_PREFETCH_MAX_URLS;
/** Spring 运行时使用默认参数构造;带参数构造器保留给测试和专项配置。 */
@Autowired
public ShopDataCrawlExcelAssemblyService(SimilarAsinImageEmbedder imageEmbedder) {
this(imageEmbedder, DEFAULT_IMAGE_CACHE_MAX_BYTES, DEFAULT_PREFETCH_MAX_URLS);
}
public ShopDataCrawlExcelAssemblyService(SimilarAsinImageEmbedder imageEmbedder, long imageCacheMaxBytes) { public ShopDataCrawlExcelAssemblyService(SimilarAsinImageEmbedder imageEmbedder, long imageCacheMaxBytes) {
this.imageEmbedder = imageEmbedder; this.imageEmbedder = imageEmbedder;
this.imageCacheMaxBytes = imageCacheMaxBytes; this.imageCacheMaxBytes = imageCacheMaxBytes;
+333
View File
@@ -0,0 +1,333 @@
(function () {
'use strict';
if (window.__adminInteractionLayerInstalled) return;
window.__adminInteractionLayerInstalled = true;
var toastRegion = document.getElementById('adminToastRegion');
var confirmMask = document.getElementById('adminConfirmModal');
var confirmTitle = document.getElementById('adminConfirmTitle');
var confirmMessage = document.getElementById('adminConfirmMessage');
var confirmAccept = document.getElementById('adminConfirmAccept');
var confirmCancel = document.getElementById('adminConfirmCancel');
var guide = document.getElementById('adminOperationGuide');
var guideText = document.getElementById('adminOperationGuideText');
var guideSteps = document.getElementById('adminOperationGuideSteps');
var guideToggle = document.getElementById('adminOperationGuideToggle');
var pendingConfirmButton = null;
var previousFocus = null;
var busyButton = null;
var busyWasDisabled = false;
var activeFetches = 0;
var activeXhrs = 0;
var mainContent = document.getElementById('adminContent');
var guides = {
users: { text: '先用筛选定位账号,再编辑角色和菜单权限;删除账号会要求二次确认。', steps: ['筛选账号', '编辑权限', '确认保存'] },
columns: { text: '菜单会影响后台和软件端的可见范围。建议先填写名称、标识和路由,再设置父菜单。', steps: ['新增或调整菜单', '设置层级', '检查权限'] },
'dedupe-total-data': { text: '支持按关键词、用户、分组和国家筛选;导入前先确认所选分组,导出会按日期范围生成文件。', steps: ['选择分组', '筛选或导入', '核对并导出'] },
'invalid-asin-data': { text: '维护不符合规则的 ASIN 或品牌。添加后可使用上方筛选快速回查。', steps: ['填写 ASIN/品牌', '选择分组', '保存并回查'] },
'shop-keys': { text: '紫鸟令牌属于敏感配置。白名单状态可悬停查看检测详情,编辑前请先核对账号名称。', steps: ['新增或筛选密钥', '查看白名单状态', '编辑或删除'] },
'shop-manage': { text: '店铺信息按分组管理。长商城名会自动缩略,悬停即可查看完整内容。', steps: ['选择分组', '维护店铺信息', '筛选核对结果'] },
'skip-price-asin': { text: '最低价 ASIN 以店铺和国家为单位维护。每个国家可单独编辑 ASIN 与最低价。', steps: ['选择店铺和国家', '填写 ASIN/最低价', '保存或批量导入'] },
'query-asin': { text: '查询 ASIN 按店铺和国家独立维护。空国家列可直接编辑,已有记录可修改或删除。', steps: ['选择店铺和国家', '维护各国 ASIN', '筛选或导出'] },
'product-categories': { text: '类目树支持展开查看层级。搜索、编辑和删除都在同一列表中完成。', steps: ['搜索类目', '展开层级', '新增或编辑'] },
'image-video-tasks': { text: '可先使用筛选缩小任务范围,再查看任务状态、结果和权限范围。', steps: ['设置筛选', '查看任务结果', '按需处理任务'] },
'shop-data-crawl-tasks': { text: '店铺数据任务按状态和时间筛选。批量操作前请核对已选任务。', steps: ['筛选任务', '检查状态', '执行批量操作'] },
history: { text: '生成记录可按用户和时间范围回溯,用于核对结果文件和执行时间。', steps: ['设置时间范围', '筛选记录', '查看结果预览'] },
version: { text: '上传版本后请核对版本号和下载链接,再通知用户更新。', steps: ['上传压缩包', '检查版本记录', '维护历史版本'] },
'digital-human-version': { text: '数字人版本需先上传草稿,再发布并标记最新版本。', steps: ['上传草稿', '确认更新日志', '发布或设为最新'] }
};
function cleanText(value) {
return String(value == null ? '' : value).replace(/\s+/g, ' ').trim();
}
function showToast(message, type) {
var value = cleanText(message);
if (!value || !toastRegion) return;
var item = document.createElement('div');
item.className = 'admin-toast' + (type === 'error' ? ' is-error' : type === 'info' ? ' is-info' : '');
var content = document.createElement('span');
content.className = 'admin-toast__text';
content.textContent = value;
item.appendChild(content);
toastRegion.appendChild(item);
window.setTimeout(function () {
item.style.opacity = '0';
item.style.transform = 'translateY(-6px)';
item.style.transition = 'opacity 160ms ease, transform 160ms ease';
window.setTimeout(function () { item.remove(); }, 180);
}, type === 'error' ? 5200 : 3200);
}
window.__adminToast = showToast;
function activeTabName() {
var tab = document.querySelector('#adminMenu .tab.active');
return tab ? (tab.dataset.tab || '') : '';
}
function updateGuide(tabName) {
if (!guide || !guideText || !guideSteps) return;
var config = guides[tabName] || { text: '先使用筛选定位记录,再进行新增、编辑、导出等操作。涉及删除的数据会要求二次确认。', steps: ['选择筛选条件', '处理记录', '核对反馈'] };
guideText.textContent = config.text;
guideSteps.innerHTML = (config.steps || []).map(function (step) { return '<li>' + step + '</li>'; }).join('');
guide.dataset.tab = tabName || '';
}
window.__adminUpdateOperationGuide = updateGuide;
function setGuideCollapsed(collapsed) {
if (!guide || !guideToggle) return;
guide.classList.toggle('is-collapsed', collapsed);
guideToggle.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
guideToggle.textContent = collapsed ? '展开提示' : '收起提示';
try { localStorage.setItem('shufuAdminGuideCollapsed', collapsed ? '1' : '0'); } catch (error) {}
}
if (guideToggle) {
var collapsed = false;
try { collapsed = localStorage.getItem('shufuAdminGuideCollapsed') === '1'; } catch (error) {}
setGuideCollapsed(collapsed);
guideToggle.addEventListener('click', function () {
setGuideCollapsed(!guide.classList.contains('is-collapsed'));
});
}
function hashTabName() {
var raw = (window.location.hash || '').replace(/^#/, '');
var match = raw.match(/(?:^|&)tab=([^&]+)/);
return match ? decodeURIComponent(match[1]) : '';
}
function syncTabHash(tabName) {
if (!tabName || !window.history || !window.history.replaceState) return;
var next = '#tab=' + encodeURIComponent(tabName);
if (window.location.hash !== next) window.history.replaceState(null, '', next);
}
function navigateToHash(attemptsLeft) {
var tabName = hashTabName();
if (!tabName) {
updateGuide(activeTabName());
return;
}
var tab = document.querySelector('#adminMenu .tab[data-tab="' + tabName + '"]');
if (tab && typeof tab.onclick === 'function') {
if (!tab.classList.contains('active')) tab.click();
else updateGuide(tabName);
return;
}
if (attemptsLeft > 0) {
window.setTimeout(function () { navigateToHash(attemptsLeft - 1); }, 80);
} else {
updateGuide(activeTabName());
}
}
function closeConfirm() {
if (!confirmMask) return;
confirmMask.classList.remove('show');
confirmMask.setAttribute('aria-hidden', 'true');
document.body.classList.remove('admin-confirm-open');
var focus = previousFocus;
pendingConfirmButton = null;
previousFocus = null;
if (focus && focus.isConnected) focus.focus();
}
function openConfirm(button) {
if (!confirmMask || !confirmMessage || !confirmAccept) return;
pendingConfirmButton = button;
previousFocus = document.activeElement;
var customMessage = cleanText(button.dataset.confirmMessage);
var label = cleanText(button.getAttribute('aria-label') || button.textContent || '删除');
var subject = cleanText(button.dataset.name || button.dataset.value || button.dataset.shopManageName || button.dataset.shopName || button.dataset.ziniaoAccountName || '');
var country = cleanText(button.dataset.country || '');
if (!customMessage && country && subject) subject += '' + country + '';
if (!customMessage && subject) customMessage = (button.classList.contains('btn-danger') ? '确认删除“' : '确认执行“') + subject + '”吗?此操作可能影响已有数据。';
if (confirmTitle) confirmTitle.textContent = button.classList.contains('btn-danger') ? '删除前确认' : '请确认操作';
confirmMessage.textContent = customMessage || ('确认执行“' + label + '”吗?此操作可能影响已有数据。');
confirmAccept.textContent = button.dataset.confirmActionLabel || (button.classList.contains('btn-danger') ? '确认删除' : '确认操作');
confirmMask.classList.add('show');
confirmMask.setAttribute('aria-hidden', 'false');
document.body.classList.add('admin-confirm-open');
window.setTimeout(function () { confirmAccept.focus(); }, 0);
}
function keepConfirmFocus(event) {
if (!confirmMask || !confirmMask.classList.contains('show') || event.key !== 'Tab') return;
var focusable = Array.prototype.filter.call(confirmMask.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'), function (el) {
return !el.disabled && el.offsetParent !== null;
});
if (!focusable.length) return;
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
}
if (confirmCancel) confirmCancel.addEventListener('click', closeConfirm);
if (confirmMask) confirmMask.addEventListener('click', function (event) {
if (event.target === confirmMask) closeConfirm();
});
if (confirmAccept) confirmAccept.addEventListener('click', function () {
var target = pendingConfirmButton;
closeConfirm();
if (!target) return;
window.__adminConfirmBypass = true;
try { target.click(); }
finally { window.setTimeout(function () { window.__adminConfirmBypass = false; }, 0); }
});
document.addEventListener('keydown', function (event) {
if (event.key === 'Enter' && event.target && event.target.matches && event.target.matches('input:not([type="file"]), select') && !event.target.closest('textarea')) {
var searchScope = event.target.closest('.form-row, .form-box');
var searchButton = searchScope && searchScope.querySelector('button[id^="btnSearch"], button[id*="Search"]');
if (searchButton && !searchButton.disabled) {
event.preventDefault();
searchButton.click();
return;
}
}
if (event.key === 'Escape' && confirmMask && confirmMask.classList.contains('show')) {
event.preventDefault();
closeConfirm();
return;
}
keepConfirmFocus(event);
});
var nativeAlert = window.alert ? window.alert.bind(window) : null;
window.alert = function (message) {
showToast(message, /失败|错误|无权|不能为空|不正确|异常/.test(String(message || '')) ? 'error' : 'info');
};
var nativeConfirm = window.confirm ? window.confirm.bind(window) : null;
window.confirm = function (message) {
if (window.__adminConfirmBypass) return true;
return nativeConfirm ? nativeConfirm(message) : false;
};
function updatePageBusy() {
if (!mainContent) return;
mainContent.setAttribute('aria-busy', activeFetches || activeXhrs ? 'true' : 'false');
}
function startBusy() {
var button = window.__adminLastActionButton;
if (!button || !button.isConnected || button.disabled || button.classList.contains('tab') || button.classList.contains('menu-group-title') || button === confirmAccept) return;
busyButton = button;
busyWasDisabled = button.disabled;
button.classList.add('is-busy');
button.setAttribute('aria-busy', 'true');
button.disabled = true;
}
function finishBusy() {
var finishedButton = busyButton;
if (finishedButton && finishedButton.isConnected) {
finishedButton.classList.remove('is-busy');
finishedButton.removeAttribute('aria-busy');
if (!busyWasDisabled) finishedButton.disabled = false;
}
if (window.__adminLastActionButton === finishedButton) window.__adminLastActionButton = null;
busyButton = null;
busyWasDisabled = false;
}
if (window.fetch) {
var nativeFetch = window.fetch.bind(window);
window.fetch = function () {
activeFetches += 1;
updatePageBusy();
if (activeFetches === 1) startBusy();
var request;
try { request = nativeFetch.apply(window, arguments); }
catch (error) { activeFetches = Math.max(0, activeFetches - 1); if (!activeFetches && !activeXhrs) finishBusy(); throw error; }
return Promise.resolve(request).finally(function () {
activeFetches = Math.max(0, activeFetches - 1);
updatePageBusy();
if (!activeFetches && !activeXhrs) finishBusy();
});
};
}
if (window.XMLHttpRequest) {
var nativeSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
activeXhrs += 1;
updatePageBusy();
if (activeXhrs === 1) startBusy();
this.addEventListener('loadend', function () {
activeXhrs = Math.max(0, activeXhrs - 1);
updatePageBusy();
if (!activeFetches && !activeXhrs) finishBusy();
}, { once: true });
try { return nativeSend.apply(this, arguments); }
catch (error) { activeXhrs = Math.max(0, activeXhrs - 1); if (!activeFetches && !activeXhrs) finishBusy(); throw error; }
};
}
function addButtonHint(button) {
if (!button || button.title) return;
var label = cleanText(button.textContent);
if (label === '编辑') button.title = '编辑当前记录';
else if (label === '删除') button.title = '删除当前记录,需二次确认';
else if (label === '查询') button.title = '按当前筛选条件查询';
else if (/^导出/.test(label)) button.title = '导出当前筛选结果';
else if (/^上传并/.test(label)) button.title = '上传文件并执行相应操作';
else if (label === '管理分组') button.title = '新增、编辑或删除分组';
else if (label === '选择店铺') button.title = '从店铺列表选择并回填';
}
function enhance(root) {
var scope = root && root.querySelectorAll ? root : document;
scope.querySelectorAll('button').forEach(addButtonHint);
scope.querySelectorAll('.table-ellipsis').forEach(function (element) {
if (!element.title) element.title = cleanText(element.textContent);
});
scope.querySelectorAll('.empty-tip').forEach(function (element) { element.setAttribute('role', 'status'); });
scope.querySelectorAll('.msg').forEach(function (element) {
var value = cleanText(element.textContent);
if (!value || (!element.classList.contains('ok') && !element.classList.contains('err'))) return;
var key = value + '|' + element.className;
if (element.dataset.adminToastKey === key) return;
element.dataset.adminToastKey = key;
showToast(value, element.classList.contains('err') ? 'error' : 'success');
});
}
document.addEventListener('click', function (event) {
var button = event.target && event.target.closest ? event.target.closest('button') : null;
if (!button || button.disabled) return;
if (button.classList.contains('tab') || button.classList.contains('menu-group-title')) {
window.__adminLastActionButton = null;
} else if (button !== confirmAccept) {
window.__adminLastActionButton = button;
}
if (button.classList.contains('tab')) {
window.setTimeout(function () {
var tabName = activeTabName();
updateGuide(tabName);
syncTabHash(tabName);
}, 0);
}
if ((!button.matches('.btn-danger') && !button.hasAttribute('data-admin-confirm')) || button === confirmAccept || window.__adminConfirmBypass) return;
event.preventDefault();
event.stopImmediatePropagation();
openConfirm(button);
}, true);
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
enhance(mutation.target && mutation.target.nodeType === 1 ? mutation.target : document);
if (mutation.type === 'attributes' && mutation.target.matches && mutation.target.matches('#adminMenu .tab')) {
window.setTimeout(function () { updateGuide(activeTabName()); }, 0);
}
});
});
enhance(document);
observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class'] });
window.addEventListener('hashchange', function () { navigateToHash(0); });
navigateToHash(25);
})();
+224 -61
View File
@@ -93,10 +93,39 @@
} }
})(); })();
// ===== 菜单分组(一级分类 + 二级菜单)=====
var ADMIN_MENU_GROUPS = [
{ key: 'account', title: '账号与权限', items: ['users', 'columns'] },
{ key: 'data', title: '数据管理', items: ['dedupe-total-data', 'invalid-asin-data', 'query-asin', 'product-categories'] },
{ key: 'shop', title: '店铺管理', items: ['shop-keys', 'shop-manage', 'skip-price-asin'] },
{ key: 'tasks', title: '任务中心', items: ['image-video-tasks', 'shop-data-crawl-tasks'] },
{ key: 'record', title: '记录与版本', items: ['history', 'version', 'digital-human-version'] }
];
var ADMIN_MENU_ICONS = {
'users': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>',
'columns': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="7" height="7" x="3" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="3" rx="1"></rect><rect width="7" height="7" x="14" y="14" rx="1"></rect><rect width="7" height="7" x="3" y="14" rx="1"></rect></svg>',
'dedupe-total-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"></path><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"></path><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"></path></svg>',
'invalid-asin-data': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"></path><path d="M12 9v4"></path><path d="M12 17h.01"></path></svg>',
'shop-keys': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg>',
'shop-manage': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 7 4.41-4.41A2 2 0 0 1 7.83 2h8.34a2 2 0 0 1 1.42.59L22 7"></path><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><path d="M15 22v-4a2 2 0 0 0-2-2h-2a2 2 0 0 0-2 2v4"></path><path d="M2 7h20"></path><path d="M22 7v3a2 2 0 0 1-2 2 2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 16 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 12 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 8 12a2.7 2.7 0 0 1-1.59-.63.7.7 0 0 0-.82 0A2.7 2.7 0 0 1 4 12a2 2 0 0 1-2-2V7"></path></svg>',
'skip-price-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="m4.9 4.9 14.2 14.2"></path></svg>',
'query-asin': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><path d="m21 21-4.3-4.3"></path></svg>',
'product-categories': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"></path></svg>',
'image-video-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 8-6 4 6 4V8Z"></path><rect width="14" height="12" x="2" y="6" rx="2" ry="2"></rect></svg>',
'shop-data-crawl-tasks': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M3 5v14a9 3 0 0 0 18 0V5"></path><path d="M3 12a9 3 0 0 0 18 0"></path></svg>',
'history': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path><path d="M3 3v5h5"></path><path d="M12 7v5l4 2"></path></svg>',
'version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7.5 4.27 9 5.15"></path><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"></path><path d="M3.3 7 12 12l8.7-5"></path><path d="M12 22V12"></path></svg>',
'digital-human-version': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg>'
};
var ADMIN_MENU_FALLBACK_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M12 8v8"></path><path d="M8 12h8"></path></svg>';
function adminMenuIcon(route) {
return ADMIN_MENU_ICONS[route] || ADMIN_MENU_FALLBACK_ICON;
}
// Tab 切换 // Tab 切换
var adminTabsEl = document.getElementById('adminTabs'); var adminMenuEl = document.getElementById('adminMenu');
if (adminTabsEl) adminTabsEl.innerHTML = '';
var activeAdminTabName = ''; var activeAdminTabName = '';
var adminMenuByRoute = {};
var ADMIN_PANEL_MAP = { var ADMIN_PANEL_MAP = {
'users': 'panel-users', 'users': 'panel-users',
'columns': 'panel-columns', 'columns': 'panel-columns',
@@ -149,12 +178,28 @@
var panel = panelId ? document.getElementById(panelId) : null; var panel = panelId ? document.getElementById(panelId) : null;
if (!panel) return; if (!panel) return;
activeAdminTabName = tabName; activeAdminTabName = tabName;
document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { document.querySelectorAll('#adminMenu .tab').forEach(function (tab) {
tab.classList.toggle('active', tab.dataset.tab === tabName); var isActive = tab.dataset.tab === tabName;
tab.classList.toggle('active', isActive);
if (isActive) tab.setAttribute('aria-current', 'page');
else tab.removeAttribute('aria-current');
}); });
// 激活页面时自动展开所属分组(折叠由用户显式控制)
if (adminMenuEl) {
adminMenuEl.querySelectorAll('.menu-group').forEach(function (group) {
if (group.querySelector('.tab[data-tab="' + tabName + '"]')) {
group.classList.remove('is-collapsed');
var groupTitle = group.querySelector('.menu-group-title');
if (groupTitle) groupTitle.setAttribute('aria-expanded', 'true');
}
});
}
hideAllAdminPanels(); hideAllAdminPanels();
panel.style.display = ''; panel.style.display = '';
panel.classList.add('active'); panel.classList.add('active');
var nameItem = adminMenuByRoute[tabName];
var titleEl = document.getElementById('adminPageTitle');
if (titleEl) titleEl.textContent = (nameItem && nameItem.name) || tabName;
runTabLoader(tabName); runTabLoader(tabName);
} }
function renderAdminTabs(items) { function renderAdminTabs(items) {
@@ -163,39 +208,75 @@
}); });
if (!knownItems.length) { if (!knownItems.length) {
activeAdminTabName = ''; activeAdminTabName = '';
if (adminTabsEl) adminTabsEl.innerHTML = '<div class="tab active" style="cursor:default;">暂无可用菜单</div>'; if (adminMenuEl) adminMenuEl.innerHTML = '<div class="menu-empty">暂无可用菜单</div>';
hideAllAdminPanels(); hideAllAdminPanels();
return; return;
} }
if (adminTabsEl) { adminMenuByRoute = {};
adminTabsEl.innerHTML = knownItems.map(function (item) { knownItems.forEach(function (item) {
return '<div class="tab" data-tab="' + item.route_path + '">' + (item.name || item.route_path) + '</div>'; adminMenuByRoute[item.route_path] = item;
});
var groupsHtml = '';
ADMIN_MENU_GROUPS.forEach(function (group) {
var groupRoutes = group.items.filter(function (route) {
return !!adminMenuByRoute[route];
});
if (!groupRoutes.length) return;
var itemsHtml = groupRoutes.map(function (route) {
var item = adminMenuByRoute[route];
var label = (item.name || route).replace(/"/g, '&quot;');
return '<button class="tab" type="button" data-tab="' + route + '" aria-controls="panel-' + route + '" title="' + label + '">' +
'<span class="adm-icon">' + adminMenuIcon(route) + '</span>' +
'<span class="adm-label">' + label + '</span></button>';
}).join(''); }).join('');
groupsHtml += '<div class="menu-group" data-menu-group="' + group.key + '">' +
'<button class="menu-group-title" type="button" aria-expanded="true">' + group.title +
'<span class="menu-group-chevron" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"></path></svg></span>' +
'</button>' +
'<div class="menu-group-body">' + itemsHtml + '</div></div>';
});
if (!groupsHtml) {
activeAdminTabName = '';
if (adminMenuEl) adminMenuEl.innerHTML = '<div class="menu-empty">暂无可用菜单</div>';
hideAllAdminPanels();
return;
} }
document.querySelectorAll('#adminTabs .tab').forEach(function (tab) { if (adminMenuEl) adminMenuEl.innerHTML = groupsHtml;
document.querySelectorAll('#adminMenu .tab').forEach(function (tab) {
tab.onclick = function () { tab.onclick = function () {
activateAdminTab(tab.dataset.tab); activateAdminTab(tab.dataset.tab);
}; };
}); });
if (window.__initAdminMenuCollapse) window.__initAdminMenuCollapse();
var fallbackTab = activeAdminTabName && knownItems.some(function (item) { return item.route_path === activeAdminTabName; }) var fallbackTab = activeAdminTabName && knownItems.some(function (item) { return item.route_path === activeAdminTabName; })
? activeAdminTabName ? activeAdminTabName
: knownItems[0].route_path; : knownItems[0].route_path;
activateAdminTab(fallbackTab); activateAdminTab(fallbackTab);
} }
function syncAdminMenuGroups() {
if (!adminMenuEl) return;
adminMenuEl.querySelectorAll('.menu-group').forEach(function (group) {
var visible = false;
group.querySelectorAll('.tab').forEach(function (tab) {
if (tab.style.display !== 'none') visible = true;
});
group.style.display = visible ? '' : 'none';
});
}
function loadAdminMenus(preferredTab) { function loadAdminMenus(preferredTab) {
if (preferredTab) activeAdminTabName = preferredTab; if (preferredTab) activeAdminTabName = preferredTab;
fetch('/api/admin/current-user/menus') fetch('/api/admin/current-user/menus')
.then(function (r) { return r.json(); }) .then(function (r) { return r.json(); })
.then(function (res) { .then(function (res) {
if (!res.success) { if (!res.success) {
if (adminTabsEl) adminTabsEl.innerHTML = '<div class="tab active" style="cursor:default;">菜单加载失败</div>'; if (adminMenuEl) adminMenuEl.innerHTML = '<div class="menu-empty">菜单加载失败</div>';
hideAllAdminPanels(); hideAllAdminPanels();
return; return;
} }
renderAdminTabs(res.items || []); renderAdminTabs(res.items || []);
}) })
.catch(function () { .catch(function () {
if (adminTabsEl) adminTabsEl.innerHTML = '<div class="tab active" style="cursor:default;">菜单加载失败</div>'; if (adminMenuEl) adminMenuEl.innerHTML = '<div class="menu-empty">菜单加载失败</div>';
hideAllAdminPanels(); hideAllAdminPanels();
}); });
} }
@@ -282,12 +363,15 @@
if (panel) panel.style.display = canUse ? '' : 'none'; if (panel) panel.style.display = canUse ? '' : 'none';
if (!canUse && tab && tab.classList.contains('active')) { if (!canUse && tab && tab.classList.contains('active')) {
tab.classList.remove('active'); tab.classList.remove('active');
tab.removeAttribute('aria-current');
if (panel) panel.classList.remove('active'); if (panel) panel.classList.remove('active');
var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersTab = document.querySelector('.tab[data-tab="users"]');
var usersPanel = document.getElementById('panel-users'); var usersPanel = document.getElementById('panel-users');
if (usersTab) usersTab.classList.add('active'); if (usersTab) { usersTab.classList.add('active'); usersTab.setAttribute('aria-current', 'page'); }
if (usersPanel) usersPanel.classList.add('active'); if (usersPanel) usersPanel.classList.add('active');
} }
syncAdminMenuGroups();
} }
function updateShopManageAccess() { function updateShopManageAccess() {
var tab = document.querySelector('.tab[data-tab="shop-manage"]'); var tab = document.querySelector('.tab[data-tab="shop-manage"]');
@@ -300,12 +384,15 @@
panel.style.display = canUse ? '' : 'none'; panel.style.display = canUse ? '' : 'none';
if (!canUse && tab.classList.contains('active')) { if (!canUse && tab.classList.contains('active')) {
tab.classList.remove('active'); tab.classList.remove('active');
tab.removeAttribute('aria-current');
panel.classList.remove('active'); panel.classList.remove('active');
var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersTab = document.querySelector('.tab[data-tab="users"]');
var usersPanel = document.getElementById('panel-users'); var usersPanel = document.getElementById('panel-users');
if (usersTab) usersTab.classList.add('active'); if (usersTab) { usersTab.classList.add('active'); usersTab.setAttribute('aria-current', 'page'); }
if (usersPanel) usersPanel.classList.add('active'); if (usersPanel) usersPanel.classList.add('active');
} }
syncAdminMenuGroups();
} }
function loadCurrentUserAdminPermissions() { function loadCurrentUserAdminPermissions() {
currentUserAdminPermissionKeys = {}; currentUserAdminPermissionKeys = {};
@@ -340,12 +427,15 @@
if (panel) panel.style.display = canUse ? '' : 'none'; if (panel) panel.style.display = canUse ? '' : 'none';
if (!canUse && tab && tab.classList.contains('active')) { if (!canUse && tab && tab.classList.contains('active')) {
tab.classList.remove('active'); tab.classList.remove('active');
tab.removeAttribute('aria-current');
if (panel) panel.classList.remove('active'); if (panel) panel.classList.remove('active');
var usersTab = document.querySelector('.tab[data-tab="users"]'); var usersTab = document.querySelector('.tab[data-tab="users"]');
var usersPanel = document.getElementById('panel-users'); var usersPanel = document.getElementById('panel-users');
if (usersTab) usersTab.classList.add('active'); if (usersTab) { usersTab.classList.add('active'); usersTab.setAttribute('aria-current', 'page'); }
if (usersPanel) usersPanel.classList.add('active'); if (usersPanel) usersPanel.classList.add('active');
} }
syncAdminMenuGroups();
} }
function hasAdminTabAccess(tabName) { function hasAdminTabAccess(tabName) {
var config = ADMIN_TAB_ACCESS_CONFIG[tabName]; var config = ADMIN_TAB_ACCESS_CONFIG[tabName];
@@ -1094,9 +1184,37 @@
Object.keys(values).forEach(function (key) { if (values[key]) params.set(key, values[key]); }); Object.keys(values).forEach(function (key) { if (values[key]) params.set(key, values[key]); });
return params.toString(); return params.toString();
} }
var SHOP_DATA_STATUS_LABELS = {
'PENDING': '待执行', 'WAITING': '排队中', 'RUNNING': '执行中', 'POLLING': '处理中',
'SUCCESS': '已完成', 'COMPLETED': '已完成', 'DONE': '已完成',
'FAILED': '失败', 'ERROR': '失败', 'CANCELLED': '已取消', 'CANCELED': '已取消',
'DELETED': '已删除', 'STOPPED': '已停止'
};
function imageVideoStatusLabel(value) {
var status = String(value || '-').toUpperCase();
var label = SHOP_DATA_STATUS_LABELS[status];
if (label) return label;
if (status.indexOf('SUCCEED') === 0) return '已完成';
if (status.indexOf('FAIL') === 0 || status.indexOf('ERROR') === 0) return '失败';
if (status.indexOf('CANCEL') === 0) return '已取消';
if (status.indexOf('RUN') === 0 || status.indexOf('WAIT') === 0 || status.indexOf('PROCESS') === 0 || status.indexOf('POLL') === 0) return '处理中';
return status === '-' ? '-' : status;
}
function countryCodeLabel(value) {
var labels = { DE: '德国', UK: '英国', FR: '法国', IT: '意大利', ES: '西班牙' };
return labels[String(value || '').toUpperCase()] || value || '-';
}
function countryListLabel(codes) {
if (Array.isArray(codes)) {
return codes.map(countryCodeLabel).join('、') || '-';
}
return String(codes || '').split(/[,]/).map(function (code) {
return countryCodeLabel(code.trim());
}).filter(Boolean).join('、') || '-';
}
function renderImageVideoStatus(value) { function renderImageVideoStatus(value) {
var status = String(value || '-').toUpperCase(); var status = String(value || '-').toUpperCase();
return '<span class="image-video-status ' + escapeHtml(status) + '">' + escapeHtml(status) + '</span>'; return '<span class="image-video-status ' + escapeHtml(status) + '" title="' + escapeHtml(status) + '">' + escapeHtml(imageVideoStatusLabel(status)) + '</span>';
} }
function safeAdminUrl(value) { function safeAdminUrl(value) {
var url = String(value || '').trim(); var url = String(value || '').trim();
@@ -1154,7 +1272,7 @@
'<div class="image-video-media">' + mediaHtml + '</div>' + '<div class="image-video-media">' + mediaHtml + '</div>' +
'<div class="image-video-card-body">' + '<div class="image-video-card-body">' +
'<div class="image-video-card-head">' + '<div class="image-video-card-head">' +
'<div class="image-video-card-title" title="任务 ' + escapeHtml(task.task_id) + '">任务 #' + escapeHtml(task.task_id) + (video ? ' · 视频 ' + (card.videoIndex + 1) : '') + '</div>' + '<div class="image-video-card-title" title="任务 ' + escapeHtml(task.task_id) + '">任务 ' + escapeHtml(task.task_id) + (video ? ' · 视频 ' + (card.videoIndex + 1) : '') + '</div>' +
renderImageVideoStatus(task.status) + renderImageVideoStatus(task.status) +
'</div>' + '</div>' +
'<div class="image-video-card-info">' + '<div class="image-video-card-info">' +
@@ -1601,7 +1719,7 @@
function renderShopDataStatus(item, status) { function renderShopDataStatus(item, status) {
var normalized = String(status || '-').toUpperCase(); var normalized = String(status || '-').toUpperCase();
var errorTitle = item && item.error ? ' title="' + escapeHtml(item.error) + '"' : ''; var errorTitle = item && item.error ? ' title="' + escapeHtml(item.error) + '"' : '';
return '<span class="image-video-status ' + escapeHtml(normalized) + '"' + errorTitle + '>' + escapeHtml(normalized) + '</span>'; return '<span class="image-video-status ' + escapeHtml(normalized) + '"' + errorTitle + '>' + escapeHtml(imageVideoStatusLabel(normalized)) + '</span>';
} }
function renderShopDataTaskResult(item) { function renderShopDataTaskResult(item) {
@@ -1610,14 +1728,14 @@
var status = String(item.status || item.file_status || '').toUpperCase(); var status = String(item.status || item.file_status || '').toUpperCase();
var terminal = ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(status) >= 0; var terminal = ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(status) >= 0;
var countryCodes = item.country_codes != null ? item.country_codes : item.countryCodes; var countryCodes = item.country_codes != null ? item.country_codes : item.countryCodes;
var countries = Array.isArray(countryCodes) ? countryCodes.join('、') : (String(countryCodes || '') || '-'); var countries = countryListLabel(countryCodes);
var filename = item.output_filename || '-'; var filename = item.output_filename || '-';
var checkbox = '<input type="checkbox" data-shop-data-select="' + (resultId || '') + '"' + var checkbox = '<input type="checkbox" data-shop-data-select="' + (resultId || '') + '"' +
(selected ? ' checked' : '') + (item.file_ready && resultId > 0 ? '' : ' disabled') + '>'; (selected ? ' checked' : '') + (item.file_ready && resultId > 0 ? '' : ' disabled') + '>';
return '<div class="shop-data-result' + (selected ? ' selected' : '') + '" data-shop-data-card="' + (resultId || '') + '">' + return '<div class="shop-data-result' + (selected ? ' selected' : '') + '" data-shop-data-card="' + (resultId || '') + '">' +
'<div class="shop-data-result-head">' + '<div class="shop-data-result-head">' +
'<label class="shop-data-task-title">' + checkbox + '<label class="shop-data-task-title">' + checkbox +
'<span title="任务 #' + escapeHtml(item.task_id || item.task_no || '-') + '">任务 #' + escapeHtml(item.task_id || item.task_no || '-') + '</span>' + '<span title="任务 ' + escapeHtml(item.task_id || item.task_no || '-') + '">任务 ' + escapeHtml(item.task_id || item.task_no || '-') + '</span>' +
'</label>' + '</label>' +
renderShopDataStatus(item, status || item.file_status) + renderShopDataStatus(item, status || item.file_status) +
'</div>' + '</div>' +
@@ -1724,9 +1842,9 @@
function deleteShopDataTask(item) { function deleteShopDataTask(item) {
var resultId = shopDataResultId(item); var resultId = shopDataResultId(item);
if (!item || !resultId || ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(String(item.status || item.file_status || '').toUpperCase()) < 0) return; if (!item || !resultId || ['SUCCESS', 'FAILED', 'CANCELLED'].indexOf(String(item.status || item.file_status || '').toUpperCase()) < 0) return;
if (!window.confirm('确认删除店铺“' + (item.shop_name || '-') + '”的任务 #' + item.task_id + ' 及结果文件?')) return; if (!window.confirm('确认删除店铺“' + (item.shop_name || '-') + '”的任务 ' + item.task_id + ' 及结果文件?')) return;
var progress = document.getElementById('shopDataTaskDownloadProgress'); var progress = document.getElementById('shopDataTaskDownloadProgress');
progress.textContent = '正在删除任务 #' + item.task_id + '...'; progress.textContent = '正在删除任务 ' + item.task_id + '...';
fetch('/api/admin/shop-data-crawl-tasks/' + resultId, { method: 'DELETE' }) fetch('/api/admin/shop-data-crawl-tasks/' + resultId, { method: 'DELETE' })
.then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); }) .then(function (response) { return response.json().then(function (data) { return { ok: response.ok, data: data }; }); })
.then(function (result) { .then(function (result) {
@@ -1983,6 +2101,17 @@
if (end) q += '&time_end=' + encodeURIComponent(end); if (end) q += '&time_end=' + encodeURIComponent(end);
return q; return q;
} }
function panelTypeLabel(value) {
var panelType = String(value || '');
var labels = {
textToImage: '反推词', productMainImage: '产品主图', buyerShow: '买家秀',
productPoster: '产品海报', clonePoster: '克隆海报', randomPoster: '随机海报',
clothingDetail: '服装详情', productDetail: '产品详情', extremeDetail: '极致详情',
cloneDetail: '克隆详情', imageEdit: '图片编辑'
};
if (labels[panelType]) return labels[panelType];
return panelType ? panelType : '-';
}
function loadHistory(page) { function loadHistory(page) {
historyPage = page || 1; historyPage = page || 1;
fetch('/api/admin/history?' + buildHistoryQuery(historyPage)) fetch('/api/admin/history?' + buildHistoryQuery(historyPage))
@@ -2004,7 +2133,7 @@
return '<img src="' + (url || '').replace(/"/g, '&quot;') + '" class="thumb" alt="">'; return '<img src="' + (url || '').replace(/"/g, '&quot;') + '" class="thumb" alt="">';
}).join(''); }).join('');
return '<tr><td>' + h.id + '</td><td>' + (h.username || '-') + '</td><td>' + return '<tr><td>' + h.id + '</td><td>' + (h.username || '-') + '</td><td>' +
(h.panel_type || '-') + '</td><td>' + (h.created_at || '') + '</td><td>' + panelTypeLabel(h.panel_type) + '</td><td>' + (h.created_at || '') + '</td><td>' +
'<div class="thumb-wrap">' + (thumbs || '-') + '</div></td></tr>'; '<div class="thumb-wrap">' + (thumbs || '-') + '</div></td></tr>';
}).join(''); }).join('');
} }
@@ -2833,8 +2962,9 @@
var details = []; var details = [];
if (item.ip_whitelist_checked_at) details.push('检测时间:' + item.ip_whitelist_checked_at); if (item.ip_whitelist_checked_at) details.push('检测时间:' + item.ip_whitelist_checked_at);
if (item.ip_whitelist_message) details.push(item.ip_whitelist_message); if (item.ip_whitelist_message) details.push(item.ip_whitelist_message);
return '<span class="shop-key-whitelist-status ' + meta.className + '" title="' + var detailText = details.join('\n') || ('白名单状态:' + meta.label);
escapeHtml(details.join('\n')) + '">' + escapeHtml(meta.label) + '</span>'; return '<span class="shop-key-whitelist-status ' + meta.className + '" role="status" aria-label="白名单状态:' + escapeHtml(meta.label) + '" title="' +
escapeHtml(detailText) + '">' + escapeHtml(meta.label) + '</span>';
} }
function loadShopKeys(page) { function loadShopKeys(page) {
shopKeyPage = page || 1; shopKeyPage = page || 1;
@@ -2852,9 +2982,9 @@
} else { } else {
tbody.innerHTML = items.map(function (item, index) { tbody.innerHTML = items.map(function (item, index) {
var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1; var rowNo = (shopKeyPage - 1) * shopKeyPageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + escapeHtml(item.remark_name || '') + '</td><td>' + escapeHtml(item.ziniao_account_name || '') + '</td><td>' + escapeHtml(item.ziniao_token || '') + '</td><td>' + renderShopKeyWhitelistStatus(item) + '</td><td>' + escapeHtml(item.created_at || '') + '</td><td>' + escapeHtml(item.updated_at || '') + '</td><td>' + return '<tr><td>' + rowNo + '</td><td>' + renderShopTableText(item.remark_name) + '</td><td>' + renderShopTableText(item.ziniao_account_name) + '</td><td>' + renderShopTableText(item.ziniao_token) + '</td><td>' + renderShopKeyWhitelistStatus(item) + '</td><td>' + renderShopTableText(item.created_at) + '</td><td>' + renderShopTableText(item.updated_at) + '</td><td>' +
'<button class="btn btn-sm" data-shop-key-edit="' + item.id + '" data-shop-key="' + (JSON.stringify(item).replace(/"/g, '&quot;')) + '">编辑</button> ' + '<button type="button" class="btn btn-sm" data-shop-key-edit="' + escapeHtml(item.id) + '" data-shop-key="' + (JSON.stringify(item).replace(/"/g, '&quot;')) + '">编辑</button> ' +
'<button class="btn btn-sm btn-danger" data-shop-key-delete="' + item.id + '" data-ziniao-account-name="' + (item.ziniao_account_name || '').replace(/"/g, '&quot;') + '">删除</button>' + '<button type="button" class="btn btn-sm btn-danger" data-shop-key-delete="' + escapeHtml(item.id) + '" data-ziniao-account-name="' + escapeHtml(item.ziniao_account_name || '') + '">删除</button>' +
'</td></tr>'; '</td></tr>';
}).join(''); }).join('');
} }
@@ -3004,6 +3134,12 @@
shopPasswordIcon(false) + '</button></span>'; shopPasswordIcon(false) + '</button></span>';
} }
function renderShopTableText(value, fallback) {
var text = String(value == null ? '' : value).trim();
var shown = text || fallback || '-';
return '<span class="table-ellipsis" title="' + escapeHtml(shown) + '">' + escapeHtml(shown) + '</span>';
}
function loadShopManage(page) { function loadShopManage(page) {
shopManagePage = page || 1; shopManagePage = page || 1;
fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage)) fetch('/api/admin/shop-manages?' + buildShopManageQuery(shopManagePage))
@@ -3020,9 +3156,19 @@
} else { } else {
tbody.innerHTML = items.map(function (item, index) { tbody.innerHTML = items.map(function (item, index) {
var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1; var rowNo = (shopManagePage - 1) * shopManagePageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td><td>' + (item.mall_name || '') + '</td><td>' + (item.zn_username || '') + '</td><td>' + (item.account || '') + '</td><td>' + renderShopPasswordCell(item) + '</td><td>' + (item.created_at || '') + '</td><td>' + (item.updated_at || '') + '</td><td>' + return '<tr>' +
'<button class="btn btn-sm" data-shop-manage-edit="' + item.id + '" data-shop-manage="' + (JSON.stringify(item).replace(/"/g, '&quot;')) + '">编辑</button> ' + '<td class="shop-col-index">' + rowNo + '</td>' +
'<button class="btn btn-sm btn-danger" data-shop-manage-delete="' + item.id + '" data-shop-manage-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '">删除</button>' + '<td class="shop-col-group">' + renderShopTableText(item.group_name) + '</td>' +
'<td class="shop-col-name">' + renderShopTableText(item.shop_name) + '</td>' +
'<td class="shop-col-mall">' + renderShopTableText(item.mall_name) + '</td>' +
'<td class="shop-col-automation">' + renderShopTableText(item.zn_username) + '</td>' +
'<td class="shop-col-account">' + renderShopTableText(item.account) + '</td>' +
'<td class="shop-col-password">' + renderShopPasswordCell(item) + '</td>' +
'<td class="shop-col-created">' + renderShopTableText(item.created_at) + '</td>' +
'<td class="shop-col-updated">' + renderShopTableText(item.updated_at) + '</td>' +
'<td class="shop-col-actions">' +
'<button type="button" class="btn btn-sm" data-shop-manage-edit="' + escapeHtml(item.id) + '" data-shop-manage="' + (JSON.stringify(item).replace(/"/g, '&quot;')) + '">编辑</button> ' +
'<button type="button" class="btn btn-sm btn-danger" data-shop-manage-delete="' + escapeHtml(item.id) + '" data-shop-manage-name="' + escapeHtml(item.shop_name || '') + '">删除</button>' +
'</td></tr>'; '</td></tr>';
}).join(''); }).join('');
} }
@@ -3655,6 +3801,7 @@
} }
function renderSkipPriceAsinInputs() { function renderSkipPriceAsinInputs() {
var container = document.getElementById('skipPriceAsinInputs'); var container = document.getElementById('skipPriceAsinInputs');
if (!container) return;
var selectedCountries = getSelectedSkipPriceCountries(); var selectedCountries = getSelectedSkipPriceCountries();
var existingValues = {}; var existingValues = {};
var existingMinimumPrices = {}; var existingMinimumPrices = {};
@@ -3665,17 +3812,17 @@
existingMinimumPrices[input.getAttribute('data-skip-price-country-minimum-price-input')] = input.value; existingMinimumPrices[input.getAttribute('data-skip-price-country-minimum-price-input')] = input.value;
}); });
if (!selectedCountries.length) { if (!selectedCountries.length) {
container.innerHTML = '<div style="color:#999;font-size:13px;">请选择国家后输入 ASIN 和最低价</div>'; container.innerHTML = '<div class="asin-empty-hint">请选择国家后输入 ASIN 和最低价</div>';
return; return;
} }
container.innerHTML = selectedCountries.map(function (countryCode) { container.innerHTML = selectedCountries.map(function (countryCode) {
var label = getSkipPriceCountryLabel(countryCode); var label = getSkipPriceCountryLabel(countryCode);
var value = existingValues[countryCode] || ''; var value = existingValues[countryCode] || '';
var minimumPrice = existingMinimumPrices[countryCode] || ''; var minimumPrice = existingMinimumPrices[countryCode] || '';
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' + return '<div class="skip-price-entry">' +
'<span style="min-width:56px;color:#555;">' + label + '</span>' + '<span class="skip-price-country">' + escapeHtml(label) + '</span>' +
'<input type="text" data-skip-price-country-input="' + countryCode + '" value="' + value.replace(/"/g, '&quot;') + '" placeholder="请输入' + label + ' ASIN" style="flex:1;min-width:180px;">' + '<input class="skip-price-asin-input" type="text" data-skip-price-country-input="' + escapeHtml(countryCode) + '" value="' + escapeHtml(value) + '" placeholder="请输入' + escapeHtml(label) + ' ASIN">' +
'<input type="number" data-skip-price-country-minimum-price-input="' + countryCode + '" value="' + minimumPrice.replace(/"/g, '&quot;') + '" min="0" step="0.01" placeholder="最低价" style="width:140px;">' + '<input class="skip-price-minimum-input" type="number" data-skip-price-country-minimum-price-input="' + escapeHtml(countryCode) + '" value="' + escapeHtml(minimumPrice) + '" min="0" step="0.01" placeholder="最低价">' +
'</div>'; '</div>';
}).join(''); }).join('');
} }
@@ -3783,23 +3930,27 @@
}); });
} }
function renderSkipPriceAsinCell(item, country) { function renderSkipPriceAsinCell(item, country) {
var asinValue = item[country.field] || ''; var asinValue = String(item[country.field] || '').trim();
var minimumPriceValue = formatSkipPriceMinimumPrice(item[country.minimumPriceField]); var minimumPriceValue = formatSkipPriceMinimumPrice(item[country.minimumPriceField]);
var hasValue = !!asinValue || !!minimumPriceValue; var hasValue = !!asinValue || !!minimumPriceValue;
var safeAsin = escapeHtml(asinValue);
var safeMinimumPrice = escapeHtml(minimumPriceValue || '-');
var countryLabel = escapeHtml(country.label || country.code || '国家');
var infoHtml = hasValue var infoHtml = hasValue
? ('<div style="display:flex;flex-direction:column;gap:4px;min-width:0;">' + ? '<div class="asin-cell-content">' +
'<span>' + (asinValue || '<span style="color:#999;">-</span>') + '</span>' + (asinValue ? '<span class="asin-cell-value" title="' + safeAsin + '">' + safeAsin + '</span>' : '<span class="asin-empty-value">-</span>') +
'<span style="color:#666;font-size:12px;">最低价:' + (minimumPriceValue || '-') + '</span>' + '<span class="asin-cell-meta">最低价:' + safeMinimumPrice + '</span>' +
'</div>') '</div>'
: '<span style="color:#999;">-</span>'; : '<span class="asin-empty-value">-</span>';
var deleteHtml = hasValue var deleteHtml = hasValue
? ('<button class="btn btn-sm btn-danger" data-skip-price-asin-delete="' + item.id + '" data-country="' + country.code + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '">删除</button>') ? '<button type="button" class="btn btn-sm btn-danger" aria-label="删除' + countryLabel + ' ASIN" data-skip-price-asin-delete="' + escapeHtml(item.id) + '" data-country="' + escapeHtml(country.code) + '" data-shop-name="' + escapeHtml(item.shop_name || '') + '">删除</button>'
: ''; : '';
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' + return '<div class="asin-cell-layout">' +
infoHtml + infoHtml +
'<button class="btn btn-sm" data-skip-price-asin-edit="' + item.id + '" data-country="' + country.code + '" data-asin="' + asinValue.replace(/"/g, '&quot;') + '" data-minimum-price="' + minimumPriceValue.replace(/"/g, '&quot;') + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '">编辑</button>' + '<div class="asin-cell-actions">' +
'<button type="button" class="btn btn-sm" aria-label="编辑' + countryLabel + ' ASIN" data-skip-price-asin-edit="' + escapeHtml(item.id) + '" data-country="' + escapeHtml(country.code) + '" data-asin="' + safeAsin + '" data-minimum-price="' + escapeHtml(minimumPriceValue) + '" data-shop-name="' + escapeHtml(item.shop_name || '') + '">编辑</button>' +
deleteHtml + deleteHtml +
'</div>'; '</div></div>';
} }
function openEditSkipPriceAsinModal(itemId, countryCode, shopName, asinValue, minimumPriceValue) { function openEditSkipPriceAsinModal(itemId, countryCode, shopName, asinValue, minimumPriceValue) {
document.getElementById('editSkipPriceAsinId').value = itemId || ''; document.getElementById('editSkipPriceAsinId').value = itemId || '';
@@ -3861,9 +4012,12 @@
} else { } else {
tbody.innerHTML = items.map(function (item, index) { tbody.innerHTML = items.map(function (item, index) {
var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1; var rowNo = (skipPriceAsinPage - 1) * skipPriceAsinPageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td>' + return '<tr>' +
'<td class="asin-col-index">' + rowNo + '</td>' +
'<td class="asin-col-group">' + renderShopTableText(item.group_name) + '</td>' +
'<td class="asin-col-shop">' + renderShopTableText(item.shop_name) + '</td>' +
skipPriceCountryColumns.map(function (country) { skipPriceCountryColumns.map(function (country) {
return '<td>' + renderSkipPriceAsinCell(item, country) + '</td>'; return '<td class="asin-col-country">' + renderSkipPriceAsinCell(item, country) + '</td>';
}).join('') + }).join('') +
'</tr>'; '</tr>';
}).join(''); }).join('');
@@ -4455,16 +4609,20 @@
}); });
} }
function renderQueryAsinCell(item, country) { function renderQueryAsinCell(item, country) {
var asinValue = item[country.field] || ''; var asinValue = String(item[country.field] || '').trim();
var infoHtml = asinValue ? '<span>' + asinValue + '</span>' : '<span style="color:#999;">-</span>'; var countryLabel = escapeHtml(country.label || country.code || '国家');
var infoHtml = asinValue
? '<span class="asin-cell-value" title="' + escapeHtml(asinValue) + '">' + escapeHtml(asinValue) + '</span>'
: '<span class="asin-empty-value">-</span>';
var deleteHtml = asinValue var deleteHtml = asinValue
? ('<button class="btn btn-sm btn-danger" data-query-asin-delete="' + item.id + '" data-country="' + country.code + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '">删除</button>') ? '<button type="button" class="btn btn-sm btn-danger" aria-label="删除' + countryLabel + ' ASIN" data-query-asin-delete="' + escapeHtml(item.id) + '" data-country="' + escapeHtml(country.code) + '" data-shop-name="' + escapeHtml(item.shop_name || '') + '">删除</button>'
: ''; : '';
return '<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' + return '<div class="asin-cell-layout">' +
infoHtml + '<div class="asin-cell-content">' + infoHtml + '</div>' +
'<button class="btn btn-sm" data-query-asin-edit="' + item.id + '" data-country="' + country.code + '" data-asin="' + asinValue.replace(/"/g, '&quot;') + '" data-shop-name="' + (item.shop_name || '').replace(/"/g, '&quot;') + '">编辑</button>' + '<div class="asin-cell-actions">' +
'<button type="button" class="btn btn-sm" aria-label="编辑' + countryLabel + ' ASIN" data-query-asin-edit="' + escapeHtml(item.id) + '" data-country="' + escapeHtml(country.code) + '" data-asin="' + escapeHtml(asinValue) + '" data-shop-name="' + escapeHtml(item.shop_name || '') + '">编辑</button>' +
deleteHtml + deleteHtml +
'</div>'; '</div></div>';
} }
function openEditQueryAsinModal(itemId, countryCode, shopName, asinValue) { function openEditQueryAsinModal(itemId, countryCode, shopName, asinValue) {
document.getElementById('editQueryAsinId').value = itemId || ''; document.getElementById('editQueryAsinId').value = itemId || '';
@@ -4519,9 +4677,12 @@
} else { } else {
tbody.innerHTML = items.map(function (item, index) { tbody.innerHTML = items.map(function (item, index) {
var rowNo = (queryAsinPage - 1) * queryAsinPageSize + index + 1; var rowNo = (queryAsinPage - 1) * queryAsinPageSize + index + 1;
return '<tr><td>' + rowNo + '</td><td>' + (item.group_name || '') + '</td><td>' + (item.shop_name || '') + '</td>' + return '<tr>' +
'<td class="asin-col-index">' + rowNo + '</td>' +
'<td class="asin-col-group">' + renderShopTableText(item.group_name) + '</td>' +
'<td class="asin-col-shop">' + renderShopTableText(item.shop_name) + '</td>' +
queryAsinCountryColumns.map(function (country) { queryAsinCountryColumns.map(function (country) {
return '<td>' + renderQueryAsinCell(item, country) + '</td>'; return '<td class="asin-col-country">' + renderQueryAsinCell(item, country) + '</td>';
}).join('') + }).join('') +
'</tr>'; '</tr>';
}).join(''); }).join('');
@@ -4970,7 +5131,7 @@
'<td><div class="tree-name-cell"><span class="tree-indent" style="--indent:' + indent + 'px;"></span>' + '<td><div class="tree-name-cell"><span class="tree-indent" style="--indent:' + indent + 'px;"></span>' +
'<button type="button" class="tree-node-mark' + (hasChildren ? '' : ' is-leaf') + '" data-product-category-toggle="' + escapeHtml(item.id) + '"' + (hasChildren ? '' : ' disabled') + '>' + mark + '</button>' + '<button type="button" class="tree-node-mark' + (hasChildren ? '' : ' is-leaf') + '" data-product-category-toggle="' + escapeHtml(item.id) + '"' + (hasChildren ? '' : ' disabled') + '>' + mark + '</button>' +
'<strong>' + escapeHtml(item.name || '') + '</strong></div></td>' + '<strong>' + escapeHtml(item.name || '') + '</strong></div></td>' +
'<td><div>' + escapeHtml(item.path || item.name || '') + '</div><div class="category-path">' + escapeHtml(item.category_key || '') + '</div></td>' + '<td><span class="category-path-value table-ellipsis" title="' + escapeHtml(item.path || item.name || '') + '">' + escapeHtml(item.path || item.name || '') + '</span></td>' +
'<td>' + escapeHtml(item.sort_order || 0) + '</td>' + '<td>' + escapeHtml(item.sort_order || 0) + '</td>' +
'<td><span class="category-tag">' + (item.is_builtin ? '内置' : '自定义') + '</span></td>' + '<td><span class="category-tag">' + (item.is_builtin ? '内置' : '自定义') + '</span></td>' +
'<td>' + escapeHtml(item.description || '') + '</td>' + '<td>' + escapeHtml(item.description || '') + '</td>' +
@@ -5302,16 +5463,16 @@
var releasedAt = v.releasedAt || '-'; var releasedAt = v.releasedAt || '-';
var actions = ''; var actions = '';
if (v.status === 'DRAFT') { if (v.status === 'DRAFT') {
actions += '<button class="btn btn-sm" onclick="releaseVersion(\'' + v.version + '\')">发布</button> '; actions += '<button type="button" class="btn btn-sm" data-admin-confirm data-confirm-message="确认发布版本 ' + v.version + ' 吗?" onclick="releaseVersion(\'' + v.version + '\')">发布</button> ';
} }
if (v.status === 'RELEASED' && !v.isLatest) { if (v.status === 'RELEASED' && !v.isLatest) {
actions += '<button class="btn btn-sm" onclick="setLatestVersion(\'' + v.version + '\')">设为最新</button> '; actions += '<button type="button" class="btn btn-sm" data-admin-confirm data-confirm-message="确认将版本 ' + v.version + ' 设为最新吗?客户端会自动检测更新。" onclick="setLatestVersion(\'' + v.version + '\')">设为最新</button> ';
} }
if (v.status === 'RELEASED') { if (v.status === 'RELEASED') {
actions += '<button class="btn btn-sm" onclick="downloadDigitalHumanVersion(\'' + encodeURIComponent(v.version || '') + '\')">下载</button> '; actions += '<button class="btn btn-sm" onclick="downloadDigitalHumanVersion(\'' + encodeURIComponent(v.version || '') + '\')">下载</button> ';
} }
if (!v.isLatest) { if (!v.isLatest) {
actions += '<button class="btn btn-sm btn-danger" onclick="deleteVersion(\'' + v.version + '\')">删除</button>'; actions += '<button type="button" class="btn btn-sm btn-danger" data-admin-confirm data-confirm-message="确认删除版本 ' + v.version + ' 吗?该操作会删除对应文件,无法恢复。" onclick="deleteVersion(\'' + v.version + '\')">删除</button>';
} }
return '<tr>' + return '<tr>' +
@@ -5601,7 +5762,7 @@
'<button class="btn btn-sm btn-secondary" data-column-move-up="' + c.id + '"' + (siblingIndex <= 0 ? ' disabled' : '') + '>上移</button> ' + '<button class="btn btn-sm btn-secondary" data-column-move-up="' + c.id + '"' + (siblingIndex <= 0 ? ' disabled' : '') + '>上移</button> ' +
'<button class="btn btn-sm btn-secondary" data-column-move-down="' + c.id + '"' + (siblingIndex < 0 || siblingIndex === siblings.length - 1 ? ' disabled' : '') + '>下移</button> '; '<button class="btn btn-sm btn-secondary" data-column-move-down="' + c.id + '"' + (siblingIndex < 0 || siblingIndex === siblings.length - 1 ? ' disabled' : '') + '>下移</button> ';
var parent = allColumnsList.find(function (item) { return String(item.id) === String(c.parent_id || c.parentId || ''); }); var parent = allColumnsList.find(function (item) { return String(item.id) === String(c.parent_id || c.parentId || ''); });
return '<tr><td>' + c.id + '</td><td>' + (c.name || '') + '</td><td>' + (c.column_key || '') + '</td><td>' + ((c.menu_type || '') === 'admin' ? '后台(admin)' : '软件(app)') + '</td><td>' + (parent ? (parent.name || '') : '-') + '</td><td>' + (c.sort_order != null ? c.sort_order : 0) + '</td><td>' + (c.route_path || '') + '</td><td>' + (c.created_at || '') + '</td><td>' + return '<tr><td>' + c.id + '</td><td>' + (c.name || '') + '</td><td>' + (c.column_key || '') + '</td><td>' + ((c.menu_type || '') === 'admin' ? '后台' : '软件') + '</td><td>' + (parent ? (parent.name || '') : '-') + '</td><td>' + (c.sort_order != null ? c.sort_order : 0) + '</td><td>' + (c.route_path || '') + '</td><td>' + (c.created_at || '') + '</td><td>' +
moveButtons + moveButtons +
'<button class="btn btn-sm" data-column-edit="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '&quot;') + '" data-key="' + (c.column_key || '').replace(/"/g, '&quot;') + '" data-route="' + (c.route_path || '').replace(/"/g, '&quot;') + '" data-menu-type="' + (c.menu_type || 'app').replace(/"/g, '&quot;') + '" data-sort-order="' + (c.sort_order != null ? String(c.sort_order) : '0').replace(/"/g, '&quot;') + '" data-parent-id="' + String(c.parent_id || c.parentId || '').replace(/"/g, '&quot;') + '">编辑</button> ' + '<button class="btn btn-sm" data-column-edit="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '&quot;') + '" data-key="' + (c.column_key || '').replace(/"/g, '&quot;') + '" data-route="' + (c.route_path || '').replace(/"/g, '&quot;') + '" data-menu-type="' + (c.menu_type || 'app').replace(/"/g, '&quot;') + '" data-sort-order="' + (c.sort_order != null ? String(c.sort_order) : '0').replace(/"/g, '&quot;') + '" data-parent-id="' + String(c.parent_id || c.parentId || '').replace(/"/g, '&quot;') + '">编辑</button> ' +
'<button class="btn btn-sm btn-danger" data-column-delete="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '&quot;') + '">删除</button></td></tr>'; '<button class="btn btn-sm btn-danger" data-column-delete="' + c.id + '" data-name="' + (c.name || '').replace(/"/g, '&quot;') + '">删除</button></td></tr>';
@@ -5855,6 +6016,8 @@
currentUserRole = item.role || currentUserRole; currentUserRole = item.role || currentUserRole;
currentUserUsername = item.username || currentUserUsername; currentUserUsername = item.username || currentUserUsername;
nameEl.textContent = item.username || '管理员'; nameEl.textContent = item.username || '管理员';
var avatarEl = document.getElementById('adminUserAvatar');
if (avatarEl) avatarEl.textContent = (item.username || '管').charAt(0).toUpperCase();
if (item.role) { if (item.role) {
roleEl.textContent = item.role === 'super_admin' roleEl.textContent = item.role === 'super_admin'
? '超级管理员' ? '超级管理员'
File diff suppressed because it is too large Load Diff
+797 -116
View File
@@ -3,166 +3,847 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="data:,">
<meta name="theme-color" content="#0b1220">
<title>登录 - 数富AI</title> <title>登录 - 数富AI</title>
<style> <style>
* { margin: 0; padding: 0; box-sizing: border-box; } :root {
color-scheme: dark;
--auth-bg: #0b1220;
--auth-surface: #141e31;
--auth-surface-raised: #1a2740;
--auth-border: rgba(148, 163, 184, 0.2);
--auth-border-strong: rgba(148, 163, 184, 0.34);
--auth-text: #f1f5f9;
--auth-text-muted: #b8c4d8;
--auth-text-subtle: #8795ad;
--auth-primary: #818cf8;
--auth-primary-strong: #a5b4fc;
--auth-success: #4ade80;
--auth-danger: #fb7185;
--auth-radius: 18px;
--auth-fast: 160ms;
--auth-base: 220ms;
}
* { box-sizing: border-box; }
html {
min-width: 320px;
min-height: 100%;
background: var(--auth-bg);
scroll-behavior: smooth;
}
body { body {
font-family: "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", sans-serif; min-width: 320px;
background: #d8e4ec;
min-height: 100vh; min-height: 100vh;
margin: 0;
font-family: Inter, "SF Pro Display", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--auth-text);
background:
radial-gradient(circle at 8% 0%, rgba(99, 102, 241, 0.19), transparent 32rem),
radial-gradient(circle at 92% 100%, rgba(34, 197, 94, 0.06), transparent 28rem),
var(--auth-bg);
}
button,
input { font: inherit; }
a { color: inherit; }
.auth-skip-link {
position: fixed;
top: 12px;
left: 12px;
z-index: 20;
padding: 10px 14px;
border-radius: 10px;
background: var(--auth-primary);
color: #0b1220;
font-weight: 700;
text-decoration: none;
transform: translateY(-160%);
transition: transform var(--auth-fast) ease;
}
.auth-skip-link:focus { transform: translateY(0); }
.auth-page {
min-height: 100vh;
display: grid;
grid-template-columns: minmax(420px, 0.9fr) minmax(420px, 1.1fr);
}
.auth-visual {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
min-height: 100vh;
padding: 36px clamp(32px, 5vw, 84px) 34px;
overflow: hidden;
border-right: 1px solid rgba(148, 163, 184, 0.13);
background:
linear-gradient(160deg, rgba(16, 26, 46, 0.92), rgba(8, 15, 29, 0.98)),
radial-gradient(circle at 20% 8%, rgba(129, 140, 248, 0.18), transparent 28rem);
}
.auth-visual::before,
.auth-visual::after {
content: "";
position: absolute;
pointer-events: none;
}
.auth-visual::before {
inset: 0;
opacity: 0.24;
background-image: linear-gradient(rgba(148, 163, 184, 0.08) 1px, transparent 1px), linear-gradient(90deg, rgba(148, 163, 184, 0.08) 1px, transparent 1px);
background-size: 42px 42px;
mask-image: linear-gradient(to bottom, black, transparent 78%);
}
.auth-visual::after {
width: 360px;
height: 360px;
right: -150px;
bottom: -160px;
border: 1px solid rgba(129, 140, 248, 0.18);
border-radius: 50%;
box-shadow: 0 0 0 32px rgba(129, 140, 248, 0.035), 0 0 0 64px rgba(129, 140, 248, 0.025);
}
.auth-visual-content,
.auth-visual-footer { position: relative; z-index: 1; }
.auth-brand {
display: inline-flex;
align-items: center;
gap: 12px;
width: fit-content;
text-decoration: none;
}
.auth-brand-mark {
display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 40px; width: 42px;
height: 42px;
border: 1px solid rgba(165, 180, 252, 0.28);
border-radius: 13px;
background: linear-gradient(135deg, #818cf8, #4f46e5);
color: #eef2ff;
font-size: 18px;
font-weight: 800;
box-shadow: 0 10px 28px rgba(79, 70, 229, 0.34);
} }
.header {
position: absolute; .auth-brand-copy { display: grid; gap: 1px; }
top: 0;
left: 0; .auth-brand-name {
right: 0; color: #f8fafc;
height: 56px; font-size: 16px;
font-weight: 700;
letter-spacing: 0.2px;
}
.auth-brand-sub {
color: var(--auth-text-subtle);
font-size: 11px;
}
.auth-visual-content {
max-width: 520px;
margin: auto 0;
padding: 72px 0 96px;
}
.auth-visual-copy {
padding-top: 72px;
}
.auth-kicker {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--auth-primary-strong);
font-size: 11px;
font-weight: 700;
letter-spacing: 1.8px;
}
.auth-kicker::before {
content: "";
width: 24px;
height: 1px;
background: var(--auth-primary);
}
.auth-visual-title {
max-width: 560px;
margin: 18px 0 18px;
color: #f8fafc;
font-size: clamp(30px, 4vw, 52px);
font-weight: 700;
letter-spacing: -1.8px;
line-height: 1.12;
}
.auth-visual-description {
max-width: 470px;
margin: 0;
color: var(--auth-text-muted);
font-size: 16px;
line-height: 1.75;
}
.auth-feature-list {
display: grid;
gap: 12px;
margin: 34px 0 0;
padding: 0;
list-style: none;
}
.auth-feature-item {
display: flex;
align-items: center;
gap: 12px;
color: #dbe4f1;
}
.auth-feature-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 30px;
width: 30px;
height: 30px;
border: 1px solid rgba(129, 140, 248, 0.25);
border-radius: 9px;
background: rgba(129, 140, 248, 0.11);
color: var(--auth-primary-strong);
}
.auth-feature-icon svg { width: 16px; height: 16px; }
.auth-visual-footer {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 24px; gap: 16px;
background: rgba(255,255,255,0.5); color: #72819a;
font-size: 12px;
} }
.header-title { font-size: 18px; font-weight: 600; color: #333; }
.login-box { .auth-system-status {
background: #fff; display: inline-flex;
border: 1px solid #b8d4e3; align-items: center;
border-radius: 12px; gap: 7px;
box-shadow: 0 4px 12px rgba(0,0,0,0.06); color: #86efac;
padding: 40px;
width: 100%;
max-width: 380px;
} }
.auth-system-status::before {
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--auth-success);
box-shadow: 0 0 0 4px rgba(74, 222, 128, 0.12);
}
.auth-content {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
padding: 40px clamp(24px, 6vw, 96px);
}
.login-card {
width: min(100%, 452px);
padding: clamp(28px, 4vw, 48px);
border: 1px solid var(--auth-border);
border-radius: 22px;
background: linear-gradient(145deg, rgba(20, 30, 49, 0.98), rgba(16, 26, 46, 0.96));
box-shadow: 0 26px 70px -38px rgba(2, 6, 23, 0.95);
}
.login-card-header { margin-bottom: 30px; }
.login-card-eyebrow {
margin: 0 0 8px;
color: var(--auth-text-subtle);
font-size: 12px;
font-weight: 700;
letter-spacing: 1.2px;
}
.login-title { .login-title {
font-size: 24px; margin: 0;
font-weight: 600; color: var(--auth-text);
color: #333; font-size: 30px;
text-align: center; font-weight: 700;
margin-bottom: 30px; letter-spacing: -0.8px;
line-height: 1.2;
} }
.form-group {
margin-bottom: 20px; .login-subtitle {
margin: 10px 0 0;
color: var(--auth-text-muted);
line-height: 1.65;
} }
.form-group { margin-bottom: 20px; }
.form-group label { .form-group label {
display: block; display: block;
font-size: 14px;
color: #555;
margin-bottom: 8px; margin-bottom: 8px;
color: var(--auth-text-muted);
font-size: 13px;
font-weight: 600;
} }
.input-shell { position: relative; }
.input-icon {
position: absolute;
top: 50%;
left: 14px;
display: inline-flex;
color: #8190a8;
pointer-events: none;
transform: translateY(-50%);
}
.input-icon svg { width: 18px; height: 18px; }
.form-group input { .form-group input {
width: 100%; width: 100%;
padding: 12px 14px; min-height: 48px;
font-size: 14px; padding: 12px 52px 12px 44px;
border: 1px solid #b8d4e3; border: 1px solid var(--auth-border);
border-radius: 8px; border-radius: 12px;
outline: none; outline: none;
transition: border-color 0.2s; background: rgba(11, 18, 32, 0.72);
background: #fff; color: var(--auth-text);
font-size: 14px;
color-scheme: dark;
transition: border-color var(--auth-fast) ease, box-shadow var(--auth-fast) ease, background var(--auth-fast) ease;
} }
.form-group input:hover { border-color: var(--auth-border-strong); }
.form-group input:focus { .form-group input:focus {
border-color: #3498db; border-color: var(--auth-primary);
background: rgba(11, 18, 32, 0.92);
box-shadow: 0 0 0 4px rgba(129, 140, 248, 0.15);
} }
.form-group input::placeholder { color: #71809a; }
.password-toggle {
position: absolute;
top: 50%;
right: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border: 0;
border-radius: 9px;
background: transparent;
color: #8190a8;
cursor: pointer;
transform: translateY(-50%);
transition: background var(--auth-fast) ease, color var(--auth-fast) ease;
}
.password-toggle:hover {
background: rgba(129, 140, 248, 0.12);
color: var(--auth-primary-strong);
}
.password-toggle svg { width: 18px; height: 18px; }
.error-msg { .error-msg {
color: #e74c3c; display: flex;
align-items: flex-start;
gap: 9px;
margin: -4px 0 18px;
padding: 11px 12px;
border: 1px solid rgba(251, 113, 133, 0.28);
border-radius: 11px;
background: rgba(251, 113, 133, 0.1);
color: #fda4af;
font-size: 13px; font-size: 13px;
margin-bottom: 12px; line-height: 1.55;
}
.error-msg::before {
content: "!";
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 18px;
width: 18px;
height: 18px;
border: 1px solid currentColor;
border-radius: 50%;
font-size: 11px;
font-weight: 800;
}
.btn-login {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 9px;
width: 100%;
min-height: 48px;
padding: 12px 18px;
border: 1px solid rgba(165, 180, 252, 0.32);
border-radius: 12px;
background: linear-gradient(135deg, #818cf8, #6366f1);
color: #0b1220;
cursor: pointer;
font-size: 15px;
font-weight: 800;
box-shadow: 0 12px 24px -16px rgba(129, 140, 248, 0.95);
transition: transform var(--auth-fast) ease, box-shadow var(--auth-fast) ease, background var(--auth-fast) ease, opacity var(--auth-fast) ease;
}
.btn-login:hover:not(:disabled) {
background: linear-gradient(135deg, #a5b4fc, #818cf8);
box-shadow: 0 16px 28px -15px rgba(129, 140, 248, 0.98);
transform: translateY(-1px);
}
.btn-login:active:not(:disabled) { transform: translateY(1px) scale(0.99); }
.btn-login:disabled { cursor: wait; opacity: 0.7; }
.btn-login-spinner {
display: none;
width: 16px;
height: 16px;
border: 2px solid rgba(11, 18, 32, 0.28);
border-top-color: #0b1220;
border-radius: 50%;
animation: login-spin 0.8s linear infinite;
}
.btn-login.is-loading .btn-login-spinner { display: inline-block; }
@keyframes login-spin { to { transform: rotate(360deg); } }
.login-security-note {
display: flex;
align-items: flex-start;
gap: 9px;
margin: 22px 0 0;
color: var(--auth-text-subtle);
font-size: 12px;
line-height: 1.55;
}
.login-security-note svg {
flex: 0 0 auto;
width: 16px;
height: 16px;
margin-top: 1px;
color: var(--auth-success);
}
.login-card-footer {
margin-top: 34px;
padding-top: 18px;
border-top: 1px solid rgba(148, 163, 184, 0.13);
color: #72819a;
font-size: 12px;
text-align: center; text-align: center;
} }
:focus-visible {
outline: 2px solid var(--auth-primary-strong);
outline-offset: 3px;
}
@media (max-width: 900px) {
.auth-page { display: block; }
.auth-visual {
min-height: auto;
padding: 22px 24px;
border-right: 0;
border-bottom: 1px solid rgba(148, 163, 184, 0.13);
}
.auth-visual-content { display: block; max-width: none; margin: 0; padding: 0; }
.auth-visual-copy { display: none; }
.auth-visual-footer { display: none; }
.auth-content { min-height: calc(100vh - 87px); padding: 32px 24px 44px; }
}
@media (max-width: 520px) {
.auth-visual { padding: 18px 16px; }
.auth-content { padding: 24px 14px 32px; }
.login-card { padding: 26px 20px; border-radius: 18px; }
.login-title { font-size: 27px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
scroll-behavior: auto !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* ===== 登录页莫兰迪亮色主题 ===== */
:root {
color-scheme: light;
--auth-bg: #f2f4f1;
--auth-surface: #ffffff;
--auth-surface-raised: #f8faf8;
--auth-border: #dbe3dd;
--auth-border-strong: #b9c9bd;
--auth-text: #29362f;
--auth-text-muted: #5d6c63;
--auth-text-subtle: #7f8d84;
--auth-primary: #607a6d;
--auth-primary-strong: #456052;
--auth-success: #4e8068;
--auth-danger: #a9545d;
}
html { background: var(--auth-bg); }
body {
color: var(--auth-text);
background:
radial-gradient(circle at 8% 0%, rgba(177, 198, 185, 0.34), transparent 34rem),
radial-gradient(circle at 96% 100%, rgba(213, 190, 178, 0.2), transparent 28rem),
var(--auth-bg);
}
.auth-skip-link {
background: var(--auth-primary);
color: #ffffff;
}
.auth-visual {
border-right-color: #d5ded7;
background:
linear-gradient(160deg, rgba(232, 239, 234, 0.96), rgba(246, 248, 245, 0.98)),
radial-gradient(circle at 20% 8%, rgba(127, 153, 138, 0.16), transparent 28rem);
}
.auth-visual::before {
opacity: 0.32;
background-image: linear-gradient(rgba(96, 122, 109, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(96, 122, 109, 0.1) 1px, transparent 1px);
}
.auth-visual::after {
border-color: rgba(96, 122, 109, 0.22);
box-shadow: 0 0 0 32px rgba(96, 122, 109, 0.055), 0 0 0 64px rgba(96, 122, 109, 0.035);
}
.auth-brand-mark {
border-color: rgba(69, 96, 82, 0.18);
background: linear-gradient(135deg, #829b8b, #607a6d);
color: #ffffff;
box-shadow: 0 10px 24px rgba(96, 122, 109, 0.24);
}
.auth-brand-name,
.auth-visual-title { color: var(--auth-text); }
.auth-brand-sub { color: #76857b; }
.auth-kicker { color: var(--auth-primary-strong); }
.auth-kicker::before { background: var(--auth-primary); }
.auth-visual-description { color: #607069; }
.auth-feature-item { color: #46564d; }
.auth-feature-icon { border-color: #c5d5c9; background: #edf3ee; color: var(--auth-primary-strong); }
.auth-visual-footer { color: #77857d; }
.auth-system-status { color: #3f7258; }
.auth-system-status::before { background: var(--auth-success); box-shadow: 0 0 0 4px rgba(78, 128, 104, 0.13); }
.auth-content {
background: rgba(248, 250, 248, 0.45);
}
.login-card {
border-color: var(--auth-border);
background: linear-gradient(145deg, #ffffff, #f9fbf9);
box-shadow: 0 26px 70px -38px rgba(60, 77, 67, 0.34);
}
.login-card-eyebrow { color: #718078; }
.login-title { color: var(--auth-text); }
.login-subtitle { color: var(--auth-text-muted); }
.form-group label { color: var(--auth-text-muted); }
.input-icon { color: #82938a; }
.form-group input {
background: #f7faf7;
border-color: #cfdad2;
color: var(--auth-text);
color-scheme: light;
}
.form-group input:hover { border-color: #aebfb3; }
.form-group input:focus { background: #ffffff; border-color: #6f8b7b; box-shadow: 0 0 0 4px rgba(111, 139, 123, 0.16); }
.form-group input::placeholder { color: #87958c; }
.password-toggle { color: #82938a; }
.password-toggle:hover { background: #edf4ee; color: var(--auth-primary-strong); }
.error-msg { border-color: #e4c2c5; background: #f8ebeb; color: #91474f; }
.btn-login { .btn-login {
width: 100%; border-color: #7d9988;
padding: 14px; background: linear-gradient(135deg, #718b7c, #607a6d);
font-size: 16px; color: #ffffff;
font-weight: 500; box-shadow: 0 12px 24px -16px rgba(96, 122, 109, 0.82);
color: #fff;
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
} }
.btn-login:hover {
opacity: 0.9; .btn-login:hover:not(:disabled) {
background: linear-gradient(135deg, #829b8b, #6b8577);
color: #ffffff;
box-shadow: 0 16px 28px -15px rgba(96, 122, 109, 0.86);
} }
.btn-login:disabled {
opacity: 0.6; .btn-login-spinner { border-color: rgba(255, 255, 255, 0.35); border-top-color: #ffffff; }
cursor: not-allowed; .login-security-note { color: #718078; }
.login-security-note svg { color: var(--auth-success); }
.login-card-footer { border-top-color: #dce5de; color: #77857d; }
:focus-visible { outline-color: #607a6d; }
@media (max-width: 900px) {
.auth-visual { border-bottom-color: #d5ded7; }
} }
/* ===== 登录页统一蓝白色调 ===== */
:root {
color-scheme: light;
--auth-bg: #f4f7fb;
--auth-surface: #ffffff;
--auth-surface-raised: #f9fbfd;
--auth-border: #d8e3ee;
--auth-border-strong: #b7c9db;
--auth-text: #24384d;
--auth-text-muted: #5b6f83;
--auth-text-subtle: #8293a5;
--auth-primary: #4f78a5;
--auth-primary-strong: #2f5d8b;
--auth-success: #4e806d;
--auth-danger: #b35f6a;
}
html, body { background: var(--auth-bg); }
body {
color: var(--auth-text);
background:
radial-gradient(circle at 8% 0%, rgba(178, 205, 229, 0.32), transparent 34rem),
radial-gradient(circle at 96% 100%, rgba(214, 226, 239, 0.28), transparent 28rem),
var(--auth-bg);
}
.auth-skip-link { background: var(--auth-primary); color: #ffffff; }
.auth-visual {
border-right-color: #d4e0eb;
background:
linear-gradient(160deg, rgba(232, 240, 248, 0.97), rgba(248, 251, 254, 0.99)),
radial-gradient(circle at 20% 8%, rgba(112, 148, 186, 0.16), transparent 28rem);
}
.auth-visual::before { opacity: 0.3; background-image: linear-gradient(rgba(79, 120, 165, 0.1) 1px, transparent 1px), linear-gradient(90deg, rgba(79, 120, 165, 0.1) 1px, transparent 1px); }
.auth-visual::after { border-color: rgba(79, 120, 165, 0.22); box-shadow: 0 0 0 32px rgba(79, 120, 165, 0.055), 0 0 0 64px rgba(79, 120, 165, 0.035); }
.auth-brand-mark { border-color: rgba(47, 93, 139, 0.18); background: linear-gradient(135deg, #7094ba, #4f78a5); color: #ffffff; box-shadow: 0 10px 24px rgba(79, 120, 165, 0.24); }
.auth-brand-name, .auth-visual-title { color: var(--auth-text); }
.auth-brand-sub { color: #77899b; }
.auth-kicker { color: var(--auth-primary-strong); }
.auth-kicker::before { background: var(--auth-primary); }
.auth-visual-description { color: #60748a; }
.auth-feature-item { color: #465d73; }
.auth-feature-icon { border-color: #c2d3e3; background: #edf5fb; color: var(--auth-primary-strong); }
.auth-visual-footer { color: #778b9f; }
.auth-system-status { color: #3d7158; }
.auth-system-status::before { background: var(--auth-success); box-shadow: 0 0 0 4px rgba(78, 128, 104, 0.13); }
.auth-content { background: rgba(249, 251, 253, 0.5); }
.login-card { border-color: var(--auth-border); background: linear-gradient(145deg, #ffffff, #f9fbfd); box-shadow: 0 26px 70px -38px rgba(39, 67, 94, 0.34); }
.login-card-eyebrow { color: #71859a; }
.login-title { color: var(--auth-text); }
.login-subtitle, .form-group label { color: var(--auth-text-muted); }
.input-icon, .password-toggle { color: #8298ad; }
.form-group input { background: #f8fbfd; border-color: #cbd9e6; color: var(--auth-text); color-scheme: light; }
.form-group input:hover { border-color: #9fb7cd; }
.form-group input:focus { background: #ffffff; border-color: #5f85ad; box-shadow: 0 0 0 4px rgba(95, 133, 173, 0.16); }
.form-group input::placeholder { color: #8293a5; }
.password-toggle:hover { background: #edf5fb; color: var(--auth-primary-strong); }
.error-msg { border-color: #e4c2c5; background: #f8ebeb; color: #91474f; }
.btn-login { border-color: #7196ba; background: linear-gradient(135deg, #5f85ad, #4f78a5); color: #ffffff; box-shadow: 0 12px 24px -16px rgba(79, 120, 165, 0.82); }
.btn-login:hover:not(:disabled) { background: linear-gradient(135deg, #7094ba, #5d83ac); color: #ffffff; box-shadow: 0 16px 28px -15px rgba(79, 120, 165, 0.86); }
.btn-login-spinner { border-color: rgba(255,255,255,0.35); border-top-color: #ffffff; }
.login-security-note { color: #71859a; }
.login-security-note svg { color: var(--auth-success); }
.login-card-footer { border-top-color: #dce5ee; color: #778b9f; }
:focus-visible { outline-color: #4f78a5; }
@media (max-width: 900px) { .auth-visual { border-bottom-color: #d4e0eb; } }
</style> </style>
</head> </head>
<body> <body>
<header class="header"> <a class="auth-skip-link" href="#loginMain">跳转到登录表单</a>
<span class="header-title">数富AI</span> <div class="auth-page">
</header> <aside class="auth-visual" aria-label="数富AI产品信息">
<div class="auth-visual-content">
<a class="auth-brand" href="/login" aria-label="数富AI 登录页">
<span class="auth-brand-mark" aria-hidden="true"></span>
<span class="auth-brand-copy">
<span class="auth-brand-name">数富AI</span>
<span class="auth-brand-sub">电商运营管理后台</span>
</span>
</a>
<div class="auth-visual-copy">
<span class="auth-kicker">数富AI · 运营工作台</span>
<h2 class="auth-visual-title">让每一次运营动作,都有清晰的工作流。</h2>
<p class="auth-visual-description">统一管理数据、店铺、任务与版本,让团队在一个可靠的运营工作台中快速协作。</p>
<ul class="auth-feature-list" aria-label="工作台能力">
<li class="auth-feature-item">
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3 4 7v5c0 4.5 3.4 7.7 8 9 4.6-1.3 8-4.5 8-9V7l-8-4Z"></path><path d="m8.5 12 2.2 2.2 4.8-5"></path></svg></span>
<span>权限隔离,操作边界清晰可控</span>
</li>
<li class="auth-feature-item">
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l3 2"></path><circle cx="12" cy="12" r="9"></circle><path d="M3 4v5h5"></path><path d="M3.5 9A9 9 0 0 1 19 5.5"></path></svg></span>
<span>任务状态,进度反馈及时透明</span>
</li>
<li class="auth-feature-item">
<span class="auth-feature-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5"></path><path d="M4 19h16"></path><path d="m7 15 3-4 3 2 4-6"></path><path d="M17 7h3v3"></path></svg></span>
<span>数据工具,支撑日常电商运营</span>
</li>
</ul>
</div>
</div>
<div class="auth-visual-footer">
<span>数富AI · 管理控制台</span>
<span class="auth-system-status">本地服务已就绪</span>
</div>
</aside>
<div class="login-box"> <main class="auth-content" id="loginMain">
<h1 class="login-title">登录</h1> <section class="login-card" aria-labelledby="loginTitle">
{% if error %} <header class="login-card-header">
<p class="error-msg">{{ error }}</p> <p class="login-card-eyebrow">欢迎回来</p>
{% endif %} <h1 class="login-title" id="loginTitle">登录工作台</h1>
<form id="loginForm" method="POST" action="/login"> <p class="login-subtitle">使用管理员账号进入数富AI运营后台。</p>
<div class="form-group"> </header>
<label>用户名</label> <form id="loginForm" method="POST" action="/login" novalidate>
<input type="text" name="username" placeholder="请输入用户名" required autofocus> {% if error %}
</div> <p class="error-msg" id="loginError" role="alert" aria-live="assertive">{{ error }}</p>
<div class="form-group"> {% endif %}
<label>密码</label> <div class="form-group">
<input type="password" name="password" placeholder="请输入密码" required> <label for="loginUsername">用户名</label>
</div> <div class="input-shell">
<button type="submit" class="btn-login" id="btnLogin">登录</button> <span class="input-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="4"></circle><path d="M4 21a8 8 0 0 1 16 0"></path></svg></span>
</form> <input type="text" id="loginUsername" name="username" autocomplete="username" placeholder="请输入用户名" required autofocus>
</div>
</div>
<div class="form-group">
<label for="loginPassword">密码</label>
<div class="input-shell">
<span class="input-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="10" x="5" y="11" rx="2"></rect><path d="M8 11V7a4 4 0 0 1 8 0v4"></path></svg></span>
<input type="password" id="loginPassword" name="password" autocomplete="current-password" placeholder="请输入密码" required>
<button type="button" class="password-toggle" id="togglePassword" aria-label="显示密码" aria-pressed="false" title="显示密码">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z"></path><circle cx="12" cy="12" r="2.5"></circle></svg>
</button>
</div>
</div>
<button type="submit" class="btn-login" id="btnLogin" aria-busy="false">
<span class="btn-login-label">登录</span>
<span class="btn-login-spinner" aria-hidden="true"></span>
</button>
</form>
<p class="login-security-note">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 4 7v5c0 4.5 3.4 7.7 8 9 4.6-1.3 8-4.5 8-9V7l-8-4Z"></path><path d="m8.5 12 2.2 2.2 4.8-5"></path></svg>
<span>请勿在公共设备保存账号凭据。登录状态由本地安全会话管理。</span>
</p>
<footer class="login-card-footer">数富AI · 电商运营管理工作台</footer>
</section>
</main>
</div> </div>
<script> <script>
(function() { (function () {
var params = new URLSearchParams(window.location.search || ''); var form = document.getElementById('loginForm');
if (params.get('logout') === '1' || params.get('switch') === '1') {
return;
}
fetch('/api/auth/check', { credentials: 'same-origin' })
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.logged_in && res.redirect) {
window.location.href = res.redirect;
}
})
.catch(function() {});
})();
document.getElementById('loginForm').onsubmit = function(e) {
e.preventDefault();
var btn = document.getElementById('btnLogin'); var btn = document.getElementById('btnLogin');
btn.disabled = true; var label = btn ? btn.querySelector('.btn-login-label') : null;
btn.textContent = '登录中...'; var password = document.getElementById('loginPassword');
var form = e.target; var togglePassword = document.getElementById('togglePassword');
var fd = new FormData(form); if (!form || !btn) return;
fetch(form.action, {
method: 'POST', if (password && togglePassword) {
body: fd, togglePassword.addEventListener('click', function () {
headers: { 'X-Requested-With': 'XMLHttpRequest' } var visible = password.type === 'password';
}) password.type = visible ? 'text' : 'password';
.then(function(r) { return r.json(); }) togglePassword.setAttribute('aria-pressed', visible ? 'true' : 'false');
.then(function(res) { togglePassword.setAttribute('aria-label', visible ? '隐藏密码' : '显示密码');
if (res.success) { togglePassword.setAttribute('title', visible ? '隐藏密码' : '显示密码');
window.location.href = res.redirect || '/admin'; });
} else { }
var errEl = document.querySelector('.error-msg');
if (!errEl) { function setError(message) {
errEl = document.createElement('p'); var errEl = document.getElementById('loginError') || document.querySelector('.error-msg');
errEl.className = 'error-msg'; if (!errEl) {
form.insertBefore(errEl, form.firstChild); errEl = document.createElement('p');
} errEl.id = 'loginError';
errEl.textContent = res.error || '登录失败'; errEl.className = 'error-msg';
btn.disabled = false; errEl.setAttribute('role', 'alert');
btn.textContent = '登录'; errEl.setAttribute('aria-live', 'assertive');
form.insertBefore(errEl, form.firstChild);
} }
}) errEl.textContent = message || '登录失败';
.catch(function() { }
form.submit();
function setLoading(loading) {
btn.disabled = loading;
btn.classList.toggle('is-loading', loading);
btn.setAttribute('aria-busy', loading ? 'true' : 'false');
if (label) label.textContent = loading ? '登录中...' : '登录';
}
form.addEventListener('submit', function (event) {
event.preventDefault();
if (btn.disabled) return;
setLoading(true);
var data = new FormData(form);
fetch(form.action, {
method: 'POST',
body: data,
headers: { 'X-Requested-With': 'XMLHttpRequest' },
credentials: 'same-origin'
})
.then(function (response) {
return response.json().catch(function () {
return { success: false, error: '登录响应异常,请重试' };
});
})
.then(function (result) {
if (result.success) {
window.location.href = result.redirect || '/admin';
return;
}
setError(result.error || '用户名或密码错误');
setLoading(false);
})
.catch(function () {
// API 不可用时保留原生表单提交作为兜底路径。
form.submit();
});
}); });
}; })();
</script> </script>
</body> </body>
</html> </html>
+174
View File
@@ -0,0 +1,174 @@
# crawler-plugin 性能与资源优化 Plan 总览
> 生成时间:2026-08-29T14:02:50+08:00Asia/Shanghai
> 适用仓库:`D:/todesk/副业/数富AI/crawler-plugin`
> 当前基线:HEAD `3229056`2026-08-29),本计划来源为代码审计;仓库当前不存在 `docs/specs/`,因此本文件先作为现状优化计划总览。
## 1. 计划说明
本计划针对大数据量、多图片、多任务并发场景,覆盖 Java 主后端、Python 插件后端、Vue 前端以及数据库/Redis/RustFS/RocketMQ 资源链路。
每个任务按约 8-15 分钟设计,开发时必须严格执行以下 TDD 顺序:
1. 先编写该任务全部测试;
2. 运行测试确认 RED
3. 实现功能并运行测试确认 GREEN;
4. 执行 lint/format、定向测试和必要的集成验证;
5. 任务独立提交 commit 后,才能开始下一个任务。
涉及 I/O、数据库、HTTP、RustFS、Redis、文件或外部服务的任务,必须包含 mock 单元测试和至少一个真实集成/启动调用验证。每个普通功能任务在对应模块计划中至少列出 8 个语义化测试用例:正常路径 3 个、边界 3 个、异常/错误 2 个。
## 2. 已验证基线
- `backend-java``mvn -q -DskipTests compile` 通过。
- `backend-java``mvn -q test`,54 个测试类、344 项测试,0 失败。
- `frontend-vue``npm run build` 通过;仍有公共 chunk 超过 500KB 的构建警告。
- `backend``python -m unittest discover -s tests -p "test*.py"`13 项测试通过。
- 当前没有生产压测、JFR、数据库 EXPLAIN 和真实 RustFS/Coze 指标,因此计划中的性能收益必须通过后续基准和压测确认。
## 3. 模块与任务范围
| 模块编号 | 模块 | 任务范围 | 任务数 |
|---|---|---:|---:|
| 01 | Similar ASIN 性能与资源优化 | 1-20 | 20 |
| 02 | 店铺数据抓取性能与累计文件优化 | 21-40 | 20 |
| 03 | 采集数据批处理与结果文件优化 | 41-60 | 20 |
| 04 | 共享任务、对象存储、调度与清理优化 | 61-80 | 20 |
| 05 | 前端轮询、缓存、构建与交付验收 | 81-100 | 20 |
模块计划文件将在本总览确认后按模块单独生成,不合并不同模块;每个模块文件将补充详细功能要求、完整测试清单、TDD RED/GREEN 流程和验证命令。
## 4. 全部任务清单
| N | 功能点 | 模块 | 依赖 | 预估耗时 |
|---:|---|---|---|---|
| 1 | 建立 Similar ASIN 性能基线夹具:1000/5000 行、图片开关、chunk 数与 payload 大小采样 | Similar ASIN 性能与资源优化 | 无 | 8-15 分钟 |
| 2 | 将解析载荷改为单一规范行集合,消除 items/groups/allItems 重复数据结构 | Similar ASIN 性能与资源优化 | 1 | 8-15 分钟 |
| 3 | 保留旧 payload 读取兼容逻辑,并验证新旧结构均可恢复全量行 | Similar ASIN 性能与资源优化 | 2 | 8-15 分钟 |
| 4 | 解析接口改为只返回固定数量预览行,完整行仅保存在后端任务载荷 | Similar ASIN 性能与资源优化 | 3 | 8-15 分钟 |
| 5 | 为预览行数量增加配置边界、空文件和超限输入校验 | Similar ASIN 性能与资源优化 | 4 | 8-15 分钟 |
| 6 | 将分组数据改为索引/范围引用,避免 groups 嵌套复制完整行对象 | Similar ASIN 性能与资源优化 | 5 | 8-15 分钟 |
| 7 | 限制单文件大小、最大行数和最大字段长度,防止解析任务无界增长 | Similar ASIN 性能与资源优化 | 6 | 8-15 分钟 |
| 8 | 将 WorkbookFactory 输入解析改为受控读取,并验证超大 Excel 的失败提示 | Similar ASIN 性能与资源优化 | 7 | 8-15 分钟 |
| 9 | 将 chunk 查询从单行分页改为批量 keyset 分页,保持低内存读取 | Similar ASIN 性能与资源优化 | 8 | 8-15 分钟 |
| 10 | 为 chunk 结果建立按 row key 的批量索引,消除跨 chunk 线性扫描 | Similar ASIN 性能与资源优化 | 9 | 8-15 分钟 |
| 11 | 将 Coze 结果合并的重复检测从 O(n²) 改为 HashSet/稳定 row key | Similar ASIN 性能与资源优化 | 10 | 8-15 分钟 |
| 12 | 扩展 Coze 结果缓冲覆盖范围,减少频繁读写完整 chunk payload | Similar ASIN 性能与资源优化 | 11 | 8-15 分钟 |
| 13 | 为 chunk 合并增加单次最大行数与 payload 字节上限 | Similar ASIN 性能与资源优化 | 12 | 8-15 分钟 |
| 14 | 图片 DB cache 改为批量读取缩略图,并只更新实际命中的 last_used_at | Similar ASIN 性能与资源优化 | 13 | 8-15 分钟 |
| 15 | 将图片缓存访问时间更新改为异步批量刷新,减少逐图 UPDATE | Similar ASIN 性能与资源优化 | 14 | 8-15 分钟 |
| 16 | 图片预取改为短预算 best-effort,超时后直接回退 URL | Similar ASIN 性能与资源优化 | 15 | 8-15 分钟 |
| 17 | 优化图片解码采样、像素上限和 JPEG 质量搜索,降低 CPU 与堆峰值 | Similar ASIN 性能与资源优化 | 16 | 8-15 分钟 |
| 18 | 统一图片 spool 生命周期,确保超时、取消和异常路径删除临时文件 | Similar ASIN 性能与资源优化 | 17 | 8-15 分钟 |
| 19 | 将 Coze 请求/响应及 Python 回传日志改为采样、截断和 DEBUG 级别 | Similar ASIN 性能与资源优化 | 18 | 8-15 分钟 |
| 20 | 完成 Similar ASIN 端到端压测、JFR/GC 分析与结果文件兼容回归 | Similar ASIN 性能与资源优化 | 19 | 8-15 分钟 |
| 21 | 建立店铺抓取性能基线:单店铺 1k/5k 行、多国家、图片成功/失败场景 | 店铺数据抓取性能与累计文件优化 | 无 | 8-15 分钟 |
| 22 | 将店铺 Excel 图片缓存替换为有界字节缓存 | 店铺数据抓取性能与累计文件优化 | 21 | 8-15 分钟 |
| 23 | 图片嵌入成功后立即释放外部缩略图字节副本 | 店铺数据抓取性能与累计文件优化 | 22 | 8-15 分钟 |
| 24 | 为店铺图片预取增加任务级数量、字节和超时上限 | 店铺数据抓取性能与累计文件优化 | 23 | 8-15 分钟 |
| 25 | 评估并实现店铺结果 workbook 的 SXSSF 或 spool 化写入路径 | 店铺数据抓取性能与累计文件优化 | 24 | 8-15 分钟 |
| 26 | 为模板 workbook 增加大行数下的样式、图片和工作表兼容测试 | 店铺数据抓取性能与累计文件优化 | 25 | 8-15 分钟 |
| 27 | 将 chunk 接收改为原子插入/幂等 upsert,减少先查后插 | 店铺数据抓取性能与累计文件优化 | 26 | 8-15 分钟 |
| 28 | 以 scope 计数器替代每个 chunk 的 COUNT(*) 完整统计 | 店铺数据抓取性能与累计文件优化 | 27 | 8-15 分钟 |
| 29 | 合并 scope 状态查询与更新,减少单 chunk 数据库往返 | 店铺数据抓取性能与累计文件优化 | 28 | 8-15 分钟 |
| 30 | 为国家结果行建立稳定去重键,替换线性重复扫描 | 店铺数据抓取性能与累计文件优化 | 29 | 8-15 分钟 |
| 31 | 将任务快照改为轻量进度字段,避免每次写入完整结果 JSON | 店铺数据抓取性能与累计文件优化 | 30 | 8-15 分钟 |
| 32 | 将 task entity 本地缓存替换为有容量和过期回收的实现 | 店铺数据抓取性能与累计文件优化 | 31 | 8-15 分钟 |
| 33 | 将店铺源文件 key 映射改为确定路径,取消临时目录递归扫描 | 店铺数据抓取性能与累计文件优化 | 32 | 8-15 分钟 |
| 34 | 将 ownerInstanceId 从 JSON 查询迁移到显式列并补充索引 | 店铺数据抓取性能与累计文件优化 | 33 | 8-15 分钟 |
| 35 | 将每日累计文件改为数据层增量模型,避免每次下载并重写完整 XLSX | 店铺数据抓取性能与累计文件优化 | 34 | 8-15 分钟 |
| 36 | 为每日累计文件引入版本号/CAS,缩短店铺级锁的持有时间 | 店铺数据抓取性能与累计文件优化 | 35 | 8-15 分钟 |
| 37 | 拆分每日累计文件组装与任务结果接收,增加异步文件作业状态 | 店铺数据抓取性能与累计文件优化 | 36 | 8-15 分钟 |
| 38 | 历史列表与进度查询增加分页、字段裁剪和批量任务加载 | 店铺数据抓取性能与累计文件优化 | 37 | 8-15 分钟 |
| 39 | 补充删除、超时、重复回传和累计文件失败的资源清理测试 | 店铺数据抓取性能与累计文件优化 | 38 | 8-15 分钟 |
| 40 | 完成店铺抓取压测并比较内存、CPU、DB QPS、对象存储流量和锁等待 | 店铺数据抓取性能与累计文件优化 | 39 | 8-15 分钟 |
| 41 | 建立 Collect Data 1k/10k 行、多个 chunk 和品牌检测场景基线 | 采集数据批处理与结果文件优化 | 无 | 8-15 分钟 |
| 42 | 限制采集解析的文件大小、最大行数和单 chunk 行数 | 采集数据批处理与结果文件优化 | 41 | 8-15 分钟 |
| 43 | 将采集源文件查找改为确定路径/索引查询 | 采集数据批处理与结果文件优化 | 42 | 8-15 分钟 |
| 44 | 保留原始 chunk payload 的同时,减少逐行 extra JSON 的重复序列化 | 采集数据批处理与结果文件优化 | 43 | 8-15 分钟 |
| 45 | 将 ASIN 去重查询与无效品牌查询统一为批量集合查询 | 采集数据批处理与结果文件优化 | 44 | 8-15 分钟 |
| 46 | 跳过空品牌批次的无效远程品牌检查请求 | 采集数据批处理与结果文件优化 | 45 | 8-15 分钟 |
| 47 | 为品牌检查结果增加任务内短期缓存,避免同品牌重复远程调用 | 采集数据批处理与结果文件优化 | 46 | 8-15 分钟 |
| 48 | 将 invalid ASIN 记录改为批量 INSERT IGNORE/upsert | 采集数据批处理与结果文件优化 | 47 | 8-15 分钟 |
| 49 | 将结果明细从逐行 RustFS 对象改为 chunk 级 payload 存储 | 采集数据批处理与结果文件优化 | 48 | 8-15 分钟 |
| 50 | 为结果明细设计批量 upsert mapper 与幂等唯一键 | 采集数据批处理与结果文件优化 | 49 | 8-15 分钟 |
| 51 | 将 accepted 行的序列化和 hash 计算改为批量处理 | 采集数据批处理与结果文件优化 | 50 | 8-15 分钟 |
| 52 | 生成结果文件时按 chunk 一次读取,取消逐行对象读取 | 采集数据批处理与结果文件优化 | 51 | 8-15 分钟 |
| 53 | 将 rawRows 与 finalRows 的内存生命周期分段,避免同时长期驻留 | 采集数据批处理与结果文件优化 | 52 | 8-15 分钟 |
| 54 | 将 finalRowCount 从每个 chunk COUNT(*) 改为任务内增量计数 | 采集数据批处理与结果文件优化 | 53 | 8-15 分钟 |
| 55 | 将进度统计更新改为节流/合并写,减少高频 task UPDATE | 采集数据批处理与结果文件优化 | 54 | 8-15 分钟 |
| 56 | 为采集结果文件增加流式写入失败后的临时文件清理 | 采集数据批处理与结果文件优化 | 55 | 8-15 分钟 |
| 57 | 为采集结果对象增加数据库删除与物理对象删除的一致性处理 | 采集数据批处理与结果文件优化 | 56 | 8-15 分钟 |
| 58 | 补充外部品牌服务不可用、RustFS 超时和重复 chunk 的降级测试 | 采集数据批处理与结果文件优化 | 57 | 8-15 分钟 |
| 59 | 完成采集模块数据库索引、批量 SQL 和对象存储调用次数验证 | 采集数据批处理与结果文件优化 | 58 | 8-15 分钟 |
| 60 | 完成采集模块 10k 行压测并验收结果完整性、内存和吞吐 | 采集数据批处理与结果文件优化 | 59 | 8-15 分钟 |
| 61 | 建立共享任务链路资源指标基线:线程、连接、队列、GC、Redis、RustFS 和 DB | 共享任务、对象存储、调度与清理优化 | 无 | 8-15 分钟 |
| 62 | 为本地任务实体缓存增加最大条目数、TTL 和定时清理 | 共享任务、对象存储、调度与清理优化 | 61 | 8-15 分钟 |
| 63 | 为前端/后端进度快照增加写入去重和最小更新间隔 | 共享任务、对象存储、调度与清理优化 | 62 | 8-15 分钟 |
| 64 | 将 transient payload 压缩改为直接 gzip 二进制流上传 | 共享任务、对象存储、调度与清理优化 | 63 | 8-15 分钟 |
| 65 | 为 transient payload 读取增加流式解压和解压后字节上限 | 共享任务、对象存储、调度与清理优化 | 64 | 8-15 分钟 |
| 66 | 限制 RustFS 并发读写与重试的总资源预算,防止多任务叠加爆发 | 共享任务、对象存储、调度与清理优化 | 65 | 8-15 分钟 |
| 67 | 复用 RustFS/MinIO 客户端与 HTTP 连接池,减少每次操作创建客户端 | 共享任务、对象存储、调度与清理优化 | 66 | 8-15 分钟 |
| 68 | 将 payload 引用删除改为批量引用检查与异步物理删除 | 共享任务、对象存储、调度与清理优化 | 67 | 8-15 分钟 |
| 69 | 为数据库删除任务补充 transient payload 指针收集和清理队列 | 共享任务、对象存储、调度与清理优化 | 68 | 8-15 分钟 |
| 70 | 将历史清理改为 keyset 分页、小批量和短事务 | 共享任务、对象存储、调度与清理优化 | 69 | 8-15 分钟 |
| 71 | 清理日志改为数量与 sample ID,禁止输出超长任务 ID 列表 | 共享任务、对象存储、调度与清理优化 | 70 | 8-15 分钟 |
| 72 | 为文件作业实现数据库原子 claim,避免重复派发同一 job | 共享任务、对象存储、调度与清理优化 | 71 | 8-15 分钟 |
| 73 | 为本地文件作业队列增加 in-flight 去重和队列背压 | 共享任务、对象存储、调度与清理优化 | 72 | 8-15 分钟 |
| 74 | 隔离调度线程池、文件作业线程池和外部 Coze/图片执行池 | 共享任务、对象存储、调度与清理优化 | 73 | 8-15 分钟 |
| 75 | 为虚拟线程任务增加等待队列上限与拒绝/延迟指标 | 共享任务、对象存储、调度与清理优化 | 74 | 8-15 分钟 |
| 76 | 将 JSON owner 查询迁移到显式列并补充任务/状态复合索引 | 共享任务、对象存储、调度与清理优化 | 75 | 8-15 分钟 |
| 77 | 统一 Coze、品牌检查和紫鸟 HTTP 客户端的连接复用策略 | 共享任务、对象存储、调度与清理优化 | 76 | 8-15 分钟 |
| 78 | 为所有外部调用增加耗时、重试、失败率和 payload 字节指标 | 共享任务、对象存储、调度与清理优化 | 77 | 8-15 分钟 |
| 79 | 为对象存储、数据库和队列增加故障注入测试 | 共享任务、对象存储、调度与清理优化 | 78 | 8-15 分钟 |
| 80 | 补充 JVM 堆、直接内存、临时磁盘和连接池容量配置说明 | 共享任务、对象存储、调度与清理优化 | 79 | 8-15 分钟 |
| 81 | 建立前端任务轮询请求量、响应体大小和页面内存基线 | 前端轮询、缓存、构建与交付验收 | 无 | 8-15 分钟 |
| 82 | 为进度响应 Map 增加 TTL 清理与最大条目数 | 前端轮询、缓存、构建与交付验收 | 81 | 8-15 分钟 |
| 83 | 统一不同页面的轮询去重、in-flight 合并和终态清理 | 前端轮询、缓存、构建与交付验收 | 82 | 8-15 分钟 |
| 84 | 优化店铺抓取队列状态合并,消除 historyItems 的线性重复查找 | 前端轮询、缓存、构建与交付验收 | 83 | 8-15 分钟 |
| 85 | 优化 Similar ASIN 轮询与文件生成等待,避免重复 force 请求 | 前端轮询、缓存、构建与交付验收 | 84 | 8-15 分钟 |
| 86 | 将隐藏页面轮询间隔、前台恢复和退避策略统一配置化 | 前端轮询、缓存、构建与交付验收 | 85 | 8-15 分钟 |
| 87 | 限制 localStorage 中任务、快照和队列数据的最大数量/字节数 | 前端轮询、缓存、构建与交付验收 | 86 | 8-15 分钟 |
| 88 | 解析结果前端只接收预览数据,避免大 payload 进入响应式对象 | 前端轮询、缓存、构建与交付验收 | 87 | 8-15 分钟 |
| 89 | 清理页面卸载时的所有 timer、请求和临时 URL | 前端轮询、缓存、构建与交付验收 | 88 | 8-15 分钟 |
| 90 | 为进度接口增加断网、超时、服务恢复和重复响应测试 | 前端轮询、缓存、构建与交付验收 | 89 | 8-15 分钟 |
| 91 | 按页面拆分 Element Plus 与公共业务 chunk | 前端轮询、缓存、构建与交付验收 | 90 | 8-15 分钟 |
| 92 | 配置 Vite manualChunks 并比较各页面首屏传输大小 | 前端轮询、缓存、构建与交付验收 | 91 | 8-15 分钟 |
| 93 | 补充 Similar ASIN、店铺抓取和采集数据页面的 E2E 核心路径 | 前端轮询、缓存、构建与交付验收 | 92 | 8-15 分钟 |
| 94 | 补充移动端与桌面端响应式页面验收截图 | 前端轮询、缓存、构建与交付验收 | 93 | 8-15 分钟 |
| 95 | 补充深色主题、错误提示、重试和终态刷新验收 | 前端轮询、缓存、构建与交付验收 | 94 | 8-15 分钟 |
| 96 | 建立 Java/Python/Vue 三端统一的 API 字段兼容检查 | 前端轮询、缓存、构建与交付验收 | 95 | 8-15 分钟 |
| 97 | 执行 Java 全量测试、Python unittest、Vue 类型检查与构建 | 前端轮询、缓存、构建与交付验收 | 96 | 8-15 分钟 |
| 98 | 执行真实启动、健康检查、核心请求和外部依赖调用验证 | 前端轮询、缓存、构建与交付验收 | 97 | 8-15 分钟 |
| 99 | 执行全链路压测并记录 CPU、内存、GC、DB、Redis、RustFS、网络结果 | 前端轮询、缓存、构建与交付验收 | 98 | 8-15 分钟 |
| 100 | 完成发布前回滚演练、git commit 对应关系检查和交付清单 | 前端轮询、缓存、构建与交付验收 | 99 | 8-15 分钟 |
## 5. 收尾硬性 Gate
以下验证不是可选项,任一失败都不能视为计划完成:
1. 使用项目实际启动命令启动 Java 服务,并通过健康检查端点。
2. 启动 Python 后端并确认核心管理入口可访问。
3. 访问 Similar ASIN、店铺数据抓取和采集数据页面,确认关键元素存在。
4. 使用真实最小文件完成一次解析、任务创建、任务执行和结果下载。
5. 使用多样化输入验证关键业务逻辑不是固定模板或纯回显。
6. 验证数据库、Redis、RustFS、RocketMQ 和外部 HTTP 服务确实发生调用。
7. 验证非法输入、依赖不可用、超时、断网和重复回传有明确错误或降级行为。
8. 验证任务取消、页面关闭、服务重启和 owner 路由后不会残留锁、线程、timer 或临时文件。
9. Java 全量测试全绿。
10. Python 测试全绿。
11. Vue 类型检查和生产构建通过。
12. Java、Python 和前端 lint/format 检查零错误。
13. Playwright 完成桌面端页面可访问和关键交互路径验证。
14. Playwright 验证断网、恢复、错误提示和重试行为。
15. 页面主题切换行为符合预期(如项目页面支持主题)。
16. 保存 Desktop 1280x800 与 Mobile 375x812 响应式验收截图。
17. 执行 Java、Python、Vue 三端接口字段一致性检查。
18. 执行 `git log` 检查 1-100 每个任务均有对应 commit。
19. 最后执行 `python check_progress.py`,仅当输出 `all tasks done` 且退出码为 0 时才宣布计划完成。
## 6. 当前阶段
当前已生成本 `00-plan-overview.md`、5 个模块详细 plan 文件及根目录 `progress.json`;各任务状态全部为 `pending`。详细 plan 已按模块拆分,完成全部任务后执行收尾 Gate 并进入 Step 3。
总任务数:100
+701
View File
@@ -0,0 +1,701 @@
# 01 Similar ASIN 性能与资源优化
> 对应总览任务:1-20
> 计划来源:`docs/plans/00-plan-overview.md`;仓库当前没有 `docs/specs/`,本模块计划根据现有代码审计结果生成。
## 模块目标
在保持 Coze 回传、结果文件格式和旧任务兼容性的前提下,降低重复 JSON、chunk 读写、图片下载/解码和结果组装的内存、CPU、数据库及网络成本。
## 模块级执行规则
- 开发阶段单线程串行执行,不并行实现多个任务;完成一个任务的测试、实现、验证和 commit 后才能进入下一个任务。
- 每个任务严格按“先写全部测试 → 运行确认 RED → 实现 → 运行确认 GREEN → lint/format → commit”执行。
- 每个普通任务至少包含 8 个语义化测试;涉及 I/O、数据库、HTTP、Redis、RustFS、文件或 UI 时,测试必须同时覆盖 mock 依赖和真实集成/启动调用。
- 禁止纯 echo、硬编码成功、空实现、跳过真实依赖调用或仅以 import 成功作为验收。
## 任务 1:建立 Similar ASIN 性能基线夹具:1000/5000 行、图片开关、chunk 数与 payload 大小采样
**所属模块**Similar ASIN 性能与资源优化
**依赖**:无
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立 Similar ASIN 性能基线夹具:1000/5000 行、图片开关、chunk 数与 payload 大小采样”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_001_payload_chunk_image_normal_default_path` — 使用正常输入验证“建立 Similar ASIN 性能基线夹具:1000/5000 行、图片开关、chunk 数与 payload 大小采样”的默认成功路径和主输出。
2. `test_task_001_payload_chunk_image_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_001_payload_chunk_image_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_001_payload_chunk_image_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_001_payload_chunk_image_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_001_payload_chunk_image_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_001_payload_chunk_image_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_001_payload_chunk_image_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 2:将解析载荷改为单一规范行集合,消除 items/groups/allItems 重复数据结构
**所属模块**Similar ASIN 性能与资源优化
**依赖**1
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将解析载荷改为单一规范行集合,消除 items/groups/allItems 重复数据结构”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_002_parsed_payload_normal_default_path` — 使用正常输入验证“将解析载荷改为单一规范行集合,消除 items/groups/allItems 重复数据结构”的默认成功路径和主输出。
2. `test_task_002_parsed_payload_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_002_parsed_payload_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_002_parsed_payload_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_002_parsed_payload_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_002_parsed_payload_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_002_parsed_payload_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_002_parsed_payload_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 3:保留旧 payload 读取兼容逻辑,并验证新旧结构均可恢复全量行
**所属模块**Similar ASIN 性能与资源优化
**依赖**2
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“保留旧 payload 读取兼容逻辑,并验证新旧结构均可恢复全量行”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_003_payload_normal_default_path` — 使用正常输入验证“保留旧 payload 读取兼容逻辑,并验证新旧结构均可恢复全量行”的默认成功路径和主输出。
2. `test_task_003_payload_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_003_payload_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_003_payload_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_003_payload_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_003_payload_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_003_payload_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_003_payload_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 4:解析接口改为只返回固定数量预览行,完整行仅保存在后端任务载荷
**所属模块**Similar ASIN 性能与资源优化
**依赖**3
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“解析接口改为只返回固定数量预览行,完整行仅保存在后端任务载荷”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_004_preview_normal_default_path` — 使用正常输入验证“解析接口改为只返回固定数量预览行,完整行仅保存在后端任务载荷”的默认成功路径和主输出。
2. `test_task_004_preview_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_004_preview_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_004_preview_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_004_preview_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_004_preview_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_004_preview_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_004_preview_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 5:为预览行数量增加配置边界、空文件和超限输入校验
**所属模块**Similar ASIN 性能与资源优化
**依赖**4
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为预览行数量增加配置边界、空文件和超限输入校验”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_005_preview_row_count_normal_default_path` — 使用正常输入验证“为预览行数量增加配置边界、空文件和超限输入校验”的默认成功路径和主输出。
2. `test_task_005_preview_row_count_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_005_preview_row_count_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_005_preview_row_count_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_005_preview_row_count_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_005_preview_row_count_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_005_preview_row_count_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_005_preview_row_count_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 6:将分组数据改为索引/范围引用,避免 groups 嵌套复制完整行对象
**所属模块**Similar ASIN 性能与资源优化
**依赖**5
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将分组数据改为索引/范围引用,避免 groups 嵌套复制完整行对象”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_006_group_normal_default_path` — 使用正常输入验证“将分组数据改为索引/范围引用,避免 groups 嵌套复制完整行对象”的默认成功路径和主输出。
2. `test_task_006_group_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_006_group_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_006_group_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_006_group_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_006_group_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_006_group_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_006_group_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 7:限制单文件大小、最大行数和最大字段长度,防止解析任务无界增长
**所属模块**Similar ASIN 性能与资源优化
**依赖**6
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“限制单文件大小、最大行数和最大字段长度,防止解析任务无界增长”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_007_file_size_row_count_normal_default_path` — 使用正常输入验证“限制单文件大小、最大行数和最大字段长度,防止解析任务无界增长”的默认成功路径和主输出。
2. `test_task_007_file_size_row_count_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_007_file_size_row_count_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_007_file_size_row_count_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_007_file_size_row_count_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_007_file_size_row_count_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_007_file_size_row_count_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_007_file_size_row_count_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 8:将 WorkbookFactory 输入解析改为受控读取,并验证超大 Excel 的失败提示
**所属模块**Similar ASIN 性能与资源优化
**依赖**7
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 WorkbookFactory 输入解析改为受控读取,并验证超大 Excel 的失败提示”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_008_workbook_excel_normal_default_path` — 使用正常输入验证“将 WorkbookFactory 输入解析改为受控读取,并验证超大 Excel 的失败提示”的默认成功路径和主输出。
2. `test_task_008_workbook_excel_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_008_workbook_excel_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_008_workbook_excel_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_008_workbook_excel_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_008_workbook_excel_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_008_workbook_excel_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_008_workbook_excel_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 9:将 chunk 查询从单行分页改为批量 keyset 分页,保持低内存读取
**所属模块**Similar ASIN 性能与资源优化
**依赖**8
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 chunk 查询从单行分页改为批量 keyset 分页,保持低内存读取”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_009_chunk_normal_default_path` — 使用正常输入验证“将 chunk 查询从单行分页改为批量 keyset 分页,保持低内存读取”的默认成功路径和主输出。
2. `test_task_009_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_009_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_009_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_009_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_009_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_009_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_009_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 10:为 chunk 结果建立按 row key 的批量索引,消除跨 chunk 线性扫描
**所属模块**Similar ASIN 性能与资源优化
**依赖**9
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为 chunk 结果建立按 row key 的批量索引,消除跨 chunk 线性扫描”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_010_chunk_row_key_normal_default_path` — 使用正常输入验证“为 chunk 结果建立按 row key 的批量索引,消除跨 chunk 线性扫描”的默认成功路径和主输出。
2. `test_task_010_chunk_row_key_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_010_chunk_row_key_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_010_chunk_row_key_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_010_chunk_row_key_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_010_chunk_row_key_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_010_chunk_row_key_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_010_chunk_row_key_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 11:将 Coze 结果合并的重复检测从 O(n²) 改为 HashSet/稳定 row key
**所属模块**Similar ASIN 性能与资源优化
**依赖**10
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 Coze 结果合并的重复检测从 O(n²) 改为 HashSet/稳定 row key”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_011_merge_row_key_normal_default_path` — 使用正常输入验证“将 Coze 结果合并的重复检测从 O(n²) 改为 HashSet/稳定 row key”的默认成功路径和主输出。
2. `test_task_011_merge_row_key_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_011_merge_row_key_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_011_merge_row_key_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_011_merge_row_key_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_011_merge_row_key_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_011_merge_row_key_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_011_merge_row_key_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 12:扩展 Coze 结果缓冲覆盖范围,减少频繁读写完整 chunk payload
**所属模块**Similar ASIN 性能与资源优化
**依赖**11
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“扩展 Coze 结果缓冲覆盖范围,减少频繁读写完整 chunk payload”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_012_payload_chunk_normal_default_path` — 使用正常输入验证“扩展 Coze 结果缓冲覆盖范围,减少频繁读写完整 chunk payload”的默认成功路径和主输出。
2. `test_task_012_payload_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_012_payload_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_012_payload_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_012_payload_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_012_payload_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_012_payload_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_012_payload_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 13:为 chunk 合并增加单次最大行数与 payload 字节上限
**所属模块**Similar ASIN 性能与资源优化
**依赖**12
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为 chunk 合并增加单次最大行数与 payload 字节上限”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_013_payload_row_count_chunk_normal_default_path` — 使用正常输入验证“为 chunk 合并增加单次最大行数与 payload 字节上限”的默认成功路径和主输出。
2. `test_task_013_payload_row_count_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_013_payload_row_count_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_013_payload_row_count_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_013_payload_row_count_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_013_payload_row_count_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_013_payload_row_count_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_013_payload_row_count_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 14:图片 DB cache 改为批量读取缩略图,并只更新实际命中的 last_used_at
**所属模块**Similar ASIN 性能与资源优化
**依赖**13
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“图片 DB cache 改为批量读取缩略图,并只更新实际命中的 last_used_at”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_014_image_normal_default_path` — 使用正常输入验证“图片 DB cache 改为批量读取缩略图,并只更新实际命中的 last_used_at”的默认成功路径和主输出。
2. `test_task_014_image_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_014_image_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_014_image_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_014_image_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_014_image_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_014_image_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_014_image_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 15:将图片缓存访问时间更新改为异步批量刷新,减少逐图 UPDATE
**所属模块**Similar ASIN 性能与资源优化
**依赖**14
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将图片缓存访问时间更新改为异步批量刷新,减少逐图 UPDATE”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_015_image_cache_normal_default_path` — 使用正常输入验证“将图片缓存访问时间更新改为异步批量刷新,减少逐图 UPDATE”的默认成功路径和主输出。
2. `test_task_015_image_cache_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_015_image_cache_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_015_image_cache_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_015_image_cache_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_015_image_cache_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_015_image_cache_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_015_image_cache_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 16:图片预取改为短预算 best-effort,超时后直接回退 URL
**所属模块**Similar ASIN 性能与资源优化
**依赖**15
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“图片预取改为短预算 best-effort,超时后直接回退 URL”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_016_image_prefetch_normal_default_path` — 使用正常输入验证“图片预取改为短预算 best-effort,超时后直接回退 URL”的默认成功路径和主输出。
2. `test_task_016_image_prefetch_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_016_image_prefetch_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_016_image_prefetch_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_016_image_prefetch_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_016_image_prefetch_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_016_image_prefetch_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_016_image_prefetch_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 17:优化图片解码采样、像素上限和 JPEG 质量搜索,降低 CPU 与堆峰值
**所属模块**Similar ASIN 性能与资源优化
**依赖**16
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“优化图片解码采样、像素上限和 JPEG 质量搜索,降低 CPU 与堆峰值”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_017_image_decode_quality_normal_default_path` — 使用正常输入验证“优化图片解码采样、像素上限和 JPEG 质量搜索,降低 CPU 与堆峰值”的默认成功路径和主输出。
2. `test_task_017_image_decode_quality_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_017_image_decode_quality_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_017_image_decode_quality_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_017_image_decode_quality_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_017_image_decode_quality_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_017_image_decode_quality_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_017_image_decode_quality_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 18:统一图片 spool 生命周期,确保超时、取消和异常路径删除临时文件
**所属模块**Similar ASIN 性能与资源优化
**依赖**17
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“统一图片 spool 生命周期,确保超时、取消和异常路径删除临时文件”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_018_image_normal_default_path` — 使用正常输入验证“统一图片 spool 生命周期,确保超时、取消和异常路径删除临时文件”的默认成功路径和主输出。
2. `test_task_018_image_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_018_image_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_018_image_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_018_image_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_018_image_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_018_image_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_018_image_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 19:将 Coze 请求/响应及 Python 回传日志改为采样、截断和 DEBUG 级别
**所属模块**Similar ASIN 性能与资源优化
**依赖**18
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 Coze 请求/响应及 Python 回传日志改为采样、截断和 DEBUG 级别”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_019_logging_normal_default_path` — 使用正常输入验证“将 Coze 请求/响应及 Python 回传日志改为采样、截断和 DEBUG 级别”的默认成功路径和主输出。
2. `test_task_019_logging_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_019_logging_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_019_logging_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_019_logging_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_019_logging_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_019_logging_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_019_logging_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 20:完成 Similar ASIN 端到端压测、JFR/GC 分析与结果文件兼容回归
**所属模块**Similar ASIN 性能与资源优化
**依赖**19
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“完成 Similar ASIN 端到端压测、JFR/GC 分析与结果文件兼容回归”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。
### 测试用例(必须先写,确认 RED)
1. `test_task_020_asin_normal_default_path` — 使用正常输入验证“完成 Similar ASIN 端到端压测、JFR/GC 分析与结果文件兼容回归”的默认成功路径和主输出。
2. `test_task_020_asin_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_020_asin_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_020_asin_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_020_asin_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_020_asin_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_020_asin_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_020_asin_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 similarasin 服务、Coze 客户端、图片嵌入器、结果 workbook 组装链路。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 模块完成 Gate
- 20 个任务全部有对应 commit,任务状态均更新为 `done` 前不得开始下一个模块。
- 运行模块定向测试、项目全量测试和 lint/format;真实依赖调用、异常降级和资源释放均有记录。
- 运行模块对应的性能基准,记录吞吐、P95/P99 延迟、峰值堆、GC、CPU、DB QPS、Redis/RustFS QPS 和临时磁盘。
+701
View File
@@ -0,0 +1,701 @@
# 02 店铺数据抓取性能与累计文件优化
> 对应总览任务:21-40
> 计划来源:`docs/plans/00-plan-overview.md`;仓库当前没有 `docs/specs/`,本模块计划根据现有代码审计结果生成。
## 模块目标
降低店铺抓取 chunk 回传、图片嵌入和每日累计文件更新的峰值内存、锁等待、全量重写和对象存储流量,同时保持现有店铺/国家结果语义。
## 模块级执行规则
- 开发阶段单线程串行执行,不并行实现多个任务;完成一个任务的测试、实现、验证和 commit 后才能进入下一个任务。
- 每个任务严格按“先写全部测试 → 运行确认 RED → 实现 → 运行确认 GREEN → lint/format → commit”执行。
- 每个普通任务至少包含 8 个语义化测试;涉及 I/O、数据库、HTTP、Redis、RustFS、文件或 UI 时,测试必须同时覆盖 mock 依赖和真实集成/启动调用。
- 禁止纯 echo、硬编码成功、空实现、跳过真实依赖调用或仅以 import 成功作为验收。
## 任务 21:建立店铺抓取性能基线:单店铺 1k/5k 行、多国家、图片成功/失败场景
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**:无
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立店铺抓取性能基线:单店铺 1k/5k 行、多国家、图片成功/失败场景”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_021_image_normal_default_path` — 使用正常输入验证“建立店铺抓取性能基线:单店铺 1k/5k 行、多国家、图片成功/失败场景”的默认成功路径和主输出。
2. `test_task_021_image_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_021_image_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_021_image_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_021_image_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_021_image_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_021_image_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_021_image_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 22:将店铺 Excel 图片缓存替换为有界字节缓存
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**21
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将店铺 Excel 图片缓存替换为有界字节缓存”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_022_image_cache_excel_normal_default_path` — 使用正常输入验证“将店铺 Excel 图片缓存替换为有界字节缓存”的默认成功路径和主输出。
2. `test_task_022_image_cache_excel_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_022_image_cache_excel_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_022_image_cache_excel_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_022_image_cache_excel_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_022_image_cache_excel_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_022_image_cache_excel_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_022_image_cache_excel_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 23:图片嵌入成功后立即释放外部缩略图字节副本
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**22
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“图片嵌入成功后立即释放外部缩略图字节副本”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_023_image_normal_default_path` — 使用正常输入验证“图片嵌入成功后立即释放外部缩略图字节副本”的默认成功路径和主输出。
2. `test_task_023_image_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_023_image_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_023_image_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_023_image_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_023_image_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_023_image_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_023_image_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 24:为店铺图片预取增加任务级数量、字节和超时上限
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**23
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为店铺图片预取增加任务级数量、字节和超时上限”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_024_image_prefetch_normal_default_path` — 使用正常输入验证“为店铺图片预取增加任务级数量、字节和超时上限”的默认成功路径和主输出。
2. `test_task_024_image_prefetch_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_024_image_prefetch_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_024_image_prefetch_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_024_image_prefetch_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_024_image_prefetch_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_024_image_prefetch_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_024_image_prefetch_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 25:评估并实现店铺结果 workbook 的 SXSSF 或 spool 化写入路径
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**24
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“评估并实现店铺结果 workbook 的 SXSSF 或 spool 化写入路径”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_025_workbook_normal_default_path` — 使用正常输入验证“评估并实现店铺结果 workbook 的 SXSSF 或 spool 化写入路径”的默认成功路径和主输出。
2. `test_task_025_workbook_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_025_workbook_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_025_workbook_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_025_workbook_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_025_workbook_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_025_workbook_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_025_workbook_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 26:为模板 workbook 增加大行数下的样式、图片和工作表兼容测试
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**25
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为模板 workbook 增加大行数下的样式、图片和工作表兼容测试”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_026_row_count_image_workbook_normal_default_path` — 使用正常输入验证“为模板 workbook 增加大行数下的样式、图片和工作表兼容测试”的默认成功路径和主输出。
2. `test_task_026_row_count_image_workbook_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_026_row_count_image_workbook_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_026_row_count_image_workbook_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_026_row_count_image_workbook_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_026_row_count_image_workbook_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_026_row_count_image_workbook_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_026_row_count_image_workbook_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 27:将 chunk 接收改为原子插入/幂等 upsert,减少先查后插
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**26
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 chunk 接收改为原子插入/幂等 upsert,减少先查后插”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_027_chunk_normal_default_path` — 使用正常输入验证“将 chunk 接收改为原子插入/幂等 upsert,减少先查后插”的默认成功路径和主输出。
2. `test_task_027_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_027_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_027_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_027_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_027_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_027_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_027_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 28:以 scope 计数器替代每个 chunk 的 COUNT(*) 完整统计
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**27
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“以 scope 计数器替代每个 chunk 的 COUNT(*) 完整统计”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_028_chunk_normal_default_path` — 使用正常输入验证“以 scope 计数器替代每个 chunk 的 COUNT(*) 完整统计”的默认成功路径和主输出。
2. `test_task_028_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_028_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_028_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_028_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_028_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_028_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_028_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 29:合并 scope 状态查询与更新,减少单 chunk 数据库往返
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**28
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“合并 scope 状态查询与更新,减少单 chunk 数据库往返”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_029_chunk_merge_normal_default_path` — 使用正常输入验证“合并 scope 状态查询与更新,减少单 chunk 数据库往返”的默认成功路径和主输出。
2. `test_task_029_chunk_merge_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_029_chunk_merge_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_029_chunk_merge_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_029_chunk_merge_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_029_chunk_merge_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_029_chunk_merge_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_029_chunk_merge_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 30:为国家结果行建立稳定去重键,替换线性重复扫描
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**29
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为国家结果行建立稳定去重键,替换线性重复扫描”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_030_task_normal_default_path` — 使用正常输入验证“为国家结果行建立稳定去重键,替换线性重复扫描”的默认成功路径和主输出。
2. `test_task_030_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_030_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_030_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_030_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_030_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_030_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_030_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 31:将任务快照改为轻量进度字段,避免每次写入完整结果 JSON
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**30
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将任务快照改为轻量进度字段,避免每次写入完整结果 JSON”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_031_snapshot_progress_normal_default_path` — 使用正常输入验证“将任务快照改为轻量进度字段,避免每次写入完整结果 JSON”的默认成功路径和主输出。
2. `test_task_031_snapshot_progress_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_031_snapshot_progress_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_031_snapshot_progress_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_031_snapshot_progress_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_031_snapshot_progress_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_031_snapshot_progress_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_031_snapshot_progress_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 32:将 task entity 本地缓存替换为有容量和过期回收的实现
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**31
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 task entity 本地缓存替换为有容量和过期回收的实现”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_032_cache_normal_default_path` — 使用正常输入验证“将 task entity 本地缓存替换为有容量和过期回收的实现”的默认成功路径和主输出。
2. `test_task_032_cache_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_032_cache_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_032_cache_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_032_cache_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_032_cache_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_032_cache_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_032_cache_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 33:将店铺源文件 key 映射改为确定路径,取消临时目录递归扫描
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**32
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将店铺源文件 key 映射改为确定路径,取消临时目录递归扫描”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_033_task_normal_default_path` — 使用正常输入验证“将店铺源文件 key 映射改为确定路径,取消临时目录递归扫描”的默认成功路径和主输出。
2. `test_task_033_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_033_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_033_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_033_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_033_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_033_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_033_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 34:将 ownerInstanceId 从 JSON 查询迁移到显式列并补充索引
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**33
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 ownerInstanceId 从 JSON 查询迁移到显式列并补充索引”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_034_owner_normal_default_path` — 使用正常输入验证“将 ownerInstanceId 从 JSON 查询迁移到显式列并补充索引”的默认成功路径和主输出。
2. `test_task_034_owner_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_034_owner_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_034_owner_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_034_owner_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_034_owner_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_034_owner_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_034_owner_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 35:将每日累计文件改为数据层增量模型,避免每次下载并重写完整 XLSX
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**34
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将每日累计文件改为数据层增量模型,避免每次下载并重写完整 XLSX”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_035_daily_file_normal_default_path` — 使用正常输入验证“将每日累计文件改为数据层增量模型,避免每次下载并重写完整 XLSX”的默认成功路径和主输出。
2. `test_task_035_daily_file_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_035_daily_file_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_035_daily_file_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_035_daily_file_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_035_daily_file_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_035_daily_file_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_035_daily_file_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 36:为每日累计文件引入版本号/CAS,缩短店铺级锁的持有时间
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**35
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为每日累计文件引入版本号/CAS,缩短店铺级锁的持有时间”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_036_daily_file_lock_normal_default_path` — 使用正常输入验证“为每日累计文件引入版本号/CAS,缩短店铺级锁的持有时间”的默认成功路径和主输出。
2. `test_task_036_daily_file_lock_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_036_daily_file_lock_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_036_daily_file_lock_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_036_daily_file_lock_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_036_daily_file_lock_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_036_daily_file_lock_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_036_daily_file_lock_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 37:拆分每日累计文件组装与任务结果接收,增加异步文件作业状态
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**36
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“拆分每日累计文件组装与任务结果接收,增加异步文件作业状态”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_037_daily_file_job_normal_default_path` — 使用正常输入验证“拆分每日累计文件组装与任务结果接收,增加异步文件作业状态”的默认成功路径和主输出。
2. `test_task_037_daily_file_job_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_037_daily_file_job_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_037_daily_file_job_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_037_daily_file_job_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_037_daily_file_job_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_037_daily_file_job_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_037_daily_file_job_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 38:历史列表与进度查询增加分页、字段裁剪和批量任务加载
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**37
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“历史列表与进度查询增加分页、字段裁剪和批量任务加载”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_038_progress_normal_default_path` — 使用正常输入验证“历史列表与进度查询增加分页、字段裁剪和批量任务加载”的默认成功路径和主输出。
2. `test_task_038_progress_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_038_progress_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_038_progress_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_038_progress_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_038_progress_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_038_progress_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_038_progress_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 39:补充删除、超时、重复回传和累计文件失败的资源清理测试
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**38
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充删除、超时、重复回传和累计文件失败的资源清理测试”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_039_daily_file_cleanup_normal_default_path` — 使用正常输入验证“补充删除、超时、重复回传和累计文件失败的资源清理测试”的默认成功路径和主输出。
2. `test_task_039_daily_file_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_039_daily_file_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_039_daily_file_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_039_daily_file_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_039_daily_file_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_039_daily_file_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_039_daily_file_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 40:完成店铺抓取压测并比较内存、CPU、DB QPS、对象存储流量和锁等待
**所属模块**:店铺数据抓取性能与累计文件优化
**依赖**39
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“完成店铺抓取压测并比较内存、CPU、DB QPS、对象存储流量和锁等待”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。
### 测试用例(必须先写,确认 RED)
1. `test_task_040_lock_object_storage_normal_default_path` — 使用正常输入验证“完成店铺抓取压测并比较内存、CPU、DB QPS、对象存储流量和锁等待”的默认成功路径和主输出。
2. `test_task_040_lock_object_storage_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_040_lock_object_storage_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_040_lock_object_storage_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_040_lock_object_storage_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_040_lock_object_storage_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_040_lock_object_storage_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_040_lock_object_storage_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 shopdatacrawl 服务、每日累计文件、店铺图片嵌入、任务快照和 owner 路由。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 模块完成 Gate
- 20 个任务全部有对应 commit,任务状态均更新为 `done` 前不得开始下一个模块。
- 运行模块定向测试、项目全量测试和 lint/format;真实依赖调用、异常降级和资源释放均有记录。
- 运行模块对应的性能基准,记录吞吐、P95/P99 延迟、峰值堆、GC、CPU、DB QPS、Redis/RustFS QPS 和临时磁盘。
+701
View File
@@ -0,0 +1,701 @@
# 03 采集数据批处理与结果文件优化
> 对应总览任务:41-60
> 计划来源:`docs/plans/00-plan-overview.md`;仓库当前没有 `docs/specs/`,本模块计划根据现有代码审计结果生成。
## 模块目标
将采集结果从逐行对象存储/逐行 upsert 改为可控批处理,降低数据库往返、RustFS 小对象数量、序列化次数和结果文件生成内存。
## 模块级执行规则
- 开发阶段单线程串行执行,不并行实现多个任务;完成一个任务的测试、实现、验证和 commit 后才能进入下一个任务。
- 每个任务严格按“先写全部测试 → 运行确认 RED → 实现 → 运行确认 GREEN → lint/format → commit”执行。
- 每个普通任务至少包含 8 个语义化测试;涉及 I/O、数据库、HTTP、Redis、RustFS、文件或 UI 时,测试必须同时覆盖 mock 依赖和真实集成/启动调用。
- 禁止纯 echo、硬编码成功、空实现、跳过真实依赖调用或仅以 import 成功作为验收。
## 任务 41:建立 Collect Data 1k/10k 行、多个 chunk 和品牌检测场景基线
**所属模块**:采集数据批处理与结果文件优化
**依赖**:无
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立 Collect Data 1k/10k 行、多个 chunk 和品牌检测场景基线”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_041_chunk_brand_normal_default_path` — 使用正常输入验证“建立 Collect Data 1k/10k 行、多个 chunk 和品牌检测场景基线”的默认成功路径和主输出。
2. `test_task_041_chunk_brand_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_041_chunk_brand_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_041_chunk_brand_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_041_chunk_brand_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_041_chunk_brand_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_041_chunk_brand_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_041_chunk_brand_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 42:限制采集解析的文件大小、最大行数和单 chunk 行数
**所属模块**:采集数据批处理与结果文件优化
**依赖**41
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“限制采集解析的文件大小、最大行数和单 chunk 行数”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_042_file_size_row_count_chunk_normal_default_path` — 使用正常输入验证“限制采集解析的文件大小、最大行数和单 chunk 行数”的默认成功路径和主输出。
2. `test_task_042_file_size_row_count_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_042_file_size_row_count_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_042_file_size_row_count_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_042_file_size_row_count_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_042_file_size_row_count_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_042_file_size_row_count_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_042_file_size_row_count_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 43:将采集源文件查找改为确定路径/索引查询
**所属模块**:采集数据批处理与结果文件优化
**依赖**42
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将采集源文件查找改为确定路径/索引查询”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_043_collect_normal_default_path` — 使用正常输入验证“将采集源文件查找改为确定路径/索引查询”的默认成功路径和主输出。
2. `test_task_043_collect_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_043_collect_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_043_collect_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_043_collect_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_043_collect_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_043_collect_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_043_collect_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 44:保留原始 chunk payload 的同时,减少逐行 extra JSON 的重复序列化
**所属模块**:采集数据批处理与结果文件优化
**依赖**43
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“保留原始 chunk payload 的同时,减少逐行 extra JSON 的重复序列化”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_044_payload_chunk_normal_default_path` — 使用正常输入验证“保留原始 chunk payload 的同时,减少逐行 extra JSON 的重复序列化”的默认成功路径和主输出。
2. `test_task_044_payload_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_044_payload_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_044_payload_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_044_payload_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_044_payload_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_044_payload_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_044_payload_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 45:将 ASIN 去重查询与无效品牌查询统一为批量集合查询
**所属模块**:采集数据批处理与结果文件优化
**依赖**44
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 ASIN 去重查询与无效品牌查询统一为批量集合查询”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_045_asin_brand_normal_default_path` — 使用正常输入验证“将 ASIN 去重查询与无效品牌查询统一为批量集合查询”的默认成功路径和主输出。
2. `test_task_045_asin_brand_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_045_asin_brand_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_045_asin_brand_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_045_asin_brand_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_045_asin_brand_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_045_asin_brand_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_045_asin_brand_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 46:跳过空品牌批次的无效远程品牌检查请求
**所属模块**:采集数据批处理与结果文件优化
**依赖**45
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“跳过空品牌批次的无效远程品牌检查请求”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_046_brand_normal_default_path` — 使用正常输入验证“跳过空品牌批次的无效远程品牌检查请求”的默认成功路径和主输出。
2. `test_task_046_brand_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_046_brand_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_046_brand_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_046_brand_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_046_brand_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_046_brand_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_046_brand_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 47:为品牌检查结果增加任务内短期缓存,避免同品牌重复远程调用
**所属模块**:采集数据批处理与结果文件优化
**依赖**46
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为品牌检查结果增加任务内短期缓存,避免同品牌重复远程调用”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_047_cache_brand_normal_default_path` — 使用正常输入验证“为品牌检查结果增加任务内短期缓存,避免同品牌重复远程调用”的默认成功路径和主输出。
2. `test_task_047_cache_brand_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_047_cache_brand_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_047_cache_brand_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_047_cache_brand_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_047_cache_brand_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_047_cache_brand_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_047_cache_brand_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 48:将 invalid ASIN 记录改为批量 INSERT IGNORE/upsert
**所属模块**:采集数据批处理与结果文件优化
**依赖**47
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 invalid ASIN 记录改为批量 INSERT IGNORE/upsert”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_048_asin_normal_default_path` — 使用正常输入验证“将 invalid ASIN 记录改为批量 INSERT IGNORE/upsert”的默认成功路径和主输出。
2. `test_task_048_asin_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_048_asin_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_048_asin_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_048_asin_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_048_asin_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_048_asin_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_048_asin_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 49:将结果明细从逐行 RustFS 对象改为 chunk 级 payload 存储
**所属模块**:采集数据批处理与结果文件优化
**依赖**48
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将结果明细从逐行 RustFS 对象改为 chunk 级 payload 存储”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_049_payload_chunk_rustfs_normal_default_path` — 使用正常输入验证“将结果明细从逐行 RustFS 对象改为 chunk 级 payload 存储”的默认成功路径和主输出。
2. `test_task_049_payload_chunk_rustfs_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_049_payload_chunk_rustfs_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_049_payload_chunk_rustfs_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_049_payload_chunk_rustfs_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_049_payload_chunk_rustfs_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_049_payload_chunk_rustfs_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_049_payload_chunk_rustfs_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 50:为结果明细设计批量 upsert mapper 与幂等唯一键
**所属模块**:采集数据批处理与结果文件优化
**依赖**49
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为结果明细设计批量 upsert mapper 与幂等唯一键”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_050_task_normal_default_path` — 使用正常输入验证“为结果明细设计批量 upsert mapper 与幂等唯一键”的默认成功路径和主输出。
2. `test_task_050_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_050_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_050_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_050_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_050_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_050_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_050_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 51:将 accepted 行的序列化和 hash 计算改为批量处理
**所属模块**:采集数据批处理与结果文件优化
**依赖**50
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 accepted 行的序列化和 hash 计算改为批量处理”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_051_task_normal_default_path` — 使用正常输入验证“将 accepted 行的序列化和 hash 计算改为批量处理”的默认成功路径和主输出。
2. `test_task_051_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_051_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_051_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_051_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_051_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_051_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_051_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 52:生成结果文件时按 chunk 一次读取,取消逐行对象读取
**所属模块**:采集数据批处理与结果文件优化
**依赖**51
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“生成结果文件时按 chunk 一次读取,取消逐行对象读取”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_052_chunk_normal_default_path` — 使用正常输入验证“生成结果文件时按 chunk 一次读取,取消逐行对象读取”的默认成功路径和主输出。
2. `test_task_052_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_052_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_052_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_052_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_052_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_052_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_052_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 53:将 rawRows 与 finalRows 的内存生命周期分段,避免同时长期驻留
**所属模块**:采集数据批处理与结果文件优化
**依赖**52
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 rawRows 与 finalRows 的内存生命周期分段,避免同时长期驻留”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_053_task_normal_default_path` — 使用正常输入验证“将 rawRows 与 finalRows 的内存生命周期分段,避免同时长期驻留”的默认成功路径和主输出。
2. `test_task_053_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_053_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_053_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_053_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_053_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_053_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_053_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 54:将 finalRowCount 从每个 chunk COUNT(*) 改为任务内增量计数
**所属模块**:采集数据批处理与结果文件优化
**依赖**53
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 finalRowCount 从每个 chunk COUNT(*) 改为任务内增量计数”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_054_chunk_normal_default_path` — 使用正常输入验证“将 finalRowCount 从每个 chunk COUNT(*) 改为任务内增量计数”的默认成功路径和主输出。
2. `test_task_054_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_054_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_054_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_054_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_054_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_054_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_054_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 55:将进度统计更新改为节流/合并写,减少高频 task UPDATE
**所属模块**:采集数据批处理与结果文件优化
**依赖**54
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将进度统计更新改为节流/合并写,减少高频 task UPDATE”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_055_merge_progress_normal_default_path` — 使用正常输入验证“将进度统计更新改为节流/合并写,减少高频 task UPDATE”的默认成功路径和主输出。
2. `test_task_055_merge_progress_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_055_merge_progress_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_055_merge_progress_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_055_merge_progress_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_055_merge_progress_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_055_merge_progress_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_055_merge_progress_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 56:为采集结果文件增加流式写入失败后的临时文件清理
**所属模块**:采集数据批处理与结果文件优化
**依赖**55
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为采集结果文件增加流式写入失败后的临时文件清理”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_056_collect_cleanup_normal_default_path` — 使用正常输入验证“为采集结果文件增加流式写入失败后的临时文件清理”的默认成功路径和主输出。
2. `test_task_056_collect_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_056_collect_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_056_collect_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_056_collect_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_056_collect_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_056_collect_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_056_collect_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 57:为采集结果对象增加数据库删除与物理对象删除的一致性处理
**所属模块**:采集数据批处理与结果文件优化
**依赖**56
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为采集结果对象增加数据库删除与物理对象删除的一致性处理”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_057_collect_normal_default_path` — 使用正常输入验证“为采集结果对象增加数据库删除与物理对象删除的一致性处理”的默认成功路径和主输出。
2. `test_task_057_collect_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_057_collect_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_057_collect_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_057_collect_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_057_collect_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_057_collect_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_057_collect_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 58:补充外部品牌服务不可用、RustFS 超时和重复 chunk 的降级测试
**所属模块**:采集数据批处理与结果文件优化
**依赖**57
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充外部品牌服务不可用、RustFS 超时和重复 chunk 的降级测试”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_058_chunk_brand_rustfs_normal_default_path` — 使用正常输入验证“补充外部品牌服务不可用、RustFS 超时和重复 chunk 的降级测试”的默认成功路径和主输出。
2. `test_task_058_chunk_brand_rustfs_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_058_chunk_brand_rustfs_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_058_chunk_brand_rustfs_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_058_chunk_brand_rustfs_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_058_chunk_brand_rustfs_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_058_chunk_brand_rustfs_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_058_chunk_brand_rustfs_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 59:完成采集模块数据库索引、批量 SQL 和对象存储调用次数验证
**所属模块**:采集数据批处理与结果文件优化
**依赖**58
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“完成采集模块数据库索引、批量 SQL 和对象存储调用次数验证”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_059_collect_object_storage_normal_default_path` — 使用正常输入验证“完成采集模块数据库索引、批量 SQL 和对象存储调用次数验证”的默认成功路径和主输出。
2. `test_task_059_collect_object_storage_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_059_collect_object_storage_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_059_collect_object_storage_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_059_collect_object_storage_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_059_collect_object_storage_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_059_collect_object_storage_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_059_collect_object_storage_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 60:完成采集模块 10k 行压测并验收结果完整性、内存和吞吐
**所属模块**:采集数据批处理与结果文件优化
**依赖**59
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“完成采集模块 10k 行压测并验收结果完整性、内存和吞吐”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。
### 测试用例(必须先写,确认 RED)
1. `test_task_060_collect_normal_default_path` — 使用正常输入验证“完成采集模块 10k 行压测并验收结果完整性、内存和吞吐”的默认成功路径和主输出。
2. `test_task_060_collect_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_060_collect_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_060_collect_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_060_collect_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_060_collect_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_060_collect_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_060_collect_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 collectdata 解析、品牌检查、ASIN 去重、结果项持久化和结果文件生成。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 模块完成 Gate
- 20 个任务全部有对应 commit,任务状态均更新为 `done` 前不得开始下一个模块。
- 运行模块定向测试、项目全量测试和 lint/format;真实依赖调用、异常降级和资源释放均有记录。
- 运行模块对应的性能基准,记录吞吐、P95/P99 延迟、峰值堆、GC、CPU、DB QPS、Redis/RustFS QPS 和临时磁盘。
+701
View File
@@ -0,0 +1,701 @@
# 04 共享任务、对象存储、调度与清理优化
> 对应总览任务:61-80
> 计划来源:`docs/plans/00-plan-overview.md`;仓库当前没有 `docs/specs/`,本模块计划根据现有代码审计结果生成。
## 模块目标
建立跨模块资源预算和可观测性,消除无界缓存、重复派发、长事务、物理对象泄漏和大 payload 读写放大。
## 模块级执行规则
- 开发阶段单线程串行执行,不并行实现多个任务;完成一个任务的测试、实现、验证和 commit 后才能进入下一个任务。
- 每个任务严格按“先写全部测试 → 运行确认 RED → 实现 → 运行确认 GREEN → lint/format → commit”执行。
- 每个普通任务至少包含 8 个语义化测试;涉及 I/O、数据库、HTTP、Redis、RustFS、文件或 UI 时,测试必须同时覆盖 mock 依赖和真实集成/启动调用。
- 禁止纯 echo、硬编码成功、空实现、跳过真实依赖调用或仅以 import 成功作为验收。
## 任务 61:建立共享任务链路资源指标基线:线程、连接、队列、GC、Redis、RustFS 和 DB
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**:无
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立共享任务链路资源指标基线:线程、连接、队列、GC、Redis、RustFS 和 DB”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_061_rustfs_metrics_normal_default_path` — 使用正常输入验证“建立共享任务链路资源指标基线:线程、连接、队列、GC、Redis、RustFS 和 DB”的默认成功路径和主输出。
2. `test_task_061_rustfs_metrics_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_061_rustfs_metrics_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_061_rustfs_metrics_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_061_rustfs_metrics_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_061_rustfs_metrics_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_061_rustfs_metrics_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_061_rustfs_metrics_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 62:为本地任务实体缓存增加最大条目数、TTL 和定时清理
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**61
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为本地任务实体缓存增加最大条目数、TTL 和定时清理”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_062_cache_cleanup_normal_default_path` — 使用正常输入验证“为本地任务实体缓存增加最大条目数、TTL 和定时清理”的默认成功路径和主输出。
2. `test_task_062_cache_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_062_cache_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_062_cache_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_062_cache_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_062_cache_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_062_cache_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_062_cache_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 63:为前端/后端进度快照增加写入去重和最小更新间隔
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**62
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为前端/后端进度快照增加写入去重和最小更新间隔”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_063_progress_frontend_normal_default_path` — 使用正常输入验证“为前端/后端进度快照增加写入去重和最小更新间隔”的默认成功路径和主输出。
2. `test_task_063_progress_frontend_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_063_progress_frontend_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_063_progress_frontend_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_063_progress_frontend_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_063_progress_frontend_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_063_progress_frontend_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_063_progress_frontend_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 64:将 transient payload 压缩改为直接 gzip 二进制流上传
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**63
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 transient payload 压缩改为直接 gzip 二进制流上传”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_064_payload_compression_normal_default_path` — 使用正常输入验证“将 transient payload 压缩改为直接 gzip 二进制流上传”的默认成功路径和主输出。
2. `test_task_064_payload_compression_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_064_payload_compression_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_064_payload_compression_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_064_payload_compression_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_064_payload_compression_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_064_payload_compression_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_064_payload_compression_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 65:为 transient payload 读取增加流式解压和解压后字节上限
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**64
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为 transient payload 读取增加流式解压和解压后字节上限”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_065_payload_normal_default_path` — 使用正常输入验证“为 transient payload 读取增加流式解压和解压后字节上限”的默认成功路径和主输出。
2. `test_task_065_payload_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_065_payload_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_065_payload_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_065_payload_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_065_payload_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_065_payload_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_065_payload_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 66:限制 RustFS 并发读写与重试的总资源预算,防止多任务叠加爆发
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**65
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“限制 RustFS 并发读写与重试的总资源预算,防止多任务叠加爆发”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_066_rustfs_normal_default_path` — 使用正常输入验证“限制 RustFS 并发读写与重试的总资源预算,防止多任务叠加爆发”的默认成功路径和主输出。
2. `test_task_066_rustfs_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_066_rustfs_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_066_rustfs_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_066_rustfs_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_066_rustfs_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_066_rustfs_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_066_rustfs_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 67:复用 RustFS/MinIO 客户端与 HTTP 连接池,减少每次操作创建客户端
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**66
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“复用 RustFS/MinIO 客户端与 HTTP 连接池,减少每次操作创建客户端”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_067_rustfs_normal_default_path` — 使用正常输入验证“复用 RustFS/MinIO 客户端与 HTTP 连接池,减少每次操作创建客户端”的默认成功路径和主输出。
2. `test_task_067_rustfs_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_067_rustfs_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_067_rustfs_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_067_rustfs_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_067_rustfs_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_067_rustfs_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_067_rustfs_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 68:将 payload 引用删除改为批量引用检查与异步物理删除
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**67
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 payload 引用删除改为批量引用检查与异步物理删除”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_068_payload_normal_default_path` — 使用正常输入验证“将 payload 引用删除改为批量引用检查与异步物理删除”的默认成功路径和主输出。
2. `test_task_068_payload_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_068_payload_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_068_payload_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_068_payload_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_068_payload_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_068_payload_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_068_payload_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 69:为数据库删除任务补充 transient payload 指针收集和清理队列
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**68
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为数据库删除任务补充 transient payload 指针收集和清理队列”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_069_payload_cleanup_normal_default_path` — 使用正常输入验证“为数据库删除任务补充 transient payload 指针收集和清理队列”的默认成功路径和主输出。
2. `test_task_069_payload_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_069_payload_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_069_payload_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_069_payload_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_069_payload_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_069_payload_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_069_payload_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 70:将历史清理改为 keyset 分页、小批量和短事务
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**69
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将历史清理改为 keyset 分页、小批量和短事务”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_070_cleanup_normal_default_path` — 使用正常输入验证“将历史清理改为 keyset 分页、小批量和短事务”的默认成功路径和主输出。
2. `test_task_070_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_070_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_070_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_070_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_070_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_070_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_070_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 71:清理日志改为数量与 sample ID,禁止输出超长任务 ID 列表
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**70
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“清理日志改为数量与 sample ID,禁止输出超长任务 ID 列表”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_071_cleanup_logging_normal_default_path` — 使用正常输入验证“清理日志改为数量与 sample ID,禁止输出超长任务 ID 列表”的默认成功路径和主输出。
2. `test_task_071_cleanup_logging_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_071_cleanup_logging_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_071_cleanup_logging_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_071_cleanup_logging_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_071_cleanup_logging_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_071_cleanup_logging_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_071_cleanup_logging_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 72:为文件作业实现数据库原子 claim,避免重复派发同一 job
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**71
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为文件作业实现数据库原子 claim,避免重复派发同一 job”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_072_job_normal_default_path` — 使用正常输入验证“为文件作业实现数据库原子 claim,避免重复派发同一 job”的默认成功路径和主输出。
2. `test_task_072_job_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_072_job_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_072_job_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_072_job_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_072_job_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_072_job_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_072_job_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 73:为本地文件作业队列增加 in-flight 去重和队列背压
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**72
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为本地文件作业队列增加 in-flight 去重和队列背压”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_073_job_normal_default_path` — 使用正常输入验证“为本地文件作业队列增加 in-flight 去重和队列背压”的默认成功路径和主输出。
2. `test_task_073_job_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_073_job_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_073_job_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_073_job_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_073_job_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_073_job_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_073_job_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 74:隔离调度线程池、文件作业线程池和外部 Coze/图片执行池
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**73
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“隔离调度线程池、文件作业线程池和外部 Coze/图片执行池”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_074_image_dispatch_job_normal_default_path` — 使用正常输入验证“隔离调度线程池、文件作业线程池和外部 Coze/图片执行池”的默认成功路径和主输出。
2. `test_task_074_image_dispatch_job_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_074_image_dispatch_job_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_074_image_dispatch_job_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_074_image_dispatch_job_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_074_image_dispatch_job_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_074_image_dispatch_job_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_074_image_dispatch_job_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 75:为虚拟线程任务增加等待队列上限与拒绝/延迟指标
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**74
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为虚拟线程任务增加等待队列上限与拒绝/延迟指标”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_075_metrics_normal_default_path` — 使用正常输入验证“为虚拟线程任务增加等待队列上限与拒绝/延迟指标”的默认成功路径和主输出。
2. `test_task_075_metrics_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_075_metrics_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_075_metrics_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_075_metrics_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_075_metrics_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_075_metrics_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_075_metrics_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 76:将 JSON owner 查询迁移到显式列并补充任务/状态复合索引
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**75
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将 JSON owner 查询迁移到显式列并补充任务/状态复合索引”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_076_owner_normal_default_path` — 使用正常输入验证“将 JSON owner 查询迁移到显式列并补充任务/状态复合索引”的默认成功路径和主输出。
2. `test_task_076_owner_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_076_owner_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_076_owner_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_076_owner_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_076_owner_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_076_owner_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_076_owner_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 77:统一 Coze、品牌检查和紫鸟 HTTP 客户端的连接复用策略
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**76
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“统一 Coze、品牌检查和紫鸟 HTTP 客户端的连接复用策略”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_077_brand_normal_default_path` — 使用正常输入验证“统一 Coze、品牌检查和紫鸟 HTTP 客户端的连接复用策略”的默认成功路径和主输出。
2. `test_task_077_brand_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_077_brand_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_077_brand_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_077_brand_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_077_brand_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_077_brand_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_077_brand_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 78:为所有外部调用增加耗时、重试、失败率和 payload 字节指标
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**77
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为所有外部调用增加耗时、重试、失败率和 payload 字节指标”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_078_payload_metrics_normal_default_path` — 使用正常输入验证“为所有外部调用增加耗时、重试、失败率和 payload 字节指标”的默认成功路径和主输出。
2. `test_task_078_payload_metrics_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_078_payload_metrics_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_078_payload_metrics_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_078_payload_metrics_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_078_payload_metrics_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_078_payload_metrics_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_078_payload_metrics_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 79:为对象存储、数据库和队列增加故障注入测试
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**78
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为对象存储、数据库和队列增加故障注入测试”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_079_object_storage_normal_default_path` — 使用正常输入验证“为对象存储、数据库和队列增加故障注入测试”的默认成功路径和主输出。
2. `test_task_079_object_storage_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_079_object_storage_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_079_object_storage_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_079_object_storage_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_079_object_storage_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_079_object_storage_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_079_object_storage_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 80:补充 JVM 堆、直接内存、临时磁盘和连接池容量配置说明
**所属模块**:共享任务、对象存储、调度与清理优化
**依赖**79
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充 JVM 堆、直接内存、临时磁盘和连接池容量配置说明”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。
### 测试用例(必须先写,确认 RED)
1. `test_task_080_task_normal_default_path` — 使用正常输入验证“补充 JVM 堆、直接内存、临时磁盘和连接池容量配置说明”的默认成功路径和主输出。
2. `test_task_080_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_080_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_080_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_080_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_080_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_080_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_080_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `mvn -q test` 通过;涉及模块时补充定向测试命令。
- 涉及 I/O、外部服务或数据库时,必须再执行 mock 测试和真实集成/启动调用,并记录资源释放结果。
- 重点观察:backend-java 的 task、RustFS/MinIO、任务文件作业、调度线程池、历史清理和指标日志。中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 模块完成 Gate
- 20 个任务全部有对应 commit,任务状态均更新为 `done` 前不得开始下一个模块。
- 运行模块定向测试、项目全量测试和 lint/format;真实依赖调用、异常降级和资源释放均有记录。
- 运行模块对应的性能基准,记录吞吐、P95/P99 延迟、峰值堆、GC、CPU、DB QPS、Redis/RustFS QPS 和临时磁盘。
+726
View File
@@ -0,0 +1,726 @@
# 05 前端轮询、缓存、构建与交付验收
> 对应总览任务:81-100
> 计划来源:`docs/plans/00-plan-overview.md`;仓库当前没有 `docs/specs/`,本模块计划根据现有代码审计结果生成。
## 模块目标
减少浏览器端轮询重复请求、响应式对象和 localStorage 无界增长,降低首屏公共 chunk 体积,并通过真实页面交互确认核心链路没有 stub。
## 模块级执行规则
- 开发阶段单线程串行执行,不并行实现多个任务;完成一个任务的测试、实现、验证和 commit 后才能进入下一个任务。
- 每个任务严格按“先写全部测试 → 运行确认 RED → 实现 → 运行确认 GREEN → lint/format → commit”执行。
- 每个普通任务至少包含 8 个语义化测试;涉及 I/O、数据库、HTTP、Redis、RustFS、文件或 UI 时,测试必须同时覆盖 mock 依赖和真实集成/启动调用。
- 禁止纯 echo、硬编码成功、空实现、跳过真实依赖调用或仅以 import 成功作为验收。
## 任务 81:建立前端任务轮询请求量、响应体大小和页面内存基线
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**:无
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立前端任务轮询请求量、响应体大小和页面内存基线”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_081_polling_frontend_normal_default_path` — 使用正常输入验证“建立前端任务轮询请求量、响应体大小和页面内存基线”的默认成功路径和主输出。
2. `test_task_081_polling_frontend_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_081_polling_frontend_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_081_polling_frontend_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_081_polling_frontend_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_081_polling_frontend_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_081_polling_frontend_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_081_polling_frontend_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 82:为进度响应 Map 增加 TTL 清理与最大条目数
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**81
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为进度响应 Map 增加 TTL 清理与最大条目数”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_082_progress_cleanup_normal_default_path` — 使用正常输入验证“为进度响应 Map 增加 TTL 清理与最大条目数”的默认成功路径和主输出。
2. `test_task_082_progress_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_082_progress_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_082_progress_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_082_progress_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_082_progress_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_082_progress_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_082_progress_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 83:统一不同页面的轮询去重、in-flight 合并和终态清理
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**82
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“统一不同页面的轮询去重、in-flight 合并和终态清理”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_083_merge_cleanup_polling_normal_default_path` — 使用正常输入验证“统一不同页面的轮询去重、in-flight 合并和终态清理”的默认成功路径和主输出。
2. `test_task_083_merge_cleanup_polling_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_083_merge_cleanup_polling_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_083_merge_cleanup_polling_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_083_merge_cleanup_polling_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_083_merge_cleanup_polling_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_083_merge_cleanup_polling_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_083_merge_cleanup_polling_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 84:优化店铺抓取队列状态合并,消除 historyItems 的线性重复查找
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**83
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“优化店铺抓取队列状态合并,消除 historyItems 的线性重复查找”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_084_merge_normal_default_path` — 使用正常输入验证“优化店铺抓取队列状态合并,消除 historyItems 的线性重复查找”的默认成功路径和主输出。
2. `test_task_084_merge_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_084_merge_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_084_merge_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_084_merge_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_084_merge_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_084_merge_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_084_merge_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 85:优化 Similar ASIN 轮询与文件生成等待,避免重复 force 请求
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**84
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“优化 Similar ASIN 轮询与文件生成等待,避免重复 force 请求”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_085_asin_polling_normal_default_path` — 使用正常输入验证“优化 Similar ASIN 轮询与文件生成等待,避免重复 force 请求”的默认成功路径和主输出。
2. `test_task_085_asin_polling_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_085_asin_polling_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_085_asin_polling_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_085_asin_polling_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_085_asin_polling_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_085_asin_polling_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_085_asin_polling_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 86:将隐藏页面轮询间隔、前台恢复和退避策略统一配置化
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**85
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“将隐藏页面轮询间隔、前台恢复和退避策略统一配置化”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_086_polling_normal_default_path` — 使用正常输入验证“将隐藏页面轮询间隔、前台恢复和退避策略统一配置化”的默认成功路径和主输出。
2. `test_task_086_polling_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_086_polling_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_086_polling_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_086_polling_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_086_polling_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_086_polling_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_086_polling_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 87:限制 localStorage 中任务、快照和队列数据的最大数量/字节数
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**86
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“限制 localStorage 中任务、快照和队列数据的最大数量/字节数”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_087_task_normal_default_path` — 使用正常输入验证“限制 localStorage 中任务、快照和队列数据的最大数量/字节数”的默认成功路径和主输出。
2. `test_task_087_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_087_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_087_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_087_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_087_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_087_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_087_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 88:解析结果前端只接收预览数据,避免大 payload 进入响应式对象
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**87
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“解析结果前端只接收预览数据,避免大 payload 进入响应式对象”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_088_payload_preview_frontend_normal_default_path` — 使用正常输入验证“解析结果前端只接收预览数据,避免大 payload 进入响应式对象”的默认成功路径和主输出。
2. `test_task_088_payload_preview_frontend_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_088_payload_preview_frontend_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_088_payload_preview_frontend_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_088_payload_preview_frontend_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_088_payload_preview_frontend_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_088_payload_preview_frontend_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_088_payload_preview_frontend_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 89:清理页面卸载时的所有 timer、请求和临时 URL
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**88
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“清理页面卸载时的所有 timer、请求和临时 URL”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_089_cleanup_normal_default_path` — 使用正常输入验证“清理页面卸载时的所有 timer、请求和临时 URL”的默认成功路径和主输出。
2. `test_task_089_cleanup_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_089_cleanup_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_089_cleanup_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_089_cleanup_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_089_cleanup_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_089_cleanup_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_089_cleanup_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 90:为进度接口增加断网、超时、服务恢复和重复响应测试
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**89
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“为进度接口增加断网、超时、服务恢复和重复响应测试”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_090_progress_normal_default_path` — 使用正常输入验证“为进度接口增加断网、超时、服务恢复和重复响应测试”的默认成功路径和主输出。
2. `test_task_090_progress_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_090_progress_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_090_progress_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_090_progress_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_090_progress_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_090_progress_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_090_progress_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 91:按页面拆分 Element Plus 与公共业务 chunk
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**90
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“按页面拆分 Element Plus 与公共业务 chunk”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_091_chunk_normal_default_path` — 使用正常输入验证“按页面拆分 Element Plus 与公共业务 chunk”的默认成功路径和主输出。
2. `test_task_091_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_091_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_091_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_091_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_091_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_091_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_091_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 92:配置 Vite manualChunks 并比较各页面首屏传输大小
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**91
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“配置 Vite manualChunks 并比较各页面首屏传输大小”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_092_chunk_normal_default_path` — 使用正常输入验证“配置 Vite manualChunks 并比较各页面首屏传输大小”的默认成功路径和主输出。
2. `test_task_092_chunk_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_092_chunk_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_092_chunk_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_092_chunk_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_092_chunk_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_092_chunk_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_092_chunk_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 93:补充 Similar ASIN、店铺抓取和采集数据页面的 E2E 核心路径
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**92
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充 Similar ASIN、店铺抓取和采集数据页面的 E2E 核心路径”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_093_collect_asin_e2e_normal_default_path` — 使用正常输入验证“补充 Similar ASIN、店铺抓取和采集数据页面的 E2E 核心路径”的默认成功路径和主输出。
2. `test_task_093_collect_asin_e2e_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_093_collect_asin_e2e_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_093_collect_asin_e2e_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_093_collect_asin_e2e_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_093_collect_asin_e2e_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_093_collect_asin_e2e_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_093_collect_asin_e2e_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 94:补充移动端与桌面端响应式页面验收截图
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**93
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充移动端与桌面端响应式页面验收截图”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_094_task_normal_default_path` — 使用正常输入验证“补充移动端与桌面端响应式页面验收截图”的默认成功路径和主输出。
2. `test_task_094_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_094_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_094_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_094_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_094_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_094_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_094_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 95:补充深色主题、错误提示、重试和终态刷新验收
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**94
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“补充深色主题、错误提示、重试和终态刷新验收”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_095_task_normal_default_path` — 使用正常输入验证“补充深色主题、错误提示、重试和终态刷新验收”的默认成功路径和主输出。
2. `test_task_095_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_095_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_095_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_095_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_095_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_095_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_095_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 96:建立 Java/Python/Vue 三端统一的 API 字段兼容检查
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**95
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“建立 Java/Python/Vue 三端统一的 API 字段兼容检查”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_096_task_normal_default_path` — 使用正常输入验证“建立 Java/Python/Vue 三端统一的 API 字段兼容检查”的默认成功路径和主输出。
2. `test_task_096_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_096_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_096_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_096_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_096_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_096_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_096_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 97:执行 Java 全量测试、Python unittest、Vue 类型检查与构建
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**96
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“执行 Java 全量测试、Python unittest、Vue 类型检查与构建”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_097_build_normal_default_path` — 使用正常输入验证“执行 Java 全量测试、Python unittest、Vue 类型检查与构建”的默认成功路径和主输出。
2. `test_task_097_build_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_097_build_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_097_build_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_097_build_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_097_build_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_097_build_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_097_build_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 98:执行真实启动、健康检查、核心请求和外部依赖调用验证
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**97
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“执行真实启动、健康检查、核心请求和外部依赖调用验证”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_098_task_normal_default_path` — 使用正常输入验证“执行真实启动、健康检查、核心请求和外部依赖调用验证”的默认成功路径和主输出。
2. `test_task_098_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_098_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_098_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_098_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_098_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_098_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_098_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 99:执行全链路压测并记录 CPU、内存、GC、DB、Redis、RustFS、网络结果
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**98
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“执行全链路压测并记录 CPU、内存、GC、DB、Redis、RustFS、网络结果”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_099_rustfs_normal_default_path` — 使用正常输入验证“执行全链路压测并记录 CPU、内存、GC、DB、Redis、RustFS、网络结果”的默认成功路径和主输出。
2. `test_task_099_rustfs_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_099_rustfs_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_099_rustfs_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_099_rustfs_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_099_rustfs_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_099_rustfs_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_099_rustfs_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 任务 100:完成发布前回滚演练、git commit 对应关系检查和交付清单
**所属模块**:前端轮询、缓存、构建与交付验收
**依赖**99
**预估耗时**:8-15 分钟(写测试 + 实现 + 自测)
### 功能点要求
- 围绕“完成发布前回滚演练、git commit 对应关系检查和交付清单”实现一个独立、可验证的功能点,不扩大到同模块其他未完成任务。
- 输入、输出、异常和资源上限必须明确;空输入、单元素、最大允许值和超限值必须有确定行为。
- 成功路径必须保留现有业务语义、幂等性和兼容字段;失败路径必须释放临时文件、连接、锁、线程任务和未引用对象。
- 实现范围限定在 frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证。
### 测试用例(必须先写,确认 RED)
1. `test_task_100_task_normal_default_path` — 使用正常输入验证“完成发布前回滚演练、git commit 对应关系检查和交付清单”的默认成功路径和主输出。
2. `test_task_100_task_normal_multiple_items` — 使用多个任务、行、chunk 或页面状态验证批量场景下结果不丢失且顺序稳定。
3. `test_task_100_task_normal_repeated_operation_is_idempotent` — 重复执行同一输入,验证不会产生重复记录、重复对象、重复请求或重复 UI 状态。
4. `test_task_100_task_boundary_empty_input` — 空集合、空字符串或无可处理数据时验证返回空结果/安全跳过,不创建无效资源。
5. `test_task_100_task_boundary_single_item` — 单任务、单行、单图片或单页面状态时验证不依赖批量路径且结果正确。
6. `test_task_100_task_boundary_limit_and_overflow` — 达到最大配置值以及超过最大值时验证限流、拒绝或降级行为,不发生无界内存增长。
7. `test_task_100_task_invalid_input_rejected` — 非法参数、缺少必填字段或格式错误时验证抛出项目约定异常及可识别错误消息。
8. `test_task_100_task_dependency_failure_releases_resources` — mock 数据库/Redis/RustFS/HTTP/文件依赖失败后验证错误可恢复,临时资源、锁和队列任务均释放。
### TDD 流程
1. 先写上述全部测试,运行项目测试命令,确认该功能测试 **RED**;如果测试一开始通过,必须先检查测试是否真正覆盖待实现行为。
2. 实现功能,运行该任务定向测试和相关模块测试,确认 **GREEN**
3. 执行 lint/format、编译或前端构建检查,确保没有新增错误。
4. 完成资源释放、异常路径和兼容性验证后创建该任务对应 commit。
### 验证方法
- `npm run build` 通过;涉及页面的任务执行对应 Playwright/E2E 用例。
- 检查浏览器 Network、Performance、Memory 面板,确认轮询请求无重复风暴、终态后停止、缓存有界。
- 重点观察:frontend-vue 的任务轮询、响应缓存、队列状态、构建拆包和页面 E2E 交付验证中的功能结果正确,且 CPU、堆内存、GC、数据库/对象存储调用次数和临时磁盘占用没有超过任务设定上限。
## 模块完成 Gate
- 20 个任务全部有对应 commit,任务状态均更新为 `done` 前不得开始下一个模块。
- 运行模块定向测试、项目全量测试和 lint/format;真实依赖调用、异常降级和资源释放均有记录。
- 运行模块对应的性能基准,记录吞吐、P95/P99 延迟、峰值堆、GC、CPU、DB QPS、Redis/RustFS QPS 和临时磁盘。
## 全局收尾 Gate
以下验证必须在任务 100 完成后按顺序执行,任一项失败都不能将计划标记为完成:
1. 使用项目实际启动命令启动 Java 服务,并通过健康检查端点。
2. 启动 Python 后端并确认核心管理入口可访问。
3. 访问 Similar ASIN、店铺数据抓取和采集数据页面,确认标题、输入控件和任务区域存在。
4. 使用真实最小文件完成一次解析、任务创建、任务执行和结果下载。
5. 使用多样化输入验证关键业务逻辑不是固定模板或输入纯回显。
6. 验证数据库、Redis、RustFS、RocketMQ 和外部 HTTP 服务在对应路径确实发生调用。
7. 验证非法输入、依赖不可用、超时、断网和重复回传有明确错误或降级行为。
8. 验证任务取消、页面关闭、服务重启和 owner 路由后不会残留锁、线程、timer 或临时文件。
9. 运行 Java 全量测试并确认全绿。
10. 运行 Python 测试并确认全绿。
11. 运行 Vue 类型检查和生产构建并确认无错误。
12. 运行 Java、Python 和前端 lint/format 检查并确认零错误。
13. 使用 Playwright 完成桌面端页面可访问和关键交互路径验证。
14. 使用 Playwright 验证断网、恢复、错误提示和重试行为。
15. 如果页面支持主题切换,验证深色/浅色主题状态发生正确变化。
16. 保存 Desktop 1280x800 与 Mobile 375x812 的响应式验收截图到项目约定目录。
17. 执行接口字段一致性检查,确认 Java、Python、Vue 三端命名和状态值一致。
18. 执行 `git log` 检查 1-100 每个任务均有对应 commit,且工作树状态符合交付要求。
19. 最后执行 `python check_progress.py`,仅当输出 `all tasks done` 且退出码为 0 时,才允许宣布计划完成。
+7
View File
@@ -0,0 +1,7 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import '@/styles/main.css'
import ImageVideoPage from '@/pages/image-video/ImageVideoPage.vue'
createApp(ImageVideoPage).use(ElementPlus).mount('#app')
+1 -1
View File
@@ -30,7 +30,7 @@ export default defineConfig({
port: 5173, port: 5173,
proxy: { proxy: {
'/newApi/api': { '/newApi/api': {
target: 'http://api.aishufu.top:18080/', target: process.env.VITE_API_TARGET || 'http://127.0.0.1:18080/',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/newApi/, ''), rewrite: (path) => path.replace(/^\/newApi/, ''),
}, },