@Wahson
2017-12-15T07:18:53.000000Z
字数 21119
阅读 2065
前端技术
- 是什么?—— MetaUI是一个基于元数据的前端框架,它是在Polymer2.0之上开发, 能够通过配置元数据,快速开发前端页面。
- 为什么?—— 曾经无数次给公司的运营系统开发不同的数据页面。但是这些界面大部分无外乎一个查询条件栏,接着是查询和重置条件的按钮,接下来是查询结果(以表格显示),然后每一行数据都会定义有不同的操作按钮,对应不同的操作,点击按钮,要么是弹窗对数据进行编辑保存操作,要么直接提交表单。来来回回都是做这些的重复性的工作,更重要是耗时。那么为什么不能设计出一个框架,通过配置元数据的形式,自动就把这种类型的页面自动完成呢?然后把剩下的时间放在差异点的实现上。甚至如果使用足够简单的话,即使是非开发人员也能通过培训,迅速搭建出界面原型,岂不很有意思?
- 怎么样?—— 且继续往下看。
bower install metaui
或者
bower install git://github.com/isuwang/metaui.git
MetaUI是一个基于Polymer的框架,所以你读到这里的时候,我已经默许你了解过Polymer,并且也知道
polymer-cli如何使用。
那么接下来,我们通过polymer-cli脚手架快速搭建一个polymer工程,并开始使用MetaUI。
metaui-demo
mkdir metaui-demo && cd metaui-demo
polymer init
选择polymer-2-application选项,然后按照提示输入。
如图:

bower install --save git://github.com/isuwang/metaui.git
polymer serve
然后复制输出的url到浏览器打开,我这里的url是http://127.0.0.1:8081/components/metaui-demo/。
如图:

你的浏览器中能看到Hello metaui-demo!吗?看不到的话,别问我为什么。
vi src/metaui-demo/metaui-demo.html
引入metaui中的m2-crud组件
<link rel="import" href="../../bower_components/metaui/m2-crud.html">
在<template>中声明<m2-crud>
<m2-crud metadata="{{metadata}}"></m2-crud>
初始化metadata属性
static get properties() {return {metadata: {type: Object,value: {query: {formUrl: "/query.do",fields: [ { name: "username", label: "用户名", editable: true, visible: true } ]},result: {grids: [{ name: "username", label: "用户名" },{ name: "phone", label: "联系电话" },{ name: "address", label: "联系地址" },{ metaType: {type: "ACTION"}, name: "actions", label: "操作" }]}}}};}
刷新一下网页,duang! duang! duang!

Opps!看到了错误警告(我开始慌了~,是不是我又~做错了什么?)--不要慌,弹出层的错误提示暂时无需搭理,因为组件加载后会自动根据metadata中配置的
query.formUrl进行查询,但是你懂的,我们还没有提供接口服务支持。
先来两张图来窥探一下MetaUI的结构:
主页:

弹窗:

