研发效能 开发微信机器人助力线上问题支持群自动回复

孤千羽 · 2022年07月08日 · 最后由 爱吃粽子的佩奇 回复于 2022年11月16日 · 6530 次阅读

业务背景

随着公司业务量激增,每天线上问题的反馈量很大,线上问题支持需要大量测试人员 24 小时在群里回复支持,这件事其中就会涉及到很多痛点:
1、线上问题涉及到很多多端的问题,多端的研发团队又是异地跨团队,问题反馈、沟通时间成本巨大。
2、测试人员手头工作量也很大,没法及时响应用户问题,导致投诉。
3、线上反馈问题 70% 为操作解释性问题,如果维护好对应的知识文档进行自动回复,可以解放很多测试人力。
4、公司通讯工具虽然接入了企业微信,但是大家都还是用个人微信建工作群和问题支持群,个人微信没有对外提供相应的 api 来进行自动群管理。

为了解决以上问题,领导考虑引入微信机器人自动回复一些操作解释性问题,既可以快速响应用户反馈的问题,又可以解放一部分测试人力出来。

解决方案

把线上问题群里全部拉入机器人的微信,实现以下功能:
1、每日定时几次发送各种常见问题以及对应操作指引,以及对应的问题需要 @ 的相应的负责人。
2、支持配置关键字及自动回复消息,一旦包含关键字就触发自动回复消息。
3、针对员工内部群,定时推送提醒工时、发日报等内容,并 @ 到对应的人。

具体实现

第一步:

服务器上安装可爱猫及对外 http 的插件及对应版本微信,电脑上微信版本设置禁止自动更新,并登录机器人微信。

可爱猫及微信安装包:

可爱猫上登录机器人微信:

安装 http 对外插件:

第二步:

配置消息回调地址:

第三步:

数据库相关表设计:

微信群组表:

微信自动消息配置表:

第四步:

开发对应的后端接口:

1、机器人所有微信群相关接口 (获取微信机器人所有群组)

 **
 * <p>
 * 微信配置表 前端控制器
 * </p>
 *
 * @author xxxx
 * @since 2021-10-15
 */
@Slf4j
@RestController
@ApiSupport(order = 19)
@Api(tags = {"微信群组配置表相关接口"})
@RequestMapping("wxGroupConfig")
public class WxGroupConfigController {

    @Autowired
    private WxGroupConfigService wxGroupConfigService;

    @ApiOperation("分页查询群组列表")
    @ApiOperationSupport(order = 2)
    @PostMapping("/queryPageWxGroup")
    public PageableEntity<WxGroupConfig> queryPageWxGroup(@RequestBody PageRequest<String> request) {
        IPage pager = MpToolkit.toPage(request);
        String groupWxName = request.getData();
        QueryWrapper<WxGroupConfig> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(groupWxName)){
            wrapper.like("group_wxname",groupWxName);
        }
        IPage<WxGroupConfig> result = wxGroupConfigService.page(pager,wrapper);
        return MpToolkit.build(result);
    }

    @ApiOperation(value = "不分页查询群组列表")
    @ApiOperationSupport(order = 1)
    @PostMapping("/queryWxGroupList")
    public ResponseEntity<List<WxGroupConfig>> queryWxGroupList() {

        return StatusCode.OK.build(wxGroupConfigService.list());
    }

    @ApiOperation(value = "获取微信机器人所有微信群组")
    @ApiOperationSupport(order = 1)
    @PostMapping("/getGroupList")
    public ResponseEntity<Boolean> getGroupList() {

        return StatusCode.OK.build(wxGroupConfigService.getGroupList());
    }
}

服务实现类:

/**
 * <p>
 * 微信配置表 服务实现类
 * </p>
 *
 * @author xxxx
 * @since 2021-10-15
 */
@Slf4j
@Service
public class WxGroupConfigServiceImpl extends MybatisPlusServiceImpl<WxGroupConfigMapper, WxGroupConfig> implements WxGroupConfigService {

    @Autowired
    private WxMsgSendUtils wxMsgSendUtils;

    @Autowired
    private WxGroupConfigService wxGroupConfigService;

    @Override
    public boolean getGroupList() {
        JSONObject json = wxMsgSendUtils.getGroupList();
        if (null != json){
            JSONArray jsonArray = json.getJSONArray("data");
            for (int i = 0;i < jsonArray.size();i++){
                JSONObject jsonObject = jsonArray.getJSONObject(i);
                WxGroupConfig wxGroupConfig = new WxGroupConfig();
                wxGroupConfig.setGroupWxid(jsonObject.getString("wxid"));
                if (StringUtils.isNotBlank(jsonObject.getString("nickname"))){
                    wxGroupConfig.setGroupWxname(jsonObject.getString("nickname"));
                }else {
                    wxGroupConfig.setGroupWxname("群聊");
                }
                try {
                    wxGroupConfigService.save(wxGroupConfig);
                }catch (DuplicateKeyException e){
                    //    log.info("已经初始化过该微信群组: {},{}" ,wxGroupConfig.getGroupWxid(),wxGroupConfig.getGroupWxname());
                }
            }
            return true;
        }
        return false;
    }

}

