后端可视化调试
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
is_true() {
|
||||
normalized=$(printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]')
|
||||
case "$normalized" in
|
||||
1|true|yes|on)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
start_browser_debug_services() {
|
||||
display="${TENCENT_BROWSER_DISPLAY:-:99}"
|
||||
resolution="${TENCENT_BROWSER_VNC_RESOLUTION:-1440x1280x24}"
|
||||
|
||||
export DISPLAY="$display"
|
||||
Xvfb "$display" -screen 0 "$resolution" -nolisten tcp >/tmp/xvfb.log 2>&1 &
|
||||
|
||||
if ! is_true "${TENCENT_BROWSER_NOVNC_ENABLED:-true}"; then
|
||||
echo "[backend-dev] headed browser enabled on ${DISPLAY}; noVNC disabled"
|
||||
return
|
||||
fi
|
||||
|
||||
vnc_port="${TENCENT_BROWSER_VNC_PORT:-5900}"
|
||||
novnc_port="${TENCENT_BROWSER_NOVNC_PORT:-6080}"
|
||||
|
||||
x11vnc \
|
||||
-display "$display" \
|
||||
-rfbport "$vnc_port" \
|
||||
-forever \
|
||||
-shared \
|
||||
-nopw \
|
||||
-listen 0.0.0.0 \
|
||||
-xkb >/tmp/x11vnc.log 2>&1 &
|
||||
|
||||
if command -v novnc_proxy >/dev/null 2>&1; then
|
||||
novnc_proxy --listen "$novnc_port" --vnc "127.0.0.1:${vnc_port}" >/tmp/novnc.log 2>&1 &
|
||||
elif [ -x /usr/share/novnc/utils/novnc_proxy ]; then
|
||||
/usr/share/novnc/utils/novnc_proxy --listen "$novnc_port" --vnc "127.0.0.1:${vnc_port}" >/tmp/novnc.log 2>&1 &
|
||||
elif command -v websockify >/dev/null 2>&1 && [ -d /usr/share/novnc ]; then
|
||||
websockify --web=/usr/share/novnc "$novnc_port" "127.0.0.1:${vnc_port}" >/tmp/novnc.log 2>&1 &
|
||||
else
|
||||
echo "[backend-dev] noVNC requested but novnc/websockify not found"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "[backend-dev] headed browser enabled on ${DISPLAY}; noVNC: http://127.0.0.1:${novnc_port}/vnc.html"
|
||||
}
|
||||
|
||||
npm install --no-fund --no-audit
|
||||
python3 -m pip install --no-cache-dir --break-system-packages -e /app/subservices/ocr-worker
|
||||
|
||||
if ! is_true "${TENCENT_BROWSER_HEADLESS:-true}"; then
|
||||
start_browser_debug_services
|
||||
fi
|
||||
|
||||
exec npm run dev
|
||||
@@ -193,6 +193,54 @@ export async function listInventoryItems({
|
||||
}
|
||||
}
|
||||
|
||||
export async function listInventorySkuSuggestions({
|
||||
credentialType = '',
|
||||
keyword = '',
|
||||
limit = 50,
|
||||
} = {}) {
|
||||
const filters = []
|
||||
const params = []
|
||||
|
||||
if (credentialType) {
|
||||
params.push(credentialType)
|
||||
filters.push(`credential_type = $${params.length}`)
|
||||
}
|
||||
|
||||
if (keyword) {
|
||||
params.push(`%${keyword}%`)
|
||||
filters.push(`sku_code ILIKE $${params.length}`)
|
||||
}
|
||||
|
||||
const normalizedLimit = Number.isFinite(Number(limit))
|
||||
? Math.max(1, Math.min(Number(limit), 100))
|
||||
: 50
|
||||
|
||||
params.push(normalizedLimit)
|
||||
const whereClause = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : ''
|
||||
const result = await query(
|
||||
`
|
||||
SELECT
|
||||
sku_code,
|
||||
credential_type,
|
||||
COUNT(*)::int AS total_count,
|
||||
COUNT(*) FILTER (WHERE status = 'available')::int AS available_count,
|
||||
MAX(updated_at) AS latest_updated_at
|
||||
FROM inventory_items
|
||||
${whereClause}
|
||||
GROUP BY sku_code, credential_type
|
||||
ORDER BY
|
||||
COUNT(*) FILTER (WHERE status = 'available') DESC,
|
||||
COUNT(*) DESC,
|
||||
MAX(updated_at) DESC,
|
||||
sku_code ASC
|
||||
LIMIT $${params.length}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
|
||||
return result.rows
|
||||
}
|
||||
|
||||
export async function createInventoryItems(rows) {
|
||||
let created = 0
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Router } from 'express'
|
||||
import {
|
||||
createAdminInventoryItem,
|
||||
getAdminInventoryItems,
|
||||
getAdminInventorySkuSuggestions,
|
||||
importAdminInventoryItems,
|
||||
invalidateAdminInventoryItem,
|
||||
releaseAdminInventoryItem,
|
||||
@@ -22,6 +23,15 @@ router.get('/inventory', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.get('/inventory/sku-suggestions', createJsonHandler(
|
||||
(req) => getAdminInventorySkuSuggestions(req.query),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取内部 SKU 建议失败',
|
||||
scope: '[admin/inventory/sku-suggestions]',
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/inventory', createJsonHandler(
|
||||
(req) => createAdminInventoryItem(req.body),
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
getInventoryItemById,
|
||||
invalidateInventoryItem,
|
||||
listInventoryItems,
|
||||
listInventorySkuSuggestions,
|
||||
markInventoryItemDelivered,
|
||||
releaseReservedInventoryItem,
|
||||
} from '../../repositories/inventory-repo.js'
|
||||
@@ -155,6 +156,7 @@ export async function getAdminOrderDetail(orderId) {
|
||||
itemSummary,
|
||||
rawPayload: safeParseJson(order.raw_payload_json),
|
||||
bindingSummary: buildOrderBindingSummary(tasks, taskBindingSummaryMap),
|
||||
agisoAutoDelivery: buildOrderAgisoAutoDeliverySummary(order, tasks),
|
||||
},
|
||||
items: items.map((item) => ({
|
||||
orderItemId: item.id,
|
||||
@@ -424,6 +426,30 @@ export async function createAdminInventoryItem(payload = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAdminInventorySkuSuggestions(query = {}) {
|
||||
const credentialType = String(query.credentialType || '').trim()
|
||||
const keyword = String(query.keyword || '').trim()
|
||||
const requestedLimit = Number(query.limit)
|
||||
const limit = Number.isFinite(requestedLimit) && requestedLimit > 0
|
||||
? Math.min(100, Math.floor(requestedLimit))
|
||||
: 50
|
||||
const items = await listInventorySkuSuggestions({
|
||||
credentialType,
|
||||
keyword,
|
||||
limit,
|
||||
})
|
||||
|
||||
return {
|
||||
items: items.map((item) => ({
|
||||
skuCode: String(item.sku_code || '').trim(),
|
||||
credentialType: String(item.credential_type || '').trim() || 'tencent_code',
|
||||
totalCount: Number(item.total_count || 0),
|
||||
availableCount: Number(item.available_count || 0),
|
||||
latestUpdatedAt: item.latest_updated_at || null,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export async function importAdminInventoryItems(payload = {}) {
|
||||
const rows = normalizeInventoryImportRows(payload)
|
||||
|
||||
@@ -1286,10 +1312,12 @@ export async function completeAdminTaskManualDispatch(taskId, payload = {}, sess
|
||||
|
||||
function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindingSummary()) {
|
||||
const binding = buildTaskBindingState(task)
|
||||
const taskContext = parseTaskContext(task)
|
||||
|
||||
return {
|
||||
taskId: task.id,
|
||||
taskNo: task.task_no,
|
||||
deliveryStatus: task.delivery_status || '',
|
||||
status: task.task_status,
|
||||
systemBindingStatus: binding.systemBindingStatus,
|
||||
userBindingStatus: binding.userBindingStatus,
|
||||
@@ -1301,6 +1329,7 @@ function mapAdminTaskSummary(task, bindingSummary = createEmptyTaskBindingSummar
|
||||
lastError: task.last_error,
|
||||
retryCount: getTaskRetryCount(task),
|
||||
bindingSummary,
|
||||
agisoAutoDelivery: mapAgisoAutoDeliveryContext(taskContext.agisoAutoDelivery),
|
||||
createdAt: task.created_at,
|
||||
updatedAt: task.updated_at,
|
||||
}
|
||||
@@ -1606,6 +1635,7 @@ async function getRequiredInventoryItem(inventoryItemId) {
|
||||
|
||||
function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindingSummary(), viewerContext = createAdminViewerContext()) {
|
||||
const binding = buildTaskBindingState(task)
|
||||
const taskContext = parseTaskContext(task)
|
||||
|
||||
return {
|
||||
taskId: task.id,
|
||||
@@ -1629,6 +1659,7 @@ function mapAdminTaskListItem(task, bindingSummary = createEmptyTaskBindingSumma
|
||||
redeemedAt: task.redeemed_at,
|
||||
retryCount: getTaskRetryCount(task),
|
||||
bindingSummary,
|
||||
agisoAutoDelivery: mapAgisoAutoDeliveryContext(taskContext.agisoAutoDelivery),
|
||||
lastError: task.last_error,
|
||||
createdAt: task.created_at,
|
||||
updatedAt: task.updated_at,
|
||||
@@ -1892,6 +1923,61 @@ function buildOrderBindingSummary(tasks, taskBindingSummaryMap = new Map()) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildOrderAgisoAutoDeliverySummary(order, tasks = []) {
|
||||
if (String(order?.provider || '').trim() !== 'agiso' || String(order?.platform || '').trim() !== 'xianyu') {
|
||||
return null
|
||||
}
|
||||
|
||||
const normalizedTasks = Array.isArray(tasks) ? tasks.filter(Boolean) : []
|
||||
const totalTaskCount = normalizedTasks.length
|
||||
const deliveredTaskCount = normalizedTasks.filter((task) => String(task?.delivery_status || '').trim() === 'delivered').length
|
||||
const latest = normalizedTasks.reduce((best, task) => {
|
||||
const autoDelivery = mapAgisoAutoDeliveryContext(parseTaskContext(task).agisoAutoDelivery)
|
||||
|
||||
if (!autoDelivery) {
|
||||
return best
|
||||
}
|
||||
|
||||
const candidate = {
|
||||
...autoDelivery,
|
||||
sourceTaskId: Number(task.id || 0) || null,
|
||||
sourceTaskNo: String(task.task_no || '').trim(),
|
||||
}
|
||||
const candidateTime = Date.parse(String(candidate.updatedAt || task.updated_at || ''))
|
||||
const bestTime = Date.parse(String(best?.updatedAt || ''))
|
||||
|
||||
if (!best || (Number.isFinite(candidateTime) && (!Number.isFinite(bestTime) || candidateTime >= bestTime))) {
|
||||
return candidate
|
||||
}
|
||||
|
||||
return best
|
||||
}, null)
|
||||
|
||||
if (latest) {
|
||||
return {
|
||||
...latest,
|
||||
totalTaskCount,
|
||||
deliveredTaskCount,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: totalTaskCount === 0 ? 'not_started' : deliveredTaskCount >= totalTaskCount ? 'pending' : 'waiting',
|
||||
trigger: '',
|
||||
reason: deliveredTaskCount >= totalTaskCount ? '' : 'waiting_other_tasks',
|
||||
platformOrderId: String(order?.platform_order_id || '').trim(),
|
||||
responseStatus: 0,
|
||||
errorMessage: '',
|
||||
requestId: '',
|
||||
aldsType: null,
|
||||
updatedAt: null,
|
||||
sourceTaskId: null,
|
||||
sourceTaskNo: '',
|
||||
totalTaskCount,
|
||||
deliveredTaskCount,
|
||||
}
|
||||
}
|
||||
|
||||
function buildTaskBindingState(task) {
|
||||
const normalizedStatus = String(task?.task_status || '').trim()
|
||||
|
||||
@@ -2018,6 +2104,24 @@ function mapManualDispatchContext(value, viewerContext = createAdminViewerContex
|
||||
}
|
||||
}
|
||||
|
||||
function mapAgisoAutoDeliveryContext(value) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
status: String(value.status || '').trim(),
|
||||
trigger: String(value.trigger || '').trim(),
|
||||
reason: String(value.reason || '').trim(),
|
||||
platformOrderId: String(value.platformOrderId || '').trim(),
|
||||
responseStatus: Number(value.responseStatus || 0),
|
||||
errorMessage: String(value.errorMessage || '').trim(),
|
||||
requestId: String(value.requestId || '').trim(),
|
||||
aldsType: Number(value.aldsType || 0) || null,
|
||||
updatedAt: value.updatedAt || null,
|
||||
}
|
||||
}
|
||||
|
||||
function mapRedeemResolutionContext(value) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return null
|
||||
|
||||
Reference in New Issue
Block a user