token获取
This commit is contained in:
@@ -14,7 +14,7 @@ public class LoginCrawler {
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
// 构造函数注入
|
||||
// 登入
|
||||
public LoginCrawler(LoginService loginService) {
|
||||
this.loginService = loginService;
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class LoginCrawler {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
|
||||
//今日已结爬取
|
||||
@GetMapping("/ocr/completedToday")
|
||||
public ResponseEntity<String> completedToday() throws IOException, TesseractException {
|
||||
String result = loginService.completedToday();
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -5,9 +5,6 @@ public interface LoginService {
|
||||
|
||||
String loginAutomatic(String username, String password,String loginUrl,Integer winNum,Integer loseNum);
|
||||
|
||||
//获取token
|
||||
String getToken(String username, String password, String loginUrl);
|
||||
|
||||
//获取token
|
||||
String completedToday();
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.tem.bocai.service.impl;
|
||||
|
||||
import com.tem.bocai.entity.LoginInfoResult;
|
||||
import com.tem.bocai.service.LoginService;
|
||||
import com.tem.bocai.util.CompletedTodayCrawler;
|
||||
import com.tem.bocai.util.LotteryDataPipeline;
|
||||
import com.tem.bocai.util.LotteryWebMagicCrawler;
|
||||
import com.tem.bocai.util.TokenCacheService;
|
||||
import com.tem.bocai.util.*;
|
||||
import org.springframework.stereotype.Service;
|
||||
import net.sourceforge.tess4j.Tesseract;
|
||||
import net.sourceforge.tess4j.TesseractException;
|
||||
@@ -24,6 +22,7 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -37,7 +36,7 @@ import us.codecraft.webmagic.Spider;
|
||||
@Service
|
||||
public class LoginServiceImpl implements LoginService {
|
||||
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
|
||||
private Tesseract tesseract;
|
||||
@Autowired
|
||||
@@ -49,9 +48,11 @@ public class LoginServiceImpl implements LoginService {
|
||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
||||
try {
|
||||
token = attemptLogin();
|
||||
token = tokenCacheService.attemptLogin();
|
||||
tokenCacheService.saveToken(token);
|
||||
if (token != null && !token.isEmpty()) {
|
||||
//保存用户信息
|
||||
addLoginInfo(username, password, loginUrl, winNum, loseNum);
|
||||
// 2. 创建爬虫实例,传入token
|
||||
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token);
|
||||
|
||||
@@ -75,7 +76,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
}
|
||||
|
||||
if (attempt < MAX_RETRY) {
|
||||
waitForRetry(attempt);
|
||||
tokenCacheService.waitForRetry(attempt);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
||||
@@ -85,29 +86,6 @@ public class LoginServiceImpl implements LoginService {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getToken(String username, String password, String loginUrl) {
|
||||
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 "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String completedToday() {
|
||||
String token = tokenCacheService.getToken();
|
||||
@@ -127,309 +105,23 @@ public class LoginServiceImpl implements LoginService {
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 添加登录信息
|
||||
*/
|
||||
public boolean addLoginInfo(String username, String password,
|
||||
String loginUrl, Integer winNum, Integer loseNum) {
|
||||
LoginInfoResult loginInfo = new LoginInfoResult();
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建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 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.tem.bocai.util;
|
||||
|
||||
import com.tem.bocai.entity.LoginInfoResult;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -24,121 +26,6 @@ public class SQLiteUtil {
|
||||
return DriverManager.getConnection(DB_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库表结构
|
||||
*/
|
||||
/*public static void initDatabase() {
|
||||
Connection conn = null;
|
||||
Statement stmt = null;
|
||||
|
||||
try {
|
||||
conn = getConnection();
|
||||
stmt = conn.createStatement();
|
||||
|
||||
// 开启事务
|
||||
conn.setAutoCommit(false);
|
||||
*//* // 删除旧表
|
||||
stmt.execute("DROP TABLE IF EXISTS lottery_results");
|
||||
System.out.println("已删除旧表");*//*
|
||||
// 检查表是否存在
|
||||
boolean tableExists = false;
|
||||
try (ResultSet rs = conn.getMetaData().getTables(null, null, "lottery_results", null)) {
|
||||
tableExists = rs.next();
|
||||
}
|
||||
|
||||
if (tableExists) {
|
||||
System.out.println("表已存在,检查列结构...");
|
||||
// checkAndUpdateTableStructure(conn);
|
||||
} else {
|
||||
System.out.println("表不存在,创建新表...");
|
||||
createTable(conn);
|
||||
}
|
||||
|
||||
// 提交事务
|
||||
conn.commit();
|
||||
System.out.println("数据库表初始化/更新成功");
|
||||
|
||||
} catch (SQLException e) {
|
||||
try {
|
||||
if (conn != null) conn.rollback();
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
e.printStackTrace();
|
||||
System.err.println("数据库表初始化失败: " + e.getMessage());
|
||||
} finally {
|
||||
closeResources(null, stmt, conn);
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* 创建新表
|
||||
*/
|
||||
private static void createTable(Connection conn) throws SQLException {
|
||||
String createTableSQL = """
|
||||
CREATE TABLE lottery_results (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
time TEXT,
|
||||
result TEXT,
|
||||
winner INTEGER,
|
||||
gd1 TEXT,
|
||||
gd2 TEXT,
|
||||
sum1 INTEGER,
|
||||
sum2 INTEGER,
|
||||
glh_result TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(createTableSQL);
|
||||
System.out.println("表创建成功");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并更新表结构
|
||||
*/
|
||||
private static void checkAndUpdateTableStructure(Connection conn) throws SQLException {
|
||||
// 定义需要的列和类型
|
||||
String[][] requiredColumns = {
|
||||
{"result", "TEXT"},
|
||||
{"winner", "INTEGER"},
|
||||
{"gd1", "TEXT"},
|
||||
{"gd2", "TEXT"},
|
||||
{"sum1", "INTEGER"},
|
||||
{"sum2", "INTEGER"},
|
||||
{"glh_result", "TEXT"},
|
||||
{"created_at", "TIMESTAMP"},
|
||||
{"updated_at", "TIMESTAMP"}
|
||||
};
|
||||
|
||||
// 获取现有列
|
||||
List<String> existingColumns = new ArrayList<>();
|
||||
try (ResultSet rs = conn.getMetaData().getColumns(null, null, "lottery_results", null)) {
|
||||
while (rs.next()) {
|
||||
existingColumns.add(rs.getString("COLUMN_NAME").toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
// 添加缺失的列
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
for (String[] column : requiredColumns) {
|
||||
String columnName = column[0];
|
||||
String columnType = column[1];
|
||||
|
||||
if (!existingColumns.contains(columnName.toLowerCase())) {
|
||||
String alterSQL = String.format(
|
||||
"ALTER TABLE lottery_results ADD COLUMN %s %s",
|
||||
columnName, columnType
|
||||
);
|
||||
stmt.execute(alterSQL);
|
||||
System.out.println("已添加列: " + columnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将爬虫数据写入SQLite数据库
|
||||
@@ -153,10 +40,10 @@ public class SQLiteUtil {
|
||||
//initDatabase();
|
||||
|
||||
String insertSQL = """
|
||||
INSERT OR REPLACE INTO lottery_results
|
||||
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
|
||||
VALUES (?,?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
INSERT OR REPLACE INTO lottery_results
|
||||
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
|
||||
VALUES (?,?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
|
||||
Connection conn = null;
|
||||
PreparedStatement pstmt = null;
|
||||
@@ -356,34 +243,144 @@ public class SQLiteUtil {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查看表结构
|
||||
* 添加/更新登录信息 - 如果用户名存在则先删除再插入(更新)
|
||||
*/
|
||||
public static void showTableStructure() {
|
||||
String sql = "PRAGMA table_info(lottery_results)";
|
||||
public static boolean addOrUpdateLoginInfo(LoginInfoResult loginInfo) {
|
||||
if (loginInfo == null || loginInfo.getUsername() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
Statement stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery(sql)) {
|
||||
Connection conn = null;
|
||||
PreparedStatement deleteStmt = null;
|
||||
PreparedStatement insertStmt = null;
|
||||
|
||||
System.out.println("表结构 lottery_results:");
|
||||
System.out.println("==========================");
|
||||
System.out.printf("%-5s %-15s %-10s %-5s %-5s %-5s%n",
|
||||
"cid", "name", "type", "notnull", "dflt_value", "pk");
|
||||
System.out.println("------------------------------------------------------------");
|
||||
try {
|
||||
conn = getConnection();
|
||||
conn.setAutoCommit(false); // 开启事务
|
||||
|
||||
while (rs.next()) {
|
||||
System.out.printf("%-5d %-15s %-10s %-5d %-10s %-5d%n",
|
||||
rs.getInt("cid"),
|
||||
rs.getString("name"),
|
||||
rs.getString("type"),
|
||||
rs.getInt("notnull"),
|
||||
rs.getString("dflt_value"),
|
||||
rs.getInt("pk"));
|
||||
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,10 +1,35 @@
|
||||
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
|
||||
@@ -13,7 +38,10 @@ 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;
|
||||
|
||||
@@ -35,8 +63,11 @@ public class TokenCacheService {
|
||||
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;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,4 +120,336 @@ public class TokenCacheService {
|
||||
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