从 Java/Go 到 React:一个后端程序员的 TypeScript 受难与破壁实录(附 Flutter 做中间翻译器)

从 Java/Go 到 React,顺便拉 Flutter 垫背 一、前言:为什么后端程序员学 React 想骂人? 某后端组有天接了个需求:用 React + TypeScript 写个管理后台。组里人均三年 Spring Boot 或 Go Gin 经验,前端认知停留在 jQuery 版本。一开始想的是"TS 不就是带类型的 JS 嘛,有类型就不慌"——结果打开第一个 React 教程就傻了:函数组件、Hooks、闭包陷阱、依赖数组、JSX 里嵌逻辑……这哪是前端,这分明是另一个世界。 后端思维高度固化:类继承、接口实现、线程阻塞、强类型、反射——这些概念在 Spring 和 Go 里是护城河,在 React 里是全都没用的东西。类?函数组件不需要。接口?TS 的结构化类型不需要显式 implements。线程?JS 单线程事件循环,根本没有多线程。 Flutter 被拉进来做"中间翻译器":Dart 语法像 Java,但框架思维像 React。先写 Flutter 再写 React,会发现很多映射:Widget 树 >= 虚拟 DOM,setState >= useState,initState/dispose >= useEffect。把 Flutter 当"桥梁",Java/Go 当"起点",React 就没那么陌生。 为什么不直接对比?因为 Java 和 TS 差距太大——标称类型 vs 结构类型,多线程阻塞 vs 单线程事件循环。中间垫一个 Flutter(标称类型 + 单线程异步 + 声明式 UI),过渡就平滑了。 ...

十月 11, 2023 · 16 分钟 · 3256 字 · yaomingye

今日日报:Redis Lua 原子脚本与三段式库存模型——从商品表拆出独立库存微服务

拆库存服务:库存不是商品的附属品 某天盯着商品表的字段列表,发现 quantity、remain_quantity、sale_count 这三个东西怎么看怎么和 name、price、cover_url 不是一家人。name 改了不频繁,库存每秒都在扣——高频写和低频读挤在同一行,互相锁着玩。 决定拆。新建了一个 mall-inventory 微服务,独立数据库 cloud_mall_inventory,三张表:inventory(主库存)、inventory_batch(批次追踪)、inventory_log(变动流水)。 最头疼的问题:扣库存的一致性 库存扣减最怕两个事:超卖和半截崩溃。 第一个做法是两条 Redis 命令: redisUtil.increment(key, -quantity); // 扣 available redisUtil.increment(frozenKey, quantity); // 加 frozen 问题很明显——第一条执行完、第二条还没跑的时候,机器崩了怎么办?available 扣了但 frozen 没加,库存"凭空消失"了。 解法是 Lua 脚本,把两条操作打包发给 Redis: local qty = -tonumber(ARGV[1]) local avail = redis.call('INCRBY', KEYS[1], qty) if avail < 0 then redis.call('INCRBY', KEYS[1], -qty) return -1 end redis.call('INCRBY', KEYS[2], -qty) return avail - qty Redis 内部保证整个脚本一次性原子执行,不存在中间状态。Spring Data Redis 的 StringRedisTemplate.execute(script, keys, args) 直接调用就行。 三段式库存模型 完整链路改成了"冻结 → 确定 → 释放": 下单 → frozen +1, available -1(冻结) ├─ 支付成功 → frozen -1, sale_count +1(确认扣减) └─ 超时/取消 → frozen -1, available +1(释放) 之前是下单直接扣 remain_quantity,30 分钟后超时取消还要回滚。但回滚依赖 MQ 消息,MQ 挂了库存就永远不恢复了。新模型不存在这个问题——「可用库存」只负责「卖」,frozen 只负责「锁」,职责拆开,逻辑自洽。 ...

七月 9, 2023 · 2 分钟 · 237 字 · yaomingye

BFF 层接口规范化踩坑记:从 Object 到 DTO 的全面改造

