mirror of
https://gitee.com/270580156/weiyu.git
synced 2026-05-18 05:08:02 +00:00
update plugins/freeswitch: add 3 mod 18 del 3 files
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<JsonResult<Page<FreeSwitchCdrDto>>> 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<FreeSwitchCdrEntity> 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<FreeSwitchCdrDto> 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<JsonResult<FreeSwitchCdrDto>> getCdrById(@PathVariable Long id) {
|
||||
try {
|
||||
Optional<FreeSwitchCdrEntity> 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<JsonResult<FreeSwitchCdrDto>> getCdrByUuid(@PathVariable String uuid) {
|
||||
try {
|
||||
Optional<FreeSwitchCdrEntity> 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<JsonResult<FreeSwitchCdrService.CallStatistics>> 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<JsonResult<FreeSwitchCdrService.CallStatistics>> 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<JsonResult<FreeSwitchCdrService.CallStatistics>> 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<JsonResult<List<Object[]>>> 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<Object[]> 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<JsonResult<List<Object[]>>> 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<Object[]> 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<JsonResult<List<FreeSwitchCdrDto>>> getLongCalls(
|
||||
@RequestParam(defaultValue = "300") long minDuration) {
|
||||
|
||||
try {
|
||||
List<FreeSwitchCdrEntity> entities = cdrService.findLongCalls(minDuration);
|
||||
List<FreeSwitchCdrDto> 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<JsonResult<Page<FreeSwitchCdrDto>>> getFailedCalls(
|
||||
@RequestParam(defaultValue = "0") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
|
||||
try {
|
||||
Pageable pageable = PageRequest.of(page, size, Sort.by("startTime").descending());
|
||||
Page<FreeSwitchCdrEntity> entities = cdrService.findFailedCalls(pageable);
|
||||
Page<FreeSwitchCdrDto> 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<JsonResult<Integer>> 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<JsonResult<Page<FreeSwitchCdrDto>>> 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<FreeSwitchCdrEntity> entities = cdrService.searchCdr(keyword, pageable);
|
||||
Page<FreeSwitchCdrDto> 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<JsonResult<String>> 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 "其他";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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仓库接口
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
|
||||
@@ -79,4 +79,11 @@ public class FreeSwitchConferenceRequest extends BaseRequest {
|
||||
* 备注
|
||||
*/
|
||||
private String remarks;
|
||||
|
||||
/**
|
||||
* 获取主持人密码(兼容性方法)
|
||||
*/
|
||||
public String getModeratorPin() {
|
||||
return this.password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FreeSwitchConferenceEntity> findAll(Pageable pageable) {
|
||||
return conferenceRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会议室名称模糊查询
|
||||
*/
|
||||
public Page<FreeSwitchConferenceEntity> 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<FreeSwitchConferenceEntity> 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<FreeSwitchConferenceEntity> findByEnabled(Boolean enabled) {
|
||||
if (enabled) {
|
||||
return conferenceRepository.findByEnabledTrue();
|
||||
} else {
|
||||
return conferenceRepository.findByEnabledFalse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找最大成员数量大于等于指定值的会议室
|
||||
*/
|
||||
public List<FreeSwitchConferenceEntity> findByMaxMembersGreaterThanEqual(Integer minCapacity) {
|
||||
return conferenceRepository.findAll().stream()
|
||||
.filter(conference -> conference.getMaxMembers() != null && conference.getMaxMembers() >= minCapacity)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有会议室(分页)
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<FreeSwitchGatewayEntity> findById(Long id) {
|
||||
return gatewayRepository.findById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据名称查找网关
|
||||
*/
|
||||
public Optional<FreeSwitchGatewayEntity> findByGatewayName(String gatewayName) {
|
||||
return gatewayRepository.findByGatewayName(gatewayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的网关
|
||||
*/
|
||||
public List<FreeSwitchGatewayEntity> findEnabledGateways() {
|
||||
return gatewayRepository.findByEnabledTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有网关(分页)
|
||||
*/
|
||||
public Page<FreeSwitchGatewayEntity> findAll(Pageable pageable) {
|
||||
return gatewayRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态查找网关
|
||||
*/
|
||||
public List<FreeSwitchGatewayEntity> 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<FreeSwitchGatewayEntity> 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<FreeSwitchGatewayEntity> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<JsonResult<?>> 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<JsonResult<?>> getUser(@PathVariable Long id) {
|
||||
Optional<FreeSwitchUserEntity> 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<JsonResult<?>> getUserByUsername(@PathVariable String username) {
|
||||
Optional<FreeSwitchUserEntity> 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<JsonResult<?>> getUserByUsernameAndDomain(
|
||||
@PathVariable String username, @PathVariable String domain) {
|
||||
Optional<FreeSwitchUserEntity> 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<JsonResult<?>> 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<FreeSwitchUserEntity> userPage = userService.findAll(pageable);
|
||||
Page<FreeSwitchUserResponse> responsePage = userPage.map(FreeSwitchUserResponse::fromEntitySafe);
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responsePage));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据域名获取用户列表
|
||||
*/
|
||||
@GetMapping("/domain/{domain}")
|
||||
public ResponseEntity<JsonResult<?>> getUsersByDomain(@PathVariable String domain) {
|
||||
List<FreeSwitchUserEntity> users = userService.findByDomain(domain);
|
||||
List<FreeSwitchUserResponse> responses = users.stream()
|
||||
.map(FreeSwitchUserResponse::fromEntitySafe)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取域名用户列表成功", responses));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的用户列表
|
||||
*/
|
||||
@GetMapping("/enabled")
|
||||
public ResponseEntity<JsonResult<?>> getEnabledUsers() {
|
||||
List<FreeSwitchUserEntity> users = userService.findEnabledUsers();
|
||||
List<FreeSwitchUserResponse> responses = users.stream()
|
||||
.map(FreeSwitchUserResponse::fromEntitySafe)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取启用用户列表成功", responses));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线的用户列表
|
||||
*/
|
||||
@GetMapping("/online")
|
||||
public ResponseEntity<JsonResult<?>> getOnlineUsers() {
|
||||
List<FreeSwitchUserEntity> users = userService.findOnlineUsers();
|
||||
List<FreeSwitchUserResponse> responses = users.stream()
|
||||
.map(FreeSwitchUserResponse::fromEntitySafe)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取在线用户列表成功", responses));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据邮箱获取用户列表
|
||||
*/
|
||||
@GetMapping("/email/{email}")
|
||||
public ResponseEntity<JsonResult<?>> getUsersByEmail(@PathVariable String email) {
|
||||
List<FreeSwitchUserEntity> users = userService.findByEmail(email);
|
||||
List<FreeSwitchUserResponse> responses = users.stream()
|
||||
.map(FreeSwitchUserResponse::fromEntitySafe)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responses));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账户代码获取用户列表
|
||||
*/
|
||||
@GetMapping("/accountcode/{accountcode}")
|
||||
public ResponseEntity<JsonResult<?>> getUsersByAccountcode(@PathVariable String accountcode) {
|
||||
List<FreeSwitchUserEntity> users = userService.findByAccountcode(accountcode);
|
||||
List<FreeSwitchUserResponse> responses = users.stream()
|
||||
.map(FreeSwitchUserResponse::fromEntitySafe)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("获取用户列表成功", responses));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户
|
||||
*/
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> 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<JsonResult<?>> getUserStatsByDomain(@PathVariable String domain) {
|
||||
long domainCount = userService.countByDomain(domain);
|
||||
return ResponseEntity.ok(JsonResult.success("获取域名用户统计成功", domainCount));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户名和域名组合是否存在
|
||||
*/
|
||||
@GetMapping("/exists")
|
||||
public ResponseEntity<JsonResult<?>> 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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FreeSwitchUserEntity> findById(Long id) {
|
||||
return userRepository.findById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名查找用户
|
||||
*/
|
||||
public Optional<FreeSwitchUserEntity> findByUsername(String username) {
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户名和域名查找用户
|
||||
*/
|
||||
public Optional<FreeSwitchUserEntity> findByUsernameAndDomain(String username, String domain) {
|
||||
return userRepository.findByUsernameAndDomain(username, domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定域名的用户列表
|
||||
*/
|
||||
public List<FreeSwitchUserEntity> findByDomain(String domain) {
|
||||
return userRepository.findByDomain(domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的用户
|
||||
*/
|
||||
public List<FreeSwitchUserEntity> findEnabledUsers() {
|
||||
return userRepository.findByEnabledTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有用户(分页)
|
||||
*/
|
||||
public Page<FreeSwitchUserEntity> findAll(Pageable pageable) {
|
||||
return userRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据域名查找用户(分页)
|
||||
*/
|
||||
public Page<FreeSwitchUserEntity> 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<FreeSwitchUserEntity> 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<FreeSwitchUserEntity> findOnlineUsers() {
|
||||
LocalDateTime cutoffTime = LocalDateTime.now().minusMinutes(5);
|
||||
return userRepository.findByEnabledTrueAndLastRegisterAfter(cutoffTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据邮箱查找用户
|
||||
*/
|
||||
public List<FreeSwitchUserEntity> findByEmail(String email) {
|
||||
return userRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账户代码查找用户
|
||||
*/
|
||||
public List<FreeSwitchUserEntity> 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<FreeSwitchUserEntity> userOpt = userRepository.findByUsernameAndDomain(username, domain);
|
||||
if (userOpt.isPresent()) {
|
||||
FreeSwitchUserEntity user = userOpt.get();
|
||||
return user.getEnabled() && user.getPassword().equals(password);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user