hap開始之旅


title: hap開始之旅
date: 2019-08-19
categories: 后端
tags:

  • hap

寫在前面:hap是什么?貼張圖:

hap架構(gòu).png

其實用的就是SSM框架,實現(xiàn)了組織管理、權(quán)限管理等咕痛。學(xué)習(xí)hap的目的是一些小項目用不到hzero這么大的架構(gòu)咽筋。

開發(fā)環(huán)境

主要進行一些配置,如git碍脏、maven、mysql等的配置稍算,環(huán)境版本:

  • JDK 1.8+
  • redis 3.0+
  • maven 3.3+
  • Tomcat 7+
  • MySQL 5.6+

新建數(shù)據(jù)庫

建表:如果手寫建表語句典尾,注意其中的schema,別手快寫成了scheam

create schema hap_dev default character set utf8;
create schema hap_prod default character set utf8;

創(chuàng)建用戶hap_dev糊探,設(shè)置密碼為hap_dev

CREATE USER hap_dev@'%' IDENTIFIED BY 'hap_dev';
CREATE USER hap_dev@'localhost' IDENTIFIED BY 'hap_dev';

將hap_dev和hap_prod的權(quán)限全部賦予用戶hap

GRANT ALL PRIVILEGES ON hap_dev.* TO hap_dev@'%';
GRANT ALL PRIVILEGES ON hap_dev.* TO hap_dev@'localhost';
GRANT ALL PRIVILEGES ON hap_prod.* TO hap_dev@'%';
GRANT ALL PRIVILEGES ON hap_prod.* TO hap_dev@'localhost';

flush privileges;

數(shù)據(jù)源配置

hap默認使用JNDI來配置數(shù)據(jù)源钾埂。數(shù)據(jù)源的配置獨立于項目配置河闰。默認的JNDI name為jdbc/hap_dev,tomcat需要配置context.xml勃教。

嗯淤击,之前學(xué)的ssm中沒有了解過JNDI是個什么東東。JNDI故源,我老是寫成JDNI...哈哈污抬;JNDI 全稱:java naming and directory interface,如果是JDNI绳军,那就是:java directory and naming interface好像也差不多哦印机。

JNDI的作用是完成從服務(wù)器中查詢數(shù)據(jù)源,和JDBC的使用不太相同门驾。JNDI的作用幫我們完成了JDBC獲取connection對象的步驟射赛。JDBC獲取的是一個連接對象,但是JNDI幫我們獲取一個數(shù)據(jù)池奶是,數(shù)據(jù)池中有多個連接楣责,我們可以獲取其中任意一個連接,所以JNDI在完成數(shù)據(jù)庫連接時能夠提供數(shù)據(jù)庫連接一個限定等配置。

話不多說,如何配置爵政?先整一個tomcat7版本好吧,進入conf目錄下的context.xml文件沮趣,在<\Context>標簽中配置:把url修改為自己的地址。

<Resource 
      auth="Container" 
      driverClassName="com.mysql.jdbc.Driver" 
      url="jdbc:mysql://192.168.15.78:3306/hap_dev"
      name="jdbc/hap_dev" 
      type="javax.sql.DataSource" 
      username="hap_dev" 
      password="hap_dev"/>

新建項目

先了解下maven命令的標簽:

  • groupId 本項目代號坷随,比如漢得的 BI 產(chǎn)品房铭,代號為 hbi
  • artifactId 本項目的頂層目錄名稱,使用項目代號(第一個字母大寫) + Parent温眉,如 HbiParent
  • package 包名稱缸匪,使用項目代號 + core ,如 hbi.core
  • archetypeVersion 對應(yīng)依賴的archetype版本

新建3.0項目

使用如下命令創(chuàng)建一個maven項目,如果出現(xiàn)版本問題类溢,參照文檔點擊

mvn archetype:generate -D archetypeGroupId=hap -D archetypeArtifactId=hap-webapp-archetype -D archetypeVersion=3.1-SNAPSHOT -D groupId=hbi -D artifactId=HbiParent -D package=hbi.core  -D archetypeRepository=http://nexus.saas.hand-china.com/content/repositories/rdcsnapshot

上面時官網(wǎng)的運行命令,下面是視頻中的運行命令:

mvn archetype:generate -D archetypeGroupId=hap -D archetypeArtifactId=hap-webapp-archetype -D archetypeVersion=3.1-SNAPSHOT -D groupId=com.demo -D artifactId=hapDemo -D package=demo  -D hap.version=3.0-SNAPSHOT -D archetypeRepository=http://nexus.saas.hand-china.com/content/repositories/rdcsnapshot

項目結(jié)構(gòu)解釋:

.
├── README.md (項目README,請在此處寫上項目開發(fā)的注意信息洒琢,方便團隊協(xié)同)
├── core(功能實現(xiàn)項目)
│   ├── pom.xml (子項目core的pom.xml文件)
│   └── src
│       └── main
│           ├── java
│           │   ├── hbi
│           │   │   └── core(前面的包名稱)
│           │   │       │
│           │   │       ├── controllers(Controller包)
│           │   │       │   └── DemoController.java(Controller類)
│           │   │       ├── db(數(shù)據(jù)表結(jié)構(gòu),數(shù)據(jù)初始化入口文件)
│           │   │       │   └── liquibase.groovy
│           │   │       ├── dto(Dto包)
│           │   │       │   └── Demo.java(Dto實現(xiàn)類)
│           │   │       ├── mapper(Mapper包)
│           │   │       │   ├── DemoMapper.java(Mapper接口)
│           │   │       └── service(Service包)
│           │   │           ├── IDemoService.java
│           │   │           └── impl(Service實現(xiàn))
│           │   │               └── DemoServiceImpl.java
│           │   └── resources(項目配置文件目錄)
│           │       ├── mapper
│           │       │   └── DemoMapper.xml(Mapper xml文件)
│           │       ├── spring (spring配置文件目錄)
│           │       ├── config.properties
│           │       └── logback.xml(日志配置文件)
│           └── webapp(Webapp目錄)
│               ├── lib(UI 資源庫目錄)
│               └── WEB-INF
│                   ├── web.xml(Web.xml配置)
│                   └── view(頁面文件目錄)
│                       └── demo(DEMO頁面文件目錄)
├── core-db(數(shù)據(jù)庫腳本及初始化數(shù)據(jù)項目)
│   ├── pom.xml
│   └── src
│       └── main
│           └── java
│               └── hbi
│                   └── core
│                       └── db
│                           ├── data(數(shù)據(jù)文件)
│                           │   └── (init-data)
│                           └── table(數(shù)據(jù)庫表結(jié)構(gòu)管理)
│                               └── 2016-06-01-init-migration.groovy
└── pom.xml

確定使用的數(shù)據(jù)庫類型

  1. 目前已經(jīng)測試過支持的數(shù)據(jù)庫有Mysql,Oracle,SqlServer 請修改 HbiParent/core/src/main/java/hbi/core/db/liquibase.groovy 以適配不同的數(shù)據(jù)庫
  2. 確定好數(shù)據(jù)庫后衷戈,按照 [Oracle,MySql,Sqlserver數(shù)據(jù)庫配置]修改項目配置文件旧乞。(配置JNDI)
  3. 修改配置文件后挫以,按照 [創(chuàng)建數(shù)據(jù)庫]中的步驟創(chuàng)建數(shù)據(jù)庫

編譯整個項目

