研究生登录,验证码解决器

old-package
lensfrex 2 years ago
parent f3aa0d4be0
commit 036c4b47be
Signed by: lensfrex
GPG Key ID: 0F69A0A2FBEE98A0
  1. 12
      mywust-core/src/main/java/cn/linghang/mywust/core/exception/ApiException.java
  2. 2
      mywust-core/src/main/java/cn/linghang/mywust/core/request/graduate/GraduateRequestFactory.java
  3. 95
      mywust-core/src/main/java/cn/linghang/mywust/core/service/auth/GraduateLogin.java
  4. 5
      mywust-core/src/main/java/cn/linghang/mywust/core/service/captcha/solver/CaptchaSolver.java
  5. 77
      mywust-core/src/main/java/cn/linghang/mywust/core/service/captcha/solver/LinghangOcrServiceCaptchaSolver.java
  6. 2
      mywust-core/src/main/java/cn/linghang/mywust/core/service/captcha/solver/LocalPaddleOcrCaptchaSolver.java
  7. 22
      mywust-model/src/main/java/cn/linghang/mywust/captcha/SolvedImageCaptcha.java
  8. 2
      mywust-model/src/main/java/cn/linghang/mywust/captcha/UnsolvedImageCaptcha.java
  9. 11
      mywust-model/src/main/java/cn/linghang/mywust/captcha/solver/LinghangOcrServiceCaptchaSolver.java
  10. 20
      mywust-util/src/main/java/cn/linghang/mywust/util/StringUtil.java

