feat(prompt): 集成flyway

dev_na
chenhao 2026-08-04 15:50:44 +08:00
parent 44a27dadcb
commit 3931143674
8 changed files with 2883 additions and 17 deletions

View File

@ -188,6 +188,10 @@
<artifactId>tencentcloud-sdk-java-asr</artifactId>
<version>3.1.1470</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,15 +1,25 @@
package com.imeeting.config;
import cn.hutool.core.util.StrUtil;
import com.unisbase.common.ApiResponse;
import com.unisbase.common.exception.BusinessException;
import com.unisbase.common.exception.ErrorCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.stream.Collectors;
@RestControllerAdvice
@Slf4j
public class ApiResponseSuccessCodeAdvice implements ResponseBodyAdvice<Object> {
private static final String LEGACY_SUCCESS_CODE = "0";
@ -32,4 +42,13 @@ public class ApiResponseSuccessCodeAdvice implements ResponseBodyAdvice<Object>
}
return body;
}
@ExceptionHandler({MethodArgumentNotValidException.class})
public ApiResponse<Void> handleBusinessException(MethodArgumentNotValidException ex) {
String msg = ex.getBindingResult().getAllErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining(", "));
return new ApiResponse(ErrorCodeEnum.SYSTEM_ERROR.getCode(), msg, (Object) null);
}
}

View File

@ -37,7 +37,7 @@ public class CreateMeetingCommand {
private Long chapterModelId;
@NotNull(message = "promptId must not be null")
@NotNull(message = "总结模板不能为空")
private Long promptId;
private Long hotWordGroupId;

View File

@ -21,7 +21,8 @@ public class AndroidPushMessageRetryTask {
private final AndroidGatewayPushService androidGatewayPushService;
private final TaskSecurityContextRunner taskSecurityContextRunner;
@Scheduled(fixedDelayString = "${imeeting.android.push.retry-interval-ms:15000}")
@Scheduled(fixedDelayString = "${imeeting.android.push.retry-interval-ms:15000}",
initialDelayString = "${imeeting.android.push.initial-delay-ms:10000}")
public void retryPendingMessages() {
taskSecurityContextRunner.callAsPlatformAdmin(() -> {
List<AndroidPushMessage> pendingMessages = androidPushMessageService.listPendingMeetingPushMessages();

View File

@ -53,6 +53,13 @@ spring:
writetimeout: 5000
# 启用调试日志(生产环境建议关闭)
debug: true
flyway:
enabled: true
locations: classpath:db/migrations
# New databases baseline at 0 and execute V1; manually initialized deployments set version to 1.
baseline-on-migrate: true
baseline-version: ${FLYWAY_BASELINE_VERSION:1}
springdoc:
api-docs:
enabled: true
@ -71,6 +78,10 @@ mybatis-plus:
logic-not-delete-value: 0
unisbase:
flyway:
base:
baseline-enabled: true
baseline-version: ${UNIS_BASELINE_VERSION:0.1.0}
web:
auth-endpoints-enabled: true
management-endpoints-enabled: true

File diff suppressed because it is too large Load Diff

View File

@ -200,24 +200,28 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
const asrRecords = asrRes.data?.data?.records || [];
const llmRecords = llmRes.data?.data?.records || [];
const hotwordRecords = hotwordRes.data?.data?.records || [];
const activeLlmModels = llmRecords.filter((item: AiModelVO) => item.status === 1);
const activePrompts = promptRecords.filter((item: PromptTemplateVO) => item.status === 1);
setCreateConfig(nextConfig);
setConfigLoaded(true);
setType(nextType);
setAsrModels(asrRecords.filter((item: AiModelVO) => item.status === 1));
setLlmModels(llmRecords.filter((item: AiModelVO) => item.status === 1));
setLlmModels(activeLlmModels);
setPrompts(activePrompts);
setHotwordList(hotwordRecords.filter((item: HotWordVO) => item.status === 1));
setHotWordGroups((hotWordGroupRes.data.data || []).filter((item: HotWordGroupVO) => item.status === 1));
setUserList(users || []);
const defaultPrompt = activePrompts.find((item: PromptTemplateVO) => item.isDefault && item.defaultAvailable);
const defaultPrompt = activePrompts.find((item: PromptTemplateVO) => item.isDefault && item.defaultAvailable) || activePrompts[0];
const defaultSummaryModel = activeLlmModels.find((item: AiModelVO) => item.id === defaultLlm.data.data?.id)
|| activeLlmModels.find((item: AiModelVO) => item.isDefault === 1)
|| activeLlmModels[0];
form.setFieldsValue({
title: nextType === "upload" ? `文件会议 ${dayjs().format("MM-DD HH:mm")}` : `实时会议 ${dayjs().format("MM-DD HH:mm")}`,
meetingTime: dayjs(),
asrModelId: defaultAsr.data.data?.id,
summaryModelId: defaultLlm.data.data?.id,
summaryModelId: defaultSummaryModel?.id,
promptId: defaultPrompt?.id,
hotWordGroupId: defaultPrompt?.hotWordGroupId ?? 0,
summaryDetailLevel: "STANDARD",
@ -295,6 +299,7 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
message.warning("当前入口已关闭,已切换到可用创建方式");
return;
}
if (type === "upload" && !audioUrl) {
message.error("请先上传录音文件");
return;
@ -308,6 +313,10 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
return;
}
}
if (!values.promptId) {
message.error("总结模板为空");
return;
}
setSubmitting(true);
try {
@ -520,7 +529,6 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
<Form.Item
name="promptId"
label="总结模板"
extra="未选择时,系统会按当前可用模板规则自动选择"
>
{prompts.length > 15 ? (
<Select allowClear placeholder="请选择总结模板" showSearch optionFilterProp="children">
@ -529,16 +537,6 @@ export const MeetingCreateDrawer: React.FC<MeetingCreateDrawerProps> = ({
) : (
<div className="meeting-create-template-grid">
<Row gutter={[12, 12]}>
<Col xs={24} sm={12} md={8}>
<div
onClick={() => form.setFieldsValue({promptId: undefined})}
className={watchedPromptId == null ? "meeting-create-template-card is-selected" : "meeting-create-template-card"}
>
<div className="meeting-create-template-card__name"></div>
{watchedPromptId == null &&
<div className="meeting-create-template-card__check"><CheckOutlined/></div>}
</div>
</Col>
{prompts.map(p => {
const isSelected = watchedPromptId === p.id;
return (

View File

@ -99,7 +99,7 @@
.permissions-name-cell {
display: flex;
width: 100%;
width: 80%;
min-width: 0;
}