xuhy
7 天以前 640d93c464c65a0ef128f7f357a3e9abe44fbd2c
虚拟号码通话
9个文件已修改
21个文件已添加
2427 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/entity/Order.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/OrderFallbackFactory.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/OrderClient.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/pom.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/controller/OrderController.java 126 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/controller/PrivateNumberCallBackController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/entity/IamConfig.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/entity/Order.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/mapper/IamConfigMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/service/IamConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/service/impl/IamConfigServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/utils/PrivateNumberUtil.java 336 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/utils/UrlToMultipartFileUtil.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/service/IAXBInterfaceDemo.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/service/impl/AXBInterfaceDemoImpl.java 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/AXBUtil.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/HttpUtilClient.java 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/StringUtil.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/OrderController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/entity/Order.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/controller/OrderController.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/entity/IamConfig.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/mapper/IamConfigMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/service/IamConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/service/impl/IamConfigServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/service/IAXBInterfaceDemo.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/service/impl/AXBInterfaceDemoImpl.java 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/AXBUtil.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/HttpUtil.java 360 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/StringUtil.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/entity/Order.java
@@ -199,4 +199,20 @@
    private String paperPic;
    @ApiModelProperty("拉起确认收款页面参数")
    private String packageInfo;
    @ApiModelProperty("绑定关系id")
    @TableField("subscription_id")
    private String subscriptionId;
    @ApiModelProperty("虚拟号码")
    @TableField("virtual_number")
    private String virtualNumber;
    @ApiModelProperty("电话录音")
    @TableField("phone_recording")
    private String phoneRecording;
    @ApiModelProperty("录音文件存储的服务器域名")
    @TableField("record_domain")
    private String recordDomain;
    @ApiModelProperty("录音文件名")
    @TableField("record_object_name")
    private String recordObjectName;
}
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/factory/OrderFallbackFactory.java
@@ -187,7 +187,7 @@
            }
            @Override
            public R<Boolean> orderSubmit(OrderSubmitRequest orderSubmitRequest) {
            public R<String> orderSubmit(OrderSubmitRequest orderSubmitRequest) {
                return R.fail(cause.getMessage());
            }
@@ -273,6 +273,16 @@
            }
            @Override
            public R<String> updateSubscriptionId(String orderId, String subscriptionId, String virtualNumber) {
                return R.fail(cause.getMessage());
            }
            @Override
            public R<String> updatePhoneRecording(String orderId, String audioUrl) {
                return R.fail(cause.getMessage());
            }
            @Override
            public R<Boolean> changeOrderState(String orderId, Integer state) {
                return R.fail(cause.getMessage());
            }
ruoyi-api/ruoyi-api-order/src/main/java/com/ruoyi/order/api/feignClient/OrderClient.java
@@ -227,8 +227,6 @@
    /**
     * 根据类型获取交易额
     *
     * @param cityList 城市列表
     * @param type     查询类型
     * @return 交易额
     */
    @GetMapping(value = "/order/tradeMoney")
@@ -296,7 +294,7 @@
     * @return 提交结果
     */
    @PostMapping(value = "/order/orderSubmit")
    R<Boolean> orderSubmit(@RequestBody OrderSubmitRequest orderSubmitRequest);
    R<String> orderSubmit(@RequestBody OrderSubmitRequest orderSubmitRequest);
    /**
     * 师傅端-定时调度记录师傅所走路线经纬度
@@ -460,4 +458,25 @@
     */
    @GetMapping(value = "/order/getTodayOrderData")
    R<List<Order>> getTodayOrderData();
    /**
     * 设置订单的绑定id
     * @param orderId
     * @param subscriptionId
     * @return
     */
    @GetMapping(value = "/order/updateSubscriptionId")
    R<String> updateSubscriptionId(@RequestParam("orderId")String orderId,
                                   @RequestParam("subscriptionId")String subscriptionId,
                                   @RequestParam("virtualNumber")String virtualNumber);
    /**
     * 设置订单的通话录音
     * @param orderId 订单id
     * @param audioUrl 音频地址
     * @return
     */
    @GetMapping(value = "/order/updatePhoneRecording")
    R<String> updatePhoneRecording(@RequestParam("orderId")String orderId,
                              @RequestParam("audioUrl")String audioUrl);
}
ruoyi-service/ruoyi-admin/pom.xml
@@ -239,6 +239,11 @@
            <version>0.2.12</version>
            <scope>compile</scope>
        </dependency>
        <!-- 转MultipartFile才需要导这个maven包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
    </dependencies>
    <build>
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/controller/OrderController.java
@@ -14,13 +14,16 @@
import com.ruoyi.admin.service.*;
import com.ruoyi.admin.utils.DescribeInstances;
import com.ruoyi.admin.utils.HttpUtil;
import com.ruoyi.admin.utils.PrivateNumberUtil;
import com.ruoyi.admin.vo.OrderByServeRecordVO;
import com.ruoyi.admin.vo.OrderDetailVO;
import com.ruoyi.admin.vo.OrderReasinDto;
import com.ruoyi.admin.vo.ReassinDto;
import com.ruoyi.admin.voice.util.AXBUtil;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.GlobalException;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.utils.DateUtils;
import com.ruoyi.common.core.utils.GaoDeMapUtil;
@@ -61,6 +64,7 @@
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -100,7 +104,15 @@
    private ExchangeDispatchClient dispatchClient;
    @Resource
    private TokenService tokenService;
    @Resource
    private RecoveryClassifyService recoveryClassifyService;
    @Resource
    private FranchiseeService franchiseeService;
    @Resource
    private PrivateNumberUtil privateNumberUtil;
    @Autowired
    private IamConfigService iamConfigService;
    /**
     * 雪花算法类
     */
