Mybatis+自定义注解,优雅的实现条件查询

第二十七章:mybatis plus 如何自定义 SQL 查询条件 多个参数的方法:当 Mapper 接口方法接受多个参数时,必须使用@Param注解为每个参数指定一个唯一的名称。否则,MyBatis 无法区分这些参数。在上面的例子中,和分别指定了参数id和name的名称,这样 MyBatis 就可以在 XML 映射文件中通过这些名称引用它们。XML 映射文件中的引用:在 MyBatis 的 XML 映射文件中,你可以使用#{}语法来引用带有@Param注解的参数。 阅读详情

Springboot+Mybatis实现条件查询可以这样实现

@Data
@NoArgsConstructor
@ApiModel("查询DTO")
public class QueryDTO  {
   
    @ApiModelProperty("名称")
    private String name;
     
    @ApiModelProperty("类型")
    private String type;

     
    @ApiModelProperty("描述")
    private String description;

}

Controller代码如下

    @GetMapping("/list")
    @ApiOperation(value = "列表查询")
    public Result list(@ApiParam QueryDTO request){
   
        QueryWrapper<QueryDTO> queryWrapper = new QueryWrapper<>();
        if(request.getName()!=null){
            queryWrapper.like("name",request.getName());
        }
        if(request.getType()!=null){
            queryWrapper.eq("type",request.getType());
        }
        if(request.getDescription()!=null){
            queryWrapper.eq("descrption",request.getDescription());
        }
        List<QueryDTO> list = service.list(queryWrapper);
        return Result.success(list);
    }

可见,每多一个条件,就要多写一段 queryWrapper.eq...这样类似的代码

使用自定义注解可以更优雅的实现条件查询

添加一个自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Query {
    /**
     * 对应数据库字段,为空取实体属性名 驼峰转下划线
     */
    String column() default "";

    /**
     * 另一个类中的属性名称,支持多级获取,以小数点隔开
     */
    String targetAttr() default "";

    /**
     * 查询类型
     */
    Type type() default Type.EQUAL;

    enum Type {
        /**
         * 相等
         */
        EQUAL((queryWrapper, filedColumn, val) -> {
            queryWrapper.eq(filedColumn, val);
        }),
        /**
         * 大于等于
         */
        GREATER_THAN((queryWrapper, filedColumn, val) -> {
            queryWrapper.ge(filedColumn, val);
        }),
        /**
         * 大于
         */
        GREATER_THAN_NQ((queryWrapper, filedColumn, val) -> {
            queryWrapper.gt(filedColumn, val);
        }),
        /**
         * 小于等于
         */
        LESS_THAN((queryWrapper, filedColumn, val) -> {
            queryWrapper.le(filedColumn, val);
        }),
        /**
         * 小于
         */
        LESS_THAN_NQ((queryWrapper, filedColumn, val) -> {
            queryWrapper.lt(filedColumn, val);
        }),
        /**
         * 不等于
         */
        NOT_EQUAL((queryWrapper, filedColumn, val) -> {
            queryWrapper.ne(filedColumn, val);
        }),
        /**
         * 不为空
         */
        NOT_NULL((queryWrapper, filedColumn, val) -> {
            queryWrapper.isNotNull(filedColumn);
        }),
        /**
         * 中模糊查询<br/>
         * like %str%
         */
        LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.like(filedColumn, val);
        }),
        /**
         * 左模糊查询
         */
        LEFT_LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.likeLeft(filedColumn, val);
        }),
        /**
         * 右模糊查询
         */
        RIGHT_LIKE((queryWrapper, filedColumn, val) -> {
            queryWrapper.likeRight(filedColumn, val);
        }),
        /**
         * 包含
         */
        IN((queryWrapper, filedColumn, val) -> {
            queryWrapper.in(filedColumn, (Collection) val);
        }),
        /**
         * JSON数组中是否包含
         */
        JSON_ARRAY_CONTAINS((queryWrapper, filedColumn, val) -> {
            queryWrapper.apply("JSON_CONTAINS(" + filedColumn + ",json_array({0}) )", val);
        }),
        ;
        private TriConsumer consumer;

        Type(TriConsumer<QueryWrapper, String, Object> consumer) {
            this.consumer = consumer;
        }

        public void buildQuery(QueryWrapper t, String field, Object val) {
            consumer.accept(t, field, val);
        }
    }
}
如果字段名与属性名相同或相匹配(驼峰转下划线 如:createTime-> create_time 表示字段匹配),不用标注column字段
如果字段名与属性名不同且不匹配,如属性名为 searchValue,需要查询的字段是name,则需要增加column字段,@Query(column="name")

