树形下拉组件数据查询覆写

一、背景与问题

Oinone 的树形下拉组件(TreeSelect)和树视图(Tree)在查询数据时,最终调用的是树节点所配置模型的数据管理器函数:

树接口 调用的数据管理器函数 场景
fetchAll queryListByWrapper 一次性全量加载
fetchChildren queryPage 按层级懒加载(默认)
queryKeywords4Tree queryListByWrapper 关键字搜索
reverselyQuery queryListByWrapper 已选数据回填

官方文档原文:「所有树查询接口都会调用模型对应的数据管理器函数,可以通过重写的方式修改其查询结果。」

直接覆写的风险

如果直接在原模型(如 business.PamirsDepartment)上覆写 queryPage:

// ⚠️ 危险写法:会影响所有使用 PamirsDepartment.queryPage 的场景
@Component
@Model.model(PamirsDepartment.MODEL_MODEL)
public class DeptQueryAction {

    @Function.Advanced(type = FunctionTypeEnum.QUERY, category = FunctionCategoryEnum.QUERY_PAGE)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public Pagination<PamirsDepartment> queryPage(Pagination<PamirsDepartment> page, IWrapper<PamirsDepartment> queryWrapper) {
        // 追加过滤条件...
        return new PamirsDepartment().queryPage(page, queryWrapper);
    }
}

该覆写会全局生效——不仅是树形下拉,所有调用 PamirsDepartment 的 queryPage 的地方(组织架构页面、人员管理列表、权限分配、框架内部关联查询等)都会受到影响,容易引发难以排查的副作用。

二、推荐方案:代理模型(PROXY)隔离

核心思路

  1. 创建一个继承原模型的代理模型(ModelTypeEnum.PROXY),拥有独立的模型命名空间
  2. 在代理模型中重新声明自关联字段(如 parent),将关联目标指向代理模型自身
  3. 在代理模型的 Action 中覆写数据管理器函数,追加树形场景专属的过滤逻辑
  4. 树形下拉的视图 DSL 中,<node> 的 model 指向代理模型

代理模型与原模型共享同一张数据表,但函数注册在各自独立的命名空间下,互不干扰。

框架官方示例

Oinone 框架自身就采用了这一模式:ResourceRegionProxyModel 继承 ResourceRegion,用于地区页面的代理展示,其扩展点 ResourceRegionProxyModelExtPoint 中的 queryPageAfter 只影响代理模型自身的查询。

三、完整实现步骤(以 PamirsDepartment 为例)

场景假设

业务需求:某个表单页面的「部门」树形下拉只需要展示当前公司(companyCode == 当前用户公司)且启用状态(dataStatus == ENABLED)的部门,而系统中其他使用 PamirsDepartment 的地方(组织架构、权限管理等)不受影响。

步骤 1:定义代理模型

package com.example.demo.model;

import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.enmu.ModelTypeEnum;
import pro.shushi.pamirs.business.api.model.PamirsDepartment;

import java.util.List;

/**
 * 部门树形下拉专用代理模型
 * <p>
 * 继承 PamirsDepartment,共享同一张数据表,
 * 但拥有独立的函数命名空间,覆写 queryPage 不影响原模型。
 */
@Model.model(DeptTreeProxy.MODEL_MODEL)
@Model.Advanced(type = ModelTypeEnum.PROXY)
@Model(displayName = "部门(树选择)", labelFields = "name")
public class DeptTreeProxy extends PamirsDepartment {

    public static final String MODEL_MODEL = "demo.DeptTreeProxy";

    /**
     * 【必须重新声明】上级部门(自关联字段)
     * <p>
     * 基类 PamirsDepartment 中 parent 的类型是 PamirsDepartment,
     * 代理模型必须将其覆盖为指向自身(DeptTreeProxy),
     * 否则设计器/树组件无法在代理模型命名空间内构成完整的自关联关系,
     * 导致树形下拉无法正确展开层级。
     */
    @Field.many2one
    @Field.Relation(relationFields = {"parentCode"}, referenceFields = {"code"})
    @Field(displayName = "上级部门")
    private DeptTreeProxy parent;

    /**
     * 【必须重新声明】下级部门(自关联 O2M 字段)
     * <p>
     * 与 parent 对应,保持自关联关系的双向完整性。
     */
    @Field.one2many
    @Field.Relation(relationFields = {"code"}, referenceFields = {"parentCode"})
    @Field(displayName = "下级部门")
    private List<DeptTreeProxy> childList;
}

