问题一:前后端联动互相干扰(字段覆盖)
问题根因:数据所有权边界模糊,缺乏数据防护机制。
| 角色 | 具体改进措施 |
|---|---|
| 后端 | 1. 数据分层防御 : - 创建ContractSaveRequestDTO,仅包含前端可修改字段 - 自动管理字段(id、createTime、creator等)坚决不放入DTO - 使用@JsonIgnore防止敏感字段意外序列化到前端 2. 字段权限控制 : - Service层显式设置后端管理字段:entity.setCreateTime(now()) - 使用Bean Validation验证入参字段范围 3. 接口文档化:Swagger明确标注每个字段的读写权限 |
| 前端 | 1. 数据提交规范 : - 提交前使用lodash.omit过滤只读字段 - 创建专门的submitData对象,不与页面显示数据混用 2. 请求拦截器 :全局axios拦截器中添加字段过滤逻辑 3. 代码审查清单:将"不提交只读字段"加入团队Code Review清单 |
| QA | 1. 契约测试 : - 使用Pact验证接口字段权限定义 - 自动化检查后端不会返回敏感字段 2. 安全专项测试 : - 使用Burp Suite尝试修改只读字段,验证后端防护 - 设计"字段覆盖"专项测试用例 3. 自动化回归:将字段权限检查加入API自动化测试 |
技术实现示例:
javascript
// 前端 - 数据清洗
const submitData = {
...formData,
// 显式排除后端管理字段
id: undefined,
createTime: undefined,
creator: undefined
}
// 后端 - 清晰的DTO设计
public class ContractSaveRequest {
@NotBlank
private String title; // 前端可修改
@NotBlank
private String content; // 前端可修改
// 不包含createTime、creator等后端字段
}
public class ContractService {
public Contract saveContract(ContractSaveRequest request) {
Contract entity = convertToEntity(request);
// 后端确保设置管理字段
entity.setCreateTime(LocalDateTime.now());
entity.setCreator(getCurrentUser());
return repository.save(entity);
}
}
问题二:Word转PDF失败(特殊格式兼容性)
问题根因:对复杂格式文档兼容性不足,缺乏渐进式降级方案。
| 角色 | 具体改进措施 |
|---|---|
| 后端 | 1. 多引擎降级策略 : - 策略模式:Aspose → iText → LibreOffice 依次尝试 - 基于历史成功率智能选择最优引擎 2. 文档预处理 : - 检测文档复杂度,复杂文档直接使用最强引擎 - 移除异常格式、标准化字体 3. 异步化处理 : - 提交转换任务到RabbitMQ异步处理 - 提供任务状态查询接口 /api/tasks/{taskId}/status |
| 前端 | 1. 用户体验优化 : - 上传时检测文件大小和类型,过大文件提前警告 - 转换中显示进度条和预计等待时间 2. 降级体验 : - 转换失败时提供"下载原Word文件"备选方案 - 复杂文档提示"转换时间较长,请耐心等待" 3. 状态轮询:定时查询转换状态,成功自动刷新 |
| QA | 1. 兼容性测试矩阵 : - 构建复杂格式测试库(表格、图表、公式、特殊字体) - 定期运行全引擎兼容性测试套件 2. 混沌工程测试 : - 模拟各转换引擎宕机、超时、内存溢出 - 验证降级策略和系统韧性 3. 性能基准测试:建立各引擎的性能基准,监控性能衰减 |
技术实现示例:
java
// 后端 - 多引擎降级策略
@Service
@Slf4j
public class DocumentConverter {
private final List<ConverterStrategy> strategies;
public ConversionResult convertToPdf(File wordFile) {
// 预处理:检测文档复杂度
DocumentComplexity complexity = analyzeComplexity(wordFile);
for (ConverterStrategy strategy : getStrategyOrder(complexity)) {
try {
log.info("Trying converter: {}", strategy.getName());
ConversionResult result = strategy.convert(wordFile);
metrics.recordSuccess(strategy.getName());
return result;
} catch (UnsupportedFormatException e) {
log.warn("{} unsupported format, trying next", strategy.getName());
continue; // 格式不支持,继续尝试
} catch (ConversionException e) {
log.warn("{} conversion failed, trying next", strategy.getName());
metrics.recordFailure(strategy.getName());
// 其他异常,继续尝试
}
}
// 全部失败时的业务降级
return ConversionResult.fallback("转换失败,请下载Word原文件");
}
}
问题三:接口幂等导致合同重复创建
问题根因:幂等性保障机制不完整,第三方调用链断裂。
| 角色 | 具体改进措施 |
|---|---|
| 后端 | 1. 统一幂等框架 : - 开发@Idempotent注解+AOP切面 - Redis原子操作(SETNX)实现防重校验 2. 第三方调用规范 : - 主动生成幂等键 :idempotentKey = "contract_"+bizId+"_"+MD5(参数) - 调用第三方时强制校验幂等键传递 3. 多级防重:数据库唯一索引 + Redis防重 + 业务逻辑校验 |
| 前端 | 1. 生成幂等键 : - 每次提交生成唯一Idempotency-Key(UUIDv4) - 放在HTTP Header中:Idempotency-Key: <uuid> 2. 交互防重 : - 提交后立即禁用按钮 + Loading状态 - 路由跳转时清理本地幂等键 3. 错误处理:接收到幂等错误时提示"请求已提交,请勿重复操作" |
| QA | 1. 并发专项测试 : - JMeter模拟相同请求并发提交(相同幂等键) - 验证是否只成功一次且结果一致 2. 幂等键有效性测试 : - 测试页面刷新、浏览器回退等场景的幂等性 - 验证幂等键生成规则的全局唯一性 3. 第三方集成测试 : - Mock第三方服务验证幂等键传递正确性 - 测试第三方超时、重试场景下的系统行为 |
技术实现示例:
java
// 后端 - 幂等框架
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
String key() default ""; // 幂等键表达式
long expireTime() default 10 * 60; // 过期时间(秒)
}
@Aspect
@Component
@Slf4j
public class IdempotentAspect {
@Around("@annotation(idempotent)")
public Object checkIdempotent(ProceedingJoinPoint joinPoint,
Idempotent idempotent) throws Throwable {
String key = generateKey(joinPoint, idempotent);
// Redis原子操作防重
Boolean firstRequest = redisTemplate.opsForValue()
.setIfAbsent(key, "PROCESSING", idempotent.expireTime(), TimeUnit.SECONDS);
if (Boolean.FALSE.equals(firstRequest)) {
// 重复请求,返回缓存结果
Object cachedResult = redisTemplate.opsForValue().get(key + "_RESULT");
if (cachedResult != null) {
log.info("Return cached result for idempotent key: {}", key);
return cachedResult;
}
throw new IdempotentException("请求正在处理中,请勿重复提交");
}
try {
Object result = joinPoint.proceed();
// 缓存成功结果
redisTemplate.opsForValue()
.set(key + "_RESULT", result, idempotent.expireTime(), TimeUnit.SECONDS);
return result;
} catch (Exception e) {
// 业务失败,删除幂等键允许重试
redisTemplate.delete(key);
throw e;
}
}
}
// 第三方调用 - 主动传递幂等键
@Service
public class ThirdPartyService {
public void callExternalService(Contract contract) {
// 主动生成并传递幂等键
String idempotentKey = "contract_" + contract.getId() + "_" +
DigestUtils.md5DigestAsHex(contract.toString().getBytes());
ThirdPartyRequest request = ThirdPartyRequest.builder()
.idempotentKey(idempotentKey) // 关键:主动传递
.data(contract.getData())
.build();
thirdPartyClient.invoke(request);
}
}