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

一、背景与问题

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);
    }
}

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

二、推荐方案:代理模型(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. 隔离

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

基类 PamirsDepartmentparent 字段的类型是 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

相关推荐

  • Oinone登录扩展:对接SSO(适应于4.7.8及之后的版本)

    适配版本 4.7.8及其之后的版本 概述 在企业内部,对于已有一套完整的登录系统(SSO)的情况下,通常会要求把所有的系统都对接到SSO中;本文主要讲解用Oinone开发的项目对接SSO的具体实现。 对接步骤 1、项目自定义实现UserCookieLogin,可参考示例说明: pro.shushi.pamirs.user.api.login.UserCookieLoginFree 2、对接SSO示例 对接流程说明: 1)【必须】从请求头Header或者Query中获取到token; 2)【必须】去SSO服务端验证token的有效性; 3)【可选】根据token去服务端获取用户信息;如果token可以直接反解析出用户信息,则该步骤忽略; 4)【可选】根据实际情况用户信息是否进行DB的存储; 5)【必须】验证token有效后,生成Session和Cookie(即token换cookie); 注意超时时间需要 <= SSO服务端token失效时间。 package pro.shushi.pamirs.demo.core.sso; import com.alibaba.fastjson.JSON; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.Order; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import pro.shushi.pamirs.demo.core.sso.constant.HttpConstant; import pro.shushi.pamirs.demo.core.sso.constant.SessionUserTypeEnum; import pro.shushi.pamirs.demo.core.sso.model.ApiCommonTransient; import pro.shushi.pamirs.demo.core.sso.model.PermissionInfoResp; import pro.shushi.pamirs.demo.core.sso.utils.AuthenticateUtils; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.model.PamirsUserDTO; import pro.shushi.pamirs.meta.api.session.PamirsSession; import pro.shushi.pamirs.meta.common.exception.PamirsException; import pro.shushi.pamirs.meta.common.spring.BeanDefinitionUtils; import pro.shushi.pamirs.resource.api.enmu.UserSignUpType; import pro.shushi.pamirs.user.api.cache.UserCache; import pro.shushi.pamirs.user.api.constants.UserConstant; import pro.shushi.pamirs.user.api.enmu.UserExpEnumerate; import pro.shushi.pamirs.user.api.enmu.UserLoginTypeEnum; import pro.shushi.pamirs.user.api.login.IUserLoginChecker; import pro.shushi.pamirs.user.api.login.UserCookieLogin; import pro.shushi.pamirs.user.api.login.UserCookieLoginSimple; import pro.shushi.pamirs.user.api.model.PamirsUser; import pro.shushi.pamirs.user.api.model.tmodel.PamirsUserTransient; import pro.shushi.pamirs.user.api.service.UserService; import pro.shushi.pamirs.user.api.utils.CookieUtil; import javax.servlet.http.HttpServletResponse; /** * * @author shushi * * 完全自定义login的过程 * 需要实现登陆部分login 以及拦截部分fetchUserIdByReq * 如果fetchUserIdByReq返回值为null的时候 将会被拦截 */ @Slf4j @Order(0) @Component public class DemoUserSSOCookieLogin extends UserCookieLogin<PamirsUser> { //刷新令牌 private static String REFRESH_TOKEN = "refreshToken"; //系统id private static String CLIENT_ID = "client-id"; //访问令牌 private static String AUTHORIZATION = "Authorization"; private IUserLoginChecker checker; @Autowired private UserService userService; @Autowired private RedisTemplate<String, String> redisTemplate; @Override public String type() { return UserLoginTypeEnum.COOKIE.value(); } @Override public PamirsUser resolveAndVerification(PamirsUserTransient user) { if (checker == null) { checker = BeanDefinitionUtils.getBean(IUserLoginChecker.class); } return checker.check4login(user); } /** * 重写登录拦截功能 * 该函数主要作用,通过三方权限校验. * @return */ // 版本升级需要修改 @Override public PamirsUserDTO fetchUserIdByReq() { String sessionId =…

    2024年4月2日
    2.4K00
  • 协同开发支持

    协同开发概述 在使用Oinone进行业务开发中,目前开发方式为: 开发各个本地启动项目 与 设计器环境共库共redis的方式进行。 在多个开发人员同时修改一个模型,或者没有及时更新其他同学提交的代码时,存在业务模型创建的数据表字段被删除的情况,协同开发模式正式为解决这个问题而生。 版本支持 4.7.x版本 已经包含分布式支持。 使用步骤 1、业务后端boot工程引入协同开发包 <dependency> <groupId>pro.shushi.pamirs.distribution</groupId> <artifactId>pamirs-distribution-session-cd</artifactId> </dependency> 2、yml文件配置ownSign pamirs: distribution: session: allMetaRefresh: false # ownSign根据实际情况修改,一般为研发各自名称 ownSign: wangxian 配置说明:allMetaRefresh,全量刷新Redis中的元数据,绝大多数情况下都不需要配置;1)第一次启动或者Redis的缓存被清空后,会自动进行全量。2)配置为true表示强制进行全量,一般都不需要配置;3)【推荐】默认增量的方式(即allMetaRefresh: false)写入redis的数据更少,相应的启动速度也更快4)【强制】ownSign是环境隔离的设置,同一个项目组不同的开发人员之间,ownSign配置成不同的(即各自配置成各自的,达到互不干扰) 3、业务系统DB和缓存的约束1)【强制】业务库和设计器Redis共用,包括Redis的前缀,租户和系统隔离键都需要一样(这三个值影响RedisKey的拼接)2)【强制】base库业务系统与设计器共用;3) 【强制】公共库即pamirs (资源-resource、用户-user、权限-auth、文件-file等)共用;4)【强制】「业务库」数据源的别名必须一直,每个开发人员必须配置到自己的本地 或者是远程库库加一个后缀区分; 4、开发同学在各自访问设计器时,URL最后面增加;ownSign=wangxian后回车,ownSign会被保存到浏览器缓存中,后续访问其他的URL访问不需要再次输入;如果需要去掉ownSign的值,则直接把界面上的悬浮窗删掉即可。说明:访问设计URL上增加的ownSign需要与开发各自本地项目yml文件中ownSign的值相同。(每个开发人员各自用各自的ownSign)PS:具体参数配置详见Oinone协同开发使用手册

    2023年12月4日
    2.1K00
  • Oinone开发实践-业务实现多租户方案

    总体方案 业务项目中,需要隔离的模型自定义增加租户字段进行数据隔离; 参考了Mybatis-Plus插件的TenantSqlParser进行的JPA实现,使用jsqlparser解析并修改SQL; 实现获取当前用户租户ID,SQL增删改查时处理租户字段,实现租户数据的隔离 参考项目: https://github.com/baomidou/mybatis-plus https://github.com/JSQLParser/JSqlParser 具体实现方式 1、业务上定义两个基础抽象模型包含租户字段 定义包含ID的基础抽象模型,且包含租户字段(如:公司编码, 用其他字段作为租户字段也可以,根据实际业务情况灵活修改)。 @Model.model(XXIdModel.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.ABSTRACT) @Model(displayName = “带公司CODE的基础ID抽象模型”, summary = “带公司Code的Id模型”) public abstract class XXIdModel extends IdModel { public static final String MODEL_MODEL = “demo.biz.XXIdModel”; @Field.String @Field(displayName = “所属公司编码”, invisible = true, index = true) private String companyCode; } 定义包含Code的基础抽象模型,且包含租户字段(如:公司编码, 用其他字段作为租户字段也可以,根据实际业务情况灵活修改)。 @Model.model(XXCodeModel.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.ABSTRACT) @Model(displayName = “带公司CODE的基础Code抽象模型”, summary = “带公司CODE的Code模型”) public abstract class XXCodeModel extends CodeModel { public static final String MODEL_MODEL = “demo.biz.XXCodeModel”; @Field.String @Field(displayName = “所属公司编码”, invisible = true, index = true) private String companyCode; } 2、业务模块的模型需租户隔离的都是继承上面这两个模型; @Model.model(PetPetCompany.MODEL_MODEL) @Model(displayName = “宠物公司”, labelFields = “name”) public class PetPetCompany extends AbstractCompanyCodeModel { public static final String MODEL_MODEL = “demo.PetPetCompany”; @Field.String @Field(displayName = “名称”) private String name; @Field.Text @Field(displayName = “简介”) private String introduction; } 3、自定义扩展Session,Session中设置租户信息 每次请求多把登录用户所属公司编码(companyCode)放到Session中;Session扩展参考:https://doc.oinone.top/oio4/9295.html 4、定义拦截器Interceptor进行数据隔离 数据创建和查询通过拦截器把Session中的中的公司编码(companyCode)设置到隔离字段中;拦截器的java示例代码参考: package pro.shushi.pamirs.demo.core.interceptor; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor; import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.*; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.values.ValuesStatement; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import…

    2024年4月6日
    1.4K00
  • Oinone构建分布式项目一些注意点

    1. Oinone如何支持构建分布式项目 参考文档:https://doc.oinone.top/kai-fa-shi-jian/5572.html 2. Oinone远程服务发布范围 泛化服务范围,可选值:module、namespacemodule:按模块维度发布远程服务namespace:按Fun的namespace维度发布远程服务默认按module维度发布服务 pamirs: distribution: service: #serviceScope: 可选值namespace、module serviceScope: module 3.关闭Dubbo服务注册元数据上报日志 logging: level: root: info pro.shushi.pamirs.framework.connectors.data.mapper.PamirsMapper: error pro.shushi.pamirs.framework.connectors.data.mapper.GenericMapper: error # mybatis sql日志 RocketmqClient: error org.apache.dubbo.registry.zookeeper.ZookeeperRegistry: error org.apache.dubbo.registry.integration.RegistryDirectory: error org.apache.dubbo.config.ServiceConfig: error com.alibaba.nacos.client.naming: error org.apache.dubbo.registry.nacos.NacosRegistry: error org.apache.dubbo.registry.support.AbstractRegistryFactory: error org.apache.dubbo.registry.integration.RegistryProtocol: error org.apache.dubbo.registry.client.metadata.store.RemoteMetadataServiceImpl: off org.apache.dubbo.metadata.store.zookeeper.ZookeeperMetadataReport: off org.apache.dubbo.metadata.store.nacos.NacosMetadataReport: off 4.Naocs配置列表出现多余配置 dubbo 集成 nacos注册中心,会出现多余的配置,详细参考:配置列表会自动创建很多无关的配置: https://github.com/apache/dubbo/issues/6645配置列表出现多余的配置:https://github.com/alibaba/nacos/issues/8843 按照下面的配置可以将其关闭(📢主要是这三项配置use-as-config-center, use-as-metadata-center,metadata-report.failfast),已生成的配置需要手动删除掉。 dubbo: application: name: pamirs-demo version: 1.0.0 metadata-type: local registry: id: pamirs-demo-registry address: nacos://192.168.0.129:8848 username: nacos password: nacos # dubbo使用nacos的注册中心往配置中心写入配置关闭配置 use-as-metadata-center: false use-as-config-center: false config-center: address: nacos://192.168.0.129:8848 username: nacos password: nacos metadata-report: failfast: false # 关闭错误上报的功能 address: nacos://192.168.0.129:8848 username: nacos password: nacos protocol: name: dubbo port: -1 serialization: pamirs scan: base-packages: pro.shushi cloud: subscribed-services:

    2024年2月1日
    1.9K10
  • 如何在代码中使用自增ID和获取序列

    在使用继承IDModel或CodeModel时,id和code是系统默认自动生成, 默认值规则:ID–>分布式ID; CODE–>根据定义的SequenceConfig规则自动生成。 在特定情况下需要落库前先生成ID或者Code,这些场景下可参照如下代码示例 一、使用自增ID 单个字段设置方式 // 主键字段,可以使用mysql的自增能力 @Field.Integer @Field.PrimaryKey(keyGenerator = KeyGeneratorEnum.AUTO_INCREMENT) @Field.Advanced(batchStrategy = FieldStrategyEnum.NEVER) @Field(displayName = "id", summary = "Id字段,⾃增") private Long id; @Field.Integer @Field(displayName = "自增版本") @Field.Sequence(sequence = "SEQ", initial = 1) private Long version; 全局设置方式 该方式会作用到每一个存储模型的id字段,在application.yml配置文件中修改id的生成规则,查找配置项关键字key-generator,默认为DISTRIBUTION(分布式id),可修改为 AUTO_INCREMENT(自增id) 二、手动方式获取序列 获取方式示例1 /** * 在特定场景下需要手动生成Id或者code时,可参照这个示例 */ public void manualSetIdCode(){ DemoItem demoItem = new DemoItem(); //手动生成ID和code Object idObj = Spider.getDefaultExtension(IdGenerator.class).generate(PamirsTableInfo.fetchKeyGenerator(DemoItem.MODEL_MODEL)); demoItem.setId(TypeUtils.createLong(idObj)); Object codeObj = CommonApiFactory.getSequenceGenerator().generate("SEQ",DemoItem.MODEL_MODEL); String code = TypeUtils.stringValueOf(codeObj); demoItem.setCode(code); //…… } 获取方式示例2 1、在系统启动的时候初始化SequenceConfig package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.extend.MetaDataEditor; import pro.shushi.pamirs.core.common.InitializationUtil; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.constant.SeqConstants; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.meta.Meta; import pro.shushi.pamirs.meta.enmu.SequenceEnum; import java.util.Map; /** * DemoMetadataEditor */ @Slf4j @Component public class DemoMetadataEditor implements MetaDataEditor { @Override public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) { InitializationUtil util = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE, DemoModule.MODULE_NAME); if (util == null) { log.error("获取初始化序列失败"); return; } bizSequence(util); } private void bizSequence(InitializationUtil util) { util.createSequenceConfig("申请单编码生成", SeqConstants.NABEL_SAMPLE_APPLY_SEQ, SequenceEnum.ORDERLY_SEQ, 8) .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); util.createSequenceConfig("订单编码生成", SeqConstants.NABEL_SAMPLE_ORDER_SEQ_YP, SequenceEnum.ORDERLY_SEQ, 8) .setPrefix("YP") .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); } } 2、在代码中使用序列 public static String getSaleOrderCode() { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_STRUCTURE_SEQ); return TypeUtils.stringValueOf(sequence); } public static String getApplyOrderCode(String prefix) { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_APPLY_SEQ); return…

    2024年5月25日
    2.4K00

Leave a Reply

Please Login to Comment