@@ -144,11 +156,6 @@
        }
        return R.ok(orderDetailVO);
    }
    @Resource
    private RecoveryClassifyService recoveryClassifyService;
    @Resource
    private FranchiseeService franchiseeService;
    /**
     * 订单列表
     *
@@ -429,70 +436,65 @@
    @RequiresPermissions("order_reassignment")
    @ApiOperation(value = "订单列表-订单派单/改派", tags = {"后台-订单管理"})
    @PostMapping(value = "/reassignment")
    @Transactional(rollbackFor = Exception.class)
    public R<String> reassignment(@RequestBody OrderReasinDto orderReasinDto) {
//        String[] split = orderIds.split(",");
        for (ReassinDto orderId : orderReasinDto.getReassinDtos()) {
        MasterWorker masterWorker = masterWorkerService.lambdaQuery()
                .eq(MasterWorker::getId, orderReasinDto.getWorkerId())
                .eq(MasterWorker::getIsDelete, 0).one();
        Order item = orderClient.detail(orderId.getOrderId()).getData();
        Order order = orderClient.exchangeOrder(orderId.getType(), orderId.getOrderId(), orderReasinDto.getWorkerId(),
                masterWorker.getRealName(), masterWorker.getPhone()).getData();
        // 订单派单
        boolean result = true;
        if (Constants.TWO.equals(orderId.getType())) {
            orderClient.updateArrivalTime(order.getId(),orderReasinDto.getArriveTime());
            if (order.getState().equals(Constants.SIX) || order.getState().equals(Constants.THREE)) {
                orderClient.updateState(order.getId(), 7);
                //如果是待改派,将上门时间设置为最新的,并且更新再投原因
                if (order.getState().equals(Constants.SIX)){
                    dispatchClient.changeReason(order.getId(), "");
            MasterWorker masterWorker = masterWorkerService.lambdaQuery()
                    .eq(MasterWorker::getId, orderReasinDto.getWorkerId())
                    .eq(MasterWorker::getIsDelete, 0).one();
            Order order = orderClient.exchangeOrder(orderId.getType(), orderId.getOrderId(), orderReasinDto.getWorkerId(),
                    masterWorker.getRealName(), masterWorker.getPhone()).getData();
            try{
                // 虚拟号码  师傅号码备案
                String fileName = privateNumberUtil.uploadNumberFile(masterWorker.getProfilePicture()).getData();
                R r = privateNumberUtil.addANumber(masterWorker.getPhone(), masterWorker.getRealName(), masterWorker.getIdNumber(), fileName);
            }catch (Exception e){
                throw new GlobalException("虚拟号码绑定异常!");
            }
            // 虚拟号码配置
            IamConfig iamConfig = iamConfigService.getById(1);
            // 订单派单
            if (Constants.TWO.equals(orderId.getType())) {
                orderClient.updateArrivalTime(order.getId(),orderReasinDto.getArriveTime());
                if (order.getState().equals(Constants.SIX) || order.getState().equals(Constants.THREE)) {
                    orderClient.updateState(order.getId(), 7);
                    //如果是待改派,将上门时间设置为最新的,并且更新再投原因
                    if (order.getState().equals(Constants.SIX)){
                        dispatchClient.changeReason(order.getId(), "");
                    }
                }
                // 订单状态为 待完工时,需要更改状态 待上门且清空师傅到达预约点时间
                if (order.getState().equals(Constants.TWO)) {
                    orderClient.updateStateAndArrivalTime(orderId.getOrderId(), Constants.ONE);
                }
                if(StringUtils.hasLength(order.getSubscriptionId())){
                    // 虚拟号码更换
                    AXBUtil.axbModifyNumber(iamConfig.getAppKey(),iamConfig.getAppSecret(),order.getSubscriptionId(), order.getReservationPhone(), masterWorker.getPhone());
                }else {
                    // 虚拟号码绑定
                    String result = AXBUtil.axbBindNumber(iamConfig.getAppKey(), iamConfig.getAppSecret(), order.getOrderNumber(), order.getReservationPhone(), masterWorker.getPhone());
                    // 存储绑定后的唯一标识
                    JSONObject jsonObject = JSONObject.parseObject(result);
                    String subscriptionId = jsonObject.getString("subscriptionId");
                    orderClient.updateSubscriptionId(order.getId(), subscriptionId,iamConfig.getVirtualNumber());
                }
            }else {
                orderClient.updateArrivalTime(order.getId(),orderReasinDto.getArriveTime());
                orderClient.updateState(order.getId(), 7);
                // 虚拟号码绑定
                String result = AXBUtil.axbBindNumber(iamConfig.getAppKey(), iamConfig.getAppSecret(), order.getOrderNumber(), order.getReservationPhone(), masterWorker.getPhone());
                // 存储绑定后的唯一标识
                JSONObject jsonObject = JSONObject.parseObject(result);
                String subscriptionId = jsonObject.getString("subscriptionId");
                orderClient.updateSubscriptionId(order.getId(), subscriptionId,iamConfig.getVirtualNumber());
            }
            // 订单状态为 待完工时,需要更改状态 待上门且清空师傅到达预约点时间
            if (order.getState().equals(Constants.TWO)) {
                orderClient.updateStateAndArrivalTime(orderId.getOrderId(), Constants.ONE);
            ChannelHandlerContext context = NettyChannelMap.getData(String.valueOf(orderReasinDto.getWorkerId()));
            if (null != context) {
                NettyWebSocketController.sendMsgToClient(context, "您有一条新的订单,请注意查收!");
            }
            // 生成改派信息
//            ChangeDispatch changeDispatch = new ChangeDispatch();
//            changeDispatch.setWorkerId(item.getServerId());
//            changeDispatch.setWorkerName(item.getServerName());
//            changeDispatch.setApplyReason(orderReasinDto.getApplyReason());
//            changeDispatch.setApplyTime(new Date());
//            changeDispatch.setState(Constants.ONE);
//            changeDispatch.setOrderId(orderId.getOrderId());
//            changeDispatch.setOrderNumber(item.getOrderNumber());
//            if (null != item.getUserId()) {
//                changeDispatch.setUserId(item.getUserId());
//            }
//            changeDispatch.setUserName(item.getReservationName());
//            changeDispatch.setIsDelete(Constants.ZERO);
//            result = dispatchClient.saveRecord(changeDispatch).getData();
        }else {
            orderClient.updateArrivalTime(order.getId(),orderReasinDto.getArriveTime());
            orderClient.updateState(order.getId(), 7);
        }
        ChannelHandlerContext context = NettyChannelMap.getData(String.valueOf(orderReasinDto.getWorkerId()));
        if (null != context) {
            NettyWebSocketController.sendMsgToClient(context, "您有一条新的订单,请注意查收!");
        }
//        try {
//            WebSocketServer.sendInfo("您有一条新的订单,请注意查收!", String.valueOf(workerId));
//        } catch (IOException e) {
//            return R.fail("订单推送失败!");
//        }
        }
        return R.ok() ;
    }
@@ -623,8 +625,6 @@
    /**
     * 订单列表-excel导出
     *
     * @param name  师傅姓名
     * @param phone 师傅电话
     */
    @RequiresPermissions("order_count")
    @ApiOperation(value = "订单统计", tags = {"后台-订单管理"})
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/controller/PrivateNumberCallBackController.java
New file
@@ -0,0 +1,61 @@
package com.ruoyi.admin.controller;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.admin.entity.Order;
import com.ruoyi.admin.service.OrderService;
import com.ruoyi.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Objects;
/**
 * <p>
 * 协议政策、司机操作指导 前端控制器
 * </p>
 *
 * @author hjl
 * @since 2024-05-29
 */
