前言
之前的一篇文章,通过传统的Page with Flow Logic形式去实现了基于Vue的One Page Application。这种做法其实存在一些设计上的问题,前端页面交互层还好说,但后端服务层明显不符合RESTful设计风格,一个API居然还有View的代码?(二当家:帮主哇,我们不用OO,光去看人家的Page with Flow Logic,被同行知道了会笑我们变态的)咳咳,有鉴于此,恰好近日因肺炎推迟上班,被禁足在家,所以特别补全一个OO方式实现的例子。
注:本篇仍将实现一个查询库存的例子,但项目的结构、前端HTML页面代码以及SAP后端代码很多重要细节都与上一篇文章有所不同,请留意。
与传统BSP方式相比,OO实现BSP的方式有两点好处
- 前后端逻辑可以完全分离,符合MVC,代码更易阅读和维护
- 可以独立于页面实现类似RESTful风格的API,显得更专业
但受制于NetWeaver平台,BSP本身也还有如下缺点
- 不能完全实现MVC,Model在BSP里面其实不存在,直接Open SQL完事。skr…skr…
- 尽管能做成RESTful API,但分发还得通过SICF,一个服务一个Application。另外,个人猜测,可能并非所有的HTTP方法BSP都支持,但这个无伤大雅,一般HTTP POST日常也足够了。
GKD 搞快点
Step I 资源和业务分开
在上一篇中,我们将应用依赖的js和css文件都上传到了应用本身的目录下,但它们其实是可以被整个Package下的BSP应用共享的,如果你其它的BSP应用也引用了这些文件,那么类库的版本、前端样式能够统一,利于代码复用和后期维护,同时浏览器当发现资源来自同一个url时,不会去重复下载,节约带宽资源。基于这种种好处,也为了避免这些MIME中的文件被业务代码污染,笔者建议单独创建一个BSP应用,专门用于存储这些常用类库。见下:
为确保允许匿名访问,请注意去掉Application中缺省勾选的XSRF Protection。下面的YBSP_OO_DEMO也是同样的,不再赘述。
将这些公用的类库放到一个归口的应用后,我们就可以创建一个空的业务BSP应用,YBSP_OO_DEMO:
Step II 通过Page Fragments复用html
正如之前我们的BSP应用,Buefy控制前端控件样式的展现,Vue.js扮演Controller的角色来做渲染和事件处理,axios调用HTTP POST异步访问SAP Server。这三板斧是我们每次都会用到的,所以必定在每个应用的html header中引用到它们。虽说我们的例子目前只涉及一个前端页面,但实际生产环境中,随着业务复杂,一个应用中可能还会包含多个页面。为了减少重复的代码,我们把这个通用的html header代码存放到Page Fragments中,以利于后续的项目使用。
_header.htm的代码见下,代码中备注的信息请留意:
<%@page language="ABAP"%>
<!DOCTYPE html> <!--解决Safari中Buefy移动模式mobile card显示不正确的问题-->
<html>
<head>
<title>Inventory Report</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0"><!--页面自适应移动端-->
<meta name="format-detection" content="telephone=no"><!--防止纯数字被识别为手机号码-->
<meta name="apple-mobile-web-app-capable" content="yes" ><!--Safari识别为Web应用-->
<meta name="mobile-web-app-capable" content="yes"><!--Chrome识别为Web应用-->
<link rel="icon" href="../YBSP_LIBRARY/monkey.png" type="image/x-icon"/> <!--页面图标-->
<link rel="stylesheet" href="../YBSP_LIBRARY/buefy.min.css" /> <!--buefy-->
<script src="../YBSP_LIBRARY/vue.js"></script> <!--vue.js-->
<script src="../YBSP_LIBRARY/axios.min.js"></script> <!--axios-->
</head>
<body>
<script src="../YBSP_LIBRARY/buefy.min.js"></script> <!--buefy-->
<script src="../YBSP_LIBRARY/all.min.js"></script> <!--icon相关-->
<div id="app"> <!--app 根节点-->
Buefy 支持两种icon类型,缺省是Materail Design Icon,而另一种是FontAwesome。最大的区别在于icon的实现机制不同。使用Material Design Icon,会有访问SAP服务资源导致弹出账号密码验证的问题,故放弃。使用FontAwesome,须要引入其js文件,即上面的
all.min.js
。这个文件,你可以在FontAwesome在Github上的代码库中找到并下载下载:https://github.com/FortAwesome/Font-Awesome
Step III 创建查询页面
不同于传统方式,OO的BSP,我们创建的View是不带Flow Logic的。
main.htm的代码见下,它在头部引用了我们的Page Fragments。
<%@page language="abap"%>
<%@include file="_header.htm" %>
<section class="section">
<b-field label="Material Number" :label-position="labelPosition">
<b-input maxlength="18" v-model="matnr"></b-input>
</b-field>
<b-field label="Plant" :label-position="labelPosition">
<b-input type="search" maxlength="4" v-model="werks" @keyup.enter.native="search"></b-input>
<p class="control">
<b-button class="button is-primary" @click="search">Search</b-button>
</p>
</b-field>
<b-table
:data="tab"
:bordered="false"
:striped="true"
:narrowed="false"
:hoverable="false"
:loading="false"
:focusable="false"
:icon-pack="icon_pack"
:mobile-cards="true"
:paginated="true"
:per-page="20">
<template slot-scope="tab">
<b-table-column field="id" label="ID" sortable numeric>
{{ tab.row.id }}
</b-table-column>
<b-table-column field="matnr" label="Material" sortable>
{{ tab.row.matnr }}
</b-table-column>
<b-table-column field="maktx" label="Description">
{{ tab.row.maktx }}
</b-table-column>
<b-table-column field="lgort" label="Location" sortable>
{{ tab.row.lgort }}
</b-table-column>
<b-table-column field="labst" label="Quantity" centered sortable numeric>
<span class="tag is-success">
{{ tab.row.labst }}
</span>
</b-table-column>
</template>
<template slot="empty">
<section class="section">
<div class="content has-text-grey has-text-centered">
<b>No Data</b>
</div>
</section>
</template>
</b-table>
</section>
<b-loading :is-full-page="true" :active.sync="isLoading" :can-cancel="false"></b-loading>
</div>
<script>
const restService = '_main.do';
var app = new Vue({
el: "#app",
data:{
viewName: "Inventory Report",
labelPosition: 'on-border',
isLoading: false,
matnr: '',
werks: '',
tab: [],
hide_hd: true,
icon_pack: 'fa'
},
methods:{
search: function(){
axios.post(restService,{
data:{
'matnr': this.matnr,
'werks': this.werks
}
}).then((response) =>{
try{
if(response.data.msg.msgty == "E"){
this.$buefy.toast.open({
duration: 5000,
message: response.data.msg.msg,
position: 'is-bottom',
type: 'is-danger'
});
}
else if(response.data.msg.msgty == "S"){
this.tab = eval(response.data.tab);
}
else{
this.$buefy.toast.open({
duration: 5000,
message: 'Unknow Exceptions',
position: 'is-bottom',
type: 'is-danger'
});
}
}
catch(err)
{
this.$buefy.toast.open({
duration: 5000,
message: 'Unknow Exceptions',
position: 'is-bottom',
type: 'is-danger'
});
}
finally{
this.isLoading = false;
}
});
this.isLoading = true;
this.hide_hd = false;
}
}
});
</script>
</body>
</html>
上面的代码中icon-pack使用fa即是使用FontAwesome中的图标,以免Buefy缺省使用Material Design中的icon导致界面图标无法显示。
OO中的View是不能直接访问的,必须通过Controller去调用它。所以我们必须新建一个Controller,在SE24
中我们创建一个类YCL_BSP_DEMO,它继承了一个BSP标准父类CL_BSP_CONTROLLER2
:
我们须要实现它的方法DO_REQUEST
,当它接收到HTTP GET请求时,返回main.htm:
method DO_REQUEST.
*CALL METHOD SUPER->DO_REQUEST
* .
DATA: MYVIEW TYPE REF TO IF_BSP_PAGE.
MYVIEW = CREATE_VIEW( VIEW_NAME = 'main.htm' ).
CALL_VIEW( MYVIEW ).
endmethod.
再次回到SE80
控制台,我们新建一个Controller YBSP_OO_DEMO:
Controller Class使用我们刚才创建的类并保存激活:
接着在SICF
中设置好登陆页面时使用的公共账号和密码:
回到SE80
控制台中,在YBSP_OO_DEMO.do
上右击并选择Test:
如无意外,你将看到熟悉的查询界面:
Step IV 创建RESTful服务
完成了前端展现页面的实现后,我们接着须要实现后端查询的功能。就像前面一早提过的,API不须要页面,它只是个接口,所以这次我们只须要新建一个Controller来实现查询功能就行了,和上面一样,我们先在SE24
中新建一个继承了CL_BSP_CONTROLLER2
的类,YCL_BSP_DEMOS:
同样实现DO_REQUEST
方法如下:
METHOD DO_REQUEST.
*CALL METHOD SUPER->DO_REQUEST
* .
DATA:LS_PARA TYPE TY_INPUT,
LV_STR TYPE STRING.
DATA:BEGIN OF RESP,
MSG TYPE TY_MSG,
TAB TYPE TY_T_TAB,
END OF RESP.
CLEAR:LS_PARA,
LV_STR,
RESP.
LV_STR = RUNTIME->SERVER->REQUEST->GET_CDATA( ).
CALL METHOD CL_FDT_JSON=>JSON_TO_DATA
EXPORTING
IV_JSON = LV_STR
CHANGING
CA_DATA = LS_PARA.
TRANSLATE:LS_PARA-DATA-MATNR TO UPPER CASE,
LS_PARA-DATA-WERKS TO UPPER CASE.
IF LS_PARA-DATA-MATNR IS INITIAL.
RESP-MSG-MSGTY = 'E'.
RESP-MSG-MSG = 'Please input a Materail Number!'.
ELSEIF LS_PARA-DATA-WERKS IS INITIAL.
RESP-MSG-MSGTY = 'E'.
RESP-MSG-MSG = 'Please input a Plant Number!'.
ELSE.
RESP-MSG-MSGTY = 'S'.
SELECT A~MATNR
A~LGORT
A~LABST
INTO CORRESPONDING FIELDS OF TABLE RESP-TAB
FROM MARD AS A
WHERE A~MATNR LIKE LS_PARA-DATA-MATNR
AND A~WERKS EQ LS_PARA-DATA-WERKS
AND A~LABST GT 0.
IF RESP-TAB[] IS NOT INITIAL.
SELECT MATNR,MAKTX
INTO TABLE @DATA(LT_MAKTX)
FROM MAKT
FOR ALL ENTRIES IN @RESP-TAB
WHERE MATNR EQ @RESP-TAB-MATNR.
SORT:LT_MAKTX BY MATNR,
RESP-TAB BY MATNR LGORT.
LOOP AT RESP-TAB ASSIGNING FIELD-SYMBOL(<FS_TAB>).
<FS_TAB>-ID = SY-TABIX.
READ TABLE LT_MAKTX ASSIGNING FIELD-SYMBOL(<FS_MAKTX>)
WITH KEY MATNR = <FS_TAB>-MATNR
BINARY SEARCH.
IF SY-SUBRC EQ 0.
<FS_TAB>-MAKTX = <FS_MAKTX>-MAKTX.
ENDIF.
ENDLOOP.
ENDIF.
ENDIF.
LV_STR = /UI2/CL_JSON=>SERIALIZE( DATA = RESP COMPRESS = ABAP_TRUE PRETTY_NAME = /UI2/CL_JSON=>PRETTY_MODE-CAMEL_CASE ).
RUNTIME->SERVER->RESPONSE->SET_HEADER_FIELD( NAME = 'Content-Type' VALUE = 'application/json;charset=utf-8' ).
RUNTIME->SERVER->RESPONSE->SET_CDATA( DATA = LV_STR ).
ENDMETHOD.
上面的代码和之前一篇文章的代码大同小异,但有几点须要注意:
- 都须要通过一个结构去解析传过来的JSON格式Request
- 都须要在返回的HTTP header中指明Response的类型格式和编码
- 非常重要的一点,使用/UI2/CL_JSON这个类去将表结构转化为了JSON格式,而在上篇文章中,我们使用的是CL_TREX_JSON_SERIALIZER。两者的区别在于,/UI2/CL_JSON不会将数字转化成文本格式返回,这样前端Buefy的Table控件才能正常的对它们排序,而后者则是无脑全部以文本形式返回,这点使用时须留意。
另用到的TYPE的定义:
CLASS YCL_BSP_DEMOS DEFINITION
PUBLIC
INHERITING FROM CL_BSP_CONTROLLER2
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
TYPES:BEGIN OF TY_DATA,
MATNR TYPE MATNR,
WERKS TYPE WERKS_D,
END OF TY_DATA,
BEGIN OF TY_INPUT,
DATA TYPE TY_DATA,
END OF TY_INPUT,
BEGIN OF TY_TAB,
ID TYPE I,
MATNR TYPE MATNR,
MAKTX TYPE MAKTX,
LGORT TYPE LGORT_D,
LABST TYPE LABST,
END OF TY_TAB,
TY_T_TAB TYPE STANDARD TABLE OF TY_TAB,
BEGIN OF TY_MSG,
MSGTY TYPE C LENGTH 1, "S/E.
MSG TYPE CHAR50, "Message.
END OF TY_MSG.
METHODS DO_REQUEST
REDEFINITION .
最后,让我们回到BSP应用中,创建一个基于YCL_BSP_DEMOS类的Controller。细心的读者可能已经猜到这个Controller的命名,在上面的html代码中,已经提示了这个Controller _main.do
:
至此,我们已经完成了从前端页面到后端服务的搭建,回到浏览器中,输入查询条件并执行,如数据没有问题,即宣告BSP开发成功:
附
YBSP_OO_DEMO 最终目录结构:
来源:CSDN
作者:ABAP匠人
链接:https://blog.csdn.net/Ash_Petis/article/details/104113450