在項目HbiParent根目錄下執(zhí)行:mvn clean install -Dmaven.test.skip=true這個步驟有點慢,花了3分多鐘猾担,需要耐心等待
繼續(xù)在HbiParent根目錄下執(zhí)行:mvn process-resources -D skipLiquibaseRun=false -D db.driver=com.mysql.jdbc.Driver -D db.url=jdbc:mysql://192.168.15.78:3306/hap_dev -Ddb.user=root -Ddb.password=root需要注意替換一下中間數(shù)據(jù)庫的地址(如果用的是虛擬機的的話)

修改redis地址

我開始的時候只修改了core包下配置文件的redis的ip,但是發(fā)現(xiàn)運行還是報錯;最后結(jié)果發(fā)現(xiàn)在release包下也有這些配置,所以我直接搜索redis.ip把他們?nèi)牧恕,F(xiàn)在就能運行了啦膜,不過頁面的中文全為?亂碼淌喻。
找了半天原因僧家,懷疑是idea的問題、tomcat的問題裸删;修改各種配置還是一樣八拱,最后發(fā)現(xiàn),問題出在MySQL的配置文件上涯塔,除了更改大小寫之外肌稻,還要增加character_set_server=utf8和max_connections=500
看文檔的時候還是不夠細心啊匕荸,要嚴格按照文檔寫的內(nèi)容執(zhí)行的咯!就按照他寫的文檔來配置爹谭。

觀看HAP環(huán)境搭建視頻

記錄一下講的一些需要留意的知識。

包結(jié)構(gòu)

  • controllers
  • dto
  • mapper
  • service

各個包下面的類命名要和spring中的配置一致榛搔,比如以ServiceImpl結(jié)尾的是配置了aop的诺凡,service和components是配置了掃描路徑的;他這個controllers是配置了掃描路徑的践惑,為啥加s腹泌,咋不知道,也不敢問尔觉。

創(chuàng)建類繼承

所有的controller都要繼承BaseController凉袱,主要是BaseController中有一些"高級方法",如checkToken之類侦铜。service繼承IBaseService和ProxySelf专甩。serviceImpl繼承BaseServiceImpl。不過mapper繼承的是Mapper钉稍,不是BaseMapper配深。實體類繼承BaseDTO

實體類中有些字段在數(shù)據(jù)庫中不存在的要加上@Transient注解。@Colum是用來指定數(shù)據(jù)庫中表的列名嫁盲。

idea中Mybatis的插件可以安裝:Free MyBatis plugin和Mybatis Tools

ssm

Spring MVC

  • 注解
  • 數(shù)據(jù)綁定 400 BadRequest篓叶,參數(shù)傳的不對
  • 數(shù)據(jù)驗證
    Spring
  • 依賴注入 Autowired 根據(jù)類型 根據(jù)名稱
  • 事件發(fā)布和監(jiān)聽
  • AOP配置,大概原理 cglib JDKProxy
    Mybatis
    只有接口和配置文件
    quartz
    activiti

定時器

基于quartz羞秤,開發(fā)時基本不需要進行application配置缸托;我們創(chuàng)建一個job包,在其中創(chuàng)一個定時任務(wù)類并且繼承AbstractJob類瘾蛋,重寫safeExecute方法俐镐;在重寫的方法里面就寫我們想要執(zhí)行的任務(wù)就ok了。這個也不需要什么注解哺哼,就這樣創(chuàng)建一個類佩抹,再在web端計劃任務(wù)中配置一個簡單任務(wù)叼风,并且把類的相對路徑拷貝一份填入web端就行。
這是idea中的代碼

hap定時任務(wù).png

這是web端的配置:注意其中的任務(wù)類名就是類的相對路徑

hap定時任務(wù)web端.png

這是執(zhí)行結(jié)果:

hap定時任務(wù)結(jié)果.png

webService

先看一下包結(jié)構(gòu)和類名:其中類名ws結(jié)尾棍苹,有一個components包其中類以Consumer結(jié)尾

hapwebService結(jié)構(gòu).png

上代碼:HelloWs无宿,先來@WebService注解

@WebService
public interface HelloWs {
    String publishHello(String message);
}

HelloWsImpl:同樣是@WebService注解,不過要配置endpointInterface的值為類的相對路徑

@WebService(endpointInterface = "demo.ws.HelloWs",serviceName = "hello")
public class HelloWsImpl implements HelloWs {
    @Autowired
    private IMessagePublisher publisher;

    @Override
    public String publishHello(String message) {
        publisher.publish("demo:hello","我想問"+message);
        return "success";
    }
}

HelloMessageConsumer:這里加@Component和@TopicMonitor注解枢里,主要是監(jiān)聽impl類中的publish方法中定義的channel孽鸡,兩個值得相同

@Component
@TopicMonitor(channel = "demo:hello")
public class HelloMessageConsumer implements IMessageConsumer<String> {
    @Override
    public void onMessage(String message, String pattern) {
        System.out.println("---------------");
        System.out.println(message);
        System.out.println("----------------");
    }
}

以為到此為止了嗎?還沒有栏豺,我們還需要在applicationContext-beans.xml中配置bean彬碱,才能生效:

<bean id="helloService" class="demo.ws.HelloWsImpl"/>
    <jaxws:endpoint id="hello12211" implementor="#helloService" address="/hello">
    </jaxws:endpoint>

到此為止才是真的結(jié)束

logger打印信息到控制臺

ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
        Logger abc = iLoggerFactory.getLogger("abc");
        abc.info("我是打印");
hap定時任務(wù)log打印.png

好吧,hap4.0更新了奥洼,該學(xué)4.0了

先來開發(fā)環(huán)境:

hap4.0開發(fā)工具.png

數(shù)據(jù)庫配置:

環(huán)境配置:

lower_case_table_names=1  
character_set_server=utf8mb4  
max_connections=500  
default-storage-engine=INNODB  
innodb_large_prefix=on

數(shù)據(jù)庫配置:數(shù)據(jù)庫字符集為utf8mb4

CREATE SCHEMA hap_dev DEFAULT CHARACTER SET utf8mb4;  
CREATE USER hap_dev@'%' IDENTIFIED BY 'hap_dev';  
GRANT ALL PRIVILEGES ON hap_dev.* TO hap_dev@'%';  
FLUSH PRIVILEGES;

新建項目

項目結(jié)構(gòu):和3.0的hap架構(gòu)后端是差不多巷疼,不過前端換成了react開發(fā)了

.
├── README.md (項目README,請在此處寫上項目開發(fā)的注意信息灵奖,方便團隊協(xié)同)
├── charts ()
│   └── hap-demo
│         ├── templates
│         │    ├── _helpers.tpl
│         │    ├── deployment.yaml
│         │    ├── ingress.yaml
│         │    ├── pre-config-config.yaml
│         │    ├── pre-config-db.yaml
│         │    └── service.yaml
│         ├── .helmignore
│         ├── Chart.ymal
│         ├── README.md
│         └── values.yaml
├── docker
│    └── Dockerfile
├── src(后端開發(fā)目錄)
│    └── main
│         └── java
│             ├── hapdemo
│             │   └── core(前面的包名稱)
│             │       │
│             │       ├── controllers(Controller包)
│             │       │   └── DemoController.java(Controller類)
│             │       ├── dto(Dto包)
│             │       │   └── Demo.java(Dto實現(xiàn)類)
│             │       ├── mapper(Mapper包)
│             │       │   ├── DemoMapper.java(Mapper接口)
│             │       └── service(Service包)
│             │           ├── IDemoService.java
│             │           └── impl(Service實現(xiàn))
│             │               └── DemoServiceImpl.java
│             └── resources(項目配置文件目錄)
│                 ├── script.db
│                 │    ├── data
│                 │    │    └── hapdemo-core-init-data.xlsx(excel初始化數(shù)據(jù))
│                 │    └── hapdemo-core-init-table-migration.groovy(groovy建表數(shù)據(jù))
│                 ├── hapdemo.core.grid.mapper
│                 ├── META-INF
│                 │    └── spring.factories
│                 └── application.yml (spring boot 配置文件)
├── react(前端開發(fā)目錄 )
│    ├── src
│    │     └── main
│    │           └── module
│    │                 ├── strores(定義的Dataset文件)
│    │                 ├── view(頁面和業(yè)務(wù)邏輯)
│    │                 ├── index.scss(自定義css樣式)
│    │                 └── index.js (頁面入口文件)
│    ├── config.js
│    └── RouteIndex.js(React訪問路由配置)
├── .gitingore
├── .gitlab-ci.yml (gitlab CI 文件)
├── package.json(node組件依賴)
└── pom.xml

