LeRobot 实战指南:从零训练并部署你的第一个机器人策略
2026/9/20 13:54:07
单体架构臃肿
把预约、病历、支付、AI 诊断全塞进一个工程,编译一次 3 min,改一行代码重启 30 s,答辩现场改 BUG 直接“社死”。
无真实业务流
用“张三”和“李四”两条假数据跑通流程,一旦遇到“患者先预约后取消,医生同时下诊断”的并发场景,数据库瞬间出现负号库存或重复病历。
安全性缺失
明文存储身份证、手机号,SQL 拼接字符串,端口 3306 直接暴露在公网,评委一句“符合等保吗?”就让项目降档。
| 维度 | 方案 A | 方案 B | 毕业设计推荐 |
|---|---|---|---|
| 语言框架 | Spring Boot 3.x | Django 4.x | Spring Boot(国内教程多,微服务生态成熟) |
| 数据库 | SQLite | PostgreSQL 15 | PostgreSQL(事务、行锁、JSONB 支持,方便后续做全文检索) |
| 部署方式 | 本地 jar 启动 | Docker + Docker Compose | Docker(一次构建,随处运行,方便评委复现) |
补充说明:边缘节点采用轻量级 MQTT 网关(Eclipse Mosquitto),把 AI 推理结果缓存到 Redis 6.x,降低云端回源延迟。
public class JwtAuthGatewayFilter implements GlobalFilter, Ordered { private final RedisTemplate<String,String> redis; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){ String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if (token == null || !token.startsWith("Bearer ")){ return unauthorized(exchange); } String jti = parseJti(token); // 工具方法省略 Boolean ok = redis.opsForSet().isMember("jwt:whitelist", jti); if (Boolean.FALSE.equals(ok)){ return unauthorized(exchange); } // 将用户 ID 放入请求头,下游直接取 ServerHttpRequest mutate = exchange.getRequest().mutate() .header("X-User-Id", parseUserId(token)) .build(); return chain.filter(exchange.mutate().request(mutate).build()); } private Mono<Void> unauthorized(ServerWebExchange exchange){ exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } @Override public int getOrder(){ return -100; } }@Entity @Table(name = "t_medical_record", uniqueConstraints = @UniqueConstraint(columnNames = {"record_no", "version"})) public class MedicalRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String recordNo; // 业务编号 private Long version; // 每次更新 +1 @Type(JsonType.class) @Column(columnDefinition = "jsonb") private String content; // 全文 JSON private Long patientId; private Timestamp updatedAt; } // Service 层幂等控制 @Transactional public MedicalRecordDTO update(UpdateCommand cmd){ MedicalRecord po = repo.findByRecordNoAndVersion(cmd.getRecordNo(), cmd.getVersion()) .orElseThrow(() -> new ConflictException("版本已过期")); po.setContent(cmd.getContent()); po.setVersion(po.getVersion() + 1); repo.save(po); return mapper.toDto(po); }@Aspect @Component public class DiagnosisCacheAspect { @Autowired private RedisTemplate<String, String> redis; @Around("@annotation(diagnosisCache)") public Object around(ProceedingJoinPoint pjp, DiagnosisCache diagnosisCache) throws Throwable { Object[] args = pjp.getArgs(); String key = "diag:" + DigestUtils.sha256Hex(args[0].toString()).substring(0, 16); String cached = redis.opsForValue().get(key); if (cached != null) { return objectMapper.readValue(cached, DiagnosisResult.class); } Object result = pjp.proceed(); redis.opsForValue().set(key, objectMapper.writeValueAsString(result), Duration.ofDays(7)); return result; } }SQL 注入防护
<foreach>参数绑定,拒绝拼接。并发访问数据竞争
SELECT ... FOR UPDATE SKIP LOCKED,将超卖率降到 0。响应时间压测(Docker 本地 4C8G)
| 场景 | 并发数 | P99 延迟 | 错误率 |
|---|---|---|---|
| 患者登录 | 1000 | 45 ms | 0 % |
| 病历查询 | 1000 | 78 ms | 0 % |
| AI 诊断(缓存命中) | 500 | 180 ms | 0 % |
| AI 诊断(未命中) | 500 | 1200 ms | 0 % |
医疗数据脱敏
HTTPS 强制启用
在 Nginx 层返回Strict-Transport-Security: max-age=31536000; includeSubDomains,评分工具 SSL Labs 达到 A+。
日志审计
采用 Loki + Promtail 收集容器日志,关键事件(登录、病历导出、诊断结果修改)以 JSON 形式落盘,保留 180 天,支持链式追踪 traceID。
当系统从单院区扩展到“多院区 + 医联体”场景时,边缘节点与中心云的数据一致性如何保证?若 AI 模型在不同院区版本不一致,诊断结果出现偏差,该如何设计灰度升级与回滚策略?期待在评论区看到你的思考。