一起来学 SpringBoot 2.x | 第二十七篇:优雅解决分布式限流

SpringBoot中如何实现限流,这种方式才叫优雅。 前面两篇文章我们介绍了如何在SpringBoot中使用Guava和Redis实现接口限流的文章。具体包括1.2.现在,一个问题摆在我们面前:如何将这两种限流机制整合到同一个组件中,以便用户随时切换呢?显然,我们需要定义一个通用的限流组件,将其引入到业务中,并支持通过配置文件自由切换不同的限流机制。举例而言,当使用时,启用Redis分布式限流组件,当使用时,启用Guava限流组件。这种自由切换机制能够为用户提供更大的灵活性和可维护性。接下来,让我们开始动手实现吧! 阅读详情

http://www.iocoder.cn/Spring-Boot/battcn/v2-cache-redislimter/ 

 

SpringBoot 是为了简化 Spring 应用的创建、运行、调试、部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个 WEB 工程

在前面的两篇文章中,介绍了一些限流的类型和策略,本篇从 Spring Boot、Redis 应用层面来实现分布式的限流….

分布式限流

单机版中我们了解到 AtomicInteger、RateLimiter、Semaphore 这几种解决方案,但它们也仅仅是单机的解决手段,在集群环境下就透心凉了,后面又讲述了 Nginx 的限流手段,可它又属于网关层面的策略之一,并不能解决所有问题。例如供短信接口,你无法保证消费方是否会做好限流控制,所以自己在应用层实现限流还是很有必要的。

本章目标

利用 自定义注解、Spring Aop、Redis Cache 实现分布式限流….

具体代码

很简单…

导入依赖

在 pom.xml 中添加上 starter-web、starter-aop、starter-data-redis 的依赖即可,习惯了使用 commons-lang3 和 guava 中的一些工具包…

 

<dependencies>
    <!-- 默认就内嵌了Tomcat 容器,如需要更换容器也极其简单-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

 

属性配置

在 application.properites 资源文件中添加 redis 相关的配置项

 

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=battcn

 

Limit 注解

创建一个 Limit 注解,不多说注释都给各位写齐全了….

 

package com.battcn.limiter.annotation;


import com.battcn.limiter.LimitType;

import java.lang.annotation.*;

/**
 * 限流
 *
 * @author Levin
 * @since 2018-02-05
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Limit {

    /**
     * 资源的名字
     *
     * @return String
     */
    String name() default "";

    /**
     * 资源的key
     *
     * @return String
     */
    String key() default "";

    /**
     * Key的prefix
     *
     * @return String
     */
    String prefix() default "";

    /**
     * 给定的时间段
     * 单位秒
     *
     * @return int
     */
    int period();

    /**
     * 最多的访问限制次数
     *
     * @return int
     */
    int count();

    /**
     * 类型
     *
     * @return LimitType
     */
    LimitType limitType() default LimitType.CUSTOMER;
}

public enum LimitType {
    /**
     * 自定义key
     */
    CUSTOMER,
    /**
     * 根据请求者IP
     */
    IP;
}

 

RedisTemplate

默认情况下 spring-boot-data-redis 为我们提供了StringRedisTemplate 但是满足不了其它类型的转换,所以还是得自己去定义其它类型的模板….

 

package com.battcn.limiter;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;

/**
 * @author Levin
 * @since 2018/8/2 0002
 */
@Configuration
public class RedisLimiterHelper {

