This commit is contained in:
xiao12feng8 2025-12-26 15:43:37 +08:00
parent c8aaa31212
commit bae216261a
9 changed files with 777 additions and 0 deletions

View File

@ -0,0 +1,112 @@
package com.zbkj.common.model.call;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* 通话记录实体类
* 同时支持 MyBatis-Plus JPA
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("eb_call_record")
@Entity
@Table(name = "eb_call_record", indexes = {
@Index(name = "idx_caller_id", columnList = "caller_id"),
@Index(name = "idx_callee_id", columnList = "callee_id"),
@Index(name = "idx_call_time", columnList = "call_time"),
@Index(name = "idx_status", columnList = "status")
})
@ApiModel(value = "CallRecord对象", description = "通话记录")
public class CallRecord implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "通话记录ID")
@TableId(value = "id", type = IdType.AUTO)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ApiModelProperty(value = "通话唯一标识(用于信令)")
@TableField("call_id")
@Column(name = "call_id", length = 64, nullable = false, unique = true)
private String callId;
@ApiModelProperty(value = "主叫用户ID")
@TableField("caller_id")
@Column(name = "caller_id", nullable = false)
private Integer callerId;
@ApiModelProperty(value = "被叫用户ID")
@TableField("callee_id")
@Column(name = "callee_id", nullable = false)
private Integer calleeId;
@ApiModelProperty(value = "通话类型: voice-语音, video-视频")
@TableField("call_type")
@Column(name = "call_type", length = 20, nullable = false)
private String callType;
@ApiModelProperty(value = "通话状态: calling-呼叫中, ringing-响铃中, connected-通话中, ended-已结束, missed-未接, rejected-已拒绝, cancelled-已取消, busy-忙线")
@TableField("status")
@Column(name = "status", length = 20, nullable = false)
private String status;
@ApiModelProperty(value = "通话发起时间")
@TableField("call_time")
@Column(name = "call_time", nullable = false)
private Date callTime;
@ApiModelProperty(value = "通话接通时间")
@TableField("connect_time")
@Column(name = "connect_time")
private Date connectTime;
@ApiModelProperty(value = "通话结束时间")
@TableField("end_time")
@Column(name = "end_time")
private Date endTime;
@ApiModelProperty(value = "通话时长(秒)")
@TableField("duration")
@Column(name = "duration", columnDefinition = "INT DEFAULT 0")
private Integer duration;
@ApiModelProperty(value = "结束原因: normal-正常结束, timeout-超时, network_error-网络错误, user_hangup-用户挂断")
@TableField("end_reason")
@Column(name = "end_reason", length = 50)
private String endReason;
@ApiModelProperty(value = "主叫是否删除记录")
@TableField("caller_deleted")
@Column(name = "caller_deleted", columnDefinition = "TINYINT(1) DEFAULT 0")
private Boolean callerDeleted;
@ApiModelProperty(value = "被叫是否删除记录")
@TableField("callee_deleted")
@Column(name = "callee_deleted", columnDefinition = "TINYINT(1) DEFAULT 0")
private Boolean calleeDeleted;
@ApiModelProperty(value = "创建时间")
@TableField("create_time")
@Column(name = "create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
@Column(name = "update_time")
private Date updateTime;
}

View File

