非存储字段搜索,适应灵活的搜索场景

1、非存储字段搜索

1.1 描述

通常根据本模型之外的信息作为搜索条件时,通常会把 这些字段放在代理模型上。这类场景我们称之为 非存储字段搜索

1.2 场景一

非存储字段为基本的String。
1.代码定义:非存储字段为基本的包装数据类型

    @Field(displayName = "确认密码", store = NullableBoolEnum.FALSE)
    private String confirmPassword;

2.设计器拖拽:列表中需要有退拽这个字段,字段是否隐藏的逻辑自身业务是否需要决定。
image.png
3.页面通过非存储字段为基本的包装数据类型进行搜索时。会拼在 queryWrapper 的属性queryData中,queryData为Map,key为字段名,value为搜索值。
image.png
4.后台逻辑处理代码示例:

        Map<String, Object> queryData = queryWrapper.getQueryData();
        if (null != queryData) {
            Object productIdObj = queryData.get(PRODUCT_ID);
            if (Objects.nonNull(productIdObj)) {
                String productId = productIdObj.toString();
                queryWrapper.lambda().eq(MesProduceOrderProxy::getProductId, productId);
            }
        }

1.3 场景二

非存储字段为非存储对象。

  1. 定义 为非存储的
    @Field(displayName = "款", store = NullableBoolEnum.FALSE)
    @Field.many2one
    @Field.Relation(store = false)
    private MesProduct product;

2.页面在搜索栏拖拽非存储字段作为搜索条件

image.png

3.后台逻辑处理代码示例:

      try {
            if (null != queryData && !queryData.isEmpty()) {
                List<Long> detailId = null;
                BasicSupplier supplier = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(supplierField), BasicSupplier.class);
                MesProduct product = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(productField), MesProduct.class);
                MesMaterial material = JsonUtils.parseMap2Object((Map<String, Object>) queryData.get(materialField), MesMaterial.class);
                if (supplier != null) {
                    detailId = bomService.queryBomDetailIdBySupplierId(supplier.getId());
                    if (CollectionUtils.isEmpty(detailId)) {
                        detailId.add(-1L);
                    }
                }

                if (product != null) {
                    List<Long> produceOrderId = produceOrderService.queryProductOrderIdByProductIds(product.getId());
                    if (CollectionUtils.isNotEmpty(produceOrderId)) {
                        queryWrapper.lambda().in(MesProduceBomSizes::getProduceOrderId, produceOrderId);
                    }
                }

                if (material != null) {
                    //找出两个bom列表的并集
                    List<Long> materBomDetailId = bomService.queryBomDetailIdByMaterialId(material.getId());
                    if (CollectionUtils.isNotEmpty(detailId)) {
                        detailId = detailId.stream().filter(materBomDetailId::contains).collect(Collectors.toList());
                    } else {
                        detailId = new ArrayList<>();
                        if (CollectionUtils.isEmpty(materBomDetailId)) {
                            detailId.add(-1L);
                        } else {
                            detailId.addAll(materBomDetailId);
                        }
                    }
                }
                if (CollectionUtils.isNotEmpty(detailId)) {
                    queryWrapper.lambda().in(MesProduceBomSizes::getProductBomId, detailId);
                }
            }
        } catch (Exception e) {
            log.error("queryData处理异常", e);
        }

注意: 如果这样定义

    @Field(displayName = "款",store = NullableBoolEnum.FALSE)
    @Field.Relation(relationFields = "produceId", referenceFields = "id",store = false)
    @Field.many2one
    private MesProduct product;

    @Field(displayName = "款Id",store = NullableBoolEnum.FALSE)
    private Long produceId;

搜索时 produceId 选择product 搜索是 produceId 会被拼接在QueryWrapper 的Rsql中

Rsql解析类

pro.shushi.pamirs.framework.gateways.rsql.RSQLHelper

Rsql参考代码