@ -35,6 +35,11 @@ public class ApiException extends BasicException {
*/ */
UNKNOWN_EXCEPTION(-1, "未知错误(开发又有活干啦)"), UNKNOWN_EXCEPTION(-1, "未知错误(开发又有活干啦)"),
/**
* 网络异常
*/
NETWORK_EXCEPTION(-2, "网络错误..."),
// -------------------------------- // --------------------------------
// 统一认证的异常(本科生、图书馆) // 统一认证的异常(本科生、图书馆)
@ -84,11 +89,16 @@ public class ApiException extends BasicException {
* 研究生密码错误 * 研究生密码错误
*/ */
GRADUATE_PASSWORD_WRONG(140100, "研究生登录: 密码错误"), GRADUATE_PASSWORD_WRONG(140100, "研究生登录: 密码错误"),
;
// -------------------------------- // --------------------------------
// 图书馆API异常代码 // 图书馆API异常代码
// --------------------------------
// 内部API异常代码
INTERNAL_EXCEPTION(160100, "内部服务出错...后端要背锅"),
;
private final int value; private final int value;
private final String describe; private final String describe;

@ -33,7 +33,7 @@ public class GraduateRequestFactory extends RequestFactory {
byte[] requestData = StringUtil.generateQueryString(params).getBytes(StandardCharsets.UTF_8); byte[] requestData = StringUtil.generateQueryString(params).getBytes(StandardCharsets.UTF_8);
return makeHttpRequest(Graduate.GRADUATE_LOGIN_API, requestData); return makeHttpRequest(Graduate.GRADUATE_LOGIN_API, requestData, captcha.getBindInfo());
} }
public static HttpRequest studentInfoRequest(String cookie) { public static HttpRequest studentInfoRequest(String cookie) {

@ -0,0 +1,95 @@
package cn.linghang.mywust.core.service.auth;
import cn.linghang.mywust.captcha.SolvedImageCaptcha;
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha;
import cn.linghang.mywust.core.exception.ApiException;
import cn.linghang.mywust.core.exception.BasicException;
import cn.linghang.mywust.core.request.graduate.GraduateRequestFactory;
import cn.linghang.mywust.core.service.captcha.solver.CaptchaSolver;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class GraduateLogin {
private final Requester requester;
private final CaptchaSolver captchaSolver;
public GraduateLogin(Requester requester, CaptchaSolver captchaSolver) {
this.requester = requester;
this.captchaSolver = captchaSolver;
}
public String getLoginCookie(String username, String password, RequestClientOption option) throws IOException, BasicException {
// 请求获取验证码
HttpRequest captchaImageRequest = GraduateRequestFactory.captchaRequest();
HttpResponse captchaImageResponse = requester.get(captchaImageRequest, option);
UnsolvedImageCaptcha unsolvedImageCaptcha = new UnsolvedImageCaptcha();
unsolvedImageCaptcha.setBindInfo(captchaImageResponse.getCookies());
byte[] processedImage = ImageUtil.process(captchaImageResponse.getBody());
unsolvedImageCaptcha.setImage(processedImage);
// 通过传入的captchaSolver来处理验证码
SolvedImageCaptcha solvedImageCaptcha = captchaSolver.solve(unsolvedImageCaptcha);
// 进行登录
HttpRequest loginRequest = GraduateRequestFactory.loginRequest(username, password, solvedImageCaptcha);
HttpResponse loginResponse = requester.post(loginRequest, option);
// 登陆成功,应该会是302跳转,不是的话多半是认证错误
if (loginResponse.getStatusCode() != HttpResponse.HTTP_REDIRECT_302) {
throw new ApiException(ApiException.Code.GRADUATE_PASSWORD_WRONG);
}
// 使用当初通过验证码得到的cookie来作为登录cookie,至于是否真正可行待验证
return captchaImageResponse.getCookies();
}
}
class ImageUtil {
// 验证码图片的长宽
private static final int IMAGE_WIDTH = 75;
private static final int IMAGE_HEIGHT = 35;
// 二值化阈值,实测339效果最佳
private static final int LIMIT = 339;
private static final int BLACK = Color.BLACK.getRGB();
private static final int WHITE = Color.WHITE.getRGB();
/**
* 初步处理验证码图片对图片去色处理以更好地进行ocr处理
*/
public static byte[] process(byte[] data) throws ApiException {
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
BufferedImage bufferedImage = ImageIO.read(byteArrayInputStream);
for (int i = 0; i < IMAGE_WIDTH; ++i) {
for (int j = 0; j < IMAGE_HEIGHT; ++j) {
int rgb = bufferedImage.getRGB(i, j);
int bright = ((rgb >> 16) & 0xff) + ((rgb >> 8) & 0xff) + (rgb & 0xff);
bufferedImage.setRGB(i, j, bright <= LIMIT ? BLACK : WHITE);
}
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", outputStream);
return byteArrayInputStream.readAllBytes();
} catch (Exception e) {
throw new ApiException(ApiException.Code.INTERNAL_EXCEPTION);
}
}
}

@ -1,8 +1,9 @@
package cn.linghang.mywust.captcha.solver; package cn.linghang.mywust.core.service.captcha.solver;
import cn.linghang.mywust.captcha.SolvedImageCaptcha; import cn.linghang.mywust.captcha.SolvedImageCaptcha;
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha; import cn.linghang.mywust.captcha.UnsolvedImageCaptcha;
import cn.linghang.mywust.core.exception.ApiException;
public interface CaptchaSolver { public interface CaptchaSolver {
SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha); SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha) throws ApiException;
} }

@ -0,0 +1,77 @@
package cn.linghang.mywust.core.service.captcha.solver;
import cn.linghang.mywust.captcha.SolvedImageCaptcha;
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha;
import cn.linghang.mywust.core.exception.ApiException;
import cn.linghang.mywust.core.request.RequestFactory;
import cn.linghang.mywust.network.RequestClientOption;
import cn.linghang.mywust.network.Requester;
import cn.linghang.mywust.network.entitys.HttpRequest;
import cn.linghang.mywust.network.entitys.HttpResponse;
import cn.linghang.mywust.util.StringUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
public class LinghangOcrServiceCaptchaSolver implements CaptchaSolver {
private final String baseUrl;
private final Requester requester;
private final String appId;
private final String secretKey;
private static final RequestClientOption requestOption = new RequestClientOption();
public LinghangOcrServiceCaptchaSolver(String baseUrl, Requester requester,
String appId, String secretKey) {
this.baseUrl = baseUrl;
this.requester = requester;
this.appId = appId;
this.secretKey = secretKey;
}
@Override
public SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha) throws ApiException {
Map<String, String> urlParams = new TreeMap<>(String::compareTo);
urlParams.put("id", appId);
urlParams.put("t", Long.toString(System.currentTimeMillis() / 1000));
urlParams.put("type", "json");
String sign = StringUtil.generateSignText(appId, secretKey, urlParams);
urlParams.put("sign", sign);
String url = baseUrl + StringUtil.generateQueryString(urlParams);
byte[] imageBase64Data = Base64.encodeBase64(unsolvedImageCaptcha.getImage());
HttpRequest request;
HttpResponse response;
OcrResult result;
try {
request = RequestFactory.makeHttpRequest(url, imageBase64Data);
response = requester.post(request, requestOption);
result = new ObjectMapper().readValue(response.getBody(), OcrResult.class);
} catch (IOException e) {
throw new ApiException(ApiException.Code.NETWORK_EXCEPTION);
}
if (result.code != OcrResult.CODE_SUCCESS) {
throw new ApiException(ApiException.Code.INTERNAL_EXCEPTION);
}
return new SolvedImageCaptcha(unsolvedImageCaptcha, result.result);
}
private static class OcrResult {
private int code;
private String result;
private String message;
public static final int CODE_SUCCESS = 0;
}
}

@ -1,4 +1,4 @@
package cn.linghang.mywust.captcha.solver; package cn.linghang.mywust.core.service.captcha.solver;
import cn.linghang.mywust.captcha.SolvedImageCaptcha; import cn.linghang.mywust.captcha.SolvedImageCaptcha;
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha; import cn.linghang.mywust.captcha.UnsolvedImageCaptcha;

@ -1,25 +1,37 @@
package cn.linghang.mywust.captcha; package cn.linghang.mywust.captcha;
import lombok.Data; import lombok.Data;
import lombok.Getter;
/** /**
* 已处理的图片验证码 * 已处理的图片验证码必须要和特定的未处理验证码绑定
*/ */
@Data @Getter
public class SolvedImageCaptcha { public class SolvedImageCaptcha {
/** /**
* 和验证码绑定的信息如cookie某种id等 * 和验证码绑定的信息如cookie某种id等
*/ */
private String bindInfo; private final String bindInfo;
/** /**
* 验证码验证数据 * 验证码验证数据
*/ */
private String result; private String result;
public SolvedImageCaptcha(String bindInfo) {
this.bindInfo = bindInfo;
}
public SolvedImageCaptcha(UnsolvedImageCaptcha unsolvedImageCaptcha) { public SolvedImageCaptcha(UnsolvedImageCaptcha unsolvedImageCaptcha) {
this.bindInfo = unsolvedImageCaptcha.getBindInfo(); this(unsolvedImageCaptcha.getBindInfo());
} }
private void setBindInfo(String bindInfo) {} public SolvedImageCaptcha(UnsolvedImageCaptcha unsolvedImageCaptcha, String result) {
this(unsolvedImageCaptcha);
this.result = result;
}
public void setResult(String result) {
this.result = result;
}
} }

@ -1,6 +1,8 @@
package cn.linghang.mywust.captcha; package cn.linghang.mywust.captcha;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* 待处理的图片验证码 * 待处理的图片验证码

@ -1,11 +0,0 @@
package cn.linghang.mywust.captcha.solver;
import cn.linghang.mywust.captcha.SolvedImageCaptcha;
import cn.linghang.mywust.captcha.UnsolvedImageCaptcha;
public class LinghangOcrServiceCaptchaSolver implements CaptchaSolver {
@Override
public SolvedImageCaptcha solve(UnsolvedImageCaptcha unsolvedImageCaptcha) {
return null;
}
}

@ -1,6 +1,7 @@
package cn.linghang.mywust.util; package cn.linghang.mywust.util;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -44,4 +45,23 @@ public class StringUtil {
.skipNulls() .skipNulls()
.join(allCookies); .join(allCookies);
} }
/**
* 生成参数签名
* @param appId appId
* @param secretKey secretKey
* @return 生成得到的签名sign字段
*/
public static String generateSignText(String appId, String secretKey, Map<String, String> params) {
StringBuilder rawText = new StringBuilder(appId + secretKey);
Set<String> keys = params.keySet();
for (String key : keys) {
rawText
.append(key)
.append('=')
.append(params.get(key));
}
return Base64.encodeBase64String(rawText.toString().getBytes(StandardCharsets.UTF_8));
}
} }

Loading…
Cancel
Save