parent
f5e42cb7d4
commit
f8ccfae2d3
@ -0,0 +1,5 @@ |
||||
package cn.linghang.mywust.core; |
||||
|
||||
public class Mywust { |
||||
|
||||
} |
@ -0,0 +1,5 @@ |
||||
package cn.linghang.mywust.core; |
||||
|
||||
public class MywustFactory { |
||||
|
||||
} |
@ -0,0 +1,7 @@ |
||||
package cn.linghang.mywust.core.service.undergraduate; |
||||
|
||||
public class JwcLogin { |
||||
public String getLoginCookie(String user, String password) { |
||||
return ""; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<groupId>cn.linghang</groupId> |
||||
<artifactId>mywust-network-httpclient</artifactId> |
||||
<version>1.0-SNAPSHOT</version> |
||||
|
||||
<properties> |
||||
<maven.compiler.source>11</maven.compiler.source> |
||||
<maven.compiler.target>11</maven.compiler.target> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
</properties> |
||||
|
||||
</project> |
@ -0,0 +1,53 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<groupId>cn.linghang</groupId> |
||||
<artifactId>mywust-network-okhttp</artifactId> |
||||
<version>1.0-SNAPSHOT</version> |
||||
|
||||
<properties> |
||||
<maven.compiler.source>11</maven.compiler.source> |
||||
<maven.compiler.target>11</maven.compiler.target> |
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
</properties> |
||||
|
||||
<dependencies> |
||||
<dependency> |
||||
<groupId>org.projectlombok</groupId> |
||||
<artifactId>lombok</artifactId> |
||||
<version>1.18.24</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>com.squareup.okhttp3</groupId> |
||||
<artifactId>okhttp</artifactId> |
||||
<version>3.14.9</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>com.fasterxml.jackson.core</groupId> |
||||
<artifactId>jackson-core</artifactId> |
||||
<version>2.13.4</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.fasterxml.jackson.core</groupId> |
||||
<artifactId>jackson-databind</artifactId> |
||||
<version>2.14.0-rc1</version> |
||||
</dependency> |
||||
<dependency> |
||||
<groupId>com.fasterxml.jackson.core</groupId> |
||||
<artifactId>jackson-annotations</artifactId> |
||||
<version>2.13.4</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>cn.linghang</groupId> |
||||
<artifactId>mywust-network</artifactId> |
||||
<version>0.0.1-dev</version> |
||||
</dependency> |
||||
</dependencies> |
||||
|
||||
</project> |
@ -0,0 +1,165 @@ |
||||
package cn.linghang.mywust.network.okhttp; |
||||
|
||||
import cn.linghang.mywust.network.HttpRequest; |
||||
import cn.linghang.mywust.network.HttpResponse; |
||||
import cn.linghang.mywust.network.RequestClientOption; |
||||
import cn.linghang.mywust.network.Requester; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import okhttp3.*; |
||||
|
||||
import java.io.IOException; |
||||
import java.net.InetSocketAddress; |
||||
import java.net.Proxy; |
||||
import java.net.URL; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.util.HashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.TimeUnit; |
||||
|
||||
/** |
||||
* <p>使用Okhttp的Requester,简单的实现了mywust的需求,能应付普通的使用</p> |
||||
* <p>可以设置OkhttpClient是否使用单例模式以节省部分内存和对象分配的消耗,默认不使用(多例模式)</p> |
||||
* <p>按照Okhttp官方的说法以及其他人的测试,使用多例Okhttp对性能消耗其实并不是很大,可以忽略,在多例模式下性能可能也比httpclient要好</p> |
||||
* <p>但是如果每次请求的代理或者超时等设置有动态需求的话只能使用多例模式,单例模式下OkhttpClient的设置是全局一致的</p> |
||||
* <br> |
||||
* <p>如果有更加高级的需求实现,也可以自己实现Requester接口</p> |
||||
* |
||||
* @author lensfrex |
||||
* @create 2022-10-15 09:49 |
||||
* @edit 2022-10-15 09:49 |
||||
*/ |
||||
public class SimpleOkhttpRequester implements Requester { |
||||
private final boolean singletonClient; |
||||
|
||||
public SimpleOkhttpRequester() { |
||||
singletonClient = false; |
||||
} |
||||
|
||||
public SimpleOkhttpRequester(boolean singletonClient) { |
||||
this.singletonClient = singletonClient; |
||||
} |
||||
|
||||
private static volatile OkHttpClient singletonOkhttp; |
||||
|
||||
private static OkHttpClient newOkhttpClient(RequestClientOption requestClientOption) { |
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder() |
||||
.callTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
||||
.readTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS) |
||||
.connectTimeout(requestClientOption.getTimeout(), TimeUnit.SECONDS); |
||||
|
||||
// 设置代理
|
||||
RequestClientOption.Proxy proxyOption = requestClientOption.getProxy(); |
||||
if (proxyOption != null) { |
||||
InetSocketAddress proxyAddress = new InetSocketAddress(proxyOption.getAddress(), proxyOption.getPort()); |
||||
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddress); |
||||
|
||||
builder.proxy(proxy); |
||||
} |
||||
|
||||
return builder.build(); |
||||
} |
||||
|
||||
private static OkHttpClient getSingletonClientInstant(RequestClientOption options) { |
||||
if (singletonOkhttp == null) { |
||||
synchronized (SimpleOkhttpRequester.class) { |
||||
if (singletonOkhttp == null) { |
||||
singletonOkhttp = newOkhttpClient(options); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return singletonOkhttp; |
||||
} |
||||
|
||||
private OkHttpClient getOkhttpClient(RequestClientOption options) { |
||||
if (singletonClient) { |
||||
return getSingletonClientInstant(options); |
||||
} else { |
||||
return newOkhttpClient(options); |
||||
} |
||||
} |
||||
|
||||
private static final String JSON_CONTENT_TYPE = "application/json; charset=utf-8"; |
||||
|
||||
private static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded"; |
||||
|
||||
public enum RequestMethod { |
||||
GET, POST |
||||
} |
||||
|
||||
private Request buildRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest) { |
||||
Request.Builder requestBuilder = new Request.Builder().url(url); |
||||
|
||||
Map<String, String> requestHeaders = httpRequest.getHeaders(); |
||||
requestHeaders.forEach(requestBuilder::header); |
||||
|
||||
requestBuilder.header("Cookie", httpRequest.getCookies()); |
||||
|
||||
if (requestMethod == RequestMethod.GET) { |
||||
requestBuilder.get(); |
||||
} else if (requestMethod == RequestMethod.POST) { |
||||
// 按照指定的Content-Type设置MediaType,不指定的话按form处理
|
||||
String contentType = httpRequest.getHeaders().get("Content-Type"); |
||||
MediaType mediaType; |
||||
if (!"".equals(contentType)) { |
||||
mediaType = MediaType.get(contentType); |
||||
} else { |
||||
mediaType = MediaType.get(DEFAULT_CONTENT_TYPE); |
||||
} |
||||
|
||||
requestBuilder.post(RequestBody.create(mediaType, httpRequest.getData())); |
||||
} |
||||
|
||||
return requestBuilder.build(); |
||||
} |
||||
|
||||
private HttpResponse sendRequest(OkHttpClient client, Request request) throws IOException { |
||||
try (Response response = client.newCall(request).execute()) { |
||||
HttpResponse httpResponse = new HttpResponse(); |
||||
|
||||
// 取所有的响应头,如果同一个响应头字段有多个值只拿第一个
|
||||
Map<String, List<String>> responseHeaders = response.headers().toMultimap(); |
||||
Map<String, String> headerMap = new HashMap<>(); |
||||
responseHeaders.forEach((k, v) -> headerMap.put(k, v.get(0))); |
||||
httpResponse.setHeaders(headerMap); |
||||
|
||||
ResponseBody body = response.body(); |
||||
if (body != null) { |
||||
// 如果响应太大的话会导致oom,但其实大部分的请求都不会oom,因此直接使用bytes()
|
||||
// 如果请求响应特别大的话请用ByteStream
|
||||
httpResponse.setBody(body.bytes()); |
||||
} |
||||
|
||||
httpResponse.setCookies(response.header("Set-Cookie")); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
private HttpResponse doRequest(RequestMethod requestMethod, URL url, HttpRequest httpRequest, RequestClientOption requestClientOption) throws IOException { |
||||
OkHttpClient client = this.getOkhttpClient(requestClientOption); |
||||
Request request = this.buildRequest(requestMethod, url, httpRequest); |
||||
|
||||
return this.sendRequest(client, request); |
||||
} |
||||
|
||||
@Override |
||||
public HttpResponse get(URL url, HttpRequest httpRequest, RequestClientOption requestClientOption) throws IOException { |
||||
return this.doRequest(RequestMethod.GET, url, httpRequest, requestClientOption); |
||||
} |
||||
|
||||
@Override |
||||
public HttpResponse post(URL url, HttpRequest request, RequestClientOption options) throws IOException { |
||||
return this.doRequest(RequestMethod.POST, url, request, options); |
||||
} |
||||
|
||||
@Override |
||||
public HttpResponse postJson(URL url, HttpRequest request, Object requestBody, RequestClientOption options) throws IOException { |
||||
String json = new ObjectMapper().writeValueAsString(requestBody); |
||||
request.setData(json.getBytes(StandardCharsets.UTF_8)); |
||||
request.getHeaders().put("Content-Type", JSON_CONTENT_TYPE); |
||||
|
||||
return this.doRequest(RequestMethod.POST, url, request, options); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
package cn.linghang.mywust.network; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import java.util.Map; |
||||
|
||||
@Data |
||||
public class HttpRequest { |
||||
private Map<String, String> urlParams; |
||||
|
||||
private Map<String, String> headers; |
||||
|
||||
private String cookies; |
||||
|
||||
private byte[] data; |
||||
} |
@ -0,0 +1,15 @@ |
||||
package cn.linghang.mywust.network; |
||||
|
||||
import lombok.Data; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
@Data |
||||
public class HttpResponse { |
||||
private Map<String, String> headers; |
||||
|
||||
private String cookies; |
||||
|
||||
private byte[] body; |
||||
} |
@ -0,0 +1,15 @@ |
||||
package cn.linghang.mywust.network; |
||||
|
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
public class RequestClientOption { |
||||
private Proxy proxy; |
||||
private long timeout; |
||||
|
||||
@Data |
||||
public static class Proxy { |
||||
private String address; |
||||
private int port; |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
package cn.linghang.mywust.network; |
||||
|
||||
import java.net.URL; |
||||
|
||||
/** |
||||
* <p>请求类的上层封装接口,传入参数直接进行请求</p> |
||||
* |
||||
* <p>实现了这个接口的类就可以提供给core使用</p> |
||||
* |
||||
* <p>例如,可以使用okhttp和httpclient实现底层网络请求(当然也可以自己实现类似的),在上层中只需要调用get或者post即可</p> |
||||
* |
||||
* @author lenfrex |
||||
* @create 2022-10-14 20:13 |
||||
*/ |
||||
public interface Requester { |
||||
|
||||
HttpResponse get(URL url, HttpRequest request, RequestClientOption requestClientOption) throws Exception; |
||||
|
||||
HttpResponse post(URL url, HttpRequest request, RequestClientOption options) throws Exception; |
||||
|
||||
HttpResponse postJson(URL url, HttpRequest request, Object requestBody, RequestClientOption options) throws Exception; |
||||
} |
@ -0,0 +1,104 @@ |
||||
package cn.linghang.mywust.util; |
||||
|
||||
|
||||
import org.apache.commons.codec.binary.Hex; |
||||
|
||||
import javax.crypto.BadPaddingException; |
||||
import javax.crypto.Cipher; |
||||
import javax.crypto.IllegalBlockSizeException; |
||||
import javax.crypto.NoSuchPaddingException; |
||||
import java.math.BigInteger; |
||||
import java.nio.charset.StandardCharsets; |
||||
import java.security.InvalidKeyException; |
||||
import java.security.KeyFactory; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.interfaces.RSAPublicKey; |
||||
import java.security.spec.InvalidKeySpecException; |
||||
import java.security.spec.RSAPublicKeySpec; |
||||
|
||||
/** |
||||
* <h>密码加密工具类</h> |
||||
* |
||||
* <p>使用统一登陆的加密方法对密码进行加密的工具类</p> |
||||
* <p>研究发现统一认证登录对密码的加密方式只是简单的RSA NoPadding加密(虽然但是,貌似私钥也放在前端了...)</p> |
||||
* <p>加密过程大概如下:</p> |
||||
* <p>- 通过modulus模数和exponent指数生成rsa公钥</p> |
||||
* <p>- 将明文反转后用上面生成的公钥进行RSA加密,明文填充使用NoPadding填充(0填充)</p> |
||||
* <p>- 将生成的字节数组转换成16进制字符串,得到加密后的数据</p> |
||||
* <br> |
||||
* <p>希望不要再改登录方式了 [拜] </p> |
||||
* @author : lensfrex |
||||
* @description : 使用统一登陆的加密方法对密码进行加密的工具类 |
||||
* @create : 2022-09-18 14:42:06 |
||||
* @edit : 2022-10-14 14:42:06 |
||||
*/ |
||||
public class PasswordEncoder { |
||||
private static final String MODULUS = "b5eeb166e069920e80bebd1fea4829d3d1f3216f2aabe79b6c47a3c18dcee5fd22c2e7ac519cab59198ece036dcf289ea8201e2a0b9ded307f8fb704136eaeb670286f5ad44e691005ba9ea5af04ada5367cd724b5a26fdb5120cc95b6431604bd219c6b7d83a6f8f24b43918ea988a76f93c333aa5a20991493d4eb1117e7b1"; |
||||
private static final String EXPONENT = "10001"; |
||||
|
||||
private static final RSAPublicKey PUBLIC_KEY = PasswordEncoder.generatePublicKey(); |
||||
|
||||
private static Cipher cipher; |
||||
|
||||
// 单例加密/解密器
|
||||
static { |
||||
try { |
||||
cipher = Cipher.getInstance("RSA/ECB/NoPadding"); |
||||
cipher.init(Cipher.ENCRYPT_MODE, PUBLIC_KEY); |
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) { |
||||
// 会报错的地方都是因为算法不正确,这里都是固定的值,出错了就是程序写错了,在测试的时候就应该发现的问题
|
||||
// 对于调用者不必处理这种异常,但是开发测试的时候应该要打日志来发现问题
|
||||
System.err.println("加密出错"); |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 按照统一登陆的加密方法,加密密码 |
||||
* |
||||
* @param password 密码明文 |
||||
* @return 加密后的密码(小写) |
||||
*/ |
||||
public static String encodePassword(String password) { |
||||
try { |
||||
// 反转明文
|
||||
byte[] reversedTextData = new StringBuffer(password) |
||||
.reverse() |
||||
.toString() |
||||
.getBytes(StandardCharsets.UTF_8); |
||||
|
||||
// 完成加密
|
||||
return Hex.encodeHexString(cipher.doFinal(reversedTextData)); |
||||
} catch (IllegalBlockSizeException | BadPaddingException e) { |
||||
// 会报错的地方都是因为算法不正确,这里都是固定的值,出错了就是程序写错了,在测试的时候就应该发现的问题
|
||||
// 对于调用者不必处理这种异常,但是开发测试的时候应该要打日志来发现问题
|
||||
System.err.println("加密出错"); |
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
return ""; |
||||
} |
||||
|
||||
/** |
||||
* 使用modulus模数和exponent指数生成rsa公钥 |
||||
* |
||||
* @return 公钥 |
||||
*/ |
||||
private static RSAPublicKey generatePublicKey() { |
||||
try { |
||||
BigInteger mod = new BigInteger(PasswordEncoder.MODULUS, 16); |
||||
BigInteger exp = new BigInteger(PasswordEncoder.EXPONENT, 16); |
||||
|
||||
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(mod, exp); |
||||
|
||||
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpec); |
||||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) { |
||||
// 会报错的地方都是因为算法不正确,这里都是固定的值,出错了就是程序写错了,在测试的时候就应该发现的问题
|
||||
// 对于调用者不必处理这种异常,但是开发测试的时候应该要打日志来发现问题
|
||||
System.err.println("生成公钥出错"); |
||||
e.printStackTrace(); |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
} |
Loading…
Reference in new issue