优化:发送通道企业微信应用支持http/socks5代理(应对IP白名单限制)

This commit is contained in:
pppscn 2022-07-04 22:36:13 +08:00
parent 93fb0b657a
commit 6ea06ed5ef
4 changed files with 314 additions and 14 deletions

View File

@ -1,6 +1,8 @@
package com.idormy.sms.forwarder.entity.setting
import com.idormy.sms.forwarder.R
import java.io.Serializable
import java.net.Proxy
data class WeworkAgentSetting(
var corpID: String = "",
@ -8,4 +10,19 @@ data class WeworkAgentSetting(
val secret: String = "",
val atAll: Boolean? = false,
val toUser: String? = "@all",
) : Serializable
val proxyType: Proxy.Type = Proxy.Type.DIRECT,
val proxyHost: String? = "",
val proxyPort: String? = "",
val proxyAuthenticator: Boolean? = false,
val proxyUsername: String? = "",
val proxyPassword: String? = "",
) : Serializable {
fun getProxyTypeCheckId(): Int {
return when (proxyType) {
Proxy.Type.HTTP -> R.id.rb_proxyHttp
Proxy.Type.SOCKS -> R.id.rb_proxySocks
else -> R.id.rb_proxyNone
}
}
}

View File

@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.RadioGroup
import androidx.fragment.app.viewModels
import com.google.gson.Gson
import com.idormy.sms.forwarder.R
@ -33,6 +34,7 @@ import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.net.Proxy
import java.util.*
@Page(name = "企业微信应用")
@ -126,6 +128,12 @@ class WeworkAgentFragment : BaseFragment<FragmentSendersWeworkAgentBinding?>(),
binding!!.etSecret.setText(settingVo.secret)
binding!!.sbAtAll.isChecked = settingVo.atAll == true
binding!!.etToUser.setText(settingVo.toUser)
binding!!.rgProxyType.check(settingVo.getProxyTypeCheckId())
binding!!.etProxyHost.setText(settingVo.proxyHost)
binding!!.etProxyPort.setText(settingVo.proxyPort)
binding!!.sbProxyAuthenticator.isChecked = settingVo.proxyAuthenticator == true
binding!!.etProxyUsername.setText(settingVo.proxyUsername)
binding!!.etProxyPassword.setText(settingVo.proxyPassword)
}
}
})
@ -136,11 +144,24 @@ class WeworkAgentFragment : BaseFragment<FragmentSendersWeworkAgentBinding?>(),
binding!!.btnDel.setOnClickListener(this)
binding!!.btnSave.setOnClickListener(this)
binding!!.sbAtAll.setOnCheckedChangeListener(this)
binding!!.sbProxyAuthenticator.setOnCheckedChangeListener(this)
binding!!.rgProxyType.setOnCheckedChangeListener { _: RadioGroup?, checkedId: Int ->
if (checkedId == R.id.rb_proxyHttp || checkedId == R.id.rb_proxySocks) {
binding!!.layoutProxyHost.visibility = View.VISIBLE
binding!!.layoutProxyPort.visibility = View.VISIBLE
binding!!.layoutProxyAuthenticator.visibility = if (binding!!.sbProxyAuthenticator.isChecked) View.VISIBLE else View.GONE
} else {
binding!!.layoutProxyHost.visibility = View.GONE
binding!!.layoutProxyPort.visibility = View.GONE
binding!!.layoutProxyAuthenticator.visibility = View.GONE
}
}
}
@SuppressLint("SetTextI18n")
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
//注意因为只有一个监听暂不需要判断id
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
when (buttonView.id) {
R.id.sb_at_all -> {
if (isChecked) {
binding!!.etToUser.setText("@all")
binding!!.layoutToUser.visibility = View.GONE
@ -149,6 +170,12 @@ class WeworkAgentFragment : BaseFragment<FragmentSendersWeworkAgentBinding?>(),
binding!!.layoutToUser.visibility = View.VISIBLE
}
}
R.id.sb_proxyAuthenticator -> {
binding!!.layoutProxyAuthenticator.visibility = if (isChecked) View.VISIBLE else View.GONE
}
else -> {}
}
}
@SingleClick
override fun onClick(v: View) {
@ -225,7 +252,26 @@ class WeworkAgentFragment : BaseFragment<FragmentSendersWeworkAgentBinding?>(),
val atAll = binding!!.sbAtAll.isChecked
val toUser = binding!!.etToUser.text.toString().trim()
return WeworkAgentSetting(corpID, agentID, secret, atAll, toUser)
val proxyType: Proxy.Type = when (binding!!.rgProxyType.checkedRadioButtonId) {
R.id.rb_proxyHttp -> Proxy.Type.HTTP
R.id.rb_proxySocks -> Proxy.Type.SOCKS
else -> Proxy.Type.DIRECT
}
val proxyHost = binding!!.etProxyHost.text.toString().trim()
val proxyPort = binding!!.etProxyPort.text.toString().trim()
if (proxyType != Proxy.Type.DIRECT && (TextUtils.isEmpty(proxyHost) || TextUtils.isEmpty(proxyPort))) {
throw Exception(getString(R.string.invalid_host_or_port))
}
val proxyAuthenticator = binding!!.sbProxyAuthenticator.isChecked
val proxyUsername = binding!!.etProxyUsername.text.toString().trim()
val proxyPassword = binding!!.etProxyPassword.text.toString().trim()
if (proxyAuthenticator && TextUtils.isEmpty(proxyUsername) && TextUtils.isEmpty(proxyPassword)) {
throw Exception(getString(R.string.invalid_username_or_password))
}
return WeworkAgentSetting(corpID, agentID, secret, atAll, toUser, proxyType, proxyHost, proxyPort, proxyAuthenticator, proxyUsername, proxyPassword)
}
override fun onDestroyView() {

View File

@ -17,6 +17,14 @@ import com.xuexiang.xhttp2.cache.model.CacheMode
import com.xuexiang.xhttp2.callback.SimpleCallBack
import com.xuexiang.xhttp2.exception.ApiException
import com.xuexiang.xui.utils.ResUtils.getString
import com.xuexiang.xutil.net.NetworkUtils
import okhttp3.Credentials
import okhttp3.Response
import okhttp3.Route
import java.net.Authenticator
import java.net.InetSocketAddress
import java.net.PasswordAuthentication
import java.net.Proxy
@Suppress("PrivatePropertyName", "UNUSED_PARAMETER")
class WeworkAgentUtils private constructor() {
@ -42,8 +50,49 @@ class WeworkAgentUtils private constructor() {
getTokenUrl += "&corpsecret=" + setting.secret
Log.d(TAG, "getTokenUrl$getTokenUrl")
XHttp.get(getTokenUrl)
.keepJson(true)
val request = XHttp.get(getTokenUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
Log.d(TAG, "proxyHost = $proxyHost, proxyPort = $proxyPort")
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(setting.proxyUsername.toString(), setting.proxyPassword?.toCharArray())
}
})
}
}
}
request.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.timeStamp(true)
@ -96,9 +145,50 @@ class WeworkAgentUtils private constructor() {
val requestMsg: String = Gson().toJson(textMsgMap)
Log.i(TAG, "requestMsg:$requestMsg")
XHttp.post(requestUrl)
.upJson(requestMsg)
val request = XHttp.post(requestUrl)
//设置代理
if ((setting.proxyType == Proxy.Type.HTTP || setting.proxyType == Proxy.Type.SOCKS)
&& !TextUtils.isEmpty(setting.proxyHost) && !TextUtils.isEmpty(setting.proxyPort)
) {
//代理服务器的IP和端口号
Log.d(TAG, "proxyHost = ${setting.proxyHost}, proxyPort = ${setting.proxyPort}")
val proxyHost = if (NetworkUtils.isIP(setting.proxyHost)) setting.proxyHost else NetworkUtils.getDomainAddress(setting.proxyHost)
if (!NetworkUtils.isIP(proxyHost)) {
throw Exception("代理服务器主机名解析失败proxyHost=$proxyHost")
}
val proxyPort: Int = setting.proxyPort?.toInt() ?: 7890
Log.d(TAG, "proxyHost = $proxyHost, proxyPort = $proxyPort")
request.okproxy(Proxy(setting.proxyType, InetSocketAddress(proxyHost, proxyPort)))
//代理的鉴权账号密码
if (setting.proxyAuthenticator == true
&& (!TextUtils.isEmpty(setting.proxyUsername) || !TextUtils.isEmpty(setting.proxyPassword))
) {
Log.i(TAG, "proxyUsername = ${setting.proxyUsername}, proxyPassword = ${setting.proxyPassword}")
if (setting.proxyType == Proxy.Type.HTTP) {
request.okproxyAuthenticator { _: Route?, response: Response ->
//设置代理服务器账号密码
val credential = Credentials.basic(setting.proxyUsername.toString(), setting.proxyPassword.toString())
response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
} else {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(setting.proxyUsername.toString(), setting.proxyPassword?.toCharArray())
}
})
}
}
}
request.upJson(requestMsg)
.keepJson(true)
.ignoreHttpsCert()
.timeOut((SettingUtils.requestTimeout * 1000).toLong()) //超时时间10s
.cacheMode(CacheMode.NO_CACHE)
.retryCount(SettingUtils.requestRetryTimes) //超时重试的次数