@Slf4j
@RestController
@RequestMapping("/privateNumber")
public class PrivateNumberCallBackController {
    @Resource
    private OrderService orderService;
    /**
     * 隐私号码回调
     */
    @PostMapping(value = "/callBack")
    public R<String> callBack(@RequestBody JSONObject jsonObject) {
        JSONArray feeLst = jsonObject.getJSONArray("feeLst");
        for (int i = 0; i < feeLst.size(); i++) {
            JSONObject feeLstJson = feeLst.getJSONObject(i);
            String subscriptionId = feeLstJson.getString("subscriptionId");
            String recordDomain = feeLstJson.getString("recordDomain");
            String recordObjectName = feeLstJson.getString("recordObjectName");
            String recordBucketName = feeLstJson.getString("recordBucketName");
            log.info("隐私号码回调:{},录音文件名:{},录音服务器名:{}", subscriptionId, recordObjectName, recordDomain);
            Order order = orderService.getOne(Wrappers.lambdaQuery(Order.class)
                    .eq(Order::getSubscriptionId, subscriptionId)
                    .last("LIMIT 1"));
            if(Objects.nonNull(order)){
                order.setRecordDomain(recordDomain);
                order.setRecordObjectName(recordObjectName);
                orderService.updateById(order);
            }
        }
        return R.ok("OK");
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/entity/IamConfig.java
New file
@@ -0,0 +1,48 @@
package com.ruoyi.admin.entity;
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.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
 * <p>
 * 虚拟号码配置
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Getter
@Setter
@TableName("t_iam_config")
@ApiModel(value = "t_iam_config对象", description = "虚拟号码配置")
public class IamConfig implements Serializable {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("用户")
    @TableField("userName")
    private String userName;
    @ApiModelProperty("子账号")
    @TableField("account")
    private String account;
    @ApiModelProperty("子账号密码")
    @TableField("password")
    private String password;
    @ApiModelProperty("虚拟号码APP_Key")
    @TableField("appKey")
    private String appKey;
    @ApiModelProperty("虚拟号码APP_Secret")
    @TableField("appSecret")
    private String appSecret;
    @ApiModelProperty("虚拟号码")
    @TableField("virtualNumber")
    private String virtualNumber;
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/entity/Order.java
@@ -148,4 +148,20 @@
    @TableField(exist = false)
    private String applyReason;
    @ApiModelProperty("绑定关系id")
    @TableField("subscription_id")
    private String subscriptionId;
    @ApiModelProperty("虚拟号码")
    @TableField("virtual_number")
    private String virtualNumber;
    @ApiModelProperty("电话录音")
    @TableField("phone_recording")
    private String phoneRecording;
    @ApiModelProperty("录音文件存储的服务器域名")
    @TableField("record_domain")
    private String recordDomain;
    @ApiModelProperty("录音文件名")
    @TableField("record_object_name")
    private String recordObjectName;
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/mapper/IamConfigMapper.java
New file
@@ -0,0 +1,18 @@
package com.ruoyi.admin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.admin.entity.IamConfig;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * 虚拟号码管理 Mapper 接口
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Mapper
public interface IamConfigMapper extends BaseMapper<IamConfig> {
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/service/IamConfigService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.admin.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.admin.entity.IamConfig;
/**
 * <p>
 * 改派管理 服务类
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
public interface IamConfigService extends IService<IamConfig> {
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/service/impl/IamConfigServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.admin.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.admin.entity.IamConfig;
import com.ruoyi.admin.mapper.IamConfigMapper;
import com.ruoyi.admin.service.IamConfigService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 改派管理 服务实现类
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Service
public class IamConfigServiceImpl extends ServiceImpl<IamConfigMapper, IamConfig> implements IamConfigService {
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/utils/PrivateNumberUtil.java
New file
@@ -0,0 +1,336 @@
package com.ruoyi.admin.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.admin.entity.IamConfig;
import com.ruoyi.admin.service.IamConfigService;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PrivateNumberUtil {
    @Autowired
    private RedisService redisService;
    @Autowired
    private IamConfigService iamConfigService;
    private final String TOKEN_URL = "https://iam.cn-south-1.myhuaweicloud.com/v3/auth/tokens";
    private final String UPLOAD_NUMBER_FILE_URL = "https://rtc.cn-north-1.myhuaweicloud.com/v1/privatenumber/a-number/files?agree_authorization_statement=true";
    private final String ADD_A_NUMBER_URL = "https://rtc.cn-north-1.myhuaweicloud.com/v1.0/privatenumber/a-number?agree_authorization_statement=true";
    /**
     * 获取token
     * @return
     */
    public String getToken() {
        String token = redisService.getCacheObject("PRIVATE_NUMBER:");
        if(StringUtils.hasLength(token)){
            return token;
        }
        IamConfig iamConfig = iamConfigService.getById(1);
        String userName = iamConfig.getUserName();
        String account = iamConfig.getAccount();
        String password = iamConfig.getPassword();
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        MediaType mediaType = MediaType.parse("text/plain");
        RequestBody body = RequestBody.create(mediaType, "{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"domain\":{\"name\":\""+userName+"\"},\"name\":\""+account+"\",\"password\":\""+password+"\"}}},\"scope\":{\"domain\":{},\"project\":{\"name\":\"cn-north-1\"}}}}");
        Request request = new Request.Builder()
                .url(TOKEN_URL)
                .method("POST", body)
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        // 获取响应头 X-Subject-Token
        token = response.header("X-Subject-Token");
        if(StringUtils.hasLength(token)){
            ResponseBody responseBody = response.body();
            if(responseBody != null) {
                try {
                    String string = responseBody.string();
                    log.info("获取token:{}",string);
                    JSONObject jsonObject = JSONObject.parseObject(string);
                    JSONObject tokenJson = jsonObject.getJSONObject("token");
                    Date expiresAt = tokenJson.getDate("expires_at");
                    redisService.setCacheObject("PRIVATE_NUMBER:",token,expiresAt.getTime()-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
                } catch (IOException e) {
                    throw new ServiceException(e.getMessage());
                }
            }
            return token;
        }else {
            return null;
        }
    }
    /**
     * 获取token
     * @return
     */
    public static String getTokenTest() {
        String account = "hsg13437173440";
        String userName = "denglu";
        String password = "6!JR8Fn0";
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        MediaType mediaType = MediaType.parse("text/plain");
        RequestBody body = RequestBody.create(mediaType, "{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"domain\":{\"name\":\""+account+"\"},\"name\":\""+userName+"\",\"password\":\""+password+"\"}}},\"scope\":{\"domain\":{},\"project\":{\"name\":\"cn-north-1\"}}}}");
        Request request = new Request.Builder()
                .url("https://iam.cn-south-1.myhuaweicloud.com/v3/auth/tokens")
                .method("POST", body)
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        // 获取响应头 X-Subject-Token
        String token = response.header("X-Subject-Token");
        if(StringUtils.hasLength(token)){
            return token;
        }else {
            return null;
        }
    }
    /**
     * 上传号码文件
     * @param filePath
     */
    public R<String> uploadNumberFile(String filePath) {
        MultipartFile file = UrlToMultipartFileUtil.urlTransferMultipartFile(filePath);
        MultipartFile[] files = new MultipartFile[]{file};
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        // 构建multipart/form-data请求体
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        // 添加文件到请求体
        try {
            for (MultipartFile multipartFile : files) {
                if (multipartFile != null && !multipartFile.isEmpty()) {
                    RequestBody fileBody = RequestBody.create(
                            MediaType.parse(multipartFile.getContentType()),
                            multipartFile.getBytes()
                    );
                    builder.addFormDataPart("files", multipartFile.getOriginalFilename(), fileBody);
                }
            }
        } catch (IOException e) {
            System.err.println("Error reading multipart file: " + e.getMessage());
            e.printStackTrace();
            return R.fail("文件读取失败: " + e.getMessage());
        }
        RequestBody body = builder.build();
        Request request = new Request.Builder()
                .url(UPLOAD_NUMBER_FILE_URL)
                .method("POST", body)
                .addHeader("X-Auth-Token", getToken())
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            ResponseBody responseBody = response.body();
            if(responseBody != null) {
                try {
                    String string = responseBody.string();
                    log.info("上传号码文件:{}",string);
                    JSONObject jsonObject = JSONObject.parseObject(string);
                    String result = jsonObject.getString("result");
                    if("Upload Successful.".equals(result)){
                        return R.ok(file.getOriginalFilename(),"");
                    }else {
                        String failReason = jsonObject.getString("failReason");
                        return R.fail(failReason);
                    }
                } catch (IOException e) {
                    throw new ServiceException(e.getMessage());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return R.fail();
    }
    public static R uploadNumberFileTest(String filePath,String accessToken) {
        MultipartFile file = UrlToMultipartFileUtil.urlTransferMultipartFile(filePath);
        MultipartFile[] files = new MultipartFile[]{file};
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        // 构建multipart/form-data请求体
        MultipartBody.Builder builder = new MultipartBody.Builder();
        builder.setType(MultipartBody.FORM);
        // 添加文件到请求体
        try {
            for (MultipartFile multipartFile : files) {
                if (multipartFile != null && !multipartFile.isEmpty()) {
                    RequestBody fileBody = RequestBody.create(
                        MediaType.parse(multipartFile.getContentType()),
                        multipartFile.getBytes()
                    );
                    builder.addFormDataPart("files", multipartFile.getOriginalFilename(), fileBody);
                }
            }
        } catch (IOException e) {
            System.err.println("Error reading multipart file: " + e.getMessage());
            e.printStackTrace();
            return R.fail("文件读取失败: " + e.getMessage());
        }
        RequestBody body = builder.build();
        Request request = new Request.Builder()
                .url("https://rtc.cn-north-1.myhuaweicloud.com/v1/privatenumber/a-number/files?agree_authorization_statement=true")
                .method("POST", body)
                .addHeader("X-Auth-Token", getTokenTest())
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            ResponseBody responseBody = response.body();
            if(responseBody != null) {
                try {
                    String string = responseBody.string();
                    System.err.println("body:::::::::::::::::"+string);
                    JSONObject jsonObject = JSONObject.parseObject(string);
                    String result = jsonObject.getString("result");
                    if("Upload Successful.".equals(result)){
                        return R.ok();
                    }else {
                        String failReason = jsonObject.getString("failReason");
                        return R.fail(failReason);
                    }
                } catch (IOException e) {
                    throw new ServiceException(e.getMessage());
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return R.fail();
    }
    /**
     * 新增租户级 A 号码报备接口
     * @param fileName
     */
    public R addANumber(String number, String realName, String idNumber, String fileName) {
        JSONObject requestBody = new JSONObject();
        JSONArray numbers = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("number", "+86"+number); // 手机号码
        jsonObject.put("numberType", 1); // 号码类型 1=手机号 2=固话
        jsonObject.put("name", realName); // 真实姓名
        jsonObject.put("identityCard",idNumber); // 身份证号
        jsonObject.put("liveFaceImage", fileName); // 号码所属人现场照片文件名
        numbers.add(jsonObject);
        requestBody.put("numbers", numbers);
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        // 构建json请求体
        MediaType mediaType = MediaType.parse("application/json");
        String requestBodyJson = requestBody.toJSONString();
        RequestBody body = RequestBody.create(mediaType, requestBodyJson);
        Request request = new Request.Builder()
                .url(ADD_A_NUMBER_URL)
                .method("POST", body)
                .addHeader("X-Auth-Token", getToken())
                .addHeader("Content-Type", "application/json")
                .addHeader("Accept", "application/json")
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            ResponseBody responseBody = response.body();
            if(responseBody != null) {
                String string = responseBody.string();
                log.info("新增租户级 A 号码报备接口:{}",string);
                // 解析响应JSON
                JSONObject responseJson = JSONObject.parseObject(string);
                String result = responseJson.getString("result");
                if("Add Successful.".equals(result)){
                    return R.ok();
                }else {
                    String errorMsg = responseJson.getString("error_msg");
                    return R.fail(errorMsg);
                }
            }
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        return R.fail();
    }
    public static R addANumberTest(String number, String realName, String idNumber, String fileName) {
        JSONObject requestBody = new JSONObject();
        JSONArray numbers = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("number", "+86"+number); // 手机号码
        jsonObject.put("numberType", 1); // 号码类型 1=手机号 2=固话
        jsonObject.put("name", realName); // 真实姓名
        jsonObject.put("identityCard",idNumber); // 身份证号
        jsonObject.put("liveFaceImage", fileName); // 号码所属人现场照片文件名
        numbers.add(jsonObject);
        requestBody.put("numbers", numbers);
        OkHttpClient client = new OkHttpClient().newBuilder()
                .build();
        // 构建json请求体
        MediaType mediaType = MediaType.parse("application/json");
        String requestBodyJson = requestBody.toJSONString();
        RequestBody body = RequestBody.create(mediaType, requestBodyJson);
        Request request = new Request.Builder()
                .url("https://rtc.cn-north-1.myhuaweicloud.com/v1.0/privatenumber/a-number?agree_authorization_statement=true")
                .method("POST", body)
                .addHeader("X-Auth-Token", getTokenTest())
                .addHeader("Content-Type", "application/json")
                .addHeader("Accept", "application/json")
                .build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            ResponseBody responseBody = response.body();
            if(responseBody != null) {
                String string = responseBody.string();
                log.info("新增租户级 A 号码报备接口:{}",string);
                System.err.println("body:::::::::::::::::+++++++++++"+string);
                // 解析响应JSON
                JSONObject responseJson = JSONObject.parseObject(string);
                String result = responseJson.getString("result");
                if("Add Successful.".equals(result)){
                    return R.ok();
                }else {
                    String errorMsg = responseJson.getString("error_msg");
                    return R.fail(errorMsg);
                }
            }
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        return R.fail();
    }
    public static void main(String[] args) {
//        String token = "MIINawYJKoZIhvcNAQcCoIINXDCCDVgCAQExDTALBglghkgBZQMEAgEwggt9BgkqhkiG9w0BBwGgggtuBIILansidG9rZW4iOnsiZXhwaXJlc19hdCI6IjIwMjUtMTAtMjFUMDI6Mzc6NTAuODE0MDAwWiIsIm1ldGhvZHMiOlsicGFzc3dvcmQiXSwiY2F0YWxvZyI6W10sInJvbGVzIjpbeyJuYW1lIjoib3BfZ2F0ZWRfY3Nic19yZXBfYWNjZWxlcmF0aW9uIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfZWNzX2Rpc2tBY2MiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9kc3NfbW9udGgiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9vYnNfZGVlcF9hcmNoaXZlIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYV9jbi1zb3V0aC00YyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2RlY19tb250aF91c2VyIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfY2JyX3NlbGxvdXQiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9lY3Nfb2xkX3Jlb3VyY2UiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9ldnNfUm95YWx0eSIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX3dlbGlua2JyaWRnZV9lbmRwb2ludF9idXkiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9jYnJfZmlsZSIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2Rtcy1yb2NrZXRtcTUtYmFzaWMiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9ldnNfRVNpbmdsZV9jb3B5U1NEIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfZG1zLWthZmthMyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX29ic19kZWNfbW9udGgiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9jc2JzX3Jlc3RvcmUiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9jYnJfdm13YXJlIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfaWRtZV9tYm1fZm91bmRhdGlvbiIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX211bHRpX2JpbmQiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9zbW5fY2FsbG5vdGlmeSIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2FfYXAtc291dGhlYXN0LTNkIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfY3Nic19wcm9ncmVzc2JhciIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2Nlc19yZXNvdXJjZWdyb3VwX3RhZyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2Vjc19vZmZsaW5lX2FjNyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2V2c19yZXR5cGUiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9rb29tYXAiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9ldnNfZXNzZDIiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9kbXMtYW1xcC1iYXNpYyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2V2c19wb29sX2NhIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYV9jbi1zb3V0aHdlc3QtMmIiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9od2NwaCIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2Vjc19vZmZsaW5lX2Rpc2tfNCIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX3Ntbl93ZWxpbmtyZWQiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9odl92ZW5kb3IiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9hX2NuLW5vcnRoLTRlIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYV9jbi1ub3J0aC00ZCIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2Vjc19oZWNzX3giLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9jYnJfZmlsZXNfYmFja3VwIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfZWNzX2FjNyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2VwcyIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2NzYnNfcmVzdG9yZV9hbGwiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9hX2NuLW5vcnRoLTRmIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfb3BfZ2F0ZWRfcm91bmR0YWJsZSIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2V2c19leHQiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9wZnNfZGVlcF9hcmNoaXZlIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYV9hcC1zb3V0aGVhc3QtMWUiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9hX3J1LW1vc2Nvdy0xYiIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2FfYXAtc291dGhlYXN0LTFkIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYXBwc3RhZ2UiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9hX2FwLXNvdXRoZWFzdC0xZiIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX3Ntbl9hcHBsaWNhdGlvbiIsImlkIjoiMCJ9LHsibmFtZSI6Im9wX2dhdGVkX2V2c19jb2xkIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfZWNzX2dwdV9nNXIiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9vcF9nYXRlZF9tZXNzYWdlb3ZlcjVnIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfZWNzX3JpIiwiaWQiOiIwIn0seyJuYW1lIjoib3BfZ2F0ZWRfYV9ydS1ub3J0aHdlc3QtMmMiLCJpZCI6IjAifSx7Im5hbWUiOiJvcF9nYXRlZF9pZWZfcGxhdGludW0iLCJpZCI6IjAifV0sInByb2plY3QiOnsiZG9tYWluIjp7Im5hbWUiOiJoc2cxMzQzNzE3MzQ0MCIsImlkIjoiNDY5ZWU1MzAzMjdlNGYzOTkxMTgyNjg0MmRiY2E3ZTcifSwibmFtZSI6ImNuLW5vcnRoLTEiLCJpZCI6IjY3MDc2YWVjOTAyODQ4Zjc4NGI3YWMxMzk0NzY1NmYzIn0sImlzc3VlZF9hdCI6IjIwMjUtMTAtMjBUMDI6Mzc6NTAuODE0MDAwWiIsInVzZXIiOnsiZG9tYWluIjp7Im5hbWUiOiJoc2cxMzQzNzE3MzQ0MCIsImlkIjoiNDY5ZWU1MzAzMjdlNGYzOTkxMTgyNjg0MmRiY2E3ZTcifSwibmFtZSI6ImRlbmdsdSIsInBhc3N3b3JkX2V4cGlyZXNfYXQiOiIiLCJpZCI6IjMyN2YwNjQ0N2Y1OTRkYzc5M2JjOTg4NTkwOGUwZWRiIn19fTGCAcEwggG9AgEBMIGXMIGJMQswCQYDVQQGEwJDTjESMBAGA1UECAwJR3VhbmdEb25nMREwDwYDVQQHDAhTaGVuWmhlbjEuMCwGA1UECgwlSHVhd2VpIFNvZnR3YXJlIFRlY2hub2xvZ2llcyBDby4sIEx0ZDEOMAwGA1UECwwFQ2xvdWQxEzARBgNVBAMMCmNhLmlhbS5wa2kCCQDcsytdEGFqEDALBglghkgBZQMEAgEwDQYJKoZIhvcNAQEBBQAEggEAbtVzZi6mqzlm1pyXLoQK2jNhfm5hqnjO8LrWK5FK1rtWpQU8ouEFJBfUDor6kcJpetMnf8fnpJF9Lmd+rT8N5-39Cpdr831ooUJufWIzBSt8Y+N+IoFUpQd-Ut5PRl0YpGWf90015EHcY-hhJk6rW-URTPXwC57f8CGkd3gOF7PPlv9TzIYtyjpCCjJah2kGjqfogYIQ9l1EBsZQplYd5OaYaH-dvxFBA8tlNteaEWGRAfhqsu6bbMVXlFG0VwlnQdUFvR6N7dUytmgKxaaHjyH2KUuGwgh6ELevfnYyqoYk-UAarnr3MbrnpmfcL7z3D-4aBOSTmXIOIHFkFRiUCQ==";
        R r = uploadNumberFileTest("https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/38878509-00b7-4fb2-9c3a-b53d8161d84a.jpg","");
//        System.err.println(r.getMsg());
//        String tokenTest = getTokenTest();
//        System.err.println(tokenTest);
//        R r = addANumberTest("17828262728", "刘杰", "511028200002150816", "avatar6278474701910805124b53d8161d84a.jpg");
//        System.err.println(r.getMsg());
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/utils/UrlToMultipartFileUtil.java
New file
@@ -0,0 +1,60 @@
package com.ruoyi.admin.utils;
import org.apache.http.entity.ContentType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.URL;
public class UrlToMultipartFileUtil {
    // url: 链接,可访问的图片,视频,或其他文件
    public static MultipartFile urlTransferMultipartFile(String url){
        //对本地文件命名,可以从链接截取,可以自己手写,看需求  https://huishou-1323682843.cos.ap-nanjing.myqcloud.com/images/98c0800f-3178-4db5-bf21-d53de2ed7849.jpg
        // 截取出文件名,不包含尾缀
        String fileName = url.substring(url.lastIndexOf("-") + 1);
        File file = null;
        URL urlfile;
        InputStream inStream = null;
        OutputStream os = null;
        MultipartFile multipartFile = null;
        try {
            file = File.createTempFile("avatar", fileName);
            //下载
            urlfile = new URL(url);
            inStream = urlfile.openStream();
            os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = inStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            // file转multipartFile,如果只需要转File就不用加下面这两行代码,直接返回File即可
            FileInputStream inputStream = new FileInputStream(file);
            multipartFile = new MockMultipartFile(file.getName(), file.getName(),
                    ContentType.APPLICATION_OCTET_STREAM.toString(), inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != os) {
                    os.close();
                }
                if (null != inStream) {
                    inStream.close();
                }
                // 用完删除
                if (null != file){
                    file.delete();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 按需返回file还是multipartFile
        return multipartFile;
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/service/IAXBInterfaceDemo.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.admin.voice.service;
/**
 * AXB模式接口
 */
public interface IAXBInterfaceDemo {
    /**
     * Set X number to be the privacy number between number a and number b |
     * 隐私号码AXB绑定
     *
     * @param relationNum 关系号码
     * @param callerNum   主叫号码
     * @param calleeNum   被叫号码
     */
    String axbBindNumber(String relationNum, String callerNum, String calleeNum);
    /**
     * Modify number a/b of the privacy relationship assigned by subscriptionId |
     * 隐私号码AXB绑定信息修改
     *
     * @param subscriptionId 绑定关系ID
     * @param callerNum      主叫号码
     * @param calleeNum      被叫号码
     */
    void axbModifyNumber(String subscriptionId, String callerNum, String calleeNum);
    /**
     * Unbind the privacy relationship between number a and number b | 隐私号码AXB解绑
     *
     * @param subscriptionId 绑定关系ID
     * @param relationNum    关系号码 都传时以subscriptionId优先
     */
    void axbUnbindNumber(String subscriptionId, String relationNum);
    /**
     * Query the privacy binding relationship on the X number | 查询AXB绑定信息
     *
     * @param subscriptionId 绑定关系ID
     * @param relationNum    关系号码 都传时以subscriptionId优先
     */
    void axbQueryBindRelation(String subscriptionId, String relationNum);
    /**
     * Get download link of the record file created in call | 获取录音文件下载地址
     *
     * @param recordDomain 录音文件存储的服务器域名
     * @param fileName     录音文件名
     */
    String axbGetRecordDownloadLink(String recordDomain, String fileName);
    /**
     * Stop the call on the X number assigned by sessionid | 终止呼叫
     *
     * @param sessionid 呼叫会话ID 通过"呼叫事件通知接口"获取
     */
    void axbStopCall(String sessionid);
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/service/impl/AXBInterfaceDemoImpl.java
New file
@@ -0,0 +1,208 @@
package com.ruoyi.admin.voice.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.admin.voice.service.IAXBInterfaceDemo;
import com.ruoyi.admin.voice.util.HttpUtilClient;
import com.ruoyi.common.core.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * AXB模式接口测试
 */
@Slf4j
public class AXBInterfaceDemoImpl implements IAXBInterfaceDemo {
    private String appKey; // APP_Key
    private String appSecret; // APP_Secret
    private String ompDomainName; // APP接入地址
    public AXBInterfaceDemoImpl(String appKey, String appSecret, String ompDomainName) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.ompDomainName = ompDomainName;
    }
    /**
     * Build the real url of https request | 构建隐私保护通话平台请求路径
     *
     * @param path 接口访问URI
     * @return
     */
    private String buildOmpUrl(String path) {
        return ompDomainName + path;
    }
    @Override
    public String axbBindNumber(String relationNum, String callerNum, String calleeNum) {
        if (StringUtils.isBlank(relationNum) || StringUtils.isBlank(callerNum) || StringUtils.isBlank(calleeNum)) {
            log.info("axbBindNumber set params error");
            throw new ServiceException("axbBindNumber set params error");
        }
        // 必填,AXB模式绑定接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("relationNum", relationNum); // X号码(关系号码)
        json.put("callerNum", callerNum); // A方真实号码(手机或固话)
        json.put("calleeNum", calleeNum); // B方真实号码(手机或固话)
        /**
         * 选填,各参数要求请参考"AXB模式绑定接口"
         */
//         json.put("areaCode", "0755"); //城市码
//         json.put("areaMatchMode", "1"); //号码筛选方式
//         json.put("callDirection", 0); //允许呼叫的方向
//         json.put("duration", 86400); //绑定关系保持时间
//         json.put("recordFlag", false); //是否通话录音
//         json.put("recordHintTone", "recordHintTone.wav"); //录音提示音
//         json.put("maxDuration", 60); //单次通话最长时间
//         json.put("lastMinVoice", "lastMinVoice.wav"); //通话最后一分钟提示音
//         json.put("privateSms", true); //是否支持短信功能
//         JSONObject preVoice = new JSONObject();
//         preVoice.put("callerHintTone", "callerHintTone.wav"); //设置A拨打X号码时的通话前等待音
//         preVoice.put("calleeHintTone", "calleeHintTone.wav"); //设置B拨打X号码时的通话前等待音
//         json.put("preVoice", preVoice); //个性化通话前等待音
        String result = HttpUtilClient.sendPost(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
        return result;
    }
    @Override
    public void axbModifyNumber(String subscriptionId, String callerNum, String calleeNum) {
        if (StringUtils.isBlank(subscriptionId)) {
            log.info("axbModifyNumber set params error");
            return;
        }
        // 必填,AXB模式绑定信息修改接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("subscriptionId", subscriptionId); // 绑定关系ID
        if (StringUtils.isNotBlank(callerNum)) {
            json.put("callerNum", callerNum); // 将A方修改为新的号码(手机或固话)
        }
        if (StringUtils.isNotBlank(calleeNum)) {
            json.put("calleeNum", calleeNum); // 将B方修改为新的号码(手机或固话)
        }
        /**
         * 选填,各参数要求请参考"AXB模式绑定信息修改接口"
         */
//         json.put("callDirection", 0); //允许呼叫的方向
//         json.put("duration", 86400); //绑定关系保持时间
//         json.put("maxDuration", 90); //单次通话最长时间
//         json.put("lastMinVoice", "lastMinVoice.wav"); //通话最后一分钟提示音
//         json.put("privateSms", true); //是否支持短信功能
//         json.put("recordFlag", false); //是否通话录音
//         JSONObject preVoice = new JSONObject();
//         preVoice.put("callerHintTone", "callerHintTone.wav"); //设置A拨打X号码时的通话前等待音
//         preVoice.put("calleeHintTone", "calleeHintTone.wav"); //设置B拨打X号码时的通话前等待音
//         json.put("preVoice", preVoice); //个性化通话前等待音
        String result = HttpUtilClient.sendPut(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
    }
    @Override
    public void axbUnbindNumber(String subscriptionId, String relationNum) {
        if (StringUtils.isBlank(subscriptionId) && StringUtils.isBlank(relationNum)) {
            log.info("axbUnbindNumber set params error");
            return;
        }
        // 必填,AXB模式解绑接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(subscriptionId)) {
            map.put("subscriptionId", subscriptionId); // 绑定关系ID
        } else {
            map.put("relationNum", relationNum); // X号码(关系号码)
        }
        String result = HttpUtilClient.sendDelete(appKey, appSecret, realUrl, HttpUtilClient.map2UrlEncodeString(map));
        log.info("Response is :" + result);
    }
    @Override
    public void axbQueryBindRelation(String subscriptionId, String relationNum) {
        if (StringUtils.isBlank(subscriptionId) && StringUtils.isBlank(relationNum)) {
            log.info("axbQueryBindRelation set params error");
            return;
        }
        // 必填,AXB模式绑定信息查询接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(subscriptionId)) {
            map.put("subscriptionId", subscriptionId); // 绑定关系ID
        } else {
            map.put("relationNum", relationNum); // X号码(关系号码)
            /**
             * 选填,各参数要求请参考"AXB模式绑定信息查询接口"
             */
//            map.put("pageIndex", 1); //查询的分页索引,从1开始编号
//            map.put("pageSize", 20); //查询的分页大小,即每次查询返回多少条数据
        }
        String result = HttpUtilClient.sendGet(appKey, appSecret, realUrl, HttpUtilClient.map2UrlEncodeString(map));
        log.info("Response is :" + result);
    }
    @Override
    public String axbGetRecordDownloadLink(String recordDomain, String fileName) {
        if (StringUtils.isBlank(recordDomain) || StringUtils.isBlank(fileName)) {
            log.info("axbGetRecordDownloadLink set params error");
            throw new ServiceException("axbGetRecordDownloadLink set params error");
        }
        // 必填,AXB模式获取录音文件下载地址接口访问URI
        String url = "/rest/provision/voice/record/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("recordDomain", recordDomain); // 录音文件存储的服务器域名
        map.put("fileName", fileName); // 录音文件名
        String result = HttpUtilClient.sendGet(appKey, appSecret, realUrl, HttpUtilClient.map2UrlEncodeString(map));
        log.info("Response is :" + result);
        return result;
    }
    @Override
    public void axbStopCall(String sessionid) {
        if (StringUtils.isBlank(sessionid)) {
            log.info("axbStopCall set params error");
            return;
        }
        // 必填,AXB模式终止呼叫接口访问URI
        String url = "/rest/httpsessions/callStop/v2.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("sessionid", sessionid); // 呼叫会话ID
        json.put("signal", "call_stop"); // 取值固定为"call_stop"
        String result = HttpUtilClient.sendPost(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/AXBUtil.java
New file
@@ -0,0 +1,60 @@
package com.ruoyi.admin.voice.util;
import com.ruoyi.admin.voice.service.IAXBInterfaceDemo;
import com.ruoyi.admin.voice.service.impl.AXBInterfaceDemoImpl;
public class AXBUtil {
    /**
     * 必填,请登录管理控制台,从"应用管理"页获取
     */
    private final static String OMPDOMAINNAME = "https://rtcpns.cn-north-1.myhuaweicloud.com"; // APP接入地址
    /**
     * AXB模式  绑定号码
     * @param relationNum X 隐私号码 13154610294
     * @param callerNum A
     * @param calleeNum B
     */
    public static String axbBindNumber(String appKey,String appSecret,String relationNum, String callerNum, String calleeNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第一步: 号码绑定,即调用AXB模式绑定接口 93f4474b-3e0b-490d-bd1d-b6fd31b63c0b
        return axb.axbBindNumber("+86"+relationNum, "+86"+callerNum, "+86"+calleeNum);
    }
    /**
     * Get download link of the record file created in call | 获取录音文件下载地址
     * @param recordDomain 录音文件存储的服务器域名
     * @param fileName     录音文件名
     */
    public static String axbGetRecordDownloadLink(String appKey,String appSecret,String recordDomain, String fileName) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第三步: 用户通话结束,若设置录音,则商户可以获取录音文件下载地址,即调用获取录音文件下载地址接口
        return axb.axbGetRecordDownloadLink(recordDomain, fileName);
    }
    /**
     * Modify number a/b of the privacy relationship assigned by subscriptionId |
     * 隐私号码AXB绑定信息修改
     * @param subscriptionId 绑定关系ID
     * @param callerNum      主叫号码
     * @param calleeNum      被叫号码
     */
    public static void axbModifyNumber(String appKey,String appSecret,String subscriptionId, String callerNum, String calleeNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第四步: 根据业务需求,可更改绑定关系,即调用AXB模式绑定信息修改接口
        axb.axbModifyNumber(subscriptionId, callerNum, calleeNum);
    }
    /**
     * AXB模式  解绑号码
     * @param subscriptionId 绑定号码后的唯一标识
     * @param relationNum X 隐私号码 13154610294
     */
    public static void axbUnbindNumber(String appKey,String appSecret,String subscriptionId, String relationNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第五步: 隐私号码循环使用,商户可将绑定关系解绑,即调用AXB模式解绑接口
        axb.axbUnbindNumber(subscriptionId, "+86"+relationNum);
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/HttpUtilClient.java
New file
@@ -0,0 +1,360 @@
package com.ruoyi.admin.voice.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
/**
 * HTTP协议传输工具类
 */
@Slf4j
public class HttpUtilClient {
    private static final int HTTP_STATUS_OK = 200;
    /**
     * 向指定 URL发送POST方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param jsonBody
     * @return
     */
    public static String sendPost(String appKey, String appSecret, String url, String jsonBody) {
        DataOutputStream out = null;
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            URL realUrl = new URL(url);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + jsonBody);
            connection.connect();
            out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(jsonBody);
            out.flush();
            out.close();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line = "";
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send Post request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送PUT方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param jsonBody
     * @return
     */
    public static String sendPut(String appKey, String appSecret, String url, String jsonBody) {
        DataOutputStream out = null;
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            URL realUrl = new URL(url);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("PUT");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + jsonBody);
            connection.connect();
            out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(jsonBody);
            out.flush();
            out.close();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send Put request catch exception: " + e.toString());
            e.printStackTrace();
        }
        finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送DELETE方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param params
     * @return
     */
    public static String sendDelete(String appKey, String appSecret, String url, String params) {
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            String realPath = url + (StringUtils.isEmpty(params) ? "" : "?" + params);
            URL realUrl = new URL(realPath);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoInput(true);
            connection.setRequestMethod("DELETE");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + params);
            connection.connect();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send DELETE request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送GET方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param params
     * @return
     */
    public static String sendGet(String appKey, String appSecret, String url, String params) {
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            String realPath = url + (StringUtils.isEmpty(params) ? "" : "?" + params);
            URL realUrl = new URL(realPath);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoInput(true);
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                     "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            connection.setInstanceFollowRedirects(false); //设置本次连接不自动处理重定向
            log.info("RequestBody is : " + params);
            connection.connect();
            int status = connection.getResponseCode();
            if (301 == status) { //获取录音文件下载地址
                return connection.getHeaderField("Location");
            }else if (HTTP_STATUS_OK == status) { //查询绑定信息
                is = connection.getInputStream();
            } else { //获取错误码
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send GET request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 键值对转查询url
     *
     * @param map
     * @return
     */
    public static String map2UrlEncodeString(Map<String, Object> map) {
        if(null == map || map.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        String temp = "";
        for (String s : map.keySet()) {
            try {
                temp = URLEncoder.encode(String.valueOf(map.get(s)), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            sb.append(s).append("=").append(temp).append("&");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }
    /**
     * 忽略SSL证书校验
     *
     * @throws Exception
     */
    static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return;
            }
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return;
            }
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        } };
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}
ruoyi-service/ruoyi-admin/src/main/java/com/ruoyi/admin/voice/util/StringUtil.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright Notice:
 *      Copyright  1998-2008, Huawei Technologies Co., Ltd.  ALL Rights Reserved.
 *
 *      Warning: This computer software sourcecode is protected by copyright law
 *      and international treaties. Unauthorized reproduction or distribution
 *      of this sourcecode, or any portion of it, may result in severe civil and
 *      criminal penalties, and will be prosecuted to the maximum extent
 *      possible under the law.
 */
package com.ruoyi.admin.voice.util;
//import java.nio.charset.Charset;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
public class StringUtil {
    public static final String AKSK_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
    public static boolean strIsNullOrEmpty(String s) {
        return (null == s || s.trim().length() < 1);
    }
    public static String buildAKSKHeader(String appKey, String appSecret) throws Exception {
        if (StringUtil.strIsNullOrEmpty(appKey) || StringUtil.strIsNullOrEmpty(appSecret)) {
            return null;
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));
        Calendar calendar = Calendar.getInstance();
        String time = format.format(calendar.getTime());
        String stNonce = UUID.randomUUID().toString().replace("-", "").toUpperCase(Locale.ROOT);
        String str = stNonce + time;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] authBytes = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
        String passwordDigestBase64Str = encodeBase64(authBytes);
        return String.format(AKSK_HEADER_FORMAT, appKey, passwordDigestBase64Str, stNonce, time);
    }
    private static String encodeBase64(byte[] bytes) {
        if (bytes.length == 0) {
            return null;
        } else {
            return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
        }
    }
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/OrderController.java
@@ -132,6 +132,35 @@
                .eq(Order::getId, orderId).set(Order::getState, state).update());
    }
    /**
     * 更改订单虚拟童话绑定id
     */
    @ApiOperation(value = "订单列表-设置虚拟号码绑定id", tags = {"后台-订单管理"})
    @GetMapping(value = "/updateSubscriptionId")
    public R<Boolean> updateSubscriptionId(@RequestParam("orderId") String orderId,
                                           @RequestParam("subscriptionId")String subscriptionId,
                                           @RequestParam("virtualNumber")String virtualNumber) {
        // 修改订单设置绑定id
        orderService.lambdaUpdate().eq(Order::getId, orderId)
                .set(Order::getSubscriptionId, subscriptionId)
                .set(Order::getVirtualNumber, virtualNumber)
                .update();
        return R.ok();
    }
    /**
     * 更改订单虚拟童话录音
     */
    @ApiOperation(value = "订单列表-设置虚拟号码绑定id", tags = {"后台-订单管理"})
    @GetMapping(value = "/updatePhoneRecording")
    public R<Boolean> updatePhoneRecording(@RequestParam("orderId") String orderId,
                                           @RequestParam("audioUrl")String audioUrl) {
        // 修改订单设置绑定id
        orderService.lambdaUpdate().eq(Order::getId, orderId)
                .set(Order::getPhoneRecording, audioUrl)
                .update();
        return R.ok();
    }
    @ApiOperation(value = "订单列表-增加打印次数", tags = {"后台-订单管理"})
    @PostMapping(value = "/count")
@@ -744,7 +773,7 @@
    @ApiOperation(value = "订单完工-提交订单", tags = {"师傅端-订单列表"})
    @PostMapping(value = "/orderSubmit")
    @Transactional(rollbackFor = Exception.class)
    public R<Boolean> orderSubmit(@RequestBody OrderSubmitRequest orderSubmitRequest) {
    public R<String> orderSubmit(@RequestBody OrderSubmitRequest orderSubmitRequest) {
        // 订单信息
        Order order = orderService.lambdaQuery().eq(Order::getId, orderSubmitRequest.getOrderId())
                .eq(Order::getIsDelete, 0).one();
@@ -765,7 +794,10 @@
        serveRecord.setCardPic(orderSubmitRequest.getCardPic());
        serveRecord.setMachinePic(orderSubmitRequest.getMachinePic());
        boolean save = serveRecordService.save(serveRecord);
        return R.ok(update && save);
        if(update && save){
            return R.ok(order.getSubscriptionId(), "订单提交成功!");
        }
        return R.ok();
    }
    /**
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/entity/Order.java
@@ -216,4 +216,19 @@
    @TableField("msg_count")
    private Integer msgCount;
    @ApiModelProperty("绑定关系id")
    @TableField("subscription_id")
    private String subscriptionId;
    @ApiModelProperty("虚拟号码")
    @TableField("virtual_number")
    private String virtualNumber;
    @ApiModelProperty("电话录音")
    @TableField("phone_recording")
    private String phoneRecording;
    @ApiModelProperty("录音文件存储的服务器域名")
    @TableField("record_domain")
    private String recordDomain;
    @ApiModelProperty("录音文件名")
    @TableField("record_object_name")
    private String recordObjectName;
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/controller/OrderController.java
@@ -4,6 +4,7 @@
import cn.afterturn.easypoi.cache.manager.IFileLoader;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.admin.api.feignClient.AdminClient;
import com.ruoyi.common.core.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.Result;
@@ -19,11 +20,14 @@
import com.ruoyi.order.api.feignClient.ExchangeDispatchClient;
import com.ruoyi.order.api.feignClient.OrderClient;
import com.ruoyi.system.api.model.LoginUserInfo;
import com.ruoyi.worker.entity.IamConfig;
import com.ruoyi.worker.entity.MasterWorker;
import com.ruoyi.worker.entity.RecoveryServe;
import com.ruoyi.worker.service.IamConfigService;
import com.ruoyi.worker.service.MasterWorkerService;
import com.ruoyi.worker.service.RecoveryServeService;
import com.ruoyi.worker.vo.ServeCoordinate;
import com.ruoyi.worker.voice.util.AXBUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
@@ -36,18 +40,14 @@
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.Synchronized;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -81,6 +81,8 @@
    @Resource
    private RedisService redisService;
    @Resource
    private IamConfigService iamConfigService;
    /**
     * linux服务器保存订单轨迹文件夹
@@ -288,7 +290,29 @@
        if (null == loginWorker) {
            return R.loginExpire("登录失效!");
        }
        return orderClient.orderSubmit(orderSubmitRequest);
        R<String> result = orderClient.orderSubmit(orderSubmitRequest);
        String subscriptionId = result.getData();
        boolean flag = false;
        if (StringUtils.hasLength(subscriptionId)) {
            IamConfig iamConfig = iamConfigService.getById(1);
            // 查询音频链接
            OrderDetailVO data = orderClient.orderDetail(orderSubmitRequest.getOrderId()).getData();
            if(Objects.nonNull(data)){
                Order orderInfo = data.getOrderInfo();
                if (Objects.nonNull(orderInfo)){
                    String recordDomain = orderInfo.getRecordDomain();
                    String recordObjectName = orderInfo.getRecordObjectName();
                    String audioUrl = AXBUtil.axbGetRecordDownloadLink(iamConfig.getAppKey(), iamConfig.getAppSecret(),recordDomain, recordObjectName);
                    orderInfo.setPhoneRecording(audioUrl);
                    // 设置音频
                    orderClient.updatePhoneRecording(orderInfo.getId(), audioUrl);
                }
            }
            // 虚拟号码解绑
            AXBUtil.axbUnbindNumber(iamConfig.getAppKey(), iamConfig.getAppSecret(),subscriptionId, iamConfig.getVirtualNumber());
            flag = true;
        }
        return R.ok(flag);
    }
    private static final double EARTH_RADIUS_METERS = 6371000.0;
    /**
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/entity/IamConfig.java
New file
@@ -0,0 +1,48 @@
package com.ruoyi.worker.entity;
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.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
 * <p>
 * 虚拟号码配置
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Getter
@Setter
@TableName("t_iam_config")
@ApiModel(value = "t_iam_config对象", description = "虚拟号码配置")
public class IamConfig implements Serializable {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("用户")
    @TableField("userName")
    private String userName;
    @ApiModelProperty("子账号")
    @TableField("account")
    private String account;
    @ApiModelProperty("子账号密码")
    @TableField("password")
    private String password;
    @ApiModelProperty("虚拟号码APP_Key")
    @TableField("appKey")
    private String appKey;
    @ApiModelProperty("虚拟号码APP_Secret")
    @TableField("appSecret")
    private String appSecret;
    @ApiModelProperty("虚拟号码")
    @TableField("virtualNumber")
    private String virtualNumber;
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/mapper/IamConfigMapper.java
New file
@@ -0,0 +1,18 @@
package com.ruoyi.worker.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.worker.entity.IamConfig;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * 虚拟号码管理 Mapper 接口
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Mapper
public interface IamConfigMapper extends BaseMapper<IamConfig> {
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/service/IamConfigService.java
New file
@@ -0,0 +1,16 @@
package com.ruoyi.worker.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.worker.entity.IamConfig;
/**
 * <p>
 * 改派管理 服务类
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
public interface IamConfigService extends IService<IamConfig> {
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/service/impl/IamConfigServiceImpl.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.worker.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.worker.entity.IamConfig;
import com.ruoyi.worker.mapper.IamConfigMapper;
import com.ruoyi.worker.service.IamConfigService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * 改派管理 服务实现类
 * </p>
 *
 * @author hjl
 * @since 2024-07-08
 */
@Service
public class IamConfigServiceImpl extends ServiceImpl<IamConfigMapper, IamConfig> implements IamConfigService {
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/service/IAXBInterfaceDemo.java
New file
@@ -0,0 +1,57 @@
package com.ruoyi.worker.voice.service;
/**
 * AXB模式接口
 */
public interface IAXBInterfaceDemo {
    /**
     * Set X number to be the privacy number between number a and number b |
     * 隐私号码AXB绑定
     *
     * @param relationNum 关系号码
     * @param callerNum   主叫号码
     * @param calleeNum   被叫号码
     */
    String axbBindNumber(String relationNum, String callerNum, String calleeNum);
    /**
     * Modify number a/b of the privacy relationship assigned by subscriptionId |
     * 隐私号码AXB绑定信息修改
     *
     * @param subscriptionId 绑定关系ID
     * @param callerNum      主叫号码
     * @param calleeNum      被叫号码
     */
    void axbModifyNumber(String subscriptionId, String callerNum, String calleeNum);
    /**
     * Unbind the privacy relationship between number a and number b | 隐私号码AXB解绑
     *
     * @param subscriptionId 绑定关系ID
     * @param relationNum    关系号码 都传时以subscriptionId优先
     */
    void axbUnbindNumber(String subscriptionId, String relationNum);
    /**
     * Query the privacy binding relationship on the X number | 查询AXB绑定信息
     *
     * @param subscriptionId 绑定关系ID
     * @param relationNum    关系号码 都传时以subscriptionId优先
     */
    void axbQueryBindRelation(String subscriptionId, String relationNum);
    /**
     * Get download link of the record file created in call | 获取录音文件下载地址
     *
     * @param recordDomain 录音文件存储的服务器域名
     * @param fileName     录音文件名
     */
    String axbGetRecordDownloadLink(String recordDomain, String fileName);
    /**
     * Stop the call on the X number assigned by sessionid | 终止呼叫
     *
     * @param sessionid 呼叫会话ID 通过"呼叫事件通知接口"获取
     */
    void axbStopCall(String sessionid);
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/service/impl/AXBInterfaceDemoImpl.java
New file
@@ -0,0 +1,208 @@
package com.ruoyi.worker.voice.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.worker.voice.service.IAXBInterfaceDemo;
import com.ruoyi.worker.voice.util.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * AXB模式接口测试
 */
@Slf4j
public class AXBInterfaceDemoImpl implements IAXBInterfaceDemo {
    private String appKey; // APP_Key
    private String appSecret; // APP_Secret
    private String ompDomainName; // APP接入地址
    public AXBInterfaceDemoImpl(String appKey, String appSecret, String ompDomainName) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.ompDomainName = ompDomainName;
    }
    /**
     * Build the real url of https request | 构建隐私保护通话平台请求路径
     *
     * @param path 接口访问URI
     * @return
     */
    private String buildOmpUrl(String path) {
        return ompDomainName + path;
    }
    @Override
    public String axbBindNumber(String relationNum, String callerNum, String calleeNum) {
        if (StringUtils.isBlank(relationNum) || StringUtils.isBlank(callerNum) || StringUtils.isBlank(calleeNum)) {
            log.info("axbBindNumber set params error");
            throw new ServiceException("axbBindNumber set params error");
        }
        // 必填,AXB模式绑定接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("relationNum", relationNum); // X号码(关系号码)
        json.put("callerNum", callerNum); // A方真实号码(手机或固话)
        json.put("calleeNum", calleeNum); // B方真实号码(手机或固话)
        /**
         * 选填,各参数要求请参考"AXB模式绑定接口"
         */
//         json.put("areaCode", "0755"); //城市码
//         json.put("areaMatchMode", "1"); //号码筛选方式
//         json.put("callDirection", 0); //允许呼叫的方向
//         json.put("duration", 86400); //绑定关系保持时间
//         json.put("recordFlag", false); //是否通话录音
//         json.put("recordHintTone", "recordHintTone.wav"); //录音提示音
//         json.put("maxDuration", 60); //单次通话最长时间
//         json.put("lastMinVoice", "lastMinVoice.wav"); //通话最后一分钟提示音
//         json.put("privateSms", true); //是否支持短信功能
//         JSONObject preVoice = new JSONObject();
//         preVoice.put("callerHintTone", "callerHintTone.wav"); //设置A拨打X号码时的通话前等待音
//         preVoice.put("calleeHintTone", "calleeHintTone.wav"); //设置B拨打X号码时的通话前等待音
//         json.put("preVoice", preVoice); //个性化通话前等待音
        String result = HttpUtil.sendPost(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
        return result;
    }
    @Override
    public void axbModifyNumber(String subscriptionId, String callerNum, String calleeNum) {
        if (StringUtils.isBlank(subscriptionId)) {
            log.info("axbModifyNumber set params error");
            return;
        }
        // 必填,AXB模式绑定信息修改接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("subscriptionId", subscriptionId); // 绑定关系ID
        if (StringUtils.isNotBlank(callerNum)) {
            json.put("callerNum", callerNum); // 将A方修改为新的号码(手机或固话)
        }
        if (StringUtils.isNotBlank(calleeNum)) {
            json.put("calleeNum", calleeNum); // 将B方修改为新的号码(手机或固话)
        }
        /**
         * 选填,各参数要求请参考"AXB模式绑定信息修改接口"
         */
//         json.put("callDirection", 0); //允许呼叫的方向
//         json.put("duration", 86400); //绑定关系保持时间
//         json.put("maxDuration", 90); //单次通话最长时间
//         json.put("lastMinVoice", "lastMinVoice.wav"); //通话最后一分钟提示音
//         json.put("privateSms", true); //是否支持短信功能
//         json.put("recordFlag", false); //是否通话录音
//         JSONObject preVoice = new JSONObject();
//         preVoice.put("callerHintTone", "callerHintTone.wav"); //设置A拨打X号码时的通话前等待音
//         preVoice.put("calleeHintTone", "calleeHintTone.wav"); //设置B拨打X号码时的通话前等待音
//         json.put("preVoice", preVoice); //个性化通话前等待音
        String result = HttpUtil.sendPut(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
    }
    @Override
    public void axbUnbindNumber(String subscriptionId, String relationNum) {
        if (StringUtils.isBlank(subscriptionId) && StringUtils.isBlank(relationNum)) {
            log.info("axbUnbindNumber set params error");
            return;
        }
        // 必填,AXB模式解绑接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(subscriptionId)) {
            map.put("subscriptionId", subscriptionId); // 绑定关系ID
        } else {
            map.put("relationNum", relationNum); // X号码(关系号码)
        }
        String result = HttpUtil.sendDelete(appKey, appSecret, realUrl, HttpUtil.map2UrlEncodeString(map));
        log.info("Response is :" + result);
    }
    @Override
    public void axbQueryBindRelation(String subscriptionId, String relationNum) {
        if (StringUtils.isBlank(subscriptionId) && StringUtils.isBlank(relationNum)) {
            log.info("axbQueryBindRelation set params error");
            return;
        }
        // 必填,AXB模式绑定信息查询接口访问URI
        String url = "/rest/caas/relationnumber/partners/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        if (StringUtils.isNotBlank(subscriptionId)) {
            map.put("subscriptionId", subscriptionId); // 绑定关系ID
        } else {
            map.put("relationNum", relationNum); // X号码(关系号码)
            /**
             * 选填,各参数要求请参考"AXB模式绑定信息查询接口"
             */
//            map.put("pageIndex", 1); //查询的分页索引,从1开始编号
//            map.put("pageSize", 20); //查询的分页大小,即每次查询返回多少条数据
        }
        String result = HttpUtil.sendGet(appKey, appSecret, realUrl, HttpUtil.map2UrlEncodeString(map));
        log.info("Response is :" + result);
    }
    @Override
    public String axbGetRecordDownloadLink(String recordDomain, String fileName) {
        if (StringUtils.isBlank(recordDomain) || StringUtils.isBlank(fileName)) {
            log.info("axbGetRecordDownloadLink set params error");
            throw new ServiceException("axbGetRecordDownloadLink set params error");
        }
        // 必填,AXB模式获取录音文件下载地址接口访问URI
        String url = "/rest/provision/voice/record/v1.0";
        String realUrl = buildOmpUrl(url);
        // 申明对象
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("recordDomain", recordDomain); // 录音文件存储的服务器域名
        map.put("fileName", fileName); // 录音文件名
        String result = HttpUtil.sendGet(appKey, appSecret, realUrl, HttpUtil.map2UrlEncodeString(map));
        log.info("Response is :" + result);
        return result;
    }
    @Override
    public void axbStopCall(String sessionid) {
        if (StringUtils.isBlank(sessionid)) {
            log.info("axbStopCall set params error");
            return;
        }
        // 必填,AXB模式终止呼叫接口访问URI
        String url = "/rest/httpsessions/callStop/v2.0";
        String realUrl = buildOmpUrl(url);
        // 封装JOSN请求
        JSONObject json = new JSONObject();
        json.put("sessionid", sessionid); // 呼叫会话ID
        json.put("signal", "call_stop"); // 取值固定为"call_stop"
        String result = HttpUtil.sendPost(appKey, appSecret, realUrl, json.toString());
        log.info("Response is :" + result);
    }
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/AXBUtil.java
New file
@@ -0,0 +1,61 @@
package com.ruoyi.worker.voice.util;
import com.ruoyi.worker.voice.service.IAXBInterfaceDemo;
import com.ruoyi.worker.voice.service.impl.AXBInterfaceDemoImpl;
public class AXBUtil {
    /**
     * 必填,请登录管理控制台,从"应用管理"页获取
     */
    private final static String OMPDOMAINNAME = "https://rtcpns.cn-north-1.myhuaweicloud.com"; // APP接入地址
    /**
     * AXB模式  绑定号码
     * @param relationNum X 隐私号码 13154610294
     * @param callerNum A
     * @param calleeNum B
     */
    public static String axbBindNumber(String appKey,String appSecret,String relationNum, String callerNum, String calleeNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第一步: 号码绑定,即调用AXB模式绑定接口 93f4474b-3e0b-490d-bd1d-b6fd31b63c0b
        return axb.axbBindNumber("+86"+relationNum, "+86"+callerNum, "+86"+calleeNum);
    }
    /**
     * Get download link of the record file created in call | 获取录音文件下载地址
     * @param recordDomain 录音文件存储的服务器域名
     * @param fileName     录音文件名
     */
    public static String axbGetRecordDownloadLink(String appKey,String appSecret,String recordDomain, String fileName) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第三步: 用户通话结束,若设置录音,则商户可以获取录音文件下载地址,即调用获取录音文件下载地址接口
        return axb.axbGetRecordDownloadLink(recordDomain, fileName);
    }
    /**
     * Modify number a/b of the privacy relationship assigned by subscriptionId |
     * 隐私号码AXB绑定信息修改
     * @param subscriptionId 绑定关系ID
     * @param callerNum      主叫号码
     * @param calleeNum      被叫号码
     */
    public static void axbModifyNumber(String appKey,String appSecret,String subscriptionId, String callerNum, String calleeNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第四步: 根据业务需求,可更改绑定关系,即调用AXB模式绑定信息修改接口
        axb.axbModifyNumber(subscriptionId, callerNum, calleeNum);
    }
    /**
     * AXB模式  解绑号码
     * @param subscriptionId 绑定号码后的唯一标识
     * @param relationNum X 隐私号码 13154610294
     */
    public static void axbUnbindNumber(String appKey,String appSecret,String subscriptionId, String relationNum) {
        IAXBInterfaceDemo axb = new AXBInterfaceDemoImpl(appKey, appSecret, OMPDOMAINNAME);
        // 第五步: 隐私号码循环使用,商户可将绑定关系解绑,即调用AXB模式解绑接口
        axb.axbUnbindNumber(subscriptionId, "+86"+relationNum);
    }
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/HttpUtil.java
New file
@@ -0,0 +1,360 @@
package com.ruoyi.worker.voice.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
/**
 * HTTP协议传输工具类
 */
@Slf4j
public class HttpUtil {
    private static final int HTTP_STATUS_OK = 200;
    /**
     * 向指定 URL发送POST方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param jsonBody
     * @return
     */
    public static String sendPost(String appKey, String appSecret, String url, String jsonBody) {
        DataOutputStream out = null;
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            URL realUrl = new URL(url);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + jsonBody);
            connection.connect();
            out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(jsonBody);
            out.flush();
            out.close();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            String line = "";
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send Post request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送PUT方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param jsonBody
     * @return
     */
    public static String sendPut(String appKey, String appSecret, String url, String jsonBody) {
        DataOutputStream out = null;
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            URL realUrl = new URL(url);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setRequestMethod("PUT");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + jsonBody);
            connection.connect();
            out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(jsonBody);
            out.flush();
            out.close();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send Put request catch exception: " + e.toString());
            e.printStackTrace();
        }
        finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送DELETE方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param params
     * @return
     */
    public static String sendDelete(String appKey, String appSecret, String url, String params) {
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            String realPath = url + (StringUtils.isEmpty(params) ? "" : "?" + params);
            URL realUrl = new URL(realPath);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoInput(true);
            connection.setRequestMethod("DELETE");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                    "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            log.info("RequestBody is : " + params);
            connection.connect();
            int status = connection.getResponseCode();
            if (HTTP_STATUS_OK == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send DELETE request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL发送GET方法的请求
     *
     * @param appKey
     * @param appSecret
     * @param url
     * @param params
     * @return
     */
    public static String sendGet(String appKey, String appSecret, String url, String params) {
        BufferedReader in = null;
        StringBuffer result = new StringBuffer();
        HttpsURLConnection connection = null;
        InputStream is = null;
        HostnameVerifier hv = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
        try {
            trustAllHttpsCertificates();
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        try {
            String realPath = url + (StringUtils.isEmpty(params) ? "" : "?" + params);
            URL realUrl = new URL(realPath);
            connection = (HttpsURLConnection) realUrl.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoInput(true);
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            connection.setRequestProperty("Authorization",
                     "AKSK realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"");
            connection.setRequestProperty("X-AKSK", StringUtil.buildAKSKHeader(appKey, appSecret));
            connection.setInstanceFollowRedirects(false); //设置本次连接不自动处理重定向
            log.info("RequestBody is : " + params);
            connection.connect();
            int status = connection.getResponseCode();
            if (301 == status) { //获取录音文件下载地址
                return connection.getHeaderField("Location");
            }else if (HTTP_STATUS_OK == status) { //查询绑定信息
                is = connection.getInputStream();
            } else { //获取错误码
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            log.info("Send GET request catch exception: " + e.toString());
        }
        finally {
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(in);
            if (null != connection) {
                IOUtils.close(connection);
            }
        }
        return result.toString();
    }
    /**
     * 键值对转查询url
     *
     * @param map
     * @return
     */
    public static String map2UrlEncodeString(Map<String, Object> map) {
        if(null == map || map.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        String temp = "";
        for (String s : map.keySet()) {
            try {
                temp = URLEncoder.encode(String.valueOf(map.get(s)), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            sb.append(s).append("=").append(temp).append("&");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }
    /**
     * 忽略SSL证书校验
     *
     * @throws Exception
     */
    static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return;
            }
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                return;
            }
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        } };
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}
ruoyi-service/ruoyi-worker/src/main/java/com/ruoyi/worker/voice/util/StringUtil.java
New file
@@ -0,0 +1,53 @@
/*
 * Copyright Notice:
 *      Copyright  1998-2008, Huawei Technologies Co., Ltd.  ALL Rights Reserved.
 *
 *      Warning: This computer software sourcecode is protected by copyright law
 *      and international treaties. Unauthorized reproduction or distribution
 *      of this sourcecode, or any portion of it, may result in severe civil and
 *      criminal penalties, and will be prosecuted to the maximum extent
 *      possible under the law.
 */
package com.ruoyi.worker.voice.util;
//import java.nio.charset.Charset;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
public class StringUtil {
    public static final String AKSK_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
    public static boolean strIsNullOrEmpty(String s) {
        return (null == s || s.trim().length() < 1);
    }
    public static String buildAKSKHeader(String appKey, String appSecret) throws Exception {
        if (StringUtil.strIsNullOrEmpty(appKey) || StringUtil.strIsNullOrEmpty(appSecret)) {
            return null;
        }
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));
        Calendar calendar = Calendar.getInstance();
        String time = format.format(calendar.getTime());
        String stNonce = UUID.randomUUID().toString().replace("-", "").toUpperCase(Locale.ROOT);
        String str = stNonce + time;
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] authBytes = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
        String passwordDigestBase64Str = encodeBase64(authBytes);
        return String.format(AKSK_HEADER_FORMAT, appKey, passwordDigestBase64Str, stNonce, time);
    }
    private static String encodeBase64(byte[] bytes) {
        if (bytes.length == 0) {
            return null;
        } else {
            return new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
        }
    }
}