From 04dbeb2ac67d9e1c6aa79e6d3171fdef299b6bcd Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 9 Oct 2026 21:23:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20=E4=BF=9D=E5=AD=98=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E9=83=A8=E7=BD=B2=E4=B8=8E=20HTTPS=20=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development-state.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/development-state.md b/docs/development-state.md index 78a4fb0..f0a47f0 100644 --- a/docs/development-state.md +++ b/docs/development-state.md @@ -2,12 +2,15 @@ 更新日期:2026-10-09。 -## CI 构建优化与 HTTPS(2026-10-09,进行中) +## CI 构建优化与实际部署(2026-10-09,完成) - PR #1 已正常合并至 master,提交 abcb1c99;触发 Actions run 38。未直接推送 master。 - Runner 下载 Docker 外部 frontend 约 14 MB 耗时数分钟,属于 Docker Hub 网络瓶颈。移除不必要的 docker/dockerfile:1 frontend 引用,改为 Runner 传源码、192.168.100.132 在独立 builds/ 目录执行完整容器检查与构建,复用已有依赖缓存;构建锁与发布锁分离,镜像本地打包,不跨主机传输大型镜像。 - 新增 PUBLIC_ORIGIN 配置,明确允许指定 HTTPS 公共域名,校验 Host 和 Origin,不盲信转发协议头;HTTPS 公共域名启用 Secure Cookie。配置与 handler 验证、完整容器内 Go race(独立 PostgreSQL)已通过。服务器公共域名配置已保存,原凭据与主密钥保留。 -- Nginx 实际不在部署主机,用户明确自行配置;已提供 veyra.sundynix.cn 完整代理配置,目标为 192.168.100.132:8080。是否生效须实际验证,不声称已经配置。 +- Nginx 实际不在部署主机,用户明确自行配置;已提供完整代理配置,目标为 192.168.100.132:8080。现已通过 https://veyra.sundynix.cn 验证实际代理生效:页面、healthz、readyz 均 200,管理员登录/me/退出均 200,Cookie Secure=true。 +- 优化提交 ac936d2 经 PR #2 正常合并,master 发布提交为 825d049e325b782d742760a466de1d00a9791ca5。Actions run 39(界面 #5)verify/deploy 均成功,首次建立正式 CI 缓存总耗时 10m7s;旧 run 38 已取消,不会覆盖新版本。后续复用 Docker npm/Go 模块层与 veyra-go-test-cache,尚未实测后续部署耗时,不承诺固定分钟数。 +- 192.168.100.132 上 veyra-app-1 已启动并 healthy,.current-image 指向上述完整 SHA,部署目录与日志目录正确;未影响其他既有服务。当前服务仍为 APP_ENV=local 的开发验证部署,产品正式认证与远程推送等待办不变。 +- 本机仍在 dev;此次通过 PR 合并,没有直接提交或推送 master。交接事实更新只提交 dev,不额外触发 master 部署。 ## 最新 CI/CD 配置(2026-10-09) @@ -20,7 +23,7 @@ - 发布脚本加锁,保留服务器配置,先建库/迁移,再等待 /readyz;健康失败回滚上一镜像与 Compose,迁移失败不替换原运行服务。数据库迁移不自动回退。脚本测试覆盖成功、迁移失败、健康失败、损坏包、缺配置与首次配置保留,共 6 项。 - 本地脚本 6 项、Go race(真实 PostgreSQL 临时 schema)、go vet、gofmt、Go 构建、管理端类型/构建/embed、App 12 项测试/类型检查/双平台导出通过;工作流 YAML、内嵌 shell 与服务器 Compose config --quiet 已校验。服务器完整 Docker 检查与最终运行镜像构建也已通过,包含容器内发布测试和独立 PostgreSQL 17 的全部 Go race 测试。服务器 proxy.golang.org 超时、goproxy.cn 返回 200,构建阶段已切换 goproxy.cn 并验证成功,不关闭 checksum。 - 运行镜像已在隔离网络、只读文件系统、临时日志目录中启动验证:内嵌管理端 200,缺少数据库/Redis 时 readyz 正确返回 503,Python 3.11.2 可执行。验证容器已清理,没有连接开发数据库或启动实际部署实例。 -- 本机 Docker daemon 未运行,使用指定部署主机的独立 /tmp/veyra-verify-* 构建目录验证。CI 文件在 dev,通过 PR 进入 master 后才触发首次自动部署。自动筛选与股票工作台已在 dev 提交 57882ff;后续 CI 配置提交不直接操作 master。 +- 本机 Docker daemon 未运行,使用指定部署主机的独立 /tmp/veyra-verify-* 构建目录验证。CI 文件已通过 PR 进入 master 并成功触发部署,实际结果见本文件最新记录。自动筛选与股票工作台已在 dev 提交 57882ff;后续 CI 配置提交不直接操作 master。 ## 最新开发状态(2026-10-09,管理端重排与真实候选联调) -- 2.54.0 From 86f1e7a897bfa7797d73c075b8da54a881976d7e Mon Sep 17 00:00:00 2001 From: Blizzard Date: Fri, 9 Oct 2026 22:33:38 +0800 Subject: [PATCH 2/2] feat: integrate AI candidate review and automatic publication --- .gitignore | 10 + README.md | 6 +- apps/mobile/.eas/build/ios-unsigned.yml | 19 + apps/mobile/.env.example | 2 +- apps/mobile/README.md | 41 +- apps/mobile/app.json | 42 +- apps/mobile/app/(tabs)/index.tsx | 2 +- apps/mobile/app/(tabs)/opportunities.tsx | 2 +- apps/mobile/app/(tabs)/portfolio.tsx | 2 +- apps/mobile/app/candidates/[id].tsx | 2 +- apps/mobile/app/market.tsx | 2 +- apps/mobile/app/notifications.tsx | 2 +- apps/mobile/app/plans/create.tsx | 2 +- apps/mobile/eas.json | 19 + apps/mobile/package-lock.json | 3376 ++++++----------- apps/mobile/package.json | 49 +- apps/mobile/scripts/build-ios-unsigned.sh | 17 + apps/mobile/src/components/ui.tsx | 2 +- apps/mobile/src/lib/candidates.ts | 2 +- apps/mobile/tests/native-text.test.ts | 32 + apps/mobile/tsconfig.json | 5 +- docs/development-state.md | 22 + .../000015_candidate_ai_analysis.sql | 25 + internal/handler/candidate_analysis.go | 65 + internal/handler/identity.go | 1 + internal/handler/provider.go | 22 + internal/handler/router.go | 1 + .../integration/candidate_analysis_test.go | 145 + .../development_user_credential_test.go | 62 + internal/integration/identity_account_test.go | 2 + internal/llm/client.go | 124 + internal/llm/client_test.go | 64 + internal/model/candidate.go | 1 + internal/model/candidate_analysis.go | 43 + internal/repository/candidate.go | 21 +- internal/repository/candidate_analysis.go | 211 ++ internal/repository/identity.go | 12 +- internal/service/candidate_analysis.go | 201 + internal/service/candidate_analysis_test.go | 21 + .../service/development_user_credential.go | 43 + internal/service/provider.go | 20 +- main.go | 49 +- web/admin/src/components/AIReviewPanel.tsx | 17 + web/admin/src/components/CandidatesPanel.tsx | 4 +- web/admin/src/components/ProvidersPanel.tsx | 8 +- web/admin/src/lib/candidates.ts | 2 +- 46 files changed, 2460 insertions(+), 2362 deletions(-) create mode 100644 apps/mobile/.eas/build/ios-unsigned.yml create mode 100644 apps/mobile/eas.json create mode 100644 apps/mobile/scripts/build-ios-unsigned.sh create mode 100644 apps/mobile/tests/native-text.test.ts create mode 100644 internal/database/migrations/000015_candidate_ai_analysis.sql create mode 100644 internal/handler/candidate_analysis.go create mode 100644 internal/integration/candidate_analysis_test.go create mode 100644 internal/integration/development_user_credential_test.go create mode 100644 internal/llm/client.go create mode 100644 internal/llm/client_test.go create mode 100644 internal/model/candidate_analysis.go create mode 100644 internal/repository/candidate_analysis.go create mode 100644 internal/service/candidate_analysis.go create mode 100644 internal/service/candidate_analysis_test.go create mode 100644 internal/service/development_user_credential.go create mode 100644 web/admin/src/components/AIReviewPanel.tsx diff --git a/.gitignore b/.gitignore index 7285e4a..e26764e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,13 @@ expo-env.d.ts # CI 临时镜像归档和 Python 缓存 /image.tar.gz* **/__pycache__/ + +# Expo 按配置生成的原生工程;个人签名不入库 +/apps/mobile/ios/ +/apps/mobile/android/ + +# Native package outputs and local signing material +apps/mobile/artifacts/ +apps/mobile/credentials.json +*.p12 +*.mobileprovision diff --git a/README.md b/README.md index 65e1745..bc24d2e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ go build -trimpath -o bin/veyra . go run . -command admin-set -previous-login <原管理员登录名> ``` -已有登录名不变时可省略 `-previous-login`。此命令仅限 local/test,保留原管理员 ID、以 bcrypt 保存新密码并撤销旧会话;登录名冲突时全部回滚。管理员密码为 8–72 字节,App 开发用户仍为 12–72 字节。 +已有登录名不变时可省略 `-previous-login`。此命令仅限 local/test,保留原管理员 ID、以 bcrypt 保存新密码并撤销旧会话;登录名冲突时全部回滚。管理员密码为 8–72 字节,App 用户创建时为 12–72 字节。已有 App 测试用户可在 local/test 环境更新 `DEV_USER_LOGIN` / `DEV_USER_PASSWORD` 后执行 `go run . -command user-set -previous-login <原登录名>`;测试维护命令允许 4–72 字节密码,保留身份和账户、撤销旧会话,不能修改管理员。 ### App 开发 @@ -173,3 +173,7 @@ Worker 每轮最多处理 20 个目标,完成后间隔 5 分钟再次检查。 ## CI/CD 自动部署 开发使用 `dev`,通过 PR 合并进入受保护的 `master`;仅 `master` push 触发 CI/CD。Gitea Runner 验证后,通过 SSH 部署内嵌管理端的 Go 镜像至 `192.168.100.132:/home/workspace/sundynix-veyra`,执行版本化迁移、依赖就绪检查与应用回滚。详见 [部署说明](deploy/README.md)。 + +### AI candidate review + +Configure and enable model_primary and model_review in the admin console. The review route can copy the primary configuration without exposing its encrypted key. Enable AI review and optional automatic publishing; two validated model conclusions and existing server rules must pass before publication. Manual mode keeps AI opinions for review. Jobs are versioned, leased for five minutes and retry up to three times with a five-minute delay. Model calls never change prices, reserve user funds or create trades. This remains a local/test simulation capability. diff --git a/apps/mobile/.eas/build/ios-unsigned.yml b/apps/mobile/.eas/build/ios-unsigned.yml new file mode 100644 index 0000000..7271f03 --- /dev/null +++ b/apps/mobile/.eas/build/ios-unsigned.yml @@ -0,0 +1,19 @@ +build: + name: Veyra iPhone unsigned IPA for personal signing + steps: + - eas/checkout + - eas/install_node_modules + - run: + name: Validate App + command: npm run typecheck && npm run test + - eas/prebuild + - run: + name: Install CocoaPods dependencies + command: cd ios && pod install + - run: + name: Build and package unsigned device app + command: bash scripts/build-ios-unsigned.sh + - eas/upload_artifact: + inputs: + type: application-archive + path: artifacts/Veyra-unsigned.ipa diff --git a/apps/mobile/.env.example b/apps/mobile/.env.example index c9c3eed..56e59c1 100644 --- a/apps/mobile/.env.example +++ b/apps/mobile/.env.example @@ -1,4 +1,4 @@ # 仅公开 API 地址,禁止在 EXPO_PUBLIC_* 中存放数据库/Redis/供应商凭据。 # iOS 模拟器可用 http://127.0.0.1:8080,Android 模拟器使用 http://10.0.2.2:8080。 # 真机填写开发电脑的内网 IP,后端 HTTP_ADDR 需允许局域网访问。 -EXPO_PUBLIC_API_URL= +EXPO_PUBLIC_API_URL=https://veyra.sundynix.cn diff --git a/apps/mobile/README.md b/apps/mobile/README.md index 04a42a7..c557204 100644 --- a/apps/mobile/README.md +++ b/apps/mobile/README.md @@ -2,7 +2,7 @@ React Native + Expo + TypeScript,白底、石墨黑与柔紫。源码路由采用 Expo Router,服务端查询使用 TanStack Query,当前账户使用 Zustand。 -当前已接通开发登录、原生安全会话恢复/过期/退出、账户查询、创建和切换。五个主导航与提醒入口已建立;候选、计划、成交、持仓明细、通知、偏好与复盘业务尚未开放,页面明确展示实际状态,不展示原型股票或预设收益。 +当前已接通开发登录、安全会话、账户、候选证据、计划预算、成交及冲正、持仓、站内通知和复盘。偏好、正式认证与远程推送仍待开发。未完成 iPhone 真机验证,尚未生成可安装 IPA。 ## 开发运行 @@ -32,6 +32,43 @@ npm --prefix apps/mobile run build 类型检查与测试命令 检查 TypeScript 与 API/金额边界测试;构建命令 导出 iOS 和 Android Metro/Hermes bundle 到本目录 `dist`,验证路由和原生依赖可打包。导出包不等于 APK、IPA,也不代表已完成真机或商店验证;后续原生包需通过开发构建或 EAS/本地编译生成。 -依赖按 [Expo SDK 配套版本](https://docs.expo.dev/versions/v55.0.0/)选取,原生模块通过 `expo install` 安装。当前锁定 SDK 55 / React Native 0.83.10 / React 19.2.4。管理端与 App 统一使用 React 19.2.4,分别独立安装依赖。SDK 55 默认建议 React 19.2.0;这里显式固定同 minor 的补丁版本并按 [Expo 版本比较配置](https://docs.expo.dev/versions/latest/config/package-json/#installexclude)配置 expo.install.exclude,仅排除 React 的建议版本比较。React Native 0.83.10 的公开 peer 约束是 ^19.2.0;继续执行类型、客户端、双平台 bundle 与依赖重复检查,真机验证仍待完成。App 的 package.json、锁文件和依赖全部位于本目录,Metro 与 Babel 使用本目录的 Expo Router。 +依赖已按 Expo SDK 57 配套更新:expo 57.0.27、React Native 0.86.3、React 19.2.3。管理端依赖独立维护。类型检查、12 项测试、双平台 Hermes 导出、Expo Doctor 21/21 已通过;真机运行仍等待实际扫码验证。 会话使用 [Expo SecureStore](https://docs.expo.dev/versions/v55.0.0/sdk/securestore/),不会写进 Zustand、URL 或日志。退出撤销服务端会话;网络中断保留原始会话供恢复,401 或到期清理身份与查询缓存。旧会话的迟到响应不会污染新登录用户。切换账户会中断旧查询、清理账户详情缓存;金额使用十进制字符串与整数校验,不做浮点账本计算。 + +## iPhone 当前测试:Expo Go + +手机 Expo Go 57.0.9 支持 SDK 57,项目已匹配。不需要本机 Xcode,也不生成独立 IPA。Mac 与手机连接同一 Wi-Fi,启动 Metro 后用 iPhone 相机扫码,允许 Expo Go 本地网络访问。 + +```sh +REACT_NATIVE_PACKAGER_HOSTNAME="$(ipconfig getifaddr en0)" npm --prefix apps/mobile run start -- --go --lan --port 8081 +``` + +Expo Go 与电脑 CLI 必须登录同一个 Expo 账号。在 App 目录执行 `npx expo login --browser`,完成后重启 Metro;登录令牌由 CLI 本机保存,不加入 Git。 + +当前入口 `exp://192.168.100.2:8081`,二维码 `/tmp/veyra-expo57-qr.png`。电脑换网后重新获取 IP;使用 VPN 时显式指定真实局域网地址。App API 连接公开域名,但 Expo Go 仍需要本机 Metro 在线,不能当作独立安装包或域名直接下载。 + +## 后续独立安装(普通 Apple ID) + +当前公开 API 为 `https://veyra.sundynix.cn`,已写入环境模板和本机私有配置;地址通过配置读取,不写死到请求代码。App 使用 USER 身份,管理员不能登录。 + +普通 Apple ID 可以通过 Xcode Personal Team 在自己的 iPhone 上运行 App,不需要上架。免费设备签名有效期为 7 天,到期需要重新安装。参见 [Apple 开发者账号说明](https://developer.apple.com/help/account/basics/about-your-developer-account)。 + +1. Mac 从 App Store 安装完整 Xcode,打开并完成首次初始化,在 Xcode Settings → Accounts 登录自己的 Apple ID。 +2. 数据线连接 iPhone,信任电脑,按系统提示开启 Developer Mode。 +3. 在仓库根目录执行 `npm --prefix apps/mobile run ios:device`,选择手机和个人开发团队。使用 Release 配置,安装完成后无需电脑 Metro 持续运行;后端通过 HTTPS 域名连接。 +4. 若自动签名需要在 Xcode 中确认,在生成的 `apps/mobile/ios` 工程中选择目标,Signing & Capabilities 选择 Personal Team 并开启自动签名,再选择手机运行。 + +个人签名和生成的原生工程不提交。当前本机为 Intel / macOS 14.8.2,只有 Command Line Tools;完整 Xcode 还须符合 SDK 57 的系统要求。此路线尚未验证,当前先采用上述 Expo Go 测试。 + +Gitea Release 可以托管安装包,但下载 IPA 本身不会绕过 iOS 签名。tag 自动上传可安装 iOS 包仍需要 macOS/Xcode 构建环境与有效签名;现有 Linux 后端 CI 只检查 Hermes 导出,并未生成 IPA。当前采用 Expo Go 测试;tag 自动上传独立安装包尚未接入,不把 Hermes bundle 冒充 IPA。 + +## iPhone 安装包 + +Expo 云构建项目为 `@blizzard-zhang/veyra`。`ios-unsigned` 使用云端 Xcode 编译设备版本并嵌入 JS,生成 `Veyra-unsigned.ipa`,无需本机 Xcode或云端 Apple 签名凭据。该 IPA 未签名,不能直接从网页安装;普通 Apple ID 需要在个人签名工具中重签后安装,安装期限由 Apple 个人签名规则决定。`preview` 为有签名凭据时的内部安装构建,不上架。 + +```sh +npx eas-cli@24.12.1 build --platform ios --profile ios-unsigned +``` + +构建脚本必须在云端 macOS/Xcode 执行。Hermes 导出和模拟器 `.app` 均不是设备 IPA。不要将 `.env`、签名证书或后端配置上传到构建服务;首次实际云构建使用独立 App 源码目录上传。 diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 7423545..761dfb9 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -1,10 +1,36 @@ { - "expo":{ - "name":"见势 · Veyra","slug":"veyra","scheme":"veyra","version":"0.1.0","orientation":"portrait","userInterfaceStyle":"light", - "platforms":["ios","android"], - "ios":{"supportsTablet":true,"bundleIdentifier":"cn.sundynix.veyra"}, - "android":{"package":"cn.sundynix.veyra"}, - "plugins":["expo-router","expo-secure-store"], - "experiments":{"typedRoutes":true} - } + "expo": { + "name": "见势 · Veyra", + "slug": "veyra", + "scheme": "veyra", + "version": "0.1.0", + "orientation": "portrait", + "userInterfaceStyle": "light", + "platforms": [ + "ios", + "android" + ], + "ios": { + "supportsTablet": true, + "bundleIdentifier": "cn.sundynix.veyra" + }, + "android": { + "package": "cn.sundynix.veyra" + }, + "plugins": [ + "expo-router", + "expo-secure-store", + "expo-font" + ], + "experiments": { + "typedRoutes": true + }, + "extra": { + "router": {}, + "eas": { + "projectId": "b643afa5-a096-40ba-9fec-06ba1e645b41" + } + }, + "owner": "blizzard-zhang" + } } diff --git a/apps/mobile/app/(tabs)/index.tsx b/apps/mobile/app/(tabs)/index.tsx index 9fd7e84..4e14d4a 100644 --- a/apps/mobile/app/(tabs)/index.tsx +++ b/apps/mobile/app/(tabs)/index.tsx @@ -3,4 +3,4 @@ import {Text,View} from "react-native" import {AccountSummary} from "../../src/components/AccountSummary" import {Body,Button,Panel,Screen,Title,styles} from "../../src/components/ui" import {useSession} from "../../src/state/session" -export default function Home(){const {session}=useSession();return 见势 <Text style={{fontSize:18,fontWeight:"400"}}>· Veyra</Text>{notice&&