    @Bean
    public RedisTemplate<String, Serializable> limitRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

 

Limit 拦截器(AOP)

熟悉 Redis 的朋友都知道它是线程安全的,我们利用它的特性可以实现分布式锁、分布式限流等组件,在一起来学SpringBoot | 第二十三篇:轻松搞定重复提交(分布式锁)中讲述了分布式锁的实现,限流相比它稍微复杂一点,官方虽然没有提供相应的API,但却提供了支持 Lua 脚本的功能,我们可以通过编写 Lua 脚本实现自己的API,同时他是满足原子性的….

下面核心就是调用 execute 方法传入我们的 Lua 脚本内容,然后通过返回值判断是否超出我们预期的范围,超出则给出错误提示。

 

package com.battcn.limiter;


import com.battcn.limiter.annotation.Limit;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Method;

/**
 * @author Levin
 * @since 2018/2/5 0005
 */
@Aspect
@Configuration
public class LimitInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LimitInterceptor.class);

    private final RedisTemplate<String, Serializable> limitRedisTemplate;

    @Autowired
    public LimitInterceptor(RedisTemplate<String, Serializable> limitRedisTemplate) {
        this.limitRedisTemplate = limitRedisTemplate;
    }


    @Around("execution(public * *(..)) && @annotation(com.battcn.limiter.annotation.Limit)")
    public Object interceptor(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        Limit limitAnnotation = method.getAnnotation(Limit.class);
        LimitType limitType = limitAnnotation.limitType();
        String name = limitAnnotation.name();
        String key;
        int limitPeriod = limitAnnotation.period();
        int limitCount = limitAnnotation.count();
        switch (limitType) {
            case IP:
                key = getIpAddress();
                break;
            case CUSTOMER:
                // TODO 如果此处想根据表达式或者一些规则生成 请看 一起来学Spring Boot | 第二十三篇:轻松搞定重复提交(分布式锁)
                key = limitAnnotation.key();
                break;
            default:
                key = StringUtils.upperCase(method.getName());
        }
        ImmutableList<String> keys = ImmutableList.of(StringUtils.join(limitAnnotation.prefix(), key));
        try {
            String luaScript = buildLuaScript();
            RedisScript<Number> redisScript = new DefaultRedisScript<>(luaScript, Number.class);
            Number count = limitRedisTemplate.execute(redisScript, keys, limitCount, limitPeriod);
            logger.info("Access try count is {} for name={} and key = {}", count, name, key);
            if (count != null && count.intValue() <= limitCount) {
                return pjp.proceed();
            } else {
                throw new RuntimeException("You have been dragged into the blacklist");
            }
        } catch (Throwable e) {
            if (e instanceof RuntimeException) {
                throw new RuntimeException(e.getLocalizedMessage());
            }
            throw new RuntimeException("server exception");
        }
    }

    /**
     * 限流 脚本
     *
     * @return lua脚本
     */
    public String buildLuaScript() {
        StringBuilder lua = new StringBuilder();
        lua.append("local c");
        lua.append("\nc = redis.call('get',KEYS[1])");
        // 调用不超过最大值,则直接返回
        lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
        lua.append("\nreturn c;");
        lua.append("\nend");
        // 执行计算器自加
        lua.append("\nc = redis.call('incr',KEYS[1])");
        lua.append("\nif tonumber(c) == 1 then");
        // 从第一次调用开始限流,设置对应键值的过期
        lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
        lua.append("\nend");
        lua.append("\nreturn c;");
        return lua.toString();
    }

    private static final String UNKNOWN = "unknown";

    public String getIpAddress() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

 

控制层

在接口上添加 @Limit() 注解,如下代码会在 Redis 中生成过期时间为 100s 的 key = test 的记录,特意定义了一个 AtomicInteger 用作测试…

 

package com.battcn.controller;

import com.battcn.limiter.annotation.Limit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Levin
 * @since 2018/8/2 0002
 */
@RestController
public class LimiterController {

    private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger();

    @Limit(key = "test", period = 100, count = 10)
    @GetMapping("/test")
    public int testLimiter() {
        // 意味著 100S 内最多允許訪問10次
        return ATOMIC_INTEGER.incrementAndGet();
    }
}

 

主函数

就一个普通的不能在普通的主函数类了

 

package com.battcn;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author Levin
 */
@SpringBootApplication
public class Chapter27Application {

