新一代低代码开发神器——Magic-API - 烤鸡计算机技术分享烤鸡计算机技术分享新一代低代码开发神器——Magic-APIhttps://roastchicken.cn/doc/magic-api.html

介绍

Magic-API 是什么?

Magic-API是一个基于 Java语言,依托于Spring Boot 生态的一个快速开发框架。以下为官方的介绍。

magic-api 是一个基于Java的接口快速开发框架,编写接口将通过magic-api提供的UI界面完成,自动映射为HTTP接口,无需定义Controller、Service、Dao、Mapper、XML、VO等Java对象即可完成常见的HTTP API接口开发

Magic-API能做什么?

Magic-API 可以开发HTTP接口、RESTful接口,能作为SpringBoot+Mybatis(包括SSH、SSM等)的替代方案。

使用 Magic-API 有什么优势?

Magic-API编写代码使用的脚本语言的Magic-Script,它语法和Java很相似,也和Javascript很相似,是吸取了二者语法的优势创造的新语言。

友好的语法意味着学习这个脚本语言的学习成本非常低。这样就不用专门去学一些不熟悉、古怪的语法、特殊的设定了。

对于熟悉Javascript和Java的程序员来说,使用Magic-API可以说是得心应手了。

语法学习成本低是一方面,另一方面是Magic-Script还支持一些比较前瞻、好用的福利语法。比如你可以放心地使用多行字符串(JDK13中的文本块""",新增的语法)、Javascript ES6中的模板字符串(形如`${变量取值}`),而不用担心你使用的JDK版本仅仅是8。

另一个能大大提高开发效率的,是因为每次改完Magic-API的代码不需要去重启项目。虽然使用IDEA配合一些jRebel之类的插件也可以做到,但是,使用这些热加载的方式写Java代码还是会有一些局限的(比如热加载的reload速度、新写方法的方法签名变化、重新注入Spring环境等问题)。

在效率方面,Magic-Script的代码也会经过解析、编译为AST,再编译为(ASM)字节码去执行(1.4.0以前的版本为解释执行)。效率相比纯Java硬编码而言肯定是比不过,不过比那种解析执行的语言会好得多。在普遍的场景下,执行性能也绝不会成为瓶颈。

关于调试:使用Magic-API的开发过程中,还可以进行debug,调试比较方便,比一些基于Nashorn引擎的框架更有优势。

导入模块:Magic-API可以导入Spring中的bean、Java中的类以及其他Magic-API接口、Magic-API中的模块、函数。内置了方便操作的一些模块:数据库操作、redis操作、mongo操作、http请求、log日志、request请求模块、response响应模块、env环境模块等。

Magic-API使用浏览器作为开发过程中的编辑器。你甚至可以在平板电脑、手机上、在家里(只要网络能连上)就可以访问到编辑器开发了。想想看,简直可以随时随地进行开发(oh no...)。

既然使用浏览器可以随意访问,那么如何保证安全性呢?开发环境可以对访问Magic-API界面设置用户名和密码,如果是线上环境,还可以根据需要关闭在线编辑功能,防止代码被修改。

使用Magic-API还可以依托JavaSpring生态上的优势。这样使得集成一些可以集成Spring的框架和组件更加方便。让代码写起来更简洁、更顺手,让Javaer的开发变得更高效、便捷。

那么我们来开始吧

需要先了解那些知识?

你需要懂一些基本的计算机知识,比如安装Java环境、使用Maven、了解一些Spring Boot的知识,这个是最基本的。如果你刚好是一名Java EE开发人员,那就最好不过了。

引用依赖

是的。需要在pom.xml中添加magic-api-spring-boot-starter的依赖。version则需要填最新的版本号(话说这版本号更新的老快了)。

如果你使用的是gradle,也类似,需要用gradle的方式添加依赖

 <dependency>
     <groupId>org.ssssssss</groupId>
     <artifactId>magic-api-spring-boot-starter</artifactId>
     <version>magic-api-lastest-version</version>
 </dependency>

还有一个properties文件中的配置。下面是官方文档的配置

 server.port=9999
 #配置web页面入口
 magic-api.web=/magic/web
 #配置文件存储位置。当以classpath开头时,为只读模式
 magic-api.resource.location=/data/magic-api

启动项目

使用IDE中的方式启动Spring Boot项目。如果不出意外的话,就可以看到控制台打印出Magic-API的访问路径了。

我在使用中配置了哪些东西?

配置

下面是关于Magic-API的一些配置内容:

 # 配置web页面入口
 magic-api.web=/magic/web
 # 下面这两个是逻辑删除的列名和标记为删除时的值
 magic-api.crud-config.logic-delete-column=deleted
 magic-api.crud-config.logic-delete-value=1
 # 指定代码存储模式为数据库
 magic-api.resource.type=database
 # 代码存储所使用的数据源,在这我使用 'source' 数据源
 magic-api.resource.datasource=source
 # 执行 SQL 时是否显示 SQL 语句,我设置为了 false,是为了后续对输出sql格式进行自定义
 magic-api.show-sql=false
 # 代码备份的设置
 magic-api.backup-config.max-history=7
 magic-api.backup-config.resource-type=database
 magic-api.backup-config.datasource=source
 magic-api.backup-config.table-name=magic_api_backup
 magic-api.debug-config.timeout=600
 # 驼峰命名规则,将表名下划线风格转为驼峰规则
 magic-api.sql-column-case=camel
 # swagger 展示的文字信息
 magic-api.swagger-config.description=\u63a5\u53e3\u4fe1\u606f
 magic-api.swagger-config.title=\u63a5\u53e3\u6587\u6863
 # editor 编辑器的配置文件
 magic-api.editor-config=classpath:./magic-editor-config.js

默认是会把写的代码存到本地磁盘文件的。可以通过magic-api.resource.type设置为数据库存储。

最后的magic-api.editor-config配置项指定了前端编辑器的一些字体配置,内容如下,位置位于classpath(maven 的 resources目录),文件名:magic-editor-config.js

 var MAGIC_EDITOR_CONFIG = {
     editorFontFamily: 'YaHei Consolas Hybrid',
 }

数据源配置

再来说数据源的部分,数据源部分是把源代码的保存指定了单独的一个位置。数据源的配置内容如下:

 spring.datasource.url=jdbc:mysql://x.x.x.x:3306/pbi?serverTimezone=GMT%2B8
 spring.datasource.jdbc-url=${spring.datasource.url}
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 spring.datasource.type=com.zaxxer.hikari.HikariDataSource
 spring.datasource.name=defaultDataSource
 spring.datasource.username=root
 spring.datasource.password=xxxx
 ​
 spring.datasource.source.url=jdbc:mysql://x.x.x.x:3306/pbi_source?serverTimezone=GMT%2B8
 spring.datasource.source.jdbc-url=${spring.datasource.source.url}
 spring.datasource.source.driver-class-name=com.mysql.cj.jdbc.Driver
 spring.datasource.source.type=com.zaxxer.hikari.HikariDataSource
 spring.datasource.source.username=root
 spring.datasource.source.password=xxxx

Spring Boot 的配置类文件:

 
 @Configuration
 public class MagicApiConfig {
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource")
     @Primary
     public DataSource dataSource() {
         return DataSourceBuilder.create().build();
     }
 ​
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.source")
     public DataSource sourceDataSource() {
         return DataSourceBuilder.create().build();
     }
     
     @Bean
     public MagicDynamicDataSource magicDynamicDataSource() {
         MagicDynamicDataSource dynamicDataSource = new MagicDynamicDataSource();
         dynamicDataSource.setDefault(dataSource());
         dynamicDataSource.add("source", sourceDataSource());
         return dynamicDataSource;
     }
 }

其中的指定的source数据源最后就会被用来存储使用Magic-API编写的源码。

这样做的目的是把代码存储和业务数据进行隔离,防止存放代码的数据库被误操作删除。

除了把代码存到数据库,还可以选择存到文件中。

SQL 打印配置

刚才提到我关闭了默认的SQL打印日志,我有个自己写的实现。还是在MagicApiConfig中添加一个自定义的SQL打印的Bean

 @Configuration
 public class MagicApiConfig {
     ...
     private static final Pattern SQL_PATTERN = Pattern.compile("\\?");
     @Bean
     public SQLInterceptor sqlInterceptor() {
         return (boundSql, requestEntity) -> {
             Logger logger = LoggerFactory.getLogger(filterName(boundSql.getRuntimeContext().getScriptContext()));
             String[] parameters = Arrays.stream(boundSql.getParameters()).map(it -> {
                 if (it == null) {
                     return "null";
                 }
                 if (it instanceof Number) {
                     return "{" + it + "}";
                 }
                 if (it instanceof String) {
                     return "{'" + it + "'}";
                 }
                 if (it instanceof Date) {
                     return "{'" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(it) + "'}";
                 }
                 return "{" + it + "(" + it.getClass().getSimpleName() + ")}";
             }).toArray(String[]::new);
             String dataSourceName = boundSql.getSqlModule().getDataSourceName();
             String sql = boundSql.getSql().trim();
             Matcher matcher = SQL_PATTERN.matcher(sql);
             int patternCount = 0;
             while (matcher.find()) {
                 patternCount++;
             }
             if (patternCount == parameters.length) {
                 matcher.reset();
                 boolean result = matcher.find();
                 if (result) {
                     int i = 0;
                     StringBuffer sb = new StringBuffer();
                     do {
                         matcher.appendReplacement(sb, parameters[i++]);
                         result = matcher.find();
                     } while (result);
                     matcher.appendTail(sb);
                     print(logger, dataSourceName, sb);
                 } else {
                     print(logger, parameters, dataSourceName, sql);
                 }
             } else {
                 print(logger, parameters, dataSourceName, sql);
             }
         };
     }
 ​
     private static void print(Logger logger, String dataSourceName, StringBuffer sb) {
         if (dataSourceName == null) {
             logger.info("sql: [{}]", toSql(sb));
         } else {
             logger.info("datasource: {}\tsql: [{}]", dataSourceName, toSql(sb));
         }
     }
 ​
     private static void print(Logger logger, String[] parameters, String dataSourceName, String sql) {
         if (dataSourceName == null) {
             logger.info("sql: [{}]\tparams: [{}]", toSql(sql), parameters);
         } else {
             logger.info("datasource: {}\tsql: [{}]\tparams: [{}]", dataSourceName, toSql(sql), parameters);
         }
     }
 ​
     private static final Pattern SQL_SPACE = Pattern.compile("\\s*(\\r\\n|\\r|\\n)*\\s+|\\s+(\\r\\n|\\r|\\n)*\\s*");
     private static String toSql(String sb) {
         return SQL_SPACE.matcher(sb).replaceAll(" ");
     }
     private static String toSql(StringBuffer sb) {
         return toSql(sb.toString());
     }
     public static String filterName(MagicScriptContext magicScriptContext) {
         return PATTERN.matcher(magicScriptContext.getScriptName()).replaceAll("magic-api.[$1]");
     }
     private static final Pattern PATTERN = Pattern.compile("^.*\\((.*?)\\).*$");
     ...
 }

还需要写另一个Config类,这个主要是对log模块的打印处理。这个Config类跟刚刚的作用不太一样,执行的时机略有区别,所以添加了@AutoConfigureAfter

 @AutoConfigureAfter(MagicAPIAutoConfiguration.class)
 @Import({MagicAPIAutoConfiguration.class, MagicConfiguration.class})
 @Configuration
 public class MagicApiLoggerConfig {
 ​
     @Resource
     @SuppressWarnings("unused")
     private MagicConfiguration magicConfiguration;
 ​
     @PostConstruct
     public void init() {
 //        MagicResourceLoader.addModule("log", (DynamicModuleImport) magicScriptContext -> LoggerFactory.getLogger(filterName(magicScriptContext))); // 这个是低版本Magic-API的兼容写法
         MagicResourceLoader.addModule("log", new DynamicModuleImport(Logger.class, context -> LoggerFactory.getLogger(MagicApiConfig.filterName(context))));
     }
 ​
 }

自定义结果结构、错误提示

我还对错误提示进行覆写,根据自己的需要重写。在下面,还对列表查询的结果类型进行了改造,便于后续调用后修改结构:

 @Component
 public class ResultHandler implements ResultProvider {
     @Override
     public Object buildResult(RequestEntity requestEntity, int code, String message, Object data) {
         return new JsonBean<>(code, message, data, (int) (System.currentTimeMillis() - requestEntity.getRequestTime()));
     }
 ​
     @Override
     public Object buildException(RequestEntity requestEntity, Throwable throwable) {
         return buildResult(requestEntity, RESPONSE_CODE_EXCEPTION, "系统繁忙,请稍后重试");
     }
 ​
     @Override
     public Object buildPageResult(RequestEntity requestEntity, Page page, long total, List<Map<String, Object>> data) {
         return new PageResult<>(total, data);
     }
 ​
     public static class PageResult<T> extends HashMap<String, Object> {
         public PageResult(long total, List<T> list) {
             super(2);
             this.put("total", total);
             if (total == 0) {
                 this.put("list", new ArrayList<>(0));
             } else {
                 this.put("list", list);
             }
         }
     }
 }

开始使用

magic-script 的语法语句行位可以写分号,也可以不写分号,用换行表示,这一点和javascript很像。

界面

图片来自官方文档

整体说明

接口信息

RequestBody

ResponseBody

debug

导入

导入是指import指令。比Java中的import功能更多。

导入类

import 可以导入某个类,例如:

 import 'java.lang.System'

这样导入之后,我们就可以正常使用System这个类了,比如执行System.out.println()

导入还可以写别名,例如:

 import 'java.lang.System' as Sys

这样就可以用别名Sys来使用System这个类了。比如Sys.out.println()。但是这样好像看起来很奇怪,在特定场景下再去用吧

导入并 new 实例:

 import 'java.util.Date'

框架默认已经导入了 java.util.* ,所以不需要写导入Date也可以直接书写。使用的时候直接写new Date()就可以啦。

导入 Spring 中的 Bean

import 可以导入 Spring 中的 Bean,例如:

 import 'javax.sql.DataSource' as ds

这样可以导入一个DataSource的bean,后续可以使用ds作为变量来使用了。

导入模块

improt 还可以导入模块,例如导入日志模块并使用:

 import log
 log.info('xxxxxx: {}', 'yy')

退出语法

magic-api 中的退出语法是指exit,作用是退出本次请求,并返回一些数据。类似于return,但是能传递更多的信息。比如某个入参条件不满足,可以使用exit 400, '参数不正确',这样就可以返回给前端接口数据了。

好了,Magic-API的介绍先告一段落了,用起来才知道什么是'真香'!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