/**
     * Rsql解析 将属性字段值从QueryWrapper中originRsql属性值的Rsql解出来
     * 将原有条件替换成 ’1‘==’1‘
     *
     * @param queryWrapper 查询Wrapper
     * @param fields       需要解析出来的属性字段列表
     * @param valeMap      属性对应值的Map
     * @return 返回生产单Id列表
     */

    public static QueryWrapper convertWrapper(QueryWrapper queryWrapper, List<String> fields, Map<String, Object> valeMap) {
        if (StringUtils.isNotBlank(queryWrapper.getOriginRsql())) {
            String rsql = RSQLHelper.toTargetString(RSQLHelper.parse(queryWrapper.getModel(), queryWrapper.getOriginRsql()), new RSQLNodeConnector() {
                @Override
                public String comparisonConnector(RSQLNodeInfo nodeInfo) {
                    //判断字段为unStore,则进行替换
                    String field = nodeInfo.getField();
                    if (fields.contains(field)) {
                        valeMap.put(field, nodeInfo.getArguments().get(0));
                        RSQLNodeInfo newNode = new RSQLNodeInfo(nodeInfo.getType());
                        //设置查询字段为name
                        newNode.setField("1");
                        newNode.setOperator(RsqlSearchOperation.EQUAL.getOperator());
                        newNode.setArguments(Collections.singletonList("1"));
                        return super.comparisonConnector(newNode);
                    }
                    return super.comparisonConnector(nodeInfo);
                }
            });
            queryWrapper = Pops.f(Pops.query().from(queryWrapper.getModel())).get();
            //把RSQL转换成SQL
            String sql = RsqlParseHelper.parseRsql2Sql(queryWrapper.getModel(), rsql);
            if (StringUtils.isNotBlank(sql)) {
                queryWrapper.apply(sql);
            }
            return queryWrapper;
        }
        return queryWrapper;
    }

Oinone社区 作者:shao原创文章,如若转载,请注明出处:https://doc.oinone.top/dai-ma-shi-jian/5592.html

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

(0)
shao的头像shao数式管理员
上一篇 2024年2月20日 pm6:46
下一篇 2024年2月20日 pm6:52

