├── .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 context = new HashMap<>(); 37 | 38 | public String getUrl() { 39 | return url; 40 | } 41 | 42 | public void setUrl(String url) { 43 | this.url = url; 44 | } 45 | 46 | /** 47 | * 由于主要需要关注的是请求头中的cookie 48 | * 为此特意提供一个方法,当然不用也是完全可以的 49 | * @param cookie cookie 50 | */ 51 | public void setCookie(String cookie) { 52 | this.headers.put("Cookie", cookie); 53 | } 54 | 55 | public String getCookie() { 56 | return this.headers.get("Cookie"); 57 | } 58 | 59 | public Map getHeaders() { 60 | return headers; 61 | } 62 | 63 | public void addAllHeaders(Map headers) { 64 | this.headers.putAll(headers); 65 | } 66 | 67 | /** 68 | * 因为已经初始化了headers,所以如果调用set的话 69 | * 需要将初始化的header全部清除 70 | * 71 | * @param headers the headers 72 | */ 73 | public void setHeaders(Map headers) { 74 | this.headers.clear(); 75 | this.headers.putAll(headers); 76 | } 77 | 78 | public String getBase64() { 79 | return base64; 80 | } 81 | 82 | public void setBase64(String base64) { 83 | this.base64 = base64; 84 | } 85 | 86 | public Map getContext() { 87 | return context; 88 | } 89 | 90 | public void setContext(Map context) { 91 | this.context = context; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/UploadContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | /** 4 | * 为每条线程绑定一个上传参数对象 5 | * com.github.echisan.wbp4j.UploadAttributes在多线程环境下是不安全的 6 | * 在多线程环境下,每条线程调用com.github.echisan.wbp4j.AbstractUploadRequest中的 7 | * doUpload(UploadAttributes uploadAttributes)方法,每个拦截器都会更改当中的内容 8 | *

9 | * 换句话说就是,多条线程中的多个拦截器去操作同一个UploadAttributes的后果是不可想象的 10 | * 所以,通过本类,为每个线程绑定一个UploadAttributes,每条线程互不干扰 11 | * 所以每条线程中的一整条拦截器链处理的是自己的UploadAttributes 12 | *

13 | * 抄一下spring中的RequestContextHolder 14 | * 我也用抽象类 15 | */ 16 | public abstract class UploadContextHolder { 17 | 18 | /** 19 | * 绑定每条线程的参数 20 | */ 21 | private static final ThreadLocal uploadAttributesHolder = new ThreadLocal<>(); 22 | 23 | 24 | /** 25 | * 获取当前线程的UploadAttributes对象 26 | * 27 | * @return 当前线程的UploadAttributes 28 | */ 29 | public static UploadAttributes getUploadAttributes() { 30 | UploadAttributes uploadAttributes = uploadAttributesHolder.get(); 31 | 32 | if (uploadAttributes == null) { 33 | throw new NullPointerException("upload context holder cannot find current uploadRequest.."); 34 | } 35 | return uploadAttributes; 36 | } 37 | 38 | 39 | /** 40 | * 为当前线程绑定一个UploadAttributes 41 | * 42 | * @param uploadAttributes UploadAttributes 43 | */ 44 | public static void setUploadAttributes(UploadAttributes uploadAttributes) { 45 | if (uploadAttributes == null) { 46 | throw new IllegalArgumentException("uploadAttributes cannot be null!"); 47 | } 48 | uploadAttributesHolder.set(uploadAttributes); 49 | } 50 | 51 | /** 52 | * 清除当前线程的UploadAttributes对象 53 | *

54 | * 当每一个请求或线程结束了操作 55 | * 应该把当前线程的UploadAttributes对象移除,避免内存泄漏 56 | */ 57 | public static void resetAttributes() { 58 | uploadAttributesHolder.remove(); 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/UploadRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.github.echisan.wbp4j.exception.UploadFailedException; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | 8 | /** 9 | * 上传图片的接口 10 | *

11 | * 提供三种上传方式 12 | * 虽然到最后都是调用upload(String base64)去实现的了。。 13 | * 主要提供upload(String base64)的包装方法 14 | * 不需要上层去实现了 15 | */ 16 | public interface UploadRequest { 17 | 18 | UploadResponse upload(String base64) throws IOException, UploadFailedException; 19 | 20 | UploadResponse upload(byte[] bytes) throws IOException, UploadFailedException; 21 | 22 | UploadResponse upload(File file) throws IOException, UploadFailedException; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/UploadRequestBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.github.echisan.wbp4j.cache.AbstractCookieContext; 4 | import com.github.echisan.wbp4j.cache.CookieCacheAccessor; 5 | import com.github.echisan.wbp4j.cache.CookieContext; 6 | import com.github.echisan.wbp4j.cache.FileCookieCacheAccessor; 7 | import com.github.echisan.wbp4j.http.DefaultWbpHttpRequest; 8 | import com.github.echisan.wbp4j.http.WbpHttpRequest; 9 | import com.github.echisan.wbp4j.interceptor.*; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * 用于构建一个UploadRequest的类 16 | * 由于需要多个组建进行配合,组装起来稍有复杂,所以提供一个类完成组装操作 17 | *

18 | * 虽然想提供一个类供调用者根据自身需求去更换某个组件 19 | * 但是想了好久都不知道该怎么去实现 20 | */ 21 | public class UploadRequestBuilder { 22 | 23 | public static UploadRequest buildDefault(String username, String password) { 24 | return new Builder(username, password).build(); 25 | } 26 | 27 | public static Builder custom(String username, String password) { 28 | return new Builder(username, password); 29 | } 30 | 31 | public static class Builder { 32 | private CookieCacheAccessor cookieCacheAccessor; 33 | private AbstractCookieContext abstractCookieContext; 34 | private List uploadInterceptors = new ArrayList<>(); 35 | private AbstractLoginRequest loginRequest; 36 | private WbpHttpRequest wbpHttpRequest = new DefaultWbpHttpRequest(); 37 | private String username; 38 | private String password; 39 | private boolean retryable = true; 40 | private RetryableUploadRequest retryableUploadRequest; 41 | private String cookieFileName = null; 42 | 43 | public Builder(String username, String password) { 44 | this.username = username; 45 | this.password = password; 46 | } 47 | 48 | public Builder addInterceptor(UploadInterceptor uploadInterceptor) { 49 | this.uploadInterceptors.add(uploadInterceptor); 50 | return this; 51 | } 52 | 53 | public Builder addInterceptors(List uploadInterceptors) { 54 | this.uploadInterceptors.addAll(uploadInterceptors); 55 | return this; 56 | } 57 | 58 | public Builder addInterceptor(int index, UploadInterceptor uploadInterceptor) { 59 | this.uploadInterceptors.add(index, uploadInterceptor); 60 | return this; 61 | } 62 | 63 | public Builder setInterceptors(List uploadInterceptors) { 64 | if (uploadInterceptors == null || uploadInterceptors.size() == 0) { 65 | throw new IllegalArgumentException("拦截器必须不能为空!"); 66 | } 67 | this.uploadInterceptors = uploadInterceptors; 68 | return this; 69 | } 70 | 71 | public Builder isRetryable(boolean retryable) { 72 | this.retryable = retryable; 73 | return this; 74 | } 75 | 76 | public Builder setRetryStrategy(RetryableUploadRequest retryStrategy) { 77 | this.retryableUploadRequest = retryStrategy; 78 | return this; 79 | } 80 | 81 | public Builder setCacheFilename(String filename) { 82 | this.cookieFileName = filename; 83 | return this; 84 | } 85 | 86 | public Builder setLoginRequest(AbstractLoginRequest loginRequest){ 87 | if (loginRequest == null){ 88 | throw new NullPointerException("loginRequest cannot be null"); 89 | } 90 | this.loginRequest = loginRequest; 91 | return this; 92 | } 93 | 94 | private void buildCookieCacheAccessor(){ 95 | if (cookieCacheAccessor == null){ 96 | if (cookieFileName != null){ 97 | cookieCacheAccessor = new FileCookieCacheAccessor(cookieFileName); 98 | }else { 99 | cookieCacheAccessor = new FileCookieCacheAccessor(); 100 | } 101 | } 102 | } 103 | 104 | public WbpHttpRequest getWbpHttpRequest(){ 105 | assert wbpHttpRequest != null; 106 | return wbpHttpRequest; 107 | } 108 | 109 | public AbstractCookieContext getCookieContext(){ 110 | assert abstractCookieContext != null; 111 | return abstractCookieContext; 112 | } 113 | 114 | private void buildCookieContext(){ 115 | assert cookieCacheAccessor != null; 116 | // build cookieContext finish. 117 | abstractCookieContext = new CookieContext(cookieCacheAccessor); 118 | } 119 | 120 | private void buildLoginRequest(){ 121 | if (loginRequest == null){ 122 | loginRequest = new SzvoneLoginRequest(wbpHttpRequest,abstractCookieContext); 123 | } 124 | loginRequest.setUsernamePassword(this.username,this.password); 125 | } 126 | 127 | private void buildInterceptors(){ 128 | InitUploadAttributesInterceptor initInterceptor = new InitUploadAttributesInterceptor(); 129 | CookieInterceptor cookieInterceptor = new CookieInterceptor(abstractCookieContext); 130 | buildLoginRequest(); 131 | LoginInterceptor loginInterceptor = new LoginInterceptor(loginRequest); 132 | ReCheckCookieInterceptor recheckInterceptor = new ReCheckCookieInterceptor(abstractCookieContext); 133 | this.uploadInterceptors.add(initInterceptor); 134 | this.uploadInterceptors.add(cookieInterceptor); 135 | this.uploadInterceptors.add(loginInterceptor); 136 | this.uploadInterceptors.add(recheckInterceptor); 137 | } 138 | 139 | 140 | public UploadRequest build() { 141 | if (username == null || password == null) { 142 | throw new IllegalArgumentException("用户名密码不能为空"); 143 | } 144 | 145 | WbpUploadRequest wbpUploadRequest = new WbpUploadRequest(uploadInterceptors, wbpHttpRequest); 146 | 147 | buildCookieCacheAccessor(); 148 | buildCookieContext(); 149 | buildInterceptors(); 150 | 151 | if (retryable) { 152 | if (this.retryableUploadRequest == null) { 153 | retryableUploadRequest = new DefaultRetryUploadRequest(wbpUploadRequest); 154 | } else { 155 | if (this.retryableUploadRequest.getUploadRequest() == null) { 156 | this.retryableUploadRequest.setUploadRequest(wbpUploadRequest); 157 | } 158 | } 159 | return retryableUploadRequest; 160 | } 161 | return wbpUploadRequest; 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/UploadResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.github.echisan.wbp4j.entity.ImageInfo; 4 | 5 | /** 6 | * 响应结果接口 7 | * 虽然好像并没有什么必要写成接口 8 | */ 9 | public interface UploadResponse { 10 | 11 | enum ResultStatus { 12 | SUCCESS, FAILED, RETRY 13 | } 14 | 15 | void setResult(ResultStatus rs); 16 | 17 | ResultStatus getResult(); 18 | 19 | void setMessage(String message); 20 | 21 | String getMessage(); 22 | 23 | ImageInfo getImageInfo(); 24 | 25 | void setImageInfo(ImageInfo imageInfo); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/WbpConstants.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | /** 4 | * 一些常量 5 | */ 6 | public class WbpConstants { 7 | 8 | /** 9 | * 标注需要登陆 10 | */ 11 | public static final String REQUIRE_LOGIN = "require_login"; 12 | 13 | /** 14 | * 标注需要重新登陆 15 | */ 16 | public static final String REQUIRE_RETRY_LOGIN = "require_retry_login"; 17 | 18 | /** 19 | * UploadAttribute.getContext()中用于保存错误信息的key 20 | */ 21 | public static final String UA_ERROR_MESSAGE = "upload_attributes_error_message"; 22 | 23 | /** 24 | * cookie长度阈值,如果长度小于50基本上可以断定该cookie是无效的 25 | */ 26 | public static final int COOKIE_LENGTH_THRESHOLD = 50; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/WbpLoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.echisan.wbp4j.entity.PreLogin; 5 | import com.github.echisan.wbp4j.cache.AbstractCookieContext; 6 | import com.github.echisan.wbp4j.exception.LoginFailedException; 7 | import com.github.echisan.wbp4j.http.DefaultWbpHttpRequest; 8 | import com.github.echisan.wbp4j.http.WbpHttpRequest; 9 | import com.github.echisan.wbp4j.http.WbpHttpResponse; 10 | import com.github.echisan.wbp4j.utils.RSAEncodeUtils; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import javax.crypto.BadPaddingException; 15 | import javax.crypto.IllegalBlockSizeException; 16 | import javax.crypto.NoSuchPaddingException; 17 | import java.io.IOException; 18 | import java.io.UnsupportedEncodingException; 19 | import java.net.URLDecoder; 20 | import java.security.InvalidKeyException; 21 | import java.security.NoSuchAlgorithmException; 22 | import java.security.spec.InvalidKeySpecException; 23 | import java.util.Base64; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import static java.net.HttpURLConnection.HTTP_OK; 28 | 29 | /** 30 | * 默认的登陆实现 31 | */ 32 | public class WbpLoginRequest extends AbstractLoginRequest { 33 | private static final Logger logger = LoggerFactory.getLogger(WbpLoginRequest.class); 34 | private static final String preLoginUrl = "https://login.sina.com.cn/sso/prelogin.php"; 35 | private static final String loginUrl = "https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)"; 36 | private final Map preLoginHeaders; 37 | private final Map preLoginParams; 38 | private final Map loginHeaders; 39 | 40 | /** 41 | * HttpRequest接口 42 | * 默认使用DefaultWbpHttpRequest 43 | * 也可以自己采用HttpClient实现该接口 44 | */ 45 | private WbpHttpRequest wbpHttpRequest; 46 | 47 | private AbstractCookieContext cookieContext; 48 | 49 | public WbpLoginRequest(AbstractCookieContext cookieContext) { 50 | this(new DefaultWbpHttpRequest(), cookieContext); 51 | } 52 | 53 | public WbpLoginRequest(WbpHttpRequest wbpHttpRequest, AbstractCookieContext cookieContext) { 54 | this.wbpHttpRequest = wbpHttpRequest; 55 | this.cookieContext = cookieContext; 56 | this.preLoginHeaders = getDefaultPreLoginHeader(); 57 | this.preLoginParams = getDefaultPreLoginParams(); 58 | this.loginHeaders = getDefaultLoginHeader(); 59 | } 60 | 61 | public WbpLoginRequest(Map preLoginHeaders, 62 | Map preLoginParams, 63 | Map loginHeaders, 64 | WbpHttpRequest wbpHttpRequest) { 65 | 66 | this.preLoginHeaders = preLoginHeaders; 67 | this.preLoginParams = preLoginParams; 68 | this.loginHeaders = loginHeaders; 69 | this.wbpHttpRequest = wbpHttpRequest; 70 | } 71 | 72 | private PreLogin preLogin() throws LoginFailedException { 73 | 74 | if (!checkAccount()) { 75 | throw new LoginFailedException("username or password cannot be null or empty."); 76 | } 77 | 78 | // put username to pre login params map. 79 | setUsernameToPreLoginParams(); 80 | 81 | try { 82 | WbpHttpResponse response = wbpHttpRequest.doGet(preLoginUrl, preLoginHeaders, preLoginParams); 83 | 84 | if (response.getStatusCode() != HTTP_OK) { 85 | throw new LoginFailedException( 86 | "pre login failed.\n" + 87 | "[ response code ] " + response.getStatusCode() + 88 | "\n[ response headers ]" + response.getHeader() + 89 | "\n[ response body ] " + response.getBody() 90 | ); 91 | } 92 | 93 | return JSON.parseObject(response.getBody(), PreLogin.class); 94 | 95 | } catch (IOException e) { 96 | e.printStackTrace(); 97 | throw new LoginFailedException("pre login failed!! message: [" + e.getMessage() + "]"); 98 | } 99 | } 100 | 101 | @Override 102 | public void login() throws LoginFailedException { 103 | 104 | PreLogin preLogin = preLogin(); 105 | 106 | Map loginParams = createLoginParams(preLogin); 107 | 108 | try { 109 | WbpHttpResponse response = wbpHttpRequest.doPost(loginUrl, loginHeaders, loginParams); 110 | 111 | if (response.getStatusCode() != HTTP_OK) { 112 | throw new LoginFailedException(createWbpLoginExceptionMessage(response)); 113 | } 114 | 115 | // get cookie from response headers 116 | Map header = response.getHeader(); 117 | String cookie = header.get("set-cookie"); 118 | if (cookie == null) { 119 | cookie = header.get("Set-Cookie"); 120 | } 121 | 122 | // 如果cookie并不符合预期 123 | if (cookie.length() < WbpConstants.COOKIE_LENGTH_THRESHOLD) { 124 | logger.debug("response cookie: " + cookie); 125 | String reason = getReason(response.getBody()); 126 | if (reason != null) { 127 | throw new LoginFailedException("登陆失败,原因:" + reason); 128 | } 129 | throw new LoginFailedException("登陆失败,未知原因。响应数据为:" + response.getBody()); 130 | } 131 | 132 | cookieContext.saveCookie(cookie); 133 | 134 | } catch (IOException e) { 135 | e.printStackTrace(); 136 | throw new LoginFailedException("login failed. message: " + e.getMessage()); 137 | } 138 | 139 | } 140 | 141 | private Map getDefaultPreLoginHeader() { 142 | Map header = new HashMap<>(); 143 | header.put("Referer", "https://weibo.com/"); 144 | 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"); 145 | return header; 146 | } 147 | 148 | private Map getDefaultPreLoginParams() { 149 | Map params = new HashMap<>(); 150 | params.put("client", "ssologin.js(v1.4.19)"); 151 | params.put("entry", "weibo"); 152 | params.put("rsakt", "mod"); 153 | params.put("checkpin", "1"); 154 | params.put("_", String.valueOf(System.currentTimeMillis())); 155 | return params; 156 | } 157 | 158 | private void setUsernameToPreLoginParams() { 159 | if (!preLoginParams.containsKey("su")) { 160 | preLoginParams.put("su", getUsername()); 161 | } 162 | } 163 | 164 | private String createWbpLoginExceptionMessage(WbpHttpResponse response) { 165 | return "[ login failed message ]" + "do request login url failed" + 166 | "\n[ response code ] " + response.getStatusCode() + 167 | "\n[ response headers ]" + response.getHeader() + 168 | "\n[ response body ]" + response.getBody(); 169 | } 170 | 171 | /** 172 | * 生成登陆需要的参数,也没什么办法写好看一点了 173 | * 这方法也写的太丑了 174 | * 175 | * @param preLogin 预登陆结果 176 | * @return 登陆参数 177 | * @throws LoginFailedException lfe 178 | */ 179 | private Map createLoginParams(PreLogin preLogin) throws LoginFailedException { 180 | // 根据微博加密js中密码拼接的方法 181 | String pwd = preLogin.getServertime() + "\t" + preLogin.getNonce() + "\n" + getPassword(); 182 | 183 | Map params = new HashMap<>(); 184 | params.put("encoding", "UTF-8"); 185 | params.put("entry", "weibo"); 186 | params.put("from", ""); 187 | params.put("gateway", "1"); 188 | params.put("nonce", preLogin.getNonce()); 189 | params.put("pagerefer", "https://login.sina.com.cn/crossdomain2.php?action=logout&r=https%3A%2F%2Fweibo.com%2Flogout.php%3Fbackurl%3D%252F"); 190 | params.put("prelt", "76"); 191 | params.put("pwencode", "rsa2"); 192 | params.put("qrcode_flag", "false"); 193 | params.put("returntype", "META"); 194 | params.put("rsakv", preLogin.getRsakv()); 195 | params.put("savestate", "7"); 196 | params.put("servertime", String.valueOf(preLogin.getServertime())); 197 | params.put("service", "miniblog"); 198 | try { 199 | params.put("sp", RSAEncodeUtils.encode(pwd, preLogin.getPubkey(), "10001")); 200 | } catch (NoSuchAlgorithmException | NoSuchPaddingException | 201 | InvalidKeySpecException | InvalidKeyException | 202 | IllegalBlockSizeException | BadPaddingException e) { 203 | e.printStackTrace(); 204 | throw new LoginFailedException("login failed. encrypt password failed. message: " + e.getMessage()); 205 | } 206 | params.put("sr", "1920*1080"); 207 | params.put("su", Base64.getEncoder().encodeToString(getUsername().getBytes())); 208 | params.put("url", "https://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack"); 209 | params.put("useticket", "1"); 210 | params.put("vsnf", "1"); 211 | return params; 212 | } 213 | 214 | 215 | /** 216 | * 从登陆的响应结果中解析,获取登陆失败中响应的原因 217 | * 218 | * @param responseHtml response 219 | * @return reason 220 | * @throws UnsupportedEncodingException uee 221 | */ 222 | private String getReason(String responseHtml) throws UnsupportedEncodingException { 223 | 224 | String location = getLocation(responseHtml); 225 | 226 | if (location == null) { 227 | return null; 228 | } 229 | 230 | int i = location.lastIndexOf("&"); 231 | if (i != -1) { 232 | String substring = location.substring(i); 233 | String replace = substring.replace("&reason=", ""); 234 | return URLDecoder.decode(replace, "GBK"); 235 | } 236 | return null; 237 | } 238 | 239 | /** 240 | * 从登陆的响应结果中解析 241 | * 242 | * @param responseHtml responseHtml 243 | * @return location未知的内容 244 | */ 245 | private String getLocation(String responseHtml) { 246 | int replace = responseHtml.indexOf("replace"); 247 | if (replace == -1) { 248 | return null; 249 | } 250 | String substring = responseHtml.substring(replace + 9); 251 | int i = substring.lastIndexOf(");"); 252 | return substring.substring(0, i - 1); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/WbpUploadRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.github.echisan.wbp4j.entity.ImageInfo; 5 | import com.github.echisan.wbp4j.entity.UploadResp; 6 | import com.github.echisan.wbp4j.entity.upload.Pic_1; 7 | import com.github.echisan.wbp4j.exception.UploadFailedException; 8 | import com.github.echisan.wbp4j.http.DefaultWbpHttpRequest; 9 | import com.github.echisan.wbp4j.http.WbpHttpRequest; 10 | import com.github.echisan.wbp4j.http.WbpHttpResponse; 11 | import com.github.echisan.wbp4j.interceptor.UploadInterceptor; 12 | import com.github.echisan.wbp4j.utils.ImageSize; 13 | import com.github.echisan.wbp4j.utils.WbpUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.io.IOException; 18 | import java.util.List; 19 | 20 | import static java.net.HttpURLConnection.HTTP_OK; 21 | 22 | /** 23 | * 本来是UploadRequest的一个默认实现,也是线程安全的类 24 | */ 25 | public class WbpUploadRequest extends AbstractUploadRequest { 26 | private static final Logger logger = LoggerFactory.getLogger(WbpUploadRequest.class); 27 | 28 | /** 29 | * http请求工具类 30 | * 1、目前默认用的是HttpURLConnection实现 31 | * 2、其实之前也有用HttpClient实现过的,为了减少依赖去掉了 32 | * 如果有需要可以添加上 33 | */ 34 | private final WbpHttpRequest wbpHttpRequest; 35 | 36 | public WbpUploadRequest(List uploadInterceptors) { 37 | this(uploadInterceptors, new DefaultWbpHttpRequest()); 38 | } 39 | 40 | public WbpUploadRequest(List uploadInterceptors, WbpHttpRequest wbpHttpRequest) { 41 | super(uploadInterceptors); 42 | this.wbpHttpRequest = wbpHttpRequest; 43 | } 44 | 45 | /** 46 | * 上传图片的请求头 47 | * 主要为请求接口提供必要的字段,比如 cookie 48 | */ 49 | @Override 50 | protected UploadResponse doUpload(UploadAttributes uploadAttributes) throws IOException, UploadFailedException { 51 | 52 | WbpUploadResponse uploadResponse = new WbpUploadResponse(); 53 | uploadResponse.setResult(UploadResponse.ResultStatus.FAILED); 54 | 55 | WbpHttpResponse response = wbpHttpRequest.doPostMultiPart(uploadAttributes.getUrl(), 56 | uploadAttributes.getHeaders(), 57 | uploadAttributes.getBase64()); 58 | if (response.getStatusCode() == HTTP_OK) { 59 | logger.debug(createWbpUploadDebugMessage("", response)); 60 | 61 | UploadResp uploadResp = parseBodyJson(response.getBody()); 62 | if (uploadResp == null) { 63 | uploadResponse.setMessage("无法解析返回结果,上传失败"); 64 | return uploadResponse; 65 | } 66 | 67 | Pic_1 pic = uploadResp.getData().getPics().getPic_1(); 68 | int ret = pic.getRet(); 69 | 70 | if (ret == -1) { 71 | uploadResponse.setResult(UploadResponse.ResultStatus.RETRY); 72 | uploadResponse.setMessage("cookie已过期,请重新获取"); 73 | logger.debug("cookie was expiration"); 74 | return uploadResponse; 75 | } 76 | if (ret == -2) { 77 | uploadResponse.setResult(UploadResponse.ResultStatus.FAILED); 78 | uploadResponse.setMessage("上传的图片为空"); 79 | return uploadResponse; 80 | } 81 | if (ret == -11) { 82 | uploadResponse.setResult(UploadResponse.ResultStatus.FAILED); 83 | uploadResponse.setMessage("上传的图片格式不正确"); 84 | return uploadResponse; 85 | } 86 | if (ret != 1) { 87 | uploadResponse.setMessage("未知问题,retCode=" + ret + "可自行搜索该ret寻求结果"); 88 | return uploadResponse; 89 | } 90 | 91 | uploadResponse.setResult(UploadResponse.ResultStatus.SUCCESS); 92 | uploadResponse.setMessage("上传图片成功"); 93 | uploadResponse.setImageInfo(buildImageInfo(pic)); 94 | return uploadResponse; 95 | 96 | } else { 97 | throw new UploadFailedException("状态码都不是200了,这谁都顶不住"); 98 | } 99 | } 100 | 101 | private ImageInfo buildImageInfo(Pic_1 pic) { 102 | ImageInfo info = new ImageInfo(); 103 | info.setPid(pic.getPid()); 104 | info.setSize(pic.getSize()); 105 | info.setWidth(pic.getWidth()); 106 | info.setHeight(pic.getHeight()); 107 | 108 | String pid = pic.getPid(); 109 | info.setLarge(WbpUtils.getImageUrl(pid, ImageSize.large, true)); 110 | info.setMiddle(WbpUtils.getImageUrl(pid, ImageSize.mw690, true)); 111 | info.setSmall(WbpUtils.getImageUrl(pid, ImageSize.small, true)); 112 | return info; 113 | } 114 | 115 | private UploadResp parseBodyJson(String body) { 116 | int i = body.indexOf(""); 117 | if (i == -1) { 118 | return null; 119 | } 120 | String substring = body.substring(i + 9); 121 | return JSON.parseObject(substring, UploadResp.class); 122 | } 123 | 124 | private String createWbpUploadDebugMessage(String message, WbpHttpResponse response) { 125 | return message + 126 | "\n[ response code ] " + response.getStatusCode() + 127 | "\n[ response headers ]" + response.getHeader() + 128 | "\n[ response body ]\n" + response.getBody(); 129 | } 130 | 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/WbpUploadResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.github.echisan.wbp4j.entity.ImageInfo; 4 | 5 | /** 6 | * Created by echisan on 2018/11/5 7 | */ 8 | public class WbpUploadResponse implements UploadResponse { 9 | private ResultStatus resultStatus; 10 | private String message; 11 | private ImageInfo imageInfo; 12 | 13 | @Override 14 | public void setResult(ResultStatus rs) { 15 | this.resultStatus = rs; 16 | } 17 | 18 | @Override 19 | public ResultStatus getResult() { 20 | return this.resultStatus; 21 | } 22 | 23 | @Override 24 | public void setMessage(String message) { 25 | this.message = message; 26 | } 27 | 28 | @Override 29 | public String getMessage() { 30 | return this.message; 31 | } 32 | 33 | @Override 34 | public ImageInfo getImageInfo() { 35 | return this.imageInfo; 36 | } 37 | 38 | @Override 39 | public void setImageInfo(ImageInfo imageInfo) { 40 | this.imageInfo = imageInfo; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return "WbpUploadResponse{" + 46 | "resultStatus=" + resultStatus + 47 | ", message='" + message + '\'' + 48 | ", imageInfo=" + imageInfo + 49 | '}'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/AbstractCookieContext.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 提供统一的获取cookie接口 7 | * 负责管理内存中的cookie以及缓存文件中的cookie 8 | * 无需上层调用者知道具体实现逻辑 9 | * 只需要简单的get与set即可 10 | */ 11 | public abstract class AbstractCookieContext implements CookieCacheAccessor{ 12 | 13 | /** 14 | * 持久层缓存的接口 15 | */ 16 | protected final CookieCacheAccessor accessor; 17 | 18 | public AbstractCookieContext(CookieCacheAccessor accessor) { 19 | if (accessor != null) { 20 | this.accessor = accessor; 21 | } else { 22 | this.accessor = new FileCookieCacheAccessor(); 23 | } 24 | } 25 | 26 | public AbstractCookieContext() { 27 | this(new FileCookieCacheAccessor()); 28 | } 29 | 30 | public CookieCacheAccessor getAccessor() { 31 | return accessor; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/CookieCacheAccessor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 一个用于访问cookie持久层的接口 7 | * 定义了两个接口,上层调用着只需关注获取以及保存两个主要功能 8 | * 而无需在意是从数据库获取还是从文件中获取 9 | */ 10 | public interface CookieCacheAccessor { 11 | 12 | /** 13 | * 保存cookie到持久层介质中 14 | * 15 | * @param cookie cookie 16 | * @throws IOException ioe 17 | */ 18 | void saveCookie(String cookie) throws IOException; 19 | 20 | String getCookie() throws IOException; 21 | 22 | void clear() throws IOException; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/CookieContext.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * cookie缓存的上下文 10 | * 是com.github.echisan.wbp4j.cache.AbstractCookieContext的实现子类 11 | * 实现具体的逻辑 12 | */ 13 | public class CookieContext extends AbstractCookieContext { 14 | private static final Logger logger = LoggerFactory.getLogger(CookieContext.class); 15 | 16 | public CookieContext(CookieCacheAccessor accessor) { 17 | super(accessor); 18 | } 19 | 20 | public CookieContext() { 21 | } 22 | 23 | /** 24 | * 当保存cookie时,需要同时保存到内存以及缓存中,以方便下一次的调用获取 25 | * 26 | * @param cookie cookie 27 | * @throws IOException IOException 28 | */ 29 | @Override 30 | public void saveCookie(String cookie) throws IOException { 31 | CookieHolder.setCookie(cookie); 32 | accessor.saveCookie(cookie); 33 | } 34 | 35 | @Override 36 | public String getCookie() throws IOException { 37 | String cookie = CookieHolder.getCookie(); 38 | if (cookie == null) { 39 | 40 | logger.debug("cannot find cookie in memory, read from persistence cache."); 41 | cookie = accessor.getCookie(); 42 | } 43 | if (cookie == null) { 44 | 45 | logger.debug("cannot find cookie in persistence cache."); 46 | 47 | return null; 48 | } 49 | 50 | CookieHolder.setCookie(cookie); 51 | 52 | return cookie; 53 | } 54 | 55 | @Override 56 | public void clear() throws IOException { 57 | CookieHolder.setCookie(null); 58 | accessor.clear(); 59 | logger.debug("clear cookie cache success!"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/CookieHolder.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | /** 4 | * 负责管理保存在内存中的cookie缓存 5 | *

6 | * 本类必须是线程安全的,cookie可能会被多个线程并发访问 7 | */ 8 | public class CookieHolder { 9 | private static volatile String _cookie; 10 | 11 | public synchronized static String getCookie() { 12 | return _cookie; 13 | } 14 | 15 | public synchronized static void setCookie(String cookie) { 16 | _cookie = cookie; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/DbCookieCacheAccessor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 假如有人想把cookie缓存到数据库里也不是不行 7 | * 可以继承本类实现saveCookie方法与getCookie方法 8 | * 或者直接实现CookieCacheAccessor借口也可 9 | *

10 | * 因为上层调用者只关心获取而不关心如何获取 11 | * 需要什么参数进行获取,比如在数据库中可能需要根据id获取响应的cookie 12 | * 即 getCookie(int id) 13 | * 但接口只有一个空参数的方法,所以获取哪一个cookie需要根据自己的策略去调整 14 | * 尽量只暴露getCookie()方法 15 | *

16 | * 可以在子类中添加供向变量,但一定要确保线程安全 17 | */ 18 | public abstract class DbCookieCacheAccessor implements CookieCacheAccessor { 19 | public abstract void setCookieToDb(String cookie) throws IOException; 20 | 21 | public abstract String getCookieFromDb() throws IOException; 22 | 23 | @Override 24 | public void saveCookie(String cookie) throws IOException { 25 | setCookieToDb(cookie); 26 | } 27 | 28 | @Override 29 | public String getCookie() throws IOException { 30 | return getCookieFromDb(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/cache/FileCookieCacheAccessor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.cache; 2 | 3 | import com.github.echisan.wbp4j.WbpConstants; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.*; 8 | 9 | /** 10 | * 该类用于访问Cookie的缓存文件 11 | */ 12 | public class FileCookieCacheAccessor implements CookieCacheAccessor { 13 | private static final Logger logger = LoggerFactory.getLogger(FileCookieCacheAccessor.class); 14 | /** 15 | * 默认的缓存文件名称,其实也可以是路径,但要符合对应操作系统的路径格式 16 | */ 17 | private static final String DEFAULT_CACHE_FILE_NAME = "wbpcookie"; 18 | 19 | /** 20 | * 缓存文件名称或路径 21 | */ 22 | private final String cacheFileName; 23 | 24 | /** 25 | * custom cookie file name 26 | * 27 | * @param cookieCacheFileName the cookieCacheFileName 28 | */ 29 | public FileCookieCacheAccessor(String cookieCacheFileName) { 30 | this.cacheFileName = cookieCacheFileName; 31 | } 32 | 33 | /** 34 | * use default cookie cache file name 35 | */ 36 | public FileCookieCacheAccessor() { 37 | this(DEFAULT_CACHE_FILE_NAME); 38 | } 39 | 40 | /** 41 | * save cookie to file 42 | * 43 | * @param cookie the cookie 44 | * @throws IOException the IOException 45 | */ 46 | @Override 47 | public void saveCookie(String cookie) throws IOException { 48 | File file = new File(cacheFileName); 49 | if (!file.exists()) { 50 | boolean newFile = file.createNewFile(); 51 | if (!newFile) { 52 | throw new IOException("create cookie cache failed!! filePath:[ " + file.getPath() + " ]."); 53 | } 54 | } 55 | FileOutputStream fos = new FileOutputStream(file); 56 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos)); 57 | bw.write(cookie); 58 | bw.flush(); 59 | bw.close(); 60 | if (logger.isDebugEnabled()) { 61 | logger.debug("write cookie to file success!! filePath: [" + file.getAbsolutePath() + " ]."); 62 | } 63 | } 64 | 65 | /** 66 | * get cookie from cache file 67 | * 68 | * @return cookie 69 | * @throws IOException the IOException 70 | */ 71 | @Override 72 | public String getCookie() throws IOException { 73 | File file = new File(cacheFileName); 74 | if (!file.exists()) { 75 | if (logger.isDebugEnabled()) { 76 | logger.debug("can not find cookie cache file. filePath: [ " + file.getAbsolutePath() + "]. "); 77 | } 78 | return null; 79 | } 80 | BufferedReader br = new BufferedReader(new FileReader(file)); 81 | String cookie = br.readLine(); 82 | br.close(); 83 | 84 | if (!isCookieCacheLegal(cookie)) { 85 | logger.info("because the cookie length less 50,assert this cookie is not correct!"); 86 | boolean delete = file.delete(); 87 | if (!delete) { 88 | throw new IOException("could not delete the incorrect cookie file!! filePath:[ " + file.getAbsolutePath() + " ]."); 89 | } 90 | return null; 91 | } 92 | logger.debug("found cookie cache in cache file. [" + file.getAbsolutePath()+" ]"); 93 | return cookie; 94 | } 95 | 96 | @Override 97 | public void clear() throws IOException { 98 | File file = new File(cacheFileName); 99 | if (file.exists()){ 100 | boolean delete = file.delete(); 101 | if (delete){ 102 | logger.debug("clear cache file success!"); 103 | }else { 104 | logger.warn("can not delete cache file,this may cause refresh cookie problem."); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * 判断缓存文件中的的cookie是否合法 111 | * 112 | * @param cookie the cookie 113 | * @return is cookie cache legal 114 | */ 115 | private boolean isCookieCacheLegal(String cookie) { 116 | if (cookie == null){ 117 | return false; 118 | } 119 | return cookie.length() > WbpConstants.COOKIE_LENGTH_THRESHOLD; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/ImageInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity; 2 | 3 | /** 4 | * Created by echisan on 2018/6/14 5 | */ 6 | public class ImageInfo { 7 | 8 | // 照片id 9 | private String pid; 10 | // 宽度 11 | private Integer width; 12 | // 长度 13 | private Integer height; 14 | // 大小 15 | private Integer size; 16 | // 原图url 17 | private String large; 18 | // 中等尺寸 19 | private String middle; 20 | // 缩略图 21 | private String small; 22 | 23 | public ImageInfo() { 24 | } 25 | 26 | public ImageInfo(String pid, 27 | Integer width, 28 | Integer height, 29 | Integer size, 30 | String large, 31 | String middle, 32 | String small) { 33 | this.pid = pid; 34 | this.width = width; 35 | this.height = height; 36 | this.size = size; 37 | this.large = large; 38 | this.middle = middle; 39 | this.small = small; 40 | } 41 | 42 | public String getPid() { 43 | return pid; 44 | } 45 | 46 | public void setPid(String pid) { 47 | this.pid = pid; 48 | } 49 | 50 | public Integer getWidth() { 51 | return width; 52 | } 53 | 54 | public void setWidth(Integer width) { 55 | this.width = width; 56 | } 57 | 58 | public Integer getHeight() { 59 | return height; 60 | } 61 | 62 | public void setHeight(Integer height) { 63 | this.height = height; 64 | } 65 | 66 | public Integer getSize() { 67 | return size; 68 | } 69 | 70 | public void setSize(Integer size) { 71 | this.size = size; 72 | } 73 | 74 | public String getLarge() { 75 | return large; 76 | } 77 | 78 | public void setLarge(String large) { 79 | this.large = large; 80 | } 81 | 82 | public String getMiddle() { 83 | return middle; 84 | } 85 | 86 | public void setMiddle(String middle) { 87 | this.middle = middle; 88 | } 89 | 90 | public String getSmall() { 91 | return small; 92 | } 93 | 94 | public void setSmall(String small) { 95 | this.small = small; 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "ImageInfo{" + 101 | "pid='" + pid + '\'' + 102 | ", width=" + width + 103 | ", height=" + height + 104 | ", size=" + size + 105 | ", large='" + large + '\'' + 106 | ", middle='" + middle + '\'' + 107 | ", small='" + small + '\'' + 108 | '}'; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/PreLogin.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity; 2 | 3 | /** 4 | * Created by echisan on 2018/6/13 5 | */ 6 | public class PreLogin { 7 | 8 | private Integer retcode; 9 | private Long servertime; 10 | private String pcid; 11 | private String nonce; 12 | private String pubkey; 13 | private String rsakv; 14 | private Integer is_openlock; 15 | private Integer showpin; 16 | private Integer exectime; 17 | 18 | public Integer getRetcode() { 19 | return retcode; 20 | } 21 | 22 | public void setRetcode(Integer retcode) { 23 | this.retcode = retcode; 24 | } 25 | 26 | public Long getServertime() { 27 | return servertime; 28 | } 29 | 30 | public void setServertime(Long servertime) { 31 | this.servertime = servertime; 32 | } 33 | 34 | public String getPcid() { 35 | return pcid; 36 | } 37 | 38 | public void setPcid(String pcid) { 39 | this.pcid = pcid; 40 | } 41 | 42 | public String getNonce() { 43 | return nonce; 44 | } 45 | 46 | public void setNonce(String nonce) { 47 | this.nonce = nonce; 48 | } 49 | 50 | public String getPubkey() { 51 | return pubkey; 52 | } 53 | 54 | public void setPubkey(String pubkey) { 55 | this.pubkey = pubkey; 56 | } 57 | 58 | public String getRsakv() { 59 | return rsakv; 60 | } 61 | 62 | public void setRsakv(String rsakv) { 63 | this.rsakv = rsakv; 64 | } 65 | 66 | public Integer getIs_openlock() { 67 | return is_openlock; 68 | } 69 | 70 | public void setIs_openlock(Integer is_openlock) { 71 | this.is_openlock = is_openlock; 72 | } 73 | 74 | public Integer getShowpin() { 75 | return showpin; 76 | } 77 | 78 | public void setShowpin(Integer showpin) { 79 | this.showpin = showpin; 80 | } 81 | 82 | public Integer getExectime() { 83 | return exectime; 84 | } 85 | 86 | public void setExectime(Integer exectime) { 87 | this.exectime = exectime; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "PreLogin{" + 93 | "retcode=" + retcode + 94 | ", servertime=" + servertime + 95 | ", pcid='" + pcid + '\'' + 96 | ", nonce='" + nonce + '\'' + 97 | ", pubkey='" + pubkey + '\'' + 98 | ", rsakv='" + rsakv + '\'' + 99 | ", is_openlock=" + is_openlock + 100 | ", showpin=" + showpin + 101 | ", exectime=" + exectime + 102 | '}'; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/UploadResp.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity; 2 | 3 | import com.github.echisan.wbp4j.entity.upload.Data; 4 | 5 | /** 6 | * Created by echisan on 2018/6/14 7 | */ 8 | public class UploadResp { 9 | 10 | private String code; 11 | private Data data; 12 | 13 | public String getCode() { 14 | return code; 15 | } 16 | 17 | public void setCode(String code) { 18 | this.code = code; 19 | } 20 | 21 | public Data getData() { 22 | return data; 23 | } 24 | 25 | public void setData(Data data) { 26 | this.data = data; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "UploadResp{" + 32 | "code='" + code + '\'' + 33 | ", data=" + data + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/upload/Data.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity.upload; 2 | 3 | /** 4 | * Created by echisan on 2018/6/14 5 | */ 6 | public class Data { 7 | private int count; 8 | 9 | private String data; 10 | 11 | private Pics pics; 12 | 13 | public void setCount(int count){ 14 | this.count = count; 15 | } 16 | public int getCount(){ 17 | return this.count; 18 | } 19 | public void setData(String data){ 20 | this.data = data; 21 | } 22 | public String getData(){ 23 | return this.data; 24 | } 25 | public void setPics(Pics pics){ 26 | this.pics = pics; 27 | } 28 | public Pics getPics(){ 29 | return this.pics; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Data{" + 35 | "count=" + count + 36 | ", data='" + data + '\'' + 37 | ", pics=" + pics + 38 | '}'; 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/upload/Pic_1.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity.upload; 2 | 3 | /** 4 | * Created by echisan on 2018/6/14 5 | */ 6 | public class Pic_1 { 7 | 8 | private int width; 9 | 10 | private int size; 11 | 12 | private int ret; 13 | 14 | private int height; 15 | 16 | private String name; 17 | 18 | private String pid; 19 | 20 | public int getWidth() { 21 | return this.width; 22 | } 23 | 24 | public void setWidth(int width) { 25 | this.width = width; 26 | } 27 | 28 | public int getSize() { 29 | return this.size; 30 | } 31 | 32 | public void setSize(int size) { 33 | this.size = size; 34 | } 35 | 36 | public int getRet() { 37 | return this.ret; 38 | } 39 | 40 | public void setRet(int ret) { 41 | this.ret = ret; 42 | } 43 | 44 | public int getHeight() { 45 | return this.height; 46 | } 47 | 48 | public void setHeight(int height) { 49 | this.height = height; 50 | } 51 | 52 | public String getName() { 53 | return this.name; 54 | } 55 | 56 | public void setName(String name) { 57 | this.name = name; 58 | } 59 | 60 | public String getPid() { 61 | return this.pid; 62 | } 63 | 64 | public void setPid(String pid) { 65 | this.pid = pid; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "Pic_1{" + 71 | "width=" + width + 72 | ", size=" + size + 73 | ", ret=" + ret + 74 | ", height=" + height + 75 | ", name='" + name + '\'' + 76 | ", pid='" + pid + '\'' + 77 | '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/entity/upload/Pics.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.entity.upload; 2 | 3 | /** 4 | * Created by echisan on 2018/6/14 5 | */ 6 | public class Pics { 7 | private Pic_1 pic_1; 8 | 9 | public void setPic_1(Pic_1 pic_1) { 10 | this.pic_1 = pic_1; 11 | } 12 | 13 | public Pic_1 getPic_1() { 14 | return this.pic_1; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "Pics{" + 20 | "pic_1=" + pic_1 + 21 | '}'; 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/exception/LoginFailedException.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.exception; 2 | 3 | /** 4 | * Created by echisan on 2018/11/5 5 | */ 6 | public class LoginFailedException extends Wbp4jException { 7 | 8 | public LoginFailedException() { 9 | } 10 | 11 | public LoginFailedException(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/exception/UploadFailedException.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.exception; 2 | 3 | public class UploadFailedException extends Wbp4jException { 4 | 5 | public UploadFailedException() { 6 | } 7 | 8 | public UploadFailedException(String message) { 9 | super(message); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/exception/Wbp4jException.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.exception; 2 | 3 | /** 4 | * Created by echisan on 2018/6/14 5 | */ 6 | public class Wbp4jException extends Exception { 7 | 8 | public Wbp4jException() { 9 | } 10 | 11 | public Wbp4jException(String message) { 12 | super(message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/http/DefaultWbpHttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.http; 2 | 3 | import java.io.*; 4 | import java.net.HttpURLConnection; 5 | import java.net.URL; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import static java.net.HttpURLConnection.HTTP_OK; 12 | 13 | /** 14 | * com.github.echisan.wbp4j.http.WbpHttpRequest默认实现 15 | * 采用java.net.HttpURLConnection实现 16 | * 当初也拿HttpClient实现过,为了减少依赖,后来删了 17 | * 采用HttpURLConnection实现 18 | *

19 | * 20 | * 总觉得这个header不安全,找个时间改一下 21 | * Created by echisan on 2018/11/5 22 | */ 23 | public class DefaultWbpHttpRequest implements WbpHttpRequest { 24 | private final Map header; 25 | 26 | public DefaultWbpHttpRequest() { 27 | this.header = new HashMap<>(); 28 | } 29 | 30 | /* 31 | * 初始化header 32 | * @param header 33 | */ 34 | public DefaultWbpHttpRequest(Map header) { 35 | this.header = header; 36 | } 37 | 38 | @Override 39 | public WbpHttpResponse doGet(String url) throws IOException { 40 | return doGet(url, this.header, null); 41 | } 42 | 43 | @Override 44 | public WbpHttpResponse doGet(String url, Map params) throws IOException { 45 | return doGet(url, this.header, params); 46 | } 47 | 48 | @Override 49 | public WbpHttpResponse doGet(String url, Map header, Map params) throws IOException { 50 | if (params != null) { 51 | url = url + "?" + convertParams(params); 52 | } 53 | 54 | URL u = new URL(url); 55 | HttpURLConnection connection = (HttpURLConnection) u.openConnection(); 56 | connection.setRequestMethod("GET"); 57 | 58 | if (header != null) { 59 | header.forEach(connection::setRequestProperty); 60 | } 61 | 62 | connection.connect(); 63 | return new DefaultWbpHttpResponse( 64 | connection.getResponseCode(), 65 | getHeaderFromConnection(connection), 66 | getBodyFromConnection(connection) 67 | ); 68 | } 69 | 70 | 71 | @Override 72 | public WbpHttpResponse doPost(String url) throws IOException { 73 | return doPost(url, null); 74 | } 75 | 76 | @Override 77 | public WbpHttpResponse doPost(String url, Map params) throws IOException { 78 | return doPost(url, this.header, params); 79 | } 80 | 81 | 82 | @Override 83 | public WbpHttpResponse doPost(String url, Map header, Map params) throws IOException { 84 | 85 | URL u = new URL(url); 86 | HttpURLConnection connection = (HttpURLConnection) u.openConnection(); 87 | connection.setRequestMethod("POST"); 88 | connection.setDoInput(true); 89 | connection.setDoOutput(true); 90 | connection.setUseCaches(false); 91 | 92 | if (header != null) { 93 | header.forEach(connection::setRequestProperty); 94 | } 95 | connection.connect(); 96 | 97 | if (params != null) { 98 | String requestBody = convertParams(params); 99 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream())); 100 | bw.write(requestBody); 101 | bw.flush(); 102 | bw.close(); 103 | } 104 | return new DefaultWbpHttpResponse( 105 | connection.getResponseCode(), 106 | getHeaderFromConnection(connection), 107 | getBodyFromConnection(connection) 108 | ); 109 | } 110 | 111 | 112 | /* 113 | * 增添header,新的header将替换旧的 114 | * @param header 115 | */ 116 | @Override 117 | public void setHeader(Map header) { 118 | Set> entries = header.entrySet(); 119 | entries.forEach(stringStringEntry -> { 120 | this.header.put(stringStringEntry.getKey(), stringStringEntry.getValue()); 121 | }); 122 | } 123 | 124 | @Override 125 | public WbpHttpResponse doPostMultiPart(String url, Map header, String content) throws IOException { 126 | URL u = new URL(url); 127 | HttpURLConnection connection = (HttpURLConnection) u.openConnection(); 128 | connection.setRequestMethod("POST"); 129 | connection.setDoInput(true); 130 | connection.setDoOutput(true); 131 | connection.setUseCaches(false); 132 | 133 | if (header != null) { 134 | header.forEach(connection::setRequestProperty); 135 | } 136 | String END_LINE = "\r\n"; 137 | String TWO = "--"; 138 | String boundary = "===" + System.currentTimeMillis() + "==="; 139 | String contentType = "multipart/form-data; boundary=" + boundary; 140 | connection.setRequestProperty("Content-Type", contentType); 141 | StringBuilder bodyBulider = new StringBuilder(); 142 | bodyBulider.append(TWO).append(boundary).append(END_LINE) 143 | .append("Content-Disposition: form-data; name=\"b64_data\"") 144 | .append(END_LINE).append(END_LINE) 145 | .append(content) 146 | .append(END_LINE) 147 | .append(TWO).append(boundary).append(TWO); 148 | connection.connect(); 149 | 150 | BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream())); 151 | 152 | bw.write(bodyBulider.toString()); 153 | bw.flush(); 154 | bw.close(); 155 | return new DefaultWbpHttpResponse( 156 | connection.getResponseCode(), 157 | getHeaderFromConnection(connection), 158 | getBodyFromConnection(connection) 159 | ); 160 | } 161 | 162 | @Override 163 | public Map getHeader() { 164 | return this.header; 165 | } 166 | 167 | 168 | private Map getHeaderFromConnection(HttpURLConnection connection) { 169 | Map> headerFields = connection.getHeaderFields(); 170 | 171 | List list = headerFields.get("Set-Cookie"); 172 | String cookie = null; 173 | if (list != null) { 174 | StringBuilder sb = new StringBuilder(); 175 | for (String s : list) { 176 | int i = s.indexOf(";"); 177 | if (i != -1) { 178 | String substring = s.substring(0, i); 179 | sb.append(substring).append("; "); 180 | } else { 181 | break; 182 | } 183 | } 184 | cookie = sb.toString(); 185 | if (cookie.length() != 0) { 186 | cookie = cookie.substring(0, cookie.length() - 2); 187 | } 188 | } 189 | 190 | Map header = new HashMap<>(); 191 | Set>> entries = headerFields.entrySet(); 192 | entries.forEach(e -> { 193 | StringBuilder sb = new StringBuilder(); 194 | List values = e.getValue(); 195 | for (String s : values) { 196 | sb.append(s); 197 | } 198 | header.put(e.getKey(), sb.toString()); 199 | }); 200 | header.put("Set-Cookie", cookie); 201 | return header; 202 | } 203 | 204 | private String getBodyFromConnection(HttpURLConnection connection) throws IOException { 205 | int responseCode = connection.getResponseCode(); 206 | 207 | if (responseCode == HTTP_OK) { 208 | InputStream inputStream = connection.getInputStream(); 209 | return readInputStream(inputStream); 210 | } else { 211 | InputStream errorStream = connection.getErrorStream(); 212 | return readInputStream(errorStream); 213 | } 214 | } 215 | 216 | private String readInputStream(InputStream is) throws IOException { 217 | String str; 218 | StringBuilder stringBuilder = new StringBuilder(); 219 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is)); 220 | while ((str = bufferedReader.readLine()) != null) { 221 | stringBuilder.append(str); 222 | } 223 | return stringBuilder.toString(); 224 | } 225 | 226 | // private String convertParams(Map params) { 227 | // Set> entries = params.entrySet(); 228 | // StringBuilder sb = new StringBuilder(); 229 | // entries.forEach(e -> { 230 | // sb.append(e.getKey()); 231 | // sb.append("="); 232 | // sb.append(e.getValue()); 233 | // sb.append("&"); 234 | // }); 235 | // String s = sb.toString(); 236 | // return s.substring(0, s.length() - 1); 237 | // } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/http/DefaultWbpHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.http; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Created by echisan on 2018/11/5 7 | */ 8 | public class DefaultWbpHttpResponse implements WbpHttpResponse { 9 | private int statusCode; 10 | private Map header; 11 | private String body; 12 | 13 | public DefaultWbpHttpResponse(int statusCode, Map header, String body) { 14 | this.statusCode = statusCode; 15 | this.header = header; 16 | this.body = body; 17 | } 18 | 19 | @Override 20 | public int getStatusCode() { 21 | return this.statusCode; 22 | } 23 | 24 | 25 | @Override 26 | public Map getHeader() { 27 | return this.header; 28 | } 29 | 30 | @Override 31 | public String getBody() { 32 | return this.body; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "DefaultWbpHttpResponse{" + 38 | "statusCode=" + statusCode + 39 | ", header=" + header + 40 | ", body='" + body + '\'' + 41 | '}'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/http/WbpHttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.http; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | /** 8 | * http请求接口封装 9 | */ 10 | public interface WbpHttpRequest { 11 | void setHeader(Map header); 12 | 13 | Map getHeader(); 14 | 15 | WbpHttpResponse doGet(String url, Map header, Map params) throws IOException; 16 | 17 | WbpHttpResponse doGet(String url, Map params) throws IOException; 18 | 19 | WbpHttpResponse doGet(String url) throws IOException; 20 | 21 | WbpHttpResponse doPost(String url, Map header, Map params) throws IOException; 22 | 23 | WbpHttpResponse doPost(String url, Map params) throws IOException; 24 | 25 | WbpHttpResponse doPost(String url) throws IOException; 26 | 27 | WbpHttpResponse doPostMultiPart(String url, Map header, String content) throws IOException; 28 | 29 | default String convertParams(Map params) { 30 | Set> entries = params.entrySet(); 31 | StringBuilder sb = new StringBuilder(); 32 | entries.forEach(e -> { 33 | sb.append(e.getKey()); 34 | sb.append("="); 35 | sb.append(e.getValue()); 36 | sb.append("&"); 37 | }); 38 | String s = sb.toString(); 39 | return s.substring(0, s.length() - 1); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/http/WbpHttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.http; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * http请求响应封装 7 | */ 8 | public interface WbpHttpResponse { 9 | int getStatusCode(); 10 | 11 | Map getHeader(); 12 | 13 | String getBody(); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/interceptor/CookieInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.interceptor; 2 | 3 | import com.github.echisan.wbp4j.UploadAttributes; 4 | import com.github.echisan.wbp4j.UploadResponse; 5 | import com.github.echisan.wbp4j.WbpConstants; 6 | import com.github.echisan.wbp4j.cache.AbstractCookieContext; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | /** 13 | * 检查是否存在cookie,并且设置cookie绑定到上传参数当中去 14 | */ 15 | public class CookieInterceptor implements UploadInterceptor { 16 | private static final Logger logger = LoggerFactory.getLogger(CookieInterceptor.class); 17 | 18 | /** 19 | * cookie会话 20 | */ 21 | private AbstractCookieContext cookieContext; 22 | 23 | public CookieInterceptor(AbstractCookieContext cookieContext) { 24 | this.cookieContext = cookieContext; 25 | } 26 | 27 | /** 28 | * 从会话中获取cookie 29 | * 如果存在则将值绑定到参数中 30 | * 不存在则通知com.github.echisan.wbp4j.interceptor.LoginInterceptor拦截器去获取cookie 31 | * 32 | * @param uploadAttributes 上传图片的参数 33 | * @return 如果返回false则说明无法操作cookie缓存 34 | */ 35 | @Override 36 | public boolean processBefore(UploadAttributes uploadAttributes) { 37 | 38 | String cookie = null; 39 | try { 40 | cookie = cookieContext.getCookie(); 41 | } catch (IOException e) { 42 | e.printStackTrace(); 43 | uploadAttributes.getContext().put(WbpConstants.UA_ERROR_MESSAGE, "无法访问缓存,获取Cookie失败,上传失败"); 44 | return false; 45 | } 46 | 47 | if (cookie != null) { 48 | // 设置cookie到请求头中 49 | uploadAttributes.getHeaders().put("Cookie", cookie); 50 | } else { 51 | // 通知 52 | uploadAttributes.getContext().put(WbpConstants.REQUIRE_LOGIN, Boolean.TRUE); 53 | } 54 | return true; 55 | } 56 | 57 | @Override 58 | public void processAfter(UploadResponse uploadResponse) { 59 | if (uploadResponse.getResult().equals(UploadResponse.ResultStatus.RETRY)){ 60 | logger.debug("found retry flag,retry upload now"); 61 | try { 62 | cookieContext.clear(); 63 | } catch (IOException e) { 64 | e.printStackTrace(); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/interceptor/InitUploadAttributesInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.interceptor; 2 | 3 | import com.github.echisan.wbp4j.UploadAttributes; 4 | import com.github.echisan.wbp4j.UploadResponse; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * 绑定上传参数的拦截器 11 | * 应该作为首个拦截器 12 | */ 13 | public class InitUploadAttributesInterceptor implements UploadInterceptor { 14 | 15 | private static final String uploadUrl = "http://picupload.service.weibo.com/interface/pic_upload.php?" + 16 | "ori=1&mime=image%2Fjpeg&data=base64&url=0&markpos=1&logo=&nick=0&marks=1&app=miniblog"; 17 | 18 | /** 19 | * 上传图片的请求头 20 | * 已经提供了默认的参数,也可以通过构造器进行覆盖 21 | */ 22 | private Map headers; 23 | 24 | public InitUploadAttributesInterceptor() { 25 | headers = initUploadRequestHeaders(); 26 | } 27 | 28 | public InitUploadAttributesInterceptor(Map headers) { 29 | this.headers = headers; 30 | } 31 | 32 | @Override 33 | public boolean processBefore(UploadAttributes uploadAttributes) { 34 | 35 | uploadAttributes.setHeaders(headers); 36 | uploadAttributes.setUrl(uploadUrl); 37 | return true; 38 | } 39 | 40 | @Override 41 | public void processAfter(UploadResponse uploadResponse) { 42 | 43 | } 44 | 45 | private Map initUploadRequestHeaders() { 46 | Map headers = new HashMap<>(); 47 | headers.put("Host", "picupload.service.weibo.com"); 48 | headers.put("Origin", "https://weibo.com/"); 49 | headers.put("Referer", "https://weibo.com/"); 50 | return headers; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/interceptor/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.interceptor; 2 | 3 | import com.github.echisan.wbp4j.LoginRequest; 4 | import com.github.echisan.wbp4j.UploadAttributes; 5 | import com.github.echisan.wbp4j.UploadResponse; 6 | import com.github.echisan.wbp4j.WbpConstants; 7 | import com.github.echisan.wbp4j.exception.LoginFailedException; 8 | 9 | /** 10 | * 登陆的拦截器 11 | *

12 | * 为了与上传解耦,将登陆的操作放在拦截器 13 | *

14 | * 目前拦截器顺序 InitUploadAttributes - InterceptorCookieInterceptor - LoginInterceptor - .. 15 | * 目的是上传之前必须存在cookie调用上传接口才有意义 16 | * 所以本拦截器主要作用在于通过登陆获取cookie 17 | */ 18 | public class LoginInterceptor implements UploadInterceptor { 19 | 20 | /** 21 | * 登陆操作的接口 22 | */ 23 | private LoginRequest loginRequest; 24 | 25 | public LoginInterceptor(LoginRequest loginRequest) { 26 | this.loginRequest = loginRequest; 27 | } 28 | 29 | @Override 30 | public boolean processBefore(UploadAttributes uploadAttributes) { 31 | 32 | if (requireLogin(uploadAttributes)) { 33 | try { 34 | loginRequest.login(); 35 | } catch (LoginFailedException e) { 36 | e.printStackTrace(); 37 | uploadAttributes.getContext().put(WbpConstants.UA_ERROR_MESSAGE,"登陆失败,上传图片失败"); 38 | return false; 39 | } 40 | } 41 | return true; 42 | } 43 | 44 | @Override 45 | public void processAfter(UploadResponse uploadResponse) { 46 | 47 | } 48 | 49 | private boolean requireLogin(UploadAttributes uploadAttributes) { 50 | return uploadAttributes.getContext().containsKey(WbpConstants.REQUIRE_LOGIN); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/interceptor/ReCheckCookieInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.interceptor; 2 | 3 | import com.github.echisan.wbp4j.UploadAttributes; 4 | import com.github.echisan.wbp4j.UploadResponse; 5 | import com.github.echisan.wbp4j.WbpConstants; 6 | import com.github.echisan.wbp4j.cache.AbstractCookieContext; 7 | 8 | import java.io.IOException; 9 | 10 | /** 11 | * 二次验证cookie是否存在的拦截器 12 | * 由于当CookieInterceptor不存在cookie时,交由下一个拦截器去获取 13 | * 所以上传参数中仍然不存在cookie 14 | * 所以用本拦截器于进行二次检查 15 | */ 16 | public class ReCheckCookieInterceptor implements UploadInterceptor { 17 | 18 | /** 19 | * cookie会话 20 | */ 21 | private AbstractCookieContext cookieContext; 22 | 23 | public ReCheckCookieInterceptor(AbstractCookieContext cookieContext) { 24 | this.cookieContext = cookieContext; 25 | } 26 | 27 | @Override 28 | public boolean processBefore(UploadAttributes uploadAttributes) { 29 | 30 | if (uploadAttributes.getContext().containsKey(WbpConstants.REQUIRE_LOGIN)) { 31 | try { 32 | uploadAttributes.getHeaders().put("Cookie", cookieContext.getCookie()); 33 | } catch (IOException e) { 34 | e.printStackTrace(); 35 | uploadAttributes.getContext().put(WbpConstants.UA_ERROR_MESSAGE, "无法访问Cookie缓存,上传失败"); 36 | return false; 37 | } 38 | } 39 | 40 | return true; 41 | } 42 | 43 | @Override 44 | public void processAfter(UploadResponse uploadResponse) { 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/interceptor/UploadInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.interceptor; 2 | 3 | import com.github.echisan.wbp4j.UploadAttributes; 4 | import com.github.echisan.wbp4j.UploadResponse; 5 | 6 | /** 7 | * 上传图片接口的拦截器接口 8 | *

9 | * 主要用于拦截上传图片的接口,对参数进行相应的处理 10 | * 而拦截处理后虽然已经不能影响上传结果了 11 | * 但是可以根据UploadResponse的结果做一些操作 12 | */ 13 | public interface UploadInterceptor { 14 | 15 | /** 16 | * 在真正调用上传图片接口之前,会先调用此方法,可以对UploadAttributes中的参数进行操作 17 | * 可以放心修改该参数中的值,因为是线程安全的 18 | * 19 | * @param uploadAttributes 上传图片的参数 20 | * @return 如果返回true才会继续往下调用,返回false则中断,最后也不会调用上传图片的方法 21 | */ 22 | boolean processBefore(UploadAttributes uploadAttributes); 23 | 24 | /** 25 | * 在调用上传接口完毕后,会调用本方法 26 | * 可以根据该相应结果进行相应的处理 27 | * 由于已经调用过上传接口了,再拦截也没有意义了,所以没有任何返回值 28 | * 29 | * @param uploadResponse 上传相应 30 | */ 31 | void processAfter(UploadResponse uploadResponse); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/utils/ImageSize.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.utils; 2 | 3 | /** 4 | * 图片尺寸 5 | */ 6 | public enum ImageSize { 7 | large, 8 | mw1024, 9 | mw690, 10 | bmiddle, 11 | small, 12 | thumb180, 13 | thumbnail, 14 | square; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/utils/RSAEncodeUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.utils; 2 | 3 | import javax.crypto.BadPaddingException; 4 | import javax.crypto.Cipher; 5 | import javax.crypto.IllegalBlockSizeException; 6 | import javax.crypto.NoSuchPaddingException; 7 | import java.math.BigInteger; 8 | import java.security.InvalidKeyException; 9 | import java.security.KeyFactory; 10 | import java.security.NoSuchAlgorithmException; 11 | import java.security.PublicKey; 12 | import java.security.spec.InvalidKeySpecException; 13 | import java.security.spec.RSAPublicKeySpec; 14 | 15 | /** 16 | * Created by echisan on 2018/6/13 17 | */ 18 | public class RSAEncodeUtils { 19 | private static final char[] HEX_CHAR = {'0', '1', '2', '3', '4', '5', 20 | '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 21 | 22 | private static final String RSA_ALGORITHM = "RSA"; 23 | 24 | public static String encode(String toEncode, String pubKey, String pubExp) 25 | throws NoSuchAlgorithmException, NoSuchPaddingException, 26 | InvalidKeySpecException, InvalidKeyException, BadPaddingException, 27 | IllegalBlockSizeException { 28 | 29 | KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM); 30 | BigInteger modulus = new BigInteger(pubKey, 16); 31 | BigInteger publicExponent = new BigInteger(pubExp, 16); 32 | RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(modulus, publicExponent); 33 | PublicKey publicKey = keyFactory.generatePublic(rsaPublicKeySpec); 34 | Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); 35 | cipher.init(Cipher.ENCRYPT_MODE, publicKey); 36 | byte[] encodeStr = cipher.doFinal(toEncode.getBytes()); 37 | return bytesToHex(encodeStr); 38 | } 39 | 40 | private static String bytesToHex(byte[] bytes) { 41 | char[] buf = new char[bytes.length * 2]; 42 | int index = 0; 43 | for (byte b : bytes) { // 利用位运算进行转换,可以看作方法一的变种 44 | buf[index++] = HEX_CHAR[b >>> 4 & 0xf]; 45 | buf[index++] = HEX_CHAR[b & 0xf]; 46 | } 47 | return new String(buf); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/echisan/wbp4j/utils/WbpUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j.utils; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | import java.util.zip.CRC32; 6 | 7 | public class WbpUtils { 8 | 9 | /** 10 | * 本方法来自 @dong4j:https://github.com/dong4j 11 | *

12 | * Gets image url. 13 | * 14 | * @param pid the pid 15 | * @param imageSize the imageSize 16 | * @param https the https 17 | * @return the image url 18 | */ 19 | public static String getImageUrl(String pid, ImageSize imageSize, boolean https) { 20 | pid = pid.trim(); 21 | String size = imageSize.name(); 22 | // 传递 pid 23 | Pattern p = Pattern.compile("^[a-zA-Z0-9]{32}$"); 24 | Matcher m = p.matcher(pid); 25 | 26 | if (m.matches()) { 27 | CRC32 crc32 = new CRC32(); 28 | crc32.update(pid.getBytes()); 29 | return (https ? "https" : "http") + "://" + (https ? "ws" : "ww") 30 | + ((crc32.getValue() & 3) + 1) + ".sinaimg.cn/" + size 31 | + "/" + pid + "." + (pid.charAt(21) == 'g' ? "gif" : "jpg"); 32 | } 33 | // 传递 url 34 | String url = pid; 35 | Pattern p1 = Pattern.compile("^(https?://[a-z]{2}d.sinaimg.cn/)(large|bmiddle|mw1024|mw690|small|square|thumb180|thumbnail)(/[a-z0-9]{32}.(jpg|gif))$"); 36 | Matcher m1 = p1.matcher(url); 37 | if (m1.find()) { 38 | return m.group(1) + size + m.group(3); 39 | } 40 | return null; 41 | } 42 | 43 | /** 44 | * 将unicode字符串转换成人能看懂的字符 45 | * @param unicode the unicode string 46 | * @return decoded unicode string 47 | */ 48 | public static String decodeUnicode(String unicode) { 49 | StringBuilder str = new StringBuilder(); 50 | String[] hex = unicode.split("\\\\u"); 51 | for (int i = 1; i < hex.length; i++) { 52 | int data = Integer.parseInt(hex[i], 16); 53 | str.append((char) data); 54 | } 55 | return str.length() > 0 ? str.toString() : unicode; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/github/echisan/wbp4j/UploadRequestBuilderTest.java: -------------------------------------------------------------------------------- 1 | package com.github.echisan.wbp4j; 2 | 3 | import com.github.echisan.wbp4j.exception.UploadFailedException; 4 | import org.junit.Test; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public class UploadRequestBuilderTest { 10 | 11 | @Test 12 | public void buildDefault() throws IOException, UploadFailedException { 13 | UploadRequest uploadRequest = UploadRequestBuilder.buildDefault("1213123", "123213"); 14 | UploadResponse response = uploadRequest.upload(new File("F:\\pics\\gopher.png")); 15 | System.out.println(response); 16 | 17 | } 18 | 19 | @Test 20 | public void custom() throws IOException, UploadFailedException { 21 | 22 | UploadRequest uploadRequest = UploadRequestBuilder.custom("", "") 23 | .setCacheFilename("F:\\Game\\wbpCache") 24 | .build(); 25 | 26 | UploadResponse uploadResponse = uploadRequest.upload(new File("")); 27 | System.out.println(uploadResponse); 28 | } 29 | 30 | } 31 | --------------------------------------------------------------------------------