安卓端-APPUI自动化实战【下】

上一篇介绍了在solopi端的二次开发内容,接下来介绍下服务端的实现原理。

框架介绍:

使用比较成熟封装度较高的开源框架,尽量减少二次开发难度:Pear Admin Boot: 🍃 基 于 Spring Boot 生 态 , 权 限 , 工 作 流 的 开 发 平 台 (gitee.com)

该框架以 layUI+springboot为脚手架进行开发。

 

服务端主要实现功能:

1.与客户端(solopi)进行websocket通信,可正常发出&接收消息,

2.管理客户端上传的设备信息,判断当前设备是否在线,方便后续用例下发执行,

3.管理客户端上传的用例信息,用例中心化管理,解决用例不同设备需要多次录制的问题,

4.可选择用例并进行模板替换后,顺序下发到指定设备的solopi端执行,

5.接收solopi端上传的测试报告并展示在前端页面,方便查看和回溯。

  

websocket通信&接收客户端消息相关:

首先配置websocket请求地址:如下配置时,客户端请求地址则为:ws://xxx.xxx.xxx.xxx:8080/reletime

@Component
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    RealTimeHandler realTimeHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(realTimeHandler, "realtime")
        .setAllowedOrigins("*");
    }
}

 

1.接收solopi上传的设备信息并进行存储,以序列号作为唯一标识,并将设备状态设置为true(在线):

//接收到的消息转成map
        Map<String, Object> messageMap = (Map<String, Object>) JSONUtil.parse(payload);

        for (String s : messageMap.keySet()) {
            String jsonString = messageMap.get(s).toString();
            if ("deviceInfo".equals(s)) {
                DeviceInfo deviceInfo = JSONObject.toJavaObject(JSONObject.parseObject(jsonString), DeviceInfo.class);

                String serial = deviceInfo.getSerial();//获取到solopi上传的设备序列号
                online(serial, session);

                DeviceInfo selectDeviceByMac = deviceInfoService.selectDeviceBySerial(serial);

                if (selectDeviceByMac == null) {
                    deviceInfo.setStatus(true);
                    deviceInfo.setSessionId(session.getId());
                    deviceInfo.setUpdate_Time(new Date());
                    deviceInfo.setCreateTime(new Date());
                    deviceInfoService.insertDeviceInfo(deviceInfo);

                } else {
                    deviceInfo.setId(selectDeviceByMac.getId());
                    deviceInfo.setStatus(true);
                    deviceInfo.setSessionId(session.getId());
                    deviceInfo.setUpdate_Time(new Date());
                    deviceInfoService.updateDeviceInfo(deviceInfo);
                }

            } 

 

断开连接时,更新设备状态为false(离线)状态:

@Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        log.info("断开链接");
        DeviceInfo deviceInfo = deviceInfoService.selectDeviceBySessionId(session.getId());
        deviceInfo.setStatus(false);
        deviceInfo.setUpdate_Time(new Date());
        deviceInfoService.updateDeviceInfo(deviceInfo);
        offline(deviceInfo.getSerial(), session);
        super.afterConnectionClosed(session, status);
    }

 

并封装了online和offline方法来处理存储的session:

 public static HashMap<String, WebSocketSession> SESSION_POOL = new HashMap();
    public static HashMap<String, String> SERIAL_SESSIONID_MAPPING = new HashMap<>();
    public static HashMap<String, String> SESSIONID_SERIAL_MAPPING = new HashMap<>();


private void online(String serial, WebSocketSession session) {
        SESSION_POOL.put(serial, session);//将序列号 token 存到map,针对设备下发命令时使用
        SERIAL_SESSIONID_MAPPING.put(serial, session.getId());
        SESSIONID_SERIAL_MAPPING.put(session.getId(), serial);
    }


    private void offline(String serial, WebSocketSession session) {
        SESSION_POOL.remove(serial);
        SERIAL_SESSIONID_MAPPING.remove(serial);
        SESSIONID_SERIAL_MAPPING.remove(session.getId());
    }

 

2.接收客户端上传的用例信息,以用例名作为唯一标识,对用例进行新建or更新

else if ("caseInfo".equals(s)) {
                JSONArray jsonArray = JSONUtil.parseArray(jsonString);
                //遍历每一个case,根据caseName和targetAppPackage判断是否已经存在,不存在则插入,存在则更新
                for (int i = 0; i < jsonArray.size(); i++) {
                    cn.hutool.json.JSONObject jsonObject = jsonArray.getJSONObject(i);
                    String caseStr = JSONUtil.toJsonStr(jsonObject);
                    CaseInfo caseInfo = JSONUtil.toBean(jsonObject, CaseInfo.class);
                    CaseInfo caseInfoObj = caseInfoService.selectCaseInfoByNameAndTargetApp(caseInfo.getCaseName(), caseInfo.getTargetAppPackage());
                    if (caseInfoObj == null) {//不存在
                        caseInfo.setCreateTime(new Date());
                        caseInfo.setUpdateTime(new Date());
                        caseInfo.setCaseinfo(caseStr);
                        caseInfoService.insertCaseInfo(caseInfo);//插入
                    } else {//存在
                        caseInfo.setId(caseInfoObj.getId());
                        caseInfo.setUpdateTime(new Date());
                        caseInfo.setCaseinfo(caseStr);
                        caseInfoService.updateCaseInfo(caseInfo);//更新
                    }
                }
            }

 

 

