feat: 添加租户业务数据逻辑删除和优化会议状态处理
- 在多个 Mapper 中添加 `logicalDeleteByTenantId` 方法,支持按租户 ID 逻辑删除数据 - 优化 `MeetingUnifiedStatusServiceImpl` 中的会议状态处理逻辑,调整 `isAndroidOfflineMeetingWaitingUpload` 的调用位置 - 更新 `Meetings.tsx` 中的会议状态判断逻辑,新增 `isUnifiedTerminalProgress` 方法 - 优化 `AndroidChunkUploadServiceImpl` 中的文件上传逻辑,移除不必要的 `try-finally` 块 - 在 `TenantManagementServicePrimaryImpl` 中添加逻辑删除租户业务数据的方法,并更新相关依赖注入dev_na
parent
146b31b809
commit
fd9ef5c885
|
|
@ -220,4 +220,14 @@ public interface LicenseMapper extends BaseMapper<LicenseEntity> {
|
|||
AND is_deleted = 0
|
||||
""")
|
||||
int invalidateById(@Param("id") Long id);
|
||||
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Update("""
|
||||
UPDATE biz_license
|
||||
SET is_deleted = 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND is_deleted = 0
|
||||
""")
|
||||
int logicalDeleteByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.imeeting.entity.biz.MeetingPointsAccount;
|
|||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface MeetingPointsAccountMapper extends BaseMapper<MeetingPointsAccount> {
|
||||
|
|
@ -18,4 +19,13 @@ public interface MeetingPointsAccountMapper extends BaseMapper<MeetingPointsAcco
|
|||
FOR UPDATE
|
||||
""")
|
||||
MeetingPointsAccount selectForUpdate(@Param("tenantId") Long tenantId, @Param("userId") Long userId);
|
||||
|
||||
@Update("""
|
||||
UPDATE biz_meeting_points_accounts
|
||||
SET is_deleted = 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND is_deleted = 0
|
||||
""")
|
||||
int logicalDeleteByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,17 @@ package com.imeeting.mapper.biz;
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.imeeting.entity.biz.MeetingPointsLedger;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface MeetingPointsLedgerMapper extends BaseMapper<MeetingPointsLedger> {
|
||||
@Update("""
|
||||
UPDATE biz_meeting_points_ledgers
|
||||
SET is_deleted = 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND is_deleted = 0
|
||||
""")
|
||||
int logicalDeleteByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.imeeting.entity.biz.MeetingSummaryChargeRecord;
|
|||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface MeetingSummaryChargeRecordMapper extends BaseMapper<MeetingSummaryChargeRecord> {
|
||||
|
|
@ -17,4 +18,13 @@ public interface MeetingSummaryChargeRecordMapper extends BaseMapper<MeetingSumm
|
|||
FOR UPDATE
|
||||
""")
|
||||
MeetingSummaryChargeRecord selectForUpdateBySummaryTaskId(@Param("summaryTaskId") Long summaryTaskId);
|
||||
|
||||
@Update("""
|
||||
UPDATE biz_meeting_summary_charge_records
|
||||
SET is_deleted = 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND is_deleted = 0
|
||||
""")
|
||||
int logicalDeleteByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.imeeting.mapper.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import com.imeeting.entity.biz.TenantMeetingPointsSetting;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface TenantMeetingPointsSettingMapper extends BaseMapper<TenantMeetingPointsSetting> {
|
||||
|
|
@ -17,4 +19,14 @@ public interface TenantMeetingPointsSettingMapper extends BaseMapper<TenantMeeti
|
|||
FOR UPDATE
|
||||
""")
|
||||
TenantMeetingPointsSetting selectForUpdate(@Param("tenantId") Long tenantId);
|
||||
|
||||
@InterceptorIgnore(tenantLine = "true")
|
||||
@Update("""
|
||||
UPDATE biz_meeting_points_tenant_settings
|
||||
SET is_deleted = 1,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE tenant_id = #{tenantId}
|
||||
AND is_deleted = 0
|
||||
""")
|
||||
int logicalDeleteByTenantId(@Param("tenantId") Long tenantId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,24 +99,24 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
|||
}
|
||||
|
||||
Path mergedFile = mergeChunks(state);
|
||||
try {
|
||||
MultipartFile mergedMultipart = new LocalMultipartFile(
|
||||
buildMergedOriginalFilename(state, mergedFile),
|
||||
state.getContentType(),
|
||||
Files.readAllBytes(mergedFile)
|
||||
MultipartFile mergedMultipart = new LocalMultipartFile(
|
||||
buildMergedOriginalFilename(state, mergedFile),
|
||||
state.getContentType(),
|
||||
Files.readAllBytes(mergedFile)
|
||||
);
|
||||
LegacyUploadAudioResponse response;
|
||||
if (authContext.isAnonymous()) {
|
||||
response = legacyMeetingAdapterService.uploadAndTriggerOfflineProcessForPublicDevice(
|
||||
meetingId,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
mergedMultipart,
|
||||
authContext
|
||||
);
|
||||
if (authContext.isAnonymous()) {
|
||||
return legacyMeetingAdapterService.uploadAndTriggerOfflineProcessForPublicDevice(
|
||||
meetingId,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
mergedMultipart,
|
||||
authContext
|
||||
);
|
||||
}
|
||||
} else {
|
||||
LoginUser loginUser = toLoginUser(authContext);
|
||||
return legacyMeetingAdapterService.uploadAndTriggerOfflineProcess(
|
||||
response = legacyMeetingAdapterService.uploadAndTriggerOfflineProcess(
|
||||
meetingId,
|
||||
null,
|
||||
null,
|
||||
|
|
@ -125,9 +125,11 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
|||
authContext,
|
||||
loginUser
|
||||
);
|
||||
} finally {
|
||||
}
|
||||
if (response != null) {
|
||||
cleanup(meetingId);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private AndroidChunkUploadSessionState getOrCreateState(Long meetingId,
|
||||
|
|
@ -140,7 +142,7 @@ public class AndroidChunkUploadServiceImpl implements AndroidChunkUploadService
|
|||
AndroidChunkUploadSessionState state = new AndroidChunkUploadSessionState();
|
||||
state.setMeetingId(meetingId);
|
||||
state.setDeviceId(authContext.getDeviceId());
|
||||
state.setFileName(chunkFile.getOriginalFilename());
|
||||
state.setFileName(normalizeChunkSourceFileName(chunkFile.getOriginalFilename()));
|
||||
state.setContentType(chunkFile.getContentType());
|
||||
saveState(meetingId, state);
|
||||
return state;
|
||||
|
|
|
|||
|
|
@ -84,15 +84,15 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ
|
|||
if (MeetingStatusEnum.isCode(meeting.getStatus(), MeetingStatusEnum.COMPLETED)) {
|
||||
return UnifiedMeetingStatusStage.COMPLETED;
|
||||
}
|
||||
if (isAndroidOfflineMeetingWaitingUpload(meeting)) {
|
||||
return UnifiedMeetingStatusStage.WAITING_UPLOAD;
|
||||
}
|
||||
UnifiedMeetingStatusStage stageFromSnapshot = resolveStageFromSnapshot(snapshot);
|
||||
if (stageFromSnapshot != null) {
|
||||
return stageFromSnapshot;
|
||||
}
|
||||
|
||||
MeetingUnifiedStageContext context = buildStageContext(meeting.getId(), snapshot);
|
||||
if (isAndroidOfflineMeetingWaitingUpload(meeting)) {
|
||||
return UnifiedMeetingStatusStage.WAITING_UPLOAD;
|
||||
}
|
||||
if (isTranscribing(context)) {
|
||||
return UnifiedMeetingStatusStage.TRANSCRIBING;
|
||||
}
|
||||
|
|
@ -173,6 +173,9 @@ public class MeetingUnifiedStatusServiceImpl implements MeetingUnifiedStatusServ
|
|||
}
|
||||
|
||||
private String resolveMessage(MeetingVO meeting, MeetingProgressSnapshot snapshot, UnifiedMeetingStatusStage stage) {
|
||||
if (stage == UnifiedMeetingStatusStage.WAITING_UPLOAD) {
|
||||
return "待上传录音文件";
|
||||
}
|
||||
if (snapshot != null && snapshot.getMessage() != null && !snapshot.getMessage().isBlank() && !Objects.equals(snapshot.getMessage(), "Waiting...")) {
|
||||
return snapshot.getMessage();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
package com.imeeting.service.biz.impl;
|
||||
|
||||
import com.imeeting.mapper.LicenseMapper;
|
||||
import com.imeeting.mapper.biz.MeetingPointsAccountMapper;
|
||||
import com.imeeting.mapper.biz.MeetingPointsLedgerMapper;
|
||||
import com.imeeting.mapper.biz.MeetingSummaryChargeRecordMapper;
|
||||
import com.imeeting.mapper.biz.TenantMeetingPointsSettingMapper;
|
||||
import com.imeeting.service.biz.LicenseService;
|
||||
import com.imeeting.service.biz.MeetingPointsService;
|
||||
import com.imeeting.service.biz.TenantMeetingPointsSettingService;
|
||||
import com.unisbase.config.properties.UnisBaseProperties;
|
||||
import com.unisbase.dto.CreateTenantDTO;
|
||||
import com.unisbase.dto.PageResult;
|
||||
import com.unisbase.dto.SysTenantDTO;
|
||||
import com.unisbase.service.SysTenantService;
|
||||
import com.unisbase.service.TenantManagementService;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -18,48 +22,51 @@ import java.util.List;
|
|||
@Service
|
||||
@Primary
|
||||
public class TenantManagementServicePrimaryImpl implements TenantManagementService {
|
||||
private static final long DEFAULT_TENANT_ID = 1L;
|
||||
|
||||
private final SysTenantService sysTenantService;
|
||||
private final UnisBaseProperties unisBaseProperties;
|
||||
private final TenantManagementService delegate;
|
||||
private final LicenseService licenseService;
|
||||
private final MeetingPointsService meetingPointsService;
|
||||
private final TenantMeetingPointsSettingService tenantMeetingPointsSettingService;
|
||||
private final LicenseMapper licenseMapper;
|
||||
private final MeetingPointsAccountMapper meetingPointsAccountMapper;
|
||||
private final MeetingPointsLedgerMapper meetingPointsLedgerMapper;
|
||||
private final MeetingSummaryChargeRecordMapper meetingSummaryChargeRecordMapper;
|
||||
private final TenantMeetingPointsSettingMapper tenantMeetingPointsSettingMapper;
|
||||
|
||||
public TenantManagementServicePrimaryImpl(SysTenantService sysTenantService,
|
||||
UnisBaseProperties unisBaseProperties,
|
||||
public TenantManagementServicePrimaryImpl(@Qualifier("tenantManagementServiceImpl") TenantManagementService delegate,
|
||||
LicenseService licenseService,
|
||||
MeetingPointsService meetingPointsService,
|
||||
TenantMeetingPointsSettingService tenantMeetingPointsSettingService) {
|
||||
this.sysTenantService = sysTenantService;
|
||||
this.unisBaseProperties = unisBaseProperties;
|
||||
TenantMeetingPointsSettingService tenantMeetingPointsSettingService,
|
||||
LicenseMapper licenseMapper,
|
||||
MeetingPointsAccountMapper meetingPointsAccountMapper,
|
||||
MeetingPointsLedgerMapper meetingPointsLedgerMapper,
|
||||
MeetingSummaryChargeRecordMapper meetingSummaryChargeRecordMapper,
|
||||
TenantMeetingPointsSettingMapper tenantMeetingPointsSettingMapper) {
|
||||
this.delegate = delegate;
|
||||
this.licenseService = licenseService;
|
||||
this.meetingPointsService = meetingPointsService;
|
||||
this.tenantMeetingPointsSettingService = tenantMeetingPointsSettingService;
|
||||
this.licenseMapper = licenseMapper;
|
||||
this.meetingPointsAccountMapper = meetingPointsAccountMapper;
|
||||
this.meetingPointsLedgerMapper = meetingPointsLedgerMapper;
|
||||
this.meetingSummaryChargeRecordMapper = meetingSummaryChargeRecordMapper;
|
||||
this.tenantMeetingPointsSettingMapper = tenantMeetingPointsSettingMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<List<SysTenantDTO>> listTenants(Integer current, Integer size, String name, String code) {
|
||||
if (isSingleTenantMode()) {
|
||||
SysTenantDTO defaultTenant = sysTenantService.findById(getDefaultTenantId());
|
||||
PageResult<List<SysTenantDTO>> result = new PageResult<>();
|
||||
result.setRecords(defaultTenant == null ? List.of() : List.of(defaultTenant));
|
||||
result.setTotal(defaultTenant == null ? 0 : 1);
|
||||
return result;
|
||||
}
|
||||
return sysTenantService.page(current, size, name, code);
|
||||
return delegate.listTenants(current, size, name, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysTenantDTO getTenant(Long tenantId) {
|
||||
return sysTenantService.findById(tenantId);
|
||||
return delegate.getTenant(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createTenant(CreateTenantDTO tenant) {
|
||||
assertTenantLifecycleAllowed();
|
||||
Long tenantId = sysTenantService.createTenantWithAdmin(tenant);
|
||||
Long tenantId = delegate.createTenant(tenant);
|
||||
licenseService.initializeTemporaryLicenses(tenantId);
|
||||
meetingPointsService.initializeTenantPointsAccount(tenantId);
|
||||
tenantMeetingPointsSettingService.initializeTenantSetting(tenantId);
|
||||
|
|
@ -68,55 +75,25 @@ public class TenantManagementServicePrimaryImpl implements TenantManagementServi
|
|||
|
||||
@Override
|
||||
public boolean updateTenant(Long tenantId, SysTenantDTO tenant) {
|
||||
assertDefaultTenantCanBeUpdated(tenantId, tenant == null ? null : tenant.getStatus());
|
||||
if (tenant == null) {
|
||||
throw new IllegalArgumentException("租户信息不能为空");
|
||||
}
|
||||
tenant.setId(tenantId);
|
||||
return sysTenantService.update(tenant);
|
||||
return delegate.updateTenant(tenantId, tenant);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean deleteTenant(Long tenantId) {
|
||||
assertTenantLifecycleAllowed();
|
||||
return sysTenantService.deleteById(tenantId);
|
||||
boolean deleted = delegate.deleteTenant(tenantId);
|
||||
if (!deleted || tenantId == null) {
|
||||
return deleted;
|
||||
}
|
||||
logicalDeleteTenantBizData(tenantId);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSingleTenantMode() {
|
||||
if (unisBaseProperties == null || unisBaseProperties.getTenant() == null) {
|
||||
return false;
|
||||
}
|
||||
String mode = unisBaseProperties.getTenant().getMode();
|
||||
if (mode != null && !mode.isBlank()) {
|
||||
return "single".equalsIgnoreCase(mode.trim());
|
||||
}
|
||||
return !unisBaseProperties.getTenant().isEnabled();
|
||||
}
|
||||
|
||||
private Long getDefaultTenantId() {
|
||||
if (unisBaseProperties == null || unisBaseProperties.getTenant() == null) {
|
||||
return DEFAULT_TENANT_ID;
|
||||
}
|
||||
Long configured = unisBaseProperties.getTenant().getDefaultTenantId();
|
||||
return configured == null || configured <= 0 ? DEFAULT_TENANT_ID : configured;
|
||||
}
|
||||
|
||||
private void assertTenantLifecycleAllowed() {
|
||||
if (isSingleTenantMode()) {
|
||||
throw new IllegalArgumentException("当前为单租户模式,不支持租户生命周期操作");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertDefaultTenantCanBeUpdated(Long tenantId, Integer status) {
|
||||
if (!isSingleTenantMode()) {
|
||||
return;
|
||||
}
|
||||
Long defaultTenantId = getDefaultTenantId();
|
||||
if (!defaultTenantId.equals(tenantId)) {
|
||||
throw new IllegalArgumentException("当前为单租户模式,只允许维护默认租户");
|
||||
}
|
||||
if (status != null && status != 1) {
|
||||
throw new IllegalArgumentException("当前为单租户模式,不允许禁用默认租户");
|
||||
}
|
||||
private void logicalDeleteTenantBizData(Long tenantId) {
|
||||
tenantMeetingPointsSettingMapper.logicalDeleteByTenantId(tenantId);
|
||||
meetingPointsLedgerMapper.logicalDeleteByTenantId(tenantId);
|
||||
meetingSummaryChargeRecordMapper.logicalDeleteByTenantId(tenantId);
|
||||
meetingPointsAccountMapper.logicalDeleteByTenantId(tenantId);
|
||||
licenseMapper.logicalDeleteByTenantId(tenantId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,18 @@ const shouldTrackGenerationProgress = (item: MeetingVO) =>
|
|||
!hasLatestGenerationFailure(item) && (item.status === 0 || item.status === 1 || item.status === 2);
|
||||
|
||||
const isTerminalMeetingProgress = (progress?: MeetingProgress | null) =>
|
||||
!!progress && (progress.percent === 100 || progress.percent < 0);
|
||||
!!progress && (
|
||||
progress.percent === 100
|
||||
|| progress.percent < 0
|
||||
|| progress.unifiedStatus?.statusCode === "COMPLETED"
|
||||
|| progress.unifiedStatus?.statusCode?.startsWith("FAILED_")
|
||||
);
|
||||
|
||||
const isUnifiedTerminalProgress = (progress?: MeetingProgress | null) =>
|
||||
!!progress && (
|
||||
progress.unifiedStatus?.statusCode === "COMPLETED"
|
||||
|| progress.unifiedStatus?.statusCode?.startsWith("FAILED_")
|
||||
);
|
||||
|
||||
const shouldPollMeetingCard = (item: MeetingVO) =>
|
||||
shouldTrackGenerationProgress(item)
|
||||
|
|
@ -212,7 +223,7 @@ const IntegratedStatusTag: React.FC<{ meeting: MeetingVO; progress: MeetingProgr
|
|||
const displayConfig = progress?.unifiedStatus?.statusText
|
||||
? { ...config, text: progress.unifiedStatus.statusText }
|
||||
: config;
|
||||
const isProcessing = shouldTrackGenerationProgress(meeting);
|
||||
const isProcessing = shouldTrackGenerationProgress(meeting) && !isUnifiedTerminalProgress(progress);
|
||||
const percent = isProcessing ? progress?.percent || 0 : 0;
|
||||
|
||||
return (
|
||||
|
|
|
|||
Loading…
Reference in New Issue