STF STF 二次开发辛酸之路

0x88 · 2016年10月14日 · 最后由 wjTest 回复于 2021年04月23日 · 6007 次阅读
本帖已被设为精华帖!

前言

接触 STF 平台已经有一段时间,但最近一个月多才正式做二次开发,分享一些二次开发的经验。有人对 STF 二次开发不太看好,代码看不懂,表示学习成本太高,不愿意去学习。语言这东西嘛,多看几遍就看懂了。对于 STF 平台的整体架构个人认为是写得非常的好,逻辑清晰,不仅仅做二次开发,我觉得其 web 架构与设计模式都是值得去学习。当然很多人觉得宁愿想用 java 和 php 去重新写一个类 STF 平台(server 和 web),但是又有多少人能做得出来更别说优于它了?当然 testin 就不说了,他们是一家公司在做这套东西。唠叨就说这么多,现在分享一下 STF 二次开发的一些经验吧。

平台的一些介绍

1.平台语言
1) 使用 html 使用的是 jade 模版,后来因为 jade 改名为 pug,因此现在使用的是 pug 模版引擎。

2) 前端使用的是 angularjs 1.x 版本,虽然 2016 年 5 月份左右,google 推出了 angularjs2.0 版本,但官方并不想更新 angularjs 的版本,继续使用 1.x 版本。如果改成 2.0 版本,相当于要重写平台。如果是重写平台,官方可能不会使用 angularjs。
3) 后端使用的是 nodejs

4) 数据库使用的是对象型数据库 rethinkdb。至于为什么使用这个数据库,官方的回答是:看到新出来的 rethinkdb,想尝试一下新的东西,于是就采用了。

2.STF 平台组成
STF 平台是由:stf,adbkit,STFService.apk,stf-syrup,adbkit-logcat,stf-device-db,adbkit-apkreader,
minicap,minitouch,minirev 等组件共同组成。

3.STF 目录结构
/.tx transifex 网站相关的配置,主要是用于其它语言翻译
/bin 启动文件,其实是链接到 lib/cli.js
/docker Docker 的相关配置
/lib 后端代码
/res 前端 web 的代码
/conf 应该是 mysql 的配置文件,但是目前没有使用,是个没用的配置
/doc 一些 STF 平台的说明
/test 检测平台的状态
/vendor 需要安装到手机上的应用或者服务
/node_modules npm 相关的组件
/rethinkdb_data rethinkdb 的数据库文件,建议在 stf 目录下执行 rethinkdb
Package.json npm 所需要安装的组件集,以 json 格式保存
.... 其它说明文件与打包文件

4.STF 平台启动
先启动 rethinkdb,再使用 stf local 启动平台,stf 后面可带参数,stf --help 可查看帮助。
stf 命令其实是调用了 stf 目录下 lib/cli.js 这个文件执行的。
修改了后端代码/lib 需要使用命令 gulp clean。
每次启动都好麻烦,而我的记性又不好。于是这里我写了一个 shell 的启动脚本 start.sh(在目录/var/stf/下面),可直接运行此脚本启动。
一般启动: ./start.sh,由于 ldap 涉及到公司内部的信息,我就不写了。自行百度 google 其 ldap 启动方法

para=$1
gulp clean
killall -9 node
killall -9 rethinkdb
ipaddr=`ifconfig eth0|awk 'NR==2{print $2}'|cut -d : -f2`
rethinkdb  --bind $ipaddr &
 sleep 3
DATE=`date +%F`

if [ "$para" = "ldap" ];then
#  echo $para
stf local --public-ip=$ipaddr  -C --poorxy-port 80 --auth-type ldap --auth-options '[ldap配置]' >> /var/stf/log/$DATE.log 2>&1 &
else
echo "noldap"
stf local --public-ip=$ipaddr  -C --poorxy-port 80 >> /var/stf/log/$DATE.log 2>&1 &
fi
sleep 6 

非 ldap 启动:直接./start.sh
Ldap 启动:./start.sh ldap