確定本項目使用到的數(shù)據(jù)庫

修改application.yml中db.type和mybatis.identity配置,默認為mysql數(shù)據(jù)庫配置皮迟。我們就不用修改了。

#db.type property is used for activiti
db:
  type: mysql
  #type: oracle
#mybatis.identity property is userd for mybatis Self-increasing primaryKey strategy
mybatis:
  identity: JDBC
  #identity: SEQUENCE

初始化數(shù)據(jù)和表結(jié)構(gòu)

新建一個init-databases.sh腳本:主要修改一下數(shù)據(jù)庫地址桑寨,用戶名和密碼我們之前新建的用戶就是這個hap_dev,他有對hap_dev_4.0的操作權(quán)

#!/usr/bin/env bash
MAVEN_LOCAL_REPO=$(cd / && mvn help:evaluate -Dexpression=settings.localRepository -q -DforceStdout)
TOOL_GROUP_ID=io.choerodon
TOOL_ARTIFACT_ID=choerodon-tool-liquibase
TOOL_VERSION=0.11.1.RELEASE
TOOL_JAR_PATH=${MAVEN_LOCAL_REPO}/${TOOL_GROUP_ID/\./\/}/${TOOL_ARTIFACT_ID}/${TOOL_VERSION}/${TOOL_ARTIFACT_ID}-${TOOL_VERSION}.jar
mvn org.apache.maven.plugins:maven-dependency-plugin:get \
 -Dartifact=${TOOL_GROUP_ID}:${TOOL_ARTIFACT_ID}:${TOOL_VERSION} \
 -Dtransitive=false

mvn clean package spring-boot:repackage

java -Dspring.datasource.url="jdbc:mysql://192.168.15.78/hap_dev_4.0?useUnicode=true&characterEncoding=utf-8&useSSL=false" \
-Dspring.datasource.username=hap_dev \
-Dspring.datasource.password=hap_dev \
-Ddata.mode=all \
-Ddata.drop=false -Ddata.init=true \
-Ddata.jar=target/hap-demo.jar \
-jar ${TOOL_JAR_PATH}

后端開發(fā)

先說開發(fā)規(guī)范:

DTO定義

BaseDTO類 包含了常規(guī) DTO 類的所有公共特性伏尼。_token 防篡改驗證標準 WHO 字段擴展屬性字段,一般用于防止當前登錄用戶修改其他用戶數(shù)據(jù)的情形尉尾。如果沒有特殊情況爆阶,當新建一個 DTO 類時,應(yīng)當繼承 BaseDTO 類沙咏,不要把重復(fù)的內(nèi)容寫到每一個類中辨图。

@EnableExtensionAttribute//是否啟用擴展字段,不加該注解表示不啟用
@Table(name = "hap_grid_demo_b")
@MultiLanguage//多語言表標記
public class GridDemo extends BaseDTO {

    @Id//表中的主鍵肢藐,如果是聯(lián)合主鍵故河,需要都加上@Id@Column(可選)
    @GeneratedValue(generator = GENERATOR_TYPE)
    @Where//根據(jù)Criteria中的參數(shù)拼接sql中where語句
    private Long id;

    @NotEmpty
    @Where
    @Length(max = 50)
    @Column
    @MultiLanguageField//多語言字段標記
    private String name;
    // TODO
}

Mapper層

自定義的 Mapper 接口繼承 io.choerodon.mybatis.common.Mapper:和hap3.0很像,不是繼承BaseMapper吆豹。它的Mapper接口繼承了BaseMapper

public interface GridDemoMapper extends Mapper<GridDemo> {
}

Service層

新建的服務(wù)都需要定義一個繼承 IBaseService<\T> 的接口,服務(wù)實現(xiàn)類實現(xiàn)自己定義的接口,之后在實現(xiàn)類上添加 @Dataset 注解,并實現(xiàn) IDatasetService<\T> 接口的 quiese 和 mutations 方法.

如果是改造現(xiàn)有服務(wù),只需要在對應(yīng)的服務(wù)實現(xiàn)類上添加 @Dataset 注解,并實現(xiàn) IDatasetService<\T> 接口的 quiese 和 mutations :

@Service
@Transactional(rollbackFor = Exception.class)
@Dataset("gridDemo")
public class GridDemoServiceImpl extends BaseServiceImpl<GridDemo> implements IGridDemoService, IDatasetService<GridDemo> {
 
    @Override
    public List queries(Map<String, Object> body, int page, int pageSize, String sortname, boolean isDesc) {
        try {
            GridDemo gridDemo = new GridDemo();
            BeanUtils.populate(gridDemo, body);
            Criteria criteria = new Criteria(gridDemo);
            return super.selectOptions(null,gridDemo,criteria ,page, pageSize);
        } catch (Exception e) {
            throw new DatasetException("dataset.error", e);
        }
    }


    @Override
    public List<GridDemo> mutations(List<GridDemo> submitItems) {
        for (GridDemo item : submitItems) {
            switch (item.get__status()) {
                case ADD:
                    this.insertSelective(null, item);
                    break;
                case DELETE:
                    this.deleteByPrimaryKey(item);
                    break;
                case UPDATE:
                    this.updateByPrimaryKeySelective(null, item);
                    break;
            }
        }
        return submitItems;
    }
}

前端開發(fā)

這個4.0項目必須要啟動前端鱼的,才能看見前端頁面的控制臺;只跑后臺只有登錄界面痘煤。
前端開發(fā)的準備:

  1. 根目錄運行mvn package
  2. 同樣根目錄運行npm install --registry https://nexus.choerodon.com.cn/repository/choerodon-npm/
  3. 運行npm start

我的npm命令執(zhí)行第二步驟的時候凑阶,出現(xiàn)什么需要安裝node-gyp;大致原因是因為沒有Python衷快,我一開始去按了node-gyp宙橱,還是不能用;我就卸載了,再在官網(wǎng)上下載了Python2.7.12师郑,然后默認步驟安裝环葵,選擇加入path路徑,就OK了宝冕。

npm install --global --production windows-build-tools

npm install -g node-gyp

DEMO開發(fā)

好了张遭,我已經(jīng)完成了demo的部分開發(fā)了。真的是心累猬仁,昨晚給的開發(fā)任務(wù),從昨晚到今天下午都特么卡在項目創(chuàng)建mapper之后跑不了的困局之中先誉。好在最后終于解決了湿刽,不過我也知道了為什么會出現(xiàn)這些問題;下面給出遇到的問題

項目結(jié)構(gòu)問題

我根據(jù)數(shù)據(jù)庫表的屬性褐耳,在hap源碼中找到了一個類似模塊诈闺,就是hap-core下的iam包;然后一看結(jié)構(gòu)铃芦,這不是DDD嗎雅镊?

hap的demo結(jié)構(gòu)1.png