    public static void main(String[] args) {
        SpringApplication.run(Chapter27Application.class, args);
    }
}

 

测试

完成准备事项后,启动 Chapter27Application 自行测试即可,测试手段相信大伙都不陌生了,如 浏览器、postman、junit、swagger,此处基于 postman,如果你觉得自带的异常信息不够友好,那么配上一起来学SpringBoot | 第十八篇:轻松搞定全局异常 可以轻松搞定…

未达设定的阀值时

正确响应正确响应

达到设置的阀值时

错误响应错误响应

总结

目前很多大佬都写过关于 Spring Boot 的教程了,如有雷同,请多多包涵,本教程基于最新的 spring-boot-starter-parent:2.0.3.RELEASE编写…

本篇文章核心的 Lua 脚本截取自军哥的 Aquarius 开源项目,有兴趣的朋友可以 fork star ,该项目干货满满…

全文代码:https://github.com/battcn/spring-boot2-learning/tree/master/chapter27

SpringBoot接口防刷实战:基于AOP与Redis的限流方案设计与实现 在分布式系统与Web服务开发中,接口限流是保障系统稳定性和安全性的核心技术之一。其核心原理是通过控制单位时间内的请求访问频率,防止因恶意刷接口或突发流量导致的服务器资源耗尽、服务响应延迟甚至崩溃。从技术价值看,有效的限流机制不仅能抵御撞库攻击、短信轰炸等常见安全威胁,还能提升系统的整体可用性和健壮性。在应用场景上,它广泛适用于用户登录、短信发送、数据提交等高敏感、易被滥用的业务接口。本文聚焦于SpringBoot框架,深入探讨如何利用Spring AOP的切面编程能力,结合Redis的高性能与分布式特性,构 阅读详情

相关推荐

SpringBoot API防火墙:内嵌式防护、在线配置与限流算法实战

在微服务与Web应用开发中,API安全防护是保障系统稳定性的核心环节。其基本原理是在请求到达业务逻辑前进行识别、过滤与管控,从而防止恶意访问、流量过载等风险,技术价值在于提升服务的韧性与安全性。常见的实现模式包括网关集中管控与边车代理,而内嵌式防护则将能力集成于应用内部,实现零网络延迟的精细化控制。应用场景广泛覆盖高并发接口、敏感业务端点及需要快速策略调整的环境。本文聚焦的SpringBoot API防火墙,正是这一理念的工程实践,它通过Servlet Filter或Interceptor机制实现请求拦截,

456

实践应用:Spring Boot优雅解决分布式限流

*** 自定义异常*/super(msg);super(msg);return msg;

qq_39093474的博客 861

置顶必读(1) |《滚雪球学 Spring Boot 4.x》专栏目录导航篇(持续更新中)!

🏆 本文收录于 《滚雪球学 Spring Boot 4.x》 专栏。 本专栏面向 有一点 Java 基础,但没有系统学过 Spring Boot 的读者,采用“滚雪球式学习法”:先跑通、再理解、再重构、再上线,最终带你从第一个 Hello API 一路完成一个可部署、可监控、可扩展的后端项目。

**My Coding Family** 8万+

Spring Boot + Redis 实现分布式限流

Spring Boot + Redis实现分布式固定窗口限流

bird_tp的博客 2101

【SpringBoot 3.x 第267节】夯爆了,如何为外部依赖增加重试、熔断与限流(超详解)!

🏆本文收录于《滚雪球学SpringBoot 3.x》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。

**My Coding Family** 1万+

详解Springboot分布式限流实践

主要介绍了详解Springboot分布式限流实践 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

SpringBoot(五)之分布式限流

为什么80%的码农都做不了架构师?>>> ...

weixin_33998125的博客 684

麒麟OS恢复出厂设置原理与实操:非重装的系统状态回滚