要点说明:

  • ModelTypeEnum.PROXY:标记为代理模型,不会新建数据表,底层复用父模型的表
  • MODEL_MODEL 使用项目自己的命名空间(如 demo.),与框架的 business. 隔离

⚠️ 关键陷阱:自关联字段必须重新声明

基类 PamirsDepartment 中 parent 字段的类型是 PamirsDepartment,其关联关系指向 business.PamirsDepartment。
如果代理模型不重新声明 parent,则树组件在解析 selfReferences="parent" 时,
发现该字段的关联目标不是当前代理模型(demo.DeptTreeProxy),而是父模型(business.PamirsDepartment),
导致设计器页面和树形下拉无法构成完整的自关联树结构,表现为:树只有一层、无法展开子节点、或报错。

因此,所有构成树形结构的自关联字段(M2O 的 parent 和 O2M 的 childList)都必须在代理模型中重新声明,将类型指向代理模型自身。

步骤 2:在代理模型 Action 中覆写数据管理器函数

package com.example.demo.action;

import org.springframework.stereotype.Component;
import pro.shushi.pamirs.framework.connectors.data.sql.query.LambdaQueryWrapper;
import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.api.dto.condition.Pagination;
import pro.shushi.pamirs.meta.api.session.PamirsSession;
import pro.shushi.pamirs.meta.constant.FunctionConstants;
import pro.shushi.pamirs.meta.enmu.FunctionCategoryEnum;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum;
import pro.shushi.pamirs.core.common.enmu.DataStatusEnum;
import com.example.demo.model.DeptTreeProxy;

import java.util.List;

/**
 * 部门树形下拉代理模型的查询动作
 * <p>
 * 覆写 queryPage 和 queryListByWrapper,
 * 仅对树形下拉组件的查询生效,不影响 PamirsDepartment 原模型。
 */
@Component
@Model.model(DeptTreeProxy.MODEL_MODEL)
public class DeptTreeProxyAction {

    /**
     * 覆写分页查询 —— 树组件懒加载(fetchChildren)时调用
     *
     * @param page         分页参数
     * @param queryWrapper 框架自动构建的查询条件(已包含树层级关联条件)
     * @return 过滤后的分页结果
     */
    @Function.Advanced(type = FunctionTypeEnum.QUERY, category = FunctionCategoryEnum.QUERY_PAGE)
    @Function.fun(FunctionConstants.queryPage)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public Pagination<DeptTreeProxy> queryPage(Pagination<DeptTreeProxy> page,
                                               QueryWrapper<DeptTreeProxy> queryWrapper) {
        // 将 QueryWrapper 转为 LambdaQueryWrapper,通过方法引用指定字段,避免硬编码列名
        LambdaQueryWrapper<DeptTreeProxy> wrapper = queryWrapper.lambda();
        // 追加业务过滤条件:只展示启用状态的部门
        wrapper.eq(DeptTreeProxy::getDataStatus, DataStatusEnum.ENABLED);
        // 追加业务过滤条件:只展示当前用户所属公司的部门
        String companyCode = PamirsSession.getContext().getCompanyCode();
        wrapper.eq(DeptTreeProxy::getCompanyCode, companyCode);
        // 调用代理模型的默认数据管理器执行查询
        return new DeptTreeProxy().queryPage(page, wrapper);
    }

    /**
     * 覆写列表查询 —— 树组件全量加载(fetchAll)和搜索(queryKeywords4Tree)时调用
     *
     * @param queryWrapper 框架自动构建的查询条件
     * @return 过滤后的列表结果
     */
    @Function.Advanced(type = FunctionTypeEnum.QUERY)
    @Function.fun(FunctionConstants.queryListByWrapper)
    @Function(openLevel = {FunctionOpenEnum.LOCAL, FunctionOpenEnum.REMOTE, FunctionOpenEnum.API})
    public List<DeptTreeProxy> queryListByWrapper(QueryWrapper<DeptTreeProxy> queryWrapper) {
        // 将 QueryWrapper 转为 LambdaQueryWrapper
        LambdaQueryWrapper<DeptTreeProxy> wrapper = queryWrapper.lambda();
        // 追加与 queryPage 相同的业务过滤条件
        wrapper.eq(DeptTreeProxy::getDataStatus, DataStatusEnum.ENABLED);
        String companyCode = PamirsSession.getContext().getCompanyCode();
        wrapper.eq(DeptTreeProxy::getCompanyCode, companyCode);
        return new DeptTreeProxy().queryListByWrapper(wrapper);
    }
}