好了,我也照貓畫虎來了一個同樣的ddd結(jié)構(gòu)刃滓,結(jié)構(gòu)是有了仁烹,實體類、mapper等等都創(chuàng)建好了咧虎,就等著啟動了呢卓缰?好來一手mvn clean packagemvn spring-boot:run結(jié)果就是直接報tk.mybatis出錯,創(chuàng)建不了bean砰诵,而且是空指針征唬。我一開始是以為,mapper.xml的目錄不對茁彭,因為它提示找不見mapper总寒,后來移動文件到新文件夾也沒用;又以為是哪里注解沒加理肺,因為掃描不出來摄闸,結(jié)果并不是注解少了。就這樣妹萨,自己瞎猜問題原因贪薪,沒有仔細想問題出現(xiàn)的本質(zhì)。
他不是報找不見mapper嗎眠副?這個框架把掃描的路徑給寫死了画切,我創(chuàng)建的目錄結(jié)構(gòu)是掃描不到的;service也是掃描不到的囱怕。最后霍弹,還是推倒重建毫别,按著已有的目錄來創(chuàng)建;經(jīng)過實踐典格,確實問題就是出在掃描不到上岛宦。下面是新的目錄結(jié)構(gòu):
hap的demo結(jié)構(gòu)2.png

這個結(jié)構(gòu)必須是這樣的四個包,controllers必須有s耍缴。
下面給出效果圖:
hap的demo效果圖1.png

項目創(chuàng)建步驟

要實現(xiàn)這樣的效果砾肺,要經(jīng)歷如下步驟:

1.創(chuàng)建實體類Header和Line;并且根據(jù)數(shù)據(jù)庫中屬性來決定是否加注解防嗡。實體類繼承BaseDTO是因為有objectVersionNumber createdBy creationDate lastUpdatedBy lastUpdateDate屬性变汪;沒有加上@EnableExtensionAttribute是因為我的數(shù)據(jù)庫中并不是完全有BaseDTO的10多種屬性,執(zhí)行mapper的插入調(diào)用的是默認的方法蚁趁,就不會提示其他屬性對應(yīng)數(shù)據(jù)庫表的列不存在的情況裙盾。還有沒有加@MultiLanguage的原因是,我在頁面上進行插入或者保存操作時他嫡,我不會保存語言屬性到_tl的表中(因為沒有創(chuàng)建該表番官,會報插入失敗)钢属,還有需要注意的是徘熔,@MultiLanguage注解不加在類上,類的屬性也不能加此注解淆党,否則會報tk.mybatis異常近顷。

@Table(name = "htrain_application_header")
public class Header extends BaseDTO{

    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long applicationHeaderId;

    @NotEmpty(message = "應(yīng)用名不為空")
    @Size(min = 1, max = 200)
    @Column
    private String applicationName;

    @NotEmpty(message = "應(yīng)用編碼不為空")
    @Size(min = 1, max = 100)
    @Column
    private String applicationCode;

    @Size(min = 1, max = 500)
    @Column
    private String applicationDesc;

    @Column(name = "is_enabled")
    private Long enabled;

    //省略getter和setter

光這個實體的注解都有好幾層意思,一定要知道什么情況下該怎么用宁否。

2.開發(fā)mapper窒升,mapper上不用加@Mapper注解,不過需要繼承Mapper慕匠,繼承的是豬齒魚的Mapper饱须。

public interface HeaderMapper extends Mapper<Header> {
    /**
     * 利用dto類的屬性查詢,返回一個Header對象
     * @param headerDTO
     * @return
     */
    List<Header> listHeadersByDTO(@Param("headerDTO") HeaderDTO headerDTO);
}

這里的DTO是我自己提取Header屬性出來的台谊。頁面上查詢傳入的屬性不完全是Header類的屬性

public class HeaderDTO {
    private String applicationName;

    private String applicationCode;

    private String applicationDesc;

    private Long enabled;
    //省略getter和setter
}

3.開發(fā)HeaderService蓉媳,需要繼承IBaseService和ProxySelf,繼承的第一個主要是可以用默認的一些crud方法锅铅,第二個字面意思代理自己酪呻,不太清它的作用。

public interface HeaderService extends IBaseService<Header>, ProxySelf<Header> {
    /**
     * 查詢Header
     * @param headerDTO
     * @return
     */
    List<Header> listHeaders(HeaderDTO headerDTO);
}

4.開發(fā)Controller盐须,需要繼承BaseController玩荠,源碼中提示能分頁和校驗,這個框架下,idea不會高亮類名

@RestController
@RequestMapping("/log-warn")
public class LogWarnController extends BaseController {

    @Autowired
    private HeaderService headerService;

    @Autowired
    private LineService lineService;

    @GetMapping("/headers")
    public List<Header> listHeaders(HeaderDTO headerDTO) {
        return headerService.listHeaders(headerDTO);
    }

    @GetMapping("/{headerId}/lines")
    public List<Line> listHeaders(@PathVariable Long headerId) {
        return lineService.listLinesByHeaderId(headerId);
    }
}

5.開發(fā)Mapper.xml阶冈,提一句Free mybatis plugin這個idea插件挺好用的

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="hap.demo.core.grid.mapper.HeaderMapper">

    <select id="listHeadersByDTO" resultType="hap.demo.core.grid.dto.Header">
        SELECT *,
        header.is_enabled enabled FROM
        htrain_application_header header
        <where>
            <if test="headerDTO.enabled != null">
                header.is_enabled = #{headerDTO.enabled}
            </if>
            <if test="headerDTO.applicationName != null">
                <bind name="pattern" value="'%' + headerDTO.getApplicationName + '%'"/>
                and header.application_name like #{pattern}
            </if>
            <if test="headerDTO.applicationCode != null">
                <bind name="pattern" value="'%' + headerDTO.getApplicationCode + '%'"/>
                and header.application_code like #{pattern}
            </if>
            <if test="headerDTO.applicationDesc != null">
                <bind name="pattern" value="'%' + headerDTO.getApplicationDesc + '%'"/>
                and header.application_desc like #{pattern}
            </if>
        </where>
    </select>
</mapper>

6.開發(fā)serviceImpl闷尿,需要繼承BaseServiceImpl,并實現(xiàn)service女坑;這里的Dataset注解我們先不用管

@Service
@Transactional(rollbackFor = Exception.class)
@Dataset("HeaderDemo")
public class HeaderServiceImpl extends BaseServiceImpl<Header> implements HeaderService, IDatasetService<Header> {

    @Autowired
    private HeaderMapper headerMapper;

    @Override
    public List<Header> listHeaders(HeaderDTO headerDTO) {
        return headerMapper.listHeadersByDTO(headerDTO);
    }

    @Override
    public List<?> queries(Map<String, Object> body, int page, int pageSize, String sortname, boolean isDesc) {
        try {
            HeaderDTO headerDTO = new HeaderDTO();
            BeanUtils.populate(headerDTO, body);
            PageHelper.startPage(page, pageSize);
            return headerMapper.listHeadersByDTO(headerDTO);
        } catch (Exception e) {
            throw new DatasetException("dataset.error", e);
        }
    }