@ -0,0 +1,195 @@
package com.zbkj.front.controller;
import com.github.pagehelper.PageInfo;
import com.zbkj.common.model.call.CallRecord;
import com.zbkj.common.model.user.User;
import com.zbkj.common.request.PageParamRequest;
import com.zbkj.common.response.CommonResult;
import com.zbkj.front.request.call.InitiateCallRequest;
import com.zbkj.front.response.call.CallRecordResponse;
import com.zbkj.front.response.call.InitiateCallResponse;
import com.zbkj.service.service.CallService;
import com.zbkj.service.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/front/call")
@Api(tags = "通话管理")
public class CallController {
@Autowired
private CallService callService;
@Autowired
private UserService userService;
@ApiOperation(value = "发起通话")
@PostMapping("/initiate")
public CommonResult<InitiateCallResponse> initiateCall(@RequestBody @Validated InitiateCallRequest request) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
User callee = userService.getById(request.getCalleeId());
if (callee == null) return CommonResult.failed("被叫用户不存在");
CallRecord record = callService.createCall(currentUser.getUid(), request.getCalleeId(), request.getCallType());
InitiateCallResponse response = new InitiateCallResponse();
response.setCallId(record.getCallId());
response.setCallType(record.getCallType());
response.setCalleeId(callee.getUid());
response.setCalleeName(callee.getNickname());
response.setCalleeAvatar(callee.getAvatar());
response.setStatus(record.getStatus());
response.setSignalingUrl("/ws/call/" + record.getCallId());
return CommonResult.success(response);
}
@ApiOperation(value = "接听通话")
@PostMapping("/accept/{callId}")
public CommonResult<Boolean> acceptCall(@PathVariable String callId) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.acceptCall(callId, currentUser.getUid()));
}
@ApiOperation(value = "拒绝通话")
@PostMapping("/reject/{callId}")
public CommonResult<Boolean> rejectCall(@PathVariable String callId) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.rejectCall(callId, currentUser.getUid()));
}
@ApiOperation(value = "取消通话")
@PostMapping("/cancel/{callId}")
public CommonResult<Boolean> cancelCall(@PathVariable String callId) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.cancelCall(callId, currentUser.getUid()));
}
@ApiOperation(value = "结束通话")
@PostMapping("/end/{callId}")
public CommonResult<Boolean> endCall(@PathVariable String callId, @RequestParam(required = false) String endReason) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.endCall(callId, currentUser.getUid(), endReason));
}
@ApiOperation(value = "获取通话记录列表")
@GetMapping("/history")
public CommonResult<PageInfo<CallRecordResponse>> getCallHistory(@Validated PageParamRequest pageParamRequest) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
List<CallRecord> records = callService.getCallHistory(currentUser.getUid(), pageParamRequest);
List<CallRecordResponse> responseList = new ArrayList<>();
Map<Integer, User> userCache = new HashMap<>();
for (CallRecord record : records) {
if (!userCache.containsKey(record.getCallerId())) {
User user = userService.getById(record.getCallerId());
if (user != null) userCache.put(record.getCallerId(), user);
}
if (!userCache.containsKey(record.getCalleeId())) {
User user = userService.getById(record.getCalleeId());
if (user != null) userCache.put(record.getCalleeId(), user);
}
}
for (CallRecord record : records) {
responseList.add(convertToResponse(record, currentUser.getUid(), userCache));
}
PageInfo<CallRecordResponse> pageInfo = new PageInfo<>(responseList);
return CommonResult.success(pageInfo);
}
@ApiOperation(value = "删除通话记录")
@DeleteMapping("/record/{recordId}")
public CommonResult<Boolean> deleteRecord(@PathVariable Long recordId) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.deleteRecord(recordId, currentUser.getUid()));
}
@ApiOperation(value = "获取未接来电数量")
@GetMapping("/missed/count")
public CommonResult<Integer> getMissedCallCount() {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
return CommonResult.success(callService.getMissedCallCount(currentUser.getUid()));
}
@ApiOperation(value = "检查是否正在通话中")
@GetMapping("/status")
public CommonResult<Map<String, Object>> getCallStatus() {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
Map<String, Object> result = new HashMap<>();
result.put("inCall", callService.isUserInCall(currentUser.getUid()));
CallRecord currentCall = callService.getCurrentCall(currentUser.getUid());
if (currentCall != null) {
result.put("callId", currentCall.getCallId());
result.put("callType", currentCall.getCallType());
result.put("status", currentCall.getStatus());
result.put("isCaller", currentCall.getCallerId().equals(currentUser.getUid()));
}
return CommonResult.success(result);
}
@ApiOperation(value = "获取通话详情")
@GetMapping("/detail/{callId}")
public CommonResult<CallRecordResponse> getCallDetail(@PathVariable String callId) {
User currentUser = userService.getInfo();
if (currentUser == null) return CommonResult.failed("请先登录");
CallRecord record = callService.getByCallId(callId);
if (record == null) return CommonResult.failed("通话记录不存在");
if (!record.getCallerId().equals(currentUser.getUid()) && !record.getCalleeId().equals(currentUser.getUid())) {
return CommonResult.failed("无权查看此通话记录");
}
Map<Integer, User> userCache = new HashMap<>();
User caller = userService.getById(record.getCallerId());
User callee = userService.getById(record.getCalleeId());
if (caller != null) userCache.put(caller.getUid(), caller);
if (callee != null) userCache.put(callee.getUid(), callee);
return CommonResult.success(convertToResponse(record, currentUser.getUid(), userCache));
}
private CallRecordResponse convertToResponse(CallRecord record, Integer currentUserId, Map<Integer, User> userCache) {
CallRecordResponse response = new CallRecordResponse();
response.setId(record.getId());
response.setCallId(record.getCallId());
response.setCallerId(record.getCallerId());
response.setCalleeId(record.getCalleeId());
response.setCallType(record.getCallType());
response.setStatus(record.getStatus());
response.setCallTime(record.getCallTime() != null ? record.getCallTime().getTime() : null);
response.setConnectTime(record.getConnectTime() != null ? record.getConnectTime().getTime() : null);
response.setEndTime(record.getEndTime() != null ? record.getEndTime().getTime() : null);
response.setDuration(record.getDuration());
response.setIsOutgoing(record.getCallerId().equals(currentUserId));
User caller = userCache.get(record.getCallerId());
if (caller != null) {
response.setCallerName(caller.getNickname());
response.setCallerAvatar(caller.getAvatar());
}
User callee = userCache.get(record.getCalleeId());
if (callee != null) {
response.setCalleeName(callee.getNickname());
response.setCalleeAvatar(callee.getAvatar());
}
Integer otherUserId = record.getCallerId().equals(currentUserId) ? record.getCalleeId() : record.getCallerId();
response.setOtherUserId(otherUserId);
User otherUser = userCache.get(otherUserId);
if (otherUser != null) {
response.setOtherUserName(otherUser.getNickname());
response.setOtherUserAvatar(otherUser.getAvatar());
}
return response;
}
}