默认使用=查询,如果要使用like或其它查询类型,则需要标注type字段,选择相应的type即可,如: @Query(type=Query.Type.LIKE) 
每个type会生成什么样的QueryWrapper语句,可以参考Type枚举类的方法

再添加一个处理注解的工具类

@Slf4j
public class QueryUtil {
    public static <T> QueryWrapper setQueryWrapper(T bean) {
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        if (bean == null) {
            return queryWrapper;
        }
        try {
            List<Object[]> fields = getFields(bean.getClass());
            for (Object[] os : fields) {
                Field field = (Field) os[0];
                Query q = (Query) os[1];
                Object val = getTargetValue(bean, field, q);
                if (val == null) {
                    continue;
                }
                String filedColumn = StringUtils.isBlank(q.column()) ? StringUtils.toUnderScoreCase(field.getName()) : q.column();
                q.type().buildQuery(queryWrapper,filedColumn,val);
            }
        } catch (Exception e) {
            log.error("设置查询条件失败{}", e);
        }
        return queryWrapper;
    }

    /**
     * 获取bean值
     *
     * @param vo
     * @param field
     * @return
     * @throws Exception
     */
    public static Object getObjectValue(Object vo, Field field) throws Exception {
        Object o = field.get(vo);
        if (o == null) {
            return null;
        }
        //空字符串转换为null对象
        if (field.getGenericType().toString().equals("class java.lang.String")) {
            String val = (String) o;
            if (StringUtils.isBlank(val)) {
                o = null;
            }
        } 
        return o;
    }

    /**
     * 获取bean中的属性值
     *
     * @param vo    实体对象
     * @param field 字段
     * @param query 注解
     * @return 最终的属性值
     * @throws Exception
     */
    private static Object getTargetValue(Object vo, Field field, Query query) throws Exception {
        Object o = getObjectValue(vo, field);
        if (o == null) {
            return o;
        }
        if (StringUtils.isNotEmpty(query.targetAttr())) {
            String target = query.targetAttr();
            if (target.contains(".")) {
                String[] targets = target.split("[.]");
                for (String name : targets) {
                    o = getValue(o, name);
                }
            } else {
                o = getValue(o, target);
            }
        }
        return o;
    }

    /**
     * 以类的属性的get方法方法形式获取值
     *
     * @param o
     * @param name
     * @return value
     * @throws Exception
     */
    private static Object getValue(Object o, String name) throws Exception {
        if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) {
            if (o instanceof Map) {
                Map<String, Object> objectMap = BeanUtils.beanToMap(o);
                if (objectMap.containsKey(name)) {
                    return objectMap.get(name);
                }
                return null;
            } else {
                Class<?> clazz = o.getClass();
                Field field = clazz.getDeclaredField(name);
                field.setAccessible(true);
                o = getObjectValue(o, field);
            }
        }
        return o;
    }

    /**
     * 获取字段注解信息
     */
    public static List<Object[]> getFields(Class clazz) {
        List<Object[]> fields = new ArrayList<Object[]>();
        List<Field> tempFields = new ArrayList<>();
        tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
        tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        for (Field field : tempFields) {
            // 单注解
            if (field.isAnnotationPresent(Query.class)) {
                Query attr = field.getAnnotation(Query.class);
                if (attr != null) {
                    field.setAccessible(true);
                    fields.add(new Object[]{field, attr});
                }
            }

            // 多注解
            if (field.isAnnotationPresent(Queries.class)) {
                Queries attrs = field.getAnnotation(Queries.class);
                Query[] queries = attrs.value();
                for (Query attr : queries) {
                    if (attr != null) {
                        field.setAccessible(true);
                        fields.add(new Object[]{field, attr});
                    }
                }
            }
        }
        return fields;
    }

}