    @Override
    public List<Header> mutations(List<Header> list) {
        for (Header header : list) {
            switch (header.get__status()) {
                case ADD:
                    headerMapper.insert(header);
                    break;
                case DELETE:
                    headerMapper.delete(header);
                    break;
                case UPDATE:
                    headerMapper.updateByPrimaryKey(header);
                    break;
                default:
                    break;
            }
        }
        return list;
    }
}

前端開發(fā)

到此為止填具,后端的邏輯是不是差不多了呢?是的匆骗,不過光有邏輯還不行劳景,需要和前端進行交互,那我們就還要開發(fā)前端碉就。前端的結(jié)構(gòu)如下:我們就需要在紅框中的三個文件中操作即可

hap的demo前端1.png

1.第一個文件HeaderDataSet.js:值得注意的是其中primaryKey是對應(yīng)表主鍵字段盟广,fields中type的類型有string和number,lookupCode是對應(yīng)前端自己創(chuàng)建的系統(tǒng)設(shè)置中的代碼維護铝噩。這些name對應(yīng)實體類的屬性名

// stores/GridDataSet.js

export default {
    //主鍵字段名衡蚂,一般用作級聯(lián)行表的查詢字段
    primaryKey: 'applicatioin_header_id',
    autoQuery: true,
    pageSize: 20,
    //對應(yīng)后臺ds的name窿克,自動生成約定的submitUrl, queryUrl, tlsUrl
    name: 'HeaderDemo',
    //與后端對應(yīng)的列的描述
    fields: [
        {name: 'applicationCode', type: 'string', label: '應(yīng)用編碼', require: true},
        {name: 'applicationName', type: 'string', label: '應(yīng)用名稱'},
        {name: 'applicationDesc', type: 'string', label: '應(yīng)用描述'},
        {name: 'enabled', type: 'string', label: '是否啟用', lookupCode: 'HEADER.ENABLED'},
    ],
    //查詢字段骏庸,自動生成查詢組件
    queryFields: [
        {name: 'applicationCode', type: 'string', label: '應(yīng)用編碼'},
        {name: 'applicationName', type: 'string', label: '應(yīng)用名稱'},
        {name: 'applicationDesc', type: 'string', label: '應(yīng)用描述'},
        {name: 'enabled', type: 'string', label: '是否啟用', lookupCode: 'HEADER.ENABLED'},
    ],
};

2.開發(fā)index.js注意其中column標簽的editor屬性對應(yīng)我們在前端操作的是輸入還是選擇

import {DataSet, IntlField, Select, Table} from "choerodon-ui/pro";
import {Content} from "@choerodon/boot";
import HeaderDataSet from "../app/stores/HeaderDataSet";
import React, { PureComponent } from 'react';

const {Column} = Table;

export default class Header extends PureComponent {
    headerDataSet = new DataSet(HeaderDataSet);

    render() {
        return (
            <Content>
                <Table buttons={['add', 'save', 'delete']} dataSet={this.headerDataSet} border={false} queryFieldsLimit={4}>
                    <Column name="applicationCode" editor={<IntlField />} />
                    <Column name="applicationName" editor={<IntlField />} />} />
                    <Column name="applicationDesc" editor={<IntlField />} />
                    <Column name="enabled" editor={<Select />} />
                </Table>
            </Content>
        );
    }
}

3.開發(fā)RouteIndex.js,注意其中path對應(yīng)的路徑是加上/log-war/headers因為這是我自己寫的controller對應(yīng)的路徑年叮。

import React, { Component } from 'react';
import CacheRoute, { CacheSwitch } from 'react-router-cache-route';
import { asyncRouter, nomatch } from '@choerodon/boot';

const Grid = asyncRouter(() => import('./src/grid'));
const Header = asyncRouter(() => import('./src/app'))

export default ({ match }) => (
  <CacheSwitch>
    <CacheRoute exact path={`${match.url}/grid`} cacheKey={`${match.url}/grid`} component={Grid} />
    <CacheRoute exact path={`${match.url}/log-warn/headers`} cacheKey={`${match.url}/log-warn/headers`} component={Header} />
    <CacheRoute path="*" component={nomatch} />
  </CacheSwitch>
);

4.配置xlsx文件具被,加上自己在路由中配置的路徑;注意路徑對應(yīng)自己的路由配置

hap的demo前端xlsx1.png

總結(jié)

其實開發(fā)后端不難只损,就是必須要依據(jù)它的框架來開發(fā)一姿。

Demo再開發(fā)

由于上面開發(fā)的demo只是一個操作單表的,沒有前端的開發(fā)基礎(chǔ)跃惫,只能仿照編碼規(guī)則的前端結(jié)構(gòu)來寫(在源碼code.rule包下)叮叹。大致要做出的效果就是這樣:
頭的:

demo重新開發(fā)前端頭1.png

行的:
demo重新開發(fā)前端行.png

項目結(jié)構(gòu)

前端:

  • react
    • src
      • logwarn
        • store
        • view

后端:

  • demo
    • controllers
    • dto
    • mapper
    • service

貼圖:

demo重新開發(fā)前后端結(jié)構(gòu).png

后端開發(fā)

在上面的demo上繼續(xù)整理,更加完善對hap的注解理解爆存。

pom文件及前端config.js修改

修改pom文件中的依賴

<dependency>
            <groupId>io.choerodon</groupId>
            <artifactId>hap-core</artifactId>
        </dependency>
        <dependency>
            <groupId>io.choerodon</groupId>
            <artifactId>hap-security-standard</artifactId>
        </dependency>

修改config.js

 modules: [
    '../target/generate-react/choerodon-fnd-util',
    '../target/generate-react/hap-core',
  ],

實體類

Header類:注意其中已經(jīng)指出了@Where @Children等注解的作用了蛉顽,我在這里就不提出來敘述了。

@Table(name = "htrain_application_header")
public class Header extends BaseDTO{

    /**
     * 指定屬性對應(yīng)的常量先较,方便service和controller調(diào)用組合出查詢篩選條件
     */
    public static final String FIELD_APPLICATION_HEADER_ID = "applicationHeaderId";
    public static final String FIELD_APPLICATION_HEADER_CODE = "applicationCode";
    public static final String FIELD_APPLICATION_HEADER_NAME = "applicationName";
    public static final String FIELD_APPLICATION_HEADER_DESC = "applicationDesc";
    public static final String FIELD_APPLICATION_HEADER_ENABLED = "enabled";


    /**
     * @Id 指定id
     * @Column 指定這是表的列携冤,如果屬性名不滿足駝峰規(guī)則,需要給注解加name屬性指定表中列名
     * @Where 標志這是一個可以用作篩選條件的屬性
     * @GeneratedValue 它的屬性strategy 中有四種值:TABLE SEQUENCE IDENTITY AUTO
     *             分別對應(yīng)的意思是:TABLE 使用一個特定的數(shù)據(jù)庫表格來保存主鍵
     *                            SEQUENCE 根據(jù)底層數(shù)據(jù)庫的序列來生成主鍵闲勺,條件時數(shù)據(jù)庫支持序列
     *                            IDENTITY 主鍵由數(shù)據(jù)庫自動生成(主要是自動增長型)
     *                            AUTO 主鍵由程序控制
     * @Transient 此注釋指定屬性或字段為不持久曾棕。它用于注釋屬性或字段實體類、映射超類或可嵌入類
     * @Children 加在 DTO 的某個 field 上,表示這個 field 是用存放子節(jié)點信息.
     *           頭行結(jié)構(gòu)中行的標記,也可以用作單個對象的子屬性標記
     *
     *
     */
    @Id
    @Column
    @Where
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long applicationHeaderId;

    @NotEmpty(message = "應(yīng)用名不為空")
    @Size(min = 1, max = 200)
    @Where
    @Column
    private String applicationName;

    @NotEmpty(message = "應(yīng)用編碼不為空")
    @Size(min = 1, max = 100)
    @Where
    @Column
    private String applicationCode;

    @Size(min = 1, max = 500)
    @Where
    @Column
    private String applicationDesc;

    @Column(name = "is_enabled")
    @Where
    private Long enabled;


