简介

AnyLine的核心是一个基于spring-jdbc生态的(No-ORM)数据库操作工具

其重点是:

  • 以最简单、快速、动态、统一的方式操作数据库(结构化与非结构化)
  • 针对结果集的 数据二次处理能力

简单来说主要作了两方面的工作:

  • 1)在运行时根据需求动态生成SQL(包括DDL和DML),特别是针对查询条件的封装
    查询条件不再需要各种空判断、遍历、类型转换,也更不需要提前创建各种配置文件、各种VOPODTO
    机械繁琐的工作交给机器 示例
  • 这里说的动态是指:
    不需要针对固定的表结构或具体的Entity,分别提供不同的Service/Dao/Mapper
    一个默认的Service可以操作一切数据

操作数据库很简单,但AnyLine要实例的不仅仅是操作数据库
灵活的处理结果集数据、快速的生成符合业务逻辑的数据结构才是重点

  • 2)为结果集定义了统一的数据结构,主要是DataSet<DataRow>结构类似于List<Map>
    不要以为DataSet<DataRow>结构比实体类功能类弱
    他将提供比实体类更全面、更强大的数据处理能力
    为前端或第三方应用提供数据时 不再需要各种遍历、判断、计算、格式转换
    一切与业务无关的数学运算,DataSet<DataRow>尽量作到 一键 ... 一键 ... 示例

