feat: 重构会议和任务模型,优化ASR和总结任务处理逻辑
- 移除 `Meeting` 实体中的 `asrModelId`, `summaryModelId`, `promptContent`, `useSpkId`, `hotWords`, `summaryContent` 字段,新增 `latestSummaryTaskId` 字段 - 更新数据库表结构,移除相关字段并添加 `latest_summary_task_id` 字段 - 在 `AiTask` 实体中新增 `task_config` 和 `result_file_path` 字段 - 重构 `AiTaskServiceImpl` 中的 ASR 和总结任务处理逻辑,支持任务配置和结果文件路径 - 重构 `MeetingServiceImpl` 中的会议创建和更新逻辑,分离 ASR 和总结任务 - 优化 `PromptTemplateServiceImpl`,新增用户模板状态管理功能dev_na
parent
d554849e8e
commit
86b3616786
|
|
@ -316,10 +316,8 @@
|
|||
| id | BIGSERIAL | PK | 主键ID |
|
||||
| tenant_id | BIGINT | NOT NULL | 租户ID |
|
||||
| title | VARCHAR(200) | NOT NULL | 会议标题 |
|
||||
| asr_model_id | BIGINT | | 使用的 ASR 模型 |
|
||||
| summary_model_id | BIGINT | | 使用的 LLM 模型 |
|
||||
| prompt_content | TEXT | | **[快照]** 发起任务时的提示词模板内容 |
|
||||
| summary_content | TEXT | | **[固化]** 最终生成的 Markdown 总结内容 |
|
||||
| audio_url | VARCHAR(500) | | 专属音频路径 |
|
||||
| latest_summary_task_id | BIGINT | | 最新成功的总结任务ID |
|
||||
| status | SMALLINT | DEFAULT 0 | 0:待处理, 1:识别中, 2:总结中, 3:已完成, 4:失败 |
|
||||
|
||||
### 5.6 `biz_meeting_transcripts`(转录明细表)
|
||||
|
|
@ -335,8 +333,11 @@
|
|||
| 字段 | 类型 | 约束 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| id | BIGSERIAL | PK | 主键ID |
|
||||
| meeting_id | BIGINT | NOT NULL | 关联会议ID |
|
||||
| task_type | VARCHAR(20) | | ASR / SUMMARY |
|
||||
| request_data | JSONB | | 请求原始数据 |
|
||||
| response_data | JSONB | | 响应原始数据 |
|
||||
| task_config | TEXT | | **[快照]** 任务配置(模型ID、提示词模板等) |
|
||||
| result_file_path | VARCHAR(500) | | 结果文件相对路径 (如MD总结文件) |
|
||||
| status | SMALLINT | | 0:排队, 1:处理中, 2:成功, 3:失败 |
|
||||
|
||||
|
|
|
|||
|
|
@ -341,12 +341,7 @@ CREATE TABLE biz_meetings (
|
|||
audio_url VARCHAR(500),
|
||||
creator_id BIGINT, -- 发起人ID
|
||||
creator_name VARCHAR(100), -- 发起人姓名
|
||||
asr_model_id BIGINT, -- ASR模型ID
|
||||
summary_model_id BIGINT, -- LLM模型ID
|
||||
prompt_content TEXT, -- 发起任务时的提示词模板快照
|
||||
use_spk_id SMALLINT DEFAULT 1, -- 是否开启声纹识别 (1:开启, 0:关闭)
|
||||
hot_words text, -- 任务发起时的热词快照
|
||||
summary_content TEXT, -- Markdown 总结结果
|
||||
latest_summary_task_id BIGINT, -- 最新成功总结任务ID
|
||||
status SMALLINT DEFAULT 0, -- 0:待处理, 1:处理中, 2:成功, 3:失败
|
||||
created_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP(6) NOT NULL DEFAULT now(),
|
||||
|
|
@ -381,6 +376,8 @@ CREATE TABLE biz_ai_tasks (
|
|||
status SMALLINT DEFAULT 0, -- 0:排队, 1:执行中, 2:成功, 3:失败
|
||||
request_data text, -- 请求三方原始JSON
|
||||
response_data text, -- 三方返回原始JSON
|
||||
task_config text, -- 任务配置参数快照
|
||||
result_file_path VARCHAR(500), -- 结果文件路径
|
||||
error_msg TEXT, -- 错误堆栈
|
||||
started_at TIMESTAMP(6),
|
||||
completed_at TIMESTAMP(6)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.imeeting.dto.biz.MeetingVO;
|
|||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.security.LoginUser;
|
||||
import com.imeeting.service.biz.MeetingService;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import org.apache.fontbox.ttf.TrueTypeCollection;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
|
|
@ -54,13 +55,16 @@ import java.util.regex.Pattern;
|
|||
public class MeetingController {
|
||||
|
||||
private final MeetingService meetingService;
|
||||
private final PromptTemplateService promptTemplateService;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
private final String uploadPath;
|
||||
|
||||
public MeetingController(MeetingService meetingService,
|
||||
PromptTemplateService promptTemplateService,
|
||||
StringRedisTemplate redisTemplate,
|
||||
@Value("${app.upload-path}") String uploadPath) {
|
||||
this.meetingService = meetingService;
|
||||
this.promptTemplateService = promptTemplateService;
|
||||
this.redisTemplate = redisTemplate;
|
||||
this.uploadPath = uploadPath;
|
||||
}
|
||||
|
|
@ -113,6 +117,18 @@ public class MeetingController {
|
|||
@PreAuthorize("isAuthenticated()")
|
||||
public ApiResponse<MeetingVO> create(@RequestBody MeetingDTO dto) {
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if (dto.getPromptId() != null) {
|
||||
boolean enabled = promptTemplateService.isTemplateEnabledForUser(
|
||||
dto.getPromptId(),
|
||||
loginUser.getTenantId(),
|
||||
loginUser.getUserId(),
|
||||
loginUser.getIsPlatformAdmin(),
|
||||
loginUser.getIsTenantAdmin()
|
||||
);
|
||||
if (!enabled) {
|
||||
return ApiResponse.error("总结模板不可用或已被你禁用");
|
||||
}
|
||||
}
|
||||
dto.setTenantId(loginUser.getTenantId());
|
||||
dto.setCreatorId(loginUser.getUserId());
|
||||
dto.setCreatorName(loginUser.getDisplayName() != null ? loginUser.getDisplayName() : loginUser.getUsername());
|
||||
|
|
@ -205,9 +221,20 @@ public class MeetingController {
|
|||
@PostMapping("/re-summary")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public ApiResponse<Boolean> reSummary(@RequestBody Map<String, Object> params) {
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
Long meetingId = Long.valueOf(params.get("meetingId").toString());
|
||||
Long summaryModelId = Long.valueOf(params.get("summaryModelId").toString());
|
||||
Long promptId = Long.valueOf(params.get("promptId").toString());
|
||||
boolean enabled = promptTemplateService.isTemplateEnabledForUser(
|
||||
promptId,
|
||||
loginUser.getTenantId(),
|
||||
loginUser.getUserId(),
|
||||
loginUser.getIsPlatformAdmin(),
|
||||
loginUser.getIsTenantAdmin()
|
||||
);
|
||||
if (!enabled) {
|
||||
return ApiResponse.error("总结模板不可用或已被你禁用");
|
||||
}
|
||||
|
||||
meetingService.reSummary(meetingId, summaryModelId, promptId);
|
||||
return ApiResponse.ok(true);
|
||||
|
|
|
|||
|
|
@ -27,27 +27,20 @@ public class PromptTemplateController {
|
|||
@PreAuthorize("isAuthenticated()")
|
||||
public ApiResponse<PromptTemplateVO> save(@RequestBody PromptTemplateDTO dto) {
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
|
||||
// 权限校验逻辑
|
||||
|
||||
if (Integer.valueOf(1).equals(dto.getIsSystem())) {
|
||||
// 只有平台管理员能创建平台级模板(tenantId=0)
|
||||
// 只有租户管理员能创建租户级模板(tenantId>0)
|
||||
if (!Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()) && !Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
return ApiResponse.error("无权创建系统模板");
|
||||
return ApiResponse.error("No permission to create public template");
|
||||
}
|
||||
|
||||
// 如果是租户管理员创建系统模板,确保 tenantId 是其所属租户
|
||||
if (!Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
dto.setTenantId(loginUser.getTenantId());
|
||||
} else {
|
||||
// 平台管理员:如果 DTO 没传 tenantId,默认设为 0
|
||||
if (dto.getTenantId() == null) dto.setTenantId(0L);
|
||||
} else if (dto.getTenantId() == null) {
|
||||
dto.setTenantId(0L);
|
||||
}
|
||||
} else {
|
||||
// 普通模板
|
||||
dto.setTenantId(loginUser.getTenantId());
|
||||
}
|
||||
|
||||
|
||||
return ApiResponse.ok(promptTemplateService.saveTemplate(dto, loginUser.getUserId(), loginUser.getTenantId()));
|
||||
}
|
||||
|
||||
|
|
@ -57,26 +50,24 @@ public class PromptTemplateController {
|
|||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
PromptTemplate existing = promptTemplateService.getById(dto.getId());
|
||||
if (existing == null) {
|
||||
return ApiResponse.error("模板不存在");
|
||||
return ApiResponse.error("Template not found");
|
||||
}
|
||||
|
||||
// 核心权限判定
|
||||
|
||||
boolean canModify = false;
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
// 平台管理员只能修改平台级模板 (tenantId = 0)
|
||||
canModify = existing.getTenantId() == 0L;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
// 租户管理员可以修改本租户的所有模板 (租户预置 + 个人模板)
|
||||
canModify = existing.getTenantId().equals(loginUser.getTenantId());
|
||||
} else {
|
||||
// 普通用户仅限自己的个人模板
|
||||
if (Integer.valueOf(0).equals(existing.getIsSystem())) {
|
||||
canModify = existing.getCreatorId().equals(loginUser.getUserId());
|
||||
} else {
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
canModify = existing.getTenantId() == 0L;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
canModify = existing.getTenantId().equals(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!canModify) {
|
||||
return ApiResponse.error("无权修改此模板");
|
||||
return ApiResponse.error("No permission to modify this template");
|
||||
}
|
||||
|
||||
|
||||
return ApiResponse.ok(promptTemplateService.updateTemplate(dto));
|
||||
}
|
||||
|
||||
|
|
@ -85,23 +76,36 @@ public class PromptTemplateController {
|
|||
public ApiResponse<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
PromptTemplate existing = promptTemplateService.getById(id);
|
||||
if (existing == null) return ApiResponse.error("模板不存在");
|
||||
|
||||
boolean canModify = false;
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
canModify = existing.getTenantId() == 0L;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
canModify = existing.getTenantId().equals(loginUser.getTenantId());
|
||||
} else {
|
||||
canModify = existing.getCreatorId().equals(loginUser.getUserId());
|
||||
if (existing == null) {
|
||||
return ApiResponse.error("Template not found");
|
||||
}
|
||||
|
||||
if (!canModify) {
|
||||
return ApiResponse.error("无权修改此模板");
|
||||
boolean canGlobalModify = false;
|
||||
if (Integer.valueOf(1).equals(existing.getIsSystem())) {
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin()) && Long.valueOf(0L).equals(existing.getTenantId())) {
|
||||
canGlobalModify = true;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin()) && existing.getTenantId().equals(loginUser.getTenantId())) {
|
||||
canGlobalModify = true;
|
||||
}
|
||||
}
|
||||
|
||||
existing.setStatus(status);
|
||||
return ApiResponse.ok(promptTemplateService.updateById(existing));
|
||||
if (canGlobalModify) {
|
||||
existing.setStatus(status);
|
||||
return ApiResponse.ok(promptTemplateService.updateById(existing));
|
||||
}
|
||||
|
||||
boolean success = promptTemplateService.updateUserTemplateStatus(
|
||||
id,
|
||||
status,
|
||||
loginUser.getTenantId(),
|
||||
loginUser.getUserId(),
|
||||
loginUser.getIsPlatformAdmin(),
|
||||
loginUser.getIsTenantAdmin()
|
||||
);
|
||||
if (!success) {
|
||||
return ApiResponse.error("Template not found or no permission");
|
||||
}
|
||||
return ApiResponse.ok(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
|
|
@ -114,16 +118,18 @@ public class PromptTemplateController {
|
|||
}
|
||||
|
||||
boolean canModify = false;
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
canModify = existing.getTenantId() == 0L;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
canModify = existing.getTenantId().equals(loginUser.getTenantId());
|
||||
} else {
|
||||
if (Integer.valueOf(0).equals(existing.getIsSystem())) {
|
||||
canModify = existing.getCreatorId().equals(loginUser.getUserId());
|
||||
} else {
|
||||
if (Boolean.TRUE.equals(loginUser.getIsPlatformAdmin())) {
|
||||
canModify = existing.getTenantId() == 0L;
|
||||
} else if (Boolean.TRUE.equals(loginUser.getIsTenantAdmin())) {
|
||||
canModify = existing.getTenantId().equals(loginUser.getTenantId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!canModify) {
|
||||
return ApiResponse.error("无权删除此模板");
|
||||
return ApiResponse.error("No permission to delete this template");
|
||||
}
|
||||
|
||||
return ApiResponse.ok(promptTemplateService.removeById(id));
|
||||
|
|
@ -136,11 +142,16 @@ public class PromptTemplateController {
|
|||
@RequestParam(defaultValue = "10") Integer size,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(required = false) String category) {
|
||||
|
||||
|
||||
LoginUser loginUser = (LoginUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
return ApiResponse.ok(promptTemplateService.pageTemplates(
|
||||
current, size, name, category,
|
||||
loginUser.getTenantId(), loginUser.getUserId(),
|
||||
loginUser.getIsPlatformAdmin(), loginUser.getIsTenantAdmin()));
|
||||
current,
|
||||
size,
|
||||
name,
|
||||
category,
|
||||
loginUser.getTenantId(),
|
||||
loginUser.getUserId(),
|
||||
loginUser.getIsPlatformAdmin(),
|
||||
loginUser.getIsTenantAdmin()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ public class MeetingVO {
|
|||
|
||||
private String participants;
|
||||
private String tags;
|
||||
private Integer useSpkId;
|
||||
private String audioUrl;
|
||||
private String summaryContent;
|
||||
private Integer status;
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ public class AiTask {
|
|||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<String, Object> responseData;
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<String, Object> taskConfig;
|
||||
|
||||
private String resultFilePath;
|
||||
|
||||
private String errorMsg;
|
||||
|
||||
private LocalDateTime startedAt;
|
||||
|
|
|
|||
|
|
@ -33,16 +33,5 @@ public class Meeting extends BaseEntity {
|
|||
|
||||
private String creatorName;
|
||||
|
||||
private Long asrModelId;
|
||||
|
||||
private Long summaryModelId;
|
||||
|
||||
private String promptContent;
|
||||
|
||||
private Integer useSpkId;
|
||||
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private List<String> hotWords;
|
||||
|
||||
private String summaryContent;
|
||||
private Long latestSummaryTaskId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
|||
import com.imeeting.dto.biz.PromptTemplateDTO;
|
||||
import com.imeeting.dto.biz.PromptTemplateVO;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.imeeting.common.PageResult;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -14,4 +13,6 @@ public interface PromptTemplateService extends IService<PromptTemplate> {
|
|||
PromptTemplateVO updateTemplate(PromptTemplateDTO dto);
|
||||
PageResult<List<PromptTemplateVO>> pageTemplates(Integer current, Integer size, String name, String category,
|
||||
Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin);
|
||||
boolean updateUserTemplateStatus(Long templateId, Integer status, Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin);
|
||||
boolean isTemplateEnabledForUser(Long templateId, Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ import java.net.http.HttpClient;
|
|||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
|
@ -54,6 +57,9 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
@Value("${app.server-base-url}")
|
||||
private String serverBaseUrl;
|
||||
|
||||
@Value("${app.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
private final HttpClient httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
|
@ -72,11 +78,34 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
Meeting meeting = meetingMapper.selectById(meetingId);
|
||||
if (meeting == null) return;
|
||||
|
||||
// 1. 执行 ASR 识别 (含接管逻辑)
|
||||
String asrText = processAsrTask(meeting);
|
||||
|
||||
// 2. 执行 LLM 总结
|
||||
processSummaryTask(meeting, asrText);
|
||||
AiTask asrTask = this.getOne(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meetingId)
|
||||
.eq(AiTask::getTaskType, "ASR")
|
||||
.orderByDesc(AiTask::getId)
|
||||
.last("limit 1"));
|
||||
|
||||
String asrText = "";
|
||||
if (asrTask != null && asrTask.getStatus() == 0) {
|
||||
asrText = processAsrTask(meeting, asrTask);
|
||||
} else {
|
||||
List<MeetingTranscript> transcripts = transcriptMapper.selectList(new LambdaQueryWrapper<MeetingTranscript>()
|
||||
.eq(MeetingTranscript::getMeetingId, meetingId)
|
||||
.orderByAsc(MeetingTranscript::getStartTime));
|
||||
asrText = transcripts.stream()
|
||||
.map(t -> (t.getSpeakerName() != null ? t.getSpeakerName() : t.getSpeakerId()) + ": " + t.getContent())
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
AiTask sumTask = this.getOne(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meetingId)
|
||||
.eq(AiTask::getTaskType, "SUMMARY")
|
||||
.orderByDesc(AiTask::getId)
|
||||
.last("limit 1"));
|
||||
if (sumTask != null && sumTask.getStatus() == 0) {
|
||||
processSummaryTask(meeting, asrText, sumTask);
|
||||
} else if (meeting.getStatus() != 3) {
|
||||
updateMeetingStatus(meetingId, 3);
|
||||
}
|
||||
|
||||
redisTemplate.delete(RedisKeys.meetingProgressKey(meetingId));
|
||||
} catch (Exception e) {
|
||||
|
|
@ -106,7 +135,14 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
.map(t -> (t.getSpeakerName() != null ? t.getSpeakerName() : t.getSpeakerId()) + ": " + t.getContent())
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
processSummaryTask(meeting, asrText);
|
||||
AiTask sumTask = this.getOne(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meetingId)
|
||||
.eq(AiTask::getTaskType, "SUMMARY")
|
||||
.orderByDesc(AiTask::getId)
|
||||
.last("limit 1"));
|
||||
if (sumTask != null && sumTask.getStatus() == 0) {
|
||||
processSummaryTask(meeting, asrText, sumTask);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Re-summary failed for meeting {}", meetingId, e);
|
||||
updateMeetingStatus(meetingId, 4);
|
||||
|
|
@ -114,60 +150,34 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
}
|
||||
}
|
||||
|
||||
private String processAsrTask(Meeting meeting) throws Exception {
|
||||
private String processAsrTask(Meeting meeting, AiTask taskRecord) throws Exception {
|
||||
updateMeetingStatus(meeting.getId(), 1);
|
||||
|
||||
AiModel asrModel = aiModelService.getById(meeting.getAsrModelId());
|
||||
taskRecord.setStatus(1);
|
||||
taskRecord.setStartedAt(LocalDateTime.now());
|
||||
this.updateById(taskRecord);
|
||||
|
||||
Long asrModelId = Long.valueOf(taskRecord.getTaskConfig().get("asrModelId").toString());
|
||||
AiModel asrModel = aiModelService.getById(asrModelId);
|
||||
if (asrModel == null) throw new RuntimeException("ASR模型配置不存在");
|
||||
|
||||
String submitUrl = asrModel.getBaseUrl().endsWith("/") ? asrModel.getBaseUrl() + "api/tasks/recognition" : asrModel.getBaseUrl() + "/api/tasks/recognition";
|
||||
String taskId = null;
|
||||
AiTask taskRecord = null;
|
||||
|
||||
// --- 核心:接管逻辑 ---
|
||||
List<AiTask> existingTasks = this.list(new LambdaQueryWrapper<AiTask>()
|
||||
.eq(AiTask::getMeetingId, meeting.getId())
|
||||
.eq(AiTask::getTaskType, "ASR")
|
||||
.orderByDesc(AiTask::getStartedAt));
|
||||
|
||||
if (!existingTasks.isEmpty()) {
|
||||
AiTask lastTask = existingTasks.get(0);
|
||||
if (lastTask.getResponseData() != null && lastTask.getResponseData().get("task_id") != null) {
|
||||
String potentialTaskId = lastTask.getResponseData().get("task_id").toString();
|
||||
log.info("Attempting to resume ASR task {} for meeting {}", potentialTaskId, meeting.getId());
|
||||
|
||||
// 验证旧 taskId 是否依然有效
|
||||
String checkUrl = asrModel.getBaseUrl().endsWith("/") ? asrModel.getBaseUrl() + "api/tasks/" + potentialTaskId : asrModel.getBaseUrl() + "/api/tasks/" + potentialTaskId;
|
||||
try {
|
||||
String checkResp = get(checkUrl);
|
||||
JsonNode node = objectMapper.readTree(checkResp);
|
||||
if (node.path("code").asInt() == 200) {
|
||||
taskId = potentialTaskId;
|
||||
taskRecord = lastTask;
|
||||
log.info("Successfully resumed ASR task {}", taskId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Old task ID {} is invalid, will start a new one", potentialTaskId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有可接管的任务,则发起新任务
|
||||
if (taskId == null) {
|
||||
updateProgress(meeting.getId(), 5, "正在提交识别请求...", 0);
|
||||
Map<String, Object> req = buildAsrRequest(meeting);
|
||||
taskRecord = createAiTask(meeting.getId(), "ASR", req);
|
||||
|
||||
String respBody = postJson(submitUrl, req);
|
||||
JsonNode submitNode = objectMapper.readTree(respBody);
|
||||
if (submitNode.path("code").asInt() != 200) {
|
||||
updateAiTaskFail(taskRecord, "Submission Failed: " + respBody);
|
||||
throw new RuntimeException("ASR引擎拒绝请求: " + submitNode.path("msg").asText());
|
||||
}
|
||||
taskId = submitNode.path("data").path("task_id").asText();
|
||||
taskRecord.setResponseData(Map.of("task_id", taskId));
|
||||
this.updateById(taskRecord);
|
||||
updateProgress(meeting.getId(), 5, "正在提交识别请求...", 0);
|
||||
Map<String, Object> req = buildAsrRequest(meeting, taskRecord);
|
||||
taskRecord.setRequestData(req);
|
||||
this.updateById(taskRecord);
|
||||
|
||||
String respBody = postJson(submitUrl, req);
|
||||
JsonNode submitNode = objectMapper.readTree(respBody);
|
||||
if (submitNode.path("code").asInt() != 200) {
|
||||
updateAiTaskFail(taskRecord, "Submission Failed: " + respBody);
|
||||
throw new RuntimeException("ASR引擎拒绝请求: " + submitNode.path("msg").asText());
|
||||
}
|
||||
taskId = submitNode.path("data").path("task_id").asText();
|
||||
taskRecord.setResponseData(Map.of("task_id", taskId));
|
||||
this.updateById(taskRecord);
|
||||
|
||||
String queryUrl = asrModel.getBaseUrl().endsWith("/") ? asrModel.getBaseUrl() + "api/tasks/" + taskId : asrModel.getBaseUrl() + "/api/tasks/" + taskId;
|
||||
|
||||
|
|
@ -210,7 +220,7 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
return saveTranscripts(meeting, resultNode);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildAsrRequest(Meeting meeting) {
|
||||
private Map<String, Object> buildAsrRequest(Meeting meeting, AiTask taskRecord) {
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
String rawAudioUrl = meeting.getAudioUrl();
|
||||
String encodedAudioUrl = Arrays.stream(rawAudioUrl.split("/"))
|
||||
|
|
@ -220,15 +230,22 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
})
|
||||
.collect(Collectors.joining("/"));
|
||||
req.put("file_path", serverBaseUrl + (encodedAudioUrl.startsWith("/") ? "" : "/") + encodedAudioUrl);
|
||||
req.put("use_spk_id", meeting.getUseSpkId() != null && meeting.getUseSpkId() == 1);
|
||||
|
||||
Object useSpkObj = taskRecord.getTaskConfig().get("useSpkId");
|
||||
boolean useSpk = useSpkObj != null && useSpkObj.toString().equals("1");
|
||||
req.put("use_spk_id", useSpk);
|
||||
|
||||
List<Map<String, Object>> hotwords = new ArrayList<>();
|
||||
if (meeting.getHotWords() != null && !meeting.getHotWords().isEmpty()) {
|
||||
List<HotWord> entities = hotWordService.list(new LambdaQueryWrapper<HotWord>()
|
||||
.eq(HotWord::getTenantId, meeting.getTenantId()).in(HotWord::getWord, meeting.getHotWords()));
|
||||
Map<String, Integer> weightMap = entities.stream().collect(Collectors.toMap(HotWord::getWord, HotWord::getWeight, (v1, v2) -> v1));
|
||||
for (String w : meeting.getHotWords()) {
|
||||
hotwords.add(Map.of("hotword", w, "weight", weightMap.getOrDefault(w, 10) / 10.0));
|
||||
Object hotWordsObj = taskRecord.getTaskConfig().get("hotWords");
|
||||
if (hotWordsObj instanceof List) {
|
||||
List<String> words = (List<String>) hotWordsObj;
|
||||
if (!words.isEmpty()) {
|
||||
List<HotWord> entities = hotWordService.list(new LambdaQueryWrapper<HotWord>()
|
||||
.eq(HotWord::getTenantId, meeting.getTenantId()).in(HotWord::getWord, words));
|
||||
Map<String, Integer> weightMap = entities.stream().collect(Collectors.toMap(HotWord::getWord, HotWord::getWeight, (v1, v2) -> v1));
|
||||
for (String w : words) {
|
||||
hotwords.add(Map.of("hotword", w, "weight", weightMap.getOrDefault(w, 10) / 10.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
req.put("hotwords", hotwords);
|
||||
|
|
@ -272,22 +289,32 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
return sb.toString();
|
||||
}
|
||||
|
||||
private void processSummaryTask(Meeting meeting, String asrText) throws Exception {
|
||||
private void processSummaryTask(Meeting meeting, String asrText, AiTask taskRecord) throws Exception {
|
||||
updateMeetingStatus(meeting.getId(), 2);
|
||||
updateProgress(meeting.getId(), 90, "正在生成智能总结纪要...", 0);
|
||||
|
||||
AiModel llmModel = aiModelService.getById(meeting.getSummaryModelId());
|
||||
taskRecord.setStatus(1);
|
||||
taskRecord.setStartedAt(LocalDateTime.now());
|
||||
this.updateById(taskRecord);
|
||||
|
||||
Long summaryModelId = Long.valueOf(taskRecord.getTaskConfig().get("summaryModelId").toString());
|
||||
AiModel llmModel = aiModelService.getById(summaryModelId);
|
||||
if (llmModel == null) return;
|
||||
|
||||
String promptContent = taskRecord.getTaskConfig().get("promptContent") != null ?
|
||||
taskRecord.getTaskConfig().get("promptContent").toString() : "";
|
||||
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
req.put("model", llmModel.getModelCode());
|
||||
req.put("temperature", llmModel.getTemperature());
|
||||
req.put("messages", List.of(
|
||||
Map.of("role", "system", "content", meeting.getPromptContent()),
|
||||
Map.of("role", "system", "content", promptContent),
|
||||
Map.of("role", "user", "content", "请总结以下会议内容:\n" + asrText)
|
||||
));
|
||||
|
||||
AiTask taskRecord = createAiTask(meeting.getId(), "SUMMARY", req);
|
||||
taskRecord.setRequestData(req);
|
||||
this.updateById(taskRecord);
|
||||
|
||||
String url = llmModel.getBaseUrl() + (llmModel.getApiPath() != null ? llmModel.getApiPath() : "/v1/chat/completions");
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
|
|
@ -302,10 +329,28 @@ public class AiTaskServiceImpl extends ServiceImpl<AiTaskMapper, AiTask> impleme
|
|||
|
||||
if (response.statusCode() == 200 && respNode.has("choices")) {
|
||||
String content = respNode.path("choices").path(0).path("message").path("content").asText();
|
||||
meeting.setSummaryContent(content);
|
||||
|
||||
// Save to File
|
||||
String timestamp = java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now());
|
||||
String fileName = "summary_" + timestamp + ".md";
|
||||
String basePath = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||
Path targetDir = Paths.get(basePath, "meetings", String.valueOf(meeting.getId()), "summaries");
|
||||
Files.createDirectories(targetDir);
|
||||
Path filePath = targetDir.resolve(fileName);
|
||||
|
||||
String frontMatter = "---\n" +
|
||||
"生成时间: " + LocalDateTime.now() + "\n" +
|
||||
"使用模型: " + llmModel.getModelName() + "\n" +
|
||||
"---\n\n";
|
||||
Files.writeString(filePath, frontMatter + content, StandardCharsets.UTF_8);
|
||||
|
||||
taskRecord.setResultFilePath("meetings/" + meeting.getId() + "/summaries/" + fileName);
|
||||
updateAiTaskSuccess(taskRecord, respNode);
|
||||
|
||||
meeting.setLatestSummaryTaskId(taskRecord.getId());
|
||||
meeting.setStatus(3);
|
||||
meetingMapper.updateById(meeting);
|
||||
updateAiTaskSuccess(taskRecord, respNode);
|
||||
|
||||
updateProgress(meeting.getId(), 100, "全流程分析完成", 0);
|
||||
} else {
|
||||
updateAiTaskFail(taskRecord, "LLM Summary failed: " + response.body());
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.imeeting.dto.biz.MeetingVO;
|
|||
import com.imeeting.dto.biz.MeetingTranscriptVO;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.AiModel;
|
||||
import com.imeeting.entity.biz.AiTask;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.entity.biz.MeetingTranscript;
|
||||
import com.imeeting.entity.biz.HotWord;
|
||||
|
|
@ -24,16 +25,27 @@ import com.imeeting.service.biz.PromptTemplateService;
|
|||
import com.imeeting.service.biz.AiTaskService;
|
||||
import com.imeeting.service.biz.HotWordService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> implements MeetingService {
|
||||
|
|
@ -46,6 +58,9 @@ public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> impl
|
|||
private final SysUserMapper sysUserMapper;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Value("${app.upload-path}")
|
||||
private String uploadPath;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MeetingVO createMeeting(MeetingDTO dto) {
|
||||
|
|
@ -54,31 +69,80 @@ public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> impl
|
|||
meeting.setMeetingTime(dto.getMeetingTime());
|
||||
meeting.setParticipants(dto.getParticipants());
|
||||
meeting.setTags(dto.getTags());
|
||||
meeting.setAudioUrl(dto.getAudioUrl());
|
||||
meeting.setAsrModelId(dto.getAsrModelId());
|
||||
meeting.setSummaryModelId(dto.getSummaryModelId());
|
||||
meeting.setUseSpkId(dto.getUseSpkId() != null ? dto.getUseSpkId() : 1);
|
||||
meeting.setCreatorId(dto.getCreatorId());
|
||||
meeting.setCreatorName(dto.getCreatorName());
|
||||
|
||||
if (dto.getPromptId() != null) {
|
||||
PromptTemplate template = promptTemplateService.getById(dto.getPromptId());
|
||||
if (template != null) {
|
||||
meeting.setPromptContent(template.getPromptContent());
|
||||
meeting.setTenantId(dto.getTenantId() != null ? dto.getTenantId() : 0L);
|
||||
meeting.setStatus(0);
|
||||
|
||||
this.save(meeting); // Save to get meeting ID
|
||||
|
||||
// File moving logic
|
||||
String audioUrl = dto.getAudioUrl();
|
||||
if (audioUrl != null && audioUrl.startsWith("/api/static/audio/")) {
|
||||
try {
|
||||
String fileName = audioUrl.substring(audioUrl.lastIndexOf("/") + 1);
|
||||
String basePath = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
|
||||
Path sourcePath = Paths.get(basePath, "audio", fileName);
|
||||
|
||||
if (Files.exists(sourcePath)) {
|
||||
String ext = "";
|
||||
int dotIdx = fileName.lastIndexOf('.');
|
||||
if (dotIdx > 0) {
|
||||
ext = fileName.substring(dotIdx);
|
||||
}
|
||||
String targetDir = basePath + "meetings/" + meeting.getId();
|
||||
Files.createDirectories(Paths.get(targetDir));
|
||||
Path targetPath = Paths.get(targetDir, "source_audio" + ext);
|
||||
Files.move(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
audioUrl = "/api/static/meetings/" + meeting.getId() + "/source_audio" + ext;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to move audio file for meeting {}", meeting.getId(), e);
|
||||
throw new RuntimeException("文件处理失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
meeting.setAudioUrl(audioUrl);
|
||||
this.updateById(meeting);
|
||||
|
||||
// ASR Task
|
||||
AiTask asrTask = new AiTask();
|
||||
asrTask.setMeetingId(meeting.getId());
|
||||
asrTask.setTaskType("ASR");
|
||||
asrTask.setStatus(0);
|
||||
|
||||
Map<String, Object> asrConfig = new HashMap<>();
|
||||
asrConfig.put("asrModelId", dto.getAsrModelId());
|
||||
asrConfig.put("useSpkId", dto.getUseSpkId() != null ? dto.getUseSpkId() : 1);
|
||||
|
||||
List<String> finalHotWords = dto.getHotWords();
|
||||
if (finalHotWords == null || finalHotWords.isEmpty()) {
|
||||
finalHotWords = hotWordService.list(new LambdaQueryWrapper<HotWord>()
|
||||
.eq(HotWord::getTenantId, dto.getTenantId())
|
||||
.eq(HotWord::getTenantId, meeting.getTenantId())
|
||||
.eq(HotWord::getStatus, 1))
|
||||
.stream().map(HotWord::getWord).collect(Collectors.toList());
|
||||
}
|
||||
meeting.setHotWords(finalHotWords);
|
||||
asrConfig.put("hotWords", finalHotWords);
|
||||
asrTask.setTaskConfig(asrConfig);
|
||||
aiTaskService.save(asrTask);
|
||||
|
||||
// SUMMARY Task
|
||||
AiTask sumTask = new AiTask();
|
||||
sumTask.setMeetingId(meeting.getId());
|
||||
sumTask.setTaskType("SUMMARY");
|
||||
sumTask.setStatus(0);
|
||||
|
||||
Map<String, Object> sumConfig = new HashMap<>();
|
||||
sumConfig.put("summaryModelId", dto.getSummaryModelId());
|
||||
if (dto.getPromptId() != null) {
|
||||
PromptTemplate template = promptTemplateService.getById(dto.getPromptId());
|
||||
if (template != null) {
|
||||
sumConfig.put("promptContent", template.getPromptContent());
|
||||
}
|
||||
}
|
||||
sumTask.setTaskConfig(sumConfig);
|
||||
aiTaskService.save(sumTask);
|
||||
|
||||
meeting.setStatus(0);
|
||||
this.save(meeting);
|
||||
eventPublisher.publishEvent(new MeetingCreatedEvent(meeting.getId()));
|
||||
return toVO(meeting);
|
||||
}
|
||||
|
|
@ -165,13 +229,21 @@ public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> impl
|
|||
Meeting meeting = this.getById(meetingId);
|
||||
if (meeting == null) throw new RuntimeException("Meeting not found");
|
||||
|
||||
meeting.setSummaryModelId(summaryModelId);
|
||||
AiTask sumTask = new AiTask();
|
||||
sumTask.setMeetingId(meetingId);
|
||||
sumTask.setTaskType("SUMMARY");
|
||||
sumTask.setStatus(0);
|
||||
|
||||
Map<String, Object> sumConfig = new HashMap<>();
|
||||
sumConfig.put("summaryModelId", summaryModelId);
|
||||
if (promptId != null) {
|
||||
PromptTemplate template = promptTemplateService.getById(promptId);
|
||||
if (template != null) {
|
||||
meeting.setPromptContent(template.getPromptContent());
|
||||
sumConfig.put("promptContent", template.getPromptContent());
|
||||
}
|
||||
}
|
||||
sumTask.setTaskConfig(sumConfig);
|
||||
aiTaskService.save(sumTask);
|
||||
|
||||
meeting.setStatus(2);
|
||||
this.updateById(meeting);
|
||||
|
|
@ -216,10 +288,8 @@ public class MeetingServiceImpl extends ServiceImpl<MeetingMapper, Meeting> impl
|
|||
vo.setTitle(meeting.getTitle());
|
||||
vo.setMeetingTime(meeting.getMeetingTime());
|
||||
vo.setTags(meeting.getTags());
|
||||
vo.setUseSpkId(meeting.getUseSpkId());
|
||||
vo.setAudioUrl(meeting.getAudioUrl());
|
||||
vo.setStatus(meeting.getStatus());
|
||||
vo.setSummaryContent(meeting.getSummaryContent());
|
||||
vo.setCreatedAt(meeting.getCreatedAt());
|
||||
|
||||
if (meeting.getParticipants() != null && !meeting.getParticipants().isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -7,17 +7,25 @@ import com.imeeting.common.PageResult;
|
|||
import com.imeeting.dto.biz.PromptTemplateDTO;
|
||||
import com.imeeting.dto.biz.PromptTemplateVO;
|
||||
import com.imeeting.entity.biz.PromptTemplate;
|
||||
import com.imeeting.entity.biz.PromptTemplateUserConfig;
|
||||
import com.imeeting.mapper.biz.PromptTemplateMapper;
|
||||
import com.imeeting.mapper.biz.PromptTemplateUserConfigMapper;
|
||||
import com.imeeting.service.biz.PromptTemplateService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper, PromptTemplate> implements PromptTemplateService {
|
||||
|
||||
private final PromptTemplateUserConfigMapper userConfigMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PromptTemplateVO saveTemplate(PromptTemplateDTO dto, Long userId, Long tenantId) {
|
||||
|
|
@ -26,7 +34,6 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
|
|||
|
||||
entity.setCreatorId(userId);
|
||||
|
||||
// 逻辑纠偏:如果是平台管理员设置了 tenantId 为 0,则设为 0;否则强制设为用户当前租户
|
||||
if (dto.getTenantId() != null && dto.getTenantId() == 0L) {
|
||||
entity.setTenantId(0L);
|
||||
} else {
|
||||
|
|
@ -35,10 +42,9 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
|
|||
|
||||
entity.setUsageCount(0);
|
||||
this.save(entity);
|
||||
return toVO(entity);
|
||||
return toVO(entity, entity.getStatus());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PromptTemplateVO updateTemplate(PromptTemplateDTO dto) {
|
||||
|
|
@ -48,54 +54,113 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
|
|||
}
|
||||
copyProperties(dto, entity);
|
||||
this.updateById(entity);
|
||||
return toVO(entity);
|
||||
return toVO(entity, entity.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<List<PromptTemplateVO>> pageTemplates(Integer current, Integer size, String name, String category,
|
||||
Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin) {
|
||||
|
||||
LambdaQueryWrapper<PromptTemplate> wrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 核心过滤逻辑:分层可见性 (精细化处理)
|
||||
if (Boolean.TRUE.equals(isPlatformAdmin)) {
|
||||
// 平台管理员:可以看到所有平台级模板 (tenantId=0)
|
||||
wrapper.eq(PromptTemplate::getTenantId, 0L);
|
||||
} else if (Boolean.TRUE.equals(isTenantAdmin)) {
|
||||
// 租户管理员:
|
||||
// 1. 本租户所有模板 (tenantId=currentTenantId)
|
||||
// 2. 平台预置 (tenantId=0 & isSystem=1)
|
||||
wrapper.and(w -> w
|
||||
.eq(PromptTemplate::getTenantId, tenantId)
|
||||
.or(sw -> sw.eq(PromptTemplate::getTenantId, 0L).eq(PromptTemplate::getIsSystem, 1))
|
||||
);
|
||||
} else {
|
||||
// 普通个人用户:
|
||||
// 1. 个人创建 (creatorId=currentUserId)
|
||||
// 2. 平台预置 (tenantId=0 & isSystem=1)
|
||||
// 3. 租户预置 (tenantId=currentTenantId & isSystem=1)
|
||||
wrapper.and(w -> w
|
||||
.eq(PromptTemplate::getCreatorId, userId)
|
||||
.or(sw -> sw.eq(PromptTemplate::getTenantId, 0L).eq(PromptTemplate::getIsSystem, 1))
|
||||
.or(sw -> sw.eq(PromptTemplate::getTenantId, tenantId).eq(PromptTemplate::getIsSystem, 1))
|
||||
);
|
||||
}
|
||||
public PageResult<List<PromptTemplateVO>> pageTemplates(Integer current, Integer size, String name, String category,
|
||||
Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin) {
|
||||
|
||||
LambdaQueryWrapper<PromptTemplate> wrapper = buildVisibilityWrapper(tenantId, userId, isPlatformAdmin, isTenantAdmin);
|
||||
|
||||
// 通用过滤条件
|
||||
wrapper.like(name != null && !name.isEmpty(), PromptTemplate::getTemplateName, name)
|
||||
.eq(category != null && !category.isEmpty(), PromptTemplate::getCategory, category)
|
||||
.orderByDesc(PromptTemplate::getIsSystem)
|
||||
.orderByDesc(PromptTemplate::getCreatedAt);
|
||||
.eq(category != null && !category.isEmpty(), PromptTemplate::getCategory, category)
|
||||
.orderByDesc(PromptTemplate::getIsSystem)
|
||||
.orderByDesc(PromptTemplate::getCreatedAt);
|
||||
|
||||
Page<PromptTemplate> page = this.page(new Page<>(current, size), wrapper);
|
||||
List<PromptTemplateVO> vos = page.getRecords().stream().map(this::toVO).collect(Collectors.toList());
|
||||
|
||||
List<PromptTemplate> records = page.getRecords();
|
||||
Map<Long, Integer> userStatusMap = queryUserStatusMap(tenantId, userId, records.stream().map(PromptTemplate::getId).collect(Collectors.toList()));
|
||||
List<PromptTemplateVO> vos = records.stream()
|
||||
.map(template -> toVO(template, effectiveStatus(template.getStatus(), userStatusMap.get(template.getId()))))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
PageResult<List<PromptTemplateVO>> result = new PageResult<>();
|
||||
result.setTotal(page.getTotal());
|
||||
result.setRecords(vos);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean updateUserTemplateStatus(Long templateId, Integer status, Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin) {
|
||||
PromptTemplate template = this.getOne(buildVisibilityWrapper(tenantId, userId, isPlatformAdmin, isTenantAdmin)
|
||||
.eq(PromptTemplate::getId, templateId)
|
||||
.last("LIMIT 1"));
|
||||
if (template == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PromptTemplateUserConfig existing = userConfigMapper.selectOne(new LambdaQueryWrapper<PromptTemplateUserConfig>()
|
||||
.eq(PromptTemplateUserConfig::getTenantId, tenantId)
|
||||
.eq(PromptTemplateUserConfig::getUserId, userId)
|
||||
.eq(PromptTemplateUserConfig::getTemplateId, templateId)
|
||||
.last("LIMIT 1"));
|
||||
|
||||
if (existing != null) {
|
||||
existing.setStatus(status);
|
||||
return userConfigMapper.updateById(existing) > 0;
|
||||
}
|
||||
|
||||
PromptTemplateUserConfig entity = new PromptTemplateUserConfig();
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setUserId(userId);
|
||||
entity.setTemplateId(templateId);
|
||||
entity.setStatus(status);
|
||||
return userConfigMapper.insert(entity) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTemplateEnabledForUser(Long templateId, Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin) {
|
||||
PromptTemplate template = this.getOne(buildVisibilityWrapper(tenantId, userId, isPlatformAdmin, isTenantAdmin)
|
||||
.eq(PromptTemplate::getId, templateId)
|
||||
.last("LIMIT 1"));
|
||||
if (template == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PromptTemplateUserConfig config = userConfigMapper.selectOne(new LambdaQueryWrapper<PromptTemplateUserConfig>()
|
||||
.eq(PromptTemplateUserConfig::getTenantId, tenantId)
|
||||
.eq(PromptTemplateUserConfig::getUserId, userId)
|
||||
.eq(PromptTemplateUserConfig::getTemplateId, templateId)
|
||||
.last("LIMIT 1"));
|
||||
Integer userStatus = config == null ? null : config.getStatus();
|
||||
return effectiveStatus(template.getStatus(), userStatus) == 1;
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<PromptTemplate> buildVisibilityWrapper(Long tenantId, Long userId, Boolean isPlatformAdmin, Boolean isTenantAdmin) {
|
||||
LambdaQueryWrapper<PromptTemplate> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.and(w -> w
|
||||
.eq(PromptTemplate::getCreatorId, userId)
|
||||
.or(sw -> sw.eq(PromptTemplate::getTenantId, 0L).eq(PromptTemplate::getIsSystem, 1))
|
||||
.or(sw -> sw.eq(PromptTemplate::getTenantId, tenantId).eq(PromptTemplate::getIsSystem, 1))
|
||||
);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private Map<Long, Integer> queryUserStatusMap(Long tenantId, Long userId, List<Long> templateIds) {
|
||||
if (templateIds == null || templateIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<PromptTemplateUserConfig> configs = userConfigMapper.selectList(new LambdaQueryWrapper<PromptTemplateUserConfig>()
|
||||
.eq(PromptTemplateUserConfig::getTenantId, tenantId)
|
||||
.eq(PromptTemplateUserConfig::getUserId, userId)
|
||||
.in(PromptTemplateUserConfig::getTemplateId, templateIds));
|
||||
|
||||
Map<Long, Integer> statusMap = new HashMap<>();
|
||||
for (PromptTemplateUserConfig config : configs) {
|
||||
statusMap.put(config.getTemplateId(), config.getStatus());
|
||||
}
|
||||
return statusMap;
|
||||
}
|
||||
|
||||
private Integer effectiveStatus(Integer templateStatus, Integer userStatus) {
|
||||
if (userStatus != null) {
|
||||
return userStatus;
|
||||
}
|
||||
return templateStatus == null ? 1 : templateStatus;
|
||||
}
|
||||
|
||||
private void copyProperties(PromptTemplateDTO dto, PromptTemplate entity) {
|
||||
entity.setTemplateName(dto.getTemplateName());
|
||||
entity.setCategory(dto.getCategory());
|
||||
|
|
@ -107,7 +172,7 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
|
|||
entity.setRemark(dto.getRemark());
|
||||
}
|
||||
|
||||
private PromptTemplateVO toVO(PromptTemplate entity) {
|
||||
private PromptTemplateVO toVO(PromptTemplate entity, Integer status) {
|
||||
PromptTemplateVO vo = new PromptTemplateVO();
|
||||
vo.setId(entity.getId());
|
||||
vo.setTenantId(entity.getTenantId());
|
||||
|
|
@ -118,7 +183,7 @@ public class PromptTemplateServiceImpl extends ServiceImpl<PromptTemplateMapper,
|
|||
vo.setTags(entity.getTags());
|
||||
vo.setUsageCount(entity.getUsageCount());
|
||||
vo.setPromptContent(entity.getPromptContent());
|
||||
vo.setStatus(entity.getStatus());
|
||||
vo.setStatus(status);
|
||||
vo.setRemark(entity.getRemark());
|
||||
vo.setCreatedAt(entity.getCreatedAt());
|
||||
vo.setUpdatedAt(entity.getUpdatedAt());
|
||||
|
|
|
|||
|
|
@ -1,152 +1,152 @@
|
|||
package com.imeeting.biz;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.imeeting.entity.biz.AiModel;
|
||||
import com.imeeting.entity.biz.Meeting;
|
||||
import com.imeeting.entity.biz.MeetingTranscript;
|
||||
import com.imeeting.mapper.biz.MeetingMapper;
|
||||
import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||
import com.imeeting.service.biz.AiModelService;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 总结模块分步分析测试类 - 真实数据版
|
||||
*/
|
||||
@SpringBootTest
|
||||
public class SummaryTest {
|
||||
|
||||
@Autowired
|
||||
private MeetingMapper meetingMapper;
|
||||
|
||||
@Autowired
|
||||
private MeetingTranscriptMapper transcriptMapper;
|
||||
|
||||
@Autowired
|
||||
private AiModelService aiModelService;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
@Test
|
||||
public void testManualSummary() throws Exception {
|
||||
// --- 步骤 1: 准备测试数据 ---
|
||||
// 请替换为您数据库中真实的 meetingId
|
||||
Long testMeetingId = 3L;
|
||||
|
||||
Meeting meeting = meetingMapper.selectById(testMeetingId);
|
||||
if (meeting == null) {
|
||||
System.out.println("❌ 错误:未找到 ID 为 " + testMeetingId + " 的会议记录");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取真实的 ASR 转录数据
|
||||
List<MeetingTranscript> transcripts = transcriptMapper.selectList(
|
||||
new LambdaQueryWrapper<MeetingTranscript>()
|
||||
.eq(MeetingTranscript::getMeetingId, testMeetingId)
|
||||
.orderByAsc(MeetingTranscript::getStartTime)
|
||||
);
|
||||
|
||||
if (transcripts.isEmpty()) {
|
||||
System.out.println("⚠️ 警告:该会议暂无转录明细数据 (MeetingTranscript)");
|
||||
// 如果没明细,您可以选择是否继续,或者手动造一点
|
||||
// return;
|
||||
}
|
||||
|
||||
String realAsrText = transcripts.stream()
|
||||
.map(t -> (t.getSpeakerName() != null ? t.getSpeakerName() : t.getSpeakerId()) + ": " + t.getContent())
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
System.out.println("\n--- [DEBUG] 提取到的真实转录文本 ---");
|
||||
System.out.println(realAsrText);
|
||||
|
||||
AiModel llmModel = aiModelService.getById(meeting.getSummaryModelId());
|
||||
if (llmModel == null) {
|
||||
System.out.println("❌ 错误:该会议未绑定总结模型配置");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("\n✅ 基础数据加载成功");
|
||||
System.out.println(" 模型名称: " + llmModel.getModelName());
|
||||
System.out.println(" 提示词模板快照: " + (meeting.getPromptContent() != null && meeting.getPromptContent().length() > 50
|
||||
? meeting.getPromptContent().substring(0, 50) + "..."
|
||||
: meeting.getPromptContent()));
|
||||
|
||||
// --- 步骤 2: 构造请求 Payload ---
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
req.put("model", llmModel.getModelCode());
|
||||
req.put("temperature", llmModel.getTemperature());
|
||||
|
||||
List<Map<String, String>> messages = new ArrayList<>();
|
||||
// 系统角色注入 Prompt
|
||||
messages.add(Map.of("role", "system", "content", meeting.getPromptContent() != null ? meeting.getPromptContent() : "请总结以下会议内容"));
|
||||
// 用户角色注入 真实的 ASR 文本
|
||||
messages.add(Map.of("role", "user", "content", "以下是会议转录全文:\n" + realAsrText));
|
||||
req.put("messages", messages);
|
||||
|
||||
String jsonPayload = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(req);
|
||||
System.out.println("\n--- [DEBUG] 发送给 AI 的请求 JSON ---");
|
||||
System.out.println(jsonPayload);
|
||||
|
||||
// --- 步骤 3: 发起网络请求 ---
|
||||
String url = llmModel.getBaseUrl() + (llmModel.getApiPath() != null ? llmModel.getApiPath() : "/v1/chat/completions");
|
||||
System.out.println("\n--- [DEBUG] 目标 URL: " + url);
|
||||
|
||||
HttpClient client = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Authorization", "Bearer " + llmModel.getApiKey())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
.build();
|
||||
|
||||
System.out.println("⏳ 正在请求第三方 AI 接口...");
|
||||
try {
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
System.out.println("\n--- [DEBUG] 接口返回状态码: " + response.statusCode());
|
||||
System.out.println("--- [DEBUG] 接口返回 Raw Body ---");
|
||||
System.out.println(response.body());
|
||||
|
||||
// --- 步骤 4: 解析结果 ---
|
||||
if (response.statusCode() == 200) {
|
||||
JsonNode respNode = objectMapper.readTree(response.body());
|
||||
if (respNode.has("choices")) {
|
||||
String finalContent = respNode.get("choices").get(0).get("message").get("content").asText();
|
||||
System.out.println("\n✨ 总结生成成功!结果如下:");
|
||||
System.out.println("------------------------------------");
|
||||
System.out.println(finalContent);
|
||||
System.out.println("------------------------------------");
|
||||
|
||||
// 可选:将结果更新回数据库以便前端查看
|
||||
// meeting.setSummaryContent(finalContent);
|
||||
// meetingMapper.updateById(meeting);
|
||||
} else {
|
||||
System.out.println("❌ 错误:返回结果中不包含 'choices' 字段,请检查厂商 API 适配。");
|
||||
}
|
||||
} else {
|
||||
System.out.println("❌ 接口请求失败,请检查 BaseUrl 和 ApiKey 是否正确。");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("❌ 网络异常:" + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
//package com.imeeting.biz;
|
||||
//
|
||||
//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
//import com.fasterxml.jackson.databind.JsonNode;
|
||||
//import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
//import com.imeeting.entity.biz.AiModel;
|
||||
//import com.imeeting.entity.biz.Meeting;
|
||||
//import com.imeeting.entity.biz.MeetingTranscript;
|
||||
//import com.imeeting.mapper.biz.MeetingMapper;
|
||||
//import com.imeeting.mapper.biz.MeetingTranscriptMapper;
|
||||
//import com.imeeting.service.biz.AiModelService;
|
||||
//import org.junit.jupiter.api.Test;
|
||||
//import org.springframework.beans.factory.annotation.Autowired;
|
||||
//import org.springframework.boot.test.context.SpringBootTest;
|
||||
//
|
||||
//import java.net.URI;
|
||||
//import java.net.http.HttpClient;
|
||||
//import java.net.http.HttpRequest;
|
||||
//import java.net.http.HttpResponse;
|
||||
//import java.time.Duration;
|
||||
//import java.util.ArrayList;
|
||||
//import java.util.HashMap;
|
||||
//import java.util.List;
|
||||
//import java.util.Map;
|
||||
//import java.util.stream.Collectors;
|
||||
//
|
||||
///**
|
||||
// * 总结模块分步分析测试类 - 真实数据版
|
||||
// */
|
||||
//@SpringBootTest
|
||||
//public class SummaryTest {
|
||||
//
|
||||
// @Autowired
|
||||
// private MeetingMapper meetingMapper;
|
||||
//
|
||||
// @Autowired
|
||||
// private MeetingTranscriptMapper transcriptMapper;
|
||||
//
|
||||
// @Autowired
|
||||
// private AiModelService aiModelService;
|
||||
//
|
||||
// @Autowired
|
||||
// private ObjectMapper objectMapper;
|
||||
//
|
||||
// @Test
|
||||
// public void testManualSummary() throws Exception {
|
||||
// // --- 步骤 1: 准备测试数据 ---
|
||||
// // 请替换为您数据库中真实的 meetingId
|
||||
// Long testMeetingId = 3L;
|
||||
//
|
||||
// Meeting meeting = meetingMapper.selectById(testMeetingId);
|
||||
// if (meeting == null) {
|
||||
// System.out.println("❌ 错误:未找到 ID 为 " + testMeetingId + " 的会议记录");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // 获取真实的 ASR 转录数据
|
||||
// List<MeetingTranscript> transcripts = transcriptMapper.selectList(
|
||||
// new LambdaQueryWrapper<MeetingTranscript>()
|
||||
// .eq(MeetingTranscript::getMeetingId, testMeetingId)
|
||||
// .orderByAsc(MeetingTranscript::getStartTime)
|
||||
// );
|
||||
//
|
||||
// if (transcripts.isEmpty()) {
|
||||
// System.out.println("⚠️ 警告:该会议暂无转录明细数据 (MeetingTranscript)");
|
||||
// // 如果没明细,您可以选择是否继续,或者手动造一点
|
||||
// // return;
|
||||
// }
|
||||
//
|
||||
// String realAsrText = transcripts.stream()
|
||||
// .map(t -> (t.getSpeakerName() != null ? t.getSpeakerName() : t.getSpeakerId()) + ": " + t.getContent())
|
||||
// .collect(Collectors.joining("\n"));
|
||||
//
|
||||
// System.out.println("\n--- [DEBUG] 提取到的真实转录文本 ---");
|
||||
// System.out.println(realAsrText);
|
||||
//
|
||||
// AiModel llmModel = aiModelService.getById(meeting.getSummaryModelId());
|
||||
// if (llmModel == null) {
|
||||
// System.out.println("❌ 错误:该会议未绑定总结模型配置");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// System.out.println("\n✅ 基础数据加载成功");
|
||||
// System.out.println(" 模型名称: " + llmModel.getModelName());
|
||||
// System.out.println(" 提示词模板快照: " + (meeting.getPromptContent() != null && meeting.getPromptContent().length() > 50
|
||||
// ? meeting.getPromptContent().substring(0, 50) + "..."
|
||||
// : meeting.getPromptContent()));
|
||||
//
|
||||
// // --- 步骤 2: 构造请求 Payload ---
|
||||
// Map<String, Object> req = new HashMap<>();
|
||||
// req.put("model", llmModel.getModelCode());
|
||||
// req.put("temperature", llmModel.getTemperature());
|
||||
//
|
||||
// List<Map<String, String>> messages = new ArrayList<>();
|
||||
// // 系统角色注入 Prompt
|
||||
// messages.add(Map.of("role", "system", "content", meeting.getPromptContent() != null ? meeting.getPromptContent() : "请总结以下会议内容"));
|
||||
// // 用户角色注入 真实的 ASR 文本
|
||||
// messages.add(Map.of("role", "user", "content", "以下是会议转录全文:\n" + realAsrText));
|
||||
// req.put("messages", messages);
|
||||
//
|
||||
// String jsonPayload = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(req);
|
||||
// System.out.println("\n--- [DEBUG] 发送给 AI 的请求 JSON ---");
|
||||
// System.out.println(jsonPayload);
|
||||
//
|
||||
// // --- 步骤 3: 发起网络请求 ---
|
||||
// String url = llmModel.getBaseUrl() + (llmModel.getApiPath() != null ? llmModel.getApiPath() : "/v1/chat/completions");
|
||||
// System.out.println("\n--- [DEBUG] 目标 URL: " + url);
|
||||
//
|
||||
// HttpClient client = HttpClient.newBuilder()
|
||||
// .connectTimeout(Duration.ofSeconds(10))
|
||||
// .build();
|
||||
//
|
||||
// HttpRequest request = HttpRequest.newBuilder()
|
||||
// .uri(URI.create(url))
|
||||
// .header("Content-Type", "application/json")
|
||||
// .header("Authorization", "Bearer " + llmModel.getApiKey())
|
||||
// .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
|
||||
// .build();
|
||||
//
|
||||
// System.out.println("⏳ 正在请求第三方 AI 接口...");
|
||||
// try {
|
||||
// HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
//
|
||||
// System.out.println("\n--- [DEBUG] 接口返回状态码: " + response.statusCode());
|
||||
// System.out.println("--- [DEBUG] 接口返回 Raw Body ---");
|
||||
// System.out.println(response.body());
|
||||
//
|
||||
// // --- 步骤 4: 解析结果 ---
|
||||
// if (response.statusCode() == 200) {
|
||||
// JsonNode respNode = objectMapper.readTree(response.body());
|
||||
// if (respNode.has("choices")) {
|
||||
// String finalContent = respNode.get("choices").get(0).get("message").get("content").asText();
|
||||
// System.out.println("\n✨ 总结生成成功!结果如下:");
|
||||
// System.out.println("------------------------------------");
|
||||
// System.out.println(finalContent);
|
||||
// System.out.println("------------------------------------");
|
||||
//
|
||||
// // 可选:将结果更新回数据库以便前端查看
|
||||
// // meeting.setSummaryContent(finalContent);
|
||||
// // meetingMapper.updateById(meeting);
|
||||
// } else {
|
||||
// System.out.println("❌ 错误:返回结果中不包含 'choices' 字段,请检查厂商 API 适配。");
|
||||
// }
|
||||
// } else {
|
||||
// System.out.println("❌ 接口请求失败,请检查 BaseUrl 和 ApiKey 是否正确。");
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// System.out.println("❌ 网络异常:" + e.getMessage());
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -95,17 +95,19 @@ const PromptTemplates: React.FC = () => {
|
|||
});
|
||||
setPreviewContent(record.promptContent);
|
||||
} else {
|
||||
const isPlatformLevel = Number(record.tenantId) === 0;
|
||||
const isPlatformLevel = Number(record.tenantId) === 0 && Number(record.isSystem) === 1;
|
||||
const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1;
|
||||
|
||||
// 权限判定逻辑
|
||||
let canEdit = false;
|
||||
if (isPlatformAdmin) {
|
||||
if (Number(record.isSystem) === 0) {
|
||||
canEdit = Number(record.creatorId) === currentUserId;
|
||||
} else if (isPlatformAdmin) {
|
||||
canEdit = isPlatformLevel;
|
||||
} else if (isTenantAdmin) {
|
||||
canEdit = Number(record.tenantId) === activeTenantId;
|
||||
} else {
|
||||
canEdit = Number(record.creatorId) === currentUserId;
|
||||
canEdit = false;
|
||||
}
|
||||
|
||||
if (!canEdit) {
|
||||
|
|
@ -191,15 +193,18 @@ const PromptTemplates: React.FC = () => {
|
|||
let canEdit = false;
|
||||
const currentUserId = userProfile.userId ? Number(userProfile.userId) : -1;
|
||||
|
||||
if (isPlatformAdmin) {
|
||||
// 平台管理员管理平台下的所有 (tenantId = 0)
|
||||
if (isPersonalLevel) {
|
||||
// 个人模板仅本人可编辑
|
||||
canEdit = Number(item.creatorId) === currentUserId;
|
||||
} else if (isPlatformAdmin) {
|
||||
// 平台管理员管理平台公开模板 (tenantId = 0)
|
||||
canEdit = Number(item.tenantId) === 0;
|
||||
} else if (isTenantAdmin) {
|
||||
// 租户管理员管理本租户所有模板
|
||||
// 租户管理员管理本租户公开模板
|
||||
canEdit = Number(item.tenantId) === activeTenantId;
|
||||
} else {
|
||||
// 普通用户仅限自己的个人模板
|
||||
canEdit = Number(item.creatorId) === currentUserId;
|
||||
// 普通用户不可编辑公开模板
|
||||
canEdit = false;
|
||||
}
|
||||
|
||||
// 标签颜色与文字
|
||||
|
|
@ -236,15 +241,15 @@ const PromptTemplates: React.FC = () => {
|
|||
size="small"
|
||||
checked={item.status === 1}
|
||||
onChange={(checked) => handleStatusChange(item.id, checked)}
|
||||
disabled={!canEdit}
|
||||
disabled={false}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/*<div style={{ marginBottom: 12 }}>*/}
|
||||
{/* <Text strong style={{ fontSize: 16, display: 'block' }} ellipsis={{ tooltip: item.templateName }}>{item.templateName}</Text>*/}
|
||||
{/* <Text type="secondary" style={{ fontSize: 12 }}>使用次数: {item.usageCount || 0}</Text>*/}
|
||||
{/*</div>*/}
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Text strong style={{ fontSize: 16, display: 'block' }} ellipsis={{ tooltip: item.templateName }}>{item.templateName}</Text>
|
||||
{/*<Text type="secondary" style={{ fontSize: 12 }}>使用次数: {item.usageCount || 0}</Text>*/}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 20, height: 22, overflow: 'hidden' }}>
|
||||
{item.tags?.map(tag => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue