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仍然被拦截请使用方案2Mapper层的自定义方法

方案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. 修改后需要测试其他功能是否受影响