View File

@ -0,0 +1,18 @@
package com.zbkj.front.request.call;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
@ApiModel(value = "InitiateCallRequest", description = "发起通话请求")
public class InitiateCallRequest {
@ApiModelProperty(value = "被叫用户ID", required = true)
@NotNull(message = "被叫用户ID不能为空")
private Integer calleeId;
@ApiModelProperty(value = "通话类型: voice-语音, video-视频", required = true)
@NotNull(message = "通话类型不能为空")
private String callType;
}

View File

@ -0,0 +1,46 @@
package com.zbkj.front.response.call;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "CallRecordResponse", description = "通话记录响应")
public class CallRecordResponse {
@ApiModelProperty(value = "记录ID")
private Long id;
@ApiModelProperty(value = "通话唯一标识")
private String callId;
@ApiModelProperty(value = "主叫用户ID")
private Integer callerId;
@ApiModelProperty(value = "主叫用户昵称")
private String callerName;
@ApiModelProperty(value = "主叫用户头像")
private String callerAvatar;
@ApiModelProperty(value = "被叫用户ID")
private Integer calleeId;
@ApiModelProperty(value = "被叫用户昵称")
private String calleeName;
@ApiModelProperty(value = "被叫用户头像")
private String calleeAvatar;
@ApiModelProperty(value = "通话类型: voice-语音, video-视频")
private String callType;
@ApiModelProperty(value = "通话状态")
private String status;
@ApiModelProperty(value = "通话发起时间戳")
private Long callTime;
@ApiModelProperty(value = "通话接通时间戳")
private Long connectTime;
@ApiModelProperty(value = "通话结束时间戳")
private Long endTime;
@ApiModelProperty(value = "通话时长(秒)")
private Integer duration;
@ApiModelProperty(value = "是否为呼出(当前用户是主叫)")
private Boolean isOutgoing;
@ApiModelProperty(value = "对方用户ID")
private Integer otherUserId;
@ApiModelProperty(value = "对方用户昵称")
private String otherUserName;
@ApiModelProperty(value = "对方用户头像")
private String otherUserAvatar;
}

