From 89fd333702d0d620c434f1df1637f5b1db0d384e Mon Sep 17 00:00:00 2001 From: jacy <18049821889@163.com> Date: Mon, 3 Aug 2026 22:30:02 +0800 Subject: [PATCH] feat(analytics): persona logging upgrade and Sentry system config Add client-logging SDK, expanded event taxonomy, API observability, admin domain events UI, and move SENTRY_DSN to HQ system settings with @sentry/node bootstrap after config preload. Co-authored-by: Cursor --- apps/admin-web/src/App.tsx | 2 + apps/admin-web/src/layouts/AdminLayout.tsx | 2 + apps/admin-web/src/lib/user-log.ts | 51 +-- apps/admin-web/src/pages/DomainEventsPage.tsx | 127 ++++++++ apps/admin-web/src/pages/UsersPage.tsx | 41 ++- apps/h5-partner/package.json | 1 + apps/h5-partner/src/lib/analytics.ts | 16 + apps/h5-partner/src/lib/usePageView.ts | 11 + apps/h5-partner/src/main.tsx | 9 +- apps/h5-partner/src/pages/BillsPage.tsx | 2 + apps/h5-partner/src/pages/HomePage.tsx | 2 + apps/h5-partner/src/pages/LeaderboardPage.tsx | 2 + apps/h5-partner/src/pages/OrderDetailPage.tsx | 2 + apps/h5-partner/src/pages/OrderListPage.tsx | 2 + apps/h5-partner/src/pages/ProxyOrderPage.tsx | 2 + apps/h5-partner/src/pages/StaffListPage.tsx | 2 + apps/h5-partner/src/pages/StoreCreatePage.tsx | 3 + apps/h5-partner/src/pages/StoreDetailPage.tsx | 2 + apps/h5-partner/src/pages/StoreListPage.tsx | 2 + .../h5-partner/src/pages/WeeklyReportPage.tsx | 2 + apps/h5-shop/package.json | 1 + apps/h5-shop/src/lib/analytics.ts | 26 ++ apps/h5-shop/src/lib/usePageView.ts | 11 + apps/h5-shop/src/main.tsx | 9 +- apps/h5-shop/src/pages/HomePage.tsx | 4 + apps/h5-shop/src/pages/MinePage.tsx | 2 + apps/h5-shop/src/pages/PackagesPage.tsx | 2 + apps/h5-shop/src/pages/PhoneRedeemPage.tsx | 2 + apps/h5-shop/src/pages/RecordsPage.tsx | 2 + apps/h5-shop/src/pages/RedeemConfirmPage.tsx | 2 + apps/h5-shop/src/pages/StaffPage.tsx | 2 + apps/h5-shop/src/pages/WithdrawPage.tsx | 2 + apps/h5-user/package.json | 1 + apps/h5-user/src/lib/analytics.ts | 29 +- apps/h5-user/src/lib/usePageView.ts | 11 + apps/h5-user/src/main.tsx | 6 + apps/h5-user/src/pages/AddressEditPage.tsx | 2 + apps/h5-user/src/pages/AddressListPage.tsx | 2 + apps/h5-user/src/pages/AfterSaleListPage.tsx | 2 + apps/h5-user/src/pages/AfterSalePage.tsx | 3 + apps/h5-user/src/pages/BenefitDetailPage.tsx | 2 + apps/h5-user/src/pages/BenefitPage.tsx | 2 + apps/h5-user/src/pages/OrderDetailPage.tsx | 2 + apps/h5-user/src/pages/OrderListPage.tsx | 2 + apps/h5-user/src/pages/PayPage.tsx | 8 + apps/h5-user/src/pages/ProductDetailPage.tsx | 5 +- apps/h5-user/src/pages/RedeemCodePage.tsx | 2 + apps/h5-user/src/pages/RedeemPage.tsx | 2 + apps/h5-user/src/pages/RedeemSuccessPage.tsx | 2 + apps/mini-user/package.json | 1 + apps/mini-user/src/lib/analytics.ts | 22 ++ apps/mini-user/src/lib/usePageView.ts | 11 + apps/mini-user/src/pages/home/index.tsx | 5 + .../src/pages/order-detail/index.tsx | 2 + apps/mini-user/src/pages/orders/index.tsx | 2 + .../src/pages/product-detail/index.tsx | 5 + packages/client-logging/package.json | 22 ++ packages/client-logging/src/analytics.ts | 121 ++++++++ packages/client-logging/src/client-error.ts | 93 ++++++ packages/client-logging/src/index.ts | 17 + packages/client-logging/src/session.ts | 18 ++ packages/client-logging/tsconfig.json | 10 + packages/shared-types/src/partner-log.ts | 29 +- packages/shared-types/src/store-log.ts | 50 ++- packages/shared-types/src/user-log.ts | 58 +++- pnpm-lock.yaml | 292 ++++++++++++++++++ server/dukang-api/.env.example | 2 + server/dukang-api/package.json | 1 + server/dukang-api/src/app.module.ts | 11 +- .../hq-operation/hq-operation.interceptor.ts | 72 +++-- .../src/common/logging/logging.interceptor.ts | 55 ++++ .../src/common/logging/logging.module.ts | 9 + .../common/logging/request-id.middleware.ts | 21 ++ .../system-config/system-config.registry.ts | 9 + .../integrations/sentry/sentry.bootstrap.ts | 14 + server/dukang-api/src/main.ts | 5 +- .../modules/analytics/analytics.controller.ts | 83 ++++- .../src/modules/analytics/analytics.module.ts | 7 +- .../modules/analytics/analytics.service.ts | 142 ++++++++- .../src/modules/analytics/dto/promo.dto.ts | 4 + .../modules/analytics/dto/track-events.dto.ts | 56 ++++ .../src/modules/iam/auth.service.ts | 11 + .../ops/admin-domain-events.controller.ts | 36 +++ .../ops/admin-domain-events.service.ts | 59 ++++ .../src/modules/ops/admin-stores.service.ts | 15 + .../dukang-api/src/modules/ops/ops.module.ts | 7 +- .../src/modules/redeem/redeem.service.ts | 11 + .../settlement/settlement.controller.ts | 5 + .../modules/settlement/settlement.service.ts | 16 + .../src/modules/trade/trade.service.ts | 15 +- 杜康好客-v3-埋点规范.md | 86 ++++++ 91 files changed, 1805 insertions(+), 136 deletions(-) create mode 100644 apps/admin-web/src/pages/DomainEventsPage.tsx create mode 100644 apps/h5-partner/src/lib/analytics.ts create mode 100644 apps/h5-partner/src/lib/usePageView.ts create mode 100644 apps/h5-shop/src/lib/analytics.ts create mode 100644 apps/h5-shop/src/lib/usePageView.ts create mode 100644 apps/h5-user/src/lib/usePageView.ts create mode 100644 apps/mini-user/src/lib/analytics.ts create mode 100644 apps/mini-user/src/lib/usePageView.ts create mode 100644 packages/client-logging/package.json create mode 100644 packages/client-logging/src/analytics.ts create mode 100644 packages/client-logging/src/client-error.ts create mode 100644 packages/client-logging/src/index.ts create mode 100644 packages/client-logging/src/session.ts create mode 100644 packages/client-logging/tsconfig.json create mode 100644 server/dukang-api/src/common/logging/logging.interceptor.ts create mode 100644 server/dukang-api/src/common/logging/logging.module.ts create mode 100644 server/dukang-api/src/common/logging/request-id.middleware.ts create mode 100644 server/dukang-api/src/integrations/sentry/sentry.bootstrap.ts create mode 100644 server/dukang-api/src/modules/analytics/dto/track-events.dto.ts create mode 100644 server/dukang-api/src/modules/ops/admin-domain-events.controller.ts create mode 100644 server/dukang-api/src/modules/ops/admin-domain-events.service.ts create mode 100644 杜康好客-v3-埋点规范.md diff --git a/apps/admin-web/src/App.tsx b/apps/admin-web/src/App.tsx index 49051bd..da8497b 100644 --- a/apps/admin-web/src/App.tsx +++ b/apps/admin-web/src/App.tsx @@ -38,6 +38,7 @@ import TicketsPage from './pages/TicketsPage'; import SupportTicketsPage from './pages/SupportTicketsPage'; import InvoicesPage from './pages/InvoicesPage'; import UserLogsPage from './pages/UserLogsPage'; +import DomainEventsPage from './pages/DomainEventsPage'; import HqLogsPage from './pages/HqLogsPage'; import ThirdPartyLogsPage from './pages/ThirdPartyLogsPage'; import StoreLogsPage from './pages/StoreLogsPage'; @@ -120,6 +121,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/apps/admin-web/src/layouts/AdminLayout.tsx b/apps/admin-web/src/layouts/AdminLayout.tsx index b9a8d12..c7d0fac 100644 --- a/apps/admin-web/src/layouts/AdminLayout.tsx +++ b/apps/admin-web/src/layouts/AdminLayout.tsx @@ -122,6 +122,7 @@ const MENU_ITEMS: MenuProps['items'] = [ { key: '/logs/partners', label: '合伙人日志' }, { key: '/logs/hq', label: 'HQ 操作日志' }, { key: '/logs/third-party', label: '第三方日志' }, + { key: '/logs/domain-events', label: '领域事件' }, ], }, { key: '/hq-permissions', icon: , label: '权限分配' }, @@ -182,6 +183,7 @@ function menuAllowed(key: string, permissionKeys: string[]): boolean { '/logs/partners': 'logs', '/logs/hq': 'logs', '/logs/third-party': 'logs', + '/logs/domain-events': 'logs', '/hq-permissions': 'hq_permissions', '/system-settings': 'system_settings_any', '/hq-accounts': 'hq_accounts', diff --git a/apps/admin-web/src/lib/user-log.ts b/apps/admin-web/src/lib/user-log.ts index 9114728..3a8c6e9 100644 --- a/apps/admin-web/src/lib/user-log.ts +++ b/apps/admin-web/src/lib/user-log.ts @@ -1,45 +1,12 @@ -export type UserLogCategory = - | 'login' - | 'browse_product' - | 'order' - | 'pay' - | 'wechat_auth' - | 'browse_store' - | 'redeem' - | 'profile'; +export { + USER_LOG_CATEGORY_OPTIONS, + resolveUserLogCategory, + eventNamesForUserLogCategory, + type UserLogCategory, +} from '@dukang/shared-types'; -const USER_LOG_EVENT_CATEGORIES: Record = { - login: ['login_success', 'sms_login', 'sms_send', 'sms_verify_fail'], - browse_product: ['home_view', 'product_click', 'product_detail_view'], - order: ['order_confirm_view', 'order_submit'], - pay: ['pay_success', 'pay_fail'], - wechat_auth: ['wechat_login', 'wechat_phone', 'wechat_location', 'wechat_album'], - browse_store: ['store_list_view', 'store_detail_view'], - redeem: ['benefit_redeem_start', 'benefit_redeem_success'], - profile: ['profile_update', 'bind_phone'], -}; +import { USER_LOG_CATEGORY_OPTIONS } from '@dukang/shared-types'; -export const USER_LOG_CATEGORY_OPTIONS: Array<{ value: UserLogCategory | ''; label: string }> = [ - { value: '', label: '全部' }, - { value: 'login', label: '登录' }, - { value: 'browse_product', label: '浏览商品' }, - { value: 'order', label: '下单' }, - { value: 'pay', label: '支付' }, - { value: 'wechat_auth', label: '微信授权' }, - { value: 'browse_store', label: '浏览门店' }, - { value: 'redeem', label: '核销' }, - { value: 'profile', label: '信息修改' }, -]; - -export function resolveUserLogCategory(eventName: string): UserLogCategory | null { - for (const [category, events] of Object.entries(USER_LOG_EVENT_CATEGORIES) as Array< - [UserLogCategory, readonly string[]] - >) { - if (events.includes(eventName)) return category; - } - return null; -} - -export const USER_LOG_CATEGORY_LABELS = Object.fromEntries( +export const USER_LOG_CATEGORY_LABELS: Record = Object.fromEntries( USER_LOG_CATEGORY_OPTIONS.filter((o) => o.value).map((o) => [o.value, o.label]), -) as Record; +); diff --git a/apps/admin-web/src/pages/DomainEventsPage.tsx b/apps/admin-web/src/pages/DomainEventsPage.tsx new file mode 100644 index 0000000..4e663cf --- /dev/null +++ b/apps/admin-web/src/pages/DomainEventsPage.tsx @@ -0,0 +1,127 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { Card, Descriptions, Drawer, Select, Space, Table, Tag } from 'antd'; +import type { ColumnsType } from 'antd/es/table'; +import { request } from '../lib/api'; +import { fmtTime } from '../lib/constants'; + +type Row = { + id: string; + eventType: string; + refType: string; + refId: string; + status?: string | null; + param1?: string | null; + param2?: string | null; + param3?: string | null; + remark?: string | null; + createdAt: string; +}; + +const EVENT_OPTIONS = [ + { value: '', label: '全部领域事件' }, + { value: 'ORDER_STATUS', label: '订单状态' }, + { value: 'BENEFIT_LEDGER', label: '权益流水' }, + { value: 'STORE_AUDIT', label: '门店审核' }, + { value: 'TICKET_COLLAB', label: '工单协作' }, + { value: 'PROMO_TOUCH', label: '推广触达' }, +]; + +export default function DomainEventsPage() { + const [params, setParams] = useSearchParams(); + const eventType = params.get('eventType') ?? ''; + const refType = params.get('refType') ?? ''; + const refId = params.get('refId') ?? ''; + const [data, setData] = useState<{ items: Row[]; total: number; page: number; pageSize: number } | null>(null); + const [loading, setLoading] = useState(false); + const [detail, setDetail] = useState(null); + + const load = useCallback(() => { + setLoading(true); + const q = new URLSearchParams(); + if (eventType) q.set('eventType', eventType); + if (refType) q.set('refType', refType); + if (refId) q.set('refId', refId); + q.set('page', params.get('page') ?? '1'); + q.set('pageSize', '20'); + request<{ items: Row[]; total: number; page: number; pageSize: number }>( + `/admin/logs/domain-events?${q.toString()}`, + ) + .then(setData) + .finally(() => setLoading(false)); + }, [eventType, refType, refId, params]); + + useEffect(() => { + load(); + }, [load]); + + const columns: ColumnsType = [ + { title: '时间', dataIndex: 'createdAt', width: 170, render: (v) => fmtTime(v) }, + { title: '类型', dataIndex: 'eventType', width: 130, render: (v) => {v} }, + { title: '关联', render: (_, r) => `${r.refType} #${r.refId}` }, + { title: '状态', dataIndex: 'status', width: 100 }, + { title: '摘要', render: (_, r) => r.param1 || r.remark || '—' }, + { + title: '操作', + width: 80, + render: (_, r) => ( + { + setDetail(await request(`/admin/logs/domain-events/${r.id}`)); + }} + > + 详情 + + ), + }, + ]; + + return ( + + +