服务端下发测试用例:

选择用例、选择设备点击执行,生成一条任务。

任务:设备=1:N

任务:用例=1:N

任务:报告=1:N

数据库设计:

 

 

 

 

 

 

 

 

 

 

 核心代码:

 

//TODO:模板替换规则不对,不应该写死登录注册的模板key,需要根据用户选择的模板进行替换
    @PostMapping("execute")
    @ResponseBody
    public int execute(@RequestBody CaseExecute caseExecute) {
        //1.创建任务
        SysUser sysUser = (SysUser) SecurityUtil.currentUserObj();//获取当前用户
        Task task = new Task();
        task.setId(SequenceUtil.makeStringId());
        task.setCasecount(Long.valueOf(caseExecute.getCaseIds().size()));
        task.setDeviceinfocount(Long.valueOf(caseExecute.getDeviceIds().size()));
        task.setCreatetime(new Date());
        task.setUpdatetime(new Date());
        task.setTaskstatus(0);
        task.setCreateId(sysUser.getUserId());
        task.setTaskname(caseExecute.getTaskname());

        //2.任务关联的caseInfo信息
        TaskCaseInfo taskCaseInfo = new TaskCaseInfo();
        ArrayList<String> caseIds = caseExecute.getCaseIds();
        caseIds.forEach((caseId) -> {
            taskCaseInfo.setId(SequenceUtil.makeStringId());
            taskCaseInfo.setCaseinfoid(caseId);
            taskCaseInfo.setTaskid(task.getId());
            taskCaseInfo.setCreatetime(new Date());
            taskCaseInfo.setUpdatetime(new Date());

            //入库
            taskCaseInfoService.insertTaskCaseInfo(taskCaseInfo);
        });

        //已使用的模板ID、模板值
        List<String> alreadyValues = new ArrayList<>();
        List<String> alreadyIds = new ArrayList<>();
        //查出所有可用的模板信息
        Template template = new Template();
        template.setKey("login.phone,login.pwd");
        List<Template> templates = templateService.selectTemplateList(template);

        //3.任务关联的deviceInfo
        TaskDeviceInfo taskDeviceInfo = new TaskDeviceInfo();
        ArrayList<String> deviceIds = caseExecute.getDeviceIds();

        ArrayList<WebSocketSession> sessionList = new ArrayList<>();
        //存储需要替换模板的session
        ArrayList<WebSocketSession> necessaryreplacesessionList = new ArrayList<>();
        deviceIds.forEach((deviceId) -> {
            taskDeviceInfo.setId(SequenceUtil.makeStringId());
            taskDeviceInfo.setDeviceinfoid(Integer.valueOf(deviceId));
            taskDeviceInfo.setTaskid(task.getId());
            taskDeviceInfo.setCreatetime(new Date());
            taskDeviceInfo.setUpdatetime(new Date());

            //入库
            taskDeviceInfoService.insertTaskDeviceInfo(taskDeviceInfo);

            //4.拿到设备序列号,通过序列号获取到对应的session
            DeviceInfo deviceInfo = deviceInfoService.selectDeviceInfoById(Long.valueOf(deviceId));
            String serial = deviceInfo.getSerial();
            WebSocketSession socketSession = (WebSocketSession) RealTimeHandler.SESSION_POOL.get(serial);
            sessionList.add(socketSession);

            //关联deviceInfo & Template
            //查出所有用户选择需要替换模板的信息deviceUseTemplateIds
            ArrayList<String> deviceUseTemplateIds = caseExecute.getDeviceUseTemplateIds();
            if (deviceUseTemplateIds.contains(deviceId)){
                //需要替换模板session
                necessaryreplacesessionList.add(socketSession);

                //判断是否有空闲合适模板替换,如果有就替换,没有就不替换
                if (templates == null || templates.size() < 0) {
                    log.info("hasAvailableTemplate not find template login.phone,login.pwd, do nothing");
                }
                // 查出所有可用的模板手机号,密码
                List<String> Ids = templates.stream().map(Template::getId).filter(Objects::nonNull).collect(Collectors.toList());
                Ids.removeAll(alreadyIds);
                List<String> values = templates.stream().map(Template::getValue).filter(Objects::nonNull).collect(Collectors.toList());
                values.removeAll(alreadyValues);

                if (values.size() > 0) {
                    //有未被占用的手机号
                    String value = values.get(0);
                    alreadyValues.add(value);
                    String id1=Ids.get(0);
                    alreadyIds.add(id1);
                    //devicesId 在deviceUseTemplateIds中,需要关联“设备模板”
                    DeviceInfoTemplate deviceInfoTemplate = new DeviceInfoTemplate();
                    deviceInfoTemplate.setId(SequenceUtil.makeStringId());
                    deviceInfoTemplate.setTemplateid(id1);
                    deviceInfoTemplate.setDeviceInfoid(deviceId);
                    deviceInfoTemplate.setCreatetime(new Date());
                    deviceInfoTemplate.setUpdatetime(new Date());
                    //入库
                    deviceInfoTemplateService.insertDeviceInfoTemplate(deviceInfoTemplate);

                    //devicesId 在deviceUseTemplateIds中,需要关联“任务模板”
                    TaskTemplate taskTemplate = new TaskTemplate();
                    taskTemplate.setId(SequenceUtil.makeStringId());
                    taskTemplate.setTemplateid(id1);
                    taskTemplate.setTaskid(task.getId());
                    taskTemplate.setCreatetime(new Date());
                    taskTemplate.setUpdatetime(new Date());
                    //入库
                    taskTemplateService.insertTaskTemplate(taskTemplate);

                } else {
                    //模板没有足够手机号|密码
                    log.info("No template available, do nothing");
                }

            }

        });

        //5.task以及关联表入库
        taskService.insertTask(task);

        //6.下发用例到solopi
        sessionList.forEach((session) -> {
            try {
                CaseExecuteDto caseExecuteDto = new CaseExecuteDto();
                ArrayList<String> caseInfoList = new ArrayList<>();
                //需要替换的用例List
                caseExecute.getCaseIds().forEach((caseId) -> {
                    CaseInfo caseInfo = caseInfoService.selectCaseInfoById(Long.valueOf(caseId));
                    if (caseInfo != null) {
                        String currentCaseInfo = caseInfo.getCaseinfo();
                        if (StringUtils.isNotBlank(currentCaseInfo) && (currentCaseInfo.contains("login.phone")
                                ||currentCaseInfo.contains("login.pwd")) && necessaryreplacesessionList.contains(session)) {
                            currentCaseInfo = convertCaseInfo(currentCaseInfo);
                        }
                        caseInfoList.add(currentCaseInfo);
                    }
                });

                caseExecuteDto.setCaseInfoList(caseInfoList);
                caseExecuteDto.setTaskId(task.getId());

                HashMap<String, CaseExecuteDto> caseInfoExecuteHashMap = new HashMap<>();
                caseInfoExecuteHashMap.put("execCase", caseExecuteDto);

                String json = new Gson().toJson(caseInfoExecuteHashMap);
                session.sendMessage(new TextMessage(json));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        alreadyValue.clear();

        return 0;
    }

下发后,solopi进行执行,执行完成后上传测试报告。

核心代码:

接收客户端上传的报告信息,以任务id为唯一标识,对任务数据进行储存。

else if (s.contains("replayResultInfo")) {
                /*GsonBuilder builder = new GsonBuilder();
                builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
                    public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
                        return new Date(json.getAsJsonPrimitive().getAsLong());
                    }
                });
                Gson gson = builder.create();

                List<ReplayResult> mResults=gson.fromJson(messageMap.get(s).toString(), new TypeToken<List<ReplayResult>>() {
                }.getType());

                int totalNum = 0;
                int successNum = 0;
                for (ReplayResult bean : mResults) {
                    totalNum++;
                    if (StringUtil.isEmpty(bean.getExceptionMessage())) {
                        successNum++;
                    }
                }*/

                //TODO:映射为报告对象,不要做字符串截取
                String taskId = s.substring(16);
                Task task = taskService.selectTaskById(taskId);//通过taskid查询到task对象
                //获取当前设备序列号
                String currentSerial = SESSIONID_SERIAL_MAPPING.get(session.getId());
                log.info("当前设备序列号" + currentSerial);

                com.alibaba.fastjson.JSONArray jsonArray = JSONObject.parseArray(messageMap.get(s).toString());
                //任务存在,代表是下发执行的,进行保存,否则不做处理
                if (task != null) {
                    TaskReport taskReport = new TaskReport();
                    taskReport.setTaskid(taskId);
                    taskReport.setContent(messageMap.get(s).toString());
                    taskReport.setDeviceinfoserial(currentSerial);
                    //taskReport.setSuccessnum(successNum);
                    //taskReport.setFailnum(totalNum-successNum);
                    for (int i = 0; i < jsonArray.size(); i++) {
                        String caseName = jsonArray.getJSONObject(i).getString("caseName");
                        taskReport.setCaseName(caseName);
                        taskReportService.insertTaskReport(taskReport);
                    }
                }
            }

 

 

最终前端报告展示:

 

posted @ 2022-05-18 20:29  小屁妞  阅读(187)  评论(0编辑  收藏  举报