在查询的DTO类中使用注解

@Data
@NoArgsConstructor
@ApiModel("查询DTO")
public class QueryDTO  {
    /**
     * 名称
     */
    @ApiModelProperty("名称")
    @Query(type = Query.Type.LIKE) //不是用等于查找条件,需要指定type
    private String name;
    /**
     * 类型
     */
    @ApiModelProperty("类型")
    @Query  //表字段与属性名相匹配,且是用等于查找的,只需要这样的简单注解即可
    private String type;

    /**
     * 描述
     */
    @ApiModelProperty("描述")
    @Query(column="description") //如果字段名与属性名不匹配,则添加column字段值指定表字段
    private String description;

}

Controller类中使用如下:

    @GetMapping("/list")
    @ApiOperation(value = "列表查询")
    public Result list(@ApiParam QueryDTO request){
   
        QueryWrapper<QueryDTO> queryWrapper = QueryUtil.setQueryWrapper(request);
        List<QueryDTO> list = service.list(queryWrapper);
        return Result.success(list);
    }

Controller中的代码更简洁了,如果需要增加一个条件,只需要在QueryDTO中增加一处属性和注解即可,不用再复制一段雷同的代码

Mybatis-puls自定义查询扩展方案 当前很多开源的低代码平台框架多对mybatis-puls进行了查询的封装使得使用起来很方便如:jeecg-boot, bladex等通过前后端协商约定基于mybatis-pulus 构造QueryWrapper能够快速实现各种条件的查询。但是存在局限性因为mybatis-pulus 的基础查询方法目前只支持单表查询,这对一些复杂的查询场景而言就比较难以支持必须自己写mapper 组织组织查询条件。那有没有一种可以关联表的工具也支持LambdaWrapper?答案是存在的做连接查询的插件。.......... 阅读详情

相关推荐

MyBatis-Plus 动态条件查询:如何优雅处理空值条件

本文详细介绍了MyBatis-Plus中动态条件查询时如何优雅处理空值条件的问题。通过分析QueryWrapper的默认行为,提出了自定义ExcludeEmptyQueryWrapper的解决方案,并对比了多种空值处理方式的优缺点。文章还提供了Lambda表达式优化、实际项目最佳实践以及常见问题的解决方案,帮助开发者提升查询效率和代码可维护性。

weixin_29306571的博客 717

MybatisPlus 自定义查询注解@Query,轻松实现复杂场景的关联查询需求

MybatisPlus 自定义查询注解@Query,轻松实现复杂场景的关联查询需求

operator的博客 7229

Java中操作字符串的工具类-判空、截取、格式化、转换驼峰、转集合和list、是否包含

场景 某些常用的对字符串进行处理的方法抽离出来成工具类,方便在多处应用。 常用的操作为: 判断是否为空 截取字符串 格式化文本 字符串转set 字符串转list 下划线转驼峰命名 是否包含字符串 注: 博客:https://blog.youkuaiyun.com/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书、教程推送与免费下载。 实现 新建工具类StringUtils import java.util.ArrayList; import java.u

BADAO_LIUMANG_QIZHI的博客 819

mybatis 自定义实现拦截器插件Interceptor示例

首先熟悉一下Mybatis的执行过程,如下图: 在这里插入图片描述 类型 先说明Mybatis中可以被拦截的类型具体有以下四种: 1.Executor:拦截执行器的方法。 2.ParameterHandler:拦截参数的处理。 3.ResultHandler:拦截结果集的处理。 4.StatementHandler:拦截Sql语法构建的处理。 规则 Intercepts注解需要一个Signature(拦截点)参数数组。通过Signature来指定拦截哪个对象里面的哪个方法。@Intercepts注解

