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;
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.ObjectMapper;
import com.imeeting.common.RedisKeys;
@ -185,8 +186,9 @@ public class AndroidMeetingController {
return ApiResponse.error("仅会议创建人可设置访问密码");
}
String password = normalizePassword(command == null ? null : command.getPassword());
meeting.setAccessPassword(password);
meetingService.updateById(meeting);
meetingService.update(new LambdaUpdateWrapper<Meeting>()
.eq(Meeting::getId,meeting.getId())
.set(Meeting::getAccessPassword, password));
return ApiResponse.ok(password);
}

View File

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

View File

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

View File

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

View File

@ -1,7 +1,10 @@
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.imeeting.dto.biz.ScreenSaverAdminVO;
import com.imeeting.dto.biz.ScreenSaverDTO;
import com.imeeting.dto.biz.ScreenSaverSelectionResult;
import com.imeeting.dto.biz.ScreenSaverUserSettingsDTO;
import com.imeeting.dto.biz.ScreenSaverUserSettingsVO;
@ -18,6 +21,8 @@ import org.mockito.ArgumentCaptor;
import java.util.List;
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.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
@ -141,6 +146,38 @@ class ScreenSaverServiceImplTest {
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
void getMySettingsShouldFallbackToDefaultDuration() {
ScreenSaverUserConfigMapper userConfigMapper = mock(ScreenSaverUserConfigMapper.class);

View File

@ -31,14 +31,14 @@ const { Title } = Typography;
type ModelType = "ASR" | "LLM";
const PROVIDER_BASE_URL_MAP: Record<string, string> = {
openai: "https://api.openai.com/v1",
openai: "https://api.openai.com",
deepseek: "https://api.deepseek.com",
aliyun: "https://dashscope.aliyuncs.com/compatible-mode/v1",
qwen: "https://dashscope.aliyuncs.com/compatible-mode/v1",
dashscope: "https://dashscope.aliyuncs.com/compatible-mode/v1",
moonshot: "https://api.moonshot.cn/v1",
kimi: "https://api.moonshot.cn/v1",
groq: "https://api.groq.com/openai/v1",
aliyun: "https://dashscope.aliyuncs.com/compatible-mode",
qwen: "https://dashscope.aliyuncs.com/compatible-mode",
dashscope: "https://dashscope.aliyuncs.com/compatible-mode",
moonshot: "https://api.moonshot.cn",
kimi: "https://api.moonshot.cn",
groq: "https://api.groq.com/openai",
};
const DEFAULT_LLM_TEST_MESSAGE = "请回复LLM 连通性测试成功。";
@ -526,7 +526,7 @@ const AiModels: React.FC = () => {
</Row>
<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

View File

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