    @Transient
    @Children
    private List<Line> lines;
    
    省略getter和setter方法
}

Line類的屬性上加的注解根據(jù)自己的需要添加:

@Table(name = "htrain_application_line")
public class Line extends BaseDTO{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Long applicationLineId;

    @Column
    @Where
    private Long applicationHeaderId;

    @NotEmpty(message = "日志路徑不為空")
    @Size(min = 1,max = 2000)
    @Column
    private String logFilePath;

    @NotEmpty(message = "ip地址不為空")
    @Size(min = 1,max = 100)
    @Column
    private String ipAddress;

    @NotEmpty(message = "規(guī)則編碼不為空")
    @Column
    private Long ruleId;

    @Column
    private Long enableGrok;

    @Size(min = 1,max = 2000)
    @Column
    private String grokPattern;

    @Size(min = 1,max = 2000)
    @Column
    private String builtInPattern;

    @Column(name = "is_enabled")
    private Long enabled;
    
    省略getter和setter
}

創(chuàng)建Mapper類及Service和它的實現(xiàn)類

這一部分的開發(fā)與上面類似菜循。hap提供了單表的crud翘地,其實在這里不需要寫mapper中方法的。
hpa中@Where注解的作用就是提供數(shù)據(jù)庫查詢篩選條件。

下面是mapper的定義:

public interface HeaderMapper extends Mapper<Header>{}

public interface LineMapper extends Mapper<Line> 

這是service的定義:

public interface LineService extends IBaseService<Line>, ProxySelf<Line> {}

public interface HeaderService extends IBaseService<Header>, ProxySelf<Header> 

service和mapper的接口開發(fā)在這里都不需要寫方法的子眶,我在我的demo中寫了沒有刪(有點懶)

下面是service的實現(xiàn)類:其中最需要注意的就是多表crud用hap默認的mapper提供的方法如何實現(xiàn)瀑凝。多表的操作方法都是要重新寫的。

/**
 * 注意這里的三個注解@Service @Transactional @Dataset
 */
@Service
@Transactional(rollbackFor = Exception.class)
@Dataset("HeaderDemo")
public class HeaderServiceImpl extends BaseServiceImpl<Header> implements HeaderService, IDatasetService<Header> {

    @Autowired
    private HeaderMapper headerMapper;

    @Autowired
    private LineMapper lineMapper;


    /**
     * 該方法未用到
     * @param headerDTO
     * @return
     */
    @Override
    public List<Header> listHeaders(HeaderDTO headerDTO) {
        return headerMapper.listHeadersByDTO(headerDTO);
    }


    /**
     * 該類實現(xiàn)了IDatasetService<Header>接口臭杰,重寫了queries方法
     * 在其中自己組合了where查詢條件粤咪,調(diào)用父類的查詢方法
     * @param body
     * @param page
     * @param pageSize
     * @param sortname
     * @param isDesc
     * @return
     */
    @Override
    public List<?> queries(Map<String, Object> body, int page, int pageSize, String sortname, boolean isDesc) {
        try {
            Header header = new Header();
            BeanUtils.populate(header,body );
            Criteria criteria = new Criteria(header);
            criteria.where(new WhereField(Header.FIELD_APPLICATION_HEADER_ID),
                    new WhereField(Header.FIELD_APPLICATION_HEADER_CODE, Comparison.LIKE),
                    new WhereField(Header.FIELD_APPLICATION_HEADER_NAME,Comparison.LIKE),
                    new WhereField(Header.FIELD_APPLICATION_HEADER_DESC,Comparison.LIKE),
                    new WhereField(Header.FIELD_APPLICATION_HEADER_ENABLED,Comparison.LIKE));
            return super.selectOptions(header,criteria,page,pageSize );
        } catch (Exception e) {
            throw new DatasetException("dataset.error", e);
        }
    }

    /**
     * 自寫插入方法
     * 頭行結(jié)構(gòu),因為前段保存的時候回操作兩張表渴杆,我們需要自己組合寫方法
     * @param header
     * @return
     */
    private Header insertHeader(Header header) {
        List<Line> lines = header.getLines();
        headerMapper.insertSelective(header);
        if (lines.size() != 0) {
            for (Line line:lines
            ) {
                line.setApplicationHeaderId(header.getApplicationHeaderId());
                lineMapper.insert(line);
            }
        }
        return header;
    }

    /**
     * 自寫更新方法
     * @param header
     * @return
     */
    private Header updateHeader(Header header) {
        List<Line> lines = header.getLines();
        headerMapper.updateByPrimaryKey(header);
        if (lines.size() != 0 ) {
            for (Line line: lines
                 ) {
                if (line.getApplicationHeaderId() != null) {
                    lineMapper.updateByPrimaryKey(line);

                }else {
                    line.setApplicationHeaderId(header.getApplicationHeaderId());
                    lineMapper.insert(line);
                }
            }
        }
        return header;
    }


    /**
     * 自寫刪除方法
     * @param header
     */
    private void deleteHeader(Header header) {
        Long applicationHeaderId = header.getApplicationHeaderId();
        lineMapper.deleteLineByHeaderId(applicationHeaderId);
        headerMapper.delete(header);
    }

    /**
     * 實現(xiàn)了IDatasetService<Header>接口寥枝,通過前端傳的對應(yīng)的status值來執(zhí)行操作
     * 增刪改都用自己的方法,因為是多表進行操作
     * @param list
     * @return
     */
    @Override
    public List<Header> mutations(List<Header> list) {
        for (Header header : list) {
            switch (header.get__status()) {
                case ADD:
                    insertHeader(header);
                    break;
                case DELETE:
                    deleteHeader(header);
                    break;
                case UPDATE:
                    updateHeader(header);
                    break;
                default:
                    break;
            }
        }
        return list;
    }
}

LineService的實現(xiàn)類:我個人認為這個demo的前端沒有用到其中mutations方法磁奖,這里只用了它的查詢囊拜。行的增刪改都夾在了頭的操作中

@Service
@Transactional(rollbackFor = Exception.class)
@Dataset("LineDemo")
public class LineServiceImpl extends BaseServiceImpl<Line> implements LineService, IDatasetService<Line> {

    @Autowired
    private LineMapper lineMapper;

    /**
     * 該方法沒有用到
     * @param headerId
     * @return
     */
    @Override
    public List<Line> listLinesByHeaderId(Long headerId) {
        return lineMapper.listLinesByHeaderId(headerId);
    }

    /**
     * 實現(xiàn)IDatasetService<Header>接口,重寫父類方法比搭;使用父類的查詢方法進行查詢操作
     * @param body
     * @param page
     * @param pageSize
     * @param sortname
     * @param isDesc
     * @return
     */
    @Override
    public List<?> queries(Map<String, Object> body, int page, int pageSize, String sortname, boolean isDesc) {
        try {
            Line line = new Line();
            BeanUtils.populate(line, body);
            Criteria criteria = new Criteria(line);
            return super.selectOptions(line, criteria);
        } catch (Exception e) {
            throw new DatasetException("dataset.error", e);
        }
    }

    /**
     * 實現(xiàn)IDatasetService<Header>接口冠跷,重寫父類方法;使用父類的更新方法操作
     * @param list
     * @return
     */
    @Override
    public List<Line> mutations(List<Line> list) {
        for (Line line :list
             ) {
            switch (line.get__status()) {
                case ADD:
                    lineMapper.insertSelective(line);
                    break;
                case DELETE:
                    lineMapper.delete(line);
                    break;
                default:
                    break;
            }
        }
        return list;
    }
}

Controller開發(fā)