BFF 重构:接口规范化流水账 从 “Object e” 和 “Map c” 说起 项目里有一个 BFF 层,最初的写法长这样: @PostMapping("/xxx/insert") public ApiResult<Integer> insert(@RequestBody Object e) { ... } @PostMapping("/xxx/page") public ApiResult<ResponsePageEntity<?>> page(@RequestBody Map c) { ... } 看起来很省事对吧?一个 Object 通吃所有入参,一个 Map 搞定所有查询条件。但问题在于——Swagger 上完全看不到请求体结构,前端看着文档只能看到 {},根本不知道要传什么字段。 于是这轮干了一件脏活累活:把 BFF 层所有接口的 @RequestBody Object 和 @RequestBody Map 全换成了具体的 DTO 类。总共涉及约 50 处改动,覆盖了认证、用户、商品、订单、营销等全部模块。 改完之后的效果: @PostMapping("/xxx/insert") public ApiResult<Integer> insert(@RequestBody XxxDTO entity) { ... } @PostMapping("/xxx/page") public ApiResult<ResponsePageEntity<?>> page(@RequestBody XxxConditionDTO c) { ... } 前端看 Swagger 终于能看到每个字段的名、类型、枚举值、示例——不需要反复在群里问"这个接口传什么"了。 ...

七月 5, 2023 · 2 分钟 · 334 字 · yaomingye

admin-bff 接口全面就绪 + 前端功能规划定稿 + 安全统一

今日工作 1. admin-bff 接口全面检查与补齐 前端功能规划定稿后,逐一比对前端目录树与 admin-bff 实际接口,发现 1 处缺失(商品图片接口),已补齐。 补齐内容: ProductPhotoFeignClient(新建)→ mall-product-client AdminProductExtraController 新增 productPhoto 5 个 CRUD 接口 最终 admin-bff 共 36 个接口,覆盖前端全部页面。这是前端开发可以直接对着写的接口清单。 2. 前端功能规划定稿 docs/30-admin前端功能规划.md 经过多轮讨论最终定稿。核心原则: 页面按角色可见: 角色 可见页面 超级管理员 全部(系统管理/商品/订单/营销/基础数据/评价) 运营部 商品管理/营销/首页/基础数据/评价 客服部 订单管理/评价 财务部 订单管理(只读金额) 不开发的前端页面: 菜单管理、角色管理、部门管理、岗位管理、字典管理、定时任务——这些由开发维护 DB,不出现在前端。 权限关系简化为:部门 + 岗位 → 角色 → 菜单,超级管理员只需要在用户管理里选部门/岗位,权限自动带出。 3. RBAC 数据初始化 向 cloud_mall_admin 数据库写入预设数据: 部门:运营部、客服部、财务部 角色:超级管理员(已有)、运营(ops)、客服(service)、财务(finance) 岗位表(auth_job)待预设,后续补上。 4. 商品上下架字段补全 发现 product 表没有上下架字段——整个商品系统没有上架/下架的概念。已修复: ALTER TABLE product ADD COLUMN status tinyint(1) DEFAULT 1 COMMENT '上下架状态 1:上架 0:下架'; ProductEntity 同步新增 status 字段,文档补充商品列表支持按状态筛选。 ...

七月 3, 2023 · 1 分钟 · 200 字 · yaomingye

今日日报:common-web 大重构 + R4 FallbackFactory 自动配置 + 技术枚举落地

今日工作 1. common-web 目录整理 + 注解化控制 common-web 之前的目录有点乱, @EnableXxx 注解和 @Configuration 配置类全混在 config/ 包里。今天拆了一刀: 改前: config/ EnableApiResultWrapper.java ← 注解 EnableRequestLogFilter.java ← 注解 ApiResultWrapperConfiguration.java RequestLogFilterConfiguration.java WebAutoConfiguration.java 改后: annotation/ ← 注解单独放 EnableApiResultWrapper.java EnableRequestLogFilter.java config/ ← 只放 @Configuration ApiResultWrapperConfiguration.java RequestLogFilterConfiguration.java WebAutoConfiguration.java 同时引入了 @EnableXxx 模式替代原本的 @ConditionalOnProperty : @EnableRequestLogFilter — 替代 3 个服务里重复的 RequestLogFilterConfig.java ( FilterRegistrationBean 配置完全相同,只是包名不同) @EnableApiResultWrapper — 控制 GlobalApiResultHandler 是否生效 2. GlobalApiResultHandler 失效之谜 GlobalApiResultHandler 实现了 ResponseBodyAdvice<Object> ,通过 WebAutoConfiguration#@Bean 注册。按理说 Spring MVC 会自动发现,但实际上没生效——所有 @RestController 的返回都是裸数据,没有被 ApiResult 包装。 ...

