一个注解搞定接口限速:自定义注解+Spring拦截器+Redis实践

发布时间:2026/9/7 19:32:17

一个注解搞定接口限速:自定义注解+Spring拦截器+Redis实践
1. 先聊清楚这个“限速注解”到底解决了什么问题做后端接口开发的时候限速是个绕不开的话题。尤其是面向公网的业务接口一旦遇到突发流量、爬虫脚本、或者某个调用方写了个有问题的重试循环服务端的压力瞬间就能被打满。轻则接口响应变慢重则数据库连接池耗尽、应用直接雪崩。常规做法无非几种网关层限流、Nginx 层限流、中间件限流比如 Sentinel但这些东西都有一个共同的问题——它们都是“全局”的或者说“按路由”的没法精细到“某个用户的某个操作最多每秒几次”。而且引入一套独立的限流中间件在小团队、小项目里往往意味着额外的运维成本和架构复杂度。那有没有一种方式能让限流这件事像Transactional一样简单往方法上一标就自动生效这就是这套“一个注解搞定网络限速”方案的出发点。核心思路是自定义注解 Spring 拦截器或 AOP 切面 分布式计数器存储把限速逻辑从业务代码里彻底剥离出去。业务方不用关心限流怎么实现只需要知道自己这个接口允许什么频率的访问然后在方法上加一个注解、填好参数即可。这篇文章我会从零开始把整个方案拆开讲清楚怎么设计注解参数、为什么用拦截器而不是过滤器、计数器存本地还是存 Redis、不同维度按 IP / 按用户 / 按接口怎么做、超限之后返回什么、以及生产环境容易踩哪些坑。无论你是刚接触 Spring Boot 的初学者还是已经在写中间件的老手这套方案都能直接落地到项目里。2. 整体方案设计为什么是“注解 拦截器 Redis”2.1 技术选型为什么不选 Filter也不选 AOP先回答一个很多人会问的问题实现限速用 Filter过滤器、Interceptor拦截器、AOP切面到底有什么区别很多网上的 demo 喜欢用 Filter 来做因为 Filter 是 Servlet 规范层面的东西Spring Boot 里注册一个FilterRegistrationBean就行。但 Filter 有一个很尴尬的问题它拿不到 HandlerMethod 的信息。也就是说在 Filter 里你根本不知道当前请求最终会进到哪个 Controller 的哪个方法自然也就没法判断“这个方法上有没有加限速注解”。你要做的话只能按 URL 前缀去匹配那就退化成了粗粒度的路由限流。AOP 能做但有一个隐藏的坑切面默认只拦截 Spring 管理的 Bean 的方法调用而 Controller 方法恰恰不走代理场景的时候容易踩坑。更麻烦的是如果你对 Controller 层用了 AOP方法内部自调用、或者某些异步调用会导致切面失效。虽然可以通过EnableAspectJAutoProxy(exposeProxy true)之类的手段绕过去但平白增加了理解成本。Interceptor 是三者中最合适的选择。它在 HandlerMapping 确认了目标 Handler 之后执行能轻松拿到HandlerMethod进而拿到方法上的自定义注解同时preHandle方法在 Controller 执行之前运行天然适合做“要不要放行”的决策。Spring MVC 本身就是这么设计的用起来最顺手。2.2 限速算法固定窗口还是滑动窗口限速算法的选择上我见过不少人一上来就写令牌桶其实很多时候是杀鸡用牛刀。令牌桶Token Bucket的优势是允许一定的突发流量适合对突发有要求的场景但它的实现相对复杂需要维护令牌的补充速率在分布式环境下还要考虑原子性问题。这套方案我推荐优先做固定窗口计数原因很简单大多数业务接口的限速需求是“每秒最多 N 次”“每分钟最多 M 次”这种语义用固定窗口直接对应实现也最简单。Redis 里面一个 key 就搞定INCR EXPIRE组合起来是原子的因为 INCR 是单命令不会有并发覆盖的问题。固定窗口唯一的问题是临界突变。比如窗口是 60 秒你允许 100 次那么在 00:59 和 01:01 这两秒内理论上可能各进来 100 次形成 2 秒内 200 次的“穿墙”。但绝大多数业务场景里这个概率和影响都小到可以忽略。真遇到不能接受的场景再把窗口缩小到 1 秒或者升级成 Lua 脚本实现的滑动窗口即可后面我会给出滑动窗口的替换方案。2.3 存储选型本地 ConcurrentHashMap 还是 Redis这一步取决于你的应用是单机部署还是多机部署。单机版直接用ConcurrentHashMap记录每个 key 的窗口计数。没有网络开销性能极高但多实例部署时限速是各自的等于没限。分布式版用 Redis。每次请求一次INCR网络开销大约零点几毫秒对于一个接口来说完全可接受。多实例之间共享计数才能真正做到整个服务维度的限速。我的建议是代码里把“计数器”抽象成一个接口默认提供 Redis 实现和本地内存实现通过配置项切换。这样开发环境不用依赖 Redis 也能跑生产环境切到 Redis 即可。后面我会把代码贴出来。3. 核心实现从零手写限速注解3.1 定义注解参数怎么设计才够用先看注解的定义。这是整个方案的“门面”参数设计得好不好直接决定业务方用起来顺不顺手。Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RateLimit { /** * 限流维度按什么区分调用方 */ Dimension dimension() default Dimension.IP; /** * 时间窗口大小默认 1 秒 */ long time() default 1L; /** * 时间窗口单位默认秒 */ TimeUnit unit() default TimeUnit.SECONDS; /** * 窗口内允许的最大请求次数 */ long count() default 100L; }再定义一个维度枚举public enum Dimension { /** 按客户端 IP 维度限流 */ IP, /** 按用户维度限流需要从上下文中获取用户 ID */ USER, /** 按接口方法维度限流接口整体限流 */ METHOD }解释一下每个参数的设计意图dimension决定 key 怎么拼。IP 维度最简单从HttpServletRequest.getRemoteAddr()取USER 维度适合登录后的接口从SecurityContext或 ThreadLocal 里拿用户 IDMETHOD 维度是接口整体限流适合防刷。timeunit合成一个窗口长度。拆成两个字段而不是直接用毫秒数是出于可读性考虑。业务方写time 1, unit TimeUnit.MINUTES比写time 60000直观得多。count窗口内允许的最大次数。这个值设多大完全取决于业务场景比如验证码接口可以严格一点读接口可以宽松一点。我见过有人把注解设计成RateLimit(rate 1/100s)这种字符串解析的样式我觉得没必要。字符串解析虽然写法上很炫但增加了出错概率和阅读成本编译期也没法检查不如用明确的字段。3.2 定义计数器接口本地和 Redis 实现随意切换这一步是为了让方案不依赖写死的 Redis也方便你后续替换成其他的存储中间件。public interface RateLimitCounter { /** * 对指定 key 做自增并设置过期时间 * * param key 计数器 key * param windowMs 窗口大小毫秒 * return 自增后的计数 */ long incrementAndGet(String key, long windowMs); }本地实现用ConcurrentHashMapComponent ConditionalOnProperty(name rate.limit.storage, havingValue local, matchIfMissing true) public class LocalRateLimitCounter implements RateLimitCounter { private final ConcurrentHashMapString, LocalCounter cache new ConcurrentHashMap(); Override public long incrementAndGet(String key, long windowMs) { long now System.currentTimeMillis(); LocalCounter counter cache.computeIfAbsent(key, k - new LocalCounter()); counter.cleanExpired(now); return counter.increment(now); } private static class LocalCounter { private long windowStart System.currentTimeMillis(); private long count 0L; private synchronized void cleanExpired(long now) { if (now - windowStart windowSize) { windowStart now; count 0L; } } private synchronized long increment(long now) { count; return count; } } }注意这里用了synchronized来保证线程安全因为固定窗口需要“先清理过期窗口再自增”两步单纯的 ConcurrentHashMap 的compute操作没法一次性完成这种复合逻辑。本地实现主要服务开发环境性能不需要极致但正确性必须保证。Redis 实现则简单得多Component ConditionalOnProperty(name rate.limit.storage, havingValue redis) public class RedisRateLimitCounter implements RateLimitCounter { private final StringRedisTemplate redisTemplate; public RedisRateLimitCounter(StringRedisTemplate redisTemplate) { this.redisTemplate redisTemplate; } Override public long incrementAndGet(String key, long windowMs) { Long count redisTemplate.opsForValue().increment(key, 1L); if (count ! null count 1L) { // 第一次自增时设置过期时间单位是毫秒 redisTemplate.expire(key, windowMs, TimeUnit.MILLISECONDS); } return count null ? 0L : count; } }生产环境没有任何锁竞争每个请求一次 Redis INCR 命令时间复杂度 O(1)。因为第一次 INCR 的值一定是 1只有这个时候才需要调用 EXPIRE 设置过期时间其余请求直接自增返回即可。3.3 拦截器核心拿到注解并判定是否超限下面是整个方案最核心的部分——拦截器实现。Component public class RateLimitInterceptor implements HandlerInterceptor { private static final String KEY_PREFIX rate_limit:; private static final String ERROR_MSG 请求过于频繁请稍后再试; private final RateLimitCounter counter; public RateLimitInterceptor(RateLimitCounter counter) { this.counter counter; } Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 非 Controller 方法直接放行比如静态资源 if (!(handler instanceof HandlerMethod handlerMethod)) { return true; } RateLimit rateLimit handlerMethod.getMethodAnnotation(RateLimit.class); if (rateLimit null) { return true; } // 拼接限流维度 key String dimensionKey buildDimensionKey(rateLimit.dimension(), request); String windowKey KEY_PREFIX handlerMethod.getBeanType().getSimpleName() _ handlerMethod.getMethod().getName() : dimensionKey; long windowMs rateLimit.unit().toMillis(rateLimit.time()); long currentCount counter.incrementAndGet(windowKey, windowMs); if (currentCount rateLimit.count()) { response.setStatus(429); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:429,\msg\:\ ERROR_MSG \}); return false; } return true; } private String buildDimensionKey(RateLimit.Dimension dimension, HttpServletRequest request) { return switch (dimension) { case METHOD - method; case USER - { // 这里根据你项目的用户上下文实现来取比如 SecurityContextHolder Object userId request.getAttribute(currentUserId); yield userId null ? anonymous : String.valueOf(userId); } case IP - getClientIp(request); }; } private String getClientIp(HttpServletRequest request) { String ip request.getHeader(X-Forwarded-For); if (ip null || ip.isBlank() || unknown.equalsIgnoreCase(ip)) { ip request.getRemoteAddr(); } else { // X-Forwarded-For 可能包含多个 IP取第一个即可 ip ip.split(,)[0].trim(); } return ip; } }代码里有两个细节值得注意。第一个是 key 的设计。handlerMethod.getBeanType().getSimpleName() _ handlerMethod.getMethod().getName()这一段用来区分不同接口。如果你不加这一段那么只要方法名相同即使 Controller 类不同也会共用一个计数器这显然不对。加了类名之后每个接口的限流是独立的语义更清晰。第二个是 IP 的获取。生产环境通常经过 Nginx 反向代理直接getRemoteAddr()拿到的是 Nginx 的地址不是客户端真实 IP。所以必须优先取X-Forwarded-For头而且要处理多级代理的情况——这个头可能是一串 IP用逗号分隔取第一个才是真实客户端。另外补充一句如果使用了X-Real-IP也可以作为备选方案不同团队习惯不同但原理一致。3.4 注册拦截器排除路径和顺序问题拦截器写了还得注册才生效。在 Spring Boot 里通过WebMvcConfigurer完成Configuration public class WebConfig implements WebMvcConfigurer { private final RateLimitInterceptor rateLimitInterceptor; public WebConfig(RateLimitInterceptor rateLimitInterceptor) { this.rateLimitInterceptor rateLimitInterceptor; } Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(rateLimitInterceptor) .addPathPatterns(/**) .excludePathPatterns(/health, /actuator/**); } }这里有个容易犯的错限速拦截器一定不要拦截静态资源和健康检查。/health、/actuator/**如果不排除监控系统的探活请求也会被计数导致误拦截。而静态资源的访问走的是另一套 handler不是 HandlerMethod拦截器里虽然做了判断直接放行但能排除还是尽量排除减少无谓逻辑。3.5 效果演示一个注解的“魔法”现场写了这么多来看一个实际使用的完整例子。假设我们有一个查询用户信息的接口希望限制每个 IP 每秒钟只能访问 10 次RestController RequestMapping(/api/user) public class UserController { GetMapping(/info) RateLimit(dimension Dimension.IP, time 1, unit TimeUnit.SECONDS, count 10) public ResultUserInfo getUserInfo(RequestParam Long userId) { // 业务逻辑 return Result.ok(userService.getUserInfo(userId)); } }然后用curl模拟 13 次连续请求看看效果for i in {1..13}; do curl -s -o /dev/null -w %{http_code}\n http://localhost:8080/api/user/info?userId1 done上面的循环连续发 13 次请求前 10 次正常返回 200从第 11 次开始系统直接返回 429意味着限速生效。这就是“一个注解搞定”的全部过程业务代码零侵入一个字符的业务逻辑都不用改。4. 进阶功能与扩展方案4.1 注解参数加一个 fallback 方法超限不走默认响应固定写死 JSON 返回体对于很多团队来说不够灵活。有的团队希望超限时返回一个自定义的错误页面有的希望切换到降级逻辑比如返回缓存数据。这时候可以在注解里增加一个fallback参数public interface RateLimit { // 其他参数省略 String fallback() default ; }然后在拦截器中检查如果超限且fallback非空就用 Bean 名称解析对应的方法执行降级逻辑。这个实现需要借助ApplicationContext拿到 bean再用反射调用对应方法。反射性能开销不大因为限速本身就是低频异常分支不影响正常请求路径。4.2 升级滑动窗口彻底干掉临界问题前面提到过固定窗口在临界点的“穿墙”问题。如果你的业务真的不能接受这种毛刺可以把 Redis 实现替换成 Lua 脚本用 ZSET 实现滑动窗口local key KEYS[1] local now tonumber(ARGV[1]) local window tonumber(ARGV[2]) local limit tonumber(ARGV[3]) redis.call(ZREMRANGEBYSCORE, key, 0, now - window) local count redis.call(ZCARD, key) if count limit then redis.call(ZADD, key, now, now .. - .. math.random(1000000)) redis.call(PEXPIRE, key, window) return 1 else return 0 end这段脚本的核心是每次请求到达时先移除窗口之外的所有记录然后统计窗口内还有多少条记录如果没到上限就加入新记录并返回 1否则返回 0。ZSET 的 score 用毫秒时间戳member 用“时间戳-随机数”保证唯一。原子性由 Lua 脚本天然保证Redis 单线程执行整个脚本无需担心并发问题。4.3 按用户限流时如何优雅获取用户 ID从request.getAttribute(currentUserId)取用户 ID 是我在上面示例代码里的临时方式实际项目中一般会有现成的用户上下文方案。如果你用的是 Spring Security可以这样改case USER - { Object principal SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails userDetails) { yield userDetails.getUsername(); } yield anonymous; }如果你用的是 Sa-Token 之类的框架取登录用户的方式类似无非是从StpUtil.getLoginId()里取。核心思想一致从你的认证体系中取出唯一标识用户的值拼进 key 里。4.4 注解 AOP 的方式什么时候更合适前面我强烈推荐了 Interceptor但有一种场景 AOP 更合适你想限制的不是 Web 请求而是某个 Service 方法的调用频率。举个例子你有一个消息推送方法上游系统可能会误调用你想给它加上限速这个方法和 HTTP 控制器完全无关用 Interceptor 就够不到。这种时候可以基于同一个注解写一个 AOP 切面逻辑思路完全一样只是把“从 request 中取维度 key”换成“从方法参数中取”。不过坦白讲绝大多数业务限速需求都发生在 Web 入口Interceptor 是首选。5. 生产环境踩坑实录这些坑我全替你踩过了5.1 常见问题速查表问题现象根本原因解决方案加了注解但不生效拦截器没有注册到 WebMvcConfigurer检查 addInterceptors 方法是否执行所有接口都被限了key 没有包含接口名或维度信息多个接口共用一个 key在 key 中拼上类名、方法名Redis 存储模式下限速失效多个实例 key 不一致比如维度 key 生成逻辑不同确保所有实例使用相同的 key 拼接规则取不到用户 ID匿名限速未登录的用户走 USER 维度时无法获得 ID降级为 anonymous 或改用 IP 维度上线后误杀正常用户count 设得太小或 IP 获取逻辑取到了代理 IP观察日志中的真实 key 与计数变化调大阈值修正 X-Forwarded-For 解析KeY 永不清理内存泄漏Redis 实现里没有设置 EXPIRE第一次 INCR 后必须调用 expire5.2 负载均衡场景下的隐蔽问题单机限速用本地存储没问题但一旦上了多节点只用ConcurrentHashMap的本地实现就会出现每个节点独立计数的漏洞。如果负载均衡恰好是轮询模式一个用户发 10 个请求经过 Nginx 分发到 3 个节点每个节点各放行 10 次总共就放了 30 次你的 10 次限制形同虚设。更隐蔽的是就算你切了 Redis如果 key 拼接逻辑里包含了节点信息比如不小心把serverIp加进去了同样会失效。所以进入生产环境之前务必确认存储实现是 Redis并且 key 的生成逻辑在所有节点上完全一致。5.3 限速器本身的性能消耗这是一个我经常被问的问题每次请求都多一次 Redis 交互性能还扛得住吗答案是一次 Redis INCR 的耗时通常在 0.1~0.5ms 之间如果你的接口本身耗时 50ms这 0.5ms 的开销只占 1%。而且 Redis 是纯内存操作单实例每秒可以处理十万级以上的 INCR 命令对于绝大多数业务体量来说毫无压力。真正要担心的是 Redis 超时。如果 Redis 出现故障超时你的业务接口也会跟着超时这在极端情况下会放大故障。建议在RedisRateLimitCounter里做一层降级捕获异常时直接放行fail-open。限速器挂掉不能拖垮业务这是生产环境的一个默认原则。但你也要知道fail-open 意味着 Redis 故障时限速会失效这是可接受的——毕竟 Redis 故障本身已经是严重事故限速失效属于次生灾害。5.4 为什么必须把拦截器顺序放在最前面Spring 拦截器是支持配置顺序的。如果你的项目里同时有认证、日志、限速等多个拦截器建议把限速拦截器放在最前面或者至少放在耗时操作之前。原因很朴素限速的目的是保护系统资源如果一个请求注定要被限掉那就不应该让它继续往下走认证、业务逻辑等重环节。先拦截成本最低。5.5 测试时别只测“超了会不会拦”很多人在自测时只关注“我连发 20 个请求第 11 个被拦截”然后就觉得完事了。但真正上线前至少还要验证三个场景窗口滑动后能不能正常恢复。比如限速 10 次/秒你连续打 15 次前 10 成功 5 失败等 1 秒后再打应该恢复到 10 次成功。不同 IP 之间会不会互相干扰。用一个伪造的X-Forwarded-For头发送请求验证不同 IP 的计数是隔离的。换了维度后 key 是否正确。比如 METHOD 维度的接口换 IP 再试应该同样被限——因为 METHOD 维度只看接口不看 IP。把这三条验证一遍才算真正测透了这个功能。6. 写在最后几点实操心得这套方案我已经在多套系统中落地过说几个真实的体会。第一注解参数的设计需要克制。Dimension、time、unit、count这四个参数已经覆盖了绝大多数场景不需要一开始就堆上“预热时间”“冷启动比例”这些花哨概念——那是专门限流框架的事。保持注解简单业务方才会愿意用。如果参数太复杂大家宁可不用直接写在代码里硬编码。第二做好超限时的可观测性。拦截器每次拒绝请求时除了返回 429最好用日志输出一下当前的 key 和 count。比如log.warn(Rate limit exceeded. key{}, count{}, limit{}, windowKey, currentCount, rateLimit.count());这样线上出了问题比如“某个用户突然访问不了”你可以快速查看日志确认是被限速了而不是代码 bug。没有日志的限速器在排障时就像黑盒非常痛苦。第三限速阈值设置要留有冗余。很多人拍脑袋把 count 设为 100但实际上线后连续几轮活动都会误杀。建议先分析正常场景下最高峰每秒请求量是多少然后乘以 1.5~2 倍作为限速阈值。限速器是兜底保护不是精确定额工具留足余量比精确重要。第四如果你有时间可以把这个方案扩展成更完整的“接口保护”体系在注解里增加ids参数支持指定方法的多个限速规则或者配合spring-boot-starter-actuator把每个接口的限流命中次数暴露成 Prometheus 指标。Micrometer 中可以用Counter记录命中次数、用Timer记录限速器耗时这样 Grafana 面板上一目了然。最后一个很强烈的建议限速代码写完之后一定要压测不要只靠 curl 手点。用 jmeter 或 wrk 发几百个并发观察限速器在并发环境下的表现。很多线程安全的问题只有在并发下才会暴露。说实话第一次写限速器时我自己也在并发测试时发现过 synchronized 加错位置的问题。限速器这类基础组件代码量不大但正确性要求极高多花时间测试永远值得。

相关新闻

用 Obsidian 管理 AI Agent Skills:从技能包到知识网络的工程化实践

用 Obsidian 管理 AI Agent Skills:从技能包到知识网络的工程化实践

2026/9/7 19:32:17

最近圈子里都在聊 AI 的 skills 机制。Claude Code 的 skills、Codex 的 skills,还有 GitHub 上那些 superpower skills、baoyu skills 仓库,本质上都是在给 AI Agent 装上一个个"技能包":一个目录、一份 SKILL.md、几个参考脚本&a…

百亿卡券数据架构升级:OceanBase单库双擎实践与踩坑实录

百亿卡券数据架构升级:OceanBase单库双擎实践与踩坑实录

2026/9/7 19:32:17

会员日大促结束当晚,我盯着监控面板上的数据库CPU曲线没敢合眼。卡券系统的核心库在峰值时段CPU已经顶到80%以上,磁盘IO等待时不时跳红,这还是在提前做了批量发券削峰之后。运营同学一个“全员领券”的运营位,就能让券表在几秒内涌…

Spring Boot + 微信小程序社区事件处理系统实战解析

Spring Boot + 微信小程序社区事件处理系统实战解析

2026/9/7 19:32:17

做社区事件处理这种面向居民的应用,如果你打算用Spring Boot做后端、微信小程序做前端,那这基本是这几年最成熟也最稳的一类组合。我刚把手头这套社区事件处理系统从需求到上线完整跑了一遍,从最初的需求梳理、表结构设计,到后端的…

Django宠物管理与社区服务小程序:全栈实战与毕设高分解密

Django宠物管理与社区服务小程序:全栈实战与毕设高分解密

2026/9/7 20:12:19

Django宠物管理与社区服务小程序这套项目,最近在毕业设计圈里出镜率相当高。它的定位很清晰:用Django作为后端框架支撑业务逻辑,微信小程序作为前端载体承接用户交互,再配合爬虫和数据可视化模块丰富功能维度,覆盖了当…

1MB内存也能找第k小?按位分块两趟扫描算法详解

1MB内存也能找第k小?按位分块两趟扫描算法详解

2026/9/7 20:12:19

最近在洛谷刷题的时候遇到了 P15799 这道“找数”,第一眼看上去平平无奇,不就是给 n 个数找第 k 小的数嘛,排序一下甚至直接用 nth_element 都能做。但真正动手提交的时候才发现,这道题最恶心的地方根本不是算法本身,而…

MySQL深分页优化:LIMIT百万偏移量的性能陷阱与四种破解方案

MySQL深分页优化:LIMIT百万偏移量的性能陷阱与四种破解方案

2026/9/7 20:12:19

我每年处理线上MySQL请求超过两万条,但真正让我在同事面前“翻车”的,不是那些复杂的多表联查,也不是什么高深的索引调优,而是一行看起来人畜无害的SQL:SELECT * FROM table LIMIT 1000000, 10。当时我信誓旦旦地在代码…

Oracle游标清理实战:dbms_shared_pool.purge为何清不掉正在使用的游标?

Oracle游标清理实战:dbms_shared_pool.purge为何清不掉正在使用的游标?

2026/9/7 20:12:19

1. 引言:当 dbms_shared_pool.purge 遇上"清不掉"的游标 前段时间线上一个核心交易库出了个怪现象。某个 SQL 因为执行计划走偏,导致响应时间从 10ms 飙到 3 秒,DBA 团队按常规思路打算把这条 SQL 的游标从共享池里 purge 掉&#…

SpringBoot医疗保健品商城毕设实战:从业务设计到高并发扣库存

SpringBoot医疗保健品商城毕设实战:从业务设计到高并发扣库存

2026/9/7 20:12:19

春日毕业设计旺季又快到了,每年这个时候后台都能收到一堆私信,问 SpringBoot 商城类课题怎么下手。今年问得尤其多的是这个题目——医疗保健品销售系统。说实话,这个题面看起来“平平无奇”,但它恰好踩在了当下最热的两个点上&…

PowerToys Advanced Paste:AI 粘贴预览机制与稀疏包身份调试实战指南

PowerToys Advanced Paste:AI 粘贴预览机制与稀疏包身份调试实战指南

2026/9/7 20:02:18

PowerToys Advanced Paste:AI 粘贴预览机制与稀疏包身份调试实战指南 【免费下载链接】PowerToys Microsoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows 项目地址: https://gitcode.com/GitHub_Trendin…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/6 1:19:56

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/7 3:44:24

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

2026/9/7 0:01:24

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

2026/9/7 0:01:24

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

2026/9/7 0:01:24

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

远程协作的工作台整理

远程协作的工作台整理

2026/9/7 3:38:07

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/4 7:42:10

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/6 23:21:51

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…