5.关于 rethinkdb
Rethinkdb 属于对象型数据库,使用的是 json 字符串格式保存数据(不建议内嵌 json 超过三层)。其操作有异于关系型数据,但其扩展性比关系型数据库好,可随时在数据表中插入数据,不需要做数据关联操作。

6.modules 组件
Node 相关的 modules 组件使用的是 npm 的安装方式,而 Package.json 是所要安装的组件名及其版本号,格式如下图所示。如需要新增组件也可以直接使用 npm install 组件名 @ 版本号。如果安装不成功,大多数是因为网络问题,建议开启 *** 进行下载。

Openstf 相关的 adbkit 等组件有源代码可以修改,底层操作手机也是用 adbkit 这些组件来做的,详细的语法及使用可直接进入相关的 github 官网查看。

前端开发

res 目录结构
/app 前端 web 代码
/auth 登录认证方式
/common 语言翻译相关
/test 登录的跳转与帮助等
/web_modules web 样式

res 目录分为 service 与 web,service 主要用于后端通讯操作,而 web 是前端展示。
Service 目录在 res/app/component/stf 目录下。
比如需要增加一个 task(任务管理的界面),则新建一个 task 文件件,里面创建几个文件:
index.js
task-controller.js
task.pug 等
修改 app 目录下的 app.js,增加 require(‘./task’).name,如下图所示

增加 task 的 service 到目录 stf/res/app/components/stf/下增加目录 task 文件夹,并创建 index.js 与 taskservice.js,用于通讯。
一般都是使用 get,post 请求,而 stf 使用的是 oboe 模块来接收发送的。代码如下

var _ = require('lodash')
var oboe = require('oboe')

module.exports = function TaskServiceFactory($http, socket) {
  var TaskService = {}
  TaskService.reports = new Array()
   oboe('后端定义的api')
   .node('reports.*',function(reports){
        TaskService.reports.push(reports)})
  return TaskService
}

后端

db 是数据操作文件,分为 api.js,index.js,setup.js,tables.js
db 的操作只需要修改 tables.js 与 api.js 即可,其它两个文件可以不用修改。
api.js 主要用于数据库增删查操作

dbapi.Name = function(input){
  return db.run(查询语句)
} 

units 是核心代码,根据其命名可得知其作用。需要增加什么功能就直接新建文件夹,当然代码肯定要自己写。
其它功能自己写,这里就只讲如何插入 log
STF 平台已经写了一个方法来显示 Log 的,require 其自带的 logger.js 就可以打日志了

var log = logger.createLogger('xxx模块')
.....
.....
log.info('描述',变量)
log.debug('描述',变量)
.....

数据库 Rethinkdb

官网:www.rethinkdb.com
一般网络是连接不上 rethinkdb 的官网的很多时候真的都打不开,跟我们国内的网络真恶心,需要开 ***。
学习 rethinkdb 没有捷径,百度 google 都没有用。只能上官网看英文,不懂上 github 中提 issue,英文不好的就得用力啃了。
启动完 rethinkdb 后的可访问网址http://ip:8080webview 的操作界面,可在 web 上面对数据库进行操作。,可进入

好了就写这么多,早晨的时间总是过得飞快,国庆过后的 7 个工作日,辛酸辛酸。

共收到 65 条回复 时间 点赞
张沛 回复

看 28 楼,登录后输入上上下下左右左右回车,可以开启管理员权限,然后在 “设备——细节” 列表,点击 “繁忙” 可以把别人踢了

涂洋 回复

我 LDAP 是自己搭建的,也是登录 stf 报错: 我怀疑是启动命令我没写对,你成功登录 stf 的命令是这样的吗?
stf local --auth-type ldap --auth-options '["--ldap-url","ldap://10.0.0.1:389", "--ldap-bind-dn","cn=admin,dc=company,dc=com","--ldap-search-dn","dc=company,dc=com", "--ldap-bind-credentials","123456", "--ldap-search-class","organization", "--ldap-search-field", "mail"]'

张沛 回复

我已经几年没搞 stf 了,现在都不知道怎么改,只能靠你们自己。

