hailin
fa64e1863a
fix(admin-service): use internal IP for MinIO to avoid hairpin NAT
...
admin-service runs on 192.168.1.222 (LAN). Connecting to MinIO via
public IP 154.84.135.121 fails with ETIMEDOUT (hairpin NAT). Use
internal gateway IP 192.168.1.200:9100 (no SSL) for S3 API calls.
Public download URLs still use https://oss.gogenex.com .
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 01:20:08 -08:00
hailin
0c3ef3b598
fix(mobile): fix version check API and platform detection in both apps
...
genex-mobile/lib/core/updater/version_checker.dart
- platform 从写死 'android' 改为运行时检测 Platform.isIOS ? 'IOS' : 'ANDROID'
admin-app/lib/core/updater/version_checker.dart
- API 路径修正: /api/app/version/check → /api/v1/app/version/check
- 新增 app_type: 'ADMIN_APP'(原缺失,后端默认返回 GENEX_MOBILE 版本)
- platform 从写死 'android' 改为运行时检测
- 响应解析修正: 原逻辑把外层 {code,data} 当版本数据用,
改为正确取 response.data['data'] 内层对象
- 新增 downloadUrl 相对路径修复(向下兼容旧记录)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:50:42 -08:00
hailin
7ba5401e2f
feat(infra): use oss.gogenex.com for app version download URLs
...
将应用版本的文件下载链接从 API 代理路径改为直接指向对象存储域名。
backend/admin-service (admin-version.controller.ts)
- uploadVersion 上传成功后,downloadUrl 改为:
https://oss.gogenex.com/app-releases/ <storageKey>
(原:/api/v1/app/version/download/:id 代理路径)
- 读取 OSS_BASE_URL 环境变量,默认 https://oss.gogenex.com
backend/docker-compose.yml
- admin-service 新增 OSS_BASE_URL=https://oss.gogenex.com
infrastructure/minio/deploy.sh
- app-releases bucket 加入公开下载列表
(APK/IPA 需被移动端直接下载,无需鉴权)
frontend/admin-web
- .env.production 新增 NEXT_PUBLIC_OSS_URL=https://oss.gogenex.com
- .env.development 新增 NEXT_PUBLIC_OSS_URL=https://oss.gogenex.com
MinIO 现状:
app-releases bucket 已在服务器上设为 anonymous download
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:42:05 -08:00
hailin
4c5f5e2cd9
fix(admin-service): update MinIO default endpoint to oss.gogenex.com
...
将 FileStorageService 的默认连接参数从 localhost 更新为生产域名:
- endPoint 默认值: localhost → oss.gogenex.com
- port 默认值: 9000 → 443
- useSSL 默认值: false → true(?? 运算符保证未设置时默认开启)
- accessKey/secretKey 默认值更新为生产凭证
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:22:53 -08:00
hailin
11bb88badd
feat(infra): configure oss.gogenex.com domain for MinIO object storage
...
按域名规划文档为 MinIO 配置专属域名访问。
变更内容:
DNS(Namecheap)
- 新增 A 记录:oss.gogenex.com → 154.84.135.121
Nginx(网关服务器 14.215.128.96)
- 新增 /etc/nginx/conf.d/oss.gogenex.com.conf
- 反代配置:oss.gogenex.com → localhost:9100
- client_max_body_size 500m(支持大文件 APK/IPA 上传)
- proxy_request_buffering off(流式上传,不缓冲至磁盘)
- Let's Encrypt SSL 证书已签发
backend/docker-compose.yml
- MINIO_ENDPOINT: 192.168.1.200 → oss.gogenex.com
- MINIO_PORT: 9100 → 443
- 新增 MINIO_USE_SSL=true
infrastructure/minio/docker-compose.yml
- 补充域名访问注释说明
服务器现状:
admin-service 已重启,MinIO 通过 https://oss.gogenex.com 访问。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:16:51 -08:00
hailin
3b60fed078
feat(infra): migrate MinIO object storage to gateway server
...
将 MinIO 对象存储从内网服务器(192.168.1.222)迁移至入口网关服务器
(192.168.1.200 / 14.215.128.96),作为独立基础设施部署。
变更内容:
backend/docker-compose.yml
- 移除 minio 和 minio-init 服务(不再随后端栈启动)
- admin-service 的 MINIO_ENDPOINT 改为 192.168.1.200,端口改为 9100
- 移除 admin-service 对 minio 服务的 depends_on 依赖
- 删除 minio_data docker volume 声明
infrastructure/minio/docker-compose.yml(新增)
- MinIO 独立部署配置
- S3 API : 9100(映射容器内 9000)
- Console : 9101(映射容器内 9001)
- 数据持久化到宿主机 /data/minio
infrastructure/minio/deploy.sh(新增)
- 支持 up / down / status / init / logs 命令
- up 时自动创建全部 7 个 bucket:
app-releases, kyc-documents, coupon-images,
issuer-documents, sar-reports, avatars, exports
- coupon-images / avatars 设为匿名可下载
部署说明:
# 在网关服务器上首次部署
cd infrastructure/minio && ./deploy.sh up
# 查看状态
./deploy.sh status
服务器现状:
genex-minio 已在 14.215.128.96 上运行,所有 bucket 已初始化。
admin-service 已重启并指向新地址,文件上传恢复正常。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 00:03:01 -08:00
hailin
9ed0d7e739
fix(kong): revert unnecessary KONG_NGINX_PROXY_PROXY_REQUEST_BUFFERING env var
...
Root cause was proxy_request_buffering off in Nginx gateway (already removed).
Kong should use default settings, matching IT0 reference architecture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 18:48:27 -08:00
hailin
19ad0d39fe
fix(kong): disable request buffering for large file uploads
...
Add KONG_NGINX_PROXY_PROXY_REQUEST_BUFFERING=off so Kong streams
the request body to admin-service without buffering to disk.
Root cause: Nginx streams (proxy_request_buffering off) → Kong buffers
to disk → Kong returns 400 with empty body when forwarding to upstream.
Fix: Kong also streams, matching Nginx's streaming behavior end-to-end.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 12:00:30 -08:00
hailin
5d9e1f7d06
fix(dashboard): handle nested API response shapes for trades and system-health
...
Backend wraps data in extra layer:
system-health → {code:0, data:{services:[...]}}
realtime-trades → {code:0, data:{items:[...], total:N}}
HttpClient strips outer data but leaves inner object.
Fix: type as {services/items} and access nested arrays.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:34:49 -08:00
hailin
45a69491d7
fix(admin-web): resolve ESLint CA boundary violations blocking build
...
- auth.store: eslint-disable with explicit comment for intentional infra access
(session orchestration is a designated cross-layer boundary)
- Add auth.use-cases.ts (LoginUseCase / LogoutUseCase) for use by views/hooks
- Fix no-explicit-any in AppVersionManagementPage (use unknown + type assertion)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:29:04 -08:00
hailin
81050767da
feat(admin-web): add ESLint flat config with Clean Architecture layer boundary enforcement
...
- eslint.config.mjs: ESLint 9 flat config with per-layer no-restricted-imports rules
- Domain: no outward deps; Infrastructure: domain only; Application: domain+infra;
Store: domain only; Presentation: no direct infra access
- Fix no-explicit-any in use-upload.ts (use unknown + type assertion)
- Add lint:boundaries npm script for CI enforcement
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:23:00 -08:00
hailin
e92059fc75
refactor(admin-web): add Presentation hooks layer for app-versions
...
- useVersionList: React Query + Use Case, select guard, invalidate helper
- useVersionMutations: toggle/delete wrapped with onSuccess callback
- useUpload: parse+upload flow extracted from modal component
- AppVersionManagementPage: purely declarative, zero business logic in JSX
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:17:26 -08:00
hailin
3765e8e6b1
refactor(admin-web): strict Clean Architecture for app-versions feature
...
Domain → Infrastructure → Application (Use Cases) → Presentation
- Domain: fix AppVersion entity fields; add IVersionRepository interface
- Infrastructure: VersionRepository implements IVersionRepository via HttpClient
- Application: 6 Use Case classes (ListVersions/Parse/Upload/Update/Toggle/Delete)
- Presentation: RTK version.slice (filters/modal state) + Zustand upload.store (form state)
- Page: zero direct apiClient calls; React Query queryFn calls use cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:11:56 -08:00
hailin
dca2031a38
fix(http-client): delete Content-Type in request interceptor when data is FormData
...
Instance-level default Content-Type: application/json was overriding
browser's auto-generated multipart/form-data boundary. Remove it for
FormData so browser sets correct Content-Type with boundary.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:03:00 -08:00
hailin
fc85983b43
fix(upload): remove explicit Content-Type header so browser sets multipart boundary
...
Without boundary multer receives undefined file. Also add guards in
backend parse/upload to avoid crash if file is missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:56:58 -08:00
hailin
30393c2867
fix(admin-web): guard versions list with Array.isArray + bump parse timeout to 300s
...
- Prevent TypeError if useApi returns non-array shape
- Add HttpClient.get logging to trace raw vs unwrapped response
- Parse timeout: 120s → 300s (matches upload, avoids timeout on large files)
- Show hint for large files (>30MB) during parse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:48:29 -08:00
hailin
e011eacbe6
fix(admin-web): add Content-Type multipart/form-data to parse and upload calls
...
Same pattern as rwadurian mobile-upgrade version-repository-impl.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:22:09 -08:00
hailin
07c171ce22
fix(admin-web): auto token refresh + restore APK parse with warnings
...
- auth.store: persist refreshToken alongside accessToken
- http.client: on 401, auto-refresh token and retry original request
with mutex lock to prevent concurrent refresh calls; only redirect
to /login if refresh itself fails
- upload modal: restore auto-parse on file select; show warning if
parse fails; add console logs for debugging; fix button disabled
during parsing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:18:31 -08:00
hailin
9a40769e0d
fix(admin-web): remove double-upload on app version page
...
- Remove auto-parse on file select (was uploading 48MB twice, took 100+ sec)
- Backend /upload already parses APK internally, version fields are now optional
- Show file name + size after selection
- Show progress hint during upload
- Better error extraction from API response
- Clear error when new file is selected
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 10:11:07 -08:00
hailin
4309e9e645
fix(admin-web): remove manual Content-Type header for multipart upload
...
Manually setting Content-Type: multipart/form-data without the boundary
causes the server to reject the request. Axios automatically sets the
correct header with boundary when FormData is passed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:53:52 -08:00
hailin
adda5e04d7
fix(admin-service): 修正 ManifestParser 调用方式 — 直接传原始 Buffer
...
ManifestParser(buffer) 内部自带 BinaryXmlParser,无需先调用 BinaryXmlParser.parse()
再把结果传入 ManifestParser,否则导致 readUInt16LE is not a function。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:31:08 -08:00
hailin
276eda2a84
fix(admin-service): 改用 yauzl.fromBuffer 解析 APK,支持 V2/V3 签名格式
...
yauzl.open() 在 APK V2/V3 签名包上报 'end of central directory record
signature not found',因为签名块会挤移 EOCD 位置。改为直接调用
yauzl.fromBuffer() + adbkit-apkreader 内部 BinaryXmlParser/ManifestParser
直接从内存解析 AndroidManifest.xml,兼容所有签名格式,且无需临时文件。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:18:56 -08:00
hailin
e00cbb71c5
fix(admin-service): 修复 APK 解析失败 — 写入临时文件后再解析
...
adbkit-apkreader v3 底层使用 yauzl.open(),只接受文件路径,
传入 Buffer 会被当成文件路径字符串导致 ENOENT。
改为先将 Buffer 写入 os.tmpdir() 临时文件,解析完成后在 finally 中删除。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 09:03:14 -08:00
hailin
4369aecf60
fix: 修复上传版本500错误 + 优化App冷启动通知请求
...
- fix(admin-service): versionCode 兜底从 Date.now() 改为 1,避免超出 PostgreSQL integer 范围
- fix(genex-mobile): NotificationBadgeManager 加登录检查,未登录跳过API请求
- fix(genex-mobile): 将通知徽章初始化移至首帧后执行,消除冷启动DNS竞争
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 08:48:09 -08:00
hailin
ce9cc5b72e
feat(telemetry): 完整实现 presence-service 迁移 + Flutter 遥测接入
...
## 后端 (backend/services/telemetry-service)
### 修复骨架中的关键缺陷
- 创建本地 JwtAuthGuard / AdminGuard,替换不存在的 @genex/common 包
- 修复 req.user.sub → req.user.id(与 JwtStrategy.validate() 返回值对齐)
- 重写 Dockerfile,移除对不存在的 packages/common 目录的引用
- 更新 telemetry.module.ts,将 JwtAuthGuard / AdminGuard 注册为 providers
- admin-telemetry.controller.ts 改用本地 AdminGuard(检查 role === 'admin')
### 新增功能
- GET /api/v1/telemetry/config:公开配置下发接口
Flutter TelemetryConfig.syncFromRemote() 每小时拉取,返回全局开关、采样率、
心跳间隔等配置,所有字段支持环境变量覆盖(TELEMETRY_GLOBAL_ENABLED 等)
- 设备字段(deviceBrand/deviceModel/deviceOs/appVersion/locale)提升为顶层列
BatchEventsDto / TelemetryEvent 实体 / recordEvents() 全链路补齐
原因:JSONB 内字段无法走 B-tree 索引,千万级数据分组查询需独立列
### DB 迁移
- 050_add_device_fields_to_telemetry_events.sql:为已有 telemetry_events 表
新增 5 个设备字段列和 device_brand / app_version 索引
### docker-compose 环境变量
- telemetry-service 新增 TELEMETRY_GLOBAL_ENABLED / SAMPLING_RATE /
HEARTBEAT_INTERVAL / CONFIG_VERSION 环境变量,支持生产环境热调整
## 前端
### genex-mobile (Flutter 消费者端)
- 复制 lib/core/telemetry/ 模块(11个文件):TelemetryService、
HeartbeatService、SessionManager、TelemetryUploader、TelemetryStorage 等
- 修正 API 路径:presence/heartbeat → telemetry/heartbeat,
analytics/events → telemetry/events
- pubspec.yaml 新增依赖:uuid ^4.3.3、equatable ^2.0.5、device_info_plus ^10.1.0
- main.dart:initState 首帧回调初始化 TelemetryService(需 BuildContext 采集设备信息)
已登录时自动注入 accessToken
- auth_service.dart:_setAuth() 登录成功后注入 userId + accessToken;
_clearAuth() 退出时清除(同时覆盖 Token 过期自动清除场景)
### admin-app (Flutter 发行方控制台)
- 复制 lib/core/telemetry/ 模块(同上)
- pubspec.yaml 新增依赖:uuid、equatable、device_info_plus、shared_preferences
- IssuerLoginPage:initState 首帧初始化 + 登录成功后注入 userId/token
使用 api.gogenex.cn(与 UpdateService 域名一致)
- settings_page.dart:退出登录时调用 clearUserId() + clearAccessToken()
## 架构说明
- 在线人数:Redis Sorted Set (genex:presence:online),心跳 60s/次,180s 窗口判定在线
- DAU:app_session_start 事件写入 telemetry_events,每天凌晨 1 点聚合到
daily_active_stats 表,同时每小时滚动更新当日 DAU
- 设备字段采用 Amplitude 风格:前端本地队列存 properties 内,
toServerJson() 上传时自动提升为顶层字段,后端写入独立索引列
- 心跳需要 JWT 认证(未登录用户自动跳过,不报错)
- 遥测完全异步,任何失败只打 debug 日志,不影响主流程
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 23:33:13 -08:00
hailin
a6aeb8799e
fix(ai-service): use hardcoded JWT_ACCESS_SECRET matching other services
2026-03-05 18:35:28 -08:00
hailin
f1f01314da
fix(ai-service): add JWT_ACCESS_SECRET env var to docker-compose
2026-03-05 18:28:13 -08:00
hailin
e9e875ac07
fix(user/ai-service): fix GET /users/kyc route conflict + add passport-jwt to ai-service
2026-03-05 18:21:31 -08:00
hailin
8fdc4e9365
fix(ai-service): add JwtStrategy — resolves Unknown authentication strategy jwt error
2026-03-05 17:35:39 -08:00
hailin
8f3d0f5d17
fix: comprehensive API compatibility fixes across issuer/user/ai services and Flutter
...
Backend:
- issuer-service: fix credit-metric entity column names to match DB schema
(breakage_ratio, market_tenure_months, user_satisfaction, computed_score, etc.)
- issuer-service: add page/limit to ListStoresQueryDto and ListEmployeesQueryDto
- issuer-service: add period field to ReconciliationQueryDto
- issuer-service: fix IssuerStatsController to use req.user.id→issuerId lookup
- issuer-service: add analytics stubs (users/demographics/repurchase/ai-insights)
- issuer-service: add issuers/me/coupons endpoint
- user-service: add GET /users/payment-methods stub before /:id route
- ai-service: add GET /ai/sessions/current/messages stub endpoint
Flutter:
- genex-mobile: fix /users/kyc/status → /users/kyc
- genex-mobile: fix announcements offset→page param
- genex-mobile: fix trading paths (/trading/* → /trades/*)
- admin-app: fix announcements offset→page param
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 09:29:44 -08:00
hailin
56cf021cc8
fix(user-service): add /wallet/balance alias endpoint
...
Flutter wallet_provider.dart calls /api/v1/wallet/balance but the
controller only had GET /wallet (root). Adding /wallet/balance alias.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 07:39:18 -08:00
hailin
6f70d7a2c2
fix(issuer/user): fix TypeORM databaseName error in coupon join + wallet frozen column name
...
- Replace leftJoinAndMapOne(Issuer entity) with two-step query to avoid TypeORM
metadata lookup error ('Cannot read databaseName of undefined')
- Extract unique issuer IDs from coupon results, then batch-fetch issuers and attach
- Fix wallet entity: map frozenBalance to 'frozen' column (not 'frozen_balance')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 06:53:09 -08:00
hailin
fa512dd2eb
fix(auth): JwtStrategy.validate() return id not sub
...
All 6 service strategies were returning { sub } but controllers use req.user.id.
Change return value from { sub: payload.sub } to { id: payload.sub } so that
req.user.id resolves correctly in all protected controllers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:52:53 -08:00
hailin
a1b134ddf6
fix(notification-service): add JWT_ACCESS_SECRET env var to docker-compose
...
notification-service was missing JWT_ACCESS_SECRET, causing it to use the
fallback 'dev-access-secret' instead of 'dev-access-secret-change-in-production'.
This made all auth-protected endpoints return 401, triggering logout in the app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:47:02 -08:00
hailin
3180114469
fix(admin-app): use correct desugar_jdk_libs artifact
...
Wrong: com.android.tools.build:desugaring-api:2.0.4
Correct: com.android.tools:desugar_jdk_libs:2.1.4 (matches genex-mobile)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:34:59 -08:00
hailin
4a2e6182be
fix(admin-app): enable core library desugaring for flutter_local_notifications
...
flutter_local_notifications requires coreLibraryDesugaringEnabled = true
and the desugaring-api dependency to build on Android.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:33:09 -08:00
hailin
89dbdb55b8
fix(admin-app): fix dynamic→int type errors in service files
...
- notification_service: cast total/unreadCount in NotificationListResponse,
cast count in getUnreadCount/getAnnouncementUnreadCount (Future<int>)
- auth_service: fix unsafe (value ?? 300) as int → (value as int?) ?? 300
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:31:35 -08:00
hailin
af5aba8efe
fix(admin-app): fix 4 compile errors
...
- credit_page.dart: remove const from Padding containing context.t() call
- issuer_coupon_service/redemption_service/issuer_finance_service:
cast inner['total'] to int? to match named record return type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:23:39 -08:00
hailin
3d0223d10a
fix: add passport-jwt deps to 3 services + fix messages page UI
...
Backend:
- Add passport/passport-jwt/@types/passport-jwt to clearing-service,
compliance-service, notification-service package.json (missing deps
caused 'Cannot find module passport-jwt' build failure)
Flutter:
- MessagePage: automaticallyImplyLeading: false (no back btn on tab page)
- TabBar: isScrollable+tabAlignment.start to prevent 'Announcements' truncation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:15:00 -08:00
hailin
9a4c2c1c19
fix: add missing issuer-service JwtStrategy + fix home search bar overflow
...
- Add issuer-service/infrastructure/strategies/jwt.strategy.ts (was omitted
from previous commit, causing build failure)
- Wrap search hint Text in Expanded to prevent Row overflow
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:11:10 -08:00
hailin
5113975c72
fix(genex-mobile): 首页移除返回键并修复搜索栏溢出
...
SliverAppBar 添加 automaticallyImplyLeading: false,
防止 Tab 导航进入时显示返回键,并修复返回键占位导致的搜索栏 overflow 6px。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:02:34 -08:00
hailin
3b733462ce
fix(auth): add local JwtStrategy to all services, fix @genex/common import
...
6 services (user/issuer/clearing/compliance/notification/telemetry) were
missing JwtStrategy provider, causing 'Unknown authentication strategy jwt'
500 errors on all auth-protected endpoints.
- Create infrastructure/strategies/jwt.strategy.ts in each service
- Update each module to import from local path (not @genex/common)
- Revert @genex/common/index.ts strategy export (passport-jwt not in each
service's node_modules, causes runtime 'Cannot find module' error)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 23:00:39 -08:00
hailin
75ed31cc04
fix: 全服务注册 JwtStrategy + 修复微信 WxApi 未配置 crash
...
**后端**
- packages/common: 新增并导出 JwtStrategy(共享 Passport JWT 策略)
- 6 个服务模块(user/issuer/clearing/compliance/notification/telemetry)
均缺少 JwtStrategy provider,导致所有受保护接口返回 500
"Unknown authentication strategy jwt"
- 统一修复:各模块 providers 添加 JwtStrategy
**Flutter**
- welcome_page: _onWechatTap() 的 isWeChatInstalled 调用未设 WECHAT_APP_ID
时会抛出 PlatformException,catch 后降级为"未安装"提示
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 22:24:07 -08:00
hailin
6bdd8d1e19
fix(admin-service): 增加 multer fileSize 限制 500MB,支持 APK/IPA 上传
...
FileInterceptor upload/parse 均未设置文件大小限制,可能导致 multer 默认
内存保护触发。与 Nginx client_max_body_size 500m 保持一致。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:57:50 -08:00
hailin
0f07698262
fix(issuer-service): 修复 LifecyclePipeline 接口字段与 Coupon.couponType 类型错误
...
- LifecyclePipeline 接口字段与实际返回值对齐 (minted/listed/sold/inCirculation/redeemed/recalled)
- coupon.type → coupon.couponType (Coupon entity 字段名修正)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 21:20:36 -08:00
hailin
4957b2ef85
feat(genex-mobile): 重构为 Clean Architecture + Riverpod
...
- 添加 flutter_riverpod ^2.5.1 依赖
- 搭建 core/error/failures.dart 和 core/usecases/usecase.dart 基础骨架
- auth feature 完整 3 层重构:
- domain: AuthUser + AuthSession 实体, IAuthRepository 接口, 全套 UseCases
- data: AuthRepositoryImpl(Strangler Fig,委托给 AuthService 保留 token 刷新逻辑)
- presentation: AuthNotifier + authProvider + currentUserProvider + isAuthenticatedProvider
- 4 个 auth 页面升级为 ConsumerStatefulWidget,使用 ref.read(authProvider.notifier)
- main.dart: ProviderScope 包裹 GenexConsumerApp,改为 ConsumerStatefulWidget
- 桥接 AuthService ValueNotifier → Riverpod authProvider(会话过期自动导航)
- 12 个 feature 全部创建 Riverpod providers(FutureProvider/NotifierProvider)
- 修复 my_coupons_page.dart 中 EmptyState/StatusTags 缺少 BuildContext 的预存在错误
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 20:12:50 -08:00
hailin
332a8dafe8
fix(admin-web): 补回 AdminLayout useState import
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 19:20:05 -08:00
hailin
4feea2667c
refactor(admin-web): 实现 Clean Architecture + Zustand + Redux Toolkit
...
按要求重构架构,从扁平的 React Context + useState 升级为大厂标准模式:
Clean Architecture 分层:
domain/entities/ — 业务实体 (AdminUser/User/Issuer/AppVersion)
domain/repositories/ — Repository 接口(契约层)
infrastructure/http/ — HttpClient(替代旧 api-client.ts)
infrastructure/repositories/ — Repository 实现(AuthRepository/UserRepository)
状态管理(大厂混合模式):
Zustand useAuthStore — 轻量客户端状态:登录会话 + localStorage 持久化
Zustand useUIStore — UI 偏好:sidebar 折叠状态持久化
Redux uiSlice — 全局通知队列、globalLoading
Redux usersSlice — 用户列表筛选/分页 client state
React Query — 服务端数据 fetching/缓存(保留)
更新:
providers.tsx — 加入 Redux Provider,移除旧 AuthProvider
auth-context.tsx — 向下兼容层,re-export Zustand store
api-client.ts — 向下兼容层,re-export httpClient
AdminLayout.tsx — 使用 Zustand auth/ui store
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 19:18:20 -08:00
hailin
96dad278ea
fix(admin-web): 修复 React #310 — useState 必须在条件 return 前调用
...
根因:expandedKeys 的 useState 调用位于 "if (!isAuthenticated) return null"
之后,违反 Rules of Hooks。认证状态变化时 hooks 调用数量不一致,
React 报 #310 "Cannot update a component while rendering a different component"。
修复:将 activeKey 计算和 expandedKeys useState 全部移至条件 return 之前,
确保每次渲染 hooks 调用顺序完全一致。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 19:05:34 -08:00
hailin
300d55ff14
fix(admin-web): 部署版本守卫,彻底防止 stale bundle 崩溃
...
方案:
- next.config.ts 构建时注入 NEXT_PUBLIC_BUILD_TIME 时间戳
- 新增 /api/version 路由,返回服务器当前 build 时间戳
- DeployGuard 组件每 3 分钟轮询 /api/version,
发现版本变化立即 window.location.reload(),用户完全无感知
- global-error / admin error 边界仅做最后兜底(静默 reload)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 18:57:12 -08:00