1.增加Maven依赖
<dependency>
<groupId>org.freeswitch.esl.client</groupId>
<artifactId>org.freeswitch.esl.client</artifactId>
<version>0.9.2</version>
</dependency>
<!-- XML-RPC -->
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-client</artifactId>
<version>3.1.3</version>
</dependency>
2.封装FreeSWITCH 工具类
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.freeswitch.esl.client.IEslEventListener;
import org.freeswitch.esl.client.inbound.Client;
import org.freeswitch.esl.client.inbound.InboundConnectionFailure;
import org.freeswitch.esl.client.transport.event.EslEvent;
import org.freeswitch.esl.client.transport.message.EslMessage;
import javax.annotation.PreDestroy;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* FreeSWITCH 工具类
* @author luo
* @date 2025-07-11
*/
@Slf4j
public class FreeswitchUtil {
private final String freeswitchHost;
private final int eslPort;
private final String eslPassword;
private final int connectionTimeout;
private final ReentrantLock connectionLock = new ReentrantLock();
private final AtomicBoolean isConnecting = new AtomicBoolean(false);
@Getter private volatile Client client;
private IEslEventListener eventListener;
private final String sipProfilesPath;
public FreeswitchUtil(String eslHost, int eslPort, String eslPassword) {
this(eslHost, eslPort, eslPassword, 10, "/usr/local/freeswitch/conf/directory/default");
}
public FreeswitchUtil(String eslHost, int eslPort, String eslPassword, int connectionTimeout) {
this(eslHost, eslPort, eslPassword, connectionTimeout, "/usr/local/freeswitch/conf/directory/default");
}
public FreeswitchUtil(String eslHost, int eslPort, String eslPassword, int connectionTimeout, String sipProfilesPath) {
this.freeswitchHost = eslHost;
this.eslPort = eslPort;
this.eslPassword = eslPassword;
this.connectionTimeout = connectionTimeout;
this.sipProfilesPath = sipProfilesPath;
this.client = new Client();
}
public void setEventListener(IEslEventListener eventListener) {
this.eventListener = eventListener;
}
public boolean connect() {
connectionLock.lock();
try {
if (isClientConnected()) {
log.info("FreeSWITCH ESL 已连接,无需重复连接");
return true;
}
if (isConnecting.getAndSet(true)) {
log.info("正在进行连接,当前线程等待连接完成");
waitForConnection();
return isClientConnected();
}
try {
log.info("尝试连接到 FreeSWITCH ESL: {}:{}", freeswitchHost, eslPort);
client = new Client();
client.connect(freeswitchHost, eslPort, eslPassword, connectionTimeout);
log.info("成功连接到 FreeSWITCH ESL");
registerEventListener();
subscribeEvents();
return true;
} catch (InboundConnectionFailure e) {
log.error("连接 FreeSWITCH ESL 失败: {}", e.getMessage());
disconnect();
return false;
} finally {
isConnecting.set(false);
}
} finally {
connectionLock.unlock();
}
}
private void registerEventListener() {
if (client != null && eventListener != null) {
client.addEventListener(eventListener);
} else if (client != null) {
client.addEventListener(new DefaultEslEventListener());
}
}
private void subscribeEvents() {
if (client != null) {
try {
client.setEventSubscriptions("plain", "all");
} catch (Exception e) {
log.error("订阅 FreeSWITCH 事件失败: {}", e.getMessage());
}
}
}
public boolean isClientConnected() {
return client != null && client.canSend();
}
private void waitForConnection() {
int attempts = 0;
while (isConnecting.get() && attempts < 10) {
try {
Thread.sleep(500);
attempts++;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
@PreDestroy
public void disconnect() {
Client localClient = this.client;
this.client = null;
if (localClient != null) {
try {
log.info("断开 FreeSWITCH ESL 连接");
localClient.close();
} catch (Exception e) {
log.error("关闭 FreeSWITCH 客户端失败: {}", e.getMessage());
}
}
}
public synchronized void reConnect() {
if (!isClientConnected()) {
log.info("检测到 FreeSWITCH 连接断开,尝试重新连接");
disconnect();
connect();
}
}
/**
* 检查用户是否在线
* @param username 用户名/分机号
* @return 用户是否在线
*/
public boolean isUserOnline(String username) {
if (!isClientConnected()) {
log.warn("FreeSWITCH 未连接,无法检查用户在线状态");
return false;
}
try {
// 修改为使用 sendSyncApiCommand 返回 EslMessage
EslMessage message = client.sendSyncApiCommand("sofia", "status profile internal reg " + username);
String response = message.getBodyLines().toString();
return response != null && !response.contains("0 registrations found");
} catch (Exception e) {
log.error("检查用户在线状态失败: {}", e.getMessage());
return false;
}
}
/**
* 获取用户注册信息
* @param username 用户名/分机号
* @return 用户注册信息,如果不在线则返回空
*/
public String getUserRegistrationInfo(String username) {
if (!isClientConnected()) {
log.warn("FreeSWITCH 未连接,无法获取用户注册信息");
return "";
}
try {
// 修改为使用 sendSyncApiCommand 返回 EslMessage
EslMessage message = client.sendSyncApiCommand("sofia", "status profile internal reg " + username);
return message.getBodyLines().toString();
} catch (Exception e) {
log.error("获取用户注册信息失败: {}", e.getMessage());
return "";
}
}
/**
* 注销用户
* @param username 用户名/分机号
* @return 操作是否成功
*/
public boolean unregisterUser(String username) {
if (!isClientConnected()) {
log.warn("FreeSWITCH 未连接,无法注销用户");
return false;
}
try {
// 修改为使用 sendSyncApiCommand 返回 EslMessage
EslMessage message = client.sendSyncApiCommand("sofia", "killreg " + username);
String response = message.getBodyLines().toString();
return response != null && response.contains("removed");
} catch (Exception e) {
log.error("注销用户失败: {}", e.getMessage());
return false;
}
}
/**
* 重启Sofia模块(使配置生效)
* @param profileName Sofia配置文件名称,如internal
* @return 操作是否成功
*/
public boolean restartSofiaProfile(String profileName) {
if (!isClientConnected()) {
log.warn("FreeSWITCH 未连接,无法重启Sofia配置");
return false;
}
try {
// 修改为使用 sendSyncApiCommand 返回 EslMessage
EslMessage message = client.sendSyncApiCommand("sofia", "profile " + profileName + " restart");
String response = message.getBodyLines().toString();
return response != null && response.contains("Starting");
} catch (Exception e) {
log.error("重启Sofia配置失败: {}", e.getMessage());
return false;
}
}
/**
* 创建新用户
* @param username 用户名/分机号
* @param password 密码
* @param displayName 显示名称
* @param domain 域名
* @return 创建结果
*/
public boolean createUser(String username, String password, String displayName,String domain) {
if (!isValidUsername(username)) {
log.error("无效的用户名: {}", username);
return false;
}
Path userFilePath = Paths.get(sipProfilesPath, username + ".xml");
if (Files.exists(userFilePath)) {
log.info("用户已存在: {},无需重复创建", username);
return true;
}
String userConfig = generateUserConfig(username, password, displayName,domain);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(userFilePath.toFile()))) {
writer.write(userConfig);
log.info("用户配置文件已创建: {}", userFilePath);
// 重载目录
return reloadDirectory();
} catch (IOException e) {
log.error("创建用户配置文件失败: {}", e.getMessage());
return false;
}
}
/**
* 删除用户
* @param username 用户名/分机号
* @return 删除结果
*/
public boolean deleteUser(String username) {
if (!isValidUsername(username)) {
log.error("无效的用户名: {}", username);
return false;
}
Path userFilePath = Paths.get(sipProfilesPath, username + ".xml");
if (!Files.exists(userFilePath)) {
log.error("用户不存在: {}", username);
return false;
}
try {
Files.delete(userFilePath);
log.info("用户配置文件已删除: {}", userFilePath);
// 重载目录
return reloadDirectory();
} catch (IOException e) {
log.error("删除用户配置文件失败: {}", e.getMessage());
return false;
}
}
/**
* 修改用户密码
* @param username 用户名/分机号
* @param newPassword 新密码
* @return 修改结果
*/
public boolean changeUserPassword(String username, String newPassword) {
if (!isValidUsername(username)) {
log.error("无效的用户名: {}", username);
return false;
}
Path userFilePath = Paths.get(sipProfilesPath, username + ".xml");
if (!Files.exists(userFilePath)) {
log.error("用户不存在: {}", username);
return false;
}
try {
// 使用Java 8兼容的文件读取方式
String content = Files.lines(userFilePath, StandardCharsets.UTF_8)
.collect(Collectors.joining(System.lineSeparator()));
String updatedContent = content.replaceAll(
"<param name=\"password\" value=\".*?\"/>",
"<param name=\"password\" value=\"" + newPassword + "\"/>"
);
// 使用Java 8兼容的文件写入方式
try (BufferedWriter writer = Files.newBufferedWriter(userFilePath, StandardCharsets.UTF_8)) {
writer.write(updatedContent);
}
log.info("用户密码已更新: {}", username);
// 重载目录
return reloadDirectory();
} catch (IOException e) {
log.error("修改用户密码失败: {}", e.getMessage());
return false;
}
}
private boolean reloadDirectory() {
if (!isClientConnected()) {
log.warn("FreeSWITCH 未连接,无法重载目录");
return false;
}
try {
// 修改为使用 sendSyncApiCommand 返回 EslMessage
EslMessage message = client.sendSyncApiCommand("reloadxml", "");
String response = message.getBodyLines().toString();
EslMessage directoryMessage = client.sendSyncApiCommand("reload", "directory");
String directoryResponse = directoryMessage.getBodyLines().toString();
return response.contains("OK") && directoryResponse.contains("OK");
} catch (Exception e) {
log.error("重载目录失败: {}", e.getMessage());
return false;
}
}
/**
* 生成用户配置文件内容
* @param username 用户名/分机号
* @param password 密码
* @param displayName 显示名称
* @return 用户配置文件内容
*/
private String generateUserConfig(String username, String password, String displayName, String domain) {
return "<include>\n" +
" <user id=\"" + username + "\">\n" +
" <params>\n" +
" <param name=\"password\" value=\"" + password + "\"/>\n" +
" </params>\n" +
" <variables>\n" +
" <variable name=\"user_context\" value=\"default\"/>\n" +
" <variable name=\"domain\" value=\"" + domain + "\"/>\n" + // 绑定指定域名
" <variable name=\"display_name\" value=\"" + displayName + "\"/>\n" +
" <variable name=\"toll_allow\" value=\"domestic,international,local\"/>\n" +
" <variable name=\"accountcode\" value=\"" + username + "\"/>\n" +
" <variable name=\"effective_caller_id_name\" value=\"" + displayName + "\"/>\n" +
" <variable name=\"effective_caller_id_number\" value=\"" + username + "\"/>\n" +
" <variable name=\"outbound_caller_id_name\" value=\"$${outbound_caller_name}\"/>\n" + // 修正为全局变量引用($${})
" <variable name=\"outbound_caller_id_number\" value=\"$${outbound_caller_id}\"/>\n" + // 修正为全局变量引用($${})
" <variable name=\"callgroup\" value=\"techsupport\"/>\n" + // 补充默认呼叫组变量
" </variables>\n" +
" </user>\n" +
"</include>";
}
/**
* 校验用户名是否有效
* @param username
* @return
*/
private boolean isValidUsername(String username) {
// 用户名只能包含数字和字母,长度1-20
return Pattern.matches("^[a-zA-Z0-9]{1,20}$", username);
}
/**
* ESL监听程序
*/
private static class DefaultEslEventListener implements IEslEventListener {
@Override
public void eventReceived(EslEvent event) {
log.debug("收到 FreeSWITCH 事件: {}", event.getEventName());
}
@Override
public void backgroundJobResultReceived(EslEvent event) {
log.debug("收到 FreeSWITCH 后台任务结果: {}", event.getEventName());
}
}
}
3.FreeSWITCH用户服务类
import com.gnss.helmet.utils.FreeswitchUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* FreeSWITCH用户服务类
* @author Luo
* @date 2025-07-11
*/
@Service
@Slf4j
public class FreeswitchUserServer {
private final FreeswitchUtil freeswitchUtil;
private final ConcurrentMap<String, UserInfo> userCache = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final int cacheRefreshInterval;
private final int reconnectDelay;
/**
* 构造器注入:Spring会先解析@Value获取配置值,再传入构造方法
* @param freeswitchHost
* @param eslPort
* @param eslPassword
* @param directoryPath
* @param defaultCacheRefreshInterval
* @param reconnectDelay
*/
public FreeswitchUserServer(
@Value("${gnss.freeswitch.host}") String freeswitchHost,
@Value("${gnss.freeswitch.port}") int eslPort,
@Value("${gnss.freeswitch.password}") String eslPassword,
@Value("${gnss.freeswitch.path:/usr/local/freeswitch/conf/directory/default}") String directoryPath,
@Value("${gnss.freeswitch.refresh-interval:300}") int defaultCacheRefreshInterval,
@Value("${gnss.freeswitch.delay:5}") int reconnectDelay
) {
this.reconnectDelay = reconnectDelay;
// 初始化缓存刷新间隔
this.cacheRefreshInterval = defaultCacheRefreshInterval;
// 此时所有参数均已通过@Value注入,可安全创建FreeswitchUtil
this.freeswitchUtil = new FreeswitchUtil(
freeswitchHost,
eslPort,
eslPassword,
10,
directoryPath
);
}
@PostConstruct
public void init() {
connect();
startCacheRefreshTask();
startConnectionMonitor();
}
@PreDestroy
public void destroy() {
scheduler.shutdownNow();
disconnect();
}
/**
* 连接 FreeSWITCH ESL
*/
private void connect() {
try {
if (!freeswitchUtil.isClientConnected()) {
log.info("正在连接到 FreeSWITCH ESL...");
boolean connected = freeswitchUtil.connect();
if (connected) {
log.info("成功连接到 FreeSWITCH ESL");
refreshUserCache();
} else {
log.error("连接 FreeSWITCH ESL 失败,将在 {} 秒后重试", reconnectDelay);
scheduler.schedule(this::connect, reconnectDelay, TimeUnit.SECONDS);
}
}
} catch (Exception e) {
log.error("连接 FreeSWITCH ESL 时发生异常: {}", e.getMessage(), e);
scheduler.schedule(this::connect, reconnectDelay, TimeUnit.SECONDS);
}
}
/**
* 断开 FreeSWITCH ESL 连接
*/
private void disconnect() {
try {
if (freeswitchUtil != null) {
freeswitchUtil.disconnect();
log.info("已断开与 FreeSWITCH ESL 的连接");
}
} catch (Exception e) {
log.error("断开 FreeSWITCH ESL 连接时发生异常: {}", e.getMessage(), e);
}
}
/**
* 刷新用户缓存
*/
private void startCacheRefreshTask() {
scheduler.scheduleAtFixedRate(() -> {
try {
refreshUserCache();
} catch (Exception e) {
log.error("刷新用户缓存时发生异常: {}", e.getMessage(), e);
}
}, cacheRefreshInterval, cacheRefreshInterval, TimeUnit.SECONDS);
log.info("用户缓存刷新任务已启动,间隔: {} 秒", cacheRefreshInterval);
}
/**
* 连接监控任务
*/
private void startConnectionMonitor() {
scheduler.scheduleAtFixedRate(() -> {
try {
if (!freeswitchUtil.isClientConnected()) {
log.warn("检测到 FreeSWITCH 连接断开,尝试重新连接");
connect();
}
} catch (Exception e) {
log.error("连接监控过程中发生异常: {}", e.getMessage(), e);
}
}, 30, 30, TimeUnit.SECONDS);
log.info("连接监控任务已启动,间隔: 30 秒");
}
/**
* 创建新用户
* @param username
* @param password
* @return
*/
public boolean createUser(String username, String password,String domain) {
UserInfo userInfo = new UserInfo();
userInfo.setUsername(username);
userInfo.setPassword(password);
userInfo.setDisplayName(username);
userInfo.setDomain(domain);
return createUser(userInfo);
}
/**
* 创建新用户
* @param userInfo 用户信息
* @return 创建结果
*/
public boolean createUser(UserInfo userInfo) {
if (!ensureConnected()) {
return false;
}
try {
boolean result = freeswitchUtil.createUser(
userInfo.getUsername(),
userInfo.getPassword(),
userInfo.getDisplayName(),
userInfo.getDomain()
);
if (result) {
userCache.put(userInfo.getUsername(), userInfo);
log.info("成功创建用户: {}", userInfo.getUsername());
} else {
log.error("创建用户失败: {}", userInfo.getUsername());
}
return result;
} catch (Exception e) {
log.error("创建用户时发生异常: {}", e.getMessage(), e);
return false;
}
}
/**
* 删除用户
* @param username 用户名
* @return 删除结果
*/
public boolean deleteUser(String username) {
if (!ensureConnected()) {
return false;
}
try {
boolean result = freeswitchUtil.deleteUser(username);
if (result) {
userCache.remove(username);
log.info("成功删除用户: {}", username);
} else {
log.error("删除用户失败: {}", username);
}
return result;
} catch (Exception e) {
log.error("删除用户时发生异常: {}", e.getMessage(), e);
return false;
}
}
/**
* 修改用户密码
* @param username 用户名
* @param newPassword 新密码
* @return 修改结果
*/
public boolean changePassword(String username, String newPassword) {
if (!ensureConnected()) {
return false;
}
try {
boolean result = freeswitchUtil.changeUserPassword(username, newPassword);
if (result) {
UserInfo userInfo = userCache.get(username);
if (userInfo != null) {
userInfo.setPassword(newPassword);
}
log.info("成功修改用户密码: {}", username);
} else {
log.error("修改用户密码失败: {}", username);
}
return result;
} catch (Exception e) {
log.error("修改用户密码时发生异常: {}", e.getMessage(), e);
return false;
}
}
/**
* 检查用户是否在线
* @param username 用户名
* @return 用户在线状态
*/
public boolean isUserOnline(String username) {
if (!ensureConnected()) {
return false;
}
try {
boolean online = freeswitchUtil.isUserOnline(username);
UserInfo userInfo = userCache.get(username);
if (userInfo != null) {
userInfo.setOnline(online);
if (online) {
userInfo.setRegistrationTime(System.currentTimeMillis());
}
}
return online;
} catch (Exception e) {
log.error("检查用户在线状态时发生异常: {}", e.getMessage(), e);
return false;
}
}
/**
* 获取用户信息
* @param username 用户名
* @return 用户信息,如果不存在则返回null
*/
public UserInfo getUser(String username) {
UserInfo userInfo = userCache.get(username);
if (userInfo != null) {
userInfo.setOnline(isUserOnline(username));
}
return userInfo;
}
/**
* 获取所有用户信息
* @return 所有用户信息的映射
*/
public ConcurrentMap<String, UserInfo> getAllUsers() {
refreshOnlineStatus();
return userCache;
}
/**
* 刷新用户缓存
*/
public void refreshUserCache() {
if (!ensureConnected()) {
return;
}
try {
log.info("开始刷新用户缓存...");
// 在实际应用中,这里应该从配置文件或数据库加载用户
// 简化示例,仅刷新在线状态
refreshOnlineStatus();
log.info("用户缓存刷新完成,当前用户数: {}", userCache.size());
} catch (Exception e) {
log.error("刷新用户缓存时发生异常: {}", e.getMessage(), e);
}
}
private void refreshOnlineStatus() {
userCache.forEach((username, userInfo) -> {
boolean online = isUserOnline(username);
userInfo.setOnline(online);
if (online) {
userInfo.setRegistrationTime(System.currentTimeMillis());
}
});
}
private boolean ensureConnected() {
if (!freeswitchUtil.isClientConnected()) {
log.warn("FreeSWITCH 未连接,尝试重新连接...");
connect();
return freeswitchUtil.isClientConnected();
}
return true;
}
@Getter
public static class UserInfo {
private String username;
private String password;
private String displayName;
private String domain;
private boolean online;
private long registrationTime;
private String sipProfile;
private String userContext;
public UserInfo setUsername(String username) {
this.username = username;
return this;
}
public UserInfo setPassword(String password) {
this.password = password;
return this;
}
public UserInfo setDisplayName(String displayName) {
this.displayName = displayName;
return this;
}
public UserInfo setDomain(String domain) {
this.domain = domain;
return this;
}
public UserInfo setOnline(boolean online) {
this.online = online;
return this;
}
public UserInfo setRegistrationTime(long registrationTime) {
this.registrationTime = registrationTime;
return this;
}
public UserInfo setSipProfile(String sipProfile) {
this.sipProfile = sipProfile;
return this;
}
public UserInfo setUserContext(String userContext) {
this.userContext = userContext;
return this;
}
}
}