0%

用Kotlin+OkHttp实现E浙理快速填报APP

写在前面

昨天今天凌晨写了篇E浙理登陆、填报等流程流量分析相关的文章,本篇文章将介绍如何使用Kotlin+OkHttp实现E浙理快速填报APP。

先来康康效果,服务器不抽风的时候,不到6秒搞定每日填报。

啧,这里暴露了马哥的学号

gif

E浙理填报流程

通过下面这张图,可以很清楚的看出来

从登录获取token–>获取用户访问报表信息的sessionID–>根据token和session填报数据

这么一个流程

png.png

(如果还是看着费劲的话,可以康康不久前发布的具体的分析文章-传送门

APP开发

准备

添加依赖

首先要有个HTTP客户端嘛,这里选择了OkHttp,在app的build.gradle中dependencies下添加依赖:

1
implementation 'com.squareup.okhttp3:okhttp:4.4.0'

插一句,由于服务器返回的信息都是json格式的,之后会考虑加上Gson来处理json数据,为了方便我就自己实现json数据处理的函数了。

添加完成后别忘了sync一下

申请权限

AndroidManifest.xml下添加如下内容,以获取网络和缓存权限(用于保存用户名和密码)

1
2
3
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

注意,这里由于安卓版本较高,有可能出现无法访问网络的情况,可通过在application下添加如下内容解决。

1
android:usesCleartextTraffic="true"

模拟登陆

首先通过模拟登陆来获取accessToken

实现如下,接收username和passwd参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private fun stuLogin(username:String, passwd:String) {
Log.d("OKHTTPTest"," Login getToken start!!")
val loginUrl = "http://xxx/webroot/decision/login"
val payload = "{\"username\":\"$username\",\"password\":\"$passwd\",\"validity\":-1,\"macAddress\":\"\",\"deviceName\":\"\"}"

val okHttpClient = OkHttpClient()
val requestBody = payload.toRequestBody()

val request = Request.Builder()
.method("POST", requestBody)
.addHeader("Content-Type", "application/json")
.url(loginUrl)
.build()

okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("OKHTTPTest"," login post fail!!")
}

override fun onResponse(call: Call, response: Response) {
Log.d("OKHTTPTest"," login post Success!!")

val responseData = response.body?.string().toString()
val token:String = getToken(responseData)
Log.d("OKHTTPTest"," login responseData : $responseData")
//runOnUiThread {
val tokenText = findViewById<EditText>(R.id.token)
tokenText.setText(token)
Log.d("OKHTTPTest", " login user token : " + tokenText.text)
//}
}
})

}

这一步我们获得了accessToken

模拟用户访问报表

之后使用上一步获取到的accessToken,访问报表,获取用户访问报表信息的sessionID

实现如下,接收token参数(这里以访问健康报备报表为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private fun healthDeclaration(token:String){
val url = "http://xxx/webroot/decision/view/report?op=h5_write&viewlet=/yewubanli/%252F%25E7%2596%25AB%25E6%2583%2585%25E4%25B8%258A%25E6%258A%25A5.cpt"
val finalCookie = "captchaToken=; UM_distinctid=; fine_auth_token=$token"

val client = OkHttpClient()
val request = Request.Builder()
.method("GET",null)
.url(url)
.addHeader("Cookie", finalCookie)
.addHeader("Connection", "close")
.build()

client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("OKHTTPTest"," fail!!")
}

override fun onResponse(call: Call, response: Response) {
Log.d("OKHTTPTest", " Success!!")

val responseData = response.body?.string().toString()
val sess = getSession(responseData)
Log.d("OKHTTPTest", " session responseData : $responseData")

val sessionText = findViewById<EditText>(R.id.session)
sessionText.setText(sess)
Log.d("OKHTTPTest", " sessionID : " + sessionText.text)

}
})
}

sessionID也得到辽

模拟报表提交

最后使用sessionID,body中添加我们要提交的数据,POST过去就大功告成辣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
private fun postReport(session:String,token:String) {

val url = "http://xxx/webroot/decision/view/report"

//set up user cookie
val finalCookie = "captchaToken=; UM_distinctid=; fine_auth_token=$token"
Log.d("OKHTTPTest", "finalCookie----> : $finalCookie")

//set up report session
Log.d("OKHTTPTest", "finalSession----> : $session")

//start post process
//get client
val client = OkHttpClient()
//set body

//TODO verifying
//cmd=submit_w_report --submit
//cmd=write_verify --verify
val payload = "op=fr_write&cmd=submit_w_report&reportXML=%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522UTF-8%2522%2520%253F%253E%253CWorkBook%253E%253CVersion%253E6.5%253C%252FVersion%253E%253CReport%2520class%253D%2522com.fr.report.WorkSheet%2522%2520name%253D%25220%2522%253E%253CCellElementList%253E%253CC%2520c%253D%25222%2522%2520r%253D%252218%2522%253E%253CO%2520t%253D%2522S%2522%253E%253C!%255BCDATA%255B%25E7%25BB%25BF%25E7%25A0%2581%255D%255D%253E%253C%252FO%253E%253C%252FC%253E%253CC%2520c%253D%25221%2522%2520r%253D%252240%2522%253E%253CO%2520t%253D%2522A%2522%253E%253C!%255BCDATA%255B%255B%2522%25E6%259C%25AC%25E4%25BA%25BA%25E6%2589%25BF%25E8%25AF%25BA%25E4%25BB%25A5%25E4%25B8%258A%25E5%25A1%25AB%25E6%258A%25A5%25E4%25BF%25A1%25E6%2581%25AF%25E7%259C%259F%25E5%25AE%259E%25E5%258F%25AF%25E9%259D%25A0%25EF%25BC%2581%2522%255D%255D%255D%253E%253C%252FO%253E%253C%252FC%253E%253C%252FCellElementList%253E%253C%252FReport%253E%253C%252FWorkBook%253E"

val requestBody = payload.toRequestBody()

val request = Request.Builder()
.method("POST", requestBody)
.addHeader("sessionID", session)
.addHeader("Cookie", finalCookie)
.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
.addHeader("clientType", "mobile/h5_5.0")
.addHeader("Connection", "close")
.addHeader("deviceType", "unknown")
.url(url)
.build()

//POST
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("OKHTTPTest"," report post fail!!")
}

override fun onResponse(call: Call, response: Response) {
Log.d("OKHTTPTest"," report post Success!!")

val responseData = response.body?.string().toString()
Log.d("OKHTTPTest", " report result : $responseData")

if (responseData.contains("success")) {
runOnUiThread{
Toast.makeText(applicationContext,"Reported Successfully..!",Toast.LENGTH_SHORT).show()
}
}
else {
runOnUiThread {
Toast.makeText(applicationContext,"Something Wrong...",Toast.LENGTH_SHORT).show()
}
}

}
})
}

ok了,最后在相应按钮的setOnClickListener中调用如上函数即可。

用户名密码保存

这里图方便,直接写个ini文件到filesDir就完事儿了

1
2
3
4
5
6
7
private fun saveUserInfo(u: String, p: String) {

val file = File(filesDir,"info.ini")
file.writeText("$u\n$p")
Log.d("fileDir"," saved username & passwd" +file.readLines().take(2).toString())

}

kotlin的文件操作是真友好,i了

最后,简单搞个界面出来就实现开头的效果了。(只是为了实现而实现,现在这玩意太简陋了,发出来丢人,回头有空继续搞完善点再发出来hhh。又是flag