View File

@ -185,6 +185,153 @@
</LinearLayout>
<LinearLayout
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/proxy_settings"
android:textStyle="bold" />
<RadioGroup
android:id="@+id/rg_proxyType"
style="@style/rg_style"
android:layout_marginStart="5dp"
android:orientation="horizontal">
<RadioButton
android:id="@+id/rb_proxyNone"
style="@style/rg_rb_style"
android:checked="true"
android:text="@string/proxy_none" />
<RadioButton
android:id="@+id/rb_proxyHttp"
style="@style/rg_rb_style"
android:text="@string/proxy_http" />
<RadioButton
android:id="@+id/rb_proxySocks"
style="@style/rg_rb_style"
android:text="@string/proxy_socks" />
</RadioGroup>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyHost"
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hostname" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyHost"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyPort"
style="@style/senderBarStyleWithSwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/port" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyPort"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:digits="0123456789"
android:inputType="number"
android:maxLength="5"
android:singleLine="true"
app:met_clearButton="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="@string/proxy_authenticator" />
<com.xuexiang.xui.widget.button.switchbutton.SwitchButton
android:id="@+id/sb_proxyAuthenticator"
style="@style/SwitchButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutProxyAuthenticator"
style="@style/senderBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/username" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_clearButton="true" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/password" />
<com.xuexiang.xui.widget.edittext.materialedittext.MaterialEditText
android:id="@+id/et_proxyPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:singleLine="true"
app:met_passWordButton="true" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>