auto_update_php_model_relationships.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. const db = require('../util/db');
  2. const fs = require('fs');
  3. const path = require('path');
  4. // 将字符串转换为驼峰命名
  5. function toCamelCase(str) {
  6. return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase());
  7. }
  8. // 根据关系类型获取对应的方法名
  9. function getRelationshipMethod(type) {
  10. const typeMap = {
  11. 'HAS_MANY': 'hasMany',
  12. 'BELONGS_TO': 'belongsTo',
  13. 'HAS_ONE': 'hasOne',
  14. 'BELONGS_TO_MANY': 'belongsToMany',
  15. 'MANY_TO_MANY': 'belongsToMany',
  16. 'MANY_TO_MANY_INVERSE': 'belongsToMany',
  17. 'MORPH_ONE': 'morphOne',
  18. 'MORPH_MANY': 'morphMany',
  19. 'MORPH_TO': 'morphTo',
  20. 'MORPH_TO_MANY': 'morphToMany',
  21. 'MORPHED_BY_MANY': 'morphedByMany',
  22. 'HAS_MANY_THROUGH': 'hasManyThrough',
  23. 'HAS_ONE_THROUGH': 'hasOneThrough',
  24. 'HAS_ONE_OF_MANY': 'hasOneOfMany',
  25. 'MORPH_TO_ONE': 'morphToOne',
  26. 'HAS_MANY_THROUGH_MANY': 'hasManyThroughMany'
  27. };
  28. return typeMap[type] || type;
  29. }
  30. // 生成关系方法的代码
  31. function generateMethodCode(data) {
  32. const {title, type, remark, args} = data;
  33. const argsObj = typeof args === 'string' ? JSON.parse(args) : args;
  34. const relationshipMethod = getRelationshipMethod(type);
  35. // 构建方法参数
  36. const methodArgs = [];
  37. // 基本参数处理
  38. if (argsObj.related) {
  39. // 只使用模型名称
  40. const modelName = argsObj.related.split('\\').pop();
  41. methodArgs.push(`${modelName}::class`);
  42. }
  43. // 多态关系参数处理
  44. if (relationshipMethod.includes('morph')) {
  45. if (relationshipMethod === 'morphTo') {
  46. // morphTo 方法的特殊处理
  47. if (argsObj.name) {
  48. methodArgs.push(`'${argsObj.name}'`);
  49. }
  50. if (argsObj.type && argsObj.id) {
  51. methodArgs.push(`'${argsObj.type}'`);
  52. methodArgs.push(`'${argsObj.id}'`);
  53. }
  54. // 支持 morphMap 类型数组
  55. if (argsObj.morphMap && Array.isArray(argsObj.morphMap)) {
  56. const morphMapStr = argsObj.morphMap.map(type => `'${type}'`).join(', ');
  57. methodArgs.push(`[${morphMapStr}]`);
  58. }
  59. } else if (relationshipMethod === 'morphToMany' || relationshipMethod === 'morphedByMany') {
  60. // morphToMany 和 morphedByMany 的特殊处理
  61. if (argsObj.name) {
  62. methodArgs.push(`'${argsObj.name}'`);
  63. }
  64. if (argsObj.table) {
  65. methodArgs.push(`'${argsObj.table}'`);
  66. }
  67. if (argsObj.foreignPivotKey) {
  68. methodArgs.push(`'${argsObj.foreignPivotKey}'`);
  69. }
  70. if (argsObj.relatedPivotKey) {
  71. methodArgs.push(`'${argsObj.relatedPivotKey}'`);
  72. }
  73. if (argsObj.parentKey) {
  74. methodArgs.push(`'${argsObj.parentKey}'`);
  75. }
  76. if (argsObj.relatedKey) {
  77. methodArgs.push(`'${argsObj.relatedKey}'`);
  78. }
  79. if (argsObj.morphType) {
  80. methodArgs.push(`'${argsObj.morphType}'`);
  81. }
  82. if (argsObj.morphClass) {
  83. // 只使用模型名称
  84. const morphClassName = argsObj.morphClass.split('\\').pop();
  85. methodArgs.push(`${morphClassName}::class`);
  86. }
  87. } else if (relationshipMethod === 'morphToOne') {
  88. // morphToOne 处理
  89. if (argsObj.name) {
  90. methodArgs.push(`'${argsObj.name}'`);
  91. }
  92. if (argsObj.type) {
  93. methodArgs.push(`'${argsObj.type}'`);
  94. }
  95. if (argsObj.id) {
  96. methodArgs.push(`'${argsObj.id}'`);
  97. }
  98. if (argsObj.morphClass) {
  99. const morphClassName = argsObj.morphClass.split('\\').pop();
  100. methodArgs.push(`${morphClassName}::class`);
  101. }
  102. if (argsObj.localKey) {
  103. methodArgs.push(`'${argsObj.localKey}'`);
  104. }
  105. } else {
  106. // morphOne 和 morphMany 处理
  107. if (argsObj.name) {
  108. methodArgs.push(`'${argsObj.name}'`);
  109. }
  110. if (argsObj.type) {
  111. methodArgs.push(`'${argsObj.type}'`);
  112. }
  113. if (argsObj.id) {
  114. methodArgs.push(`'${argsObj.id}'`);
  115. }
  116. if (argsObj.localKey) {
  117. methodArgs.push(`'${argsObj.localKey}'`);
  118. }
  119. if (argsObj.morphClass) {
  120. // 只使用模型名称
  121. const morphClassName = argsObj.morphClass.split('\\').pop();
  122. methodArgs.push(`${morphClassName}::class`);
  123. }
  124. }
  125. } else {
  126. // 普通关系参数处理
  127. if (argsObj.foreignKey) {
  128. methodArgs.push(`'${argsObj.foreignKey}'`);
  129. }
  130. if (argsObj.localKey) {
  131. methodArgs.push(`'${argsObj.localKey}'`);
  132. }
  133. // belongsTo 额外参数
  134. if (relationshipMethod === 'belongsTo' && argsObj.ownerKey) {
  135. methodArgs.push(`'${argsObj.ownerKey}'`);
  136. }
  137. // belongsToMany 额外参数
  138. if (relationshipMethod === 'belongsToMany') {
  139. if (argsObj.table) {
  140. methodArgs.push(`'${argsObj.table}'`);
  141. }
  142. if (argsObj.foreignPivotKey) {
  143. methodArgs.push(`'${argsObj.foreignPivotKey}'`);
  144. }
  145. if (argsObj.relatedPivotKey) {
  146. methodArgs.push(`'${argsObj.relatedPivotKey}'`);
  147. }
  148. if (argsObj.parentKey) {
  149. methodArgs.push(`'${argsObj.parentKey}'`);
  150. }
  151. if (argsObj.relatedKey) {
  152. methodArgs.push(`'${argsObj.relatedKey}'`);
  153. }
  154. }
  155. // through 关系额外参数
  156. if (relationshipMethod.includes('Through')) {
  157. if (argsObj.through) {
  158. // 只使用模型名称
  159. const throughModelName = argsObj.through.split('\\').pop();
  160. methodArgs.push(`${throughModelName}::class`);
  161. }
  162. if (argsObj.firstKey) {
  163. methodArgs.push(`'${argsObj.firstKey}'`);
  164. }
  165. if (argsObj.secondKey) {
  166. methodArgs.push(`'${argsObj.secondKey}'`);
  167. }
  168. if (argsObj.localKey) {
  169. methodArgs.push(`'${argsObj.localKey}'`);
  170. }
  171. if (argsObj.secondLocalKey) {
  172. methodArgs.push(`'${argsObj.secondLocalKey}'`);
  173. }
  174. if (argsObj.farKey) {
  175. methodArgs.push(`'${argsObj.farKey}'`);
  176. }
  177. }
  178. // hasManyThroughMany 特殊参数
  179. if (relationshipMethod === 'hasManyThroughMany') {
  180. if (argsObj.through) {
  181. const throughModelName = argsObj.through.split('\\').pop();
  182. methodArgs.push(`${throughModelName}::class`);
  183. }
  184. if (argsObj.firstKey) {
  185. methodArgs.push(`'${argsObj.firstKey}'`);
  186. }
  187. if (argsObj.secondKey) {
  188. methodArgs.push(`'${argsObj.secondKey}'`);
  189. }
  190. if (argsObj.localKey) {
  191. methodArgs.push(`'${argsObj.localKey}'`);
  192. }
  193. if (argsObj.secondLocalKey) {
  194. methodArgs.push(`'${argsObj.secondLocalKey}'`);
  195. }
  196. }
  197. // hasOneOfMany 特殊参数
  198. if (relationshipMethod === 'hasOneOfMany') {
  199. if (argsObj.aggregate) {
  200. methodArgs.push(`'${argsObj.aggregate}'`);
  201. }
  202. if (argsObj.column) {
  203. methodArgs.push(`'${argsObj.column}'`);
  204. }
  205. }
  206. }
  207. // 构建方法链
  208. let methodChain = relationshipMethod;
  209. // 软删除相关
  210. if (argsObj.withTrashed) {
  211. methodChain += '->withTrashed()';
  212. } else if (argsObj.onlyTrashed) {
  213. methodChain += '->onlyTrashed()';
  214. } else {
  215. methodChain += '';
  216. }
  217. // 生成方法代码
  218. return ` /**
  219. * @Author FelixYin
  220. * @description ${remark}
  221. */
  222. public function ${toCamelCase(title)}()
  223. {
  224. return $this->${methodChain}(${methodArgs.join(', ')});
  225. }`;
  226. }
  227. // 查找类的边界(开始和结束行)
  228. function findClassBoundaries(lines) {
  229. let classStartLine = -1;
  230. let classEndLine = -1;
  231. let braceCount = 0;
  232. let foundClass = false;
  233. for (let i = 0; i < lines.length; i++) {
  234. const line = lines[i];
  235. if (line.includes('class ')) {
  236. classStartLine = i;
  237. foundClass = true;
  238. }
  239. if (foundClass) {
  240. braceCount += (line.match(/{/g) || []).length;
  241. braceCount -= (line.match(/}/g) || []).length;
  242. if (braceCount === 0 && line.includes('}')) {
  243. classEndLine = i;
  244. break;
  245. }
  246. }
  247. }
  248. if (classStartLine === -1 || classEndLine === -1) {
  249. throw new Error('无效的PHP类格式');
  250. }
  251. return { start: classStartLine, end: classEndLine };
  252. }
  253. // 在类中查找特定方法
  254. function findMethodInClass(lines, methodName, classStart, classEnd) {
  255. let methodStartLine = -1;
  256. let methodEndLine = -1;
  257. let braceCount = 0;
  258. let inMethod = false;
  259. // 向上查找注释的起始位置
  260. function findCommentStart(startLine) {
  261. let line = startLine;
  262. while (line >= classStart) {
  263. if (!lines[line].trim().startsWith('*') && !lines[line].trim().startsWith('/*')) {
  264. return line + 1;
  265. }
  266. line--;
  267. }
  268. return startLine;
  269. }
  270. for (let i = classStart; i <= classEnd; i++) {
  271. const line = lines[i].trim();
  272. if (line.startsWith('public function') && line.includes(`function ${methodName}`)) {
  273. methodStartLine = findCommentStart(i - 1);
  274. inMethod = true;
  275. }
  276. if (inMethod) {
  277. braceCount += (line.match(/{/g) || []).length;
  278. braceCount -= (line.match(/}/g) || []).length;
  279. if (braceCount === 0 && line.includes('}')) {
  280. methodEndLine = i;
  281. break;
  282. }
  283. }
  284. }
  285. if (methodStartLine === -1 || methodEndLine === -1) {
  286. return null;
  287. }
  288. return { start: methodStartLine, end: methodEndLine };
  289. }
  290. // 更新模型文件
  291. async function updateModelFile(modelPath, methodCode, title) {
  292. try {
  293. // 检查文件是否存在
  294. if (!fs.existsSync(modelPath)) {
  295. console.error(`未找到模型文件: ${modelPath}`);
  296. return;
  297. }
  298. // 读取文件内容并按行分割
  299. let lines = fs.readFileSync(modelPath, 'utf8').split('\n');
  300. // 查找类的边界
  301. const classBoundaries = findClassBoundaries(lines);
  302. // 在类边界内查找现有方法
  303. const existingMethod = findMethodInClass(lines, toCamelCase(title), classBoundaries.start, classBoundaries.end);
  304. // 将新方法代码分割成行
  305. const newLines = methodCode.split('\n');
  306. if (existingMethod) {
  307. // 替换现有方法
  308. lines.splice(existingMethod.start, existingMethod.end - existingMethod.start + 1);
  309. // 确保方法前有一个空行
  310. if (existingMethod.start === 0 || lines[existingMethod.start - 1].trim() !== '') {
  311. lines.splice(existingMethod.start, 0, '', ...newLines);
  312. } else {
  313. lines.splice(existingMethod.start, 0, ...newLines);
  314. }
  315. } else {
  316. // 在类结束前添加新方法
  317. let insertPosition = classBoundaries.end;
  318. // 如果前一行不是空行,添加一个空行
  319. if (lines[insertPosition - 1].trim() !== '') {
  320. lines.splice(insertPosition, 0, '', ...newLines);
  321. } else {
  322. // 如果前一行是空行,直接添加方法
  323. lines.splice(insertPosition, 0, ...newLines);
  324. }
  325. }
  326. // 清理多余的空行
  327. let cleanedLines = [];
  328. let lastLineEmpty = false;
  329. for (let i = 0; i < lines.length; i++) {
  330. const line = lines[i].trim();
  331. const isEmpty = line === '';
  332. // 如果当前行不为空,或者(当前行为空但前一行不为空)
  333. if (!isEmpty || !lastLineEmpty) {
  334. cleanedLines.push(lines[i]);
  335. }
  336. lastLineEmpty = isEmpty;
  337. }
  338. // 将更新后的内容写回文件
  339. fs.writeFileSync(modelPath, cleanedLines.join('\n'), 'utf8');
  340. console.log(`已更新模型文件: ${modelPath}`);
  341. } catch (error) {
  342. console.error(`更新模型文件 ${modelPath} 时出错:`, error);
  343. }
  344. }
  345. // 主函数
  346. async function main() {
  347. try {
  348. // 查询关系数据
  349. const [rows] = await db.query('SELECT * FROM admin_relationships');
  350. console.log('找到的关系数量:', rows.length);
  351. // 遍历每个关系
  352. for (const relation of rows) {
  353. console.log('正在处理关系:', relation.model);
  354. // 将模型命名空间转换为文件路径
  355. const modelFile = relation.model
  356. .replace('App\\Models\\', '')
  357. .replace(/\\/g, '/')
  358. + '.php';
  359. // 构建完整的模型文件路径
  360. const modelPath = path.join('/home/fy/work/xiaoding/owl-admin/app/Models', modelFile);
  361. // 生成关系方法代码并更新模型文件
  362. const methodCode = generateMethodCode(relation);
  363. await updateModelFile(modelPath, methodCode, relation.title);
  364. }
  365. console.log('已完成模型关系的更新');
  366. process.exit(0);
  367. } catch (error) {
  368. console.error('发生错误:', error);
  369. process.exit(1);
  370. }
  371. }
  372. // 执行主函数
  373. main();