Compare commits
32 Commits
a83fa8669f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67335e08aa | ||
| 029109d559 | |||
|
|
740dc15197 | ||
| 274f2a31af | |||
|
|
f6a9730879 | ||
|
|
e1695854a8 | ||
| 7a61514e1c | |||
| 22e3d844bf | |||
|
|
1a562ffc1c | ||
| 3f7b0568d3 | |||
|
|
419e00e74a | ||
| bded6f899b | |||
| c7918ad0e4 | |||
| 9376a85673 | |||
| 3f4e0b8f8f | |||
|
|
9460367dc1 | ||
| 19a8dc1a48 | |||
|
|
6920855953 | ||
|
|
f852676c83 | ||
| 4c2fe1d2a1 | |||
| 2060ddff83 | |||
| 4ff6b4bb24 | |||
|
|
06cd285d9f | ||
|
|
57968c183f | ||
|
|
7f9885253f | ||
| f7db42d060 | |||
| d3af15df1c | |||
| f0ec17934f | |||
| 97adad8d28 | |||
| eaccc78eff | |||
| be8bec753b | |||
| f913b331cd |
@@ -63,6 +63,15 @@ def get_history_records(data_dirs):
|
|||||||
return all_records
|
return all_records
|
||||||
|
|
||||||
|
|
||||||
|
def generate_time_series(start, end):
|
||||||
|
current = start
|
||||||
|
series = []
|
||||||
|
while current <= end:
|
||||||
|
series.append(current)
|
||||||
|
current += timedelta(minutes=5)
|
||||||
|
return series
|
||||||
|
|
||||||
|
|
||||||
def main(next_period_time, df):
|
def main(next_period_time, df):
|
||||||
entry = {
|
entry = {
|
||||||
"id": next_period_time.strftime('%Y%m%d%H%M'),
|
"id": next_period_time.strftime('%Y%m%d%H%M'),
|
||||||
@@ -77,22 +86,37 @@ def main(next_period_time, df):
|
|||||||
if next_period_time.strftime('%H:%M:%S') in EMPTY_TIME_WINDOWS:
|
if next_period_time.strftime('%H:%M:%S') in EMPTY_TIME_WINDOWS:
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
result = []
|
||||||
predictor = LotteryPredictorV14Profit()
|
predictor = LotteryPredictorV14Profit()
|
||||||
|
|
||||||
next_period_time = pd.to_datetime(next_period_time)
|
next_period_time = pd.to_datetime(next_period_time)
|
||||||
recent_x = df[df['time'] < next_period_time].tail(10000)
|
start_time, _ = predictor.check_init_daily_pnl_tracker(next_period_time)
|
||||||
predictor.check_init_daily_pnl_tracker(next_period_time)
|
time_series = generate_time_series(start_time, next_period_time)
|
||||||
predictions = predictor.predict(next_period_time, recent_x)
|
|
||||||
|
|
||||||
for pos, nums_bet in predictions.items():
|
for dt in time_series:
|
||||||
if not nums_bet:
|
entry = {
|
||||||
continue
|
"id": dt.strftime('%Y%m%d%H%M'),
|
||||||
entry['result'][pos] = nums_bet
|
"time": dt.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"result": {i: {n: None for n in range(1, 11)} for i in range(10)},
|
||||||
|
"result_detail": {i: {"单": None, "双": None, "大": None, "小": None} for i in range(10)},
|
||||||
|
"winner": {},
|
||||||
|
"GD1": {"冠亚大": None, "冠亚小": None},
|
||||||
|
"GD2": {"冠亚单": None, "冠亚双": None},
|
||||||
|
"GLH_result": {}
|
||||||
|
}
|
||||||
|
current_time = pd.to_datetime(dt)
|
||||||
|
recent_x = df[df['time'] < current_time].tail(10000)
|
||||||
|
predictions = predictor.predict(current_time, recent_x)
|
||||||
|
|
||||||
last_result = df[df['time'] == next_period_time]
|
for pos, nums_bet in predictions.items():
|
||||||
if last_result.size != 0:
|
if not nums_bet:
|
||||||
predictor.update_result(last_result, entry)
|
continue
|
||||||
return entry
|
entry['result'][pos] = nums_bet
|
||||||
|
|
||||||
|
last_result = df[df['time'] == current_time]
|
||||||
|
if last_result.size != 0:
|
||||||
|
predictor.update_result(last_result, entry)
|
||||||
|
result.append(entry)
|
||||||
|
return result[-1]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -101,7 +125,7 @@ if __name__ == "__main__":
|
|||||||
all_records = get_history_records(data_dirs)
|
all_records = get_history_records(data_dirs)
|
||||||
parser = argparse.ArgumentParser(description="下一期时间 next_period_time, 例: 2026-01-02 06:00:00")
|
parser = argparse.ArgumentParser(description="下一期时间 next_period_time, 例: 2026-01-02 06:00:00")
|
||||||
# 必选位置参数(直接传入值,无需前缀)
|
# 必选位置参数(直接传入值,无需前缀)
|
||||||
parser.add_argument("--next_period_time", type=str, help="下一期时间, 例: 2026-01-02 06:00:00", default="2026-01-02 06:00:00")
|
parser.add_argument("--next_period_time", type=str, help="下一期时间, 例: 2026-01-02 06:00:00", default="2026-01-02 07:05:00")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
fmt = "%Y-%m-%d %H:%M:%S"
|
fmt = "%Y-%m-%d %H:%M:%S"
|
||||||
next_period_time = datetime.strptime(args.next_period_time, fmt)
|
next_period_time = datetime.strptime(args.next_period_time, fmt)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
[init]
|
[init]
|
||||||
ODDS = 9.599
|
ODDS = 9.599
|
||||||
BASE_BET_UNIT = 100
|
BASE_BET_UNIT = 20
|
||||||
BET_THIS_ROUND = 100
|
|
||||||
INITIAL_CAPITAL = 20000
|
|
||||||
DAILY_STOP_LOSS_RATE = 0.30
|
DAILY_STOP_LOSS_RATE = 0.30
|
||||||
|
|
||||||
[dynamic]
|
[dynamic]
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class LotteryPredictorV14Profit:
|
|||||||
ODDS = float(conf["init"]["ODDS"])
|
ODDS = float(conf["init"]["ODDS"])
|
||||||
REBATE_RATE = float(conf["dynamic"]["REBATE_RATE"])
|
REBATE_RATE = float(conf["dynamic"]["REBATE_RATE"])
|
||||||
BASE_BET_UNIT = int(conf["init"]["BASE_BET_UNIT"])
|
BASE_BET_UNIT = int(conf["init"]["BASE_BET_UNIT"])
|
||||||
_bet_this_round = int(conf["init"]["BET_THIS_ROUND"])
|
_bet_this_round = BASE_BET_UNIT
|
||||||
SAFE_BET_LEVEL_1 = 0.05
|
SAFE_BET_LEVEL_1 = 0.05
|
||||||
SAFE_BET_LEVEL_2 = 0.10
|
SAFE_BET_LEVEL_2 = 0.10
|
||||||
SAFE_BET_LEVEL_3 = 0.15
|
SAFE_BET_LEVEL_3 = 0.15
|
||||||
@@ -28,7 +28,7 @@ class LotteryPredictorV14Profit:
|
|||||||
RADICAL_BET_LEVEL_0 = 0.05
|
RADICAL_BET_LEVEL_0 = 0.05
|
||||||
RADICAL_BET_LEVEL_1 = 0.1
|
RADICAL_BET_LEVEL_1 = 0.1
|
||||||
RADICAL_BET_LEVEL_2 = 0.2
|
RADICAL_BET_LEVEL_2 = 0.2
|
||||||
INITIAL_CAPITAL = int(conf["init"]["INITIAL_CAPITAL"])
|
INITIAL_CAPITAL = BASE_BET_UNIT * 200
|
||||||
DAILY_STOP_LOSS_RATE = float(conf["init"]["DAILY_STOP_LOSS_RATE"]) # 绝佳熔断点:6000元熔断
|
DAILY_STOP_LOSS_RATE = float(conf["init"]["DAILY_STOP_LOSS_RATE"]) # 绝佳熔断点:6000元熔断
|
||||||
POSITION_LOOKBACK_PERIODS = int(conf["dynamic"]["POSITION_LOOKBACK_PERIODS"])
|
POSITION_LOOKBACK_PERIODS = int(conf["dynamic"]["POSITION_LOOKBACK_PERIODS"])
|
||||||
OMISSIONS_LEVEL_1 = int(conf["dynamic"]["OMISSIONS_LEVEL_1"])
|
OMISSIONS_LEVEL_1 = int(conf["dynamic"]["OMISSIONS_LEVEL_1"])
|
||||||
@@ -126,10 +126,17 @@ class LotteryPredictorV14Profit:
|
|||||||
start_time, end_time = date_range
|
start_time, end_time = date_range
|
||||||
if start_time <= current_date <= end_time:
|
if start_time <= current_date <= end_time:
|
||||||
_date_range = (start_time, end_time)
|
_date_range = (start_time, end_time)
|
||||||
|
break
|
||||||
|
|
||||||
if not _date_range:
|
if not _date_range:
|
||||||
current_date_start_time = pd.to_datetime(current_date.date()) + timedelta(hours=7, minutes=5)
|
|
||||||
current_date_end_time = current_date_start_time + timedelta(hours=22, minutes=55)
|
if current_date.hour <= 6:
|
||||||
|
current_date_start_time = pd.to_datetime(current_date.date()) + timedelta(days=-1, hours=7, minutes=5)
|
||||||
|
current_date_end_time = current_date_start_time + timedelta(hours=22, minutes=55)
|
||||||
|
else:
|
||||||
|
current_date_start_time = pd.to_datetime(current_date.date()) + timedelta(hours=7, minutes=5)
|
||||||
|
current_date_end_time = current_date_start_time + timedelta(hours=22, minutes=55)
|
||||||
|
|
||||||
_date_range = (current_date_start_time, current_date_end_time)
|
_date_range = (current_date_start_time, current_date_end_time)
|
||||||
|
|
||||||
self.daily_pnl_tracker[_date_range] = {'pnl': 0, 'bets': 0, 'miss_count': 0}
|
self.daily_pnl_tracker[_date_range] = {'pnl': 0, 'bets': 0, 'miss_count': 0}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import axios from 'axios';
|
|||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
// 输入框数据
|
// 输入框数据
|
||||||
const input1 = ref('');
|
const betAmount = ref('');
|
||||||
const input2 = ref('');
|
|
||||||
|
|
||||||
// 登录模态框数据
|
// 登录模态框数据
|
||||||
const loginDialogVisible = ref(false);
|
const loginDialogVisible = ref(false);
|
||||||
const isLoggedIn = ref(false);
|
const isLoggedIn = ref(false);
|
||||||
const username = ref('未记录');
|
const username = ref('未记录');
|
||||||
|
const balance = ref('0'); // 余额
|
||||||
const loginForm = ref({
|
const loginForm = ref({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
@@ -64,7 +64,27 @@ function updateChart1() {
|
|||||||
left: 'center'
|
left: 'center'
|
||||||
},
|
},
|
||||||
tooltip: {
|
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: {
|
legend: {
|
||||||
data: ['数据'],
|
data: ['数据'],
|
||||||
@@ -217,20 +237,20 @@ async function fetchUserSettings() {
|
|||||||
username.value = response.data.username;
|
username.value = response.data.username;
|
||||||
isLoggedIn.value = true;
|
isLoggedIn.value = true;
|
||||||
}
|
}
|
||||||
// 更新止盈点
|
// 更新投注金额
|
||||||
if (response.data.winNum) {
|
if (response.data.betAmount) {
|
||||||
input1.value = response.data.winNum;
|
betAmount.value = response.data.betAmount;
|
||||||
}
|
}
|
||||||
// 更新止亏点
|
// 更新余额
|
||||||
if (response.data.loseNum) {
|
if (response.data.balance) {
|
||||||
input2.value = response.data.loseNum;
|
balance.value = response.data.balance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('用户设置数据获取完成');
|
console.log('用户设置数据获取完成');
|
||||||
console.log('username.value:', username.value);
|
console.log('username.value:', username.value);
|
||||||
console.log('input1.value:', input1.value);
|
console.log('betAmount.value:', betAmount.value);
|
||||||
console.log('input2.value:', input2.value);
|
console.log('balance.value:', balance.value);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取用户设置数据失败:', err);
|
console.error('获取用户设置数据失败:', err);
|
||||||
// 失败时不显示错误,使用默认值
|
// 失败时不显示错误,使用默认值
|
||||||
@@ -279,7 +299,8 @@ async function handleLogin() {
|
|||||||
// 调用登录API
|
// 调用登录API
|
||||||
const response = await axios.post(loginApiUrl, {
|
const response = await axios.post(loginApiUrl, {
|
||||||
username: loginForm.value.username,
|
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;
|
username.value = loginForm.value.username;
|
||||||
|
|
||||||
// 重置表单
|
|
||||||
loginForm.value = {
|
|
||||||
username: '',
|
|
||||||
password: '',
|
|
||||||
loginUrl: 'https://4701268539-esh.qdk63ayw8g.com'
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('登录成功:', response.data);
|
console.log('登录成功:', response.data);
|
||||||
} else {
|
} else {
|
||||||
// 登录失败
|
// 登录失败
|
||||||
@@ -330,19 +343,18 @@ function handleLogout() {
|
|||||||
|
|
||||||
// 处理确认按钮点击
|
// 处理确认按钮点击
|
||||||
async function handleConfirm() {
|
async function handleConfirm() {
|
||||||
console.log('确认按钮点击,止盈点:', input1.value, '止亏点:', input2.value);
|
console.log('确认按钮点击,投注金额:', betAmount.value);
|
||||||
|
|
||||||
// 验证输入
|
// 验证输入
|
||||||
if (!input1.value || !input2.value) {
|
if (!betAmount.value) {
|
||||||
alert('请填写完整的止盈止亏点');
|
alert('请填写投注金额');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 构建提交数据
|
// 构建提交数据
|
||||||
const submitData = {
|
const submitData = {
|
||||||
winNum: input1.value,
|
betAmount: betAmount.value
|
||||||
loseNum: input2.value
|
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('提交数据:', submitData);
|
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(() => {
|
onUnmounted(() => {
|
||||||
// 销毁图表
|
// 销毁图表
|
||||||
chart1.value?.dispose();
|
chart1.value?.dispose();
|
||||||
@@ -408,9 +445,12 @@ onUnmounted(() => {
|
|||||||
<div class="account-avatar">👤</div>
|
<div class="account-avatar">👤</div>
|
||||||
<div class="account-details">
|
<div class="account-details">
|
||||||
<div class="account-name">{{ username }}</div>
|
<div class="account-name">{{ username }}</div>
|
||||||
|
<div class="account-balance">余额: {{ balance }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="account-actions">
|
<div class="account-actions">
|
||||||
<button type="button" class="login-button" @click="loginDialogVisible = true">账号信息</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -418,7 +458,7 @@ onUnmounted(() => {
|
|||||||
<div v-if="loginDialogVisible" class="modal-overlay">
|
<div v-if="loginDialogVisible" class="modal-overlay">
|
||||||
<div class="modal-content" @click.stop>
|
<div class="modal-content" @click.stop>
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>登录</h3>
|
<h3>账号信息</h3>
|
||||||
<button type="button" class="modal-close" @click="loginDialogVisible = false">×</button>
|
<button type="button" class="modal-close" @click="loginDialogVisible = false">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
@@ -453,16 +493,11 @@ onUnmounted(() => {
|
|||||||
<!-- 顶部输入框区域 -->
|
<!-- 顶部输入框区域 -->
|
||||||
<div class="top-inputs">
|
<div class="top-inputs">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
|
<label for="betAmount">投注金额:</label>
|
||||||
<div class="input-with-button">
|
<div class="input-with-button">
|
||||||
<input type="text" id="input1" v-model="input1" placeholder="请输入止盈点">
|
<input type="text" id="betAmount" v-model="betAmount" placeholder="请输入投注金额">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-with-button">
|
|
||||||
<input type="text" id="input2" v-model="input2" placeholder="请输入止亏点">
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<button type="button" class="confirm-button" @click="handleConfirm">确认</button>
|
<button type="button" class="confirm-button" @click="handleConfirm">确认</button>
|
||||||
<button type="button" class="stop-button" @click="handleStop">停止</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -483,17 +518,19 @@ onUnmounted(() => {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
<th>期数</th>
|
<th>投注金额</th>
|
||||||
<th>开奖时间</th>
|
<th>结果</th>
|
||||||
<th>开奖号码</th>
|
<th>金额</th>
|
||||||
|
<th>投注时间</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="item in tableData" :key="item.id">
|
<tr v-for="item in tableData" :key="item.id">
|
||||||
<td>{{ item.id }}</td>
|
<td>{{ item.betId }}</td>
|
||||||
<td>{{ item.name }}</td>
|
<td>{{ item.betAmount }}</td>
|
||||||
<td>{{ item.time }}</td>
|
|
||||||
<td>{{ item.result }}</td>
|
<td>{{ item.result }}</td>
|
||||||
|
<td>{{ item.resultAmount }}</td>
|
||||||
|
<td>{{ item.time }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -569,6 +606,11 @@ onUnmounted(() => {
|
|||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account-balance {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.account-role {
|
.account-role {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -600,6 +642,13 @@ onUnmounted(() => {
|
|||||||
transform: translateY(1px);
|
transform: translateY(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 账号操作中的开始和停止按钮 */
|
||||||
|
.account-actions .start-button,
|
||||||
|
.account-actions .stop-button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
.logout-button {
|
.logout-button {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
@@ -633,14 +682,15 @@ onUnmounted(() => {
|
|||||||
.input-group {
|
.input-group {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-button {
|
.input-with-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-with-button input {
|
.input-with-button input {
|
||||||
@@ -686,6 +736,29 @@ onUnmounted(() => {
|
|||||||
gap: 8px;
|
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 {
|
.stop-button {
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
|||||||
27
pom.xml
27
pom.xml
@@ -92,11 +92,38 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
<finalName>bocai</finalName>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>net.roseboy</groupId>
|
||||||
|
<artifactId>classfinal-maven-plugin</artifactId>
|
||||||
|
<version>1.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>classFinal</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<!-- 加密密码(必填) -->
|
||||||
|
<password>#</password>
|
||||||
|
<!-- 需加密的包名(可选,多个用逗号分隔) -->
|
||||||
|
<packages>com.tem.bocai</packages>
|
||||||
|
<!-- 排除加密的类(可选) -->
|
||||||
|
<excludes>org.springframework.*</excludes>
|
||||||
|
<!-- 机器码(可选,绑定机器) -->
|
||||||
|
<!-- <code>xxxxxx</code>-->
|
||||||
|
<!-- 调试模式(可选) -->
|
||||||
|
<debug>false</debug>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
|
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
rem 启动后端Spring Boot应用
|
rem 启动后端Spring Boot应用
|
||||||
echo 正在启动后端应用...
|
echo 正在启动后端应用...
|
||||||
start "后端服务" cmd /k "cd /d e:\git_resp\bocai && java -jar target/bocai-0.0.1-SNAPSHOT.jar"
|
start "后端服务" cmd /k "cd /d e:\git_resp\bocai && java -Dlogging.level.root=info -Dlogging.file.name=logs/application.log -jar target/bocai-0.0.1-SNAPSHOT.jar"
|
||||||
|
|
||||||
rem 等待后端启动
|
rem 等待后端启动
|
||||||
ping 127.0.0.1 -n 5 > nul
|
ping 127.0.0.1 -n 5 > nul
|
||||||
|
|
||||||
rem 启动前端Vue应用
|
rem 启动前端Vue应用
|
||||||
echo 正在启动前端应用...
|
echo 正在启动前端应用...
|
||||||
start "前端服务" cmd /k "cd /d e:\git_resp\bocai\frontend && npm run dev"
|
start "前端服务" cmd /k "cd /d C:\Users\admin\Desktop\boc\frontend && npm run dev"
|
||||||
|
|
||||||
echo 前后端应用已启动!
|
echo 前后端应用已启动!
|
||||||
echo 后端服务:http://localhost:8080
|
echo 后端服务:http://localhost:8080
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 启动后端Spring Boot应用
|
|
||||||
echo "正在启动后端应用..."
|
|
||||||
cd "$(dirname "$0")" && java -jar target/bocai-0.0.1-SNAPSHOT.jar &
|
|
||||||
|
|
||||||
# 等待后端启动
|
|
||||||
sleep 5
|
|
||||||
|
|
||||||
# 启动前端Vue应用
|
|
||||||
echo "正在启动前端应用..."
|
|
||||||
cd frontend && npm run dev &
|
|
||||||
|
|
||||||
echo "前后端应用已启动!"
|
|
||||||
echo "后端服务:http://localhost:8080"
|
|
||||||
echo "前端服务:通常为 http://localhost:5173 (具体端口请查看前端启动日志)"
|
|
||||||
echo "按 Ctrl+C 停止所有服务..."
|
|
||||||
|
|
||||||
# 等待用户中断
|
|
||||||
trap "kill 0" EXIT
|
|
||||||
wait
|
|
||||||
@@ -14,20 +14,21 @@ public class BocaiApplication {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
ApplicationContext context = SpringApplication.run(BocaiApplication.class, args);
|
ApplicationContext context = SpringApplication.run(BocaiApplication.class, args);
|
||||||
|
|
||||||
/*BetSchedule betSchedule = context.getBean(BetSchedule.class);
|
|
||||||
betSchedule.placeBet();*/
|
|
||||||
|
|
||||||
// // 依次执行三个任务
|
// // 依次执行三个任务
|
||||||
//
|
//
|
||||||
// // 1. 执行CrawlerSchedule方法
|
// 1. 执行CrawlerSchedule方法
|
||||||
// System.out.println("\n=== 开始执行CrawlerSchedule任务 ===");
|
System.out.println("\n=== 开始执行初始化爬取最近7天的开奖结果任务 ===");
|
||||||
// CrawlerSchedule crawlerSchedule = context.getBean(CrawlerSchedule.class);
|
CrawlerSchedule crawlerSchedule = context.getBean(CrawlerSchedule.class);
|
||||||
// crawlerSchedule.executeLotteryDraw();
|
crawlerSchedule.executeLotteryDrawHistory();
|
||||||
//
|
//
|
||||||
// 3. 执行ExBetScriptSchedule方法
|
// 3. 执行ExBetScriptSchedule方法
|
||||||
// System.out.println("\n=== 开始执行ExBetScriptSchedule任务 ===");
|
// System.out.println("\n=== 开始执行ExBetScriptSchedule任务 ===");
|
||||||
// ExBetScriptSchedule exBetScriptSchedule = context.getBean(ExBetScriptSchedule.class);
|
// ExBetScriptSchedule exBetScriptSchedule = context.getBean(ExBetScriptSchedule.class);
|
||||||
// exBetScriptSchedule.executePythonScript();
|
// exBetScriptSchedule.executePythonScript();
|
||||||
|
//
|
||||||
|
// BetSchedule betSchedule = context.getBean(BetSchedule.class);
|
||||||
|
// betSchedule.placeBet();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,8 @@ public class TessConfig {
|
|||||||
|
|
||||||
Tesseract instance = new Tesseract();
|
Tesseract instance = new Tesseract();
|
||||||
instance.setLanguage("oci"); // 设置语言包,这里使用英语
|
instance.setLanguage("oci"); // 设置语言包,这里使用英语
|
||||||
instance.setDatapath("src/main/resources/tessdata"); // 设置语言包路径
|
// instance.setDatapath("src/main/resources/tessdata"); // 设置语言包路径
|
||||||
|
instance.setDatapath("tessdata"); // 设置语言包路径
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.tem.bocai.controller;
|
package com.tem.bocai.controller;
|
||||||
|
|
||||||
import com.tem.bocai.entity.CompletedToday;
|
import com.tem.bocai.entity.CompletedToday;
|
||||||
import com.tem.bocai.entity.LotteryResult;
|
|
||||||
import com.tem.bocai.repository.CompletedTodayRepository;
|
import com.tem.bocai.repository.CompletedTodayRepository;
|
||||||
import com.tem.bocai.repository.LotteryResultRepository;
|
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
@@ -15,9 +13,6 @@ import java.util.*;
|
|||||||
@RequestMapping("/api")
|
@RequestMapping("/api")
|
||||||
public class ChartController {
|
public class ChartController {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private LotteryResultRepository lotteryResultRepository;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private CompletedTodayRepository completedTodayRepository;
|
private CompletedTodayRepository completedTodayRepository;
|
||||||
|
|
||||||
@@ -27,56 +22,92 @@ public class ChartController {
|
|||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取今日日期的字符串表示(格式:yyyy-MM-dd)
|
// 获取今日日期
|
||||||
String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
|
Date now = new Date();
|
||||||
System.out.println("今日日期: " + today);
|
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数据
|
// 从数据库获取今日的CompletedToday数据
|
||||||
List<CompletedToday> completedTodays = completedTodayRepository.findAll();
|
|
||||||
System.out.println("获取到" + completedTodays.size() + "条CompletedToday数据");
|
// 过滤出当前时间之前的数据
|
||||||
|
List<CompletedToday> filteredData = new ArrayList<>();
|
||||||
// 过滤出今日的数据
|
for (CompletedToday item : todayData) {
|
||||||
List<CompletedToday> todayData = new ArrayList<>();
|
if (item.getCreateTime().after(startOfDay)) {
|
||||||
for (CompletedToday item : completedTodays) {
|
filteredData.add(item);
|
||||||
if (item.getTime() != null && item.getTime().toString().contains(today)) {
|
|
||||||
todayData.add(item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("过滤后获取到" + todayData.size() + "条今日数据");
|
todayData = filteredData;
|
||||||
|
|
||||||
// 按时间排序
|
// 按时间排序
|
||||||
todayData.sort(Comparator.comparing(CompletedToday::getTime));
|
todayData.sort(Comparator.comparing(CompletedToday::getCreateTime));
|
||||||
|
|
||||||
// 提取数据
|
// 生成每5分钟的时间间隔标签
|
||||||
List<Double> data = new ArrayList<>();
|
|
||||||
List<String> labels = new ArrayList<>();
|
List<String> labels = new ArrayList<>();
|
||||||
|
List<Double> data = new ArrayList<>();
|
||||||
for (CompletedToday item : todayData) {
|
|
||||||
// 使用resultAmount作为折线图数据
|
// 遍历每5分钟间隔
|
||||||
data.add(item.getResultAmount());
|
Calendar intervalCalendar = (Calendar) calendar.clone();
|
||||||
// 使用时间作为标签(只保留时分部分)
|
while (intervalCalendar.getTime().before(currentTime)) {
|
||||||
Date time = item.getTime();
|
// 生成时间标签
|
||||||
String format = DateFormatUtils.format(time, "yyyy-MM-dd HH:mm:ss");
|
String timeLabel = DateFormatUtils.format(intervalCalendar.getTime(), "HH:mm");
|
||||||
if (time != null && format.length() >= 16) {
|
labels.add(timeLabel);
|
||||||
labels.add(format.substring(11, 16)); // 提取时分部分
|
|
||||||
} else {
|
// 计算该时间点的累计盈亏
|
||||||
labels.add(format);
|
double intervalProfit = 0.0;
|
||||||
|
for (CompletedToday item : todayData) {
|
||||||
|
if (item.getCreateTime().before(intervalCalendar.getTime())) {
|
||||||
|
intervalProfit += item.getResultAmount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// 更新总盈亏
|
||||||
|
data.add(intervalProfit);
|
||||||
|
|
||||||
|
// 增加5分钟
|
||||||
|
intervalCalendar.add(Calendar.MINUTE, 5);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果没有今日数据,使用默认数据
|
// 如果没有今日数据,生成默认的5分钟间隔数据
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
data = Arrays.asList(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
labels = new ArrayList<>();
|
||||||
labels = Arrays.asList("00:00", "04:00", "08:00", "12:00", "16:00", "20:00", "23:59");
|
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("data", data);
|
||||||
response.put("labels", labels);
|
response.put("labels", labels);
|
||||||
response.put("title", "今日盈亏数据");
|
response.put("title", "今日累计盈亏数据");
|
||||||
System.out.println("返回的数据长度: " + data.size());
|
|
||||||
System.out.println("返回的标签长度: " + labels.size());
|
|
||||||
System.out.println("返回的标签: " + labels);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取折线图1数据失败: " + e.getMessage());
|
System.err.println("获取折线图1数据失败: " + e.getMessage());
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -99,23 +130,28 @@ public class ChartController {
|
|||||||
List<Map<String, Object>> tableData = new ArrayList<>();
|
List<Map<String, Object>> tableData = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 获取今日日期的字符串表示(格式:yyyy-MM-dd)
|
// 从数据库获取今日的CompletedToday数据
|
||||||
String today = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new java.util.Date());
|
Calendar calendar = Calendar.getInstance();
|
||||||
System.out.println("今日日期: " + today);
|
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);
|
completedTodayList.sort(Comparator.comparing(CompletedToday::getCreateTime).reversed());
|
||||||
System.out.println("获取到" + lotteryResults.size() + "条今日数据");
|
|
||||||
|
|
||||||
// 将LotteryResult对象转换为前端需要的Map格式
|
// 将CompletedToday对象转换为前端需要的Map格式
|
||||||
for (LotteryResult result : lotteryResults) {
|
for (CompletedToday item : completedTodayList) {
|
||||||
Map<String, Object> item = new HashMap<>();
|
Map<String, Object> map = new HashMap<>();
|
||||||
item.put("id", result.getId());
|
map.put("betId", item.getBetId());
|
||||||
item.put("name", result.getIssue()); // 使用期号作为名称
|
map.put("betAmount", item.getBetAmount());
|
||||||
item.put("time", result.getTime()); // 添加开奖时间
|
map.put("result", item.getResult());
|
||||||
// 将List<String>转换为逗号分隔的字符串,避免序列化问题
|
map.put("resultAmount", item.getResultAmount());
|
||||||
item.put("result", result.getResult()); // 添加开奖号码
|
map.put("time", DateFormatUtils.format(item.getTime(), "yyyy-MM-dd HH:mm:ss"));
|
||||||
tableData.add(item);
|
tableData.add(map);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取表格数据失败: " + e.getMessage());
|
System.err.println("获取表格数据失败: " + e.getMessage());
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ public class LoginInfoResult {
|
|||||||
@Column(name = "cookie")
|
@Column(name = "cookie")
|
||||||
private String cookie;
|
private String cookie;
|
||||||
|
|
||||||
|
//余额
|
||||||
|
@Column(name = "balance")
|
||||||
|
private String balance;
|
||||||
|
|
||||||
|
private Integer betAmount;
|
||||||
|
|
||||||
@Column(name = "create_time", nullable = false, updatable = false)
|
@Column(name = "create_time", nullable = false, updatable = false)
|
||||||
@CreationTimestamp
|
@CreationTimestamp
|
||||||
@Convert(converter = SQLiteDateConverter.class)
|
@Convert(converter = SQLiteDateConverter.class)
|
||||||
|
|||||||
@@ -19,39 +19,41 @@ public class LotteryResult {
|
|||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
/*@Column(name = "id", nullable = false, unique = true)
|
/*@Column(name = "id", unique = true)
|
||||||
private String id; // 期号*/
|
private String id; // 期号*/
|
||||||
|
|
||||||
@Column(name = "issue", nullable = false, unique = true)
|
@Column(name = "issue", unique = true)
|
||||||
private String issue; // 期号
|
private String issue; // 期号
|
||||||
|
|
||||||
@Column(name = "time", nullable = false)
|
@Column(name = "time")
|
||||||
private String time; // 开奖时间
|
private String time; // 开奖时间
|
||||||
|
|
||||||
@Column(name = "result")
|
@Column(name = "result")
|
||||||
private String result; // 开奖号码
|
private String result; // 开奖号码
|
||||||
|
|
||||||
@Column(name = "winner", nullable = false)
|
@Column(name = "winner")
|
||||||
private String winner; //
|
private String winner; //
|
||||||
|
|
||||||
|
|
||||||
@Column(name = "sum1", nullable = false)
|
@Column(name = "sum1")
|
||||||
private String sum1; // 总和值
|
private String sum1; // 总和值
|
||||||
|
|
||||||
@Column(name = "sum2", nullable = false)
|
@Column(name = "sum2")
|
||||||
private String sum2; // 冠亚和
|
private String sum2; // 冠亚和
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Column(name = "gd1", nullable = false)
|
@Column(name = "gd1")
|
||||||
private String gd1; // 冠亚单
|
private String gd1; // 冠亚单
|
||||||
|
|
||||||
|
|
||||||
@Column(name = "gd2", nullable = false)
|
@Column(name = "gd2")
|
||||||
private String gd2; // 冠亚大
|
private String gd2; // 冠亚大
|
||||||
|
|
||||||
|
|
||||||
@Column(name = "glh_result", nullable = false)
|
@Column(name = "glh_result")
|
||||||
private String glh_result; //[ "龙", "龙", "龙", "虎", "虎" ] 龙虎
|
private String glh_result; //[ "龙", "龙", "龙", "虎", "虎" ] 龙虎
|
||||||
|
|
||||||
|
@Column(name = "bet_result")
|
||||||
|
private String betResult;
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
@@ -19,4 +20,17 @@ public interface CompletedTodayRepository extends JpaRepository<CompletedToday,
|
|||||||
@Query("SELECT SUM(ct.resultAmount) FROM CompletedToday ct WHERE ct.time > :startTime")
|
@Query("SELECT SUM(ct.resultAmount) FROM CompletedToday ct WHERE ct.time > :startTime")
|
||||||
Double sumResultAmountByCreateTimeAfter(@Param("startTime") Date 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface LotteryResultRepository extends JpaRepository<LotteryResult, Long> {
|
public interface LotteryResultRepository extends JpaRepository<LotteryResult, Long> {
|
||||||
// 根据时间查询(使用like匹配日期部分)
|
// 根据时间查询(使用like匹配日期部分)
|
||||||
List<LotteryResult> findByTimeContaining(String date);
|
List<LotteryResult> findByTimeContaining(String date);
|
||||||
|
|
||||||
|
// 查询最新的记录(按time降序取第一条)
|
||||||
|
Optional<LotteryResult> findTopByOrderByTimeDesc();
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.tem.bocai.schedules;
|
package com.tem.bocai.schedules;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.tem.bocai.util.TokenCacheService;
|
import com.tem.bocai.util.TokenCacheService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -19,9 +21,11 @@ import java.util.Optional;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import com.tem.bocai.entity.LoginInfoResult;
|
import com.tem.bocai.entity.LoginInfoResult;
|
||||||
import com.tem.bocai.entity.BetRecord;
|
import com.tem.bocai.entity.BetRecord;
|
||||||
|
import com.tem.bocai.entity.LotteryResult;
|
||||||
import com.tem.bocai.repository.CompletedTodayRepository;
|
import com.tem.bocai.repository.CompletedTodayRepository;
|
||||||
import com.tem.bocai.repository.LoginInfoRepository;
|
import com.tem.bocai.repository.LoginInfoRepository;
|
||||||
import com.tem.bocai.repository.BetRecordRepository;
|
import com.tem.bocai.repository.BetRecordRepository;
|
||||||
|
import com.tem.bocai.repository.LotteryResultRepository;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -39,8 +43,11 @@ public class BetSchedule {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private BetRecordRepository betRecordRepository;
|
private BetRecordRepository betRecordRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LotteryResultRepository lotteryResultRepository;
|
||||||
|
|
||||||
// 从7:02分钟起每5分钟执行一次
|
// 从7:02分钟起每5分钟执行一次
|
||||||
@Scheduled(cron = "30 2/5 * * * ?")
|
@Scheduled(cron = "30 2/5 * * * ?")
|
||||||
public void placeBet() {
|
public void placeBet() {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
int hour = now.getHour();
|
int hour = now.getHour();
|
||||||
@@ -52,7 +59,7 @@ public class BetSchedule {
|
|||||||
String currentTime = now.format(
|
String currentTime = now.format(
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
);
|
);
|
||||||
System.out.println(currentTime + " - 不在投注时间范围内,跳过执行");
|
log.info("{}", currentTime + " - 不在投注时间范围内,跳过执行");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,54 +70,53 @@ public class BetSchedule {
|
|||||||
|
|
||||||
LoginInfoResult loginInfo = loginInfoRepository.findFirstByOrderByCreateTimeDesc().orElse(null);
|
LoginInfoResult loginInfo = loginInfoRepository.findFirstByOrderByCreateTimeDesc().orElse(null);
|
||||||
if (loginInfo == null) {
|
if (loginInfo == null) {
|
||||||
System.out.println(currentTime + " - 未找到登录信息,跳过执行");
|
log.info("{}", currentTime + " - 未找到登录信息,跳过执行");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查onOff字段是否为1(开启状态)
|
// 检查onOff字段是否为1(开启状态)
|
||||||
if (loginInfo.getOnOff() != 1) {
|
if (loginInfo.getOnOff() != 1) {
|
||||||
System.out.println(currentTime + " - 投注功能未开启,跳过执行");
|
log.info("{}", currentTime + " - 投注功能未开启,跳过执行");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查winNum和loseNum字段是否合理
|
// // 检查winNum和loseNum字段是否合理
|
||||||
Integer winNum = loginInfo.getWinNum();
|
// Integer winNum = loginInfo.getWinNum();
|
||||||
Integer loseNum = loginInfo.getLoseNum();
|
// 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) {
|
log.info("{}", currentTime + " - 开始执行投注...");
|
||||||
// 根据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 + " - 开始执行投注...");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 执行投注逻辑
|
// 执行投注逻辑
|
||||||
executeBet(loginInfo);
|
executeBet(loginInfo);
|
||||||
|
|
||||||
System.out.println(currentTime + " - 投注执行完成");
|
log.info("{}", currentTime + " - 投注执行完成");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(currentTime + " - 投注执行失败:");
|
log.error("{} - 投注执行失败:", currentTime, e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +135,7 @@ public class BetSchedule {
|
|||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
// 2. 从BetRecord中获取第一条记录(根据betTime排序)
|
// 2. 从BetRecord中获取第一条记录(根据betTime排序)
|
||||||
System.out.println(" - 从BetRecord中获取第一条记录...");
|
log.info(" - 从BetRecord中获取第一条记录...");
|
||||||
Optional<BetRecord> optionalBetRecord = betRecordRepository.findFirstByOrderByBetTimeDesc();
|
Optional<BetRecord> optionalBetRecord = betRecordRepository.findFirstByOrderByBetTimeDesc();
|
||||||
|
|
||||||
if (optionalBetRecord.isPresent()) {
|
if (optionalBetRecord.isPresent()) {
|
||||||
@@ -137,37 +143,36 @@ public class BetSchedule {
|
|||||||
String betData = betRecord.getBetData();
|
String betData = betRecord.getBetData();
|
||||||
String betTime = betRecord.getBetTime();
|
String betTime = betRecord.getBetTime();
|
||||||
|
|
||||||
System.out.println(" - 投注时间: " + betTime);
|
log.info(" - 投注时间: {}", betTime);
|
||||||
System.out.println(" - 投注数据: " + betData);
|
log.info(" - 投注数据: {}", betData);
|
||||||
|
|
||||||
// 调用投注接口
|
// 调用投注接口
|
||||||
System.out.println(" - 提交投注...");
|
log.info(" - 提交投注...");
|
||||||
String betResult = callBetApi(betData, loginInfo);
|
String betResult = callBetApi(betData, null);
|
||||||
System.out.println(" - 投注结果: " + betResult);
|
log.info(" - 投注结果: {}", betResult);
|
||||||
|
|
||||||
// 记录投注结果
|
// 记录投注结果
|
||||||
System.out.println(" - 记录投注结果...");
|
log.info(" - 记录投注结果...");
|
||||||
recordBetResult(betData, betResult);
|
recordBetResult(betData, betResult);
|
||||||
|
|
||||||
// 投注成功后删除BetRecord记录
|
// 投注成功后删除BetRecord记录
|
||||||
if (betResult == null) {
|
if (betResult == null) {
|
||||||
betRecordRepository.delete(betRecord);
|
betRecordRepository.delete(betRecord);
|
||||||
System.out.println(" - 已删除投注记录,期数: " + betRecord.getBetNum());
|
log.info(" - 已删除投注记录,期数: {}", betRecord.getBetNum());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println(" - 未找到投注记录");
|
log.info(" - 未找到投注记录");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println(" - 投注执行失败:");
|
log.error(" - 投注执行失败:", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用投注接口
|
* 调用投注接口
|
||||||
*/
|
*/
|
||||||
private String callBetApi(String betData, LoginInfoResult loginInfo) throws IOException, InterruptedException {
|
private String callBetApi(String betData, String token) throws IOException, InterruptedException {
|
||||||
// 假设投注接口地址为http://localhost:8080/api/bet
|
// 假设投注接口地址为http://localhost:8080/api/bet
|
||||||
String apiUrl = "https://4701268539-esh.qdk63ayw8g.com/member/bet";
|
String apiUrl = "https://4701268539-esh.qdk63ayw8g.com/member/bet";
|
||||||
|
|
||||||
@@ -176,7 +181,7 @@ public class BetSchedule {
|
|||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(apiUrl))
|
.uri(URI.create(apiUrl))
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.header("cookie", "token=" + tokenCacheService.getToken());
|
.header("cookie", "token=" + (StringUtils.isBlank(token)?tokenCacheService.getToken():token));
|
||||||
|
|
||||||
HttpRequest request = requestBuilder
|
HttpRequest request = requestBuilder
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(betData))
|
.POST(HttpRequest.BodyPublishers.ofString(betData))
|
||||||
@@ -187,10 +192,14 @@ public class BetSchedule {
|
|||||||
|
|
||||||
// 解析响应
|
// 解析响应
|
||||||
if (response.statusCode() == 200) {
|
if (response.statusCode() == 200) {
|
||||||
|
|
||||||
log.info("投注成功");
|
log.info("投注成功");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}else if (response.statusCode() == 302){
|
||||||
|
String tokenSqlite = tokenCacheService.getTokenSqlite();
|
||||||
|
callBetApi(betData, tokenSqlite);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
return "error: " + response.statusCode();
|
return "error: " + response.statusCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,9 +208,27 @@ public class BetSchedule {
|
|||||||
* 记录投注结果
|
* 记录投注结果
|
||||||
*/
|
*/
|
||||||
private void recordBetResult(String betData, String betResult) {
|
private void recordBetResult(String betData, String betResult) {
|
||||||
// 这里可以实现将投注结果记录到数据库或日志文件的逻辑
|
// 这里实现将投注结果记录到LotteryResult的betResult字段的逻辑
|
||||||
// 为了简单起见,我们这里只打印日志
|
|
||||||
String currentTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
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获取drawNumber(term)
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,46 @@
|
|||||||
package com.tem.bocai.schedules;
|
package com.tem.bocai.schedules;
|
||||||
|
|
||||||
import com.tem.bocai.entity.LoginInfoResult;
|
import com.tem.bocai.entity.LoginInfoResult;
|
||||||
|
import com.tem.bocai.entity.LotteryResult;
|
||||||
import com.tem.bocai.repository.LoginInfoRepository;
|
import com.tem.bocai.repository.LoginInfoRepository;
|
||||||
|
import com.tem.bocai.repository.LotteryResultRepository;
|
||||||
import com.tem.bocai.util.*;
|
import com.tem.bocai.util.*;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import us.codecraft.webmagic.Spider;
|
import us.codecraft.webmagic.Spider;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.util.List;
|
||||||
import java.io.IOException;
|
import java.util.Map;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class CrawlerSchedule {
|
public class CrawlerSchedule {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TokenCacheService tokenCacheService;
|
private TokenCacheService tokenCacheService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private LoginInfoRepository loginInfoRepository;
|
private LoginInfoRepository loginInfoRepository;
|
||||||
|
@Autowired
|
||||||
|
private LotteryResultRepository lotteryResultRepository;
|
||||||
|
|
||||||
|
|
||||||
private static final int MAX_CRA = 3;
|
private static final int MAX_CRA = 3;
|
||||||
private static final Integer ONOFF = 0;
|
private static final Integer ONOFF = 0;
|
||||||
|
@Value("${pypath}")
|
||||||
|
private String pypath;
|
||||||
// 每天凌晨2点执行爬取开奖结果
|
// 每天凌晨2点执行爬取开奖结果
|
||||||
//@Scheduled(cron = "0 0 2 * * ?")
|
//@Scheduled(cron = "0 0 2 * * ?")
|
||||||
// 每7秒执行一次爬取开奖结果
|
// 每7秒执行一次爬取开奖结果
|
||||||
// @Scheduled(cron = "*/9 * * * * ?")
|
//@Scheduled(cron = "*/9 * * * * ?")
|
||||||
/*@Scheduled(cron = "0 6-59/5 7-23 * * ?")
|
/*@Scheduled(cron = "0 6-59/5 7-23 * * ?")
|
||||||
@Scheduled(cron = "0 0-55/5 0-6 * * ?")*/
|
@Scheduled(cron = "0 0-55/5 0-6 * * ?")*/
|
||||||
// 从7:00分30秒起每5分钟执行一次
|
// 从7:00分30秒起每5分钟执行一次
|
||||||
@Scheduled(cron = "30 0/5 * * * ?")
|
@Scheduled(cron = "30 0/5 * * * ?")
|
||||||
public void executeLotteryDraw() {
|
public void executeLotteryDraw() {
|
||||||
System.out.println("开始爬取开奖结果...");
|
log.info("开始爬取开奖结果");
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
||||||
@@ -40,22 +48,25 @@ public class CrawlerSchedule {
|
|||||||
if (firstByOrderByCreateTimeDesc == null) {
|
if (firstByOrderByCreateTimeDesc == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(firstByOrderByCreateTimeDesc.getOnOff() == ONOFF){
|
||||||
|
return;
|
||||||
|
}
|
||||||
String token = tokenCacheService.getToken();
|
String token = tokenCacheService.getToken();
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
while (!success && retryCount < MAX_CRA) {
|
while (!success && retryCount < MAX_CRA) {
|
||||||
System.out.println("\n=== 第 " + (retryCount + 1) + " 次尝试获取开奖结果 ===");
|
log.info("\n=== 第 " + (retryCount + 1) + " 次尝试获取开奖结果 ===");
|
||||||
|
|
||||||
if (token == null || token.isEmpty()) {
|
if (token == null || token.isEmpty()) {
|
||||||
System.out.println("token为空,从数据库重新获取");
|
log.info("token为空,从数据库重新获取");
|
||||||
token = tokenCacheService.getTokenSqlite();
|
token = tokenCacheService.getTokenSqlite();
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
System.err.println("无法获取有效token");
|
log.error("无法获取有效token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println("使用token: " + (token.length() > 20 ? token.substring(0, 20) + "..." : token));
|
log.info("使用token: " + (token.length() > 20 ? token.substring(0, 20) + "..." : token));
|
||||||
|
|
||||||
// 创建爬虫实例,传入token
|
// 创建爬虫实例,传入token
|
||||||
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token);
|
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token,pypath);
|
||||||
|
|
||||||
String todayDate = DateUtils.getTodayDate();// 4. 执行爬虫
|
String todayDate = DateUtils.getTodayDate();// 4. 执行爬虫
|
||||||
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/dresult?lottery=SGFT&date="+todayDate;
|
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/dresult?lottery=SGFT&date="+todayDate;
|
||||||
@@ -69,8 +80,7 @@ public class CrawlerSchedule {
|
|||||||
success = LotteryWebMagicCrawler.isLastParseSuccess();
|
success = LotteryWebMagicCrawler.isLastParseSuccess();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.out.println("本次尝试未解析到数据");
|
log.info("本次尝试未解析到数据");
|
||||||
|
|
||||||
// 重新获取token(下次重试用)
|
// 重新获取token(下次重试用)
|
||||||
token = tokenCacheService.getTokenSqlite();
|
token = tokenCacheService.getTokenSqlite();
|
||||||
retryCount++;
|
retryCount++;
|
||||||
@@ -84,17 +94,61 @@ public class CrawlerSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println("成功解析到数据");
|
log.info("成功解析到数据");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println("获取开奖结果失败,所有重试均未成功");
|
log.error("获取开奖结果失败,所有重试均未成功");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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() {
|
/*public void executeLotteryDraw() {
|
||||||
System.out.println("开始爬取开奖结果...");
|
System.out.println("开始爬取开奖结果...");
|
||||||
String token = tokenCacheService.getToken();
|
String token = tokenCacheService.getToken();
|
||||||
@@ -115,16 +169,22 @@ public class CrawlerSchedule {
|
|||||||
|
|
||||||
|
|
||||||
// 从7:00分30秒起每5分钟执行一次爬取今日已经结算
|
// 从7:00分30秒起每5分钟执行一次爬取今日已经结算
|
||||||
@Scheduled(cron = "30 0/5 * * * ?")
|
@Scheduled(cron = "35 0/5 * * * ?")
|
||||||
public void executeSettlement() {
|
public void executeSettlement() {
|
||||||
System.out.println("开始爬取今日已经结算...");
|
log.info("开始爬取今日已经结算...");
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
String token = tokenCacheService.getToken();
|
String token = tokenCacheService.getToken();
|
||||||
|
if (token == null || token.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
||||||
|
.orElse(null);
|
||||||
|
if(firstByOrderByCreateTimeDesc.getOnOff() == ONOFF){
|
||||||
|
return;
|
||||||
|
}
|
||||||
while (!success && retryCount < MAX_CRA) {
|
while (!success && retryCount < MAX_CRA) {
|
||||||
System.out.println("\n=== 第 " + (retryCount + 1) + " 次尝试获取今日注单 ===");
|
log.info("\n=== 第 " + (retryCount + 1) + " 次尝试获取今日注单 ===");
|
||||||
|
|
||||||
if (token == null || token.isEmpty()) {
|
if (token == null || token.isEmpty()) {
|
||||||
System.out.println("token为空,从数据库重新获取");
|
System.out.println("token为空,从数据库重新获取");
|
||||||
token = tokenCacheService.getTokenSqlite();
|
token = tokenCacheService.getTokenSqlite();
|
||||||
@@ -136,8 +196,7 @@ public class CrawlerSchedule {
|
|||||||
|
|
||||||
// 创建爬虫实例,传入token
|
// 创建爬虫实例,传入token
|
||||||
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
|
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
|
||||||
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
|
||||||
.orElse(null);
|
|
||||||
// 执行爬虫
|
// 执行爬虫
|
||||||
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
|
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
|
||||||
|
|
||||||
@@ -150,8 +209,7 @@ public class CrawlerSchedule {
|
|||||||
success = CompletedTodayCrawler.isLastParseSuccess();
|
success = CompletedTodayCrawler.isLastParseSuccess();
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.out.println("本次尝试未解析到数据");
|
log.info("本次尝试未解析到数据");
|
||||||
|
|
||||||
// 重新获取token(下次重试用)
|
// 重新获取token(下次重试用)
|
||||||
token = tokenCacheService.getTokenSqlite();
|
token = tokenCacheService.getTokenSqlite();
|
||||||
retryCount++;
|
retryCount++;
|
||||||
@@ -165,32 +223,249 @@ public class CrawlerSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println("成功解析到数据");
|
log.info("成功解析到数据");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
System.err.println("获取今日注单失败,所有重试均未成功");
|
log.error("获取今日注单失败,所有重试均未成功");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每5分钟执行一次获取余额信息
|
||||||
|
* 从7:00分40秒起每5分钟执行一次
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "40 0/5 * * * ?")
|
||||||
|
//@Scheduled(cron = "*/9 * * * * ?")
|
||||||
|
public void executeGetBalance() {
|
||||||
|
log.info("开始获取余额信息");
|
||||||
|
|
||||||
/*public void executeSettlement() {
|
// 获取最新的登录信息
|
||||||
|
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (firstByOrderByCreateTimeDesc == null) {
|
||||||
|
log.warn("未找到登录信息,跳过余额获取");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstByOrderByCreateTimeDesc.getOnOff() == ONOFF) {
|
||||||
|
log.info("开关关闭,跳过余额获取");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token
|
||||||
String token = tokenCacheService.getToken();
|
String token = tokenCacheService.getToken();
|
||||||
System.out.println("得到token = " + token);
|
if (token == null || token.isEmpty()) {
|
||||||
if (token != null && !token.isEmpty()) {
|
return;
|
||||||
// 2. 创建爬虫实例,传入token
|
}
|
||||||
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
|
|
||||||
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
int retryCount = 0;
|
||||||
.orElse(null);
|
boolean success = false;
|
||||||
// 4. 执行爬虫
|
|
||||||
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
|
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)
|
Spider.create(crawler)
|
||||||
.addUrl(url)
|
.addUrl(url)
|
||||||
.thread(1)
|
.thread(1)
|
||||||
.run();
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,55 +2,54 @@ package com.tem.bocai.schedules;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
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.LocalDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.ZoneId;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import com.tem.bocai.entity.BetRecord;
|
import com.tem.bocai.entity.BetRecord;
|
||||||
|
import com.tem.bocai.entity.LotteryResult;
|
||||||
import com.tem.bocai.repository.BetRecordRepository;
|
import com.tem.bocai.repository.BetRecordRepository;
|
||||||
|
import com.tem.bocai.repository.LotteryResultRepository;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行预测脚本
|
* 执行预测脚本
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class ExBetScriptSchedule {
|
public class ExBetScriptSchedule {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BetRecordRepository betRecordRepository;
|
private BetRecordRepository betRecordRepository;
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理文件路径,确保路径正确
|
|
||||||
* @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) {
|
@Autowired
|
||||||
ExBetScriptSchedule schedule = new ExBetScriptSchedule();
|
private LotteryResultRepository lotteryResultRepository;
|
||||||
schedule.executePythonScript();
|
|
||||||
}
|
@Value("${pypath}")
|
||||||
|
private String pypath;
|
||||||
|
|
||||||
|
|
||||||
// 从7:01分钟起每5分钟执行一次
|
// 从7:01分钟起每5分钟执行一次
|
||||||
@Scheduled(cron = "30 1/5 * * * ?")
|
@Scheduled(cron = "30 1/5 * * * ?")
|
||||||
public void executePythonScript() {
|
public void executePythonScript() {
|
||||||
LocalDateTime now = LocalDateTime.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
int hour = now.getHour();
|
int hour = now.getHour();
|
||||||
@@ -61,14 +60,20 @@ public class ExBetScriptSchedule {
|
|||||||
String currentTime = now.format(
|
String currentTime = now.format(
|
||||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
);
|
);
|
||||||
System.out.println(currentTime + " - 不在投注时间范围内,跳过执行");
|
log.info("{}", currentTime + " - 不在投注时间范围内,跳过执行");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("开始执行Python脚本...");
|
// 检查PyModel/current_data目录下最新的文件是否存在新数据
|
||||||
|
if (!checkNewDataExists()) {
|
||||||
|
log.info("未发现新数据,跳过执行Python脚本");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("开始执行Python脚本...");
|
||||||
|
|
||||||
// 获取当前时间,格式化为yyyy-MM-dd HH:mm:ss
|
// 获取当前时间减去5分钟,格式化为yyyy-MM-dd HH:mm:ss
|
||||||
String currentTime = java.time.LocalDateTime.now().format(
|
String currentTime = java.time.LocalDateTime.now().plusMinutes(5).format(
|
||||||
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -76,7 +81,8 @@ public class ExBetScriptSchedule {
|
|||||||
String[] params = {"--next_period_time", currentTime};
|
String[] params = {"--next_period_time", currentTime};
|
||||||
executePythonScriptWithParams("batch_predict_betting_v8.py", params);
|
executePythonScriptWithParams("batch_predict_betting_v8.py", params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行带参数的Python脚本
|
* 执行带参数的Python脚本
|
||||||
* @param scriptPath Python脚本路径
|
* @param scriptPath Python脚本路径
|
||||||
@@ -102,12 +108,12 @@ public class ExBetScriptSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println("执行命令: " + String.join(" ", command));
|
log.info("执行命令: {}", String.join(" ", command));
|
||||||
|
|
||||||
// 创建ProcessBuilder并设置工作目录为PyModel
|
// 创建ProcessBuilder并设置工作目录为PyModel
|
||||||
ProcessBuilder pb = new ProcessBuilder(command);
|
ProcessBuilder pb = new ProcessBuilder(command);
|
||||||
// 设置工作目录为PyModel
|
// 设置工作目录为PyModel
|
||||||
pb.directory(new java.io.File("PyModel"));
|
pb.directory(new java.io.File(pypath));
|
||||||
|
|
||||||
// 执行Python脚本
|
// 执行Python脚本
|
||||||
Process process = pb.start();
|
Process process = pb.start();
|
||||||
@@ -136,18 +142,17 @@ public class ExBetScriptSchedule {
|
|||||||
// 等待脚本执行完成
|
// 等待脚本执行完成
|
||||||
int exitCode = process.waitFor();
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
System.out.println("Python脚本执行完成,退出码: " + exitCode);
|
log.info("Python脚本执行完成,退出码: {}", exitCode);
|
||||||
System.out.println("脚本输出:\n" + output.toString());
|
log.info("脚本输出:\n{}", output.toString());
|
||||||
|
|
||||||
if (exitCode != 0) {
|
if (exitCode != 0) {
|
||||||
System.err.println("脚本执行错误:\n" + errorOutput.toString());
|
log.error("脚本执行错误:\n{}", errorOutput.toString());
|
||||||
} else {
|
} else {
|
||||||
parseScriptOutput(output.toString());
|
parseScriptOutput(output.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
System.err.println("执行Python脚本时发生错误:");
|
log.error("执行Python脚本时发生错误:", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,16 +162,16 @@ public class ExBetScriptSchedule {
|
|||||||
*/
|
*/
|
||||||
private void parseScriptOutput(String output) {
|
private void parseScriptOutput(String output) {
|
||||||
try {
|
try {
|
||||||
System.out.println("开始解析脚本输出...");
|
log.info("开始解析脚本输出...");
|
||||||
JSONObject jsonOutput = JSON.parseObject(output);
|
JSONObject jsonOutput = JSON.parseObject(output);
|
||||||
if (jsonOutput == null) {
|
if (jsonOutput == null) {
|
||||||
System.out.println("输出为空或无法解析为JSON");
|
log.info("输出为空或无法解析为JSON");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject result = jsonOutput.getJSONObject("result");
|
JSONObject result = jsonOutput.getJSONObject("result");
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
System.out.println("未找到result字段");
|
log.info("未找到result字段");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,20 +195,19 @@ public class ExBetScriptSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!validResults.isEmpty()) {
|
if (!validResults.isEmpty()) {
|
||||||
System.out.println("提取到的有效预测结果:");
|
log.info("提取到的有效预测结果:");
|
||||||
for (Map.Entry<Integer, Map<Integer, Object>> entry : validResults.entrySet()) {
|
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格式的数据
|
// 生成符合test.json格式的数据
|
||||||
generateOutputData(validResults);
|
generateOutputData(validResults);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("未找到有效的预测结果(所有值均为None)");
|
log.info("未找到有效的预测结果(所有值均为None)");
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("解析脚本输出时发生错误:");
|
log.error("解析脚本输出时发生错误:", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,8 +267,8 @@ public class ExBetScriptSchedule {
|
|||||||
betObject.put("contents", String.valueOf(num));
|
betObject.put("contents", String.valueOf(num));
|
||||||
|
|
||||||
// 设置固定字段
|
// 设置固定字段
|
||||||
betObject.put("amount", 1);
|
// betObject.put("amount", 1);
|
||||||
// betObject.put("amount", value);
|
betObject.put("amount", value);
|
||||||
betObject.put("odds", 9.599);
|
betObject.put("odds", 9.599);
|
||||||
|
|
||||||
// 设置标题
|
// 设置标题
|
||||||
@@ -283,8 +287,8 @@ public class ExBetScriptSchedule {
|
|||||||
outputData.put("fastBets", false);
|
outputData.put("fastBets", false);
|
||||||
outputData.put("ignore", false);
|
outputData.put("ignore", false);
|
||||||
|
|
||||||
System.out.println("生成的输出数据:");
|
log.info("生成的输出数据:");
|
||||||
System.out.println(outputData.toString(2));
|
log.info("{}", outputData.toString(2));
|
||||||
|
|
||||||
// 保存到数据库
|
// 保存到数据库
|
||||||
try {
|
try {
|
||||||
@@ -299,13 +303,77 @@ public class ExBetScriptSchedule {
|
|||||||
// 检查betNum是否已存在,避免重复保存
|
// 检查betNum是否已存在,避免重复保存
|
||||||
if (!betRecordRepository.existsByBetNum(drawNumber)) {
|
if (!betRecordRepository.existsByBetNum(drawNumber)) {
|
||||||
betRecordRepository.save(betRecord);
|
betRecordRepository.save(betRecord);
|
||||||
System.out.println("保存投注记录到数据库成功,期数: " + drawNumber);
|
log.info("保存投注记录到数据库成功,期数: {}", drawNumber);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("投注记录已存在,期数: " + drawNumber);
|
log.info("投注记录已存在,期数: {}", drawNumber);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("保存投注记录到数据库失败:");
|
log.error("保存投注记录到数据库失败:", e);
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查数据库中最新的开奖记录是否存在新数据
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.tem.bocai.util.*;
|
|||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import net.sourceforge.tess4j.Tesseract;
|
import net.sourceforge.tess4j.Tesseract;
|
||||||
import org.apache.http.client.methods.*;
|
import org.apache.http.client.methods.*;
|
||||||
@@ -32,6 +33,9 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
private LoginInfoRepository loginInfoRepository;
|
private LoginInfoRepository loginInfoRepository;
|
||||||
private static final int MAX_CRA = 3;
|
private static final int MAX_CRA = 3;
|
||||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
@Value("${pypath}")
|
||||||
|
private String pypath;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String loginAutomatic(LoginInfoParam loginInfoParam) {
|
public String loginAutomatic(LoginInfoParam loginInfoParam) {
|
||||||
@@ -45,7 +49,7 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
//保存用户信息
|
//保存用户信息
|
||||||
addLoginInfo(loginInfoParam);
|
addLoginInfo(loginInfoParam);
|
||||||
// 2. 创建爬虫实例,传入token
|
// 2. 创建爬虫实例,传入token
|
||||||
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token);
|
LotteryWebMagicCrawler crawler = new LotteryWebMagicCrawler(token,pypath);
|
||||||
|
|
||||||
// 3. 创建数据处理器
|
// 3. 创建数据处理器
|
||||||
LotteryDataPipeline pipeline = new LotteryDataPipeline();
|
LotteryDataPipeline pipeline = new LotteryDataPipeline();
|
||||||
@@ -184,6 +188,10 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
if (loginInfoResult.getAmount() != null) {
|
if (loginInfoResult.getAmount() != null) {
|
||||||
dbUser.setAmount(loginInfoResult.getAmount());
|
dbUser.setAmount(loginInfoResult.getAmount());
|
||||||
}
|
}
|
||||||
|
// 如果传入了 betAmount,写入到配置文件
|
||||||
|
if (loginInfoResult.getBetAmount() != null) {
|
||||||
|
writeBaseBetUnitToConfig(loginInfoResult.getBetAmount());
|
||||||
|
}
|
||||||
if (loginInfoResult.getOnOff() != null) {
|
if (loginInfoResult.getOnOff() != null) {
|
||||||
dbUser.setOnOff(loginInfoResult.getOnOff());
|
dbUser.setOnOff(loginInfoResult.getOnOff());
|
||||||
if (loginInfoResult.getOnOff().equals(1)) {
|
if (loginInfoResult.getOnOff().equals(1)) {
|
||||||
@@ -254,16 +262,115 @@ public class LoginServiceImpl implements LoginService {
|
|||||||
return SQLiteUtil.addOrUpdateLoginInfo(loginInfo);
|
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
|
@Override
|
||||||
public LoginInfoResult getUserSettings() {
|
public LoginInfoResult getUserSettings() {
|
||||||
try {
|
try {
|
||||||
// 获取最新的用户设置信息
|
// 获取最新的用户设置信息
|
||||||
Optional<LoginInfoResult> existingUser = loginInfoRepository.findFirstByOrderByCreateTimeDesc();
|
Optional<LoginInfoResult> existingUser = loginInfoRepository.findFirstByOrderByCreateTimeDesc();
|
||||||
|
LoginInfoResult loginInfoResult;
|
||||||
if (existingUser.isPresent()) {
|
if (existingUser.isPresent()) {
|
||||||
return existingUser.get();
|
loginInfoResult = existingUser.get();
|
||||||
|
} else {
|
||||||
|
// 如果没有找到,返回一个空的对象
|
||||||
|
loginInfoResult = new LoginInfoResult();
|
||||||
}
|
}
|
||||||
// 如果没有找到,返回一个空的对象
|
|
||||||
return 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) {
|
} catch (Exception e) {
|
||||||
System.err.println("获取用户设置信息失败: " + e.getMessage());
|
System.err.println("获取用户设置信息失败: " + e.getMessage());
|
||||||
return new LoginInfoResult();
|
return new LoginInfoResult();
|
||||||
|
|||||||
225
src/main/java/com/tem/bocai/util/BalanceWebMagicCrawler.java
Normal file
225
src/main/java/com/tem/bocai/util/BalanceWebMagicCrawler.java
Normal 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("获取余额信息失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.tem.bocai.entity.CompletedToday;
|
|||||||
import com.tem.bocai.repository.CompletedTodayRepository;
|
import com.tem.bocai.repository.CompletedTodayRepository;
|
||||||
import com.tem.bocai.repository.LoginInfoRepository;
|
import com.tem.bocai.repository.LoginInfoRepository;
|
||||||
import com.tem.bocai.service.CompletedTodayService;
|
import com.tem.bocai.service.CompletedTodayService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
@@ -26,7 +27,7 @@ import java.util.*;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class CompletedTodayCrawler implements PageProcessor {
|
public class CompletedTodayCrawler implements PageProcessor {
|
||||||
|
|
||||||
private final String token;
|
private final String token;
|
||||||
@@ -66,36 +67,33 @@ public class CompletedTodayCrawler implements PageProcessor {
|
|||||||
@Override
|
@Override
|
||||||
public void process(Page page) {
|
public void process(Page page) {
|
||||||
String url = page.getUrl().toString();
|
String url = page.getUrl().toString();
|
||||||
System.out.println("处理页面: " + url);
|
log.info("处理页面: " + url);
|
||||||
|
|
||||||
Html html = page.getHtml();
|
Html html = page.getHtml();
|
||||||
String content = html.toString();
|
String content = html.toString();
|
||||||
|
|
||||||
// 打印一些基本信息
|
// 打印一些基本信息
|
||||||
System.out.println("页面标题: " + html.xpath("//title/text()").get());
|
log.info("页面标题: " + html.xpath("//title/text()").get());
|
||||||
System.out.println("页面大小: " + content.length() + " 字符");
|
|
||||||
|
|
||||||
// 检查是否有"暂无数据"提示
|
// 检查是否有"暂无数据"提示
|
||||||
if (content.contains("暂无数据")) {
|
if (content.contains("暂无数据")) {
|
||||||
System.out.println("警告: 页面显示'暂无数据'");
|
log.info("警告: 页面显示'暂无数据'");
|
||||||
lastParseSuccess = true; // 标记失败
|
lastParseSuccess = true; // 标记失败
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (content.contains("可赢金额")) {
|
if (content.contains("可赢金额")) {
|
||||||
System.out.println("未结明细不用爬");
|
log.info("未结明细不用爬");
|
||||||
lastParseSuccess = false; // 标记失败
|
lastParseSuccess = false; // 标记失败
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 解析注单数据
|
// 解析注单数据
|
||||||
List<Map<String, Object>> betList = parseBetHtml(content);
|
List<Map<String, Object>> betList = parseBetHtml(content);
|
||||||
if (betList.isEmpty()) {
|
if (betList.isEmpty()) {
|
||||||
System.out.println("未解析到注单数据");
|
log.info("未解析到注单数据");
|
||||||
lastParseSuccess = false; // 标记失败
|
lastParseSuccess = false; // 标记失败
|
||||||
// 尝试从其他可能的位置解析
|
// 尝试从其他可能的位置解析
|
||||||
//extractDebugInfo(html);
|
//extractDebugInfo(html);
|
||||||
} else {
|
} else {
|
||||||
lastParseSuccess = true; // 标记成功
|
lastParseSuccess = true; // 标记成功
|
||||||
System.out.println("解析到 " + betList.size() + " 条注单数据");
|
log.info("解析到 " + betList.size() + " 条注单数据");
|
||||||
List<CompletedToday> completedTodayList = convertForDatabase(betList);
|
List<CompletedToday> completedTodayList = convertForDatabase(betList);
|
||||||
SQLiteUtil.saveCompletedToday(completedTodayList);
|
SQLiteUtil.saveCompletedToday(completedTodayList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,16 +48,48 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
|
|||||||
*
|
*
|
||||||
* @return 当天日期的字符串,例如:2026-01-27
|
* @return 当天日期的字符串,例如:2026-01-27
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
public static String getTodayDate() {
|
public static String getTodayDate() {
|
||||||
LocalDate today = LocalDate.now();
|
LocalDateTime now = LocalDateTime.now();
|
||||||
return today.format(DateTimeFormatter.ofPattern(YYYY_MM_DD));
|
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) {
|
public static void main(String[] args) {
|
||||||
List<String> test = new ArrayList<>();
|
System.out.println("====="+getTodayDate());
|
||||||
test.add("12312");
|
|
||||||
test.add("12312");
|
|
||||||
System.out.println("====="+test.get(6));
|
|
||||||
/* Date now = new Date(); // 当前时间
|
/* Date now = new Date(); // 当前时间
|
||||||
Date fifteenMinutesAgo = DateUtil.offsetMinute(now, -15); // 15分钟前的时间
|
Date fifteenMinutesAgo = DateUtil.offsetMinute(now, -15); // 15分钟前的时间
|
||||||
|
|
||||||
|
|||||||
577
src/main/java/com/tem/bocai/util/HttpClientExample.java
Normal file
577
src/main/java/com/tem/bocai/util/HttpClientExample.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
package com.tem.bocai.util;
|
|
||||||
|
|
||||||
import net.sourceforge.tess4j.Tesseract;
|
|
||||||
import net.sourceforge.tess4j.TesseractException;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.http.Header;
|
|
||||||
import org.apache.http.NameValuePair;
|
|
||||||
import org.apache.http.client.CookieStore;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.*;
|
|
||||||
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.core.io.Resource;
|
|
||||||
import org.springframework.core.io.ResourceLoader;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
|
|
||||||
import org.apache.http.client.config.RequestConfig;
|
|
||||||
import org.apache.http.cookie.Cookie;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class ImageOcrService {
|
|
||||||
|
|
||||||
private final Tesseract tesseract;
|
|
||||||
private final ResourceLoader resourceLoader;
|
|
||||||
private static CloseableHttpClient httpClient;
|
|
||||||
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
|
||||||
@Autowired
|
|
||||||
public ImageOcrService(Tesseract tesseract, ResourceLoader resourceLoader) {
|
|
||||||
|
|
||||||
this.tesseract = tesseract;
|
|
||||||
this.resourceLoader = resourceLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从本地文件路径读取图片并进行 OCR 处理
|
|
||||||
*
|
|
||||||
* @param imagePath 图片文件路径
|
|
||||||
* @return OCR 结果文本
|
|
||||||
*/
|
|
||||||
public String ocrLocalImage(String imagePath) throws IOException, TesseractException {
|
|
||||||
|
|
||||||
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(imagePath);
|
|
||||||
BufferedImage image = ImageIO.read(inputStream);
|
|
||||||
return tesseract.doOCR(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从远程 URL 获取图片并进行 OCR 处理
|
|
||||||
*
|
|
||||||
* @return OCR 结果文本
|
|
||||||
*/
|
|
||||||
public String ocrRemoteImage() throws IOException, TesseractException, InterruptedException {
|
|
||||||
int maxRetry = 5;
|
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= maxRetry; attempt++) {
|
|
||||||
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
|
||||||
|
|
||||||
// 每次尝试都创建新的HttpClient和CookieStore
|
|
||||||
CookieStore cookieStore = new BasicCookieStore();
|
|
||||||
CloseableHttpClient httpClient = HttpClients.custom()
|
|
||||||
.setDefaultCookieStore(cookieStore)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 获取验证码
|
|
||||||
System.out.println("获取验证码...");
|
|
||||||
HttpGet getCaptcha = new HttpGet(BASE_URL + "/code");
|
|
||||||
setCommonHeaders(getCaptcha);
|
|
||||||
// 添加Referer头
|
|
||||||
getCaptcha.setHeader("Referer", BASE_URL + "/login");
|
|
||||||
// 添加随机延迟,避免请求过快
|
|
||||||
Thread.sleep(1000 + (long)(Math.random() * 1000));
|
|
||||||
|
|
||||||
CloseableHttpResponse captchaResponse = httpClient.execute(getCaptcha);
|
|
||||||
byte[] imageData = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
int captchaStatus = captchaResponse.getStatusLine().getStatusCode();
|
|
||||||
System.out.println("验证码响应状态码: " + captchaStatus);
|
|
||||||
|
|
||||||
if (captchaStatus == 200) {
|
|
||||||
imageData = EntityUtils.toByteArray(captchaResponse.getEntity());
|
|
||||||
} else if (captchaStatus == 429) {
|
|
||||||
System.out.println("获取验证码被限速,等待后重试...");
|
|
||||||
Thread.sleep(3000); // 等待3秒
|
|
||||||
continue; // 继续下一次尝试
|
|
||||||
} else {
|
|
||||||
System.out.println("获取验证码失败: " + captchaStatus);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
captchaResponse.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. OCR识别验证码
|
|
||||||
String code = null;
|
|
||||||
if (imageData != null) {
|
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageData));
|
|
||||||
code = tesseract.doOCR(image);
|
|
||||||
|
|
||||||
// 清理验证码
|
|
||||||
code = code.replaceAll("\\s+", "").trim();
|
|
||||||
code = code.replaceAll("[^0-9]", ""); // 只保留数字
|
|
||||||
|
|
||||||
System.out.println("OCR原始结果: " + tesseract.doOCR(image));
|
|
||||||
System.out.println("清理后验证码: [" + code + "] 长度: " + code.length());
|
|
||||||
|
|
||||||
// 保存图片用于调试
|
|
||||||
File output = new File("captcha_attempt_" + attempt + ".png");
|
|
||||||
ImageIO.write(image, "png", output);
|
|
||||||
System.out.println("验证码图片已保存到: " + output.getAbsolutePath());
|
|
||||||
|
|
||||||
if (code.length() != 4) {
|
|
||||||
System.out.println("验证码长度不是4位,跳过本次尝试");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
System.out.println("验证码数据为空");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待一下再发送登录请求
|
|
||||||
Thread.sleep(1500 + (long)(Math.random() * 1000));
|
|
||||||
|
|
||||||
// 3. 登录(不自动重定向)
|
|
||||||
System.out.println("执行登录...");
|
|
||||||
HttpPost loginPost = new HttpPost(BASE_URL + "/login");
|
|
||||||
setCommonHeaders(loginPost);
|
|
||||||
// 重要:添加Referer和Origin头
|
|
||||||
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);
|
|
||||||
CloseableHttpResponse loginResponse = httpClient.execute(loginPost);
|
|
||||||
try {
|
|
||||||
int statusCode = loginResponse.getStatusLine().getStatusCode();
|
|
||||||
System.out.println("登录响应状态码: " + statusCode);
|
|
||||||
|
|
||||||
// 处理429错误
|
|
||||||
if (statusCode == 429) {
|
|
||||||
System.out.println("登录请求被限速 (429 Too Many Requests)");
|
|
||||||
|
|
||||||
// 检查Retry-After头
|
|
||||||
Header retryAfterHeader = loginResponse.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);
|
|
||||||
}
|
|
||||||
continue; // 继续下一次尝试
|
|
||||||
}
|
|
||||||
// 打印响应头
|
|
||||||
System.out.println("响应头:");
|
|
||||||
for (Header header : loginResponse.getAllHeaders()) {
|
|
||||||
System.out.println(" " + header.getName() + ": " + header.getValue());
|
|
||||||
}
|
|
||||||
// 检查是否是重定向
|
|
||||||
if (statusCode == 302) {
|
|
||||||
Header locationHeader = loginResponse.getFirstHeader("Location");
|
|
||||||
if (locationHeader != null) {
|
|
||||||
String location = locationHeader.getValue();
|
|
||||||
System.out.println("重定向到: " + location);
|
|
||||||
|
|
||||||
if (location.contains("e=3")) {
|
|
||||||
System.out.println("验证码错误 (e=3)");
|
|
||||||
continue; // 继续下一次尝试
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 读取响应体(如果有)
|
|
||||||
if (loginResponse.getEntity() != null) {
|
|
||||||
String responseBody = EntityUtils.toString(loginResponse.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(loginResponse.getEntity());
|
|
||||||
}
|
|
||||||
// 4. 检查cookies中是否有token
|
|
||||||
String token = null;
|
|
||||||
List<Cookie> cookies = cookieStore.getCookies();
|
|
||||||
System.out.println("所有cookies (" + cookies.size() + "个):");
|
|
||||||
for (Cookie cookie : cookies) {
|
|
||||||
System.out.println(" " + cookie.getName() + " = " + cookie.getValue());
|
|
||||||
|
|
||||||
// 查找token
|
|
||||||
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) {
|
|
||||||
// 如果是200状态码但没有token,可能是登录成功但token在其他地方
|
|
||||||
System.out.println("登录返回200但没有找到token,可能需要检查其他认证方式");
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
loginResponse.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
httpClient.close();
|
|
||||||
}
|
|
||||||
// 如果不是最后一次尝试,等待一段时间
|
|
||||||
if (attempt < maxRetry) {
|
|
||||||
System.out.println("\n等待2秒后进行下一次尝试...");
|
|
||||||
Thread.sleep(2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("\n[FAILED] " + maxRetry + " 次尝试都失败了");
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCommonHeaders(HttpRequestBase request) {
|
|
||||||
request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/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");
|
|
||||||
request.setHeader("Sec-Fetch-Dest", "document");
|
|
||||||
request.setHeader("Sec-Fetch-Mode", "navigate");
|
|
||||||
request.setHeader("Sec-Fetch-Site", "same-origin");
|
|
||||||
request.setHeader("Sec-Fetch-User", "?1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
332
src/main/java/com/tem/bocai/util/LotteryHistoryCrawler.java
Normal file
332
src/main/java/com/tem/bocai/util/LotteryHistoryCrawler.java
Normal 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. 提取winner(other1)
|
||||||
|
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. 提取sum1(dldhl_sum)、sum2(dldhh_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;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.tem.bocai.util;
|
package com.tem.bocai.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
@@ -25,6 +26,7 @@ import java.io.IOException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
//开奖的历史结果
|
//开奖的历史结果
|
||||||
|
@Slf4j
|
||||||
public class LotteryWebMagicCrawler implements PageProcessor {
|
public class LotteryWebMagicCrawler implements PageProcessor {
|
||||||
|
|
||||||
private final String token;
|
private final String token;
|
||||||
@@ -34,8 +36,11 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
// 添加一个字段标记是否成功解析数据
|
// 添加一个字段标记是否成功解析数据
|
||||||
private static volatile boolean lastParseSuccess = true;
|
private static volatile boolean lastParseSuccess = true;
|
||||||
|
|
||||||
public LotteryWebMagicCrawler(String token) {
|
private String path;
|
||||||
|
|
||||||
|
public LotteryWebMagicCrawler(String token,String path) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
this.path =path;
|
||||||
initSite();
|
initSite();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +67,11 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
Html html = page.getHtml();
|
Html html = page.getHtml();
|
||||||
|
|
||||||
// 打印页面基本信息
|
// 打印页面基本信息
|
||||||
System.out.println("页面URL: " + page.getUrl());
|
log.info("页面URL: " + page.getUrl());
|
||||||
System.out.println("页面标题: " + html.xpath("//title/text()").get());
|
log.info("页面标题: " + html.xpath("//title/text()").get());
|
||||||
|
|
||||||
// 示例:提取所有表格数据
|
// 示例:提取所有表格数据
|
||||||
Selectable tables = html.xpath("//table");
|
Selectable tables = html.xpath("//table");
|
||||||
System.out.println("找到 " + tables.nodes().size() + " 个表格");
|
log.info("找到 " + tables.nodes().size() + " 个表格");
|
||||||
if(tables.nodes().isEmpty()){
|
if(tables.nodes().isEmpty()){
|
||||||
lastParseSuccess = false;
|
lastParseSuccess = false;
|
||||||
}else {
|
}else {
|
||||||
@@ -120,7 +124,7 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
* @param htmlContent 爬取到的HTML文本内容
|
* @param htmlContent 爬取到的HTML文本内容
|
||||||
* @return 解析后的结构化数据列表
|
* @return 解析后的结构化数据列表
|
||||||
*/
|
*/
|
||||||
public static List<Map<String, Object>> parseLotteryHtml(String htmlContent) {
|
public List<Map<String, Object>> parseLotteryHtml(String htmlContent) {
|
||||||
List<Map<String, Object>> resultList = new ArrayList<>();
|
List<Map<String, Object>> resultList = new ArrayList<>();
|
||||||
|
|
||||||
// 初始化Jsoup解析器
|
// 初始化Jsoup解析器
|
||||||
@@ -235,11 +239,11 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
SQLiteUtil.writeToSQLite(resultList);
|
SQLiteUtil.writeToSQLite(resultList);
|
||||||
// 将数据写入JSON文件(保留原有功能)
|
// 将数据写入JSON文件(保留原有功能)
|
||||||
writeToJsonFile(resultList);
|
writeToJsonFile(resultList);
|
||||||
System.out.println("打印结果===" + resultList);
|
log.info("历史爬虫打印结果===" + resultList);
|
||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeToJsonFile(List<Map<String, Object>> resultList) {
|
public void writeToJsonFile(List<Map<String, Object>> resultList) {
|
||||||
try {
|
try {
|
||||||
// 创建 ObjectMapper 实例
|
// 创建 ObjectMapper 实例
|
||||||
ObjectMapper objectMapper = new ObjectMapper();
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
@@ -248,11 +252,10 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||||
|
|
||||||
// 定义输出目录
|
// 定义输出目录
|
||||||
String directoryPath = "PyModel/current_data"; // 项目根目录下的 output/json 文件夹
|
String directoryPath = path+"/current_data"; // 项目根目录下的 output/json 文件夹
|
||||||
|
|
||||||
// 使用年月日作为文件名(格式:result_yyyyMMdd.json)
|
// 使用年月日作为文件名(格式:result_yyyyMMdd.json)
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
String dateStr = DateUtils.getTodayDate();
|
||||||
String dateStr = dateFormat.format(new Date());
|
|
||||||
String fileName = "result_" + dateStr + ".json";
|
String fileName = "result_" + dateStr + ".json";
|
||||||
String filePath = directoryPath + "/" + fileName;
|
String filePath = directoryPath + "/" + fileName;
|
||||||
|
|
||||||
@@ -265,31 +268,60 @@ public class LotteryWebMagicCrawler implements PageProcessor {
|
|||||||
// 创建文件对象
|
// 创建文件对象
|
||||||
File outputFile = new File(filePath);
|
File outputFile = new File(filePath);
|
||||||
|
|
||||||
// 如果文件已存在,删除旧文件(实现替换功能)
|
// 如果文件已存在,读取现有数据并对比
|
||||||
|
List<Map<String, Object>> existingData = new ArrayList<>();
|
||||||
|
Set<String> existingIds = new HashSet<>();
|
||||||
if (outputFile.exists()) {
|
if (outputFile.exists()) {
|
||||||
boolean deleted = outputFile.delete();
|
try {
|
||||||
if (!deleted) {
|
existingData = objectMapper.readValue(outputFile,
|
||||||
throw new IOException("无法删除已存在的文件: " + filePath);
|
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 文件
|
// 筛选出新增的数据(id不在existingIds中的记录)
|
||||||
objectMapper.writeValue(outputFile, resultList);
|
List<Map<String, Object>> newData = new ArrayList<>();
|
||||||
System.out.println("数据已成功写入文件: " + outputFile.getAbsolutePath());
|
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) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
log.error("写入 JSON 文件失败: " + e.getMessage(), e);
|
||||||
throw new RuntimeException("写入 JSON 文件失败: " + e.getMessage(), e);
|
throw new RuntimeException("写入 JSON 文件失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
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(""))
|
Spider.create(new LotteryWebMagicCrawler("",""))
|
||||||
.addUrl(url) // 添加起始URL
|
.addUrl(url) // 添加起始URL
|
||||||
.thread(1) // 线程数
|
.thread(1) // 线程数
|
||||||
.run(); // 开始爬取
|
.run(); // 开始爬取
|
||||||
|
|||||||
@@ -42,12 +42,25 @@ public class SQLiteUtil {
|
|||||||
// 先初始化数据库
|
// 先初始化数据库
|
||||||
//initDatabase();
|
//initDatabase();
|
||||||
|
|
||||||
String insertSQL = """
|
/*String insertSQL = """
|
||||||
INSERT OR REPLACE INTO lottery_results
|
INSERT OR REPLACE INTO lottery_results
|
||||||
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
|
(issue,time, result, winner, gd1, gd2, sum1, sum2, glh_result)
|
||||||
VALUES (?,?, ?, ?, ?, ?, ?, ?, ?)
|
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;
|
Connection conn = null;
|
||||||
PreparedStatement pstmt = null;
|
PreparedStatement pstmt = null;
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import com.tem.bocai.param.LoginInfoParam;
|
|||||||
import com.tem.bocai.repository.LoginInfoRepository;
|
import com.tem.bocai.repository.LoginInfoRepository;
|
||||||
import com.tem.bocai.service.LoginService;
|
import com.tem.bocai.service.LoginService;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sourceforge.tess4j.Tesseract;
|
import net.sourceforge.tess4j.Tesseract;
|
||||||
import net.sourceforge.tess4j.TesseractException;
|
import net.sourceforge.tess4j.TesseractException;
|
||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpHost;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.CookieStore;
|
import org.apache.http.client.CookieStore;
|
||||||
import org.apache.http.client.config.RequestConfig;
|
import org.apache.http.client.config.RequestConfig;
|
||||||
@@ -35,7 +37,9 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
public class TokenCacheService {
|
public class TokenCacheService {
|
||||||
|
|
||||||
@@ -44,12 +48,19 @@ public class TokenCacheService {
|
|||||||
private static final String TOKEN_INFO_KEY = "token_info";
|
private static final String TOKEN_INFO_KEY = "token_info";
|
||||||
private static final int MAX_RETRY = 10;
|
private static final int MAX_RETRY = 10;
|
||||||
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
private static final String BASE_URL = "https://4701268539-esh.qdk63ayw8g.com";
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private Tesseract tesseract;
|
private Tesseract tesseract;
|
||||||
@Autowired
|
@Autowired
|
||||||
private CacheManager tokenCacheManager;
|
private CacheManager tokenCacheManager;
|
||||||
@Autowired
|
@Autowired
|
||||||
private LoginInfoRepository loginInfoRepository;
|
private LoginInfoRepository loginInfoRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否允许登录的标志 - 密码错误一次就禁止
|
||||||
|
*/
|
||||||
|
private volatile boolean loginEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存token到缓存
|
* 保存token到缓存
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +68,7 @@ public class TokenCacheService {
|
|||||||
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
cache.put(TOKEN_KEY, token);
|
cache.put(TOKEN_KEY, token);
|
||||||
System.out.println("Token已保存到内存缓存");
|
log.info("Token已保存到内存缓存");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,17 +76,23 @@ public class TokenCacheService {
|
|||||||
* 从缓存获取token
|
* 从缓存获取token
|
||||||
*/
|
*/
|
||||||
public String getToken() {
|
public String getToken() {
|
||||||
|
// 检查是否允许登录
|
||||||
|
if (!loginEnabled) {
|
||||||
|
log.info("账号密码错误,已禁止登录");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
String token = cache.get(TOKEN_KEY, String.class);
|
String token = cache.get(TOKEN_KEY, String.class);
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
System.out.println("从缓存获取到token");
|
log.info("从缓存获取到token");
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 缓存不存在或token为空时,从SQLite获取
|
// 缓存不存在或token为空时,从SQLite获取
|
||||||
String token = getTokenSqlite();
|
String token = getTokenSqlite();
|
||||||
System.out.println("重新登入获取的token==" + token);
|
log.info("重新登入获取的token: {}", token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +100,12 @@ public class TokenCacheService {
|
|||||||
* 从缓存获取token,如果不存在则通过回调函数获取
|
* 从缓存获取token,如果不存在则通过回调函数获取
|
||||||
*/
|
*/
|
||||||
public String getToken(Callable<String> tokenLoader) {
|
public String getToken(Callable<String> tokenLoader) {
|
||||||
|
// 检查是否允许登录
|
||||||
|
if (!loginEnabled) {
|
||||||
|
log.info("账号密码错误,已禁止登录");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
return cache.get(TOKEN_KEY, tokenLoader);
|
return cache.get(TOKEN_KEY, tokenLoader);
|
||||||
@@ -97,7 +120,7 @@ public class TokenCacheService {
|
|||||||
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
Cache cache = tokenCacheManager.getCache(TOKEN_CACHE_NAME);
|
||||||
if (cache != null) {
|
if (cache != null) {
|
||||||
cache.evict(TOKEN_KEY);
|
cache.evict(TOKEN_KEY);
|
||||||
System.out.println("Token已从内存缓存清除");
|
log.info("Token已从内存缓存清除");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +128,10 @@ public class TokenCacheService {
|
|||||||
* 检查是否有有效token
|
* 检查是否有有效token
|
||||||
*/
|
*/
|
||||||
public boolean hasValidToken() {
|
public boolean hasValidToken() {
|
||||||
|
// 检查是否允许登录
|
||||||
|
if (!loginEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return getToken() != null;
|
return getToken() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,20 +156,78 @@ public class TokenCacheService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录账号密码错误 - 密码错误一次就禁止
|
||||||
|
*/
|
||||||
|
private void recordAccountPasswordError() {
|
||||||
|
loginEnabled = false;
|
||||||
|
log.info("账号密码错误,禁止登录");
|
||||||
|
// 清除缓存中的token,避免使用旧的token
|
||||||
|
clearToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置账号密码错误计数
|
||||||
|
*/
|
||||||
|
private void resetAccountPasswordError() {
|
||||||
|
loginEnabled = true;
|
||||||
|
log.info("登录状态已重置,可以重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部重置登录状态的方法
|
||||||
|
*/
|
||||||
|
public void resetLoginStatus() {
|
||||||
|
resetAccountPasswordError();
|
||||||
|
log.info("登录状态已重置,可以重新登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否允许登录
|
||||||
|
*/
|
||||||
|
public boolean isLoginEnabled() {
|
||||||
|
return loginEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录状态描述
|
||||||
|
*/
|
||||||
|
public String getLoginStatus() {
|
||||||
|
return loginEnabled ? "登录已启用" : "账号密码错误,登录已禁用";
|
||||||
|
}
|
||||||
|
|
||||||
public String getTokenSqlite() {
|
public String getTokenSqlite() {
|
||||||
|
// 检查是否允许登录
|
||||||
|
if (!loginEnabled) {
|
||||||
|
log.info("账号密码错误,已禁止登录");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
String token = "";
|
String token = "";
|
||||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
for (int attempt = 1; attempt <= MAX_RETRY; attempt++) {
|
||||||
System.out.println("\n=== 第 " + attempt + " 次尝试 ===");
|
// 每次循环开始前检查登录状态
|
||||||
|
if (!loginEnabled) {
|
||||||
|
log.info("账号密码错误,已禁止登录,停止尝试");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("\n=== 第 {} 次尝试 ===", attempt);
|
||||||
try {
|
try {
|
||||||
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
|
if (firstByOrderByCreateTimeDesc == null) {
|
||||||
|
log.error("未找到登录信息");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
LoginInfoParam loginInfoParam = new LoginInfoParam();
|
LoginInfoParam loginInfoParam = new LoginInfoParam();
|
||||||
loginInfoParam.setUsername(firstByOrderByCreateTimeDesc.getUsername());
|
loginInfoParam.setUsername(firstByOrderByCreateTimeDesc.getUsername());
|
||||||
loginInfoParam.setPassword(firstByOrderByCreateTimeDesc.getPassword());
|
loginInfoParam.setPassword(firstByOrderByCreateTimeDesc.getPassword());
|
||||||
loginInfoParam.setLoginUrl(firstByOrderByCreateTimeDesc.getLoginUrl());
|
loginInfoParam.setLoginUrl(firstByOrderByCreateTimeDesc.getLoginUrl());
|
||||||
token = attemptLogin(loginInfoParam);
|
token = attemptLogin(loginInfoParam);
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
|
// 登录成功,重置错误计数
|
||||||
|
resetAccountPasswordError();
|
||||||
//保存缓存token
|
//保存缓存token
|
||||||
saveToken(token);
|
saveToken(token);
|
||||||
return token;
|
return token;
|
||||||
@@ -151,8 +236,12 @@ public class TokenCacheService {
|
|||||||
waitForRetry(attempt);
|
waitForRetry(attempt);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("第 " + attempt + " 次尝试失败: " + e.getMessage());
|
log.error("第 {} 次尝试失败: {}", attempt, e.getMessage(), e);
|
||||||
e.printStackTrace();
|
// 检查是否因为密码错误导致禁止登录
|
||||||
|
if (!loginEnabled) {
|
||||||
|
log.info("检测到账号密码错误,停止尝试");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
@@ -162,7 +251,7 @@ public class TokenCacheService {
|
|||||||
* 等待重试
|
* 等待重试
|
||||||
*/
|
*/
|
||||||
public void waitForRetry(int attempt) throws InterruptedException {
|
public void waitForRetry(int attempt) throws InterruptedException {
|
||||||
System.out.println("\n等待2秒后进行下一次尝试...");
|
log.info("\n等待2秒后进行下一次尝试...");
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +275,7 @@ public class TokenCacheService {
|
|||||||
return performLogin(httpClient, cookieStore, code,loginInfoParam);
|
return performLogin(httpClient, cookieStore, code,loginInfoParam);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("登录尝试失败" + e);
|
log.error("登录尝试失败", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -207,7 +296,7 @@ public class TokenCacheService {
|
|||||||
private byte[] fetchCaptcha(CloseableHttpClient httpClient)
|
private byte[] fetchCaptcha(CloseableHttpClient httpClient)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
|
|
||||||
System.out.println("获取验证码...");
|
log.info("获取验证码...");
|
||||||
|
|
||||||
// 添加随机延迟
|
// 添加随机延迟
|
||||||
Thread.sleep(1000 + (long) (Math.random() * 1000));
|
Thread.sleep(1000 + (long) (Math.random() * 1000));
|
||||||
@@ -218,15 +307,15 @@ public class TokenCacheService {
|
|||||||
|
|
||||||
try (CloseableHttpResponse captchaResponse = httpClient.execute(getCaptcha)) {
|
try (CloseableHttpResponse captchaResponse = httpClient.execute(getCaptcha)) {
|
||||||
int captchaStatus = captchaResponse.getStatusLine().getStatusCode();
|
int captchaStatus = captchaResponse.getStatusLine().getStatusCode();
|
||||||
System.out.println("验证码响应状态码: " + captchaStatus);
|
log.info("验证码响应状态码: {}", captchaStatus);
|
||||||
|
|
||||||
if (captchaStatus == 200) {
|
if (captchaStatus == 200) {
|
||||||
return EntityUtils.toByteArray(captchaResponse.getEntity());
|
return EntityUtils.toByteArray(captchaResponse.getEntity());
|
||||||
} else if (captchaStatus == 429) {
|
} else if (captchaStatus == 429) {
|
||||||
System.out.println("获取验证码被限速,等待后重试...");
|
log.info("获取验证码被限速,等待后重试...");
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("获取验证码失败: " + captchaStatus);
|
log.info("获取验证码失败: {}", captchaStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,8 +334,8 @@ public class TokenCacheService {
|
|||||||
// 清理验证码
|
// 清理验证码
|
||||||
String code = rawOcr.replaceAll("\\s+", "").trim();
|
String code = rawOcr.replaceAll("\\s+", "").trim();
|
||||||
code = code.replaceAll("[^0-9]", ""); // 只保留数字
|
code = code.replaceAll("[^0-9]", ""); // 只保留数字
|
||||||
System.out.println("OCR原始结果: " + rawOcr);
|
log.info("OCR原始结果: {}", rawOcr);
|
||||||
System.out.println("清理后验证码: [" + code + "] 长度: " + code.length());
|
log.info("清理后验证码: [{}] 长度: {}", code, code.length());
|
||||||
// 保存图片用于调试
|
// 保存图片用于调试
|
||||||
//saveCaptchaImage(image);
|
//saveCaptchaImage(image);
|
||||||
|
|
||||||
@@ -270,7 +359,7 @@ public class TokenCacheService {
|
|||||||
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||||
File output = new File("captcha_" + timestamp + ".png");
|
File output = new File("captcha_" + timestamp + ".png");
|
||||||
ImageIO.write(image, "png", output);
|
ImageIO.write(image, "png", output);
|
||||||
System.out.println("验证码图片已保存到: " + output.getAbsolutePath());
|
log.info("验证码图片已保存到: {}", output.getAbsolutePath());
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -280,12 +369,20 @@ public class TokenCacheService {
|
|||||||
CookieStore cookieStore,
|
CookieStore cookieStore,
|
||||||
String code,LoginInfoParam loginInfoParam) throws IOException, InterruptedException {
|
String code,LoginInfoParam loginInfoParam) throws IOException, InterruptedException {
|
||||||
|
|
||||||
System.out.println("执行登录...");
|
log.info("执行登录...");
|
||||||
// 等待一下再发送登录请求
|
// 等待一下再发送登录请求
|
||||||
Thread.sleep(1500 + (long) (Math.random() * 1000));
|
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);
|
HttpPost loginPost = createLoginRequest(code,loginInfoParam);
|
||||||
|
// // 新增代码:将代理类放入配置中
|
||||||
|
// loginPost.setConfig(proxyConfig);
|
||||||
try (CloseableHttpResponse loginResponse = httpClient.execute(loginPost)) {
|
try (CloseableHttpResponse loginResponse = httpClient.execute(loginPost)) {
|
||||||
return processLoginResponse(loginResponse, cookieStore);
|
return processLoginResponse(loginResponse, cookieStore);
|
||||||
}
|
}
|
||||||
@@ -295,6 +392,9 @@ public class TokenCacheService {
|
|||||||
* 创建登录请求
|
* 创建登录请求
|
||||||
*/
|
*/
|
||||||
private HttpPost createLoginRequest(String code,LoginInfoParam loginInfoParam) throws UnsupportedEncodingException {
|
private HttpPost createLoginRequest(String code,LoginInfoParam loginInfoParam) throws UnsupportedEncodingException {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
HttpPost loginPost = new HttpPost(loginInfoParam.loginUrl + "/login");
|
HttpPost loginPost = new HttpPost(loginInfoParam.loginUrl + "/login");
|
||||||
|
|
||||||
// 设置请求头
|
// 设置请求头
|
||||||
@@ -302,7 +402,6 @@ public class TokenCacheService {
|
|||||||
loginPost.setHeader("Referer", loginInfoParam.loginUrl + "/login");
|
loginPost.setHeader("Referer", loginInfoParam.loginUrl + "/login");
|
||||||
loginPost.setHeader("Origin", loginInfoParam.loginUrl);
|
loginPost.setHeader("Origin", loginInfoParam.loginUrl);
|
||||||
loginPost.setHeader("Accept", "application/json, text/plain, */*");
|
loginPost.setHeader("Accept", "application/json, text/plain, */*");
|
||||||
|
|
||||||
// 构建登录参数
|
// 构建登录参数
|
||||||
List<NameValuePair> params = new ArrayList<>();
|
List<NameValuePair> params = new ArrayList<>();
|
||||||
params.add(new BasicNameValuePair("type", "1"));
|
params.add(new BasicNameValuePair("type", "1"));
|
||||||
@@ -328,7 +427,7 @@ public class TokenCacheService {
|
|||||||
CookieStore cookieStore) {
|
CookieStore cookieStore) {
|
||||||
try {
|
try {
|
||||||
int statusCode = loginResponse.getStatusLine().getStatusCode();
|
int statusCode = loginResponse.getStatusLine().getStatusCode();
|
||||||
System.out.println("登录响应状态码: " + statusCode);
|
log.info("登录响应状态码: {}", statusCode);
|
||||||
|
|
||||||
// 处理限速
|
// 处理限速
|
||||||
if (statusCode == 429) {
|
if (statusCode == 429) {
|
||||||
@@ -355,7 +454,7 @@ public class TokenCacheService {
|
|||||||
// 从cookies中提取token
|
// 从cookies中提取token
|
||||||
return extractTokenFromCookies(cookieStore, statusCode);
|
return extractTokenFromCookies(cookieStore, statusCode);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("请求异常" + e);
|
log.error("请求异常", e);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -364,9 +463,9 @@ public class TokenCacheService {
|
|||||||
* 打印响应头
|
* 打印响应头
|
||||||
*/
|
*/
|
||||||
private void printResponseHeaders(CloseableHttpResponse response) {
|
private void printResponseHeaders(CloseableHttpResponse response) {
|
||||||
System.out.println("响应头:");
|
log.info("响应头:");
|
||||||
for (Header header : response.getAllHeaders()) {
|
for (Header header : response.getAllHeaders()) {
|
||||||
System.out.println(" " + header.getName() + ": " + header.getValue());
|
log.info(" {}: {}", header.getName(), header.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,10 +476,17 @@ public class TokenCacheService {
|
|||||||
Header locationHeader = response.getFirstHeader("Location");
|
Header locationHeader = response.getFirstHeader("Location");
|
||||||
if (locationHeader != null) {
|
if (locationHeader != null) {
|
||||||
String location = locationHeader.getValue();
|
String location = locationHeader.getValue();
|
||||||
System.out.println("重定向到: " + location);
|
log.info("重定向到: {}", location);
|
||||||
|
|
||||||
|
if (location.contains("e=4")) {
|
||||||
|
log.info("账号密码错误 (e=4)");
|
||||||
|
// 记录账号密码错误 - 密码错误一次就禁止
|
||||||
|
recordAccountPasswordError();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (location.contains("e=3")) {
|
if (location.contains("e=3")) {
|
||||||
System.out.println("验证码错误 (e=3)");
|
log.info("验证码错误 (e=3)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -392,11 +498,11 @@ public class TokenCacheService {
|
|||||||
*/
|
*/
|
||||||
private String extractTokenFromCookies(CookieStore cookieStore, int statusCode) {
|
private String extractTokenFromCookies(CookieStore cookieStore, int statusCode) {
|
||||||
List<Cookie> cookies = cookieStore.getCookies();
|
List<Cookie> cookies = cookieStore.getCookies();
|
||||||
System.out.println("所有cookies (" + cookies.size() + "个):");
|
log.info("所有cookies ({}个):", cookies.size());
|
||||||
|
|
||||||
String token = null;
|
String token = null;
|
||||||
for (Cookie cookie : cookies) {
|
for (Cookie cookie : cookies) {
|
||||||
System.out.println(" " + cookie.getName() + " = " + cookie.getValue());
|
log.info(" {} = {}", cookie.getName(), cookie.getValue());
|
||||||
|
|
||||||
if ("token".equals(cookie.getName()) ||
|
if ("token".equals(cookie.getName()) ||
|
||||||
cookie.getName().toLowerCase().contains("token")) {
|
cookie.getName().toLowerCase().contains("token")) {
|
||||||
@@ -405,11 +511,11 @@ public class TokenCacheService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (token != null && !token.isEmpty()) {
|
if (token != null && !token.isEmpty()) {
|
||||||
System.out.println("\n[SUCCESS] Login OK!");
|
log.info("\n[SUCCESS] Login OK!");
|
||||||
System.out.println("Token: " + token);
|
log.info("Token: {}", token);
|
||||||
return token;
|
return token;
|
||||||
} else if (statusCode == 200) {
|
} else if (statusCode == 200) {
|
||||||
System.out.println("登录返回200但没有找到token,可能需要检查其他认证方式");
|
log.info("登录返回200但没有找到token,可能需要检查其他认证方式");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -423,7 +529,7 @@ public class TokenCacheService {
|
|||||||
if (response.getEntity() != null) {
|
if (response.getEntity() != null) {
|
||||||
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
|
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
|
||||||
if (responseBody != null && !responseBody.isEmpty()) {
|
if (responseBody != null && !responseBody.isEmpty()) {
|
||||||
System.out.println("响应体: " + responseBody);
|
log.info("响应体: {}", responseBody);
|
||||||
|
|
||||||
// 检查响应体中是否有token(JSON格式)
|
// 检查响应体中是否有token(JSON格式)
|
||||||
if (responseBody.contains("\"token\"")) {
|
if (responseBody.contains("\"token\"")) {
|
||||||
@@ -434,8 +540,8 @@ public class TokenCacheService {
|
|||||||
int end = responseBody.indexOf("\"", start);
|
int end = responseBody.indexOf("\"", start);
|
||||||
if (end != -1) {
|
if (end != -1) {
|
||||||
String token = responseBody.substring(start, end);
|
String token = responseBody.substring(start, end);
|
||||||
System.out.println("\n[SUCCESS] 从响应体找到Token!");
|
log.info("\n[SUCCESS] 从响应体找到Token!");
|
||||||
System.out.println("Token: " + token);
|
log.info("Token: {}", token);
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,21 +557,21 @@ public class TokenCacheService {
|
|||||||
* 处理速率限制
|
* 处理速率限制
|
||||||
*/
|
*/
|
||||||
private void handleRateLimit(CloseableHttpResponse response) throws InterruptedException {
|
private void handleRateLimit(CloseableHttpResponse response) throws InterruptedException {
|
||||||
System.out.println("登录请求被限速 (429 Too Many Requests)");
|
log.info("登录请求被限速 (429 Too Many Requests)");
|
||||||
|
|
||||||
Header retryAfterHeader = response.getFirstHeader("Retry-After");
|
Header retryAfterHeader = response.getFirstHeader("Retry-After");
|
||||||
if (retryAfterHeader != null) {
|
if (retryAfterHeader != null) {
|
||||||
try {
|
try {
|
||||||
int retryAfterSeconds = Integer.parseInt(retryAfterHeader.getValue());
|
int retryAfterSeconds = Integer.parseInt(retryAfterHeader.getValue());
|
||||||
System.out.println("服务器要求等待 " + retryAfterSeconds + " 秒");
|
log.info("服务器要求等待 {} 秒", retryAfterSeconds);
|
||||||
Thread.sleep(retryAfterSeconds * 1000L);
|
Thread.sleep(retryAfterSeconds * 1000L);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
System.out.println("等待5秒后重试");
|
log.info("等待5秒后重试");
|
||||||
Thread.sleep(5000);
|
Thread.sleep(5000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
System.out.println("等待3秒后重试");
|
log.info("等待3秒后重试");
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,4 +8,6 @@ spring.datasource.password=
|
|||||||
spring.jpa.hibernate.ddl-auto=update
|
spring.jpa.hibernate.ddl-auto=update
|
||||||
spring.jpa.show-sql=true
|
spring.jpa.show-sql=true
|
||||||
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
|
||||||
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
||||||
|
|
||||||
|
pypath:D:/py/PyModel
|
||||||
Reference in New Issue
Block a user