同时摒弃了各种繁琐呆板的Service/Dao/Entity/*O/Mapper
没有Hibernate,MyBatis 没有各种配置 各种O
没有需要自动生成的代码,没有模板文件(自动生成的都是程序员的负担)
熟悉了Anyline之后你就可以告别Hibernate,MyBatis了

误解

当然我们并不是要抛弃Entity或ORM,相反的 AnyLine源码中也使用了多达几十个Entity
在一些 可预知的 固定的 场景下,Entity的优势还是不可替代的
程序员应该有分辨场景的能力
AnyLine希望程序员手中多一个数据库操作的利器,而不是被各种模式各种hello world限制

适用场景

Anyline一的切都是面向动态、面向运行时环境
适合于抽象设计阶段(实体概念还不明确或者设计工作不局限于某个特别的实体)
常用于需要大量复杂动态的查询,以及查询的结果集需要经过深度处理的场景 如:

  • 可视化数据源
    主要用来处理动态属性,以及适配前端的多维度多结构的数据转换
    参考

  • 低代码后台
    主要用来处理动态属性、动态数据源(下拉列表)以及用户自定义的一些属性
    灵活的DDL也可以快速统一的操作各种表结构(包括各种时序、列式数据库)
    示例

  • 物联网车联网数据处理
    如车载终端、交通信号灯、数字化工厂传感器、环境检测设备数据等
    示例

  • 数据清洗、数据批量处理
    各种结构的数据、更多的是不符合标准甚至是错误的结构  
    这种场景下需要一个灵活的数据结构来统一处理各种结构的数据    
    再想像一下临时有几个数据需要处理一下(如补齐或替换几个字符)  
    这个时候先去创建个Entity,XML,Service,Dao吗  
    示例

  • 报表输出,特别是用户自定义报表
    类似于可视化环境,只是样式简单一点
    示例

  • 运行时自定义表单/查询条件/数据结构
    各个阶段都要自定义,比低代码要求更高的是:操作用户不懂编程 
    示例

  • 网络爬虫数据解析
    不固定的结构、html解析(当然不是用正则或dom那太费脑子了)
    示例

  • 异构数据库迁移同步
    动态的数据结构可以灵活的适配多种不同的表,需不需要反复的get/set
    兼容多种数据库的DDL也可以方便的在不同类型的数据库中执行
    示例

  • 还有一种很实现的场景是 许多项目到了交付的那一天 实体也没有设计完成
    别说设计了,需求都有可能还没结束就催交付了,Entity哪里找
    示例

什么情况下说明你的应该考虑换工具了

  • 非常简单的增删改查,Entity中大部分只用到了get/set方法,很少需要计算
    这一般都是些hello world 或 练习作业
    这样的直接利用默认的service查出数据返回给前端就可以收工了
    不要再生成一堆重复的模板,简单改个属性也要层层修改,从头改个遍。
    示例

  • 代码中出现了大量的List,Map结构 或者 针对查询结果集需要大量的二次计算
    这种情况应该非常多见
    随着系统的增强完善和高度的抽象,同一份数据源将为各种不同的业务场景提供数据支持
    每个场景需要的数据结构各不雷同
    这时经常是为每类场景订制视图或SQL
    但数据支持部门不可能针对每种场景每个视图、每个SQL 生成不同的Entity
    更也不可能生成一个大而全的Entity以应万变
    示例

  • 与第三方系统交换数据时
    只在自己内部系统的小圈子里时,List/Entity还勉强可以
    遇到各种第三方系统呢,根本不知道会出现什么实体什么结果
    难道还要像EJB那样相互给对方生成一堆Bean么?
    示例

无论是Map还是Entity计算能力都非常有限,通常需要开发人员各种遍历、计算、格式化
而这种大量的机械的计算不应该占用开发人员太多的时间
Anyline提供的默认数据结构DataSet/DataRow已经实现了常用的数据二次处理功能,如:
排序、维度转换、截取、去重、方差、偏差、交集合集差集、分组、忽略大小写对比、行列转换、类SQL过滤筛选(like,eq,in,less,between...)、JSON、XML格式转换等
示例

不适用场景

对已经非常明确的实体执行增删改查操作
不要跨过设计人员或者架构师/技术经理等直接拿给业务开发人员用
示例

关于数据库的适配

直接看示例(代码都是一样的、可以用来测试一下自己的数据库是否被支持) https://gitee.com/anyline/anyline-simple/tree/master/anyline-simple-data-jdbc-dialect

MySQL PostgreSQL Oracle 11G SQL Server MariaDB IBM DB2 clickhouse sqlite 达梦 tdengine tdengine H2 hsqldb kingbase Neo4j hgdb 南大通用 cassandra oceanbase 神舟通用 polardb questdb timescale

没有示例的看这个目录下有没有 【anyline-data-jdbc-dialect】还没有的请QQ群管理管理员

如何使用

数据操作不要再从生成xml/dao/service以及各种配置各种O开始
默认的service已经提供了大部分的数据库操作功能。
操作过程大致如下:

DataSet set = service.querys("HR_USER(ID,NM)",   condition(true,"anyline根据约定自动生成的=,in,like等查询条件")); 

这里的查询条件不再需要各种配置,各种if else foreach标签
Anyline会自动生成,生成规则可以参考这里的【约定规则】
分页也不需要另外的插件,更不需要繁琐的计算和配置,指定true或false即可

如何集成

只需要一个依赖、一个注解即可实现与springboot,netty等框架项目完美整合
直接看代码【anyline-simple-hello】
生产环境可以参考这几个pom
【anyboot-start】 没有web环境,如定时任务,爬虫等
【anyboot-start-mvc】 基于spring-mvc
【anyboot-start-mvc-mysql】 基于spring-mvc MySQL数据库
【anyboot-start-mvc-jsp-mysql】 基于spring-mvc MySQL数据库 支持JSP
以下可以略过

根据数据库类型添加依赖,如


<dependency>  
<groupId>org.anyline</groupId>  
<artifactId>anyline-data-jdbc-mysql(oracle|clickhouse...)</artifactId>  
<version>8.6.1-20221010</version> 
</dependency>


在需要操作数据库的地方注入AnylineService


@Qualifier("anyline.service") 
protected AnylineService service;


接下来service就可以完成大部分的数据库操作了。常用示例可以参考【示例代码】

想的HelloWorld环境下,任何方式都可以快速实现目标,更能体现优劣的是复杂多变的实战环境。

 首先要承认银弹是没有的,所以先说 劣势

  • 在增、删、改、查4个过程中,增的环境劣势比较明显
  • 操作查询结果时,不能像Entity一样有IDE的提示和自动补齐,减少了IDE的协助确实让许多人寸步难行,
    大部分人也是在这里被劝退的。
  • 在插入数据时,不能像像Entity一样:userService.save(user),而是需要指定表名:service.save(HR_USER, row);

  以上问题如果平衡的

  • AnyLine返回的结果集与Entity之间随时可以相互转换,也可以在查询时直接返回Entity

 有思想的程序员会想为何要造个轮子 可靠吗,所以再说 疑问

  • AnylineLine并非新造了一个轮子,只是简单的把业务参数传给了底层的spring-jdbc
    接下来的操作(如事务控制、连接池等)完全交给了spring-jdbc(没有能力作好的事我们不作)

  • 如果非要说是一个新轮子,那只能说原来的轮子太难用,太消耗程序员体力了。
    正事还没开始就先生成一堆的mapper,OOO,各种铺垫
    铺垫完了要操作数据实现业务了,依然啰嗦,各种 劳力 不劳心 的遍历及加减乘除
    不但操作数据库慢,查询出来的结果集更是一堆废柴,除了能遍历能get/set啥也不是

 所以重点说 优势

  1. 关于查询条件

    这是开发人员最繁重的体力劳动之一
    接收参数、验证、格式化、层层封装传递到mapper.xml,再各种判断、遍历就为生成一条SQL
    下面的这些标签许多人可能感觉习以为常了,以下是反例 反例 反例


 <if test="code != null and code != '' ">
    AND CODE = #{code}
 </if>

 <if test="name != null and name != '' ">
  AND NAME like concat('%',#{name},'%')
</if>

<if test="types != null and types.size > 0 ">
    AND TYPE IN
    <foreach collection="types" item="type" open="(" close=")" separator=",">
        #{type}
    </foreach>
</if>

但这并不正常,这期间还有什么是必须程序员参与的,程序员不参与就自动不了,就约定不了的吗?
  
换一种方式处理:  
不要mapper.xml了,也更不要定位SQL的ID的
    
直接在java中这样处理          
condition("CODE:code","NAME:name%", "TYPE:[type]")
其他的交给机器处理,生成SQL     
WHERE CODE = ? AND NAME LIKE '?%' AND TYPE IN(?,?...)

    更多的约定可以参考这里的【约定规则】

  2. 结果集的二次操作

    这是开发人员最繁重的劳动之二
    从数据库中查询出数据后,根据业务需求还需要对结果集作各种操作,最简单的如加减乘除、交集差集、筛选过滤等
    这些常见的操作DataSet中都已经提供默认实现了,如ngl表达式、聚合函数、类SQL筛选过滤、维度转换等。

 3. 关于面向动态与运行时环境

    这里说的动态是指出动态数据源、动态数据结构、动态结果集
    运行时环境是指在系统运行阶段才能确定以上内容,而不是在需求、设计、编码阶段
        

    动态数据源:

    一般是在系统运行时生成
    典型场景如数据中台,用户通过管理端提交第三方数据库的地址帐号,中台汇聚多个数据源的数据
    
    这种情况下显示不是在配置文件中添加多个数据源可以解决的
    而是需要在接收到用户提交数据后,生成动态的数据源
    生成的动态数据源最好交给Spring等容器管理
    以充分利用其生态内的连接池,事务管理,切面等现有工具
    
    在切换数据源时也不能通过切面来实现
    而是根据组织或租户身份等上下文环境来切换

    动态数据结构:

    一般由非专业开发人员甚至是最终用户来设计表结构
    根据用户设置或不同场景返回不同结构的结果集
    查询条件也由用户动态指定
    结果集与查询条件的选择范围也不能在编码阶段设置限定
    典型场景如物联网平台仪器设备参数、低代码平台、报表工具

    常用的数据结构有两种

    1).DataRow类似于一个Map
    2).DataSet是DataRow的集合,并内含了分页信息

    以下场景中将逐步体现出相对于List,Entity的优势

    1). 最常见的如更新或查询部分列

        DataRow row = service.query("HR_USER(ID,CODE)")
        service.update(row,"CODE")

    2).可视化数据源、报表输出、数据清洗

        这些场景下都需要的数据结构都是灵活多变的
        经常是针对不同的业务从多个表中合成不同的结构集
        甚至是运行时根据用户输入动态结合的结构集
        输出结果集后又需要大量的对比及聚合操作
        这种情况下显示不可能为每个结果集生成一个对应Entity,只能是动态的Map结构
        在对结构集的二次操作上,DataRow/DataSet可以在抽象设计阶段就完成,而Entity却很难                           

    3).低代码后台、元数据管理

        作为一个低代码的后台,首先需要具体灵活可定制的表结构(通常会是一个半静半动的结构)
        我们将不再操作具体的业务对象与属性。对大部分业务的操作都只能通过抽象的元数据进行。
        举例来说一个简单的求和过程,原来在对静态结构时常用的的遍历、Lamda、反射都难堪重任了。
        我们能接收到的信息通常是这样的:类型(学生)、属性(年龄)、条件(年级=1)、聚合公式(平均值)
        Anyline的实现过程类似这样
        DataSet set = service.querys(学生,年级=1);
        int 平均年龄 = set.agg(平均值,年龄);

    4).运行时自定义表单、查询条件

        许多情况下我们的基础版本产品,很难满足用户100%的需求,
        而这些新需求又大部分是一些简单的表单、查询条件
        如果是让程序员去开发一个表单,添加几个查询条件,那确实很简单
        但用户不是程序员,我们也不可能为每个用户提供全面全天候的技术支持
        考虑到成本与用户体验的问题通常会给用户提供一个自定义表单与查询条件的功能
        自定义并不难,难的是对自定义表单的存储、查询、关联,以及对自定义查询条件的支持
        与上一条说的元数据管理一样,我们在代码实现环节还是不知道会有什么对象什么属性
        当然也更不会有对应的service, dao, mapper, VO/DTO/BO/DO/PO/POJO
        Anyline的动态查询类似这样实现
        service.query(类型(属性集合),condition().add('对比方式','属性','值');

    5).物联网环境(特别是像Cassandra、ClickHouse等列式数据库 InfluxDB、timescale等时序数据库)

        与低代码平台类似都需要一种动态的结构,并且为了数据读取的高效,数据在水平方向上变的更分散。
        这与最终用户需要显示的格式完全不一样,直接通过数据库查询出来的原始数据通常是类似这样

时间戳 KEY VALUE
1657330073131 LAT 39.917055
1657330073131 LNG 116.392191
1657330073132 LAT 39.917055
1657330073132 LNG 116.392191
1657330073133 LAT 39.917055
1657330073134 LNG 116.392191

         而最终展示的界面可能是这样:

时间戳 LNG LAT
1657330073131 116.392191 39.917055
1657330073131 116.392191 39.917055
日期 时间点1 时间点2 时间点...N
LNG LAT LNG LAT LNG LAT
01-01 116.392191 39.917055 116.392191 39.917055 116.392191 39.917055
01-02 116.392191 39.917055 116.392191 39.917055 116.392191 39.917055

        当然实战中会比这更复杂,历经实战的程序员一定体验过什么是千变万化、什么是刁钻苛刻
        数据库中将不再有一一对应的hello表格,java中也没有对应的Entity
        可以想像的出来基于一个静态结构或者原始的Map,List结构需要程序员负责多少体力
        要在这个基础上实现让用户自定义报表,那可能比把用户培养成一个程序员还要困难

        而一个有思想的程序员应该会把以上问题抽象成简单的行列转换的问题
        并在项目之前甚至没有项目的时候就已经解决之。
        各种维度的转换可以参考DataSet.pivot()的几个重载 或示例代码 anyline-simple-result

                                   

    6).关于分页查询的数据存储结构

        

         通过默认的方式查询
  • 无论是否分页 都可以通过DataSet结构接收数据
  • 不同的是分页后DataSet.PageNavi中会嵌入详细的分页信息
         通过User.class查询数据时
  • 如果没有分页 可以通过List<User>>结构接收数据
  • 如果有分页了 那需要通过Page<List<User>>结构接收数据
  • 简单查询个部门列表,还要根据分不分页写两个接口吗
    7).数据加密

    对于需要加密的数据经常会遇到数字类型的ID
        而加密后的数据类型通常是String类型,导致原对象无法存储
                                            

    
    
                 



  • http参数到jdbc参数的转换

    在实际开发中、业务开发人员经常需要大量的时间,不厌其烦的从http/rpc中提取参数,判断验证,生成jdbc要求格式的参数,再把参数依次传到service、dao,最后返回一个实现bean。这整个过程中经常有各种小细节容易忽略而导致异常,如空值处理,IN条件生成等。
    而在整个项目中这些过程又是大量重复或类似的。这不但影响开发速度与代码质量,更影响心情。
    所以AnyLine提供了一个统一约定格式来实现这些繁琐的操作,格式大致如下

      参数值⇢
    约定格式⇣
    1 2 3 4 5 6 7
      code=0 code= code=0&code=1&cd=2&user=5 code=0,1&cd=2&user=5
    cd=2&cd=3 code=0(密文) cd=2(密文)&cd=3(密文)
    1 CODE:code CODE = 0 忽略 CODE = 0
    CODE = 0 忽略 忽略 忽略
    2 CODE:%code% CODE LIKE '%0%' 忽略 CODE LIKE '%0%'
    CODE LIKE '%0%'
    忽略 忽略 忽略
    3 CODE:%code CODE LIKE '%0' 忽略 CODE LIKE '%0'
    CODE LIKE '%0' 忽略 忽略 忽略
    4 CODE:code% CODE LIKE '0%' 忽略 CODE LIKE '0%'
    CODE LIKE '0%' 忽略 忽略 忽略
    5 CODE:%code:cd% CODE LIKE '%0%' 忽略 CODE LIKE '%0%'
    CODE LIKE '%0%' CODE LIKE '%2%' 忽略 忽略
    6 CODE:%code:cd:${9}% CODE LIKE '%0%' CODE LIKE '%9%' CODE LIKE '%0%'
    CODE LIKE '%0%' CODE LIKE '%2%' 忽略 忽略
    7 CODE:%code:cd CODE LIKE '%0' 忽略 CODE LIKE '%0'
    CODE LIKE '%0' CODE LIKE '%2' 忽略 忽略
    8 CODE:%code:cd:${9} CODE LIKE '%0' CODE LIKE '%9' CODE LIKE '%0'
    CODE LIKE '%0' CODE LIKE '%2' 忽略 忽略
    9 CODE:[code] CODE = 0 忽略 CODE IN(0,1)
    CODE IN(0,1) 忽略 忽略 忽略
    10 CODE:[split(code)] CODE = 1 忽略
    CODE IN(0,1)
    CODE IN(0,1)
    忽略
    忽略
    忽略
    11 CODE:[org.ClassA.split(code)] CODE = 1 忽略
    CODE IN(0,1)
    CODE IN(0,1)
    忽略
    忽略
    忽略
    12 CODE:[code:cd] CODE = 0 忽略 CODE IN(0,1)
    CODE IN(0,1) CODE IN(2,3) 忽略 忽略
    13 CODE:[cd+] 忽略
    忽略
    CODE = 2
    CODE = 2 CODE IN(2,3) 忽略
    CODE IN(2,3)
    14 CODE:[code:cd:${[6,7,8]}] CODE = 0 CODE IN(6,7,8) CODE IN(0,1)
    CODE IN(0,1) CODE IN(2,3) 忽略 忽略
    15 CODE:[code:cd:${6,7,8}]
    CODE = 0 CODE IN(6,7,8) CODE IN(0,1)
    CODE IN(0,1) CODE IN(2,3) 忽略 忽略
    16 +CODE:code CODE = 0 CODE IS NULL CODE = 0
    CODE = 0 CODE IS NULL 忽略 忽略
    17 ++CODE:code CODE = 0 不执行 CODE = 0
    CODE = 0 不执行 忽略 忽略
    18 CODE:>code CODE > 0 忽略 CODE > 0
    CODE > 0 忽略 忽略 忽略
    19 CODE:>code:cd CODE > 0 忽略 CODE > 0
    CODE > 0 CODE > 2 忽略 忽略
    20 CODE:>code:${9} CODE > 0 CODE > 9 CODE > 0
    CODE >0 CODE > 9 CODE > 9 CODE > 9
    21 CODE:code:cd CODE = 0 忽略 CODE = 2
    CODE = 2 CODE = 2 忽略 忽略
    22 CODE:code:cd:${9} CODE = 0 CODE = 9 CODE = 0
    CODE = 0 CODE = 2 忽略 忽略
    23 CODE:code|cd 只有code取值成功,当前条件才生效,注意与下一条的区别 CODE = 0 忽略 CODE =0 OR CODE = 2
    CODE =0 OR CODE = 2 忽略 忽略 忽略
    24 CODE:code|{NULL} CODE = 0 OR CODE IS NULL 忽略 CODE = 0 OR CODE IS NULL
    CODE = 0 OR CODE IS NULL 忽略
    忽略
    忽略
    25 CODE:code|CODE:cd code与cd不相干,哪个有值取哪个 CODE = 0 忽略 CODE = 0 OR CODE = 1
    CODE = 0 OR CODE = 1 CODE = 2 忽略 忽略
    26 CODE:code|CD:cd 与上一条规则相同 CODE = 0 忽略 CODE = 0 OR CD = 2
    CODE = 0 OR CD = 2 CD = 2 忽略 忽略
    27 CODE:code:cd|user
    CODE = 0 忽略 CODE = 0 OR CODE = 5
    CODE = 0 OR CODE = 5 CODE = 2 忽略 忽略
    28 CODE:code:cd|{9}
    CODE = 0
    忽略 CODE = 0 OR CODE = 9
    CODE = 0 OR CODE = 9 CODE = 2 OR CODE = 9 CODE = 9 CODE = 9
    29 CODE:code+:${9} http参数值实际应该是密文 CODE = 9 CODE = 9 CODE = 9
    CODE = 9 CODE = 9 CODE = 0 CODE = 9
    30 CODE:code+:cd:${9}code需要密文,cd需要明文 CODE = 9 CODE = 9 CODE = 2
    CODE = 2 CODE = 2 CODE = 0 CODE = 9
    31 CODE:code+:cd+ 忽略
    忽略
    忽略
    忽略
    忽略
    CODE = 0 CODE = 2
    32 CODE:code|CODE:cd|CD:cd|CD:code CODE = 0 OR CD = 0 忽略 CODE =0 OR CODE = 2 OR ID =0 OR ID = 2
    CODE =0 OR CODE = 2 OR ID =0 OR ID = 2 CODE =2 OR CD =2 忽略 忽略
    33 CODE:code:${9}|CD:cd:${9} CODE = 0 OR CD = 9 CODE = 9 OR CD = 9 CODE = 0 OR CD = 2
    CODE = 0 OR CD = 2 CODE = 9 OR CD = 2 CODE = 9 OR CD = 9 CODE = 9 OR CD = 9

    以上SQL在实际运行中以占位符?生成,类似CODE > '0'的条件实际是CODE > ?,java中通过 preapreStatement赋值,最终执行结果与数据类型有

    忽略:表示合成SQL时不拼接当前查询条件

    不执行:表示整个SQL不执行,querys返回长度为0的DataSet,query返回null

    "+"开头表示必须条件,如果没有值传则生成CODE IS NULL的条件(仅"="时有效,其他IN,>时,当前条件忽略)

    “++”开头时,如果没有传值则整个SQL不执行,返回长度为零的DataSet

  • 常规数据库操作

    AnyLine提供了一对基础的service/dao。通过接收上一步约定好格式的参数,只需要在项目中注入AnylineService即可完成绝大部分的数据库操作。(AnylineService已通过@Service("anyline.service")的形式注册到Spring上下文中)
    在实际开发过程中,通常是用项目的BaseController继承tAnylineController(适用于springmvc框架,如果是Struts2框架则继承AnylineAction)
    AnylineController中已经注入AnylineService serive,并重载了大量condition函数。
     

    Controller层操作过程大致如下:

    DataSet set = service.querys("HR_USER(ID,NM)"
    	, condition(true,"TYPE_CODE:[type]","NM:nm%","+ZIP_CODE:zip:","++DEPT_CODE:dept","POST_CODE:post:pt:{101}")
    	, "IS_PUB:1");

    实现功能:
        根据TYPE_CODE,NM,IS_PUB列查询HR_USER表,并根据http参数自动分页
    生成的SQL(以mysql为例):

    SELECT 
    	ID,NM
    FROM HR_USER  
    WHERE 
    	TYPE_CODE IN (?,?,?)  
    	AND NM LIKE CONCAT(?,'%')
    	AND ZIP_CODE = ?
    	AND DEPT_CODE = ?
    	AND POST_CODE = ?
    	AND IS_PUB = ?
    LIMIT 0,10

    以SQL根据http参数传递情况动态生成,如果http/rpc中没有相关的参数则相应的查询条件不生成。
    IS_PUB值不是来自http
    TYPE_CODE IN(?,?,?)中的占位符数量根据http中type参数数量生成
    如果http中没有提供zip则生成ZIP_CODE IS NULL的查询条件
    如果http中没有提供dept则整个SQL将不执行,当前querys函数返回长度为0的DataSet
    如果http中没有提供post,则取根据pt取值,如果没有提供pt,则使用默认值101,其中的{}表示常量,而不是http中的参数key
    注意IS_PUB的值是一个固定值1,也不是通过http参数中取值,所以不需要放在condition函数中转换

相关分类