{notice}

}{error&&

{error}

}
{results.length===0&&

暂无 AI 任务。启用后自动处理有效待审核候选。

}{results.map(a=>
{a.analysis?.symbol??"候选分析任务"}{states[a.status]??a.status}

{a.message||"正在调用模型…"}

{a.analysis&&<>

分析:{a.analysis.summary}

风险:{a.analysis.risks}

}{a.review&&

复核:{a.review.summary}

}

{a.primary_model||"待调用"} / {a.review_model||"待调用"} · 尝试 {a.attempts} · {new Date(a.updated_at).toLocaleString("zh-CN")}

)}
+} diff --git a/web/admin/src/components/CandidatesPanel.tsx b/web/admin/src/components/CandidatesPanel.tsx index c27aa26..5c01fee 100644 --- a/web/admin/src/components/CandidatesPanel.tsx +++ b/web/admin/src/components/CandidatesPanel.tsx @@ -29,7 +29,7 @@ export function CandidatesPanel({onSessionExpired,initialCandidateID}:{onSession const set=(key:keyof CandidateContent,value:string)=>setContent(c=>({...c,[key]:value})) return
void refresh()}/> -
模拟候选

自动筛选形成队列,审核股票依据后发布。

+
模拟候选

自动筛选形成队列;AI 审核通过可自动发布,也可人工复核。

{error&&

{error}

} {editing&&{source?`创建第 ${source.revision+1} 次修订` : "录入观察候选"}原修订保留。证据、反证与参考价格需完整填写;此表单为手工录入;自动筛选结果从策略中心进入待审核。
void save(e)} className="space-y-5">
{([["证券代码","symbol"],["证券名称","name"],["候选标题","title"]] as const).map(([label,key])=>)}
{([["观察逻辑","thesis"],["反证与风险","counter_evidence"],["失效条件","invalidation"]] as const).map(([label,key])=>