Commit ce0327b4 authored by 陈冲's avatar 陈冲

fix: 更新

parent 5132a46c
......@@ -21,6 +21,38 @@ pub struct Game {
}
impl Game {
pub async fn exists(wechat_id: &str, item_num: i32, batch_no: &str) -> bool {
let Some(_) = &config.db else {
return false;
};
let res = crate::db::db_query(
r#"SELECT COUNT(1) AS record_count
FROM tb_game
WHERE wechat_id = ? AND item_num = ? AND batch_no = ?"#,
vec![json!(wechat_id), json!(item_num), json!(batch_no)],
)
.await;
match res {
Ok(rows) => rows
.first()
.and_then(|row| row.get("record_count"))
.and_then(|value| value.as_i64().or_else(|| value.as_u64().map(|v| v as i64)))
.unwrap_or(0)
> 0,
Err(_) => false,
}
}
pub async fn insert_once(&self) -> bool {
if Self::exists(&self.wechat_id, self.item_num, &self.batch_no).await {
return true;
}
self.insert().await
}
pub async fn insert(&self) -> bool {
let Some(_) = &config.db else {
println!("没有配置数据库, return true;");
......
......@@ -26,6 +26,8 @@ lazy_static::lazy_static! {
static ref CLIENT_ADMIN_SESSION: std::sync::Mutex<AdminSession> = std::sync::Mutex::new(AdminSession::default());
static ref CLIENT_PLAYER_MAP: DashMap<String, (i32,SocketRef)> = DashMap::new(); // 1: 正在游戏中
static ref CLIENT_TOP_RANKED_MAP: DashMap<String, bool> = DashMap::new();
static ref CLIENT_AUTH_TOKEN_MAP: DashMap<String, String> = DashMap::new();
static ref SAVED_GAME_RECORD_MAP: DashMap<String, bool> = DashMap::new();
static ref ROOM_STATE: std::sync::Mutex<RoomState> = std::sync::Mutex::new(RoomState::default());
}
......@@ -555,6 +557,137 @@ fn push_room_score_ranking() -> bool {
true
}
fn game_code(item_num: i32) -> &'static str {
match item_num {
1 => "JGQF",
2 => "JBJF",
3 => "MSYF",
4 => "CMYF",
5 => "QCNF",
6 => "FYDT",
_ => "",
}
}
async fn post_screen_game_score(game: &Game) {
let auth_token = CLIENT_AUTH_TOKEN_MAP
.get(&game.wechat_id)
.map(|token| token.value().clone())
.unwrap_or_default();
if auth_token.is_empty() {
return;
}
let code = game_code(game.item_num);
if code.is_empty() {
return;
}
let resp = reqwest::Client::new()
.post("https://api.guocai365.org.cn/api/offline_clearance/user/screen_game_score")
.header("Authorization", auth_token)
.json(&json!({
"game_code": code,
"score": game.score,
"batch_no": game.batch_no,
"rank_no": game.game_rank,
}))
.send()
.await;
let log_msg = match resp {
Ok(resp) if resp.status().is_success() => return,
Ok(resp) => {
let status = resp.status();
let body = resp.text().await.unwrap_or_default();
format!("提交第三方大屏游戏分数失败: {status}, {body}")
}
Err(err) => err.to_string(),
};
{
let log = Log {
id: 0,
msg: log_msg,
params: json!({
"wechat_id": game.wechat_id,
"item_num": game.item_num,
"game_rank": game.game_rank,
"score": game.score,
"batch_no": game.batch_no,
}),
create_date: None,
};
log.insert().await;
}
}
async fn insert_game_once(game: &Game) -> bool {
let save_key = format!("{}:{}:{}", game.batch_no, game.item_num, game.wechat_id);
match SAVED_GAME_RECORD_MAP.entry(save_key.clone()) {
Entry::Occupied(_) => return true,
Entry::Vacant(entry) => {
entry.insert(true);
}
}
let inserted = game.insert_once().await;
if !inserted {
SAVED_GAME_RECORD_MAP.remove(&save_key);
} else {
post_screen_game_score(game).await;
}
inserted
}
async fn save_room_final_scores() {
let games = if let Ok(state) = ROOM_STATE.lock() {
if state.batch_no.is_empty() || state.current_item_num <= 0 {
return;
}
let batch_no = state.batch_no.clone();
let item_num = state.current_item_num;
let full_ranking = state.full_score_ranking();
let eligible_ranking = state.eligible_score_ranking();
let eligible_rank_map = eligible_ranking
.iter()
.enumerate()
.map(|(index, player)| (player.wechat.clone(), (index + 1) as i32))
.collect::<HashMap<_, _>>();
full_ranking
.into_iter()
.map(|player| {
let game_rank = if player.already_top_ranked {
0
} else {
eligible_rank_map.get(&player.wechat).cloned().unwrap_or(0)
};
Game {
wechat_id: player.wechat,
nickname: player.nickname,
avatar: player.avatar,
item_num,
game_rank,
score: player.score as i32,
create_date: None,
batch_no: batch_no.clone(),
}
})
.collect::<Vec<_>>()
} else {
return;
};
for game in games {
insert_game_once(&game).await;
}
}
async fn sleep_room_status(round_id: u64, secs: u64, expected_status: RoomStatus) -> bool {
for _ in 0..secs {
tokio::time::sleep(Duration::from_secs(1)).await;
......@@ -614,6 +747,8 @@ fn spawn_game_timeout(round_id: u64, secs: u64) {
return;
}
save_room_final_scores().await;
if let Ok(mut state) = ROOM_STATE.lock() {
if state.status == RoomStatus::Submit {
state.status = RoomStatus::Ended;
......@@ -846,6 +981,7 @@ async fn handle_room_command(
//每次开始房间时记录当前批次
let batch_no = format!("{}-{}", utils::get_current_timestamp(), play_len);
state.batch_no = batch_no.clone();
SAVED_GAME_RECORD_MAP.clear();
Some((round_id, batch_no, item_num, play_len))
//通知所有玩家倒计时60秒,实际上还有3秒倒计时,以前2秒统计结果,所以是65
......@@ -937,7 +1073,7 @@ async fn handle_room_command(
if already_top_ranked {
result["alreadyTopRanked"] = json!(true);
result["message"] =
json!("一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧s");
json!("一人只能上榜领奖一次,您的福利已到手,把机会留给其他玩家吧");
}
emit_msgpack(&socket, evt, &ok_resp(Some(result)));
return;
......@@ -970,7 +1106,6 @@ async fn handle_room_command(
if wechat.is_empty() {
return;
}
let (score, rank, ranking, already_top_ranked, batch_no) =
if let Ok(state) = ROOM_STATE.lock() {
let batch_no = state.batch_no.clone();
......@@ -1028,7 +1163,7 @@ async fn handle_room_command(
batch_no: batch_no,
};
if game.insert().await {
if insert_game_once(&game).await {
if !already_top_ranked && (1..=ROOM_SCORE_RANK_LIMIT as i64).contains(&rank) {
CLIENT_TOP_RANKED_MAP.insert(userid.to_string(), true);
}
......@@ -1064,6 +1199,8 @@ async fn handle_room_command(
.collect::<Vec<_>>();
CLIENT_PLAYER_MAP.clear();
CLIENT_TOP_RANKED_MAP.clear();
CLIENT_AUTH_TOKEN_MAP.clear();
SAVED_GAME_RECORD_MAP.clear();
emit_msgpack(&socket, evt, &ok_resp(None));
// broadcast_msgpack_all_player_2(evt, &ok_resp(None));
......@@ -1134,6 +1271,11 @@ pub async fn on_connect(_io: SocketIo, socket: SocketRef, Data(data): Data<Value
_ = socket.disconnect();
return;
}
if let Some(token) = data.get("token").and_then(|v| v.as_str()).filter(|v| !v.is_empty()) {
CLIENT_AUTH_TOKEN_MAP.insert(userid.clone(), token.to_string());
}
let mut is_admin = false;
// 接入微信小程序不做验证,但只要传了 validuser 字段且验证成功,就是管理员。
......
......@@ -93,15 +93,17 @@ export function $toast(message?: string | ToastLine[], duration = 2000) {
'left: 50%',
'top: 45%',
'z-index: 99999',
'max-width: 680px',
'box-sizing: border-box',
'width: 420px',
'padding: 28px 40px',
'border-radius: 18px',
'background: rgba(0, 0, 0, 0.72)',
'color: #fff',
'font-size: 26px',
'font-size: 28px',
'line-height: 1.65',
'text-align: center',
'white-space: pre-line',
'overflow-wrap: break-word',
'transform: translate(-50%, -40%)',
'opacity: 0',
'pointer-events: none',
......@@ -145,7 +147,7 @@ export function $toast(message?: string | ToastLine[], duration = 2000) {
export function $scoreSubmitFailedToast(duration = 10 * 60 * 1000) {
$toast([
'网络异常,分数提交失败',
'网络异常,分数提交失败',
'请以大屏游戏结果排行为准。',
], duration);
}
......@@ -153,7 +155,7 @@ export function $scoreSubmitFailedToast(duration = 10 * 60 * 1000) {
export function $alreadyRankedToast(duration = 10 * 60 * 1000) {
$toast([
{ text: '一人只能上榜领奖一次', bold: true },
'您的福利已到手',
'您的福利已到手',
'把机会留给其他玩家吧',
], duration);
}
......@@ -392,27 +394,3 @@ export function $getWechat(): any | null {
return null;
}
export async function postScreenGameScore(data:any) {
try {
const resp = await fetch('https://api.guocai365.org.cn/api/offline_clearance/user/screen_game_score', {
method: 'POST',
headers: {
Authorization: $getWechat()?.token_origin,
'Content-Type': 'application/json',
},
body: JSON.stringify({
game_code: data.code,
score:data.score,
batch_no:data.batch_no,
rank_no:data.rank_no,
}),
});
if (!resp.ok) {
console.error('提交大屏游戏分数失败:', resp.status, await resp.text());
}
} catch (error) {
console.error('提交大屏游戏分数异常:', error);
}
}
......@@ -2,7 +2,7 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router'
import type { Socket } from 'socket.io-client'
import { initSocket, onSocketMessage, sendSocketMessage } from '@/commons/ws'
import { $alreadyRankedToast, $getWechat, $hideToast, $read, $save, $scoreSubmitFailedToast, $toast, postScreenGameScore } from '@/commons/utils'
import { $alreadyRankedToast, $getWechat, $hideToast, $read, $save, $scoreSubmitFailedToast, $toast } from '@/commons/utils'
type SocketPayload = {
msg?: string
......@@ -85,32 +85,6 @@ export function useGameSocket(options: GameSocketOptions) {
let statusTimer: ReturnType<typeof window.setInterval> | undefined
const domain = $read('domain', '');
let resubmitcount = 0;
let pendingSubmittedScore: SubmittedGameScore | undefined
let finalScorePosted = false
const postFinalScoreIfReady = (data: any) => {
if (finalScorePosted || !pendingSubmittedScore || Number(data?.status) !== 4) {
return
}
const token_origin = $getWechat()?.token_origin
if (!token_origin || !Number.isFinite(pendingSubmittedScore.score)) {
return
}
const finalRank = getServerRank(data)
const finalScore = Number(data?.score)
const batch_no = $read('batch_no')
finalScorePosted = true
void postScreenGameScore({
code: pendingSubmittedScore.code,
score: Number.isFinite(finalScore) ? finalScore : pendingSubmittedScore.score,
batch_no,
rank_no: finalRank,
})
}
const stopStatusPolling = () => {
if (statusTimer) {
window.clearInterval(statusTimer)
......@@ -127,7 +101,10 @@ export function useGameSocket(options: GameSocketOptions) {
socket = initSocket({
socket_path: '/socket.io',
client_ns: `${domain}/ws`,
auth: options.auth,
auth: {
...options.auth,
token: options.auth?.token ?? $getWechat()?.token_origin ?? '',
},
onConnect: (socket) => {
isGameSocketConnected.value = true;
$hideToast();
......@@ -219,8 +196,6 @@ export function useGameSocket(options: GameSocketOptions) {
case 'game_start_result': {
if (data.index === options.gameId) {
$save('batch_no', data.batch_no);
pendingSubmittedScore = undefined
finalScorePosted = false
options.onGameStart?.(data)
}
break
......@@ -230,7 +205,7 @@ export function useGameSocket(options: GameSocketOptions) {
break
}
case 'submit_score_save_result': {
pendingSubmittedScore = options.onScoreSubmitted?.(true, data) || pendingSubmittedScore
options.onScoreSubmitted?.(true, data)
break
}
case 'status_result': {
......@@ -238,7 +213,6 @@ export function useGameSocket(options: GameSocketOptions) {
return
}
options.onStatus?.(data)
postFinalScoreIfReady(data)
break
}
case 'room_back_result': {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment