Files
smartDriveEEUniApp/docs/backend-fix-tenant-id-issue.md
2026-01-09 19:41:18 +08:00

8.6 KiB
Raw Blame History

后端修复:转文本更新时去掉tenant_id限制

⚡ 快速修复(推荐)

最快解决方案:在Mapper接口中添加自定义更新方法

  1. 在 AudioManagementSegmentsMapper.java 中添加:
@InterceptorIgnore(tenantLine = "true")
@Update("UPDATE audio_management_segments SET recording_text = #{recordingText}, transcribe_end_time = #{transcribeEndTime} WHERE id = #{id}")
int updateTranscriptionWithoutTenant(
    @Param("id") String id, 
    @Param("recordingText") String recordingText, 
    @Param("transcribeEndTime") LocalDateTime transcribeEndTime
);
  1. 在Controller的转文本方法中,将:
boolean updateSuccess = audioManagementSegmentsService.update(textUpdateEntity, textUpdateWrapper);

替换为:

int updateCount = audioManagementSegmentsMapper.updateTranscriptionWithoutTenant(
    segmentId, transcriptionText, LocalDateTime.now()
);
boolean updateSuccess = updateCount > 0;

问题描述

在转文本功能中,更新 audio_management_segments 表时,MyBatis-Plus 的多租户插件自动添加了 AND tenant_id = null 条件,导致更新失败。

从日志可以看到:

UPDATE audio_management_segments SET recording_text=?, transcribe_end_time=? 
WHERE (id = ?) AND tenant_id = null

解决方案

方案1:在Controller层使用baseMapper直接更新(最简单有效)

在转文本的Controller方法中,直接使用 baseMapper 更新,绕过Service层的多租户拦截:

@PostMapping("/transcribe/{id}")
@Operation(summary = "转文本", description = "分段音频文件转文本")
public ResponseEntity<Map<String, Object>> transcribeSegmentById(
        @Parameter(description = "分段ID", required = true)
        @PathVariable String id) {
    try {
        // ... 转文本逻辑 ...
        
        // 更新录音文本 - 直接使用baseMapper,绕过多租户拦截
        AudioManagementSegments textUpdateEntity = new AudioManagementSegments();
        textUpdateEntity.setRecordingText(transcriptionText);
        textUpdateEntity.setTranscribeEndTime(LocalDateTime.now());
        
        LambdaUpdateWrapper<AudioManagementSegments> textUpdateWrapper = new LambdaUpdateWrapper<>();
        textUpdateWrapper.eq(AudioManagementSegments::getId, segmentId);
        
        // 关键:使用 baseMapper 直接更新,并设置忽略多租户拦截
        // 方法1:使用 @SqlParser(filter = true) - 旧版本MyBatis-Plus
        // 方法2:直接调用 baseMapper.update,但需要在Mapper方法上添加注解
        
        // 推荐:在Mapper接口中添加自定义更新方法
        int updateCount = audioManagementSegmentsMapper.updateTranscriptionWithoutTenant(
            segmentId, 
            transcriptionText, 
            LocalDateTime.now()
        );
        
        boolean updateSuccess = updateCount > 0;
        
        // ... 其他逻辑 ...
    } catch (Exception e) {
        // ... 错误处理 ...
    }
}

方案2:在Mapper接口中添加自定义更新方法(推荐)

在 AudioManagementSegmentsMapper 中添加自定义更新方法,使用 @InterceptorIgnore 注解:

@Mapper
public interface AudioManagementSegmentsMapper extends BaseMapper<AudioManagementSegments> {
    
    /**
     * 更新转文本结果,忽略多租户拦截
     */
    @InterceptorIgnore(tenantLine = "true")
    @Update("UPDATE audio_management_segments SET recording_text = #{recordingText}, transcribe_end_time = #{transcribeEndTime} WHERE id = #{id}")
    int updateTranscriptionWithoutTenant(
        @Param("id") String id, 
        @Param("recordingText") String recordingText, 
        @Param("transcribeEndTime") LocalDateTime transcribeEndTime
    );
}

然后在Controller中调用:

// 在转文本方法中
int updateCount = audioManagementSegmentsMapper.updateTranscriptionWithoutTenant(
    segmentId, 
    transcriptionText, 
    LocalDateTime.now()
);
boolean updateSuccess = updateCount > 0;

