index.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. import { createDir, error, firstUpperCase, readFile, rootDir, toCamel } from "../utils";
  2. import { join } from "path";
  3. import axios from "axios";
  4. import { isArray, isEmpty, last, merge, values } from "lodash";
  5. import { createWriteStream } from "fs";
  6. import prettier from "prettier";
  7. import { config } from "../config";
  8. import type { Eps } from "../../types";
  9. const service = {};
  10. let list: Eps.Entity[] = [];
  11. let customList: Eps.Entity[] = [];
  12. // 获取请求地址
  13. function getEpsUrl() {
  14. let url = config.eps.api;
  15. if (!url) {
  16. url = config.type;
  17. }
  18. switch (url) {
  19. case "app":
  20. url = "/app/base/comm/eps";
  21. break;
  22. case "admin":
  23. url = "/admin/base/open/eps";
  24. break;
  25. }
  26. return url;
  27. }
  28. // 获取路径
  29. function getEpsPath(filename?: string) {
  30. return join(
  31. config.type == "admin" ? config.eps.dist : rootDir(config.eps.dist),
  32. filename || "",
  33. );
  34. }
  35. // 获取方法名
  36. function getNames(v: any) {
  37. return Object.keys(v).filter((e) => !["namespace", "permission"].includes(e));
  38. }
  39. // 获取数据
  40. async function getData(data?: Eps.Entity[]) {
  41. // 自定义数据
  42. if (!isEmpty(data)) {
  43. customList = (data || []).map((e) => {
  44. return {
  45. ...e,
  46. isLocal: true,
  47. };
  48. });
  49. }
  50. // 读取本地数据
  51. list = readFile(getEpsPath("eps.json"), true) || [];
  52. // 请求地址
  53. const url = config.reqUrl + getEpsUrl();
  54. // 请求数据
  55. await axios
  56. .get(url, {
  57. timeout: 5000,
  58. })
  59. .then((res) => {
  60. const { code, data, message } = res.data;
  61. if (code === 1000) {
  62. if (!isEmpty(data) && data) {
  63. list = values(data).flat();
  64. }
  65. } else {
  66. error(`[cool-eps] ${message || "获取数据失败"}`);
  67. }
  68. })
  69. .catch(() => {
  70. error(`[cool-eps] 后端未启动 ➜ ${url}`);
  71. });
  72. // 合并自定义数据
  73. if (isArray(customList)) {
  74. customList.forEach((e) => {
  75. const d = list.find((a) => e.prefix === a.prefix);
  76. if (d) {
  77. merge(d, e);
  78. } else {
  79. list.push(e);
  80. }
  81. });
  82. }
  83. // 设置默认值
  84. list.forEach((e) => {
  85. if (!e.namespace) {
  86. e.namespace = "";
  87. }
  88. if (!e.api) {
  89. e.api = [];
  90. }
  91. if (!e.columns) {
  92. e.columns = [];
  93. }
  94. });
  95. }
  96. // 创建 json 文件
  97. function createJson() {
  98. const arr = list.map((e) => {
  99. return {
  100. prefix: e.prefix,
  101. name: e.name || "",
  102. api: e.api.map((e) => {
  103. return {
  104. name: e.name,
  105. method: e.method,
  106. path: e.path,
  107. };
  108. }),
  109. };
  110. });
  111. const content = JSON.stringify(arr);
  112. const local_content = readFile(getEpsPath("eps.json"));
  113. // 是否需要更新
  114. const isUpdate = content != local_content;
  115. if (isUpdate) {
  116. createWriteStream(getEpsPath("eps.json"), {
  117. flags: "w",
  118. }).write(content);
  119. }
  120. return isUpdate;
  121. }
  122. // 创建描述文件
  123. async function createDescribe({ list, service }: { list: Eps.Entity[]; service: any }) {
  124. // 获取类型
  125. function getType({ propertyName, type }: any) {
  126. for (const map of config.eps.mapping) {
  127. if (map.custom) {
  128. const resType = map.custom({ propertyName, type });
  129. if (resType) return resType;
  130. }
  131. if (map.test) {
  132. if (map.test.includes(type)) return map.type;
  133. }
  134. }
  135. return type;
  136. }
  137. // 格式化方法名
  138. function formatName(name: string) {
  139. return (name || "").replace(/[:,\s,\/,-]/g, "");
  140. }
  141. // 创建 Entity
  142. function createEntity() {
  143. const ignore: string[] = [];
  144. let t0 = "";
  145. for (const item of list) {
  146. if (!item.name) continue;
  147. let t = `interface ${formatName(item.name)} {`;
  148. for (const col of item.columns || []) {
  149. t += `
  150. /**
  151. * ${col.comment}
  152. */
  153. ${col.propertyName}?: ${getType({
  154. propertyName: col.propertyName,
  155. type: col.type,
  156. })}
  157. `;
  158. }
  159. t += `
  160. /**
  161. * 任意键值
  162. */
  163. [key: string]: any;
  164. }
  165. `;
  166. if (!ignore.includes(item.name)) {
  167. ignore.push(item.name);
  168. t0 += t + "\n\n";
  169. }
  170. }
  171. return t0;
  172. }
  173. // 创建 Service
  174. function createDts() {
  175. let controller = "";
  176. let chain = "";
  177. // 处理数据
  178. function deep(d: any, k?: string) {
  179. if (!k) k = "";
  180. for (const i in d) {
  181. const name = k + toCamel(firstUpperCase(formatName(i)));
  182. if (d[i].namespace) {
  183. // 查找配置
  184. const item = list.find((e) => (e.prefix || "") === `/${d[i].namespace}`);
  185. if (item) {
  186. let t = `interface ${name} {`;
  187. // 插入方法
  188. if (item.api) {
  189. // 权限列表
  190. const permission: string[] = [];
  191. item.api.forEach((a) => {
  192. // 方法名
  193. const n = toCamel(
  194. formatName(a.name || last(a.path.split("/")) || ""),
  195. );
  196. if (n) {
  197. // 参数类型
  198. let q: string[] = [];
  199. // 参数列表
  200. const { parameters = [] } = a.dts || {};
  201. parameters.forEach((p) => {
  202. if (p.description) {
  203. q.push(`\n/** ${p.description} */\n`);
  204. }
  205. if (p.name.includes(":")) {
  206. return false;
  207. }
  208. const a = `${p.name}${p.required ? "" : "?"}`;
  209. const b = `${p.schema.type || "string"}`;
  210. q.push(`${a}: ${b},`);
  211. });
  212. if (isEmpty(q)) {
  213. q = ["any"];
  214. } else {
  215. q.unshift("{");
  216. q.push("}");
  217. }
  218. // 返回类型
  219. let res = "";
  220. // 实体名
  221. const en = item.name || "any";
  222. switch (a.path) {
  223. case "/page":
  224. res = `
  225. {
  226. pagination: { size: number; page: number; total: number; [key: string]: any };
  227. list: ${en} [];
  228. [key: string]: any;
  229. }
  230. `;
  231. break;
  232. case "/list":
  233. res = `${en} []`;
  234. break;
  235. case "/info":
  236. res = en;
  237. break;
  238. default:
  239. res = "any";
  240. break;
  241. }
  242. // 描述
  243. t += `
  244. /**
  245. * ${a.summary || n}
  246. */
  247. ${n}(data${q.length == 1 ? "?" : ""}: ${q.join("")}): Promise<${res}>;
  248. `;
  249. if (!permission.includes(n)) {
  250. permission.push(n);
  251. }
  252. }
  253. });
  254. // 权限标识
  255. t += `
  256. /**
  257. * 权限标识
  258. */
  259. permission: { ${permission.map((e) => `${e}: string;`).join("\n")} };
  260. `;
  261. // 权限状态
  262. t += `
  263. /**
  264. * 权限状态
  265. */
  266. _permission: { ${permission.map((e) => `${e}: boolean;`).join("\n")} };
  267. `;
  268. t += `
  269. request: Service['request']
  270. `;
  271. }
  272. t += "}\n\n";
  273. controller += t;
  274. chain += `${formatName(i)}: ${name};`;
  275. }
  276. } else {
  277. chain += `${formatName(i)}: {`;
  278. deep(d[i], name);
  279. chain += "},";
  280. }
  281. }
  282. }
  283. // 遍历
  284. deep(service);
  285. return `
  286. type json = any;
  287. ${controller}
  288. type Service = {
  289. /**
  290. * 基础请求
  291. */
  292. request(options?: {
  293. url: string;
  294. method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
  295. data?: any;
  296. params?: any;
  297. headers?: {
  298. authorization?: string;
  299. [key: string]: any;
  300. },
  301. timeout?: number;
  302. proxy?: boolean;
  303. [key: string]: any;
  304. }): Promise<any>;
  305. ${chain}
  306. }
  307. `;
  308. }
  309. // 文件内容
  310. const text = `
  311. declare namespace Eps {
  312. ${createEntity()}
  313. ${createDts()}
  314. }
  315. `;
  316. // 文本内容
  317. const content = await prettier.format(text, {
  318. parser: "typescript",
  319. useTabs: true,
  320. tabWidth: 4,
  321. endOfLine: "lf",
  322. semi: true,
  323. singleQuote: false,
  324. printWidth: 100,
  325. trailingComma: "none",
  326. });
  327. const local_content = readFile(getEpsPath("eps.d.ts"));
  328. // 是否需要更新
  329. if (content != local_content) {
  330. // 创建 eps 描述文件
  331. createWriteStream(getEpsPath("eps.d.ts"), {
  332. flags: "w",
  333. }).write(content);
  334. }
  335. }
  336. // 创建 service
  337. function createService() {
  338. // 路径第一层作为 id 标识
  339. const id = getEpsUrl().split("/")[1];
  340. list.forEach((e) => {
  341. // 请求地址
  342. const path = e.prefix[0] == "/" ? e.prefix.substring(1, e.prefix.length) : e.prefix;
  343. // 分隔路径
  344. const arr = path.replace(id, "").split("/").filter(Boolean).map(toCamel);
  345. // 遍历
  346. function deep(d: any, i: number) {
  347. const k = arr[i];
  348. if (k) {
  349. // 是否最后一个
  350. if (arr[i + 1]) {
  351. if (!d[k]) {
  352. d[k] = {};
  353. }
  354. deep(d[k], i + 1);
  355. } else {
  356. // 不存在则创建
  357. if (!d[k]) {
  358. d[k] = {
  359. namespace: path,
  360. permission: {},
  361. };
  362. }
  363. // 创建方法
  364. e.api.forEach((a) => {
  365. // 方法名
  366. const n = a.path.replace("/", "");
  367. if (n && !/[-:]/g.test(n)) {
  368. d[k][n] = a;
  369. }
  370. });
  371. // 创建权限
  372. getNames(d[k]).forEach((i) => {
  373. d[k].permission[i] = `${d[k].namespace.replace(`${id}/`, "")}/${i}`.replace(
  374. /\//g,
  375. ":",
  376. );
  377. });
  378. }
  379. }
  380. }
  381. deep(service, 0);
  382. });
  383. }
  384. // 创建 eps
  385. export async function createEps(query?: { list: any[] }) {
  386. if (config.eps.enable) {
  387. // 获取数据
  388. await getData(query?.list || []);
  389. // 创建 service
  390. createService();
  391. // 创建目录
  392. createDir(getEpsPath(), true);
  393. // 创建 json 文件
  394. const isUpdate = createJson();
  395. // 创建描述文件
  396. createDescribe({ service, list });
  397. return {
  398. service,
  399. list,
  400. isUpdate,
  401. };
  402. } else {
  403. return {
  404. service: {},
  405. list: [],
  406. };
  407. }
  408. }