diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallEventListener.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallEventListener.java index 1991ba9f4a..74895250f6 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallEventListener.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallEventListener.java @@ -23,7 +23,7 @@ import com.bytedesk.freeswitch.callcenter.event.CallAnsweredEvent; import com.bytedesk.freeswitch.callcenter.event.CallHangupEvent; import com.bytedesk.freeswitch.callcenter.event.CallStartEvent; import com.bytedesk.freeswitch.callcenter.event.DtmfEvent; -import com.bytedesk.freeswitch.service.FreeSwitchUserService; +import com.bytedesk.freeswitch.user.FreeSwitchUserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallService.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallService.java index ae3a5a6de6..e80b0f9d36 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallService.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/callcenter/CallService.java @@ -25,10 +25,10 @@ import org.springframework.stereotype.Service; // import com.bytedesk.freeswitch.freeswitch.FreeSwitchProperties; import com.bytedesk.freeswitch.freeswitch.FreeSwitchService; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; -import com.bytedesk.freeswitch.model.FreeSwitchUserEntity; -import com.bytedesk.freeswitch.service.FreeSwitchCdrService; -import com.bytedesk.freeswitch.service.FreeSwitchUserService; +import com.bytedesk.freeswitch.cdr.FreeSwitchCdrEntity; +import com.bytedesk.freeswitch.user.FreeSwitchUserEntity; +import com.bytedesk.freeswitch.cdr.FreeSwitchCdrService; +import com.bytedesk.freeswitch.user.FreeSwitchUserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrController.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrController.java index 31bcf03889..e69de29bb2 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrController.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrController.java @@ -1,320 +0,0 @@ -package com.bytedesk.freeswitch.controller; - -import com.bytedesk.core.rbac.annotation.CurrentUser; -import com.bytedesk.core.rbac.user.UserEntity; -import com.bytedesk.core.utils.JsonResult; -import com.bytedesk.freeswitch.dto.FreeSwitchCdrDto; -import com.bytedesk.freeswitch.dto.FreeSwitchDtoMapper; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; -import com.bytedesk.freeswitch.service.FreeSwitchCdrService; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * FreeSwitch通话详单REST API控制器 - */ -@Slf4j -@RestController -@RequestMapping("/freeswitch/api/v1/cdr") -@AllArgsConstructor -@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true") -public class FreeSwitchCdrController { - - private final FreeSwitchCdrService cdrService; - private final FreeSwitchDtoMapper dtoMapper; - - /** - * 获取通话详单列表 - */ - @GetMapping - public ResponseEntity>> getCdrList( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "20") int size, - @RequestParam(defaultValue = "startTime") String sort, - @RequestParam(defaultValue = "desc") String direction, - @RequestParam(required = false) String callerNumber, - @RequestParam(required = false) String destinationNumber, - @RequestParam(required = false) String direction_filter, - @RequestParam(required = false) String hangupCause, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { - - try { - Sort.Direction sortDirection = "asc".equalsIgnoreCase(direction) ? - Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort)); - - Page entities; - - if (startTime != null && endTime != null) { - entities = cdrService.findByTimeRange(startTime, endTime, pageable); - } else if (callerNumber != null) { - entities = cdrService.findByCallerNumber(callerNumber, pageable); - } else if (destinationNumber != null) { - entities = cdrService.findByDestinationNumber(destinationNumber, pageable); - } else if (direction_filter != null) { - entities = cdrService.findByDirection(direction_filter, pageable); - } else if (hangupCause != null) { - entities = cdrService.findByHangupCause(hangupCause, pageable); - } else { - entities = cdrService.findAll(pageable); - } - - Page dtos = entities.map(dtoMapper::toCdrDto); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取FreeSwitch CDR列表失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 根据ID获取通话详单详情 - */ - @GetMapping("/{id}") - public ResponseEntity> getCdrById(@PathVariable Long id) { - try { - Optional entity = cdrService.findById(id); - if (entity.isPresent()) { - FreeSwitchCdrDto dto = dtoMapper.toCdrDto(entity.get()); - return ResponseEntity.ok(JsonResult.success(dto)); - } else { - return ResponseEntity.notFound().build(); - } - } catch (Exception e) { - log.error("获取FreeSwitch CDR详情失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 根据UUID获取通话详单详情 - */ - @GetMapping("/uuid/{uuid}") - public ResponseEntity> getCdrByUuid(@PathVariable String uuid) { - try { - Optional entity = cdrService.findByUuid(uuid); - if (entity.isPresent()) { - FreeSwitchCdrDto dto = dtoMapper.toCdrDto(entity.get()); - return ResponseEntity.ok(JsonResult.success(dto)); - } else { - return ResponseEntity.notFound().build(); - } - } catch (Exception e) { - log.error("获取FreeSwitch CDR详情失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取今日通话统计 - */ - @GetMapping("/statistics/today") - public ResponseEntity> getTodayStatistics() { - try { - FreeSwitchCdrService.CallStatistics statistics = cdrService.getTodayStatistics(); - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取今日通话统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取时间范围内的通话统计 - */ - @GetMapping("/statistics/range") - public ResponseEntity> getStatisticsByRange( - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { - - try { - FreeSwitchCdrService.CallStatistics statistics = cdrService.getStatisticsByTimeRange(startTime, endTime); - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取时间范围通话统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取用户通话统计 - */ - @GetMapping("/statistics/user/{callerNumber}") - public ResponseEntity> getUserStatistics( - @PathVariable String callerNumber, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { - - try { - FreeSwitchCdrService.CallStatistics statistics; - if (startTime != null && endTime != null) { - statistics = cdrService.getUserStatistics(callerNumber, startTime, endTime); - } else { - statistics = cdrService.getUserStatistics(callerNumber); - } - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取用户通话统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取通话方向统计 - */ - @GetMapping("/statistics/direction") - public ResponseEntity>> getCallDirectionStatistics( - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { - - try { - List statistics; - if (startTime != null && endTime != null) { - statistics = cdrService.getCallDirectionStatistics(startTime, endTime); - } else { - statistics = cdrService.getCallDirectionStatistics(); - } - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取通话方向统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取挂断原因统计 - */ - @GetMapping("/statistics/hangup-cause") - public ResponseEntity>> getHangupCauseStatistics( - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime) { - - try { - List statistics; - if (startTime != null && endTime != null) { - statistics = cdrService.getHangupCauseStatistics(startTime, endTime); - } else { - statistics = cdrService.getHangupCauseStatistics(); - } - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取挂断原因统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取长通话记录 - */ - @GetMapping("/long-calls") - public ResponseEntity>> getLongCalls( - @RequestParam(defaultValue = "300") long minDuration) { - - try { - List entities = cdrService.findLongCalls(minDuration); - List dtos = entities.stream() - .map(dtoMapper::toCdrDto) - .collect(Collectors.toList()); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取长通话记录失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取失败通话记录 - */ - @GetMapping("/failed-calls") - public ResponseEntity>> getFailedCalls( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "20") int size) { - - try { - Pageable pageable = PageRequest.of(page, size, Sort.by("startTime").descending()); - Page entities = cdrService.findFailedCalls(pageable); - Page dtos = entities.map(dtoMapper::toCdrDto); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取失败通话记录失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 删除过期的CDR记录 - */ - @DeleteMapping("/cleanup") - public ResponseEntity> cleanupExpiredCdr( - @RequestParam(defaultValue = "90") int retentionDays, - @CurrentUser UserEntity currentUser) { - - try { - int deletedCount = cdrService.cleanupExpiredRecords(retentionDays); - return ResponseEntity.ok(JsonResult.success(deletedCount, - String.format("已清理%d条过期CDR记录", deletedCount))); - } catch (Exception e) { - log.error("清理过期CDR记录失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 搜索通话记录 - */ - @GetMapping("/search") - public ResponseEntity>> searchCdr( - @RequestParam String keyword, - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "20") int size) { - - try { - Pageable pageable = PageRequest.of(page, size, Sort.by("startTime").descending()); - Page entities = cdrService.searchCdr(keyword, pageable); - Page dtos = entities.map(dtoMapper::toCdrDto); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("搜索CDR记录失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 导出CDR记录 - */ - @GetMapping("/export") - public ResponseEntity> exportCdr( - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startTime, - @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endTime, - @RequestParam(defaultValue = "csv") String format) { - - try { - // 这里可以实现实际的导出逻辑 - // 返回导出文件的下载链接或直接返回数据 - - String message = String.format("CDR记录导出请求已提交,格式: %s", format); - if (startTime != null && endTime != null) { - message += String.format(",时间范围: %s 至 %s", startTime, endTime); - } - - return ResponseEntity.ok(JsonResult.success("", message)); - } catch (Exception e) { - log.error("导出CDR记录失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } -} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrDto.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrDto.java deleted file mode 100644 index 06fb2bbf6b..0000000000 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrDto.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.bytedesk.freeswitch.dto; - -import com.fasterxml.jackson.annotation.JsonFormat; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * FreeSwitch通话详单DTO - */ -@Data -public class FreeSwitchCdrDto { - - private Long id; - - private String uuid; - - private String callerIdName; - - private String callerIdNumber; - - private String destinationNumber; - - private String context; - - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime startTime; - - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime answerTime; - - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime endTime; - - private Long duration; - - private Long billDuration; - - private String hangupCause; - - private String accountCode; - - private String direction; - - private String readCodec; - - private String writeCodec; - - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createdAt; - - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updatedAt; - - // 计算属性 - public String getFormattedDuration() { - if (duration == null) return "00:00:00"; - - long hours = duration / 3600; - long minutes = (duration % 3600) / 60; - long seconds = duration % 60; - - return String.format("%02d:%02d:%02d", hours, minutes, seconds); - } - - public String getCallStatus() { - if (answerTime == null) { - return "未接听"; - } else if ("NORMAL_CLEARING".equals(hangupCause)) { - return "正常结束"; - } else if ("USER_BUSY".equals(hangupCause)) { - return "用户忙"; - } else if ("NO_ANSWER".equals(hangupCause)) { - return "无应答"; - } else { - return "其他"; - } - } -} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntity.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntity.java index 440adfe870..a4d6d43b9a 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntity.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntity.java @@ -11,7 +11,7 @@ * * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. */ -package com.bytedesk.freeswitch.model; +package com.bytedesk.freeswitch.cdr; import java.time.LocalDateTime; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntityListener.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntityListener.java index bb52149e53..0e392a5b3b 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntityListener.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrEntityListener.java @@ -11,7 +11,7 @@ * * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. */ -package com.bytedesk.freeswitch.model; +package com.bytedesk.freeswitch.cdr; import org.springframework.stereotype.Component; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrRepository.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrRepository.java index bceca7a511..c143e6c0d6 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrRepository.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrRepository.java @@ -11,7 +11,7 @@ * * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. */ -package com.bytedesk.freeswitch.repository; +package com.bytedesk.freeswitch.cdr; import java.time.LocalDateTime; import java.util.List; @@ -25,8 +25,6 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; - /** * FreeSwitch CDR仓库接口 */ diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrService.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrService.java index ff4ce67b2f..5f9f03b36f 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrService.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/cdr/FreeSwitchCdrService.java @@ -2,7 +2,7 @@ * @Author: jackning 270580156@qq.com * @Date: 2025-06-09 10:00:00 * @LastEditors: jackning 270580156@qq.com - * @LastEditTime: 2025-06-09 10:00:00 + * @LastEditTime: 2025-06-08 17:40:37 * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk * Please be aware of the BSL license restrictions before installing Bytedesk IM – * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. @@ -11,7 +11,7 @@ * * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. */ -package com.bytedesk.freeswitch.service; +package com.bytedesk.freeswitch.cdr; import java.time.LocalDateTime; import java.util.List; @@ -23,9 +23,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; -import com.bytedesk.freeswitch.repository.FreeSwitchCdrRepository; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/CreateFreeSwitchConferenceRequest.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/CreateFreeSwitchConferenceRequest.java deleted file mode 100644 index ae532939bb..0000000000 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/CreateFreeSwitchConferenceRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.bytedesk.freeswitch.dto; - -import lombok.Data; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; -import javax.validation.constraints.Min; - -/** - * 创建FreeSwitch会议室请求DTO - */ -@Data -public class CreateFreeSwitchConferenceRequest { - - @NotBlank(message = "会议室名称不能为空") - @Size(min = 2, max = 50, message = "会议室名称长度必须在2-50个字符之间") - private String name; - - @Size(max = 200, message = "描述不能超过200个字符") - private String description; - - @Size(min = 4, max = 20, message = "主持人PIN长度必须在4-20个字符之间") - private String moderatorPin; - - @Size(min = 4, max = 20, message = "成员PIN长度必须在4-20个字符之间") - private String memberPin; - - @Min(value = 1, message = "最大成员数必须大于0") - private Integer maxMembers; - - private Boolean active = true; -} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceController.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceController.java index 98136f6214..fad19e5d77 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceController.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceController.java @@ -1,332 +1 @@ -package com.bytedesk.freeswitch.controller; - -import com.bytedesk.core.rbac.user.UserEntity; -import com.bytedesk.core.utils.JsonResult; -import com.bytedesk.freeswitch.dto.*; -import com.bytedesk.freeswitch.model.FreeSwitchConferenceEntity; -import com.bytedesk.freeswitch.service.FreeSwitchConferenceService; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -/** - * FreeSwitch会议室管理REST API控制器 - */ -@Slf4j -@RestController -@RequestMapping("/freeswitch/api/v1/conferences") -@AllArgsConstructor -@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true") -public class FreeSwitchConferenceController { - - private final FreeSwitchConferenceService conferenceService; - private final FreeSwitchDtoMapper dtoMapper; - - /** - * 创建会议室 - */ - @PostMapping - public ResponseEntity> createConference( - @Valid @RequestBody CreateFreeSwitchConferenceRequest request, - @CurrentUser UserEntity currentUser) { - - try { - FreeSwitchConferenceEntity created = conferenceService.createConference( - request.getName(), - request.getDescription(), - request.getModeratorPin(), - request.getMemberPin(), - request.getMaxMembers() - ); - - FreeSwitchConferenceDto dto = dtoMapper.toConferenceDto(created); - return ResponseEntity.ok(JsonResult.success(dto, "会议室创建成功")); - } catch (Exception e) { - log.error("创建FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取会议室列表 - */ - @GetMapping - public ResponseEntity>> getConferences( - @RequestParam(defaultValue = "0") int page, - @RequestParam(defaultValue = "20") int size, - @RequestParam(defaultValue = "id") String sort, - @RequestParam(defaultValue = "desc") String direction, - @RequestParam(required = false) String keyword, - @RequestParam(required = false) Boolean active, - @RequestParam(required = false) Boolean withMembers) { - - try { - Sort.Direction sortDirection = "asc".equalsIgnoreCase(direction) ? - Sort.Direction.ASC : Sort.Direction.DESC; - Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort)); - - Page entities; - - if (keyword != null && !keyword.trim().isEmpty()) { - entities = conferenceService.searchConferences(keyword, pageable); - } else { - entities = conferenceService.searchConferences("", pageable); - } - - Page dtos = entities.map(dtoMapper::toConferenceDto); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取FreeSwitch会议室列表失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 根据ID获取会议室详情 - */ - @GetMapping("/{id}") - public ResponseEntity> getConferenceById(@PathVariable Long id) { - try { - Optional entity = conferenceService.findById(id); - if (entity.isPresent()) { - FreeSwitchConferenceDto dto = dtoMapper.toConferenceDto(entity.get()); - return ResponseEntity.ok(JsonResult.success(dto)); - } else { - return ResponseEntity.notFound().build(); - } - } catch (Exception e) { - log.error("获取FreeSwitch会议室详情失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 根据名称获取会议室详情 - */ - @GetMapping("/name/{name}") - public ResponseEntity> getConferenceByName(@PathVariable String name) { - try { - Optional entity = conferenceService.findByName(name); - if (entity.isPresent()) { - FreeSwitchConferenceDto dto = dtoMapper.toConferenceDto(entity.get()); - return ResponseEntity.ok(JsonResult.success(dto)); - } else { - return ResponseEntity.notFound().build(); - } - } catch (Exception e) { - log.error("获取FreeSwitch会议室详情失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 更新会议室信息 - */ - @PutMapping("/{id}") - public ResponseEntity> updateConference( - @PathVariable Long id, - @Valid @RequestBody CreateFreeSwitchConferenceRequest request, - @CurrentUser UserEntity currentUser) { - - try { - FreeSwitchConferenceEntity updated = conferenceService.updateConference( - id, - request.getName(), - request.getDescription(), - request.getModeratorPin(), - request.getMemberPin(), - request.getMaxMembers(), - request.getActive() - ); - - FreeSwitchConferenceDto dto = dtoMapper.toConferenceDto(updated); - return ResponseEntity.ok(JsonResult.success(dto, "会议室更新成功")); - } catch (Exception e) { - log.error("更新FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 删除会议室 - */ - @DeleteMapping("/{id}") - public ResponseEntity> deleteConference( - @PathVariable Long id, - @CurrentUser UserEntity currentUser) { - - try { - conferenceService.deleteConference(id); - return ResponseEntity.ok(JsonResult.success("会议室删除成功")); - } catch (Exception e) { - log.error("删除FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 激活/停用会议室 - */ - @PatchMapping("/{id}/toggle-status") - public ResponseEntity> toggleConferenceStatus( - @PathVariable Long id, - @CurrentUser UserEntity currentUser) { - - try { - conferenceService.toggleConferenceStatus(id); - return ResponseEntity.ok(JsonResult.success("会议室状态切换成功")); - } catch (Exception e) { - log.error("切换FreeSwitch会议室状态失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 验证会议室访问权限 - */ - @PostMapping("/{name}/validate-access") - public ResponseEntity> validateAccess( - @PathVariable String name, - @RequestParam String pin, - @RequestParam(defaultValue = "false") boolean isModerator) { - - try { - boolean hasAccess = conferenceService.validateAccess(name, pin, isModerator); - return ResponseEntity.ok(JsonResult.success(hasAccess, - hasAccess ? "访问验证成功" : "访问验证失败")); - } catch (Exception e) { - log.error("验证FreeSwitch会议室访问权限失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 成员加入会议室 - */ - @PostMapping("/{name}/join") - public ResponseEntity> joinConference( - @PathVariable String name, - @RequestParam String memberUuid) { - - try { - boolean joined = conferenceService.joinConference(name, memberUuid); - return ResponseEntity.ok(JsonResult.success(joined, - joined ? "成功加入会议室" : "加入会议室失败")); - } catch (Exception e) { - log.error("加入FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 成员离开会议室 - */ - @PostMapping("/{name}/leave") - public ResponseEntity> leaveConference( - @PathVariable String name, - @RequestParam String memberUuid) { - - try { - conferenceService.leaveConference(name, memberUuid); - return ResponseEntity.ok(JsonResult.success("成功离开会议室")); - } catch (Exception e) { - log.error("离开FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 重置会议室成员数 - */ - @PatchMapping("/{id}/reset-members") - public ResponseEntity> resetMemberCount( - @PathVariable Long id, - @CurrentUser UserEntity currentUser) { - - try { - conferenceService.resetMemberCount(id); - return ResponseEntity.ok(JsonResult.success("会议室成员数重置成功")); - } catch (Exception e) { - log.error("重置FreeSwitch会议室成员数失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取活跃会议室列表 - */ - @GetMapping("/active") - public ResponseEntity>> getActiveConferences() { - try { - List entities = conferenceService.getActiveConferences(); - List dtos = entities.stream() - .map(dtoMapper::toConferenceDto) - .collect(Collectors.toList()); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取活跃FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取当前有成员的会议室 - */ - @GetMapping("/with-members") - public ResponseEntity>> getConferencesWithMembers() { - try { - List entities = conferenceService.getConferencesWithMembers(); - List dtos = entities.stream() - .map(dtoMapper::toConferenceDto) - .collect(Collectors.toList()); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("获取有成员的FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 根据最大成员数过滤会议室 - */ - @GetMapping("/by-capacity") - public ResponseEntity>> getConferencesByCapacity( - @RequestParam Integer minCapacity) { - - try { - List entities = conferenceService.findByMaxMembersGreaterThanEqual(minCapacity); - List dtos = entities.stream() - .map(dtoMapper::toConferenceDto) - .collect(Collectors.toList()); - return ResponseEntity.ok(JsonResult.success(dtos)); - } catch (Exception e) { - log.error("根据容量获取FreeSwitch会议室失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } - - /** - * 获取会议室统计信息 - */ - @GetMapping("/statistics") - public ResponseEntity> getStatistics() { - try { - FreeSwitchConferenceService.ConferenceStatistics statistics = conferenceService.getStatistics(); - return ResponseEntity.ok(JsonResult.success(statistics)); - } catch (Exception e) { - log.error("获取FreeSwitch会议室统计失败", e); - return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); - } - } -} +package com.bytedesk.freeswitch.conference;import com.bytedesk.core.utils.JsonResult;import lombok.AllArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.data.domain.Page;import org.springframework.data.domain.PageRequest;import org.springframework.data.domain.Pageable;import org.springframework.data.domain.Sort;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import javax.validation.Valid;import java.util.List;import java.util.Optional;/** * FreeSwitch会议室管理REST API控制器 */@Slf4j@RestController@RequestMapping("/freeswitch/api/v1/conferences")@AllArgsConstructor@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true")public class FreeSwitchConferenceController { private final FreeSwitchConferenceService conferenceService; /** * 创建会议室 */ @PostMapping public ResponseEntity> createConference( @Valid @RequestBody FreeSwitchConferenceRequest request) { try { FreeSwitchConferenceEntity created = conferenceService.createConference( request.getConferenceName(), request.getDescription(), request.getModeratorPin(), request.getMaxMembers() ); FreeSwitchConferenceResponse response = FreeSwitchConferenceResponse.fromEntity(created); return ResponseEntity.ok(JsonResult.success("会议室创建成功", response)); } catch (Exception e) { log.error("创建会议室失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage(), 400, (FreeSwitchConferenceResponse) null)); } } /** * 分页查询会议室列表 */ @GetMapping public ResponseEntity>> listConferences( @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, @RequestParam(defaultValue = "createdAt") String sortBy, @RequestParam(defaultValue = "desc") String sortDirection, @RequestParam(required = false) String keyword) { try { Sort.Direction direction = Sort.Direction.fromString(sortDirection); Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy)); Page entities; if (keyword != null && !keyword.trim().isEmpty()) { entities = conferenceService.findByConferenceNameContaining(keyword.trim(), pageable); } else { entities = conferenceService.findAll(pageable); } Page responses = entities.map(FreeSwitchConferenceResponse::fromEntity); return ResponseEntity.ok(JsonResult.success("查询成功", responses)); } catch (Exception e) { log.error("查询会议室列表失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } } /** * 根据ID获取会议室详情 */ @GetMapping("/{id}") public ResponseEntity> getConference(@PathVariable Long id) { try { Optional entity = conferenceService.findById(id); if (entity.isPresent()) { FreeSwitchConferenceResponse response = FreeSwitchConferenceResponse.fromEntity(entity.get()); return ResponseEntity.ok(JsonResult.success("查询成功", response)); } else { return ResponseEntity.notFound().build(); } } catch (Exception e) { log.error("获取会议室详情失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } } /** * 根据名称获取会议室详情 */ @GetMapping("/by-name/{name}") public ResponseEntity> getConferenceByName(@PathVariable String name) { try { Optional entity = conferenceService.findByConferenceName(name); if (entity.isPresent()) { FreeSwitchConferenceResponse response = FreeSwitchConferenceResponse.fromEntity(entity.get()); return ResponseEntity.ok(JsonResult.success("查询成功", response)); } else { return ResponseEntity.notFound().build(); } } catch (Exception e) { log.error("根据名称获取会议室详情失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } } /** * 更新会议室信息 */ @PutMapping("/{id}") public ResponseEntity> updateConference( @PathVariable Long id, @Valid @RequestBody FreeSwitchConferenceRequest request) { try { FreeSwitchConferenceEntity updated = conferenceService.updateConference( id, request.getConferenceName(), request.getDescription(), request.getModeratorPin(), request.getMaxMembers(), request.getEnabled() ); FreeSwitchConferenceResponse response = FreeSwitchConferenceResponse.fromEntity(updated); return ResponseEntity.ok(JsonResult.success("会议室更新成功", response)); } catch (Exception e) { log.error("更新会议室失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage(), 400, (FreeSwitchConferenceResponse) null)); } } /** * 删除会议室 */ @DeleteMapping("/{id}") public ResponseEntity> deleteConference(@PathVariable Long id) { try { conferenceService.deleteById(id); return ResponseEntity.ok(JsonResult.success("会议室删除成功", "会议室删除成功")); } catch (Exception e) { log.error("删除会议室失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage(), 400, "删除失败")); } } /** * 切换会议室状态 */ @PatchMapping("/{id}/toggle-status") public ResponseEntity> toggleConferenceStatus(@PathVariable Long id) { try { conferenceService.toggleStatus(id); return ResponseEntity.ok(JsonResult.success("会议室状态切换成功", "会议室状态切换成功")); } catch (Exception e) { log.error("切换会议室状态失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage(), 400, "状态切换失败")); } } /** * 验证会议室访问权限 */ @PostMapping("/{name}/validate-access") public ResponseEntity> validateAccess( @PathVariable String name, @RequestParam String pin) { try { boolean hasAccess = conferenceService.validateAccess(name, pin); return ResponseEntity.ok(JsonResult.success(hasAccess ? "访问验证成功" : "访问验证失败", hasAccess)); } catch (Exception e) { log.error("验证会议室访问失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } } /** * 获取所有启用的会议室 */ @GetMapping("/enabled") public ResponseEntity>> getEnabledConferences() { try { List entities = conferenceService.findByEnabled(true); List responses = entities.stream() .map(FreeSwitchConferenceResponse::fromEntity) .toList(); return ResponseEntity.ok(JsonResult.success("查询成功", responses)); } catch (Exception e) { log.error("获取启用会议室列表失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } } /** * 根据最大成员数查询会议室 */ @GetMapping("/by-capacity") public ResponseEntity>> getConferencesByCapacity( @RequestParam Integer minCapacity) { try { List entities = conferenceService.findByMaxMembersGreaterThanEqual(minCapacity); List responses = entities.stream() .map(FreeSwitchConferenceResponse::fromEntity) .toList(); return ResponseEntity.ok(JsonResult.success("查询成功", responses)); } catch (Exception e) { log.error("根据容量查询会议室失败", e); return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); } }} \ No newline at end of file diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceDto.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceDto.java index d142f1bfa3..a20654d78c 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceDto.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceDto.java @@ -1,3 +1,16 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-08 16:01:30 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-08 17:26:54 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ package com.bytedesk.freeswitch.dto; import com.fasterxml.jackson.annotation.JsonFormat; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceRequest.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceRequest.java index 1b1d484806..b80c6b8418 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceRequest.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceRequest.java @@ -79,4 +79,11 @@ public class FreeSwitchConferenceRequest extends BaseRequest { * 备注 */ private String remarks; + + /** + * 获取主持人密码(兼容性方法) + */ + public String getModeratorPin() { + return this.password; + } } diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceResponse.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceResponse.java index 5de3b3390b..619e541269 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceResponse.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceResponse.java @@ -17,14 +17,14 @@ import com.bytedesk.core.base.BaseResponse; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; +import lombok.experimental.SuperBuilder; @Data -@Builder +@SuperBuilder @Accessors(chain = true) @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @@ -86,4 +86,26 @@ public class FreeSwitchConferenceResponse extends BaseResponse { * 是否有密码保护 */ private Boolean passwordProtected; + + /** + * 从实体对象创建响应对象 + */ + public static FreeSwitchConferenceResponse fromEntity(FreeSwitchConferenceEntity entity) { + return FreeSwitchConferenceResponse.builder() + .uid(entity.getUid()) + .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) + .conferenceName(entity.getConferenceName()) + .description(entity.getDescription()) + .password(entity.getPassword()) + .maxMembers(entity.getMaxMembers()) + .enabled(entity.getEnabled()) + .recordEnabled(entity.getRecordEnabled()) + .recordPath(entity.getRecordPath()) + .creator(entity.getCreator()) + .configJson(entity.getConfigJson()) + .remarks(entity.getRemarks()) + .passwordProtected(entity.getPassword() != null && !entity.getPassword().isEmpty()) + .build(); + } } diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceService.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceService.java index 585456a5e1..6bfbeaf5e2 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceService.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/conference/FreeSwitchConferenceService.java @@ -23,6 +23,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; /** * FreeSwitch会议服务类 @@ -63,6 +64,20 @@ public class FreeSwitchConferenceService { return conferenceRepository.findById(id); } + /** + * 分页查询所有会议室 + */ + public Page findAll(Pageable pageable) { + return conferenceRepository.findAll(pageable); + } + + /** + * 根据会议室名称模糊查询 + */ + public Page findByConferenceNameContaining(String keyword, Pageable pageable) { + return conferenceRepository.findByConferenceNameContainingIgnoreCase(keyword, pageable); + } + /** * 根据会议室名称查找会议室 */ @@ -157,6 +172,51 @@ public class FreeSwitchConferenceService { return false; } + /** + * 根据ID删除会议室 + */ + @Transactional + public void deleteById(Long id) { + conferenceRepository.deleteById(id); + log.info("删除会议室: ID = {}", id); + } + + /** + * 切换会议室状态 + */ + @Transactional + public void toggleStatus(Long id) { + Optional conferenceOpt = conferenceRepository.findById(id); + if (conferenceOpt.isPresent()) { + FreeSwitchConferenceEntity conference = conferenceOpt.get(); + conference.setEnabled(!conference.getEnabled()); + conferenceRepository.save(conference); + log.info("切换会议室状态: {} -> {}", conference.getConferenceName(), conference.getEnabled() ? "启用" : "禁用"); + } else { + throw new RuntimeException("会议室不存在: " + id); + } + } + + /** + * 根据启用状态查找会议室 + */ + public List findByEnabled(Boolean enabled) { + if (enabled) { + return conferenceRepository.findByEnabledTrue(); + } else { + return conferenceRepository.findByEnabledFalse(); + } + } + + /** + * 查找最大成员数量大于等于指定值的会议室 + */ + public List findByMaxMembersGreaterThanEqual(Integer minCapacity) { + return conferenceRepository.findAll().stream() + .filter(conference -> conference.getMaxMembers() != null && conference.getMaxMembers() >= minCapacity) + .collect(Collectors.toList()); + } + /** * 获取所有会议室(分页) */ diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/dto/FreeSwitchDtoMapper.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/dto/FreeSwitchDtoMapper.java deleted file mode 100644 index 7ef002e83c..0000000000 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/dto/FreeSwitchDtoMapper.java +++ /dev/null @@ -1,110 +0,0 @@ -package com.bytedesk.freeswitch.dto; - -import com.bytedesk.freeswitch.model.FreeSwitchUserEntity; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; -import com.bytedesk.freeswitch.model.FreeSwitchConferenceEntity; -import com.bytedesk.freeswitch.model.FreeSwitchGatewayEntity; -import org.springframework.beans.BeanUtils; -import org.springframework.stereotype.Component; - -/** - * FreeSwitch DTO转换工具类 - */ -@Component -public class FreeSwitchDtoMapper { - - /** - * 用户实体转DTO - */ - public FreeSwitchUserDto toUserDto(FreeSwitchUserEntity entity) { - if (entity == null) { - return null; - } - - FreeSwitchUserDto dto = new FreeSwitchUserDto(); - BeanUtils.copyProperties(entity, dto); - return dto; - } - - /** - * CDR实体转DTO - */ - public FreeSwitchCdrDto toCdrDto(FreeSwitchCdrEntity entity) { - if (entity == null) { - return null; - } - - FreeSwitchCdrDto dto = new FreeSwitchCdrDto(); - BeanUtils.copyProperties(entity, dto); - return dto; - } - - /** - * 会议室实体转DTO - */ - public FreeSwitchConferenceDto toConferenceDto(FreeSwitchConferenceEntity entity) { - if (entity == null) { - return null; - } - - FreeSwitchConferenceDto dto = new FreeSwitchConferenceDto(); - BeanUtils.copyProperties(entity, dto); - return dto; - } - - /** - * 网关实体转DTO - */ - public FreeSwitchGatewayDto toGatewayDto(FreeSwitchGatewayEntity entity) { - if (entity == null) { - return null; - } - - FreeSwitchGatewayDto dto = new FreeSwitchGatewayDto(); - BeanUtils.copyProperties(entity, dto); - // 不复制密码字段 - dto.setPassword(null); - return dto; - } - - /** - * 创建用户请求转实体 - */ - public FreeSwitchUserEntity toUserEntity(CreateFreeSwitchUserRequest request) { - if (request == null) { - return null; - } - - FreeSwitchUserEntity entity = new FreeSwitchUserEntity(); - BeanUtils.copyProperties(request, entity); - return entity; - } - - /** - * 创建会议室请求转实体 - */ - public FreeSwitchConferenceEntity toConferenceEntity(CreateFreeSwitchConferenceRequest request) { - if (request == null) { - return null; - } - - FreeSwitchConferenceEntity entity = new FreeSwitchConferenceEntity(); - BeanUtils.copyProperties(request, entity); - entity.setCurrentMembers(0); // 初始成员数为0 - return entity; - } - - /** - * 创建网关请求转实体 - */ - public FreeSwitchGatewayEntity toGatewayEntity(CreateFreeSwitchGatewayRequest request) { - if (request == null) { - return null; - } - - FreeSwitchGatewayEntity entity = new FreeSwitchGatewayEntity(); - BeanUtils.copyProperties(request, entity); - entity.setStatus("CREATED"); // 初始状态 - return entity; - } -} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/freeswitch/FreeSwitchEventListener.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/freeswitch/FreeSwitchEventListener.java index 53e0230742..d35bd04820 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/freeswitch/FreeSwitchEventListener.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/freeswitch/FreeSwitchEventListener.java @@ -13,10 +13,10 @@ import com.bytedesk.freeswitch.callcenter.event.CallAnsweredEvent; import com.bytedesk.freeswitch.callcenter.event.CallHangupEvent; import com.bytedesk.freeswitch.callcenter.event.CallStartEvent; import com.bytedesk.freeswitch.callcenter.event.DtmfEvent; -import com.bytedesk.freeswitch.model.FreeSwitchCdrEntity; -import com.bytedesk.freeswitch.model.FreeSwitchUserEntity; -import com.bytedesk.freeswitch.service.FreeSwitchCdrService; -import com.bytedesk.freeswitch.service.FreeSwitchUserService; +import com.bytedesk.freeswitch.cdr.FreeSwitchCdrEntity; +import com.bytedesk.freeswitch.user.FreeSwitchUserEntity; +import com.bytedesk.freeswitch.cdr.FreeSwitchCdrService; +import com.bytedesk.freeswitch.user.FreeSwitchUserService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayRequest.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayRequest.java index e69de29bb2..b3ba79224c 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayRequest.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayRequest.java @@ -0,0 +1,107 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.gateway; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * FreeSwitch网关请求实体 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FreeSwitchGatewayRequest { + + /** + * 网关名称 + */ + @NotBlank(message = "网关名称不能为空") + @Size(max = 100, message = "网关名称长度不能超过100字符") + private String gatewayName; + + /** + * 网关描述 + */ + @Size(max = 255, message = "网关描述长度不能超过255字符") + private String description; + + /** + * SIP服务器地址 + */ + @NotBlank(message = "SIP服务器地址不能为空") + @Size(max = 255, message = "SIP服务器地址长度不能超过255字符") + private String proxy; + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + @Size(max = 100, message = "用户名长度不能超过100字符") + private String username; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + @Size(max = 255, message = "密码长度不能超过255字符") + private String password; + + /** + * 从号码 + */ + @Size(max = 100, message = "从号码长度不能超过100字符") + private String fromUser; + + /** + * 从域名 + */ + @Size(max = 100, message = "从域名长度不能超过100字符") + private String fromDomain; + + /** + * 注册 + */ + @NotNull(message = "注册标志不能为空") + private Boolean register = true; + + /** + * 注册传输协议 + */ + @Size(max = 20, message = "注册传输协议长度不能超过20字符") + private String registerTransport = "udp"; + + /** + * 是否启用 + */ + @NotNull(message = "启用标志不能为空") + private Boolean enabled = true; + + /** + * 扩展配置(JSON格式) + */ + private String configJson; + + /** + * 备注 + */ + @Size(max = 500, message = "备注长度不能超过500字符") + private String remarks; +} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayService.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayService.java index e69de29bb2..0020489417 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayService.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/gateway/FreeSwitchGatewayService.java @@ -0,0 +1,225 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.gateway; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +/** + * FreeSwitch网关服务类 + * 处理SIP网关配置、状态监控、连接管理等业务逻辑 + */ +@Slf4j +@Service +@AllArgsConstructor +@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true") +public class FreeSwitchGatewayService { + + private final FreeSwitchGatewayRepository gatewayRepository; + + /** + * 创建新网关 + */ + @Transactional + public FreeSwitchGatewayEntity createGateway(String gatewayName, String description, String proxy, + String username, String password, String fromUser, + String fromDomain, Boolean register, String registerTransport) { + FreeSwitchGatewayEntity gateway = FreeSwitchGatewayEntity.builder() + .gatewayName(gatewayName) + .description(description) + .proxy(proxy) + .username(username) + .password(password) + .fromUser(fromUser) + .fromDomain(fromDomain) + .register(register != null ? register : true) + .registerTransport(registerTransport != null ? registerTransport : "udp") + .enabled(true) + .status("DOWN") + .build(); + + FreeSwitchGatewayEntity saved = gatewayRepository.save(gateway); + log.info("创建网关: {} (ID: {}) -> {}", gatewayName, saved.getId(), proxy); + return saved; + } + + /** + * 根据ID查找网关 + */ + public Optional findById(Long id) { + return gatewayRepository.findById(id); + } + + /** + * 根据名称查找网关 + */ + public Optional findByGatewayName(String gatewayName) { + return gatewayRepository.findByGatewayName(gatewayName); + } + + /** + * 获取所有启用的网关 + */ + public List findEnabledGateways() { + return gatewayRepository.findByEnabledTrue(); + } + + /** + * 获取所有网关(分页) + */ + public Page findAll(Pageable pageable) { + return gatewayRepository.findAll(pageable); + } + + /** + * 根据状态查找网关 + */ + public List findByStatus(String status) { + return gatewayRepository.findByStatus(status); + } + + /** + * 更新网关信息 + */ + @Transactional + public FreeSwitchGatewayEntity updateGateway(Long id, String description, String proxy, + String username, String password, String fromUser, + String fromDomain, Boolean register, String registerTransport) { + FreeSwitchGatewayEntity gateway = gatewayRepository.findById(id) + .orElseThrow(() -> new RuntimeException("网关不存在: " + id)); + + if (description != null) gateway.setDescription(description); + if (proxy != null) gateway.setProxy(proxy); + if (username != null) gateway.setUsername(username); + if (password != null) gateway.setPassword(password); + if (fromUser != null) gateway.setFromUser(fromUser); + if (fromDomain != null) gateway.setFromDomain(fromDomain); + if (register != null) gateway.setRegister(register); + if (registerTransport != null) gateway.setRegisterTransport(registerTransport); + + FreeSwitchGatewayEntity saved = gatewayRepository.save(gateway); + log.info("更新网关: {} (ID: {})", gateway.getGatewayName(), id); + return saved; + } + + /** + * 启用网关 + */ + @Transactional + public void enableGateway(Long id) { + FreeSwitchGatewayEntity gateway = gatewayRepository.findById(id) + .orElseThrow(() -> new RuntimeException("网关不存在: " + id)); + + gateway.setEnabled(true); + gatewayRepository.save(gateway); + log.info("启用网关: {} (ID: {})", gateway.getGatewayName(), id); + } + + /** + * 禁用网关 + */ + @Transactional + public void disableGateway(Long id) { + FreeSwitchGatewayEntity gateway = gatewayRepository.findById(id) + .orElseThrow(() -> new RuntimeException("网关不存在: " + id)); + + gateway.setEnabled(false); + gatewayRepository.save(gateway); + log.info("禁用网关: {} (ID: {})", gateway.getGatewayName(), id); + } + + /** + * 更新网关状态 + */ + @Transactional + public void updateGatewayStatus(Long id, String status) { + FreeSwitchGatewayEntity gateway = gatewayRepository.findById(id) + .orElseThrow(() -> new RuntimeException("网关不存在: " + id)); + + gateway.setStatus(status); + gatewayRepository.save(gateway); + log.debug("更新网关状态: {} (ID: {}) -> {}", gateway.getGatewayName(), id, status); + } + + /** + * 根据网关名称更新状态 + */ + @Transactional + public void updateGatewayStatusByName(String gatewayName, String status) { + Optional gatewayOpt = gatewayRepository.findByGatewayName(gatewayName); + if (gatewayOpt.isPresent()) { + FreeSwitchGatewayEntity gateway = gatewayOpt.get(); + gateway.setStatus(status); + gatewayRepository.save(gateway); + log.debug("更新网关状态: {} -> {}", gatewayName, status); + } else { + log.warn("网关不存在: {}", gatewayName); + } + } + + /** + * 删除网关 + */ + @Transactional + public void deleteGateway(Long id) { + FreeSwitchGatewayEntity gateway = gatewayRepository.findById(id) + .orElseThrow(() -> new RuntimeException("网关不存在: " + id)); + + gatewayRepository.delete(gateway); + log.info("删除网关: {} (ID: {})", gateway.getGatewayName(), id); + } + + /** + * 获取所有在线的网关 + */ + public List findOnlineGateways() { + return gatewayRepository.findByStatusAndEnabledTrue("UP"); + } + + /** + * 获取网关总数 + */ + public long countTotal() { + return gatewayRepository.count(); + } + + /** + * 获取启用的网关数量 + */ + public long countEnabled() { + return gatewayRepository.countByEnabledTrue(); + } + + /** + * 获取在线的网关数量 + */ + public long countOnline() { + return gatewayRepository.countByStatusAndEnabledTrue("UP"); + } + + /** + * 检查网关名称是否存在 + */ + public boolean existsByGatewayName(String gatewayName) { + return gatewayRepository.existsByGatewayName(gatewayName); + } +} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserController.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserController.java new file mode 100644 index 0000000000..e54852913c --- /dev/null +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserController.java @@ -0,0 +1,342 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.user; + +import com.bytedesk.core.utils.JsonResult; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * FreeSwitch用户管理REST API控制器 + */ +@Slf4j +@RestController +@RequestMapping("/freeswitch/api/v1/users") +@AllArgsConstructor +@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true") +public class FreeSwitchUserController { + + private final FreeSwitchUserService userService; + + /** + * 创建用户 + */ + @PostMapping + public ResponseEntity> createUser(@Valid @RequestBody FreeSwitchUserRequest request) { + try { + FreeSwitchUserEntity user = userService.createUser( + request.getUsername(), + request.getDomain(), + request.getPassword(), + request.getDisplayName(), + request.getEmail(), + request.getAccountcode() + ); + + FreeSwitchUserResponse response = FreeSwitchUserResponse.fromEntitySafe(user); + return ResponseEntity.ok(JsonResult.success("用户创建成功", response)); + } catch (Exception e) { + log.error("创建用户失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 获取用户详情 + */ + @GetMapping("/{id}") + public ResponseEntity> getUser(@PathVariable Long id) { + Optional user = userService.findById(id); + if (user.isPresent()) { + FreeSwitchUserResponse response = FreeSwitchUserResponse.fromEntitySafe(user.get()); + return ResponseEntity.ok(JsonResult.success("获取用户详情成功", response)); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * 根据用户名获取用户 + */ + @GetMapping("/username/{username}") + public ResponseEntity> getUserByUsername(@PathVariable String username) { + Optional user = userService.findByUsername(username); + if (user.isPresent()) { + FreeSwitchUserResponse response = FreeSwitchUserResponse.fromEntitySafe(user.get()); + return ResponseEntity.ok(JsonResult.success("获取用户详情成功", response)); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * 根据用户名和域名获取用户 + */ + @GetMapping("/username/{username}/domain/{domain}") + public ResponseEntity> getUserByUsernameAndDomain( + @PathVariable String username, @PathVariable String domain) { + Optional user = userService.findByUsernameAndDomain(username, domain); + if (user.isPresent()) { + FreeSwitchUserResponse response = FreeSwitchUserResponse.fromEntitySafe(user.get()); + return ResponseEntity.ok(JsonResult.success("获取用户详情成功", response)); + } else { + return ResponseEntity.notFound().build(); + } + } + + /** + * 获取用户列表(分页) + */ + @GetMapping + public ResponseEntity> getUsers( + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "desc") String sortDir) { + + Sort sort = sortDir.equalsIgnoreCase("desc") ? + Sort.by(sortBy).descending() : Sort.by(sortBy).ascending(); + Pageable pageable = PageRequest.of(page, size, sort); + + Page userPage = userService.findAll(pageable); + Page responsePage = userPage.map(FreeSwitchUserResponse::fromEntitySafe); + + return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responsePage)); + } + + /** + * 根据域名获取用户列表 + */ + @GetMapping("/domain/{domain}") + public ResponseEntity> getUsersByDomain(@PathVariable String domain) { + List users = userService.findByDomain(domain); + List responses = users.stream() + .map(FreeSwitchUserResponse::fromEntitySafe) + .collect(Collectors.toList()); + + return ResponseEntity.ok(JsonResult.success("获取域名用户列表成功", responses)); + } + + /** + * 获取启用的用户列表 + */ + @GetMapping("/enabled") + public ResponseEntity> getEnabledUsers() { + List users = userService.findEnabledUsers(); + List responses = users.stream() + .map(FreeSwitchUserResponse::fromEntitySafe) + .collect(Collectors.toList()); + + return ResponseEntity.ok(JsonResult.success("获取启用用户列表成功", responses)); + } + + /** + * 获取在线的用户列表 + */ + @GetMapping("/online") + public ResponseEntity> getOnlineUsers() { + List users = userService.findOnlineUsers(); + List responses = users.stream() + .map(FreeSwitchUserResponse::fromEntitySafe) + .collect(Collectors.toList()); + + return ResponseEntity.ok(JsonResult.success("获取在线用户列表成功", responses)); + } + + /** + * 根据邮箱获取用户列表 + */ + @GetMapping("/email/{email}") + public ResponseEntity> getUsersByEmail(@PathVariable String email) { + List users = userService.findByEmail(email); + List responses = users.stream() + .map(FreeSwitchUserResponse::fromEntitySafe) + .collect(Collectors.toList()); + + return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responses)); + } + + /** + * 根据账户代码获取用户列表 + */ + @GetMapping("/accountcode/{accountcode}") + public ResponseEntity> getUsersByAccountcode(@PathVariable String accountcode) { + List users = userService.findByAccountcode(accountcode); + List responses = users.stream() + .map(FreeSwitchUserResponse::fromEntitySafe) + .collect(Collectors.toList()); + + return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responses)); + } + + /** + * 更新用户 + */ + @PutMapping("/{id}") + public ResponseEntity> updateUser( + @PathVariable Long id, + @Valid @RequestBody FreeSwitchUserRequest request) { + try { + FreeSwitchUserEntity user = userService.updateUser( + id, + request.getPassword(), + request.getDisplayName(), + request.getEmail(), + request.getAccountcode() + ); + + FreeSwitchUserResponse response = FreeSwitchUserResponse.fromEntitySafe(user); + return ResponseEntity.ok(JsonResult.success("用户更新成功", response)); + } catch (Exception e) { + log.error("更新用户失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 启用用户 + */ + @PutMapping("/{id}/enable") + public ResponseEntity> enableUser(@PathVariable Long id) { + try { + userService.enableUser(id); + return ResponseEntity.ok(JsonResult.success("用户启用成功")); + } catch (Exception e) { + log.error("启用用户失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 禁用用户 + */ + @PutMapping("/{id}/disable") + public ResponseEntity> disableUser(@PathVariable Long id) { + try { + userService.disableUser(id); + return ResponseEntity.ok(JsonResult.success("用户禁用成功")); + } catch (Exception e) { + log.error("禁用用户失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 更新用户注册信息 + */ + @PutMapping("/registration") + public ResponseEntity> updateUserRegistration( + @RequestParam String username, + @RequestParam String domain, + @RequestParam String registerIp, + @RequestParam(required = false) String userAgent) { + try { + userService.updateUserRegistration(username, domain, registerIp, userAgent); + return ResponseEntity.ok(JsonResult.success("用户注册信息更新成功")); + } catch (Exception e) { + log.error("更新用户注册信息失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 删除用户 + */ + @DeleteMapping("/{id}") + public ResponseEntity> deleteUser(@PathVariable Long id) { + try { + userService.deleteUser(id); + return ResponseEntity.ok(JsonResult.success("用户删除成功")); + } catch (Exception e) { + log.error("删除用户失败", e); + return ResponseEntity.badRequest().body(JsonResult.error(e.getMessage())); + } + } + + /** + * 验证用户密码 + */ + @PostMapping("/validate") + public ResponseEntity> validateUserPassword( + @RequestParam String username, + @RequestParam String domain, + @RequestParam String password) { + boolean valid = userService.validateUserPassword(username, domain, password); + return ResponseEntity.ok(JsonResult.success("密码验证完成", valid)); + } + + /** + * 获取用户统计信息 + */ + @GetMapping("/stats") + public ResponseEntity> getUserStats() { + long totalCount = userService.countTotal(); + long enabledCount = userService.countEnabled(); + long onlineCount = userService.countOnline(); + + UserStats stats = new UserStats(totalCount, enabledCount, onlineCount); + return ResponseEntity.ok(JsonResult.success("获取用户统计成功", stats)); + } + + /** + * 根据域名获取用户统计信息 + */ + @GetMapping("/stats/domain/{domain}") + public ResponseEntity> getUserStatsByDomain(@PathVariable String domain) { + long domainCount = userService.countByDomain(domain); + return ResponseEntity.ok(JsonResult.success("获取域名用户统计成功", domainCount)); + } + + /** + * 检查用户名和域名组合是否存在 + */ + @GetMapping("/exists") + public ResponseEntity> checkUserExists( + @RequestParam String username, + @RequestParam String domain) { + boolean exists = userService.existsByUsernameAndDomain(username, domain); + return ResponseEntity.ok(JsonResult.success("检查用户名成功", exists)); + } + + /** + * 用户统计信息内部类 + */ + public static class UserStats { + private final long totalCount; + private final long enabledCount; + private final long onlineCount; + + public UserStats(long totalCount, long enabledCount, long onlineCount) { + this.totalCount = totalCount; + this.enabledCount = enabledCount; + this.onlineCount = onlineCount; + } + + public long getTotalCount() { return totalCount; } + public long getEnabledCount() { return enabledCount; } + public long getOnlineCount() { return onlineCount; } + } +} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserRequest.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserRequest.java index e69de29bb2..bb271a9d82 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserRequest.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserRequest.java @@ -0,0 +1,85 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.user; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * FreeSwitch用户请求实体 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FreeSwitchUserRequest { + + /** + * 用户名(SIP用户名) + */ + @NotBlank(message = "用户名不能为空") + @Size(max = 50, message = "用户名长度不能超过50字符") + private String username; + + /** + * SIP域名 + */ + @NotBlank(message = "SIP域名不能为空") + @Size(max = 100, message = "SIP域名长度不能超过100字符") + private String domain; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + @Size(max = 255, message = "密码长度不能超过255字符") + private String password; + + /** + * 显示名称 + */ + @Size(max = 100, message = "显示名称长度不能超过100字符") + private String displayName; + + /** + * 邮箱 + */ + @Email(message = "邮箱格式不正确") + @Size(max = 100, message = "邮箱长度不能超过100字符") + private String email; + + /** + * 账户代码 + */ + @Size(max = 50, message = "账户代码长度不能超过50字符") + private String accountcode; + + /** + * 是否启用 + */ + @NotNull(message = "启用标志不能为空") + private Boolean enabled = true; + + /** + * 备注 + */ + @Size(max = 500, message = "备注长度不能超过500字符") + private String remarks; +} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserResponse.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserResponse.java index e69de29bb2..0d15235df4 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserResponse.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserResponse.java @@ -0,0 +1,138 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.user; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * FreeSwitch用户响应实体 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class FreeSwitchUserResponse { + + /** + * ID + */ + private Long id; + + /** + * 用户名(SIP用户名) + */ + private String username; + + /** + * SIP域名 + */ + private String domain; + + /** + * 显示名称 + */ + private String displayName; + + /** + * 邮箱 + */ + private String email; + + /** + * 账户代码 + */ + private String accountcode; + + /** + * 是否启用 + */ + private Boolean enabled; + + /** + * 最后注册时间 + */ + private LocalDateTime lastRegister; + + /** + * 注册IP地址 + */ + private String registerIp; + + /** + * 用户代理 + */ + private String userAgent; + + /** + * 备注 + */ + private String remarks; + + /** + * 创建时间 + */ + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + private LocalDateTime updatedAt; + + /** + * SIP地址 + */ + private String sipAddress; + + /** + * 是否在线 + */ + private Boolean online; + + /** + * 从实体创建响应对象 + */ + public static FreeSwitchUserResponse fromEntity(FreeSwitchUserEntity entity) { + return FreeSwitchUserResponse.builder() + .id(entity.getId()) + .username(entity.getUsername()) + .domain(entity.getDomain()) + .displayName(entity.getDisplayName()) + .email(entity.getEmail()) + .accountcode(entity.getAccountcode()) + .enabled(entity.getEnabled()) + .lastRegister(entity.getLastRegister()) + .registerIp(entity.getRegisterIp()) + .userAgent(entity.getUserAgent()) + .remarks(entity.getRemarks()) + .createdAt(entity.getCreatedAt()) + .updatedAt(entity.getUpdatedAt()) + .sipAddress(entity.getSipAddress()) + .online(entity.isOnline()) + .build(); + } + + /** + * 从实体创建响应对象(隐藏密码) + */ + public static FreeSwitchUserResponse fromEntitySafe(FreeSwitchUserEntity entity) { + FreeSwitchUserResponse response = fromEntity(entity); + // 不返回密码信息 + return response; + } +} diff --git a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserService.java b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserService.java index e69de29bb2..75a645e464 100644 --- a/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserService.java +++ b/plugins/freeswitch/src/main/java/com/bytedesk/freeswitch/user/FreeSwitchUserService.java @@ -0,0 +1,254 @@ +/* + * @Author: jackning 270580156@qq.com + * @Date: 2025-06-09 10:00:00 + * @LastEditors: jackning 270580156@qq.com + * @LastEditTime: 2025-06-09 10:00:00 + * @Description: bytedesk.com https://github.com/Bytedesk/bytedesk + * Please be aware of the BSL license restrictions before installing Bytedesk IM – + * selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license. + * Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE + * contact: 270580156@qq.com + * + * Copyright (c) 2025 by bytedesk.com, All Rights Reserved. + */ +package com.bytedesk.freeswitch.user; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +/** + * FreeSwitch用户服务类 + * 处理SIP用户管理、注册状态监控等业务逻辑 + */ +@Slf4j +@Service +@AllArgsConstructor +@ConditionalOnProperty(name = "bytedesk.freeswitch.enabled", havingValue = "true") +public class FreeSwitchUserService { + + private final FreeSwitchUserRepository userRepository; + + /** + * 创建新用户 + */ + @Transactional + public FreeSwitchUserEntity createUser(String username, String domain, String password, + String displayName, String email, String accountcode) { + FreeSwitchUserEntity user = FreeSwitchUserEntity.builder() + .username(username) + .domain(domain) + .password(password) + .displayName(displayName) + .email(email) + .accountcode(accountcode) + .enabled(true) + .build(); + + FreeSwitchUserEntity saved = userRepository.save(user); + log.info("创建用户: {} (ID: {}) -> {}@{}", username, saved.getId(), username, domain); + return saved; + } + + /** + * 根据ID查找用户 + */ + public Optional findById(Long id) { + return userRepository.findById(id); + } + + /** + * 根据用户名查找用户 + */ + public Optional findByUsername(String username) { + return userRepository.findByUsername(username); + } + + /** + * 根据用户名和域名查找用户 + */ + public Optional findByUsernameAndDomain(String username, String domain) { + return userRepository.findByUsernameAndDomain(username, domain); + } + + /** + * 获取指定域名的用户列表 + */ + public List findByDomain(String domain) { + return userRepository.findByDomain(domain); + } + + /** + * 获取所有启用的用户 + */ + public List findEnabledUsers() { + return userRepository.findByEnabledTrue(); + } + + /** + * 获取所有用户(分页) + */ + public Page findAll(Pageable pageable) { + return userRepository.findAll(pageable); + } + + /** + * 根据域名查找用户(分页) + */ + public Page findByDomain(String domain, Pageable pageable) { + return userRepository.findByDomain(domain, pageable); + } + + /** + * 更新用户信息 + */ + @Transactional + public FreeSwitchUserEntity updateUser(Long id, String password, String displayName, + String email, String accountcode) { + FreeSwitchUserEntity user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在: " + id)); + + if (password != null) user.setPassword(password); + if (displayName != null) user.setDisplayName(displayName); + if (email != null) user.setEmail(email); + if (accountcode != null) user.setAccountcode(accountcode); + + FreeSwitchUserEntity saved = userRepository.save(user); + log.info("更新用户: {} (ID: {})", user.getUsername(), id); + return saved; + } + + /** + * 启用用户 + */ + @Transactional + public void enableUser(Long id) { + FreeSwitchUserEntity user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在: " + id)); + + user.setEnabled(true); + userRepository.save(user); + log.info("启用用户: {} (ID: {})", user.getUsername(), id); + } + + /** + * 禁用用户 + */ + @Transactional + public void disableUser(Long id) { + FreeSwitchUserEntity user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在: " + id)); + + user.setEnabled(false); + userRepository.save(user); + log.info("禁用用户: {} (ID: {})", user.getUsername(), id); + } + + /** + * 更新用户注册信息 + */ + @Transactional + public void updateUserRegistration(String username, String domain, String registerIp, String userAgent) { + Optional userOpt = userRepository.findByUsernameAndDomain(username, domain); + if (userOpt.isPresent()) { + FreeSwitchUserEntity user = userOpt.get(); + user.setLastRegister(LocalDateTime.now()); + user.setRegisterIp(registerIp); + user.setUserAgent(userAgent); + userRepository.save(user); + log.debug("更新用户注册信息: {}@{} from {}", username, domain, registerIp); + } else { + log.warn("用户不存在: {}@{}", username, domain); + } + } + + /** + * 删除用户 + */ + @Transactional + public void deleteUser(Long id) { + FreeSwitchUserEntity user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("用户不存在: " + id)); + + userRepository.delete(user); + log.info("删除用户: {} (ID: {})", user.getUsername(), id); + } + + /** + * 获取在线用户列表 + */ + public List findOnlineUsers() { + LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(5); + return userRepository.findByEnabledTrueAndLastRegisterAfter(cutoffTime); + } + + /** + * 根据邮箱查找用户 + */ + public List findByEmail(String email) { + return userRepository.findByEmail(email); + } + + /** + * 根据账户代码查找用户 + */ + public List findByAccountcode(String accountcode) { + return userRepository.findByAccountcode(accountcode); + } + + /** + * 获取用户总数 + */ + public long countTotal() { + return userRepository.count(); + } + + /** + * 获取启用的用户数量 + */ + public long countEnabled() { + return userRepository.countByEnabledTrue(); + } + + /** + * 获取在线的用户数量 + */ + public long countOnline() { + LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(5); + return userRepository.countByEnabledTrueAndLastRegisterAfter(cutoffTime); + } + + /** + * 根据域名统计用户数量 + */ + public long countByDomain(String domain) { + return userRepository.countByDomain(domain); + } + + /** + * 检查用户名和域名组合是否存在 + */ + public boolean existsByUsernameAndDomain(String username, String domain) { + return userRepository.existsByUsernameAndDomain(username, domain); + } + + /** + * 验证用户密码 + */ + public boolean validateUserPassword(String username, String domain, String password) { + Optional userOpt = userRepository.findByUsernameAndDomain(username, domain); + if (userOpt.isPresent()) { + FreeSwitchUserEntity user = userOpt.get(); + return user.getEnabled() && user.getPassword().equals(password); + } + return false; + } +}