DatabaseInitializer.java 171 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103
  1. package com.etotem.cfc.config;
  2. import com.etotem.cfc.mapper.*;
  3. import lombok.extern.slf4j.Slf4j;
  4. import javax.annotation.Resource;
  5. import org.springframework.boot.CommandLineRunner;
  6. import org.springframework.core.io.ResourceLoader;
  7. import org.springframework.jdbc.core.JdbcTemplate;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.util.StreamUtils;
  10. import java.nio.charset.StandardCharsets;
  11. import java.util.ArrayList;
  12. import java.util.List;
  13. import java.util.regex.Matcher;
  14. import java.util.regex.Pattern;
  15. @Slf4j
  16. @Component
  17. public class DatabaseInitializer implements CommandLineRunner {
  18. @Resource
  19. private JdbcTemplate jdbcTemplate;
  20. @Resource
  21. private ResourceLoader resourceLoader;
  22. @Override
  23. public void run(String... args) throws Exception {
  24. log.info("开始初始化数据库...");
  25. // 1. 检查并创建数据库
  26. initializeDatabase();
  27. // 2. 检查并创建表
  28. initializeTables();
  29. // 3. 执行数据库迁移(添加新列)
  30. runMigrations();
  31. // 4. 初始化默认数据
  32. initializeDefaultData();
  33. // 5. 初始化食品相关表
  34. initFoodTables();
  35. log.info("数据库初始化完成!");
  36. }
  37. private void initializeDatabase() {
  38. try {
  39. // 尝试连接数据库,如果不存在则创建
  40. jdbcTemplate.execute("CREATE DATABASE IF NOT EXISTS xzyj DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
  41. log.info("数据库检查完成");
  42. } catch (Exception e) {
  43. log.warn("创建数据库失败: {}", e.getMessage());
  44. }
  45. }
  46. private void initializeTables() {
  47. try {
  48. org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:schema.sql");
  49. String content;
  50. try (java.io.InputStream is = resource.getInputStream()) {
  51. content = new String(StreamUtils.copyToByteArray(is), StandardCharsets.UTF_8);
  52. }
  53. // Remove SQL single-line comments
  54. content = content.replaceAll("(?m)^\\s*--.*$", "");
  55. // Extract all CREATE TABLE IF NOT EXISTS statements
  56. Pattern pattern = Pattern.compile(
  57. "CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+.*?;",
  58. Pattern.CASE_INSENSITIVE | Pattern.DOTALL
  59. );
  60. Matcher matcher = pattern.matcher(content);
  61. List<String> createTableSQLs = new ArrayList<>();
  62. while (matcher.find()) {
  63. String sql = matcher.group().trim();
  64. if (!sql.isEmpty()) {
  65. createTableSQLs.add(sql);
  66. }
  67. }
  68. log.info("从 schema.sql 加载了 {} 个建表语句", createTableSQLs.size());
  69. for (String sql : createTableSQLs) {
  70. try {
  71. jdbcTemplate.execute(sql);
  72. } catch (Exception e) {
  73. log.warn("创建表失败: {}", e.getMessage());
  74. }
  75. }
  76. log.info("数据表检查完成");
  77. } catch (Exception e) {
  78. log.error("读取 schema.sql 失败", e);
  79. }
  80. }
  81. private void runMigrations() {
  82. // 迁移1: 任务表添加执行者字段
  83. try {
  84. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_type ENUM('child', 'parent') DEFAULT 'child' COMMENT '执行者类型'");
  85. log.info("已添加executor_type列到tasks表");
  86. } catch (Exception e) {
  87. // 列已存在,忽略错误
  88. }
  89. try {
  90. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_id BIGINT COMMENT '执行者ID'");
  91. log.info("已添加executor_id列到tasks表");
  92. } catch (Exception e) {
  93. // 列已存在,忽略错误
  94. }
  95. try {
  96. jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_executor (executor_type, executor_id)");
  97. log.info("已添加idx_executor索引到tasks表");
  98. } catch (Exception e) {
  99. // 索引已存在,忽略错误
  100. }
  101. // 迁移2: 用户表添加角色和积分字段
  102. try {
  103. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN roles VARCHAR(100) COMMENT '角色列表'");
  104. log.info("已添加roles列到users表");
  105. } catch (Exception e) {
  106. // 列已存在,忽略错误
  107. }
  108. try {
  109. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_points INT DEFAULT 0 COMMENT '家长总积分'");
  110. log.info("已添加total_points列到users表");
  111. } catch (Exception e) {
  112. // 列已存在,忽略错误
  113. }
  114. try {
  115. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_roles (roles(20))");
  116. log.info("已添加idx_roles索引到users表");
  117. } catch (Exception e) {
  118. // 索引已存在,忽略错误
  119. }
  120. // 迁移3: users表添加新字段
  121. try {
  122. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN id_card VARCHAR(18) COMMENT '身份证号'");
  123. log.info("已添加id_card列到users表");
  124. } catch (Exception e) {
  125. // 列已存在,忽略错误
  126. }
  127. try {
  128. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN real_name VARCHAR(50) COMMENT '真实姓名'");
  129. log.info("已添加real_name列到users表");
  130. } catch (Exception e) {
  131. // 列已存在,忽略错误
  132. }
  133. try {
  134. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN phone VARCHAR(11) COMMENT '手机号'");
  135. log.info("已添加phone列到users表");
  136. } catch (Exception e) {
  137. // 列已存在,忽略错误
  138. }
  139. try {
  140. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN birthday DATE COMMENT '生日'");
  141. log.info("已添加birthday列到users表");
  142. } catch (Exception e) {
  143. // 列已存在,忽略错误
  144. }
  145. try {
  146. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  147. log.info("已添加gender列到users表");
  148. } catch (Exception e) {
  149. // 列已存在,忽略错误
  150. }
  151. try {
  152. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_phone (phone)");
  153. log.info("已添加idx_phone索引到users表");
  154. } catch (Exception e) {
  155. // 索引已存在,忽略错误
  156. }
  157. // 迁移4: children表添加新字段
  158. try {
  159. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN birthday DATE COMMENT '生日'");
  160. log.info("已添加birthday列到children表");
  161. } catch (Exception e) {
  162. // 列已存在,忽略错误
  163. }
  164. try {
  165. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN gender ENUM('male', 'female') COMMENT '性别'");
  166. log.info("已添加gender列到children表");
  167. } catch (Exception e) {
  168. // 列已存在,忽略错误
  169. }
  170. // 迁移5: 修改users.role为支持teacher
  171. try {
  172. jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent', 'child', 'teacher') NOT NULL");
  173. log.info("已更新users.role字段支持teacher角色");
  174. } catch (Exception e) {
  175. // 修改失败,忽略错误
  176. }
  177. // 迁移5.1: 修改users.role为支持admin
  178. try {
  179. jdbcTemplate.execute("ALTER TABLE users MODIFY COLUMN role ENUM('parent', 'child', 'teacher', 'admin') NOT NULL");
  180. log.info("已更新users.role字段支持admin角色");
  181. } catch (Exception e) {
  182. // 修改失败,忽略错误
  183. }
  184. // 迁移5.2: 确保管理员角色为admin(修复旧数据遗留问题)
  185. try {
  186. int updated = jdbcTemplate.update("UPDATE admins SET role = 'admin' WHERE username = 'admin'");
  187. if (updated > 0) {
  188. log.info("已修正管理员角色为admin");
  189. }
  190. } catch (Exception e) {
  191. // 更新失败,忽略错误
  192. }
  193. // 迁移6: 创建streets表(街道地址维度表)
  194. try {
  195. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS streets (" +
  196. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  197. "province VARCHAR(50) COMMENT '省份', " +
  198. "province_code VARCHAR(20) COMMENT '省份编码', " +
  199. "city VARCHAR(50) COMMENT '城市', " +
  200. "city_code VARCHAR(20) COMMENT '城市编码', " +
  201. "district VARCHAR(50) COMMENT '区县', " +
  202. "district_code VARCHAR(20) COMMENT '区县编码', " +
  203. "street VARCHAR(100) COMMENT '街道', " +
  204. "street_code VARCHAR(20) COMMENT '街道编码', " +
  205. "full_name VARCHAR(200) COMMENT '完整地址', " +
  206. "level INT COMMENT '层级:1省2市3区4街道', " +
  207. "parent_id BIGINT COMMENT '父级ID', " +
  208. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  209. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  210. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  211. "INDEX idx_level (level), " +
  212. "INDEX idx_parent_id (parent_id), " +
  213. "INDEX idx_street_code (street_code), " +
  214. "INDEX idx_full_name (full_name(50))" +
  215. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  216. log.info("已创建streets表");
  217. } catch (Exception e) {
  218. // 表已存在,忽略错误
  219. }
  220. // 迁移7: families表添加地址字段
  221. try {
  222. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  223. log.info("已添加street_id列到families表");
  224. } catch (Exception e) {
  225. // 列已存在,忽略错误
  226. }
  227. try {
  228. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  229. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  230. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  231. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  232. log.info("已添加地址字段到families表");
  233. } catch (Exception e) {
  234. // 列已存在,忽略错误
  235. }
  236. try {
  237. jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_street_id (street_id)");
  238. log.info("已添加idx_street_id索引到families表");
  239. } catch (Exception e) {
  240. // 索引已存在,忽略错误
  241. }
  242. // 迁移7b: families表添加teacher_id和creator_id字段
  243. try {
  244. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN teacher_id BIGINT COMMENT '绑定的成长规划师ID'");
  245. log.info("已添加teacher_id列到families表");
  246. } catch (Exception e) {
  247. // 列已存在,忽略错误
  248. }
  249. try {
  250. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN creator_id BIGINT COMMENT '家庭创建者ID'");
  251. log.info("已添加creator_id列到families表");
  252. } catch (Exception e) {
  253. // 列已存在,忽略错误
  254. }
  255. // 迁移8: teachers表添加地址字段
  256. try {
  257. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  258. log.info("已添加street_id列到teachers表");
  259. } catch (Exception e) {
  260. // 列已存在,忽略错误
  261. }
  262. try {
  263. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  264. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  265. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  266. jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  267. log.info("已添加地址字段到teachers表");
  268. } catch (Exception e) {
  269. // 列已存在,忽略错误
  270. }
  271. try {
  272. jdbcTemplate.execute("ALTER TABLE teachers ADD INDEX idx_street_id (street_id)");
  273. log.info("已添加idx_street_id索引到teachers表");
  274. } catch (Exception e) {
  275. // 索引已存在,忽略错误
  276. }
  277. // 迁移9: users表添加成长规划师相关字段
  278. try {
  279. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_no VARCHAR(50) COMMENT '成长规划师证书号'");
  280. log.info("已添加teacher_no列到users表");
  281. } catch (Exception e) {
  282. // 列已存在,忽略错误
  283. }
  284. try {
  285. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_photo VARCHAR(500) COMMENT '成长规划师照片'");
  286. log.info("已添加teacher_photo列到users表");
  287. } catch (Exception e) {
  288. // 列已存在,忽略错误
  289. }
  290. try {
  291. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN certificate_image VARCHAR(500) COMMENT '成长规划师证书照片'");
  292. log.info("已添加certificate_image列到users表");
  293. } catch (Exception e) {
  294. // 列已存在,忽略错误
  295. }
  296. try {
  297. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_status VARCHAR(20) DEFAULT 'pending' COMMENT '成长规划师审核状态'");
  298. log.info("已添加teacher_status列到users表");
  299. } catch (Exception e) {
  300. // 列已存在,忽略错误
  301. }
  302. try {
  303. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_teacher_status (teacher_status)");
  304. log.info("已添加idx_teacher_status索引到users表");
  305. } catch (Exception e) {
  306. // 索引已存在,忽略错误
  307. }
  308. // 迁移10: users表添加teacherFamilyIds字段(成长规划师关联的家庭ID列表)
  309. try {
  310. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN teacher_family_ids VARCHAR(500) COMMENT '成长规划师关联的家庭ID列表(逗号分隔)'");
  311. log.info("已添加teacher_family_ids列到users表");
  312. } catch (Exception e) {
  313. // 列已存在,忽略错误
  314. }
  315. // 迁移11: families表添加teacherId字段(绑定的成长规划师ID)
  316. try {
  317. jdbcTemplate.execute("ALTER TABLE families ADD COLUMN teacher_id BIGINT COMMENT '绑定的成长规划师ID'");
  318. log.info("已添加teacher_id列到families表");
  319. } catch (Exception e) {
  320. // 列已存在,忽略错误
  321. }
  322. try {
  323. jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_teacher_id (teacher_id)");
  324. log.info("已添加idx_teacher_id索引到families表");
  325. } catch (Exception e) {
  326. // 索引已存在,忽略错误
  327. }
  328. // 迁移12: 创建 teacher_messages 表(家长咨询成长规划师)
  329. try {
  330. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS teacher_messages (" +
  331. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  332. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  333. "parent_id BIGINT NOT NULL COMMENT '家长用户ID', " +
  334. "teacher_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
  335. "content TEXT NOT NULL COMMENT '消息内容', " +
  336. "reply_content TEXT COMMENT '回复内容', " +
  337. "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending(待回复)/replied(已回复)', " +
  338. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  339. "replied_at DATETIME COMMENT '回复时间', " +
  340. "INDEX idx_family_id (family_id), " +
  341. "INDEX idx_teacher_id (teacher_id), " +
  342. "INDEX idx_parent_id (parent_id), " +
  343. "INDEX idx_status (status)" +
  344. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  345. log.info("已创建teacher_messages表");
  346. } catch (Exception e) {
  347. // 表已存在,忽略错误
  348. }
  349. // 迁移13: admin_task_templates表添加teacher_id字段
  350. try {
  351. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN teacher_id BIGINT COMMENT '成长规划师ID'");
  352. log.info("已添加teacher_id列到admin_task_templates表");
  353. } catch (Exception e) {
  354. // 列已存在,忽略错误
  355. }
  356. // 迁移13b: admin_task_templates表添加新字段
  357. try {
  358. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
  359. log.info("已添加complete_types列到admin_task_templates表");
  360. } catch (Exception e) {
  361. // 列已存在,忽略错误
  362. }
  363. try {
  364. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN duration INT COMMENT '预计完成时长(分钟)'");
  365. log.info("已添加duration列到admin_task_templates表");
  366. } catch (Exception e) {
  367. // 列已存在,忽略错误
  368. }
  369. try {
  370. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
  371. log.info("已添加review_type列到admin_task_templates表");
  372. } catch (Exception e) {
  373. // 列已存在,忽略错误
  374. }
  375. try {
  376. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
  377. log.info("已添加task_type列到admin_task_templates表");
  378. } catch (Exception e) {
  379. // 列已存在,忽略错误
  380. }
  381. try {
  382. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
  383. log.info("已添加frequency列到admin_task_templates表");
  384. } catch (Exception e) {
  385. // 列已存在,忽略错误
  386. }
  387. try {
  388. jdbcTemplate.execute("ALTER TABLE admin_task_templates ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
  389. log.info("已添加max_frequency列到admin_task_templates表");
  390. } catch (Exception e) {
  391. // 列已存在,忽略错误
  392. }
  393. // 迁移14: rewards表child_id字段允许NULL(模板奖励不需要child_id)
  394. try {
  395. jdbcTemplate.execute("ALTER TABLE rewards MODIFY COLUMN child_id BIGINT NULL COMMENT '孩子ID,模板奖励为NULL'");
  396. log.info("已修改rewards.child_id字段允许NULL");
  397. } catch (Exception e) {
  398. // 修改失败,忽略错误
  399. }
  400. // 迁移15: admins表添加缺失列
  401. try {
  402. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN nickname VARCHAR(50) DEFAULT NULL AFTER password");
  403. log.info("已添加nickname列到admins表");
  404. } catch (Exception e) {
  405. // 列已存在,忽略错误
  406. }
  407. try {
  408. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN email VARCHAR(100) DEFAULT NULL AFTER phone");
  409. log.info("已添加email列到admins表");
  410. } catch (Exception e) {
  411. // 列已存在,忽略错误
  412. }
  413. try {
  414. jdbcTemplate.execute("ALTER TABLE admins MODIFY COLUMN status VARCHAR(20) DEFAULT 'active'");
  415. log.info("已修改admins.status字段类型");
  416. } catch (Exception e) {
  417. // 修改失败,忽略错误
  418. }
  419. // 迁移16: 创建用户操作日志表
  420. try {
  421. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS user_operation_logs (" +
  422. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  423. "user_id BIGINT COMMENT \'用户ID\', " +
  424. "nickname VARCHAR(50) COMMENT \'用户昵称\', " +
  425. "operation_type VARCHAR(20) NOT NULL COMMENT \'操作类型: login/logout/create/update/delete/query等\', " +
  426. "description VARCHAR(500) COMMENT \'操作描述\', " +
  427. "request_path VARCHAR(200) COMMENT \'请求路径\', " +
  428. "request_method VARCHAR(10) COMMENT \'请求方法\', " +
  429. "request_params TEXT COMMENT \'请求参数(脱敏)\', " +
  430. "response_status INT COMMENT \'响应状态码\', " +
  431. "client_ip VARCHAR(50) COMMENT \'客户端IP\', " +
  432. "user_agent VARCHAR(500) COMMENT \'User-Agent\', " +
  433. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  434. "INDEX idx_user_id (user_id), " +
  435. "INDEX idx_operation_type (operation_type), " +
  436. "INDEX idx_created_at (created_at)" +
  437. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  438. log.info("已创建user_operation_logs表");
  439. } catch (Exception e) {
  440. // 表已存在,忽略错误
  441. }
  442. // 迁移16: tasks表添加bonus_rule字段(POINT-002: 超额积分规则)
  443. try {
  444. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN bonus_rule VARCHAR(500) COMMENT \'超额积分规则JSON\'");
  445. log.info("已添加bonus_rule列到tasks表");
  446. } catch (Exception e) {
  447. // 列已存在,忽略错误
  448. }
  449. // 迁移17: tasks表添加points_expire_days字段(POINT-009: 积分有效期)
  450. try {
  451. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN points_expire_days INT DEFAULT 0 COMMENT \'积分有效期天数,0表示永久有效\'");
  452. log.info("已添加points_expire_days列到tasks表");
  453. } catch (Exception e) {
  454. // 列已存在,忽略错误
  455. }
  456. // 迁移18: points_log表添加expire_at字段(POINT-009: 积分有效期)
  457. try {
  458. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expire_at DATE COMMENT \'积分过期日期\'");
  459. log.info("已添加expire_at列到points_log表");
  460. } catch (Exception e) {
  461. // 列已存在,忽略错误
  462. }
  463. // 迁移19: points_log表添加expired字段(POINT-009: 过期状态标记)
  464. try {
  465. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN expired TINYINT DEFAULT 0 COMMENT \'是否已过期: 0否1是\'");
  466. log.info("已添加expired列到points_log表");
  467. } catch (Exception e) {
  468. // 列已存在,忽略错误
  469. }
  470. // 迁移20: tasks表添加template_id字段(TASK-004: 重复任务模板追踪)
  471. try {
  472. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN template_id BIGINT COMMENT \'模板任务ID\'");
  473. log.info("已添加template_id列到tasks表");
  474. } catch (Exception e) {
  475. // 列已存在,忽略错误
  476. }
  477. // 迁移21: tasks表添加is_template字段(TASK-004: 标记模板任务)
  478. try {
  479. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN is_template TINYINT DEFAULT 0 COMMENT '是否为模板任务'");
  480. log.info("已添加is_template列到tasks表");
  481. } catch (Exception e) {
  482. // 列已存在,忽略错误
  483. }
  484. // 迁移22: 修复tasks表child_id和deadline允许NULL(支持多孩子和家长任务)
  485. try {
  486. jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN child_id BIGINT NULL COMMENT '执行者ID'");
  487. log.info("已修改tasks.child_id允许NULL");
  488. } catch (Exception e) {
  489. // 修改失败,忽略错误
  490. }
  491. try {
  492. jdbcTemplate.execute("ALTER TABLE tasks MODIFY COLUMN deadline DATETIME NULL COMMENT '截止时间'");
  493. log.info("已修改tasks.deadline允许NULL");
  494. } catch (Exception e) {
  495. // 修改失败,忽略错误
  496. }
  497. // 迁移23: tasks表添加新字段
  498. try {
  499. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring'");
  500. log.info("已添加task_type列到tasks表");
  501. } catch (Exception e) {
  502. // 列已存在,忽略错误
  503. }
  504. try {
  505. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly'");
  506. log.info("已添加frequency列到tasks表");
  507. } catch (Exception e) {
  508. // 列已存在,忽略错误
  509. }
  510. try {
  511. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN max_frequency INT COMMENT '周期内最多完成次数'");
  512. log.info("已添加max_frequency列到tasks表");
  513. } catch (Exception e) {
  514. // 列已存在,忽略错误
  515. }
  516. try {
  517. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN complete_types VARCHAR(100) COMMENT '完成方式JSON'");
  518. log.info("已添加complete_types列到tasks表");
  519. } catch (Exception e) {
  520. // 列已存在,忽略错误
  521. }
  522. try {
  523. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai'");
  524. log.info("已添加review_type列到tasks表");
  525. } catch (Exception e) {
  526. // 列已存在,忽略错误
  527. }
  528. try {
  529. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN earliest_start DATETIME COMMENT '最早开始时间'");
  530. log.info("已添加earliest_start列到tasks表");
  531. } catch (Exception e) {
  532. // 列已存在,忽略错误
  533. }
  534. try {
  535. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN latest_end DATETIME COMMENT '最晚结束时间'");
  536. log.info("已添加latest_end列到tasks表");
  537. } catch (Exception e) {
  538. // 列已存在,忽略错误
  539. }
  540. // 迁移24: tasks表添加minigame_code字段(小游戏任务)
  541. try {
  542. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN minigame_code VARCHAR(50) DEFAULT NULL COMMENT '小游戏代码: schulte/1a2b/sudoku'");
  543. log.info("已添加minigame_code列到tasks表");
  544. } catch (Exception e) {
  545. // 列已存在,忽略错误
  546. }
  547. // 迁移25: tasks表添加duration字段(TASK-011: 任务预计时长)
  548. try {
  549. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN duration INT DEFAULT 0 COMMENT '预计时长(分钟)'");
  550. log.info("已添加duration列到tasks表");
  551. } catch (Exception e) {
  552. // 列已存在,忽略错误
  553. }
  554. try {
  555. jdbcTemplate.execute("ALTER TABLE tasks ADD INDEX idx_minigame (minigame_code) WHERE minigame_code IS NOT NULL");
  556. log.info("已添加idx_minigame索引到tasks表");
  557. } catch (Exception e) {
  558. // 索引已存在,忽略错误
  559. }
  560. // assessment_appointments 新字段
  561. try {
  562. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN user_id BIGINT COMMENT '用户ID'");
  563. } catch (Exception e) {
  564. }
  565. try {
  566. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID'");
  567. } catch (Exception e) {
  568. }
  569. try {
  570. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN package_id BIGINT COMMENT '套餐ID'");
  571. } catch (Exception e) {
  572. }
  573. try {
  574. jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN remark VARCHAR(500) COMMENT '备注'");
  575. } catch (Exception e) {
  576. }
  577. // assessment_orders 表
  578. try {
  579. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS assessment_orders (" +
  580. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  581. "order_no VARCHAR(64) NOT NULL UNIQUE, " +
  582. "family_id BIGINT NOT NULL, " +
  583. "user_id BIGINT NOT NULL, " +
  584. "child_id BIGINT, " +
  585. "guide_id BIGINT NOT NULL, " +
  586. "package_id BIGINT NOT NULL, " +
  587. "guide_name VARCHAR(50), " +
  588. "package_name VARCHAR(100), " +
  589. "total_price BIGINT DEFAULT 0, " +
  590. "discount_amount BIGINT DEFAULT 0, " +
  591. "actual_price BIGINT DEFAULT 0, " +
  592. "status VARCHAR(20) DEFAULT 'pending', " +
  593. "pay_time VARCHAR(50), " +
  594. "pay_type VARCHAR(20), " +
  595. "transaction_id VARCHAR(64), " +
  596. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  597. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  598. "INDEX idx_family (family_id), " +
  599. "INDEX idx_guide (guide_id), " +
  600. "INDEX idx_status (status)) " +
  601. "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表'");
  602. } catch (Exception e) {
  603. }
  604. // 迁移26: guide_applications表添加address和bank_card字段
  605. try {
  606. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN address VARCHAR(500) COMMENT '地址'");
  607. log.info("已添加address列到guide_applications表");
  608. } catch (Exception e) {
  609. // 列已存在,忽略错误
  610. }
  611. try {
  612. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_card VARCHAR(50) COMMENT '银行卡号'");
  613. log.info("已添加bank_card列到guide_applications表");
  614. } catch (Exception e) {
  615. // 列已存在,忽略错误
  616. }
  617. try {
  618. jdbcTemplate.execute("ALTER TABLE guide_applications ADD COLUMN bank_name VARCHAR(100) COMMENT '开户行'");
  619. log.info("已添加bank_name列到guide_applications表");
  620. } catch (Exception e) {
  621. // 列已存在,忽略错误
  622. }
  623. // 迁移27: users表添加address字段(成长规划师地址)
  624. try {
  625. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN address VARCHAR(500) COMMENT '地址'");
  626. log.info("已添加address列到users表");
  627. } catch (Exception e) {
  628. // 列已存在,忽略错误
  629. }
  630. // 迁移28: 创建guide_application_records表(申请过程记录)
  631. try {
  632. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_application_records (" +
  633. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  634. "application_id BIGINT NOT NULL COMMENT '关联申请ID', " +
  635. "user_id BIGINT NOT NULL COMMENT '操作人用户ID', " +
  636. "action VARCHAR(50) NOT NULL COMMENT '操作: submit/approve/reject/withdraw/resubmit', " +
  637. "from_status VARCHAR(20) COMMENT '操作前状态', " +
  638. "to_status VARCHAR(20) NOT NULL COMMENT '操作后状态', " +
  639. "remark VARCHAR(500) COMMENT '备注/原因', " +
  640. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  641. "INDEX idx_application_id (application_id), " +
  642. "INDEX idx_user_id (user_id), " +
  643. "INDEX idx_action (action)" +
  644. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请过程记录表'");
  645. log.info("已创建guide_application_records表");
  646. } catch (Exception e) {
  647. // 表已存在,忽略错误
  648. }
  649. // 迁移20: children表添加system_points字段
  650. try {
  651. Integer exists = jdbcTemplate.queryForObject(
  652. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'children' AND COLUMN_NAME = 'system_points'",
  653. Integer.class);
  654. if (exists == null || exists == 0) {
  655. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN system_points INT DEFAULT 0 COMMENT '系统积分'");
  656. log.info("已添加system_points列到children表");
  657. } else {
  658. log.info("system_points列已存在,跳过");
  659. }
  660. } catch (Exception e) {
  661. log.warn("检查/添加system_points列失败: {}", e.getMessage());
  662. }
  663. // 迁移21: points_log表添加category字段
  664. try {
  665. Integer exists = jdbcTemplate.queryForObject(
  666. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'points_log' AND COLUMN_NAME = 'category'",
  667. Integer.class);
  668. if (exists == null || exists == 0) {
  669. jdbcTemplate.execute("ALTER TABLE points_log ADD COLUMN category VARCHAR(20) DEFAULT 'task' COMMENT '积分分类: task任务积分, system系统积分'");
  670. log.info("已添加category列到points_log表");
  671. } else {
  672. log.info("category列已存在,跳过");
  673. }
  674. } catch (Exception e) {
  675. log.warn("检查/添加category列失败: {}", e.getMessage());
  676. }
  677. // 迁移22: 创建invite_card表
  678. try {
  679. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS invite_card (" +
  680. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  681. "type VARCHAR(20) NOT NULL COMMENT 'child/family/guide', " +
  682. "code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码', " +
  683. "ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)', " +
  684. "creator_id BIGINT NOT NULL COMMENT '创建人', " +
  685. "expires_at DATETIME NOT NULL, " +
  686. "used TINYINT DEFAULT 0, " +
  687. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  688. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  689. "INDEX idx_code (code), " +
  690. "INDEX idx_ref_type (ref_id, type)" +
  691. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请卡片'");
  692. log.info("已创建invite_card表");
  693. } catch (Exception e) {
  694. // 表已存在,忽略错误
  695. }
  696. // 迁移23: 创建wishes表
  697. try {
  698. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS wishes (" +
  699. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  700. "family_id BIGINT COMMENT '家庭ID', " +
  701. "child_id BIGINT COMMENT '目标孩子ID', " +
  702. "created_by BIGINT COMMENT '创建者用户ID', " +
  703. "creator_type VARCHAR(20) COMMENT '创建者类型: child/parent', " +
  704. "title VARCHAR(200) NOT NULL COMMENT '心愿标题', " +
  705. "description VARCHAR(500) COMMENT '心愿描述', " +
  706. "category VARCHAR(32) COMMENT '心愿分类', " +
  707. "points_required INT COMMENT '所需积分', " +
  708. "points_set_by BIGINT COMMENT '定价者ID', " +
  709. "points_set_at DATETIME COMMENT '定价时间', " +
  710. "status VARCHAR(32) DEFAULT 'active' COMMENT '状态: draft/active/pending_exchange/approved/exchanging/fulfilled/cancelled/rejected', " +
  711. "exchange_requested_at DATETIME COMMENT '申请兑换时间', " +
  712. "exchange_approved_by BIGINT COMMENT '批准者ID', " +
  713. "exchange_approved_at DATETIME COMMENT '批准时间', " +
  714. "points_deducted_at DATETIME COMMENT '扣积分时间', " +
  715. "fulfilled_by BIGINT COMMENT '兑现者ID', " +
  716. "fulfilled_at DATETIME COMMENT '兑现时间', " +
  717. "child_confirmed_at DATETIME COMMENT '孩子确认收货时间', " +
  718. "rejected_by BIGINT COMMENT '拒绝者ID', " +
  719. "rejected_at DATETIME COMMENT '拒绝时间', " +
  720. "reject_reason VARCHAR(500) COMMENT '拒绝原因', " +
  721. "reject_count INT DEFAULT 0 COMMENT '被拒绝次数', " +
  722. "cancelled_by BIGINT COMMENT '取消者ID', " +
  723. "cancelled_at DATETIME COMMENT '取消时间', " +
  724. "cancel_reason VARCHAR(500) COMMENT '取消原因', " +
  725. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  726. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  727. "INDEX idx_family_id (family_id), " +
  728. "INDEX idx_child_id (child_id), " +
  729. "INDEX idx_status (status)" +
  730. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿单表'");
  731. log.info("已创建wishes表");
  732. } catch (Exception e) {
  733. log.warn("创建wishes表可能已存在: {}", e.getMessage());
  734. }
  735. // 迁移24: users表添加个人信息补充字段
  736. try {
  737. Integer exists = jdbcTemplate.queryForObject(
  738. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'ethnicity'",
  739. Integer.class);
  740. if (exists == null || exists == 0) {
  741. jdbcTemplate.execute("ALTER TABLE users " +
  742. "ADD COLUMN ethnicity VARCHAR(32) DEFAULT NULL COMMENT '民族', " +
  743. "ADD COLUMN blood_type VARCHAR(16) DEFAULT NULL COMMENT '血型', " +
  744. "ADD COLUMN highest_education VARCHAR(32) DEFAULT NULL COMMENT '最高学历', " +
  745. "ADD COLUMN marital_status VARCHAR(16) DEFAULT NULL COMMENT '婚姻状态', " +
  746. "ADD COLUMN family_role VARCHAR(16) DEFAULT NULL COMMENT '家长身份: 爸爸/妈妈/爷爷/奶奶/姥爷/姥姥/其他'");
  747. log.info("已添加个人信息补充字段到users表");
  748. } else {
  749. log.info("个人信息补充字段已存在,跳过");
  750. }
  751. } catch (Exception e) {
  752. log.warn("检查/添加个人信息补充字段失败: {}", e.getMessage());
  753. }
  754. // Phase 0: 浠艾福俱乐部 — 服务商字段
  755. try {
  756. Integer exists = jdbcTemplate.queryForObject(
  757. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'vendor_type'",
  758. Integer.class);
  759. if (exists == null || exists == 0) {
  760. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_type VARCHAR(32) DEFAULT NULL COMMENT '服务商类型: planner/activity_provider/product_supplier'");
  761. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_status VARCHAR(16) DEFAULT NULL COMMENT '服务商审核状态: pending/approved/rejected'");
  762. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_reject_reason VARCHAR(500) DEFAULT NULL COMMENT '审核拒绝原因'");
  763. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN is_family_admin TINYINT DEFAULT 0 COMMENT '是否家庭管理员'");
  764. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN vendor_info VARCHAR(2000) DEFAULT NULL COMMENT '服务商资质信息(JSON)'");
  765. log.info("已添加浠艾福俱乐部服务商字段到users表");
  766. } else {
  767. log.info("浠艾福俱乐部服务商字段已存在,跳过");
  768. }
  769. } catch (Exception e) {
  770. log.warn("检查/添加服务商字段失败: {}", e.getMessage());
  771. }
  772. // Phase 0: 迁移已有规划师到服务商体系
  773. try {
  774. int updated = jdbcTemplate.update(
  775. "UPDATE users SET vendor_type = 'planner', vendor_status = 'approved' WHERE teacher_status = 'approved' AND vendor_type IS NULL"
  776. );
  777. if (updated > 0) {
  778. log.info("已迁移 {} 名规划师到服务商体系", updated);
  779. }
  780. } catch (Exception e) {
  781. log.warn("迁移规划师到服务商体系失败: {}", e.getMessage());
  782. }
  783. // ==================== Phase 1: 浠艾福俱乐部商品系统 ====================
  784. // 商品表
  785. try {
  786. jdbcTemplate.execute(
  787. "CREATE TABLE IF NOT EXISTS products (" +
  788. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  789. " name VARCHAR(200) NOT NULL," +
  790. " description VARCHAR(1000)," +
  791. " intro TEXT," +
  792. " cover_image VARCHAR(500)," +
  793. " images TEXT," +
  794. " price INT NOT NULL DEFAULT 0 COMMENT '价格(分)'," +
  795. " stock INT NOT NULL DEFAULT 0," +
  796. " sales_count INT NOT NULL DEFAULT 0," +
  797. " product_type VARCHAR(32) NOT NULL COMMENT 'activity/course/physical/digital/service'," +
  798. " vendor_id BIGINT," +
  799. " vendor_name VARCHAR(100)," +
  800. " status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT 'draft/pending/approved/rejected/on_shelf/off_shelf'," +
  801. " reject_reason VARCHAR(500)," +
  802. " member_price INT COMMENT '会员价(分)'," +
  803. " profit_rate INT DEFAULT 0 COMMENT '平台利润率(千分比)'," +
  804. " member_eligible VARCHAR(32) DEFAULT 'none' COMMENT 'all/budget/none'," +
  805. " domain VARCHAR(32) COMMENT 'action/mind/body/wisdom/wealth'," +
  806. " external_source VARCHAR(64)," +
  807. " external_id VARCHAR(128)," +
  808. " external_data TEXT," +
  809. " created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
  810. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  811. ")"
  812. );
  813. } catch (Exception e) {
  814. log.warn("创建products表失败: {}", e.getMessage());
  815. }
  816. // 订单表
  817. try {
  818. jdbcTemplate.execute(
  819. "CREATE TABLE IF NOT EXISTS product_orders (" +
  820. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  821. " order_no VARCHAR(64) NOT NULL UNIQUE," +
  822. " product_id BIGINT NOT NULL," +
  823. " product_name VARCHAR(200)," +
  824. " product_type VARCHAR(32)," +
  825. " buyer_id BIGINT NOT NULL," +
  826. " family_id BIGINT," +
  827. " quantity INT NOT NULL DEFAULT 1," +
  828. " unit_price INT NOT NULL COMMENT '单价(分)'," +
  829. " total_amount INT NOT NULL COMMENT '总金额(分)'," +
  830. " status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/completed/cancelled/refunded'," +
  831. " payment_method VARCHAR(32) COMMENT 'wechat/points/membership'," +
  832. " remark VARCHAR(500)," +
  833. " paid_at DATETIME," +
  834. " created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
  835. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  836. ")"
  837. );
  838. } catch (Exception e) {
  839. log.warn("创建product_orders表失败: {}", e.getMessage());
  840. }
  841. // 系统配置表
  842. try {
  843. jdbcTemplate.execute(
  844. "CREATE TABLE IF NOT EXISTS sys_config (" +
  845. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  846. " config_key VARCHAR(64) NOT NULL UNIQUE," +
  847. " config_value TEXT," +
  848. " description VARCHAR(500)," +
  849. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  850. ")"
  851. );
  852. } catch (Exception e) {
  853. log.warn("创建sys_config表失败: {}", e.getMessage());
  854. }
  855. // SysConfig 种子数据
  856. try {
  857. insertSysConfigSeed("points_exchange_rate", "1", "积分兑换汇率(1积分=1分,即0.01元)");
  858. insertSysConfigSeed("points_deduct_cap", "0.5", "积分抵扣上限(50%)");
  859. insertSysConfigSeed("member_fee_standard", "1314", "标准会员年费(元)");
  860. insertSysConfigSeed("member_fee_premium", "3650", "高级会员年费(元)");
  861. insertSysConfigSeed("member_eligible_budget", "1314", "等值商品选择预算上限(元)");
  862. insertSysConfigSeed("renewal_discount_rate", "0.85", "续费折扣率");
  863. insertSysConfigSeed("member_discount_standard", "0.9", "标准会员折扣率");
  864. insertSysConfigSeed("member_discount_premium", "0.8", "高级会员折扣率");
  865. insertSysConfigSeed("member_fee_family", "36500", "家庭会员年费(分)");
  866. insertSysConfigSeed("member_upgrade_threshold", "131400", "自动升级家庭会员累计消费门槛(分)");
  867. insertSysConfigSeed("member_discount_family", "9000", "家庭会员商品折扣(bps)");
  868. insertSysConfigSeed("commission_member_l1_rate", "2000", "推荐家庭会员L1佣金率(bps)");
  869. insertSysConfigSeed("commission_member_l2_rate", "500", "推荐家庭会员L2佣金率(bps)");
  870. insertSysConfigSeed("commission_product_profit_share", "1000", "下线商品购买利润分成(bps)");
  871. insertSysConfigSeed("commission_service_rate", "1000", "下线服务购买佣金率(bps)");
  872. insertSysConfigSeed("commission_event_fixed", "2000", "推荐新用户报名活动固定佣金(分)");
  873. insertSysConfigSeed("withdraw_min_amount", "10000", "最低提现金额(分)");
  874. insertSysConfigSeed("withdraw_tax_rate", "2000", "佣金税率(bps)");
  875. insertSysConfigSeed("withdraw_fee_free_threshold", "80000", "免税额度(分)");
  876. insertSysConfigSeed("article_energy_rate_normal", "1", "普通文章能量汇率(每100字)");
  877. insertSysConfigSeed("article_energy_rate_premium", "2", "优质文章能量汇率(每100字)");
  878. insertSysConfigSeed("article_energy_rate_original", "3", "原创文章能量汇率(每100字)");
  879. insertSysConfigSeed("article_reading_min_seconds", "60", "文章阅读达标最少秒数");
  880. insertSysConfigSeed("activity_checkin_default_points", "5", "活动签到默认积分");
  881. insertSysConfigSeed("health_checkin_points", "5", "健康打卡积分");
  882. insertSysConfigSeed("finance_checkin_points", "5", "理财打卡积分");
  883. log.info("SysConfig 种子数据已加载");
  884. } catch (Exception e) {
  885. log.warn("SysConfig 种子数据初始化失败: {}", e.getMessage());
  886. }
  887. // Product 种子数据(5 个示例商品,覆盖全部 5 种 productType)
  888. try {
  889. insertProductSeed("亲子关系工作坊", "线下半日工作坊,重建家庭对话模式", "activity", 38800, "action", "all");
  890. insertProductSeed("情绪管理21天训练营", "21天视频课程,识别并管理情绪", "course", 19900, "mind", "all");
  891. insertProductSeed("肠道菌群检测套装", "家用检测 + 报告解读", "physical", 59900, "body", "budget");
  892. insertProductSeed("儿童多模态认知测评", "在线测评 + 个性化建议", "digital", 9900, "wisdom", "none");
  893. insertProductSeed("成长规划师1对1咨询", "60分钟专业规划", "service", 50000, "wealth", "budget");
  894. log.info("Product 种子数据已加载");
  895. } catch (Exception e) {
  896. log.warn("Product 种子数据初始化失败: {}", e.getMessage());
  897. }
  898. // ==================== Phase 2: 内容区块可见性配置 ====================
  899. // 内容区块配置表
  900. try {
  901. jdbcTemplate.execute(
  902. "CREATE TABLE IF NOT EXISTS content_sections (" +
  903. " id BIGINT AUTO_INCREMENT PRIMARY KEY," +
  904. " page_key VARCHAR(50) NOT NULL," +
  905. " section_key VARCHAR(50) NOT NULL," +
  906. " title VARCHAR(100)," +
  907. " allowed_roles VARCHAR(255) DEFAULT '[\"anonymous\"]'," +
  908. " sort_order INT DEFAULT 0," +
  909. " status VARCHAR(20) DEFAULT 'active'," +
  910. " created_at DATETIME DEFAULT CURRENT_TIMESTAMP," +
  911. " updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  912. ")"
  913. );
  914. log.info("content_sections 表已创建");
  915. } catch (Exception e) {
  916. log.warn("创建 content_sections 表失败: {}", e.getMessage());
  917. }
  918. // ContentSection 种子数据
  919. try {
  920. insertContentSectionSeed("mind", "func_entries", "功能入口", "[\"anonymous\"]", 1);
  921. insertContentSectionSeed("mind", "daily_tip", "每日心理", "[\"anonymous\"]", 2);
  922. insertContentSectionSeed("mind", "recommended_reading", "推荐阅读", "[\"anonymous\"]", 3);
  923. insertContentSectionSeed("mind", "premium_content", "会员专属内容", "[\"parent\",\"teacher\"]", 4);
  924. insertContentSectionSeed("mind", "mind_tasks", "心智任务", "[\"parent\",\"child\"]", 5);
  925. insertContentSectionSeed("mind", "related_activities", "相关活动", "[\"parent\",\"child\"]", 6);
  926. insertContentSectionSeed("mind", "purchased_courses", "已购课程", "[\"parent\",\"child\"]", 7);
  927. insertContentSectionSeed("body", "func_entries", "功能入口", "[\"anonymous\"]", 1);
  928. insertContentSectionSeed("body", "daily_stats", "今日数据", "[\"anonymous\"]", 2);
  929. insertContentSectionSeed("body", "health_tips", "健康小贴士", "[\"anonymous\"]", 3);
  930. insertContentSectionSeed("body", "teacher_tips", "规划师推荐", "[\"teacher\"]", 4);
  931. log.info("ContentSection 种子数据已加载");
  932. } catch (Exception e) {
  933. log.warn("ContentSection 种子数据初始化失败: {}", e.getMessage());
  934. }
  935. // ==================== 文章内容系统种子数据 ====================
  936. // 文章分类种子数据
  937. try {
  938. Integer catCount = jdbcTemplate.queryForObject(
  939. "SELECT COUNT(*) FROM article_categories WHERE id IN (1,2,3,4,5)", Integer.class);
  940. if (catCount == null || catCount == 0) {
  941. jdbcTemplate.execute("INSERT IGNORE INTO article_categories (id, name, icon, color, sort_order, status) VALUES " +
  942. "(1, '心理健康', '🧠', '#5B9BD5', 1, 1), " +
  943. "(2, '情绪管理', '💖', '#FF6B35', 2, 1), " +
  944. "(3, '亲子教育', '👨‍👩‍👧‍👦', '#4CAF50', 3, 1), " +
  945. "(4, '学习力', '📚', '#FFD700', 4, 1), " +
  946. "(5, '专注力', '🎯', '#8D6E63', 5, 1)");
  947. log.info("文章分类种子数据已加载");
  948. }
  949. } catch (Exception e) {
  950. log.warn("文章分类种子数据初始化失败: {}", e.getMessage());
  951. }
  952. // ==================== 分佣裂变系统 ====================
  953. // 迁移: users表添加推荐人字段
  954. try {
  955. Integer exists = jdbcTemplate.queryForObject(
  956. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'referrer_id'",
  957. Integer.class);
  958. if (exists == null || exists == 0) {
  959. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN referrer_id BIGINT DEFAULT NULL COMMENT '推荐人ID'");
  960. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN referral_code VARCHAR(32) DEFAULT NULL COMMENT '个人邀请码'");
  961. jdbcTemplate.execute("ALTER TABLE users ADD INDEX idx_referral_code (referral_code)");
  962. log.info("已添加分佣字段到users表");
  963. } else {
  964. log.info("分佣金字段已存在,跳过");
  965. }
  966. } catch (Exception e) {
  967. log.warn("检查/添加分佣金字段失败: {}", e.getMessage());
  968. }
  969. // 迁移: products表添加利润率字段
  970. try {
  971. Integer exists = jdbcTemplate.queryForObject(
  972. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'products' AND COLUMN_NAME = 'profit_rate'",
  973. Integer.class);
  974. if (exists == null || exists == 0) {
  975. jdbcTemplate.execute("ALTER TABLE products ADD COLUMN profit_rate INT DEFAULT 0 COMMENT '平台利润率(bps,100=1%)'");
  976. log.info("已添加profit_rate列到products表");
  977. } else {
  978. log.info("profit_rate列已存在,跳过");
  979. }
  980. } catch (Exception e) {
  981. log.warn("检查/添加profit_rate列失败: {}", e.getMessage());
  982. }
  983. // 新表: commission_records (佣金记录)
  984. try {
  985. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS commission_records (" +
  986. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  987. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  988. "order_type VARCHAR(32) NOT NULL COMMENT '订单类型: membership/product/assessment/package', " +
  989. "referrer_id BIGINT NOT NULL COMMENT '推荐人ID', " +
  990. "buyer_id BIGINT NOT NULL COMMENT '购买人ID', " +
  991. "commission_type VARCHAR(16) NOT NULL COMMENT '佣金类型: member/profit', " +
  992. "order_amount INT NOT NULL COMMENT '订单金额(分)', " +
  993. "profit_rate INT DEFAULT 0 COMMENT '利润率(bps,100=1%)', " +
  994. "commission_amount INT NOT NULL COMMENT '佣金金额(分)', " +
  995. "status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled', " +
  996. "remark VARCHAR(255) DEFAULT NULL, " +
  997. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  998. "INDEX idx_referrer (referrer_id), " +
  999. "INDEX idx_buyer (buyer_id), " +
  1000. "INDEX idx_order (order_id, order_type)" +
  1001. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  1002. log.info("已创建commission_records表");
  1003. } catch (Exception e) {
  1004. log.warn("创建commission_records表失败: {}", e.getMessage());
  1005. }
  1006. // 新表: withdrawal_requests (提现申请)
  1007. try {
  1008. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS withdrawal_requests (" +
  1009. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1010. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  1011. "amount INT NOT NULL COMMENT '提现金额(分)', " +
  1012. "account_info TEXT COMMENT '账户信息(JSON)', " +
  1013. "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/approved/rejected', " +
  1014. "audit_by BIGINT DEFAULT NULL COMMENT '审核人ID', " +
  1015. "audit_at DATETIME DEFAULT NULL COMMENT '审核时间', " +
  1016. "remark VARCHAR(255) DEFAULT NULL COMMENT '审核备注', " +
  1017. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1018. "INDEX idx_user (user_id), " +
  1019. "INDEX idx_status (status)" +
  1020. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
  1021. log.info("已创建withdrawal_requests表");
  1022. } catch (Exception e) {
  1023. log.warn("创建withdrawal_requests表失败: {}", e.getMessage());
  1024. }
  1025. // ==================== 会员体系 & 分佣扩展 ====================
  1026. // 迁移: users表添加会员字段
  1027. try {
  1028. Integer exists = jdbcTemplate.queryForObject(
  1029. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'member_level'",
  1030. Integer.class);
  1031. if (exists == null || exists == 0) {
  1032. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_level VARCHAR(20) DEFAULT 'FREE' COMMENT '会员等级: FREE/FAMILY/PROVIDER'");
  1033. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间'");
  1034. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_consumption BIGINT DEFAULT 0 COMMENT '累计消费金额(分)'");
  1035. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN upgrade_from_consumption TINYINT DEFAULT 0 COMMENT '是否通过累计消费自动升级'");
  1036. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN direct_count INT DEFAULT 0 COMMENT '直接推荐人数'");
  1037. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN indirect_count INT DEFAULT 0 COMMENT '间接推荐人数'");
  1038. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_commission_earned BIGINT DEFAULT 0 COMMENT '累计获得佣金(分)'");
  1039. log.info("已添加会员字段到users表");
  1040. } else {
  1041. log.info("会员字段已存在,跳过");
  1042. }
  1043. } catch (Exception e) {
  1044. log.warn("检查/添加会员字段失败: {}", e.getMessage());
  1045. }
  1046. // 迁移: commission_records表添加扩展字段
  1047. try {
  1048. Integer exists = jdbcTemplate.queryForObject(
  1049. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'commission_records' AND COLUMN_NAME = 'level'",
  1050. Integer.class);
  1051. if (exists == null || exists == 0) {
  1052. jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN level INT DEFAULT 1 COMMENT '佣金层级: 1=直接推荐, 2=间接推荐'");
  1053. jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN settled_at DATETIME DEFAULT NULL COMMENT '结算时间'");
  1054. jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间'");
  1055. log.info("已添加扩展字段到commission_records表");
  1056. } else {
  1057. log.info("commission_records扩展字段已存在,跳过");
  1058. }
  1059. } catch (Exception e) {
  1060. log.warn("检查/添加commission_records扩展字段失败: {}", e.getMessage());
  1061. }
  1062. // 新表: member_upgrade_records (会员升级记录)
  1063. try {
  1064. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_upgrade_records (" +
  1065. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1066. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  1067. "from_level VARCHAR(20) NOT NULL COMMENT '升级前等级', " +
  1068. "to_level VARCHAR(20) NOT NULL COMMENT '升级后等级', " +
  1069. "upgrade_type VARCHAR(20) NOT NULL COMMENT '升级类型: pay/consumption/admin', " +
  1070. "order_id BIGINT DEFAULT NULL COMMENT '关联支付订单ID', " +
  1071. "consumption_amount BIGINT DEFAULT NULL COMMENT '触发升级的累计消费金额', " +
  1072. "expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间', " +
  1073. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1074. "INDEX idx_user (user_id), " +
  1075. "INDEX idx_order (order_id)" +
  1076. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员升级记录'");
  1077. log.info("已创建member_upgrade_records表");
  1078. } catch (Exception e) {
  1079. log.warn("创建member_upgrade_records表失败: {}", e.getMessage());
  1080. }
  1081. // ==================== 文章内容系统 ====================
  1082. // 文章分类表
  1083. try {
  1084. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_categories (" +
  1085. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1086. "name VARCHAR(50) NOT NULL COMMENT '分类名', " +
  1087. "icon VARCHAR(20) DEFAULT '' COMMENT '图标emoji', " +
  1088. "color VARCHAR(20) DEFAULT '#5B9BD5' COMMENT '标识色', " +
  1089. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  1090. "status TINYINT DEFAULT 1 COMMENT '1启用/0禁用'" +
  1091. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类'");
  1092. log.info("已创建article_categories表");
  1093. } catch (Exception e) {
  1094. log.warn("创建article_categories表失败: {}", e.getMessage());
  1095. }
  1096. // 文章表
  1097. try {
  1098. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS articles (" +
  1099. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1100. "category_id BIGINT DEFAULT 0 COMMENT '所属分类ID', " +
  1101. "title VARCHAR(200) NOT NULL COMMENT '标题', " +
  1102. "summary VARCHAR(500) DEFAULT '' COMMENT '摘要', " +
  1103. "cover_image VARCHAR(500) DEFAULT '' COMMENT '封面图URL', " +
  1104. "content LONGTEXT COMMENT '富文本内容', " +
  1105. "tags VARCHAR(200) DEFAULT '' COMMENT '标签JSON数组', " +
  1106. "author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者', " +
  1107. "read_time INT DEFAULT 0 COMMENT '预计阅读分钟数', " +
  1108. "related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维JSON数组', " +
  1109. "visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private', " +
  1110. "visible_to TEXT COMMENT '私密指定人群JSON', " +
  1111. "status VARCHAR(20) DEFAULT 'draft' COMMENT 'draft/published/archived', " +
  1112. "is_featured TINYINT DEFAULT 0 COMMENT '1精选/0普通', " +
  1113. "published_at DATETIME DEFAULT NULL COMMENT '发布时间', " +
  1114. "view_count INT DEFAULT 0 COMMENT '浏览次数', " +
  1115. "created_by BIGINT DEFAULT 0 COMMENT '发布人adminID', " +
  1116. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1117. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  1118. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章'");
  1119. log.info("已创建articles表");
  1120. } catch (Exception e) {
  1121. log.warn("创建articles表失败: {}", e.getMessage());
  1122. }
  1123. // 迁移: articles表补充新字段(兼容旧表)
  1124. try {
  1125. Integer colExists = jdbcTemplate.queryForObject(
  1126. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'visibility'",
  1127. Integer.class);
  1128. if (colExists == null || colExists == 0) {
  1129. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private' AFTER related_dimensions");
  1130. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN visible_to TEXT COMMENT '私密指定人群JSON' AFTER visibility");
  1131. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN tags VARCHAR(200) DEFAULT '' COMMENT '标签JSON数组' AFTER content");
  1132. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维JSON数组' AFTER read_time");
  1133. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN is_featured TINYINT DEFAULT 0 COMMENT '1精选/0普通' AFTER status");
  1134. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN published_at DATETIME DEFAULT NULL COMMENT '发布时间' AFTER is_featured");
  1135. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN view_count INT DEFAULT 0 COMMENT '浏览次数' AFTER published_at");
  1136. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN created_by BIGINT DEFAULT 0 COMMENT '发布人adminID' AFTER view_count");
  1137. log.info("已迁移articles表补充字段");
  1138. } else {
  1139. log.info("articles表字段已完整,跳过迁移");
  1140. }
  1141. } catch (Exception e) {
  1142. log.warn("articles表字段迁移失败: {}", e.getMessage());
  1143. }
  1144. // ==================== 五维能量系统(账本模式) ====================
  1145. // 维度定义表
  1146. try {
  1147. jdbcTemplate.execute(
  1148. "CREATE TABLE IF NOT EXISTS energy_dimension (" +
  1149. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1150. "code VARCHAR(16) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth', " +
  1151. "name VARCHAR(20) NOT NULL COMMENT '维度名称: 身/心/智/行/富', " +
  1152. "icon VARCHAR(16) COMMENT '图标emoji', " +
  1153. "element VARCHAR(10) COMMENT '五行元素: 土/火/金/木/水', " +
  1154. "sort_order INT DEFAULT 0 COMMENT '显示顺序', " +
  1155. "status TINYINT DEFAULT 1 COMMENT '1启用/0禁用', " +
  1156. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  1157. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量维度定义表'"
  1158. );
  1159. log.info("已创建energy_dimension表");
  1160. } catch (Exception e) {
  1161. log.warn("创建energy_dimension表失败: {}", e.getMessage());
  1162. }
  1163. // 服务-维度比例配置表
  1164. try {
  1165. jdbcTemplate.execute(
  1166. "CREATE TABLE IF NOT EXISTS energy_source_config (" +
  1167. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1168. "source_type VARCHAR(32) NOT NULL COMMENT '来源类型: task/activity/course/product/consultation', " +
  1169. "source_id BIGINT NOT NULL COMMENT '来源业务表ID', " +
  1170. "source_name VARCHAR(200) COMMENT '冗余名称', " +
  1171. "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
  1172. "ratio INT NOT NULL COMMENT '占比(bps,10000=100%)', " +
  1173. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1174. "INDEX idx_source (source_type, source_id)" +
  1175. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务-维度比例配置表'"
  1176. );
  1177. log.info("已创建energy_source_config表");
  1178. } catch (Exception e) {
  1179. log.warn("创建energy_source_config表失败: {}", e.getMessage());
  1180. }
  1181. // 能量流水表
  1182. try {
  1183. jdbcTemplate.execute(
  1184. "CREATE TABLE IF NOT EXISTS energy_log (" +
  1185. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1186. "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
  1187. "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
  1188. "amount INT NOT NULL COMMENT '变动量(正=获得,负=消耗)', " +
  1189. "balance_after INT NOT NULL COMMENT '变动后余额', " +
  1190. "source_type VARCHAR(32) COMMENT '来源类型: task/product/medical_report/daily_record', " +
  1191. "source_id BIGINT COMMENT '来源ID', " +
  1192. "ref_id BIGINT COMMENT '关联旧流水ID(覆盖时标记)', " +
  1193. "expires_at DATETIME COMMENT '过期时间(空=永不过期)', " +
  1194. "description VARCHAR(500) COMMENT '描述', " +
  1195. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1196. "INDEX idx_child_dim (child_id, dimension_id), " +
  1197. "INDEX idx_created (created_at), " +
  1198. "INDEX idx_source (source_type, source_id)" +
  1199. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量流水表'"
  1200. );
  1201. log.info("已创建energy_log表");
  1202. } catch (Exception e) {
  1203. log.warn("创建energy_log表失败: {}", e.getMessage());
  1204. }
  1205. // 维度余额表
  1206. try {
  1207. jdbcTemplate.execute(
  1208. "CREATE TABLE IF NOT EXISTS energy_balance (" +
  1209. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1210. "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
  1211. "dimension_id BIGINT NOT NULL COMMENT '维度ID', " +
  1212. "balance INT DEFAULT 0 COMMENT '当前能量值(可为负)', " +
  1213. "total_earned INT DEFAULT 0 COMMENT '累计获得', " +
  1214. "total_spent INT DEFAULT 0 COMMENT '累计消耗', " +
  1215. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1216. "UNIQUE KEY uk_child_dim (child_id, dimension_id)" +
  1217. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度余额表'"
  1218. );
  1219. log.info("已创建energy_balance表");
  1220. } catch (Exception e) {
  1221. log.warn("创建energy_balance表失败: {}", e.getMessage());
  1222. }
  1223. // 维度种子数据(按相生链: 智→富→行→心→身)
  1224. try {
  1225. Integer dimCount = jdbcTemplate.queryForObject(
  1226. "SELECT COUNT(*) FROM energy_dimension", Integer.class);
  1227. if (dimCount == null || dimCount == 0) {
  1228. jdbcTemplate.execute("INSERT INTO energy_dimension (code, name, icon, element, sort_order, status) VALUES " +
  1229. "('mind', '心', '🔥', '火', 4, 1), " +
  1230. "('body', '身', '🌏', '土', 5, 1), " +
  1231. "('wisdom', '智', '⚔️', '金', 1, 1), " +
  1232. "('action', '行', '🌿', '木', 3, 1), " +
  1233. "('wealth', '富', '💧', '水', 2, 1)");
  1234. log.info("能量维度种子数据已初始化");
  1235. }
  1236. } catch (Exception e) {
  1237. log.warn("初始化能量维度种子数据失败: {}", e.getMessage());
  1238. }
  1239. // 为已有商品插入默认比例配置(与Product.domain一致)
  1240. try {
  1241. Integer productConfigCount = jdbcTemplate.queryForObject(
  1242. "SELECT COUNT(*) FROM energy_source_config WHERE source_type = 'product'", Integer.class);
  1243. if (productConfigCount == null || productConfigCount == 0) {
  1244. String[][] domainMapping = {
  1245. {"action", "action"}, {"mind", "mind"}, {"body", "body"},
  1246. {"wisdom", "wisdom"}, {"wealth", "wealth"}
  1247. };
  1248. for (String[] mapping : domainMapping) {
  1249. jdbcTemplate.execute(
  1250. "INSERT INTO energy_source_config (source_type, source_id, source_name, dimension_id, ratio) " +
  1251. "SELECT 'product', p.id, p.name, d.id, 10000 " +
  1252. "FROM products p " +
  1253. "JOIN energy_dimension d ON d.code = '" + mapping[1] + "' " +
  1254. "WHERE p.domain = '" + mapping[0] + "' " +
  1255. "AND NOT EXISTS (" +
  1256. " SELECT 1 FROM energy_source_config esc " +
  1257. " WHERE esc.source_type = 'product' AND esc.source_id = p.id" +
  1258. ")"
  1259. );
  1260. }
  1261. log.info("已有商品能量比例配置已初始化");
  1262. }
  1263. } catch (Exception e) {
  1264. log.warn("初始化商品能量比例配置失败: {}", e.getMessage());
  1265. }
  1266. // ==================== 精准营养 P0: health_report_survey ====================
  1267. try {
  1268. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_report_survey (" +
  1269. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1270. "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
  1271. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  1272. "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
  1273. "survey_data JSON COMMENT '调研问卷结果JSON', " +
  1274. "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/completed', " +
  1275. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1276. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1277. "UNIQUE KEY uk_report (report_id)" +
  1278. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报告调研问卷'");
  1279. log.info("已创建 health_report_survey 表");
  1280. } catch (Exception e) {
  1281. log.warn("创建 health_report_survey 表失败: {}", e.getMessage());
  1282. }
  1283. // ==================== 精准营养 P0: health_gut_flora + health_disease_risk ====================
  1284. try {
  1285. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_gut_flora (" +
  1286. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1287. "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
  1288. "bacteria_name VARCHAR(100) NOT NULL COMMENT '菌种名称', " +
  1289. "bacteria_value VARCHAR(50) COMMENT '菌种丰度值', " +
  1290. "normal_range VARCHAR(100) COMMENT '正常范围', " +
  1291. "status VARCHAR(20) COMMENT '状态: 正常/偏低/偏高', " +
  1292. "category VARCHAR(50) COMMENT '分类: 有益菌/有害菌/中性菌', " +
  1293. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  1294. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1295. "INDEX idx_report (report_id)" +
  1296. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菌群检测专项数据'");
  1297. log.info("已创建 health_gut_flora 表");
  1298. } catch (Exception e) {
  1299. log.warn("创建 health_gut_flora 表失败: {}", e.getMessage());
  1300. }
  1301. try {
  1302. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_disease_risk (" +
  1303. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1304. "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
  1305. "disease_name VARCHAR(100) NOT NULL COMMENT '疾病名称', " +
  1306. "risk_value VARCHAR(50) COMMENT '风险值(0-1)', " +
  1307. "risk_level VARCHAR(20) COMMENT '风险等级: 高风险/需注意/低风险', " +
  1308. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1309. "INDEX idx_report (report_id)" +
  1310. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疾病风险评估数据'");
  1311. log.info("已创建 health_disease_risk 表");
  1312. } catch (Exception e) {
  1313. log.warn("创建 health_disease_risk 表失败: {}", e.getMessage());
  1314. }
  1315. // ==================== 迁移: health_reports 表添加新字段 ====================
  1316. try {
  1317. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN balance_score INT DEFAULT NULL COMMENT '菌群平衡分'");
  1318. log.info("已添加balance_score列到health_reports表");
  1319. } catch (Exception e) {
  1320. // 列已存在,忽略错误
  1321. }
  1322. try {
  1323. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN diversity_score INT DEFAULT NULL COMMENT '菌群多样性分'");
  1324. log.info("已添加diversity_score列到health_reports表");
  1325. } catch (Exception e) {
  1326. // 列已存在,忽略错误
  1327. }
  1328. try {
  1329. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN beneficial_score INT DEFAULT NULL COMMENT '有益菌评分'");
  1330. log.info("已添加beneficial_score列到health_reports表");
  1331. } catch (Exception e) {
  1332. // 列已存在,忽略错误
  1333. }
  1334. try {
  1335. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN harmful_score INT DEFAULT NULL COMMENT '有害菌评分'");
  1336. log.info("已添加harmful_score列到health_reports表");
  1337. } catch (Exception e) {
  1338. // 列已存在,忽略错误
  1339. }
  1340. try {
  1341. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN core_genus_score INT DEFAULT NULL COMMENT '核心菌属评分'");
  1342. log.info("已添加core_genus_score列到health_reports表");
  1343. } catch (Exception e) {
  1344. // 列已存在,忽略错误
  1345. }
  1346. // 迁移: health_reports 表添加报告编号和姓名字段
  1347. try {
  1348. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN report_number VARCHAR(50) DEFAULT NULL COMMENT '报告编号'");
  1349. log.info("已添加report_number列到health_reports表");
  1350. } catch (Exception e) {
  1351. // 列已存在,忽略错误
  1352. }
  1353. try {
  1354. jdbcTemplate.execute("ALTER TABLE health_reports ADD COLUMN person_name VARCHAR(50) DEFAULT NULL COMMENT '被检测人姓名'");
  1355. log.info("已添加person_name列到health_reports表");
  1356. } catch (Exception e) {
  1357. // 列已存在,忽略错误
  1358. }
  1359. // ==================== 迁移: health_gut_flora 表添加新字段 ====================
  1360. try {
  1361. jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN population_level VARCHAR(50) DEFAULT NULL COMMENT '处于人群%水平'");
  1362. log.info("已添加population_level列到health_gut_flora表");
  1363. } catch (Exception e) {
  1364. // 列已存在,忽略错误
  1365. }
  1366. try {
  1367. jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN detection_rate VARCHAR(50) DEFAULT NULL COMMENT '%正常人有检出'");
  1368. log.info("已添加detection_rate列到health_gut_flora表");
  1369. } catch (Exception e) {
  1370. // 列已存在,忽略错误
  1371. }
  1372. try {
  1373. jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN description TEXT DEFAULT NULL COMMENT '菌种说明'");
  1374. log.info("已添加description列到health_gut_flora表");
  1375. } catch (Exception e) {
  1376. // 列已存在,忽略错误
  1377. }
  1378. try {
  1379. jdbcTemplate.execute("ALTER TABLE health_gut_flora ADD COLUMN `level` VARCHAR(20) DEFAULT 'GENUS' COMMENT '级别: GENUS/SPECIES'");
  1380. log.info("已添加level列到health_gut_flora表");
  1381. } catch (Exception e) {
  1382. // 列已存在,忽略错误
  1383. }
  1384. // ==================== 迁移: guides 表统一 ====================
  1385. // 迁移: guides 添加 type 字段(区分服务商类型)
  1386. try {
  1387. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN type VARCHAR(32) DEFAULT 'teacher' COMMENT '服务商类型: teacher/planner/activity_provider/product_supplier'");
  1388. log.info("已添加type列到guides表");
  1389. } catch (Exception e) {
  1390. // 列已存在,忽略错误
  1391. }
  1392. // 迁移: guides 添加 teachers 合并字段
  1393. try {
  1394. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN teacher_no VARCHAR(20) COMMENT '服务商编号'");
  1395. log.info("已添加teacher_no列到guides表");
  1396. } catch (Exception e) {}
  1397. try {
  1398. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN title VARCHAR(50) COMMENT '职称/称号'");
  1399. } catch (Exception e) {}
  1400. try {
  1401. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN specialty VARCHAR(200) COMMENT '专长领域'");
  1402. } catch (Exception e) {}
  1403. try {
  1404. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN bio TEXT COMMENT '简介'");
  1405. } catch (Exception e) {}
  1406. try {
  1407. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN experience_years TINYINT DEFAULT 0 COMMENT '从业年限'");
  1408. } catch (Exception e) {}
  1409. try {
  1410. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN rating INT DEFAULT 500 COMMENT '评分(百分位,500=5.00)'");
  1411. } catch (Exception e) {}
  1412. try {
  1413. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN total_appointments INT DEFAULT 0 COMMENT '总服务次数'");
  1414. } catch (Exception e) {}
  1415. try {
  1416. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street_id BIGINT COMMENT '街道ID'");
  1417. } catch (Exception e) {}
  1418. try {
  1419. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN province VARCHAR(50) COMMENT '省份'");
  1420. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN city VARCHAR(50) COMMENT '城市'");
  1421. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN district VARCHAR(50) COMMENT '区县'");
  1422. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street VARCHAR(100) COMMENT '街道'");
  1423. log.info("已添加地址字段到guides表");
  1424. } catch (Exception e) {}
  1425. try {
  1426. jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN verified_at DATETIME COMMENT '审核通过时间'");
  1427. } catch (Exception e) {}
  1428. // 迁移: 将 teachers 表数据合并到 guides(type='teacher')
  1429. try {
  1430. int migrated = jdbcTemplate.update(
  1431. "INSERT IGNORE INTO guides (user_id, type, teacher_no, title, specialty, bio, experience_years, rating, total_appointments, " +
  1432. "street_id, province, city, district, street, verified_at, status, level, created_at, updated_at) " +
  1433. "SELECT t.user_id, 'teacher', t.teacher_no, t.title, t.specialty, t.bio, t.experience_years, t.rating, t.total_appointments, " +
  1434. "t.street_id, t.province, t.city, t.district, t.street, t.verified_at, " +
  1435. "CASE WHEN t.status = 'active' THEN 'active' ELSE 'inactive' END, " +
  1436. "CASE WHEN t.verification_status = 'approved' THEN 'senior' WHEN t.verification_status = 'pending' THEN 'junior' ELSE 'junior' END, " +
  1437. "t.created_at, t.updated_at " +
  1438. "FROM teachers t " +
  1439. "LEFT JOIN guides g ON g.user_id = t.user_id " +
  1440. "WHERE g.id IS NULL"
  1441. );
  1442. if (migrated > 0) {
  1443. log.info("已从 teachers 表迁移 {} 条数据到 guides(type=teacher)", migrated);
  1444. }
  1445. } catch (Exception e) {
  1446. log.warn("迁移 teachers → guides 数据失败: {}", e.getMessage());
  1447. }
  1448. // 迁移: dan_assessment_results 表添加认知维度字段(6维认知体系扩展)
  1449. try {
  1450. Integer colExists = jdbcTemplate.queryForObject(
  1451. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'perception_score'",
  1452. Integer.class);
  1453. if (colExists == null || colExists == 0) {
  1454. jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
  1455. "ADD COLUMN perception_score INT COMMENT '感知得分', " +
  1456. "ADD COLUMN spatial_score INT COMMENT '空间思维得分', " +
  1457. "ADD COLUMN processing_speed_score INT COMMENT '加工速度得分'");
  1458. log.info("已为 dan_assessment_results 表添加认知维度字段");
  1459. } else {
  1460. log.info("dan_assessment_results 认知维度字段已存在,跳过");
  1461. }
  1462. } catch (Exception e) {
  1463. log.warn("检查/添加 dan_assessment_results 认知维度字段失败: {}", e.getMessage());
  1464. }
  1465. // 迁移: dan_assessment_results 表添加 EMI 维度字段(v1.2 心智模型重构)
  1466. try {
  1467. Integer colExists = jdbcTemplate.queryForObject(
  1468. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'emotion_management_score'",
  1469. Integer.class);
  1470. if (colExists == null || colExists == 0) {
  1471. jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
  1472. "ADD COLUMN emotion_management_score INT COMMENT '情绪管理得分(EMI)', " +
  1473. "ADD COLUMN empathy_score INT COMMENT '同理心得分(EMI)', " +
  1474. "ADD COLUMN social_adaptability_score INT COMMENT '社会适应得分(EMI)', " +
  1475. "ADD COLUMN self_motivation_score INT COMMENT '自我激励得分(EMI)'");
  1476. log.info("已为 dan_assessment_results 表添加 EMI 维度字段");
  1477. } else {
  1478. log.info("dan_assessment_results EMI 维度字段已存在,跳过");
  1479. }
  1480. } catch (Exception e) {
  1481. log.warn("检查/添加 dan_assessment_results EMI 维度字段失败: {}", e.getMessage());
  1482. }
  1483. // 迁移: dan_assessment_results 表添加 EMI 心理子维度字段
  1484. try {
  1485. Integer colExists = jdbcTemplate.queryForObject(
  1486. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'emotion_score'",
  1487. Integer.class);
  1488. if (colExists == null || colExists == 0) {
  1489. jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
  1490. "ADD COLUMN emotion_score INT COMMENT '情绪商数(0-100)', " +
  1491. "ADD COLUMN resilience_score INT COMMENT '心理韧性(0-100)', " +
  1492. "ADD COLUMN stress_coping_score INT COMMENT '压力应对(0-100)', " +
  1493. "ADD COLUMN self_awareness_score INT COMMENT '自我认知(0-100)'");
  1494. log.info("已为 dan_assessment_results 表添加 EMI 心理子维度字段");
  1495. } else {
  1496. log.info("dan_assessment_results EMI 心理子维度字段已存在,跳过");
  1497. }
  1498. } catch (Exception e) {
  1499. log.warn("检查/添加 dan_assessment_results EMI 心理子维度字段失败: {}", e.getMessage());
  1500. }
  1501. // 迁移: dan_assessment_results 表添加大五人格维度字段
  1502. try {
  1503. Integer colExists = jdbcTemplate.queryForObject(
  1504. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'openness_score'",
  1505. Integer.class);
  1506. if (colExists == null || colExists == 0) {
  1507. jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
  1508. "ADD COLUMN openness_score INT COMMENT '开放性(0-100)', " +
  1509. "ADD COLUMN conscientiousness_score INT COMMENT '尽责性(0-100)', " +
  1510. "ADD COLUMN extraversion_score INT COMMENT '外向性(0-100)', " +
  1511. "ADD COLUMN agreeableness_score INT COMMENT '宜人性(0-100)', " +
  1512. "ADD COLUMN neuroticism_score INT COMMENT '神经质(0-100)', " +
  1513. "ADD COLUMN big_five_overall_score INT COMMENT '大五人格综合分'");
  1514. log.info("已为 dan_assessment_results 表添加大五人格维度字段");
  1515. } else {
  1516. log.info("dan_assessment_results 大五人格维度字段已存在,跳过");
  1517. }
  1518. } catch (Exception e) {
  1519. log.warn("检查/添加 dan_assessment_results 大五人格维度字段失败: {}", e.getMessage());
  1520. }
  1521. // 迁移: users 表添加出生时辰字段
  1522. try {
  1523. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN birth_hour VARCHAR(10) COMMENT '出生时辰: 子丑寅卯辰巳午未申酉戌亥'");
  1524. log.info("已添加birth_hour列到users表");
  1525. } catch (Exception e) {
  1526. // 列已存在,忽略错误
  1527. }
  1528. // 迁移: 重建 family_member_attributes 表结构(v1.2 心智模型)
  1529. try {
  1530. Integer colExists = jdbcTemplate.queryForObject(
  1531. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'family_member_attributes' AND COLUMN_NAME = 'member_id'",
  1532. Integer.class);
  1533. if (colExists == null || colExists == 0) {
  1534. // 旧表存在但结构不同,先删除旧表重建
  1535. jdbcTemplate.execute("DROP TABLE IF EXISTS family_member_attributes_old");
  1536. jdbcTemplate.execute("RENAME TABLE family_member_attributes TO family_member_attributes_old");
  1537. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_member_attributes (" +
  1538. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1539. "member_id BIGINT NOT NULL COMMENT '家庭成员ID(children表或users表)', " +
  1540. "member_type VARCHAR(10) NOT NULL COMMENT '成员类型: child/parent', " +
  1541. "birth_datetime DATETIME COMMENT '出生日期(精确到分钟,用于八字排盘)', " +
  1542. "eight_characters JSON COMMENT '八字四柱JSON', " +
  1543. "wuxing_elements JSON COMMENT '五行星盘元素JSON', " +
  1544. "zodiac VARCHAR(16) COMMENT '生肖: rat/ox/tiger/rabbit/dragon/snake/horse/sheep/monkey/rooster/dog/pig', " +
  1545. "blood_type VARCHAR(5) COMMENT '血型: A/B/AB/O', " +
  1546. "mind_base_score INT DEFAULT 0 COMMENT '心维度基础分(百分位,500=5.00)', " +
  1547. "wisdom_base_score INT DEFAULT 0 COMMENT '智维度基础分(百分位,500=5.00)', " +
  1548. "behavior_modifier JSON COMMENT '行为修正因子JSON', " +
  1549. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1550. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1551. "UNIQUE KEY uk_member (member_id, member_type)" +
  1552. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员先天属性表'");
  1553. log.info("已重建 family_member_attributes 表(v1.2 心智模型)");
  1554. } else {
  1555. log.info("family_member_attributes 表已为 v1.2 结构,跳过");
  1556. }
  1557. } catch (Exception e) {
  1558. log.warn("重建 family_member_attributes 表失败: {}", e.getMessage());
  1559. }
  1560. // 迁移: children 表添加 show_to_family 列(v1.3 家庭成员卡)
  1561. try {
  1562. Integer colExists = jdbcTemplate.queryForObject(
  1563. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'children' AND COLUMN_NAME = 'show_to_family'",
  1564. Integer.class);
  1565. if (colExists == null || colExists == 0) {
  1566. jdbcTemplate.execute("ALTER TABLE children ADD COLUMN show_to_family TINYINT DEFAULT 1 COMMENT '是否对家庭成员可见: 1=是, 0=否'");
  1567. log.info("已为 children 表添加 show_to_family 列");
  1568. } else {
  1569. log.info("children 表 show_to_family 列已存在,跳过");
  1570. }
  1571. } catch (Exception e) {
  1572. log.warn("检查/添加 children 表 show_to_family 列失败: {}", e.getMessage());
  1573. }
  1574. // 迁移: users 表添加 show_to_family 列(v1.3 家庭成员卡)
  1575. try {
  1576. Integer colExists = jdbcTemplate.queryForObject(
  1577. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'show_to_family'",
  1578. Integer.class);
  1579. if (colExists == null || colExists == 0) {
  1580. jdbcTemplate.execute("ALTER TABLE users ADD COLUMN show_to_family TINYINT DEFAULT 1 COMMENT '是否对家庭成员可见: 1=是, 0=否'");
  1581. log.info("已为 users 表添加 show_to_family 列");
  1582. } else {
  1583. log.info("users 表 show_to_family 列已存在,跳过");
  1584. }
  1585. } catch (Exception e) {
  1586. log.warn("检查/添加 users 表 show_to_family 列失败: {}", e.getMessage());
  1587. }
  1588. // ==================== DanShop 电商模块建表 ====================
  1589. // 1. danshop_categories (商品分类表)
  1590. try {
  1591. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_categories (" +
  1592. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1593. "parent_id BIGINT DEFAULT NULL COMMENT '父分类ID(null=一级)', " +
  1594. "name VARCHAR(64) NOT NULL COMMENT '分类名称', " +
  1595. "image VARCHAR(512) DEFAULT NULL COMMENT '分类图标URL', " +
  1596. "sort_order INT DEFAULT 0 COMMENT '排序(越小越靠前)', " +
  1597. "energy_dimension_id BIGINT DEFAULT NULL COMMENT '关联五维维度ID', " +
  1598. "status INT DEFAULT 1 COMMENT '1启用/0禁用', " +
  1599. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1600. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1601. "INDEX idx_parent (parent_id), " +
  1602. "INDEX idx_status (status)" +
  1603. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'");
  1604. log.info("已创建 danshop_categories 表");
  1605. } catch (Exception e) {
  1606. log.warn("创建 danshop_categories 表失败: {}", e.getMessage());
  1607. }
  1608. // 2. danshop_products (商品表)
  1609. try {
  1610. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_products (" +
  1611. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1612. "category_id BIGINT NOT NULL COMMENT '所属分类ID', " +
  1613. "name VARCHAR(128) NOT NULL COMMENT '商品名称', " +
  1614. "description TEXT COMMENT '商品详细描述', " +
  1615. "cover_image VARCHAR(512) COMMENT '封面图URL', " +
  1616. "images TEXT COMMENT '多图(JSON数组)', " +
  1617. "price INT NOT NULL COMMENT '售价(分)', " +
  1618. "member_price INT DEFAULT NULL COMMENT '会员价(分)', " +
  1619. "cost_price INT DEFAULT NULL COMMENT '成本价(分)', " +
  1620. "stock INT DEFAULT 0 COMMENT '库存数量', " +
  1621. "sales_count INT DEFAULT 0 COMMENT '销量', " +
  1622. "product_type VARCHAR(32) NOT NULL COMMENT '类型: physical/virtual/coupon/course', " +
  1623. "vendor_id BIGINT DEFAULT NULL COMMENT '供应商ID', " +
  1624. "vendor_name VARCHAR(64) COMMENT '供应商名称', " +
  1625. "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/approved/rejected/on_shelf/off_shelf', " +
  1626. "reject_reason VARCHAR(255) DEFAULT NULL COMMENT '审核拒绝原因', " +
  1627. "profit_rate INT DEFAULT 0 COMMENT '平台利润率(bps,100=1%)', " +
  1628. "external_source VARCHAR(32) COMMENT '外部商品来源', " +
  1629. "external_id VARCHAR(64) COMMENT '外部商品ID', " +
  1630. "external_data TEXT COMMENT '外部商品完整数据(JSON)', " +
  1631. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1632. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1633. "INDEX idx_category (category_id), " +
  1634. "INDEX idx_vendor (vendor_id), " +
  1635. "INDEX idx_status (status)" +
  1636. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'");
  1637. log.info("已创建 danshop_products 表");
  1638. } catch (Exception e) {
  1639. log.warn("创建 danshop_products 表失败: {}", e.getMessage());
  1640. }
  1641. // 3. danshop_orders (主订单表)
  1642. try {
  1643. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_orders (" +
  1644. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1645. "order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号(DS+时间戳+6位随机)', " +
  1646. "buyer_id BIGINT NOT NULL COMMENT '买家ID', " +
  1647. "family_id BIGINT DEFAULT NULL COMMENT '家庭ID', " +
  1648. "total_amount INT NOT NULL COMMENT '订单总金额(分)', " +
  1649. "discount_amount INT DEFAULT 0 COMMENT '优惠金额(分)', " +
  1650. "actual_amount INT NOT NULL COMMENT '实付金额(分)', " +
  1651. "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/shipped/completed/refunding/refunded/cancelled', " +
  1652. "payment_method VARCHAR(16) DEFAULT 'wechat' COMMENT 'wechat/alipay', " +
  1653. "transaction_id VARCHAR(64) DEFAULT NULL COMMENT '微信/支付宝交易号', " +
  1654. "paid_at DATETIME DEFAULT NULL COMMENT '支付时间', " +
  1655. "shipped_at DATETIME DEFAULT NULL COMMENT '发货时间', " +
  1656. "completed_at DATETIME DEFAULT NULL COMMENT '完成时间', " +
  1657. "remark VARCHAR(255) DEFAULT NULL COMMENT '买家备注', " +
  1658. "cancel_reason VARCHAR(255) DEFAULT NULL COMMENT '取消原因', " +
  1659. "receiver_name VARCHAR(32) DEFAULT NULL COMMENT '收件人姓名', " +
  1660. "receiver_phone VARCHAR(16) DEFAULT NULL COMMENT '收件人电话', " +
  1661. "receiver_address VARCHAR(256) DEFAULT NULL COMMENT '收件人地址', " +
  1662. "freight INT DEFAULT 0 COMMENT '运费(分)', " +
  1663. "profit_amount INT DEFAULT 0 COMMENT '分润金额(分)', " +
  1664. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1665. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1666. "INDEX idx_buyer (buyer_id), " +
  1667. "INDEX idx_family (family_id), " +
  1668. "INDEX idx_status (status), " +
  1669. "INDEX idx_order_no (order_no)" +
  1670. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='主订单表'");
  1671. log.info("已创建 danshop_orders 表");
  1672. } catch (Exception e) {
  1673. log.warn("创建 danshop_orders 表失败: {}", e.getMessage());
  1674. }
  1675. // 4. danshop_order_items (订单明细表)
  1676. try {
  1677. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_order_items (" +
  1678. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1679. "order_id BIGINT NOT NULL COMMENT '所属订单ID', " +
  1680. "product_id BIGINT NOT NULL COMMENT '商品ID', " +
  1681. "product_name VARCHAR(128) NOT NULL COMMENT '商品名称(快照)', " +
  1682. "cover_image VARCHAR(512) COMMENT '封面图(快照)', " +
  1683. "unit_price INT NOT NULL COMMENT '单价(分,快照)', " +
  1684. "price INT NOT NULL COMMENT '单价(分,别名兼容)', " +
  1685. "quantity INT NOT NULL DEFAULT 1 COMMENT '购买数量', " +
  1686. "subtotal INT NOT NULL COMMENT '小计(分)', " +
  1687. "product_type VARCHAR(32) COMMENT '商品类型(快照)', " +
  1688. "vendor_id BIGINT COMMENT '供应商ID(快照)', " +
  1689. "vendor_name VARCHAR(64) COMMENT '供应商名称(快照)', " +
  1690. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1691. "INDEX idx_order (order_id), " +
  1692. "INDEX idx_product (product_id)" +
  1693. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表'");
  1694. log.info("已创建 danshop_order_items 表");
  1695. } catch (Exception e) {
  1696. log.warn("创建 danshop_order_items 表失败: {}", e.getMessage());
  1697. }
  1698. // 5. danshop_order_logs (订单操作日志表)
  1699. try {
  1700. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_order_logs (" +
  1701. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1702. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  1703. "action VARCHAR(32) NOT NULL COMMENT '操作: create/pay/ship/confirm/refund/cancel', " +
  1704. "operator_id BIGINT DEFAULT NULL COMMENT '操作人ID', " +
  1705. "operator_type VARCHAR(16) DEFAULT NULL COMMENT 'buyer/vendor/admin/system', " +
  1706. "detail TEXT DEFAULT NULL COMMENT '操作详情(JSON)', " +
  1707. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1708. "INDEX idx_order (order_id)" +
  1709. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单操作日志表'");
  1710. log.info("已创建 danshop_order_logs 表");
  1711. } catch (Exception e) {
  1712. log.warn("创建 danshop_order_logs 表失败: {}", e.getMessage());
  1713. }
  1714. // 6. danshop_logistics (物流信息表)
  1715. try {
  1716. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_logistics (" +
  1717. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1718. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  1719. "order_item_id BIGINT DEFAULT NULL COMMENT '订单明细ID', " +
  1720. "logistics_no VARCHAR(64) NOT NULL COMMENT '物流单号', " +
  1721. "logistics_company VARCHAR(32) NOT NULL COMMENT '物流公司编码: sf/yto/zt/sto/yd/ttkd', " +
  1722. "logistics_name VARCHAR(32) DEFAULT NULL COMMENT '物流公司名称', " +
  1723. "sender_name VARCHAR(32) DEFAULT NULL COMMENT '发货人姓名', " +
  1724. "sender_phone VARCHAR(16) DEFAULT NULL COMMENT '发货人电话', " +
  1725. "sender_address VARCHAR(256) DEFAULT NULL COMMENT '发货人地址', " +
  1726. "receiver_name VARCHAR(32) NOT NULL COMMENT '收件人姓名', " +
  1727. "receiver_phone VARCHAR(16) NOT NULL COMMENT '收件人电话', " +
  1728. "receiver_address VARCHAR(256) NOT NULL COMMENT '收件人地址', " +
  1729. "status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/shipped/signing/completed', " +
  1730. "tracking_data TEXT DEFAULT NULL COMMENT '物流轨迹(JSON数组)', " +
  1731. "shipped_at DATETIME DEFAULT NULL COMMENT '发货时间', " +
  1732. "signed_at DATETIME DEFAULT NULL COMMENT '签收时间', " +
  1733. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1734. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1735. "INDEX idx_order (order_id), " +
  1736. "INDEX idx_logistics_no (logistics_no), " +
  1737. "INDEX idx_status (status)" +
  1738. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流信息表'");
  1739. log.info("已创建 danshop_logistics 表");
  1740. } catch (Exception e) {
  1741. log.warn("创建 danshop_logistics 表失败: {}", e.getMessage());
  1742. }
  1743. // 7. danshop_profit_rules (分润规则配置表)
  1744. try {
  1745. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_profit_rules (" +
  1746. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1747. "rule_name VARCHAR(64) NOT NULL COMMENT '规则名称', " +
  1748. "rule_type VARCHAR(32) NOT NULL COMMENT '规则类型: product_category/vendor_level/global', " +
  1749. "target_id BIGINT DEFAULT NULL COMMENT '关联目标ID', " +
  1750. "platform_profit_rate INT NOT NULL COMMENT '平台利润率(bps,100=1%)', " +
  1751. "referrer_l1_rate INT DEFAULT 0 COMMENT '直接推荐人分润比例(bps,占利润%)', " +
  1752. "referrer_l2_rate INT DEFAULT 0 COMMENT '间接推荐人分润比例(bps,占利润%)', " +
  1753. "platform_service_rate INT DEFAULT 0 COMMENT '平台服务费率(bps,占订单金额%)', " +
  1754. "is_active INT DEFAULT 1 COMMENT '1启用/0禁用', " +
  1755. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1756. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1757. "INDEX idx_type (rule_type, target_id), " +
  1758. "INDEX idx_active (is_active)" +
  1759. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分润规则配置表'");
  1760. log.info("已创建 danshop_profit_rules 表");
  1761. } catch (Exception e) {
  1762. log.warn("创建 danshop_profit_rules 表失败: {}", e.getMessage());
  1763. }
  1764. // 8. danshop_profit_logs (分润流水表)
  1765. try {
  1766. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_profit_logs (" +
  1767. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1768. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  1769. "order_no VARCHAR(32) NOT NULL COMMENT '订单号', " +
  1770. "order_amount INT NOT NULL COMMENT '订单实付金额(分)', " +
  1771. "cost_amount INT DEFAULT 0 COMMENT '成本金额(分)', " +
  1772. "profit_amount INT NOT NULL COMMENT '利润金额(分)', " +
  1773. "platform_retained INT NOT NULL COMMENT '平台留存金额(分)', " +
  1774. "vendor_income INT NOT NULL COMMENT '供应商实收金额(分)', " +
  1775. "commission_l1 INT DEFAULT 0 COMMENT 'L1推荐人佣金(分)', " +
  1776. "commission_l2 INT DEFAULT 0 COMMENT 'L2推荐人佣金(分)', " +
  1777. "rule_id BIGINT DEFAULT NULL COMMENT '使用的分润规则ID', " +
  1778. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1779. "INDEX idx_order (order_id), " +
  1780. "INDEX idx_order_no (order_no)" +
  1781. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分润流水表'");
  1782. log.info("已创建 danshop_profit_logs 表");
  1783. } catch (Exception e) {
  1784. log.warn("创建 danshop_profit_logs 表失败: {}", e.getMessage());
  1785. }
  1786. // 9. danshop_favorites (商品收藏表)
  1787. try {
  1788. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_favorites (" +
  1789. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1790. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  1791. "product_id BIGINT NOT NULL COMMENT '商品ID', " +
  1792. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1793. "UNIQUE INDEX idx_user_product (user_id, product_id), " +
  1794. "INDEX idx_user (user_id)" +
  1795. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品收藏表'");
  1796. log.info("已创建 danshop_favorites 表");
  1797. } catch (Exception e) {
  1798. log.warn("创建 danshop_favorites 表失败: {}", e.getMessage());
  1799. }
  1800. // 10. danshop_addresses (收货地址表)
  1801. try {
  1802. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS danshop_addresses (" +
  1803. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1804. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  1805. "receiver_name VARCHAR(32) NOT NULL COMMENT '收货人姓名', " +
  1806. "phone VARCHAR(16) NOT NULL COMMENT '收货人电话', " +
  1807. "province VARCHAR(32) NOT NULL COMMENT '省份', " +
  1808. "city VARCHAR(32) NOT NULL COMMENT '城市', " +
  1809. "district VARCHAR(32) NOT NULL COMMENT '区县', " +
  1810. "street VARCHAR(256) NOT NULL COMMENT '详细地址', " +
  1811. "is_default INT DEFAULT 0 COMMENT '是否默认地址 1=是 0=否', " +
  1812. "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
  1813. "updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1814. "INDEX idx_user (user_id), " +
  1815. "INDEX idx_user_default (user_id, is_default)" +
  1816. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='收货地址表'");
  1817. log.info("已创建 danshop_addresses 表");
  1818. } catch (Exception e) {
  1819. log.warn("创建 danshop_addresses 表失败: {}", e.getMessage());
  1820. }
  1821. // ==================== 迁移: activities表添加visible_scope列 ====================
  1822. try {
  1823. Integer colExists = jdbcTemplate.queryForObject(
  1824. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'activities' AND COLUMN_NAME = 'visible_scope'",
  1825. Integer.class);
  1826. if (colExists == null || colExists == 0) {
  1827. jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN visible_scope VARCHAR(20) DEFAULT NULL COMMENT '可见范围: family(家庭)/role(角色)/user(用户)' AFTER visibility");
  1828. log.info("已添加visible_scope列到activities表");
  1829. } else {
  1830. log.info("activities表visible_scope列已存在,跳过");
  1831. }
  1832. } catch (Exception e) {
  1833. log.warn("检查/添加activities表visible_scope列失败: {}", e.getMessage());
  1834. }
  1835. // ==================== 迁移: articles表添加article_type列 ====================
  1836. try {
  1837. Integer colExists = jdbcTemplate.queryForObject(
  1838. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'article_type'",
  1839. Integer.class);
  1840. if (colExists == null || colExists == 0) {
  1841. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN article_type VARCHAR(20) DEFAULT 'normal' COMMENT '文章类型: normal(普通)/premium(优质)/original(原创)' AFTER read_time");
  1842. log.info("已添加article_type列到articles表");
  1843. } else {
  1844. log.info("articles表article_type列已存在,跳过");
  1845. }
  1846. } catch (Exception e) {
  1847. log.warn("检查/添加articles表article_type列失败: {}", e.getMessage());
  1848. }
  1849. // ==================== 迁移: articles表添加word_count列 ====================
  1850. try {
  1851. Integer colExists = jdbcTemplate.queryForObject(
  1852. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'articles' AND COLUMN_NAME = 'word_count'",
  1853. Integer.class);
  1854. if (colExists == null || colExists == 0) {
  1855. jdbcTemplate.execute("ALTER TABLE articles ADD COLUMN word_count INT DEFAULT 0 COMMENT '文章字数' AFTER article_type");
  1856. log.info("已添加word_count列到articles表");
  1857. } else {
  1858. log.info("articles表word_count列已存在,跳过");
  1859. }
  1860. } catch (Exception e) {
  1861. log.warn("检查/添加articles表word_count列失败: {}", e.getMessage());
  1862. }
  1863. // ==================== 迁移: article_reading_records表添加task_id列 ====================
  1864. try {
  1865. Integer colExists = jdbcTemplate.queryForObject(
  1866. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'article_reading_records' AND COLUMN_NAME = 'task_id'",
  1867. Integer.class);
  1868. if (colExists == null || colExists == 0) {
  1869. jdbcTemplate.execute("ALTER TABLE article_reading_records ADD COLUMN task_id BIGINT DEFAULT NULL COMMENT '关联的任务ID' AFTER duration_seconds");
  1870. log.info("已添加task_id列到article_reading_records表");
  1871. } else {
  1872. log.info("article_reading_records表task_id列已存在,跳过");
  1873. }
  1874. } catch (Exception e) {
  1875. log.warn("检查/添加article_reading_records表task_id列失败: {}", e.getMessage());
  1876. }
  1877. // ==================== 迁移: tasks表添加source_type列 ====================
  1878. try {
  1879. Integer colExists = jdbcTemplate.queryForObject(
  1880. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tasks' AND COLUMN_NAME = 'source_type'",
  1881. Integer.class);
  1882. if (colExists == null || colExists == 0) {
  1883. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN source_type VARCHAR(20) DEFAULT NULL COMMENT '来源类型: article/activity/manual' AFTER minigame_code");
  1884. log.info("已添加source_type列到tasks表");
  1885. } else {
  1886. log.info("tasks表source_type列已存在,跳过");
  1887. }
  1888. } catch (Exception e) {
  1889. log.warn("检查/添加tasks表source_type列失败: {}", e.getMessage());
  1890. }
  1891. // ==================== 迁移: tasks表添加source_id列 ====================
  1892. try {
  1893. Integer colExists = jdbcTemplate.queryForObject(
  1894. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'tasks' AND COLUMN_NAME = 'source_id'",
  1895. Integer.class);
  1896. if (colExists == null || colExists == 0) {
  1897. jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN source_id BIGINT DEFAULT NULL COMMENT '来源ID' AFTER source_type");
  1898. log.info("已添加source_id列到tasks表");
  1899. } else {
  1900. log.info("tasks表source_id列已存在,跳过");
  1901. }
  1902. } catch (Exception e) {
  1903. log.warn("检查/添加tasks表source_id列失败: {}", e.getMessage());
  1904. }
  1905. // ==================== 迁移: activities表添加checkin_points列 ====================
  1906. try {
  1907. Integer colExists = jdbcTemplate.queryForObject(
  1908. "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'activities' AND COLUMN_NAME = 'checkin_points'",
  1909. Integer.class);
  1910. if (colExists == null || colExists == 0) {
  1911. jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN checkin_points INT DEFAULT 0 COMMENT '签到积分(0=使用系统默认)' AFTER price");
  1912. log.info("已添加checkin_points列到activities表");
  1913. } else {
  1914. log.info("activities表checkin_points列已存在,跳过");
  1915. }
  1916. } catch (Exception e) {
  1917. log.warn("检查/添加activities表checkin_points列失败: {}", e.getMessage());
  1918. }
  1919. // 迁移: 创建family_invitations表(家庭邀请令牌)
  1920. try {
  1921. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_invitations (" +
  1922. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  1923. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  1924. "token VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请令牌(UUID)', " +
  1925. "created_by BIGINT NOT NULL COMMENT '创建人用户ID', " +
  1926. "status VARCHAR(16) DEFAULT 'active' COMMENT '状态: active/used/expired/revoked', " +
  1927. "expires_at DATETIME COMMENT '过期时间', " +
  1928. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  1929. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  1930. "INDEX idx_family_id (family_id), " +
  1931. "INDEX idx_token (token), " +
  1932. "INDEX idx_status (status)" +
  1933. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭邀请令牌'");
  1934. log.info("已创建family_invitations表");
  1935. } catch (Exception e) {
  1936. log.warn("创建family_invitations表失败: {}", e.getMessage());
  1937. }
  1938. log.info("数据库迁移完成");
  1939. }
  1940. private void initializeDefaultData() {
  1941. // 初始化预设奖励模板
  1942. try {
  1943. // 检查是否已有预设奖励
  1944. Integer count = jdbcTemplate.queryForObject(
  1945. "SELECT COUNT(*) FROM rewards WHERE is_template = 1", Integer.class);
  1946. if (count == null || count == 0) {
  1947. // 插入预设奖励模板(child_id为NULL表示模板)
  1948. jdbcTemplate.execute("INSERT INTO rewards (family_id, child_id, title, points_required, category, is_template) VALUES " +
  1949. "(0, NULL, \'看动画片30分钟\', 30, \'精神类\', 1), " +
  1950. "(0, NULL, \'玩平板游戏30分钟\', 40, \'特权类\', 1), " +
  1951. "(0, NULL, \'去游乐场\', 100, \'物质类\', 1), " +
  1952. "(0, NULL, \'买玩具\', 150, \'物质类\', 1), " +
  1953. "(0, NULL, \'吃冰淇淋\', 20, \'物质类\', 1), " +
  1954. "(0, NULL, \'选择晚餐\', 25, \'特权类\', 1)");
  1955. log.info("预设奖励模板已初始化");
  1956. }
  1957. } catch (Exception e) {
  1958. log.warn("初始化预设奖励失败: {}", e.getMessage());
  1959. }
  1960. // 初始化奖励分类
  1961. try {
  1962. Integer count = jdbcTemplate.queryForObject(
  1963. "SELECT COUNT(*) FROM reward_categories WHERE family_id = 0", Integer.class);
  1964. if (count == null || count == 0) {
  1965. jdbcTemplate.execute("INSERT INTO reward_categories (family_id, name, icon, sort_order) VALUES " +
  1966. "(0, '物质类', 'gift', 1), " +
  1967. "(0, '精神类', 'star', 2), " +
  1968. "(0, '特权类', 'crown', 3)");
  1969. log.info("奖励分类已初始化");
  1970. }
  1971. } catch (Exception e) {
  1972. log.warn("初始化奖励分类失败: {}", e.getMessage());
  1973. }
  1974. // 初始化打卡里程碑
  1975. try {
  1976. Integer count = jdbcTemplate.queryForObject(
  1977. "SELECT COUNT(*) FROM streak_milestones", Integer.class);
  1978. if (count == null || count == 0) {
  1979. jdbcTemplate.execute("INSERT INTO streak_milestones (days, reward_points) VALUES " +
  1980. "(3, 5), (7, 10), (30, 30), (100, 100)");
  1981. log.info("打卡里程碑已初始化");
  1982. }
  1983. } catch (Exception e) {
  1984. log.warn("初始化打卡里程碑失败: {}", e.getMessage());
  1985. }
  1986. // 初始化勋章定义
  1987. try {
  1988. Integer count = jdbcTemplate.queryForObject(
  1989. "SELECT COUNT(*) FROM badge_definitions", Integer.class);
  1990. if (count == null || count == 0) {
  1991. jdbcTemplate.execute("INSERT INTO badge_definitions (badge_type, name, description, icon, condition_type, condition_value, reward_points) VALUES " +
  1992. "('first_task', '初次完成任务', '首次完成任意任务', 'star', 'task_count', 1, 0), " +
  1993. "('streak_3', '连续3天', '连续3天按时完成任务', 'fire', 'streak_days', 3, 5), " +
  1994. "('streak_7', '连续7天', '连续7天按时完成任务', 'fire', 'streak_days', 7, 10), " +
  1995. "('points_50', '积分达人', '一周获得50积分', 'trophy', 'weekly_points', 50, 0), " +
  1996. "('task_100', '任务达人', '完成100个任务', 'medal', 'task_count', 100, 20)");
  1997. log.info("勋章定义已初始化");
  1998. }
  1999. } catch (Exception e) {
  2000. log.warn("初始化勋章定义失败: {}", e.getMessage());
  2001. }
  2002. // 初始化小游戏数据
  2003. try {
  2004. Integer count = jdbcTemplate.queryForObject(
  2005. "SELECT COUNT(*) FROM mini_games", Integer.class);
  2006. if (count == null || count == 0) {
  2007. jdbcTemplate.execute("INSERT INTO mini_games (game_code, game_name, description, icon, default_duration, default_points, game_url, sort_order, status) VALUES " +
  2008. "('schulte', '舒尔特方格', '训练专注力和眼动能力,找到1-25按顺序点击', '🎯', 5, 5, '/pages/games/schulte', 1, 1), " +
  2009. "('1a2b', '猜数字', '猜对数字和位置获得积分,锻炼逻辑思维', '🔢', 10, 5, '/pages/games/1a2b', 2, 1), " +
  2010. "('sudoku', '数独', '填充数字使每行每列每个宫格都包含1-9', '🔳', 15, 8, '/pages/games/sudoku', 3, 1)");
  2011. log.info("小游戏数据已初始化");
  2012. }
  2013. } catch (Exception e) {
  2014. log.warn("初始化小游戏数据失败: {}", e.getMessage());
  2015. }
  2016. // 初始化默认管理员
  2017. try {
  2018. // 先检查并添加role列(如果不存在)
  2019. try {
  2020. jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN role VARCHAR(20) DEFAULT \'admin\' AFTER email");
  2021. log.info("已添加role列到admins表");
  2022. } catch (Exception e) {
  2023. // 列已存在,忽略错误
  2024. }
  2025. Integer count = jdbcTemplate.queryForObject(
  2026. "SELECT COUNT(*) FROM admins", Integer.class);
  2027. if (count == null || count == 0) {
  2028. // 默认管理员: 手机号 13800138000, 密码 admin123
  2029. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  2030. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  2031. "(\'admin\', \'" + encryptedPassword + "\', \'系统管理员\', \'13800138000\', \'admin@xzyj.com\', \'admin\', 1)");
  2032. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  2033. }
  2034. } catch (Exception e) {
  2035. log.warn("初始化默认管理员失败: {}", e.getMessage());
  2036. }
  2037. // 初始化成长规划师WEB管理账号
  2038. try {
  2039. Integer teacherCount = jdbcTemplate.queryForObject(
  2040. "SELECT COUNT(*) FROM admins WHERE username = 'teacher'", Integer.class);
  2041. if (teacherCount == null || teacherCount == 0) {
  2042. String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_teacher123".getBytes());
  2043. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, role, status) VALUES " +
  2044. "('teacher', '" + teacherPassword + "', '成长规划师', '13800138003', 'teacher', 1)");
  2045. log.info("成长规划师管理账号已初始化: 用户名 teacher, 密码 teacher123");
  2046. }
  2047. } catch (Exception e) {
  2048. log.warn("初始化成长规划师管理账号失败: {}", e.getMessage());
  2049. }
  2050. // 初始化任务模板示例数据
  2051. try {
  2052. Integer count = jdbcTemplate.queryForObject(
  2053. "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
  2054. if (count == null || count == 0) {
  2055. jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES " +
  2056. "('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1), " +
  2057. "('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1), " +
  2058. "('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1), " +
  2059. "('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1), " +
  2060. "('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1), " +
  2061. "('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1), " +
  2062. "('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1), " +
  2063. "('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1)");
  2064. log.info("任务模板示例数据已初始化");
  2065. }
  2066. } catch (Exception e) {
  2067. log.warn("初始化任务模板失败: {}", e.getMessage());
  2068. }
  2069. // 初始化街道数据示例(广东省广州市)
  2070. try {
  2071. Integer count = jdbcTemplate.queryForObject(
  2072. "SELECT COUNT(*) FROM streets", Integer.class);
  2073. if (count == null || count == 0) {
  2074. // 省
  2075. jdbcTemplate.execute("INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES " +
  2076. "('广东省', '44', '广东省', 1, NULL, 1)");
  2077. // 市
  2078. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES " +
  2079. "('广东省', '44', '广州市', '4401', '广东省广州市', 2, 1, 1), " +
  2080. "('广东省', '44', '深圳市', '4403', '广东省深圳市', 2, 1, 2), " +
  2081. "('广东省', '44', '珠海市', '4404', '广东省珠海市', 2, 1, 3)");
  2082. // 区
  2083. jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES " +
  2084. "('广东省', '44', '广州市', '4401', '天河区', '440106', '广东省广州市天河区', 3, 2, 1), " +
  2085. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '广东省广州市越秀区', 3, 2, 2), " +
  2086. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '广东省广州市番禺区', 3, 2, 3), " +
  2087. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '广东省深圳市南山区', 3, 3, 1), " +
  2088. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '广东省深圳市福田区', 3, 3, 2)");
  2089. // 街道
  2090. 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 " +
  2091. "('广东省', '44', '广州市', '4401', '天河区', '440106', '天河南街道', '440106001', '广东省广州市天河区天河南街道', 4, 4, 1), " +
  2092. "('广东省', '44', '广州市', '4401', '天河区', '440106', '沙河街道', '440106002', '广东省广州市天河区沙河街道', 4, 4, 2), " +
  2093. "('广东省', '44', '广州市', '4401', '越秀区', '440104', '东山街道', '440104001', '广东省广州市越秀区东山街道', 4, 5, 1), " +
  2094. "('广东省', '44', '广州市', '4401', '番禺区', '440113', '市桥街道', '440113001', '广东省广州市番禺区市桥街道', 4, 6, 1), " +
  2095. "('广东省', '44', '深圳市', '4403', '南山区', '440305', '南山街道', '440305001', '广东省深圳市南山区南山街道', 4, 7, 1), " +
  2096. "('广东省', '44', '深圳市', '4403', '福田区', '440304', '福田街道', '440304001', '广东省深圳市福田区福田街道', 4, 8, 1)");
  2097. log.info("街道示例数据已初始化");
  2098. }
  2099. } catch (Exception e) {
  2100. log.warn("初始化街道数据失败: {}", e.getMessage());
  2101. }
  2102. try {
  2103. Integer count = jdbcTemplate.queryForObject(
  2104. "SELECT COUNT(*) FROM admins", Integer.class);
  2105. if (count == null || count == 0) {
  2106. // 默认管理员: 手机号 13800138000, 密码 admin123
  2107. String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
  2108. jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
  2109. "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 'admin', 1)");
  2110. log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
  2111. }
  2112. } catch (Exception e) {
  2113. log.warn("初始化任务模板失败: {}", e.getMessage());
  2114. }
  2115. // 初始化 users 表测试用户
  2116. try {
  2117. // 创建默认家庭(如果不存在)
  2118. Integer familyCount = jdbcTemplate.queryForObject(
  2119. "SELECT COUNT(*) FROM families WHERE id = 1", Integer.class);
  2120. if (familyCount == null || familyCount == 0) {
  2121. jdbcTemplate.execute("INSERT INTO families (id, name, invite_code) VALUES (1, '默认家庭', '888888')");
  2122. log.info("默认家庭已创建");
  2123. }
  2124. // 密码加密: MD5("xzyj_salt_" + password)
  2125. String adminPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_admin123".getBytes());
  2126. String teacherPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_salt_teacher123".getBytes());
  2127. // 创建管理员用户(管理员角色,密码 admin123)
  2128. Integer adminUserCount = jdbcTemplate.queryForObject(
  2129. "SELECT COUNT(*) FROM users WHERE phone = '13800138000'", Integer.class);
  2130. if (adminUserCount == null || adminUserCount == 0) {
  2131. jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
  2132. "('seed_admin_13800138000', 1, 'admin', '13800138000', '系统管理员', '系统管理员', '" + adminPassword + "')");
  2133. log.info("测试管理员用户已创建: 13800138000 (admin角色, 密码 admin123)");
  2134. }
  2135. // 创建测试教师用户
  2136. Integer teacherUserCount = jdbcTemplate.queryForObject(
  2137. "SELECT COUNT(*) FROM users WHERE phone = '13800138001'", Integer.class);
  2138. if (teacherUserCount == null || teacherUserCount == 0) {
  2139. jdbcTemplate.execute("INSERT INTO users (openid, family_id, role, phone, nickname, real_name, password) VALUES " +
  2140. "('seed_teacher_13800138001', 1, 'teacher', '13800138001', '测试教师', '测试教师', '" + teacherPassword + "')");
  2141. log.info("测试教师用户已创建: 13800138001 (教师角色, 密码 teacher123)");
  2142. }
  2143. } catch (Exception e) {
  2144. log.warn("初始化测试用户失败: {}", e.getMessage());
  2145. }
  2146. // 初始化星座配置种子数据
  2147. try {
  2148. Integer count = jdbcTemplate.queryForObject(
  2149. "SELECT COUNT(*) FROM zodiac_config", Integer.class);
  2150. if (count == null || count == 0) {
  2151. jdbcTemplate.execute("INSERT INTO zodiac_config (zodiac_code, zodiac_name, element, start_month, start_day, end_month, end_day, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
  2152. "('aries', '白羊座', '火', 3, 21, 4, 19, 65, 70, 1.10, 1.05, 0.95), " +
  2153. "('taurus', '金牛座', '土', 4, 20, 5, 20, 60, 65, 1.05, 1.10, 1.15), " +
  2154. "('gemini', '双子座', '金', 5, 21, 6, 21, 70, 80, 0.95, 1.15, 0.90), " +
  2155. "('cancer', '巨蟹座', '水', 6, 22, 7, 22, 75, 60, 1.00, 0.90, 1.05), " +
  2156. "('leo', '狮子座', '火', 7, 23, 8, 22, 65, 75, 1.15, 1.00, 1.10), " +
  2157. "('virgo', '处女座', '土', 8, 23, 9, 22, 55, 85, 0.95, 1.05, 1.00), " +
  2158. "('libra', '天秤座', '金', 9, 23, 10, 23, 70, 75, 0.90, 0.95, 1.10), " +
  2159. "('scorpio', '天蝎座', '水', 10, 24, 11, 22, 80, 70, 1.10, 1.00, 1.15), " +
  2160. "('sagittarius', '射手座', '火', 11, 23, 12, 21, 60, 65, 1.15, 1.15, 0.95), " +
  2161. "('capricorn', '摩羯座', '土', 12, 22, 1, 19, 55, 75, 1.05, 1.10, 1.20), " +
  2162. "('aquarius', '水瓶座', '金', 1, 20, 2, 18, 75, 85, 0.95, 0.90, 1.00), " +
  2163. "('pisces', '双鱼座', '水', 2, 19, 3, 20, 80, 60, 0.90, 0.95, 0.95)");
  2164. log.info("星座配置种子数据已初始化");
  2165. }
  2166. } catch (Exception e) {
  2167. log.warn("初始化星座配置失败: {}", e.getMessage());
  2168. }
  2169. // 初始化八字配置种子数据(十天干 + 十二地支)
  2170. try {
  2171. Integer count = jdbcTemplate.queryForObject(
  2172. "SELECT COUNT(*) FROM bazi_config", Integer.class);
  2173. if (count == null || count == 0) {
  2174. jdbcTemplate.execute("INSERT INTO bazi_config (stem_or_branch, type, element, yin_yang, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
  2175. "('甲', 'stem', '木', 'yang', 60, 75, 1.05, 1.10, 1.00), " +
  2176. "('乙', 'stem', '木', 'yin', 65, 70, 0.95, 1.05, 1.00), " +
  2177. "('丙', 'stem', '火', 'yang', 75, 65, 1.10, 1.00, 0.95), " +
  2178. "('丁', 'stem', '火', 'yin', 70, 60, 1.05, 0.95, 1.00), " +
  2179. "('戊', 'stem', '土', 'yang', 60, 65, 1.15, 1.05, 1.10), " +
  2180. "('己', 'stem', '土', 'yin', 55, 60, 1.10, 1.00, 1.15), " +
  2181. "('庚', 'stem', '金', 'yang', 65, 80, 1.05, 1.10, 1.05), " +
  2182. "('辛', 'stem', '金', 'yin', 60, 75, 0.95, 1.00, 1.10), " +
  2183. "('壬', 'stem', '水', 'yang', 70, 70, 1.00, 0.95, 1.15), " +
  2184. "('癸', 'stem', '水', 'yin', 75, 65, 0.95, 0.90, 1.10), " +
  2185. "('子', 'branch', '水', 'yang', 70, 65, 1.00, 0.95, 1.05), " +
  2186. "('丑', 'branch', '土', 'yin', 55, 60, 1.10, 1.05, 1.10), " +
  2187. "('寅', 'branch', '木', 'yang', 65, 75, 1.15, 1.10, 1.00), " +
  2188. "('卯', 'branch', '木', 'yin', 60, 70, 1.05, 1.05, 0.95), " +
  2189. "('辰', 'branch', '土', 'yang', 60, 65, 1.10, 1.00, 1.10), " +
  2190. "('巳', 'branch', '火', 'yin', 75, 70, 1.05, 0.95, 1.00), " +
  2191. "('午', 'branch', '火', 'yang', 80, 65, 1.15, 1.00, 0.95), " +
  2192. "('未', 'branch', '土', 'yin', 55, 60, 1.05, 1.00, 1.05), " +
  2193. "('申', 'branch', '金', 'yang', 65, 80, 1.00, 1.10, 1.05), " +
  2194. "('酉', 'branch', '金', 'yin', 60, 75, 0.95, 1.05, 1.10), " +
  2195. "('戌', 'branch', '土', 'yang', 60, 65, 1.10, 1.00, 1.15), " +
  2196. "('亥', 'branch', '水', 'yin', 70, 60, 1.00, 0.95, 1.05)");
  2197. log.info("八字配置种子数据已初始化");
  2198. }
  2199. } catch (Exception e) {
  2200. log.warn("初始化八字配置失败: {}", e.getMessage());
  2201. }
  2202. // 初始化血型配置种子数据
  2203. try {
  2204. Integer count = jdbcTemplate.queryForObject(
  2205. "SELECT COUNT(*) FROM blood_type_config", Integer.class);
  2206. if (count == null || count == 0) {
  2207. jdbcTemplate.execute("INSERT INTO blood_type_config (blood_type, description, mind_base, wisdom_base, body_modifier, action_modifier, wealth_modifier) VALUES " +
  2208. "('A', '认真负责、严谨细致,但易焦虑', 70, 75, 0.95, 1.00, 1.05), " +
  2209. "('B', '乐观开朗、好奇心强,但缺乏耐心', 65, 70, 1.05, 1.10, 0.95), " +
  2210. "('AB', '理性冷静、适应力强,但优柔寡断', 75, 80, 1.00, 0.95, 1.10), " +
  2211. "('O', '自信果断、行动力强,但固执己见', 60, 65, 1.15, 1.15, 1.15)");
  2212. log.info("血型配置种子数据已初始化");
  2213. }
  2214. } catch (Exception e) {
  2215. log.warn("初始化血型配置失败: {}", e.getMessage());
  2216. }
  2217. // 初始化成长规划师模块相关表
  2218. initGuideModuleTables(jdbcTemplate);
  2219. }
  2220. private void initGuideModuleTables(JdbcTemplate jdbcTemplate) {
  2221. // 服务类型表
  2222. try {
  2223. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_types (" +
  2224. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2225. "name VARCHAR(100) NOT NULL COMMENT '类型名称', " +
  2226. "icon VARCHAR(50) COMMENT '图标', " +
  2227. "description VARCHAR(500) COMMENT '描述', " +
  2228. "sort_order INT DEFAULT 0 COMMENT '排序', " +
  2229. "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
  2230. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2231. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2232. ")");
  2233. } catch (Exception e) {
  2234. log.warn("创建service_types表失败: {}", e.getMessage());
  2235. }
  2236. // 服务内容表
  2237. try {
  2238. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS service_contents (" +
  2239. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2240. "type_id BIGINT NOT NULL COMMENT '关联服务类型', " +
  2241. "title VARCHAR(200) NOT NULL COMMENT '内容标题', " +
  2242. "content TEXT COMMENT '富文本内容', " +
  2243. "duration INT COMMENT '建议时长(分钟)', " +
  2244. "age_range VARCHAR(50) COMMENT '适用年龄', " +
  2245. "price INT COMMENT '参考价格(分)', " +
  2246. "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/ACTIVE', " +
  2247. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2248. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2249. ")");
  2250. } catch (Exception e) {
  2251. log.warn("创建service_contents表失败: {}", e.getMessage());
  2252. }
  2253. // 套餐模板表
  2254. try {
  2255. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_templates (" +
  2256. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2257. "name VARCHAR(100) NOT NULL COMMENT '模板名称', " +
  2258. "validity_months INT COMMENT '有效期(月)', " +
  2259. "reference_price INT COMMENT '参考价格(分)', " +
  2260. "min_price INT COMMENT '最低售价(分)', " +
  2261. "description VARCHAR(500) COMMENT '说明', " +
  2262. "status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态:ACTIVE/DISABLED', " +
  2263. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2264. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2265. ")");
  2266. } catch (Exception e) {
  2267. log.warn("创建guide_package_templates表失败: {}", e.getMessage());
  2268. }
  2269. // 套餐模板服务项表
  2270. try {
  2271. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS package_template_services (" +
  2272. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2273. "template_id BIGINT NOT NULL COMMENT '关联模板', " +
  2274. "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
  2275. "quantity INT DEFAULT 1 COMMENT '数量', " +
  2276. "unit VARCHAR(20) COMMENT '单位' " +
  2277. ")");
  2278. } catch (Exception e) {
  2279. log.warn("创建package_template_services表失败: {}", e.getMessage());
  2280. }
  2281. // 成长规划师套餐表
  2282. try {
  2283. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_packages (" +
  2284. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2285. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  2286. "template_id BIGINT COMMENT '关联模板', " +
  2287. "name VARCHAR(100) NOT NULL COMMENT '套餐名称', " +
  2288. "price INT NOT NULL COMMENT '销售价格(分)', " +
  2289. "validity_months INT COMMENT '有效期(月)', " +
  2290. "description VARCHAR(500) COMMENT '套餐说明', " +
  2291. "status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态:DRAFT/PUBLISHED/PAUSED', " +
  2292. "sales_count INT DEFAULT 0 COMMENT '销量', " +
  2293. "share_code VARCHAR(50) COMMENT '分享码', " +
  2294. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2295. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2296. ")");
  2297. } catch (Exception e) {
  2298. log.warn("创建guide_packages表失败: {}", e.getMessage());
  2299. }
  2300. // 迁移:guide_packages 表添加缺失列
  2301. try {
  2302. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID' AFTER id");
  2303. } catch (Exception e) {
  2304. }
  2305. try {
  2306. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN package_id BIGINT COMMENT '关联模板' AFTER guide_id");
  2307. } catch (Exception e) {
  2308. }
  2309. try {
  2310. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN commission_rate INT COMMENT '佣金比例(bps,100=1%)' AFTER package_id");
  2311. } catch (Exception e) {
  2312. }
  2313. try {
  2314. jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN teacher_id BIGINT NULL");
  2315. } catch (Exception e) {
  2316. }
  2317. try {
  2318. jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN template_id BIGINT NULL");
  2319. } catch (Exception e) {
  2320. }
  2321. // 迁移:guide_packages 表添加 level 列(GuidePackage 实体需要)
  2322. try {
  2323. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN level VARCHAR(20) COMMENT '套餐等级' AFTER name");
  2324. } catch (Exception e) {
  2325. }
  2326. // 迁移:guide_packages 表添加 validity_days 列
  2327. try {
  2328. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN validity_days INT DEFAULT 0 COMMENT '有效期天数' AFTER validity_months");
  2329. } catch (Exception e) {
  2330. }
  2331. // 迁移:guide_packages 表添加 sort_order 列
  2332. try {
  2333. jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序' AFTER status");
  2334. } catch (Exception e) {
  2335. }
  2336. // 成长规划师套餐服务项表
  2337. try {
  2338. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_package_services (" +
  2339. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2340. "package_id BIGINT NOT NULL COMMENT '关联套餐', " +
  2341. "content_id BIGINT NOT NULL COMMENT '关联服务内容', " +
  2342. "quantity INT DEFAULT 1 COMMENT '数量', " +
  2343. "unit VARCHAR(20) COMMENT '单位' " +
  2344. ")");
  2345. } catch (Exception e) {
  2346. log.warn("创建guide_package_services表失败: {}", e.getMessage());
  2347. }
  2348. // 订单表
  2349. try {
  2350. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_orders (" +
  2351. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2352. "order_no VARCHAR(50) NOT NULL COMMENT '订单号', " +
  2353. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  2354. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  2355. "package_id BIGINT NOT NULL COMMENT '套餐ID', " +
  2356. "amount INT NOT NULL COMMENT '金额(分)', " +
  2357. "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/PAID/COMPLETED/REFUNDED', " +
  2358. "pay_time DATETIME COMMENT '支付时间', " +
  2359. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2360. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2361. ")");
  2362. } catch (Exception e) {
  2363. log.warn("创建guide_orders表失败: {}", e.getMessage());
  2364. }
  2365. // 佣金明细表
  2366. try {
  2367. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_commissions (" +
  2368. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2369. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  2370. "order_id BIGINT NOT NULL COMMENT '订单ID', " +
  2371. "amount INT NOT NULL COMMENT '佣金金额(分)', " +
  2372. "rate INT COMMENT '佣金比例(bps,100=1%)', " +
  2373. "status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态:PENDING/COMPLETED', " +
  2374. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  2375. ")");
  2376. } catch (Exception e) {
  2377. log.warn("创建guide_commissions表失败: {}", e.getMessage());
  2378. }
  2379. // 服务记录表
  2380. try {
  2381. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_service_logs (" +
  2382. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2383. "teacher_id BIGINT NOT NULL COMMENT '成长规划师ID', " +
  2384. "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
  2385. "order_id BIGINT COMMENT '订单ID', " +
  2386. "service_content_id BIGINT NOT NULL COMMENT '服务内容ID', " +
  2387. "notes VARCHAR(500) COMMENT '服务记录', " +
  2388. "duration INT COMMENT '服务时长(分钟)', " +
  2389. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
  2390. ")");
  2391. } catch (Exception e) {
  2392. log.warn("创建guide_service_logs表失败: {}", e.getMessage());
  2393. }
  2394. // 成长规划师邀请码表
  2395. try {
  2396. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS guide_invite_codes (" +
  2397. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2398. "guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID', " +
  2399. "code VARCHAR(50) NOT NULL UNIQUE COMMENT '邀请码', " +
  2400. "status VARCHAR(20) DEFAULT 'active' COMMENT '状态', " +
  2401. "used_by_user_id BIGINT COMMENT '使用该邀请码的用户ID', " +
  2402. "used_at DATETIME COMMENT '使用时间', " +
  2403. "expire_at DATETIME COMMENT '过期时间', " +
  2404. "use_count INT DEFAULT 0 COMMENT '使用次数', " +
  2405. "max_use_count INT COMMENT '最大使用次数', " +
  2406. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2407. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP" +
  2408. ")");
  2409. } catch (Exception e) {
  2410. log.warn("创建guide_invite_codes表失败: {}", e.getMessage());
  2411. }
  2412. // 迁移29: assessment_orders表添加appointment_id字段
  2413. try {
  2414. jdbcTemplate.execute("ALTER TABLE assessment_orders ADD COLUMN appointment_id BIGINT COMMENT '关联的预约ID'");
  2415. log.info("已添加appointment_id列到assessment_orders表");
  2416. } catch (Exception e) {
  2417. // 列已存在,忽略错误
  2418. }
  2419. // 财富维度: 财商打卡记录表
  2420. try {
  2421. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS finance_checkins (" +
  2422. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2423. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  2424. "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
  2425. "checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review', " +
  2426. "checkin_date DATE NOT NULL COMMENT '打卡日期', " +
  2427. "amount INT COMMENT '金额(分,收支记录)', " +
  2428. "category VARCHAR(50) COMMENT '收支分类', " +
  2429. "note VARCHAR(200) COMMENT '备注', " +
  2430. "mood VARCHAR(20) COMMENT '心情', " +
  2431. "earned_energy INT DEFAULT 0 COMMENT '获得的能量值', " +
  2432. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2433. "INDEX idx_user_date (user_id, checkin_date), " +
  2434. "INDEX idx_child (child_id)" +
  2435. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财商打卡记录表'");
  2436. log.info("已创建finance_checkins表");
  2437. } catch (Exception e) {
  2438. log.warn("创建finance_checkins表失败: {}", e.getMessage());
  2439. }
  2440. // 财富维度: 保单管理表
  2441. try {
  2442. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS insurance_policies (" +
  2443. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2444. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  2445. "child_id BIGINT COMMENT '关联孩子ID', " +
  2446. "policy_name VARCHAR(100) NOT NULL COMMENT '保单名称', " +
  2447. "insurance_company VARCHAR(100) COMMENT '保险公司', " +
  2448. "policy_type VARCHAR(50) COMMENT '保单类型: medical/life/accident/education/endowment', " +
  2449. "insured_person VARCHAR(50) NOT NULL COMMENT '被保人姓名', " +
  2450. "premium INT COMMENT '保费(分)', " +
  2451. "sum_insured INT COMMENT '保额(分)', " +
  2452. "start_date DATE COMMENT '生效日期', " +
  2453. "end_date DATE COMMENT '到期日期', " +
  2454. "renewal_type VARCHAR(20) DEFAULT 'annual' COMMENT '续费类型: annual/monthly/single', " +
  2455. "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired/cancelled', " +
  2456. "policy_no VARCHAR(100) COMMENT '保单号', " +
  2457. "remark VARCHAR(500) COMMENT '备注', " +
  2458. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2459. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  2460. "INDEX idx_user (user_id), " +
  2461. "INDEX idx_child (child_id), " +
  2462. "INDEX idx_status (status)" +
  2463. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单管理表'");
  2464. log.info("已创建insurance_policies表");
  2465. } catch (Exception e) {
  2466. log.warn("创建insurance_policies表失败: {}", e.getMessage());
  2467. }
  2468. // ==================== 健康检测报告模块 ====================
  2469. // 健康报告表(体检报告/菌群检测报告)
  2470. try {
  2471. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_reports (" +
  2472. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2473. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  2474. "family_id BIGINT COMMENT '家庭ID', " +
  2475. "report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)', " +
  2476. "report_date DATE COMMENT '报告日期', " +
  2477. "overall_score INT COMMENT '健康总分(0-100)', " +
  2478. "gut_health_score INT COMMENT '菌群健康分', " +
  2479. "chronic_disease_score INT COMMENT '慢病控制分', " +
  2480. "nutrition_score INT COMMENT '营养均衡分', " +
  2481. "gut_age VARCHAR(32) COMMENT '肠道预测年龄', " +
  2482. "gut_type VARCHAR(50) COMMENT '肠型', " +
  2483. "file_url VARCHAR(500) COMMENT '报告文件URL', " +
  2484. "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived', " +
  2485. "remark VARCHAR(500) COMMENT '备注', " +
  2486. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2487. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
  2488. "INDEX idx_user_id (user_id), " +
  2489. "INDEX idx_family_id (family_id), " +
  2490. "INDEX idx_report_type (report_type)" +
  2491. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康检测报告表'");
  2492. log.info("已创建health_reports表");
  2493. } catch (Exception e) {
  2494. log.warn("创建health_reports表可能已存在: {}", e.getMessage());
  2495. }
  2496. // 健康指标明细表
  2497. try {
  2498. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_indicators (" +
  2499. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2500. "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
  2501. "category VARCHAR(50) COMMENT '指标分类: 营养状况/疾病风险/肠道屏障/神经递质/...', " +
  2502. "indicator_name VARCHAR(100) NOT NULL COMMENT '指标名称', " +
  2503. "indicator_value VARCHAR(100) COMMENT '检测值', " +
  2504. "unit VARCHAR(50) COMMENT '单位', " +
  2505. "ref_range VARCHAR(200) COMMENT '参考范围', " +
  2506. "status VARCHAR(50) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出', " +
  2507. "sort_order INT DEFAULT 0 COMMENT '显示排序', " +
  2508. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2509. "INDEX idx_report_id (report_id), " +
  2510. "INDEX idx_category (category)" +
  2511. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标明细表'");
  2512. log.info("已创建health_indicators表");
  2513. } catch (Exception e) {
  2514. log.warn("创建health_indicators表可能已存在: {}", e.getMessage());
  2515. }
  2516. // ---------- 健康报告表 新增菌群摘要字段 ----------
  2517. try {
  2518. jdbcTemplate.execute("ALTER TABLE health_reports " +
  2519. "ADD COLUMN gut_balance_score INT COMMENT '菌群平衡分(0-100)', " +
  2520. "ADD COLUMN gut_diversity_score INT COMMENT '菌群多样性分(0-100)', " +
  2521. "ADD COLUMN beneficial_bacteria_score INT COMMENT '有益菌分(0-100)', " +
  2522. "ADD COLUMN harmful_bacteria_score INT COMMENT '有害菌分(0-100)', " +
  2523. "ADD COLUMN core_species_score INT COMMENT '核心菌属分(0-100)', " +
  2524. "ADD COLUMN health_rating VARCHAR(100) COMMENT '总分评级,如亚健康/优秀等'");
  2525. log.info("已为health_reports表添加6个菌群摘要字段");
  2526. } catch (Exception e) {
  2527. log.warn("为health_reports表添加字段可能已存在: {}", e.getMessage());
  2528. }
  2529. // ---------- 健康指标表 新增symptoms字段 ----------
  2530. try {
  2531. jdbcTemplate.execute("ALTER TABLE health_indicators ADD COLUMN symptoms TEXT COMMENT '过量/缺乏相关症状描述'");
  2532. log.info("已为health_indicators表添加symptoms字段");
  2533. } catch (Exception e) {
  2534. log.warn("为health_indicators表添加symptoms字段可能已存在: {}", e.getMessage());
  2535. }
  2536. log.info("成长规划师模块、财富维度及健康检测报告数据库表初始化完成");
  2537. // 健康打卡表
  2538. try {
  2539. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_checkins (" +
  2540. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2541. "user_id BIGINT COMMENT '用户ID(家长)', " +
  2542. "child_id BIGINT COMMENT '孩子ID', " +
  2543. "checkin_date DATE COMMENT '打卡日期', " +
  2544. "behaviors TEXT COMMENT '打卡行为JSON数组', " +
  2545. "note VARCHAR(500) COMMENT '备注', " +
  2546. "mood VARCHAR(20) COMMENT '心情: happy/normal/sad/angry/sick', " +
  2547. "earned_energy INT DEFAULT 5 COMMENT '获得能量值', " +
  2548. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2549. "INDEX idx_child_id (child_id), " +
  2550. "INDEX idx_checkin_date (checkin_date)" +
  2551. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康打卡表'");
  2552. log.info("已创建health_checkins表");
  2553. } catch (Exception e) {
  2554. log.warn("创建health_checkins表可能已存在: {}", e.getMessage());
  2555. }
  2556. // 迁移31: 创建联系人表
  2557. try {
  2558. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS contacts (" +
  2559. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2560. "user_id BIGINT NOT NULL COMMENT '用户ID', " +
  2561. "name VARCHAR(100) NOT NULL COMMENT '姓名', " +
  2562. "phone VARCHAR(20) COMMENT '电话', " +
  2563. "avatar VARCHAR(500) COMMENT '头像', " +
  2564. "relationship_type VARCHAR(20) COMMENT '关系类型: family/friend/partner/colleague/other', " +
  2565. "known_since DATETIME COMMENT '认识时间', " +
  2566. "bio VARCHAR(500) COMMENT '简介', " +
  2567. "birthday DATE COMMENT '生日', " +
  2568. "intimacy_level INT DEFAULT 0 COMMENT '亲密度(0-100)', " +
  2569. "contact_source VARCHAR(20) DEFAULT 'manual' COMMENT '来源: phone/manual', " +
  2570. "tags VARCHAR(500) COMMENT '标签(逗号分隔)', " +
  2571. "last_contact_at DATETIME COMMENT '最近联系时间', " +
  2572. "contact_count INT DEFAULT 0 COMMENT '联系次数', " +
  2573. "notes TEXT COMMENT '备注', " +
  2574. "created_at DATETIME, " +
  2575. "updated_at DATETIME, " +
  2576. "INDEX idx_user_id (user_id)" +
  2577. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='联系人表'");
  2578. log.info("已创建contacts联系表");
  2579. } catch (Exception e) {
  2580. log.warn("创建contacts表可能已存在: {}", e.getMessage());
  2581. }
  2582. // 创建文章阅读记录表
  2583. try {
  2584. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS article_reading_records (" +
  2585. "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
  2586. "user_id BIGINT COMMENT '用户ID', " +
  2587. "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
  2588. "content VARCHAR(500) COMMENT '阅读内容摘要', " +
  2589. "duration_seconds INT DEFAULT 0 COMMENT '阅读时长(秒)', " +
  2590. "read_at DATETIME COMMENT '阅读时间', " +
  2591. "created_at DATETIME, " +
  2592. "INDEX idx_child_id (child_id), " +
  2593. "INDEX idx_read_at (read_at)" +
  2594. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章阅读记录表'");
  2595. log.info("已创建article_reading_records表");
  2596. } catch (Exception e) {
  2597. log.warn("创建article_reading_records表可能已存在: {}", e.getMessage());
  2598. }
  2599. // 迁移30: activities表添加visibility和visible_to字段
  2600. try {
  2601. jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN visibility VARCHAR(20) DEFAULT 'all' COMMENT '可见性: all/member'");
  2602. log.info("已添加visibility列到activities表");
  2603. } catch (Exception e) {
  2604. // 列已存在,忽略错误
  2605. }
  2606. try {
  2607. jdbcTemplate.execute("ALTER TABLE activities ADD COLUMN visible_to VARCHAR(255) COMMENT '可见角色列表(逗号分隔)'");
  2608. log.info("已添加visible_to列到activities表");
  2609. } catch (Exception e) {
  2610. // 列已存在,忽略错误
  2611. }
  2612. // 创建维度配置表
  2613. try {
  2614. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_dimension_config (" +
  2615. "id BIGINT PRIMARY KEY AUTO_INCREMENT, " +
  2616. "dimension_code VARCHAR(50) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth', " +
  2617. "dimension_name VARCHAR(100) NOT NULL COMMENT '维度名称', " +
  2618. "dimension_type VARCHAR(50) NOT NULL DEFAULT 'five' COMMENT '类型: five(五维)/action(行动)/other', " +
  2619. "parent_id BIGINT COMMENT '父级ID,无父级为NULL', " +
  2620. "sort INT DEFAULT 0 COMMENT '排序', " +
  2621. "enabled TINYINT(1) DEFAULT 1 COMMENT '启用: 0禁用 1启用', " +
  2622. "dify_dataset_id VARCHAR(100) COMMENT 'Dify Dataset ID,不填则不同步', " +
  2623. "remark VARCHAR(500) COMMENT '备注', " +
  2624. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2625. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP " +
  2626. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度配置表'");
  2627. log.info("已创建product_dimension_config表");
  2628. } catch (Exception e) {
  2629. log.warn("创建product_dimension_config表可能已存在: {}", e.getMessage());
  2630. }
  2631. // 创建知识库表
  2632. try {
  2633. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dan_knowledge_base (" +
  2634. "id BIGINT PRIMARY KEY AUTO_INCREMENT, " +
  2635. "title VARCHAR(200) NOT NULL COMMENT '知识标题', " +
  2636. "content TEXT NOT NULL COMMENT '知识内容', " +
  2637. "sort INT DEFAULT 0 COMMENT '排序', " +
  2638. "status TINYINT(1) DEFAULT 1 COMMENT '状态: 0禁用 1启用', " +
  2639. "remark VARCHAR(500) COMMENT '备注', " +
  2640. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2641. "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP " +
  2642. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识库表'");
  2643. log.info("已创建dan_knowledge_base表");
  2644. } catch (Exception e) {
  2645. log.warn("创建dan_knowledge_base表可能已存在: {}", e.getMessage());
  2646. }
  2647. // 创建标签表
  2648. try {
  2649. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dan_knowledge_tag (" +
  2650. "id BIGINT PRIMARY KEY AUTO_INCREMENT, " +
  2651. "tag_name VARCHAR(100) NOT NULL UNIQUE COMMENT '标签名', " +
  2652. "tag_type VARCHAR(50) DEFAULT 'custom' COMMENT '标签类型: custom/auto', " +
  2653. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP " +
  2654. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='标签表'");
  2655. log.info("已创建dan_knowledge_tag表");
  2656. } catch (Exception e) {
  2657. log.warn("创建dan_knowledge_tag表可能已存在: {}", e.getMessage());
  2658. }
  2659. // 创建知识-维度关联表
  2660. try {
  2661. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dan_knowledge_base_dimension (" +
  2662. "id BIGINT PRIMARY KEY AUTO_INCREMENT, " +
  2663. "knowledge_id BIGINT NOT NULL COMMENT '知识库ID', " +
  2664. "dimension_id BIGINT NOT NULL COMMENT '维度配置ID', " +
  2665. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2666. "UNIQUE KEY uk_knowledge_dimension (knowledge_id, dimension_id) " +
  2667. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识-维度关联表'");
  2668. log.info("已创建dan_knowledge_base_dimension表");
  2669. // 添加外键约束(单独执行避免CREATE TABLE时依赖表不存在)
  2670. jdbcTemplate.execute("ALTER TABLE dan_knowledge_base_dimension ADD CONSTRAINT fk_kd_knowledge FOREIGN KEY (knowledge_id) REFERENCES dan_knowledge_base(id) ON DELETE CASCADE");
  2671. jdbcTemplate.execute("ALTER TABLE dan_knowledge_base_dimension ADD CONSTRAINT fk_kd_dimension FOREIGN KEY (dimension_id) REFERENCES product_dimension_config(id) ON DELETE CASCADE");
  2672. log.info("已添加dan_knowledge_base_dimension外键约束");
  2673. } catch (Exception e) {
  2674. log.warn("创建dan_knowledge_base_dimension表可能已存在: {}", e.getMessage());
  2675. }
  2676. // 创建知识-标签关联表
  2677. try {
  2678. jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS dan_knowledge_base_tag (" +
  2679. "id BIGINT PRIMARY KEY AUTO_INCREMENT, " +
  2680. "knowledge_id BIGINT NOT NULL, " +
  2681. "tag_id BIGINT NOT NULL, " +
  2682. "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
  2683. "UNIQUE KEY uk_knowledge_tag (knowledge_id, tag_id) " +
  2684. ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知识-标签关联表'");
  2685. log.info("已创建dan_knowledge_base_tag表");
  2686. // 添加外键约束
  2687. jdbcTemplate.execute("ALTER TABLE dan_knowledge_base_tag ADD CONSTRAINT fk_kt_knowledge FOREIGN KEY (knowledge_id) REFERENCES dan_knowledge_base(id) ON DELETE CASCADE");
  2688. jdbcTemplate.execute("ALTER TABLE dan_knowledge_base_tag ADD CONSTRAINT fk_kt_tag FOREIGN KEY (tag_id) REFERENCES dan_knowledge_tag(id) ON DELETE CASCADE");
  2689. log.info("已添加dan_knowledge_base_tag外键约束");
  2690. } catch (Exception e) {
  2691. log.warn("创建dan_knowledge_base_tag表可能已存在: {}", e.getMessage());
  2692. }
  2693. }
  2694. private void insertSysConfigSeed(String key, String value, String desc) {
  2695. Integer count = jdbcTemplate.queryForObject(
  2696. "SELECT COUNT(*) FROM sys_config WHERE config_key = ?", Integer.class, key);
  2697. if (count == null || count == 0) {
  2698. jdbcTemplate.update(
  2699. "INSERT INTO sys_config (config_key, config_value, description, updated_at) VALUES (?, ?, ?, NOW())",
  2700. key, value, desc);
  2701. }
  2702. }
  2703. private void initFoodTables() {
  2704. log.info("开始初始化食品相关表...");
  2705. // 4张新表 (CREATE TABLE IF NOT EXISTS)
  2706. String[] ddl = {
  2707. "CREATE TABLE IF NOT EXISTS foods ("
  2708. + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
  2709. + "name VARCHAR(100) NOT NULL COMMENT '食材名称', "
  2710. + "category VARCHAR(50) COMMENT '分类: 蔬菜/水果/肉禽/水产/蛋奶/豆类/谷物/调味/其他', "
  2711. + "unit VARCHAR(20) DEFAULT 'g' COMMENT '计量单位', "
  2712. + "calories DECIMAL(8,2) COMMENT '热量(kcal/100g)', "
  2713. + "protein DECIMAL(8,2) COMMENT '蛋白质(g/100g)', "
  2714. + "fat DECIMAL(8,2) COMMENT '脂肪(g/100g)', "
  2715. + "carbs DECIMAL(8,2) COMMENT '碳水(g/100g)', "
  2716. + "fiber DECIMAL(8,2) COMMENT '膳食纤维(g/100g)', "
  2717. + "price_level TINYINT COMMENT '价格等级 1-5', "
  2718. + "nutrition_tags VARCHAR(500) COMMENT '营养标签JSON', "
  2719. + "suitable_for VARCHAR(200) COMMENT '适宜人群JSON', "
  2720. + "allergens VARCHAR(200) COMMENT '过敏原JSON', "
  2721. + "external_source VARCHAR(100) COMMENT '数据来源', "
  2722. + "external_id VARCHAR(100) COMMENT '外部ID', "
  2723. + "cover_image VARCHAR(500) COMMENT '食材图片', "
  2724. + "status VARCHAR(20) DEFAULT 'active', "
  2725. + "sort_order INT DEFAULT 0, "
  2726. + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
  2727. + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
  2728. + "INDEX idx_category (category), "
  2729. + "INDEX idx_price (price_level)"
  2730. + ");",
  2731. "CREATE TABLE IF NOT EXISTS recipes ("
  2732. + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
  2733. + "name VARCHAR(200) NOT NULL COMMENT '食谱名称', "
  2734. + "meal_type VARCHAR(20) COMMENT '早餐/午餐/晚餐/加餐', "
  2735. + "description TEXT COMMENT '简介', "
  2736. + "ingredients TEXT NOT NULL COMMENT '食材清单JSON', "
  2737. + "steps TEXT COMMENT '烹饪步骤JSON', "
  2738. + "cook_time INT COMMENT '烹饪时间(分钟)', "
  2739. + "difficulty VARCHAR(20) COMMENT '难度: 简单/中等/困难', "
  2740. + "total_calories DECIMAL(8,2) COMMENT '总热量', "
  2741. + "total_protein DECIMAL(8,2) COMMENT '总蛋白质', "
  2742. + "total_fat DECIMAL(8,2) COMMENT '总脂肪', "
  2743. + "total_carbs DECIMAL(8,2) COMMENT '总碳水', "
  2744. + "total_fiber DECIMAL(8,2) COMMENT '总膳食纤维', "
  2745. + "suitable_for VARCHAR(200) COMMENT '适宜场景', "
  2746. + "nutrition_tags VARCHAR(500) COMMENT '营养标签', "
  2747. + "external_source VARCHAR(100), "
  2748. + "external_id VARCHAR(100), "
  2749. + "cover_image VARCHAR(500), "
  2750. + "status VARCHAR(20) DEFAULT 'active', "
  2751. + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
  2752. + "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "
  2753. + "INDEX idx_meal_type (meal_type), "
  2754. + "INDEX idx_cook_time (cook_time)"
  2755. + ");",
  2756. "CREATE TABLE IF NOT EXISTS seasonal_foods ("
  2757. + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
  2758. + "food_id BIGINT NOT NULL COMMENT '关联foods.id', "
  2759. + "month TINYINT NOT NULL COMMENT '月份 1-12', "
  2760. + "region VARCHAR(50) DEFAULT '全国' COMMENT '产地', "
  2761. + "peak_flag TINYINT DEFAULT 0 COMMENT '盛产标志', "
  2762. + "sort_order INT DEFAULT 0, "
  2763. + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
  2764. + "INDEX idx_food_month (food_id, month), "
  2765. + "INDEX idx_month (month)"
  2766. + ");",
  2767. "CREATE TABLE IF NOT EXISTS meal_logs ("
  2768. + "id BIGINT AUTO_INCREMENT PRIMARY KEY, "
  2769. + "user_id BIGINT NOT NULL COMMENT '用户ID', "
  2770. + "child_id BIGINT COMMENT '孩子ID', "
  2771. + "meal_type VARCHAR(20) NOT NULL COMMENT '早餐/午餐/晚餐/加餐', "
  2772. + "meal_date DATE NOT NULL COMMENT '用餐日期', "
  2773. + "meal_time DATETIME COMMENT '具体时间', "
  2774. + "foods TEXT NOT NULL COMMENT '食物清单JSON', "
  2775. + "total_calories DECIMAL(8,2) COMMENT '总热量', "
  2776. + "total_protein DECIMAL(8,2) COMMENT '总蛋白质', "
  2777. + "total_fat DECIMAL(8,2) COMMENT '总脂肪', "
  2778. + "total_carbs DECIMAL(8,2) COMMENT '总碳水', "
  2779. + "total_fiber DECIMAL(8,2) COMMENT '总膳食纤维', "
  2780. + "note TEXT COMMENT '备注', "
  2781. + "images VARCHAR(1000) COMMENT '图片JSON', "
  2782. + "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, "
  2783. + "INDEX idx_user_date (user_id, meal_date), "
  2784. + "INDEX idx_child_date (child_id, meal_date)"
  2785. + ");"
  2786. };
  2787. for (String sql : ddl) {
  2788. try {
  2789. jdbcTemplate.execute(sql);
  2790. } catch (Exception e) {
  2791. log.warn("食品表DDL执行失败(可能已存在): {}", e.getMessage());
  2792. }
  2793. }
  2794. // 扩展 user_nutrition_profile(如果表不存在则跳过)
  2795. ensureColumn("user_nutrition_profile", "budget_monthly", "INT COMMENT '月食品预算(元)'");
  2796. ensureColumn("user_nutrition_profile", "family_taste_preferences", "TEXT COMMENT '家庭成员口味偏好JSON'");
  2797. ensureColumn("user_nutrition_profile", "cuisine_style", "VARCHAR(100) COMMENT '偏好菜系'");
  2798. ensureColumn("user_nutrition_profile", "meal_count", "INT DEFAULT 3 COMMENT '每日餐数'");
  2799. ensureColumn("user_nutrition_profile", "cooking_ability", "VARCHAR(20) DEFAULT 'simple' COMMENT '烹饪能力: simple/medium/advanced'");
  2800. log.info("食品相关表初始化完成");
  2801. }
  2802. private void ensureColumn(String table, String column, String definition) {
  2803. try {
  2804. jdbcTemplate.execute("ALTER TABLE " + table + " ADD COLUMN " + column + " " + definition);
  2805. log.info("已添加列 {}.{}", table, column);
  2806. } catch (Exception e) {
  2807. if (e.getMessage() != null && e.getMessage().contains("Duplicate column")) {
  2808. log.info("列 {}.{} 已存在,跳过", table, column);
  2809. } else {
  2810. log.warn("添加列 {}.{} 失败: {}", table, column, e.getMessage());
  2811. }
  2812. }
  2813. }
  2814. private void insertProductSeed(String name, String desc, String type,
  2815. Integer price,
  2816. String domain, String memberEligible) {
  2817. Integer count = jdbcTemplate.queryForObject(
  2818. "SELECT COUNT(*) FROM products WHERE name = ? AND product_type = ?", Integer.class, name, type);
  2819. if (count == null || count == 0) {
  2820. jdbcTemplate.update(
  2821. "INSERT INTO products (name, description, price, product_type, vendor_id, vendor_name, status, domain, member_eligible, created_at, updated_at) " +
  2822. "VALUES (?, ?, ?, ?, NULL, '平台官方', 'on_shelf', ?, ?, NOW(), NOW())",
  2823. name, desc, price, type, domain, memberEligible);
  2824. }
  2825. }
  2826. private void insertContentSectionSeed(String pageKey, String sectionKey, String title,
  2827. String allowedRoles, int sortOrder) {
  2828. Integer count = jdbcTemplate.queryForObject(
  2829. "SELECT COUNT(*) FROM content_sections WHERE page_key = ? AND section_key = ?",
  2830. Integer.class, pageKey, sectionKey);
  2831. if (count == null || count == 0) {
  2832. jdbcTemplate.update(
  2833. "INSERT INTO content_sections (page_key, section_key, title, allowed_roles, sort_order, status, created_at, updated_at) " +
  2834. "VALUES (?, ?, ?, ?, ?, 'active', NOW(), NOW())",
  2835. pageKey, sectionKey, title, allowedRoles, sortOrder);
  2836. }
  2837. }
  2838. }