Compare commits

..

22 Commits

Author SHA1 Message Date
xuelijun
67335e08aa 初始化爬取最近7天的开奖结果任务 2026-02-25 09:53:43 +08:00
029109d559 新增投注记录到数据库 2026-02-13 18:14:45 +08:00
xuelijun
740dc15197 修改插入 2026-02-12 10:29:35 +08:00
274f2a31af 新增投注记录到数据库 2026-02-11 17:44:00 +08:00
xuelijun
f6a9730879 修改时间1 2026-02-10 10:21:59 +08:00
xuelijun
e1695854a8 修改时间 2026-02-10 09:55:35 +08:00
7a61514e1c 修改爬取数据为增量 2026-02-10 09:42:13 +08:00
22e3d844bf 去除测试 2026-02-06 11:29:08 +08:00
xuelijun
1a562ffc1c 请求重试 2026-02-06 11:12:51 +08:00
3f7b0568d3 调整逻辑 2026-02-06 01:02:46 +08:00
xuelijun
419e00e74a 修改时间 2026-02-06 09:49:46 +08:00
bded6f899b 打开定时任务 2026-02-04 11:41:19 +08:00
c7918ad0e4 修改前端 2026-02-04 11:38:54 +08:00
9376a85673 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	bocai.db
#	src/main/java/com/tem/bocai/util/HttpClientExample.java
2026-02-04 09:55:37 +08:00
3f4e0b8f8f 调整金额 2026-02-04 09:55:09 +08:00
xuelijun
9460367dc1 余额 2026-01-31 19:39:31 +08:00
19a8dc1a48 调整金额 2026-01-31 18:40:27 +08:00
xuelijun
6920855953 登入失败禁止登入3 2026-01-31 18:16:48 +08:00
xuelijun
f852676c83 登入失败禁止登入2 2026-01-31 17:44:04 +08:00
4c2fe1d2a1 调整金额 2026-01-31 17:14:05 +08:00
2060ddff83 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/com/tem/bocai/schedules/CrawlerSchedule.java
2026-01-31 14:59:34 +08:00
4ff6b4bb24 调整金额 2026-01-31 14:59:18 +08:00
21 changed files with 2062 additions and 247 deletions

BIN
bocai.db

Binary file not shown.

View File

