# Mini Program Analytics Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add mini program behavior analytics collection, backend event storage and aggregation, and a web-admin behavior analytics dashboard. **Architecture:** The mini program queues analytics events locally and flushes them to `POST /analytics/events/batch`. `backend-single` stores raw events in `t_analytics_event` and exposes admin-only aggregation endpoints under `/admin/analytics`. `web-admin` renders overview, trend, funnel, preference, and top-event stats using Element Plus and ECharts. **Tech Stack:** Spring Boot 2.7, MyBatis Plus, MySQL JSON, Java 17, uni-app/Vue, Vue 3, Element Plus, ECharts. --- ## File Structure - Create `sql/2026-05-17-analytics-event.sql`: migration for `t_analytics_event`. - Create `backend-single/src/main/java/com/emotion/entity/AnalyticsEvent.java`: MyBatis Plus entity. - Create `backend-single/src/main/java/com/emotion/mapper/AnalyticsEventMapper.java`: mapper plus aggregation SQL. - Create `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsEventBatchRequest.java`: mini program batch request. - Create `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsEventRequest.java`: one event payload. - Create `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsQueryRequest.java`: admin query filters. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsBatchResponse.java`: accepted/rejected counts. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsOverviewResponse.java`: overview cards. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsTrendItem.java`: trend item. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsFunnelItem.java`: funnel item. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsPreferenceItem.java`: preference item. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsTopEventItem.java`: top event item. - Create `backend-single/src/main/java/com/emotion/dto/response/analytics/AnalyticsUserItem.java`: user ranking item. - Create `backend-single/src/main/java/com/emotion/service/AnalyticsService.java`: write and aggregate contract. - Create `backend-single/src/main/java/com/emotion/service/impl/AnalyticsServiceImpl.java`: validation, persistence, aggregation. - Create `backend-single/src/main/java/com/emotion/controller/AnalyticsController.java`: mini program write endpoint. - Create `backend-single/src/main/java/com/emotion/controller/AdminAnalyticsController.java`: admin read endpoints. - Modify `backend-single/src/main/java/com/emotion/config/WebMvcConfig.java`: allow anonymous analytics batch endpoint while keeping admin analytics protected. - Create `backend-single/src/test/java/com/emotion/service/AnalyticsServiceTest.java`: service validation and aggregation tests. - Create `backend-single/src/test/java/com/emotion/controller/AnalyticsControllerTest.java`: endpoint tests. - Create `mini-program/src/services/analytics.js`: client SDK. - Modify `mini-program/src/App.vue`: initialize and flush analytics. - Modify `mini-program/src/pages/main/index.vue`: track main tab/page activity if this page owns tab switching. - Modify `mini-program/src/pages/main/ScriptView.vue`: track script list, inspiration, generation events. - Modify `mini-program/src/pages/main/ScriptDetailView.vue`: track detail view and path mapping. - Modify `mini-program/src/pages/life-event/form.vue`: track life event create/update and AI assist. - Modify `mini-program/src/pages/life-event/detail.vue`: track detail, favorite, share. - Create `web-admin/src/api/analytics.ts`: admin analytics API client and types. - Create `web-admin/src/views/analytics/AnalyticsDashboard.vue`: dashboard page. - Modify `web-admin/src/router/index.ts`: add `/analytics`. - Modify `web-admin/src/config/menu.ts`: add menu item. ## Event Naming Contract Use lowercase snake case only. - `app_launch`, `app_show`, `app_hide` - `page_view`, `page_leave` - `profile_complete` - `life_event_create`, `life_event_update`, `life_event_ai_assist`, `life_event_favorite`, `life_event_share` - `script_inspiration_view`, `script_inspiration_click`, `script_generate_start`, `script_generate_success`, `script_generate_fail` - `script_detail_view`, `path_select` - `script_tts_request`, `script_tts_play`, `script_tts_pause`, `script_tts_complete`, `script_tts_error` Do not put full user-created text in `properties`. --- ### Task 1: Add Analytics SQL Migration **Files:** - Create: `sql/2026-05-17-analytics-event.sql` - [ ] **Step 1: Create the migration** ```sql CREATE TABLE IF NOT EXISTS t_analytics_event ( id VARCHAR(64) PRIMARY KEY COMMENT 'Primary key', user_id VARCHAR(64) NULL COMMENT 'Logged-in user id', anonymous_id VARCHAR(128) NULL COMMENT 'Anonymous client id', session_id VARCHAR(128) NOT NULL COMMENT 'Client session id', event_name VARCHAR(100) NOT NULL COMMENT 'Event name', event_type VARCHAR(50) NOT NULL COMMENT 'Event category', page_path VARCHAR(255) NULL COMMENT 'Current page path', referrer_path VARCHAR(255) NULL COMMENT 'Referrer page path', properties JSON NULL COMMENT 'Business metadata', device_info JSON NULL COMMENT 'Device metadata', duration_ms BIGINT NULL COMMENT 'Duration in milliseconds', occurred_at DATETIME NOT NULL COMMENT 'Client occurrence time', server_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Server receive time', create_by VARCHAR(64) NULL COMMENT 'Creator', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'Create time', update_by VARCHAR(64) NULL COMMENT 'Updater', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time', is_deleted TINYINT DEFAULT 0 COMMENT 'Logic delete flag', remarks VARCHAR(500) NULL COMMENT 'Remarks' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Analytics event table'; CREATE INDEX idx_analytics_event_name ON t_analytics_event (event_name); CREATE INDEX idx_analytics_event_type ON t_analytics_event (event_type); CREATE INDEX idx_analytics_event_user_id ON t_analytics_event (user_id); CREATE INDEX idx_analytics_event_anonymous_id ON t_analytics_event (anonymous_id); CREATE INDEX idx_analytics_event_occurred_at ON t_analytics_event (occurred_at); CREATE INDEX idx_analytics_event_name_time ON t_analytics_event (event_name, occurred_at); CREATE INDEX idx_analytics_event_user_time ON t_analytics_event (user_id, occurred_at); ``` - [ ] **Step 2: Verify SQL syntax locally** Run: ```bash mysql --version ``` Expected: MySQL client version prints. If no client is installed, skip execution and rely on review plus deployment migration. - [ ] **Step 3: Commit** ```bash git add sql/2026-05-17-analytics-event.sql git commit -m "feat: add analytics event table" ``` --- ### Task 2: Add Backend DTOs and Entity **Files:** - Create: `backend-single/src/main/java/com/emotion/entity/AnalyticsEvent.java` - Create: `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsEventBatchRequest.java` - Create: `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsEventRequest.java` - Create: `backend-single/src/main/java/com/emotion/dto/request/analytics/AnalyticsQueryRequest.java` - Create: response DTO files under `backend-single/src/main/java/com/emotion/dto/response/analytics/` - [ ] **Step 1: Add the entity** ```java package com.emotion.entity; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.emotion.common.BaseEntity; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import lombok.experimental.SuperBuilder; import java.time.LocalDateTime; import java.util.Map; @Data @EqualsAndHashCode(callSuper = true) @SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName(value = "t_analytics_event", autoResultMap = true) public class AnalyticsEvent extends BaseEntity { @TableField("user_id") private String userId; @TableField("anonymous_id") private String anonymousId; @TableField("session_id") private String sessionId; @TableField("event_name") private String eventName; @TableField("event_type") private String eventType; @TableField("page_path") private String pagePath; @TableField("referrer_path") private String referrerPath; @TableField(value = "properties", typeHandler = JacksonTypeHandler.class) private Map properties; @TableField(value = "device_info", typeHandler = JacksonTypeHandler.class) private Map deviceInfo; @TableField("duration_ms") private Long durationMs; @TableField("occurred_at") private LocalDateTime occurredAt; @TableField("server_time") private LocalDateTime serverTime; } ``` - [ ] **Step 2: Add request DTOs** ```java package com.emotion.dto.request.analytics; import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.Size; import java.util.List; import java.util.Map; @Data public class AnalyticsEventBatchRequest { @Size(max = 128) private String anonymousId; @NotBlank @Size(max = 128) private String sessionId; private Map deviceInfo; @Valid @NotEmpty @Size(max = 50) private List events; } ``` ```java package com.emotion.dto.request.analytics; import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; import java.time.LocalDateTime; import java.util.Map; @Data public class AnalyticsEventRequest { @NotBlank @Size(max = 100) private String eventName; @NotBlank @Size(max = 50) private String eventType; @Size(max = 255) private String pagePath; @Size(max = 255) private String referrerPath; private Map properties; private Long durationMs; private LocalDateTime occurredAt; } ``` ```java package com.emotion.dto.request.analytics; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDate; import java.util.List; @Data public class AnalyticsQueryRequest { @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate startDate; @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate endDate; private String granularity; private List eventNames; private String eventType; private Integer limit; } ``` - [ ] **Step 3: Add response DTOs** ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsBatchResponse { private int accepted; private int rejected; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsOverviewResponse { private long pv; private long uv; private long eventCount; private long activeUsers; private long ttsRequests; private long ttsPlays; private double avgStayMs; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsTrendItem { private String bucket; private String eventName; private long count; private long users; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsFunnelItem { private String eventName; private String label; private long users; private double conversionRate; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsPreferenceItem { private String dimension; private String value; private long count; private long users; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; @Data @Builder public class AnalyticsTopEventItem { private String eventName; private String eventType; private long count; private long users; } ``` ```java package com.emotion.dto.response.analytics; import lombok.Builder; import lombok.Data; import java.time.LocalDateTime; @Data @Builder public class AnalyticsUserItem { private String userId; private String anonymousId; private long eventCount; private LocalDateTime lastActiveTime; } ``` - [ ] **Step 4: Run compile** Run: ```bash cd backend-single mvn -DskipTests compile ``` Expected: `BUILD SUCCESS`. - [ ] **Step 5: Commit** ```bash git add backend-single/src/main/java/com/emotion/entity/AnalyticsEvent.java backend-single/src/main/java/com/emotion/dto/request/analytics backend-single/src/main/java/com/emotion/dto/response/analytics git commit -m "feat: add analytics DTOs and entity" ``` --- ### Task 3: Add Analytics Mapper and Service **Files:** - Create: `backend-single/src/main/java/com/emotion/mapper/AnalyticsEventMapper.java` - Create: `backend-single/src/main/java/com/emotion/service/AnalyticsService.java` - Create: `backend-single/src/main/java/com/emotion/service/impl/AnalyticsServiceImpl.java` - Test: `backend-single/src/test/java/com/emotion/service/AnalyticsServiceTest.java` - [ ] **Step 1: Write service tests** ```java package com.emotion.service; import com.emotion.dto.request.analytics.AnalyticsEventBatchRequest; import com.emotion.dto.request.analytics.AnalyticsEventRequest; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; public class AnalyticsServiceTest { @Test void safeEventNameAllowsSnakeCase() { assertTrue(com.emotion.service.impl.AnalyticsServiceImpl.isSafeEventName("script_generate_success")); } @Test void safeEventNameRejectsUnsafeCharacters() { assertFalse(com.emotion.service.impl.AnalyticsServiceImpl.isSafeEventName("script.generate")); assertFalse(com.emotion.service.impl.AnalyticsServiceImpl.isSafeEventName(" ``` - [ ] **Step 3: Add route** Add under the `Layout` children group in `web-admin/src/router/index.ts`: ```typescript { path: 'analytics', name: 'AnalyticsDashboard', component: () => import('@/views/analytics/AnalyticsDashboard.vue'), meta: { title: '行为分析', icon: 'TrendCharts' } } ``` - [ ] **Step 4: Add menu** Add to `menuConfig`: ```typescript { path: '/analytics', title: '行为分析', icon: 'TrendCharts' } ``` - [ ] **Step 5: Build admin** Run: ```bash cd web-admin npm run build ``` Expected: `vue-tsc` and Vite build succeed. - [ ] **Step 6: Commit** ```bash git add web-admin/src/api/analytics.ts web-admin/src/views/analytics/AnalyticsDashboard.vue web-admin/src/router/index.ts web-admin/src/config/menu.ts git commit -m "feat: add analytics admin dashboard" ``` --- ### Task 8: Final Verification for Analytics **Files:** - No code changes unless verification finds bugs. - [ ] **Step 1: Run backend tests** ```bash cd backend-single mvn test ``` Expected: `BUILD SUCCESS`. - [ ] **Step 2: Run admin build** ```bash cd web-admin npm run build ``` Expected: build succeeds. - [ ] **Step 3: Run mini program build** ```bash cd mini-program npm run build:mp-weixin:test ``` Expected: build succeeds. - [ ] **Step 4: Manual API smoke test** With backend running locally, send: ```bash curl -X POST http://localhost:19089/analytics/events/batch ^ -H "Content-Type: application/json" ^ -d "{\"anonymousId\":\"anon_smoke\",\"sessionId\":\"session_smoke\",\"deviceInfo\":{\"platform\":\"dev\"},\"events\":[{\"eventName\":\"page_view\",\"eventType\":\"page\",\"pagePath\":\"/pages/main/index\",\"occurredAt\":\"2026-05-17 10:00:00\"}]}" ``` Expected response includes `"accepted":1`. - [ ] **Step 5: Commit fixes if needed** ```bash git add git commit -m "fix: stabilize analytics verification" ``` Only run this commit if verification required changes. ## Self-Review - Spec coverage: event storage, anonymous reporting, admin aggregation, mini program SDK, privacy filtering, and dashboard are covered. - Placeholder scan: no task uses TBD/TODO as an instruction. - Type consistency: event field names use `eventName`, `eventType`, `durationMs`, and `occurredAt` consistently across mini program and backend.