# 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
[特性](#特性) | [快速开始](#快速开始) | [文档/演示](#文档演示) | [示例项目](#示例项目) | 更新日志 | [项目截图](#项目截图) | [交流群](#交流群)
# 简介
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');
}
}
```
# 项目截图
|  |  |
|---|---|
|  |  |
|  |  |
|  |  |
# 交流群
| 微信群 | QQ群 |
| ----- | --- |
|
|
|
| 备注:加群,邀您加入群聊| 点击加入QQ群:700818216 |