refactor: 优化会议访问密码更新逻辑和屏保实体字段策略

- 在 `LegacyMeetingController` 和 `AndroidMeetingController` 中使用 `LambdaUpdateWrapper` 更新会议访问密码
- 更新 `ScreenSaver` 实体的 `ownerUserId` 字段策略为 `ALWAYS`
- 添加相关测试用例以验证屏保实体字段策略和更新逻辑
- 优化 `AiModels.tsx` 中的提供商基础 URL 和表单占位符
dev_na
chenhao 2026-04-24 13:44:29 +08:00
parent f6ffaddae1
commit a295a3b15b
7 changed files with 68 additions and 23 deletions

View File

@ -1,6 +1,7 @@
package com.imeeting.controller.android; package com.imeeting.controller.android;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imeeting.common.RedisKeys; import com.imeeting.common.RedisKeys;
@ -185,8 +186,9 @@ public class AndroidMeetingController {
return ApiResponse.error("仅会议创建人可设置访问密码"); return ApiResponse.error("仅会议创建人可设置访问密码");
} }
String password = normalizePassword(command == null ? null : command.getPassword()); String password = normalizePassword(command == null ? null : command.getPassword());
meeting.setAccessPassword(password); meetingService.update(new LambdaUpdateWrapper<Meeting>()
meetingService.updateById(meeting); .eq(Meeting::getId,meeting.getId())
.set(Meeting::getAccessPassword, password));
return ApiResponse.ok(password); return ApiResponse.ok(password);
} }

View File