2、群自动回复消息相关接口

/**
 * <p>
 * 群自动回复消息配置表 前端控制器
 * </p>
 *
 * @author xxxxx
 * @since 2021-10-15
 */
@Slf4j
@RestController
@ApiSupport(order = 20)
@Api(tags = {"群自动回复消息配置表相关接口"})
@RequestMapping("wxAutomsgConfig")
public class WxAutomsgConfigController extends BaseController {

    @Autowired
    private WxAutomsgConfigService wxautomsgconfigService;

    @Resource
    private WxAutomsgConfigMapper wxAutomsgConfigMapper;


    /**
     * 服务器回调接口,用于接收微信机器人消息
     *
     * 可爱猫,消息上报
     * @param request
     * @return
     */
    @ApiOperation(value = "监听可爱猫的消息")
    @ApiOperationSupport(order = 1)
    @PostMapping("/callback")
    public void handleMessage(HttpServletRequest request) throws Exception{
        RequestVo requestVo = new RequestVo();
        requestVo.setType(request.getParameter("type"));
        requestVo.setFrom_wxid(request.getParameter("from_wxid"));
        requestVo.setFinal_from_wxid(request.getParameter("final_from_wxid"));
        requestVo.setFrom_name(request.getParameter("from_name"));
        requestVo.setFinal_from_name(request.getParameter("final_from_name"));
        requestVo.setRobot_wxid(request.getParameter("robot_wxid"));
        requestVo.setMsg(request.getParameter("msg"));
        requestVo.setTime(request.getParameter("time"));
        requestVo.setRid(request.getParameter("rid"));
    //  log.info("收到微信消息:{}", JSON.toJSONString(requestVo));
        if (requestVo.getType().equals("200") && requestVo.getMsg().contains("@")&& !requestVo.getMsg().startsWith("<?xml")){
            List<WxAutomsgConfig> wxAutomsgConfigs = wxAutomsgConfigMapper.queryByGroupWxId(requestVo.getFrom_wxid());
            if (CollectionUtils.isNotEmpty(wxAutomsgConfigs)){
                wxautomsgconfigService.handleGroupMsg(requestVo,wxAutomsgConfigs);
            }
        }
    }

    @ApiOperation("分页查询自动消息配置信息")
    @ApiOperationSupport(order = 2)
    @PostMapping("/queryPageWxAutomsg")
    public PageableEntity<WxAutomsgConfigView> page(@RequestBody PageRequest<WxAutomsgConfigDto> request) {
        IPage pager = MpToolkit.toPage(request);
        WxAutomsgConfigDto wxAutomsgConfigDto = request.getData();
        if (null == wxAutomsgConfigDto) {
            return MpToolkit.build(pager);
        }
        Map<String,Object> mapParam = new HashMap<>();
        mapParam.put("page",pager);
        mapParam.put("groupId",wxAutomsgConfigDto.getGroupId());
        mapParam.put("keyword",wxAutomsgConfigDto.getKeyword());
        pager.setRecords(wxAutomsgConfigMapper.queryPageWxAutomsg(mapParam));
        return MpToolkit.build(pager);
    }

    @ApiOperation("新增自动消息配置信息")
    @ApiOperationSupport(order = 3)
    @PostMapping("/addWxAutomsg")
    public ResponseEntity<Boolean> add(@RequestBody WxAutomsgConfigDto wxAutomsgConfigDto) {
        // 实体校验
        ComplexResult cr = FluentValidator.checkAll().failOver()
                .on(wxAutomsgConfigDto, new WxAutomsgConfigDtoValidator())
                .doValidate()
                .result(ResultCollectors.toComplex());
        // 验证失败
        if (!cr.isSuccess()) {
            throw new FluentException(cr.getErrors());
        }

        return StatusCode.OK.build(wxautomsgconfigService.addWxAutomsgConfig(wxAutomsgConfigDto));
    }

    @ApiOperation("更新自动消息配置信息")
    @ApiOperationSupport(order = 4)
    @PostMapping("/updateWxAutomsg")
    public ResponseEntity<Boolean> update(@RequestBody WxAutomsgConfigDto wxAutomsgConfigDto) {
        // 实体校验
        ComplexResult cr = FluentValidator.checkAll().failOver()
                .on(wxAutomsgConfigDto, new WxAutomsgConfigDtoValidator())
                .on(wxAutomsgConfigDto.getMsgId(),new ObjectValidator("msgId"))
                .doValidate()
                .result(ResultCollectors.toComplex());
        // 验证失败
        if (!cr.isSuccess()) {
            throw new FluentException(cr.getErrors());
        }

        return StatusCode.OK.build(wxautomsgconfigService.editWxAutomsgConfig(wxAutomsgConfigDto));

    }

