发动态
图文
列表
一、为什么要“优雅”?产品一句话: “凡哥,接口明天上线,支持 10w 并发,数据脱敏,不能丢单,不能重复,还要安全。” 优雅不是装,是为了让自己少加班、少背锅、少掉发。 今天晓凡就把压箱底的东西掏出来,手把手带你撸一套能扛生产的模板。为方便阅读,晓凡以Java代码为例给出“核心代码 + 使用姿势”,全部亲测可直接使用。二、项目骨架(Spring Boot 3.x) demo-api ├── src/main/java/com/example/demo │ ├── config // 配置:限流、加解密、日志等 │ ├── annotation // 自定义注解(幂等、日志、脱敏) │ ├── aspect // 切面统一干活 │ ├── interceptor // 拦截器(签名、白名单) │ ├── common // 统一返回、异常、常量 │ ├── controller // 对外暴露 │ ├── service │ └── DemoApplication.java └── pom.xml 三、 签名(防篡改)对外提供的接口要做签名认证,认证不通过的请求不允许访问接口、提供服务思路 “时间戳 + 随机串 + 业务参数”排好序,最后 APP_SECRET 拼后面,SHA256 一下。 前后端、第三方都统一,拒绝吵架。工具类 public class SignUtil { /** * 生成签名 * @param map 除 sign 外的所有参数 * @param secret 分配给你的私钥 */ public static String sign(Map<String, String> map, String secret) { // 1. 参数名升序排列 Map<String, String> tree = new TreeMap<>(map); // 2. 拼成 k=v&k=v String join = tree.entrySet().stream() .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining("&")); // 3. 最后拼密钥 String raw = join + "&key=" + secret; // 4. SHA256 return DigestUtils.sha256Hex(raw).toUpperCase(); } /** 验签:直接比对即可 */ public static boolean verify(Map<String, String> map, String secret, String requestSign) { return sign(map, secret).equals(requestSign); } } 拦截器统一验签 @Component public class SignInterceptor implements HandlerInterceptor { @Value("${sign.secret}") private String secret; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 只拦截接口 if (!(handler instanceof HandlerMethod)) return true; Map<String, String> params = Maps.newHashMap(); request.getParameterMap().forEach((k, v) -> params.put(k, v[0])); String sign = params.remove("sign"); // 签名不参与计算 if (!SignUtil.verify(params, secret, sign)) { throw new BizException("签名错误"); } return true; } } ​坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~​四、 加密(防泄露)敏感数据在网络传输过程中都应该加密处理思路 AES 对称加密,密钥放配置中心,支持一键开关。 只对敏感字段加密,别一上来全包加密,排查日志想打人。AES 工具 public class AesUtil { private static final String ALG = "AES/CBC/PKCS5Padding"; // 16 位 private static final String KEY = "1234567890abcdef"; private static final String IV = "abcdef1234567890"; public static String encrypt(String src) { try { Cipher cipher = Cipher.getInstance(ALG); SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); return Base64.getEncoder().encodeToString(cipher.doFinal(src.getBytes())); } catch (Exception e) { throw new RuntimeException("加密失败", e); } } public static String decrypt(String src) { try { Cipher cipher = Cipher.getInstance(ALG); SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); return new String(cipher.doFinal(Base64.getDecoder().decode(src))); } catch (Exception e) { throw new RuntimeException("解密失败", e); } } } 五、 IP 白名单限制请求的IP,增加IP白名单,一般在网关层处理配置 white: ips: 127.0.0.1,10.0.0.0/8,192.168.0.0/16 拦截器 @Component public class WhiteListInterceptor implements HandlerInterceptor { @Value("#{'${white.ips}'.split(',')}") private List<String> allowList; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String ip = IpUtil.getIp(request); boolean ok = allowList.stream() .anyMatch(rule -> IpUtil.match(ip, rule)); if (!ok) throw new BizException("IP 不允许访问"); return true; } } 六、 限流(Sentinel 注解版)尤其对外提供的接口,无法保障调用频率,应该做限流处理,保障接口服务正常的提供服务依赖 <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-boot-starter</artifactId> <version>1.8.6</version> </dependency> 配置 spring: application: name: demo-api sentinel: transport: dashboard: localhost:8080 使用姿势 @GetMapping("/order/{id}") @SentinelResource(value = "getOrder", blockHandler = "getOrderBlock") public Result<OrderVO> getOrder(@PathVariable Long id) { return Result.success(orderService.get(id)); } // 限流兜底 public Result<OrderVO> getOrderBlock(Long id, BlockException e) { return Result.fail("访问太频繁,稍后再试"); } 七、 参数校验(JSR303 + 分组)即使前端做了非空,规范性校验,服务端参数校验任然是必不可少的DTO public class OrderCreateDTO { @NotNull(message = "用户 ID 不能为空") private Long userId; @NotEmpty(message = "商品列表不能为空") @Size(max = 20, message = "一次最多买 20 件") private List<Item> items; @Valid @NotNull private PayInfo payInfo; @Data public static class PayInfo { @Min(value = 1, message = "金额必须大于 0") private Integer amount; } } 分组接口 java 体验AI代码助手 代码解读 复制代码 public interface Create {} Controller @PostMapping("/order") public Result<Long> create(@RequestBody @Validated(Create.class) OrderCreateDTO dto) { Long orderId = orderService.create(dto); return Result.success(orderId); } 八、 统一返回值提供统一的返回结果,不应该返回值五花八门 @Data @AllArgsConstructor @NoArgsConstructor public class Result<T> implements Serializable { private int code; private String msg; private T data; public static <T> Result<T> success(T data) { return new Result<>(200, "success", data); } public static <T> Result<T> fail(String msg) { return new Result<>(500, msg, null); } /** 返回 200 但提示业务失败 */ public static <T> Result<T> bizFail(int code, String msg) { return new Result<>(code, msg, null); } } 九、 统一异常处理系统报错信息需要提供友好的提示,避免暴露出SQL异常的信息给调用方和客户端。 @RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** 业务异常 */ @ExceptionHandler(BizException.class) public Result<Void> handle(BizException e) { log.warn("业务异常:{}", e.getMessage()); return Result.bizFail(e.getCode(), e.getMessage()); } /** 参数校验失败 */ @ExceptionHandler(MethodArgumentNotValidException.class) public Result<Void> handleValid(MethodArgumentNotValidException e) { String msg = e.getBindingResult() .getFieldErrors() .stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.joining(",")); return Result.fail(msg); } /** 兜底 */ @ExceptionHandler(Exception.class) public Result<Void> handleAll(Exception e) { log.error("系统异常", e); return Result.fail("服务器开小差"); } } 十、 请求日志(切面 + 注解)记录请求的入参日志和返回日志,出问题时方便快速定位。也给运维人员提供了方便注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ApiLog {} 切面 @Aspect @Component public class LogAspect { private static final Logger log = LoggerFactory.getLogger("api.log"); @Around("@annotation(apiLog)") public Object around(ProceedingJoinPoint p, ApiLog apiLog) throws Throwable { long start = System.currentTimeMillis(); ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest req = attr.getRequest(); String uri = req.getRequestURI(); String params = JSON.toJSONString(p.getArgs()); Object result; try { result = p.proceed(); } catch (Exception e) { log.error("【{}】params={} error={}", uri, params, e.getMessage()); throw e; } finally { long cost = System.currentTimeMillis() - start; log.info("【{}】params={} cost={}ms", uri, params, cost); } return result; } } 用法 @ApiLog @PostMapping("/order") public Result<Long> create(...) {} 十一、幂等设计(Token & 分布式锁双保险)对于一些涉及到数据一致性的接口一定要做好幂等设计,以防数据出现重复问题思路下单前先申请一个幂等 Token(存在 Redis,5 分钟失效)。下单时带着 Token,后端用 Lua 脚本“判断存在并删除”,原子性保证只能用一次。对并发极高场景,再补一层分布式锁(Redisson)。代码 @Service public class IdempotentService { @Resource private StringRedisTemplate redis; /** 申请 Token */ public String createToken() { String token = UUID.fastUUID().toString(); redis.opsForValue().set("token:" + token, "1", Duration.ofMinutes(5)); return token; } /** 验证并删除 */ public boolean checkToken(String token) { String key = "token:" + token; // 原子删除成功才算用过 return Boolean.TRUE.equals(redis.delete(key)); } } Controller @GetMapping("/token") public Result<String> getToken() { return Result.success(idempotentService.createToken()); } @PostMapping("/order") @ApiLog public Result<Long> create(@RequestBody @Valid OrderCreateDTO dto, @RequestHeader("Idempotent-Token") String token) { if (!idempotentService.checkToken(token)) { throw new BizException("请勿重复提交"); } Long orderId = orderService.create(dto); return Result.success(orderId); } 十二、限制记录条数(分页 + SQL 保护)对于批量数据接口,一定要限制返回的记录条数,不让会造成恶意攻击导致服务器宕机。MyBatis-Plus 分页插件 @Configuration public class MybatisConfig { @Bean public MybatisPlusInterceptor interceptor() { MybatisPlusInterceptor i = new MybatisPlusInterceptor(); i.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return i; } } Service public Page<OrderVO> list(OrderListDTO dto) { // 前端不传默认 10 条,最多 200 long size = Math.min(dto.getPageSize(), 200); Page<Order> page = new Page<>(dto.getPageNo(), size); LambdaQueryWrapper<Order> w = Wrappers.lambdaQuery(); if (StrUtil.isNotBlank(dto.getUserName())) { w.like(Order::getUserName, dto.getUserName()); } Page<Order> po = orderMapper.selectPage(page, w); return po.convert(o -> BeanUtil.copyProperties(o, OrderVO.class)); } 十三、 压测(JMeter + 自带脚本)上线前,务必要对API接口进行压力测试,知道各个接口的qps情况。以便我们能够更好的预估,需要部署多少服务节点,对于API接口的稳定性至关重要。起服务: java -jar -Xms1g -Xmx1g demo-api.jarJMeter 线程组: 500 线程、Ramp-up 10s、循环 20。观测:Sentinel 控制台看 QPS、RTtop -H 看 CPUarthas 火焰图找慢方法调优:限流阈值 = 压测 80% 最高水位发现慢 SQL 加索引热点数据加本地缓存(Caffeine)十四、异步处理如果同步处理业务,耗时会非常长。这种情况下,为了提升API接口性能,我们可以改为异步处理下单成功后,发 MQ 异步发短信/扣库存,接口 RT 直接降一半。 @Async("asyncExecutor") // 自定义线程池 public void sendSmsAsync(Long userId, String content) { smsService.send(userId, content); } 十五、数据脱敏业务中对与用户的敏感数据,如密码等需要进行脱敏处理返回前统一用 Jackson 序列化过滤器,字段加注解就行,代码零侵入。 @JsonSerialize(using = SensitiveSerializer.class) @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Sensitive { SensitiveType type(); } public enum SensitiveType { PHONE, ID_CARD, BANK_CARD } public class SensitiveSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator g, SerializerProvider p) throws IOException { if (StrUtil.isBlank(value)) { g.writeString(value); return; } g.writeString(DesensitizeUtil.desPhone(value)); } } 十六、完整的接口文档(Knife4j)提供在线接口文档,既方便开发调试接口,也方便运维人员排查错误依赖 <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-spring-boot-starter</artifactId> <version>4.1.0</version> </dependency> 配置 knife4j: enable: true setting: language: zh_cn 启动后访问 http://localhost:8080/doc.html 支持在线调试、导出 PDF、Word。十七、小结接口开发就像炒菜:签名、加密是“食材保鲜”限流、幂等是“火候掌控”日志、文档是“摆盘拍照”每道工序做到位,才能端到桌上“色香味”俱全。 上面 13 段核心代码,直接粘过去就能跑,跑通后再按业务微调,基本能扛 90% 的生产场景。 祝你在领导问起接口怎么样了?的时候,可以淡淡来一句: “接口已经准备好了,压测报告发群里了。”——转载自:程序员晓凡
接口开发,咱得整得“优雅”点
开源硬件平台
嘉立创纸盒
先说结论:前端不会凉,但“只会几个框架 API”的前端,确实越来越难混 这两年“前端要凉了”“全栈替代前端”的声音此起彼伏,本质是门槛重新洗牌:简单 CRUD、纯样式开发被低代码、模板代码和 AI 模型快速蚕食;复杂业务、工程体系、跨端体验、AI 能力集成,反而需要更强的前端工程师去撑住。如果你对“前端的尽头是跑路转管理”已经开始迷茫,那这篇就是给你看的:别再死磕框架版本号,该更新的是你的技术路线图。一、先搞清楚:2025 的前端到底在变什么?框架红海:从“会用”到“用得值”React、Vue、Svelte、Solid、Qwik、Next、Nuxt……Meta Framework 一大堆,远远超过岗位需求。 现在企业选型更关注:生态成熟度(如 Next.js 的 SSR/SSG 能力)框架在应用生命周期中的角色(渲染策略、数据流转、SEO、部署)趋势:框架 Meta 化(Next.js、Nuxt)将路由、数据获取、缓存策略整体纳入规范;约定优于配置,不再是“一个前端库”,而是“一套完整解决方案”。以前是“你会 Vue/React 就能干活”,现在是“你要理解框架在整个应用中的角色”。工具有 AI,开发方式也在变AI 工具(如 Cursor、GitHub Copilot X)可以显著提速,甚至替代重复劳动。 真正拉开差距的变成了:你能给 AI 写出清晰、可实现的需求描述(Prompt);你能判断 AI 生成代码的质量、潜在风险、性能问题;你能基于生成结果做出合理抽象和重构。AI 不是来抢饭碗,而是逼你从“码农”进化成“架构和决策的人”。业务侧:前端不再是“画界面”,而是“做体验 + 做增长”B 端产品:交互工程师 + 低代码拼装师 + 复杂表单处理专家;C 端产品:与产品运营深度捆绑,懂 A/B 测试、埋点、Funnel 分析、广告投放链路;跨平台:Web + 小程序 + App(RN/Flutter/WebView)混合形态成为常态。那些还在喊“切图仔优化 padding”的岗位确实在消失,但对“懂业务、有数据意识、能搭全链路体验”的前端需求更高。​坑位技术大厂,前端-后端-测试,新一线和一二线城市等地均有坑位,感兴趣可以试试。待遇和稳定性都不错~​二、别再死磕框架 API:2025 的前端核心能力长什么样?基石能力:Web 原生三件套,得真的吃透重点不是“会用”,而是理解底层原理:JS:事件循环、原型链、Promise 执行模型、ESM 模块化;浏览器:渲染流程(DOM/CSSOM/布局/绘制/合成)、HTTP/2/3、安全防护(XSS/CSRF)。这块扎实了,你在任何框架下都不会慌,也更能看懂“框架为什么这么设计”。工程能力:从“会用脚手架”到“能看懂和调整工程栈”Vite、Rspack、Turbopack 等工具让工程构建从“黑魔法”变成“可组合拼装件”。 你需要:看懂项目的构建配置(Vite/Webpack/Rspack 任意一种);理解打包拆分、动态加载、CI/CD 流程;能排查构建问题(路径解析、依赖冲突)。如果你在团队里能主动做这些事,别人对你的“级别判断”会明显不一样。跨端和运行时:不只会“写 Web 页”2025 年前端视角的关键方向:小程序/多端框架(Taro、Uni-app);混合方案(RN/Flutter/WebView 通信机制);桌面端(Electron、Tauri)。建议:至少深耕一个“跨端主战场”(如 Web + 小程序 或 Web + Flutter)。数据和状态:从“会用 Vuex/Redux”到“能设计状态模型”现代前端复杂度 70% 在“数据和状态管理”。 进阶点在于:设计合理的数据模型(本地 UI 状态 vs 服务端真相);学会用 Query 库、State Machine 解耦状态与视图。当你能把“状态设计清楚”,你在复杂业务团队里会非常吃香。性能、稳定性、可观测性:高级前端的硬指标你需要系统性回答问题,而不是“瞎猜”:性能优化:首屏加载(资源拆分、CDN)、运行时优化(减少重排、虚拟列表);稳定性:错误采集、日志上报、灰度发布;工具:Lighthouse、Web Vitals、Session Replay。这块做得好的人往往是技术骨干,且很难被低代码或 AI 直接替代。AI 时代的前端:不是“写 AI”,而是“让 AI 真正跑进产品”你需要驾驭:基础能力:调用 AI 平台 API(流式返回处理、增量渲染);产品思维:哪些场景适合 AI(智能搜索、文档问答);如何做权限控制、错误兜底。三、路线图别再按“框架学习顺序”排了,按角色来选初中级:从“会用”到“能独立负责一个功能”目标:独立完成中等复杂度模块(登录、权限、表单、列表分页)。建议路线:夯实 JS + 浏览器基础;选择 React/Vue + Next/Nuxt 做完整项目;搭建 eslint + prettier + git hooks 的开发习惯。进阶:从“功能前端”到“工程前端 + 业务前端”目标:优化项目、推进基础设施、给后端/产品提技术方案。建议路线:深入构建工具(Webpack/Vite);主导一次性能优化或埋点方案;引入 AI 能力(如智能搜索、工单回复建议)。高级/资深:从“高级前端”到“前端技术负责人”目标:设计技术体系、推动长期价值。建议路线:明确团队技术栈(框架、状态管理、打包策略);主导跨部门项目、建立知识分享机制;评估 AI/低代码/新框架的引入价值。四、2025 年不要再犯的几个错误只跟着热点学框架,不做项目和抽象选一个主战场 + 一个备胎(React+Next.js,Vue+Nuxt.js),用它们做 2~3 个完整项目。完全忽略业务,沉迷写“优雅代码”把重构和业务迭代绑一起,而不是搞“纯技术重构”。对 AI 持敌视和逃避态度把重复劳动交给 AI,把时间投到架构设计、业务抽象上。把“管理”当成唯一出路做前端架构、性能优化平台、低代码平台的技术专家,薪资和自由度不输管理岗。五、一个现实点的建议:给自己的 2025 做个“年度规划”Q1:选定主技术栈(React+Next 或 Vue+Nuxt);做一个完整小项目(登录、权限、列表/详情、SSR、部署)。Q2:深入工程化方向(优化打包体积、搭建监控埋点系统)。Q3:选一个业务场景引入 AI 或配置化能力(如智能搜索、低代码表单)。Q4:输出和沉淀(写 3~5 篇技术文章、踩坑复盘)。最后:别问前端凉没凉,先问问自己“是不是还停在 2018 年的玩法”如果你还把“熟练掌握 Vue/React”当成简历亮点,那确实会焦虑;但如果你能说清楚:在复杂项目里主导过哪些工程优化;如何把业务抽象成可复用的组件/平台;如何在产品里融入 AI/多端/数据驱动; 那么,在 2025 年的前端市场,你不仅不会“凉”,反而会成为别人眼中的“稀缺”。别再死磕框架了,更新你的技术路线图,从“写页面的人”变成“打造体验和平台的人”。这才是 2025 年前端真正的进化方向。——转载自:纯爱掌门人
别再死磕框架了!你的技术路线图该更新了
开源硬件平台
社区数据
今日帖子
-
今日互动量
-
在线人数
-
帖子总量
-
用户总量
-
推荐话题 换一批
#DIY设计#
#嘉立创PCB#
#嘉立创3D打印#
#嘉立创免费3D打印#
#立创开源六周年#
#高校动态#
#技术干货#
#嘉立创纸盒#
查看更多热门话题
功能讨论
()
主题
打赏记录
服务时间:周一至周六 9::00-18:00 · 联系地址:中国·深圳(福田区商报路奥林匹克大厦27楼) · 媒体沟通:pr@jlc.com · 集团介绍
移动社区