freeswitch 通话录音及通话记录

1、接听后开始录音

dialplan/default.xml

      <action application="set" data="RECORD_APPEND=true"/> 
      <action application="set" data="RECORD_COPYRIGHT=(c) 2020"/>  
      <action application="set" data="RECORD_SOFTWARE=FreeSWITCH"/>
      <action application="set" data="RECORD_ARTIST=FreeSWITCH"/> 
      <action application="set" data="RECORD_COMMENT=FreeSWITCH"/> 
      <action application="set" data="RECORD_DATE=${strftime(%Y-%m-%d %H:%M)}"/>  
      <action application="set" data="RECORD_STEREO=true"/>
      <action application="set" data="enable_file_write_buffering=false"/>
      <action application="set" data="media_bug_answer_req=true"/>
      
      <action application="set" data="record_relative_dir=${strftime(%Y-%m-%d)}"/>
      <action application="set" data="record_file=${caller_id_number}.${strftime(%Y-%m-%d-%H-%M-%S)}.wav"/>
      <action application="system" data="/bin/mkdir -m 755 -p $${recordings_dir}/${record_relative_dir}"/>
      <action application="export" data="execute_on_answer=record_session $${recordings_dir}/${record_relative_dir}/${record_file}" />

RECORD_APPEND 如果不开启,双方同时说话录音会有问题

/bin/mkdir -m 755 -p $${recordings_dir}/${record_relative_dir}

使用这个命令来创建根据日期归档的录音文件,同时设置目录权限755,避免后面其他应用访问出现权限问题

execute_on_answer=record_session $${recordings_dir}/${record_relative_dir}/${record_file}

这一段用来指示在对方应答后开始录音,并且指定录音文件路径

2、通话记录通过mod_xml_cdr上传

autoload_configs/modules.conf.xml

<load module="mod_xml_cdr"/>

配置要上传的api

autoload_configs/xml_cdr.conf.xml

<param name="url" value="https://xxxx/call/callRecordApi/create"/>

开启模块, 需要在控制台加载模块

freeswitch@VM-8-4-centos> load mod_xml_cdr
+OK Reloading XML
-ERR [Module already loaded]

3、关联录音文件和通话记录

在freeswitch服务器上搭建一个nginx静态网站

在网站root目录创建一个软连接,指向录音文件目录

ln -s /var/log/freeswitch/recording recording

前面设置的通话记录接口可以接受xml记录里面的variables里面的录音文件路径,保存记录时添加上freeswitch服务器的域名或者ip就可以获取直接访问到录音文件的链接了。

freeswitch搭建

1、获取token

https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Installation/HOWTO-Create-a-SignalWire-Personal-Access-Token_67240087/#attachments

2、 通过包安装

https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Installation/Linux/CentOS-7-and-RHEL-7_10289546#installing-from-rpm-packages

echo "signalwire" > /etc/yum/vars/signalwireusername
echo "TOKEN" > /etc/yum/vars/signalwiretoken
yum install -y https://$(< /etc/yum/vars/signalwireusername):$(< /etc/yum/vars/signalwiretoken)@freeswitch.signalwire.com/repo/yum/centos-release/freeswitch-release-repo-0-1.noarch.rpm epel-release
yum install -y freeswitch-config-vanilla freeswitch-lang-* freeswitch-sounds-*
systemctl enable freeswith
systemctl start freeswith

3、修改注册端口

vars.xml

  <!-- Internal SIP Profile -->
  <X-PRE-PROCESS cmd="set" data="internal_auth_calls=true"/>
  <X-PRE-PROCESS cmd="set" data="internal_sip_port=10777"/>
  <X-PRE-PROCESS cmd="set" data="internal_tls_port=10778"/>
  <X-PRE-PROCESS cmd="set" data="internal_ssl_enable=false"/>

  <!-- External SIP Profile -->
  <X-PRE-PROCESS cmd="set" data="external_auth_calls=false"/>
  <X-PRE-PROCESS cmd="set" data="external_sip_port=10888"/>
  <X-PRE-PROCESS cmd="set" data="external_tls_port=10889"/>
  <X-PRE-PROCESS cmd="set" data="external_ssl_enable=false"/>

4、修改sdp端口(如果不设置后开放防火墙,接通后听不到声音)

autoload_configs/switch.conf.xml

    <!-- RTP port range -->
    <param name="rtp-start-port" value="15000"/>
    <param name="rtp-end-port" value="16000"/>

5、设置外部ip

vars.xml

  <X-PRE-PROCESS cmd="stun-set" data="external_rtp_ip=43.138.146.222"/>
  <X-PRE-PROCESS cmd="stun-set" data="external_sip_ip=43.138.146.222"/>

6、重启fs

蒙提霍尔(三门问题)go程序模拟

在某个电视节目比赛环节中,参赛者会看见三扇关闭了的门,其中一扇的后面有一辆汽车,选中后面有车的那扇门可赢得该汽车,另外两扇门后面则各藏有一只山羊。参赛者选定了一扇门,但未开启它,随后节目主持人蒙提霍尔(Monty Hall)开启了剩下的两扇门中的一扇并且发现后面是一只山羊,此时主持人会给予选手重新选择的机会。问题是:此时参赛者换另一扇门会否增加赢得汽车的机率?