3420

基于Java自定义注解结合MyBatis实现数据库过滤的实战案例

在当今复杂的企业级Java应用中,开发者常常面临一个看似简单却极为棘手的问题:如何在不侵入业务逻辑的前提下,实现对数据库查询行为的灵活控制?想象这样一个场景:你正在开发一个SaaS系统,每个租户的数据必须严格隔离。传统做法是在每个DAO方法里手动拼接条件——但随着接口数量增长,这种重复劳动不仅枯燥,还极易出错。更糟糕的是,一旦权限策略调整,你就得翻遍整个代码库进行修改。有没有一种方式,能让这些横切关注点像“贴纸”一样,优雅地附加到目标方法上,而又不影响核心业务代码的纯净性?

weixin_42433737的博客 977

FPGA学习之路—应用程序—原码二位乘法器及Verilog代码分析

FPGA学习之路——原码二位乘法器及Verilog代码分析 原理 原码乘法可以分为原码一位乘和原码二位乘,两者在实现规则上大同小异。原码一位乘每次判断乘数的最低位,对被乘数和部分积进行相应操作。而原码二位乘则是对乘数的低二位进行判断,并执行相关操作。 两位乘数的取值可以有四种可能组合,每种组合对应于以下操作: 00相当于0x,部分积Dresult右移2位,不进行其他运算; 01相当于1x,部分积D...

谦豫 3716

如何优雅地使用 MyBatis 注解版

自从 Java 1.5 开始引入了注解,注解便被广泛地应用在了各种开源软件中,使用注解大大地降低了系统中的配置项,让编程变得更为优雅。MyBatis 也顺应潮流基于注解推出了 MyBatis 的注解版本,避免开发过程中频繁切换到 XML 或者 Java 代码中,从而让开发者使用 MyBatis 会有统一的开发体验。 因为最初设计时,MyBatis 是一个 XML 驱动的框架,配置信息是基于 XML...

taojin12的博客 1467

基于MOS的防反接电路

防反接电路一般用于电源入口处,在电源反接时,保证电源入口之后的电路不会受到损害。一般在电源电流较小、对1V左右的压降不敏感时可以使用二极管进行防反接。防反接的形式有直接在正极串联二极管(如图1),或使用整流桥的方式(如图2),但这两种方式均存在:1、存在二极管的管压降;2、过大电流时,二极管消耗的功率大,发热严重。这两个问题。 图 1 二极管防反接电路 图 2 整流管防反接电路 1、基于NMO...

qq_42682826的博客 2万+

Mybatis 使用注解和Provider类实现动态条件查询

Mybatis 提供了基于Xml和注解的自定义SQL查询,相比Xml的方式,注解更加便捷、优雅。为了防止遗忘具体的细节编写,摘录一段曾经写过的代码。 此处,一大片注解内容十分不优雅,更好的办法是通过调用Provider类的指定方法生成SQL。 1.注解内拼写 Mybatis SQL 脚本 @Repository public interface CustomerFeedMapper exte...

w522301629的博客 3691

DisplayPort 1.4数据扰码实战:16bit LFSR并行优化技巧(附Verilog代码)

本文深入探讨了DisplayPort 1.4协议中16bit数据扰码的并行化实现。针对高带宽视频传输需求,文章详细推导了16位LFSR(线性反馈移位寄存器)的单周期16步并行计算公式,并提供了可直接使用的Verilog代码,解决了传统8bit串行处理吞吐量不足的瓶颈,为高速接口设计提供了关键优化技巧。

y4z5a6b7的博客 259

用友U8库龄分析避坑指南:物料出入库日期计算的5个常见错误

本文深入剖析了在用友U8系统中进行库龄分析时,物料出入库日期计算的五个常见陷阱。从数据源选择、业务类型过滤、计价方式逻辑、期初数据处理到查询性能优化,逐一解析错误根源并提供避坑指南,帮助企业获得准确的库龄数据,有效管理库存与资金风险。

