DatabaseInitializer.java 90 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819
  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','admin') 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 COMMENT '执行者ID(child任务时必填,parent任务时可空)', " +
  91. "executor_type VARCHAR(20) COMMENT '执行者类型: child/parent', " +
  92. "executor_id BIGINT COMMENT '执行者ID', " +
  93. "title VARCHAR(200) NOT NULL, " +
  94. "description VARCHAR(500), " +
  95. "points TINYINT DEFAULT 2, " +
  96. "deadline DATETIME, " +
  97. "repeat_type ENUM('none','daily','weekly') DEFAULT 'none', " +
  98. "category VARCHAR(32), " +
  99. "need_review TINYINT DEFAULT 0, " +
  100. "review_by_category TINYINT DEFAULT 0, " +
  101. "status ENUM('pending','completed','overdue','cancelled') DEFAULT 'pending', " +
  102. "completed_at DATETIME, " +
  103. "bonus_rule VARCHAR(500), " +
  104. "points_expire_days INT DEFAULT 0, " +
  105. "template_id BIGINT, " +
  106. "is_template TINYINT DEFAULT 0, " +
  107. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  108. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  109. "INDEX idx_child_id_deadline (child_id, deadline), " +
  110. "INDEX idx_status (status), " +
  111. "INDEX idx_category (category), " +
  112. "INDEX idx_executor (executor_type, executor_id)" +
  113. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  114. // 积分流水表
  115. "CREATE TABLE IF NOT EXISTS points_log (" +
  116. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  117. "child_id BIGINT NOT NULL, " +
  118. "task_id BIGINT, " +
  119. "amount INT NOT NULL, " +
  120. "type ENUM('earn','spend','bonus','penalty','adjust','reset','refund') NOT NULL, " +
  121. "description VARCHAR(255), " +
  122. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  123. "INDEX idx_child_id_created (child_id, created_at), " +
  124. "INDEX idx_type (type)" +
  125. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  126. // 积分清零记录表
  127. "CREATE TABLE IF NOT EXISTS points_reset_log (" +
  128. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  129. "child_id BIGINT NOT NULL, " +
  130. "balance_before INT NOT NULL, " +
  131. "balance_after INT DEFAULT 0, " +
  132. "reset_type ENUM('monthly','manual') NOT NULL, " +
  133. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  134. "INDEX idx_child_id (child_id)" +
  135. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  136. // 奖励表
  137. "CREATE TABLE IF NOT EXISTS rewards (" +
  138. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  139. "family_id BIGINT NOT NULL, " +
  140. "child_id BIGINT NULL COMMENT '孩子ID,模板奖励为NULL', " +
  141. "title VARCHAR(100) NOT NULL, " +
  142. "points_required INT NOT NULL, " +
  143. "category VARCHAR(32), " +
  144. "status ENUM(\'available\',\'pending\',\'exchanged\',\'rejected\') DEFAULT \'available\', " +
  145. "exchange_approved TINYINT, " +
  146. "exchanged_at DATETIME, " +
  147. "is_template TINYINT DEFAULT 0, " +
  148. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  149. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  150. "INDEX idx_child_id_status (child_id, status), " +
  151. "INDEX idx_is_template (is_template)" +
  152. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  153. // 奖励分类表
  154. "CREATE TABLE IF NOT EXISTS reward_categories (" +
  155. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  156. "family_id BIGINT NOT NULL, " +
  157. "name VARCHAR(32) NOT NULL, " +
  158. "icon VARCHAR(64), " +
  159. "sort_order INT DEFAULT 0, " +
  160. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  161. "INDEX idx_family_id (family_id)" +
  162. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  163. // 敏感词表
  164. "CREATE TABLE IF NOT EXISTS sensitive_words (" +
  165. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  166. "word VARCHAR(50) NOT NULL, " +
  167. "category VARCHAR(32) NOT NULL, " +
  168. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  169. "INDEX idx_word (word)" +
  170. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  171. // 任务审核表
  172. "CREATE TABLE IF NOT EXISTS task_reviews (" +
  173. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  174. "task_id BIGINT NOT NULL, " +
  175. "child_id BIGINT NOT NULL, " +
  176. "photo_url VARCHAR(255), " +
  177. "voice_note_url VARCHAR(255), " +
  178. "ai_result ENUM('pass','review','pending'), " +
  179. "ai_confidence DECIMAL(3,2), " +
  180. "ai_suggestion VARCHAR(255), " +
  181. "ai_threshold ENUM('strict','normal','loose') DEFAULT 'normal', " +
  182. "parent_result ENUM('pass','reject'), " +
  183. "parent_override TINYINT DEFAULT 0, " +
  184. "review_type ENUM('ai','manual') NOT NULL, " +
  185. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  186. "reviewed_at DATETIME, " +
  187. "INDEX idx_task_id (task_id), " +
  188. "INDEX idx_ai_result (ai_result)" +
  189. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  190. // 勋章定义表
  191. "CREATE TABLE IF NOT EXISTS badge_definitions (" +
  192. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  193. "badge_type VARCHAR(32) NOT NULL, " +
  194. "name VARCHAR(50) NOT NULL, " +
  195. "description VARCHAR(200), " +
  196. "icon VARCHAR(64), " +
  197. "condition_type VARCHAR(32) NOT NULL, " +
  198. "condition_value INT NOT NULL, " +
  199. "reward_points INT DEFAULT 0, " +
  200. "INDEX idx_badge_type (badge_type)" +
  201. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  202. // 勋章表
  203. "CREATE TABLE IF NOT EXISTS badges (" +
  204. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  205. "child_id BIGINT NOT NULL, " +
  206. "badge_type VARCHAR(32) NOT NULL, " +
  207. "unlocked_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  208. "INDEX idx_child_id_type (child_id, badge_type)" +
  209. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  210. // 打卡里程碑表
  211. "CREATE TABLE IF NOT EXISTS streak_milestones (" +
  212. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  213. "days INT NOT NULL, " +
  214. "reward_points INT DEFAULT 0, " +
  215. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  216. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  217. // 专注训练记录表
  218. "CREATE TABLE IF NOT EXISTS focus_sessions (" +
  219. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  220. "child_id BIGINT NOT NULL, " +
  221. "mode ENUM('simple','pomodoro') NOT NULL, " +
  222. "duration TINYINT NOT NULL, " +
  223. "stars TINYINT, " +
  224. "points_earned INT, " +
  225. "voice_note_url VARCHAR(255), " +
  226. "started_at DATETIME NOT NULL, " +
  227. "completed_at DATETIME, " +
  228. "INDEX idx_child_id_completed (child_id, completed_at)" +
  229. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  230. // 专注训练配置表
  231. "CREATE TABLE IF NOT EXISTS focus_config (" +
  232. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  233. "family_id BIGINT, " +
  234. "max_daily_count TINYINT DEFAULT 3, " +
  235. "allow_exit TINYINT DEFAULT 1, " +
  236. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  237. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  238. // DAN测评表
  239. "CREATE TABLE IF NOT EXISTS dan_assessments (" +
  240. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  241. "child_id BIGINT NOT NULL, " +
  242. "dan_level VARCHAR(2) NOT NULL, " +
  243. "assessment_date DATE NOT NULL, " +
  244. "details JSON, " +
  245. "source ENUM('api','manual') DEFAULT 'manual', " +
  246. "photo_url VARCHAR(255), " +
  247. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  248. "INDEX idx_child_id_level (child_id, dan_level)" +
  249. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  250. // 任务模板表
  251. "CREATE TABLE IF NOT EXISTS task_templates (" +
  252. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  253. "dan_level VARCHAR(2) NOT NULL, " +
  254. "title VARCHAR(200) NOT NULL, " +
  255. "points TINYINT DEFAULT 2, " +
  256. "duration_minutes TINYINT, " +
  257. "category VARCHAR(32), " +
  258. "is_default TINYINT DEFAULT 1, " +
  259. "family_id BIGINT, " +
  260. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  261. "INDEX idx_dan_level (dan_level), " +
  262. "INDEX idx_is_default (is_default)" +
  263. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  264. // 管理员表
  265. "CREATE TABLE IF NOT EXISTS admins (" +
  266. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  267. "username VARCHAR(50) NOT NULL, " +
  268. "password VARCHAR(128) NOT NULL, " +
  269. "real_name VARCHAR(50), " +
  270. "phone VARCHAR(11), " +
  271. "email VARCHAR(100), " +
  272. "role VARCHAR(20) DEFAULT 'admin', " +
  273. "status TINYINT DEFAULT 1, " +
  274. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  275. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  276. "INDEX idx_phone (phone), " +
  277. "INDEX idx_username (username)" +
  278. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  279. // 验证码表
  280. "CREATE TABLE IF NOT EXISTS verification_codes (" +
  281. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  282. "phone VARCHAR(11) NOT NULL, " +
  283. "code VARCHAR(6) NOT NULL, " +
  284. "type VARCHAR(20) NOT NULL, " +
  285. "expires_in INT DEFAULT 300, " +
  286. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  287. "expires_at DATETIME NOT NULL, " +
  288. "used TINYINT DEFAULT 0, " +
  289. "INDEX idx_phone_type (phone, type), " +
  290. "INDEX idx_expires_at (expires_at)" +
  291. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  292. // 管理后台任务模板表
  293. "CREATE TABLE IF NOT EXISTS admin_task_templates (" +
  294. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  295. "title VARCHAR(200) NOT NULL, " +
  296. "description TEXT, " +
  297. "points INT DEFAULT 10, " +
  298. "category VARCHAR(32), " +
  299. "need_review TINYINT DEFAULT 0, " +
  300. "review_by_category TINYINT DEFAULT 0, " +
  301. "recommended_age INT, " +
  302. "difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium', " +
  303. "is_active TINYINT DEFAULT 1, " +
  304. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  305. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  306. "INDEX idx_category (category), " +
  307. "INDEX idx_difficulty (difficulty), " +
  308. "INDEX idx_is_active (is_active)" +
  309. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
  310. // 测评资料表
  311. "CREATE TABLE IF NOT EXISTS assessment_materials (" +
  312. "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
  313. "title VARCHAR(100) NOT NULL COMMENT '资料标题', " +
  314. "content TEXT COMMENT '测评介绍内容', " +
  315. "cover_image VARCHAR(255) COMMENT '封面图片', " +
  316. "status VARCHAR(20) DEFAULT 'active' COMMENT '状态:active/inactive', " +
  317. "created_at DATETIME COMMENT '创建时间', " +
  318. "updated_at DATETIME COMMENT '更新时间', " +
  319. "INDEX idx_status (status)" +
  320. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评资料表'",
  321. // 积分配置表
  322. "CREATE TABLE IF NOT EXISTS assessment_points_config (" +
  323. "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
  324. "points_value INT DEFAULT 100 COMMENT '完成测评获得积分', " +
  325. "points_value_yuan DECIMAL(10,2) DEFAULT 0.01 COMMENT '1积分等于多少元', " +
  326. "is_active TINYINT DEFAULT 1 COMMENT '是否启用', " +
  327. "created_at DATETIME COMMENT '创建时间', " +
  328. "updated_at DATETIME COMMENT '更新时间'" +
  329. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分配置表'",
  330. // 家庭测评配置表
  331. "CREATE TABLE IF NOT EXISTS family_assessment_config (" +
  332. "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
  333. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  334. "material_id BIGINT COMMENT '测评资料ID', " +
  335. "remind_months INT DEFAULT 12 COMMENT '提醒周期(月)', " +
  336. "last_remind_at DATETIME COMMENT '上次提醒时间', " +
  337. "next_remind_at DATETIME COMMENT '下次提醒时间', " +
  338. "is_enabled TINYINT DEFAULT 1 COMMENT '是否启用:1启用0关闭', " +
  339. "created_by BIGINT COMMENT '创建人(规划师ID)', " +
  340. "created_at DATETIME COMMENT '创建时间', " +
  341. "updated_at DATETIME COMMENT '更新时间', " +
  342. "UNIQUE KEY uk_family (family_id), " +
  343. "INDEX idx_next_remind (next_remind_at), " +
  344. "INDEX idx_enabled (is_enabled)" +
  345. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭测评配置表'",
  346. // 测评记录表
  347. "CREATE TABLE IF NOT EXISTS assessment_records (" +
  348. "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
  349. "family_id BIGINT COMMENT '家庭ID', " +
  350. "child_id BIGINT COMMENT '孩子ID', " +
  351. "material_id BIGINT COMMENT '测评资料ID', " +
  352. "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending待完成/completed已完成', " +
  353. "completed_at DATETIME COMMENT '完成时间', " +
  354. "created_at DATETIME COMMENT '创建时间', " +
  355. "INDEX idx_family (family_id), " +
  356. "INDEX idx_status (status)" +
  357. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评记录表'",
  358. // 成长规划师表(已认证的规划师)
  359. "CREATE TABLE IF NOT EXISTS guides (" +
  360. "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
  361. "user_id BIGINT NOT NULL UNIQUE COMMENT '用户ID(唯一)', " +
  362. "level VARCHAR(20) NOT NULL DEFAULT 'junior' COMMENT '等级: junior-初级, intermediate-中级, senior-高级', " +
  363. "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active-活跃, inactive-停用', " +
  364. "service_count INT DEFAULT 0 COMMENT '当前服务家庭数', " +
  365. "package_id BIGINT COMMENT '购买套餐ID', " +
  366. "nickname VARCHAR(100) COMMENT '规划师昵称', " +
  367. "phone VARCHAR(20) COMMENT '联系电话', " +
  368. "intro TEXT COMMENT '个人介绍', " +
  369. "avatar VARCHAR(500) COMMENT '头像URL', " +
  370. "commission_rate DECIMAL(5,4) DEFAULT 0.1000 COMMENT '佣金比例', " +
  371. "approved_at DATETIME COMMENT '审核通过时间', " +
  372. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
  373. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
  374. "INDEX idx_user_id (user_id), " +
  375. "INDEX idx_level (level), " +
  376. "INDEX idx_status (status)" +
  377. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师表'",
  378. // 成长规划师申请表
  379. "CREATE TABLE IF NOT EXISTS guide_applications (" +
  380. "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
  381. "user_id BIGINT NOT NULL COMMENT '申请人用户ID', " +
  382. "type VARCHAR(20) NOT NULL DEFAULT 'new_apply' COMMENT '申请类型: new_apply-新注册, upgrade-升级', " +
  383. "target_level VARCHAR(20) NOT NULL COMMENT '目标等级: junior-初级, intermediate-中级, senior-高级', " +
  384. "current_level VARCHAR(20) DEFAULT NULL COMMENT '当前等级(升级时填写)', " +
  385. "package_id BIGINT COMMENT '申请的套餐ID', " +
  386. "payment_proof VARCHAR(500) COMMENT '付款凭证URL', " +
  387. "payment_amount DECIMAL(10,2) COMMENT '付款金额', " +
  388. "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待审核, approved-已通过, rejected-已拒绝', " +
  389. "reject_reason VARCHAR(500) COMMENT '拒绝原因', " +
  390. "reviewed_at DATETIME COMMENT '审核时间', " +
  391. "reviewed_by BIGINT COMMENT '审核人ID', " +
  392. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
  393. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
  394. "INDEX idx_user_id (user_id), " +
  395. "INDEX idx_status (status), " +
  396. "INDEX idx_type (type)" +
  397. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请表'",
  398. // 小游戏表
  399. "CREATE TABLE IF NOT EXISTS mini_games (" +
  400. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  401. "game_code VARCHAR(20) NOT NULL UNIQUE COMMENT '游戏代码', " +
  402. "game_name VARCHAR(50) NOT NULL COMMENT '游戏名称', " +
  403. "description VARCHAR(255) COMMENT '游戏描述', " +
  404. "icon VARCHAR(50) COMMENT '图标emoji', " +
  405. "default_duration INT DEFAULT 5 COMMENT '默认时长(分钟)', " +
  406. "default_points INT DEFAULT 5 COMMENT '默认奖励积分', " +
  407. "game_url VARCHAR(255) COMMENT '游戏H5页面URL', " +
  408. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  409. "status TINYINT DEFAULT 1 COMMENT '状态:0禁用1启用', " +
  410. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  411. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  412. "INDEX idx_game_code (game_code), " +
  413. "INDEX idx_status (status)" +
  414. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
  415. );
  416. // 执行创建表SQL
  417. for (String sql : createTableSQLs) {
  418. try {
  419. jdbcTemplate.execute(sql);
  420. } catch (Exception e) {
  421. log.warn("创建表失败: {}", e.getMessage());
  422. }
  423. }
  424. log.info("数据表检查完成");
  425. }
  426. private void runMigrations() {
  427. // 迁移1: 任务表添加执行者字段
  428. try {
  429. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_type ENUM('child', 'parent') DEFAULT 'child' COMMENT '执行者类型'");
  430. log.info("已添加executor_type列到tasks表");
  431. } catch (Exception e) {
  432. // 列已存在,忽略错误
  433. }
  434. try {
  435. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_id BIGINT COMMENT '执行者ID'");
  436. log.info("已添加executor_id列到tasks表");
  437. } catch (Exception e) {
  438. // 列已存在,忽略错误
  439. }
  440. try {
  441. jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_executor (executor_type, executor_id)");
  442. log.info("已添加idx_executor索引到tasks表");
  443. } catch (Exception e) {
  444. // 索引已存在,忽略错误
  445. }
  446. // 迁移2: 用户表添加角色和积分字段
  447. try {
  448. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN roles VARCHAR(100) COMMENT '角色列表'");
  449. log.info("已添加roles列到users表");
  450. } catch (Exception e) {
  451. // 列已存在,忽略错误
  452. }
  453. try {
  454. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_points INT DEFAULT 0 COMMENT '家长总积分'");
  455. log.info("已添加total_points列到users表");
  456. } catch (Exception e) {
  457. // 列已存在,忽略错误
  458. }
  459. try {
  460. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_roles (roles(20))");
  461. log.info("已添加idx_roles索引到users表");
  462. } catch (Exception e) {
  463. // 索引已存在,忽略错误
  464. }
  465. // 迁移3: users表添加新字段
  466. try {
  467. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN id_card VARCHAR(18) COMMENT '身份证号'");
  468. log.info("已添加id_card列到users表");
  469. } catch (Exception e) {
  470. // 列已存在,忽略错误
  471. }
  472. try {
  473. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN real_name VARCHAR(50) COMMENT '真实姓名'");
  474. log.info("已添加real_name列到users表");
  475. } catch (Exception e) {
  476. // 列已存在,忽略错误
  477. }
  478. try {
  479. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN phone VARCHAR(11) COMMENT '手机号'");
  480. log.info("已添加phone列到users表");
  481. } catch (Exception e) {
  482. // 列已存在,忽略错误
  483. }
  484. try {
  485. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN birthday DATE COMMENT '生日'");
  486. log.info("已添加birthday列到users表");
  487. } catch (Exception e) {
  488. // 列已存在,忽略错误
  489. }
  490. try {
  491. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  492. log.info("已添加gender列到users表");
  493. } catch (Exception e) {
  494. // 列已存在,忽略错误
  495. }
  496. try {
  497. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_phone (phone)");
  498. log.info("已添加idx_phone索引到users表");
  499. } catch (Exception e) {
  500. // 索引已存在,忽略错误
  501. }
  502. // 迁移4: children表添加新字段
  503. try {
  504. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN birthday DATE COMMENT '生日'");
  505. log.info("已添加birthday列到children表");
  506. } catch (Exception e) {
  507. // 列已存在,忽略错误
  508. }
  509. try {
  510. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  511. log.info("已添加gender列到children表");
  512. } catch (Exception e) {
  513. // 列已存在,忽略错误
  514. }
  515. // 迁移5: 修改users.role为支持teacher
  516. try {
  517. jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent', 'child', 'teacher') NOT NULL");
  518. log.info("已更新users.role字段支持teacher角色");
  519. } catch (Exception e) {
  520. // 修改失败,忽略错误
  521. }
  522. // 迁移5.1: 修改users.role为支持admin
  523. try {
  524. jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent', 'child', 'teacher', 'admin') NOT NULL");
  525. log.info("已更新users.role字段支持admin角色");
  526. } catch (Exception e) {
  527. // 修改失败,忽略错误
  528. }
  529. // 迁移5.2: 确保管理员角色为admin(修复旧数据遗留问题)
  530. try {
  531. int updated = jdbcTemplate.update("UPDATE admins SET role = 'admin' WHERE username = 'admin'");
  532. if (updated > 0) {
  533. log.info("已修正管理员角色为admin");
  534. }
  535. } catch (Exception e) {
  536. // 更新失败,忽略错误
  537. }
  538. // 迁移6: 创建streets表(街道地址维度表)
  539. try {
  540. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS streets (" +
  541. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  542. "province VARCHAR(50) COMMENT '省份', " +
  543. "province_code VARCHAR(20) COMMENT '省份编码', " +
  544. "city VARCHAR(50) COMMENT '城市', " +
  545. "city_code VARCHAR(20) COMMENT '城市编码', " +
  546. "district VARCHAR(50) COMMENT '区县', " +
  547. "district_code VARCHAR(20) COMMENT '区县编码', " +
  548. "street VARCHAR(100) COMMENT '街道', " +
  549. "street_code VARCHAR(20) COMMENT '街道编码', " +
  550. "full_name VARCHAR(200) COMMENT '完整地址', " +
  551. "level INT COMMENT '层级:1省2市3区4街道', " +
  552. "parent_id BIGINT COMMENT '父级ID', " +
  553. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  554. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  555. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  556. "INDEX idx_level (level), " +
  557. "INDEX idx_parent_id (parent_id), " +
  558. "INDEX idx_street_code (street_code), " +
  559. "INDEX idx_full_name (full_name(50))" +
  560. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  561. log.info("已创建streets表");
  562. } catch (Exception e) {
  563. // 表已存在,忽略错误
  564. }
  565. // 迁移7: families表添加地址字段
  566. try {
  567. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  568. log.info("已添加street_id列到families表");
  569. } catch (Exception e) {
  570. // 列已存在,忽略错误
  571. }
  572. try {
  573. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  574. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  575. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  576. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  577. log.info("已添加地址字段到families表");
  578. } catch (Exception e) {
  579. // 列已存在,忽略错误
  580. }
  581. try {
  582. jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_street_id (street_id)");
  583. log.info("已添加idx_street_id索引到families表");
  584. } catch (Exception e) {
  585. // 索引已存在,忽略错误
  586. }
  587. // 迁移8: teachers表添加地址字段
  588. try {
  589. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  590. log.info("已添加street_id列到teachers表");
  591. } catch (Exception e) {
  592. // 列已存在,忽略错误
  593. }
  594. try {
  595. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  596. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  597. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  598. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  599. log.info("已添加地址字段到teachers表");
  600. } catch (Exception e) {
  601. // 列已存在,忽略错误
  602. }
  603. try {
  604. jdbcTemplate.execute("ALTER TABLE teachers ADD INDEX idx_street_id (street_id)");
  605. log.info("已添加idx_street_id索引到teachers表");
  606. } catch (Exception e) {
  607. // 索引已存在,忽略错误
  608. }
  609. // 迁移9: users表添加成长规划师相关字段
  610. try {
  611. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_no VARCHAR(50) COMMENT '成长规划师证书号'");
  612. log.info("已添加teacher_no列到users表");
  613. } catch (Exception e) {
  614. // 列已存在,忽略错误
  615. }
  616. try {
  617. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_photo VARCHAR(500) COMMENT '成长规划师照片'");
  618. log.info("已添加teacher_photo列到users表");
  619. } catch (Exception e) {
  620. // 列已存在,忽略错误
  621. }
  622. try {
  623. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN certificate_image VARCHAR(500) COMMENT '成长规划师证书照片'");
  624. log.info("已添加certificate_image列到users表");
  625. } catch (Exception e) {
  626. // 列已存在,忽略错误
  627. }
  628. try {
  629. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_status VARCHAR(20) DEFAULT 'pending' COMMENT '成长规划师审核状态'");
  630. log.info("已添加teacher_status列到users表");
  631. } catch (Exception e) {
  632. // 列已存在,忽略错误
  633. }
  634. try {
  635. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_teacher_status (teacher_status)");
  636. log.info("已添加idx_teacher_status索引到users表");
  637. } catch (Exception e) {
  638. // 索引已存在,忽略错误
  639. }
  640. // 迁移10: users表添加teacherFamilyIds字段(成长规划师关联的家庭ID列表)
  641. try {
  642. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_family_ids VARCHAR(500) COMMENT '成长规划师关联的家庭ID列表(逗号分隔)'");
  643. log.info("已添加teacher_family_ids列到users表");
  644. } catch (Exception e) {
  645. // 列已存在,忽略错误
  646. }
  647. // 迁移11: families表添加teacherId字段(绑定的成长规划师ID)
  648. try {
  649. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN teacher_id BIGINT COMMENT '绑定的成长规划师ID'");
  650. log.info("已添加teacher_id列到families表");
  651. } catch (Exception e) {
  652. // 列已存在,忽略错误
  653. }
  654. try {
  655. jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_teacher_id (teacher_id)");
  656. log.info("已添加idx_teacher_id索引到families表");
  657. } catch (Exception e) {
  658. // 索引已存在,忽略错误
  659. }
  660. // 迁移12: 创建 teacher_messages 表(家长咨询成长规划师)
  661. try {
  662. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS teacher_messages (" +
  663. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  664. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  665. "parent_id BIGINT NOT NULL COMMENT '家长用户ID', " +
  666. "teacher_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
  667. "content TEXT NOT NULL COMMENT '消息内容', " +
  668. "reply_content TEXT COMMENT '回复内容', " +
  669. "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending(待回复)/replied(已回复)', " +
  670. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  671. "replied_at DATETIME COMMENT '回复时间', " +
  672. "INDEX idx_family_id (family_id), " +
  673. "INDEX idx_teacher_id (teacher_id), " +
  674. "INDEX idx_parent_id (parent_id), " +
  675. "INDEX idx_status (status)" +
  676. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  677. log.info("已创建teacher_messages表");
  678. } catch (Exception e) {
  679. // 表已存在,忽略错误
  680. }
  681. // 迁移13: admin_task_templates表添加teacher_id字段
  682. try {
  683. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN teacher_id BIGINT COMMENT '成长规划师ID'");
  684. log.info("已添加teacher_id列到admin_task_templates表");
  685. } catch (Exception e) {
  686. // 列已存在,忽略错误
  687. }
  688. // 迁移13b: admin_task_templates表添加新字段
  689. try {
  690. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
  691. log.info("已添加complete_types列到admin_task_templates表");
  692. } catch (Exception e) {
  693. // 列已存在,忽略错误
  694. }
  695. try {
  696. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN duration INT COMMENT '预计完成时长(分钟)'");
  697. log.info("已添加duration列到admin_task_templates表");
  698. } catch (Exception e) {
  699. // 列已存在,忽略错误
  700. }
  701. try {
  702. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
  703. log.info("已添加review_type列到admin_task_templates表");
  704. } catch (Exception e) {
  705. // 列已存在,忽略错误
  706. }
  707. try {
  708. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
  709. log.info("已添加task_type列到admin_task_templates表");
  710. } catch (Exception e) {
  711. // 列已存在,忽略错误
  712. }
  713. try {
  714. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
  715. log.info("已添加frequency列到admin_task_templates表");
  716. } catch (Exception e) {
  717. // 列已存在,忽略错误
  718. }
  719. try {
  720. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
  721. log.info("已添加max_frequency列到admin_task_templates表");
  722. } catch (Exception e) {
  723. // 列已存在,忽略错误
  724. }
  725. // 迁移14: rewards表child_id字段允许NULL(模板奖励不需要child_id)
  726. try {
  727. jdbcTemplate.execute("ALTER TABLE rewards MODIFY COLUMN child_id BIGINT NULL COMMENT '孩子ID,模板奖励为NULL'");
  728. log.info("已修改rewards.child_id字段允许NULL");
  729. } catch (Exception e) {
  730. // 修改失败,忽略错误
  731. }
  732. // 迁移15: admins表添加缺失列
  733. try {
  734. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN nickname VARCHAR(50) DEFAULT NULL AFTER password");
  735. log.info("已添加nickname列到admins表");
  736. } catch (Exception e) {
  737. // 列已存在,忽略错误
  738. }
  739. try {
  740. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN email VARCHAR(100) DEFAULT NULL AFTER phone");
  741. log.info("已添加email列到admins表");
  742. } catch (Exception e) {
  743. // 列已存在,忽略错误
  744. }
  745. try {
  746. jdbcTemplate.execute("ALTER TABLE admins MODIFY COLUMN status VARCHAR(20) DEFAULT 'active'");
  747. log.info("已修改admins.status字段类型");
  748. } catch (Exception e) {
  749. // 修改失败,忽略错误
  750. }
  751. // 迁移16: 创建用户操作日志表
  752. try {
  753. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_operation_logs (" +
  754. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  755. "user_id BIGINT COMMENT \'用户ID\', " +
  756. "nickname VARCHAR(50) COMMENT \'用户昵称\', " +
  757. "operation_type VARCHAR(20) NOT NULL COMMENT \'操作类型: login/logout/create/update/delete/query等\', " +
  758. "description VARCHAR(500) COMMENT \'操作描述\', " +
  759. "request_path VARCHAR(200) COMMENT \'请求路径\', " +
  760. "request_method VARCHAR(10) COMMENT \'请求方法\', " +
  761. "request_params TEXT COMMENT \'请求参数(脱敏)\', " +
  762. "response_status INT COMMENT \'响应状态码\', " +
  763. "client_ip VARCHAR(50) COMMENT \'客户端IP\', " +
  764. "user_agent VARCHAR(500) COMMENT \'User-Agent\', " +
  765. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  766. "INDEX idx_user_id (user_id), " +
  767. "INDEX idx_operation_type (operation_type), " +
  768. "INDEX idx_created_at (created_at)" +
  769. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  770. log.info("已创建user_operation_logs表");
  771. } catch (Exception e) {
  772. // 表已存在,忽略错误
  773. }
  774. // 迁移16: tasks表添加bonus_rule字段(POINT-002: 超额积分规则)
  775. try {
  776. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN bonus_rule VARCHAR(500) COMMENT \'超额积分规则JSON\'");
  777. log.info("已添加bonus_rule列到tasks表");
  778. } catch (Exception e) {
  779. // 列已存在,忽略错误
  780. }
  781. // 迁移17: tasks表添加points_expire_days字段(POINT-009: 积分有效期)
  782. try {
  783. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN points_expire_days INT DEFAULT 0 COMMENT \'积分有效期天数,0表示永久有效\'");
  784. log.info("已添加points_expire_days列到tasks表");
  785. } catch (Exception e) {
  786. // 列已存在,忽略错误
  787. }
  788. // 迁移18: points_log表添加expire_at字段(POINT-009: 积分有效期)
  789. try {
  790. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expire_at DATE COMMENT \'积分过期日期\'");
  791. log.info("已添加expire_at列到points_log表");
  792. } catch (Exception e) {
  793. // 列已存在,忽略错误
  794. }
  795. // 迁移19: points_log表添加expired字段(POINT-009: 过期状态标记)
  796. try {
  797. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expired TINYINT DEFAULT 0 COMMENT \'是否已过期: 0否1是\'");
  798. log.info("已添加expired列到points_log表");
  799. } catch (Exception e) {
  800. // 列已存在,忽略错误
  801. }
  802. // 迁移20: tasks表添加template_id字段(TASK-004: 重复任务模板追踪)
  803. try {
  804. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN template_id BIGINT COMMENT \'模板任务ID\'");
  805. log.info("已添加template_id列到tasks表");
  806. } catch (Exception e) {
  807. // 列已存在,忽略错误
  808. }
  809. // 迁移21: tasks表添加is_template字段(TASK-004: 标记模板任务)
  810. try {
  811. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN is_template TINYINT DEFAULT 0 COMMENT '是否为模板任务'");
  812. log.info("已添加is_template列到tasks表");
  813. } catch (Exception e) {
  814. // 列已存在,忽略错误
  815. }
  816. // 迁移22: 修复tasks表child_id和deadline允许NULL(支持多孩子和家长任务)
  817. try {
  818. jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN child_id BIGINT NULL COMMENT '执行者ID'");
  819. log.info("已修改tasks.child_id允许NULL");
  820. } catch (Exception e) {
  821. // 修改失败,忽略错误
  822. }
  823. try {
  824. jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN deadline DATETIME NULL COMMENT '截止时间'");
  825. log.info("已修改tasks.deadline允许NULL");
  826. } catch (Exception e) {
  827. // 修改失败,忽略错误
  828. }
  829. // 迁移23: tasks表添加新字段
  830. try {
  831. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
  832. log.info("已添加task_type列到tasks表");
  833. } catch (Exception e) {
  834. // 列已存在,忽略错误
  835. }
  836. try {
  837. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
  838. log.info("已添加frequency列到tasks表");
  839. } catch (Exception e) {
  840. // 列已存在,忽略错误
  841. }
  842. try {
  843. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
  844. log.info("已添加max_frequency列到tasks表");
  845. } catch (Exception e) {
  846. // 列已存在,忽略错误
  847. }
  848. try {
  849. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
  850. log.info("已添加complete_types列到tasks表");
  851. } catch (Exception e) {
  852. // 列已存在,忽略错误
  853. }
  854. try {
  855. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
  856. log.info("已添加review_type列到tasks表");
  857. } catch (Exception e) {
  858. // 列已存在,忽略错误
  859. }
  860. try {
  861. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN earliest_start DATETIME COMMENT '最早开始时间'");
  862. log.info("已添加earliest_start列到tasks表");
  863. } catch (Exception e) {
  864. // 列已存在,忽略错误
  865. }
  866. try {
  867. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN latest_end DATETIME COMMENT '最晚结束时间'");
  868. log.info("已添加latest_end列到tasks表");
  869. } catch (Exception e) {
  870. // 列已存在,忽略错误
  871. }
  872. // 迁移24: tasks表添加minigame_code字段(小游戏任务)
  873. try {
  874. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN minigame_code VARCHAR(50) DEFAULT NULL COMMENT '小游戏代码: schulte/1a2b/sudoku'");
  875. log.info("已添加minigame_code列到tasks表");
  876. } catch (Exception e) {
  877. // 列已存在,忽略错误
  878. }
  879. // 迁移25: tasks表添加duration字段(TASK-011: 任务预计时长)
  880. try {
  881. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN duration INT DEFAULT 0 COMMENT '预计时长(分钟)'");
  882. log.info("已添加duration列到tasks表");
  883. } catch (Exception e) {
  884. // 列已存在,忽略错误
  885. }
  886. try {
  887. jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_minigame (minigame_code) WHERE minigame_code IS NOT NULL");
  888. log.info("已添加idx_minigame索引到tasks表");
  889. } catch (Exception e) {
  890. // 索引已存在,忽略错误
  891. }
  892. // assessment_appointments 新字段
  893. try {
  894. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN user_id BIGINT COMMENT '用户ID'");
  895. } catch (Exception e) {
  896. }
  897. try {
  898. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID'");
  899. } catch (Exception e) {
  900. }
  901. try {
  902. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN package_id BIGINT COMMENT '套餐ID'");
  903. } catch (Exception e) {
  904. }
  905. try {
  906. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN remark VARCHAR(500) COMMENT '备注'");
  907. } catch (Exception e) {
  908. }
  909. // assessment_orders 表
  910. try {
  911. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_orders (" +
  912. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  913. "order_no VARCHAR(64) NOT NULL UNIQUE, " +
  914. "family_id BIGINT NOT NULL, " +
  915. "user_id BIGINT NOT NULL, " +
  916. "child_id BIGINT, " +
  917. "guide_id BIGINT NOT NULL, " +
  918. "package_id BIGINT NOT NULL, " +
  919. "guide_name VARCHAR(50), " +
  920. "package_name VARCHAR(100), " +
  921. "total_price BIGINT DEFAULT 0, " +
  922. "discount_amount BIGINT DEFAULT 0, " +
  923. "actual_price BIGINT DEFAULT 0, " +
  924. "status VARCHAR(20) DEFAULT 'pending', " +
  925. "pay_time VARCHAR(50), " +
  926. "pay_type VARCHAR(20), " +
  927. "transaction_id VARCHAR(64), " +
  928. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  929. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  930. "INDEX idx_family (family_id), " +
  931. "INDEX idx_guide (guide_id), " +
  932. "INDEX idx_status (status)) " +
  933. "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表'");
  934. } catch (Exception e) {
  935. }
  936. // 迁移26: guide_applications表添加address和bank_card字段
  937. try {
  938. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN address VARCHAR(500) COMMENT '地址'");
  939. log.info("已添加address列到guide_applications表");
  940. } catch (Exception e) {
  941. // 列已存在,忽略错误
  942. }
  943. try {
  944. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_card VARCHAR(50) COMMENT '银行卡号'");
  945. log.info("已添加bank_card列到guide_applications表");
  946. } catch (Exception e) {
  947. // 列已存在,忽略错误
  948. }
  949. try {
  950. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_name VARCHAR(100) COMMENT '开户行'");
  951. log.info("已添加bank_name列到guide_applications表");
  952. } catch (Exception e) {
  953. // 列已存在,忽略错误
  954. }
  955. // 迁移27: users表添加address字段(成长规划师地址)
  956. try {
  957. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN address VARCHAR(500) COMMENT '地址'");
  958. log.info("已添加address列到users表");
  959. } catch (Exception e) {
  960. // 列已存在,忽略错误
  961. }
  962. // 迁移28: 创建guide_application_records表(申请过程记录)
  963. try {
  964. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_application_records (" +
  965. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  966. "application_id BIGINT NOT NULL COMMENT '关联申请ID', " +
  967. "user_id BIGINT NOT NULL COMMENT '操作人用户ID', " +
  968. "action VARCHAR(50) NOT NULL COMMENT '操作: submit/approve/reject/withdraw/resubmit', " +
  969. "from_status VARCHAR(20) COMMENT '操作前状态', " +
  970. "to_status VARCHAR(20) NOT NULL COMMENT '操作后状态', " +
  971. "remark VARCHAR(500) COMMENT '备注/原因', " +
  972. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  973. "INDEX idx_application_id (application_id), " +
  974. "INDEX idx_user_id (user_id), " +
  975. "INDEX idx_action (action)" +
  976. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请过程记录表'");
  977. log.info("已创建guide_application_records表");
  978. } catch (Exception e) {
  979. // 表已存在,忽略错误
  980. }
  981. // 迁移20: children表添加system_points字段
  982. try {
  983. Integer exists = jdbcTemplate.queryForObject(
  984. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'children' AND COLUMN_NAME = 'system_points'",
  985. Integer.class);
  986. if (exists == null || exists == 0) {
  987. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN system_points INT DEFAULT 0 COMMENT '系统积分'");
  988. log.info("已添加system_points列到children表");
  989. } else {
  990. log.info("system_points列已存在,跳过");
  991. }
  992. } catch (Exception e) {
  993. log.warn("检查/添加system_points列失败: {}", e.getMessage());
  994. }
  995. // 迁移21: points_log表添加category字段
  996. try {
  997. Integer exists = jdbcTemplate.queryForObject(
  998. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'points_log' AND COLUMN_NAME = 'category'",
  999. Integer.class);
  1000. if (exists == null || exists == 0) {
  1001. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task任务积分, system系统积分'");
  1002. log.info("已添加category列到points_log表");
  1003. } else {
  1004. log.info("category列已存在,跳过");
  1005. }
  1006. } catch (Exception e) {
  1007. log.warn("检查/添加category列失败: {}", e.getMessage());
  1008. }
  1009. // 迁移22: 创建invite_card表
  1010. try {
  1011. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS invite_card (" +
  1012. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1013. "type VARCHAR(20) NOT NULL COMMENT 'child/family/guide', " +
  1014. "code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码', " +
  1015. "ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)', " +
  1016. "creator_id BIGINT NOT NULL COMMENT '创建人', " +
  1017. "expires_at DATETIME NOT NULL, " +
  1018. "used TINYINT DEFAULT 0, " +
  1019. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1020. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1021. "INDEX idx_code (code), " +
  1022. "INDEX idx_ref_type (ref_id, type)" +
  1023. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请卡片'");
  1024. log.info("已创建invite_card表");
  1025. } catch (Exception e) {
  1026. // 表已存在,忽略错误
  1027. }
  1028. // 迁移23: 创建wishes表
  1029. try {
  1030. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS wishes (" +
  1031. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1032. "family_id BIGINT COMMENT '家庭ID', " +
  1033. "child_id BIGINT COMMENT '目标孩子ID', " +
  1034. "created_by BIGINT COMMENT '创建者用户ID', " +
  1035. "creator_type VARCHAR(20) COMMENT '创建者类型: child/parent', " +
  1036. "title VARCHAR(200) NOT NULL COMMENT '心愿标题', " +
  1037. "description VARCHAR(500) COMMENT '心愿描述', " +
  1038. "category VARCHAR(32) COMMENT '心愿分类', " +
  1039. "points_required INT COMMENT '所需积分', " +
  1040. "points_set_by BIGINT COMMENT '定价者ID', " +
  1041. "points_set_at DATETIME COMMENT '定价时间', " +
  1042. "status VARCHAR(32) DEFAULT 'active' COMMENT '状态: draft/active/pending_exchange/approved/exchanging/fulfilled/cancelled/rejected', " +
  1043. "exchange_requested_at DATETIME COMMENT '申请兑换时间', " +
  1044. "exchange_approved_by BIGINT COMMENT '批准者ID', " +
  1045. "exchange_approved_at DATETIME COMMENT '批准时间', " +
  1046. "points_deducted_at DATETIME COMMENT '扣积分时间', " +
  1047. "fulfilled_by BIGINT COMMENT '兑现者ID', " +
  1048. "fulfilled_at DATETIME COMMENT '兑现时间', " +
  1049. "child_confirmed_at DATETIME COMMENT '孩子确认收货时间', " +
  1050. "rejected_by BIGINT COMMENT '拒绝者ID', " +
  1051. "rejected_at DATETIME COMMENT '拒绝时间', " +
  1052. "reject_reason VARCHAR(500) COMMENT '拒绝原因', " +
  1053. "reject_count INT DEFAULT 0 COMMENT '被拒绝次数', " +
  1054. "cancelled_by BIGINT COMMENT '取消者ID', " +
  1055. "cancelled_at DATETIME COMMENT '取消时间', " +
  1056. "cancel_reason VARCHAR(500) COMMENT '取消原因', " +
  1057. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1058. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1059. "INDEX idx_family_id (family_id), " +
  1060. "INDEX idx_child_id (child_id), " +
  1061. "INDEX idx_status (status)" +
  1062. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿单表'");
  1063. log.info("已创建wishes表");
  1064. } catch (Exception e) {
  1065. log.warn("创建wishes表可能已存在: {}", e.getMessage());
  1066. }
  1067. // 迁移24: users表添加个人信息补充字段
  1068. try {
  1069. Integer exists = jdbcTemplate.queryForObject(
  1070. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'ethnicity'",
  1071. Integer.class);
  1072. if (exists == null || exists == 0) {
  1073. jdbcTemplate.execute("ALTER TABLE users " +
  1074. "ADD COLUMN ethnicity VARCHAR(32) DEFAULT NULL COMMENT '民族', " +
  1075. "ADD COLUMN blood_type VARCHAR(16) DEFAULT NULL COMMENT '血型', " +
  1076. "ADD COLUMN highest_education VARCHAR(32) DEFAULT NULL COMMENT '最高学历', " +
  1077. "ADD COLUMN marital_status VARCHAR(16) DEFAULT NULL COMMENT '婚姻状态', " +
  1078. "ADD COLUMN family_role VARCHAR(16) DEFAULT NULL COMMENT '家长身份: 爸爸/妈妈/爷爷/奶奶/姥爷/姥姥/其他'");
  1079. log.info("已添加个人信息补充字段到users表");
  1080. } else {
  1081. log.info("个人信息补充字段已存在,跳过");
  1082. }
  1083. } catch (Exception e) {
  1084. log.warn("检查/添加个人信息补充字段失败: {}", e.getMessage());
  1085. }
  1086. // Phase 0: 汐福俱乐部 — 服务商字段
  1087. try {
  1088. Integer exists = jdbcTemplate.queryForObject(
  1089. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'vendor_type'",
  1090. Integer.class);
  1091. if (exists == null || exists == 0) {
  1092. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_type VARCHAR(32) DEFAULT NULL COMMENT '服务商类型: planner/activity_provider/product_supplier'");
  1093. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_status VARCHAR(16) DEFAULT NULL COMMENT '服务商审核状态: pending/approved/rejected'");
  1094. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_reject_reason VARCHAR(500) DEFAULT NULL COMMENT '审核拒绝原因'");
  1095. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN is_family_admin TINYINT DEFAULT 0 COMMENT '是否家庭管理员'");
  1096. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_info VARCHAR(2000) DEFAULT NULL COMMENT '服务商资质信息(JSON)'");
  1097. log.info("已添加汐福俱乐部服务商字段到users表");
  1098. } else {
  1099. log.info("汐福俱乐部服务商字段已存在,跳过");
  1100. }
  1101. } catch (Exception e) {
  1102. log.warn("检查/添加服务商字段失败: {}", e.getMessage());
  1103. }
  1104. // Phase 0: 迁移已有规划师到服务商体系
  1105. try {
  1106. int updated = jdbcTemplate.update(
  1107. "UPDATE users SET vendor_type = 'planner', vendor_status = 'approved' WHERE teacher_status = 'approved' AND vendor_type IS NULL"
  1108. );
  1109. if (updated > 0) {
  1110. log.info("已迁移 {} 名规划师到服务商体系", updated);
  1111. }
  1112. } catch (Exception e) {
  1113. log.warn("迁移规划师到服务商体系失败: {}", e.getMessage());
  1114. }
  1115. // ==================== Phase 1: 汐福俱乐部商品系统 ====================
  1116. // 商品表
  1117. try {
  1118. jdbcTemplate.execute(
  1119. "CREATE TABLE IF NOT EXISTS products (" +
  1120. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  1121. " name VARCHAR(200) NOT NULL," +
  1122. " description VARCHAR(1000)," +
  1123. " intro TEXT," +
  1124. " cover_image VARCHAR(500)," +
  1125. " images TEXT," +
  1126. " price DECIMAL(10,2) NOT NULL DEFAULT 0," +
  1127. " stock INT NOT NULL DEFAULT 0," +
  1128. " sales_count INT NOT NULL DEFAULT 0," +
  1129. " product_type VARCHAR(32) NOT NULL COMMENT 'activity/course/physical/digital/service'," +
  1130. " vendor_id BIGINT," +
  1131. " vendor_name VARCHAR(100)," +
  1132. " status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT 'draft/pending/approved/rejected/on_shelf/off_shelf'," +
  1133. " reject_reason VARCHAR(500)," +
  1134. " member_price DECIMAL(10,2)," +
  1135. " member_eligible VARCHAR(32) DEFAULT 'none' COMMENT 'all/budget/none'," +
  1136. " domain VARCHAR(32) COMMENT 'action/mind/body/wisdom/wealth'," +
  1137. " external_source VARCHAR(64)," +
  1138. " external_id VARCHAR(128)," +
  1139. " external_data TEXT," +
  1140. " created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
  1141. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1142. ")"
  1143. );
  1144. } catch (Exception e) {
  1145. log.warn("创建products表失败: {}", e.getMessage());
  1146. }
  1147. // 订单表
  1148. try {
  1149. jdbcTemplate.execute(
  1150. "CREATE TABLE IF NOT EXISTS product_orders (" +
  1151. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  1152. " order_no VARCHAR(64) NOT NULL UNIQUE," +
  1153. " product_id BIGINT NOT NULL," +
  1154. " product_name VARCHAR(200)," +
  1155. " product_type VARCHAR(32)," +
  1156. " buyer_id BIGINT NOT NULL," +
  1157. " family_id BIGINT," +
  1158. " quantity INT NOT NULL DEFAULT 1," +
  1159. " unit_price DECIMAL(10,2) NOT NULL," +
  1160. " total_amount DECIMAL(10,2) NOT NULL," +
  1161. " status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/completed/cancelled/refunded'," +
  1162. " payment_method VARCHAR(32) COMMENT 'wechat/points/membership'," +
  1163. " remark VARCHAR(500)," +
  1164. " paid_at DATETIME," +
  1165. " created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
  1166. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1167. ")"
  1168. );
  1169. } catch (Exception e) {
  1170. log.warn("创建product_orders表失败: {}", e.getMessage());
  1171. }
  1172. // 系统配置表
  1173. try {
  1174. jdbcTemplate.execute(
  1175. "CREATE TABLE IF NOT EXISTS sys_config (" +
  1176. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  1177. " config_key VARCHAR(64) NOT NULL UNIQUE," +
  1178. " config_value TEXT," +
  1179. " description VARCHAR(500)," +
  1180. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1181. ")"
  1182. );
  1183. } catch (Exception e) {
  1184. log.warn("创建sys_config表失败: {}", e.getMessage());
  1185. }
  1186. // SysConfig 种子数据
  1187. try {
  1188. insertSysConfigSeed("points_exchange_rate", "0.01", "积分兑换汇率(1积分=0.01元)");
  1189. insertSysConfigSeed("points_deduct_cap", "0.5", "积分抵扣上限(50%)");
  1190. insertSysConfigSeed("member_fee_standard", "1314", "标准会员年费(元)");
  1191. insertSysConfigSeed("member_fee_premium", "3650", "高级会员年费(元)");
  1192. insertSysConfigSeed("member_eligible_budget", "1314", "等值商品选择预算上限(元)");
  1193. insertSysConfigSeed("renewal_discount_rate", "0.85", "续费折扣率");
  1194. insertSysConfigSeed("member_discount_standard", "0.9", "标准会员折扣率");
  1195. insertSysConfigSeed("member_discount_premium", "0.8", "高级会员折扣率");
  1196. log.info("SysConfig 种子数据已加载");
  1197. } catch (Exception e) {
  1198. log.warn("SysConfig 种子数据初始化失败: {}", e.getMessage());
  1199. }
  1200. // Product 种子数据(5 个示例商品,覆盖全部 5 种 productType)
  1201. try {
  1202. insertProductSeed("亲子关系工作坊", "线下半日工作坊,重建家庭对话模式", "activity", new java.math.BigDecimal("388.00"), "action", "all");
  1203. insertProductSeed("情绪管理21天训练营", "21天视频课程,识别并管理情绪", "course", new java.math.BigDecimal("199.00"), "mind", "all");
  1204. insertProductSeed("肠道菌群检测套装", "家用检测 + 报告解读", "physical", new java.math.BigDecimal("599.00"), "body", "budget");
  1205. insertProductSeed("儿童多模态认知测评", "在线测评 + 个性化建议", "digital", new java.math.BigDecimal("99.00"), "wisdom", "none");
  1206. insertProductSeed("成长规划师1对1咨询", "60分钟专业规划", "service", new java.math.BigDecimal("500.00"), "wealth", "budget");
  1207. log.info("Product 种子数据已加载");
  1208. } catch (Exception e) {
  1209. log.warn("Product 种子数据初始化失败: {}", e.getMessage());
  1210. }
  1211. log.info("数据库迁移完成");
  1212. }
  1213. private void initializeDefaultData() {
  1214. // 初始化预设奖励模板
  1215. try {
  1216. // 检查是否已有预设奖励
  1217. Integer count = jdbcTemplate.queryForObject(
  1218. "SELECT COUNT(*) FROM rewards WHERE is_template = 1", Integer.class);
  1219. if (count == null || count == 0) {
  1220. // 插入预设奖励模板(child_id为NULL表示模板)
  1221. jdbcTemplate.execute("INSERT INTO rewards (family_id, child_id, title, points_required, category, is_template) VALUES " +
  1222. "(0, NULL, \'看动画片30分钟\', 30, \'精神类\', 1), " +
  1223. "(0, NULL, \'玩平板游戏30分钟\', 40, \'特权类\', 1), " +
  1224. "(0, NULL, \'去游乐场\', 100, \'物质类\', 1), " +
  1225. "(0, NULL, \'买玩具\', 150, \'物质类\', 1), " +
  1226. "(0, NULL, \'吃冰淇淋\', 20, \'物质类\', 1), " +
  1227. "(0, NULL, \'选择晚餐\', 25, \'特权类\', 1)");
  1228. log.info("预设奖励模板已初始化");
  1229. }
  1230. } catch (Exception e) {
  1231. log.warn("初始化预设奖励失败: {}", e.getMessage());
  1232. }
  1233. // 初始化奖励分类
  1234. try {
  1235. Integer count = jdbcTemplate.queryForObject(
  1236. "SELECT COUNT(*) FROM reward_categories WHERE family_id = 0", Integer.class);
  1237. if (count == null || count == 0) {
  1238. jdbcTemplate.execute("INSERT INTO reward_categories (family_id, name, icon, sort_order) VALUES " +
  1239. "(0, '物质类', 'gift', 1), " +
  1240. "(0, '精神类', 'star', 2), " +
  1241. "(0, '特权类', 'crown', 3)");
  1242. log.info("奖励分类已初始化");
  1243. }
  1244. } catch (Exception e) {
  1245. log.warn("初始化奖励分类失败: {}", e.getMessage());
  1246. }
  1247. // 初始化打卡里程碑
  1248. try {
  1249. Integer count = jdbcTemplate.queryForObject(
  1250. "SELECT COUNT(*) FROM streak_milestones", Integer.class);
  1251. if (count == null || count == 0) {
  1252. jdbcTemplate.execute("INSERT INTO streak_milestones (days, reward_points) VALUES " +
  1253. "(3, 5), (7, 10), (30, 30), (100, 100)");
  1254. log.info("打卡里程碑已初始化");
  1255. }
  1256. } catch (Exception e) {
  1257. log.warn("初始化打卡里程碑失败: {}", e.getMessage());
  1258. }
  1259. // 初始化勋章定义
  1260. try {
  1261. Integer count = jdbcTemplate.queryForObject(
  1262. "SELECT COUNT(*) FROM badge_definitions", Integer.class);
  1263. if (count == null || count == 0) {
  1264. jdbcTemplate.execute("INSERT INTO badge_definitions (badge_type, name, description, icon, condition_type, condition_value, reward_points) VALUES " +
  1265. "('first_task', '初次完成任务', '首次完成任意任务', 'star', 'task_count', 1, 0), " +
  1266. "('streak_3', '连续3天', '连续3天按时完成任务', 'fire', 'streak_days', 3, 5), " +
  1267. "('streak_7', '连续7天', '连续7天按时完成任务', 'fire', 'streak_days', 7, 10), " +
  1268. "('points_50', '积分达人', '一周获得50积分', 'trophy', 'weekly_points', 50, 0), " +
  1269. "('task_100', '任务达人', '完成100个任务', 'medal', 'task_count', 100, 20)");
  1270. log.info("勋章定义已初始化");
  1271. }
  1272. } catch (Exception e) {
  1273. log.warn("初始化勋章定义失败: {}", e.getMessage());
  1274. }
  1275. // 初始化小游戏数据
  1276. try {
  1277. Integer count = jdbcTemplate.queryForObject(
  1278. "SELECT COUNT(*) FROM mini_games", Integer.class);
  1279. if (count == null || count == 0) {
  1280. jdbcTemplate.execute("INSERT INTO mini_games (game_code, game_name, description, icon, default_duration, default_points, game_url, sort_order, status) VALUES " +
  1281. "('schulte', '舒尔特方格', '训练专注力和眼动能力,找到1-25按顺序点击', '🎯', 5, 5, '/pages/games/schulte', 1, 1), " +
  1282. "('1a2b', '猜数字', '猜对数字和位置获得积分,锻炼逻辑思维', '🔢', 10, 5, '/pages/games/1a2b', 2, 1), " +
  1283. "('sudoku', '数独', '填充数字使每行每列每个宫格都包含1-9', '🔳', 15, 8, '/pages/games/sudoku', 3, 1)");
  1284. log.info("小游戏数据已初始化");
  1285. }
  1286. } catch (Exception e) {
  1287. log.warn("初始化小游戏数据失败: {}", e.getMessage());
  1288. }
  1289. // 初始化默认管理员
  1290. try {
  1291. // 先检查并添加role列(如果不存在)
  1292. try {
  1293. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN role VARCHAR(20) DEFAULT \'admin\' AFTER email");
  1294. log.info("已添加role列到admins表");
  1295. } catch (Exception e) {
  1296. // 列已存在,忽略错误
  1297. }
  1298. Integer count = jdbcTemplate.queryForObject(
  1299. "SELECT COUNT(*) FROM admins", Integer.class);
  1300. if (count == null || count == 0) {
  1301. // 默认管理员: 手机号 13800138000, 密码 admin123
  1302. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  1303. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  1304. "(\'admin\', \'" + encryptedPassword + "\', \'系统管理员\', \'13800138000\', \'admin@xzyj.com\', \'admin\', 1)");
  1305. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  1306. }
  1307. } catch (Exception e) {
  1308. log.warn("初始化默认管理员失败: {}", e.getMessage());
  1309. }
  1310. // 初始化成长规划师WEB管理账号
  1311. try {
  1312. Integer teacherCount = jdbcTemplate.queryForObject(
  1313. "SELECT COUNT(*) FROM admins WHERE username = 'teacher'", Integer.class);
  1314. if (teacherCount == null || teacherCount == 0) {
  1315. String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_teacher123".getBytes());
  1316. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, role, status) VALUES " +
  1317. "('teacher', '" + teacherPassword + "', '成长规划师', '13800138003', 'teacher', 1)");
  1318. log.info("成长规划师管理账号已初始化: 用户名 teacher, 密码 teacher123");
  1319. }
  1320. } catch (Exception e) {
  1321. log.warn("初始化成长规划师管理账号失败: {}", e.getMessage());
  1322. }
  1323. // 初始化任务模板示例数据
  1324. try {
  1325. Integer count = jdbcTemplate.queryForObject(
  1326. "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
  1327. if (count == null || count == 0) {
  1328. jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES " +
  1329. "('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1), " +
  1330. "('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1), " +
  1331. "('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1), " +
  1332. "('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1), " +
  1333. "('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1), " +
  1334. "('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1), " +
  1335. "('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1), " +
  1336. "('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1)");
  1337. log.info("任务模板示例数据已初始化");
  1338. }
  1339. } catch (Exception e) {
  1340. log.warn("初始化任务模板失败: {}", e.getMessage());
  1341. }
  1342. // 初始化街道数据示例(广东省广州市)
  1343. try {
  1344. Integer count = jdbcTemplate.queryForObject(
  1345. "SELECT COUNT(*) FROM streets", Integer.class);
  1346. if (count == null || count == 0) {
  1347. // 省
  1348. jdbcTemplate.execute("INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES " +
  1349. "('广东省', '44', '广东省', 1, NULL, 1)");
  1350. // 市
  1351. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES " +
  1352. "('广东省', '44', '广州市', '4401', '广东省广州市', 2, 1, 1), " +
  1353. "('广东省', '44', '深圳市', '4403', '广东省深圳市', 2, 1, 2), " +
  1354. "('广东省', '44', '珠海市', '4404', '广东省珠海市', 2, 1, 3)");
  1355. // 区
  1356. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES " +
  1357. "('广东省', '44', '广州市', '4401', '天河区', '440106', '广东省广州市天河区', 3, 2, 1), " +
  1358. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '广东省广州市越秀区', 3, 2, 2), " +
  1359. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '广东省广州市番禺区', 3, 2, 3), " +
  1360. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '广东省深圳市南山区', 3, 3, 1), " +
  1361. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '广东省深圳市福田区', 3, 3, 2)");
  1362. // 街道
  1363. 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 " +
  1364. "('广东省', '44', '广州市', '4401', '天河区', '440106', '天河南街道', '440106001', '广东省广州市天河区天河南街道', 4, 4, 1), " +
  1365. "('广东省', '44', '广州市', '4401', '天河区', '440106', '沙河街道', '440106002', '广东省广州市天河区沙河街道', 4, 4, 2), " +
  1366. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '东山街道', '440104001', '广东省广州市越秀区东山街道', 4, 5, 1), " +
  1367. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '市桥街道', '440113001', '广东省广州市番禺区市桥街道', 4, 6, 1), " +
  1368. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '南山街道', '440305001', '广东省深圳市南山区南山街道', 4, 7, 1), " +
  1369. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '福田街道', '440304001', '广东省深圳市福田区福田街道', 4, 8, 1)");
  1370. log.info("街道示例数据已初始化");
  1371. }
  1372. } catch (Exception e) {
  1373. log.warn("初始化街道数据失败: {}", e.getMessage());
  1374. }
  1375. try {
  1376. Integer count = jdbcTemplate.queryForObject(
  1377. "SELECT COUNT(*) FROM admins", Integer.class);
  1378. if (count == null || count == 0) {
  1379. // 默认管理员: 手机号 13800138000, 密码 admin123
  1380. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  1381. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  1382. "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 'admin', 1)");
  1383. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  1384. }
  1385. } catch (Exception e) {
  1386. log.warn("初始化任务模板失败: {}", e.getMessage());
  1387. }
  1388. // 初始化 users 表测试用户
  1389. try {
  1390. // 创建默认家庭(如果不存在)
  1391. Integer familyCount = jdbcTemplate.queryForObject(
  1392. "SELECT COUNT(*) FROM families WHERE id = 1", Integer.class);
  1393. if (familyCount == null || familyCount == 0) {
  1394. jdbcTemplate.execute("INSERT INTO families (id, name, invite_code) VALUES (1, '默认家庭', '888888')");
  1395. log.info("默认家庭已创建");
  1396. }
  1397. // 密码加密: MD5("xzyj_salt_" + password)
  1398. String adminPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_admin123".getBytes());
  1399. String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_teacher123".getBytes());
  1400. // 创建管理员用户(管理员角色,密码 admin123)
  1401. Integer adminUserCount = jdbcTemplate.queryForObject(
  1402. "SELECT COUNT(*) FROM users WHERE phone = '13800138000'", Integer.class);
  1403. if (adminUserCount == null || adminUserCount == 0) {
  1404. jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
  1405. "('seed_admin_13800138000', 1, 'admin', '13800138000', '系统管理员', '系统管理员', '" + adminPassword + "')");
  1406. log.info("测试管理员用户已创建: 13800138000 (admin角色, 密码 admin123)");
  1407. }
  1408. // 创建测试教师用户
  1409. Integer teacherUserCount = jdbcTemplate.queryForObject(
  1410. "SELECT COUNT(*) FROM users WHERE phone = '13800138001'", Integer.class);
  1411. if (teacherUserCount == null || teacherUserCount == 0) {
  1412. jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
  1413. "('seed_teacher_13800138001', 1, 'teacher', '13800138001', '测试教师', '测试教师', '" + teacherPassword + "')");
  1414. log.info("测试教师用户已创建: 13800138001 (教师角色, 密码 teacher123)");
  1415. }
  1416. } catch (Exception e) {
  1417. log.warn("初始化测试用户失败: {}", e.getMessage());
  1418. }
  1419. // 初始化成长规划师模块相关表
  1420. initGuideModuleTables(jdbcTemplate);
  1421. }
  1422. private void initGuideModuleTables(JdbcTemplate jdbcTemplate) {
  1423. // 服务类型表
  1424. try {
  1425. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_types (" +
  1426. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1427. "name VARCHAR(100) NOT NULL COMMENT '类型名称', " +
  1428. "icon VARCHAR(50) COMMENT '图标', " +
  1429. "description VARCHAR(500) COMMENT '描述', " +
  1430. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  1431. "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
  1432. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1433. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1434. ")");
  1435. } catch (Exception e) {
  1436. log.warn("创建service_types表失败: {}", e.getMessage());
  1437. }
  1438. // 服务内容表
  1439. try {
  1440. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_contents (" +
  1441. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1442. "type_id BIGINT NOT NULL COMMENT '关联服务类型', " +
  1443. "title VARCHAR(200) NOT NULL COMMENT '内容标题', " +
  1444. "content TEXT COMMENT '富文本内容', " +
  1445. "duration INT COMMENT '建议时长(分钟)', " +
  1446. "age_range VARCHAR(50) COMMENT '适用年龄', " +
  1447. "price DECIMAL(10,2) COMMENT '参考价格', " +
  1448. "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/ACTIVE', " +
  1449. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1450. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1451. ")");
  1452. } catch (Exception e) {
  1453. log.warn("创建service_contents表失败: {}", e.getMessage());
  1454. }
  1455. // 套餐模板表
  1456. try {
  1457. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_templates (" +
  1458. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1459. "name VARCHAR(100) NOT NULL COMMENT '模板名称', " +
  1460. "validity_months INT COMMENT '有效期(月)', " +
  1461. "reference_price DECIMAL(10,2) COMMENT '参考价格', " +
  1462. "min_price DECIMAL(10,2) COMMENT '最低售价', " +
  1463. "description VARCHAR(500) COMMENT '说明', " +
  1464. "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
  1465. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1466. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1467. ")");
  1468. } catch (Exception e) {
  1469. log.warn("创建guide_package_templates表失败: {}", e.getMessage());
  1470. }
  1471. // 套餐模板服务项表
  1472. try {
  1473. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS package_template_services (" +
  1474. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1475. "template_id BIGINT NOT NULL COMMENT '关联模板', " +
  1476. "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
  1477. "quantity INT DEFAULT 1 COMMENT '数量', " +
  1478. "unit VARCHAR(20) COMMENT '单位' " +
  1479. ")");
  1480. } catch (Exception e) {
  1481. log.warn("创建package_template_services表失败: {}", e.getMessage());
  1482. }
  1483. // 成长规划师套餐表
  1484. try {
  1485. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_packages (" +
  1486. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1487. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  1488. "template_id BIGINT COMMENT '关联模板', " +
  1489. "name VARCHAR(100) NOT NULL COMMENT '套餐名称', " +
  1490. "price DECIMAL(10,2) NOT NULL COMMENT '销售价格', " +
  1491. "validity_months INT COMMENT '有效期(月)', " +
  1492. "description VARCHAR(500) COMMENT '套餐说明', " +
  1493. "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/PAUSED', " +
  1494. "sales_count INT DEFAULT 0 COMMENT '销量', " +
  1495. "share_code VARCHAR(50) COMMENT '分享码', " +
  1496. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1497. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1498. ")");
  1499. } catch (Exception e) {
  1500. log.warn("创建guide_packages表失败: {}", e.getMessage());
  1501. }
  1502. // 迁移:guide_packages 表添加缺失列
  1503. try {
  1504. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID' AFTER id");
  1505. } catch (Exception e) {
  1506. }
  1507. try {
  1508. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN package_id BIGINT COMMENT '关联模板' AFTER guide_id");
  1509. } catch (Exception e) {
  1510. }
  1511. try {
  1512. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN commission_rate DECIMAL(5,2) COMMENT '佣金比例' AFTER package_id");
  1513. } catch (Exception e) {
  1514. }
  1515. try {
  1516. jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN teacher_id BIGINT NULL");
  1517. } catch (Exception e) {
  1518. }
  1519. try {
  1520. jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN template_id BIGINT NULL");
  1521. } catch (Exception e) {
  1522. }
  1523. // 迁移:guide_packages 表添加 level 列(GuidePackage 实体需要)
  1524. try {
  1525. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN level VARCHAR(20) COMMENT '套餐等级' AFTER name");
  1526. } catch (Exception e) {
  1527. }
  1528. // 迁移:guide_packages 表添加 validity_days 列
  1529. try {
  1530. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN validity_days INT DEFAULT 0 COMMENT '有效期天数' AFTER validity_months");
  1531. } catch (Exception e) {
  1532. }
  1533. // 迁移:guide_packages 表添加 sort_order 列
  1534. try {
  1535. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序' AFTER status");
  1536. } catch (Exception e) {
  1537. }
  1538. // 成长规划师套餐服务项表
  1539. try {
  1540. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_services (" +
  1541. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1542. "package_id BIGINT NOT NULL COMMENT '关联套餐', " +
  1543. "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
  1544. "quantity INT DEFAULT 1 COMMENT '数量', " +
  1545. "unit VARCHAR(20) COMMENT '单位' " +
  1546. ")");
  1547. } catch (Exception e) {
  1548. log.warn("创建guide_package_services表失败: {}", e.getMessage());
  1549. }
  1550. // 订单表
  1551. try {
  1552. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_orders (" +
  1553. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1554. "order_no VARCHAR(50) NOT NULL COMMENT '订单号', " +
  1555. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  1556. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  1557. "package_id BIGINT NOT NULL COMMENT '套餐ID', " +
  1558. "amount DECIMAL(10,2) NOT NULL COMMENT '金额', " +
  1559. "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/PAID/COMPLETED/REFUNDED', " +
  1560. "pay_time DATETIME COMMENT '支付时间', " +
  1561. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1562. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1563. ")");
  1564. } catch (Exception e) {
  1565. log.warn("创建guide_orders表失败: {}", e.getMessage());
  1566. }
  1567. // 佣金明细表
  1568. try {
  1569. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_commissions (" +
  1570. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1571. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  1572. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  1573. "amount DECIMAL(10,2) NOT NULL COMMENT '佣金金额', " +
  1574. "rate DECIMAL(5,2) COMMENT '佣金比例', " +
  1575. "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/COMPLETED', " +
  1576. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  1577. ")");
  1578. } catch (Exception e) {
  1579. log.warn("创建guide_commissions表失败: {}", e.getMessage());
  1580. }
  1581. // 服务记录表
  1582. try {
  1583. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_service_logs (" +
  1584. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1585. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  1586. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  1587. "order_id BIGINT COMMENT '订单ID', " +
  1588. "service_content_id BIGINT NOT NULL COMMENT '服务内容ID', " +
  1589. "notes VARCHAR(500) COMMENT '服务记录', " +
  1590. "duration INT COMMENT '服务时长(分钟)', " +
  1591. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  1592. ")");
  1593. } catch (Exception e) {
  1594. log.warn("创建guide_service_logs表失败: {}", e.getMessage());
  1595. }
  1596. // 成长规划师邀请码表
  1597. try {
  1598. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_invite_codes (" +
  1599. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1600. "guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
  1601. "code VARCHAR(50) NOT NULL UNIQUE COMMENT '邀请码', " +
  1602. "status VARCHAR(20) DEFAULT 'active' COMMENT '状态', " +
  1603. "used_by_user_id BIGINT COMMENT '使用该邀请码的用户ID', " +
  1604. "used_at DATETIME COMMENT '使用时间', " +
  1605. "expire_at DATETIME COMMENT '过期时间', " +
  1606. "use_count INT DEFAULT 0 COMMENT '使用次数', " +
  1607. "max_use_count INT COMMENT '最大使用次数', " +
  1608. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1609. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1610. ")");
  1611. } catch (Exception e) {
  1612. log.warn("创建guide_invite_codes表失败: {}", e.getMessage());
  1613. }
  1614. // 迁移29: assessment_orders表添加appointment_id字段
  1615. try {
  1616. jdbcTemplate.execute("ALTER TABLE assessment_orders ADD COLUMN appointment_id BIGINT COMMENT '关联的预约ID'");
  1617. log.info("已添加appointment_id列到assessment_orders表");
  1618. } catch (Exception e) {
  1619. // 列已存在,忽略错误
  1620. }
  1621. log.info("成长规划师模块数据库表初始化完成");
  1622. }
  1623. private void insertSysConfigSeed(String key, String value, String desc) {
  1624. Integer count = jdbcTemplate.queryForObject(
  1625. "SELECT COUNT(*) FROM sys_config WHERE config_key = ?", Integer.class, key);
  1626. if (count == null || count == 0) {
  1627. jdbcTemplate.update(
  1628. "INSERT INTO sys_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, NOW())",
  1629. key, value, desc);
  1630. }
  1631. }
  1632. private void insertProductSeed(String name, String desc, String type,
  1633. java.math.BigDecimal price,
  1634. String domain, String memberEligible) {
  1635. Integer count = jdbcTemplate.queryForObject(
  1636. "SELECT COUNT(*) FROM products WHERE name = ? AND product_type = ?", Integer.class, name, type);
  1637. if (count == null || count == 0) {
  1638. jdbcTemplate.update(
  1639. "INSERT INTO products (name, description, price, product_type, vendor_id, vendor_name, status, domain, member_eligible, created_at, updated_at) " +
  1640. "VALUES (?, ?, ?, ?, NULL, '平台官方', 'on_shelf', ?, ?, NOW(), NOW())",
  1641. name, desc, price, type, domain, memberEligible);
  1642. }
  1643. }
  1644. }