DatabaseInitializer.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. package com.zxyj.config;
  2. import com.zxyj.mapper.*;
  3. import lombok.extern.slf4j.Slf4j;
  4. import javax.annotation.Resource;
  5. import org.springframework.boot.CommandLineRunner;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.stereotype.Component;
  8. import java.util.Arrays;
  9. import java.util.List;
  10. @Slf4j
  11. @Component
  12. public class DatabaseInitializer implements CommandLineRunner {
  13. @Resource
  14. private JdbcTemplate jdbcTemplate;
  15. @Override
  16. public void run(String... args) throws Exception {
  17. log.info("开始初始化数据库...");
  18. // 1. 检查并创建数据库
  19. initializeDatabase();
  20. // 2. 检查并创建表
  21. initializeTables();
  22. // 3. 执行数据库迁移(添加新列)
  23. runMigrations();
  24. // 4. 初始化默认数据
  25. initializeDefaultData();
  26. log.info("数据库初始化完成!");
  27. }
  28. private void initializeDatabase() {
  29. try {
  30. // 尝试连接数据库,如果不存在则创建
  31. jdbcTemplate.execute("CREATE DATABASE IF NOT EXISTS xzyj DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
  32. log.info("数据库检查完成");
  33. } catch (Exception e) {
  34. log.warn("创建数据库失败: {}", e.getMessage());
  35. }
  36. }
  37. private void initializeTables() {
  38. // 表创建SQL - 使用IF NOT EXISTS确保幂等性
  39. List<String> createTableSQLs = Arrays.asList(
  40. // 家庭表
  41. "CREATE TABLE IF NOT EXISTS families (" +
  42. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  43. "name VARCHAR(100), " +
  44. "invite_code VARCHAR(8) UNIQUE, " +
  45. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  46. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  47. "INDEX idx_invite_code (invite_code)" +
  48. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  49. // 用户表
  50. "CREATE TABLE IF NOT EXISTS users (" +
  51. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  52. "openid VARCHAR(64) NOT NULL, " +
  53. "unionid VARCHAR(64), " +
  54. "family_id BIGINT NOT NULL, " +
  55. "role ENUM('parent','child') NOT NULL, " +
  56. "nickname VARCHAR(50), " +
  57. "avatar VARCHAR(255), " +
  58. "password VARCHAR(128), " +
  59. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  60. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  61. "INDEX idx_openid (openid), " +
  62. "INDEX idx_family_id (family_id)" +
  63. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  64. // 孩子详情表
  65. "CREATE TABLE IF NOT EXISTS children (" +
  66. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  67. "user_id BIGINT NOT NULL, " +
  68. "family_id BIGINT NOT NULL, " +
  69. "nickname VARCHAR(50) NOT NULL, " +
  70. "age TINYINT NOT NULL, " +
  71. "dan_level VARCHAR(2), " +
  72. "penalty_enabled TINYINT DEFAULT 1, " +
  73. "theme VARCHAR(32) DEFAULT \'default\', " +
  74. "total_points INT DEFAULT 0, " +
  75. "streak_days INT DEFAULT 0, " +
  76. "last_task_date DATE, " +
  77. "focus_max_daily TINYINT DEFAULT 3, " +
  78. "focus_remaining TINYINT DEFAULT 3, " +
  79. "focus_reset_date DATE, " +
  80. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  81. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  82. "INDEX idx_user_id (user_id), " +
  83. "INDEX idx_family_id (family_id)" +
  84. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  85. // 任务表
  86. "CREATE TABLE IF NOT EXISTS tasks (" +
  87. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  88. "family_id BIGINT NOT NULL, " +
  89. "creator_id BIGINT NOT NULL, " +
  90. "child_id BIGINT NOT NULL, " +
  91. "title VARCHAR(200) NOT NULL, " +
  92. "description VARCHAR(500), " +
  93. "points TINYINT DEFAULT 2, " +
  94. "deadline DATETIME NOT NULL, " +
  95. "repeat_type ENUM('none','daily','weekly') DEFAULT 'none', " +
  96. "category VARCHAR(32), " +
  97. "need_review TINYINT DEFAULT 0, " +
  98. "review_by_category TINYINT DEFAULT 0, " +
  99. "status ENUM('pending','completed','overdue','cancelled') DEFAULT 'pending', " +
  100. "completed_at DATETIME, " +
  101. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  102. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  103. "INDEX idx_child_id_deadline (child_id, deadline), " +
  104. "INDEX idx_status (status), " +
  105. "INDEX idx_category (category)" +
  106. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  107. // 积分流水表
  108. "CREATE TABLE IF NOT EXISTS points_log (" +
  109. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  110. "child_id BIGINT NOT NULL, " +
  111. "task_id BIGINT, " +
  112. "amount INT NOT NULL, " +
  113. "type ENUM('earn','spend','bonus','penalty','adjust','reset','refund') NOT NULL, " +
  114. "description VARCHAR(255), " +
  115. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  116. "INDEX idx_child_id_created (child_id, created_at), " +
  117. "INDEX idx_type (type)" +
  118. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  119. // 积分清零记录表
  120. "CREATE TABLE IF NOT EXISTS points_reset_log (" +
  121. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  122. "child_id BIGINT NOT NULL, " +
  123. "balance_before INT NOT NULL, " +
  124. "balance_after INT DEFAULT 0, " +
  125. "reset_type ENUM('monthly','manual') NOT NULL, " +
  126. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  127. "INDEX idx_child_id (child_id)" +
  128. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  129. // 奖励表
  130. "CREATE TABLE IF NOT EXISTS rewards (" +
  131. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  132. "family_id BIGINT NOT NULL, " +
  133. "child_id BIGINT NOT NULL, " +
  134. "title VARCHAR(100) NOT NULL, " +
  135. "points_required INT NOT NULL, " +
  136. "category VARCHAR(32), " +
  137. "status ENUM('available','pending','exchanged','rejected') DEFAULT 'available', " +
  138. "exchange_approved TINYINT, " +
  139. "exchanged_at DATETIME, " +
  140. "is_template TINYINT DEFAULT 0, " +
  141. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  142. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  143. "INDEX idx_child_id_status (child_id, status), " +
  144. "INDEX idx_is_template (is_template)" +
  145. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  146. // 奖励分类表
  147. "CREATE TABLE IF NOT EXISTS reward_categories (" +
  148. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  149. "family_id BIGINT NOT NULL, " +
  150. "name VARCHAR(32) NOT NULL, " +
  151. "icon VARCHAR(64), " +
  152. "sort_order INT DEFAULT 0, " +
  153. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  154. "INDEX idx_family_id (family_id)" +
  155. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  156. // 敏感词表
  157. "CREATE TABLE IF NOT EXISTS sensitive_words (" +
  158. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  159. "word VARCHAR(50) NOT NULL, " +
  160. "category VARCHAR(32) NOT NULL, " +
  161. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  162. "INDEX idx_word (word)" +
  163. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  164. // 任务审核表
  165. "CREATE TABLE IF NOT EXISTS task_reviews (" +
  166. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  167. "task_id BIGINT NOT NULL, " +
  168. "child_id BIGINT NOT NULL, " +
  169. "photo_url VARCHAR(255), " +
  170. "voice_note_url VARCHAR(255), " +
  171. "ai_result ENUM('pass','review','pending'), " +
  172. "ai_confidence DECIMAL(3,2), " +
  173. "ai_suggestion VARCHAR(255), " +
  174. "ai_threshold ENUM('strict','normal','loose') DEFAULT 'normal', " +
  175. "parent_result ENUM('pass','reject'), " +
  176. "parent_override TINYINT DEFAULT 0, " +
  177. "review_type ENUM('ai','manual') NOT NULL, " +
  178. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  179. "reviewed_at DATETIME, " +
  180. "INDEX idx_task_id (task_id), " +
  181. "INDEX idx_ai_result (ai_result)" +
  182. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  183. // 勋章定义表
  184. "CREATE TABLE IF NOT EXISTS badge_definitions (" +
  185. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  186. "badge_type VARCHAR(32) NOT NULL, " +
  187. "name VARCHAR(50) NOT NULL, " +
  188. "description VARCHAR(200), " +
  189. "icon VARCHAR(64), " +
  190. "condition_type VARCHAR(32) NOT NULL, " +
  191. "condition_value INT NOT NULL, " +
  192. "reward_points INT DEFAULT 0, " +
  193. "INDEX idx_badge_type (badge_type)" +
  194. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  195. // 勋章表
  196. "CREATE TABLE IF NOT EXISTS badges (" +
  197. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  198. "child_id BIGINT NOT NULL, " +
  199. "badge_type VARCHAR(32) NOT NULL, " +
  200. "unlocked_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  201. "INDEX idx_child_id_type (child_id, badge_type)" +
  202. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  203. // 打卡里程碑表
  204. "CREATE TABLE IF NOT EXISTS streak_milestones (" +
  205. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  206. "days INT NOT NULL, " +
  207. "reward_points INT DEFAULT 0, " +
  208. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  209. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  210. // 专注训练记录表
  211. "CREATE TABLE IF NOT EXISTS focus_sessions (" +
  212. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  213. "child_id BIGINT NOT NULL, " +
  214. "mode ENUM('simple','pomodoro') NOT NULL, " +
  215. "duration TINYINT NOT NULL, " +
  216. "stars TINYINT, " +
  217. "points_earned INT, " +
  218. "voice_note_url VARCHAR(255), " +
  219. "started_at DATETIME NOT NULL, " +
  220. "completed_at DATETIME, " +
  221. "INDEX idx_child_id_completed (child_id, completed_at)" +
  222. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  223. // 专注训练配置表
  224. "CREATE TABLE IF NOT EXISTS focus_config (" +
  225. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  226. "family_id BIGINT, " +
  227. "max_daily_count TINYINT DEFAULT 3, " +
  228. "allow_exit TINYINT DEFAULT 1, " +
  229. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  230. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  231. // DAN测评表
  232. "CREATE TABLE IF NOT EXISTS dan_assessments (" +
  233. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  234. "child_id BIGINT NOT NULL, " +
  235. "dan_level VARCHAR(2) NOT NULL, " +
  236. "assessment_date DATE NOT NULL, " +
  237. "details JSON, " +
  238. "source ENUM('api','manual') DEFAULT 'manual', " +
  239. "photo_url VARCHAR(255), " +
  240. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  241. "INDEX idx_child_id_level (child_id, dan_level)" +
  242. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  243. // 任务模板表
  244. "CREATE TABLE IF NOT EXISTS task_templates (" +
  245. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  246. "dan_level VARCHAR(2) NOT NULL, " +
  247. "title VARCHAR(200) NOT NULL, " +
  248. "points TINYINT DEFAULT 2, " +
  249. "duration_minutes TINYINT, " +
  250. "category VARCHAR(32), " +
  251. "is_default TINYINT DEFAULT 1, " +
  252. "family_id BIGINT, " +
  253. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  254. "INDEX idx_dan_level (dan_level), " +
  255. "INDEX idx_is_default (is_default)" +
  256. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  257. // 管理员表
  258. "CREATE TABLE IF NOT EXISTS admins (" +
  259. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  260. "username VARCHAR(50) NOT NULL, " +
  261. "password VARCHAR(128) NOT NULL, " +
  262. "real_name VARCHAR(50), " +
  263. "phone VARCHAR(11), " +
  264. "email VARCHAR(100), " +
  265. "role VARCHAR(20) DEFAULT 'admin', " +
  266. "status TINYINT DEFAULT 1, " +
  267. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  268. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  269. "INDEX idx_phone (phone), " +
  270. "INDEX idx_username (username)" +
  271. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  272. // 验证码表
  273. "CREATE TABLE IF NOT EXISTS verification_codes (" +
  274. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  275. "phone VARCHAR(11) NOT NULL, " +
  276. "code VARCHAR(6) NOT NULL, " +
  277. "type VARCHAR(20) NOT NULL, " +
  278. "expires_in INT DEFAULT 300, " +
  279. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  280. "expires_at DATETIME NOT NULL, " +
  281. "used TINYINT DEFAULT 0, " +
  282. "INDEX idx_phone_type (phone, type), " +
  283. "INDEX idx_expires_at (expires_at)" +
  284. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  285. // 管理后台任务模板表
  286. "CREATE TABLE IF NOT EXISTS admin_task_templates (" +
  287. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  288. "title VARCHAR(200) NOT NULL, " +
  289. "description TEXT, " +
  290. "points INT DEFAULT 10, " +
  291. "category VARCHAR(32), " +
  292. "need_review TINYINT DEFAULT 0, " +
  293. "review_by_category TINYINT DEFAULT 0, " +
  294. "recommended_age INT, " +
  295. "difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium', " +
  296. "is_active TINYINT DEFAULT 1, " +
  297. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  298. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  299. "INDEX idx_category (category), " +
  300. "INDEX idx_difficulty (difficulty), " +
  301. "INDEX idx_is_active (is_active)" +
  302. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
  303. );
  304. // 执行创建表SQL
  305. for (String sql : createTableSQLs) {
  306. try {
  307. jdbcTemplate.execute(sql);
  308. } catch (Exception e) {
  309. log.warn("创建表失败: {}", e.getMessage());
  310. }
  311. }
  312. log.info("数据表检查完成");
  313. }
  314. private void runMigrations() {
  315. // 迁移1: 任务表添加执行者字段
  316. try {
  317. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_type ENUM('child', 'parent') DEFAULT 'child' COMMENT '执行者类型'");
  318. log.info("已添加executor_type列到tasks表");
  319. } catch (Exception e) {
  320. // 列已存在,忽略错误
  321. }
  322. try {
  323. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_id BIGINT COMMENT '执行者ID'");
  324. log.info("已添加executor_id列到tasks表");
  325. } catch (Exception e) {
  326. // 列已存在,忽略错误
  327. }
  328. try {
  329. jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_executor (executor_type, executor_id)");
  330. log.info("已添加idx_executor索引到tasks表");
  331. } catch (Exception e) {
  332. // 索引已存在,忽略错误
  333. }
  334. // 迁移2: 用户表添加角色和积分字段
  335. try {
  336. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN roles VARCHAR(100) COMMENT '角色列表'");
  337. log.info("已添加roles列到users表");
  338. } catch (Exception e) {
  339. // 列已存在,忽略错误
  340. }
  341. try {
  342. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_points INT DEFAULT 0 COMMENT '家长总积分'");
  343. log.info("已添加total_points列到users表");
  344. } catch (Exception e) {
  345. // 列已存在,忽略错误
  346. }
  347. try {
  348. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_roles (roles(20))");
  349. log.info("已添加idx_roles索引到users表");
  350. } catch (Exception e) {
  351. // 索引已存在,忽略错误
  352. }
  353. // 迁移3: users表添加新字段
  354. try {
  355. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN id_card VARCHAR(18) COMMENT '身份证号'");
  356. log.info("已添加id_card列到users表");
  357. } catch (Exception e) {
  358. // 列已存在,忽略错误
  359. }
  360. try {
  361. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN real_name VARCHAR(50) COMMENT '真实姓名'");
  362. log.info("已添加real_name列到users表");
  363. } catch (Exception e) {
  364. // 列已存在,忽略错误
  365. }
  366. try {
  367. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN phone VARCHAR(11) COMMENT '手机号'");
  368. log.info("已添加phone列到users表");
  369. } catch (Exception e) {
  370. // 列已存在,忽略错误
  371. }
  372. try {
  373. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN birthday DATE COMMENT '生日'");
  374. log.info("已添加birthday列到users表");
  375. } catch (Exception e) {
  376. // 列已存在,忽略错误
  377. }
  378. try {
  379. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  380. log.info("已添加gender列到users表");
  381. } catch (Exception e) {
  382. // 列已存在,忽略错误
  383. }
  384. try {
  385. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_phone (phone)");
  386. log.info("已添加idx_phone索引到users表");
  387. } catch (Exception e) {
  388. // 索引已存在,忽略错误
  389. }
  390. // 迁移4: children表添加新字段
  391. try {
  392. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN birthday DATE COMMENT '生日'");
  393. log.info("已添加birthday列到children表");
  394. } catch (Exception e) {
  395. // 列已存在,忽略错误
  396. }
  397. try {
  398. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  399. log.info("已添加gender列到children表");
  400. } catch (Exception e) {
  401. // 列已存在,忽略错误
  402. }
  403. // 迁移5: 修改users.role为支持teacher
  404. try {
  405. jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent', 'child', 'teacher') NOT NULL");
  406. log.info("已更新users.role字段支持teacher角色");
  407. } catch (Exception e) {
  408. // 修改失败,忽略错误
  409. }
  410. // 迁移6: 创建streets表(街道地址维度表)
  411. try {
  412. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS streets (" +
  413. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  414. "province VARCHAR(50) COMMENT '省份', " +
  415. "province_code VARCHAR(20) COMMENT '省份编码', " +
  416. "city VARCHAR(50) COMMENT '城市', " +
  417. "city_code VARCHAR(20) COMMENT '城市编码', " +
  418. "district VARCHAR(50) COMMENT '区县', " +
  419. "district_code VARCHAR(20) COMMENT '区县编码', " +
  420. "street VARCHAR(100) COMMENT '街道', " +
  421. "street_code VARCHAR(20) COMMENT '街道编码', " +
  422. "full_name VARCHAR(200) COMMENT '完整地址', " +
  423. "level INT COMMENT '层级:1省2市3区4街道', " +
  424. "parent_id BIGINT COMMENT '父级ID', " +
  425. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  426. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  427. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  428. "INDEX idx_level (level), " +
  429. "INDEX idx_parent_id (parent_id), " +
  430. "INDEX idx_street_code (street_code), " +
  431. "INDEX idx_full_name (full_name(50))" +
  432. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  433. log.info("已创建streets表");
  434. } catch (Exception e) {
  435. // 表已存在,忽略错误
  436. }
  437. // 迁移7: families表添加地址字段
  438. try {
  439. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  440. log.info("已添加street_id列到families表");
  441. } catch (Exception e) {
  442. // 列已存在,忽略错误
  443. }
  444. try {
  445. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  446. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  447. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  448. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  449. log.info("已添加地址字段到families表");
  450. } catch (Exception e) {
  451. // 列已存在,忽略错误
  452. }
  453. try {
  454. jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_street_id (street_id)");
  455. log.info("已添加idx_street_id索引到families表");
  456. } catch (Exception e) {
  457. // 索引已存在,忽略错误
  458. }
  459. // 迁移8: teachers表添加地址字段
  460. try {
  461. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  462. log.info("已添加street_id列到teachers表");
  463. } catch (Exception e) {
  464. // 列已存在,忽略错误
  465. }
  466. try {
  467. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  468. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  469. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  470. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  471. log.info("已添加地址字段到teachers表");
  472. } catch (Exception e) {
  473. // 列已存在,忽略错误
  474. }
  475. try {
  476. jdbcTemplate.execute("ALTER TABLE teachers ADD INDEX idx_street_id (street_id)");
  477. log.info("已添加idx_street_id索引到teachers表");
  478. } catch (Exception e) {
  479. // 索引已存在,忽略错误
  480. }
  481. log.info("数据库迁移完成");
  482. }
  483. private void initializeDefaultData() {
  484. // 初始化预设奖励模板
  485. try {
  486. // 检查是否已有预设奖励
  487. Integer count = jdbcTemplate.queryForObject(
  488. "SELECT COUNT(*) FROM rewards WHERE is_template = 1", Integer.class);
  489. if (count == null || count == 0) {
  490. // 插入预设奖励模板
  491. jdbcTemplate.execute("INSERT INTO rewards (family_id, child_id, title, points_required, category, is_template) VALUES " +
  492. "(0, 0, '看动画片30分钟', 30, '精神类', 1), " +
  493. "(0, 0, '玩平板游戏30分钟', 40, '特权类', 1), " +
  494. "(0, 0, '去游乐场', 100, '物质类', 1), " +
  495. "(0, 0, '买玩具', 150, '物质类', 1), " +
  496. "(0, 0, '吃冰淇淋', 20, '物质类', 1), " +
  497. "(0, 0, '选择晚餐', 25, '特权类', 1)");
  498. log.info("预设奖励模板已初始化");
  499. }
  500. } catch (Exception e) {
  501. log.warn("初始化预设奖励失败: {}", e.getMessage());
  502. }
  503. // 初始化奖励分类
  504. try {
  505. Integer count = jdbcTemplate.queryForObject(
  506. "SELECT COUNT(*) FROM reward_categories WHERE family_id = 0", Integer.class);
  507. if (count == null || count == 0) {
  508. jdbcTemplate.execute("INSERT INTO reward_categories (family_id, name, icon, sort_order) VALUES " +
  509. "(0, '物质类', 'gift', 1), " +
  510. "(0, '精神类', 'star', 2), " +
  511. "(0, '特权类', 'crown', 3)");
  512. log.info("奖励分类已初始化");
  513. }
  514. } catch (Exception e) {
  515. log.warn("初始化奖励分类失败: {}", e.getMessage());
  516. }
  517. // 初始化打卡里程碑
  518. try {
  519. Integer count = jdbcTemplate.queryForObject(
  520. "SELECT COUNT(*) FROM streak_milestones", Integer.class);
  521. if (count == null || count == 0) {
  522. jdbcTemplate.execute("INSERT INTO streak_milestones (days, reward_points) VALUES " +
  523. "(3, 5), (7, 10), (30, 30), (100, 100)");
  524. log.info("打卡里程碑已初始化");
  525. }
  526. } catch (Exception e) {
  527. log.warn("初始化打卡里程碑失败: {}", e.getMessage());
  528. }
  529. // 初始化勋章定义
  530. try {
  531. Integer count = jdbcTemplate.queryForObject(
  532. "SELECT COUNT(*) FROM badge_definitions", Integer.class);
  533. if (count == null || count == 0) {
  534. jdbcTemplate.execute("INSERT INTO badge_definitions (badge_type, name, description, icon, condition_type, condition_value, reward_points) VALUES " +
  535. "('first_task', '初次完成任务', '首次完成任意任务', 'star', 'task_count', 1, 0), " +
  536. "('streak_3', '连续3天', '连续3天按时完成任务', 'fire', 'streak_days', 3, 5), " +
  537. "('streak_7', '连续7天', '连续7天按时完成任务', 'fire', 'streak_days', 7, 10), " +
  538. "('points_50', '积分达人', '一周获得50积分', 'trophy', 'weekly_points', 50, 0), " +
  539. "('task_100', '任务达人', '完成100个任务', 'medal', 'task_count', 100, 20)");
  540. log.info("勋章定义已初始化");
  541. }
  542. } catch (Exception e) {
  543. log.warn("初始化勋章定义失败: {}", e.getMessage());
  544. }
  545. // 初始化默认管理员
  546. try {
  547. // 先检查并添加role列(如果不存在)
  548. try {
  549. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN role VARCHAR(20) DEFAULT \'admin\' AFTER email");
  550. log.info("已添加role列到admins表");
  551. } catch (Exception e) {
  552. // 列已存在,忽略错误
  553. }
  554. Integer count = jdbcTemplate.queryForObject(
  555. "SELECT COUNT(*) FROM admins", Integer.class);
  556. if (count == null || count == 0) {
  557. // 默认管理员: 手机号 13800138000, 密码 admin123
  558. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  559. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  560. "(\'admin\', \'" + encryptedPassword + "\', \'系统管理员\', \'13800138000\', \'admin@xzyj.com\', \'admin\', 1)");
  561. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  562. }
  563. } catch (Exception e) {
  564. log.warn("初始化默认管理员失败: {}", e.getMessage());
  565. }
  566. // 初始化任务模板示例数据
  567. try {
  568. Integer count = jdbcTemplate.queryForObject(
  569. "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
  570. if (count == null || count == 0) {
  571. jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES " +
  572. "('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1), " +
  573. "('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1), " +
  574. "('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1), " +
  575. "('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1), " +
  576. "('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1), " +
  577. "('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1), " +
  578. "('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1), " +
  579. "('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1)");
  580. log.info("任务模板示例数据已初始化");
  581. }
  582. } catch (Exception e) {
  583. log.warn("初始化任务模板失败: {}", e.getMessage());
  584. }
  585. // 初始化街道数据示例(广东省广州市)
  586. try {
  587. Integer count = jdbcTemplate.queryForObject(
  588. "SELECT COUNT(*) FROM streets", Integer.class);
  589. if (count == null || count == 0) {
  590. // 省
  591. jdbcTemplate.execute("INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES " +
  592. "('广东省', '44', '广东省', 1, NULL, 1)");
  593. // 市
  594. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES " +
  595. "('广东省', '44', '广州市', '4401', '广东省广州市', 2, 1, 1), " +
  596. "('广东省', '44', '深圳市', '4403', '广东省深圳市', 2, 1, 2), " +
  597. "('广东省', '44', '珠海市', '4404', '广东省珠海市', 2, 1, 3)");
  598. // 区
  599. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES " +
  600. "('广东省', '44', '广州市', '4401', '天河区', '440106', '广东省广州市天河区', 3, 2, 1), " +
  601. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '广东省广州市越秀区', 3, 2, 2), " +
  602. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '广东省广州市番禺区', 3, 2, 3), " +
  603. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '广东省深圳市南山区', 3, 3, 1), " +
  604. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '广东省深圳市福田区', 3, 3, 2)");
  605. // 街道
  606. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, street, street_code, full_name, level, parent_id, sort_order) VALUES " +
  607. "('广东省', '44', '广州市', '4401', '天河区', '440106', '天河南街道', '440106001', '广东省广州市天河区天河南街道', 4, 4, 1), " +
  608. "('广东省', '44', '广州市', '4401', '天河区', '440106', '沙河街道', '440106002', '广东省广州市天河区沙河街道', 4, 4, 2), " +
  609. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '东山街道', '440104001', '广东省广州市越秀区东山街道', 4, 5, 1), " +
  610. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '市桥街道', '440113001', '广东省广州市番禺区市桥街道', 4, 6, 1), " +
  611. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '南山街道', '440305001', '广东省深圳市南山区南山街道', 4, 7, 1), " +
  612. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '福田街道', '440304001', '广东省深圳市福田区福田街道', 4, 8, 1)");
  613. log.info("街道示例数据已初始化");
  614. }
  615. } catch (Exception e) {
  616. log.warn("初始化街道数据失败: {}", e.getMessage());
  617. }
  618. try {
  619. Integer count = jdbcTemplate.queryForObject(
  620. "SELECT COUNT(*) FROM admins", Integer.class);
  621. if (count == null || count == 0) {
  622. // 默认管理员: 手机号 13800138000, 密码 admin123
  623. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  624. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  625. "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 'admin', 1)");
  626. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  627. }
  628. } catch (Exception e) {
  629. log.warn("初始化任务模板失败: {}", e.getMessage());
  630. }
  631. }
  632. }