要点说明:

  • @Function.fun(FunctionConstants.queryPage):将方法注册为代理模型命名空间下的 queryPage 数据管理器函数,覆盖默认实现
  • 树组件内部通过 Models.data().queryPage(pagination, queryWrapper) 调用时,pagination.model 已被设置为节点配置的模型编码,因此会自动路由到代理模型的覆写版本
  • 通过 queryWrapper.lambda() 转为 LambdaQueryWrapper,使用 DeptTreeProxy::getDataStatus 等方法引用指定字段,避免硬编码数据库列名,字段重命名时编译期即可发现错误
  • 方法体内调用 new DeptTreeProxy().queryPage(page, wrapper) 执行实际的数据库查询

步骤 3:配置树形下拉视图 DSL

在表单视图中,将 TreeSelect 字段的 <node> 指向代理模型:

<view model="业务模型编码" type="form" name="xxx_form_view">
  <template slot="form">
    <!-- 树形下拉字段:node 的 model 指向代理模型 -->
    <field data="department" label="部门" widget="TreeSelect" span="1">
      <nodes>
        <node label="activeRecord.name"
              model="demo.DeptTreeProxy"
              selfReferences="parent"
              searchFields="name" />
      </nodes>
    </field>
  </template>
</view>

DSL 关键属性:

属性 说明
model 树节点数据来源模型 —— 指向代理模型,这是隔离生效的关键
selfReferences 自关联字段名(parent),表示该模型通过 M2O 字段 parent 构成树
label 节点显示文本的表达式,activeRecord.name 表示取记录的 name 字段
searchFields 搜索时匹配的字段列表
filter 可选,RSQL 语法的静态过滤条件(简单场景可替代后端覆写)

步骤 4(可选):多级树节点配置

如果树形下拉需要「公司 → 部门」两级结构:

<field data="department" label="部门" widget="TreeSelect" span="1">
  <nodes>
    <!-- 第一级:公司 -->
    <node label="activeRecord.name" model="business.PamirsCompany" />
    <!-- 第二级:部门,通过 company 字段关联上级,parent 构成部门内自关联 -->
    <node label="activeRecord.name"
          model="demo.DeptTreeProxy"
          references="company"
          selfReferences="parent"
          searchFields="name" />
  </nodes>
</field>

四、方案对比

维度 直接覆写原模型 代理模型隔离(推荐) DSL filter 属性
影响范围 全局所有使用该模型查询的场景 仅树组件(指向代理模型的视图) 仅该视图的树节点
灵活性 高(可写任意 Java 逻辑) 高(可写任意 Java 逻辑) 低(仅支持 RSQL 静态条件)
副作用风险 高 无 无
适用场景 确实需要全局修改查询行为 特定页面/组件需要定制数据 简单的静态过滤

五、注意事项

  1. 自关联字段必须在代理模型中重新声明:基类的 parent(M2O)和 childList(O2M)字段的关联目标指向基类模型,代理模型必须用自身类型重新声明这些字段,否则设计器无法识别代理模型内的完整树形关联关系,树形下拉将无法正确展开
  2. queryPage 和 queryListByWrapper 都要覆写:树组件的懒加载走 queryPage,全量加载和搜索走 queryListByWrapper,只覆写一个会导致部分场景过滤失效
  3. 代理模型不建新表:ModelTypeEnum.PROXY 类型的模型复用父模型的物理表,无需担心数据同步问题
  4. 使用 LambdaQueryWrapper + 方法引用指定字段:通过 queryWrapper.lambda() 转换后,使用 DeptTreeProxy::getDataStatus 等方法引用指定字段,避免硬编码数据库列名字符串,字段重命名时编译期即可发现错误
  5. 不要在覆写方法中调用 Models.data().queryPage():这会再次路由到函数注册中心,可能形成递归调用;应使用 new XxxProxy().queryPage(page, wrapper) 直接执行
  6. 简单过滤优先考虑 DSL filter:如果过滤条件是静态的(如 dataStatus == ENABLED),直接在 <node filter="..."> 中配置 RSQL 即可,无需写代码

Oinone社区 作者:nation原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/25596.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

Like (1)
nation's avatarnation数式员工
Previous 2026年7月2日 pm4:53
Next 2023年11月1日 pm6:40

