收口微信领域能力
将 api-server 微信 HTTP/BFF 适配统一迁移到 wechat 目录。 将微信支付和虚拟支付消息协议细节下沉到 platform-wechat。 拆分 platform-wechat 的订阅消息与支付模块并补齐依赖。 修正微信相关测试的用户 ID 夹具并同步后端架构文档。
This commit is contained in:
@@ -16,6 +16,14 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-08 微信能力按领域收口
|
||||
|
||||
- 背景:微信登录、订阅消息、普通微信支付和小程序虚拟支付能力曾分散在 `api-server` 根模块、`platform-auth` 与 `platform-wechat`,支付协议细节和业务 handler 边界不够清晰。
|
||||
- 决策:`api-server` 内微信相关 HTTP/BFF 适配统一收在 `server-rs/crates/api-server/src/wechat.rs` 与 `wechat/*`;`platform-wechat` 负责微信订阅消息、微信支付 V3、虚拟支付消息推送的协议 client、header、签名、验签、解密、mock 和 payload 解析;`api-server::wechat` 只负责 AppConfig 映射、Axum handler、用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。微信 OAuth / 小程序登录 provider 暂继续在 `platform-auth`,通过 `api-server::wechat::provider` 作为组合根 adapter 接入。
|
||||
- 影响范围:`server-rs/crates/api-server/src/wechat.rs`、`server-rs/crates/api-server/src/wechat/*`、`server-rs/crates/platform-wechat/src/*`、微信支付 / 订阅消息 / 小程序消息推送文档。
|
||||
- 验证方式:执行 `cargo check --manifest-path server-rs/Cargo.toml -p platform-wechat`、`cargo check --manifest-path server-rs/Cargo.toml -p api-server`、微信相关定向测试和编码检查;新增微信协议细节优先落到 `platform-wechat`。
|
||||
- 关联文档:`docs/【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md`、`docs/【技术方案】微信虚拟支付接入-2026-05-26.md`。
|
||||
|
||||
## 2026-06-07 推荐页运行态先封面预载再 ready 渐隐
|
||||
|
||||
- 背景:移动端推荐页上下切换公开作品时,如果运行态和封面资源没有明确准备边界,用户会看到未加载完成的 runtime、黑底闪动,或切卡后反向回弹。
|
||||
|
||||
@@ -22,7 +22,7 @@ SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`
|
||||
|
||||
- HTTP 服务:`api-server`。
|
||||
- 领域模块:`module-ai`、`module-assets`、`module-auth`、`module-bark-battle`、`module-big-fish`、`module-combat`、`module-creative-agent`、`module-custom-world`、`module-inventory`、`module-match3d`、`module-npc`、`module-progression`、`module-puzzle`、`module-quest`、`module-runtime`、`module-runtime-item`、`module-runtime-story`、`module-square-hole`、`module-story`、`module-visual-novel`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-image`、`platform-llm`、`platform-oss`、`platform-speech`。
|
||||
- 平台副作用:`platform-agent`、`platform-auth`、`platform-image`、`platform-llm`、`platform-oss`、`platform-wechat`、`platform-speech`。
|
||||
- 共享层:`shared-contracts`、`shared-kernel`、`shared-logging`。
|
||||
- SpacetimeDB:`spacetime-client`、`spacetime-module`。
|
||||
- 测试支撑:`tests-support`。
|
||||
@@ -35,6 +35,7 @@ SpacetimeDB 版本口径:当前 Rust crate `spacetimedb`、`spacetimedb-sdk`
|
||||
4. 后端访问 SpacetimeDB 必须经 `spacetime-client` facade。
|
||||
5. HTTP 鉴权、BFF 聚合、SSE、外部模型编排、OSS 上传和第三方回调在 `api-server`。
|
||||
6. 前端共享 DTO 通过 `shared-contracts` 和 `packages/shared` 对齐,不在页面内重新发明旧接口。
|
||||
7. 微信能力按两层收口:`server-rs/crates/platform-wechat` 承载微信协议 client、订阅消息 `stable_token` / `subscribeMessage.send`、微信支付 V3 / 虚拟支付消息推送的 HTTP header、签名、验签、解密、mock 响应和协议 payload 解析;`server-rs/crates/api-server/src/wechat.rs` 与 `wechat/*` 承载 Axum handler、AppConfig 到平台配置的映射、Genarrative 用户 / 订单 / 钱包 / SSE / 错误 envelope 编排。`platform-auth` 当前仍承载微信 OAuth / 小程序登录 provider 协议,`api-server::wechat::provider` 只作为组合根 adapter,不在业务 handler 内散落 provider 构造。
|
||||
|
||||
验证:
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
- 充值入口:`src/components/rpg-entry/RpgEntryHomeView.tsx`
|
||||
- 小程序支付承接页:`miniprogram/pages/wechat-pay/index.shared.js`
|
||||
- API 契约:`packages/shared/src/contracts/runtime.ts`、`server-rs/crates/shared-contracts/src/runtime.rs`
|
||||
- 后端下单与签名:`server-rs/crates/api-server/src/runtime_profile.rs`
|
||||
- 后端下单与订单编排:`server-rs/crates/api-server/src/runtime_profile.rs`、`server-rs/crates/api-server/src/wechat/pay.rs`
|
||||
- 微信支付 / 虚拟支付协议适配:`server-rs/crates/platform-wechat/src/pay.rs`
|
||||
- 微信订阅消息协议适配:`server-rs/crates/platform-wechat/src/subscribe_message.rs`
|
||||
- WebView 回流确认:`GET /api/profile/recharge/orders/{orderId}/wechat/events`、`POST /api/profile/recharge/orders/{orderId}/wechat/confirm`
|
||||
- 微信登录态保存:`server-rs/crates/platform-auth/src/lib.rs`、`server-rs/crates/module-auth/src/lib.rs`
|
||||
|
||||
|
||||
Generated
+10
@@ -2513,11 +2513,21 @@ dependencies = [
|
||||
name = "platform-wechat"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"base64 0.22.1",
|
||||
"cbc",
|
||||
"hex",
|
||||
"reqwest 0.12.28",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"shared-contracts",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -41,7 +41,7 @@ use crate::{
|
||||
start_visual_novel_run, stream_visual_novel_action, stream_visual_novel_message,
|
||||
submit_visual_novel_message, update_visual_novel_work,
|
||||
},
|
||||
wechat_pay::{
|
||||
wechat::pay::{
|
||||
handle_wechat_pay_notify, handle_wechat_virtual_payment_message_push_verify,
|
||||
handle_wechat_virtual_payment_notify,
|
||||
},
|
||||
@@ -1507,8 +1507,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn wooden_fish_session_creation_accepts_legacy_audio_body_above_default_limit() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let seed_user =
|
||||
seed_phone_user_with_password(&state, "13800138026", TEST_PASSWORD).await;
|
||||
let seed_user = seed_phone_user_with_password(&state, "13800138026", TEST_PASSWORD).await;
|
||||
let token = sign_test_user_token(&state, &seed_user, "sess_wooden_fish_audio_body");
|
||||
let app = build_router(state);
|
||||
let request_body = format!(
|
||||
@@ -1548,8 +1547,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn wooden_fish_actions_accept_legacy_audio_body_above_default_limit() {
|
||||
let state = AppState::new(AppConfig::default()).expect("state should build");
|
||||
let seed_user =
|
||||
seed_phone_user_with_password(&state, "13800138027", TEST_PASSWORD).await;
|
||||
let seed_user = seed_phone_user_with_password(&state, "13800138027", TEST_PASSWORD).await;
|
||||
let token = sign_test_user_token(&state, &seed_user, "sess_wooden_fish_action_body");
|
||||
let app = build_router(state);
|
||||
let request_body = format!(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
http::StatusCode,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{env, fs, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
use std::{env, fs, net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use platform_llm::{
|
||||
DEFAULT_ARK_BASE_URL, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS,
|
||||
|
||||
@@ -37,11 +37,11 @@ use spacetime_client::{
|
||||
CustomWorldAgentOperationRecord, CustomWorldAgentSessionCreateRecordInput,
|
||||
CustomWorldAgentSessionRecord, CustomWorldDraftCardDetailRecord,
|
||||
CustomWorldDraftCardDetailSectionRecord, CustomWorldDraftCardRecord,
|
||||
CustomWorldLibraryEntryRecord,
|
||||
CustomWorldProfileLikeReportRecordInput, CustomWorldProfilePlayReportRecordInput,
|
||||
CustomWorldProfileRemixRecordInput, CustomWorldProfileUpsertRecordInput,
|
||||
CustomWorldPublishGateRecord, CustomWorldResultPreviewBlockerRecord,
|
||||
CustomWorldSupportedActionRecord, CustomWorldWorkSummaryRecord, SpacetimeClientError,
|
||||
CustomWorldLibraryEntryRecord, CustomWorldProfileLikeReportRecordInput,
|
||||
CustomWorldProfilePlayReportRecordInput, CustomWorldProfileRemixRecordInput,
|
||||
CustomWorldProfileUpsertRecordInput, CustomWorldPublishGateRecord,
|
||||
CustomWorldResultPreviewBlockerRecord, CustomWorldSupportedActionRecord,
|
||||
CustomWorldWorkSummaryRecord, SpacetimeClientError,
|
||||
};
|
||||
use std::{collections::BTreeSet, convert::Infallible, sync::Arc, time::Instant};
|
||||
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
|
||||
|
||||
@@ -10,9 +10,9 @@ use axum::{
|
||||
response::Response,
|
||||
};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STANDARD};
|
||||
use image::{DynamicImage, GenericImageView, codecs::jpeg::JpegEncoder, imageops::FilterType};
|
||||
#[cfg(test)]
|
||||
use image::ImageFormat;
|
||||
use image::{DynamicImage, GenericImageView, codecs::jpeg::JpegEncoder, imageops::FilterType};
|
||||
use module_assets::{
|
||||
AssetObjectAccessPolicy, AssetObjectFieldError, build_asset_entity_binding_input,
|
||||
build_asset_object_upsert_input, generate_asset_binding_id, generate_asset_object_id,
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::{
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use generated_asset_sheets_impl::{
|
||||
GeneratedAssetSheetAlphaOptions, GeneratedAssetSheetError, GeneratedAssetSheetKeyColor,
|
||||
GeneratedAssetSheetPersistInput, GeneratedAssetSheetPersistPrompt, GeneratedAssetSheetSliceImage,
|
||||
GeneratedAssetSheetUpload,
|
||||
GeneratedAssetSheetPersistInput, GeneratedAssetSheetPersistPrompt,
|
||||
GeneratedAssetSheetSliceImage, GeneratedAssetSheetUpload,
|
||||
apply_generated_asset_sheet_alpha_with_options, apply_generated_asset_sheet_green_screen_alpha,
|
||||
crop_generated_asset_sheet_view_edge_matte,
|
||||
crop_generated_asset_sheet_view_edge_matte_with_options,
|
||||
|
||||
@@ -14,10 +14,9 @@ use shared_contracts::jump_hop::{
|
||||
JumpHopActionRequest, JumpHopActionType, JumpHopCharacterAsset, JumpHopDraftResponse,
|
||||
JumpHopGalleryDetailResponse, JumpHopGenerationStatus, JumpHopJumpRequest, JumpHopJumpResponse,
|
||||
JumpHopLeaderboardEntry, JumpHopLeaderboardResponse, JumpHopRestartRunRequest,
|
||||
JumpHopRunResponse,
|
||||
JumpHopSessionResponse, JumpHopSessionSnapshotResponse, JumpHopStartRunRequest,
|
||||
JumpHopTileAsset, JumpHopTileType, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse,
|
||||
JumpHopWorksResponse, JumpHopWorkspaceCreateRequest,
|
||||
JumpHopRunResponse, JumpHopSessionResponse, JumpHopSessionSnapshotResponse,
|
||||
JumpHopStartRunRequest, JumpHopTileAsset, JumpHopTileType, JumpHopWorkDetailResponse,
|
||||
JumpHopWorkMutationResponse, JumpHopWorksResponse, JumpHopWorkspaceCreateRequest,
|
||||
};
|
||||
use shared_kernel::{build_prefixed_uuid_id, format_timestamp_micros};
|
||||
use spacetime_client::SpacetimeClientError;
|
||||
@@ -45,7 +44,7 @@ use crate::{
|
||||
},
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
wechat_subscribe_message::{
|
||||
wechat::subscribe_message::{
|
||||
GenerationResultSubscribeMessage, GenerationResultSubscribeMessageStatus,
|
||||
send_generation_result_subscribe_message_after_completion,
|
||||
},
|
||||
@@ -295,10 +294,7 @@ pub async fn get_jump_hop_work_detail(
|
||||
ensure_non_empty(&request_context, &profile_id, "profileId")?;
|
||||
let work = state
|
||||
.spacetime_client()
|
||||
.get_jump_hop_work_profile(
|
||||
profile_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
)
|
||||
.get_jump_hop_work_profile(profile_id, authenticated.claims().user_id().to_string())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
@@ -323,10 +319,7 @@ pub async fn delete_jump_hop_work(
|
||||
ensure_non_empty(&request_context, &profile_id, "profileId")?;
|
||||
let works = state
|
||||
.spacetime_client()
|
||||
.delete_jump_hop_work(
|
||||
profile_id,
|
||||
authenticated.claims().user_id().to_string(),
|
||||
)
|
||||
.delete_jump_hop_work(profile_id, authenticated.claims().user_id().to_string())
|
||||
.await
|
||||
.map_err(|error| {
|
||||
jump_hop_error_response(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Extension, State},
|
||||
};
|
||||
|
||||
@@ -89,10 +89,7 @@ mod tracking_outbox;
|
||||
mod vector_engine_audio_generation;
|
||||
mod visual_novel;
|
||||
mod volcengine_speech;
|
||||
mod wechat_auth;
|
||||
mod wechat_pay;
|
||||
mod wechat_provider;
|
||||
mod wechat_subscribe_message;
|
||||
mod wechat;
|
||||
mod wooden_fish;
|
||||
mod work_author;
|
||||
mod work_play_tracking;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
@@ -84,7 +84,7 @@ use crate::{
|
||||
vector_engine_audio_generation::{
|
||||
GeneratedCreationAudioTarget, generate_sound_effect_asset_for_creation,
|
||||
},
|
||||
wechat_subscribe_message::{
|
||||
wechat::subscribe_message::{
|
||||
GenerationResultSubscribeMessage, GenerationResultSubscribeMessageStatus,
|
||||
send_generation_result_subscribe_message_after_completion,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::{
|
||||
use axum::{
|
||||
Router, middleware,
|
||||
routing::{get, post},
|
||||
};
|
||||
@@ -16,7 +16,7 @@ use crate::{
|
||||
phone_auth::{phone_login, send_phone_code},
|
||||
refresh_session::refresh_session,
|
||||
state::AppState,
|
||||
wechat_auth::{
|
||||
wechat::auth::{
|
||||
bind_wechat_phone, handle_wechat_callback, login_wechat_mini_program, start_wechat_login,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use axum::{
|
||||
middleware,
|
||||
Router, middleware,
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -9,9 +8,8 @@ use crate::{
|
||||
jump_hop::{
|
||||
create_jump_hop_session, delete_jump_hop_work, execute_jump_hop_action,
|
||||
get_jump_hop_gallery_detail, get_jump_hop_leaderboard, get_jump_hop_runtime_work,
|
||||
get_jump_hop_session, get_jump_hop_work_detail, jump_hop_run_jump,
|
||||
list_jump_hop_gallery, list_jump_hop_works, publish_jump_hop_work, restart_jump_hop_run,
|
||||
start_jump_hop_run,
|
||||
get_jump_hop_session, get_jump_hop_work_detail, jump_hop_run_jump, list_jump_hop_gallery,
|
||||
list_jump_hop_works, publish_jump_hop_work, restart_jump_hop_run, start_jump_hop_run,
|
||||
},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
@@ -24,9 +24,7 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
"/api/creation/wooden-fish/sessions",
|
||||
post(create_wooden_fish_session)
|
||||
// 中文注释:兼容旧小程序把参考图或录音 Data URL 放进创作 JSON 的请求;新前端音频会先直传 OSS。
|
||||
.layer(DefaultBodyLimit::max(
|
||||
WOODEN_FISH_CREATION_BODY_LIMIT_BYTES,
|
||||
))
|
||||
.layer(DefaultBodyLimit::max(WOODEN_FISH_CREATION_BODY_LIMIT_BYTES))
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
@@ -43,9 +41,7 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
"/api/creation/wooden-fish/sessions/{session_id}/actions",
|
||||
post(execute_wooden_fish_action)
|
||||
// 中文注释:compile/regenerate 会携带参考图旧兼容输入,避免 Axum 默认 2MB 先于 handler 拦截。
|
||||
.layer(DefaultBodyLimit::max(
|
||||
WOODEN_FISH_CREATION_BODY_LIMIT_BYTES,
|
||||
))
|
||||
.layer(DefaultBodyLimit::max(WOODEN_FISH_CREATION_BODY_LIMIT_BYTES))
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
require_bearer_auth,
|
||||
@@ -98,4 +94,4 @@ pub fn router(state: AppState) -> Router<AppState> {
|
||||
"/api/runtime/wooden-fish/gallery/{public_work_code}",
|
||||
get(get_wooden_fish_gallery_detail),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use axum::http::{HeaderValue, StatusCode};
|
||||
use axum::http::{HeaderValue, StatusCode};
|
||||
use platform_auth::{AuthPlatformErrorKind, WechatProviderError};
|
||||
use platform_llm::{LlmError, LlmErrorKind};
|
||||
use platform_oss::{OssError, OssErrorKind};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{
|
||||
use std::{
|
||||
collections::{BTreeMap, HashSet},
|
||||
sync::{Mutex, OnceLock},
|
||||
time::{Instant, SystemTime, UNIX_EPOCH},
|
||||
@@ -105,7 +105,7 @@ use crate::{
|
||||
puzzle_gallery_cache::{build_puzzle_gallery_window_response, puzzle_gallery_cached_json},
|
||||
request_context::RequestContext,
|
||||
state::{AppState, PuzzleApiState},
|
||||
wechat_subscribe_message::{
|
||||
wechat::subscribe_message::{
|
||||
GenerationResultSubscribeMessage, GenerationResultSubscribeMessageStatus,
|
||||
send_generation_result_subscribe_message_after_completion,
|
||||
},
|
||||
|
||||
@@ -9,12 +9,12 @@ use shared_contracts::{
|
||||
puzzle_gallery::{PuzzleGalleryResponse, PuzzleGalleryWorkRefResponse},
|
||||
puzzle_works::PuzzleWorkSummaryResponse,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use tokio::sync::OwnedMutexGuard;
|
||||
use tokio::{
|
||||
sync::{Mutex, MutexGuard, RwLock},
|
||||
time,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use tokio::sync::OwnedMutexGuard;
|
||||
|
||||
use crate::{api_response::json_success_data_bytes_response, request_context::RequestContext};
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ use module_runtime::{
|
||||
RuntimeProfileWalletLedgerSourceType, RuntimeReferralInviteCenterRecord,
|
||||
RuntimeTrackingScopeKind,
|
||||
};
|
||||
use platform_wechat::pay::WechatPayNotifyOrder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use sha2::Sha256;
|
||||
@@ -81,9 +82,9 @@ use crate::{
|
||||
http_error::AppError,
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
wechat_pay::{
|
||||
WechatPayNotifyOrder, build_wechat_payment_request, build_wechat_web_payment_request,
|
||||
current_unix_micros, map_wechat_pay_error,
|
||||
wechat::pay::{
|
||||
build_wechat_payment_request, build_wechat_web_payment_request, current_unix_micros,
|
||||
map_wechat_pay_error,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3056,11 +3057,12 @@ mod tests {
|
||||
}
|
||||
|
||||
fn issue_access_token(state: &AppState) -> String {
|
||||
let user_id = test_authenticated_user_id(state);
|
||||
let claims = AccessTokenClaims::from_input(
|
||||
AccessTokenClaimsInput {
|
||||
user_id: "user_00000001".to_string(),
|
||||
user_id: user_id.clone(),
|
||||
session_id: state
|
||||
.seed_test_refresh_session_for_user_id("user_00000001", "sess_runtime_profile"),
|
||||
.seed_test_refresh_session_for_user_id(&user_id, "sess_runtime_profile"),
|
||||
provider: AuthProvider::Password,
|
||||
roles: vec!["user".to_string()],
|
||||
token_version: 2,
|
||||
@@ -3081,11 +3083,11 @@ mod tests {
|
||||
client_platform: &str,
|
||||
session_id: &str,
|
||||
) -> String {
|
||||
let user_id = test_authenticated_user_id(state);
|
||||
let claims = AccessTokenClaims::from_input_with_device(
|
||||
AccessTokenClaimsInput {
|
||||
user_id: "user_00000001".to_string(),
|
||||
session_id: state
|
||||
.seed_test_refresh_session_for_user_id("user_00000001", session_id),
|
||||
user_id: user_id.clone(),
|
||||
session_id: state.seed_test_refresh_session_for_user_id(&user_id, session_id),
|
||||
provider: AuthProvider::Wechat,
|
||||
roles: vec!["user".to_string()],
|
||||
token_version: 2,
|
||||
@@ -3105,4 +3107,13 @@ mod tests {
|
||||
|
||||
sign_access_token(&claims, state.auth_jwt_config()).expect("token should sign")
|
||||
}
|
||||
|
||||
fn test_authenticated_user_id(state: &AppState) -> String {
|
||||
state
|
||||
.auth_user_service()
|
||||
.get_user_by_public_user_code("SY-00000001")
|
||||
.expect("test user lookup should succeed")
|
||||
.expect("seeded test user should exist")
|
||||
.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ use crate::{
|
||||
SquareHoleAgentTurnRequest, build_finalize_record_input, run_square_hole_agent_turn,
|
||||
},
|
||||
state::AppState,
|
||||
wechat_subscribe_message::{
|
||||
wechat::subscribe_message::{
|
||||
GenerationResultSubscribeMessage, GenerationResultSubscribeMessageStatus,
|
||||
send_generation_result_subscribe_message_after_completion,
|
||||
},
|
||||
@@ -1119,12 +1119,15 @@ async fn compile_square_hole_draft_for_session(
|
||||
.map(|tags| serde_json::to_string(&normalize_tags(tags.clone())).unwrap_or_default());
|
||||
|
||||
let resolved_game_name = game_name.or_else(|| Some(format!("{}方洞挑战", config.theme_text)));
|
||||
let generation_points_cost = crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
|
||||
state,
|
||||
SQUARE_HOLE_TEMPLATE_ID,
|
||||
u64::from(shared_contracts::creation_entry_config::DEFAULT_UNIFIED_CREATION_MUD_POINT_COST),
|
||||
)
|
||||
.await;
|
||||
let generation_points_cost =
|
||||
crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
|
||||
state,
|
||||
SQUARE_HOLE_TEMPLATE_ID,
|
||||
u64::from(
|
||||
shared_contracts::creation_entry_config::DEFAULT_UNIFIED_CREATION_MUD_POINT_COST,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let result = state
|
||||
.spacetime_client()
|
||||
.compile_square_hole_draft(SquareHoleCompileDraftRecordInput {
|
||||
|
||||
@@ -27,7 +27,7 @@ use platform_auth::{
|
||||
};
|
||||
use platform_llm::{LlmClient, LlmConfig, LlmError, LlmProvider};
|
||||
use platform_oss::{OssClient, OssConfig, OssError};
|
||||
use platform_wechat::{WechatClient, WechatConfig};
|
||||
use platform_wechat::{WechatClient, WechatConfig, pay::WechatPayClient};
|
||||
use serde_json::Value;
|
||||
use shared_contracts::creation_entry_config::CreationEntryConfigResponse;
|
||||
use shared_contracts::creative_agent::CreativeAgentSessionSnapshot;
|
||||
@@ -39,8 +39,8 @@ use tracing::{info, warn};
|
||||
use crate::config::AppConfig;
|
||||
use crate::puzzle_gallery_cache::PuzzleGalleryCache;
|
||||
use crate::tracking_outbox::TrackingOutbox;
|
||||
use crate::wechat_pay::{WechatPayClient, map_wechat_pay_init_error};
|
||||
use crate::wechat_provider::build_wechat_provider;
|
||||
use crate::wechat::pay::{build_wechat_pay_config, map_wechat_pay_init_error};
|
||||
use crate::wechat::provider::build_wechat_provider;
|
||||
use crate::work_author::{
|
||||
ORPHAN_WORK_AUTHOR_DISPLAY_NAME, ORPHAN_WORK_AUTHOR_PUBLIC_USER_CODE, ORPHAN_WORK_OWNER_USER_ID,
|
||||
};
|
||||
@@ -388,8 +388,8 @@ impl AppState {
|
||||
let wechat_auth_service = WechatAuthService::new(auth_store.clone());
|
||||
let wechat_provider = build_wechat_provider(&config);
|
||||
let wechat_client = build_wechat_client(&config);
|
||||
let wechat_pay_client =
|
||||
WechatPayClient::from_config(&config).map_err(map_wechat_pay_init_error)?;
|
||||
let wechat_pay_client = WechatPayClient::from_config(&build_wechat_pay_config(&config))
|
||||
.map_err(map_wechat_pay_init_error)?;
|
||||
let refresh_session_service =
|
||||
RefreshSessionService::new(auth_store.clone(), config.refresh_session_ttl_days);
|
||||
// AI 编排服务当前先挂接内存态 store,后续再按 task table / procedure 接到 SpacetimeDB 真相源。
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
prompt::visual_novel as vn_prompt,
|
||||
request_context::RequestContext,
|
||||
state::AppState,
|
||||
wechat_subscribe_message::{
|
||||
wechat::subscribe_message::{
|
||||
GenerationResultSubscribeMessage, GenerationResultSubscribeMessageStatus,
|
||||
send_generation_result_subscribe_message_after_completion,
|
||||
},
|
||||
@@ -1748,12 +1748,15 @@ async fn compile_visual_novel_session_inner(
|
||||
);
|
||||
let projection = project_draft_for_work(&draft, &profile_id)?;
|
||||
let notification_work_name = projection.work_title.clone();
|
||||
let generation_points_cost = crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
|
||||
state,
|
||||
VISUAL_NOVEL_RUNTIME_KIND,
|
||||
u64::from(shared_contracts::creation_entry_config::DEFAULT_UNIFIED_CREATION_MUD_POINT_COST),
|
||||
)
|
||||
.await;
|
||||
let generation_points_cost =
|
||||
crate::creation_entry_config::resolve_creation_entry_mud_point_cost(
|
||||
state,
|
||||
VISUAL_NOVEL_RUNTIME_KIND,
|
||||
u64::from(
|
||||
shared_contracts::creation_entry_config::DEFAULT_UNIFIED_CREATION_MUD_POINT_COST,
|
||||
),
|
||||
)
|
||||
.await;
|
||||
let author = resolve_work_author_by_user_id(state, &owner_user_id, None, None);
|
||||
let compile_result = state
|
||||
.spacetime_client()
|
||||
@@ -1770,9 +1773,7 @@ async fn compile_visual_novel_session_inner(
|
||||
compiled_at_micros: current_utc_micros(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| {
|
||||
visual_novel_error_response(request_context, map_spacetime_error(error))
|
||||
});
|
||||
.map_err(|error| visual_novel_error_response(request_context, map_spacetime_error(error)));
|
||||
let compiled_session = match compile_result {
|
||||
Ok(session) => {
|
||||
send_generation_result_subscribe_message_after_completion(
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
pub(crate) mod auth;
|
||||
pub(crate) mod pay;
|
||||
pub(crate) mod provider;
|
||||
pub(crate) mod subscribe_message;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user