MetaUI的结构还是比较简单的,看蓝字上可以找到每个元数据配置对应的界面元素。
MetaUI中提供了
<m2-crud>、<m2-grid>、<m2-crud-query>、<m2-action>、<m2-form>、<m2-field>6个基础元数据组件。
<m2-crud> => DomainMeta/CrudMetaDemo & API Doc
Simple demo:
<m2-crud metadata="{{metadata}}"></m2-crud>metadata = {query: {formUrl: "/query.do",fields: [{name: "username",label: "用户名",editable: true,visible: true}]},actions: {"record-edit": {actionId: "record-edit",actionName: "Edit",operType: 1,formMeta: {formUrl: "/url-to-send-form",confirm: "Sure to submit?",fields: [{name: "name",label: "Name"},{name: "phone",label: "Phone"},],}}},result: {grids: [{name: "username",label: "用户名"},{name: "phone",label: "联系电话"}]}}
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| query | FormMeta | 是 | 表格数据请求表单的元数据 |
| actions | array.<ActionMeta> |
是 | 针对domain上的操作元数据 |
| result | GridMeta | 是 | 表格的元数据 |
<m2-grid> => GridMeta
<m2-grid metadata="{{metadata}}" value="{{value}}"></m2-crud>metadata = {title: "I am the title!",grids: [{ name: "name", label: "Name" },{ name: "phone", label: "Phone" },{ metaType: {type: "ACTION"}, name: "actions", label: "Actions", }],actions: {"action-1": {actionId: "action-1",actionName: "Action1",group:"GROUP",operType: 1,formMeta: {formUrl: "/url-to-send-form",confirm: "Sure to submit?",fields: [{ name: "name", label: "Name" },{ name: "phone", label: "Phone" },]}},"action-2": {actionId: "action-2",actionName: "Action2",group:"GROUP",operType: 2,formMeta: {title: "Dialog Title",formUrl: "/url-to-send-form",confirm: "Sure to submit?",fields: [{ name: "name", label: "Name", visible: true, editable: true },{ name: "phone", label: "Phone", visible: true, editable: true },]}}},style: `.grid-column {font-size: 16px;}`};value = [{name: "foo",phone: "10086",actions: ["action-1", "action-2"]},{name: "foo1",phone: "10086",actions: ["action-1"]}];
| 字段名 | 类型 | Required | 描述 | 描述 | 描述 | 描述 |
|---|---|---|---|---|---|---|
| grids | array.<FieldMeta> |
是 | 表格sdfsdfsdfsdfsfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsfsdfsdfsdfsdfsdfsdfsfdsfssdf单元格元数据 | :-- | :-- | |
| actions | map.<string, ActionMeta> |
是 | 操作集 | :-- | :-- | |
| title | string | 否 | 表格标题 | :-- | :-- | |
| editable | boolean | 否 | 是否可编辑 | :-- | :-- |
<m2-action> => ActionMetaDemo & API Doc
Simple demo:
<m2-action metadata="{{metadata}}" context="{{context}}"></m2-action>metadata = {actionId: "record-add",actionName: "添加记录",operType: 1,formMeta: {formUrl: "/url-to-send-form",confirm: "Sure to submit?",fields: [{name: "name",label:"Name"},{name: "phone",label:"Phone"}]}};context = {name: "foo",phone: "10086"}
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| actionId | string | 是 | 操作的唯一标识 |
| actionName | string | 是 | 操作名,如 “收货” |
| group | string | 否 | 操作组,如 “更多” |
| operType | number | 是 | 操作类型,1:表单,2.弹窗 |
| formMeta | FormMeta | 是 | 表单meta |
| modelSrc | string | 否 | 弹窗的model请求url |
| modelQueryParams | array.<string> |
否 | 弹窗的model请求参数字段名,如 ["id", "name"] |
| customAction | Function | 否 | 自定义按钮动作,声明了该字段后,会忽略默认的按钮动作 |
<m2-form> => FormMetaDemo & API Doc
Simple demo:
<m2-form metadata="{{metadata}}" context="{{context}}"></m2-form>metadata = {formUrl: "/url-to-send-form",title: "我是标题",confirm: "Sure to submit?",fields: [{name: "name",label: "姓名",required: true,editable: true,visible: true},{name: "phone",label: "电话",required: true,editable: true,visible: true}],style: `.container {grid-column-gap: 4%;grid-template-columns: 48% 48%;}.container__item {border: 1px dashed}`,};context = {name: "foo",phone: "10086"}
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| title | string | 是 | 表单头 |
| formUrl | string | 是 | 表单提交的url |
| fields | array. | 是 | 字段元数据 |
| confirm | string | 否 | 表单提交前确认提示 |
| hideSubmitBtn | boolean | 否 | 是否禁用提交按钮 |
| responseHandleAs | string | 否 | 请求返回实体处理:text,json,blob,arrayBuffer, formData |
| sendAsSearchParams | boolean | 否 | 请求是否以 URLSearchParams 格式发送 |
| style | string | 否 | 组件自定义样式 |
<m2-crud-query> => 阉割的FormMeta
<m2-crud-query metadata="{{metadata}}" query-after-reset></m2-crud-query>metadata = {formUrl: "/url-to-send-form",fields: [{name: "name",label: "姓名",required: true,editable: true,visible: true},{name: "phone",label: "电话",required: true,editable: true,visible: true}],noPaging: true}
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| fields | array. | 是 | 字段元数据 |
| noPaging | boolean | 否 | 是否取消分页 |
<m2-field> => FieldMeta
<m2-field metadata="{{metadata}}" context="{{context}}"></m2-field>metadata = {name: "sex",label: "Please select your sex: ",element: "m2-select-list",required: true,candidates: [{ label: "Male", value: "1" },{ label: "Female", value: "2" }],candidateLabel: "label",candidateValue: "value",render: (model) => `<div style="color:blue">${model.name}</div>`};context = {name: "foo",phone: "10086",sex: 1}
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| name | string | 是 | 字段名,如“orderNo” |
| label | string | 是 | 字段名,如“订单号”,缺省值:name字段的值 |
| require | boolean | 否 | 是否必填,缺省值:false |
| mutil | boolean | 否 | 是否多选,缺省值:false |
| editable | boolean | 否 | 是否可编辑,缺省值:false |
| validate | boolean | 否 | 是否需要校验,缺省值:false |
| visible | boolean | 否 | 界面是否可见,仅对表单中的字段有效,缺省值:false |
| disabled | boolean | 否 | 是否禁用组件,缺省值:false |
| element | string | 否 | ui元素,缺省时,提供默认ui:m2-text |
| format | string | 否 | 显示格式,用于日期显示,如“yyyy-MM-dd” |
| length | number | 否 | 字符长度 |
| maxLength | number | 否 | string允许的最大长度 |
| minLength | number | 否 | string允许的最小长度 |
| regexp | string | 否 | 正则匹配 |
| prompt | string | 否 | 校验失败时的提示语,如“请输入备注” |
| min | number | 否 | 数字最小值 |
| max | number | 否 | 数字最大值 |
| placeholder | string | 否 | placeholder |
| candidates | string | 否 | 候选值,下拉选项中的值,json串,如 "[{value: 1,label: '男'}, {value: 2,label: '女'}]" |
| candidateLabel | string | 否 | 组件显示字段名,针对下拉组件、radio-group组件等,“label” |
| candidateValue | string | 否 | 组件值字段名,针对下拉组件、radio-group组件等,“value” |
| srcKey | string | 否 | 当一个字段的值是来源于另外一个字段,则通过srcKey配置值来源字段的字段名,如 "id" |
| metaType | MetaType或string | 否 | 元数据类型,如果缺少metaType,默认渲染的组件是;在表格元数据中定义操作列时,可以省略操作的元数据,如 metaType:"ACTION" See h2-grid |
| render | Function(Closure) | 否 | 只读模式下会执行此闭包生成显示内容 |
MetaType定义
| 字段名 | 类型 | Required | 描述 |
|---|---|---|---|
| type | string | 是 | 数据类型:BOOLEAN、BYTE、SHORT、INTEGER、LONG、DOUBLE、STRING、LIST、MAP、SET、STRUCT、ACTION |
| metadata | object | 是 | 如果type是list,map,set中的一种,则metadata是这些嵌套类型的元数据 |
<m2-form>以及<m2-crud-query>中提供了样式扩展能力,你可以通过配置metadata.style属性了传入自定义的样式。
例如:
metadata.style = `.container {grid-column-gap: 4%;grid-template-columns: 48% 48%;}.container__item {border: 1px dashed}`;
这些组件默认使用CSS Grid Layout布局,你可以根据需要在
.container类选择器中修改。你可以通过选择器,对<m2-form>和<m2-crud-query>的shadow dom中的任何元素进行样式修改。
<m2-field>组件在只读模式下,会查找metadata.render,如果该字段有值且是一个函数,组件内部会执行这个函数并传入组件的context属性值作为参数,并把函数返回值作为显示内容,渲染到shadow dom中。render函数除了返回字符串,也可以返回一个HTMLElement。
例如:
// return stringmetadata.render = ({name}) => `<div style="color:blue">${name}</div>`;// return HTMLElementmetadata.render = ({id, taskNo}) => {const aTag = document.createElement("a");aTag.href = "javascript:void(0)";aTag.onclick = (e) => {// do something here};aTag.innerHTML = taskNo;return aTag;}
<m2-action>默认的点击操作是弹窗或者提交表单,但是你可以metadata.customAction字段传入函数来覆盖默认的行为。customAction函数执行时会传入当前m2-action中的context属性值作为参数。
metadata.customAction = (context) => {console.log(context.name);}
m2-crud-query组件中定义了一个slot插槽,允许你通过light dom的形式定义额外的查询条件。
<m2-crud-query metadata="{{metadata}}"><div slot="ext-condition"><o-status-select label="状态" on-status-selected="onStatusSelect"></o-status-select></div></m2-crud-query>onStatusSelect(e) {e.target.dispatchEvent(new CustomEvent('query-domain',{// e.detail的值是组件中返回的状态值,比如 {status: 1}detail: e.detail,composed: true,bubbles: true}));}
<m2-crud-query>内部监听了query-domain事件,并且会把通过e.detail传递过来的字段作为请求参数的一部分,发起查询请求。另外需要注意dispatch事件的元素必须是绑定到slot中的元素(上例中可以是div[slot=ext-condition]或o-status-select)。
m2-field组件只是一个壳,内部会首先判断metadata.element是否指定了组件名,如果有,创建改组件,并添加到shadow dom 中,否则判断metadata.metaType的值:
switch (metaType) {case "ACTION":return "m2-action";case "FORM":return "m2-form";case "GRID":return "m2-grid";default:return "m2-text";}
匹配不上时默认渲染出
<m2-text>。
你可以定义你的组件,并在metadata.element中指定该组件名。
自定义组件时,你需要遵循以下规定:
1. 组件需要提供value属性,来指定组件的值。而且value属性需要声明notify: true。
2. 组件需要提供validate()方法,返回一个布尔值,用来检测组件中的值是否合法。
3. 你可以直接继承 M2WidgetBase父类(/bower_components/metaui/widgets/m2-widget-base.html, M2WidgetBase提供了value的定义以及validate()的默认实现。
参考例子:
<link rel="import" href="../bower_components/metaui/widgets/m2-widget-base.html"><link rel="import" href="../bower_components/paper-input/paper-input.html"><dom-module id="your-widget-name"><template><paper-inputid="input"always-float-labelvalue="{{ value }}"label="[[ metadata.label ]]"auto-validaterequired="[[ metadata.required]]"error-message="[[ metadata.prompt ]]"min="[[ metadata.min ]]"minlength="[[ metadata.minLength ]]"max="[[ metadata.max ]]"maxlength="[[ metadata.maxLength ]]"pattern="[[ metadata.regexp ]]"readonly="[[ !metadata.editable ]]"></paper-input></template><script>class YourWidget extends M2WidgetBase {static get is() {return "your-widget-name";}/*** Before a form being submitted, validate() will be called automatically to check if the value is legal.* You can override it if needed.*/validate() {// return this.$.input.validate();return true;}// more methods here}window.customElements.define(YourWidget.is, YourWidget);</script></dom-module>
m2-field中的context属性值也会自动绑定到自定义组件内部。
在
m2-form组件像是一个m2-field容器,所有的m2-field会共享m2-form中context属性值,任何一个m2-field中的value发生变化,会同步到context属性上,而且在任何一个m2-field中你可以监听另外一个m2-field值的变化。看下面例子:
metadata = {formUrl: "/url-to-send-form",title: "Form Title",confirm: "Sure to submit?",fields: [{name: "bar",label: "bar",required: true,editable: true,visible: true},{name: "foo",label: "foo",required: true,editable: true,visible: true,onAttached: (fieldEle, formEle) => {formEle.watch(['bar'], ({bar}, whoChange) => {console.log(whoChange);fieldEle.style = bar.length > 4 ?'background:red;' : 'background:green;'});}}]}
重点看看metadata.fields[1].onAttached:
onAttached: (fieldEle, formEle) => {/*** ['bar'] 订阅bar字段的变化* ({bar}, whoChange) => {...} 回调函数,入参是m2-field对应的context, 以及值发生变化的字段名。*/formEle.watch(['bar'], ({bar}, whoChange) => {console.log(whoChange); // "bar"fieldEle.style = bar.length > 4 ?'background:red;' : 'background:green;'});}
这里在
foo的字段上,订阅了bar字段值的变化。当foo字段对应的m2-field被依附到m2-form的shadow dom时,onAttached函数会被调用来初始化订阅,通过m2-form内部的watch()方法来完成订阅。bar字段的值一旦变化,传入watch()方法的回调就会执行。因此在此回调函数里面,你可以定义实现一些功能,比如根据bar值来调整foo字段所在的m2-field的样式,等等。
isuwang-soa各位看官已经见识到单独使用
MetaUI的威力,但是随着开发的界面越来越多,很多重复性的配置难免会带来一些枯燥感。接下来,我们结合 isuwang-soa,江湖人称dapeng框架,以及meta-constructor,带来更快速的开发体验。
isuwang-soa是一个SOA框架,具体的使用不在这里介绍,请移步https://github.com/isuwang/isuwang-soa。
接下来,我们开始编写IDL(接口定义语言),这个过程我们通过编写Thrift文件来完成。
先看一个简单的例子:
TaskAdminService {/*** 根据条件查询任务*/task_domain.TTaskResponse findTasks(task_domain.TTaskRequest request)(m.form.url="/task/taskadmin/findTasks.do")/*** 新建任务*/void createTask(task_domain.TCreateTaskRequest request)(m.form.url="/task/taskadmin/createTask.do",m.type="domain-action",m.id="task_add",m.name="新增任务", m.operType="2", m.form.title="新增任务")/*** 修改任务*/void updateTask(task_domain.TUpdateTaskRequest request)(m.form.url="/task/taskadmin/updateTask.do",m.type="entity-action",m.id="task_update",m.name="编辑任务", m.operType="2", m.form.title="编辑任务")/*** 删除任务*/void deleteTask(i32 id)(m.form.url="/task/taskadmin/deleteTask.do",m.type="entity-action",m.id="task_delete",m.name="删除", m.operType="1", m.form.title="删除",m.form.confirm="确定删除?",m.form.sendAsSearchParams = "true")}
dapeng内嵌了对thrift文件的解析,通过dapeng-code-generator插件可以基于thrift文件生成接口以及实体的代码,另外还会生成一组描述每个服务以及服务相关实体元信息的xml文件,诸如(m.form.url="/task/taskadmin/findTasks.do")这类的注释也会解析到xml文件中。这个xml文件,将会是MetaUI的数据来源,我们稍后会介绍。
现在先来介绍一下对thrift注释所做的扩展:
| 注释key | 对应元数据字段 | 取值 | 描述 |
|---|---|---|---|
| m.type | domain-action, entity-action | domain-action:domain作用域的操作,比如添加记录,entity-action:实体作用域的操作,比如删除或修改一个记录 | |
| m.id | ActionMeta.actionId | 默认值:方法名称 | 操作id,建议跟权限名相同 |
| m.name | ActionMeta.actionName | 操作名称 | |
| m.operType | ActionMeta.operType | "1" 或 "2" | "1":直接表单提交,"2":弹窗 |
| m.modelSrc | ActionMeta.modelSrc | ||
| m.group | ActionMeta.group | 操作分组 | |
| m.form.confirm | FormMeta.confirm | 表单提交前的确认提示 | |
| m.form.title | FormMeta.title | 表单页面的标题 | |
| m.form.url | FormMeta.url | 表单提交的url,与前端controller对应 | |
| m.form.hideSubmitBtn | Form.hideSubmitBtn | "true" or "false",缺省值:"false" | 表单中是否隐藏默认的保存按钮 |
| m.form.sendAsSearchParams | FormMeta.sendAsSearchParams | "true" or "false",缺省值:"false" | 请求是否以 URLSearchParams 格式发送 |
看一个Struct上添加注释的例子:
struct TTaskRequest {1: base_model.TPageRequest pageRequest2: optional string taskNo (m.label="任务编号", ... ),3: optional i32 taskId (m.srcKey="id", m.visible="false",...)4: ...}
在实体字段上配置的注释,key跟FieleMeta上的字段是完全一致的。
你也可以直接在接口入参后面添加注释,下面是一个简单的例子,但是这个例子有点为了举例而举例的嫌疑,因为直接把参数名改为 id 就可以避免 m.srcKey="id" 的配置了。
TaskAdminService {// ...void deleteTask(i32 taskId (m.srcKey="id"))(m.form.url="...", ...)// ...}
刚才我们讲到服务的元数据会解析到一个xml文件中,该文件以
服务的namespace+名字命名,如com.isuwang.soa.task.service.TaskAdminService.xml。
我们依稀记得MetaUI元数据的配置字段可远不止这些,但是先不着急,通过下图,我希望能够让你对MetaUI跟Thrift配置的关系有一个更清晰的认识。
!图在这
实体(Struct)的每个字段对应的是一个<m2-field/>,FieldMeta的信息会在数据库中配置。
DROP DATABASE IF EXISTS metadb;CREATE DATABASE IF NOT EXISTS metadb DEFAULT CHARSET utf8 COLLATE utf8_general_ci;DROP TABLE IF EXISTS `metadb`.`fields`;CREATE TABLE `fields` (`id` int(11) NOT NULL AUTO_INCREMENT,`struct_name` varchar(200) NOT NULL COMMENT '实体名',`name` varchar(50) NOT NULL COMMENT '字段名,如"orderNo"',`element` varchar(50) DEFAULT NULL COMMENT '替换元素,如 "paper-input" ',`label` varchar(50) NOT NULL COMMENT '字段名,如"订单号"',`required` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否必填 1: 是 0:否',`multi` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否多选 1: 是 0:否',`format` varchar(50) DEFAULT NULL COMMENT '显示格式,用于日期显示,如“yyyy-MM-dd”',`editable` smallint(1) NOT NULL DEFAULT '0' COMMENT '是否可编辑 1: 是 0:否',`validate` smallint(1) NOT NULL DEFAULT '0' COMMENT '前是否需要校验, 1: 是 0:否',`length` int(11) DEFAULT NULL COMMENT '字符允许长度',`max_length` int(11) DEFAULT NULL COMMENT '允许最大长度',`min_length` int(11) DEFAULT NULL COMMENT '允许最小长度',`regexp` varchar(50) DEFAULT NULL COMMENT '正则匹配',`prompt` varchar(50) DEFAULT NULL COMMENT '校验失败时的提示语,如“请输入备注”',`min` int(11) DEFAULT NULL COMMENT '数字最小值',`max` int(11) DEFAULT NULL COMMENT '数字最大值',`candidates` varchar(1000) DEFAULT NULL COMMENT '下拉选项中的值,json串,如 [{value:1,label:"男"},{value:2,label:"女"}]',`candidate_label` varchar(50) DEFAULT NULL COMMENT '组件显示字段名,针对下拉组件、radio-group组件等,“label”',`candidate_value` varchar(50) DEFAULT NULL COMMENT '组件值字段名,针对下拉组件、radio-group组件等,“value”',`placeholder` varchar(50) DEFAULT NULL COMMENT 'placeholder',`src_key` varchar(50) DEFAULT NULL COMMENT '源字段名',`visible` smallint(1) NOT NULL DEFAULT '1' COMMENT '界面是否可见 1: 是 0:否',`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`created_by` int(11) DEFAULT NULL,`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`updated_by` int(11) DEFAULT NULL,`disabled` int(11) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `field_unique` (`struct_name`,`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='字段元数据';
看一个插入语句的例子:
INSERT INTO metadb.fields (struct_name, name, element, label, required, multi, format, editable, validate, length, max_length, min_length, `regexp`, prompt, min, max, candidates, candidate_label, candidate_value, placeholder, src_key, visible, created_at, created_by, updated_at, updated_by, disabled) VALUES ('com.isuwang.soa.task.domain.TTaskRequest', 'taskNo', 'm2-task-picker', '任务编号', 0, 0, null, 1, 0, null, null, null, null, null, null, null, null, null, null, null, null, 1, '2017-11-09 17:51:03', null, '2017-11-23 18:08:04', null, null);
你应该注意到了,thrift文件和数据库中都可以配置FieldMeta字段的值,比如在上面例子中taskId配置了
m.visible="false",同时metadb.fields表中的name="taskId"的记录中visible字段的值是1 (会转换成true),那么谁的优先级更高呢?这取决于你如何解析他们。MetaUI提供了一个工具,在utils目录下的<meta-constructor />,它把thrift跟数据库中定义的元数据,组装成MetaUI组件认识的结构。<meta-constructor />处理元数据时,thrift上定义的FieldMeta字段值优先级更高。待会会详细介绍<meta-constructor />。
dapeng-metaui-servlet 来部署元数据查询服务
dapeng-metaui-servlet是一个简单的servlet,servlet会从请求参数中读取serviceName和version,然后通过反射的方式查找到服务元数据的xml文件,解析xml文件,从中获取到跟改service相关的实体信息,到metadb.fields表中查询相关的元数据记录,xml会装换成json,跟元数据记录一并返回。
<dependency><groupId>com.isuwang</groupId><artifactId>dapeng-metaui-servlet</artifactId><version>1.0.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.25</version></dependency>
web.xml 中配置servlet
<servlet><servlet-name>metadataServlet</servlet-name><servlet-class>com.isuwang.dapeng.metadata.servlet.MetadataServlet</servlet-class><init-param><param-name>driverClassName</param-name><param-value>com.mysql.jdbc.Driver</param-value></init-param></servlet><servlet-mapping><servlet-name>metadataServlet</servlet-name><url-pattern>/dapeng/metadata</url-pattern></servlet-mapping>
init.properties 配置文件,写入以下内容并修改数据库配置
DB_METADB_URL=jdbc:mysql://127.0.0.1:3306/metadb?useUnicode=true&characterEncoding=utf8DB_METADB_USER=your-db-usernameDB_METADB_PASSWD=your-db-password
也可以设置系统的环境变量,如:
![]()
设置环境变量后,会优先读取环境变量的值。
这里是一个请求的例子:
fetch("/dapeng/metadata?serviceName=com.isuwang.soa.task.service.TaskAdminService&version=1.0.0", {method:"POST",credentials:"include"})
返回的结果:
{fieldsInStruct: {com.isuwang.soa.task.domain.TTaskRequest: [...],...},serviceMeta: {enumDefinitions: [],meta: {version: "1.0.0", timeout: 30000},methods: [...],name: "TaskAdminService",namespace: "com.isuwang.soa.task.service",structDefinitions: [...]}}
<meta-constructor />
<meta-constructor>是一个工具组件,它会自动请求dapeng/metadata获取元数据,并把返回的数据进行重新组装成<m2-* />组件能读懂的结构,然后通过metadata字段返回。看下面的例子:
<link rel="import" href="../bower_components/polymer/polymer-element.html"><link rel="import" href="../bower_components/metaui/utils/meta-constructor.html"><link rel="import" href="../bower_components/metaui/m2-crud.html"><dom-module id="task-admin-index"><template><meta-constructor src="/dapeng/metadata"metadata="{{ metadata }}"option="[[ option ]]"on-return="[[ onReturn() ]]"></meta-constructor><m2-crud class="main"id="crud"metadata="[[ metadata ]]"key-for-page-result="pageResponse.results"result-decorator="[[ _getResultChangeHandler() ]]"></m2-crud></template><script>class TaskAdminIndex extends Polymer.Element {static get is() {return "task-admin-index";}static get properties() {return {metadata: {type: Object},option: {type: Object,value: function () {return {serviceName: "com.isuwang.soa.task.service.TaskAdminService",version: "1.0.0",methodName: "findTasks"};}}};}_getResultChangeHandler() {return (result) => {// do something with resultreturn result;}}onReturn() {return (metadata) => {// do something with metadatareturn metadata;}}}window.customElements.define(TaskAdminIndex.is, TaskAdminIndex);</script></dom-module>
首先强调一点,我这里讲的mock不是大家所熟知的 mockjs(当然未来可能会集成进来),请大家注意不要混淆。mock使用一种对代码尽可能小侵入的方式(两处侵入:1. head标签中的代码库引入,2. URI侵入),实现请求的模拟。
接下来看看mock如何使用:
<head>...<script type="text/javascript" src="../bower_components/h2-elements/utils/mock_setup.js"></script>...</head>
For Example:
import {data} from "./data.js";MockDataPool.when("POST", "/path/to/index.do").withExpectedHeader("Content-Type", "application/json;charset=utf-8").withExpectedHeader("Cache-Control", "no-cache").responseWith({status: 200, body: JSON.stringify(data)});
For example:
http://127.0.0.1:8000/components/h2-elements/demo/h2-fetch/index.html?mock=/your/path/to/mockData.js,
/your/path/to/index.html 到 mockData.js 的相对路径。比如,如果index.html和mockData.js在一个相同的目录下,则mock的值可以是index.html?mock=mockData.js或者index.html?mock=./mockData.js。
MockDataPool.match( request: Request|Requestable-object ): Response|Responsable-object// democonst response = MockDataPool.match({url: "/path/to/index.do",method: "POST",headers: {"Content-Type": "application/json;charset=utf-8","Cache-Control", "no-cache"}});
“如果一个动物看起来像鸭子,叫起来也像鸭子,那么它就是一个鸭子。” -- 鲁迅
Requestable-object就是一个看起来像Request的对象,它跟Request有相似的字段跟行为。
Responsable-object同理。
建议你直接使用 h2-elements/h2-fetch.html 组件,里面封装了MockDataPool的使用。
用法1:
<h2-fetch request="{{request}}" response="{{response}}" error="{{error}}"></h2-fetch>request = {url: "/path/to/index.do",method: "POST",headers: {"Cache-Control": "no-cache"}}
用法2:
const request = {url: "/path/to/index.do",method: "POST",headers: {"Cache-Control": "no-cache"}};new H2Fetch().fetchIt(request).then(res => res.json()).then(console.log).catch(console.log);
你仍然需要执行 步骤1 和 步骤2,因为它们是使用mock的前提。下面是 <h2-fetch/>中使用MockDataPool的代码:
if (window.__mockEnabled) { // mock开关标志,mock_setup.js 初始化时会设置该值const matchedRes = MockDataPool.match(requset);if(matchedRes) {return Promise.resolve(new Response(matchedRes.body, matchedRes));}}return window.fetch(collectedReq);
<h2-fetch />会首先使用match方法去匹配,匹配上了则返回封装的Response,否则发起真实的fetch请求。