Compare commits
5 Commits
20d7538333
...
01dd1d5191
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01dd1d5191 | ||
|
|
6f1c990d51 | ||
|
|
7bb698c9c0 | ||
|
|
f9a1c21bee | ||
|
|
1d094c8c3c |
12
pom.xml
12
pom.xml
@@ -78,6 +78,18 @@
|
|||||||
<artifactId>tess4j</artifactId>
|
<artifactId>tess4j</artifactId>
|
||||||
<version>5.8.0</version>
|
<version>5.8.0</version>
|
||||||
</dependency>-->
|
</dependency>-->
|
||||||
|
<!-- pom.xml -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-cache</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 使用Caffeine作为缓存实现 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||||
|
<artifactId>caffeine</artifactId>
|
||||||
|
<version>3.1.8</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.tem.bocai;
|
package com.tem.bocai;
|
||||||
|
|
||||||
|
import com.tem.bocai.util.SQLiteUtil;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
|||||||
42
src/main/java/com/tem/bocai/config/CacheConfig.java
Normal file
42
src/main/java/com/tem/bocai/config/CacheConfig.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package com.tem.bocai.config;
|
||||||
|
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.cache.annotation.EnableCaching;
|
||||||
|
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableCaching // 启用缓存支持
|
||||||
|
public class CacheConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主缓存管理器 - 用于token缓存(19分钟过期)
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@Primary // 标记为主缓存管理器
|
||||||
|
public CacheManager cacheManager() {
|
||||||
|
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
|
||||||
|
cacheManager.setCaffeine(Caffeine.newBuilder()
|
||||||
|
.expireAfterWrite(19, TimeUnit.MINUTES) // 19分钟过期
|
||||||
|
.maximumSize(100) // 最大缓存数量
|
||||||
|
.recordStats() // 记录统计信息
|
||||||
|
);
|
||||||
|
return cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备用缓存管理器 - 如果没有配置Caffeine,使用ConcurrentMap
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean(CacheManager.class)
|
||||||
|
public CacheManager fallbackCacheManager() {
|
||||||
|
return new org.springframework.cache.concurrent.ConcurrentMapCacheManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public class LoginCrawler {
|
|||||||
|
|
||||||
private final LoginService loginService;
|
private final LoginService loginService;
|
||||||
|
|
||||||
// 构造函数注入
|
// 登入
|
||||||
public LoginCrawler(LoginService loginService) {
|
public LoginCrawler(LoginService loginService) {
|
||||||
this.loginService = loginService;
|
this.loginService = loginService;
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,13 @@ public class LoginCrawler {
|
|||||||
return ResponseEntity.ok(result);
|
return ResponseEntity.ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//今日已结爬取
|
||||||
|
@GetMapping("/ocr/completedToday")
|
||||||
|
public ResponseEntity<String> completedToday() throws IOException, TesseractException {
|
||||||
|
String result = loginService.completedToday();
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
47
src/main/java/com/tem/bocai/entity/LoginInfoResult.java
Normal file
47
src/main/java/com/tem/bocai/entity/LoginInfoResult.java
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package com.tem.bocai.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import net.sourceforge.tess4j.TesseractException;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Entity //用户登入信息
|
||||||
|
@Table(name = "login_info")
|
||||||
|
@Data
|
||||||
|
public class LoginInfoResult {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "user_name", nullable = false)
|
||||||
|
private String username; //
|
||||||
|
|
||||||
|
@Column(name = "password", nullable = false)
|
||||||
|
private String password; //
|
||||||
|
|
||||||
|
|
||||||
|
@Column(name = "login_url", nullable = false)
|
||||||
|
private String loginUrl;
|
||||||
|
|
||||||
|
//限制赢多少
|
||||||
|
@Column(name = "win_num", nullable = false)
|
||||||
|
private Integer winNum;
|
||||||
|
|
||||||
|
//限制输多少
|
||||||
|
@Column(name = "lose_num", nullable = false)
|
||||||
|
private Integer loseNum;
|
||||||
|
|
||||||
|
/* @Column(name = "current_num", nullable = false)
|
||||||
|
private Integer currentNum;*/
|
||||||
|
|
||||||
|
@Column(name = "create_time", nullable = false)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@Column(name = "update_time", nullable = false)
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -9,59 +9,50 @@ import jakarta.persistence.GenerationType;
|
|||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "lottery_results")
|
@Table(name = "lottery_results")
|
||||||
|
@Data
|
||||||
public class LotteryResult {
|
public class LotteryResult {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
/*@Column(name = "id", nullable = false, unique = true)
|
||||||
|
private String id; // 期号*/
|
||||||
|
|
||||||
@Column(name = "issue", nullable = false, unique = true)
|
@Column(name = "issue", nullable = false, unique = true)
|
||||||
private String issue; // 期号
|
private String issue; // 期号
|
||||||
|
|
||||||
@Column(name = "date", nullable = false)
|
|
||||||
private String date; // 日期
|
|
||||||
|
|
||||||
@Column(name = "time", nullable = false)
|
@Column(name = "time", nullable = false)
|
||||||
private String time; // 开奖时间
|
private String time; // 开奖时间
|
||||||
|
|
||||||
@ElementCollection
|
@Column(name = "result")
|
||||||
@JoinColumn(name = "result_id")
|
private List<String> result; // 开奖号码
|
||||||
@Column(name = "number")
|
|
||||||
private List<String> numbers; // 开奖号码
|
|
||||||
|
|
||||||
@Column(name = "sum", nullable = false)
|
@Column(name = "winner", nullable = false)
|
||||||
private String sum; // 总和值
|
private String winner; //
|
||||||
|
|
||||||
@Column(name = "first_second_sum", nullable = false)
|
|
||||||
private String firstSecondSum; // 冠亚和
|
|
||||||
|
|
||||||
@Column(name = "two_series", nullable = false)
|
@Column(name = "sum1", nullable = false)
|
||||||
private String twoSeries; // 2串
|
private String sum1; // 总和值
|
||||||
|
|
||||||
@Column(name = "four_series", nullable = false)
|
@Column(name = "sum2", nullable = false)
|
||||||
private String fourSeries; // 4串
|
private String sum2; // 冠亚和
|
||||||
|
|
||||||
@Column(name = "size", nullable = false)
|
|
||||||
private String size; // 大小
|
|
||||||
|
|
||||||
@Column(name = "dragon_tiger", nullable = false)
|
|
||||||
private String dragonTiger; // 龙虎
|
|
||||||
|
|
||||||
@ElementCollection
|
@Column(name = "gd1", nullable = false)
|
||||||
@JoinColumn(name = "result_id")
|
private String gd1; // 冠亚单
|
||||||
@Column(name = "tail")
|
|
||||||
private List<String> tails; // 1~5尾
|
|
||||||
|
|
||||||
// getter和setter方法
|
|
||||||
public Long getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Long id) {
|
@Column(name = "gd2", nullable = false)
|
||||||
this.id = id;
|
private String gd2; // 冠亚大
|
||||||
}
|
|
||||||
|
|
||||||
|
@Column(name = "glh_result", nullable = false)
|
||||||
|
private String glh_result; //[ "龙", "龙", "龙", "虎", "虎" ] 龙虎
|
||||||
|
|
||||||
public String getIssue() {
|
public String getIssue() {
|
||||||
return issue;
|
return issue;
|
||||||
@@ -71,12 +62,12 @@ public class LotteryResult {
|
|||||||
this.issue = issue;
|
this.issue = issue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDate() {
|
public Long getId() {
|
||||||
return date;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDate(String date) {
|
public void setId(Long id) {
|
||||||
this.date = date;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTime() {
|
public String getTime() {
|
||||||
@@ -87,67 +78,59 @@ public class LotteryResult {
|
|||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getNumbers() {
|
public List<String> getResult() {
|
||||||
return numbers;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNumbers(List<String> numbers) {
|
public void setResult(List<String> result) {
|
||||||
this.numbers = numbers;
|
this.result = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSum() {
|
public String getWinner() {
|
||||||
return sum;
|
return winner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSum(String sum) {
|
public void setWinner(String winner) {
|
||||||
this.sum = sum;
|
this.winner = winner;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFirstSecondSum() {
|
public String getSum1() {
|
||||||
return firstSecondSum;
|
return sum1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFirstSecondSum(String firstSecondSum) {
|
public void setSum1(String sum1) {
|
||||||
this.firstSecondSum = firstSecondSum;
|
this.sum1 = sum1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTwoSeries() {
|
public String getSum2() {
|
||||||
return twoSeries;
|
return sum2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTwoSeries(String twoSeries) {
|
public void setSum2(String sum2) {
|
||||||
this.twoSeries = twoSeries;
|
this.sum2 = sum2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFourSeries() {
|
public String getGd2() {
|
||||||
return fourSeries;
|
return gd2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFourSeries(String fourSeries) {
|
public void setGd2(String gd2) {
|
||||||
this.fourSeries = fourSeries;
|
this.gd2 = gd2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSize() {
|
public String getGd1() {
|
||||||
return size;
|
return gd1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSize(String size) {
|
public void setGd1(String gd1) {
|
||||||
this.size = size;
|
this.gd1 = gd1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDragonTiger() {
|
public String getGlh_result() {
|
||||||
return dragonTiger;
|
return glh_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDragonTiger(String dragonTiger) {
|
public void setGlh_result(String glh_result) {
|
||||||
this.dragonTiger = dragonTiger;
|
this.glh_result = glh_result;
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getTails() {
|
|
||||||
return tails;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTails(List<String> tails) {
|
|
||||||
this.tails = tails;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,6 @@ public interface LoginService {
|
|||||||
String loginAutomatic(String username, String password,String loginUrl,Integer winNum,Integer loseNum);
|
String loginAutomatic(String username, String password,String loginUrl,Integer winNum,Integer loseNum);
|
||||||
|
|
||||||
//获取token
|
//获取token
|
||||||
String getToken(String username, String password, String loginUrl);
|
String completedToday();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package com.tem.bocai.service.impl;
|
package com.tem.bocai.service.impl;
|
||||||
|
|
||||||
|
import com.tem.bocai.entity.LoginInfoResult;
|
||||||
import com.tem.bocai.service.LoginService;
|
import com.tem.bocai.service.LoginService;
|
||||||
import com.tem.bocai.util.LotteryDataPipeline;
|
import com.tem.bocai.util.*;
|
||||||
import com.tem.bocai.util.LotteryWebMagicCrawler;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import net.sourceforge.tess4j.Tesseract;
|
import net.sourceforge.tess4j.Tesseract;
|
||||||
import net.sourceforge.tess4j.TesseractException;
|
import net.sourceforge.tess4j.TesseractException;
|
||||||
@@ -22,6 +22,7 @@ import javax.imageio.ImageIO;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -35,9 +36,11 @@ import us.codecraft.webmagic.Spider;
|
|||||||
@Service
|
@Service
|
||||||
public class LoginServiceImpl implements LoginService {
|
public class LoginServiceImpl implements LoginService {
|
||||||
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
||||||
private static final int MAX_RETRY = 5;
|
private static final int MAX_RETRY = 10;
|
||||||
@Autowired
|
@Autowired
|
||||||
private Tesseract tesseract;
|
private Tesseract tesseract;
|
||||||
|
@Autowired
|
||||||
|
private TokenCacheService tokenCacheService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String loginAutomatic(String username, String password, String loginUrl, Integer winNum, Integer loseNum) {
|
public String loginAutomatic(String username, String password, String loginUrl, Integer winNum, Integer loseNum) {
|
||||||
@@ -45,8 +48,11 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||||
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
||||||
try {
|
try {
|
||||||
token = attemptLogin();
|
token = tokenCacheService.attemptLogin();
|
||||||
|
tokenCacheService.saveToken(token);
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
|
//保存用户信息
|
||||||
|
addLoginInfo(username, password, loginUrl, winNum, loseNum);
|
||||||
// 2. 创建爬虫实例,传入token
|
// 2. 创建爬虫实例,传入token
|
||||||
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token);
|
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token);
|
||||||
|
|
||||||
@@ -70,7 +76,7 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (attempt < MAX_RETRY) {
|
if (attempt < MAX_RETRY) {
|
||||||
waitForRetry(attempt);
|
tokenCacheService.waitForRetry(attempt);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
||||||
@@ -80,331 +86,42 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getToken(String username, String password, String loginUrl) {
|
public String completedToday() {
|
||||||
String token = "";
|
String token = tokenCacheService.getToken();
|
||||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
System.out.println("得到token = " + token);
|
||||||
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
|
||||||
try {
|
|
||||||
token = attemptLogin();
|
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
return token;
|
// 2. 创建爬虫实例,传入token
|
||||||
}
|
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
|
||||||
if (attempt < MAX_RETRY) {
|
|
||||||
waitForRetry(attempt);
|
// 4. 执行爬虫
|
||||||
}
|
String url = "https://4701268539-esh.qdk63ayw8g.com/member/bets?settled=true";
|
||||||
} catch (Exception e) {
|
|
||||||
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
Spider.create(crawler)
|
||||||
e.printStackTrace();
|
.addUrl(url)
|
||||||
}
|
.thread(1)
|
||||||
|
.run();
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 单次登录尝试
|
|
||||||
*/
|
|
||||||
private String attemptLogin() throws IOException, TesseractException, InterruptedException {
|
|
||||||
CookieStore cookieStore = new BasicCookieStore();
|
|
||||||
try (CloseableHttpClient httpClient = createHttpClient(cookieStore)) {
|
|
||||||
// 1. 获取验证码
|
|
||||||
byte[] imageData = fetchCaptcha(httpClient);
|
|
||||||
if (imageData == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 2. OCR识别验证码
|
|
||||||
String code = processCaptcha(imageData);
|
|
||||||
if (code == null || code.length() != 4) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 3. 执行登录
|
|
||||||
return performLogin(httpClient, cookieStore, code);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IOException("登录尝试失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建HttpClient
|
* 添加登录信息
|
||||||
*/
|
*/
|
||||||
private CloseableHttpClient createHttpClient(CookieStore cookieStore) {
|
public boolean addLoginInfo(String username, String password,
|
||||||
return HttpClients.custom()
|
String loginUrl, Integer winNum, Integer loseNum) {
|
||||||
.setDefaultCookieStore(cookieStore)
|
LoginInfoResult loginInfo = new LoginInfoResult();
|
||||||
.build();
|
loginInfo.setUsername(username);
|
||||||
|
loginInfo.setPassword(password);
|
||||||
|
loginInfo.setLoginUrl(loginUrl);
|
||||||
|
loginInfo.setWinNum(winNum != null ? winNum : 0);
|
||||||
|
loginInfo.setLoseNum(loseNum != null ? loseNum : 0);
|
||||||
|
loginInfo.setCreateTime(new Date());
|
||||||
|
loginInfo.setUpdateTime(new Date());
|
||||||
|
|
||||||
|
return SQLiteUtil.addOrUpdateLoginInfo(loginInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取验证码图片
|
|
||||||
*/
|
|
||||||
private byte[] fetchCaptcha(CloseableHttpClient httpClient)
|
|
||||||
throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
System.out.println("获取验证码...");
|
|
||||||
|
|
||||||
// 添加随机延迟
|
|
||||||
Thread.sleep(1000 + (long) (Math.random() * 1000));
|
|
||||||
|
|
||||||
HttpGet getCaptcha = new HttpGet(BASE_URL + "/code");
|
|
||||||
setCommonHeaders(getCaptcha);
|
|
||||||
getCaptcha.setHeader("Referer", BASE_URL + "/login");
|
|
||||||
|
|
||||||
try (CloseableHttpResponse captchaResponse = httpClient.execute(getCaptcha)) {
|
|
||||||
int captchaStatus = captchaResponse.getStatusLine().getStatusCode();
|
|
||||||
System.out.println("验证码响应状态码: " + captchaStatus);
|
|
||||||
|
|
||||||
if (captchaStatus == 200) {
|
|
||||||
return EntityUtils.toByteArray(captchaResponse.getEntity());
|
|
||||||
} else if (captchaStatus == 429) {
|
|
||||||
System.out.println("获取验证码被限速,等待后重试...");
|
|
||||||
Thread.sleep(3000);
|
|
||||||
} else {
|
|
||||||
System.out.println("获取验证码失败: " + captchaStatus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理验证码识别
|
|
||||||
*/
|
|
||||||
private String processCaptcha(byte[] imageData)
|
|
||||||
throws IOException, TesseractException {
|
|
||||||
|
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
|
|
||||||
String rawOcr = tesseract.doOCR(image);
|
|
||||||
|
|
||||||
// 清理验证码
|
|
||||||
String code = rawOcr.replaceAll("\\s+", "").trim();
|
|
||||||
code = code.replaceAll("[^0-9]", ""); // 只保留数字
|
|
||||||
System.out.println("OCR原始结果: " + rawOcr);
|
|
||||||
System.out.println("清理后验证码: [" + code + "] 长度: " + code.length());
|
|
||||||
// 保存图片用于调试
|
|
||||||
//saveCaptchaImage(image);
|
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存验证码图片
|
|
||||||
*/
|
|
||||||
/* private void saveCaptchaImage(BufferedImage image) throws IOException {
|
|
||||||
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
|
||||||
File output = new File("captcha_" + timestamp + ".png");
|
|
||||||
ImageIO.write(image, "png", output);
|
|
||||||
System.out.println("验证码图片已保存到: " + output.getAbsolutePath());
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行登录请求
|
|
||||||
*/
|
|
||||||
private String performLogin(CloseableHttpClient httpClient,
|
|
||||||
CookieStore cookieStore,
|
|
||||||
String code) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
System.out.println("执行登录...");
|
|
||||||
// 等待一下再发送登录请求
|
|
||||||
Thread.sleep(1500 + (long) (Math.random() * 1000));
|
|
||||||
|
|
||||||
HttpPost loginPost = createLoginRequest(code);
|
|
||||||
|
|
||||||
try (CloseableHttpResponse loginResponse = httpClient.execute(loginPost)) {
|
|
||||||
return processLoginResponse(loginResponse, cookieStore);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建登录请求
|
|
||||||
*/
|
|
||||||
private HttpPost createLoginRequest(String code) throws UnsupportedEncodingException {
|
|
||||||
HttpPost loginPost = new HttpPost(BASE_URL + "/login");
|
|
||||||
|
|
||||||
// 设置请求头
|
|
||||||
setCommonHeaders(loginPost);
|
|
||||||
loginPost.setHeader("Referer", BASE_URL + "/login");
|
|
||||||
loginPost.setHeader("Origin", BASE_URL);
|
|
||||||
loginPost.setHeader("Accept", "application/json, text/plain, */*");
|
|
||||||
|
|
||||||
// 构建登录参数
|
|
||||||
List<NameValuePair> params = new ArrayList<>();
|
|
||||||
params.add(new BasicNameValuePair("type", "1"));
|
|
||||||
params.add(new BasicNameValuePair("account", "pmk1"));
|
|
||||||
params.add(new BasicNameValuePair("password", "Asd123123"));
|
|
||||||
params.add(new BasicNameValuePair("code", code));
|
|
||||||
|
|
||||||
loginPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
|
|
||||||
|
|
||||||
// 禁用自动重定向
|
|
||||||
RequestConfig requestConfig = RequestConfig.custom()
|
|
||||||
.setRedirectsEnabled(false)
|
|
||||||
.build();
|
|
||||||
loginPost.setConfig(requestConfig);
|
|
||||||
|
|
||||||
return loginPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理登录响应
|
|
||||||
*/
|
|
||||||
private String processLoginResponse(CloseableHttpResponse loginResponse,
|
|
||||||
CookieStore cookieStore) throws IOException, InterruptedException {
|
|
||||||
|
|
||||||
int statusCode = loginResponse.getStatusLine().getStatusCode();
|
|
||||||
System.out.println("登录响应状态码: " + statusCode);
|
|
||||||
|
|
||||||
// 处理限速
|
|
||||||
if (statusCode == 429) {
|
|
||||||
handleRateLimit(loginResponse);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打印响应头
|
|
||||||
printResponseHeaders(loginResponse);
|
|
||||||
|
|
||||||
// 检查重定向
|
|
||||||
if (statusCode == 302) {
|
|
||||||
if (checkRedirectForError(loginResponse)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取响应体
|
|
||||||
String tokenFromBody = extractTokenFromResponseBody(loginResponse);
|
|
||||||
if (tokenFromBody != null) {
|
|
||||||
return tokenFromBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从cookies中提取token
|
|
||||||
return extractTokenFromCookies(cookieStore, statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理速率限制
|
|
||||||
*/
|
|
||||||
private void handleRateLimit(CloseableHttpResponse response) throws InterruptedException {
|
|
||||||
System.out.println("登录请求被限速 (429 Too Many Requests)");
|
|
||||||
|
|
||||||
Header retryAfterHeader = response.getFirstHeader("Retry-After");
|
|
||||||
if (retryAfterHeader != null) {
|
|
||||||
try {
|
|
||||||
int retryAfterSeconds = Integer.parseInt(retryAfterHeader.getValue());
|
|
||||||
System.out.println("服务器要求等待 " + retryAfterSeconds + " 秒");
|
|
||||||
Thread.sleep(retryAfterSeconds * 1000L);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
System.out.println("等待5秒后重试");
|
|
||||||
Thread.sleep(5000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.out.println("等待3秒后重试");
|
|
||||||
Thread.sleep(3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查重定向是否包含错误
|
|
||||||
*/
|
|
||||||
private boolean checkRedirectForError(CloseableHttpResponse response) {
|
|
||||||
Header locationHeader = response.getFirstHeader("Location");
|
|
||||||
if (locationHeader != null) {
|
|
||||||
String location = locationHeader.getValue();
|
|
||||||
System.out.println("重定向到: " + location);
|
|
||||||
|
|
||||||
if (location.contains("e=3")) {
|
|
||||||
System.out.println("验证码错误 (e=3)");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打印响应头
|
|
||||||
*/
|
|
||||||
private void printResponseHeaders(CloseableHttpResponse response) {
|
|
||||||
System.out.println("响应头:");
|
|
||||||
for (Header header : response.getAllHeaders()) {
|
|
||||||
System.out.println(" " + header.getName() + ": " + header.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从响应体中提取token
|
|
||||||
*/
|
|
||||||
private String extractTokenFromResponseBody(CloseableHttpResponse response) throws IOException {
|
|
||||||
if (response.getEntity() != null) {
|
|
||||||
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
|
|
||||||
if (responseBody != null && !responseBody.isEmpty()) {
|
|
||||||
System.out.println("响应体: " + responseBody);
|
|
||||||
|
|
||||||
// 检查响应体中是否有token(JSON格式)
|
|
||||||
if (responseBody.contains("\"token\"")) {
|
|
||||||
// 简单提取token
|
|
||||||
int start = responseBody.indexOf("\"token\":\"");
|
|
||||||
if (start != -1) {
|
|
||||||
start += 9;
|
|
||||||
int end = responseBody.indexOf("\"", start);
|
|
||||||
if (end != -1) {
|
|
||||||
String token = responseBody.substring(start, end);
|
|
||||||
System.out.println("\n[SUCCESS] 从响应体找到Token!");
|
|
||||||
System.out.println("Token: " + token);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 消耗实体
|
|
||||||
EntityUtils.consume(response.getEntity());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从cookies中提取token
|
|
||||||
*/
|
|
||||||
private String extractTokenFromCookies(CookieStore cookieStore, int statusCode) {
|
|
||||||
List<Cookie> cookies = cookieStore.getCookies();
|
|
||||||
System.out.println("所有cookies (" + cookies.size() + "个):");
|
|
||||||
|
|
||||||
String token = null;
|
|
||||||
for (Cookie cookie : cookies) {
|
|
||||||
System.out.println(" " + cookie.getName() + " = " + cookie.getValue());
|
|
||||||
|
|
||||||
if ("token".equals(cookie.getName()) ||
|
|
||||||
cookie.getName().toLowerCase().contains("token")) {
|
|
||||||
token = cookie.getValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token != null && !token.isEmpty()) {
|
|
||||||
System.out.println("\n[SUCCESS] Login OK!");
|
|
||||||
System.out.println("Token: " + token);
|
|
||||||
return token;
|
|
||||||
} else if (statusCode == 200) {
|
|
||||||
System.out.println("登录返回200但没有找到token,可能需要检查其他认证方式");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置通用请求头
|
|
||||||
*/
|
|
||||||
private void setCommonHeaders(HttpRequestBase request) {
|
|
||||||
request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
|
||||||
request.setHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
|
|
||||||
request.setHeader("Accept-Encoding", "gzip, deflate, br");
|
|
||||||
request.setHeader("Connection", "keep-alive");
|
|
||||||
request.setHeader("Upgrade-Insecure-Requests", "1");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 等待重试
|
|
||||||
*/
|
|
||||||
private void waitForRetry(int attempt) throws InterruptedException {
|
|
||||||
System.out.println("\n等待2秒后进行下一次尝试...");
|
|
||||||
Thread.sleep(2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
396
src/main/java/com/tem/bocai/util/CompletedTodayCrawler.java
Normal file
396
src/main/java/com/tem/bocai/util/CompletedTodayCrawler.java
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
package com.tem.bocai.util;
|
||||||
|
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import us.codecraft.webmagic.Page;
|
||||||
|
import us.codecraft.webmagic.Site;
|
||||||
|
import us.codecraft.webmagic.Spider;
|
||||||
|
import us.codecraft.webmagic.processor.PageProcessor;
|
||||||
|
import us.codecraft.webmagic.selector.Html;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class CompletedTodayCrawler implements PageProcessor {
|
||||||
|
|
||||||
|
private final String token;
|
||||||
|
private Site site;
|
||||||
|
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public CompletedTodayCrawler(String token) {
|
||||||
|
this.token = token;
|
||||||
|
initSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initSite() {
|
||||||
|
site = Site.me()
|
||||||
|
.setRetryTimes(3)
|
||||||
|
.setSleepTime(2000) // 增加等待时间
|
||||||
|
.setTimeOut(15000) // 增加超时时间
|
||||||
|
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36")
|
||||||
|
.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8")
|
||||||
|
.addHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||||
|
.addHeader("Accept-Encoding", "gzip, deflate, br")
|
||||||
|
.addHeader("Connection", "keep-alive")
|
||||||
|
.addHeader("Upgrade-Insecure-Requests", "1")
|
||||||
|
.addHeader("Sec-Fetch-Dest", "document")
|
||||||
|
.addHeader("Sec-Fetch-Mode", "navigate")
|
||||||
|
.addHeader("Sec-Fetch-Site", "same-origin")
|
||||||
|
.addHeader("Sec-Fetch-User", "?1");
|
||||||
|
|
||||||
|
// 设置cookie
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
site.addHeader("cookie", "token=" + token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Page page) {
|
||||||
|
String url = page.getUrl().toString();
|
||||||
|
System.out.println("处理页面: " + url);
|
||||||
|
|
||||||
|
Html html = page.getHtml();
|
||||||
|
String content = html.toString();
|
||||||
|
|
||||||
|
// 打印一些基本信息
|
||||||
|
System.out.println("页面标题: " + html.xpath("//title/text()").get());
|
||||||
|
System.out.println("页面大小: " + content.length() + " 字符");
|
||||||
|
|
||||||
|
// 检查是否有"暂无数据"提示
|
||||||
|
if (content.contains("暂无数据")) {
|
||||||
|
System.out.println("警告: 页面显示'暂无数据'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 解析注单数据
|
||||||
|
List<Map<String, Object>> betList = parseBetHtml(content);
|
||||||
|
if (betList.isEmpty()) {
|
||||||
|
System.out.println("未解析到注单数据");
|
||||||
|
|
||||||
|
// 尝试从其他可能的位置解析
|
||||||
|
extractDebugInfo(html);
|
||||||
|
} else {
|
||||||
|
System.out.println("解析到 " + betList.size() + " 条注单数据");
|
||||||
|
|
||||||
|
// 打印部分数据示例
|
||||||
|
printSampleData(betList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存原始HTML用于调试
|
||||||
|
saveHtmlForDebug(content, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析注单HTML数据
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> parseBetHtml(String htmlContent) {
|
||||||
|
List<Map<String, Object>> betList = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Document doc = Jsoup.parse(htmlContent);
|
||||||
|
|
||||||
|
// 查找注单表格
|
||||||
|
Element table = doc.selectFirst("table.list");
|
||||||
|
if (table == null) {
|
||||||
|
System.out.println("未找到注单表格");
|
||||||
|
return betList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找表头
|
||||||
|
Elements headers = table.select("thead th");
|
||||||
|
List<String> headerList = new ArrayList<>();
|
||||||
|
for (Element header : headers) {
|
||||||
|
headerList.add(header.text().trim());
|
||||||
|
}
|
||||||
|
System.out.println("表头信息: " + headerList);
|
||||||
|
|
||||||
|
// 查找数据行(跳过表头)
|
||||||
|
Elements rows = table.select("tbody tr");
|
||||||
|
|
||||||
|
for (Element row : rows) {
|
||||||
|
// 跳过"暂无数据"的行
|
||||||
|
if (row.select("td.nodata").size() > 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> betData = new HashMap<>();
|
||||||
|
Elements cells = row.select("td");
|
||||||
|
|
||||||
|
// 按列解析数据
|
||||||
|
for (int i = 0; i < cells.size() && i < headerList.size(); i++) {
|
||||||
|
String header = headerList.get(i);
|
||||||
|
String value = cells.get(i).text().trim();
|
||||||
|
|
||||||
|
// 根据表头映射到对应的字段名
|
||||||
|
switch (header) {
|
||||||
|
case "注单号":
|
||||||
|
betData.put("bet_id", value);
|
||||||
|
break;
|
||||||
|
case "时间":
|
||||||
|
betData.put("time", value);
|
||||||
|
break;
|
||||||
|
case "类型":
|
||||||
|
betData.put("type", value);
|
||||||
|
break;
|
||||||
|
case "玩法":
|
||||||
|
betData.put("game_type", value);
|
||||||
|
break;
|
||||||
|
case "盘":
|
||||||
|
betData.put("plate", value);
|
||||||
|
break;
|
||||||
|
case "下注金额":
|
||||||
|
betData.put("bet_amount", parseAmount(value));
|
||||||
|
break;
|
||||||
|
case "退水(%)":
|
||||||
|
betData.put("rebate_rate", parseRate(value));
|
||||||
|
break;
|
||||||
|
case "结果":
|
||||||
|
betData.put("result", parseResult(value));
|
||||||
|
betData.put("result_amount", parseResultAmount(value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
betData.put(header, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加额外信息
|
||||||
|
if (!betData.isEmpty()) {
|
||||||
|
betData.put("parse_time", dateFormat.format(new Date()));
|
||||||
|
betData.put("source", "completed_today");
|
||||||
|
|
||||||
|
// 提取期数信息(从玩法中提取)
|
||||||
|
extractPeriodInfo(betData);
|
||||||
|
|
||||||
|
betList.add(betData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("解析HTML时出错: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return betList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从玩法中提取期数信息
|
||||||
|
*/
|
||||||
|
private void extractPeriodInfo(Map<String, Object> betData) {
|
||||||
|
try {
|
||||||
|
Object gameTypeObj = betData.get("game_type");
|
||||||
|
if (gameTypeObj instanceof String) {
|
||||||
|
String gameType = (String) gameTypeObj;
|
||||||
|
|
||||||
|
// 尝试匹配期数模式,如"2024001", "001", "期号2024001"等
|
||||||
|
Pattern pattern = Pattern.compile("(\\d{7})|期[号码]?(\\d{3,7})|(\\d{3,4})期");
|
||||||
|
Matcher matcher = pattern.matcher(gameType);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
for (int i = 1; i <= matcher.groupCount(); i++) {
|
||||||
|
if (matcher.group(i) != null) {
|
||||||
|
betData.put("period", matcher.group(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略提取错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析金额(去除货币符号)
|
||||||
|
*/
|
||||||
|
private Double parseAmount(String amountStr) {
|
||||||
|
try {
|
||||||
|
if (amountStr == null || amountStr.isEmpty()) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
// 移除非数字字符(保留小数点和负号)
|
||||||
|
String cleaned = amountStr.replaceAll("[^\\d.-]", "");
|
||||||
|
return cleaned.isEmpty() ? 0.0 : Double.parseDouble(cleaned);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析退水率
|
||||||
|
*/
|
||||||
|
private Double parseRate(String rateStr) {
|
||||||
|
try {
|
||||||
|
if (rateStr == null || rateStr.isEmpty()) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
// 移除百分号
|
||||||
|
String cleaned = rateStr.replace("%", "").trim();
|
||||||
|
return cleaned.isEmpty() ? 0.0 : Double.parseDouble(cleaned) / 100;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析结果状态
|
||||||
|
*/
|
||||||
|
private String parseResult(String resultStr) {
|
||||||
|
if (resultStr == null) {
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultStr.contains("赢") || resultStr.contains("+")) {
|
||||||
|
return "赢";
|
||||||
|
} else if (resultStr.contains("输") || resultStr.contains("-")) {
|
||||||
|
return "输";
|
||||||
|
} else if (resultStr.contains("和") || resultStr.contains("0")) {
|
||||||
|
return "和";
|
||||||
|
} else if (resultStr.contains("取消")) {
|
||||||
|
return "取消";
|
||||||
|
} else {
|
||||||
|
return "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析结果金额
|
||||||
|
*/
|
||||||
|
private Double parseResultAmount(String resultStr) {
|
||||||
|
try {
|
||||||
|
// 提取数字部分(包含负号)
|
||||||
|
Pattern pattern = Pattern.compile("[-+]?\\d+\\.?\\d*");
|
||||||
|
Matcher matcher = pattern.matcher(resultStr);
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
return Double.parseDouble(matcher.group());
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换数据结构以适应数据库
|
||||||
|
*/
|
||||||
|
private List<Map<String, Object>> convertForDatabase(List<Map<String, Object>> betList) {
|
||||||
|
List<Map<String, Object>> dbData = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Map<String, Object> bet : betList) {
|
||||||
|
Map<String, Object> dbRecord = new HashMap<>();
|
||||||
|
|
||||||
|
dbRecord.put("id", bet.get("bet_id"));
|
||||||
|
dbRecord.put("bet_id", bet.get("bet_id"));
|
||||||
|
dbRecord.put("period", bet.get("period"));
|
||||||
|
dbRecord.put("bet_time", bet.get("time"));
|
||||||
|
dbRecord.put("game_type", bet.get("game_type"));
|
||||||
|
dbRecord.put("plate", bet.get("plate"));
|
||||||
|
dbRecord.put("bet_amount", bet.get("bet_amount"));
|
||||||
|
dbRecord.put("rebate_rate", bet.get("rebate_rate"));
|
||||||
|
dbRecord.put("result", bet.get("result"));
|
||||||
|
dbRecord.put("result_amount", bet.get("result_amount"));
|
||||||
|
dbRecord.put("parse_time", bet.get("parse_time"));
|
||||||
|
dbRecord.put("source", bet.get("source"));
|
||||||
|
|
||||||
|
dbData.add(dbRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dbData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存HTML用于调试
|
||||||
|
*/
|
||||||
|
private void saveHtmlForDebug(String content, String url) {
|
||||||
|
try {
|
||||||
|
String safeUrl = url.replaceAll("[^a-zA-Z0-9]", "_");
|
||||||
|
String fileName = "debug_" + safeUrl + "_" + System.currentTimeMillis() + ".html";
|
||||||
|
String filePath = "output/debug/" + fileName;
|
||||||
|
|
||||||
|
File directory = new File("output/debug");
|
||||||
|
if (!directory.exists()) {
|
||||||
|
directory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
File outputFile = new File(filePath);
|
||||||
|
java.nio.file.Files.write(outputFile.toPath(), content.getBytes());
|
||||||
|
|
||||||
|
System.out.println("调试HTML已保存: " + outputFile.getAbsolutePath());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("保存调试HTML失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取调试信息
|
||||||
|
*/
|
||||||
|
private void extractDebugInfo(Html html) {
|
||||||
|
System.out.println("\n=== 调试信息 ===");
|
||||||
|
|
||||||
|
// 检查所有表格
|
||||||
|
List<String> tables = html.xpath("//table/@class").all();
|
||||||
|
System.out.println("所有表格class: " + tables);
|
||||||
|
|
||||||
|
// 检查所有tr
|
||||||
|
int trCount = html.xpath("//tr").all().size();
|
||||||
|
System.out.println("TR数量: " + trCount);
|
||||||
|
|
||||||
|
// 检查所有td
|
||||||
|
int tdCount = html.xpath("//td").all().size();
|
||||||
|
System.out.println("TD数量: " + tdCount);
|
||||||
|
|
||||||
|
// 检查cookie相关元素
|
||||||
|
String cookieScript = html.xpath("//script[contains(text(), 'token')]/text()").get();
|
||||||
|
if (cookieScript != null && cookieScript.contains("token")) {
|
||||||
|
System.out.println("发现token相关脚本");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有JavaScript重定向
|
||||||
|
String redirectScript = html.xpath("//script[contains(text(), 'location.href') or contains(text(), 'window.location')]/text()").get();
|
||||||
|
if (redirectScript != null) {
|
||||||
|
System.out.println("发现重定向脚本: " + redirectScript.substring(0, Math.min(100, redirectScript.length())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印示例数据
|
||||||
|
*/
|
||||||
|
private void printSampleData(List<Map<String, Object>> betList) {
|
||||||
|
System.out.println("\n=== 前3条数据示例 ===");
|
||||||
|
int count = Math.min(3, betList.size());
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Map<String, Object> bet = betList.get(i);
|
||||||
|
System.out.printf("注单%d: ID=%s, 时间=%s, 金额=%.2f, 结果=%s, 金额=%.2f%n",
|
||||||
|
i + 1,
|
||||||
|
bet.get("bet_id"),
|
||||||
|
bet.get("time"),
|
||||||
|
bet.get("bet_amount"),
|
||||||
|
bet.get("result"),
|
||||||
|
bet.get("result_amount"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Site getSite() {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
String url = "https://4701268539-esh.qdk63ayw8g.com/member/bets?settled=true";
|
||||||
|
// 创建爬虫
|
||||||
|
Spider.create(new CompletedTodayCrawler(""))
|
||||||
|
.addUrl(url) // 添加起始URL
|
||||||
|
.thread(1) // 线程数
|
||||||
|
.run(); // 开始爬取
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
//开奖的历史结果
|
||||||
public class LotteryWebMagicCrawler implements PageProcessor {
|
public class LotteryWebMagicCrawler implements PageProcessor {
|
||||||
|
|
||||||
private final String token;
|
private final String token;
|
||||||
@@ -220,6 +221,9 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
resultList.add(rowData);
|
resultList.add(rowData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 将数据写入SQLite数据库
|
||||||
|
SQLiteUtil.writeToSQLite(resultList);
|
||||||
|
// 将数据写入JSON文件(保留原有功能)
|
||||||
writeToJsonFile(resultList);
|
writeToJsonFile(resultList);
|
||||||
System.out.println("打印结果===" + resultList);
|
System.out.println("打印结果===" + resultList);
|
||||||
return resultList;
|
return resultList;
|
||||||
|
|||||||
386
src/main/java/com/tem/bocai/util/SQLiteUtil.java
Normal file
386
src/main/java/com/tem/bocai/util/SQLiteUtil.java
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
package com.tem.bocai.util;
|
||||||
|
|
||||||
|
import com.tem.bocai.entity.LoginInfoResult;
|
||||||
|
|
||||||
|
import java.sql.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class SQLiteUtil {
|
||||||
|
private static final String DB_URL = "jdbc:sqlite:bocai.db";
|
||||||
|
|
||||||
|
static {
|
||||||
|
// 确保数据库驱动已加载
|
||||||
|
try {
|
||||||
|
Class.forName("org.sqlite.JDBC");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库连接
|
||||||
|
*/
|
||||||
|
public static Connection getConnection() throws SQLException {
|
||||||
|
return DriverManager.getConnection(DB_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将爬虫数据写入SQLite数据库
|
||||||
|
*/
|
||||||
|
public static void writeToSQLite(List<Map<String, Object>> resultList) {
|
||||||
|
if (resultList == null || resultList.isEmpty()) {
|
||||||
|
System.out.println("没有数据需要写入");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先初始化数据库
|
||||||
|
//initDatabase();
|
||||||
|
|
||||||
|
String insertSQL = """
|
||||||
|
INSERT OR REPLACE INTO lottery_results
|
||||||
|
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
|
||||||
|
VALUES (?,?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
Connection conn = null;
|
||||||
|
PreparedStatement pstmt = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = getConnection();
|
||||||
|
conn.setAutoCommit(false); // 开启事务
|
||||||
|
|
||||||
|
pstmt = conn.prepareStatement(insertSQL);
|
||||||
|
|
||||||
|
int batchCount = 0;
|
||||||
|
for (Map<String, Object> rowData : resultList) {
|
||||||
|
// 检查必要字段是否存在
|
||||||
|
if (!rowData.containsKey("id") || !rowData.containsKey("result")) {
|
||||||
|
System.out.println("跳过缺失必要字段的数据: " + rowData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 期数
|
||||||
|
pstmt.setString(1, rowData.get("id").toString());
|
||||||
|
|
||||||
|
// 开奖时间
|
||||||
|
if (rowData.containsKey("time")) {
|
||||||
|
pstmt.setString(2, rowData.get("time").toString());
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(2, Types.VARCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开出号码(转换为逗号分隔的字符串)
|
||||||
|
try {
|
||||||
|
List<Integer> resultNumbers = (List<Integer>) rowData.get("result");
|
||||||
|
String resultStr = String.join(",", resultNumbers.stream()
|
||||||
|
.map(String::valueOf)
|
||||||
|
.toArray(String[]::new));
|
||||||
|
pstmt.setString(3, resultStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("result字段转换失败: " + rowData.get("result"));
|
||||||
|
pstmt.setNull(3, Types.VARCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// winner
|
||||||
|
try {
|
||||||
|
Object winnerObj = rowData.get("winner");
|
||||||
|
if (winnerObj instanceof Integer) {
|
||||||
|
pstmt.setInt(4, (Integer) winnerObj);
|
||||||
|
} else if (winnerObj instanceof String) {
|
||||||
|
pstmt.setInt(4, Integer.parseInt((String) winnerObj));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(4, Types.INTEGER);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
pstmt.setNull(4, Types.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gd1
|
||||||
|
if (rowData.containsKey("GD1")) {
|
||||||
|
pstmt.setString(5, rowData.get("GD1").toString());
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(5, Types.VARCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// gd2
|
||||||
|
if (rowData.containsKey("GD2")) {
|
||||||
|
pstmt.setString(6, rowData.get("GD2").toString());
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(6, Types.VARCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum1
|
||||||
|
try {
|
||||||
|
Object sum1Obj = rowData.get("sum1");
|
||||||
|
if (sum1Obj instanceof Integer) {
|
||||||
|
pstmt.setInt(7, (Integer) sum1Obj);
|
||||||
|
} else if (sum1Obj instanceof String) {
|
||||||
|
pstmt.setInt(7, Integer.parseInt((String) sum1Obj));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(7, Types.INTEGER);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
pstmt.setNull(7, Types.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sum2
|
||||||
|
try {
|
||||||
|
Object sum2Obj = rowData.get("sum2");
|
||||||
|
if (sum2Obj instanceof Integer) {
|
||||||
|
pstmt.setInt(8, (Integer) sum2Obj);
|
||||||
|
} else if (sum2Obj instanceof String) {
|
||||||
|
pstmt.setInt(8, Integer.parseInt((String) sum2Obj));
|
||||||
|
} else {
|
||||||
|
pstmt.setNull(8, Types.INTEGER);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
pstmt.setNull(8, Types.INTEGER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLH_result
|
||||||
|
try {
|
||||||
|
List<String> glhResults = (List<String>) rowData.get("GLH_result");
|
||||||
|
String glhResultStr = String.join(",", glhResults);
|
||||||
|
pstmt.setString(9, glhResultStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("GLH_result字段转换失败: " + rowData.get("GLH_result"));
|
||||||
|
pstmt.setNull(9, Types.VARCHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
pstmt.addBatch();
|
||||||
|
batchCount++;
|
||||||
|
|
||||||
|
// 每100条执行一次批量插入
|
||||||
|
if (batchCount % 100 == 0) {
|
||||||
|
pstmt.executeBatch();
|
||||||
|
conn.commit();
|
||||||
|
System.out.println("已批量插入 " + batchCount + " 条数据");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行剩余的批量插入
|
||||||
|
int[] results = pstmt.executeBatch();
|
||||||
|
conn.commit();
|
||||||
|
|
||||||
|
int successCount = 0;
|
||||||
|
for (int result : results) {
|
||||||
|
if (result >= 0) successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("成功写入" + successCount + "条数据到SQLite数据库");
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
try {
|
||||||
|
if (conn != null) conn.rollback();
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
e.printStackTrace();
|
||||||
|
System.err.println("写入SQLite数据库失败: " + e.getMessage());
|
||||||
|
} finally {
|
||||||
|
closeResources(null, pstmt, conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭数据库资源
|
||||||
|
*/
|
||||||
|
private static void closeResources(ResultSet rs, Statement stmt, Connection conn) {
|
||||||
|
try {
|
||||||
|
if (rs != null) rs.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (stmt != null) stmt.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (conn != null) conn.close();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询数据总数
|
||||||
|
*/
|
||||||
|
public static int getTotalCount() {
|
||||||
|
String sql = "SELECT COUNT(*) FROM lottery_results";
|
||||||
|
|
||||||
|
try (Connection conn = getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(sql)) {
|
||||||
|
|
||||||
|
return rs.next() ? rs.getInt(1) : 0;
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除数据库表
|
||||||
|
*/
|
||||||
|
public static void clearTable() {
|
||||||
|
String sql = "DELETE FROM lottery_results";
|
||||||
|
|
||||||
|
try (Connection conn = getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
int rows = stmt.executeUpdate(sql);
|
||||||
|
System.out.println("已清除 " + rows + " 条数据");
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加/更新登录信息 - 如果用户名存在则先删除再插入(更新)
|
||||||
|
*/
|
||||||
|
public static boolean addOrUpdateLoginInfo(LoginInfoResult loginInfo) {
|
||||||
|
if (loginInfo == null || loginInfo.getUsername() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection conn = null;
|
||||||
|
PreparedStatement deleteStmt = null;
|
||||||
|
PreparedStatement insertStmt = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = getConnection();
|
||||||
|
conn.setAutoCommit(false); // 开启事务
|
||||||
|
|
||||||
|
String username = loginInfo.getUsername();
|
||||||
|
|
||||||
|
// 1. 先检查用户是否存在
|
||||||
|
boolean exists = existsByUsername(conn, username);
|
||||||
|
|
||||||
|
// 2. 如果存在,先删除
|
||||||
|
if (exists) {
|
||||||
|
String deleteSQL = "DELETE FROM login_info WHERE user_name = ?";
|
||||||
|
deleteStmt = conn.prepareStatement(deleteSQL);
|
||||||
|
deleteStmt.setString(1, username);
|
||||||
|
int deleted = deleteStmt.executeUpdate();
|
||||||
|
System.out.println("删除已存在的用户记录: " + username + ", 删除条数: " + deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 插入新记录
|
||||||
|
String insertSQL = """
|
||||||
|
INSERT INTO login_info
|
||||||
|
(user_name, password, login_url, win_num, lose_num, create_time, update_time)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""";
|
||||||
|
|
||||||
|
insertStmt = conn.prepareStatement(insertSQL);
|
||||||
|
insertStmt.setString(1, loginInfo.getUsername());
|
||||||
|
insertStmt.setString(2, loginInfo.getPassword());
|
||||||
|
insertStmt.setString(3, loginInfo.getLoginUrl());
|
||||||
|
insertStmt.setInt(4, loginInfo.getWinNum() != null ? loginInfo.getWinNum() : 0);
|
||||||
|
insertStmt.setInt(5, loginInfo.getLoseNum() != null ? loginInfo.getLoseNum() : 0);
|
||||||
|
insertStmt.setTimestamp(6, new Timestamp(
|
||||||
|
loginInfo.getCreateTime() != null ?
|
||||||
|
loginInfo.getCreateTime().getTime() :
|
||||||
|
System.currentTimeMillis()
|
||||||
|
));
|
||||||
|
insertStmt.setTimestamp(7, new Timestamp(
|
||||||
|
loginInfo.getUpdateTime() != null ?
|
||||||
|
loginInfo.getUpdateTime().getTime() :
|
||||||
|
System.currentTimeMillis()
|
||||||
|
));
|
||||||
|
|
||||||
|
int inserted = insertStmt.executeUpdate();
|
||||||
|
|
||||||
|
// 提交事务
|
||||||
|
conn.commit();
|
||||||
|
|
||||||
|
if (inserted > 0) {
|
||||||
|
System.out.println((exists ? "更新" : "添加") + "登录信息成功: " + username);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
System.out.println("添加/更新登录信息失败: " + username);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
try {
|
||||||
|
if (conn != null) conn.rollback();
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
System.err.println("添加/更新登录信息失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
closeResources(null, deleteStmt, null);
|
||||||
|
closeResources(null, insertStmt, null);
|
||||||
|
closeResources(null, null, conn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户名是否存在(使用现有连接)
|
||||||
|
*/
|
||||||
|
private static boolean existsByUsername(Connection conn, String username) throws SQLException {
|
||||||
|
String sql = "SELECT COUNT(*) FROM login_info WHERE user_name = ?";
|
||||||
|
|
||||||
|
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||||
|
pstmt.setString(1, username);
|
||||||
|
|
||||||
|
try (ResultSet rs = pstmt.executeQuery()) {
|
||||||
|
return rs.next() && rs.getInt(1) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询
|
||||||
|
*/
|
||||||
|
public static LoginInfoResult getLoginInfo() {
|
||||||
|
String sql = "SELECT * FROM login_info ";
|
||||||
|
|
||||||
|
try (Connection conn = getConnection();
|
||||||
|
PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||||
|
|
||||||
|
try (ResultSet rs = pstmt.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return mapResultSetToLoginInfo(rs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SQLException e) {
|
||||||
|
System.err.println("根据ID查询登录信息失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将ResultSet映射为LoginInfoResult对象
|
||||||
|
*/
|
||||||
|
private static LoginInfoResult mapResultSetToLoginInfo(ResultSet rs) throws SQLException {
|
||||||
|
LoginInfoResult loginInfo = new LoginInfoResult();
|
||||||
|
|
||||||
|
loginInfo.setId(rs.getLong("id"));
|
||||||
|
loginInfo.setUsername(rs.getString("user_name"));
|
||||||
|
loginInfo.setPassword(rs.getString("password"));
|
||||||
|
loginInfo.setLoginUrl(rs.getString("login_url"));
|
||||||
|
loginInfo.setWinNum(rs.getInt("win_num"));
|
||||||
|
loginInfo.setLoseNum(rs.getInt("lose_num"));
|
||||||
|
loginInfo.setCreateTime(rs.getTimestamp("create_time"));
|
||||||
|
loginInfo.setUpdateTime(rs.getTimestamp("update_time"));
|
||||||
|
|
||||||
|
return loginInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
package com.tem.bocai.util;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TokenCacheManager {
|
|
||||||
|
|
||||||
private static final String CACHE_FILE_PATH = "token_cache.json";
|
|
||||||
private static final long TOKEN_EXPIRE_TIME = 1140000; // 19分钟过期(毫秒) 1140000 = 19 * 60 * 1000
|
|
||||||
|
|
||||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存token到缓存文件
|
|
||||||
*/
|
|
||||||
public void saveToken(String token) {
|
|
||||||
try {
|
|
||||||
Map<String, Object> cacheData = new HashMap<>();
|
|
||||||
cacheData.put("token", token);
|
|
||||||
cacheData.put("timestamp", System.currentTimeMillis());
|
|
||||||
cacheData.put("expireTime", TOKEN_EXPIRE_TIME);
|
|
||||||
cacheData.put("expireMinutes", 19); // 记录过期分钟数
|
|
||||||
|
|
||||||
String json = objectMapper.writeValueAsString(cacheData);
|
|
||||||
Files.write(Paths.get(CACHE_FILE_PATH), json.getBytes());
|
|
||||||
|
|
||||||
System.out.println("Token已保存到缓存文件: " + CACHE_FILE_PATH);
|
|
||||||
System.out.println("Token将在19分钟后过期");
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("保存token缓存失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从缓存文件读取token
|
|
||||||
*/
|
|
||||||
public String readToken() {
|
|
||||||
try {
|
|
||||||
File cacheFile = new File(CACHE_FILE_PATH);
|
|
||||||
if (!cacheFile.exists()) {
|
|
||||||
System.out.println("缓存文件不存在");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = new String(Files.readAllBytes(Paths.get(CACHE_FILE_PATH)));
|
|
||||||
Map<String, Object> cacheData = objectMapper.readValue(json, Map.class);
|
|
||||||
|
|
||||||
String token = (String) cacheData.get("token");
|
|
||||||
Long timestamp = (Long) cacheData.get("timestamp");
|
|
||||||
|
|
||||||
if (!StringUtils.hasText(token) || timestamp == null) {
|
|
||||||
System.out.println("缓存数据不完整");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查token是否过期
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
long elapsedTime = currentTime - timestamp;
|
|
||||||
|
|
||||||
if (elapsedTime > TOKEN_EXPIRE_TIME) {
|
|
||||||
long expiredSeconds = (elapsedTime - TOKEN_EXPIRE_TIME) / 1000;
|
|
||||||
System.out.println("Token已过期 " + expiredSeconds + " 秒");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算剩余时间
|
|
||||||
long remainingTime = TOKEN_EXPIRE_TIME - elapsedTime;
|
|
||||||
long remainingMinutes = remainingTime / 60000;
|
|
||||||
long remainingSeconds = (remainingTime % 60000) / 1000;
|
|
||||||
|
|
||||||
System.out.println("从缓存读取token,剩余有效时间: " +
|
|
||||||
remainingMinutes + "分" + remainingSeconds + "秒");
|
|
||||||
return token;
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("读取token缓存失败: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除token缓存
|
|
||||||
*/
|
|
||||||
public void clearToken() {
|
|
||||||
try {
|
|
||||||
File cacheFile = new File(CACHE_FILE_PATH);
|
|
||||||
if (cacheFile.exists()) {
|
|
||||||
Files.delete(Paths.get(CACHE_FILE_PATH));
|
|
||||||
System.out.println("Token缓存已清除");
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
System.err.println("清除token缓存失败: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查token是否存在且有效
|
|
||||||
*/
|
|
||||||
public boolean hasValidToken() {
|
|
||||||
return readToken() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查token是否即将过期(例如5分钟内过期)
|
|
||||||
*/
|
|
||||||
public boolean isTokenExpiringSoon(int warningMinutes) {
|
|
||||||
try {
|
|
||||||
File cacheFile = new File(CACHE_FILE_PATH);
|
|
||||||
if (!cacheFile.exists()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = new String(Files.readAllBytes(Paths.get(CACHE_FILE_PATH)));
|
|
||||||
Map<String, Object> cacheData = objectMapper.readValue(json, Map.class);
|
|
||||||
|
|
||||||
Long timestamp = (Long) cacheData.get("timestamp");
|
|
||||||
if (timestamp == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
long elapsedTime = currentTime - timestamp;
|
|
||||||
long remainingTime = TOKEN_EXPIRE_TIME - elapsedTime;
|
|
||||||
|
|
||||||
// 检查是否在指定分钟内过期
|
|
||||||
return remainingTime > 0 && remainingTime <= (warningMinutes * 60000);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取token信息(用于调试)
|
|
||||||
*/
|
|
||||||
public Map<String, Object> getTokenInfo() {
|
|
||||||
try {
|
|
||||||
File cacheFile = new File(CACHE_FILE_PATH);
|
|
||||||
if (!cacheFile.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
String json = new String(Files.readAllBytes(Paths.get(CACHE_FILE_PATH)));
|
|
||||||
Map<String, Object> cacheData = objectMapper.readValue(json, Map.class);
|
|
||||||
|
|
||||||
Long timestamp = (Long) cacheData.get("timestamp");
|
|
||||||
if (timestamp != null) {
|
|
||||||
long currentTime = System.currentTimeMillis();
|
|
||||||
long elapsedTime = currentTime - timestamp;
|
|
||||||
long remainingTime = TOKEN_EXPIRE_TIME - elapsedTime;
|
|
||||||
|
|
||||||
cacheData.put("savedTime", new Date(timestamp).toString());
|
|
||||||
cacheData.put("elapsedTime", formatTime(elapsedTime));
|
|
||||||
cacheData.put("remainingTime", formatTime(remainingTime));
|
|
||||||
cacheData.put("isValid", remainingTime > 0);
|
|
||||||
cacheData.put("expireMinutes", 19);
|
|
||||||
|
|
||||||
// 添加过期警告
|
|
||||||
if (remainingTime > 0) {
|
|
||||||
if (remainingTime <= 300000) { // 5分钟内过期
|
|
||||||
cacheData.put("warning", "Token将在5分钟内过期,建议刷新");
|
|
||||||
} else if (remainingTime <= 600000) { // 10分钟内过期
|
|
||||||
cacheData.put("warning", "Token将在10分钟内过期");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cacheData;
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 格式化时间显示
|
|
||||||
*/
|
|
||||||
private String formatTime(long millis) {
|
|
||||||
if (millis <= 0) {
|
|
||||||
return "已过期";
|
|
||||||
}
|
|
||||||
|
|
||||||
long minutes = millis / 60000;
|
|
||||||
long seconds = (millis % 60000) / 1000;
|
|
||||||
|
|
||||||
if (minutes > 0) {
|
|
||||||
return minutes + "分" + seconds + "秒";
|
|
||||||
} else {
|
|
||||||
return seconds + "秒";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取token过期时间配置
|
|
||||||
*/
|
|
||||||
public static long getTokenExpireTime() {
|
|
||||||
return TOKEN_EXPIRE_TIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取token过期分钟数
|
|
||||||
*/
|
|
||||||
public static int getTokenExpireMinutes() {
|
|
||||||
return (int) (TOKEN_EXPIRE_TIME / 60000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
455
src/main/java/com/tem/bocai/util/TokenCacheService.java
Normal file
455
src/main/java/com/tem/bocai/util/TokenCacheService.java
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
package com.tem.bocai.util;
|
||||||
|
|
||||||
|
import com.tem.bocai.service.LoginService;
|
||||||
|
import net.sourceforge.tess4j.Tesseract;
|
||||||
|
import net.sourceforge.tess4j.TesseractException;
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.CookieStore;
|
||||||
|
import org.apache.http.client.config.RequestConfig;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.apache.http.cookie.Cookie;
|
||||||
|
import org.apache.http.impl.client.BasicCookieStore;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.cache.Cache;
|
||||||
|
import org.springframework.cache.CacheManager;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class TokenCacheService {
|
||||||
|
|
||||||
|
private static final String TOKEN_CACHE_NAME = "tokenCache";
|
||||||
|
private static final String TOKEN_KEY = "login_token";
|
||||||
|
private static final String TOKEN_INFO_KEY = "token_info";
|
||||||
|
private static final int MAX_RETRY = 10;
|
||||||
|
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
||||||
|
@Autowired
|
||||||
|
private Tesseract tesseract;
|
||||||
|
@Autowired
|
||||||
|
private CacheManager tokenCacheManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存token到缓存
|
||||||
|
*/
|
||||||
|
public void saveToken(String token) {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
cache.put(TOKEN_KEY, token);
|
||||||
|
System.out.println("Token已保存到内存缓存");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存获取token
|
||||||
|
*/
|
||||||
|
public String getToken() {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
return cache.get(TOKEN_KEY, String.class);
|
||||||
|
} else {
|
||||||
|
String token = getTokenSqlite();
|
||||||
|
System.out.println("重新登入获取的token==" + token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存获取token,如果不存在则通过回调函数获取
|
||||||
|
*/
|
||||||
|
public String getToken(Callable<String> tokenLoader) {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
return cache.get(TOKEN_KEY, tokenLoader);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除token缓存
|
||||||
|
*/
|
||||||
|
public void clearToken() {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
cache.evict(TOKEN_KEY);
|
||||||
|
System.out.println("Token已从内存缓存清除");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否有有效token
|
||||||
|
*/
|
||||||
|
public boolean hasValidToken() {
|
||||||
|
return getToken() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存token相关信息
|
||||||
|
*/
|
||||||
|
public void saveTokenInfo(String key, Object value) {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
cache.put(TOKEN_INFO_KEY + "_" + key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取token相关信息
|
||||||
|
*/
|
||||||
|
public <T> T getTokenInfo(String key, Class<T> type) {
|
||||||
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
|
if (cache != null) {
|
||||||
|
return cache.get(TOKEN_INFO_KEY + "_" + key, type);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getTokenSqlite() {
|
||||||
|
String token = "";
|
||||||
|
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||||
|
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
||||||
|
try {
|
||||||
|
token = attemptLogin();
|
||||||
|
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
if (attempt < MAX_RETRY) {
|
||||||
|
waitForRetry(attempt);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 等待重试
|
||||||
|
*/
|
||||||
|
public void waitForRetry(int attempt) throws InterruptedException {
|
||||||
|
System.out.println("\n等待2秒后进行下一次尝试...");
|
||||||
|
Thread.sleep(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单次登录尝试
|
||||||
|
*/
|
||||||
|
public String attemptLogin() {
|
||||||
|
CookieStore cookieStore = new BasicCookieStore();
|
||||||
|
try (CloseableHttpClient httpClient = createHttpClient(cookieStore)) {
|
||||||
|
// 1. 获取验证码
|
||||||
|
byte[] imageData = fetchCaptcha(httpClient);
|
||||||
|
if (imageData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 2. OCR识别验证码
|
||||||
|
String code = processCaptcha(imageData);
|
||||||
|
if (code == null || code.length() != 4) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 3. 执行登录
|
||||||
|
return performLogin(httpClient, cookieStore, code);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("登录尝试失败" + e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建HttpClient
|
||||||
|
*/
|
||||||
|
private CloseableHttpClient createHttpClient(CookieStore cookieStore) {
|
||||||
|
return HttpClients.custom()
|
||||||
|
.setDefaultCookieStore(cookieStore)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码图片
|
||||||
|
*/
|
||||||
|
private byte[] fetchCaptcha(CloseableHttpClient httpClient)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
System.out.println("获取验证码...");
|
||||||
|
|
||||||
|
// 添加随机延迟
|
||||||
|
Thread.sleep(1000 + (long) (Math.random() * 1000));
|
||||||
|
|
||||||
|
HttpGet getCaptcha = new HttpGet(BASE_URL + "/code");
|
||||||
|
setCommonHeaders(getCaptcha);
|
||||||
|
getCaptcha.setHeader("Referer", BASE_URL + "/login");
|
||||||
|
|
||||||
|
try (CloseableHttpResponse captchaResponse = httpClient.execute(getCaptcha)) {
|
||||||
|
int captchaStatus = captchaResponse.getStatusLine().getStatusCode();
|
||||||
|
System.out.println("验证码响应状态码: " + captchaStatus);
|
||||||
|
|
||||||
|
if (captchaStatus == 200) {
|
||||||
|
return EntityUtils.toByteArray(captchaResponse.getEntity());
|
||||||
|
} else if (captchaStatus == 429) {
|
||||||
|
System.out.println("获取验证码被限速,等待后重试...");
|
||||||
|
Thread.sleep(3000);
|
||||||
|
} else {
|
||||||
|
System.out.println("获取验证码失败: " + captchaStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理验证码识别
|
||||||
|
*/
|
||||||
|
private String processCaptcha(byte[] imageData)
|
||||||
|
throws IOException, TesseractException {
|
||||||
|
|
||||||
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
|
||||||
|
String rawOcr = tesseract.doOCR(image);
|
||||||
|
|
||||||
|
// 清理验证码
|
||||||
|
String code = rawOcr.replaceAll("\\s+", "").trim();
|
||||||
|
code = code.replaceAll("[^0-9]", ""); // 只保留数字
|
||||||
|
System.out.println("OCR原始结果: " + rawOcr);
|
||||||
|
System.out.println("清理后验证码: [" + code + "] 长度: " + code.length());
|
||||||
|
// 保存图片用于调试
|
||||||
|
//saveCaptchaImage(image);
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置通用请求头
|
||||||
|
*/
|
||||||
|
private void setCommonHeaders(HttpRequestBase request) {
|
||||||
|
request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||||
|
request.setHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
|
||||||
|
request.setHeader("Accept-Encoding", "gzip, deflate, br");
|
||||||
|
request.setHeader("Connection", "keep-alive");
|
||||||
|
request.setHeader("Upgrade-Insecure-Requests", "1");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存验证码图片
|
||||||
|
*/
|
||||||
|
/* private void saveCaptchaImage(BufferedImage image) throws IOException {
|
||||||
|
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||||
|
File output = new File("captcha_" + timestamp + ".png");
|
||||||
|
ImageIO.write(image, "png", output);
|
||||||
|
System.out.println("验证码图片已保存到: " + output.getAbsolutePath());
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行登录请求
|
||||||
|
*/
|
||||||
|
private String performLogin(CloseableHttpClient httpClient,
|
||||||
|
CookieStore cookieStore,
|
||||||
|
String code) throws IOException, InterruptedException {
|
||||||
|
|
||||||
|
System.out.println("执行登录...");
|
||||||
|
// 等待一下再发送登录请求
|
||||||
|
Thread.sleep(1500 + (long) (Math.random() * 1000));
|
||||||
|
|
||||||
|
HttpPost loginPost = createLoginRequest(code);
|
||||||
|
|
||||||
|
try (CloseableHttpResponse loginResponse = httpClient.execute(loginPost)) {
|
||||||
|
return processLoginResponse(loginResponse, cookieStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建登录请求
|
||||||
|
*/
|
||||||
|
private HttpPost createLoginRequest(String code) throws UnsupportedEncodingException {
|
||||||
|
HttpPost loginPost = new HttpPost(BASE_URL + "/login");
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
setCommonHeaders(loginPost);
|
||||||
|
loginPost.setHeader("Referer", BASE_URL + "/login");
|
||||||
|
loginPost.setHeader("Origin", BASE_URL);
|
||||||
|
loginPost.setHeader("Accept", "application/json, text/plain, */*");
|
||||||
|
|
||||||
|
// 构建登录参数
|
||||||
|
List<NameValuePair> params = new ArrayList<>();
|
||||||
|
params.add(new BasicNameValuePair("type", "1"));
|
||||||
|
params.add(new BasicNameValuePair("account", "pmk1"));
|
||||||
|
params.add(new BasicNameValuePair("password", "Asd123123"));
|
||||||
|
params.add(new BasicNameValuePair("code", code));
|
||||||
|
|
||||||
|
loginPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
|
||||||
|
|
||||||
|
// 禁用自动重定向
|
||||||
|
RequestConfig requestConfig = RequestConfig.custom()
|
||||||
|
.setRedirectsEnabled(false)
|
||||||
|
.build();
|
||||||
|
loginPost.setConfig(requestConfig);
|
||||||
|
|
||||||
|
return loginPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理登录响应
|
||||||
|
*/
|
||||||
|
private String processLoginResponse(CloseableHttpResponse loginResponse,
|
||||||
|
CookieStore cookieStore) {
|
||||||
|
try {
|
||||||
|
int statusCode = loginResponse.getStatusLine().getStatusCode();
|
||||||
|
System.out.println("登录响应状态码: " + statusCode);
|
||||||
|
|
||||||
|
// 处理限速
|
||||||
|
if (statusCode == 429) {
|
||||||
|
handleRateLimit(loginResponse);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印响应头
|
||||||
|
printResponseHeaders(loginResponse);
|
||||||
|
|
||||||
|
// 检查重定向
|
||||||
|
if (statusCode == 302) {
|
||||||
|
if (checkRedirectForError(loginResponse)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取响应体
|
||||||
|
String tokenFromBody = extractTokenFromResponseBody(loginResponse);
|
||||||
|
if (tokenFromBody != null) {
|
||||||
|
return tokenFromBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从cookies中提取token
|
||||||
|
return extractTokenFromCookies(cookieStore, statusCode);
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("请求异常" + e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印响应头
|
||||||
|
*/
|
||||||
|
private void printResponseHeaders(CloseableHttpResponse response) {
|
||||||
|
System.out.println("响应头:");
|
||||||
|
for (Header header : response.getAllHeaders()) {
|
||||||
|
System.out.println(" " + header.getName() + ": " + header.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查重定向是否包含错误
|
||||||
|
*/
|
||||||
|
private boolean checkRedirectForError(CloseableHttpResponse response) {
|
||||||
|
Header locationHeader = response.getFirstHeader("Location");
|
||||||
|
if (locationHeader != null) {
|
||||||
|
String location = locationHeader.getValue();
|
||||||
|
System.out.println("重定向到: " + location);
|
||||||
|
|
||||||
|
if (location.contains("e=3")) {
|
||||||
|
System.out.println("验证码错误 (e=3)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从cookies中提取token
|
||||||
|
*/
|
||||||
|
private String extractTokenFromCookies(CookieStore cookieStore, int statusCode) {
|
||||||
|
List<Cookie> cookies = cookieStore.getCookies();
|
||||||
|
System.out.println("所有cookies (" + cookies.size() + "个):");
|
||||||
|
|
||||||
|
String token = null;
|
||||||
|
for (Cookie cookie : cookies) {
|
||||||
|
System.out.println(" " + cookie.getName() + " = " + cookie.getValue());
|
||||||
|
|
||||||
|
if ("token".equals(cookie.getName()) ||
|
||||||
|
cookie.getName().toLowerCase().contains("token")) {
|
||||||
|
token = cookie.getValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
System.out.println("\n[SUCCESS] Login OK!");
|
||||||
|
System.out.println("Token: " + token);
|
||||||
|
return token;
|
||||||
|
} else if (statusCode == 200) {
|
||||||
|
System.out.println("登录返回200但没有找到token,可能需要检查其他认证方式");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应体中提取token
|
||||||
|
*/
|
||||||
|
private String extractTokenFromResponseBody(CloseableHttpResponse response) throws IOException {
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
|
||||||
|
if (responseBody != null && !responseBody.isEmpty()) {
|
||||||
|
System.out.println("响应体: " + responseBody);
|
||||||
|
|
||||||
|
// 检查响应体中是否有token(JSON格式)
|
||||||
|
if (responseBody.contains("\"token\"")) {
|
||||||
|
// 简单提取token
|
||||||
|
int start = responseBody.indexOf("\"token\":\"");
|
||||||
|
if (start != -1) {
|
||||||
|
start += 9;
|
||||||
|
int end = responseBody.indexOf("\"", start);
|
||||||
|
if (end != -1) {
|
||||||
|
String token = responseBody.substring(start, end);
|
||||||
|
System.out.println("\n[SUCCESS] 从响应体找到Token!");
|
||||||
|
System.out.println("Token: " + token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 消耗实体
|
||||||
|
EntityUtils.consume(response.getEntity());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理速率限制
|
||||||
|
*/
|
||||||
|
private void handleRateLimit(CloseableHttpResponse response) throws InterruptedException {
|
||||||
|
System.out.println("登录请求被限速 (429 Too Many Requests)");
|
||||||
|
|
||||||
|
Header retryAfterHeader = response.getFirstHeader("Retry-After");
|
||||||
|
if (retryAfterHeader != null) {
|
||||||
|
try {
|
||||||
|
int retryAfterSeconds = Integer.parseInt(retryAfterHeader.getValue());
|
||||||
|
System.out.println("服务器要求等待 " + retryAfterSeconds + " 秒");
|
||||||
|
Thread.sleep(retryAfterSeconds * 1000L);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
System.out.println("等待5秒后重试");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("等待3秒后重试");
|
||||||
|
Thread.sleep(3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user