hailin
c00c48c8bd
fix(notification): accept optional type query param to fix 400 error
...
Backend entity has no type column yet; whitelist the type field in DTO
so forbidNonWhitelisted doesn't reject requests from the mobile app.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:50:35 -08:00
hailin
825e619224
fix(referral): auto-create profile for legacy users on getMyInfo
...
Users registered before referral-service was deployed have no profile row.
getMyInfo() now auto-provisions a profile on first access instead of
throwing NotFoundException.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:36:58 -08:00
hailin
9e368cbf37
fix(mobile+backend): fix notification DNS, message page API bugs, add read-all
...
Android DNS fix (native_dio_adapter):
- Dart's getaddrinfo() fails on some Android 12+ devices when browser works
fine — root cause is Dart native socket using different DNS path than
Android Java stack; fix by using NativeAdapter (OkHttp) on Android
- Add native_dio_adapter ^1.5.1 to pubspec; enable in ApiClient for Android
Message page bugs (5 fixes):
- Fix offset->page: Flutter sent ?offset=0 but backend NotificationQueryDto
uses page/limit (1-based); now sends ?page=1&limit=50
- Fix announcement tab API: Tab 2 now calls /api/v1/announcements (separate
resource) instead of /api/v1/notifications?type=ANNOUNCEMENT
- Fix markAllAsRead routing: calls correct API based on active tab
- Fix markAsRead routing: announcement items use announcements API
- Fix detail navigation: passes NotificationItem as route argument
Backend notification-service:
- Add PUT /api/v1/notifications/read-all endpoint
- Add markAllReadByUserId() to repository interface + TypeORM implementation
- Add markAllRead() to NotificationService
i18n (4 languages): add time.justNow/minutesAgo/hoursAgo/daysAgo keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:15:03 -08:00
hailin
daf32eaef1
fix(admin-service): Dockerfile 预建 /app/uploads 并 chown node:node
...
Docker named volume 首次挂载时会继承镜像中目标目录的属主。
若目录不存在或属主为 root,容器以 node(1000) 运行时无写权限。
在 USER node 之前创建目录并设置正确属主即可解决。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 08:27:01 -08:00
hailin
7bb89b87ae
fix(admin-service): 本地磁盘存储 + Docker volume 解决 EACCES 权限问题
...
回退 MinIO 方案,改用本地磁盘存储(与 git 历史及 rwadurian 一致)。
根本原因:容器内无权在 ./uploads 创建目录(EACCES)。
修复方案:
- UPLOAD_DIR 默认改为 /app/uploads(明确绝对路径)
- docker-compose 添加命名卷 admin-uploads 挂载到 /app/uploads
- 挂载卷由 Docker 管理,容器拥有写权限
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 08:20:05 -08:00
hailin
b9d2393aa1
fix(admin-service): 将文件存储从本地磁盘改为 MinIO
...
原实现将 APK/IPA 写入容器内 ./uploads 目录,导致 EACCES 权限错误。
改为通过 MinIO SDK 上传到 oss.gogenex.com / app-releases bucket,
与 docker-compose 中已有的 MINIO_* 环境变量保持一致。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 08:15:42 -08:00
hailin
6236ff3632
fix(auth): 将 JWT access token 有效期从 15m 改为 24h
...
上传大文件(如 53.7MB APK/IPA)时,若 access token 在上传过程中
到期,服务端返回 401,导致前端被迫重传整个文件(极差 UX)。
实际业务场景下 15m 过短,统一改为 24h。
涉及文件:
- backend/services/auth-service/src/application/services/token.service.ts
- backend/services/auth-service/src/auth.module.ts
- backend/services/auth-service/.env.example
- backend/.env.example
- backend/docker-compose.yml(两处)
- backend/deploy.sh
- frontend/admin-web/src/views/compliance/IpoReadinessPage.tsx(移除废弃 insuranceData)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 08:06:37 -08:00
hailin
7c8b79161a
feat(upload): parse=upload+save+metadata, register=JSON only — no double upload
...
Previously the flow uploaded the 53MB file twice:
1. POST /parse → parse metadata (file discarded)
2. POST /upload → parse again + save (file sent again)
New flow — file sent exactly once:
1. POST /parse → upload file, save to disk, parse metadata
returns {versionName, versionCode, minSdkVersion, storageKey, fileSize, fileSha256}
2. POST /register → JSON only (no file), creates DB record using storageKey
Frontend:
- handleFileChange: async, immediately uploads to /parse with progress bar (0-100%)
- handleSubmit: calls /register with storageKey + form metadata (instant)
- Upload modal: real-time progress bar, "confirm" button disabled until parse complete
- Console logs at every step for debugging
Backend:
- POST /parse: saves file after parsing, returns storageKey in response
- POST /register: new endpoint, accepts JSON + storageKey, creates version record
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 07:19:28 -08:00
hailin
eca1490c72
debug(admin-service): add timing logs to parse and upload endpoints
...
Logs file size on receipt, parse duration, file save duration, and
total request time — to pinpoint where upload latency comes from.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 06:33:18 -08:00
hailin
a6cb7add60
fix(admin-service): align APK/IPA parser with RWADurian implementation
...
Replace fragile internal ManifestParser approach with RWADurian's proven pattern:
- APK: write buffer to temp file → ApkReader.open(path) → readManifest() → cleanup
- IPA: unzipper.Open.buffer() → Info.plist → bplist-parser with XML fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 06:08:47 -08:00
hailin
7933f3fe4a
perf(upload): replace MinIO presigned-URL flow with local-disk storage
...
Previously the APK/IPA upload required two full public-internet transfers:
1. POST /parse → browser → gateway → admin-service (full file, for metadata)
2. PUT presigned → browser → oss.gogenex.com / MinIO (full file, to store)
Now follows the same pattern as RWADurian admin-service:
- Single multipart POST /admin/versions/upload
- admin-service parses buffer in-memory (yauzl / unzipper)
- Saves to local disk (UPLOAD_DIR env, default ./uploads)
- Download served via existing GET /app/version/download/:id (streams local file)
Changes:
- file-storage.service.ts: drop minio dep, use fs/promises + crypto
- admin-version.controller.ts: POST upload now accepts multipart file,
removes GET presigned-url endpoint (no longer needed)
- version.repository.ts (frontend): single FormData POST, removes
three-step presigned-URL flow
Result: file crosses public internet once instead of twice.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 05:49:36 -08:00
hailin
b710d416d1
fix(admin-service): restore FileInterceptor imports for parse endpoint
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 04:44:04 -08:00
hailin
0f611cf8cd
feat(upload): presigned URL — browser uploads directly to MinIO
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 04:41:38 -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
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
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
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
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
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
828770add8
fix(alipay): 适配 tobias 5.x 新 auth API,后端生成签名 authString
...
tobias 3.x+ 移除了顶层函数 aliPayAuth(appId, scope),
改为需要后端预签名的 Tobias().auth(authString)。
变更:
- alipay.provider.ts: 新增 generateMobileAuthString(scope) 方法,
用 RSA2 私钥生成符合 Alipay SDK 格式的签名授权字符串
- auth.controller.ts: 新增 GET /auth/alipay/auth-string 接口
- pubspec.yaml: tobias ^3.0.0 → ^5.0.0
- auth_service.dart: 新增 getAlipayAuthString() 方法
- welcome_page.dart: 更新支付宝登录流程,先获取 authString 再调用 tobias
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 09:26:30 -08:00
hailin
91f06890a5
docs(auth): 完善三方登录模块注释 — 含申请步骤与算法说明
...
AlipayProvider:
- callGateway: 补充完整请求参数结构说明
- sign: 详细说明 RSA2 签名5步骤(字典序→拼接→SHA256WithRSA→Base64)
- chunkBase64: 说明 PEM 格式每64字符换行要求
AppleProvider:
- verifyIdentityToken: 详细说明 claims 验证逻辑和签名验证原理
- getAppleJWKS: 说明 JWKS 缓存策略和 Apple 密钥轮换机制
- jwkToPublicKeyPem: 说明 JWK→SPKI PEM 转换过程
- base64urlDecode: 说明 Base64URL 与标准 Base64 的区别
GoogleProvider:
- verifyIdToken: 说明 tokeninfo vs JWKS 本地验证的权衡,补充响应字段说明
welcome_page.dart:
- 类头注释: 补充平台配置要求和各登录方式申请前提
- _onAlipayTap: 补充 tobias v3 API 结构、result 格式解析、scope 含义、iOS/Android 配置要求
- _parseParam: 说明 URI query 解析的原因
- _onGoogleTap: 补充 signIn 返回 null 的含义、idToken 说明、Android 配置
- _onAppleTap: 补充 Apple 3 项重要限制(姓名/邮箱只在首次授权时返回)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 04:56:11 -08:00
hailin
e8e2a44fef
fix(auth-service): 修复 AlipayProvider TypeScript 类型错误 (unknown 类型断言)
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 04:47:01 -08:00
hailin
1fc0fcb95e
feat(auth): 支付宝 + Google + Apple 三方登录
...
后端 (auth-service):
- 新增 AlipayProvider (RSA2签名) + AlipayService + POST /auth/alipay
- 新增 GoogleProvider (tokeninfo验证) + GoogleService + POST /auth/google
- 新增 AppleProvider (JWKS验证ES256 JWT) + AppleService + POST /auth/apple
- SocialProvider 枚举新增 ALIPAY
- .env.example 补充三方登录申请步骤文档
Flutter (genex-mobile):
- pubspec.yaml: 新增 tobias / google_sign_in / sign_in_with_apple
- auth_service.dart: loginByAlipay / loginByGoogle / loginByApple
- welcome_page.dart: Android=微信+支付宝+Google, iOS=+Apple
- AndroidManifest: 添加支付宝包名查询
- Info.plist: 支付宝 URL Scheme + alipay/alipays queries
- i18n: 4 语言补充失败提示文案
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 04:40:31 -08:00
hailin
44de21a733
docs(auth): 完善微信登录模块注释 — 含申请移动应用完整步骤
...
wechat.provider.ts:
- 补充微信开放平台申请移动应用的完整流程(企业资质、创建应用、
获取 AppID/AppSecret、启用 unionid、获取 Android 签名 MD5)
- 说明常见 errcode 含义(40029/42003/40163)
- 安全注意事项(AppSecret 保密、服务端换 token、防重放建议)
wechat.service.ts:
- 完整业务流程注释(6步,含新老用户分支逻辑)
- unionid 优先策略原理(跨 App 唯一性,防重复注册)
- 每次登录同步微信信息说明
- 自动生成账号字段说明(nickname/email/phone/password/kycLevel)
.env.example:
- 微信开放平台申请步骤(注册/认证/创建/获取/启用 unionid)
- Android 签名 MD5 获取命令
- Flutter --dart-define 构建参数说明
- Universal Links 说明
WXEntryActivity.kt:
- 包名路径规范说明(applicationId vs namespace 区别)
- AndroidManifest 配置要求
- 回调未触发的排查步骤(签名/包名/debug包名)
- 获取 Android 签名 MD5 命令
main.dart:
- registerWxApi 传参说明(--dart-define 用法)
- 未配置 WECHAT_APP_ID 时的降级行为
- Universal Links 配置说明
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 04:04:26 -08:00
hailin
c9100d3262
fix(auth): 移除 wechat.service.ts 中对 bcrypt 的直接依赖
...
改用 Password VO 生成随机密码哈希,避免 TypeScript 找不到
bcrypt 类型声明的编译错误。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 03:52:17 -08:00
hailin
d68d48cb95
feat(auth): 微信登录 / 注册完整实现 — social_accounts + fluwx 全链路
...
后端:
- 新增 social_accounts 表 Entity/Repository/Migration (049)
- WechatProvider: code ↔ access_token / 获取用户信息 (native https)
- WechatService: unionid 优先查找 → 自动登录/注册 → 发布事件
- POST /auth/wechat 端点 (WechatLoginDto, referralCode 支持)
- auth.module.ts 注册 SocialAccount、WechatProvider、WechatService
Flutter (genex-mobile):
- pubspec.yaml: 添加 fluwx ^3.10.0
- main.dart: registerWxApi 初始化 (WECHAT_APP_ID via --dart-define)
- AuthService: loginByWechat(code, referralCode?, deviceInfo?)
- WelcomePage: 改为 StatefulWidget,监听 weChatResponseEventHandler
微信按钮触发 sendWeChatAuth,授权成功后自动登录 → /main
未安装微信 / 登录失败均有 SnackBar 提示
- 4语言 i18n: wechatNotInstalled / wechatLoginFailed
Android:
- AndroidManifest: WXEntryActivity + queries(com.tencent.mm)
- WXEntryActivity.kt: 继承 fluwx 提供的基类,无额外代码
- proguard-rules.pro: keep WeChat SDK 类
iOS:
- Info.plist: CFBundleURLTypes (wx${WECHAT_APP_ID}) + LSApplicationQueriesSchemes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 03:37:51 -08:00
hailin
ff558ab77f
docs(auth): 完善邮件注册模块注释与说明
...
为以下新增文件补充详细的文件头注释:
- EmailVerification entity: 验证流程、字段说明、清理策略
- EmailLog entity: 审计日志、状态枚举、provider 说明
- EmailCodeService: Redis 缓存层职责、Key 模式、降级机制
- IEmailProvider: 已实现 Provider 列表、切换逻辑、扩展说明
- IEmailVerificationRepository: 关键方法说明
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 03:20:42 -08:00
hailin
9473512530
feat(auth): 邮箱注册完整实现 — Gmail SMTP + 邮件验证码全链路
...
后端 (auth-service):
- 新增 EmailVerification / EmailLog 实体 + TypeORM 映射
- Email 值对象:格式校验、小写归一化、脱敏展示
- Gmail SMTP Provider (nodemailer) + ConsoleEmailProvider (dev)
- EmailCodeService:Redis 缓存快速路径,与 SmsCodeService 对称
- EmailService:sendCode/verifyCode + 日限额 + 业务规则校验
- 新增端点:POST /auth/email/send / register-email / login-email / reset-password-email
- EMAIL_ENABLED 环境变量切换真实/控制台发送
- 数据库迁移:048_create_email_verifications.sql
前端 (genex-mobile):
- AuthService 新增 sendEmailCode / registerByEmail / loginByEmail / resetPasswordByEmail
- RegisterPage 根据 isEmail 参数自动切换 SMS/Email API 调用
- WelcomePage 邮箱注册按钮传递 isEmail:true 参数
- i18n 新增 register.errorEmailRequired(4语种)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 02:27:43 -08:00
hailin
0ecf295c35
feat(referral): 推荐服务全链路实现
...
Backend:
- referral-service: 全新微服务 (NestJS + DDD + Clean Architecture)
- ReferralProfile 聚合根 (TypeORM entity)
- ReferralCode / ReferralChain 值对象
- 仓储接口 + 实现
- ReferralService 应用服务: 创建档案/验证码/查询直推
- UserRegisteredHandler: 订阅 genex.user.registered Kafka 事件
- REST: GET /referral/my, GET /referral/validate, GET /referral/direct
- 内网: POST /internal/referral/profiles
- 端口 3013 (外部 4013)
- auth-service: RegisterDto 增加 referralCode 字段
- auth-service: UserRegisteredEvent 携带 referralCode
- auth-service: EventPublisherService 实际接入 Kafka (之前是 stub)
- migrations: 047_create_referral_profiles.sql
- docker-compose: 新增 referral-service 服务块
Frontend (genex-mobile):
- 注册页添加推荐码输入框 (可选),输入时实时验证推荐码有效性
- AuthService.register() 增加 referralCode 参数
- AuthService.validateReferralCode() 新增方法
- i18n: zh_CN/zh_TW/en/ja 各新增 4 个推荐码相关 key
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 00:58:28 -08:00
hailin
ec0af6c47e
fix(auth-service): 密码登录时对手机号做 E.164 归一化
...
注册时手机号存储为 +8618926762721,但密码登录用原始输入
18926762721 查库,导致找不到用户。在 login() 中先尝试
Phone.create() 归一化,如果不是合法手机号则作为邮箱使用。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 00:23:04 -08:00
hailin
286e4d8886
fix(admin-service+mobile): 修复 OTA 下载三处 Bug
...
1. MinIO 内网 hostname 导致 redirect 失效
→ 改为 admin-service 直接流式代理传输文件(streamFile)
→ MinIO 仅绑 127.0.0.1:49000,不对外暴露
2. version_checker.dart 响应解析错误
→ API 返回 {"code":0,"data":{...}} 但代码误把外层 Map 当 VersionInfo
→ 现在正确提取 responseMap["data"] 内层对象
3. VersionInfo.fromJson 在 releaseDate=null 时崩溃
→ releaseDate 改为 nullable (DateTime?),null-safe 解析
→ 同步修复 version_checker.dart 拼接绝对 downloadUrl
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 22:19:10 -08:00
hailin
2cf90db0b1
fix(admin-service): 移除全局前缀排除规则 — 让 app/version 路由正确响应 /api/v1 前缀
...
Kong 以 strip_path=false 转发 /api/v1/app/version/check,但服务排除了该路径的
全局前缀,导致 404。去掉 exclude 后所有路由统一挂载在 /api/v1 下。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:39:32 -08:00
hailin
c6c434a07a
fix(update): 修复 app 版本更新检查路径 + 解决 MinIO presigned URL 24h 过期
...
Mobile 端:
- version_checker.dart: /api/app/... → /api/v1/app/... (与 Kong 路由匹配)
Backend (admin-service):
- AppVersion 实体新增 storage_key 字段(已执行 ALTER TABLE)
- FileStorageService: uploadFile 不再返回 presigned URL,只返回 objectName
- AdminVersionController: upload 后保存 storageKey,downloadUrl 设为
/api/v1/app/version/download/{id}(稳定 API 地址,不过期)
- AppVersionController.downloadVersion: storageKey 存在时每次请求动态
生成 presigned URL(1小时有效,只需够下载完成即可)
- AppVersionService.checkUpdate: 有 storageKey 的版本统一返回 API 下载地址
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:13:13 -08:00
hailin
04a55e3e12
fix(ui): 修复登录页中英混用 + 搜索栏溢出,更新品牌口号并支持后台配置
...
- admin-web 登录页底部 "Genex 券金融平台" → "Genex 管理后台",风格统一
- admin-web 顶部搜索栏改为弹性宽度(width:100%/maxWidth:320),修复窄屏溢出
- 全平台品牌口号统一更新为"让每一张券,自由流动"
覆盖:genex-mobile(4语言) / miniapp(3语言) / portal(zh-CN/en-US)
- backend admin-system.service: getConfig() 新增 brandConfig 字段(4语言口号)
- admin-web 系统配置页新增品牌配置卡片:支持4语言口号在线编辑并保存
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 20:41:37 -08:00
hailin
fcf49b5257
fix(admin-service): 注册 JwtStrategy 解决 "Unknown authentication strategy" 错误
...
admin-service 使用了 @genex/common 的 JwtAuthGuard 保护管理端接口,
但缺少对应的 Passport JWT Strategy 注册。新增轻量级 JwtStrategy,
仅验证 token 签名和提取 payload,不依赖 UserRepository。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 08:16:38 -08:00
hailin
6f87be4454
fix(admin-service): 修复公开 API 路由前缀排除规则
...
setGlobalPrefix exclude 应匹配 controller 路径而非完整 URL。
修复前: exclude: ['api/app/version/(.*)']
→ 不生效,公开 API 被加上 /api/v1 前缀
→ 移动端需访问 /api/v1/app/version/check (错误)
修复后: exclude: ['app/version/(.*)']
→ 正确排除 AppVersionController 的路由
→ 移动端访问 /api/app/version/check (无 /api/v1 前缀)
→ 管理端继续使用 /api/v1/admin/versions (不受影响)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 07:37:44 -08:00