分析页面
首先所有的自动化都是拟人操作,只需要模拟出正常的签到步骤并定时重复即可满足需求。
获取登录状态
- 签到总要知道是哪个用户签的嘛,所以所有的请求都要带上用户登录标识,也就是在header中添加对应的cookie
- 如何获取cookie呢,这里看了下掘金的登录请求还比较麻烦,对熟悉爬虫的同学可能小菜一碟,对后端来说虽然能做但是本着投入时间与收益的原则,我毅然决然的选择绕过登录,直接拦截请求把request header的cookie复制出来。
处理cookie失效
- 没有登录逻辑的话等cookie失效签到请求会返回请登录,这里话通过邮箱提醒更换cookie也是可以接受的
处理https请求
- 随便从网上找一个http方法即可
落地方案
为了快速满足直接用最熟悉的Java+SpringBoot撸出来个jar包,打开IDEA创建SpringBoot脚手架
创建脚手架
pom里直接引入springboot-web、springboot-mail、lombok、fastjson,版本管理直接继承下spring-boot-dependencies。
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.4.3</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.41</version></dependency></dependencies>
启动类上添加@EnableScheduling注解开启定时任务
publicclassSignApplication { publicstaticvoidmain(String[] args) { SpringApplication.run(SignApplication.class, args); } }
整体的骨架就这么简单。
通用http方法
publicstaticStringcommonReuqest(Stringurl, Stringmethod, Stringcookie) throwsException { URLserverUrl=newURL(url); HttpURLConnectionconn= (HttpURLConnection) serverUrl.openConnection(); conn.setRequestMethod(method); conn.setRequestProperty("Content-type", "application/json"); conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"); conn.setRequestProperty("Cookie", cookie); //必须设置false,否则会自动redirect到重定向后的地址conn.setInstanceFollowRedirects(false); conn.connect(); Stringresult=getReturn(conn); returnresult; } /*请求url获取返回的内容*/publicstaticStringgetReturn(HttpURLConnectionconnection) throwsIOException { StringBufferbuffer=newStringBuffer(); //将返回的输入流转换成字符串try (InputStreaminputStream=connection.getInputStream(); InputStreamReaderinputStreamReader=newInputStreamReader(inputStream); BufferedReaderbufferedReader=newBufferedReader(inputStreamReader);) { Stringstr=null; while ((str=bufferedReader.readLine()) !=null) { buffer.append(str); } Stringresult=buffer.toString(); returnresult; } }
封装成一个util类,入口加上cookie即可。
邮箱逻辑
- 如何开启smtp邮箱服务就不说了,这里直接贴一下教程https://blog.csdn.net/weixin_46822367/article/details/123893527
- yml配置里需要配置上必要信息,授权码按照上述博客中的步骤获取。
spring: mail: host: smtp.qq.com#发送者邮箱username: #申请到的授权码password: #端口号465或587port: 587#默认的邮件编码为UTF-8default-encoding: UTF-8#其他参数properties: mail: #配置SSL加密工厂smtp: ssl: #本地测试,先放开sslenable: falserequired: false#开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误debug: true
- 邮箱业务类
publicclassSendMailServiceImplimplementsSendMailService { privateJavaMailSenderjavaMailSender; "${spring.mail.username}") (privateStringsendMailer; publicvoidcheckMail(MailRequestmailRequest) { Assert.notNull(mailRequest,"邮件请求不能为空"); Assert.notNull(mailRequest.getSendTo(), "邮件收件人不能为空"); Assert.notNull(mailRequest.getSubject(), "邮件主题不能为空"); Assert.notNull(mailRequest.getText(), "邮件收件人不能为空"); } publicvoidsendMail(MailRequestmailRequest) { SimpleMailMessagemessage=newSimpleMailMessage(); checkMail(mailRequest); //邮件发件人message.setFrom(sendMailer); //邮件收件人 1或多个message.setTo(mailRequest.getSendTo().split(",")); //邮件主题message.setSubject(mailRequest.getSubject()); //邮件内容message.setText(mailRequest.getText()); //邮件发送时间message.setSentDate(newDate()); // JavaMailSender javaMailSender = new JavaMailSenderImpl();javaMailSender.send(message); log.info("发送邮件成功:{}->{}",sendMailer,mailRequest.getSendTo()); } }
JavaMailSender为spring-boot-starter-mail依赖中封装好的业务类,yml中添加对应的配置该类会被注入到容器中。
签到请求
publicclassActionService { //签到publicstaticfinalStringCHECK_IN="https://api.juejin.cn/growth_api/v1/check_in"; //抽奖publicstaticfinalStringDRAW="https://api.juejin.cn/growth_api/v1/lottery/draw"; "${spring.mail.username}") (privateStringusername; SendMailServicesendMailService; /*** 签到*/publicvoidcheckIn(Stringcookie) throwsException { Stringresponse=BaseRequest.commonReuqest(CHECK_IN, "POST", cookie); log.info("get result from juejin {}", response); Map<String, Object>resultMap=JSONObject.parseObject(response, Map.class); if((Integer) resultMap.get("err_no") !=0){ log.error((String) resultMap.get("err_msg")); // 推送失败消息MailRequestmailRequest=newMailRequest(); mailRequest.setText("掘金签到失败!err_msg: "+resultMap.get("err_msg")); mailRequest.setSendTo(username); mailRequest.setSubject("juejin sign"); sendMailService.sendMail(mailRequest); } } /*** 抽奖*/publicvoiddraw(Stringcookie) throwsException { Stringresponse=BaseRequest.commonReuqest(DRAW, "POST", cookie); DrawResponcedata=JSON.parseObject(response,newTypeReference<DrawResponce>(){}); log.info(response); } }
签到请求和抽奖请求都很简单没有参数,带上cookie发一下矿石就到账了,每天第一次抽奖不花矿石,默认第一次抽一下。签到成功就不发邮箱了发多了烦,签到失败会把失败原因发送到配置的邮箱内。
定时任务
publicclasstask { "${uptown.cookie}") (Stringcookie; ActionServiceactionService; cron="0 0 5 * * ?") (publicvoidrun() throwsException { log.info("{} start!", newSimpleDateFormat("yyyy-MM-dd").format(newDate())); this.actionService.checkIn(cookie); // 第一次免费抽奖this.actionService.draw(cookie); } }
最后加上定时任务就完事了,每天5点签到,签到失败发邮箱提醒。直接打包扔服务器上大功告成~