View File

@ -0,0 +1,24 @@
package com.zbkj.front.response.call;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "InitiateCallResponse", description = "发起通话响应")
public class InitiateCallResponse {
@ApiModelProperty(value = "通话唯一标识")
private String callId;
@ApiModelProperty(value = "通话类型")
private String callType;
@ApiModelProperty(value = "被叫用户ID")
private Integer calleeId;
@ApiModelProperty(value = "被叫用户昵称")
private String calleeName;
@ApiModelProperty(value = "被叫用户头像")
private String calleeAvatar;
@ApiModelProperty(value = "通话状态")
private String status;
@ApiModelProperty(value = "WebSocket信令地址")
private String signalingUrl;
}

View File

@ -0,0 +1,42 @@
package com.zbkj.front.websocket;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.zbkj.common.model.call.CallRecord;
import com.zbkj.common.model.user.User;
import com.zbkj.service.service.CallService;
import com.zbkj.service.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
public class CallSignalingHandler extends TextWebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(CallSignalingHandler.class);
private final ObjectMapper objectMapper = new ObjectMapper();
private static final long CALL_TIMEOUT = 60 * 1000;
@Autowired
private CallService callService;
@Autowired
private UserService userService;
private final Map<String, Set<WebSocketSession>> callSessions = new ConcurrentHashMap<>();
private final Map<Integer, WebSocketSession> userCallSessions = new ConcurrentHashMap<>();
private final Map<String, Integer> sessionUserMap = new ConcurrentHashMap<>();
private final Map<String, String> sessionCallMap = new ConcurrentHashMap<>();
private final Map<String, Long> callCreateTime = new ConcurrentHashMap<>();

View File

@ -0,0 +1,12 @@
package com.zbkj.service.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zbkj.common.model.call.CallRecord;
import org.apache.ibatis.annotations.Mapper;
/**
* 通话记录数据访问层
*/
@Mapper
public interface CallRecordDao extends BaseMapper<CallRecord> {
}

View File

@ -0,0 +1,78 @@
package com.zbkj.service.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zbkj.common.model.call.CallRecord;
import com.zbkj.common.request.PageParamRequest;
import java.util.List;
/**
* 通话服务接口
*/
public interface CallService extends IService<CallRecord> {
/**
* 创建通话记录
*/
CallRecord createCall(Integer callerId, Integer calleeId, String callType);
/**
* 根据callId获取通话记录
*/
CallRecord getByCallId(String callId);
/**
* 更新通话状态
*/
Boolean updateStatus(String callId, String status);
/**
* 接听通话
*/
Boolean acceptCall(String callId, Integer userId);
/**
* 拒绝通话
*/
Boolean rejectCall(String callId, Integer userId);
/**
* 取消通话
*/
Boolean cancelCall(String callId, Integer userId);
/**
* 结束通话
*/
Boolean endCall(String callId, Integer userId, String endReason);
/**
* 通话超时未接听
*/
Boolean missedCall(String callId);
/**
* 获取用户通话记录列表
*/
List<CallRecord> getCallHistory(Integer userId, PageParamRequest pageParamRequest);
/**
* 删除通话记录软删除
*/
Boolean deleteRecord(Long recordId, Integer userId);
/**
* 获取用户未接来电数量
*/
Integer getMissedCallCount(Integer userId);
/**
* 检查用户是否正在通话中
*/
Boolean isUserInCall(Integer userId);
/**
* 获取用户当前进行中的通话
*/
CallRecord getCurrentCall(Integer userId);
}