相关推荐

  • 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.2K00
  • 系统图标使用自定义CDN地址(内网部署)

    在实际项目中,客户网络环境不能访问外网即纯内网部署。此时需要将所有的静态资源都放在客户内部的CDN上,该篇详细说明实现步骤。 实现步骤 1、把图片等静态资源上传到本地CDN上(如MINIO、Nginx等),图片等静态资源找 数式支持人员 提供; 【注意】:MINIO情况,放置图片等静态资源的桶权限需设置为公共读; 2、项目中YAML的OSS配置,使用本地CDN、并指定使用的本地CDN图标的标识appLogoUseCdn: true, OSS配置参考如下: 本地CDN使用MINIO(仅示例需根据实际情况修改) cdn: oss: name: MINIO type: MINIO # MINIO的配置根据实际情况修改 bucket: pamirs(您的bucket) # 上传和下载地址根据实际情况修改 uploadUrl: http://39.103.145.77:9000 downloadUrl: http://39.103.145.77:9000 accessKeyId: 您的accessKeyId accessKeySecret: 您的accessKeySecret # mainDir对用CDN的图片目录,根据项目情况自行修改 mainDir: upload/demo/ validTime: 3600000 timeout: 600000 active: true referer: # 使用客户自己的CDN的图片,否则系统默认的从数式的CDN中获取 appLogoUseCdn: true 或本地CDN使用Nginx(仅示例需根据实际情况修改) cdn: oss: name: 本地文件NG系统 type: LOCAL bucket: # uploadUrl 这个是Oinone后端服务地址和端口 uploadUrl: http://192.168.0.129:8099 # downloadUrl前端地址,即直接映射在nginx的静态资源的路径和端口 downloadUrl: http://192.168.0.129:9999 validTime: 3600000 timeout: 600000 active: true referer: # 本地Nginx静态资源目录 localFolderUrl: /opt/pamirs/static # 使用客户自己的CDN的图片,否则系统默认的从数式的CDN中获取 appLogoUseCdn: true 3、前端工程3.1 前端源码工程,在.evn中把 STATIC_IMG地址进行修改;http(https)、IP和端口改成与CDN对应的配置,URL中/oinone/static/images是固定的;例如: 本地CDN使用MINIO(仅示例需根据实际情况修改) STATIC_IMG: 'http://39.103.145.77:9000/pamirs(这里替换为OSS中的bucket)/oinone/static/images' 或本地CDN使用Nginx(仅示例需根据实际情况修改) STATIC_IMG: 'http://192.168.0.129:9999/static/oinone/static/images' 3.2 对于已经打包好的前端资源对于已打包好的前端资源即无法修改.evn的情况;需在前端资源的根目录,新建config/manifest.js. 如果已存在则不需要新建,同时原来的内容也不需要删除(追加即可),需增加的配置: 本地CDN使用MINIO(仅示例需根据实际情况修改) runtimeConfigResolve({ STATIC_IMG: 'http://39.103.145.77:9000/pamirs(这里替换为OSS中的bucket)/oinone/static/images', plugins: { usingRemote: true } }) 或本地CDN使用Nginx(仅示例需根据实际情况修改) runtimeConfigResolve({ STATIC_IMG: 'http://192.168.0.129:9999/static/oinone/static/images', plugins: { usingRemote: true } })

    2025年2月8日
    1.5K00
  • 如何使用位运算的数据字典

    场景举例 日常有很多项目,数据库中都有表示“多选状态标识”的字段。在这里用我们项目中的一个例子进行说明一下: 示例一: 表示某个商家是否支持多种会员卡打折(如有金卡、银卡、其他卡等),项目中的以往的做法是:在每条商家记录中为每种会员卡建立一个标志位字段。如图: 用多字段来表示“多选标识”存在一定的缺点:首先这种设置方式很明显不符合数据库设计第一范式,增加了数据冗余和存储空间。再者,当业务发生变化时,不利于灵活调整。比如,增加了一种新的会员卡类型时,需要在数据表中增加一个新的字段,以适应需求的变化。  – 改进设计:标签位flag设计二进制的“位”本来就有表示状态的作用。可以用各个位来分别表示不同种类的会员卡打折支持:这样,“MEMBERCARD”字段仍采用整型。当某个商家支持金卡打折时,则保存“1(0001)”,支持银卡时,则保存“2(0010)”,两种都支持,则保存“3(0011)”。其他类似。表结构如图: 我们在编写SQL语句时,只需要通过“位”的与运算,就能简单的查询出想要数据。通过这样的处理方式既节省存储空间,查询时又简单方便。 //查询支持金卡打折的商家信息:   select * from factory where MEMBERCARD & b'0001'; // 或者:   select * from factory where MEMBERCARD & 1;    // 查询支持银卡打折的商家信息:   select * from factory where MEMBERCARD & b'0010'; // 或者:   select * from factory where MEMBERCARD & 2; 二进制( 位运算)枚举 可以通过@Dict注解设置数据字典的bit属性或者实现BitEnum接口来标识该枚举值为2的次幂。二进制枚举最大的区别在于值的序列化和反序列化方式是不一样的。 位运算的枚举定义示例 import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BitEnum; @Dict(dictionary = ClientTypeEnum.DICTIONARY, displayName = "客户端类型枚举", summary = "客户端类型枚举") public enum ClientTypeEnum implements BitEnum { PC(1L, "PC端", "PC端"), MOBILE(1L << 1, "移动端", "移动端"), ; public static final String DICTIONARY = "base.ClientTypeEnum"; private final Long value; private final String displayName; private final String help; ClientTypeEnum(Long value, String displayName, String help) { this.value = value; this.displayName = displayName; this.help = help; } @Override public Long value() { return value; } @Override public String displayName() { return displayName; } @Override public String help() { return help; } } 使用方法示例 API: addTo 和 removeFrom List<ClientTypeEnum> clientTypes = module.getClientTypes(); // addTo ClientTypeEnum.PC.addTo(clientTypes); // removeFrom ClientTypeEnum.PC.removeFrom(clientTypes); 在查询条件中的使用 List<Menu> moduleMenus = new Menu().queryListByWrapper(menuPage, LoaderUtils.authQuery(wrapper).eq(Menu::getClientTypes, ClientTypeEnum.PC));

    2023年11月24日
    1.9K00
  • 字段类型之关系描述的特殊场景(常量关联)

    场景概述 【字段类型之关系与引用】一文中已经描述了各种关系字段的常规写法,还有一些特殊场景如:关系映射中存在常量,或者M2M中间表是大于两个字段构成。 场景描述 1、PetTalent模型增加talentType字段2、PetItem与PetTalent的多对多关系增加talentType(达人类型),3、PetItemRelPetTalent中间表维护petItemId、petTalentId以及talentType,PetDogItem和PetCatItem分别重写petTalents字段,关系中增加常量描述。示意图如下: 实际操作步骤 Step1 新增 TalentTypeEnum package pro.shushi.pamirs.demo.api.enumeration; import pro.shushi.pamirs.meta.annotation.Dict; import pro.shushi.pamirs.meta.common.enmu.BaseEnum; @Dict(dictionary = TalentTypeEnum.DICTIONARY,displayName = "达人类型") public class TalentTypeEnum extends BaseEnum<TalentTypeEnum,Integer> { public static final String DICTIONARY ="demo.TalentTypeEnum"; public final static TalentTypeEnum DOG =create("DOG",1,"狗达人","狗达人"); public final static TalentTypeEnum CAT =create("CAT",2,"猫达人","猫达人"); } Step2 PetTalent模型增加talentType字段 package pro.shushi.pamirs.demo.api.model; import pro.shushi.pamirs.demo.api.enumeration.TalentTypeEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; @Model.model(PetTalent.MODEL_MODEL) @Model(displayName = "宠物达人",summary="宠物达人",labelFields ={"name"}) public class PetTalent extends AbstractDemoIdModel{ public static final String MODEL_MODEL="demo.PetTalent"; @Field(displayName = "达人") private String name; @Field(displayName = "达人类型") private TalentTypeEnum talentType; } Step3 修改PetItem的petTalents字段,在关系描述中增加talentType(达人类型) @Field.many2many(relationFields = {"petItemId"},referenceFields = {"petTalentId","talentType"},through = PetItemRelPetTalent.MODEL_MODEL ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id","talentType"}) @Field(displayName = "推荐达人",summary = "推荐该商品的达人们") private List<PetTalent> petTalents; Step4 PetDogItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field(displayName = "推荐达人") @Field.many2many( through = "PetItemRelPetTalent", relationFields = {"petItemId"}, referenceFields = {"petTalentId","talentType"} ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id", "#1#"}, domain = " talentType == DOG") private List<PetTalent> petTalents; Step5 PetCatItem增加petTalents字段,重写父类PetItem的关系描述 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Field(displayName = "推荐达人") @Field.many2many( through = "PetItemRelPetTalent", relationFields = {"petItemId"}, referenceFields = {"petTalentId","talentType"} ) @Field.Relation(relationFields = {"id"}, referenceFields = {"id", "#2#"}, domain = " talentType == CAT") private List<PetTalent> petTalents; Step6 PetCatItem增加petTalents字段,many2one关系示例 talentType配置为常量,填入枚举的值 增加domain描述用户页面选择的时候自动过滤出特定类型的达人,RSQL用枚举的name @Model.model(PetPet.MODEL_MODEL) @Model(displayName…

    2024年5月25日
    1.9K00
  • 模型定义在数据库中的映射

    模型定义在数据库中的映射 Oinone中通过定义模型来建立数据表,使用注解的方式来使多张表之间的关联。 数据库字段与模型定义字段映射 package pro.shushi.pamirs.top.api.model; import pro.shushi.pamirs.core.common.enmu.DataStatusEnum; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; import pro.shushi.pamirs.meta.enmu.DateFormatEnum; import pro.shushi.pamirs.meta.enmu.DateTypeEnum; import pro.shushi.pamirs.meta.enmu.MimeTypeEnum; import java.math.BigDecimal; import java.util.Date; @Model.model(PamirsDemo.MODEL_MODEL) @Model(displayName = “PamirsDemo”) public class PamirsDemo extends IdModel { public static final String MODEL_MODEL = “top.PamirsDemo”; @Field.Binary(mime = MimeTypeEnum.html) @Field(displayName = “二进制类型”) private Byte[] byteType; @Field.Integer @Field(displayName = “整数”) private Long longType; @Field.Float @Field(displayName = “浮点数”) private BigDecimal floatType; @Field.Boolean @Field(displayName = “布尔类型”) private Boolean booleanType; @Field.Enum @Field(displayName = “枚举”) private DataStatusEnum enumType; @Field.String @Field(displayName = “字符串”) private String stringType; @Field.Text @Field(displayName = “多行文本”) private String textType; @Field.Html @Field(displayName = “富文本”) private String richText; @Field.Date(type = DateTypeEnum.DATE, format = DateFormatEnum.DATE) @Field(displayName = “日期类型”) private Date dataType; @Field.Date(type = DateTypeEnum.DATETIME, format = DateFormatEnum.DATETIME) @Field(displayName = “日期时间类型”) private Date dataTimeType; @Field.Money @Field(displayName = “金额”) private BigDecimal amount; } 更多字段基础请参考文档字段基础与复合 多对一的关系映射 例:设计一张教师表,一张科目表,教师表对科目表属于多对一的关系,在教师表中使用科目id管理关联关系。 教师表teacher 科目表professional 那么在Oinone的模型定义中,这两张表定义是这样的; 教师模型 package pro.shushi.pamirs.top.api.model; import pro.shushi.pamirs.meta.annotation.Field; import pro.shushi.pamirs.meta.annotation.Model; import pro.shushi.pamirs.meta.base.IdModel; @Model.model(Teacher.MODEL_MODEL) @Model(displayName = “教师”, summary = “教师”) public class Teacher extends IdModel { public static final String MODEL_MODEL = “top.Teacher”; @Field.String @Field(displayName = “教师名字”) private String teacherName; @Field.Integer @Field(displayName = “科目id”) private Long professionalId; @Field(displayName…

    2024年8月16日
    1.4K00

Leave a Reply

登录后才能评论