# magic-api **Repository Path**: leheyue/magic-api ## Basic Information - **Project Name**: magic-api - **Description**: magic-api 是一个基于Java的接口快速开发框架,通过magic-api提供的UI界面完成编写接口,无需定义Controller、Service、Dao、Mapper、XML、VO等Java对象即可完成常见的HTTP API接口开发 - **Primary Language**: Java - **License**: MIT - **Default Branch**: rocketmq - **Homepage**: http://ssssssss.org - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2004 - **Created**: 2023-09-13 - **Last Updated**: 2024-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

maven

[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | 更新日志 | [项目截图](#项目截图) | [交流群](#交流群) # 简介 magic-api 是一个基于Java的接口快速开发框架,编写接口将通过magic-api提供的UI界面完成,自动映射为HTTP接口,无需定义Controller、Service、Dao、Mapper、XML、VO等Java对象即可完成常见的HTTP API接口开发 【已有上千家中小型公司使用,上万名开发者用于接口配置开发。上百名开发者参与提交了功能建议,接近20多名贡献者参与。已被gitee长期推荐。从首个版本开始不断优化升级,目前版本稳定,开发者交流群活跃。参与交流QQ群④700818216】 # 特性 - 支持MySQL、MariaDB、Oracle、DB2、PostgreSQL、SQLServer 等支持jdbc规范的数据库 - 支持非关系型数据库Redis、Mongodb - 支持集群部署、接口自动同步。 - 支持分页查询以及自定义分页查询 - 支持多数据源配置,支持在线配置数据源 - 支持SQL缓存,以及自定义SQL缓存 - 支持自定义JSON结果、自定义分页结果 - 支持对接口权限配置、拦截器等功能 - 支持运行时动态修改数据源 - 支持Swagger接口文档生成 - 基于[magic-script](https://gitee.com/ssssssss-team/magic-script)脚本引擎,动态编译,无需重启,实时发布 - 支持Linq式查询,关联、转换更简单 - 支持数据库事务、SQL支持拼接,占位符,判断等语法 - 支持文件上传、下载、输出图片 - 支持脚本历史版本对比与恢复 - 支持脚本代码自动提示、参数提示、悬浮提示、错误提示 - 支持导入Spring中的Bean、Java中的类 - 支持在线调试 - 支持自定义工具类、自定义模块包、自定义类型扩展、自定义方言、自定义列名转换等自定义操作 # 快速开始 ## maven引入 ```xml io.github.leheyue magic-api-spring-boot-starter 2.1.1.6.7 ``` ## 修改application.properties ```properties server.port=9999 #配置web页面入口 magic-api.web=/magic/web #配置文件存储位置。当以classpath开头时,为只读模式 magic-api.resource.location=/data/magic-api ``` ## 在线编辑 访问`http://localhost:9999/magic/web`进行操作 # 文档/演示 - 文档地址:[https://ssssssss.org](https://ssssssss.org) - 在线演示:[https://magic-api.ssssssss.org](https://magic-api.ssssssss.org) # 示例项目 - [magic-api-example](https://gitee.com/ssssssss-team/magic-api-example) # v2.1.1.6.3 前端修改备注 - 消息队列 数据源 展示区分生产与消费 ```js R("label",null,F(J.name||h(c)("datasource.primary")),1),R("span",null,"("+F(J.key||"default")+")",1) ⬇ R("label",null,F(((J.extra?"【"+h(c)("datasource.extra."+J.extra)+"】":"")+J.name)||h(c)("datasource.primary")),1),R("span",null,"("+F(J.key||"default")+")",1) ``` - 右侧数据源可注入服务 ```js D.PLUGINS.filter(b=>b.datasources&&b.datasources.length>0).map(b=>b.datasources).forEach(b=>b.forEach(S=>{g.push({type:S.type,icon:S.icon,title:S.title,name:S.name})})) ⬇ D.PLUGINS.filter(b=>b.datasources&&b.datasources.length>0).map(b=>b.datasources).forEach(b=>b.forEach(S=>{g.push({type:S.type,icon:S.icon,title:S.title,name:S.name}),S.service&&(d[S.type]=S.service)})) ``` # v2.1.1.4 前端修改备注 - 重新加载时重置数据源及表定义 ```js Y.sendGet("/reload").success(()=>{T.status("message.reloadResourceSuccess"),E(null,()=>T.$emit($.RELOAD_RESOURCES_FINISH))}) ⬇ Y.sendGet("/reload").success(()=>{T.status("message.reloadResourceSuccess"),E(null,()=>T.$emit($.RELOAD_RESOURCES_FINISH)),window.dsDbs=[]}) ``` # v2.1.1.2 前端修改备注 - 添加人大金仓jdbc驱动 ```js JDBC_DRIVERS:["com.mysql.jdbc.Driver","com.mysql.cj.jdbc.Driver","oracle.jdbc.driver.OracleDriver","org.postgresql.Driver","com.microsoft.sqlserver.jdbc.SQLServerDriver","com.ibm.db2.jcc.DB2Driver"] ⬇ JDBC_DRIVERS:["com.mysql.jdbc.Driver","com.mysql.cj.jdbc.Driver","oracle.jdbc.driver.OracleDriver","org.postgresql.Driver","com.microsoft.sqlserver.jdbc.SQLServerDriver","com.ibm.db2.jcc.DB2Driver","com.kingbase8.Driver"] ``` # v2.1.1.1 前端修改备注 - 版本升级校验 ```js const C=()=>{fetch("https://console.ssssssss.org.cn/latest?group=org.ssssssss&artifactId=magic-api&from="+D.MAGIC_API_VERSION_TEXT).then(L=>{t.config.checkUpdate!==!1&&L.status===200&&L.json().then(j=>{j.version&&j.version!=="unknown"&&D.config.version!==j.version ↓ const C=()=>{fetch("https://console.ssssssss.org.cn/latest?group=org.ssssssss&artifactId=magic-api&from="+D.MAGIC_API_VERSION_TEXT).then(L=>{t.config.checkUpdate!==!1&&L.status===200&&L.json().then(j=>{j.version&&j.version!=="unknown"&&D.config.version{const t=D.config.persistenceResponseBody!==!1;return{id:e.id,name:e.name,path:e.path,groupId:e.groupId,lock:e.lock,method:e.method,description:e.description,createBy:e.createBy,createDate:e.createDate,properties:e.properties,script:e.script,responseBody:t&&e.responseBody||void 0,responseBodyDefinition:t&&e.responseBodyDefinition||void 0,requestBody:e.requestBody,requestBodyDefinition:e.requestBodyDefinition,parameters:e.parameters.filter(a=>a.name),headers:e.headers.filter(a=>a.name),paths:e.paths.filter(a=>a.name),options:e.options.filter(a=>a.name)}} ⬇ processSave:e=>{const t=D.config.persistenceResponseBody!==!1;return{id:e.id,name:e.name,path:e.path,groupId:e.groupId,lock:e.lock,method:e.method,description:e.description,createBy:e.createBy,createDate:e.createDate,updateTime:e.updateTime,properties:e.properties,script:e.script,responseBody:t&&e.responseBody||void 0,responseBodyDefinition:t&&e.responseBodyDefinition||void 0,requestBody:e.requestBody,requestBodyDefinition:e.requestBodyDefinition,parameters:e.parameters.filter(a=>a.name),headers:e.headers.filter(a=>a.name),paths:e.paths.filter(a=>a.name),options:e.options.filter(a=>a.name)}} // 保存接口成功后获取最新updateTime const g=x=>{const b=i.value;if(b&&b.item){const S=i.value.processSave(b.item);return Object.keys(S).forEach(A=>b.item[A]=S[A]),Y.sendJson(`/resource/file/${i.value.type}/save?auto=${x?0:1}`,S).success(A=>{const M=`${b.title}\u300C${b.path()}\u300D`;A?(T.status("message.saveSuccess",!0,M),b.tmpObject=JSON.parse(JSON.stringify(S)),b.item.id!==A&&T.loading(1),b.item.id=A):(T.status("message.saveFailed",!1,M),a.$alert(c("message.saveFailed",M)))})}} ⬇ const g=x=>{const b=i.value;if(b&&b.item){const S=i.value.processSave(b.item);return Object.keys(S).forEach(A=>b.item[A]=S[A]),Y.sendJson(`/resource/file/${i.value.type}/save?auto=${x?0:1}`,S).success(A=>{const M=`${b.title}\u300C${b.path()}\u300D`;A?(T.status("message.saveSuccess",!0,M),b.tmpObject=JSON.parse(JSON.stringify(S)),b.item.id!==A&&T.loading(1),b.item.id=A,Y.sendGet(`/resource/file/${b.item.id}`).success(T=>{b.item["updateTime"]=T["updateTime"],b.tmpObject=JSON.parse(JSON.stringify(T))})):(T.status("message.saveFailed",!1,M),a.$alert(c("message.saveFailed",M)))})}} ``` - 修改包名 ```js ae.setExtensionAttribute("org.ssssssss.magicapi.modules.db.SQLModule",()=>{var e;return xa&&(((e=xa("datasource")[0])==null?void 0:e.children)||[]).filter(t=>t.key).map(t=>({name:t.key,type:"org.ssssssss.magicapi.modules.db.SQLModule",comment:t.name}))||[]}); ⬇ ae.setExtensionAttribute("io.github.leheyue.magicapi.modules.db.SQLModule",()=>{var e;return xa&&(((e=xa("datasource")[0])==null?void 0:e.children)||[]).filter(t=>t.key).map(t=>({name:t.key,type:"io.github.leheyue.magicapi.modules.db.SQLModule",comment:t.name}))||[]}); ``` - 编辑器中数据源链式调用获取表名及字段名 ```js async getJavaType(t){let a=await this.target.getJavaType(t),n=await ae.loadClass(a),i=n==null?void 0:n.attributes;const s=this.member.getText(); ⬇ async getJavaType(t){let a=await this.target.getJavaType(t),n=await ae.loadClass(a),i=n==null?void 0:n.attributes;if (n.className == 'io.github.leheyue.magicapi.modules.ds.DatasourceModule') { if (!window.dsDbs) { window.dsDbs = {}; } var e; xa&&(((e=xa("datasource")[0])==null?void 0:e.children)||[]).forEach(t => { var dsKey = t.key ? t.key : 'default'; if (!dsDbs[dsKey]) { dsDbs[dsKey] = { dsName: dsKey, dsComment: t.name, tables: {} }; } }); for (var dsKey in dsDbs) { if (((e == null ? void 0 : e.children) || []).every(eItem => (eItem.key || 'default') != dsKey)) { delete window.dsDbs[dsKey]; } } Object.keys(dsDbs).forEach(x => { let ty = 'ds.' + x; if (!Je[ty]) { Je[ty] = { attributes: [], className: ty, enums: null, interfaces: [], methods: [], module: false, superClass: null }; } var y = i.find(y => y.name == x); if (y) { y = { type: ty, name: x, comment: dsDbs[x].dsComment }; } else { i.push({ type: ty, name: x, comment: dsDbs[x].dsComment }); } }); } else if (n.className.indexOf('ds.') == 0 && n.className.lastIndexOf('.') == 2) { let dsKey = n.className.replace('ds.', ''); try { dsDbs[dsKey] && !Object.keys(dsDbs[dsKey].tables).length && (await Y.execute({ url: "/datasource/jdbc/table/" + dsKey, data: {} })).data.data.forEach(i=>{ i.columns = [] dsDbs[dsKey].tables[i.tableKey] = i }); } catch {} dsDbs[dsKey] && Object.keys(dsDbs[dsKey].tables).forEach(x => { let ty = 'ds.' + dsKey + '.' + x; if (!Je[ty]) { let tabelNode = Je['io.github.leheyue.magicapi.datasource.model.MagicDynamicDataSource$DataSourceNode$TableNode']; Je[ty] = { attributes: [], className: ty, enums: null, interfaces: [], methods: [], module: false, superClass: null }; tabelNode.methods.forEach(z => { Je[ty].methods.push({ ...z, returnType: ty }); }); } var y = i.find(y => y.name == x); if (y) { y = { type: 'ds.' + dsKey + '.' + x, name: x, comment: dsDbs[dsKey].tables[x].tableComment }; } else { i.push({ type: 'ds.' + dsKey + '.' + x, name: x, comment: dsDbs[dsKey].tables[x].tableComment }); } }); } else if (n.className.indexOf('ds.') == 0 && n.className.lastIndexOf(".") > 4) { var clzs = n.className.split('.'); let tableKey = clzs[2]; let dsKey = clzs[1]; try { dsDbs[dsKey] && !dsDbs[dsKey].tables[tableKey].columns.length && (await Y.execute({ url: "/datasource/jdbc/column/" + dsKey + "/" + dsDbs[dsKey].tables[tableKey].tableName, data: {} })).data.data.forEach(i=>{ dsDbs[dsKey].tables[tableKey].columns.push(i) }); } catch {} dsDbs[dsKey] && Object.keys(dsDbs[dsKey].tables[tableKey].columns || []).forEach(x => { let column = dsDbs[dsKey].tables[tableKey].columns[x]; var y = i.find(y => y.name == column.columnKey); var columnComment = column.columnName + " " + (column.columnDefault ? (' (' + column.columnDefault + ')') : '') + " " + column.columnType + " " + column.columnComment; if (y) { y = { type: 'java.lang.String', name: column.columnKey, comment: columnComment }; } else { i.push({ type: 'java.lang.String', name: column.columnKey, comment: columnComment }); } }); } const s=this.member.getText(); if (n.className == 'io.github.leheyue.magicapi.rocketmq.RocketMqModule') { var e; var rmDs = xa && (((e = xa("rocketmq-datasource")[0]) == null ? void 0 : e.children) || []).find(t=> t.extra == 'producer' && t.key == s); if (rmDs) { return ae.getWrapperClass('io.github.leheyue.magicapi.rocketmq.producer.RocketMqProducer'); } } else if (n.className == 'io.github.leheyue.magicapi.rabbitmq.RabbitMqModule') { var e; var rmDs = xa && (((e = xa("rabbitmq-datasource")[0]) == null ? void 0 : e.children) || []).find(t=> t.extra == 'producer' && t.key == s); if (rmDs) { return ae.getWrapperClass('io.github.leheyue.magicapi.rabbitmq.producer.RabbitMqProducer'); } } else if (n.className == 'io.github.leheyue.magicapi.kafka.KafkaModule') { var e; var rmDs = xa && (((e = xa("kafka-datasource")[0]) == null ? void 0 : e.children) || []).find(t=> t.extra == 'producer' && t.key == s); if (rmDs) { return ae.getWrapperClass('io.github.leheyue.magicapi.kafka.producer.KafkaProducer'); } } else if (n.className == 'io.github.leheyue.magicapi.mqtt.MqttModule') { var e; var rmDs = xa && (((e = xa("mqtt-datasource")[0]) == null ? void 0 : e.children) || []).find(t=> t.extra == 'producer' && t.key == s); if (rmDs) { return ae.getWrapperClass('io.github.leheyue.magicapi.mqtt.producer.MqttProducer'); } } ``` # 项目截图 | ![整体截图](https://images.gitee.com/uploads/images/2021/0711/105714_c1cacf2c_297689.png "整体截图") | ![代码提示](https://images.gitee.com/uploads/images/2021/0711/110448_11b6626b_297689.gif "代码提示") | |---|---| | ![DEBUG](https://images.gitee.com/uploads/images/2021/0711/110515_755f178a_297689.gif "DEBUG") | ![参数提示](https://images.gitee.com/uploads/images/2021/0711/110322_9dd6d149_297689.gif "参数提示") | | ![远程推送](https://images.gitee.com/uploads/images/2021/0711/105803_b53e0d7e_297689.png "远程推送") | ![历史记录](https://images.gitee.com/uploads/images/2021/0711/105910_f2440ea4_297689.png "历史记录") | | ![数据源](https://images.gitee.com/uploads/images/2021/0711/105846_7ec51a50_297689.png "数据源") | ![全局搜索](https://images.gitee.com/uploads/images/2021/0711/105823_ac18ada7_297689.png "全局搜索") | # 交流群 | 微信群 | QQ群 | | ----- | --- | | 作者微信 | QQ群 | | 备注:加群,邀您加入群聊| 点击加入QQ群:700818216 |