Linux操作系统中的‘恢复出厂设置’并非简单重装或镜像还原,而是一种基于状态快照、包管理与配置模板的可控回滚机制。其核心原理依赖LVM快照技术、RPM包签名验证和预置配置模板,兼顾系统稳定性与用户数据安全。该方案在政务、教育等国产化场景中具有显著技术价值——不格式化硬盘、不丢失/home文档、不重配驱动与授权,大幅降低运维复杂度。典型应用场景包括系统卡顿修复、软件冲突清理、桌面环境异常恢复等。本文深入解析银河麒麟V10的kylin-restore-cli工具链与麒麟管家集成逻辑,聚焦‘恢复出厂设置’这一高

weixin_30879169的博客 770

一起来学 SpringBoot 2.x | 第二十七篇优雅解决分布式限流

熟悉 Redis 的朋友都知道它是线程安全的,我们利用它的特性可以实现分布式锁、分布式限流等组件,在一起来学SpringBoot | 第二十三篇:轻松搞定重复提交(分布式锁)中讲述了分布式锁的实现,限流相比它稍微复杂一点,官方虽然没有提供相应的API,但却提供了支持 Lua 脚本的功能,我们可以通过编写 Lua 脚本实现自己的API,同时他是满足原子性的…. 下面核心就是调用 execute 方法传入我们的 Lua 脚本内容,然后通过返回值判断是否超出我们预期的范围,超出则给出错误提示。

weixin_45811927的博客 665

小华半导体HC32芯片开发使用说明

HC32F/L系列MCU在Keil MDK和IAR Workbench环境下通过工程样例进行软件开发。

小布的芯视角 5626

DDPG算法实战:用Python一步步实现深度强化学习中的连续动作控制

本文详细介绍了DDPG算法在深度强化学习中的连续动作控制应用,通过Python和PyTorch实现从零构建智能体。内容涵盖环境搭建、网络架构设计、经验回放、OU噪声实现及训练流程优化,帮助开发者掌握DDPG在机械臂控制等场景的核心技术。

weixin_30335575的博客 414

SpringBoot 2.x 优雅解决分布式限流

某天A君突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。 一、常用的限流算...

我飞故我在的专栏 1万+

flink新版本无bat启动文件的解决办法

flink

6 4932

SpringBoot 中 AOP 实现接口限流

比如登录接口、短信验证码接口、支付接口,很容易被恶意请求刷爆(比如频繁调用发送短信、恶意登录试错),导致系统响应变慢、服务崩溃,甚至产生额外的费用(短信费、接口调用费)。举例:同样配置“1分钟内最多请求10次”,59秒请求10次后,61秒请求时,统计的是1-61秒内的请求次数(仍为10次),会被限流,避免临界问题。原理:将固定窗口拆分为多个小窗口(如 1分钟拆分为6个10秒小窗口),每次请求时,只统计“当前时间往前推1分钟”内的请求次数,超过阈值则限流。60秒后进入新窗口,请求次数重置,可再次请求。

。。。 886

Windows下Python调用D435i相机避坑指南:从SDK版本选择到RealSense Viewer报错解决

本文详细介绍了在Windows环境下使用Python调用Intel D435i相机的完整避坑指南。重点解决了SDK版本选择问题,推荐使用稳定的Intel RealSense SDK 2.54.2版本,并提供了从环境配置、常见报错解决到实际应用案例的全流程实践指导,帮助开发者高效完成相机部署与开发。

palm99的专栏 923

Plant Simulation 双深位立库开发系列教程-货架参数化

为了方便用户进行货架参数化,我们需要在交互界面上进行参数调整,右键打开RackLane,打开Dialog,在Rack_tab的tab下创建相应的对象,并创建NumberOfRightRack和NumberOfLeftRack作为数据连接对象。通过Rack._3D.VisibleGraphicGroups := ["default"]来切换需要显示的Rack,当不需要显示则可以Rack._3D.VisibleGraphicGroups := [""]即可。首先,加载默认的HBW库到模型中,确保结构树当中有。