七月 1, 2023 · 3 分钟 · 477 字 · yaomingye

今日日报:Nacos 配置统一收敛 + FeignFallbackProxy 降级代理重构 + Resilience4j 落地

今日工作 1. FeignFallbackProxy:用动态代理干掉模板代码 昨天写了 8 个 FallbackFactory,每个 50+ 行,全是 return null / 空列表 / 0 的模板代码。今天抽了个 FeignFallbackProxy 到 common-core,基于 JDK 动态代理,根据方法返回类型自动推断兜底值。 // 改前:14 个方法逐个手写 return new UserFeignClient() { @Override public List<UserDTO> findByIds(List<Long> ids) { log.warn("[降级] findByIds 返回空列表"); return Collections.emptyList(); } // ... 每个方法都来一遍 }; // 改后:一行搞定 return FeignFallbackProxy.create(UserFeignClient.class, cause); 8 个工厂从 ~400 行缩到 ~40 行,核心逻辑全部收敛到 FeignFallbackProxy 一处。以后加新的 FeignClient 降级也只需要 5 行。 2. Nacos 配置统一收敛 之前每个服务在 Nacos 里都有一份独立的 yml,Redis 连接配了 8 遍、JWT 密钥配了 11 遍。花了半天把 common.yaml 重新扶正——所有共享配置(Redis、JWT、devtools、bean-override、Resilience4j)归到 common,各服务只留数据库、中间件等特有配置。 ...

六月 29, 2023 · 1 分钟 · 167 字 · yaomingye

今日日报:Admin 控制器合并、Swagger 描述优化、Feign 熔断降级体系搭建

今日工作 1. Admin 控制器合并重构 mall-admin 的 controller 层从 15 个砍到了 7 个。 删了 8 个——4 个 internal 控制器(UserInternalController、RoleInternalController、DeptInternalController、JobInternalController),4 个关联表控制器(UserRoleController、RoleMenuController、RoleDeptController、UserAvatarController)。 理由很简单:internal 控制器的方法本来就是对同一张表查数据,合并到 UserController、RoleController、JobController 就够了。关联表控制器纯 CRUD,也没人调,留它过年。 UserFeignClient 的请求路径也顺手从 /v1/internal/user/* 改到了 /v1/auth/user/*,因为 internal 路径已经不存在了。 2. Swagger 描述统一 之前 @Operation(summary = "通过id查询")、@Operation(description = "删除") 这种写了等于没写。 这次给 6 个 Controller 的每个接口都加了三段式描述——认证要求 + 参数说明 + 业务说明: 需 Bearer Token + admin 角色 | 查询参数:id(用户ID) 无需认证(公开接口)| 无参,返回全部角色列表 需 Bearer Token | 请求体:RoleConditionEntity(分页条件) 顺便把白名单逻辑的现状写进了 Security 注释,哪些接口免登录一目了然。 3. Feign 熔断降级体系 这是今天的大头。项目之前 Feign 调用异常处理几乎裸奔——FeignResultDecoder 的核心逻辑被人注释掉了,没有 ErrorDecoder、没有 FallbackFactory、没有断路器。 ...

六月 27, 2023 · 1 分钟 · 166 字 · yaomingye

全链路修复:微服务 JWT 鉴权踩坑记

今日工作 1. Nacos 配置 namespace 发现与修复 项目里 12 个微服务全部使用 Nacos 作为配置中心和注册中心,namespace 是 7af60364-xxx-xxx-xxx 的自定义空间,但之前某次推送配置时没指定 namespace,全部落到了 public 下。等于写了半天的配置全白干。 问题: 服务启动时就报数据库连接超时、Redis 连不上——因为在 public 命名空间下根本找不到 mall-admin-api-dev.yaml 这类配置。 修复: 确认各服务本地 application.yml 中的 spring.cloud.nacos.config.namespace 已正确指向目标命名空间。由用户在 Nacos 控制台手动导回配置。 同时发现 3 个服务(gateway、admin、customer)在本地 application.yml 中通过 shared-configs 引用了 common.yaml ,但该文件早已名存实亡、内容冲突(同一个 key 在 YAML 里出现两次,如 spring: 和 management: 各两遍)。直接移除 shared-configs 引用。 2. MyBatis mapper XML 加载失败 admin 服务调用 POST /v1/internal/user/testLogin 返回 {"code":1,"message":"用户名或密码错误"} ,但密码哈希确认没问题,数据库也查得到用户。查日志发现原因并非密码不匹配,而是 MyBatis mapper XML 文件根本未被加载。 // UserMapper 接口 package cn.net.mall.admin.mapper.auth; // 接口在 auth 包 // UserMapper.xml(XML 文件) // 实际路径: .../mapper/admin/UserMapper.xml // 文件在 admin 目录 接口包名是 auth ,XML 文件路径是 admin ——MyBatis 默认的 mapper-locations 是 classpath*:mapper/**/*.xml ,根本扫不到 cn/net/mall/... 路径下的文件。所有 mapper 方法调用都静默抛异常,被 testLogin 的 catch 块兜底包装为"用户名或密码错误",极具迷惑性。 ...

