美文网首页
hap开始之旅

hap开始之旅

作者: 格雷福豪 | 来源:发表于2019-12-23 10:08 被阅读0次

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

    • hap

    写在前面:hap是什么?贴张图:

    hap架构.png

    其实用的就是SSM框架,实现了组织管理、权限管理等。学习hap的目的是一些小项目用不到hzero这么大的架构。

    开发环境

    主要进行一些配置,如git、maven、mysql等的配置,环境版本:

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

    新建数据库

    建表:如果手写建表语句,注意其中的schema,别手快写成了scheam

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

    创建用户hap_dev,设置密码为hap_dev

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

    将hap_dev和hap_prod的权限全部赋予用户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;
    

    数据源配置

    hap默认使用JNDI来配置数据源。数据源的配置独立于项目配置。默认的JNDI name为jdbc/hap_dev,tomcat需要配置context.xml。

    嗯,之前学的ssm中没有了解过JNDI是个什么东东。JNDI,我老是写成JDNI...哈哈;JNDI 全称:java naming and directory interface,如果是JDNI,那就是:java directory and naming interface好像也差不多哦。

    JNDI的作用是完成从服务器中查询数据源,和JDBC的使用不太相同。JNDI的作用帮我们完成了JDBC获取connection对象的步骤。JDBC获取的是一个连接对象,但是JNDI帮我们获取一个数据池,数据池中有多个连接,我们可以获取其中任意一个连接,所以JNDI在完成数据库连接时能够提供数据库连接一个限定等配置。

    话不多说,如何配置?先整一个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 产品,代号为 hbi
    • artifactId 本项目的顶层目录名称,使用项目代号(第一个字母大写) + Parent,如 HbiParent
    • package 包名称,使用项目代号 + core ,如 hbi.core
    • archetypeVersion 对应依赖的archetype版本

    新建3.0项目

    使用如下命令创建一个maven项目,如果出现版本问题,参照文档点击

    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
    

    上面时官网的运行命令,下面是视频中的运行命令:

    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
    

    项目结构解释:

    .
    ├── README.md (项目README,请在此处写上项目开发的注意信息,方便团队协同)
    ├── core(功能实现项目)
    │   ├── pom.xml (子项目core的pom.xml文件)
    │   └── src
    │       └── main
    │           ├── java
    │           │   ├── hbi
    │           │   │   └── core(前面的包名称)
    │           │   │       │
    │           │   │       ├── controllers(Controller包)
    │           │   │       │   └── DemoController.java(Controller类)
    │           │   │       ├── db(数据表结构,数据初始化入口文件)
    │           │   │       │   └── liquibase.groovy
    │           │   │       ├── dto(Dto包)
    │           │   │       │   └── Demo.java(Dto实现类)
    │           │   │       ├── mapper(Mapper包)
    │           │   │       │   ├── DemoMapper.java(Mapper接口)
    │           │   │       └── service(Service包)
    │           │   │           ├── IDemoService.java
    │           │   │           └── impl(Service实现)
    │           │   │               └── 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(数据库脚本及初始化数据项目)
    │   ├── pom.xml
    │   └── src
    │       └── main
    │           └── java
    │               └── hbi
    │                   └── core
    │                       └── db
    │                           ├── data(数据文件)
    │                           │   └── (init-data)
    │                           └── table(数据库表结构管理)
    │                               └── 2016-06-01-init-migration.groovy
    └── pom.xml
    

    确定使用的数据库类型

    1. 目前已经测试过支持的数据库有Mysql,Oracle,SqlServer 请修改 HbiParent/core/src/main/java/hbi/core/db/liquibase.groovy 以适配不同的数据库
    2. 确定好数据库后,按照 [Oracle,MySql,Sqlserver数据库配置]修改项目配置文件。(配置JNDI)
    3. 修改配置文件后,按照 [创建数据库]中的步骤创建数据库

    编译整个项目

    在项目HbiParent根目录下执行:mvn clean install -Dmaven.test.skip=true这个步骤有点慢,花了3分多钟,需要耐心等待
    继续在HbiParent根目录下执行: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需要注意替换一下中间数据库的地址(如果用的是虚拟机的的话)

    修改redis地址

    我开始的时候只修改了core包下配置文件的redis的ip,但是发现运行还是报错;最后结果发现在release包下也有这些配置,所以我直接搜索redis.ip把他们全改了。现在就能运行了,不过页面的中文全为?乱码。
    找了半天原因,怀疑是idea的问题、tomcat的问题;修改各种配置还是一样,最后发现,问题出在MySQL的配置文件上,除了更改大小写之外,还要增加character_set_server=utf8和max_connections=500
    看文档的时候还是不够细心啊,要严格按照文档写的内容执行的咯!就按照他写的文档来配置。

    观看HAP环境搭建视频

    记录一下讲的一些需要留意的知识。

    包结构

    • controllers
    • dto
    • mapper
    • service

    各个包下面的类命名要和spring中的配置一致,比如以ServiceImpl结尾的是配置了aop的,service和components是配置了扫描路径的;他这个controllers是配置了扫描路径的,为啥加s,咋不知道,也不敢问。

    创建类继承

    所有的controller都要继承BaseController,主要是BaseController中有一些"高级方法",如checkToken之类。service继承IBaseService和ProxySelf。serviceImpl继承BaseServiceImpl。不过mapper继承的是Mapper,不是BaseMapper。实体类继承BaseDTO

    实体类中有些字段在数据库中不存在的要加上@Transient注解。@Colum是用来指定数据库中表的列名。

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

    ssm

    Spring MVC

    • 注解
    • 数据绑定 400 BadRequest,参数传的不对
    • 数据验证
      Spring
    • 依赖注入 Autowired 根据类型 根据名称
    • 事件发布和监听
    • AOP配置,大概原理 cglib JDKProxy
      Mybatis
      只有接口和配置文件
      quartz
      activiti

    定时器

    基于quartz,开发时基本不需要进行application配置;我们创建一个job包,在其中创一个定时任务类并且继承AbstractJob类,重写safeExecute方法;在重写的方法里面就写我们想要执行的任务就ok了。这个也不需要什么注解,就这样创建一个类,再在web端计划任务中配置一个简单任务,并且把类的相对路径拷贝一份填入web端就行。
    这是idea中的代码

    hap定时任务.png

    这是web端的配置:注意其中的任务类名就是类的相对路径

    hap定时任务web端.png

    这是执行结果:

    hap定时任务结果.png

    webService

    先看一下包结构和类名:其中类名ws结尾,有一个components包其中类以Consumer结尾

    hapwebService结构.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注解,主要是监听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>
    

    到此为止才是真的结束

    logger打印信息到控制台

    ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
            Logger abc = iLoggerFactory.getLogger("abc");
            abc.info("我是打印");
    
    hap定时任务log打印.png

    好吧,hap4.0更新了,该学4.0了

    先来开发环境:

    hap4.0开发工具.png

    数据库配置:

    环境配置:

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

    数据库配置:数据库字符集为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;
    

    新建项目

    项目结构:和3.0的hap架构后端是差不多,不过前端换成了react开发了

    .
    ├── README.md (项目README,请在此处写上项目开发的注意信息,方便团队协同)
    ├── 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(后端开发目录)
    │    └── main
    │         └── java
    │             ├── hapdemo
    │             │   └── core(前面的包名称)
    │             │       │
    │             │       ├── controllers(Controller包)
    │             │       │   └── DemoController.java(Controller类)
    │             │       ├── dto(Dto包)
    │             │       │   └── Demo.java(Dto实现类)
    │             │       ├── mapper(Mapper包)
    │             │       │   ├── DemoMapper.java(Mapper接口)
    │             │       └── service(Service包)
    │             │           ├── IDemoService.java
    │             │           └── impl(Service实现)
    │             │               └── DemoServiceImpl.java
    │             └── resources(项目配置文件目录)
    │                 ├── script.db
    │                 │    ├── data
    │                 │    │    └── hapdemo-core-init-data.xlsx(excel初始化数据)
    │                 │    └── hapdemo-core-init-table-migration.groovy(groovy建表数据)
    │                 ├── hapdemo.core.grid.mapper
    │                 ├── META-INF
    │                 │    └── spring.factories
    │                 └── application.yml (spring boot 配置文件)
    ├── react(前端开发目录 )
    │    ├── src
    │    │     └── main
    │    │           └── module
    │    │                 ├── strores(定义的Dataset文件)
    │    │                 ├── view(页面和业务逻辑)
    │    │                 ├── index.scss(自定义css样式)
    │    │                 └── index.js (页面入口文件)
    │    ├── config.js
    │    └── RouteIndex.js(React访问路由配置)
    ├── .gitingore
    ├── .gitlab-ci.yml (gitlab CI 文件)
    ├── package.json(node组件依赖)
    └── pom.xml
    

    确定本项目使用到的数据库

    修改application.yml中db.type和mybatis.identity配置,默认为mysql数据库配置。我们就不用修改了。

    #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
    

    初始化数据和表结构

    新建一个init-databases.sh脚本:主要修改一下数据库地址,用户名和密码我们之前新建的用户就是这个hap_dev,他有对hap_dev_4.0的操作权

    #!/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}
    

    后端开发

    先说开发规范:

    DTO定义

    BaseDTO类 包含了常规 DTO 类的所有公共特性。_token 防篡改验证标准 WHO 字段扩展属性字段,一般用于防止当前登录用户修改其他用户数据的情形。如果没有特殊情况,当新建一个 DTO 类时,应当继承 BaseDTO 类,不要把重复的内容写到每一个类中。

    @EnableExtensionAttribute//是否启用扩展字段,不加该注解表示不启用
    @Table(name = "hap_grid_demo_b")
    @MultiLanguage//多语言表标记
    public class GridDemo extends BaseDTO {
    
        @Id//表中的主键,如果是联合主键,需要都加上@Id@Column(可选)
        @GeneratedValue(generator = GENERATOR_TYPE)
        @Where//根据Criteria中的参数拼接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层

    新建的服务都需要定义一个继承 IBaseService<\T> 的接口,服务实现类实现自己定义的接口,之后在实现类上添加 @Dataset 注解,并实现 IDatasetService<\T> 接口的 quiese 和 mutations 方法.

    如果是改造现有服务,只需要在对应的服务实现类上添加 @Dataset 注解,并实现 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;
        }
    }
    

    前端开发

    这个4.0项目必须要启动前端,才能看见前端页面的控制台;只跑后台只有登录界面。
    前端开发的准备:

    1. 根目录运行mvn package
    2. 同样根目录运行npm install --registry https://nexus.choerodon.com.cn/repository/choerodon-npm/
    3. 运行npm start

    我的npm命令执行第二步骤的时候,出现什么需要安装node-gyp;大致原因是因为没有Python,我一开始去按了node-gyp,还是不能用;我就卸载了,再在官网上下载了Python2.7.12,然后默认步骤安装,选择加入path路径,就OK了。

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

    npm install -g node-gyp

    DEMO开发

    好了,我已经完成了demo的部分开发了。真的是心累,昨晚给的开发任务,从昨晚到今天下午都特么卡在项目创建mapper之后跑不了的困局之中。好在最后终于解决了,不过我也知道了为什么会出现这些问题;下面给出遇到的问题

    项目结构问题

    我根据数据库表的属性,在hap源码中找到了一个类似模块,就是hap-core下的iam包;然后一看结构,这不是DDD吗?

    hap的demo结构1.png
    好了,我也照猫画虎来了一个同样的ddd结构,结构是有了,实体类、mapper等等都创建好了,就等着启动了呢?好来一手mvn clean packagemvn spring-boot:run结果就是直接报tk.mybatis出错,创建不了bean,而且是空指针。我一开始是以为,mapper.xml的目录不对,因为它提示找不见mapper,后来移动文件到新文件夹也没用;又以为是哪里注解没加,因为扫描不出来,结果并不是注解少了。就这样,自己瞎猜问题原因,没有仔细想问题出现的本质。
    他不是报找不见mapper吗?这个框架把扫描的路径给写死了,我创建的目录结构是扫描不到的;service也是扫描不到的。最后,还是推倒重建,按着已有的目录来创建;经过实践,确实问题就是出在扫描不到上。下面是新的目录结构:
    hap的demo结构2.png
    这个结构必须是这样的四个包,controllers必须有s。
    下面给出效果图:
    hap的demo效果图1.png

    项目创建步骤

    要实现这样的效果,要经历如下步骤:

    1.创建实体类Header和Line;并且根据数据库中属性来决定是否加注解。实体类继承BaseDTO是因为有objectVersionNumber createdBy creationDate lastUpdatedBy lastUpdateDate属性;没有加上@EnableExtensionAttribute是因为我的数据库中并不是完全有BaseDTO的10多种属性,执行mapper的插入调用的是默认的方法,就不会提示其他属性对应数据库表的列不存在的情况。还有没有加@MultiLanguage的原因是,我在页面上进行插入或者保存操作时,我不会保存语言属性到_tl的表中(因为没有创建该表,会报插入失败),还有需要注意的是,@MultiLanguage注解不加在类上,类的属性也不能加此注解,否则会报tk.mybatis异常。

    @Table(name = "htrain_application_header")
    public class Header extends BaseDTO{
    
        @Id
        @Column
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long applicationHeaderId;
    
        @NotEmpty(message = "应用名不为空")
        @Size(min = 1, max = 200)
        @Column
        private String applicationName;
    
        @NotEmpty(message = "应用编码不为空")
        @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.开发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.开发HeaderService,需要继承IBaseService和ProxySelf,继承的第一个主要是可以用默认的一些crud方法,第二个字面意思代理自己,不太清它的作用。

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

    4.开发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.开发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.开发serviceImpl,需要继承BaseServiceImpl,并实现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;
        }
    }
    

    前端开发

    到此为止,后端的逻辑是不是差不多了呢?是的,不过光有逻辑还不行,需要和前端进行交互,那我们就还要开发前端。前端的结构如下:我们就需要在红框中的三个文件中操作即可

    hap的demo前端1.png

    1.第一个文件HeaderDataSet.js:值得注意的是其中primaryKey是对应表主键字段,fields中type的类型有string和number,lookupCode是对应前端自己创建的系统设置中的代码维护。这些name对应实体类的属性名

    // stores/GridDataSet.js
    
    export default {
        //主键字段名,一般用作级联行表的查询字段
        primaryKey: 'applicatioin_header_id',
        autoQuery: true,
        pageSize: 20,
        //对应后台ds的name,自动生成约定的submitUrl, queryUrl, tlsUrl
        name: 'HeaderDemo',
        //与后端对应的列的描述
        fields: [
            {name: 'applicationCode', type: 'string', label: '应用编码', require: true},
            {name: 'applicationName', type: 'string', label: '应用名称'},
            {name: 'applicationDesc', type: 'string', label: '应用描述'},
            {name: 'enabled', type: 'string', label: '是否启用', lookupCode: 'HEADER.ENABLED'},
        ],
        //查询字段,自动生成查询组件
        queryFields: [
            {name: 'applicationCode', type: 'string', label: '应用编码'},
            {name: 'applicationName', type: 'string', label: '应用名称'},
            {name: 'applicationDesc', type: 'string', label: '应用描述'},
            {name: 'enabled', type: 'string', label: '是否启用', lookupCode: 'HEADER.ENABLED'},
        ],
    };
    

    2.开发index.js注意其中column标签的editor属性对应我们在前端操作的是输入还是选择

    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.开发RouteIndex.js,注意其中path对应的路径是加上/log-war/headers因为这是我自己写的controller对应的路径。

    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文件,加上自己在路由中配置的路径;注意路径对应自己的路由配置

    hap的demo前端xlsx1.png

    总结

    其实开发后端不难,就是必须要依据它的框架来开发。

    Demo再开发

    由于上面开发的demo只是一个操作单表的,没有前端的开发基础,只能仿照编码规则的前端结构来写(在源码code.rule包下)。大致要做出的效果就是这样:
    头的:

    demo重新开发前端头1.png
    行的:
    demo重新开发前端行.png

    项目结构

    前端:

    • react
      • src
        • logwarn
          • store
          • view

    后端:

    • demo
      • controllers
      • dto
      • mapper
      • service

    贴图:

    demo重新开发前后端结构.png

    后端开发

    在上面的demo上继续整理,更加完善对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类:注意其中已经指出了@Where @Children等注解的作用了,我在这里就不提出来叙述了。

    @Table(name = "htrain_application_header")
    public class Header extends BaseDTO{
    
        /**
         * 指定属性对应的常量,方便service和controller调用组合出查询筛选条件
         */
        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 指定这是表的列,如果属性名不满足驼峰规则,需要给注解加name属性指定表中列名
         * @Where 标志这是一个可以用作筛选条件的属性
         * @GeneratedValue 它的属性strategy 中有四种值:TABLE SEQUENCE IDENTITY AUTO
         *             分别对应的意思是:TABLE 使用一个特定的数据库表格来保存主键
         *                            SEQUENCE 根据底层数据库的序列来生成主键,条件时数据库支持序列
         *                            IDENTITY 主键由数据库自动生成(主要是自动增长型)
         *                            AUTO 主键由程序控制
         * @Transient 此注释指定属性或字段为不持久。它用于注释属性或字段实体类、映射超类或可嵌入类
         * @Children 加在 DTO 的某个 field 上,表示这个 field 是用存放子节点信息.
         *           头行结构中行的标记,也可以用作单个对象的子属性标记
         *
         *
         */
        @Id
        @Column
        @Where
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long applicationHeaderId;
    
        @NotEmpty(message = "应用名不为空")
        @Size(min = 1, max = 200)
        @Where
        @Column
        private String applicationName;
    
        @NotEmpty(message = "应用编码不为空")
        @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类的属性上加的注解根据自己的需要添加:

    @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 = "规则编码不为空")
        @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
    }
    

    创建Mapper类及Service和它的实现类

    这一部分的开发与上面类似。hap提供了单表的crud,其实在这里不需要写mapper中方法的。
    hpa中@Where注解的作用就是提供数据库查询筛选条件。

    下面是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的接口开发在这里都不需要写方法的,我在我的demo中写了没有删(有点懒)

    下面是service的实现类:其中最需要注意的就是多表crud用hap默认的mapper提供的方法如何实现。多表的操作方法都是要重新写的。

    /**
     * 注意这里的三个注解@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);
        }
    
    
        /**
         * 该类实现了IDatasetService<Header>接口,重写了queries方法
         * 在其中自己组合了where查询条件,调用父类的查询方法
         * @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);
            }
        }
    
        /**
         * 自写插入方法
         * 头行结构,因为前段保存的时候回操作两张表,我们需要自己组合写方法
         * @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);
        }
    
        /**
         * 实现了IDatasetService<Header>接口,通过前端传的对应的status值来执行操作
         * 增删改都用自己的方法,因为是多表进行操作
         * @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的实现类:我个人认为这个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);
        }
    
        /**
         * 实现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);
            }
        }
    
        /**
         * 实现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开发

    HeaderController:我感觉这个查询方法并没有用到,因为在service的实现queries中就写了这个方法,而且通过xlsx添加进数据库就映射的有这个queries;并且前端的查询就是通过Dataset访问的。

    demo重新开发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开发:它的查询方法和上面类似,我感觉没有用到;因为都在service的实现类中写了,前端访问的也是Dataset的查询方法

    @RestController
    @RequestMapping("/log-warn/line")
    public class LineController extends BaseController {
        @Autowired
        private LineService service;
    
        /**
         * 查头的同时会调用这个方法查出头对应的行
         * @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));
        }
    }
    

    前端开发

    store包下的开发:
    HeaderDataSet.js
    LineDataSet.js

    先来HeaderDataSet:其中需要注意的就是enabled这个属性了,它在前端是boolean类型,不过对应的值是后端类型中的Long;所以trueValue的值就要写后端对应的类型

    
    export default {
      primaryKey: 'applicationHeaderId',
      name: 'HeaderDemo',
      autoQuery: true,
      pageSize: 20,
        fields: [
            {name: 'applicationCode', type: 'string', label: '应用编码', require: true, unique: true},
            {name: 'applicationName', type: 'string', label: '应用名称'},
            {name: 'applicationDesc', type: 'string', label: '应用描述'},
            { name: 'enabled', type: 'boolean', label: '是否启用', trueValue: 1, falseValue: 0, defaultValue: 1 },
        ],
        //查询字段,自动生成查询组件
        queryFields: [
            {name: 'applicationCode', type: 'string', label: '应用编码'},
            {name: 'applicationName', type: 'string', label: '应用名称'},
            {name: 'applicationDesc', type: 'string', label: '应用描述'},
            {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: '告警规则',},
        { name: 'enabled', type: 'boolean', label: '是否启用', trueValue: 1, falseValue: 0, defaultValue: 1 },
      ],
    };
    

    view包下开发:
    CodeRule.js
    CodeRuleModal.js

    先来CodeRule.js,这里它对应的前端头,渲染的也是前端头表格

    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;
    
      /**
       * 确定编码规则编辑修改
       * 数据校验成功时保存
       */
      async function handleOnOkCodeRuleModal() {
        isCancel = false;
        if (await headerDS.current.validate()) {
          await headerDS.submit();
        } else {
          return false;
        }
      }
    
      function handleOnCancelCodeRuleModal() {
        isCancel = true;
      }
    
      /**
       * 关闭编码规则弹窗.
       *
       */
      function handleOnCloseCodeRuleModal() {
        if (isCancel) {
          // 新建时取消,移除dataSet记录
          if (created) {
            headerDS.remove(created);
          } else {
            // 修改时取消 重置当前记录数据
            headerDS.current.reset();
            lineDS.reset();
          }
        }
        // 重置新建记录标记
        created = null;
      }
    
      /**
       * 打开编码规则弹窗.
       * @param applicationHeaderId 头Id
       * @param enabled 是否启用
       */
      function openCodeRuleModal(applicationHeaderId, enabled) {
        if (!applicationHeaderId) {
          created = headerDS.create();
        }
        // 如果是编辑状态 编码不可编辑
        const isEditDisabled = !!applicationHeaderId;
        // 如果为启用状态 只可以进行查看 不能编辑数据
        const isEnableDisabled = (isEditDisabled) && (enabled === 1);
        // 如果为启用状态 编码规则行,不可被选中
        lineDS.selection = isEnableDisabled ? false : 'multiple';
        Modal.open({
            //唯一键, 当destroyOnClose为false时,必须指定key。
            // 为了避免与其他modal的key重复,可通过Modal.key()来获取唯一key。
          key: modalKey,
            //标题
          title: applicationHeaderId ? '编辑' : '添加',
            //抽屉模式
          drawer: true,
            //关闭时是否销毁
          destroyOnClose: true,
            //同时显示ok和cancel按钮,false的时候只显示ok按钮
          okCancel: !isEnableDisabled,
            //确认按钮文字
          okText: !isEnableDisabled ? '保存' : '关闭',
            //点击确定回调,返回false Promise.resolve(false)或
            // Promise.reject()不会关闭, 其他自动关闭
          onOk: !isEnableDisabled ? handleOnOkCodeRuleModal : handleOnCancelCodeRuleModal,
            //点击取消回调,返回false Promise.resolve(false)或
            // Promise.reject()不会关闭, 其他自动关闭
          onCancel: handleOnCancelCodeRuleModal,
            //关闭后回调
          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>
      );
    
      /**
       * 渲染表格内容.
       */
      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是行的实现

    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={'应用编码'} dataSet={headerDS} required disabled={isEditDisabled} />
            <TextField name="applicationName" label={'应用名称'} dataSet={headerDS} disabled={isEnableDisabled} />
            <TextField name="applicationDesc" label={'应用描述'} 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={'日志规则'} 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>
    );
    

    前端不太会,只能仿照编码规则的前端来写。

    xlsx的配置

    主要配置的是 permission menu role_permission三个sheet页。需要配置与路由对应

    permission:

    demo重新开发xlsx1.png
    menu:
    demo重新开发xlsx2.png

    总结

    这个demo的开发代码已经传到了码云上,下次遇到同样的可以试着仿照来写。这里面比较难搞的就是前端,后端只要把头行结构的增删改的逻辑写好就行。

    相关文章

      网友评论

          本文标题:hap开始之旅

          本文链接:https://www.haomeiwen.com/subject/olqznctx.html