篮球小凯 level
获赞
14
粉丝
3
关注
11
看过 TA
264
河北工程大学
2028
Java
IP属地:河北
暂未填写个人简介
私信
关注
Redis 中一种非常高效的二进制数据结构。核心是通过位(bit)来存储和操作数据,特别适合处理海量的布尔型(是 / 否)数据场景。Bitmap 本质上是 Redis 的 String 类型 的「特殊使用方式」—— String 类型最大能存储 512MB 数据,对应可以存储 512MB * 8 = 4294967296 个位(约 43 亿位)。Redis 的 String 类型 是二进制安全的字节序列,官方规定其最大存储容量为 512MB(字节);而 Bitmap 本质上是对 String 字节的「位级操作」—— 把每个字节(Byte)拆成 8 个位(Bit)来使用,因此 Bitmap 的最大可用位数 = 512MB × 8 = 4294967296 位(约 43 亿位)。每个位只有 0 或 1 两种状态;通过「偏移量(offset)」定位具体的位(偏移量从 0 开始,无上限,只要内存足够);核心价值:用极少的内存存储海量的布尔型数据。512MB 限制的底层原因Redis 对 String 设 512MB 上限,并非技术上无法突破,而是基于「性能 + 设计定位」的权衡:内存与性能平衡:String 是 Redis 最基础、使用最频繁的类型,若允许无限制存储大字符串,会导致:单 key 占用过多内存。读写大 String 时(如 GET/SET 512MB 数据),网络传输、内存拷贝耗时过长,阻塞 Redis 主线程;符合 Redis 设计定位:Redis 是「内存数据库」,核心优势是「高速读写」,适合存储小而高频访问的数据(如缓存、计数器),而非大文件 / 大 blob 数据;底层编码适配:Redis String 会根据长度自动切换编码(int→embstr→raw),超过 44 字节用 raw 编码,512MB 是 raw 编码的安全上限,避免编码切换或内存管理异常。bitmap直观理解比如用 Bitmap 记录「用户是否登录」:offset = 用户 ID,value = 1(登录)/ 0(未登录);记录 1 亿用户的登录状态,仅需 1亿 / 8 = 12.5MB 内存,远低于用 Hash/Set 等结构的内存消耗。实际操作所有命令都有 BIT1. 设置指定偏移量的位值:SETBIT key offset value(重要)作用:给 key 的第 offset 位设置值(0 或 1)示例:记录用户 ID=10086 的用户今天(20260106)登录了(设为 1):SETBIT login:20260106 10086 1(integer) 0  # 返回值:该位原来的值(首次设置为0)2 获取指定偏移量的位值:GETBIT key offset(重)查询 key 的第 offset 位的值;示例:查询用户 10086 20260106 是否登录:GETBIT login:20260106 100863. 统计指定范围内值为 1 的位数量:(重)BITCOUNT key [start end]统计 key 中值为 1 的位的总数(可选指定字节范围,默认全部)BITCOUNT login:202601064位运算(与 / 或 / 异或 / 非):BITOP operation destkey key1 [key2 ...]作用:对多个 Bitmap 执行位运算,结果存入 destkey;常用运算:AND:与(都为 1 才为 1);OR:或(任意一个为 1 则为 1);XOR:异或(不同为 1,相同为 0);示例:统计用户「20260105 和 20260106 两天都登录」的数量:SETBIT login:20260105 10086 1SETBIT login:20260105 10000 1SETBIT login:20260106 10086 1SETBIT login:20260106 10001 1执行AND运算,结果存入 login:20260105_06_bothBITOP AND login:20260105_06_both login:20260105 login:20260106统计结果(只有10086两天都登录)BITCOUNT login:20260105_06_both
0 点赞 评论 收藏
分享
考虑一种场景,我们的短信发送接口被同一个人使用不同的手机号多次恶意调用,那么同样会导致之前说过的问题。要实现 IP 地址黑名单功能,核心是基于 IP 标识请求方,并通过 Redis 维护黑名单列表,结合拦截器在请求入口处校验。在redisconstants中// ========== 新增【登录IP管控】核心配置 ==========// IP 1分钟限流Key(格式:verify:ip:limit:xxx.xxx.xxx.xxx):1IP1分钟仅能发1次验证码public static final String VERIFY_IP_LIMIT_KEY = "verify:ip:limit:";// IP请求次数统计Key(格式:verify:ip:count:xxx.xxx.xxx.xxx):统计短时间内IP总请求数public static final String VERIFY_IP_COUNT_KEY = "verify:ip:count:";// IP黑名单Key(格式:verify:ip:black:xxx.xxx.xxx.xxx):IP被封禁的标识public static final String VERIFY_IP_BLACK_KEY = "verify:ip:black:";// 通用过期时间(复用原有,无需新增):限流60s、统计1h、黑名单24h在usersericeimpl中//ip黑名单检测// 从session中获取HttpServletRequest对象HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();// ====== 第一步:获取客户端真实IP(必须!适配本地/服务器/代理环境) ======String clientIp = getRealIp(request);// ====== 第二步:校验黑名单【优先级最高】 ======// 1. 校验IP是否在黑名单 → IP拉黑直接拒绝String ipBlackKey = RedisConstants.VERIFY_IP_BLACK_KEY + clientIp;if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(ipBlackKey))) {return Result.fail("ip在黑名单中!!");}// ====== 第三步:校验1分钟限流【防高频请求】 ======// 1. 校验IP:同一个IP,1分钟内仅能请求1次String ipLimitKey = RedisConstants.VERIFY_IP_LIMIT_KEY + clientIp;if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(ipLimitKey))) {return Result.fail("同一个IP,1分钟内仅能请求1次");}// 1. IP请求次数统计:短时间内超3次 → 拉黑IPString ipCountKey = RedisConstants.VERIFY_IP_COUNT_KEY + clientIp;Long ipCount = stringRedisTemplate.opsForValue().increment(ipCountKey);if (ipCount == 1) { // 首次请求,设置1小时过期stringRedisTemplate.expire(ipCountKey, RedisConstants.VERIFY_COUNT_TTL, TimeUnit.SECONDS);}// IP超3次 → 封禁IP(24小时)if (ipCount > 2) {stringRedisTemplate.opsForValue().set(ipBlackKey, "1", RedisConstants.VERIFY_BLACK_TTL, TimeUnit.SECONDS);}// ========== 工具方法1:获取客户端真实IP(关键!适配所有环境) ==========private String getRealIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}// 处理多代理场景,取第一个有效IPif (ip != null && ip.contains(",")) {ip = ip.split(",")[0].trim();}return ip;}// ========== 工具方法2:超阈值时,清理无用的限流/统计Key ==========private void cleanLimitKey(String... keys) {for (String key : keys) {stringRedisTemplate.delete(key);}}
0 点赞 评论 收藏
分享
01-12 01:19
已编辑
河北工程大学 Java
借鉴了一个博主的,我把完整代码实现,测试没问题下面考虑一个问题,用户请求发送验证码时,服务器这边的逻辑是直接调用相关接口(这里需要运营商提供增值服务),将验证码发给用户,同时服务器端也会存储一份验证码。但是如果有人恶意使用这项功能,大量发送无效验证码,那么就会给服务器带来压力,同时增加公司的开销,这正是我们愿意看到的(bushi)。所以这里需要对请求中的手机号获取验证码进行次数限制。在我们的日常使用中,一般一分钟只能获取一次验证码,这里的实现思路很简单,可以称为使用锁的思想,每次有请求想要获取验证码时先检查redis中是否存在对应的锁,如果存在,则返回失败,如果不存在,则生成验证码并在redis中设置一个过期时间为一分钟的锁。同时还可以实现一个黑名单功能,限制一个手机号一小时内只能获取3次验证码,超过次数则拉入黑名单,24小时后从黑名单中移除。public class RedisConstants {// 1分钟限频Key前缀(格式:verify:limit:手机号)public static final String VERIFY_LIMIT_KEY = "verify:limit:";// 1分钟限频过期时间(单位:秒)public static final Long VERIFY_LIMIT_TTL = 60L;// 请求次数统计Key前缀(格式:verify:count:手机号)public static final String VERIFY_COUNT_KEY = "verify:count:";// 次数统计过期时间(比如1小时,避免长期占用内存)public static final Long VERIFY_COUNT_TTL = 3600L;// 黑名单Key前缀(格式:verify:black:手机号)public static final String VERIFY_BLACK_KEY = "verify:black:";// 黑名单过期时间(24小时,单位:秒)public static final Long VERIFY_BLACK_TTL = 86400L;}@Overridepublic Result sendCode(String phone, HttpSession session) {//1,校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2,不符合,返回错误信息return Result.fail("手机格式错误!");}String blackKey = RedisConstants.VERIFY_BLACK_KEY + phone;if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(blackKey))) {// 已在黑名单,拒绝请求return Result.fail("已在黑名单,拒绝请求,24小时之后再访问,亲!");}// 步骤2:判断是否触发“1分钟限1次”String limitKey = RedisConstants.VERIFY_LIMIT_KEY + phone;if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(limitKey))) {// 1分钟内已请求过,拒绝return Result.fail("1分钟内访问过一次");}// 步骤3:统计请求次数,判断是否超3次String countKey = RedisConstants.VERIFY_COUNT_KEY + phone;// 自增请求次数(初始为1)Long count = stringRedisTemplate.opsForValue().increment(countKey);// 设置过期时间(仅第一次自增时设置)if (count == 1) {stringRedisTemplate.expire(countKey, RedisConstants.VERIFY_COUNT_TTL, TimeUnit.SECONDS);}// 步骤4:次数超3次 → 加入黑名单if (count > 2) { //调试的这里是2,因为这个逻辑在验证的后面stringRedisTemplate.opsForValue().set(blackKey, "1", RedisConstants.VERIFY_BLACK_TTL, TimeUnit.SECONDS);// 清除次数统计和限频Key(可选,避免干扰)stringRedisTemplate.delete(countKey);stringRedisTemplate.delete(limitKey);}// 步骤5:通过所有校验 → 设置1分钟限频锁stringRedisTemplate.opsForValue().set(limitKey, "1", RedisConstants.VERIFY_LIMIT_TTL, TimeUnit.SECONDS);// 后续逻辑:生成验证码、调用运营商接口发送等//3,符合,生成验证码String code = RandomUtil.randomNumbers(6);//4保存验证码到redis 有效期stringRedisTemplate.opsForValue().set("login:code"+phone,code,2, TimeUnit.MINUTES);//5,发送验证码log.debug("发送短信验证码成功,验证码:{}",code);//返回okreturn Result.ok();}
0 点赞 评论 收藏
分享
2025-12-27 20:35
河北工程大学 Java
其实从cookie到token演变经历了这10多年的互联网发展。三者在作用上是一致的,都是为了标识用户,区别在于对于安全性的要求及探索。cookie很简单,就是用户登录后,服务器生成一串字符返回给客户浏览器,存储下来。下次访问该网站,浏览器自动携带该字符串,以便标识该用户,省去输入账号和密码的麻烦。其实早期就是为了方便,并没有考虑安全,早期网页很简单,没什么敏感数据。这时的cookie字符串很多都是账号与密码的简单组合。随着大家注意到安全问题,大家就着手对cookie做些变化,从简单变化到使用哈希算法加密,逐渐演变成了session。无论cookie还是session其实都没有真正解决安全问题,session像是掩耳盗铃,虽然对cookie各种变化包装,但只要session泄漏,别人轻而易举就可以访问该用户数据。这时已经有很多公司考虑一种更加安全的机制。由于移动互联的发展,手机应用不像浏览器自带cookie,这推动了一种更加安全的方案token。session之于cookie区别在于那串字符的加密。token区别除了加密更侧重的则是客户端与服务端的鉴权,结合对称与非对称加密,使用时间戳等等。比较完善的加密鉴权方案还是挺复杂的,而且每个公司,每个产品的方案可能都有所区别。cookie可以说是已经淘汰的东西了,现在公司一般都会采用token方案。实际开发也不用想的太复杂,各种库一般都封装了基本的功能,调用接口根据需求相应调整即可。大些的公司可能会结合jwt设计一套自己的方案。总之作为开发者要知道:技术不是绝对的,技术是在不断变化发展的。
0 点赞 评论 收藏
分享

创作者周榜

更多
关注他的用户也关注了:
牛客网
牛客网在线编程
牛客网题解
牛客企业服务