录音管理功能联调
This commit is contained in:
@@ -4,10 +4,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.rj.common.DataEnrichmentUtil;
|
||||
import com.rj.entity.AudioManagement;
|
||||
import com.rj.entity.CustomerManagement;
|
||||
import com.rj.entity.SalesManagement;
|
||||
import com.rj.service.IAudioManagementService;
|
||||
import com.rj.service.IFileUploadService;
|
||||
import com.rj.service.MinIOService;
|
||||
import com.rj.service.ITtsRequestLogService;
|
||||
import com.rj.dto.AsrRequest;
|
||||
import com.rj.dto.AsrResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -15,9 +17,12 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -44,28 +49,140 @@ public class AudioManagementController {
|
||||
@Autowired
|
||||
private IFileUploadService fileUploadService;
|
||||
|
||||
@Autowired
|
||||
private MinIOService minIOService;
|
||||
|
||||
@Autowired
|
||||
private ITtsRequestLogService ttsRequestLogService;
|
||||
|
||||
/**
|
||||
* 新增录音
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
@PostMapping("/uploadAndSubmit")
|
||||
@Operation(summary = "新增录音", description = "添加新的录音信息")
|
||||
public ResponseEntity<Map<String, Object>> addAudio(
|
||||
@Parameter(description = "录音信息", required = true)
|
||||
@RequestBody AudioManagement audioManagement) {
|
||||
@Parameter(description = "录音文件", required = true)
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@Parameter(description = "录音名称", required = false)
|
||||
@RequestParam(value = "recordingName", required = false) String recordingName,
|
||||
@Parameter(description = "录音时间", required = false)
|
||||
@RequestParam(value = "recordingTime", required = false) String recordingTime,
|
||||
@Parameter(description = "所属销售ID", required = false)
|
||||
@RequestParam(value = "salesId", required = false) String salesId,
|
||||
@Parameter(description = "所属销售名称", required = false)
|
||||
@RequestParam(value = "salesName", required = false) String salesName,
|
||||
@Parameter(description = "销售人员电话", required = false)
|
||||
@RequestParam(value = "salesPhone", required = false) String salesPhone,
|
||||
@Parameter(description = "录音时长(分钟)", required = false)
|
||||
@RequestParam(value = "duration", required = false) BigDecimal duration,
|
||||
@Parameter(description = "客户ID", required = false)
|
||||
@RequestParam(value = "customerId", required = false) String customerId,
|
||||
@Parameter(description = "客户姓名", required = false)
|
||||
@RequestParam(value = "customerName", required = false) String customerName,
|
||||
@Parameter(description = "客户手机号", required = false)
|
||||
@RequestParam(value = "customerPhone", required = false) String customerPhone,
|
||||
@Parameter(description = "意向级别", required = false)
|
||||
@RequestParam(value = "intentionLevel", required = false) String intentionLevel,
|
||||
@Parameter(description = "所属门店ID", required = false)
|
||||
@RequestParam(value = "dealershipId", required = false) String dealershipId,
|
||||
@Parameter(description = "所属门店名称", required = false)
|
||||
@RequestParam(value = "dealershipName", required = false) String dealershipName,
|
||||
@Parameter(description = "所属项目ID", required = false)
|
||||
@RequestParam(value = "projectId", required = false) String projectId,
|
||||
@Parameter(description = "所属项目名称", required = false)
|
||||
@RequestParam(value = "projectName", required = false) String projectName,
|
||||
@Parameter(description = "话术模型", required = false)
|
||||
@RequestParam(value = "scriptModel", required = false) String scriptModel,
|
||||
@Parameter(description = "录音备注", required = false)
|
||||
@RequestParam(value = "remarks", required = false) String remarks,
|
||||
@Parameter(description = "录音文本内容", required = false)
|
||||
@RequestParam(value = "recordingText", required = false) String recordingText) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 验证文件
|
||||
if (file == null || file.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音文件不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 验证文件类型
|
||||
String contentType = file.getContentType();
|
||||
if (contentType == null || (!contentType.startsWith("audio/"))) {
|
||||
result.put("success", false);
|
||||
result.put("message", "只支持音频文件格式");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 验证文件大小(限制为100MB)
|
||||
if (file.getSize() > 100 * 1024 * 1024) {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件大小不能超过100MB");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 生成录音ID
|
||||
if (audioManagement.getId() == null || audioManagement.getId().trim().isEmpty()) {
|
||||
audioManagement.setId(UUID.randomUUID().toString());
|
||||
String audioId = UUID.randomUUID().toString();
|
||||
|
||||
// 先将音频文件上传到MinIO
|
||||
String minioUrl;
|
||||
try {
|
||||
minioUrl = minIOService.uploadFile(file);
|
||||
log.info("音频文件上传到MinIO成功,URL: {}", minioUrl);
|
||||
} catch (Exception e) {
|
||||
log.error("音频文件上传到MinIO失败: {}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "音频文件上传失败:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
|
||||
// 创建录音管理对象
|
||||
AudioManagement audioManagement = new AudioManagement();
|
||||
|
||||
// 设置录音ID
|
||||
audioManagement.setId(audioId);
|
||||
|
||||
// 设置基本信息
|
||||
audioManagement.setRecordingName(recordingName);
|
||||
if (recordingTime != null && !recordingTime.trim().isEmpty()) {
|
||||
audioManagement.setRecordingTime(LocalDateTime.parse(recordingTime));
|
||||
} else {
|
||||
audioManagement.setRecordingTime(LocalDateTime.now());
|
||||
}
|
||||
audioManagement.setSalesId(salesId);
|
||||
audioManagement.setSalesName(salesName);
|
||||
audioManagement.setSalesPhone(salesPhone);
|
||||
audioManagement.setDuration(duration);
|
||||
audioManagement.setCustomerId(customerId);
|
||||
audioManagement.setCustomerName(customerName);
|
||||
audioManagement.setCustomerPhone(customerPhone);
|
||||
audioManagement.setIntentionLevel(intentionLevel);
|
||||
audioManagement.setDealershipId(dealershipId);
|
||||
audioManagement.setDealershipName(dealershipName);
|
||||
audioManagement.setProjectId(projectId);
|
||||
audioManagement.setProjectName(projectName);
|
||||
audioManagement.setScriptModel(scriptModel);
|
||||
audioManagement.setRemarks(remarks);
|
||||
audioManagement.setRecordingText(recordingText);
|
||||
|
||||
// 设置文件信息(MinIO返回的URL和文件信息)
|
||||
audioManagement.setAudioFileOriginalName(file.getOriginalFilename());
|
||||
audioManagement.setAudioFileSize(file.getSize());
|
||||
audioManagement.setAudioFileExtension(getFileExtension(file.getOriginalFilename()));
|
||||
audioManagement.setAudioFileUrl(minioUrl); // 保存MinIO返回的URL
|
||||
|
||||
// 设置时间
|
||||
audioManagement.setCreateTime(LocalDateTime.now());
|
||||
audioManagement.setUpdateTime(LocalDateTime.now());
|
||||
audioManagement.setUploadTime(LocalDateTime.now());
|
||||
|
||||
// 保存录音记录到数据库
|
||||
boolean success = audioManagementService.save(audioManagement);
|
||||
if (success) {
|
||||
result.put("success", true);
|
||||
result.put("message", "录音添加成功");
|
||||
result.put("data", audioManagement);
|
||||
result.put("minioUrl", minioUrl);
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
@@ -79,6 +196,20 @@ public class AudioManagementController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
private String getFileExtension(String filename) {
|
||||
if (filename == null || filename.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
return filename.substring(lastDotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
@PostMapping("/difyCallbackPLForCustomerProfile")
|
||||
@Operation(summary = "dify回调,为了客户画像完善", description = "dify回调,为了客户画像完善")
|
||||
public Map<String, Object> difyCallbackPLForCustomerProfile(@RequestBody String message){
|
||||
@@ -214,18 +345,21 @@ public class AudioManagementController {
|
||||
// 使用工具类批量获取关联数据
|
||||
Map<String, String> dealershipNameMap = dataEnrichmentUtil.getDealershipIdNameMap(dealershipIds);
|
||||
Map<String, String> saleNameMap = dataEnrichmentUtil.getSalesIdNameMap(saleIds);
|
||||
Map<String, String> salePhoneMap = dataEnrichmentUtil.getSalesIdPhoneMap(saleIds);
|
||||
Map<String, String> projectNameMap = dataEnrichmentUtil.getProjectIdNameMap(projectIds);
|
||||
// Map<String, String> customerNameMap = dataEnrichmentUtil.getCustomerNameMap(customerIds);
|
||||
|
||||
audioPage.getRecords().forEach(audio -> {
|
||||
audio.setDealershipName(dealershipNameMap.getOrDefault(audio.getDealershipId(), ""));
|
||||
audio.setSalesName(saleNameMap.getOrDefault(audio.getSalesId(), ""));
|
||||
audio.setSalesPhone(salePhoneMap.getOrDefault(audio.getSalesId(), ""));
|
||||
audio.setProjectName(projectNameMap.getOrDefault(audio.getProjectId(), ""));
|
||||
// audio.setCustomerName(customerNameMap.getOrDefault(audio.getCustomerId(), ""));
|
||||
|
||||
// 检查音频文件是否存在
|
||||
boolean fileExists = fileUploadService.audioFileExists(audio.getId());
|
||||
// 注意:这里不需要设置,因为实体类中已经有这些字段了
|
||||
//音频的url不要暴露对前端。
|
||||
if (audio.getAudioFileUrl() != null) {
|
||||
audio.setAudioFileUrl( audio.getId());
|
||||
}
|
||||
// 注意:这里不需要检查文件存在性,因为实体类中已经有这些字段了
|
||||
});
|
||||
|
||||
result.put("success", true);
|
||||
@@ -366,6 +500,381 @@ public class AudioManagementController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频文件的预签名URL
|
||||
* 短期播放: 1小时(3600秒)
|
||||
正常使用: 7天(604800秒)
|
||||
长期访问: 30天(2592000秒)
|
||||
|
||||
*/
|
||||
@PostMapping("/presigned-url")
|
||||
@Operation(summary = "获取音频文件预签名URL", description = "根据录音ID和URL获取音频文件的预签名访问URL")
|
||||
public ResponseEntity<Map<String, Object>> getAudioPresignedUrl(
|
||||
@Parameter(description = "录音ID", required = false)
|
||||
@RequestParam(required = false) String audioId,
|
||||
@Parameter(description = "录音文件URL", required = false)
|
||||
@RequestParam(required = false) String audioFileUrl,
|
||||
@Parameter(description = "过期时间(秒),默认1小时")
|
||||
@RequestParam(defaultValue = "3600") int expires){
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
String finalAudioFileUrl = null;
|
||||
String finalAudioId = null;
|
||||
|
||||
|
||||
// 如果录音文件URL为空,则通过录音ID查询数据库
|
||||
if (audioId != null && !audioId.trim().isEmpty()) {
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音不存在");
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
finalAudioFileUrl = audio.getAudioFileUrl();
|
||||
finalAudioId = audioId;
|
||||
log.info("从数据库查询录音文件URL: {}", finalAudioFileUrl);
|
||||
}
|
||||
// 如果两个参数都为空,返回错误
|
||||
else {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音ID和录音文件URL不能同时为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 检查最终的文件URL是否有效
|
||||
if (finalAudioFileUrl == null || finalAudioFileUrl.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "音频文件URL为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 提取文件名
|
||||
String fileName = finalAudioFileUrl.substring(finalAudioFileUrl.lastIndexOf("/") + 1);
|
||||
|
||||
// 生成新的预签名URL
|
||||
String presignedUrl = minIOService.getPresignedUrl(fileName, expires);
|
||||
|
||||
if (presignedUrl != null) {
|
||||
result.put("success", true);
|
||||
result.put("message", "预签名URL生成成功");
|
||||
result.put("audioId", finalAudioId);
|
||||
result.put("fileName", fileName);
|
||||
result.put("presignedUrl", presignedUrl);
|
||||
result.put("expires", expires);
|
||||
result.put("expiresInHours", expires / 3600);
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "预签名URL生成失败");
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取音频文件预签名URL异常:{}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "获取预签名URL异常:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配录音到项目
|
||||
*/
|
||||
@PostMapping("/allocateProject")
|
||||
@Operation(summary = "批量分配录音到项目", description = "将多个录音批量分配到指定项目")
|
||||
public ResponseEntity<Map<String, Object>> allocateProject(
|
||||
@Parameter(description = "录音ID列表", required = true)
|
||||
@RequestParam List<String> audioIds,
|
||||
@Parameter(description = "项目ID", required = true)
|
||||
@RequestParam String projectId,
|
||||
@Parameter(description = "项目名称", required = true)
|
||||
@RequestParam String projectName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 验证参数
|
||||
if (audioIds == null || audioIds.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音ID列表不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
if (projectId == null || projectId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "项目ID不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
if (projectName == null || projectName.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "项目名称不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 批量更新录音记录
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
List<String> failedIds = new java.util.ArrayList<>();
|
||||
|
||||
for (String audioId : audioIds) {
|
||||
try {
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio != null) {
|
||||
audio.setProjectId(projectId);
|
||||
audio.setProjectName(projectName);
|
||||
audio.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
boolean updated = audioManagementService.updateById(audio);
|
||||
if (updated) {
|
||||
successCount++;
|
||||
log.info("录音分配项目成功:audioId={}, projectId={}, projectName={}",
|
||||
audioId, projectId, projectName);
|
||||
} else {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.warn("录音分配项目失败:audioId={}", audioId);
|
||||
}
|
||||
} else {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.warn("录音不存在:audioId={}", audioId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.error("录音分配项目异常:audioId={}, error={}", audioId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
result.put("success", true);
|
||||
result.put("message", "批量分配完成");
|
||||
result.put("totalCount", audioIds.size());
|
||||
result.put("successCount", successCount);
|
||||
result.put("failureCount", failureCount);
|
||||
result.put("projectId", projectId);
|
||||
result.put("projectName", projectName);
|
||||
|
||||
if (failureCount > 0) {
|
||||
result.put("failedIds", failedIds);
|
||||
result.put("message", String.format("批量分配完成,成功:%d,失败:%d", successCount, failureCount));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量分配录音到项目异常:{}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "批量分配异常:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量分配录音到销售人员
|
||||
*/
|
||||
@PostMapping("/allocateSales")
|
||||
@Operation(summary = "批量分配录音到销售人员", description = "将多个录音批量分配到指定销售人员")
|
||||
public ResponseEntity<Map<String, Object>> allocateSales(
|
||||
@Parameter(description = "录音ID列表", required = true)
|
||||
@RequestParam List<String> audioIds,
|
||||
@Parameter(description = "销售人员ID", required = true)
|
||||
@RequestParam String salesId,
|
||||
@Parameter(description = "销售人员姓名", required = true)
|
||||
@RequestParam String salesName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 验证参数
|
||||
if (audioIds == null || audioIds.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音ID列表不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
if (salesId == null || salesId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "销售人员ID不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
if (salesName == null || salesName.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "销售人员姓名不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 批量更新录音记录
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
List<String> failedIds = new java.util.ArrayList<>();
|
||||
|
||||
for (String audioId : audioIds) {
|
||||
try {
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio != null) {
|
||||
audio.setSalesId(salesId);
|
||||
audio.setSalesName(salesName);
|
||||
audio.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
boolean updated = audioManagementService.updateById(audio);
|
||||
if (updated) {
|
||||
successCount++;
|
||||
log.info("录音分配销售人员成功:audioId={}, salesId={}, salesName={}",
|
||||
audioId, salesId, salesName);
|
||||
} else {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.warn("录音分配销售人员失败:audioId={}", audioId);
|
||||
}
|
||||
} else {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.warn("录音不存在:audioId={}", audioId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failureCount++;
|
||||
failedIds.add(audioId);
|
||||
log.error("录音分配销售人员异常:audioId={}, error={}", audioId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
result.put("success", true);
|
||||
result.put("message", "批量分配完成");
|
||||
result.put("totalCount", audioIds.size());
|
||||
result.put("successCount", successCount);
|
||||
result.put("failureCount", failureCount);
|
||||
result.put("salesId", salesId);
|
||||
result.put("salesName", salesName);
|
||||
|
||||
if (failureCount > 0) {
|
||||
result.put("failedIds", failedIds);
|
||||
result.put("message", String.format("批量分配完成,成功:%d,失败:%d", successCount, failureCount));
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("批量分配录音到销售人员异常:{}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "批量分配异常:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 录音转文本
|
||||
*/
|
||||
@PostMapping("/convertAudioToText")
|
||||
@Operation(summary = "录音转文本", description = "将录音文件转换为文本内容")
|
||||
public ResponseEntity<Map<String, Object>> convertAudioToText(
|
||||
@Parameter(description = "录音ID", required = true)
|
||||
@RequestParam String audioId,
|
||||
@Parameter(description = "录音名称", required = true)
|
||||
@RequestParam String audioName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
// 验证参数
|
||||
if (audioId == null || audioId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音ID不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
if (audioName == null || audioName.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音名称不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 检查录音是否存在
|
||||
AudioManagement audio = audioManagementService.getById(audioId);
|
||||
if (audio == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音不存在");
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
// 检查录音文件URL是否存在
|
||||
String audioFileUrl = audio.getAudioFileUrl();
|
||||
if (audioFileUrl == null || audioFileUrl.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音文件不存在");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
// 提取文件名
|
||||
String fileName = audioFileUrl.substring(audioFileUrl.lastIndexOf("/") + 1);
|
||||
|
||||
// 生成音频文件的预签名URL用于ASR服务
|
||||
String presignedUrl = minIOService.getPresignedUrl(fileName, 3600); // 1小时有效期
|
||||
|
||||
if (presignedUrl == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "生成音频文件访问URL失败");
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
|
||||
// 创建ASR请求对象
|
||||
AsrRequest asrRequest = new AsrRequest();
|
||||
asrRequest.setAudioUrl(presignedUrl);
|
||||
asrRequest.setAudioName(audioName);
|
||||
asrRequest.setModel("paraformer-v2"); // 使用默认模型
|
||||
asrRequest.setFormat("wav"); // 默认格式
|
||||
asrRequest.setSampleRate(16000); // 默认采样率
|
||||
asrRequest.setEnablePunctuation(true); // 启用标点符号
|
||||
asrRequest.setEnableNumberConversion(true); // 启用数字转换
|
||||
asrRequest.setEnableSpeakerDiarization(false); // 不启用说话人分离
|
||||
|
||||
// 调用ASR服务进行语音识别
|
||||
AsrResponse asrResponse = ttsRequestLogService.speechToText(asrRequest);
|
||||
|
||||
if (!asrResponse.isSuccess()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "语音识别失败:" + asrResponse.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
|
||||
String recognizedText = asrResponse.getText();
|
||||
|
||||
// 更新录音记录中的文本内容
|
||||
audio.setRecordingText(recognizedText);
|
||||
audio.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
boolean updated = audioManagementService.updateById(audio);
|
||||
|
||||
if (updated) {
|
||||
result.put("success", true);
|
||||
result.put("message", "录音转文本成功");
|
||||
result.put("audioId", audioId);
|
||||
result.put("audioName", audioName);
|
||||
result.put("recognizedText", recognizedText);
|
||||
result.put("textLength", recognizedText.length());
|
||||
result.put("taskId", asrResponse.getTaskId());
|
||||
result.put("requestId", asrResponse.getRequestId());
|
||||
result.put("duration", asrResponse.getDuration());
|
||||
result.put("processingTimeMs", asrResponse.getProcessingTimeMs());
|
||||
|
||||
log.info("录音转文本成功:audioId={}, audioName={}, textLength={}, taskId={}",
|
||||
audioId, audioName, recognizedText.length(), asrResponse.getTaskId());
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "录音转文本成功但更新数据库失败");
|
||||
result.put("recognizedText", recognizedText);
|
||||
result.put("taskId", asrResponse.getTaskId());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("录音转文本异常:{}", e.getMessage(), e);
|
||||
result.put("success", false);
|
||||
result.put("message", "录音转文本异常:" + e.getMessage());
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取录音的音频文件信息
|
||||
*/
|
||||
|
||||
@@ -36,12 +36,12 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "手动生成指定日期的统计数据", description = "手动触发指定日期的门店录音统计")
|
||||
@PostMapping("/generate")
|
||||
public Result generateStatistics(
|
||||
public Result<?> generateStatistics(
|
||||
@Parameter(description = "统计日期,格式:yyyy-MM-dd")
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
|
||||
try {
|
||||
int count = audioStatisticsScheduler.manualGenerateStatistics(date);
|
||||
Result result = Result.ok(count);
|
||||
Result<?> result = Result.ok(count);
|
||||
result.setMsg("统计生成成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -54,14 +54,14 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "查询指定日期范围的统计数据", description = "查询指定日期范围内的门店录音统计数据")
|
||||
@GetMapping("/range")
|
||||
public Result getStatisticsByDateRange(
|
||||
public Result<?> getStatisticsByDateRange(
|
||||
@Parameter(description = "开始日期,格式:yyyy-MM-dd")
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
|
||||
@Parameter(description = "结束日期,格式:yyyy-MM-dd")
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
try {
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsByDateRange(startDate, endDate);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -74,7 +74,7 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "查询指定门店的统计数据", description = "查询指定门店在指定日期范围内的录音统计数据")
|
||||
@GetMapping("/dealership/{dealershipId}")
|
||||
public Result getStatisticsByDealership(
|
||||
public Result<?> getStatisticsByDealership(
|
||||
@Parameter(description = "门店ID")
|
||||
@PathVariable Long dealershipId,
|
||||
@Parameter(description = "开始日期,格式:yyyy-MM-dd")
|
||||
@@ -83,7 +83,7 @@ public class AudioStatisticsController {
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
try {
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsByDealership(dealershipId, startDate, endDate);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -96,7 +96,7 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "查询指定销售人员的统计数据", description = "查询指定销售人员在指定日期范围内的录音统计数据")
|
||||
@GetMapping("/sales/{salesId}")
|
||||
public Result getStatisticsBySales(
|
||||
public Result<?> getStatisticsBySales(
|
||||
@Parameter(description = "销售人员ID")
|
||||
@PathVariable String salesId,
|
||||
@Parameter(description = "开始日期,格式:yyyy-MM-dd")
|
||||
@@ -105,7 +105,7 @@ public class AudioStatisticsController {
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
try {
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsBySales(salesId, startDate, endDate);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -118,7 +118,7 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "查询指定项目的统计数据", description = "查询指定项目在指定日期范围内的录音统计数据")
|
||||
@GetMapping("/project/{projectId}")
|
||||
public Result getStatisticsByProject(
|
||||
public Result<?> getStatisticsByProject(
|
||||
@Parameter(description = "项目ID")
|
||||
@PathVariable String projectId,
|
||||
@Parameter(description = "开始日期,格式:yyyy-MM-dd")
|
||||
@@ -127,7 +127,7 @@ public class AudioStatisticsController {
|
||||
@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
|
||||
try {
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsByProject(projectId, startDate, endDate);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -140,12 +140,12 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "查询指定日期的统计数据", description = "查询指定日期的所有门店录音统计数据")
|
||||
@GetMapping("/date/{date}")
|
||||
public Result getStatisticsByDate(
|
||||
public Result<?> getStatisticsByDate(
|
||||
@Parameter(description = "统计日期,格式:yyyy-MM-dd")
|
||||
@PathVariable @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date) {
|
||||
try {
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsByDateRange(date, date);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
@@ -158,16 +158,16 @@ public class AudioStatisticsController {
|
||||
*/
|
||||
@Operation(summary = "获取统计概览", description = "获取最近7天的门店录音统计概览")
|
||||
@GetMapping("/overview")
|
||||
public Result getStatisticsOverview() {
|
||||
public Result<?> getStatisticsOverview() {
|
||||
try {
|
||||
LocalDate endDate = LocalDate.now().minusDays(1); // 昨天
|
||||
LocalDate startDate = endDate.minusDays(6); // 7天前
|
||||
List<AudioManagementStatistics> statistics = audioStatisticsService.getStatisticsByDateRange(startDate, endDate);
|
||||
Result result = Result.ok(statistics);
|
||||
Result<?> result = Result.ok(statistics);
|
||||
result.setMsg("查询成功");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
return Result.error("查询失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.rj.entity.CommunicationRecord;
|
||||
import com.rj.service.ICommunicationRecordService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 沟通记录表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author 系统生成
|
||||
* @since 2025-01-30
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/communication-record")
|
||||
@Tag(name = "沟通记录管理", description = "沟通记录管理相关接口")
|
||||
@Slf4j
|
||||
public class CommunicationRecordController {
|
||||
|
||||
@Autowired
|
||||
private ICommunicationRecordService communicationRecordService;
|
||||
|
||||
/**
|
||||
* 分页查询沟通记录
|
||||
*/
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页查询沟通记录", description = "根据条件分页查询沟通记录列表")
|
||||
public ResponseEntity<Map<String, Object>> getPage(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") Integer current,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "10") Integer size,
|
||||
@Parameter(description = "客户姓名") @RequestParam(required = false) String customerName,
|
||||
@Parameter(description = "客户电话") @RequestParam(required = false) String customerPhone,
|
||||
@Parameter(description = "沟通类型") @RequestParam(required = false) String communicationType,
|
||||
@Parameter(description = "所属人姓名") @RequestParam(required = false) String ownerName) {
|
||||
|
||||
try {
|
||||
Page<CommunicationRecord> page = new Page<>(current, size);
|
||||
LambdaQueryWrapper<CommunicationRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
// 构建查询条件
|
||||
queryWrapper.like(customerName != null, CommunicationRecord::getCustomerName, customerName)
|
||||
.like(customerPhone != null, CommunicationRecord::getCustomerPhone, customerPhone)
|
||||
.like(communicationType != null, CommunicationRecord::getCommunicationType, communicationType)
|
||||
.like(ownerName != null, CommunicationRecord::getOwnerName, ownerName)
|
||||
.eq(CommunicationRecord::getStatus, 1)
|
||||
.orderByDesc(CommunicationRecord::getCreateTime);
|
||||
|
||||
Page<CommunicationRecord> result = communicationRecordService.page(page, queryWrapper);
|
||||
log.info("分页查询沟通记录成功,结果:{}", result.toString());
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 200);
|
||||
response.put("message", "查询成功");
|
||||
response.put("data", result.getRecords());
|
||||
response.put("total", result.getTotal());
|
||||
response.put("current", result.getCurrent());
|
||||
response.put("size", result.getSize());
|
||||
response.put("pages", result.getPages());
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("分页查询沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "查询失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID查询沟通记录
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "根据ID查询沟通记录", description = "根据ID查询沟通记录详情")
|
||||
public ResponseEntity<Map<String, Object>> getById(
|
||||
@Parameter(description = "沟通记录ID") @PathVariable String id) {
|
||||
try {
|
||||
CommunicationRecord record = communicationRecordService.getById(id);
|
||||
if (record == null) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 404);
|
||||
response.put("message", "沟通记录不存在");
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 200);
|
||||
response.put("message", "查询成功");
|
||||
response.put("data", record);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("查询沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "查询失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增沟通记录
|
||||
*/
|
||||
@PostMapping
|
||||
@Operation(summary = "新增沟通记录", description = "新增沟通记录")
|
||||
public ResponseEntity<Map<String, Object>> save(@Valid @RequestBody CommunicationRecord communicationRecord) {
|
||||
try {
|
||||
// 设置创建时间
|
||||
communicationRecord.setCreateTime(LocalDateTime.now());
|
||||
communicationRecord.setStatus(1);
|
||||
|
||||
boolean success = communicationRecordService.save(communicationRecord);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (success) {
|
||||
response.put("code", 200);
|
||||
response.put("message", "新增成功");
|
||||
response.put("data", null);
|
||||
} else {
|
||||
response.put("code", 500);
|
||||
response.put("message", "新增失败");
|
||||
response.put("data", null);
|
||||
}
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("新增沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "新增失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改沟通记录
|
||||
*/
|
||||
@PutMapping
|
||||
@Operation(summary = "修改沟通记录", description = "修改沟通记录")
|
||||
public ResponseEntity<Map<String, Object>> update(@Valid @RequestBody CommunicationRecord communicationRecord) {
|
||||
try {
|
||||
// 设置更新时间
|
||||
communicationRecord.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
boolean success = communicationRecordService.updateById(communicationRecord);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (success) {
|
||||
response.put("code", 200);
|
||||
response.put("message", "修改成功");
|
||||
response.put("data", null);
|
||||
} else {
|
||||
response.put("code", 500);
|
||||
response.put("message", "修改失败");
|
||||
response.put("data", null);
|
||||
}
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("修改沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "修改失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除沟通记录(软删除)
|
||||
*/
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除沟通记录", description = "删除沟通记录(软删除)")
|
||||
public ResponseEntity<Map<String, Object>> delete(@Parameter(description = "沟通记录ID") @PathVariable String id) {
|
||||
try {
|
||||
CommunicationRecord record = communicationRecordService.getById(id);
|
||||
if (record == null) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 404);
|
||||
response.put("message", "沟通记录不存在");
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
// 软删除
|
||||
record.setStatus(0);
|
||||
record.setUpdateTime(LocalDateTime.now());
|
||||
boolean success = communicationRecordService.updateById(record);
|
||||
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
if (success) {
|
||||
response.put("code", 200);
|
||||
response.put("message", "删除成功");
|
||||
response.put("data", null);
|
||||
} else {
|
||||
response.put("code", 500);
|
||||
response.put("message", "删除失败");
|
||||
response.put("data", null);
|
||||
}
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("删除沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "删除失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据客户ID查询沟通记录
|
||||
*/
|
||||
@GetMapping("/customer/{customerId}")
|
||||
@Operation(summary = "根据客户ID查询沟通记录", description = "根据客户ID查询该客户的所有沟通记录")
|
||||
public ResponseEntity<Map<String, Object>> getByCustomerId(
|
||||
@Parameter(description = "客户ID") @PathVariable String customerId) {
|
||||
try {
|
||||
LambdaQueryWrapper<CommunicationRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CommunicationRecord::getCustomerId, customerId)
|
||||
.eq(CommunicationRecord::getStatus, 1)
|
||||
.orderByDesc(CommunicationRecord::getCommunicationTime);
|
||||
|
||||
List<CommunicationRecord> records = communicationRecordService.list(queryWrapper);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 200);
|
||||
response.put("message", "查询成功");
|
||||
response.put("data", records);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("根据客户ID查询沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "查询失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据所属人查询沟通记录
|
||||
*/
|
||||
@GetMapping("/owner/{ownerName}")
|
||||
@Operation(summary = "根据所属人查询沟通记录", description = "根据所属人姓名查询沟通记录")
|
||||
public ResponseEntity<Map<String, Object>> getByOwnerName(
|
||||
@Parameter(description = "所属人姓名") @PathVariable String ownerName) {
|
||||
try {
|
||||
LambdaQueryWrapper<CommunicationRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(CommunicationRecord::getOwnerName, ownerName)
|
||||
.eq(CommunicationRecord::getStatus, 1)
|
||||
.orderByDesc(CommunicationRecord::getCreateTime);
|
||||
|
||||
List<CommunicationRecord> records = communicationRecordService.list(queryWrapper);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 200);
|
||||
response.put("message", "查询成功");
|
||||
response.put("data", records);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (Exception e) {
|
||||
log.error("根据所属人查询沟通记录失败", e);
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("code", 500);
|
||||
response.put("message", "查询失败:" + e.getMessage());
|
||||
response.put("data", null);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -404,6 +404,13 @@ public class MenuController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -374,6 +374,13 @@ public class RoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -380,6 +380,13 @@ public class UserRoleController {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user