HeaderController:我感覺這個查詢方法并沒有用到身诺,因為在service的實現(xiàn)queries中就寫了這個方法蜜托,而且通過xlsx添加進數(shù)據(jù)庫就映射的有這個queries;并且前端的查詢就是通過Dataset訪問的霉赡。

demo重新開發(fā)queries.png
@RestController
@RequestMapping("/log-warn/header")
public class HeaderController extends BaseController {


    @Autowired
    private HeaderService headerService;

    /**
     * 利用實體類上@Where注解來組合查詢篩選條件
     * @param header
     * @param page
     * @param pageSize
     * @param request
     * @return
     */
    @Permission(type = ResourceType.SITE)
    @PostMapping("/query")
    public ResponseData query(Header header, @RequestParam(defaultValue = DEFAULT_PAGE) int page,
                              @RequestParam(defaultValue = DEFAULT_PAGE_SIZE) int pageSize, HttpServletRequest request) {
        Criteria criteria = new Criteria(header);
        criteria.where(new WhereField(Header.FIELD_APPLICATION_HEADER_ID),
                new WhereField(Header.FIELD_APPLICATION_HEADER_CODE, Comparison.LIKE),
                new WhereField(Header.FIELD_APPLICATION_HEADER_NAME,Comparison.LIKE),
                new WhereField(Header.FIELD_APPLICATION_HEADER_DESC,Comparison.LIKE),
                new WhereField(Header.FIELD_APPLICATION_HEADER_ENABLED,Comparison.LIKE));
        return new ResponseData(headerService.selectOptions(header,criteria,page,pageSize ));
    }


}

LineController開發(fā):它的查詢方法和上面類似橄务,我感覺沒有用到;因為都在service的實現(xiàn)類中寫了穴亏,前端訪問的也是Dataset的查詢方法

@RestController
@RequestMapping("/log-warn/line")
public class LineController extends BaseController {
    @Autowired
    private LineService service;

    /**
     * 查頭的同時會調(diào)用這個方法查出頭對應(yīng)的行
     * @param line
     * @param request
     * @return
     */
    @Permission(type = ResourceType.SITE)
    @PostMapping("/query")
    public ResponseData query(Line line, HttpServletRequest request) {
        IRequest requestContext = createRequestContext(request);
        Criteria criteria = new Criteria(line);
        return new ResponseData(service.selectOptions(line, criteria));
    }
}

前端開發(fā)

store包下的開發(fā):
HeaderDataSet.js
LineDataSet.js

先來HeaderDataSet:其中需要注意的就是enabled這個屬性了蜂挪,它在前端是boolean類型,不過對應(yīng)的值是后端類型中的Long嗓化;所以trueValue的值就要寫后端對應(yīng)的類型


export default {
  primaryKey: 'applicationHeaderId',
  name: 'HeaderDemo',
  autoQuery: true,
  pageSize: 20,
    fields: [
        {name: 'applicationCode', type: 'string', label: '應(yīng)用編碼', require: true, unique: true},
        {name: 'applicationName', type: 'string', label: '應(yīng)用名稱'},
        {name: 'applicationDesc', type: 'string', label: '應(yīng)用描述'},
        { name: 'enabled', type: 'boolean', label: '是否啟用', trueValue: 1, falseValue: 0, defaultValue: 1 },
    ],
    //查詢字段棠涮,自動生成查詢組件
    queryFields: [
        {name: 'applicationCode', type: 'string', label: '應(yīng)用編碼'},
        {name: 'applicationName', type: 'string', label: '應(yīng)用名稱'},
        {name: 'applicationDesc', type: 'string', label: '應(yīng)用描述'},
        {name: 'enabled', type: 'string', label: '是否啟用', lookupCode: 'HEADER.ENABLED'},
    ],
};

LineDataSet.js

export default {
  name: 'LineDemo',
  fields: [
    { name: 'ipAddress', type: 'string', label: 'IP地址', required: true },
    { name: 'logFilePath', type: 'string', label: '日志路徑', required: true },
    { name: 'ruleId', type: 'number', label: '告警規(guī)則',},
    { name: 'enabled', type: 'boolean', label: '是否啟用', trueValue: 1, falseValue: 0, defaultValue: 1 },
  ],
};

view包下開發(fā):
CodeRule.js
CodeRuleModal.js

先來CodeRule.js,這里它對應(yīng)的前端頭刺覆,渲染的也是前端頭表格

import React from 'react';
import {Button, IntlField, Modal, Table, Tooltip} from 'choerodon-ui/pro';
import CodeRuleModal from './CodeRuleModal';

const { Column } = Table;
const modalKey = Modal.key();

export default ({ headerDS, lineDS }) => {
  let isCancel;
  let created;

  /**
   * 確定編碼規(guī)則編輯修改
   * 數(shù)據(jù)校驗成功時保存
   */
  async function handleOnOkCodeRuleModal() {
    isCancel = false;
    if (await headerDS.current.validate()) {
      await headerDS.submit();
    } else {
      return false;
    }
  }

  function handleOnCancelCodeRuleModal() {
    isCancel = true;
  }

  /**
   * 關(guān)閉編碼規(guī)則彈窗.
   *
   */
  function handleOnCloseCodeRuleModal() {
    if (isCancel) {
      // 新建時取消严肪,移除dataSet記錄
      if (created) {
        headerDS.remove(created);
      } else {
        // 修改時取消 重置當前記錄數(shù)據(jù)
        headerDS.current.reset();
        lineDS.reset();
      }
    }
    // 重置新建記錄標記
    created = null;
  }

  /**
   * 打開編碼規(guī)則彈窗.
   * @param applicationHeaderId 頭Id
   * @param enabled 是否啟用
   */
  function openCodeRuleModal(applicationHeaderId, enabled) {
    if (!applicationHeaderId) {
      created = headerDS.create();
    }
    // 如果是編輯狀態(tài) 編碼不可編輯
    const isEditDisabled = !!applicationHeaderId;
    // 如果為啟用狀態(tài) 只可以進行查看 不能編輯數(shù)據(jù)
    const isEnableDisabled = (isEditDisabled) && (enabled === 1);
    // 如果為啟用狀態(tài) 編碼規(guī)則行,不可被選中
    lineDS.selection = isEnableDisabled ? false : 'multiple';
    Modal.open({
        //唯一鍵隅津, 當destroyOnClose為false時诬垂,必須指定key。
        // 為了避免與其他modal的key重復(fù)伦仍,可通過Modal.key()來獲取唯一key结窘。
      key: modalKey,
        //標題
      title: applicationHeaderId ? '編輯' : '添加',
        //抽屜模式
      drawer: true,
        //關(guān)閉時是否銷毀
      destroyOnClose: true,
        //同時顯示ok和cancel按鈕,false的時候只顯示ok按鈕
      okCancel: !isEnableDisabled,
        //確認按鈕文字
      okText: !isEnableDisabled ? '保存' : '關(guān)閉',
        //點擊確定回調(diào)充蓝,返回false Promise.resolve(false)或
        // Promise.reject()不會關(guān)閉隧枫, 其他自動關(guān)閉
      onOk: !isEnableDisabled ? handleOnOkCodeRuleModal : handleOnCancelCodeRuleModal,
        //點擊取消回調(diào)喉磁,返回false Promise.resolve(false)或
        // Promise.reject()不會關(guān)閉, 其他自動關(guān)閉
      onCancel: handleOnCancelCodeRuleModal,
        //關(guān)閉后回調(diào)
      afterClose: handleOnCloseCodeRuleModal,
      children: (
        <CodeRuleModal headerDS={headerDS} lineDS={lineDS} isEditDisabled={isEditDisabled} isEnableDisabled={isEnableDisabled} />
      ),
      style: {
        width: 1100,
      },
    });
  }

  const addBtn = (
    <Button
      icon="playlist_add"
      funcType="flat"
      color="blue"
      onClick={() => openCodeRuleModal(null, null)}
    >
      {'添加'}
    </Button>
  );

  /**
   * 渲染表格內(nèi)容.
   */
  return (
    <Table buttons={[addBtn, 'save', 'delete']} dataSet={headerDS} queryFieldsLimit={4}>
        <Column name="applicationCode"  />
        <Column name="applicationName" editor />
        <Column name="applicationDesc" editor />
      <Column name="enabled" editor align="center" width={120} />
      <Column
        header={'操作'}
        align="center"
        width={120}
        renderer={({ record, text, name }) => {
          const title = record.get('enabled') === 1 ? '觀看' : '編輯';
          const icon = record.get('enabled') === 1 ? 'visibility' : 'mode_edit';
          return (
            <Tooltip
              title={title}
            >
              <Button
                funcType="flat"
                icon={icon}
                onClick={() => openCodeRuleModal(record.get('applicationHeaderId'), record.get('enabled'))}
              />
            </Tooltip>
          );
        }}
      />
    </Table>
  );

};

CodeRuleModal.js是行的實現(xiàn)

import React from 'react';
import {CheckBox, Form, Table, TextField} from 'choerodon-ui/pro';

const { Column } = Table;

export default ({ headerDS, lineDS, isEditDisabled, isEnableDisabled }) => {
    let btnGroup = [];
  if (!isEnableDisabled) {
    btnGroup = ['add', 'delete'];
  }

  return (
    <div>
      <Form
        columns={2}
        abelWidth={100}
      >
        <TextField name="applicationCode" label={'應(yīng)用編碼'} dataSet={headerDS} required disabled={isEditDisabled} />
        <TextField name="applicationName" label={'應(yīng)用名稱'} dataSet={headerDS} disabled={isEnableDisabled} />
        <TextField name="applicationDesc" label={'應(yīng)用描述'} dataSet={headerDS}  disabled={isEnableDisabled} />
        <CheckBox name="enabled" label={'是否啟用'} dataSet={headerDS} disabled={isEnableDisabled} />
      </Form>
      <Table
        buttons={btnGroup}
        dataSet={lineDS}
        header={'行'}
      >
        <Column name="ipAddress"  label={'IP地址'} editor/>
        <Column name="logFilePath" label={'日志文件目錄'} editor />
        <Column name="ruleId" label={'日志規(guī)則'} editor />
          <Column name="enabled" label={'是否啟用'} editor align="center" width={120}/>
      </Table>
    </div>
  );
};

最后來一個index.js

import React, { PureComponent } from 'react';
import { DataSet } from 'choerodon-ui/pro';
import { ContentPro as Content } from '@choerodon/boot';
import HeadDataSet from './store/HeadDataSet';
import LineDataSet from './store/LineDataSet';
import CodeRule from './view/CodeRule';

export default class Index extends PureComponent {
    lineDS = new DataSet(LineDataSet);

    headerDS = new DataSet({
        ...HeadDataSet,
        children: {
            lines: this.lineDS,
        },

    });

    render() {
        return (
            <Content>
                <CodeRule headerDS={this.headerDS} lineDS={this.lineDS} />
            </Content>
        );
    }
}

還有一個RoutIndex.js

import React, { Component } from 'react';
import CacheRoute, { CacheSwitch } from 'react-router-cache-route';
import { asyncRouter, nomatch } from '@choerodon/boot';

const Grid = asyncRouter(() => import('./src/grid'));
const Header = asyncRouter(() => import('./src/logwarn'))

export default ({ match }) => (
  <CacheSwitch>
    <CacheRoute exact path={`${match.url}/grid`} cacheKey={`${match.url}/grid`} component={Grid} />
    <CacheRoute exact path={`${match.url}/log-warn`} cacheKey={`${match.url}/log-warn`} component={Header} />
    <CacheRoute path="*" component={nomatch} />
  </CacheSwitch>
);

前端不太會官脓,只能仿照編碼規(guī)則的前端來寫协怒。

xlsx的配置

主要配置的是 permission menu role_permission三個sheet頁。需要配置與路由對應(yīng)

permission:

demo重新開發(fā)xlsx1.png

menu:
demo重新開發(fā)xlsx2.png

總結(jié)

這個demo的開發(fā)代碼已經(jīng)傳到了碼云上卑笨,下次遇到同樣的可以試著仿照來寫孕暇。這里面比較難搞的就是前端,后端只要把頭行結(jié)構(gòu)的增刪改的邏輯寫好就行赤兴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末妖滔,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子桶良,更是在濱河造成了極大的恐慌座舍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陨帆,死亡現(xiàn)場離奇詭異曲秉,居然都是意外死亡,警方通過查閱死者的電腦和手機疲牵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門承二,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人瑰步,你說我怎么就攤上這事矢洲¤得撸” “怎么了缩焦?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長责静。 經(jīng)常有香客問我袁滥,道長,這世上最難降的妖魔是什么灾螃? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任题翻,我火速辦了婚禮,結(jié)果婚禮上腰鬼,老公的妹妹穿的比我還像新娘嵌赠。我一直安慰自己,他們只是感情好熄赡,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布姜挺。 她就那樣靜靜地躺著,像睡著了一般彼硫。 火紅的嫁衣襯著肌膚如雪炊豪。 梳的紋絲不亂的頭發(fā)上凌箕,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音词渤,去河邊找鬼牵舱。 笑死,一個胖子當著我的面吹牛缺虐,可吹牛的內(nèi)容都是我干的芜壁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼高氮,長吁一口氣:“原來是場噩夢啊……” “哼沿盅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纫溃,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤腰涧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后紊浩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體村砂,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡君丁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片箍邮。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杖剪,靈堂內(nèi)的尸體忽然破棺而出菩收,到底是詐尸還是另有隱情,我是刑警寧澤鬓椭,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布颠猴,位于F島的核電站,受9級特大地震影響小染,放射性物質(zhì)發(fā)生泄漏翘瓮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一裤翩、第九天 我趴在偏房一處隱蔽的房頂上張望资盅。 院中可真熱鬧,春花似錦踊赠、人聲如沸呵扛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今穿。三九已至,卻和暖如春烫堤,著一層夾襖步出監(jiān)牢的瞬間荣赶,已是汗流浹背凤价。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拔创,地道東北人利诺。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像剩燥,于是被迫代替她去往敵國和親慢逾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 《如何培養(yǎng)孩子的社會能力》 社會能力就是孩子解決沖突和與人相處的能力灭红,人是社會動物侣滩,沒有社會能力的孩子很難取得成功...
    楊華英lena閱讀 252評論 0 0
  • 2018.5.5 星期六 小雨轉(zhuǎn)陰 感覺好久沒有寫日記了,最近事比較多变擒。公司現(xiàn)在也不太忙君珠,主要是我自己心態(tài)發(fā)生了變...
    麗兒閱讀 148評論 0 0
  • 我來北京已經(jīng)是第2年頭了,已經(jīng)修養(yǎng)好自身身心娇斑。 我清楚的知道自己想要的是什么策添。 感恩遇見的所有人事物,讓我在苦樂交...
    蘇楠雮閱讀 233評論 0 1
  • ##廣告商或比普通用戶的Voice更響亮 在Steemit上,運行該網(wǎng)站的Steem代幣可以直接購買苦丁。Steem代...
    天天_49e3閱讀 212評論 0 3