方案3:在Service层添加专门的方法并禁用多租户拦截

在 AudioManagementSegmentsService 接口中添加新方法:

public interface AudioManagementSegmentsService extends IService<AudioManagementSegments> {
    /**
     * 更新转文本结果,忽略多租户拦截
     */
    boolean updateTranscription(String id, String recordingText, LocalDateTime transcribeEndTime);
}

在实现类中:

@Service
public class AudioManagementSegmentsServiceImpl extends ServiceImpl<AudioManagementSegmentsMapper, AudioManagementSegments> 
    implements AudioManagementSegmentsService {
    
    @Override
    @InterceptorIgnore(tenantLine = "true")  // 禁用多租户拦截
    public boolean updateTranscription(String id, String recordingText, LocalDateTime transcribeEndTime) {
        AudioManagementSegments entity = new AudioManagementSegments();
        entity.setId(id);
        entity.setRecordingText(recordingText);
        entity.setTranscribeEndTime(transcribeEndTime);
        
        LambdaUpdateWrapper<AudioManagementSegments> wrapper = new LambdaUpdateWrapper<>();
        wrapper.eq(AudioManagementSegments::getId, id);
        
        // 注意:这里仍然可能被拦截,所以最好使用Mapper的自定义方法
        return this.update(entity, wrapper);
    }
}

注意:如果方案3仍然被拦截,请使用方案2(Mapper层的自定义方法)。

方案4:配置多租户插件排除该表(全局方案)

在 MyBatis-Plus 的多租户配置中,排除 audio_management_segments 表:

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 多租户插件
        TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
        tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
            @Override
            public Expression getTenantId() {
                // 返回租户ID(从上下文获取)
                String tenantId = TenantContextHolder.getTenantId();
                return tenantId != null ? new StringValue(tenantId) : null;
            }
            
            @Override
            public String getTenantIdColumn() {
                return "tenant_id";
            }
            
            @Override
            public boolean ignoreTable(String tableName) {
                // 排除 audio_management_segments 表,不进行多租户拦截
                return "audio_management_segments".equalsIgnoreCase(tableName);
            }
        });
        
        interceptor.addInnerInterceptor(tenantInterceptor);
        return interceptor;
    }
}

注意:这个方案会影响所有对 audio_management_segments 表的操作,请确保安全性。

方案4:使用自定义SQL(不推荐,但可行)

如果以上方案都不适用,可以使用自定义SQL,直接执行更新语句:

@Mapper
public interface AudioManagementSegmentsMapper extends BaseMapper<AudioManagementSegments> {
    
    @Update("UPDATE audio_management_segments SET recording_text = #{recordingText}, transcribe_end_time = #{transcribeEndTime} WHERE id = #{id}")
    int updateTranscription(@Param("id") String id, 
                           @Param("recordingText") String recordingText, 
                           @Param("transcribeEndTime") LocalDateTime transcribeEndTime);
}

推荐方案(按优先级)

最推荐:方案2 - Mapper层自定义方法

强烈推荐使用方案2,在Mapper接口中添加自定义更新方法,使用 @InterceptorIgnore 注解:

优点:

  1. ✅ 最直接有效,不会被拦截器拦截
  2. ✅ 只影响转文本的更新操作
  3. ✅ 不影响其他查询和更新操作
  4. ✅ 代码清晰,易于维护
  5. ✅ 性能好,直接执行SQL

实现步骤:

  1. 在 AudioManagementSegmentsMapper 接口中添加方法(见方案2代码)
  2. 在Controller中调用 audioManagementSegmentsMapper.updateTranscriptionWithoutTenant()
  3. 替换原来的 service.update() 调用

备选方案:方案4 - 配置排除表

如果方案2不适用,可以使用方案4,在配置中排除整个表:

  • ⚠️ 会影响所有对该表的操作
  • ⚠️ 需要确保安全性
  • ✅ 一次配置,全局生效

注意事项

  1. 如果使用方案3(全局排除表),会影响所有对该表的操作,需要确保安全性
  2. 如果使用方案4(自定义SQL),需要手动处理SQL注入防护
  3. 修改后需要测试其他功能是否受影响