@@ -4,13 +4,13 @@ import axios from 'axios';
import * as echarts from 'echarts';
// 输入框数据
const input1 = ref('');
const input2 = ref('');
const betAmount = ref('');
// 登录模态框数据
const loginDialogVisible = ref(false);
const isLoggedIn = ref(false);
const username = ref('未记录');
const balance = ref('0'); // 余额
const loginForm = ref({
username: '',
password: '',
@@ -64,7 +64,27 @@ function updateChart1() {
left: 'center'
},
tooltip: {
trigger: 'axis'
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
},
formatter: function(params) {
let result = params[0].name + '<br/>';
params.forEach(function(item) {
let value = item.value;
let color = item.color;
let status = value >= 0 ? '盈利' : '亏损';
let statusColor = value >= 0 ? '#4CAF50' : '#ff6b6b';
result += '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' + color + '"></span>';
result += item.seriesName + ': ';
result += '<span style="color:' + statusColor + ';">' + value + '</span> ';
result += '<span style="color:' + statusColor + ';">(' + status + ')</span><br/>';
});
return result;
}
},
legend: {
data: ['数据'],
@@ -217,20 +237,20 @@ async function fetchUserSettings() {
username.value = response.data.username;
isLoggedIn.value = true;
}
// 更新止盈点
if (response.data.winNum) {
input1.value = response.data.winNum;
// 更新投注金额
if (response.data.betAmount) {
betAmount.value = response.data.betAmount;
}
// 更新止亏点
if (response.data.loseNum) {
input2.value = response.data.loseNum;
// 更新余额
if (response.data.balance) {
balance.value = response.data.balance;
}
}
console.log('用户设置数据获取完成');
console.log('username.value:', username.value);
console.log('input1.value:', input1.value);
console.log('input2.value:', input2.value);
console.log('betAmount.value:', betAmount.value);
console.log('balance.value:', balance.value);
} catch (err) {
console.error('获取用户设置数据失败:', err);
// 失败时不显示错误,使用默认值
@@ -279,7 +299,8 @@ async function handleLogin() {
// 调用登录API
const response = await axios.post(loginApiUrl, {
username: loginForm.value.username,
password: loginForm.value.password
password: loginForm.value.password,
loginUrl: loginForm.value.loginUrl
});
// 处理登录结果
@@ -291,14 +312,6 @@ async function handleLogin() {
// 更新用户名
username.value = loginForm.value.username;
// 重置表单
loginForm.value = {
username: '',
password: '',
loginUrl: 'https://4701268539-esh.qdk63ayw8g.com'
};
console.log('登录成功:', response.data);
} else {
// 登录失败
@@ -330,19 +343,18 @@ function handleLogout() {
// 处理确认按钮点击
async function handleConfirm() {
console.log('确认按钮点击,止盈点:', input1.value, '止亏点:', input2.value);
console.log('确认按钮点击,投注金额:', betAmount.value);
// 验证输入
if (!input1.value || !input2.value) {
alert('请填写完整的止盈止亏点');
if (!betAmount.value) {
alert('请填写投注金额');
return;
}
try {
// 构建提交数据
const submitData = {
winNum: input1.value,
loseNum: input2.value
betAmount: betAmount.value
};
console.log('提交数据:', submitData);
@@ -390,6 +402,31 @@ async function handleStop() {
}
}
// 处理开始按钮点击
async function handleStart() {
console.log('开始按钮点击');
try {
// 调用后端开始API
const response = await axios.post('http://localhost:8080/api/ocr/saveUserInfo', {
onOff: 1
});
// 处理响应结果
if (response.data && response.code !== 500) {
console.log('开始成功:', response.data);
alert('开始成功');
} else {
console.error('开始失败:', response.data);
alert('开始失败: ' + (response.data.message || '未知错误'));
}
} catch (err) {
console.error('开始失败:', err);
alert('开始操作失败,请检查网络连接');
}
}
onUnmounted(() => {
// 销毁图表
chart1.value?.dispose();
@@ -408,9 +445,12 @@ onUnmounted(() => {
<div class="account-avatar">👤</div>
<div class="account-details">
<div class="account-name">{{ username }}</div>
<div class="account-balance">余额: {{ balance }}</div>
</div>
<div class="account-actions">
<button type="button" class="login-button" @click="loginDialogVisible = true">账号信息</button>
<button type="button" class="start-button" @click="handleStart">开始</button>
<button type="button" class="stop-button" @click="handleStop">停止</button>
</div>
</div>
@@ -418,7 +458,7 @@ onUnmounted(() => {
<div v-if="loginDialogVisible" class="modal-overlay">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>登录</h3>
<h3>账号信息</h3>
<button type="button" class="modal-close" @click="loginDialogVisible = false">×</button>
</div>
<div class="modal-body">
@@ -453,16 +493,11 @@ onUnmounted(() => {
<!-- 顶部输入框区域 -->
<div class="top-inputs">
<div class="input-group">
<label for="betAmount">投注金额</label>
<div class="input-with-button">
<input type="text" id="input1" v-model="input1" placeholder="请输入止盈点">
</div>
</div>
<div class="input-group">
<div class="input-with-button">
<input type="text" id="input2" v-model="input2" placeholder="请输入止亏点">
<input type="text" id="betAmount" v-model="betAmount" placeholder="请输入投注金额">
<div class="button-group">
<button type="button" class="confirm-button" @click="handleConfirm">确认</button>
<button type="button" class="stop-button" @click="handleStop">停止</button>
</div>
</div>
</div>
@@ -483,17 +518,19 @@ onUnmounted(() => {
<thead>
<tr>
<th>ID</th>
<th>期数</th>
<th>开奖时间</th>
<th>开奖号码</th>
<th>投注金额</th>
<th>结果</th>
<th>金额</th>
<th>投注时间</th>
</tr>
</thead>
<tbody>
<tr v-for="item in tableData" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.time }}</td>
<td>{{ item.betId }}</td>
<td>{{ item.betAmount }}</td>
<td>{{ item.result }}</td>
<td>{{ item.resultAmount }}</td>
<td>{{ item.time }}</td>
</tr>
</tbody>
</table>
@@ -569,6 +606,11 @@ onUnmounted(() => {
margin-bottom: 4px;
}
.account-balance {
font-size: 0.85rem;
color: #666;
}
.account-role {
font-size: 0.85rem;
color: #666;
@@ -600,6 +642,13 @@ onUnmounted(() => {
transform: translateY(1px);
}
/* 账号操作中的开始和停止按钮 */
.account-actions .start-button,
.account-actions .stop-button {
padding: 6px 12px;
font-size: 0.85rem;
}
.logout-button {
padding: 6px 12px;
background-color: #6c757d;
@@ -633,14 +682,15 @@ onUnmounted(() => {
.input-group {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.input-with-button {
display: flex;
gap: 8px;
align-items: flex-start;
align-items: center;
flex: 1;
}
.input-with-button input {
@@ -686,6 +736,29 @@ onUnmounted(() => {
gap: 8px;
}
/* 开始按钮 */
.start-button {
padding: 8px 14px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.start-button:hover {
background-color: #45a049;
}
.start-button:active {
background-color: #3d8b40;
transform: translateY(1px);
}
/* 停止按钮 */
.stop-button {
padding: 10px 16px;

View File

@@ -18,9 +18,9 @@ public class BocaiApplication {
// // 依次执行三个任务
//
// 1. 执行CrawlerSchedule方法
System.out.println("\n=== 开始执行CrawlerSchedule任务 ===");
System.out.println("\n=== 开始执行初始化爬取最近7天的开奖结果任务 ===");
CrawlerSchedule crawlerSchedule = context.getBean(CrawlerSchedule.class);
crawlerSchedule.executeLotteryDraw();
crawlerSchedule.executeLotteryDrawHistory();
//
// 3. 执行ExBetScriptSchedule方法
// System.out.println("\n=== 开始执行ExBetScriptSchedule任务 ===");

View File

@@ -12,6 +12,7 @@ public class TessConfig {
Tesseract instance = new Tesseract();
instance.setLanguage("oci"); // 设置语言包,这里使用英语
// instance.setDatapath("src/main/resources/tessdata"); // 设置语言包路径
instance.setDatapath("tessdata"); // 设置语言包路径
return instance;
}

View File

@@ -1,9 +1,7 @@
package com.tem.bocai.controller;
import com.tem.bocai.entity.CompletedToday;
import com.tem.bocai.entity.LotteryResult;
import com.tem.bocai.repository.CompletedTodayRepository;
import com.tem.bocai.repository.LotteryResultRepository;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,9 +13,6 @@ import java.util.*;
@RequestMapping("/api")
public class ChartController {
@Autowired
private LotteryResultRepository lotteryResultRepository;
@Autowired
private CompletedTodayRepository completedTodayRepository;
@@ -27,56 +22,92 @@ public class ChartController {
Map<String, Object> response = new HashMap<>();
try {
// 获取今日日期的字符串表示格式yyyy-MM-dd
String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
System.out.println("今日日期: " + today);
// 获取今日日期
Date now = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
// 设置为当天00:00:00
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date startOfDay = calendar.getTime();
// 计算当前时间
Date currentTime = new Date();
// 计算结束时间明天的00:00:00
Calendar endCalendar = (Calendar) calendar.clone();
endCalendar.add(Calendar.DAY_OF_MONTH, 1);
Date endOfDay = endCalendar.getTime();
// 将startTime和endTime转换为字符串格式使用"yyyy-MM-dd HH:mm:ss"格式
String startTimeStr = DateFormatUtils.format(startOfDay, "yyyy-MM-dd HH:mm:ss");
String endTimeStr = DateFormatUtils.format(endOfDay, "yyyy-MM-dd HH:mm:ss");
List<CompletedToday> todayData = completedTodayRepository.findTodayData(startTimeStr, endTimeStr);
// 从数据库获取今日的CompletedToday数据
List<CompletedToday> completedTodays = completedTodayRepository.findAll();
System.out.println("获取到" + completedTodays.size() + "条CompletedToday数据");
// 过滤出今日的数据
List<CompletedToday> todayData = new ArrayList<>();
for (CompletedToday item : completedTodays) {
if (item.getTime() != null && item.getTime().toString().contains(today)) {
todayData.add(item);
// 过滤出当前时间之前的数据
List<CompletedToday> filteredData = new ArrayList<>();
for (CompletedToday item : todayData) {
if (item.getCreateTime().after(startOfDay)) {
filteredData.add(item);
}
}
System.out.println("过滤后获取到" + todayData.size() + "条今日数据");
todayData = filteredData;
// 按时间排序
todayData.sort(Comparator.comparing(CompletedToday::getTime));
todayData.sort(Comparator.comparing(CompletedToday::getCreateTime));
// 提取数据
List<Double> data = new ArrayList<>();
// 生成每5分钟的时间间隔标签
List<String> labels = new ArrayList<>();
List<Double> data = new ArrayList<>();
// 遍历每5分钟间隔
Calendar intervalCalendar = (Calendar) calendar.clone();
while (intervalCalendar.getTime().before(currentTime)) {
// 生成时间标签
String timeLabel = DateFormatUtils.format(intervalCalendar.getTime(), "HH:mm");
labels.add(timeLabel);
// 计算该时间点的累计盈亏
double intervalProfit = 0.0;
for (CompletedToday item : todayData) {
// 使用resultAmount作为折线图数据
data.add(item.getResultAmount());
// 使用时间作为标签(只保留时分部分)
Date time = item.getTime();
String format = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss");
if (time != null && format.length() >= 16) {
labels.add(format.substring(11, 16)); // 提取时分部分
} else {
labels.add(format);
if (item.getCreateTime().before(intervalCalendar.getTime())) {
intervalProfit += item.getResultAmount();
}
}
// 更新总盈亏
data.add(intervalProfit);
// 增加5分钟
intervalCalendar.add(Calendar.MINUTE, 5);
}
// 如果没有今日数据,使用默认数据
// 如果没有今日数据,生成默认的5分钟间隔数据
if (data.isEmpty()) {
data = Arrays.asList(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
labels = Arrays.asList("00:00", "04:00", "08:00", "12:00", "16:00", "20:00", "23:59");
labels = new ArrayList<>();
data = new ArrayList<>();
Calendar defaultCalendar = (Calendar) calendar.clone();
while (defaultCalendar.getTime().before(currentTime)) {
String timeLabel = DateFormatUtils.format(defaultCalendar.getTime(), "HH:mm");
labels.add(timeLabel);
data.add(0.0);
defaultCalendar.add(Calendar.MINUTE, 5);
}
// 添加当前时间点
if (!labels.isEmpty()) {
String currentTimeLabel = DateFormatUtils.format(currentTime, "HH:mm");
labels.add(currentTimeLabel);
data.add(0.0);
}
}
response.put("data", data);
response.put("labels", labels);
response.put("title", "今日盈亏数据");
System.out.println("返回的数据长度: " + data.size());
System.out.println("返回的标签长度: " + labels.size());
System.out.println("返回的标签: " + labels);
response.put("title", "今日累计盈亏数据");
} catch (Exception e) {
System.err.println("获取折线图1数据失败: " + e.getMessage());
e.printStackTrace();
@@ -99,23 +130,28 @@ public class ChartController {
List<Map<String, Object>> tableData = new ArrayList<>();
try {
// 获取今日日期的字符串表示格式yyyy-MM-dd
String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
System.out.println("今日日期: " + today);
// 从数据库获取今日的CompletedToday数据
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date startOfDay = calendar.getTime();
List<CompletedToday> completedTodayList = completedTodayRepository.findByCreateTimeAfter(startOfDay);
// 从数据库获取今日的LotteryResult数据
List<LotteryResult> lotteryResults = lotteryResultRepository.findByTimeContaining(today);
System.out.println("获取到" + lotteryResults.size() + "条今日数据");
// 按时间倒序排序
completedTodayList.sort(Comparator.comparing(CompletedToday::getCreateTime).reversed());
// 将LotteryResult对象转换为前端需要的Map格式
for (LotteryResult result : lotteryResults) {
Map<String, Object> item = new HashMap<>();
item.put("id", result.getId());
item.put("name", result.getIssue()); // 使用期号作为名称
item.put("time", result.getTime()); // 添加开奖时间
// 将List<String>转换为逗号分隔的字符串,避免序列化问题
item.put("result", result.getResult()); // 添加开奖号码
tableData.add(item);
// 将CompletedToday对象转换为前端需要的Map格式
for (CompletedToday item : completedTodayList) {
Map<String, Object> map = new HashMap<>();
map.put("betId", item.getBetId());
map.put("betAmount", item.getBetAmount());
map.put("result", item.getResult());
map.put("resultAmount", item.getResultAmount());
map.put("time", DateFormatUtils.format(item.getTime(), "yyyy-MM-dd HH:mm:ss"));
tableData.add(map);
}
} catch (Exception e) {
System.err.println("获取表格数据失败: " + e.getMessage());

View File

@@ -52,6 +52,12 @@ public class LoginInfoResult {
@Column(name = "cookie")
private String cookie;
//余额
@Column(name = "balance")
private String balance;
private Integer betAmount;
@Column(name = "create_time", nullable = false, updatable = false)
@CreationTimestamp
@Convert(converter = SQLiteDateConverter.class)

View File

@@ -19,39 +19,41 @@ public class LotteryResult {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/*@Column(name = "id", nullable = false, unique = true)
/*@Column(name = "id", unique = true)
private String id; // 期号*/
@Column(name = "issue", nullable = false, unique = true)
@Column(name = "issue", unique = true)
private String issue; // 期号
@Column(name = "time", nullable = false)
@Column(name = "time")
private String time; // 开奖时间
@Column(name = "result")
private String result; // 开奖号码
@Column(name = "winner", nullable = false)
@Column(name = "winner")
private String winner; //
@Column(name = "sum1", nullable = false)
@Column(name = "sum1")
private String sum1; // 总和值
@Column(name = "sum2", nullable = false)
@Column(name = "sum2")
private String sum2; // 冠亚和
@Column(name = "gd1", nullable = false)
@Column(name = "gd1")
private String gd1; // 冠亚单
@Column(name = "gd2", nullable = false)
@Column(name = "gd2")
private String gd2; // 冠亚大
@Column(name = "glh_result", nullable = false)
@Column(name = "glh_result")
private String glh_result; //[ "龙", "龙", "龙", "虎", "虎" ] 龙虎
@Column(name = "bet_result")
private String betResult;
}

View File

@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Date;
import java.util.List;
import java.util.Optional;
@Repository
@@ -19,4 +20,17 @@ public interface CompletedTodayRepository extends JpaRepository<CompletedToday,
@Query("SELECT SUM(ct.resultAmount) FROM CompletedToday ct WHERE ct.time > :startTime")
Double sumResultAmountByCreateTimeAfter(@Param("startTime") Date startTime);
// 根据时间范围查询数据
List<CompletedToday> findByTimeBetween(Date startTime, Date endTime);
// 根据时间查询数据(时间大于等于指定时间)
List<CompletedToday> findByTimeAfter(Date startTime);
// 根据创建时间查询数据(创建时间大于等于指定时间)
@Query("SELECT ct FROM CompletedToday ct WHERE ct.time >= :startTime")
List<CompletedToday> findByCreateTimeAfter(@Param("startTime") Date startTime);
@Query(value = "SELECT * FROM completed_today WHERE create_time >= :startTime AND create_time < :endTime", nativeQuery = true)
List<CompletedToday> findTodayData(@Param("startTime") String startTime, @Param("endTime") String endTime);
}

View File

@@ -5,9 +5,13 @@ import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface LotteryResultRepository extends JpaRepository<LotteryResult, Long> {
// 根据时间查询使用like匹配日期部分
List<LotteryResult> findByTimeContaining(String date);
// 查询最新的记录按time降序取第一条
Optional<LotteryResult> findTopByOrderByTimeDesc();
}

View File

@@ -1,5 +1,6 @@
package com.tem.bocai.schedules;
import com.alibaba.fastjson.JSON;
import com.tem.bocai.util.TokenCacheService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@@ -20,9 +21,11 @@ import java.util.Optional;
import org.json.JSONObject;
import com.tem.bocai.entity.LoginInfoResult;
import com.tem.bocai.entity.BetRecord;
import com.tem.bocai.entity.LotteryResult;
import com.tem.bocai.repository.CompletedTodayRepository;
import com.tem.bocai.repository.LoginInfoRepository;
import com.tem.bocai.repository.BetRecordRepository;
import com.tem.bocai.repository.LotteryResultRepository;
@Slf4j
@Component
@@ -40,6 +43,9 @@ public class BetSchedule {
@Autowired
private BetRecordRepository betRecordRepository;
@Autowired
private LotteryResultRepository lotteryResultRepository;
// 从7:02分钟起每5分钟执行一次
@Scheduled(cron = "30 2/5 * * * ?")
public void placeBet() {
@@ -53,7 +59,7 @@ public class BetSchedule {
String currentTime = now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
System.out.println(currentTime + " - 不在投注时间范围内,跳过执行");
log.info("{}", currentTime + " - 不在投注时间范围内,跳过执行");
return;
}
@@ -64,54 +70,53 @@ public class BetSchedule {
LoginInfoResult loginInfo = loginInfoRepository.findFirstByOrderByCreateTimeDesc().orElse(null);
if (loginInfo == null) {
System.out.println(currentTime + " - 未找到登录信息,跳过执行");
log.info("{}", currentTime + " - 未找到登录信息,跳过执行");
return;
}
// 检查onOff字段是否为1开启状态
if (loginInfo.getOnOff() != 1) {
System.out.println(currentTime + " - 投注功能未开启,跳过执行");
log.info("{}", currentTime + " - 投注功能未开启,跳过执行");
return;
}
// 检查winNum和loseNum字段是否合理
Integer winNum = loginInfo.getWinNum();
Integer loseNum = loginInfo.getLoseNum();
// // 检查winNum和loseNum字段是否合理
// Integer winNum = loginInfo.getWinNum();
// Integer loseNum = loginInfo.getLoseNum();
//
// if (winNum != null || loseNum != null) {
// // 根据LoginInfo的startTime 查询CompletedToday的resultAmount总和 判断是否达到 winNum 和 loseNum的值
// Date startTime = loginInfo.getStartTime();
// if (startTime != null) {
// Double totalResultAmount = completedTodayRepository.sumResultAmountByCreateTimeAfter(startTime);
// if (totalResultAmount != null) {
// log.info(" - 今日盈亏总和: {}", totalResultAmount);
//
// // 判断是否达到止盈点
// if (totalResultAmount >= winNum) {
// log.info("{}", currentTime + " - 已达到止盈点 " + winNum + ",跳过执行");
// return;
// }
//
// // 判断是否达到止亏点
// if (totalResultAmount <= -loseNum) {
// log.info("{}", currentTime + " - 已达到止亏点 " + loseNum + ",跳过执行");
// return;
// }
// }
// }
// }
if (winNum != null || loseNum != null) {
// 根据LoginInfo的startTime 查询CompletedToday的resultAmount总和 判断是否达到 winNum 和 loseNum的值
Date startTime = loginInfo.getStartTime();
if (startTime != null) {
Double totalResultAmount = completedTodayRepository.sumResultAmountByCreateTimeAfter(startTime);
if (totalResultAmount != null) {
System.out.println(" - 今日盈亏总和: " + totalResultAmount);
// 判断是否达到止盈点
if (totalResultAmount >= winNum) {
System.out.println(currentTime + " - 已达到止盈点 " + winNum + ",跳过执行");
return;
}
// 判断是否达到止亏点
if (totalResultAmount <= -loseNum) {
System.out.println(currentTime + " - 已达到止亏点 " + loseNum + ",跳过执行");
return;
}
}
}
}
System.out.println(currentTime + " - 开始执行投注...");
log.info("{}", currentTime + " - 开始执行投注...");
try {
// 执行投注逻辑
executeBet(loginInfo);
System.out.println(currentTime + " - 投注执行完成");
log.info("{}", currentTime + " - 投注执行完成");
} catch (Exception e) {
System.err.println(currentTime + " - 投注执行失败:");
e.printStackTrace();
log.error("{} - 投注执行失败:", currentTime, e);
}
}
@@ -130,7 +135,7 @@ public class BetSchedule {
LocalDateTime now = LocalDateTime.now();
// 2. 从BetRecord中获取第一条记录根据betTime排序
System.out.println(" - 从BetRecord中获取第一条记录...");
log.info(" - 从BetRecord中获取第一条记录...");
Optional<BetRecord> optionalBetRecord = betRecordRepository.findFirstByOrderByBetTimeDesc();
if (optionalBetRecord.isPresent()) {
@@ -138,30 +143,29 @@ public class BetSchedule {
String betData = betRecord.getBetData();
String betTime = betRecord.getBetTime();
System.out.println(" - 投注时间: " + betTime);
System.out.println(" - 投注数据: " + betData);
log.info(" - 投注时间: {}", betTime);
log.info(" - 投注数据: {}", betData);
// 调用投注接口
System.out.println(" - 提交投注...");
log.info(" - 提交投注...");
String betResult = callBetApi(betData, null);
System.out.println(" - 投注结果: " + betResult);
log.info(" - 投注结果: {}", betResult);
// 记录投注结果
System.out.println(" - 记录投注结果...");
log.info(" - 记录投注结果...");
recordBetResult(betData, betResult);
// 投注成功后删除BetRecord记录
if (betResult == null) {
betRecordRepository.delete(betRecord);
System.out.println(" - 已删除投注记录,期数: " + betRecord.getBetNum());
log.info(" - 已删除投注记录,期数: {}", betRecord.getBetNum());
}
} else {
System.out.println(" - 未找到投注记录");
log.info(" - 未找到投注记录");
}
} catch (Exception e) {
System.err.println(" - 投注执行失败:");
e.printStackTrace();
log.error(" - 投注执行失败:", e);
}
}
@@ -204,9 +208,27 @@ public class BetSchedule {
* 记录投注结果
*/
private void recordBetResult(String betData, String betResult) {
// 这里可以实现将投注结果记录到数据库或日志文件的逻辑
// 为了简单起见,我们这里只打印日志
// 这里实现将投注结果记录到LotteryResult的betResult字段的逻辑
String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(" - 投注记录: [" + currentTime + "] 数据: " + betData + ", 结果: " + betResult);
log.info(" - 投注记录: [{}] 数据: {}, 结果: {}", currentTime, betData, betResult);
try {
// 解析betData获取drawNumberterm
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(betData);
String drawNumber = jsonObject.getString("drawNumber");
if (StringUtils.isBlank(drawNumber)){
log.error(" - 新增LotteryResult记录失败:");
return;
}
// 创建新的LotteryResult记录
LotteryResult newLotteryResult = new LotteryResult();
newLotteryResult.setIssue(drawNumber);
newLotteryResult.setBetResult(betData);
// 保存新记录
lotteryResultRepository.save(newLotteryResult);
log.info(" - 已新增LotteryResult记录期号: {}", drawNumber);
} catch (Exception e) {
log.error(" - 新增LotteryResult记录失败:", e);
}
}
}

View File

@@ -1,20 +1,19 @@
package com.tem.bocai.schedules;
import com.tem.bocai.entity.LoginInfoResult;
import com.tem.bocai.entity.LotteryResult;
import com.tem.bocai.repository.LoginInfoRepository;
import com.tem.bocai.repository.LotteryResultRepository;
import com.tem.bocai.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Spider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
@Component
@Slf4j
@@ -24,6 +23,10 @@ public class CrawlerSchedule {
private TokenCacheService tokenCacheService;
@Autowired
private LoginInfoRepository loginInfoRepository;
@Autowired
private LotteryResultRepository lotteryResultRepository;
private static final int MAX_CRA = 3;
private static final Integer ONOFF = 0;
@Value("${pypath}")
@@ -45,6 +48,9 @@ public class CrawlerSchedule {
if (firstByOrderByCreateTimeDesc == null) {
return;
}
if(firstByOrderByCreateTimeDesc.getOnOff() == ONOFF){
return;
}
String token = tokenCacheService.getToken();
if (token == null || token.isEmpty()) {
return;
@@ -99,6 +105,50 @@ public class CrawlerSchedule {
}
@Scheduled(cron = "55 0/5 * * * ?")
//@Scheduled(cron = "*/9 * * * * ?")
public void executePksHistory() {
log.info("开始获取历史开奖结果");
// 获取数据库最新记录
LotteryResult lotteryResult = lotteryResultRepository.findTopByOrderByTimeDesc()
.orElse(null);
// 如果数据库为空,直接获取并保存所有历史数据
if (lotteryResult == null) {
log.info("数据库为空,获取全部历史数据");
List<Map<String, Object>> historyList = HttpClientExample.getPksHistoryList();
HttpClientExample.getPksHistory(historyList, pypath);
return;
}
String lastTime = lotteryResult.getTime();
List<Map<String, Object>> historyList = HttpClientExample.getPksHistoryList();
// 检查API数据中是否有比数据库最新时间更晚的数据
boolean hasNewerData = false;
for (Map<String, Object> item : historyList) {
String itemTime = (String) item.get("time");
// 假设时间格式是 "yyyy-MM-dd HH:mm:ss" 或其他可比较的格式
// 比较时间字符串(前提是格式一致且可按字典序比较)
if (itemTime.compareTo(lastTime) > 0) {
hasNewerData = true;
log.info("发现新数据:{} > {}", itemTime, lastTime);
break;
}
}
// 如果有比数据库时间更新的数据,执行获取操作
if (hasNewerData) {
log.info("发现比数据库时间 {} 更新的数据,执行获取", lastTime);
HttpClientExample.getPksHistory(historyList, pypath);
} else {
log.info("未发现比数据库时间 {} 更新的数据,无需处理", lastTime);
}
}
/*public void executeLotteryDraw() {
System.out.println("开始爬取开奖结果...");
String token = tokenCacheService.getToken();
@@ -128,6 +178,11 @@ public class CrawlerSchedule {
if (token == null || token.isEmpty()) {
return;
}
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
.orElse(null);
if(firstByOrderByCreateTimeDesc.getOnOff() == ONOFF){
return;
}
while (!success && retryCount < MAX_CRA) {
log.info("\n=== 第 " + (retryCount + 1) + " 次尝试获取今日注单 ===");
if (token == null || token.isEmpty()) {
@@ -141,8 +196,7 @@ public class CrawlerSchedule {
// 创建爬虫实例传入token
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
.orElse(null);
// 执行爬虫
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
@@ -178,23 +232,240 @@ public class CrawlerSchedule {
}
}
/**
* 每5分钟执行一次获取余额信息
* 从7:00分40秒起每5分钟执行一次
*/
@Scheduled(cron = "40 0/5 * * * ?")
//@Scheduled(cron = "*/9 * * * * ?")
public void executeGetBalance() {
log.info("开始获取余额信息");
/*public void executeSettlement() {
String token = tokenCacheService.getToken();
System.out.println("得到token = " + token);
if (token != null && !token.isEmpty()) {
// 2. 创建爬虫实例传入token
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
// 获取最新的登录信息
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
.orElse(null);
// 4. 执行爬虫
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
if (firstByOrderByCreateTimeDesc == null) {
log.warn("未找到登录信息,跳过余额获取");
return;
}
if (firstByOrderByCreateTimeDesc.getOnOff() == ONOFF) {
log.info("开关关闭,跳过余额获取");
return;
}
// 获取token
String token = tokenCacheService.getToken();
if (token == null || token.isEmpty()) {
return;
}
int retryCount = 0;
boolean success = false;
while (!success && retryCount < MAX_CRA) {
log.info("=== 第 {} 次尝试获取余额信息 ===", retryCount + 1);
// 重新获取token如果重试
if (retryCount > 0) {
token = tokenCacheService.getTokenSqlite();
if (token == null) {
log.error("重试时无法获取有效token");
retryCount++;
continue;
}
}
log.info("使用token: " + (token.length() > 20 ? token.substring(0, 20) + "..." : token));
// 创建爬虫实例
BalanceWebMagicCrawler crawler = new BalanceWebMagicCrawler(token);
// 构建URL
String url = firstByOrderByCreateTimeDesc.getLoginUrl() + "/member/index";
// 执行爬虫
Spider.create(crawler)
.addUrl(url)
.thread(1)
.run();
}
}*/
// 检查是否成功解析数据
success = BalanceWebMagicCrawler.isLastParseSuccess();
if (success) {
// 获取并处理余额信息
Map<String, String> balanceInfo = BalanceWebMagicCrawler.getBalanceInfo();
if (!balanceInfo.isEmpty()) {
log.info("成功获取余额信息:");
for (Map.Entry<String, String> entry : balanceInfo.entrySet()) {
if (entry.getKey().contains("快开彩额度")) {
firstByOrderByCreateTimeDesc.setBalance(entry.getValue());
if (entry.getValue()!=null) {
loginInfoRepository.save(firstByOrderByCreateTimeDesc);
}
}
}
} else {
log.warn("解析到空余额信息");
success = false;
}
}
if (!success) {
log.info("本次尝试未解析到余额信息");
retryCount++;
// 等待一下再重试
if (retryCount < MAX_CRA) {
try {
Thread.sleep(2000 * retryCount);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
if (!success) {
log.error("获取余额信息失败,所有重试均未成功");
} else {
log.info("余额信息获取完成");
}
}
//开始爬取最近7天的开奖结果
public void executeLotteryDrawHistory() {
log.info("开始爬取最近7天的开奖结果");
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
.orElse(null);
if (firstByOrderByCreateTimeDesc == null) {
log.error("未找到登录信息");
return;
}
if(firstByOrderByCreateTimeDesc.getOnOff() == ONOFF){
log.info("开关已关闭,停止爬取");
return;
}
String token = tokenCacheService.getToken();
if (token == null || token.isEmpty()) {
log.error("token为空");
return;
}
// 获取过去7天的日期列表
List<String> dateList = DateUtils.getLast7Days();
for (String date : dateList) {
log.info("\n=== 开始爬取日期: {} 的数据 ===", date);
// 检查该日期的数据文件是否已存在且有数据
if (isDateDataExists(date)) {
log.info("日期 {} 的数据已存在,跳过爬取", date);
continue;
}
// 对每个日期进行重试
boolean success = crawlDataForDate(date, token);
if (success) {
log.info("日期 {} 数据爬取成功", date);
} else {
log.error("日期 {} 数据爬取失败,已达到最大重试次数", date);
}
// 每次请求后稍作等待,避免请求过于频繁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
log.info("最近7天数据爬取完成");
}
/**
* 爬取指定日期的数据
*/
private boolean crawlDataForDate(String date, String token) {
int retryCount = 0;
boolean success = false;
String currentToken = token;
LoginInfoResult loginInfo = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
.orElse(null);
if (loginInfo == null) {
return false;
}
while (!success && retryCount < MAX_CRA) {
log.info("\n=== 第 " + (retryCount + 1) + " 次尝试获取 " + date + " 的开奖结果 ===");
if (currentToken == null || currentToken.isEmpty()) {
log.info("token为空从数据库重新获取");
currentToken = tokenCacheService.getTokenSqlite();
if (currentToken == null) {
log.error("无法获取有效token");
retryCount++;
continue;
}
}
log.info("使用token: " + (currentToken.length() > 20 ? currentToken.substring(0, 20) + "..." : currentToken));
// 创建爬虫实例传入token
LotteryHistoryCrawler crawler = new LotteryHistoryCrawler(currentToken, pypath,date);
// 构建URL
String url = loginInfo.getLoginUrl() + "/member/dresult?lottery=SGFT&date=" + date;
Spider.create(crawler)
.addUrl(url)
.thread(1)
.run();
// 检查是否成功解析数据
success = LotteryHistoryCrawler.isLastParseSuccess();
if (!success) {
log.info("本次尝试未解析到数据");
// 重新获取token下次重试用
currentToken = tokenCacheService.getTokenSqlite();
retryCount++;
// 等待一下再重试
if (retryCount < MAX_CRA) {
try {
Thread.sleep(2000 * retryCount); // 等待时间递增
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} else {
log.info("成功解析到数据");
}
}
return success;
}
/**
* 检查指定日期的数据文件是否存在且包含数据
*/
private boolean isDateDataExists(String date) {
try {
List<LotteryResult> data = lotteryResultRepository.findByTimeContaining(date);
return data != null && !data.isEmpty();
} catch (Exception e) {
log.warn("检查文件失败: " + e.getMessage());
return false;
}
}
}

View File

@@ -2,56 +2,51 @@ package com.tem.bocai.schedules;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.json.JSONArray;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.tem.bocai.entity.BetRecord;
import com.tem.bocai.entity.LotteryResult;
import com.tem.bocai.repository.BetRecordRepository;
import com.tem.bocai.repository.LotteryResultRepository;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 执行预测脚本
*/
@Slf4j
@Component
public class ExBetScriptSchedule {
@Autowired
private BetRecordRepository betRecordRepository;
@Autowired
private LotteryResultRepository lotteryResultRepository;
@Value("${pypath}")
private String pypath;
/**
* 处理文件路径,确保路径正确
* @param filePath 文件路径
* @return 处理后的文件路径
*/
private String handleFilePath(String filePath) {
// 处理路径分隔符,统一使用系统默认分隔符
filePath = filePath.replace("/", System.getProperty("file.separator"));
filePath = filePath.replace("\\", System.getProperty("file.separator"));
// 如果路径包含空格,确保路径被正确处理
// Runtime.exec会自动处理带空格的路径不需要手动添加引号
return filePath;
}
public static void main(String[] args) {
ExBetScriptSchedule schedule = new ExBetScriptSchedule();
schedule.executePythonScript();
}
// 从7:01分钟起每5分钟执行一次
@Scheduled(cron = "30 1/5 * * * ?")
@@ -65,14 +60,20 @@ public class ExBetScriptSchedule {
String currentTime = now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
System.out.println(currentTime + " - 不在投注时间范围内,跳过执行");
log.info("{}", currentTime + " - 不在投注时间范围内,跳过执行");
return;
}
System.out.println("开始执行Python脚本...");
// 检查PyModel/current_data目录下最新的文件是否存在新数据
if (!checkNewDataExists()) {
log.info("未发现新数据跳过执行Python脚本");
return;
}
// 获取当前时间格式化为yyyy-MM-dd HH:mm:ss
String currentTime = java.time.LocalDateTime.now().format(
log.info("开始执行Python脚本...");
// 获取当前时间减去5分钟格式化为yyyy-MM-dd HH:mm:ss
String currentTime = java.time.LocalDateTime.now().plusMinutes(5).format(
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
);
@@ -81,6 +82,7 @@ public class ExBetScriptSchedule {
executePythonScriptWithParams("batch_predict_betting_v8.py", params);
}
/**
* 执行带参数的Python脚本
* @param scriptPath Python脚本路径
@@ -106,7 +108,7 @@ public class ExBetScriptSchedule {
}
}
System.out.println("执行命令: " + String.join(" ", command));
log.info("执行命令: {}", String.join(" ", command));
// 创建ProcessBuilder并设置工作目录为PyModel
ProcessBuilder pb = new ProcessBuilder(command);
@@ -140,18 +142,17 @@ public class ExBetScriptSchedule {
// 等待脚本执行完成
int exitCode = process.waitFor();
System.out.println("Python脚本执行完成退出码: " + exitCode);
System.out.println("脚本输出:\n" + output.toString());
log.info("Python脚本执行完成退出码: {}", exitCode);
log.info("脚本输出:\n{}", output.toString());
if (exitCode != 0) {
System.err.println("脚本执行错误:\n" + errorOutput.toString());
log.error("脚本执行错误:\n{}", errorOutput.toString());
} else {
parseScriptOutput(output.toString());
}
} catch (IOException | InterruptedException e) {
System.err.println("执行Python脚本时发生错误:");
e.printStackTrace();
log.error("执行Python脚本时发生错误:", e);
}
}
@@ -161,16 +162,16 @@ public class ExBetScriptSchedule {
*/
private void parseScriptOutput(String output) {
try {
System.out.println("开始解析脚本输出...");
log.info("开始解析脚本输出...");
JSONObject jsonOutput = JSON.parseObject(output);
if (jsonOutput == null) {
System.out.println("输出为空或无法解析为JSON");
log.info("输出为空或无法解析为JSON");
return;
}
JSONObject result = jsonOutput.getJSONObject("result");
if (result == null) {
System.out.println("未找到result字段");
log.info("未找到result字段");
return;
}
@@ -194,20 +195,19 @@ public class ExBetScriptSchedule {
}
}
if (!validResults.isEmpty()) {
System.out.println("提取到的有效预测结果:");
log.info("提取到的有效预测结果:");
for (Map.Entry<Integer, Map<Integer, Object>> entry : validResults.entrySet()) {
System.out.println("位置 " + entry.getKey() + ": " + entry.getValue());
log.info("位置 {}: {}", entry.getKey(), entry.getValue());
}
// 生成符合test.json格式的数据
generateOutputData(validResults);
} else {
System.out.println("未找到有效的预测结果所有值均为None");
log.info("未找到有效的预测结果所有值均为None");
}
} catch (Exception e) {
System.err.println("解析脚本输出时发生错误:");
e.printStackTrace();
log.error("解析脚本输出时发生错误:", e);
}
}
@@ -287,8 +287,8 @@ public class ExBetScriptSchedule {
outputData.put("fastBets", false);
outputData.put("ignore", false);
System.out.println("生成的输出数据:");
System.out.println(outputData.toString(2));
log.info("生成的输出数据:");
log.info("{}", outputData.toString(2));
// 保存到数据库
try {
@@ -303,13 +303,77 @@ public class ExBetScriptSchedule {
// 检查betNum是否已存在避免重复保存
if (!betRecordRepository.existsByBetNum(drawNumber)) {
betRecordRepository.save(betRecord);
System.out.println("保存投注记录到数据库成功,期数: " + drawNumber);
log.info("保存投注记录到数据库成功,期数: {}", drawNumber);
} else {
System.out.println("投注记录已存在,期数: " + drawNumber);
log.info("投注记录已存在,期数: {}", drawNumber);
}
} catch (Exception e) {
System.err.println("保存投注记录到数据库失败:");
e.printStackTrace();
log.error("保存投注记录到数据库失败:", e);
}
}
/**
* 检查数据库中最新的开奖记录是否存在新数据
* @return 如果存在新数据返回true否则返回false
*/
private boolean checkNewDataExists() {
try {
// 查询最新的开奖记录
Optional<LotteryResult> latestResult = lotteryResultRepository.findTopByOrderByTimeDesc();
if (!latestResult.isPresent()) {
log.info("数据库中未找到开奖记录");
return false;
}
// 获取最新记录的时间
String timeStr = latestResult.get().getTime();
if (timeStr == null || timeStr.isEmpty()) {
log.info("最新开奖记录时间为空");
return false;
}
// 解析时间字符串为LocalDateTime
LocalDateTime recordTime;
try {
// 假设时间格式为yyyy-MM-dd HH:mm:ss
recordTime = LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
} catch (Exception e) {
log.error("解析开奖记录时间失败: {}", timeStr, e);
return false;
}
// 检查时间是否在最近5分钟内
LocalDateTime now = LocalDateTime.now();
LocalDateTime fiveMinutesAgo = now.minusMinutes(5);
if (recordTime.isAfter(fiveMinutesAgo)) {
log.info("发现新开奖记录: 期号={}, 时间={}", latestResult.get().getIssue(), timeStr);
return true;
} else {
log.info("最新开奖记录: 期号={}, 时间={}, 超过5分钟跳过执行", latestResult.get().getIssue(), timeStr);
return false;
}
} catch (Exception e) {
log.error("检查新数据时发生错误:", e);
return false;
}
}
/**
* 处理文件路径,确保路径正确
* @param filePath 文件路径
* @return 处理后的文件路径
*/
private String handleFilePath(String filePath) {
// 处理路径分隔符,统一使用系统默认分隔符
filePath = filePath.replace("/", System.getProperty("file.separator"));
filePath = filePath.replace("\\", System.getProperty("file.separator"));
// 如果路径包含空格,确保路径被正确处理
// Runtime.exec会自动处理带空格的路径不需要手动添加引号
return filePath;
}
}

View File

@@ -188,6 +188,10 @@ public class LoginServiceImpl implements LoginService {
if (loginInfoResult.getAmount() != null) {
dbUser.setAmount(loginInfoResult.getAmount());
}
// 如果传入了 betAmount写入到配置文件
if (loginInfoResult.getBetAmount() != null) {
writeBaseBetUnitToConfig(loginInfoResult.getBetAmount());
}
if (loginInfoResult.getOnOff() != null) {
dbUser.setOnOff(loginInfoResult.getOnOff());
if (loginInfoResult.getOnOff().equals(1)) {
@@ -258,16 +262,115 @@ public class LoginServiceImpl implements LoginService {
return SQLiteUtil.addOrUpdateLoginInfo(loginInfo);
}
/**
* 将 BASE_BET_UNIT 值写入到配置文件,保留原有结构
*/
private void writeBaseBetUnitToConfig(Integer betAmount) {
String configPath = pypath+"/conf.ini";
java.io.File configFile = new java.io.File(configPath);
if (configFile.exists()) {
try {
// 读取所有行
java.util.List<String> lines = new java.util.ArrayList<>();
try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(configFile))) {
String line;
while ((line = br.readLine()) != null) {
lines.add(line);
}
}
// 查找并更新 BASE_BET_UNIT 行
boolean updated = false;
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
if (line.startsWith("BASE_BET_UNIT =")) {
lines.set(i, "BASE_BET_UNIT = " + betAmount);
updated = true;
break;
}
}
// 如果没有找到,在 [init] 节末尾添加
if (!updated) {
for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i);
if (line.startsWith("[init]")) {
// 找到 [init] 节,在其后添加
for (int j = i + 1; j < lines.size(); j++) {
String nextLine = lines.get(j);
if (nextLine.startsWith("[")) {
// 遇到下一个节,在此之前添加
lines.add(j, "BASE_BET_UNIT = " + betAmount);
updated = true;
break;
}
}
if (!updated) {
// 如果是最后一个节,在文件末尾添加
lines.add("BASE_BET_UNIT = " + betAmount);
updated = true;
}
break;
}
}
}
// 写回文件
if (updated) {
try (java.io.BufferedWriter bw = new java.io.BufferedWriter(new java.io.FileWriter(configFile))) {
for (String line : lines) {
bw.write(line);
bw.newLine();
}
}
System.out.println("BASE_BET_UNIT 已更新为: " + betAmount);
} else {
System.err.println("未找到 [init] 节,无法更新 BASE_BET_UNIT");
}
} catch (java.io.IOException e) {
System.err.println("读写配置文件失败: " + e.getMessage());
}
} else {
System.err.println("配置文件不存在: " + configPath);
}
}
@Override
public LoginInfoResult getUserSettings() {
try {
// 获取最新的用户设置信息
Optional<LoginInfoResult> existingUser = loginInfoRepository.findFirstByOrderByCreateTimeDesc();
LoginInfoResult loginInfoResult;
if (existingUser.isPresent()) {
return existingUser.get();
}
loginInfoResult = existingUser.get();
} else {
// 如果没有找到,返回一个空的对象
return new LoginInfoResult();
loginInfoResult = new LoginInfoResult();
}
// 读取 PyModel/conf.ini 文件中的 BASE_BET_UNIT 值
String configPath = pypath+"/conf.ini";
java.io.File configFile = new java.io.File(configPath);
if (configFile.exists()) {
java.util.Properties properties = new java.util.Properties();
try (java.io.FileInputStream fis = new java.io.FileInputStream(configFile)) {
properties.load(fis);
String baseBetUnit = properties.getProperty("BASE_BET_UNIT");
if (baseBetUnit != null) {
try {
loginInfoResult.setBetAmount(Integer.parseInt(baseBetUnit));
} catch (NumberFormatException e) {
System.err.println("BASE_BET_UNIT 格式错误: " + e.getMessage());
}
}
} catch (java.io.IOException e) {
System.err.println("读取配置文件失败: " + e.getMessage());
}
} else {
System.err.println("配置文件不存在: " + configPath);
}
return loginInfoResult;
} catch (Exception e) {
System.err.println("获取用户设置信息失败: " + e.getMessage());
return new LoginInfoResult();

View File

@@ -0,0 +1,225 @@
package com.tem.bocai.util;
import lombok.extern.slf4j.Slf4j;
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 us.codecraft.webmagic.selector.Selectable;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class BalanceWebMagicCrawler implements PageProcessor {
private final String token;
private Site site;
private static volatile boolean lastParseSuccess = false;
private static Map<String, String> balanceInfo = new HashMap<>();
public BalanceWebMagicCrawler(String token) {
this.token = token;
initSite();
}
/**
* 初始化Site配置
*/
private void initSite() {
site = Site.me()
.setRetryTimes(3)
.setSleepTime(1000)
.setTimeOut(10000)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
// 设置cookie
if (token != null && !token.isEmpty()) {
site.addHeader("cookie", "token=" + token);
}
}
@Override
public void process(Page page) {
try {
// 获取页面HTML
Html html = page.getHtml();
String htmlContent = html.toString();
log.info("访问余额页面URL: " + page.getUrl());
log.info("页面标题: " + html.xpath("//title/text()").get());
// 解析HTML获取余额信息
Map<String, String> result = parseBalanceInfo(htmlContent);
if (!result.isEmpty()) {
lastParseSuccess = true;
balanceInfo = result;
// 打印解析到的余额信息
log.info("========== 余额信息解析成功 ==========");
for (Map.Entry<String, String> entry : result.entrySet()) {
log.info("{}: {}", entry.getKey(), entry.getValue());
}
log.info("=====================================");
// 将数据存入结果
page.putField("balanceInfo", result);
page.putField("html", htmlContent.substring(0, Math.min(500, htmlContent.length())) + "...");
} else {
lastParseSuccess = false;
log.warn("未解析到余额信息");
}
} catch (Exception e) {
lastParseSuccess = false;
log.error("解析余额页面时发生错误: " + e.getMessage(), e);
}
}
/**
* 解析余额信息
* @param htmlContent HTML内容
* @return 余额信息Map
*/
private Map<String, String> parseBalanceInfo(String htmlContent) {
Map<String, String> result = new HashMap<>();
try {
Document doc = Jsoup.parse(htmlContent);
// 查找账户信息部分
Element userInfoDiv = doc.selectFirst("div.user_info");
if (userInfoDiv == null) {
log.warn("未找到账户信息div");
return result;
}
// 查找所有账户类型
Elements accountDivs = userInfoDiv.select("div.accounts");
for (Element accountDiv : accountDivs) {
// 检查是否显示(有些账户类型可能不显示)
String style = accountDiv.attr("style");
if (style.contains("display:none")) {
continue;
}
// 获取账户类型标签和余额
Elements infoDivs = accountDiv.select("div.info");
for (Element infoDiv : infoDivs) {
Element label = infoDiv.selectFirst("label");
Element value = infoDiv.selectFirst("span");
if (label != null && value != null) {
String labelText = label.text().trim();
String valueText = value.text().trim();
// 特别关注快开彩额度
if (labelText.contains("快开彩额度")) {
result.put("快开彩额度", valueText);
log.info("找到快开彩额度: {}", valueText);
} else if (labelText.contains("未结算金额")) {
result.put("未结算金额", valueText);
} else if (labelText.contains("全国彩额度")) {
result.put("全国彩额度", valueText);
} else if (labelText.contains("香港彩额度")) {
result.put("香港彩额度", valueText);
} else if (labelText.contains("第三方额度")) {
result.put("第三方额度", valueText);
}
}
}
}
// 如果没找到账户信息,尝试其他方式查找
if (result.isEmpty()) {
// 查找账号信息
Element accountSpan = doc.selectFirst("div.inline-name span");
if (accountSpan != null) {
result.put("账号", accountSpan.text().trim());
}
// 查找所有包含"额度"的元素
Elements balanceElements = doc.select("span.balance");
for (Element balance : balanceElements) {
Element parent = balance.parent();
if (parent != null) {
Element label = parent.selectFirst("label");
if (label != null && label.text().contains("额度")) {
result.put(label.text().trim(), balance.text().trim());
}
}
}
}
} catch (Exception e) {
log.error("解析余额信息时发生错误: " + e.getMessage(), e);
}
return result;
}
@Override
public Site getSite() {
return site;
}
/**
* 获取解析状态
*/
public static boolean isLastParseSuccess() {
return lastParseSuccess;
}
/**
* 获取解析到的余额信息
*/
public static Map<String, String> getBalanceInfo() {
return new HashMap<>(balanceInfo);
}
/**
* 清除余额信息缓存
*/
public static void clearBalanceInfo() {
balanceInfo.clear();
}
/**
* 测试方法
*/
public static void main(String[] args) {
// 测试用的token
String testToken = "31ea12c0a75f1f17dc2004ea5f7501aed73c00fa";
String url = "https://4701268539-esh.qdk63ayw8g.com/member/index";
log.info("开始测试余额爬虫...");
log.info("使用URL: " + url);
// 创建爬虫
Spider.create(new BalanceWebMagicCrawler(testToken))
.addUrl(url)
.thread(1)
.run();
// 打印结果
if (isLastParseSuccess()) {
Map<String, String> info = getBalanceInfo();
System.out.println("\n========== 余额信息 ==========");
for (Map.Entry<String, String> entry : info.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
System.out.println("==============================");
} else {
System.out.println("获取余额信息失败");
}
}
}

View File

@@ -48,16 +48,48 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
*
* @return 当天日期的字符串例如2026-01-27
*/
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static String getTodayDate() {
LocalDate today = LocalDate.now();
return today.format(DateTimeFormatter.ofPattern(YYYY_MM_DD));
LocalDateTime now = LocalDateTime.now();
LocalTime thresholdTime = LocalTime.of(6, 1, 0);
LocalDate date;
if (now.toLocalTime().isBefore(thresholdTime)) {
date = now.toLocalDate().minusDays(1); // 昨天
} else {
date = now.toLocalDate(); // 今天
}
return date.format(DateTimeFormatter.ofPattern(YYYY_MM_DD));
}
/*public static String getTodayDate() {
// Java 17中可使用var简化局部变量声明
var now = LocalDateTime.now();
// 核心判断逻辑:小时数<6则取前一天否则取当天
LocalDate targetDate = now.getHour() <= 6
? now.toLocalDate().minusDays(1)
: now.toLocalDate();
// 格式化返回
return targetDate.format(DATE_FORMATTER);
}*/
// 近7天日期的方法
public static List<String> getLast7Days() {
List<String> dateList = new ArrayList<>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar calendar = Calendar.getInstance();
// 从今天开始往前推7天
for (int i = 0; i < 7; i++) {
dateList.add(sdf.format(calendar.getTime()));
calendar.add(Calendar.DAY_OF_YEAR, -1);
}
return dateList;
}
public static void main(String[] args) {
List<String> test = new ArrayList<>();
test.add("12312");
test.add("12312");
System.out.println("====="+test.get(6));
System.out.println("====="+getTodayDate());
/* Date now = new Date(); // 当前时间
Date fifteenMinutesAgo = DateUtil.offsetMinute(now, -15); // 15分钟前的时间

View File

@@ -0,0 +1,577 @@
package com.tem.bocai.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.util.EntityUtils;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
@Slf4j
public class HttpClientExample {
public static String getPksHistory(List<Map<String, Object>> resultList ,String pypath) {
// 将数据写入SQLite数据库
SQLiteUtil.writeToSQLite(resultList);
// 将数据写入JSON文件保留原有功能
writeToJsonFile(resultList,pypath);
log.info("历史爬虫打印结果===" + resultList);
return "";
}
public static List<Map<String, Object>> getPksHistoryList() {
List<Map<String, Object>> resultList = new ArrayList<>();
String todayDate = DateUtils.getTodayDate();
// 最大重试次数
int maxRetries = 10;
// 重试延迟(毫秒)
int retryDelay = 2000;
for (int retryCount = 0; retryCount < maxRetries; retryCount++) {
System.out.println("=== 第 " + (retryCount + 1) + " 次尝试请求 ===");
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
String url = "https://api.api168168.com/pks/getPksHistoryList.do?lotCode=10058&date=" + todayDate;
System.out.println("请求URL: " + url);
// 创建自定义配置的HttpClient
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000) // 15秒连接超时
.setSocketTimeout(15000) // 15秒socket超时
.setConnectionRequestTimeout(15000)
.setCircularRedirectsAllowed(true)
.setMaxRedirects(5)
.build();
httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setRedirectStrategy(new LaxRedirectStrategy())
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.disableCookieManagement()
.build();
HttpGet request = new HttpGet(url);
// 设置完整的请求头
request.setHeader("Accept", "application/json, text/javascript, */*; q=0.01");
request.setHeader("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
request.setHeader("Cache-Control", "no-cache");
request.setHeader("Connection", "keep-alive");
request.setHeader("Host", "api.api168168.com");
request.setHeader("Origin", "https://xy678kjw.com");
request.setHeader("Pragma", "no-cache");
request.setHeader("Referer", "https://xy678kjw.com/");
request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36");
request.setHeader("X-Requested-With", "XMLHttpRequest");
// 设置Sec-Fetch相关头
request.setHeader("Sec-Fetch-Dest", "empty");
request.setHeader("Sec-Fetch-Mode", "cors");
request.setHeader("Sec-Fetch-Site", "cross-site");
// 执行请求
response = httpClient.execute(request);
int statusCode = response.getStatusLine().getStatusCode();
System.out.println("响应状态码: " + statusCode);
// 打印响应头用于调试
System.out.println("响应头信息:");
Arrays.stream(response.getAllHeaders())
.limit(10)
.forEach(header -> System.out.println(" " + header.getName() + ": " + header.getValue()));
if (statusCode == 200) {
HttpEntity entity = response.getEntity();
if (entity != null) {
String responseString = EntityUtils.toString(entity, "UTF-8");
System.out.println("响应内容长度: " + responseString.length());
// 打印响应前200个字符用于调试
if (responseString.length() > 0) {
int previewLength = Math.min(200, responseString.length());
System.out.println("响应预览: " + responseString.substring(0, previewLength));
// 检查响应是否有效
if (responseString.contains("errorCode") || responseString.contains("result")) {
// 解析JSON
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(responseString);
// 检查是否成功
int errorCode = rootNode.get("errorCode").asInt();
if (errorCode == 0) {
// 获取data数组
JsonNode dataArray = rootNode
.get("result")
.get("data");
if (dataArray.isArray()) {
for (JsonNode item : dataArray) {
Map<String, Object> map = new HashMap<>();
// 解析原始数据
String preDrawCode = item.get("preDrawCode").asText();
String[] codeArray = preDrawCode.split(",");
// 转换为List<Integer>
List<Integer> resultNumbers = new ArrayList<>();
for (String code : codeArray) {
resultNumbers.add(Integer.parseInt(code.trim()));
}
// 计算sum1和sum2
int sum1 = 0;
int sum2 = 0;
for (int i = 0; i < 5; i++) {
sum1 += resultNumbers.get(i);
}
for (int i = 5; i < 10; i++) {
sum2 += resultNumbers.get(i);
}
// 计算winner前五个号码的和
int winner = sum1;
// 计算冠亚和(前两个号码的和)
int championSum = resultNumbers.get(0) + resultNumbers.get(1);
// 判断冠亚单双
String GD2 = (championSum % 2 == 0) ? "冠亚双" : "冠亚单";
// 判断冠亚大小冠亚和11为大11为小
String GD1 = (championSum > 11) ? "冠亚大" : "冠亚小";
if (championSum == 11) {
GD1 = "冠亚小"; // 特殊处理和值为11算小
}
// 计算龙虎1-5位对比
List<String> GLH_result = new ArrayList<>();
for (int i = 0; i < 5; i++) {
if (resultNumbers.get(i) > resultNumbers.get(9 - i)) {
GLH_result.add("");
} else {
GLH_result.add("");
}
}
// 构建转换后的map
map.put("result", resultNumbers);
map.put("sum1", sum1);
map.put("sum2", sum2);
map.put("winner", winner);
map.put("GD2", GD2);
map.put("GD1", GD1);
map.put("GLH_result", GLH_result);
map.put("id", String.valueOf(item.get("preDrawIssue").asLong()));
map.put("time", item.get("preDrawTime").asText());
resultList.add(map);
}
System.out.println("成功获取 " + resultList.size() + " 条数据");
// 成功获取数据,关闭资源并返回
if (response != null) {
response.close();
}
if (httpClient != null) {
httpClient.close();
}
return resultList;
}
} else {
System.err.println("API返回错误: errorCode=" + errorCode);
}
} else {
System.err.println("响应内容格式不正确,不包含期望的字段");
}
} else {
System.err.println("响应内容为空");
}
}
} else if (statusCode == 403 || statusCode == 404 || statusCode == 500) {
System.err.println("服务器返回错误状态码: " + statusCode);
// 如果是这些状态码,可能不需要重试
break;
}
} catch (Exception e) {
System.err.println("" + (retryCount + 1) + " 次请求异常: " +
e.getClass().getSimpleName() + " - " + e.getMessage());
// 如果是连接重置错误,打印更多信息
if (e.getMessage() != null && e.getMessage().contains("Connection reset")) {
System.err.println("连接被服务器重置");
}
// 最后一次重试仍然失败,打印完整堆栈
if (retryCount == maxRetries - 1) {
e.printStackTrace();
}
} finally {
// 确保资源关闭
try {
if (response != null) {
response.close();
}
if (httpClient != null) {
httpClient.close();
}
} catch (Exception e) {
System.err.println("关闭资源时出错: " + e.getMessage());
}
}
// 如果不是最后一次尝试,等待后重试
if (retryCount < maxRetries - 1) {
System.out.println("等待 " + (retryDelay/1000) + " 秒后重试...");
try {
Thread.sleep(retryDelay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
System.err.println("重试等待被中断");
break;
}
}
}
if (resultList.isEmpty()) {
System.err.println("经过 " + maxRetries + " 次重试后,仍未能获取数据");
}
return resultList;
}
/*public static List<Map<String, Object>> getPksHistoryList(){
List<Map<String, Object>> resultList = new ArrayList<>();
String todayDate = DateUtils.getTodayDate();
try {
String url = "https://api.api168168.com/pks/getPksHistoryList.do?lotCode=10058&"+todayDate;
// 设置代理 127.0.0.1:7890
//Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Origin", "https://xy678kjw.com");
conn.setRequestProperty("Referer", "https://xy678kjw.com/");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 使用Jackson解析JSON
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(response.toString());
// 检查是否成功
int errorCode = rootNode.get("errorCode").asInt();
if (errorCode == 0) {
// 获取data数组
JsonNode dataArray = rootNode
.get("result")
.get("data");
if (dataArray.isArray()) {
for (JsonNode item : dataArray) {
Map<String, Object> map = new HashMap<>();
// 解析原始数据
String preDrawCode = item.get("preDrawCode").asText();
String[] codeArray = preDrawCode.split(",");
// 转换为List<Integer>
List<Integer> resultNumbers = new ArrayList<>();
for (String code : codeArray) {
resultNumbers.add(Integer.parseInt(code.trim()));
}
// 计算sum1和sum2
int sum1 = 0;
int sum2 = 0;
for (int i = 0; i < 5; i++) {
sum1 += resultNumbers.get(i);
}
for (int i = 5; i < 10; i++) {
sum2 += resultNumbers.get(i);
}
// 计算winner前五个号码的和
int winner = sum1;
// 计算冠亚和(前两个号码的和)
int championSum = resultNumbers.get(0) + resultNumbers.get(1);
// 判断冠亚单双
String GD2 = (championSum % 2 == 0) ? "冠亚双" : "冠亚单";
// 判断冠亚大小冠亚和11为大11为小
String GD1 = (championSum > 11) ? "冠亚大" : "冠亚小";
if (championSum == 11) {
GD1 = "冠亚小"; // 特殊处理和值为11算小
}
// 计算龙虎1-5位对比
List<String> GLH_result = new ArrayList<>();
for (int i = 0; i < 5; i++) {
if (resultNumbers.get(i) > resultNumbers.get(9 - i)) {
GLH_result.add("龙");
} else {
GLH_result.add("虎");
}
}
// 构建转换后的map使用List<Integer>作为result
map.put("result", resultNumbers);
map.put("sum1", sum1);
map.put("sum2", sum2);
map.put("winner", winner);
map.put("GD2", GD2);
map.put("GD1", GD1);
map.put("GLH_result", GLH_result);
map.put("id", String.valueOf(item.get("preDrawIssue").asLong()));
map.put("time", item.get("preDrawTime").asText());
resultList.add(map);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return resultList;
}*/
/*public static List<Map<String, Object>> getPksHistoryList(){
List<Map<String, Object>> resultList = new ArrayList<>();
String todayDate = DateUtils.getTodayDate();
try {
String url = "https://api.api168168.com/pks/getPksHistoryList.do?lotCode=10058&"+todayDate;
// 移除代理设置,直接创建连接
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Origin", "https://xy678kjw.com");
conn.setRequestProperty("Referer", "https://xy678kjw.com/");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 使用Jackson解析JSON
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(response.toString());
// 检查是否成功
int errorCode = rootNode.get("errorCode").asInt();
if (errorCode == 0) {
// 获取data数组
JsonNode dataArray = rootNode
.get("result")
.get("data");
if (dataArray.isArray()) {
for (JsonNode item : dataArray) {
Map<String, Object> map = new HashMap<>();
// 解析原始数据
String preDrawCode = item.get("preDrawCode").asText();
String[] codeArray = preDrawCode.split(",");
// 转换为List<Integer>
List<Integer> resultNumbers = new ArrayList<>();
for (String code : codeArray) {
resultNumbers.add(Integer.parseInt(code.trim()));
}
// 计算sum1和sum2
int sum1 = 0;
int sum2 = 0;
for (int i = 0; i < 5; i++) {
sum1 += resultNumbers.get(i);
}
for (int i = 5; i < 10; i++) {
sum2 += resultNumbers.get(i);
}
// 计算winner前五个号码的和
int winner = sum1;
// 计算冠亚和(前两个号码的和)
int championSum = resultNumbers.get(0) + resultNumbers.get(1);
// 判断冠亚单双
String GD2 = (championSum % 2 == 0) ? "冠亚双" : "冠亚单";
// 判断冠亚大小冠亚和11为大11为小
String GD1 = (championSum > 11) ? "冠亚大" : "冠亚小";
if (championSum == 11) {
GD1 = "冠亚小"; // 特殊处理和值为11算小
}
// 计算龙虎1-5位对比
List<String> GLH_result = new ArrayList<>();
for (int i = 0; i < 5; i++) {
if (resultNumbers.get(i) > resultNumbers.get(9 - i)) {
GLH_result.add("龙");
} else {
GLH_result.add("虎");
}
}
// 构建转换后的map使用List<Integer>作为result
map.put("result", resultNumbers);
map.put("sum1", sum1);
map.put("sum2", sum2);
map.put("winner", winner);
map.put("GD2", GD2);
map.put("GD1", GD1);
map.put("GLH_result", GLH_result);
map.put("id", String.valueOf(item.get("preDrawIssue").asLong()));
map.put("time", item.get("preDrawTime").asText());
resultList.add(map);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return resultList;
}*/
public static void writeToJsonFile(List<Map<String, Object>> resultList,String pypath) {
try {
// 创建 ObjectMapper 实例
ObjectMapper objectMapper = new ObjectMapper();
// 设置 JSON 格式化(可选,更易读)
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// 定义输出目录
String directoryPath = pypath+"/current_data"; // 项目根目录下的 output/json 文件夹
// 使用年月日作为文件名格式result_yyyyMMdd.json
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = DateUtils.getTodayDate();
String fileName = "result_" + dateStr + ".json";
String filePath = directoryPath + "/" + fileName;
// 创建目录(如果不存在)
File directory = new File(directoryPath);
if (!directory.exists()) {
directory.mkdirs(); // 创建多级目录
}
// 创建文件对象
File outputFile = new File(filePath);
// 如果文件已存在,读取现有数据并对比
List<Map<String, Object>> existingData = new ArrayList<>();
Set<String> existingIds = new HashSet<>();
if (outputFile.exists()) {
try {
existingData = objectMapper.readValue(outputFile,
objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
for (Map<String, Object> item : existingData) {
if (item.containsKey("id")) {
existingIds.add(item.get("id").toString());
}
}
log.info("已读取现有数据,共 " + existingData.size() + " 条记录");
} catch (IOException e) {
log.warn("读取现有文件失败,将覆盖写入: " + e.getMessage());
existingIds.clear();
}
}
// 筛选出新增的数据id不在existingIds中的记录
List<Map<String, Object>> newData = new ArrayList<>();
for (Map<String, Object> item : resultList) {
if (item.containsKey("id")) {
String id = item.get("id").toString();
if (!existingIds.contains(id)) {
newData.add(item);
}
}
}
// 合并现有数据和新数据
List<Map<String, Object>> finalData = new ArrayList<>();
if (!existingData.isEmpty()) {
finalData.addAll(existingData);
}
finalData.addAll(newData);
// 将合并后的数据写入 JSON 文件
objectMapper.writeValue(outputFile, finalData);
log.info("数据已成功写入文件: " + outputFile.getAbsolutePath() +
" (现有: " + existingData.size() + " 条, 新增: " + newData.size() + " 条, 总计: " + finalData.size() + " 条)");
/* // 创建文件对象
File outputFile = new File(filePath);
// 如果文件已存在,删除旧文件(实现替换功能)
if (outputFile.exists()) {
boolean deleted = outputFile.delete();
if (!deleted) {
throw new IOException("无法删除已存在的文件: " + filePath);
}
System.out.println("已删除旧文件,准备创建新文件: " + fileName);
}
// 将 List 写入 JSON 文件
objectMapper.writeValue(outputFile, resultList);
log.info("数据已成功写入文件: " + outputFile.getAbsolutePath());*/
} catch (IOException e) {
e.printStackTrace();
log.error("写入 JSON 文件失败: " + e.getMessage(), e);
throw new RuntimeException("写入 JSON 文件失败: " + e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,332 @@
package com.tem.bocai.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
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 us.codecraft.webmagic.selector.Selectable;
import java.io.File;
import java.io.IOException;
import java.util.*;
//开奖的历史结果
@Slf4j
public class LotteryHistoryCrawler implements PageProcessor {
private final String token;
// 站点配置
private Site site;
// final LoginService loginService;
// 添加一个字段标记是否成功解析数据
private static volatile boolean lastParseSuccess = true;
private String path;
private String date;
public LotteryHistoryCrawler(String token, String path,String date) {
this.token = token;
this.path =path;
this.date =date;
initSite();
}
/**
* 初始化Site配置
*/
private void initSite() {
site = Site.me()
.setRetryTimes(3)
.setSleepTime(1000)
.setTimeOut(10000)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
// 设置cookie
if (token != null && !token.isEmpty()) {
site.addHeader("cookie", "token=" + token);
}
}
@Override
public void process(Page page) {
// 获取页面HTML
Html html = page.getHtml();
// 打印页面基本信息
log.info("页面URL: " + page.getUrl());
log.info("页面标题: " + html.xpath("//title/text()").get());
// 示例:提取所有表格数据
Selectable tables = html.xpath("//table");
log.info("找到 " + tables.nodes().size() + " 个表格");
if(tables.nodes().isEmpty()){
lastParseSuccess = false;
}else {
lastParseSuccess = true;
}
// 提取表格数据(根据实际页面结构调整选择器)
extractTableData(html);
// 示例:提取所有链接
Selectable links = html.links();
System.out.println("页面包含 " + links.all().size() + " 个链接");
// 如果需要继续爬取其他页面
// page.addTargetRequests(links.all());
// 将数据存入结果
/* page.putField("html========", html.toString());
page.putField("title", html.xpath("//title/text()").get());*/
parseLotteryHtml(html.toString());
}
private void extractTableData(Html html) {
// 根据实际页面结构编写数据提取逻辑
// 示例提取所有tr元素
Selectable rows = html.xpath("//tr");
for (Selectable row : rows.nodes()) {
// 提取每行的td内容
String rowText = row.xpath("//td/text()").all().toString();
if (!rowText.isEmpty()) {
System.out.println("行数据: " + rowText);
}
}
}
@Override
public Site getSite() {
return site;
}
/**
* 添加一个方法获取解析状态
*/
public static boolean isLastParseSuccess() {
return lastParseSuccess;
}
/**
* 解析彩票HTML数据转换成指定的List<Map<String, Object>>格式
*
* @param htmlContent 爬取到的HTML文本内容
* @return 解析后的结构化数据列表
*/
public List<Map<String, Object>> parseLotteryHtml(String htmlContent) {
List<Map<String, Object>> resultList = new ArrayList<>();
// 初始化Jsoup解析器
Document doc = Jsoup.parse(htmlContent);
// 定位到数据所在的表格行drawTable下的table > tbody > tr
Element targetTable = doc.selectFirst("#drawTable");
if (targetTable == null) {
return resultList;
}
Elements trList = targetTable.select("table > tbody > tr");
// 遍历每一行数据
for (Element tr : trList) {
Map<String, Object> rowData = new HashMap<>();
// 1. 提取期数id
Element periodTd = tr.selectFirst("td.period");
rowData.put("id", periodTd != null ? periodTd.text().trim() : "");
// 2. 提取开奖时间time
Element timeTd = tr.selectFirst("td.drawTime");
rowData.put("time", timeTd != null ? timeTd.text().trim() : "");
// 3. 提取开出号码result- 10个ballname的数字
Elements ballTds = tr.select("td.ballname");
List<Integer> resultNumbers = new ArrayList<>();
int count = 0;
for (Element td : ballTds) {
if (count >= 10) break;
String text = td.text().trim();
if (text.matches("\\d+")) {
resultNumbers.add(Integer.parseInt(text));
count++;
}
}
rowData.put("result", resultNumbers);
// 4. 提取winnerother1
Element winnerTd = tr.selectFirst("td.other1");
if (winnerTd != null) {
String winnerText = winnerTd.text().trim();
if (winnerText.matches("\\d+")) {
rowData.put("winner", Integer.parseInt(winnerText));
} else {
rowData.put("winner", "");
}
} else {
rowData.put("winner", "");
}
// 5. 提取GD1冠亚小/大、GD2冠亚单/双)
Elements otherTds = tr.select("td.other");
String gd1 = "";
String gd2 = "";
for (Element td : otherTds) {
String className = td.className();
if (className.contains("GDX")) {
gd1 = td.text().trim();
} else if (className.contains("GDS")) {
gd2 = td.text().trim();
}
}
rowData.put("GD1", gd1);
rowData.put("GD2", gd2);
// 6. 提取sum1dldhl_sum、sum2dldhh_sum
Element sum1Td = tr.selectFirst("td.dldhl_sum");
if (sum1Td != null) {
String sum1Text = sum1Td.text().trim();
if (sum1Text.matches("\\d+")) {
rowData.put("sum1", Integer.parseInt(sum1Text));
} else {
rowData.put("sum1", "");
}
} else {
rowData.put("sum1", "");
}
Element sum2Td = tr.selectFirst("td.dldhh_sum");
if (sum2Td != null) {
String sum2Text = sum2Td.text().trim();
if (sum2Text.matches("\\d+")) {
rowData.put("sum2", Integer.parseInt(sum2Text));
} else {
rowData.put("sum2", "");
}
} else {
rowData.put("sum2", "");
}
// 7. 提取GLH_result龙虎结果5个GLH开头的td
List<String> glhResults = new ArrayList<>();
int glhCount = 0;
for (Element td : otherTds) {
if (glhCount >= 5) break;
String className = td.className();
if (className.contains("GLH_")) {
glhResults.add(td.text().trim());
glhCount++;
}
}
rowData.put("GLH_result", glhResults);
// 将单行数据加入结果列表(只保留有期数的有效行)
if (!rowData.get("id").toString().isEmpty()) {
resultList.add(rowData);
}
}
// 将数据写入SQLite数据库
SQLiteUtil.writeToSQLite(resultList);
// 将数据写入JSON文件保留原有功能
writeToJsonFile(resultList);
log.info("历史爬虫打印结果===" + resultList);
return resultList;
}
public void writeToJsonFile(List<Map<String, Object>> resultList) {
try {
// 创建 ObjectMapper 实例
ObjectMapper objectMapper = new ObjectMapper();
// 设置 JSON 格式化(可选,更易读)
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
// 定义输出目录
String directoryPath = path+"/current_data"; // 项目根目录下的 output/json 文件夹
// 使用年月日作为文件名格式result_yyyyMMdd.json
String fileName = "result_" + date + ".json";
String filePath = directoryPath + "/" + fileName;
// 创建目录(如果不存在)
File directory = new File(directoryPath);
if (!directory.exists()) {
directory.mkdirs(); // 创建多级目录
}
// 创建文件对象
File outputFile = new File(filePath);
// 如果文件已存在,读取现有数据并对比
List<Map<String, Object>> existingData = new ArrayList<>();
Set<String> existingIds = new HashSet<>();
if (outputFile.exists()) {
try {
existingData = objectMapper.readValue(outputFile,
objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
for (Map<String, Object> item : existingData) {
if (item.containsKey("id")) {
existingIds.add(item.get("id").toString());
}
}
log.info("已读取现有数据,共 " + existingData.size() + " 条记录");
} catch (IOException e) {
log.warn("读取现有文件失败,将覆盖写入: " + e.getMessage());
existingIds.clear();
}
}
// 筛选出新增的数据id不在existingIds中的记录
List<Map<String, Object>> newData = new ArrayList<>();
for (Map<String, Object> item : resultList) {
if (item.containsKey("id")) {
String id = item.get("id").toString();
if (!existingIds.contains(id)) {
newData.add(item);
}
}
}
// 合并现有数据和新数据
List<Map<String, Object>> finalData = new ArrayList<>();
if (!existingData.isEmpty()) {
finalData.addAll(existingData);
}
finalData.addAll(newData);
// 将合并后的数据写入 JSON 文件
objectMapper.writeValue(outputFile, finalData);
log.info("数据已成功写入文件: " + outputFile.getAbsolutePath() +
" (现有: " + existingData.size() + " 条, 新增: " + newData.size() + " 条, 总计: " + finalData.size() + " 条)");
} catch (IOException e) {
e.printStackTrace();
log.error("写入 JSON 文件失败: " + e.getMessage(), e);
throw new RuntimeException("写入 JSON 文件失败: " + e.getMessage(), e);
}
}
public static void main(String[] args) {
String url = "https://4701268539-esh.qdk63ayw8g.com/member/dresult?lottery=SGFT&date=2026-02-06";
// 创建爬虫
Spider.create(new LotteryHistoryCrawler("","",""))
.addUrl(url) // 添加起始URL
.thread(1) // 线程数
.run(); // 开始爬取
}
// 自定义headers
/*private Map<String, String> getHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put("cookie", "token=a1b219fe7e39374d6af532c56fdc911b76ae8f83");
return headers;
}*/
}

View File

@@ -255,8 +255,7 @@ public class LotteryWebMagicCrawler implements PageProcessor {
String directoryPath = path+"/current_data"; // 项目根目录下的 output/json 文件夹
// 使用年月日作为文件名格式result_yyyyMMdd.json
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = dateFormat.format(new Date());
String dateStr = DateUtils.getTodayDate();
String fileName = "result_" + dateStr + ".json";
String filePath = directoryPath + "/" + fileName;
@@ -269,18 +268,47 @@ public class LotteryWebMagicCrawler implements PageProcessor {
// 创建文件对象
File outputFile = new File(filePath);
// 如果文件已存在,删除旧文件(实现替换功能)
// 如果文件已存在,读取现有数据并对比
List<Map<String, Object>> existingData = new ArrayList<>();
Set<String> existingIds = new HashSet<>();
if (outputFile.exists()) {
boolean deleted = outputFile.delete();
if (!deleted) {
throw new IOException("无法删除已存在的文件: " + filePath);
try {
existingData = objectMapper.readValue(outputFile,
objectMapper.getTypeFactory().constructCollectionType(List.class, Map.class));
for (Map<String, Object> item : existingData) {
if (item.containsKey("id")) {
existingIds.add(item.get("id").toString());
}
}
log.info("已读取现有数据,共 " + existingData.size() + " 条记录");
} catch (IOException e) {
log.warn("读取现有文件失败,将覆盖写入: " + e.getMessage());
existingIds.clear();
}
System.out.println("已删除旧文件,准备创建新文件: " + fileName);
}
// 将 List 写入 JSON 文件
objectMapper.writeValue(outputFile, resultList);
log.info("数据已成功写入文件: " + outputFile.getAbsolutePath());
// 筛选出新增的数据id不在existingIds中的记录
List<Map<String, Object>> newData = new ArrayList<>();
for (Map<String, Object> item : resultList) {
if (item.containsKey("id")) {
String id = item.get("id").toString();
if (!existingIds.contains(id)) {
newData.add(item);
}
}
}
// 合并现有数据和新数据
List<Map<String, Object>> finalData = new ArrayList<>();
if (!existingData.isEmpty()) {
finalData.addAll(existingData);
}
finalData.addAll(newData);
// 将合并后的数据写入 JSON 文件
objectMapper.writeValue(outputFile, finalData);
log.info("数据已成功写入文件: " + outputFile.getAbsolutePath() +
" (现有: " + existingData.size() + " 条, 新增: " + newData.size() + " 条, 总计: " + finalData.size() + " 条)");
} catch (IOException e) {
e.printStackTrace();
log.error("写入 JSON 文件失败: " + e.getMessage(), e);
@@ -290,7 +318,7 @@ public class LotteryWebMagicCrawler implements PageProcessor {
public static void main(String[] args) {
String url = "https://4701268539-esh.qdk63ayw8g.com/member/dresult?lottery=SGFT&date=2026-01-18";
String url = "https://4701268539-esh.qdk63ayw8g.com/member/dresult?lottery=SGFT&date=2026-02-06";
// 创建爬虫
Spider.create(new LotteryWebMagicCrawler("",""))

View File

@@ -42,12 +42,25 @@ public class SQLiteUtil {
// 先初始化数据库
//initDatabase();
String insertSQL = """
/*String insertSQL = """
INSERT OR REPLACE INTO lottery_results
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
VALUES (?,?, ?, ?, ?, ?, ?, ?, ?)
""";*/
String insertSQL = """
INSERT INTO lottery_results
(issue, time, result, winner, gd1, gd2, sum1, sum2, glh_result)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(issue) DO UPDATE SET
time = excluded.time,
result = excluded.result,
winner = excluded.winner,
gd1 = excluded.gd1,
gd2 = excluded.gd2,
sum1 = excluded.sum1,
sum2 = excluded.sum2,
glh_result = excluded.glh_result
""";
Connection conn = null;
PreparedStatement pstmt = null;

View File

@@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
@@ -372,8 +373,16 @@ public class TokenCacheService {
// 等待一下再发送登录请求
Thread.sleep(1500 + (long) (Math.random() * 1000));
// // 新增代码:增加代理
// String proxyHost = "127.0.0.1";
// int proxyPort = 7890;
// HttpHost proxy = new HttpHost(proxyHost, proxyPort);
// RequestConfig proxyConfig = RequestConfig.custom()
// .setProxy(proxy)
// .build();
HttpPost loginPost = createLoginRequest(code,loginInfoParam);
// // 新增代码:将代理类放入配置中
// loginPost.setConfig(proxyConfig);
try (CloseableHttpResponse loginResponse = httpClient.execute(loginPost)) {
return processLoginResponse(loginResponse, cookieStore);
}
@@ -383,13 +392,16 @@ public class TokenCacheService {
* 创建登录请求
*/
private HttpPost createLoginRequest(String code,LoginInfoParam loginInfoParam) throws UnsupportedEncodingException {
HttpPost loginPost = new HttpPost(loginInfoParam.loginUrl + "/login");
// 设置请求头
setCommonHeaders(loginPost);
loginPost.setHeader("Referer", loginInfoParam.loginUrl + "/login");
loginPost.setHeader("Origin", loginInfoParam.loginUrl);
loginPost.setHeader("Accept", "application/json, text/plain, */*");
// 构建登录参数
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("type", "1"));

View File

@@ -10,4 +10,4 @@ spring.jpa.show-sql=true
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
pypath:d:/py/PyModel
pypath:D:/py/PyModel