是什么大仙风把你给刮来了🍃?
我是一名懂工业现场的后端开发工程师👨💻,目前就职于南京思通数科,主要从事 AI 视觉👀与行业数字化产品🤖的后端研发。
希望对缅甸不了解的朋友可以读一读。 从被西方世界殖民,到日本的屠杀暴行。从国家无休止的内战,到毁灭性的自然灾害。缅甸这个国家背负了一个国家所能遭受的几乎所有的不幸,甚至和旧中国有几分相似。除此以外,现在一些中国人趁着缅甸政治局势的动荡,中央对边境管辖的局限,到缅甸北部搞电信诈骗。这本不是缅甸人民的所作所为,也被扣上了一个危险国家的帽子。国内不明真相的网民们跟风吆喝,似乎把缅甸描述成了一个地狱,给它贴上了所有黑色的标签。 这就像夜晚一个村子里的恶霸逃了出来,到另一个村的边境路上坑蒙拐骗路自己原住村的居民。坑蒙拐骗得手了之后就藏起来。大家不讨论怎么铲除村里恶霸,而是对另一个村庄的所有人们恶语相向。这失智的群众,恶臭的三观,离谱的操作,荒诞的狗血情节。 缅甸人能够修好多座大金塔,每个人都能参观。仰光的大金塔的修建更是用了数十吨的黄金,塔顶的伞盖镶嵌着大量的宝石。按黄金的最近翻倍价格,如果缅甸人真如大家贴的标签那样利欲熏心,坑蒙拐骗,无法靠近,想必早大金塔被当地的窃贼逐渐磨平,甚至被出卖。作为信奉佛教人数比例最高的国家之一,缅甸人信仰的佛法,佛道,对佛经的理解,早已是毕生必须经历的修行。对佛的侮辱,甚至甚过违犯法律。军政府在当地的统治,军阀在各地的割据,可以自己改法,吸血压榨自己的人民,枪毙侮辱军阀的百姓,但是唯独不能摧毁每个人心目中的释迦牟尼。 请你不要被大数据的推送、夸张的短视频被遮住了眼睛,也别忘了去倾听那些沉默却坚定的声音。缅甸并不是只有战争、诈骗与苦难,她也有善良、虔诚与坚韧。这个国家的光亮,可能微弱,甚至你从抵达仰光机场的飞机向下俯瞰也不见几处灯火,但它的希望始终没有熄灭。每个缅甸人的内心和大家一样朴素,在乎的是自己的下一餐是否是美食,天冷了自己的孩子今天有没有受凉,商店里的牛奶有没有打折,以及明天,是不是会变得更好。 我相信不管缅甸会在未来遇到多大的困难,她总会再站起来。一个国家甚至一个民族的坚强,不在于她遭受到了多么毁灭性的打击,而是在于她总是能在最恶劣的环境下抬起头来,面向阳光,茁壮成长。
长相失败, 化妆无奈。 皮黑耐晒, 口音奇怪。 腿粗缺钙, 不会做菜。 如果被爱, 纯属意外。
📥下载MSCZ格式曲谱 📥下载PDF格式曲谱
你理解这段代码吗? do { oldseed = seed.get(); newseed = (oldseed * ...) & mask; } while (!seed.compareAndSet(oldseed, newseed)); // ← CAS 第〇层:先搞懂"变量"在多线程里的危险 假设有一个变量 seed = 100,两个线程同时想改它: 线程A:读到 seed=100 → 算出新值 200 → 写回 seed=200 线程B:读到 seed=100 → 算出新值 300 → 写回 seed=300 问题来了——两个人都读到了 100,各算各的,最后谁写得晚谁赢。A 的结果被 B 覆盖了,A 白干了,而且程序根本不知道出错了。 这就是经典的 线程安全问题。 第一层:解决办法有两条路 路线一:悲观锁(synchronized) → "我先把门锁上,别人别进来" → 安全但慢(别人要排队) 路线二:乐观锁(CAS) → "门不锁,但我写之前验一下,被人改了我就重来" → 快但可能要重试 你这段代码走的就是 路线二。 第二层:什么是 CAS? CAS = Compare And Set(比较并设置),三个参数: CAS(内存地址, 我期望的旧值, 我想写入的新值) 它做的事用大白话说: “我记得这个值是 42,如果现在还是 42,就帮我改成 77。如果不是 42 了,说明有人动过了,啥也别干,告诉我失败了。” ...
缓存三兄弟 缓存穿透 是什么:大量请求查询数据库不存在的数据。缓存没有,持续击打数据库 防范方法: 缓存空值 数据库查不到也往缓存放一个空标记(TTL短,2分钟,该TTL过期了才能进行再次数据库查询) 下一次同样的id来了直接被挡住 优点:有效拦截大量穿透请求 缺点: 恶意攻击造成大量不同的不存在的key,缓存堆满无效数据,浪费内存 延迟了数据一致性,新数据必须等缓存过期 布隆过滤器(推荐) 启动时把所有合法id放入布隆过滤器; 布隆过滤器不给进就真的没有; 优点: 没有高内存占用风险 没有数据一致性风险 缺点: 有极小误判率 增删要维护。数据更新时,要同步更新布隆过滤器 接口层限流和校验 API网关或Controller层做拦截 参数校验、限流降级 互斥锁重建 互斥锁主要解决缓存击穿(热点key过期),但是在缓存空对象场景下,如果有大量并发请求一个不存在的key,可以使用锁 每次访问让一个线程去查DB,其他线程等待 大厂的实践 网关层 校验参数格式,IP限流(挡掉脚本攻击) 过滤器层 布隆过滤器 缓存层 缓存击穿 是什么:某个被疯狂访问的热点key,因为TTL一到,失效的一瞬间海量并发请求同时miss,同时到DB重建同一个key 防范方法: 互斥锁 只让第1个miss的线程去查DB 其他线程等一下再读缓存 高一致性:线程傻傻等待 低一致性:先给旧数据,下一次来查就是新的 逻辑过期 key永不物理过期,把过期时间存在value里面 如果发现逻辑上过期了,异步开个线程重建 当前请求先返回旧值 互斥锁的实现 如何搭建出正确的锁? v1:SETNX key 1 抢锁,DEL key 释放 SETNX=“不存在才设置成功”,天然互斥。但问题:抢到锁的线程崩了、DEL 没执行 → 锁永远不释放 → 死锁。 v2:给锁加过期间 崩了也能自动过期。但关键:必须用一条原子命令 SET key value NX EX 30。 陷阱:别写成 SETNX + 再 EXPIRE 两条——如果刚 SETNX 成功、还没 EXPIRE 就崩了,又变回 v1 的死锁。“抢锁"和"设过期"必须一步做完。 ...
基础知识 TCP 三次握手 客户端 - 服务器 三次握手目的:客户端的收发没问题,服务器的收发也没问题。 第一次握手:客户端发 SYN(同步请求) 客户端:我啥也不知道,我发个 SYN 消息出去,看看有没有人接收到。 如果没人回复,我再发几次(超时重传)。 这个时刻,客户端知道的事实是: 我发出去了,但不知道有没有人收到 ❌ 我能不能收消息?不知道 ❌ 对方存不存在?不知道 ❌ 第二次握手:服务器回 SYN+ACK(同步+确认) 服务器收到了客户端的 SYN。 服务器回复 SYN+ACK:“我收到你的消息了,我也发一条试试,看看你能不能收到。” 这个时刻,服务器知道的事实是: 客户端发消息是没有问题的 ✅(因为我收到了) 服务器收消息是没有问题的 ✅(因为我收到了) 服务器不知道服务器发消息有没有问题 ❌(我发出去了,但不知道对方收到没) 服务器不知道客户端收消息有没有问题 ❌(我发出去了,但不知道对方收到没) 服务器需要发送一条 SYN+ACK,等对方回复 ACK 来确认“我能发、对方能收” 当客户端收到 SYN+ACK 后,客户端可以确定以下事实: 我发消息是没有问题的 ✅(我发的 SYN 被收到了) 我收消息也是没有问题的 ✅(我收到了对方的回复) 服务器收消息是没有问题的 ✅(对方收到了我的 SYN) 服务器发消息也是没有问题的 ✅(我收到了对方的 SYN+ACK) ✅ 客户端这边已经确认全双工通信没问题了! 但是服务器他还有顾虑(不知道自己能不能发、不知道我能不能收),我再发个 ACK 给他吧 第三次握手:客户端发 ACK(确认) 客户端收到 SYN+ACK 后,回复一个 ACK:“我收到你的 SYN+ACK 了,我这边收发都没问题,你也可以放心了。” 当服务器收到这个 ACK 后,服务器可以确定以下事实: ...
正常 JWT 请求流程 sequenceDiagram participant User as 用户 participant Frontend as 前端(浏览器/App) participant Auth as 认证服务器 participant Backend as 后端API服务器 Note over User,Backend: 1. 登录阶段 User->>Frontend: 输入用户名/密码 Frontend->>Auth: POST /login (凭证) Auth->>Auth: 验证凭证 Auth-->>Frontend: 返回 access_token + refresh_token Frontend->>Frontend: 存储token(内存/localStorage) Frontend-->>User: 登录成功 Note over User,Backend: 2. 正常请求阶段 User->>Frontend: 请求受保护资源 Frontend->>Backend: GET /api/resourceAuthorization: Bearer access_token Backend->>Backend: 验证access_token签名和过期时间 Backend-->>Frontend: 返回请求的资源 Frontend-->>User: 展示数据 Note over User,Backend: 3. Access Token过期 User->>Frontend: 继续请求 Frontend->>Backend: GET /api/resourceAuthorization: Bearer access_token(已过期) Backend-->>Frontend: 401 Unauthorized (token过期) Frontend->>Frontend: 检测到401,触发刷新逻辑 Note over Frontend,Auth: 4. 刷新Token阶段 Frontend->>Auth: POST /refreshrefresh_token Auth->>Auth: 验证refresh_token Auth-->>Frontend: 返回新的 access_token(可选的新refresh_token) Frontend->>Frontend: 更新存储的access_token Frontend->>Backend: 重试原请求(新access_token) Backend-->>Frontend: 返回请求的资源 Frontend-->>User: 展示数据 为什么不能只靠JWT refresh token 发出去后后端不保存,会变成 不可控的长期通行证 无法主动踢人下线 无法注销单点设备 无法判断 token 是否被盗用(攻击者拿到了 refresh token 可以一直刷新 access token) 无法实现会话管理 使用 refresh tokens 表后的流程图 sequenceDiagram participant User as 用户 participant Frontend as 前端 participant Auth as 认证服务器 participant DB as 数据库(refresh_tokens表) participant Backend as 后端API服务器 Note over User,DB: 1. 登录阶段(签发token + 入库) User->>Frontend: 输入用户名/密码 Frontend->>Auth: POST /login Auth->>Auth: 验证凭证 Auth->>DB: 生成唯一refresh_token_idINSERT INTO refresh_tokens(user_id, token_hash, expires_at, device_info, ip_address, revoked) DB-->>Auth: 插入成功 Auth->>Auth: 生成access_token + refresh_token(refresh_token包含id引用) Auth-->>Frontend: 返回access_token + refresh_token Frontend->>Frontend: 存储token Frontend-->>User: 登录成功 Note over User,Backend: 2. 正常请求(与之前相同) User->>Frontend: 请求资源 Frontend->>Backend: GET /api/resourceAuthorization: Bearer access_token Backend->>Backend: 验证access_token Backend-->>Frontend: 返回资源 Note over Frontend,DB: 3. Access Token过期 → 刷新 Frontend->>Backend: GET /api/resource (access_token过期) Backend-->>Frontend: 401 Unauthorized Frontend->>Auth: POST /refreshrefresh_token Note over Auth,DB: 4. 刷新验证(多步校验) Auth->>Auth: 解析refresh_token,提取token_id Auth->>DB: SELECT * FROM refresh_tokensWHERE id = token_id DB-->>Auth: 返回记录 Auth->>Auth: 校验清单: Note over Auth: ✅ token_hash是否匹配✅ 是否过期 (expires_at > now())✅ 是否被撤销 (revoked = false)✅ 用户是否仍有效✅ 设备信息是否一致(可选) alt 校验全部通过 Auth->>DB: UPDATE refresh_tokensSET last_used_at = now(), last_used_ip = current_ipWHERE id = token_id Auth->>Auth: 生成新access_token(可选:延长refresh_token有效期) Auth-->>Frontend: 返回新access_token Frontend->>Frontend: 更新access_token Frontend->>Backend: 重试原请求(新token) Backend-->>Frontend: 返回资源 else 校验失败 Auth-->>Frontend: 401/403 (刷新失败) Frontend->>Frontend: 清除所有token Frontend->>User: 跳转登录页 end Note over User,DB: 5. 主动登出 User->>Frontend: 点击登出 Frontend->>Auth: POST /logoutrefresh_token Auth->>DB: UPDATE refresh_tokensSET revoked = trueWHERE id = token_id Auth-->>Frontend: 登出成功 Frontend->>Frontend: 清除本地token Frontend-->>User: 已登出 Note over User,DB: 6. 安全场景:密码修改 User->>Frontend: 修改密码 Frontend->>Auth: POST /change-password Auth->>DB: UPDATE refresh_tokensSET revoked = trueWHERE user_id = current_user_id Note over DB: 撤销该用户所有refresh_token(强制所有设备重新登录) Auth-->>Frontend: 密码修改成功 Frontend->>Frontend: 清除本地token Frontend-->>User: 请重新登录
数据来源:Spring Cloud Alibaba 官方 Wiki SpringBoot 3.3.4 之后 Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version 2025.1.0.0 2025.1.0 4.0.0 Spring Cloud Alibaba Version Spring Cloud Version Spring Boot Version 2025.0.0.0 2025.0.0 3.5.0 组件版本关系 每个 Spring Cloud Alibaba 版本及其自身所适配的各组件对应版本如下表所示: Spring Cloud Alibaba Version Sentinel Version Nacos Version RocketMQ Version SchedulerX Version Seata Version 2025.1.0.0 1.8.9 3.1.1 5.3.1 1.13.3 2.5.0 2025.0.0.0 1.8.9 3.0.3 5.3.1 1.13.1 2.5.0 依赖引入 <dependencyManagement> <dependencies> <!-- Spring Boot 版本统一管理 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.5.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud 官方版本管理 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2025.0.0</version> <type>pom</type> <scope>import</scope> </dependency> <!-- Spring Cloud Alibaba 阿里生态统一版本 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2025.0.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> SpringBoot 3.3.4 及以前 📌 新版本(Calendar Versioning,推荐使用) Spring Cloud Alibaba Spring Cloud Spring Boot 2023.0.3.2 2023.0.3 3.3.4 2023.0.3.0 2023.0.3 3.3.4 2023.0.1.0 2023.0.1 3.2.4 2023.0.0.0-RC1 2023.0.0 3.2.0 2022.0.0.0 2022.0.0 3.0.2 2022.0.0.0-RC2 2022.0.0-RC2 3.0.2 2022.0.0.0-RC1 2022.0.0-RC1 3.0.0 2021.0.6.2 2021.0.9 2.7.18 2021.0.6.0 2021.0.9 2.7.18 2021.0.5.0 2021.0.5 2.6.13 2021.0.4.0 2021.0.4 2.6.11 2021.0.1.0 2021.0.1 2.6.3 2021.1 2020.0.1 2.4.2 📌 旧版本(RELEASE 命名) Spring Cloud Alibaba Spring Cloud Spring Boot 2.2.10.RELEASE Hoxton.SR12 2.3.12.RELEASE 2.2.9.RELEASE Hoxton.SR12 2.3.12.RELEASE 2.2.8.RELEASE Hoxton.SR12 2.3.12.RELEASE 2.2.7.RELEASE Hoxton.SR12 2.3.12.RELEASE 2.2.6.RELEASE Hoxton.SR9 2.3.2.RELEASE 2.2.1.RELEASE Hoxton.SR3 2.2.5.RELEASE 2.2.0.RELEASE Hoxton.RELEASE 2.2.X.RELEASE 2.1.4.RELEASE Greenwich.SR6 2.1.13.RELEASE 2.1.2.RELEASE Greenwich 2.1.X.RELEASE 2.0.4.RELEASE ⛔ Finchley 2.0.X.RELEASE 1.5.1.RELEASE ⛔ Edgware 1.5.X.RELEASE ⛔ 表示已停止维护 ...
技术栈:Spring Boot 3 · Spring Cloud Alibaba · OpenFeign · JWT · Gateway · Nacos 如何使用这份文档 面试讲项目,最忌讳从代码细节讲起。正确顺序是: 先讲它解决什么问题 → 再讲整体架构 → 然后按请求流程串一遍 → 最后应对深挖。 本文档按这个顺序组织。带 🎤 标记的引用框,是可以基本照着说的话术。 一、一句话概述(开场用) 🎤 我做了一套基于 Spring Cloud Alibaba 的微服务认证系统。它把「认证」这件事拆成三个角色:auth 服务负责签发令牌、网关负责统一校验令牌、各业务服务通过请求头拿到用户身份。整体用 JWT 做无状态认证,服务之间用 OpenFeign 通信,靠 Nacos 做服务发现。 关键词锚点(面试官会顺着这些追问,要准备好):无状态 JWT、网关统一鉴权、OpenFeign 远程调用、Nacos 服务发现、BCrypt 密码加密、职责分离。 二、它解决什么问题(讲动机) 背景:系统是微服务架构,有多个独立服务(auth、user-service、archive-service 等)。如果每个服务各自做一遍登录校验,会有两个问题: 重复:每个服务都写一遍鉴权逻辑,改规则要改很多处。 有状态难扩展:传统 session 存在单台服务器内存里,多实例之间不共享,扩容困难。 解决思路:「网关统一安检 + JWT 无状态令牌」。所有请求先经过网关校验令牌,业务服务不再重复鉴权;令牌本身自包含用户信息,验证只需用密钥本地验签,不依赖服务端存储。 🎤 类比机场:网关是安检口,严格查一次护照(验签);过了安检给你贴个写着身份的手环(请求头里的用户 id);后面登机口、免税店(各业务服务)只看手环,不再查护照。验签只做一次,业务服务零负担。 三、整体架构(讲骨架) 模块划分:项目是 Maven 多模块微服务,核心模块及职责如下。 模块 类型 职责 gateway 网关 所有请求入口;统一校验 JWT;按路由转发到后端服务 auth 认证服务 登录、注册;验密码;签发 JWT。自己不连数据库 user-service 业务服务 管理用户数据(tb_user 表的增删改查) archive-service 业务服务 档案业务;从请求头读取当前用户身份 api 契约模块 存放各服务的 Feign 接口声明 + 配套 DTO(被各服务依赖) common 基建底座 统一返回 Result、全局配置、ThreadLocal 用户上下文等 一个关键设计——auth 与 user-service 分离:auth 只管「认证动作」(验密码、发令牌),不碰数据库;它要用户数据时,通过 OpenFeign 远程调用 user-service 去查。user-service 只管「用户数据」,不掺和登录逻辑。 ...
每个写过后端的人,迟早都要跟"登录"打一场硬仗。它看起来是个小功能——一个用户名、一个密码、一个按钮——但凡是真正做过的人都知道,这是整个系统里坑最深、改起来最痛、出事最致命的一块。 它麻烦,不是因为技术有多难,而是因为它卡在三方利益的正中间:用户想省事,产品想拉新,安全想严防死守。这三件事天然打架。你每往其中一边挪一寸,另外两边就开始喊疼。 这篇文章想把这件"麻烦事"摊开讲清楚:主流厂商现在都怎么做、每种做法烂在哪、未来可能怎么变,以及如果你今天就要动手,应该怎么落地。 一、先看战场:主流厂商现在都在用什么 如果你今天注册任何一个稍微正经点的 App,会发现登录方式早就不是"账号 + 密码"一条路了。现在的主流玩法大致可以分成五类。 1. 手机号 + 短信验证码 这是中国互联网的绝对统治者。点开微博、抖音、美团、拼多多,第一屏几乎清一色是"输入手机号 → 收验证码 → 进"。 它能赢,是因为它一次性解决了三个问题:手机号天然实名(运营商帮你做了 KYC)、不用记密码、注册和登录是同一个动作。对产品经理来说,这意味着注册转化率极高——少一个"设置密码"的步骤,就少漏一批用户。 代价是:你把身份的命脉,交给了运营商和短信通道。 2. 邮箱 + 密码 这是全球(尤其欧美)的默认范式。GitHub、Google、Notion、几乎所有 SaaS 都以邮箱为账号主体。 邮箱的好处是全球通用、跨国可用、不依赖运营商、可以承载找回流程。一个邮箱地址几乎就是你在互联网上的"主键"。坏处后面细说。 3. 第三方登录(OAuth / 社交登录) “用微信登录"“Sign in with Google"“Continue with Apple”——本质上是把身份认证外包给一个你已经信任的大平台。 它的杀手锏是:用户一次都不用输。点一下,授权,进去了。对开发者来说,你还省掉了自己存密码的风险(密码根本不经过你的服务器)。代价是你被绑在了平台生态上,而且用户的账号体系实际上不在你手里。 4. 魔法链接(Magic Link) 无密码的一种:你输入邮箱,系统给你发一封带一次性链接的邮件,点开即登录。Slack、Medium 早期都靠这个。 它把"记密码"这件事彻底删掉了,安全模型简单清晰。但它把整个登录体验的流畅度,押在了"邮件能不能秒到"上——而邮件这玩意,慢起来能慢到你怀疑人生。 5. Passkey / WebAuthn(通行密钥) 这是最新、也是各大厂商正在猛推的方向。Apple、Google、Microsoft 已经全面支持。它基于公私钥密码学,用你设备上的生物识别(指纹、Face ID)来完成认证,服务器端根本不存任何可被盗的密码。 这是目前公认"理论上最优"的方案,我们最后单独讲。 下面这张图,把这五种方式按"用户省心程度"和"安全强度"两个维度摆一摆,你能直观看到它们各自的生态位: graph TD A["登录方式光谱"] --> B["短信验证码省心高 / 安全中"] A --> C["邮箱密码省心低 / 安全中"] A --> D["第三方登录省心高 / 安全中高"] A --> E["魔法链接省心中 / 安全中"] A --> F["Passkey省心高 / 安全高"] B --> B1["依赖运营商换号即失联"] C --> C1["密码要记容易被撞库"] D --> D1["绑死大平台账号不在自己手里"] E --> E1["押注邮件时效慢到怀疑人生"] F --> F1["体验最好但迁移设备是痛点"] style A fill:#1a1a2e,color:#fff style F fill:#16213e,color:#7fff7f 二、麻烦的本质:每一种方式都在某处偷偷塌方 上面每种方式听起来都还行,但它们都有一个藏在水面下的塌方点。用户平时感觉不到,一旦踩中,体验直接归零。 ...