View File

@ -0,0 +1,250 @@
package com.zbkj.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.zbkj.common.exception.CrmebException;
import com.zbkj.common.model.call.CallRecord;
import com.zbkj.common.request.PageParamRequest;
import com.zbkj.service.dao.CallRecordDao;
import com.zbkj.service.service.CallService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* 通话服务实现类
*/
@Service
public class CallServiceImpl extends ServiceImpl<CallRecordDao, CallRecord> implements CallService {
private static final Logger logger = LoggerFactory.getLogger(CallServiceImpl.class);
public static final String STATUS_CALLING = "calling";
public static final String STATUS_RINGING = "ringing";
public static final String STATUS_CONNECTED = "connected";
public static final String STATUS_ENDED = "ended";
public static final String STATUS_MISSED = "missed";
public static final String STATUS_REJECTED = "rejected";
public static final String STATUS_CANCELLED = "cancelled";
public static final String STATUS_BUSY = "busy";
public static final String TYPE_VOICE = "voice";
public static final String TYPE_VIDEO = "video";
private static final List<String> ACTIVE_STATUSES = Arrays.asList(STATUS_CALLING, STATUS_RINGING, STATUS_CONNECTED);
@Override
@Transactional(rollbackFor = Exception.class)
public CallRecord createCall(Integer callerId, Integer calleeId, String callType) {
if (callerId == null || calleeId == null) {
throw new CrmebException("用户ID不能为空");
}
if (callerId.equals(calleeId)) {
throw new CrmebException("不能呼叫自己");
}
if (!TYPE_VOICE.equals(callType) && !TYPE_VIDEO.equals(callType)) {
throw new CrmebException("无效的通话类型");
}
if (isUserInCall(callerId)) {
throw new CrmebException("您正在通话中,无法发起新通话");
}
if (isUserInCall(calleeId)) {
throw new CrmebException("对方正在通话中");
}
CallRecord record = new CallRecord();
record.setCallId("call_" + UUID.randomUUID().toString().replace("-", ""));
record.setCallerId(callerId);
record.setCalleeId(calleeId);
record.setCallType(callType);
record.setStatus(STATUS_CALLING);
record.setCallTime(new Date());
record.setDuration(0);
record.setCallerDeleted(false);
record.setCalleeDeleted(false);
record.setCreateTime(new Date());
record.setUpdateTime(new Date());
save(record);
logger.info("[Call] 创建通话: callId={}, caller={}, callee={}", record.getCallId(), callerId, calleeId);
return record;
}
@Override
public CallRecord getByCallId(String callId) {
LambdaQueryWrapper<CallRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CallRecord::getCallId, callId);
return getOne(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateStatus(String callId, String status) {
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, status)
.set(CallRecord::getUpdateTime, new Date());
return update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean acceptCall(String callId, Integer userId) {
CallRecord record = getByCallId(callId);
if (record == null) throw new CrmebException("通话记录不存在");
if (!record.getCalleeId().equals(userId)) throw new CrmebException("无权操作此通话");
if (!STATUS_CALLING.equals(record.getStatus()) && !STATUS_RINGING.equals(record.getStatus())) {
throw new CrmebException("通话状态不正确");
}
Date now = new Date();
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, STATUS_CONNECTED)
.set(CallRecord::getConnectTime, now)
.set(CallRecord::getUpdateTime, now);
return update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean rejectCall(String callId, Integer userId) {
CallRecord record = getByCallId(callId);
if (record == null) throw new CrmebException("通话记录不存在");
if (!record.getCalleeId().equals(userId)) throw new CrmebException("无权操作此通话");
Date now = new Date();
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, STATUS_REJECTED)
.set(CallRecord::getEndTime, now)
.set(CallRecord::getEndReason, "user_reject")
.set(CallRecord::getUpdateTime, now);
return update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancelCall(String callId, Integer userId) {
CallRecord record = getByCallId(callId);
if (record == null) throw new CrmebException("通话记录不存在");
if (!record.getCallerId().equals(userId)) throw new CrmebException("无权操作此通话");
Date now = new Date();
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, STATUS_CANCELLED)
.set(CallRecord::getEndTime, now)
.set(CallRecord::getEndReason, "user_cancel")
.set(CallRecord::getUpdateTime, now);
return update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean endCall(String callId, Integer userId, String endReason) {
CallRecord record = getByCallId(callId);
if (record == null) throw new CrmebException("通话记录不存在");
if (!record.getCallerId().equals(userId) && !record.getCalleeId().equals(userId)) {
throw new CrmebException("无权操作此通话");
}
Date now = new Date();
int duration = 0;
if (record.getConnectTime() != null) {
duration = (int) ((now.getTime() - record.getConnectTime().getTime()) / 1000);
}
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, STATUS_ENDED)
.set(CallRecord::getEndTime, now)
.set(CallRecord::getDuration, duration)
.set(CallRecord::getEndReason, endReason != null ? endReason : "normal")
.set(CallRecord::getUpdateTime, now);
return update(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean missedCall(String callId) {
CallRecord record = getByCallId(callId);
if (record == null) return false;
Date now = new Date();
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getCallId, callId)
.set(CallRecord::getStatus, STATUS_MISSED)
.set(CallRecord::getEndTime, now)
.set(CallRecord::getEndReason, "timeout")
.set(CallRecord::getUpdateTime, now);
return update(wrapper);
}
@Override
public List<CallRecord> getCallHistory(Integer userId, PageParamRequest pageParamRequest) {
PageHelper.startPage(pageParamRequest.getPage(), pageParamRequest.getLimit());
LambdaQueryWrapper<CallRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w
.and(inner -> inner.eq(CallRecord::getCallerId, userId).eq(CallRecord::getCallerDeleted, false))
.or(inner -> inner.eq(CallRecord::getCalleeId, userId).eq(CallRecord::getCalleeDeleted, false))
);
wrapper.orderByDesc(CallRecord::getCallTime);
return list(wrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteRecord(Long recordId, Integer userId) {
CallRecord record = getById(recordId);
if (record == null) throw new CrmebException("通话记录不存在");
LambdaUpdateWrapper<CallRecord> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(CallRecord::getId, recordId);
if (record.getCallerId().equals(userId)) {
wrapper.set(CallRecord::getCallerDeleted, true);
} else if (record.getCalleeId().equals(userId)) {
wrapper.set(CallRecord::getCalleeDeleted, true);
} else {
throw new CrmebException("无权删除此记录");
}
wrapper.set(CallRecord::getUpdateTime, new Date());
return update(wrapper);
}
@Override
public Integer getMissedCallCount(Integer userId) {
LambdaQueryWrapper<CallRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CallRecord::getCalleeId, userId)
.eq(CallRecord::getStatus, STATUS_MISSED)
.eq(CallRecord::getCalleeDeleted, false);
return count(wrapper);
}
@Override
public Boolean isUserInCall(Integer userId) {
LambdaQueryWrapper<CallRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w.eq(CallRecord::getCallerId, userId).or().eq(CallRecord::getCalleeId, userId));
wrapper.in(CallRecord::getStatus, ACTIVE_STATUSES);
return count(wrapper) > 0;
}
@Override
public CallRecord getCurrentCall(Integer userId) {
LambdaQueryWrapper<CallRecord> wrapper = new LambdaQueryWrapper<>();
wrapper.and(w -> w.eq(CallRecord::getCallerId, userId).or().eq(CallRecord::getCalleeId, userId));
wrapper.in(CallRecord::getStatus, ACTIVE_STATUSES);
wrapper.orderByDesc(CallRecord::getCallTime);
wrapper.last("LIMIT 1");
return getOne(wrapper);
}
}