楼主还在吗,我想请教下 minicap 怎么删除呀~我现在想用 scrcpy 把 minicap 换掉

仅楼主可见

请问下 STFService.apk 具体怎么使用,有没有接口调用的一些教程,谢谢了

0x88 回复

大神意思是直接去数据库里改这个字段么?这个并不需要管理员的角色就能改吧?

0x88 #19 · 2019年07月23日 Author
stronger 回复

修改数据库 device 表中的 owner 字段,设置成空就可以了。

0x88 回复

请问下大神,STF 支持管理员强制释放正在被别的用户占用的设备,这个功能吗?

仅楼主可见
60楼 已删除
0x88 回复

我现在是用 ubt 作为开发机的,但我开发好后要部署到生产环境使用。感觉…… 说不清楚,我自己再摸索看看吧。

0x88 #58 · 2018年11月06日 Author
测试生 回复

没懂你的意思,你说的是 stf 的开发代码怎么部署么?跟 ci 有啥关系?如果是代码调试你得做好单机开发能力,stf 那边是推荐使用 ubt 作为开发电脑系统,所以他们本地就能调试。我们当时是搞了好几台 ubt 的电脑作为开发环境。

@0x88 这几天折腾了一番,准备二次开发了,但是有一个问题,该如何持续集成呢?例如我本机调了一遍环境要部署到后期运行机器上,总不能每次调试完都又重新部署一次吧~ docker 不太了解,但是好像可以起到作用?请问楼主是怎么处理的?

测试生 STF 部署踩坑小记 中提及了此贴 10月31日 11:36
0x88 #55 · 2018年09月04日 Author
zhanglimin 回复

百度一下 nodejs 跨域就好啦,好像是在 poorxy 的 index.js 上面加。

0x88 回复

我用的 1.0.39,用的这个命令:adb nodaemon server -a,前提是本地 adb 服务没有开,好像对外提供的默认端口是 5037 端口

0x88 回复

楼主跨域的帖子能给个链接参考吗

0x88 · #52 · 2018年07月30日 Author
仅楼主可见
仅楼主可见
0x88 #8 · 2018年07月20日 Author
jason 回复

你想聊啥呢?如果是问题讨论直接在这里说就好啦。

仅楼主可见
60楼 已删除
0x88 回复

后面可以了,原来是字段写错了,谢谢大佬👍

0x88 #5 · 2018年05月24日 Author
涂洋 回复

stf local --public-ip=stf.oa.com -C --poorxy-port 80 --auth-type ldap --auth-options '["--ldap-url","ldap://192.168.100.51:389", "--ldap-bind-dn","CN=LdapSync,OU=SpecUsers,OU=PublicOU,OU=BoyaaSZ,DC=boyaa,DC=com","--ldap-search-dn","OU=BoyaaSZ,DC=boyaa,DC=com", "--ldap-bind-credentials","ldap234%", "--ldap-search-class","Person", "--ldap-search-field", "sAMAccountName"]' >> /var/stf/log/$DATE.log 2>&1 &

0x88 #45 · 2018年05月23日 Author

最近公司的上网权限过期了,明天晚上我开下家里的电脑,把命令贴出来

44楼 已删除
笑天 回复

大佬,你用 ldap 登录成功了没?我一直不成功,具体是这样的:
控制台的情况是这样的:

xshell 报的错如下:
2018-05-22T05:59:51.988Z WRN/auth-ldap 26040 [::ffff:127.0.0.1] Authentication failure for "admin@cpu-os.com"
Unhandled rejection InvalidCredentialsError
at EventEmitter.endListener (/usr/local/tuyang/stf/stf-master/lib/util/ldaputil.js:78:25)
at emitOne (events.js:116:13)
at EventEmitter.emit (events.js:211:7)
at sendResult (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1389:22)
at messageCallback (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1421:16)
at Parser.onMessage (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1089:14)
at emitOne (events.js:116:13)
at Parser.emit (events.js:211:7)
at Parser.write (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/messages/parser.js:111:8)
at Socket.onData (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1076:22)
at emitOne (events.js:116:13)
at Socket.emit (events.js:211:7)
at addChunk (_stream_readable.js:263:12)
at readableAddChunk (_stream_readable.js:250:11)
at Socket.Readable.push (_stream_readable.js:208:10)
at TCP.onread (net.js:597:20)