@ -1,6 +1,7 @@
package com.imeeting.controller.android.legacy; package com.imeeting.controller.android.legacy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.imeeting.common.RedisKeys; import com.imeeting.common.RedisKeys;
@ -205,8 +206,9 @@ public class LegacyMeetingController {
return LegacyApiResponse.error("403", "仅会议创建人可设置访问密码"); return LegacyApiResponse.error("403", "仅会议创建人可设置访问密码");
} }
String password = normalizePassword(request == null ? null : request.getPassword()); String password = normalizePassword(request == null ? null : request.getPassword());
meeting.setAccessPassword(password); meetingService.update(new LambdaUpdateWrapper<Meeting>()
meetingService.updateById(meeting); .eq(Meeting::getId,meeting.getId())
.set(Meeting::getAccessPassword, password));
return LegacyApiResponse.ok(new LegacyMeetingAccessPasswordResponse(password)); return LegacyApiResponse.ok(new LegacyMeetingAccessPasswordResponse(password));
} }
@ -235,16 +237,16 @@ public class LegacyMeetingController {
: null; : null;
boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank(); boolean hasSummary = detail != null && detail.getSummaryContent() != null && !detail.getSummaryContent().isBlank();
if (hasSummary) { if (summaryCompleted) {
return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask)); return new LegacyMeetingPreviewResult("200", "success", buildCompletedPreview(meeting, detail, summaryTask));
} }
if (summaryCompleted) { // if (summaryCompleted) {
return new LegacyMeetingPreviewResult( // return new LegacyMeetingPreviewResult(
"504", // "504",
"处理已完成,但摘要尚未同步,请稍后重试", // "处理已完成,但摘要尚未同步,请稍后重试",
buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED)) // buildProcessingPreview(meeting, summaryTask, processingStatus("摘要已生成,可扫码查看", 100, STAGE_COMPLETED))
); // );
} // }
if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) { if (isFailed(asrTask) || Integer.valueOf(4).equals(meeting.getStatus())) {
return new LegacyMeetingPreviewResult( return new LegacyMeetingPreviewResult(
"503", "503",

View File

@ -48,6 +48,7 @@ public class Meeting extends BaseEntity {
private String audioSaveMessage; private String audioSaveMessage;
@Schema(description = "访问密码") @Schema(description = "访问密码")
private String accessPassword; private String accessPassword;
@Schema(description = "创建人ID") @Schema(description = "创建人ID")

View File

@ -1,6 +1,8 @@
package com.imeeting.entity.biz; package com.imeeting.entity.biz;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.unisbase.entity.BaseEntity; import com.unisbase.entity.BaseEntity;
@ -21,6 +23,7 @@ public class ScreenSaver extends BaseEntity {
private String scopeType; private String scopeType;
@Schema(description = "所属用户ID") @Schema(description = "所属用户ID")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private Long ownerUserId; private Long ownerUserId;
@Schema(description = "屏保名称") @Schema(description = "屏保名称")

View File

@ -1,7 +1,10 @@
package com.imeeting.service.biz.impl; package com.imeeting.service.biz.impl;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.imeeting.dto.biz.ScreenSaverAdminVO; import com.imeeting.dto.biz.ScreenSaverAdminVO;
import com.imeeting.dto.biz.ScreenSaverDTO;
import com.imeeting.dto.biz.ScreenSaverSelectionResult; import com.imeeting.dto.biz.ScreenSaverSelectionResult;
import com.imeeting.dto.biz.ScreenSaverUserSettingsDTO; import com.imeeting.dto.biz.ScreenSaverUserSettingsDTO;
import com.imeeting.dto.biz.ScreenSaverUserSettingsVO; import com.imeeting.dto.biz.ScreenSaverUserSettingsVO;
@ -18,6 +21,8 @@ import org.mockito.ArgumentCaptor;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
@ -141,6 +146,38 @@ class ScreenSaverServiceImplTest {
verify(userConfigMapper, never()).insert(any(ScreenSaverUserConfig.class)); verify(userConfigMapper, never()).insert(any(ScreenSaverUserConfig.class));
} }
@Test
void updateShouldClearOwnerWhenPromotingUserScopeToPlatformScope() {
ScreenSaverUserConfigMapper userConfigMapper = mock(ScreenSaverUserConfigMapper.class);
ScreenSaverUserSettingsMapper userSettingsMapper = mock(ScreenSaverUserSettingsMapper.class);
SysUserMapper sysUserMapper = mock(SysUserMapper.class);
ScreenSaverServiceImpl service = spy(new ScreenSaverServiceImpl(userConfigMapper, userSettingsMapper, sysUserMapper));
ScreenSaver existing = screenSaver(101L, "USER", 88L, 1, 1);
doReturn(existing).when(service).getById(101L);
doReturn(true).when(service).updateById(any(ScreenSaver.class));
ScreenSaverDTO dto = new ScreenSaverDTO();
dto.setScopeType("PLATFORM");
ScreenSaver result = service.update(101L, dto, loginUser(88L, 9L, true));
ArgumentCaptor<ScreenSaver> captor = ArgumentCaptor.forClass(ScreenSaver.class);
verify(service).updateById(captor.capture());
assertEquals("PLATFORM", captor.getValue().getScopeType());
assertNull(captor.getValue().getOwnerUserId());
assertEquals("PLATFORM", result.getScopeType());
assertNull(result.getOwnerUserId());
}
@Test
void ownerUserIdShouldAlwaysParticipateInUpdateStatements() throws NoSuchFieldException {
TableField tableField = ScreenSaver.class.getDeclaredField("ownerUserId").getAnnotation(TableField.class);
assertNotNull(tableField);
assertEquals(FieldStrategy.ALWAYS, tableField.updateStrategy());
}
@Test @Test
void getMySettingsShouldFallbackToDefaultDuration() { void getMySettingsShouldFallbackToDefaultDuration() {
ScreenSaverUserConfigMapper userConfigMapper = mock(ScreenSaverUserConfigMapper.class); ScreenSaverUserConfigMapper userConfigMapper = mock(ScreenSaverUserConfigMapper.class);

View File

@ -31,14 +31,14 @@ const { Title } = Typography;
type ModelType = "ASR" | "LLM"; type ModelType = "ASR" | "LLM";
const PROVIDER_BASE_URL_MAP: Record<string, string> = { const PROVIDER_BASE_URL_MAP: Record<string, string> = {
openai: "https://api.openai.com/v1", openai: "https://api.openai.com",
deepseek: "https://api.deepseek.com", deepseek: "https://api.deepseek.com",
aliyun: "https://dashscope.aliyuncs.com/compatible-mode/v1", aliyun: "https://dashscope.aliyuncs.com/compatible-mode",
qwen: "https://dashscope.aliyuncs.com/compatible-mode/v1", qwen: "https://dashscope.aliyuncs.com/compatible-mode",
dashscope: "https://dashscope.aliyuncs.com/compatible-mode/v1", dashscope: "https://dashscope.aliyuncs.com/compatible-mode",
moonshot: "https://api.moonshot.cn/v1", moonshot: "https://api.moonshot.cn",
kimi: "https://api.moonshot.cn/v1", kimi: "https://api.moonshot.cn",
groq: "https://api.groq.com/openai/v1", groq: "https://api.groq.com/openai",
}; };
const DEFAULT_LLM_TEST_MESSAGE = "请回复LLM 连通性测试成功。"; const DEFAULT_LLM_TEST_MESSAGE = "请回复LLM 连通性测试成功。";
@ -526,7 +526,7 @@ const AiModels: React.FC = () => {
</Row> </Row>
<Form.Item name="baseUrl" label="Base URL" rules={[{ required: true, message: "请输入 Base URL" }]}> <Form.Item name="baseUrl" label="Base URL" rules={[{ required: true, message: "请输入 Base URL" }]}>
<Input placeholder="https://api.example.com/v1" /> <Input placeholder="https://api.example.com" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item

View File

@ -740,9 +740,9 @@ export default function ScreenSaverManagement() {
<Button icon={<ReloadOutlined />} onClick={() => void loadData()} loading={loading}> <Button icon={<ReloadOutlined />} onClick={() => void loadData()} loading={loading}>
</Button> </Button>
<Button icon={<SettingOutlined />} onClick={openSettingsModal}> {/*<Button icon={<SettingOutlined />} onClick={openSettingsModal}>*/}
{mySettings.displayDurationSec} / {/* 播放设置({mySettings.displayDurationSec} 秒/张)*/}
</Button> {/*</Button>*/}
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}> <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
</Button> </Button>