weixin_29209801的博客 455

@query 注解的定义_Mybatis Plus + 自定义注解优雅校验数据重复问题

在我们平常开发的过程中,在添加或修改数据之前,为了避免有些数据重复问题,我们通常的做法都是先去数据库里面根据某个字段去数据库里面查询下这条数据是否存在。比如,会员注册的问题,一般会员的账号或者会员昵称都要求在数据表里面必须是唯一存在的。但是作为一个企业级的web 项目,在实际开发过程中会有很多这种相似的业务,因为如果在每个接口里面都去做重复的事情无疑显得十分枯燥无味。因此,小编就在想能不能把这种类...

weixin_42316909的博客 2173

防护装备穿戴检查:基于YOLOv8与UI界面的深度学习应用

通过结合YOLOv8模型与UI界面,本系统实现了防护装备穿戴检查的自动化。该系统能够快速识别工作人员是否正确佩戴了所需的防护装备,帮助提高安全管理水平。未来,随着技术的发展,防护装备穿戴检查系统将在更多领域得到应用,如工业现场、建筑工地等,实现更加精确和高效的实时监控。

YOLO 355

使用MyBatis的动态SQL注解实现实体的CRUD操作

在使用MyBatis进行数据库操作时,动态SQL注解提供了一种优雅的方式来编写动态SQL语句。MyBatis 3.x 版本提供了以下四个CRUD的高级注解: @SelectProvider:用于构建动态查询SQL @InsertProvider:用于构建动态新增SQL @UpdateProvider:用于构建动态更新SQL。 @DeleteProvider:用于构建动态删除SQL。 此外,我们将与MyBatis Plus中的`@Select`注解进行对比,展示MyBatis动态SQL注解的优势和灵活性。

后端开发 2525

996引擎 - 【任务系统】和【cfg_newtask.xls】

996这个任务系统仅用于控制主界面左上角的【任务列表】,不包含具体任务的业务逻辑。任务列表有多个任务时支持上下滑动,可以调用接口将指定任务【置顶】任务列表中,每个任务一行,分标题和(当前状态对应的)内容两部分,点击能触发事件具体的任务系统业务逻辑,需要自己写代码实现。(配合996提供的任务接口和触发,实现更新任务列表)

灵台方寸山斜月三星洞 1434

电磁场仿真软件:Sonnet_(3).Sonnet用户界面与操作

Sonnet支持用户通过二次开发自定义工具,例如:编写Python脚本,实现自定义功能。将脚本保存为工具插件。在Sonnet中加载并使用自定义工具。# 示例:自定义工具 import sonnet def custom_tool(design , object , parameter , value) : """自定义工具示例,设置对象的指定参数。

kkchenjj的博客 1523

MyBatis/mybatis_plus的项目 如何优雅地打印SQL

其中类上的Intercepts注解含义为:在 Executor 的 query、update 方法执行前后进行自定义的处理,其中Executor 是最底层的执行器,负责与数据库进行通信。平常像用的比较多的PageHelper,就是一个MyBatis的插件,实现原理和我们这次要做的功能十分相似。拦截过程中,如果存在拦截器,且方法的签名与拦截器配置的方法签名匹配,则调用拦截器的intercept方法。在参数少并且SQL简单的情况下,这样的SQL我们能通过手动去替换占位符,来获取到真正执行的SQL。

weixin_42471196的博客 2972

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

在SpringBoot项目中,自定义注解+拦截器优雅的实现敏感数据的加解密!

作者:CoderTanzJblog.youkuaiyun.com/bbcckkl/article/details/104069487在实际生产项目中,经常需要对如身份证信息、手机号、真实姓名等的敏感数...

Java知音 2573

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

mybatisplus自定义SQL查询特定条件的TTS任务

在AI语音系统中,面对百万级TTS任务的复杂筛选需求,MyBatis-Plus通过自定义SQL、动态XML映射和JSON字段解析,实现高效多维度查询。结合注解、.apply()扩展与分页优化,兼顾灵活性与性能,支撑方言、情感等非结构化数据检索。

