Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
fc-minigame
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陈冲
fc-minigame
Commits
ce0327b4
Commit
ce0327b4
authored
Jul 01, 2026
by
陈冲
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: 更新
parent
5132a46c
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
189 additions
and
63 deletions
+189
-63
game.rs
backend/src/db/models/game.rs
+32
-0
socket_io.rs
backend/src/protocols/socket_io.rs
+145
-3
utils.ts
frontend-h5/src/commons/utils.ts
+6
-28
useGameSocket.ts
frontend-h5/src/composables/useGameSocket.ts
+6
-32
No files found.
backend/src/db/models/game.rs
View file @
ce0327b4
...
...
@@ -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;"
);
...
...
backend/src/protocols/socket_io.rs
View file @
ce0327b4
...
...
@@ -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 字段且验证成功,就是管理员。
...
...
frontend-h5/src/commons/utils.ts
View file @
ce0327b4
...
...
@@ -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: 2
6
px'
,
'font-size: 2
8
px'
,
'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
);
}
}
frontend-h5/src/composables/useGameSocket.ts
View file @
ce0327b4
...
...
@@ -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'
:
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment