├── .gitignore
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── github
│ └── echisan
│ └── wbp4j
│ ├── AbstractLoginRequest.java
│ ├── AbstractUploadRequest.java
│ ├── DefaultRetryUploadRequest.java
│ ├── LoginRequest.java
│ ├── RetryableUploadRequest.java
│ ├── SzvoneLoginRequest.java
│ ├── UploadAttributes.java
│ ├── UploadContextHolder.java
│ ├── UploadRequest.java
│ ├── UploadRequestBuilder.java
│ ├── UploadResponse.java
│ ├── WbpConstants.java
│ ├── WbpLoginRequest.java
│ ├── WbpUploadRequest.java
│ ├── WbpUploadResponse.java
│ ├── cache
│ ├── AbstractCookieContext.java
│ ├── CookieCacheAccessor.java
│ ├── CookieContext.java
│ ├── CookieHolder.java
│ ├── DbCookieCacheAccessor.java
│ └── FileCookieCacheAccessor.java
│ ├── entity
│ ├── ImageInfo.java
│ ├── PreLogin.java
│ ├── UploadResp.java
│ └── upload
│ │ ├── Data.java
│ │ ├── Pic_1.java
│ │ └── Pics.java
│ ├── exception
│ ├── LoginFailedException.java
│ ├── UploadFailedException.java
│ └── Wbp4jException.java
│ ├── http
│ ├── DefaultWbpHttpRequest.java
│ ├── DefaultWbpHttpResponse.java
│ ├── WbpHttpRequest.java
│ └── WbpHttpResponse.java
│ ├── interceptor
│ ├── CookieInterceptor.java
│ ├── InitUploadAttributesInterceptor.java
│ ├── LoginInterceptor.java
│ ├── ReCheckCookieInterceptor.java
│ └── UploadInterceptor.java
│ └── utils
│ ├── ImageSize.java
│ ├── RSAEncodeUtils.java
│ └── WbpUtils.java
└── test
└── java
└── com
└── github
└── echisan
└── wbp4j
└── UploadRequestBuilderTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Intellij IDEA ###
2 | .idea
3 | *.iws
4 | *.iml
5 | *.ipr
6 |
7 | /target/
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wbp4j
2 | > weibo picture api for java (中二一下)
3 |
4 | 使用Java实现的微博图床API,提供简单的api即可完成上传图片到微博图床,可方便集成到自己的项目当中。
5 |
6 | 如果有兴趣或奇怪的需求或者想看故事可以查看[说明文档](https://github.com/echisan/wbp4j/wiki)
7 | 如果出现任何问题欢迎提issue、欢迎提pr
8 | 如果这个项目帮助到你了欢迎star鼓励一下^^
9 |
10 | # 特色
11 | - 使用方便简单
12 | - 获取简单,直接加入maven依赖即可
13 | - cookie缓存
14 | - cookie过期自动登录
15 | - 第三方依赖少,仅依赖fastjson,logback
16 | - 自由度高,一切均可自定义配置
17 | - 可自定义缓存介质
18 | - 可[自定义重试策略](https://github.com/echisan/wbp4j/wiki/%E8%87%AA%E5%AE%9A%E4%B9%89%E9%87%8D%E8%AF%95%E7%AD%96%E7%95%A5)
19 | - 可自定义[拦截器](https://github.com/echisan/wbp4j/wiki/%E6%8B%A6%E6%88%AA%E5%99%A8)添加自己的逻辑
20 | - 仍在维护
21 |
22 | ## Maven
23 | 引入maven依赖即可
24 | ```xml
25 |
26 | com.github.echisan
27 | wbp4j
28 | 3.3
29 |
30 | ```
31 |
32 | ## 用法
33 |
34 | ### 使用默认配置
35 | **这个方式只做演示,请不要每次调用上传接口都使用UploadRequestBuilder build一次**
36 | build()方法会初始化所有的`CookieContext` `WbpHttpRequest` `LoginRequest` `Interceptor列表`等等,但是这些东西只需初始化一次,之后便是对cookie的管理。
37 |
38 | ```java
39 | UploadRequest uploadRequest = UploadRequestBuilder.buildDefault("your username", "your password");
40 | UploadResponse response = uploadRequest.upload(new File("go.png"));
41 | ```
42 |
43 | **建议写成单例,在有需要的时候拿到UploadRequest对象调用upload方法即可**
44 |
45 | ```java
46 | public enum UploadUtils {
47 | INSTANCE;
48 |
49 | private UploadRequest uploadRequest;
50 |
51 | UploadUtils() {
52 | uploadRequest = UploadRequestBuilder.buildDefault("yourUsername","yourPassword");
53 | }
54 |
55 | public UploadResponse upload(File file) throws IOException, UploadFailedException {
56 | return uploadRequest.upload(file);
57 | }
58 |
59 | public UploadRequest getUploadRequest(){
60 | return this.uploadRequest;
61 | }
62 | }
63 | ```
64 |
65 |
66 | ### 自定义配置
67 | 支持自定义拦截器,具体查看文档
68 |
69 | ```java
70 | UploadRequest uploadRequest = UploadRequestBuilder.custom("your username", "your password")
71 | .setCacheFilename("myCache")
72 | .addInterceptor(new UploadInterceptor() {
73 | @Override
74 | public boolean processBefore(UploadAttributes uploadAttributes) {
75 | System.out.println("hello world");
76 | return true;
77 | }
78 | @Override
79 | public void processAfter(UploadResponse uploadResponse) {
80 | }
81 | }).build();
82 |
83 | UploadResponse uploadResponse = uploadRequest.upload(new File(""));
84 | ```
85 |
86 | 返回结果
87 | ```json
88 | {
89 | "message": "上传图片成功",
90 | "imageInfo": {
91 | "pid": "7fa15162gy1g1e5o2vlmwj20dn07e0t7",
92 | "width": 491,
93 | "height": 266,
94 | "size": 27707,
95 | "large": "https://ws3.sinaimg.cn/large/7fa15162gy1g1e5o2vlmwj20dn07e0t7.jpg",
96 | "middle": "https://ws3.sinaimg.cn/mw690/7fa15162gy1g1e5o2vlmwj20dn07e0t7.jpg",
97 | "small": "https://ws3.sinaimg.cn/small/7fa15162gy1g1e5o2vlmwj20dn07e0t7.jpg"
98 | },
99 | "result": "SUCCESS"
100 | }
101 | ```
102 |
103 | ## 使用
104 |
105 | ## Spring中使用
106 |
107 | ```java
108 | @SpringBootApplication
109 | public class DemoApplication {
110 | @Bean
111 | public UploadRequest uploadRequest() {
112 | return UploadRequestBuilder.buildDefault("your username", "your password");
113 | }
114 | public static void main(String[] args) {
115 | SpringApplication.run(DemoApplication.class, args);
116 | }
117 |
118 | }
119 |
120 | @RestController
121 | @RequestMapping("/wbp4j")
122 | class TestController {
123 |
124 | @Autowired
125 | private UploadRequest uploadRequest;
126 |
127 | @PostMapping
128 | public WbpUploadResponse uploadImage(@RequestPart("file") MultipartFile multipartFile) throws IOException, UploadFailedException {
129 | UploadResponse upload = uploadRequest.upload(multipartFile.getBytes());
130 | // 推荐先做一个判断
131 | // if (response.getResult().equals(UploadResponse.ResultStatus.SUCCESS)) {
132 | // 做自己的响应封装
133 | //}
134 | return (WbpUploadResponse) upload;
135 | }
136 |
137 | }
138 | ```
139 |
140 | **注意:UploadRequest是一个线程安全的类,可直接注入到你想使用的类中去,不要每次调用上传api时都去调用`UploadRequestBuilder.build()`是没有任何意义的**
141 |
142 |
143 | ## 更新日志
144 |
145 | ### 2019.04.23
146 | 修复了修改缓存文件名不生效的问题 .
147 | 增加了登陆失败返回的信息以及对unicode的解码 .
148 | ### 2019.03.30
149 | 优化了重试代码 .
150 | 修复了重试机制还是不生效的问题 .
151 | ### 2019.03.25
152 | 修复了重试机制不生效的问题 .
153 | ### 2019.03.24
154 | 修复了部署到服务器后无法登陆的问题 .
155 | 修复了返回的图片格式问题 .
156 | ### 2019.03.23
157 | 重构代码,代码结构更清晰稳定,减低各模块的耦合 .
158 | 修复缓存文件位置错误的问题 .
159 | 修复上传图片格式问题 .
160 | 支持了上传gif .
161 | ### 2018.11.08
162 | 重构了代码,减少第三方依赖,目前只依赖logging,fastjson .
163 | 将包上传至官方仓库使用更方便 .
164 |
165 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | com.github.echisan
8 | wbp4j
9 | 3.3
10 |
11 | wbp4j
12 | a simple java api for weibo picture bed
13 | https://github.com/echisan/wbp4j
14 |
15 | jar
16 |
17 |
18 |
19 | The Apache Software License, Version 2.0
20 | http://www.apache.org/licenses/LICENSE-2.0.txt
21 |
22 |
23 |
24 |
25 | scm:git:git@github.com:echisan/wbp4j.git
26 | scm:git:git@github.com:echisan/wbp4j.git
27 | git@github.com:echisan/wbp4j.git
28 |
29 |
30 |
31 |
32 | echisan
33 |
34 |
35 |
36 |
37 | UTF-8
38 | 1.8
39 | 1.8
40 |
41 |
42 |
43 |
44 | junit
45 | junit
46 | 4.13.1
47 | test
48 |
49 |
50 |
51 | com.alibaba
52 | fastjson
53 | 1.2.83
54 |
55 |
56 | ch.qos.logback
57 | logback-classic
58 | 1.2.3
59 |
60 |
61 |
62 |
63 |
64 | release
65 |
66 |
67 |
68 |
69 | maven-clean-plugin
70 | 3.0.0
71 |
72 |
73 |
74 | org.apache.maven.plugins
75 | maven-source-plugin
76 | 3.0.1
77 |
78 |
79 | package
80 |
81 | jar-no-fork
82 |
83 |
84 |
85 |
86 |
87 | maven-compiler-plugin
88 | 3.7.0
89 |
90 |
91 | maven-surefire-plugin
92 | 2.20.1
93 |
94 |
95 | maven-jar-plugin
96 | 3.0.2
97 |
98 |
99 | maven-install-plugin
100 | 2.5.2
101 |
102 |
103 | maven-deploy-plugin
104 | 2.8.2
105 |
106 |
107 |
108 |
109 |
110 | org.apache.maven.plugins
111 | maven-compiler-plugin
112 |
113 | 8
114 | 8
115 |
116 |
117 |
118 |
119 | org.apache.maven.plugins
120 | maven-source-plugin
121 |
122 |
123 | package
124 |
125 | jar-no-fork
126 |
127 |
128 |
129 |
130 |
131 | org.apache.maven.plugins
132 | maven-javadoc-plugin
133 | 2.9.1
134 |
135 |
136 | package
137 |
138 | jar
139 |
140 |
141 |
142 |
143 |
144 |
145 | org.apache.maven.plugins
146 | maven-gpg-plugin
147 | 1.6
148 |
149 |
150 | verify
151 |
152 | sign
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | oss
162 | https://oss.sonatype.org/content/repositories/snapshots/
163 |
164 |
165 | oss
166 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
167 |
168 |
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/AbstractLoginRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * 扩展了LoginRequest接口
8 | * 毕竟需要用户名跟密码
9 | *
10 | * 但是不应该每个上层调用者都需要知道用户名密码
11 | * 所以LoginRequest中只存在了一个空参数的登陆方法login()
12 | *
13 | * 账号密码只需要本类以及本类的子类知道可以了
14 | */
15 | public abstract class AbstractLoginRequest implements LoginRequest {
16 | private String username;
17 | private String password;
18 |
19 | public void setUsernamePassword(String username, String password) {
20 | this.username = username;
21 | this.password = password;
22 | }
23 |
24 | public String getUsername() {
25 | return username;
26 | }
27 |
28 | public String getPassword() {
29 | return password;
30 | }
31 |
32 | /**
33 | * 检查一下调用者填写的账号,
34 | * 早点检查一下是否合法
35 | * 免得分明没填账号就在那里请求登陆,不浪费资源了
36 | *
37 | * @return 如果没问题就return true
38 | */
39 | public boolean checkAccount() {
40 | if (username == null || password == null) {
41 | return false;
42 | }
43 | return !username.trim().equals("") && !password.trim().equals("");
44 | }
45 |
46 | /**
47 | * 生成默认的登陆请求头
48 | *
49 | * @return 请求头
50 | */
51 | protected Map getDefaultLoginHeader() {
52 | Map header = new HashMap<>();
53 | header.put("Referer", "https://weibo.com/");
54 | header.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.79 Safari/537.36\"");
55 | header.put("Content-Type", "application/x-www-form-urlencoded");
56 | header.put("Accept", "text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8");
57 | header.put("Accept-Encoding", "deflate, br");
58 | header.put("Accept-Language", "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2");
59 | return header;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/AbstractUploadRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import com.github.echisan.wbp4j.exception.UploadFailedException;
4 | import com.github.echisan.wbp4j.interceptor.UploadInterceptor;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.io.File;
9 | import java.io.FileInputStream;
10 | import java.io.FileNotFoundException;
11 | import java.io.IOException;
12 | import java.util.ArrayList;
13 | import java.util.Base64;
14 | import java.util.HashMap;
15 | import java.util.List;
16 |
17 | /**
18 | * 本类是一个上传图片接口的抽象类,已经被设计成线程安全的类
19 | * 允许多线程情况下访问本类
20 | *
21 | * 因为upload(byte[] bytes),upload(File file)到最后都是依赖upload(String base64)
22 | * 所以只需实现upload(String base64)接口即可
23 | *
24 | * 但是调用该接口需要登陆获取cookie,过期则需要重新登陆
25 | * 根据单一职责原则,会发现上传接口与登陆功能强耦合
26 | * 上传不应该直接依赖登陆,所以引入拦截器模式
27 | *
28 | * 调用上传接口upload(byte[] bytes)等接口时,会先通过一系列预定义好的拦截器
29 | * 最后才会真正调用上传操作
30 | *
31 | * 由于上传参数,接口路径有可能会发生变化,为了尽量符合开闭原则
32 | * 将真正上传的接口doUpload(UploadAttributes uploadAttributes)交由子类去实现
33 | * 因该可以应付后续可能的改变吧
34 | */
35 | public abstract class AbstractUploadRequest implements UploadRequest {
36 | private static final Logger logger = LoggerFactory.getLogger(AbstractUploadRequest.class);
37 |
38 | /**
39 | * 拦截器,用于执行上传前/后的操作
40 | * 如果有其中一个返回false则不会上传
41 | */
42 | private final List uploadInterceptors;
43 |
44 | public AbstractUploadRequest(List uploadInterceptors) {
45 | this.uploadInterceptors = uploadInterceptors;
46 | }
47 |
48 | public AbstractUploadRequest() {
49 | this(new ArrayList<>());
50 | }
51 |
52 |
53 | /**
54 | * 提供一个包装方法,虽然入参是File,但是内部会把file转换成base64再调用上传接口上传
55 | *
56 | * @param file image file
57 | * @return uploadResponse
58 | * @throws IOException ioe
59 | * @throws UploadFailedException ufe
60 | */
61 | @Override
62 | public UploadResponse upload(File file) throws IOException, UploadFailedException {
63 | return upload(imageToBase64(file));
64 | }
65 |
66 | /**
67 | * 提供一个byte数组的一个包装方法,可以方便的为springMVC中的multipartFile提供上传接口
68 | * spring中仅需upload(multipartFile.getBytes())即可
69 | *
70 | * @param bytes bytes数组
71 | * @return uploadResponse
72 | * @throws IOException ioe
73 | * @throws UploadFailedException ufe
74 | */
75 | @Override
76 | public UploadResponse upload(byte[] bytes) throws IOException, UploadFailedException {
77 | return upload(Base64.getEncoder().encodeToString(bytes));
78 | }
79 |
80 | /**
81 | * 真正的上传接口,主要上传base64数据到微博接口
82 | *
83 | * @param base64 b64
84 | * @return uploadResponse
85 | * @throws IOException ioe
86 | * @throws UploadFailedException ufe
87 | */
88 | @Override
89 | public UploadResponse upload(String base64) throws IOException, UploadFailedException {
90 |
91 | UploadAttributes attributes = new UploadAttributes();
92 | attributes.setBase64(base64);
93 | attributes.setContext(new HashMap<>());
94 |
95 | UploadContextHolder.setUploadAttributes(attributes);
96 |
97 | // pre process
98 | for (UploadInterceptor interceptor : uploadInterceptors) {
99 | boolean processBefore = interceptor.processBefore(UploadContextHolder.getUploadAttributes());
100 | // 如果return false则说明有拦截器进行了拦截,终止交易
101 | if (!processBefore) {
102 | WbpUploadResponse response = new WbpUploadResponse();
103 | response.setResult(UploadResponse.ResultStatus.FAILED);
104 | Object o = attributes.getContext().get(WbpConstants.UA_ERROR_MESSAGE);
105 | response.setMessage(o != null ? (String) o : "拦截器:[ " + interceptor.getClass().getName() + " ]拦截了上传操作");
106 | return response;
107 | }
108 | }
109 |
110 | UploadResponse uploadResponse = doUpload(UploadContextHolder.getUploadAttributes());
111 |
112 | // post process
113 | for (UploadInterceptor interceptor : uploadInterceptors) {
114 | interceptor.processAfter(uploadResponse);
115 | }
116 |
117 | // reset contextHolder attributes
118 | UploadContextHolder.resetAttributes();
119 |
120 | return uploadResponse;
121 | }
122 |
123 | /**
124 | * 真正的上传逻辑在这里,upload(String base64)负责调用本方法
125 | * 由于采用了拦截器模式,处理拦截器列表不应该直接与上传操作耦合
126 | * 所以应该交由子类去负责上传,子类只需实现本方法即可
127 | *
128 | * @param uploadAttributes uploadAttributes上传图片的参数
129 | * @return uploadResponse
130 | * @throws IOException ioe
131 | * @throws UploadFailedException ufe
132 | */
133 | protected abstract UploadResponse doUpload(UploadAttributes uploadAttributes) throws IOException, UploadFailedException;
134 |
135 | /**
136 | * 负责将file文件转成base64,主要为了适用于上传图片的接口
137 | *
138 | * @param imageFile the imageFile
139 | * @return image base64
140 | */
141 | private String imageToBase64(File imageFile) {
142 | String base64Image = "";
143 | try (FileInputStream imageInFile = new FileInputStream(imageFile)) {
144 | // Reading a Image file from file system
145 | byte[] imageData = new byte[(int) imageFile.length()];
146 | int read = imageInFile.read(imageData);
147 | logger.debug("read imageFile: [" + read + "]");
148 | base64Image = Base64.getEncoder().encodeToString(imageData);
149 | } catch (FileNotFoundException e) {
150 | logger.error("Image not found" + e);
151 | } catch (IOException ioe) {
152 | logger.error("Exception while reading the Image " + ioe);
153 | }
154 | return base64Image;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/DefaultRetryUploadRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | /**
4 | * 默认的重试策略
5 | *
6 | * 本策略只会在cookie过期导致的上传失败后进行重试
7 | * 而非登陆失败或者获取cookie失败而进行重试
8 | */
9 | public class DefaultRetryUploadRequest extends RetryableUploadRequest {
10 | public DefaultRetryUploadRequest(AbstractUploadRequest uploadRequest) {
11 | super(uploadRequest);
12 | }
13 |
14 | /**
15 | * 只要返回的结果是RETRY则进行重试
16 | * 因为在com.github.echisan.wbp4j.WbpUploadRequest中定义了该响应结果
17 | *
18 | * @param uploadResponse uploadResponse
19 | * @return 是否需要重试
20 | */
21 | @Override
22 | public boolean shouldRetry(UploadResponse uploadResponse) {
23 | return uploadResponse.getResult().equals(UploadResponse.ResultStatus.RETRY);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/LoginRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import com.github.echisan.wbp4j.exception.LoginFailedException;
4 |
5 | /**
6 | * 登陆的接口
7 | * 因为似乎发现了还有另外一种登陆方式
8 | * 所以将方法抽离出来了
9 | */
10 | public interface LoginRequest {
11 |
12 | /**
13 | * 空参方法,上层调用者只管调用
14 | * 假如接口设计成login(String username,String password)的话
15 | * 每个调用者都需要保存,或者通过某种方法获取到用户名密码
16 | * 这样就与登陆功能产生耦合了
17 | * 最好的方法我觉得应该就是保存在本接口的子类或者实现类中
18 | *
19 | * 因为不允许运行时动态加载用户名密码或密码,所以肯定是线程安全的
20 | * 不过可以考虑添加该功能,有需要的话在说好了
21 | *
22 | * @throws LoginFailedException lfe
23 | */
24 | void login() throws LoginFailedException;
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/RetryableUploadRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import com.github.echisan.wbp4j.exception.UploadFailedException;
4 |
5 | import java.io.IOException;
6 |
7 | /**
8 | * 可重试的上传模板
9 | *
10 | * 请保证本类的子类应该是线程安全的
11 | * 具体重试逻辑已经实现
12 | *
13 | * 而重试的策略可能有多个,所以是否进行重试的判断交由子类去决定
14 | */
15 | public abstract class RetryableUploadRequest extends AbstractUploadRequest {
16 |
17 | private AbstractUploadRequest uploadRequest;
18 |
19 | public RetryableUploadRequest(AbstractUploadRequest uploadRequest) {
20 | this.uploadRequest = uploadRequest;
21 | }
22 |
23 | public RetryableUploadRequest() {
24 | }
25 |
26 | /**
27 | * 应该是用while还是用if呢,纠结
28 | *
29 | * @param base64 b64
30 | * @return uploadResponse
31 | * @throws IOException ioe
32 | * @throws UploadFailedException ufe
33 | */
34 | @Override
35 | public UploadResponse upload(String base64) throws IOException, UploadFailedException {
36 | UploadResponse response = uploadRequest.upload(base64);
37 | if (shouldRetry(response)) {
38 | return uploadRequest.upload(base64);
39 | }
40 | return response;
41 | }
42 |
43 | /**
44 | * 交由实现类去操作
45 | *
46 | * @param uploadAttributes uploadAttributes
47 | * @return UploadResponse
48 | * @throws IOException IOException
49 | * @throws UploadFailedException UploadFailedException
50 | */
51 | @Override
52 | protected UploadResponse doUpload(UploadAttributes uploadAttributes) throws IOException, UploadFailedException {
53 | return uploadRequest.doUpload(uploadAttributes);
54 | }
55 |
56 | /**
57 | * 根据子类的重试策略判断是否重试
58 | * 可以有多种判断策略
59 | *
60 | * @param uploadResponse uploadResponse
61 | * @return 如果返回true则会进行重试,如果未false则直接返回结果
62 | */
63 | public abstract boolean shouldRetry(UploadResponse uploadResponse);
64 |
65 | public AbstractUploadRequest getUploadRequest() {
66 | return uploadRequest;
67 | }
68 |
69 | public void setUploadRequest(AbstractUploadRequest uploadRequest) {
70 | this.uploadRequest = uploadRequest;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/SzvoneLoginRequest.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import com.alibaba.fastjson.JSON;
4 | import com.github.echisan.wbp4j.cache.AbstractCookieContext;
5 | import com.github.echisan.wbp4j.exception.LoginFailedException;
6 | import com.github.echisan.wbp4j.http.DefaultWbpHttpRequest;
7 | import com.github.echisan.wbp4j.http.WbpHttpRequest;
8 | import com.github.echisan.wbp4j.http.WbpHttpResponse;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | import java.io.IOException;
13 | import java.util.Base64;
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 |
18 | import static java.net.HttpURLConnection.HTTP_OK;
19 |
20 | /**
21 | * 另一种登陆方案
22 | * 来自 @szvone: https://github.com/szvone/imgApi
23 | */
24 | public class SzvoneLoginRequest extends AbstractLoginRequest {
25 | private static final Logger logger = LoggerFactory.getLogger(SzvoneLoginRequest.class);
26 | private static final String loginUrl = "https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.15)&_=";
27 |
28 | private WbpHttpRequest wbpHttpRequest;
29 |
30 | private AbstractCookieContext cookieContext;
31 |
32 | public SzvoneLoginRequest(WbpHttpRequest wbpHttpRequest, AbstractCookieContext cookieContext) {
33 | this.wbpHttpRequest = wbpHttpRequest;
34 | this.cookieContext = cookieContext;
35 | }
36 |
37 | public SzvoneLoginRequest(AbstractCookieContext cookieContext) {
38 | this(new DefaultWbpHttpRequest(), cookieContext);
39 | }
40 |
41 | @Override
42 | public void login() throws LoginFailedException {
43 |
44 | try {
45 | WbpHttpResponse wbpHttpResponse = wbpHttpRequest.doPost(loginUrl, getDefaultLoginHeader(), getLoginParams());
46 |
47 | if (wbpHttpResponse.getStatusCode() != HTTP_OK) {
48 | throw new LoginFailedException("登陆失败,响应状态码为:" + wbpHttpResponse.getStatusCode());
49 | }
50 |
51 | logger.debug("login...");
52 | logger.debug("responseHeader:" + wbpHttpResponse.getHeader());
53 | logger.debug("responseBody:" + wbpHttpResponse.getBody());
54 |
55 | LoginResponseEntity loginResponseEntity = JSON.parseObject(wbpHttpResponse.getBody(), LoginResponseEntity.class);
56 |
57 | if (loginResponseEntity.getRetcode().equals("101")) {
58 | LoginFailedResponse loginFailedResponse;
59 | String reason;
60 | try {
61 | loginFailedResponse = JSON.parseObject(wbpHttpResponse.getBody(), LoginFailedResponse.class);
62 | reason = loginFailedResponse.getReason();
63 | } catch (Exception e) {
64 | logger.warn("can not parse str: " + wbpHttpResponse.getBody());
65 | reason = wbpHttpResponse.getBody();
66 | }
67 | throw new LoginFailedException("登陆失败,原因:" + reason);
68 | }
69 |
70 | if (!loginResponseEntity.retcode.equals("0")) {
71 | throw new LoginFailedException("登陆失败,原因未知。" + wbpHttpResponse.getBody());
72 | }
73 |
74 | logger.debug("login successful!");
75 |
76 | String cookieFromHeaders = getCookieFromHeaders(wbpHttpResponse.getHeader());
77 | cookieContext.saveCookie(cookieFromHeaders);
78 |
79 | logger.debug("save cookie to cache success!");
80 |
81 | } catch (IOException e) {
82 | e.printStackTrace();
83 | throw new LoginFailedException("登陆失败,无法发送请求。");
84 | }
85 |
86 | }
87 |
88 | private Map getLoginParams() {
89 | Map params = new HashMap<>();
90 | params.put("entry", "sso");
91 | params.put("gateway", "1");
92 | params.put("from", "null");
93 | params.put("savestate", "30");
94 | params.put("useticket", "0");
95 | params.put("pagerefer", "");
96 | params.put("vsnf", "1");
97 | params.put("su", Base64.getEncoder().encodeToString(getUsername().getBytes()));
98 | params.put("service", "sso");
99 | params.put("sp", getPassword());
100 | params.put("sr", "1024*768");
101 | params.put("encoding", "UTF-8");
102 | params.put("cdult", "3");
103 | params.put("domain", "sina.com.cn");
104 | params.put("prelt", "0");
105 | params.put("returntype", "TEXT");
106 | return params;
107 | }
108 |
109 |
110 | public static class LoginResponseEntity {
111 | private String retcode;
112 |
113 | private String uid;
114 |
115 | private String nick;
116 |
117 | private List crossDomainUrlList;
118 |
119 | public void setRetcode(String retcode) {
120 | this.retcode = retcode;
121 | }
122 |
123 | public String getRetcode() {
124 | return this.retcode;
125 | }
126 |
127 | public void setUid(String uid) {
128 | this.uid = uid;
129 | }
130 |
131 | public String getUid() {
132 | return this.uid;
133 | }
134 |
135 | public void setNick(String nick) {
136 | this.nick = nick;
137 | }
138 |
139 | public String getNick() {
140 | return this.nick;
141 | }
142 |
143 | public void setString(List crossDomainUrlList) {
144 | this.crossDomainUrlList = crossDomainUrlList;
145 | }
146 |
147 | public List getString() {
148 | return this.crossDomainUrlList;
149 | }
150 |
151 | }
152 |
153 | public static class LoginFailedResponse {
154 | private Integer retcode;
155 | private String reason;
156 |
157 | public Integer getRetcode() {
158 | return retcode;
159 | }
160 |
161 | public void setRetcode(Integer retcode) {
162 | this.retcode = retcode;
163 | }
164 |
165 | public String getReason() {
166 | return reason;
167 | }
168 |
169 | public void setReason(String reason) {
170 | this.reason = reason;
171 | }
172 | }
173 |
174 | private String getCookieFromHeaders(Map headers) throws LoginFailedException {
175 | String str = headers.get("Set-Cookie");
176 | if (str == null) {
177 | throw new LoginFailedException("登陆失败,没有返回cookie");
178 | }
179 |
180 | if (str.contains("SUB=")) {
181 | String[] split = str.split(";");
182 | String cookie = null;
183 | for (String s : split) {
184 | if (s.contains("SUB=")) {
185 | cookie = s.trim();
186 | break;
187 | }
188 | }
189 | return cookie;
190 | }
191 | logger.error(str);
192 | throw new LoginFailedException("登陆失败,未获取到必须的cookie字段。");
193 | }
194 | }
195 |
196 |
197 |
--------------------------------------------------------------------------------
/src/main/java/com/github/echisan/wbp4j/UploadAttributes.java:
--------------------------------------------------------------------------------
1 | package com.github.echisan.wbp4j;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | /**
7 | * 上传图片所需要的的参数
8 | */
9 | public class UploadAttributes {
10 |
11 | /**
12 | * 上传图片的url
13 | */
14 | private String url;
15 |
16 | /**
17 | * 调用上传图片接口时所需要的请求头
18 | * 主要关注点在cookie
19 | *
20 | * # 我寻思还是直接new一个了吧
21 | */
22 | private Map headers = new HashMap<>();
23 |
24 | /**
25 | * 需要上传的图片的base64
26 | */
27 | private String base64;
28 |
29 | /**
30 | * 上下文
31 | * 调用上传接口并不需要本信息
32 | * 主要提供能本字段去供给拦截器通信或叫传参
33 | * 可以网context中添加信息
34 | * 让拦截器去判断,去获取相关信息
35 | */
36 | private Map