Java SpringBoot 对接FreeSwitch

发布于:2025-07-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

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;
        }
    }
}


网站公告

今日签到

点亮在社区的每一天
去签到