weixin_35871529的博客 871

MyBatis-Plus前置拦截器,基于注解按需分表方案

在实际业务中,并不是所有表都需要做分表处理。因此我们结合自定义注解与 MyBatis 拦截器机制,优雅地实现了按需分表查询的功能。本篇文章聚焦于实现逻辑,不深入探讨 MyBatis-plus 的底层机制,重点介绍如何在使用查询时,实现自动分表。👉业务背景:本例场景仅需将数据划分为“今日表”和“历史表”。如果你有更多维度的分表需求,可在此基础上扩展 SQL 替换逻辑。🎯 完整代码在最后,文章主要是分步解析,有基础的可以直接看完整代码,对疑惑的点在看详细的解析。

weixin_44073650的博客 1398

MyBatis-Plus自定义SQL与复杂查询实战指南

MyBatis-Plus作为MyBatis的增强工具,在简化CRUD操作的同时,面对多表关联、动态条件等复杂查询场景时,自定义SQL成为开发者的必备技能。通过XML映射文件或注解方式,开发者可以灵活编写SQL语句,实现高效的数据查询与处理。特别是在处理多表联合查询、动态条件构造等场景时,合理使用ResultMap或DTO投影能显著提升查询性能。结合MyBatis-Plus的条件构造器,可以更优雅地实现动态SQL,同时注意SQL注入防护和慢查询优化,确保系统安全与性能。本文通过实战案例,详细解析了MyBati

weixin_33901926的博客 380

如何优雅地使用MyBatis注解版

1 第3-3课:如何优雅地使用MyBatis注解版 自从 Java 1.5 开始引入了注解,注解便被广泛地应用在了各种开源软件中,使用注解大大地降低了系统中的配置项,让编程变得更为优雅。MyBatis 也顺应潮流基于注解推出了 MyBatis 的注解版本,避免开发过程中频繁切换到 XML 或者 Java 代码中,从而让开发者使用 MyBatis 会有统一的开发体验。 因为最初设计时,My...

weixin_33881140的博客 342

MyBatis 注解全解析:告别XML,优雅开发数据库操作

在传统的 MyBatis 开发中,我们通常需要编写大量的 XML 映射文件。然而,随着项目规模的扩大和团队协作的需要,XML 配置的维护成本逐渐显现。MyBatis 注解的出现,为开发者提供了更简洁、直观的编码方式。本文将系统性地介绍 MyBatis 的各种注解,帮助你做出合适的技术选型。用于定义查询结果与 Java 对象的映射关系,特别是当数据库字段名与 Java 属性名不一致时。// 基本映射@Results({})// 枚举类型映射@Results({})@ResultMap。

用文字记录技术,用分享回馈社区,用坚持见证成长 2164

mybatis+自定义注解实现对数据库敏感字段进行加密

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括://语句执行拦截// 参数获取、设置时进行拦截// 对返回结果进行拦截//sql语句拦截简而言之,即在执行sql的整个周期中,我们可以任意切入到某一点对sql的参数、sql执行结果集、sql语句本身等进行切面处理。

gehanyang的博客 1728

第 3-3 课:SpringBoot如何优雅地使⽤ MyBatis 注解版

⾃从 Java 1.5 开始引⼊了注解,注解便被⼴泛地应⽤在了各种开源软件中,使⽤注解⼤⼤地降低了系统中的 配置项,让编程变得更为优雅。MyBatis 也顺应潮流基于注解推出了 MyBatis 的注解版本,避免开发过程中 频繁切换到 XML 或者 Java 代码中,从⽽让开发者使⽤ MyBatis 会有统⼀的开发体验。 因为最初设计时,MyBatis 是⼀个 XML 驱动的框架,...

qq_34227896的博客 1万+
上一篇: MySQL使用GROUP_CONTACT和JSON_OBJECT代替烦人的子查询
下一篇: micro:bit是什么?小学生拿着它就能召唤神龙?
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值