完善后端联调链路与模拟器多通道支持
This commit is contained in:
375
miniprogram/utils/backendApi.ts
Normal file
375
miniprogram/utils/backendApi.ts
Normal file
@@ -0,0 +1,375 @@
|
||||
import { normalizeBackendBaseUrl } from './backendAuth'
|
||||
|
||||
export interface BackendApiError {
|
||||
statusCode: number
|
||||
code: string
|
||||
message: string
|
||||
details?: unknown
|
||||
}
|
||||
|
||||
export interface BackendAuthLoginResult {
|
||||
user?: {
|
||||
id?: string
|
||||
nickname?: string
|
||||
avatarUrl?: string
|
||||
}
|
||||
tokens: {
|
||||
accessToken: string
|
||||
refreshToken: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface BackendResolvedRelease {
|
||||
launchMode: string
|
||||
source: string
|
||||
eventId: string
|
||||
releaseId: string
|
||||
configLabel: string
|
||||
manifestUrl: string
|
||||
manifestChecksumSha256?: string | null
|
||||
routeCode?: string | null
|
||||
}
|
||||
|
||||
export interface BackendEntrySessionSummary {
|
||||
id: string
|
||||
status: string
|
||||
eventId?: string
|
||||
eventName?: string
|
||||
releaseId?: string | null
|
||||
configLabel?: string | null
|
||||
routeCode?: string | null
|
||||
launchedAt?: string | null
|
||||
startedAt?: string | null
|
||||
endedAt?: string | null
|
||||
// 兼容前端旧字段名,避免联调过渡期多处判断
|
||||
sessionId?: string
|
||||
sessionStatus?: string
|
||||
eventDisplayName?: string
|
||||
}
|
||||
|
||||
export interface BackendCardResult {
|
||||
id: string
|
||||
type: string
|
||||
title: string
|
||||
subtitle?: string | null
|
||||
coverUrl?: string | null
|
||||
displaySlot: string
|
||||
displayPriority: number
|
||||
event?: {
|
||||
id: string
|
||||
displayName: string
|
||||
summary?: string | null
|
||||
} | null
|
||||
htmlUrl?: string | null
|
||||
}
|
||||
|
||||
export interface BackendEntryHomeResult {
|
||||
user: {
|
||||
id: string
|
||||
publicId: string
|
||||
status: string
|
||||
nickname?: string | null
|
||||
avatarUrl?: string | null
|
||||
}
|
||||
tenant: {
|
||||
id: string
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
channel: {
|
||||
id: string
|
||||
code: string
|
||||
type: string
|
||||
platformAppId?: string | null
|
||||
displayName: string
|
||||
status: string
|
||||
isDefault: boolean
|
||||
}
|
||||
cards: BackendCardResult[]
|
||||
ongoingSession?: BackendEntrySessionSummary | null
|
||||
recentSession?: BackendEntrySessionSummary | null
|
||||
}
|
||||
|
||||
export interface BackendEventPlayResult {
|
||||
event: {
|
||||
id: string
|
||||
slug: string
|
||||
displayName: string
|
||||
summary?: string | null
|
||||
status: string
|
||||
}
|
||||
release?: {
|
||||
id: string
|
||||
configLabel: string
|
||||
manifestUrl: string
|
||||
manifestChecksumSha256?: string | null
|
||||
routeCode?: string | null
|
||||
} | null
|
||||
resolvedRelease?: BackendResolvedRelease | null
|
||||
play: {
|
||||
canLaunch: boolean
|
||||
primaryAction: string
|
||||
reason: string
|
||||
launchSource?: string
|
||||
ongoingSession?: BackendEntrySessionSummary | null
|
||||
recentSession?: BackendEntrySessionSummary | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface BackendLaunchResult {
|
||||
event: {
|
||||
id: string
|
||||
displayName: string
|
||||
}
|
||||
launch: {
|
||||
source: string
|
||||
resolvedRelease?: BackendResolvedRelease | null
|
||||
config: {
|
||||
configUrl: string
|
||||
configLabel: string
|
||||
configChecksumSha256?: string | null
|
||||
releaseId: string
|
||||
routeCode?: string | null
|
||||
}
|
||||
business: {
|
||||
source: string
|
||||
eventId: string
|
||||
sessionId: string
|
||||
sessionToken: string
|
||||
sessionTokenExpiresAt: string
|
||||
routeCode?: string | null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface BackendSessionFinishSummaryPayload {
|
||||
finalDurationSec?: number
|
||||
finalScore?: number
|
||||
completedControls?: number
|
||||
totalControls?: number
|
||||
distanceMeters?: number
|
||||
averageSpeedKmh?: number
|
||||
maxHeartRateBpm?: number
|
||||
}
|
||||
|
||||
export interface BackendSessionResult {
|
||||
session: {
|
||||
id: string
|
||||
status: string
|
||||
clientType: string
|
||||
deviceKey: string
|
||||
routeCode?: string | null
|
||||
sessionTokenExpiresAt: string
|
||||
launchedAt: string
|
||||
startedAt?: string | null
|
||||
endedAt?: string | null
|
||||
}
|
||||
event: {
|
||||
id: string
|
||||
displayName: string
|
||||
}
|
||||
resolvedRelease?: BackendResolvedRelease | null
|
||||
}
|
||||
|
||||
export interface BackendSessionResultView {
|
||||
session: BackendEntrySessionSummary
|
||||
result: {
|
||||
status: string
|
||||
finalDurationSec?: number
|
||||
finalScore?: number
|
||||
completedControls?: number
|
||||
totalControls?: number
|
||||
distanceMeters?: number
|
||||
averageSpeedKmh?: number
|
||||
maxHeartRateBpm?: number
|
||||
summary?: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
type BackendEnvelope<T> = {
|
||||
data: T
|
||||
}
|
||||
|
||||
type RequestOptions = {
|
||||
method: 'GET' | 'POST'
|
||||
baseUrl: string
|
||||
path: string
|
||||
authToken?: string
|
||||
body?: Record<string, unknown>
|
||||
}
|
||||
|
||||
function requestBackend<T>(options: RequestOptions): Promise<T> {
|
||||
const url = `${normalizeBackendBaseUrl(options.baseUrl)}${options.path}`
|
||||
const header: Record<string, string> = {}
|
||||
if (options.body) {
|
||||
header['Content-Type'] = 'application/json'
|
||||
}
|
||||
if (options.authToken) {
|
||||
header.Authorization = `Bearer ${options.authToken}`
|
||||
}
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
wx.request({
|
||||
url,
|
||||
method: options.method,
|
||||
header,
|
||||
data: options.body,
|
||||
success: (response) => {
|
||||
const statusCode = typeof response.statusCode === 'number' ? response.statusCode : 0
|
||||
const data = response.data as BackendEnvelope<T> | { error?: { code?: string; message?: string; details?: unknown } }
|
||||
if (statusCode >= 200 && statusCode < 300 && data && typeof data === 'object' && 'data' in data) {
|
||||
resolve((data as BackendEnvelope<T>).data)
|
||||
return
|
||||
}
|
||||
|
||||
const errorPayload = data && typeof data === 'object' && 'error' in data
|
||||
? (data as { error?: { code?: string; message?: string; details?: unknown } }).error
|
||||
: undefined
|
||||
reject({
|
||||
statusCode,
|
||||
code: errorPayload && errorPayload.code ? errorPayload.code : 'backend_error',
|
||||
message: errorPayload && errorPayload.message ? errorPayload.message : `request failed: ${statusCode}`,
|
||||
details: errorPayload && errorPayload.details ? errorPayload.details : response.data,
|
||||
} as BackendApiError)
|
||||
},
|
||||
fail: (error) => {
|
||||
reject({
|
||||
statusCode: 0,
|
||||
code: 'network_error',
|
||||
message: error && error.errMsg ? error.errMsg : 'network request failed',
|
||||
} as BackendApiError)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function loginWechatMini(input: {
|
||||
baseUrl: string
|
||||
code: string
|
||||
deviceKey: string
|
||||
clientType?: string
|
||||
}): Promise<BackendAuthLoginResult> {
|
||||
return requestBackend<BackendAuthLoginResult>({
|
||||
method: 'POST',
|
||||
baseUrl: input.baseUrl,
|
||||
path: '/auth/login/wechat-mini',
|
||||
body: {
|
||||
code: input.code,
|
||||
clientType: input.clientType || 'wechat',
|
||||
deviceKey: input.deviceKey,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getEventPlay(input: {
|
||||
baseUrl: string
|
||||
eventId: string
|
||||
accessToken: string
|
||||
}): Promise<BackendEventPlayResult> {
|
||||
return requestBackend<BackendEventPlayResult>({
|
||||
method: 'GET',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/events/${encodeURIComponent(input.eventId)}/play`,
|
||||
authToken: input.accessToken,
|
||||
})
|
||||
}
|
||||
|
||||
export function getEntryHome(input: {
|
||||
baseUrl: string
|
||||
accessToken: string
|
||||
channelCode: string
|
||||
channelType: string
|
||||
}): Promise<BackendEntryHomeResult> {
|
||||
const query = `channelCode=${encodeURIComponent(input.channelCode)}&channelType=${encodeURIComponent(input.channelType)}`
|
||||
return requestBackend<BackendEntryHomeResult>({
|
||||
method: 'GET',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/me/entry-home?${query}`,
|
||||
authToken: input.accessToken,
|
||||
})
|
||||
}
|
||||
|
||||
export function launchEvent(input: {
|
||||
baseUrl: string
|
||||
eventId: string
|
||||
accessToken: string
|
||||
releaseId?: string
|
||||
clientType: string
|
||||
deviceKey: string
|
||||
}): Promise<BackendLaunchResult> {
|
||||
const body: Record<string, unknown> = {
|
||||
clientType: input.clientType,
|
||||
deviceKey: input.deviceKey,
|
||||
}
|
||||
if (input.releaseId) {
|
||||
body.releaseId = input.releaseId
|
||||
}
|
||||
return requestBackend<BackendLaunchResult>({
|
||||
method: 'POST',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/events/${encodeURIComponent(input.eventId)}/launch`,
|
||||
authToken: input.accessToken,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
export function startSession(input: {
|
||||
baseUrl: string
|
||||
sessionId: string
|
||||
sessionToken: string
|
||||
}): Promise<BackendSessionResult> {
|
||||
return requestBackend<BackendSessionResult>({
|
||||
method: 'POST',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/sessions/${encodeURIComponent(input.sessionId)}/start`,
|
||||
body: {
|
||||
sessionToken: input.sessionToken,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function finishSession(input: {
|
||||
baseUrl: string
|
||||
sessionId: string
|
||||
sessionToken: string
|
||||
status: 'finished' | 'failed' | 'cancelled'
|
||||
summary: BackendSessionFinishSummaryPayload
|
||||
}): Promise<BackendSessionResult> {
|
||||
return requestBackend<BackendSessionResult>({
|
||||
method: 'POST',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/sessions/${encodeURIComponent(input.sessionId)}/finish`,
|
||||
body: {
|
||||
sessionToken: input.sessionToken,
|
||||
status: input.status,
|
||||
summary: input.summary,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function getSessionResult(input: {
|
||||
baseUrl: string
|
||||
accessToken: string
|
||||
sessionId: string
|
||||
}): Promise<BackendSessionResultView> {
|
||||
return requestBackend<BackendSessionResultView>({
|
||||
method: 'GET',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/sessions/${encodeURIComponent(input.sessionId)}/result`,
|
||||
authToken: input.accessToken,
|
||||
})
|
||||
}
|
||||
|
||||
export function getMyResults(input: {
|
||||
baseUrl: string
|
||||
accessToken: string
|
||||
limit?: number
|
||||
}): Promise<BackendSessionResultView[]> {
|
||||
const limit = typeof input.limit === 'number' ? input.limit : 20
|
||||
return requestBackend<BackendSessionResultView[]>({
|
||||
method: 'GET',
|
||||
baseUrl: input.baseUrl,
|
||||
path: `/me/results?limit=${encodeURIComponent(String(limit))}`,
|
||||
authToken: input.accessToken,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user