santayana的专栏 1256

springboot 系列技术教程目录

一、教程目录地址:springboot系列技术教程目录 二、教程内容: springboot2.X系列: springboot整合mybatis 使用HikariCP连接池 springboot Mybatis 整合 springboot mybatis 使用多数据源 一起来学SpringBoot | 第一篇:构建第一个SpringBoot工程 一起来学SpringBoot | 第...

愿我如星君如月 ... 夜夜流光相皎洁 ... 2553

【异常】Cursor导入Karpathy指南失败解决方案Failed to import GitHub/GitLab rules: Error executing git: Cloning into

摘要:本文解决了Git克隆单个文件报错的问题,指出git clone只能克隆整个仓库而非单个文件。提供了两种解决方案:1) 推荐手动创建规则文件,在Cursor项目中新建.mdc文件并粘贴Karpathy行为指南完整内容;2) 备选克隆整个仓库获取文件。指南包含编码行为规范,强调简单性、精准修改和明确目标。还说明了如何设置全局规则和即时生效的方法。

奶爸的编程之路,也就一周冷个三天 174

(17)SpringBoot 2.X 高并发秒杀系统总结

(18)SpringBoot 2.X 高并发秒杀系统总结1.系统介绍2.开发环境和工具3.开发技术4.秒杀优化方向5.项目亮点5.1 使用分布式Seesion,可以实现让多台服务器同时可以响应。5.2 使用redis做缓存提高访问速度和并发量,减少数据库压力,利用内存标记减少redis的访问5.3 使用页面静态化,加快用户访问速度,提高QPS,缓存页面至浏览器,前后端分离降低服务器压力5.4 使用...

Java后端 2537

半导体物理基础:载流子输运理论_(4).载流子浓度与费米能级

费米能级(Fermi Level)是描述半导体中电子和空穴能量分布的基准能级。在绝对零度下,费米能级以下的所有能级都被电子占据,费米能级以上的所有能级都是空的。随着温度的升高,电子会获得足够的能量跃迁到费米能级以上的能级,形成导带中的自由电子和价带中的空穴。费米能级在半导体中的位置取决于掺杂浓度和温度。对于本征半导体,费米能级位于禁带的中心;对于掺杂半导体,费米能级会偏移,具体位置取决于掺杂类型和浓度。载流子浓度(Carrier Concentration)是指半导体中自由电子和空穴的数量密度。

2401_87715305的博客 831

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储

使用Minio实现文件的分布式存储

皓亮君的博客 4574

计算机控制技术及应用

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

springboot教程目录

占个坑

LuisChen的博客 4万+

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

【实践篇】4.9 使用Redisson 优雅实现业务的5种场景

Redisson是一个基于Redis的分布式Java对象和数据结构库,它提供了丰富的功能和易于使用的API,使开发人员能够轻松地在分布式环境中操作和管理数据。作为一个分布式对象和数据结构库,Redisson提供了许多常见的数据结构和算法的实现,包括通用对象桶、二进制流、地理空间对象桶、BitSet、原子长整型、原子双精度浮点数、话题(订阅分发)、布隆过滤器和基数估计算法。这些数据结构和算法为开发人员提供了处理分布式数据的工具,从而简化了复杂性,提高了效率。

日拱一卒无有尽,功不唐捐终入海 3300

springboot系列技术教程目录

https://blog.csdn.net/Winter_chen001/article/details/80537829 springboot系列教程,有原创和转载,希望能帮到大家。 springboot2.X系列: springboot整合mybatis 使用HikariCP连接池 springboot Mybatis 整合 springboot mybatis 使用多数据源 ...

qq_32534441的博客 218
上一篇: Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析
下一篇: 一起来学 SpringBoot 2.x | 第二十三篇:轻松搞定重复提交(分布式锁)
god_love_hlq_xff
博客等级 码龄11年 27粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值