背景
康师傅公布了站内api,提供大家开发APP,现在就来体验下是否有bug。 api地址:https://www.sunofbeach.net/a/1403262826952323074
推荐使用这个库来实现cookie保存:https://github.com/franmontiel/PersistentCookieJar,一行代码搞定
Android端实现
Android studio 4x java 8 kotlin 1.4x retrofit 项目代码以kt为主
经过我一顿操作之后,登录界面做好了
图片验证码显示
图片接口已经给出
https://api.sunofbeach.net
/uc/ut/captcha?code=随机数
/uc/user/login/{captcha}
{
phoneNum: '',
password: '取md5'
}
接口部分
interface BlogService {
/**
* 登录
*/
@POST("/uc/user/login/{captcha}")
suspend fun doLogin(
@Path("captcha") verifyCode: String,
@Body user: LoginBody): BaseResponse<SobUser>
}
网络请求处理
LoginViewModel
viewModelScope.launch {
request {
doLogin(
verifyCode.get()!!,
LoginBody(userName.get(), AppMd5Utils.getMD5(password.get())))
}
.onSucceed {
navigateAndDestroy(MainActivity::class.java)
}
.onFailure {
AppToast.toast(it)
getCaptchaCode()
}
}
隐藏的cookie处理
这里需要把验证码请求得到的cookie,放到登录接口中回传。需要额外处理。 为什么?
验证码接口生成了一个l_c_i数据,通过验证码图片下放了,当点击登录的情况下会检查cookie中是否有刚刚生成的数据。 我们显示图片的时候,如果使用默认的glide实现,是无法拿到cookie的,需要配合okhttp配置cookieManager获取。 当请求图片之后,在cookieManager中拦截下来保存,登录接口调用的时候,把cookie信息带上去。
我使用的glide图片加载框架
//okHttp3 : https://github.com/square/okhttp
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
//okHttp3LoggingInterceptor
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'com.github.bumptech.glide:compiler:4.11.0'
都是必须的
新增一个glide配置类
@GlideModule
public class HttpGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
// 注意这里用我们刚才现有的Client实例传入即可
registry.replace(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(ApiKt.getOkHttpClient()));
}
}
提供的okhttp client
fun getOkHttpClient(): OkHttpClient {
val builder: OkHttpClient.Builder = OkHttpClient.Builder()
.cookieJar(cookieJar)
.readTimeout(60, TimeUnit.SECONDS) //设置读取超时时间
.writeTimeout(60, TimeUnit.SECONDS) //设置写的超时时间
.connectTimeout(60, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val httpLoggingInterceptor = HttpLoggingInterceptor()
builder.addInterceptor(httpLoggingInterceptor.apply {
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
})
}
val client = builder.build()
return client
}
val cookieJar by lazy {
CookiesManager()
}
cookie的管理类
class CookiesManager : CookieJar {
private val cookieStoreBlog = HashMap<String, List<Cookie>>()
override fun saveFromResponse(httpUrl: HttpUrl, list: List<Cookie>) {
LL.d("Response httpUrl:$httpUrl")
if (BuildConfig.UNION_BASE_URL.contains(httpUrl.host)) {
LL.d("保存sob的数据")
cookieStoreBlog.put(BuildConfig.UNION_BASE_URL, list)
}
}
override fun loadForRequest(httpUrl: HttpUrl): List<Cookie> {
val list = ArrayList<Cookie>()
val sob = cookieStoreBlog.get(BuildConfig.UNION_BASE_URL);
sob?.apply {
LL.e("返回sob的cookie", sob.toString())
return this
}
return list
}
}
需要的配置都完成了。到此登录接口完成。 登录最重要是验证码携带cookie,在登录接口中需要回传。其他的逻辑处理就和普通的差不多了。
最后上全部代码
act
class LoginActivity : BaseActivity<ActivityLoginBinding, LoginViewModel>() {
private lateinit var shake: Animation
override fun getLayoutId() = R.layout.activity_login
override fun initView() {
shake = AnimationUtils.loadAnimation(this, R.anim.ani_shake)
//ImageHelper.load(vb.ivVerifyCode, getVerifyUrl())
vb.btnLogin.singleClick {
viewModel.login()
}
//navActivity<JpBlogActivity>()
vb.ivVerifyCode.singleClick {
viewModel.getCaptchaCode()
}
}
override fun initData(savedInstanceState: Bundle?) {
viewModel.getCaptchaCode()
}
override fun getVariableId(): Int {
return BR.loginVm
}
override fun startObserve() {
viewModel.liveDataCaptcha.observe(this, {
Glide.with(this@LoginActivity)
.load(it)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.into(vb.ivVerifyCode)
})
viewModel.shakeAnim.observe(this, {
showAnimShake(it)
})
}
private fun showAnimShake(view: Int) {
when (view) {
1 -> {
vb.etUser.startAnimation(shake)
}
2 -> {
vb.etPwd.startAnimation(shake)
}
3 -> {
vb.etCode.startAnimation(shake)
}
}
}
override fun onStop() {
super.onStop()
vb.etCode.animation?.cancel()
vb.etPwd.animation?.cancel()
vb.etUser.animation?.cancel()
}
}
vm
class LoginViewModel : BaseViewModel() {
val shakeAnim = MutableLiveData<Int>()
val userName = ObservableField<String>().apply { set("") }
val password = ObservableField<String>().apply { set("") }
val verifyCode = ObservableField<String>().apply { set("") }
val liveDataCaptcha = MutableLiveData<String>()
fun login() {
if (userName.get()?.isEmpty() == true) {
AppToast.toast("输入账户")
shakeAnim.postValue(1)
return
}
if (password.get()?.isEmpty() == true) {
AppToast.toast("输入密码")
shakeAnim.postValue(2)
return
}
if (verifyCode.get()?.isEmpty() == true) {
AppToast.toast("输入验证码")
shakeAnim.postValue(3)
return
}
viewModelScope.launch {
request {
doLogin(
verifyCode.get()!!,
LoginBody(userName.get(), AppMd5Utils.getMD5(password.get())))
}
.onSucceed {
navigateAndDestroy(MainActivity::class.java)
}
.onFailure {
AppToast.toast(it)
getCaptchaCode()
}
}
}
fun getCaptchaCode() {
liveDataCaptcha.value = BuildConfig.UNION_BASE_URL + "/uc/ut/captcha?code=" + System.currentTimeMillis()
}
}
MD5工具 AppMd5Utils
public class AppMd5Utils {
public AppMd5Utils() {
}
public static String getMD5(String var0) {
Object var1 = null;
if (var0 == null) {
return null;
} else {
try {
byte[] var2 = var0.getBytes();
MessageDigest var3 = MessageDigest.getInstance("MD5");
var3.update(var2);
return ByteUtils.a(var3.digest());
} catch (Exception var4) {
return (String)var1;
}
}
}
public static byte[] getMD5(byte[] var0) {
try {
MessageDigest var1 = MessageDigest.getInstance("MD5");
var1.update(var0);
return var1.digest();
} catch (Exception var2) {
return null;
}
}
public static String getMD5(File var0) {
FileInputStream var1 = null;
Object var3;
try {
MessageDigest var2 = null;
try {
var2 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException var18) {
var18.printStackTrace();
}
var1 = new FileInputStream(var0);
byte[] var22 = new byte[8192];
int var4;
while((var4 = var1.read(var22)) != -1) {
var2.update(var22, 0, var4);
}
String var5 = ByteUtils.a(var2.digest());
return var5;
} catch (FileNotFoundException var19) {
var3 = null;
return (String)var3;
} catch (IOException var20) {
var3 = null;
} finally {
try {
if (var1 != null) {
var1.close();
}
} catch (IOException var17) {
var17.printStackTrace();
}
}
return (String)var3;
}
public static byte[] getMD5(InputStream var0) {
byte[] var1 = null;
if (var0 != null) {
try {
MessageDigest var2 = null;
var2 = MessageDigest.getInstance("MD5");
if (var2 != null) {
byte[] var3 = new byte[8192];
int var4;
while((var4 = var0.read(var3)) != -1) {
var2.update(var3, 0, var4);
}
var1 = var2.digest();
}
} catch (Throwable var5) {
var1 = null;
}
}
return var1;
}
}
登录流程是正常登录的,如果遇到其他问题,来评论区讨论吧。