相关推荐

  • 如何通过 Oineone 平台自定义视图

    在 Oineone 平台上,自定义视图允许用户替换默认提供的页面布局,以使用自定义页面。本文将指导您如何利用 Oineone 提供的 API 来实现这一点。 默认视图介绍 Oineone 平台提供了多种默认视图,包括: 表单视图 表格视图 表格视图 (左树右表) 详情视图 画廊视图 树视图 每种视图都有其标准的 layout。自定义视图实际上是替换这些默认 layout 的过程。 默认的表单视图 layout <view type="FORM"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> 内嵌的的表单视图 layout <view type="FORM"> <element widget="form" slot="form"> <xslot name="fields" slotSupport="pack,field" /> </element> </view> 默认的表格 <view type="TABLE"> <pack widget="group"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> </pack> <pack widget="group" slot="tableGroup"> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="table" slot="table" slotSupport="field"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" slotSupport="action" /> </element> </pack> </view> 内嵌的的表格 <view type="TABLE"> <view type="SEARCH"> <element widget="search" slot="search" slotSupport="field" /> </view> <element widget="actionBar" slot="actionBar" slotSupport="action"> <xslot name="actions" slotSupport="action" /> </element> <element widget="table" slot="table"> <element widget="expandColumn" slot="expandRow" /> <xslot name="fields" slotSupport="field" /> <element widget="rowActions" slot="rowActions" /> </element> </view> 左树右表 <view type="table"> <pack title="" widget="group"> <view type="search"> <element slot="search" widget="search"/> </view> </pack> <pack title="" widget="group"> <pack widget="row" wrap="false"> <pack widget="col" width="257"> <pack title="" widget="group"> <pack widget="col"> <element slot="tree" widget="tree"/> </pack> </pack> </pack> <pack mode="full" widget="col"> <pack widget="row"> <element justify="START" slot="actionBar"…

    2024年4月3日
    1.9K00
  • 【DM】后端部署使用Dameng数据库(达梦)

    达梦数据库配置 驱动配置 达梦数据库的服务端版本和驱动版本需要匹配,建议使用服务端安装时提供的jdbc驱动,不要使用官方maven仓库中的驱动。 报错 表 xx 中不能同时包含聚集 KEY 和大字段,建表的时候就指定非聚集主键。SELECT * FROM V$DM_INI WHERE PARA_NAME = ‘PK_WITH_CLUSTER’;SP_SET_PARA_VALUE(1,’PK_WITH_CLUSTER’,0) Maven配置 DM8(目前maven仓库最新版本) <dm.version>8.1.2.192</dm.version> <dependency> <groupId>com.dameng</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>${dm.version}</version> </dependency> PS: 8.1.3.12版本驱动需要手动上传到nexus仓库使用,本文包含该版本相关内容。 Maven配置 DM7 <dm7.version>7.6.1.120</dm7.version> <dependency> <groupId>com.dameng</groupId> <artifactId>Dm7JdbcDriver18</artifactId> <version>${dm7.version}</version> </dependency> PS: 7.6.1.120版本驱动需要手动上传到nexus仓库使用,本文包含该版本相关内容。 离线驱动下载 Dm7JdbcDriver18-7.6.1.120.jarDmJdbcDriver18-8.1.3.12.jar JDBC连接配置 pamirs: datasource: base: type: com.alibaba.druid.pool.DruidDataSource driverClassName: dm.jdbc.driver.DmDriver # url: jdbc:dm://127.0.0.1:5236/BASE?clobAsString=true&useUnicode=true&characterEncoding=utf8&compatibleMode=mysql url: jdbc:dm://127.0.0.1:5236?schema=BASE&clobAsString=true&columnNameUpperCase=false&useUnicode=true&characterEncoding=utf8&compatibleMode=mysql username: xxxxxx password: xxxxxx initialSize: 5 maxActive: 200 minIdle: 5 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true asyncInit: true validConnectionCheckerClassName: com.alibaba.druid.pool.vendor.OracleValidConnectionChecker validationQuery: SELECT 1 FROM DUAL 连接url配置 点击查看官方文档:DM JDBC 编程指南 连接串1 jdbc:dm://127.0.0.1:5236?schema=BASE&clobAsString=true&columnNameUpperCase=false&useUnicode=true&characterEncoding=utf8&compatibleMode=mysql PS:schema参数在低版本驱动区分大小写,高版本驱动不再区分大小写,为了避免错误,统一使用全大写。columnNameUpperCase参数与官方介绍不一致,为了避免错误,需要显式指定。 连接串2 jdbc:dm://127.0.0.1:5236/BASE?clobAsString=true&useUnicode=true&characterEncoding=utf8&compatibleMode=mysql PS:可能是未来更高版本中使用的连接串形式。 达梦数据库在不同驱动版本下需要使用不同的连接串进行处理,具体可参考下表:(使用错误的连接串将无法正常启动) Dm7JdbcDriver18版本 Build-Time 使用的连接串类型 是否支持指定schema schema是否区分大小写 是否可用 不可用原因 7.6.0.165 2019.06.04 1 否 是 否 不支持LocalDateTime类型 7.6.1.120(建议) 2022.09.14 1 是 是 是 – DmJdbcDriver18版本 Build-Time 使用的连接串类型 是否支持指定schema schema是否区分大小写 是否可用 不可用原因 8.1.2.192 2023.01.12 1 是 否 是 – 8.1.3.12(建议) 2023.04.17 2 是 否 是 – 方言配置 pamirs方言配置 pamirs: dialect: ds: base: type: DM version: 8 majorVersion: 8 pamirs: type: DM version: 8 majorVersion: 8 数据库版本 type version majorVersion 7-20220916 DM 7 20220916 8-20230418 DM 8 8 schedule方言配置 pamirs: event: schedule: dialect: type: DM version: 8 majorVersion: 8 type version majorVersion…

    2023年11月1日
    15.7K00
  • 低无一体使用 (后端)

    低无一体使用 (后端) 低无一体应用 打开低无一体应用。 选择应用模块 在选择模块选择框中,下拉选择需要使用低无一体的应用模块。 生成SDK 点击生成SDK, 生成当前选择应用模块的低无一体SDK。 点击之后的系统消息 提示"生成SDK成功",表示操作完成。 生成扩展工程 点击下载扩展工程模板, 生成当前选择应用模块的低无一体SDK。 点击之后的系统消息 提示"下载扩展工程模板成功",表示操作完成。 之后刷新页面 下载扩展工程 使用系统消息中的链接或者详情页中的下载地址下载扩展工程 扩展工程结构概览 自定义Action示例 import org.springframework.stereotype.Component; import pro.shushi.oinone.stand.testExt.model.Model0000000001; import pro.shushi.pamirs.meta.annotation.Action; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.api.dto.condition.Pagination; import pro.shushi.pamirs.meta.api.dto.wrapper.IWrapper; import pro.shushi.pamirs.meta.constant.FunctionConstants; import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum; import pro.shushi.pamirs.meta.enmu.FunctionTypeEnum; /** * Model0000000001Action * * @author yakir on 2025/01/20 14:59. */ @Component @Model.model(Model0000000001.MODEL_MODEL) public class Model0000000001Action { @Function.Advanced(type = FunctionTypeEnum.QUERY) @Function.fun(FunctionConstants.queryPage) @Function(openLevel = {FunctionOpenEnum.API}) public Pagination<Model0000000001> queryPage(Pagination<Model0000000001> page, IWrapper<Model0000000001> queryWrapper) { return new Model0000000001().queryPage(page, queryWrapper); } @Action(displayName = "sayHello") @Action.Advanced(type = FunctionTypeEnum.QUERY) public Model0000000001 sayHello(Model0000000001 query) { query.setName(query.getName() + System.currentTimeMillis()); return query; } } 注意事项 ⚠️⚠️⚠️ Oinone底层依赖版本与设计器和业务应用一致 (参考 版本更新日志 ) 扩展工程如需独立启动, 手动修改application.yml中安装模块和pom.xml中模块jar的依赖配置

    2025年2月17日
    2.0K00
  • 多模型联表查询

    多模型联表查询 多对一或者一对一关联关系,通过关联模型的字段查询数据 模型结构定义 模型A @Model(displayName = "A") @Model.model(A.MODEL_MODEL) public class A extends IdModel { public final static String MODEL_MODEL = "test.A"; @Field(displayName = "b") @Field.many2one @Field.Relation(relationFields = {"bId"}, referenceFields = {"id"}) private B b; @Field(displayName = "bId") @Field.Integer private Long bId; @Field(displayName = "B审批状态") @Field.Enum @Field.Related(related = {"b", "approvalEnum"}) private ApprovalEnum approvalEnum; } 模型B @Model(displayName = "B") @Model.model(B.MODEL_MODEL) public class B extends IdModel { public final static String MODEL_MODEL = "test.B"; @Field(displayName = "审批状态") @Field.Enum private ApprovalEnum approvalEnum; } 页面设计 在界面设计器中, 设计相对应的表格页面。 A模型related字段拖到搜索栏中。 发布页面 自定义Hook import cz.jirutka.rsql.parser.ast.RSQLOperators; import org.apache.commons.lang3.ArrayUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.framework.connectors.data.sql.AbstractWrapper; import pro.shushi.pamirs.framework.connectors.data.sql.query.QueryWrapper; import pro.shushi.pamirs.meta.annotation.Hook; import pro.shushi.pamirs.meta.api.Models; import pro.shushi.pamirs.meta.api.core.faas.HookBefore; import pro.shushi.pamirs.meta.api.core.orm.convert.ClientDataConverter; import pro.shushi.pamirs.meta.api.core.orm.template.context.ModelComputeContext; import pro.shushi.pamirs.meta.api.dto.config.ModelConfig; import pro.shushi.pamirs.meta.api.dto.config.ModelFieldConfig; import pro.shushi.pamirs.meta.api.dto.fun.Function; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.base.D; import pro.shushi.pamirs.meta.common.spi.Spider; import pro.shushi.pamirs.meta.domain.model.ModelField; import pro.shushi.pamirs.meta.enmu.TtypeEnum; import pro.shushi.pamirs.resource.api.constants.FieldConstants; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 通用 queryData处理。 */ @Slf4j @Component public class QueryDataHook implements HookBefore { @Override @Hook(priority = 30) public Object run(Function function, Object… args) { getValueByType(args); return function; } private void getValueByType(Object… args) { if (ArrayUtils.isEmpty(args)) { return; } for (int index = 0; index < args.length &&…

    2025年1月9日
    2.1K00
  • 平台配置日志输出和推送到APM与LogStash

    场景描述 目前设计器镜像启动后日志文件为out.log,是启动脚本中定向输出了(>>)out.log文件。实际项目可能: 日志输出到特定目录的特定文件名中 指定以日志保留策略(单个文件大小和文件保留个数) 日志输出到APM工具中(如skywalking) 日志推送到LogStash 日志自定义输出 不定向输出,采用自己配置的方式,与标准的SpringBoot工程配置日志一样。两种方式(都是Spring提供的方式): 方式一 bootstrap.yml 里面可以按profiles指定logback的配置文件,具体文件名和文件输入在logback里面进行配置,跟通用的logback配置一致. 例如: logging: config: classpath:logback-pre.xml 方式二 resources的根目录,直接配置 logback-spring.xml, 启动会自动加载。 日志自定义场景 配置日志推送到LogStash <!–配置日志推送到LogStash–> <contextListener class="pro.shushi.pamirs.demo.core.config.DemoLogbackFiledConfig"/> <appender name="LogStash" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <destination>127.0.0.1:4560</destination> <!– encoder必须配置,有多种可选 –> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"> <!– SkyWalking插件, log加tid–> <provider class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.logstash.TraceIdJsonProvider" /> <!–在生成的json中会加这些字段–> <customFields> {"app.name":"pamirs-demo", "app.type":"Microservice", "platform":"pamirs", "env":"dev"} </customFields> <timeZone>Asia/Shanghai</timeZone> <writeVersionAsInteger>true</writeVersionAsInteger> <providers> <pattern> <pattern> <!–动态的变量–> { "ip": "%{ip}", "server.name": "%{server.name}", "logger_name": "%logger" } </pattern> </pattern> </providers> </encoder> </appender> skywalking的日志rpc上传 <!– skywalking的日志rpc上传 –> <appender name="SkyWalkingLogs" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout"> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> </layout> </encoder> </appender> 完整的代码示例 Logback自定义字段 package pro.shushi.pamirs.demo.core.config; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.core.Context; import ch.qos.logback.core.spi.ContextAwareBase; import ch.qos.logback.core.spi.LifeCycle; import java.net.InetAddress; import java.net.UnknownHostException; /** * Logback自定义字段 * * @author wx@shushi.pro * @date 2024/4/17 */ public class DemoLogbackFiledConfig extends ContextAwareBase implements LoggerContextListener, LifeCycle { private boolean started = false; @Override public boolean isResetResistant() { return false; } @Override public void onStart(LoggerContext loggerContext) { } @Override public void onReset(LoggerContext loggerContext) { } @Override public void onStop(LoggerContext loggerContext) { } @Override public void onLevelChange(Logger logger, Level level) { } @Override public void start() { if (started) { return; } Context context = getContext();…

    2024年5月18日
    2.0K00

Leave a Reply

Please Login to Comment