我启动的命令如下:
stf local --public-ip='172.19.37.57' --auth-type ldap --auth-options '["--ldap-url","ldap://172.19.37.57", "--ldap-bind-dn","cn=admin, dc=cpu-os, dc=com","--ldap-search-dn","dc=cpu-os, dc=com", "--ldap-bind-credentials","123456", "--ldap-search-class", "top", "--ldap-search-field", "mail"]'

我的 LDAP 是自己搭建的,运行 ldapsearch -x 结果如下:
root@seatu-VirtualBox:/usr/local/tuyang/stf/stf-master# ldapsearch -x

extended LDIF
#

LDAPv3
base (default) with scope subtree
filter: (objectclass=*)
requesting: ALL
#

cpu-os.com
dn: dc=cpu-os,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: cpu-os
dc: cpu-os

admin, cpu-os.com
dn: cn=admin,dc=cpu-os,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

jinlong, cpu-os.com
dn: cn=jinlong,dc=cpu-os,dc=com
cn:: IGppbmxvbmc=
sn: jinlong
objectClass: inetOrgPerson
objectClass: top

taowen, cpu-os.com
dn: cn=taowen,dc=cpu-os,dc=com
cn:: IHRhb3dlbg==
sn: taowen
objectClass: inetOrgPerson
objectClass: top

search result
search: 2
result: 0 Success

numResponses: 5
numEntries: 4
另外,我用 java 和 node 去连接我的 ldap 服务器都是可以的,估计是 stf 启动命令格式有问题,大佬求助啊!!!大佬方便留个 QQ 嘛?不方便的话,可以加一下嘛?我的 qq 是:1052113431,真的万分感谢!

blueshark 回复

6666666666666666666666666666666

匿名 #68 · 2018年02月08日

stf 代码写的挺好的,逻辑思路和模块划分思路都挺清晰的

0x88 #40 · 2017年11月28日 Author

要用 adb 1.0.32 版本其他版本我没看到-a 参数,需要看下是不是被改成别的了。在任意 pc 端启动 adb -a -P 5037 fork-server server.
在已经启动 STF 服务端再输入命令 stf --provider …,命令很长,手机输入很麻烦,你自行百度一下吧。把参数改车你 PC 端的地址就可以了

0x88 回复

你的意思是所有的电脑都装上 STF 然后每台接上手机都能连接到一个 web 界面用?
您好,请教下,这个是怎么操作呢?

0x88 #67 · 2017年07月31日 Author
furong 回复

把代码发给你看如果你不懂 SSO 也没用,其实 SSO 没那么复杂,我这里时间比较宽裕,我记得好像就用了一天看了一下 SSO,一天看了下 STF 平台,然后用了两天时间改了一下,然后用了一天测试了一下。先了解 SSO 就行了,把跳转改一下。好久之前写的代码,虽然是我写的,但我记性不好,至于怎么实现,什么是 SSO,别问我为什么,因为我忘了。
PS:大小括号是不全的,所以只供你参考。

app.get('/', function(req, res) {
   res.redirect('/auth/mock/')
})

app.get('/auth/mock/',function(req,res){
if(!req.cookies.admin_key){
  res.status(200)
        .json({
          success: false
          , redirect: 'http://sso.oa.com/Index/login/appid/1111/'
        })
  }
else{
 var urlpath='/api?do=getInfo&appid='+1111+'&uid='
  +req.cookies.admin_uid+'&key='+encodeURIComponent(req.cookies.admin_key)
  log.info('urlpath:',urlpath)
  var opts = {
      host: 'sso.oa.com',
      port: 8888,
      path: urlpath,
      method: 'GET',
      headers: {
          'Content-Type': 'application/json'
      }
  }
  log.info('opts',opts)
var req = http.request(opts,function(res){
  res.setEncoding('utf8')
  res.on('data',function(chunk){
    log.info('aaaa',chunk)
    ssoreturn(chunk)
  })
})
req.end()

function ssoreturn(chunk){
  var chunkjson=JSON.parse(chunk)
  var token = jwtutil.encode({
      payload: {
        email: chunkjson.email
      , name: chunkjson.username
      }
    , secret: options.secret
    })
  log.info('chunk',chunkjson)
  log.info('chunk ret',chunkjson.ret)
    if(chunkjson.ret == 1){
      res.status(200)
        .json({
          success: true
          , user_name: chunkjson.username
          , redirect: urlutil.addParams(options.appUrl, {
                jwt: token
              })
          })
    }
    else{
      log.info('redirect.......')
      res.status(200)
        .json({
          success: false
          , user_name: chunkjson.username
          , redirect: 'sso.oa.com'
        })
    }
  }

0x88 回复

博主能不能把你的 sso 实现过程说明的更详细一点啊

0x88 #65 · 2017年07月31日 Author
furong 回复

看不懂 STF 的 auth2.0 的用法,我是直接把 mock 认证改成 sso,sso 无非是个跳转而已。

0x88 回复

看到了 stf 支持 oauth2.0,想请教一下博主 SSO 认证的实现思路,最近刚刚入 stf

#33 楼 @huafeihua 我没搭过 LDAP 服务,对 LDAP 这一块不懂,这个是 LDAP 服务是公司的,参数也是 IT 组给我的,直接拿来用的。现在我都已经不用 LDAP,重新写了一个 SSO 认证,所以这一块我也不会去学习了。

#32 楼 @0x88 ldap 服务器我是自己搭的,一个的 openladp,一直搞不懂用户名和密码各自对应的是 ladp 中的那个属性,"--ldap-search-field", "mail",这样等于把用户名关联到 mail 上了,那么密码应该是哪个属性呢,userPassword 吗,我试了,这个连接失败了.

0x88 #54 · 2017年02月07日 Author

#31 楼 @huafeihua 我不知道你的密码是什么,密码是 LDAP 服务器上面的用户对应的。

#29 楼 @0x88 stf 采用 ldap 进行验证的话, "--ldap-search-field", "mail" 这个是用户名吧,跳转的验证页面有用户名和密码两个输入框,密码应该是什么呢?

#29 楼 @0x88 STF 一个后门,可以打开 root 模式

0x88 #29 · 2016年12月09日 Author

#28 楼 @blueshark 这是啥操作呀。

#22 楼 @onlinesen123 想踢掉任何一个占用设备的账号,告诉你一个后门,在 stf 界面 up up down down left right left right enter 就可以开启 admin 模式,可以踢掉任何用户。up 是方向键上,down 是下,enter 是回车

0x88 #27 · 2016年12月09日 Author

你的意思是所有的电脑都装上 STF 然后每台接上手机都能连接到一个 web 界面用?

#25 楼 @0x88 HI,再请教下,貌似看到一个功能,provider 设备。我的需求是再随便一台 PC 上插入设备,stf 能获取到这个 PC 地址下的设备吗?参数能否提供下,我试了多个参数都没成功

0x88 #48 · 2016年11月17日 Author

#24 楼 @onlinesen123😅 我只能发以下图片给你看看。用下代码对比工具嘛。

#23 楼 @0x88 我试过了,干掉以后,device 显示是可用状态,但实际谁都用不了,点击设备进去,没有任何显示。而且貌似之前的用户刷新下,虽然不再显示任何图像,但感觉还是 owner 的状态。

0x88 #46 · 2016年11月17日 Author

#22 楼 @onlinesen123 请不要叫我大神,我只是个 Tester。
你太执著于改 STF 的代码了,我觉得你得换个思维方式。无论代码如何改,最终于的结果是,web-->中间一大串逻辑或者操作-->写数据库。
你可以试一下用数据库操作把设备占用的用户干掉。应该是 devices 表中的 owner。

#21 楼 @0x88 大神,再请教下,我现在想加一个权限,好让我后台可以踢掉任何一个占用设备的账号。
我看了下这段 disconnect 的代码,调用 api.user.deleteUserDeviceBySerial,需要 auth_token,这个我后台拿不到啊,拿如何能直接踢掉用户占用的设备呢?请指教下吧,谢谢了。。。

var AUTH_TOKEN = '03f5e019a2f94a35b90c30e40829395b5a0d0f0e7fd14bc496a176b03e229540';

var client = new Swagger({
url: SWAGGER_URL
, usePromise: true
, authorizations: {
accessTokenAuth: new Swagger.ApiKeyAuthorization('Authorization', 'Bearer ' + AUTH_TOKEN, 'header')
}
})

0x88 #21 · 2016年11月11日 Author

#20 楼 @onlinesen123 我只改 lib/然后把 http 接口化,web 端是找另外的开发做的,而且这是公司的项目,知识产权的问题,不适合公开源码。

#19 楼 @0x88 果然,换我自己的手机就好了,公司手机都这样。。晕死。。
我再 ubuntu14.04 上,手机是 7.0,软件基本就是 google 原生的。。估计是 stf 切换的时候发送了什么命令,导致 intent 启动输入了字符。。这个我会自己研究下。

另外请问下,你做 2 次开发,能否把项目放到 github 上面,我们一起来瞻仰下啊,这样大家一起来做,或许能把 stf 做完善点。

0x88 #42 · 2016年11月11日 Author

#18 楼 @onlinesen123 部署的系统是什么??手机型号?有没有试下换台手机?据我了解新的手机都不会出现这种问题,是否是因为你的手机安装了什么软件?

#17 楼 @0x88
感谢回复,
我昨天才安装的,发现这个问题,也没搞懂如何看 log 之类的,抱歉了。我贴个图。

就是从 device 界面切换到 control 界面,设备的 searchbar 上,会自动打入几个字符 “123456”,而且切一次就打几个进去。。

0x88 #17 · 2016年11月11日 Author

#16 楼 @onlinesen123 你是个测试人员?要会问问题,你这啥都没有,谁能帮得了你?

多个设备从 device 切换到 control 界面,手机会自动打 “123456” 等字符到 search bar 里,这个谁遇到过啊?

0x88 #38 · 2016年11月09日 Author

#14 楼 @zangtian2 不建议把登录去掉,后面数据库逻辑与其它地方都会用到 username 和 useremaiil,我这边的做法是通过跨域提供 http 接口,等我这边把事情做完后续会现发一个帖子。

你好有什么办法取消掉登陆界面吗

梳理得很清楚!

#9 楼 @blueshark 可以干掉的,相信我,只是需要重写的东西多一些而已

0x88 #11 · 2016年10月14日 Author

#10 楼 @blueshark 官方的说先观望,看谁去接盘 rethinkdb;再考虑是否更换。不过我想等我把界面改好了后,我也会考虑换 mangodb。

rethinkdb 已经倒闭了,不知道后面官方会不会换数据库

#8 楼 @codeskyblue zmq 不好干掉啊,它是 stf 的主力😂

#7 楼 @0x88 stf 的代码我已经快看了两遍了,我不会全部重写的,远控控制肯定是要写的,zeromq 先干掉

0x88 #31 · 2016年10月14日 Author

#5 楼 @codeskyblue 这开发量估计比你改 stf 的工作量大 N 倍呀!😫

正在用其他语言重写中 哈哈

加精理由。清晰,有效实践

Monkey 将本帖设为了精华贴 10月14日 10:31
0x88 #27 · 2016年10月14日 Author

没在本地上配置过.....
我比较懒,不想搞 docker,于是通过打印 log.info 或者 log.bug 后直接扔测试服务器看报什么错来调试的。你可以试试搭个 docker 来调试。

好文,写得好清晰。 有个问题请教下,你们有对 stf 在 webstorm 做断点调试吗?具体是怎么进行的呢?

我在我本地上配置一直不成功,用默认代码的话 child-process 在 fork 的时候子进程使用同一个调试端口,会引起端口冲突无法启动。改用 child-process-debug 后,其中一个依赖库报错不支持 --debug 参数。

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