package main

import (
	"fmt"
	"math/rand"
	"time"
)

var (
	choose, answer                                   int
	successChange, successKeep, successUnknownChange = 0, 0, 0
)

func main() {
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < 10000; i++ {
		choose = rand.Intn(3)
		answer = rand.Intn(3)
		//不换且正确
		if answer == choose {
			successKeep++
		}
		//主持知道答案,排除一个,选择换(第一次选中换后不可能选中)
		if (choose+1)%3 == answer || (choose+2)%3 == answer {
			successChange++
		}
		//主持不知道答案,随机排除,选择换
		newChoose := (choose + rand.Intn(2)) % 3
		if newChoose == answer {
			successUnknownChange++
		}
	}
	fmt.Println(successChange)
	fmt.Println(successKeep)
	fmt.Println(successUnknownChange)
}

运行结果

~/projects/go_test$ go run main.go
6586
3414
3369

mysql导入报错: unknown command ‘\”‘

最近开始使用dbeaver,在从服务器导出线上sql到本地的时候,发现会出现这样的报错,找了半天原因以为是dbeaver的问题,后来直接用mysqldump执行导出,source命令导入,问题还是发生了。

导出

mysqldump -uroot -p xxx >/tmp/test.sql

导入

mysql -uroot

use xxx; source /path/to/sql

这样执行后还是报错

最后发现是导入连接数据库的默认character-set 问题

连接语句改为mysql -uroot –default-character-set=utf8

重新执行source,问题解决

dbeaver 解决方式: 在导入时设置参数–default-character-set=utf8

DateTime Class 使用

DateTime vs DateTimeImmutable

DateTime 使用modify/add/sub 等修改后,本身时间对象也会跟着修改,format输出的就是最新的时间

DateTimeImmutable修改后,本身时间对象不会修改,相关修改方法返回一个新的时间对象,用新的对象进行赋值才会修改原来的对象

修改时间方法

motify(使用方法和date类似, 传入的参数string和date构造string规则相同)

参考: https://www.php.net/manual/en/datetime.formats.relative.php

代码例子:

这里用放款后生成的一个还款通知举例

$returnTime = new DateTimeImmutable($loan['return_time']); //放款时间
$nextMonth = $returnTime->modify('first day of next month'); //下月开始还款
$notifyTime = $nextMonth->modify('+'. $loan['repayment_notify_day'] . ' days'); //还款通知时间
$repaymentTime = $nextMonth->modify('+'. $loan['repayment_last_day'] . ' days'); //最后还款时间
$notifies = [];

//$schedules 是计算出的每月应还利息本金信息
foreach ($schedules as $schedule) {
    $notifies[] = [
        'customer_id' => $loan['customer_id'],
        'loan_id' => $loan['id'],
        'notify_data' => $notifyTime->format('Y-m-d'),
        'repayment_date' => $repaymentTime->format('Y-m-d'),
        'month' => $notifyTime->format('Ym'),
        'capital_amount' => $schedule['capital'],
        'interests_amount' => $schedule['interests'],
        'total_amount' => round($schedule['interests'] + $schedule['capital'], 2),
        'status' => 'wait',
        'index' => $schedule['index'],
        'notify_time' => null,
    ];
    $notifyTime = $notifyTime->modify('+1 month');
    $repaymentTime = $repaymentTime->modify('+1 month');
}

//$notifies 就是生成的每月还款通知的对应信息了 

docker desktop 修改镜像容器存储位置

docker desktop 进入配置页,里面有可以设置存储位置的配置项,不过这个配置对于用windows WSL 的客户端来说并不会生效,每次设置后都会被重新修改回默认项。

要想修改这个存储位置只能修改wsl的存储位置才行

首先查看当前安装的wsl

wsl -l -v

  NAME                   STATE           VERSION
* docker-desktop-data    Running         2
  docker-desktop         Running         2

关闭wsl

wsl --shutdown

导出desktop-data镜像

wsl --export docker-desktop-data "临时存储地址"

取消注册

wsl --unregister docker-desktop-data

导入备份并指定新路径

wsl --import docker-desktop-data "目标存储路径" "备份地址" --version 2

手撸的一个阶段编辑的组件

element-ui + vue2

效果如下

为自己点赞

<template>
  <div class="stage-container">
    <el-popover
      v-for="(stage) in stageList" :key="stage.id"
      width="200"
      trigger="hover"
    >
      <div slot="reference" :class="stageState(stage)" class="stage">
        <div class="stage-title">{{ stage.name }}</div>
        <div :class="stageState(stage)" class="stage-side stage-left"/>
        <div :class="stageState(stage)" class="stage-side stage-right"/>
      </div>
      <div class="label-list">
        <div v-for="(label) in stage.labels" :key="label.id">
          <el-link :underline="false" :type="labelState(label)" class="label" @click="selectLabel(label)">{{ label.name }}</el-link>
        </div>
      </div>
    </el-popover>
  </div>