六月 25, 2023 · 4 分钟 · 772 字 · yaomingye

合并与修复:mall-common重构、Nacos恢复、中间件排查

今日工作 1. 外部 Starter 合并回 mall-common 之前将 mall-common 的基础设施拆分到了独立仓库 mall-spring-boot-starters (含 common-core 、 redis-starter 、 workid-starter 、 web-starter 、 sensitive-starter ),但独立维护成本高、构建链长、改个工具类要跨仓库发版。今日全部合并回主项目。 具体操作: 5 个 external starter 模块搬入主项目,改用主项目 parent POM mall-common-core 与 mall-common 去重(删除 14 个重复工具类,由 common-core 提供) Redisson 依赖彻底移除(零处使用,纯历史包袱) JJWT 统一为 0.12.6 拆分 artifacts,替代旧版合并 JAR 各服务 POM 去掉外部 starter 引用,改为本地模块依赖 2. Application 启动类命名统一 去除 Api 后缀,统一为 {模块名}Application (如 BasicApiApplication → BasicApplication );BFF 层加 Bff 后缀( AdminApiApplication → AdminBffApplication )。 影响范围: 8 个文件改名 + 对应 SpringApplication.run() 引用修复。 ...

六月 23, 2023 · 2 分钟 · 218 字 · yaomingye

今日日报:auth拆分、模块更名、RSA删除、全量编译

今日工作 1. auth 模块拆分与清理 mall-auth 服务整体删除:业务代码(用户管理/RBAC/收货地址)全部迁入 mall-admin ,仅 JWT + Redis 黑名单功能原属 auth,现已整合进 mall-admin mall-auth-client 删除:所有调用方(mall-basic、mall-marketing、mall-message、mall-product、mall-order、mall-order-client)依赖全部切换至 mall-admin-client mall-auth-api-starter 保留不动(AuthApiInterceptor + FeignAuthInterceptor 全项目在用) 2. 模块更名(四组) 原名 新名 说明 mall-admin-api mall-admin-bff BFF 聚合层 mall-mobile-api mall-mobile-bff BFF 聚合层 mall-member mall-customer C 端业务服务 mall-member-client mall-customer-client Feign 接口 对应 Nacos 注册名同步更新:mall-admin-bff, mall-mobile-bff, mall-customer-api Gateway 路由同步:新增 /api/customer/, /api/admin-api/, /api/admin/, /api/mobile/ 路由 Nacos 配置:新建 mall-customer-api-dev.yaml, mall-admin-api-dev.yaml,删除旧名配置 3. 删除 RSA 密码加密层 原登录流程:前端 JS RSA 加密 → 后端 RSA 私钥解密 → BCrypt 校验 ...

六月 21, 2023 · 1 分钟 · 123 字 · yaomingye
Cat Radio