    @ApiOperation("删除自动消息配置信息")
    @ApiOperationSupport(order = 5)
    @GetMapping("/removeWxAutomsg")
    public ResponseEntity<Boolean> remove(Long msgId) {
        // ID校验
        ComplexResult cr = FluentValidator.checkAll().failOver()
                .on(msgId, new ObjectValidator("msgId"))
                .doValidate()
                .result(ResultCollectors.toComplex());
        // 验证失败
        if (!cr.isSuccess()) {
            throw new FluentException(cr.getErrors());
        }
        return StatusCode.OK.build(wxautomsgconfigService.deleteWxAutomsgConfig(msgId));
    }
}

服务实现类:

/**
 * <p>
 * 群自动回复消息配置表 服务实现类
 * </p>
 *
 * @author xxxx
 * @since 2021-10-15
 */
@Slf4j
@Service
public class WxAutomsgConfigServiceImpl extends MybatisPlusServiceImpl<WxAutomsgConfigMapper, WxAutomsgConfig> implements WxAutomsgConfigService {

    @Autowired
    private WxMsgSendUtils wxMsgSendUtils;

    @Override
    public synchronized void handleGroupMsg(RequestVo requestVo, List<WxAutomsgConfig> wxAutomsgConfigs) {
        for (WxAutomsgConfig wxAutomsgConfig : wxAutomsgConfigs){
            if (StringUtils.isNotBlank(wxAutomsgConfig.getAutoMsg())){
                if (requestVo.getMsg().contains(wxAutomsgConfig.getKeyword())){
                //  log.info("自动回复消息");
                    wxMsgSendUtils.sendTextMsg(wxAutomsgConfig.getAutoMsg(),requestVo);
                    break;
                }
            }
        }
    }

    @Override
    public boolean addWxAutomsgConfig(WxAutomsgConfigDto wxAutomsgConfigDto) {
        WxAutomsgConfig wxAutomsgConfig = WxAutomsgConfigDxo.INSTANCE.toEntity(wxAutomsgConfigDto);
        UserContextUtils.addModel(wxAutomsgConfig);
        wxAutomsgConfig.setMsgId(null);
        try {
            return this.save(wxAutomsgConfig);
        } catch (DuplicateKeyException e) {
            throw new BusinessException("微信群ID不能重复",e);
        }
    }

    @Override
    public boolean editWxAutomsgConfig(WxAutomsgConfigDto wxAutomsgConfigDto) {
        WxAutomsgConfig dbWxAutomsgConfig = this.getById(wxAutomsgConfigDto.getMsgId());
        if (null == dbWxAutomsgConfig) {
            throw new BusinessException("要修改的自动消息配置不存在");
        }
        WxAutomsgConfig wxAutomsgConfig = WxAutomsgConfigDxo.INSTANCE.toEntity(wxAutomsgConfigDto);
        //防止修改主键id值
        wxAutomsgConfig.setMsgId(dbWxAutomsgConfig.getMsgId());
        UserContextUtils.editModel(wxAutomsgConfig);
        try {
            return this.updateById(wxAutomsgConfig);
        } catch (DuplicateKeyException e) {
            throw new BusinessException("微信群ID不能重复", e);
        }
    }

    @Override
    public boolean deleteWxAutomsgConfig(Long msgId) {
        QueryWrapper<WxAutomsgConfig> wrapper = new QueryWrapper<>();
        wrapper.eq("is_delete", Constants.IS_DELETE_NO);
        wrapper.eq("msg_id", msgId);
        WxAutomsgConfig wxAutomsgConfig = this.getOne(wrapper);
        if (null == wxAutomsgConfig) {
            throw new BusinessException("要删除的数据不存在");
        }
        UserContextUtils.deleteModel(wxAutomsgConfig);
        return this.updateById(wxAutomsgConfig);
    }


}

第五步:

前端相关功能页面:

微信群组:

自动回复消息配置:

总结

虽然看起来一个很小的工具,却可以实在的提升一些效能,从中也提升了个人的解决问题的能力,另外代码自我感觉写得很烂,不大会发帖子,各位大佬轻喷。

共收到 10 条回复 时间 点赞

钉钉机器人是不是可以试试,我记得群聊设置页面有 webhook

学习一下!

这个应用收费吗?

不收费的

孤千羽 回复

有 ocr 吗 解析发来的图片作为关键字

没太明白你的意思,可爱猫能监听到所有的微信消息包括图片,代码里监听回调图片消息后,图片消息可能是个 url 或者 base64 的字符串,你代码里可以自己随意处理。

孤千羽 回复

我的意思是 能识别到图片里的文字吗?根据不同的图片文字发送不同的消息

可以监听识别到图片消息

孤千羽 回复

在 github 上有吗 能拉倒代码吗?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册