</template>

<script>
import { customerStages } from '@/api/admin/crm'
export default {
  props: {
    stageId: Number,
    stageLabelId: Number
  },
  data() {
    return {
      stageList: []
    }
  },
  computed: {
    stageState() {
      return stage => {
        if (!this.currentStage.order) {
          return 'not-active'
        }
        return stage.order > this.currentStage.order ? 'not-active' : ''
      }
    },
    labelState() {
      return label => {
        return this.stageLabelId == label.id ? 'primary' : 'info'
      }
    },
    currentStage() {
      for (const stage of this.stageList) {
        if (stage.id == this.stageId) {
          return stage
        }
      }
      return {}
    }
  },
  async mounted() {
    const res = await customerStages()
    this.stageList = res.data
  },
  methods: {
    selectLabel(label) {
      this.$emit('selectLabel', label)
    }
  }
}
</script>

<style lang="scss">
.stage-container {
  margin-left: 50px;
  display: flex;
}

.stage-side {
  height: 38px;
  width: 38px;
  top: 0px;
  position: absolute;
  transform: rotate(45deg) scale(0.707);
  &.stage-left {
    z-index: 5;
    left: -19px;
    background-color: white;
  }
  &.stage-right {
    z-index: 10;
    right: -18px;
    background-color: $xr-color-primary;
  }
  &.not-active {
    background-color: white;
    border-top: 2px solid $xr-color-primary;
    border-right: 2px solid $xr-color-primary;
    top: -2px;
  }
}

.stage {
  color: #fff;
  position: relative;
  padding: 10px 20px 10px 37px;
  margin: 28px 2px;
  height: 38px;
  background-color: $xr-color-primary;
  cursor: pointer;
  &.not-active {
    color: $xr-color-primary;
    background-color: white;
    border-top: 2px solid $xr-color-primary;
    border-bottom: 2px solid $xr-color-primary;
  }
}

.label {
  padding: 5px;
  font-size: 13px;
  width: 100%;
}
</style>

随记

2022-12-28

clash 本地连接错误

修改profile 设置dns false

php版本切换(ubuntu)

安装apt install php8.1 php8.1-fpm
安装apt install php7.4 php7.4-fpm
update-alternatives — config php

systemctl start php7.4-fpm

/etc/nginx/conf.d/test.conf

        location ~ \.php$ {
                fastcgi_pass unix:/run/php/php8.1-fpm.sock;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
                include fastcgi_params;
        }

宝塔运行指定版本composer

php80 /www/server/php/80/bin/composer 

文档工具

mkdocs

生成随机id

uuid 雪花算法(php-snowflake)

du命令 du 命令用于显示文件或目录的磁盘使用情况。它会递归地显示指定文件或目录的大小,并可以按照不同的格式进行显示。常用的选项包括: -h:以人类可读的格式显示大小(例如,K、M、G)。 -s:仅显示总大小,而不显示每个子目录的大小。 -c:同时显示所有目录的总大小

swoole redis连接池应用

遇到问题

有一个cli应用,需要很多协程频繁访问redis,用swoole的redis连接池实现链接;
但是遇到问题,发现内存很快溢出,第一反应是变量没释放,unset了半天没有解决;
然后通过

use Swoole\Coroutine;
$coros = Coroutine::listCoroutines();

定时查看了一下协程数量,发现协程缓慢在增加,慢慢的内存就溢出了。
限制了一下协程数量,扩充了一下连接池大小,挂起的协程数量明显减少了,但还是越来越多,猜测是从redis连接池获取连接的时候获取的连接不可用,然后一直挂起了。

解决方式

从连接池中取出连接后测试一下,可以使用再取出,不然push(null)让连接池生成一个新的,接着继续取,知道连接可用,这样处理后协程数量就一直不会一直增长了。

    protected function redisOperation($callback) {
        $connection = $this->pool->get();
        while(!$connection->ping()) {
            $this->pool->put(null);
            $connection = $this->pool->get();
        }
        $result = call_user_func($callback, $connection);
        $this->pool->put($connection);
        return $result;
    }

问题顺利解决,撒花

git pull/push 速度慢

问题:

windows环境下,不知道为什么最近gitpush和pull速度很慢,经常连接超时;

解决方案:

搜了一下解决方案,大概记录一下

~/.ssh/config

Host github.com *.github.com *.codeup.aliyun.com
    User git
    # SSH默认端口22,git://, HTTPS默认端口443,https://
    Port 22
    Hostname %h
    # 这里放你的SSH私钥
    IdentityFile ~\.ssh\id_rsa
    # 设置代理, 127.0.0.1:10808 换成你自己代理软件监听的本地地址
    # HTTPS使用-H,SOCKS使用-S
    ProxyCommand connect -S 127.0.0.1:10808 %h %p

需要用到connect客户端
connect
解压exe文件后放到C:\Windows\System32目录下面