瀏覽代碼

feat: 添加独立测试数据初始化脚本和接口

- 新增 seed-test-data.sql: 3 活动 + 3 文章 + 2 商品,所有价格 ≤ 0.1 元
- 新增 SeedsController.java: POST /api/admin/seeds/init-test-data,需 admin 权限
- 幂等执行: INSERT IGNORE 防止重复插入
- 已回滚 DatabaseInitializer.java 中的临时种子数据代码
iwt 1 月之前
父節點
當前提交
745c62b84f

+ 96 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/config/SeedsController.java

@@ -0,0 +1,96 @@
+package com.etotem.cfc.controller.config;
+
+import com.etotem.cfc.common.Result;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 测试数据初始化接口 —— 仅限开发/测试环境使用
+ * 调用方式: POST /api/admin/seeds/init-test-data
+ * 需 role=admin 权限
+ */
+@RestController
+@RequestMapping("/api/admin/seeds")
+public class SeedsController {
+
+    @Resource
+    private JdbcTemplate jdbcTemplate;
+
+    @PostMapping("init-test-data")
+    public Result<String> initTestData(HttpServletRequest request) {
+        // 权限检查:仅 role=admin 可调用
+        String role = (String) request.getAttribute("role");
+        if (role == null || !"admin".equals(role)) {
+            return Result.error(403, "仅限管理员角色调用");
+        }
+
+        try {
+            int inserted = executeSeedData();
+            return Result.success(String.format("测试数据初始化完成:活动 %d 条,文章 %d 条,商品 %d 条",
+                inserted / 3, inserted / 3, inserted / 3));
+        } catch (Exception e) {
+            return Result.error("初始化失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 读取 classpath 下的 seed-test-data.sql 并执行所有 INSERT 语句
+     */
+    private int executeSeedData() {
+        String sqlContent = loadResource("/seed-test-data.sql");
+        // 按 ; 分割 SQL 语句,去除注释和空行
+        List<String> statements = splitStatements(sqlContent);
+
+        int count = 0;
+        for (String stmt : statements) {
+            stmt = stmt.trim();
+            if (stmt.isEmpty() || stmt.startsWith("--") || stmt.startsWith("/*")) {
+                continue;
+            }
+            // 只执行 INSERT 语句(避免执行最后的 SELECT UNION ALL)
+            if (stmt.trim().toUpperCase().startsWith("INSERT")) {
+                jdbcTemplate.update(stmt);
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 从类路径加载资源文件
+     */
+    private String loadResource(String path) {
+        try (InputStream is = getClass().getResourceAsStream(path);
+             java.util.Scanner scanner = new java.util(is, StandardCharsets.UTF_8.name())) {
+            scanner.useDelimiter("\\A");
+            return scanner.hasNext() ? scanner.next() : "";
+        } catch (IOException e) {
+            throw new RuntimeException("加载资源文件失败: " + path, e);
+        }
+    }
+
+    /**
+     * 按 ; 分割 SQL 语句(本脚本中字符串内无分号,简单分割即可)
+     */
+    private List<String> splitStatements(String sql) {
+        List<String> statements = new ArrayList<>();
+        String[] rawParts = sql.split(";");
+        for (String part : rawParts) {
+            part = part.trim();
+            if (!part.isEmpty()) {
+                statements.add(part);
+            }
+        }
+        return statements;
+    }
+}

+ 86 - 0
cfc-backend/src/main/resources/seed-test-data.sql

@@ -0,0 +1,86 @@
+-- ==========================================================
+-- 测试种子数据:活动 / 阅读 / 商品
+-- 价格均不超过 0.1 元(≤ 10 分),适合测试环境使用
+-- 使用方法: mysql -u root -p cfc < seed-test-data.sql
+-- 幂等: INSERT IGNORE / ON DUPLICATE KEY 避免重复插入
+-- ==========================================================
+
+-- ==========================================================
+-- 1. 活动(activities)—— 身 / 心 / 智
+-- ==========================================================
+
+INSERT IGNORE INTO activities (title, description, dimension_code, dimension_weights, activity_type, status, start_time, end_time, location, max_participants, current_participants, price, member_price, require_registration, checkin_points, created_at, updated_at)
+VALUES
+('亲子户外徒步探险',
+ '周末亲子徒步活动,在自然中锻炼体魄、增进亲子关系,全程约5公里。',
+ 'body',
+ '{"body":80,"mind":0,"wisdom":0,"action":20,"wealth":0}',
+ 'offline', 'published',
+ '2026-08-15 09:00:00', '2026-08-15 16:00:00',
+ '北京奥林匹克森林公园', 50, 0, 0, 0, 1, 10, NOW(), NOW()),
+
+('家庭正念静心工作坊',
+ '专业导师带领的正念冥想体验,学习情绪调节和家庭沟通技巧。',
+ 'mind',
+ '{"body":0,"mind":80,"wisdom":10,"action":10,"wealth":0}',
+ 'offline', 'published',
+ '2026-08-22 14:00:00', '2026-08-22 17:00:00',
+ '北京市朝阳区心理成长中心', 30, 0, 10, 5, 1, 10, NOW(), NOW()),
+
+('少儿科学思维探索营',
+ '通过动手实验激发孩子对科学的兴趣,培养逻辑思维与创造力。',
+ 'wisdom',
+ '{"body":0,"mind":0,"wisdom":70,"action":30,"wealth":0}',
+ 'online', 'published',
+ '2026-09-05 10:00:00', '2026-09-05 12:00:00',
+ '线上直播', 200, 0, 10, 5, 1, 10, NOW(), NOW());
+
+-- ==========================================================
+-- 2. 文章(articles)—— 学习力 / 情绪管理 / 亲子教育
+-- ==========================================================
+
+INSERT IGNORE INTO articles (title, summary, content, content_type, category_id, related_dimensions, dimension_weights, status, author, article_type, read_time, word_count, is_featured, view_count, author_type, audit_status, visibility, created_at, updated_at)
+VALUES
+('如何培养孩子的自主学习力',
+ '从内在动机出发,帮助孩子建立自主学习习惯的实用方法指南。',
+ '(测试数据:本文为示例文章正文,实际数据应在运营后台编辑发布。)',
+ 'article', 4, '["wisdom"]',
+ '{"body":0,"mind":0,"wisdom":60,"action":40,"wealth":0}',
+ 'published', '曹老师', 'normal', 5, 1800, 1, 0, 'admin', 'approved', 'public', NOW(), NOW()),
+
+('儿童情绪管理的五个关键步骤',
+ '当孩子情绪爆发时,家长应该如何应对?本文提供一套可操作的情绪管理框架。',
+ '(测试数据:本文为示例文章正文,实际数据应在运营后台编辑发布。)',
+ 'article', 2, '["mind"]',
+ '{"body":0,"mind":80,"wisdom":10,"action":10,"wealth":0}',
+ 'published', '李老师', 'premium', 4, 2200, 1, 0, 'admin', 'approved', 'public', NOW(), NOW()),
+
+('亲子运动的正确打开方式',
+ '不同年龄段孩子的运动建议,让全家人在快乐中收获健康。',
+ '(测试数据:本文为示例文章正文,实际数据应在运营后台编辑发布。)',
+ 'article', 3, '["action"]',
+ '{"body":40,"mind":0,"wisdom":0,"action":60,"wealth":0}',
+ 'published', '王老师', 'normal', 3, 1500, 1, 0, 'admin', 'approved', 'public', NOW(), NOW());
+
+-- ==========================================================
+-- 3. 商品(products)—— 智 / 行
+-- ==========================================================
+
+INSERT IGNORE INTO products (name, description, price, product_type, vendor_id, vendor_name, status, domain, member_eligible, member_price, stock, sales_count, created_at, updated_at)
+VALUES
+('少儿专注力训练教具盒',
+ '含舒尔特方格、记忆卡片等8种专注力训练工具,适合5-12岁儿童。',
+ 10, 'physical', NULL, '平台官方', 'on_shelf', 'wisdom', 'all', 5, 999, 0, NOW(), NOW()),
+
+('家庭年度VIP成长会员',
+ '体验版会员,畅享线上课程与活动折扣。',
+ 10, 'digital', NULL, '平台官方', 'on_shelf', 'action', 'all', 5, 9999, 0, NOW(), NOW());
+
+-- ==========================================================
+-- 验证插入结果
+-- ==========================================================
+SELECT '[活动]' AS `维度`, COUNT(*) AS `记录数` FROM activities WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE)
+UNION ALL
+SELECT '[文章]', COUNT(*) FROM articles WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE)
+UNION ALL
+SELECT '[商品]', COUNT(*) FROM products WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE);