突然想到這么一個標題黨的事情,試試看。注:僅基於PHP做簡單梳理,未完成成品。
先想清楚核心原理,然后分別從后端、前端設計實現。
核心原理
低代碼,如果簡單理解為針對常規應用的CRUD場景,以一種DSL語言的形式,實現系統的開發。這種形式,減少了程序員的重復勞動,甚至可以讓不太懂程序開發的人也能完成系統的開發-這也許正是“低”的含義。實則,我們知道多數人的認知是模型驅動的開發思想。
要達到后者的目的,首先得定義一套DSL語言,以便一般用戶容易掌握,從而通過對CRUD這類功能所需信息的定義來實現系統的開發。
軟件工程中,傳統過程是要先設計,再實現;通過低代碼的DSL語言,實際上將設計和實現兩個動作合二為一了,設計即實現。
軟件設計方法分結構化設計和面向對象設計,我們所熟知的畫ER圖就是結構化設計的經典步驟。數據實體、屬性、關系,作為我們對現實世界事物靜態狀態下的認知的映射,是最基礎的分析和設計工作。因此,我們的DSL語言中,首先要支持實體的定義。
這里,我們可以考慮使用PlantUML中的實體關系圖語言作為我們的DSL語言,這樣有一個很大的好處,實體圖可以渲染成圖形格式便於評審交流。
不過稍微有點尷尬的是,目前PlantUML還沒有原生的PHP解析引擎,需要封裝其官方的Jar包調用執行解析和轉換,才能最終生成PHP版的實體類代碼。https://github.com/mk-conn/plant2code
后端實現
路由
選一MVC框架,如ThinkPHP v6,基於其靈活的路由機制,實現按實體的CRUD界面和API請求入口調度。
在路由定義文件(config/route.php)中加上:
use think\facade\Route;
// 主界面
Route::get('lc/:entity$', 'EntityCRUD/index');
// API
Route::rule('lc-api/:entity/:action', 'EntityCRUD/:action');
CRUD
基於 think-orm 的特性,可以很便捷的實現相應功能。下面做個示范:
https://www.kancloud.cn/manual/think-orm/1257998
<?php
namespace app\controller;
use app\BaseController;
use think\facade\Db;
class EntityCRUD extends BaseController
{
/**
* 構造 CRUD UI
* @param $entity
*/
public function index($entity)
{
echo $entity . ' Entity/index';
}
/**
* 查詢API
* @param $entity
*/
public function retrieve($entity)
{
echo $entity . ' Entity/retrieve';
}
/**
* 增加API
* @param $entity
*/
public function create($entity)
{
$data = input('post.');
$id = Db::name($entity)->insertGetId($data);
echo $entity . ' Entity/create:' . $id;
}
/**
* 修改API
* @param $entity
* @param $id
*/
public function update($entity, $id)
{
$data = input('post.');
Db::name($entity)->where('id' , $id)->update($data);
echo $entity . ' Entity/update ' . $id;
}
/**
* 刪除API
* @param $entity
* @param $id
*/
public function delete($entity, $id)
{
Db::table($entity)->delete($id);
echo $entity . ' Entity/delete ' . $id;
}
}
實體定義
基於plantuml 的語法,如果數據庫使用mongodb,則直接用plantuml的實體關系圖特性即可完成實體類的完整定義,都不用考慮與數據庫層字段類型的映射問題;如果基於傳統的關系型數據庫如mysql,則需要進一步實現字段類型的映射。
app/definition/entity/company.puml
@startuml
entity "Company" as e01 {
*id: int <<generated>>
*name: string
*phone1: string
--
phone2: string
fax: string
address1: string
address2: string
city: string
state: string
zip: string
primary_url: string
owner: int
*type: int
email: string
description: text
}
@enduml
或者基於plantuml也能處理的JSON或YAML格式:
@startyaml
name: account_receivable_invoice
label: 收票
icon: account
enable_api: true
enable_files: true
fields:
name:
label: 標題
type: text
required: true
bill_id:
label: 付款單ID
omit: true
hidden: true
type: text
amount:
label: 發票總金額
type: number
required: true
invoice_number:
label: 發票張數
type: number
required: true
payable_id:
label: 應付記錄
type: lookup
reference_to: account_receivable
relatedList: true
required: true
contract_id:
label: 合同
type: master_detail
reference_to: contracts
required: true
owner:
label: 上傳人
omit: false
readonly: true
type: lookup
reference_to: users
company_id:
omit: false
hidden: false
label: 我方單位
list_views:
all:
label: 所有
columns:
- name
- amount
- invoice_number
- owner
- created
permission_set:
user:
allowCreate: false
allowDelete: false
allowEdit: false
allowRead: true
modifyAllRecords: false
viewAllRecords: false
modifyCompanyRecords: false
viewCompanyRecords: true
admin:
allowCreate: true
allowDelete: true
allowEdit: true
allowRead: true
modifyAllRecords: true
viewAllRecords: true
@endyaml
注:取自 steedos 項目中代碼,最終語法與前端框架統一或不統一均可,僅演示思路。
對應的SQL
CREATE TABLE `company` (
`id` INT(10) NOT NULL auto_increment,
`module` INT(10) NOT NULL default '0',
`name` varchar(100) default '',
`phone1` varchar(30) default '',
`phone2` varchar(30) default '',
`fax` varchar(30) default '',
`address1` varchar(50) default '',
`address2` varchar(50) default '',
`city` varchar(30) default '',
`state` varchar(30) default '',
`zip` varchar(11) default '',
`primary_url` varchar(255) default '',
`owner` int(11) NOT NULL default '0',
`description` text,
`type` int(3) NOT NULL DEFAULT '0',
`email` varchar(255),
`custom` LONGTEXT,
PRIMARY KEY (`id`),
KEY `idx_cpy1` (`owner`)
);
以前一直眼饞的是yii框架中的db相關工具,比較專業又易懂,如果能將其抽取出來獨立使用就好了。
DB初始化
實現實體定義中字段類型與相應數據庫引擎的字段映射關系解析和處理后,再借助於框架的數據庫遷移工具,可以比較容易實現DB Shcema的初始化,包括Schema升級.
https://www.kancloud.cn/manual/thinkphp6_0/1118028
UI 渲染
選一前端低代碼框架如百度的 amis,其中自帶CRUD專用組件,可以很簡單的方式實現。
https://baidu.gitee.io/amis/zh-CN/components/crud
按照其CRUD所需返回的相應格式在后端組裝數據格式,返回傳遞給amis即可。
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>amis demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<?php
$base = request()->root();
$root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base;
if ('' != $root) {
$root = '/' . ltrim($root, '/');
}
?>
<link rel="stylesheet" href="<?php echo $root ?>/static/amis-sdk-v1.1.6/sdk.css" />
<link rel="stylesheet" href="<?php echo $root ?>/static/amis-sdk-v1.1.6/helper.css" />
<!-- 從 1.1.0 開始 sdk.css 將不支持 IE 11,如果要支持 IE11 請引用這個 css,並把前面那個刪了 -->
<!-- <link rel="stylesheet" href="sdk-ie11.css" /> -->
<!-- 不過 amis 開發團隊幾乎沒測試過 IE 11 下的效果,所以可能有細節功能用不了,如果發現請報 issue -->
<style>
html,
body,
.app-wrapper {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="root" class="app-wrapper"></div>
<script src="<?php echo $root ?>/static/amis-sdk-v1.1.6/sdk.js"></script>
<script type="text/javascript">
(function () {
let amis = amisRequire('amis/embed');
// 通過替換下面這個配置來生成不同頁面
let amisJSON = {
"type": "page",
"body": [
{
"label": "新增",
"type": "button",
"actionType": "dialog",
"level": "primary",
"className": "m-b-sm",
"dialog": {
"title": "新增表單",
"body": {
"type": "form",
"api": "post:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample",
"controls": [
{
"type": "text",
"name": "engine",
"label": "Engine"
},
{
"type": "text",
"name": "browser",
"label": "Browser"
}
]
}
}
}, {
"type": "crud",
"api": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample",
"syncLocation": false,
"columns": [
{
"name": "id",
"label": "ID"
},
{
"name": "engine",
"label": "Rendering engine"
},
{
"name": "browser",
"label": "Browser"
},
{
"name": "platform",
"label": "Platform(s)"
},
{
"name": "version",
"label": "Engine version"
},
{
"type": "operation",
"label": "操作",
"buttons": [
{
"label": "修改",
"type": "button",
"actionType": "drawer",
"drawer": {
"title": "修改表單",
"body": {
"type": "form",
"initApi": "https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}",
"api": "post:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}",
"controls": [
{
"type": "text",
"name": "engine",
"label": "Engine"
},
{
"type": "text",
"name": "browser",
"label": "Browser"
}
]
}
}
},
{
"label": "刪除",
"type": "button",
"actionType": "ajax",
"level": "danger",
"confirmText": "確認要刪除?",
"api": "delete:https://3xsw4ap8wah59.cfc-execute.bj.baidubce.com/api/amis-mock/sample/${id}"
}
]
}
]
},
]
};
let amisScoped = amis.embed('#root', amisJSON);
})();
</script>
</body>
</html>
注:未完成動態拼接。
進階
權限、安全、代碼緩存、細節完善等。