chore(repo): consolidate local env and clean root files

This commit is contained in:
yoyuzh
2026-04-06 23:50:56 +08:00
parent ed837f5ec9
commit 3afebbb338
21 changed files with 200 additions and 5105 deletions

31
.env.example Normal file
View File

@@ -0,0 +1,31 @@
# Copy to `.env` and fill real values before local backend runs or deploy work:
# cp .env.example .env
# Backend runtime
APP_JWT_SECRET="replace-with-at-least-32-bytes"
APP_ADMIN_USERNAMES=""
APP_AUTH_REGISTRATION_INVITE_CODE=""
# DogeCloud API credentials
YOYUZH_DOGECLOUD_API_BASE_URL="https://api.dogecloud.com"
YOYUZH_DOGECLOUD_API_ACCESS_KEY="replace-me"
YOYUZH_DOGECLOUD_API_SECRET_KEY="replace-me"
YOYUZH_DOGECLOUD_S3_REGION="automatic"
# Frontend / storage deploy scopes
YOYUZH_DOGECLOUD_FRONT_SCOPE="yoyuzh-front"
YOYUZH_DOGECLOUD_FRONT_TTL_SECONDS="3600"
YOYUZH_DOGECLOUD_FRONT_PREFIX=""
YOYUZH_DOGECLOUD_STORAGE_SCOPE="yoyuzh-files"
YOYUZH_DOGECLOUD_STORAGE_TTL_SECONDS="3600"
YOYUZH_DOGECLOUD_ANDROID_SCOPE="yoyuzh-files"
YOYUZH_ANDROID_RELEASE_PREFIX="android/releases"
# Server access and deploy metadata
YOYUZH_SERVER_HOST="replace-me"
YOYUZH_SERVER_USER="replace-me"
YOYUZH_SERVER_PASSWORD="replace-me"
YOYUZH_BACKEND_SYSTEMD_SERVICE="my-site-api.service"
YOYUZH_BACKEND_REMOTE_JAR_PATH="/opt/yoyuzh/yoyuzh-portal-backend.jar"
YOYUZH_BACKEND_REMOTE_ENV_FILE="/opt/yoyuzh/app.env"
YOYUZH_BACKEND_REMOTE_CONFIG="/opt/yoyuzh/application-prod.yml"

View File

@@ -1,39 +0,0 @@
# 复制本文件为 `.env.oss.local` 后再填写真实值:
# cp .env.oss.example .env.oss.local
#
# 发布命令:
# node scripts/deploy-front-oss.mjs
#
# 仅预览将要上传的文件:
# node scripts/deploy-front-oss.mjs --skip-build --dry-run
# 说明:文件名仍叫 `.env.oss.local`,但内容已经切换为多吉云临时密钥配置。
# 多吉云服务端 API AccessKey / SecretKey。
# 不要把真实值提交到 git。
YOYUZH_DOGECLOUD_API_ACCESS_KEY="YOUR_DOGECLOUD_ACCESS_KEY"
YOYUZH_DOGECLOUD_API_SECRET_KEY="YOUR_DOGECLOUD_SECRET_KEY"
# 可选:多吉云服务端 API 地址。
YOYUZH_DOGECLOUD_API_BASE_URL="https://api.dogecloud.com"
# 多吉云 S3 兼容区域。多吉云官方文档建议使用 automatic。
YOYUZH_DOGECLOUD_S3_REGION="automatic"
# 前端静态站点的逻辑桶名 / scope。
# 你的当前桶建议填写 `yoyuzh-front`。
YOYUZH_DOGECLOUD_FRONT_SCOPE="yoyuzh-front"
# 可选:前端发布拿临时密钥的有效期,单位秒。
YOYUZH_DOGECLOUD_FRONT_TTL_SECONDS="3600"
# 可选:上传到桶内的子目录。
# 为空表示直接上传到桶根目录。
YOYUZH_DOGECLOUD_FRONT_PREFIX=""
# Android APK 发布默认走文件桶;如需单独逻辑桶,可显式填写。
YOYUZH_DOGECLOUD_STORAGE_SCOPE="yoyuzh-files"
YOYUZH_DOGECLOUD_ANDROID_SCOPE="yoyuzh-files"
# APK 与元数据在文件桶中的发布前缀。
YOYUZH_ANDROID_RELEASE_PREFIX="android/releases"

View File

@@ -1,9 +0,0 @@
YOYUZH_DOGECLOUD_API_BASE_URL="https://api.dogecloud.com"
YOYUZH_DOGECLOUD_API_ACCESS_KEY="eb8e79012b435492"
YOYUZH_DOGECLOUD_API_SECRET_KEY="3b9f241e61762f382ab2b6f88b9b4345"
YOYUZH_DOGECLOUD_S3_REGION="automatic"
YOYUZH_DOGECLOUD_FRONT_SCOPE="yoyuzh-front"
YOYUZH_DOGECLOUD_FRONT_TTL_SECONDS="3600"
YOYUZH_DOGECLOUD_FRONT_PREFIX=""
YOYUZH_DOGECLOUD_STORAGE_SCOPE="yoyuzh-files"
YOYUZH_DOGECLOUD_STORAGE_TTL_SECONDS="3600"

3
.gitignore vendored
View File

@@ -19,6 +19,9 @@ frontend-dev.err.log
.vscode/ .vscode/
.idea/ .idea/
.gemini/ .gemini/
.env
.env.local
.env.*.local
.codex/* .codex/*
!.codex/config.toml !.codex/config.toml
!.codex/agents/ !.codex/agents/

View File

@@ -14,6 +14,7 @@ This repository is split across a Java backend, a Vite/React frontend, a small `
- `backend/`: Spring Boot 3.3.8, Java 17, Maven, domain packages under `com.yoyuzh.{auth,cqu,files,config,common}`. - `backend/`: Spring Boot 3.3.8, Java 17, Maven, domain packages under `com.yoyuzh.{auth,cqu,files,config,common}`.
- `front/`: Vite 6, React 19, TypeScript, Tailwind CSS v4, route/page code under `src/pages`, reusable UI under `src/components`, shared logic under `src/lib`. - `front/`: Vite 6, React 19, TypeScript, Tailwind CSS v4, route/page code under `src/pages`, reusable UI under `src/components`, shared logic under `src/lib`.
- `docs/`: currently contains implementation plans under `docs/superpowers/plans/`. - `docs/`: currently contains implementation plans under `docs/superpowers/plans/`.
- `docs/agents/`: supplementary agent / handoff docs. Keep root `AGENTS.md` as the entrypoint and put extra collaboration notes there instead of scattering more root files.
- `scripts/`: deployment, migration, smoke, and local startup helpers. - `scripts/`: deployment, migration, smoke, and local startup helpers.
## Command source of truth ## Command source of truth
@@ -120,7 +121,7 @@ Important:
## Repo-specific guardrails ## Repo-specific guardrails
- Do not run `npm` commands at the repository root. This repo has a root `package-lock.json` but no root `package.json`. - Do not run `npm` commands at the repository root. The repository root is not an application package; frontend commands belong under `front/`.
- Frontend API proxying is defined in `front/vite.config.ts`, with `VITE_BACKEND_URL` defaulting to `http://localhost:8080`. - Frontend API proxying is defined in `front/vite.config.ts`, with `VITE_BACKEND_URL` defaulting to `http://localhost:8080`.
- Backend local development behavior is split between `backend/src/main/resources/application.yml` and `application-dev.yml`; the `dev` profile uses H2 and mock CQU data. - Backend local development behavior is split between `backend/src/main/resources/application.yml` and `application-dev.yml`; the `dev` profile uses H2 and mock CQU data.
- Backend tests already exist under `backend/src/test/java/com/yoyuzh/...`; prefer adding or updating tests in the matching package. - Backend tests already exist under `backend/src/test/java/com/yoyuzh/...`; prefer adding or updating tests in the matching package.

147
CLAUDE.md
View File

@@ -1,138 +1,75 @@
# CLAUDE.md # CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. This file provides guidance to Claude Code and similar agents when working in this repository.
## Session Startup ## Session Startup
Read these files in order before planning, coding, reviewing, or deploying: Read these files in order before planning, coding, reviewing, or deploying:
1. `memory.md` — current project state and handoff context
2. `docs/architecture.md` — authoritative system-level source of truth 1. `memory.md`
3. `docs/api-reference.md` — backend endpoint and auth boundary reference 2. `docs/architecture.md`
4. `AGENTS.md` — role routing rules and hard guardrails 3. `docs/api-reference.md`
4. `AGENTS.md`
Use `docs/agents/handoff.md` only as supplemental background when the primary docs are not enough.
## Project Overview ## Project Overview
**yoyuzh.xyz** full-stack personal site with four functional areas: `yoyuzh.xyz` is a full-stack personal site focused on:
- Personal cloud drive (netdisk)
- P2P browser-to-browser file transfer (快传 / quick-transfer)
- Account system with invite-code registration
- Admin console
Frontend is hosted on Aliyun OSS (static). Backend runs as `my-site-api.service` on a Linux server at `1.14.49.201`, jar at `/opt/yoyuzh/yoyuzh-portal-backend.jar`. - account/auth flows
- personal cloud drive
- quick transfer
- admin console
- Capacitor Android shell
## Commands ## Commands
**Do not run `npm` at the repo root.** The root has no real `package.json`. Do not run frontend business commands from the repository root.
### Frontend (`cd front`) ### Frontend (`cd front`)
```bash ```bash
npm run dev # Vite dev server on port 3000 npm run dev
npm run build # Production build npm run build
npm run preview # Preview built output npm run preview
npm run lint # tsc --noEmit (this IS the type-check; no ESLint) npm run clean
npm run test # node --import tsx --test src/**/*.test.ts npm run lint
npm run clean # rm -rf dist npm run test
``` ```
### Backend (`cd backend`) ### Backend (`cd backend`)
```bash ```bash
mvn spring-boot:run # Default profile (MySQL) mvn spring-boot:run
mvn spring-boot:run -Dspring-boot.run.profiles=dev # Dev profile (H2, mock data) mvn spring-boot:run -Dspring-boot.run.profiles=dev
mvn test mvn test
mvn package # Produces backend/target/yoyuzh-portal-backend-0.0.1-SNAPSHOT.jar mvn package
``` ```
There is no backend lint or typecheck command. There is no dedicated backend lint or typecheck command.
### Deploy ### Deploy
```bash ```bash
node scripts/deploy-front-oss.mjs # Build + upload frontend to Aliyun OSS node scripts/deploy-front-oss.mjs
node scripts/deploy-front-oss.mjs --dry-run # Preview without uploading node scripts/deploy-front-oss.mjs --dry-run
node scripts/deploy-front-oss.mjs --skip-build # Upload only node scripts/deploy-front-oss.mjs --skip-build
``` node scripts/deploy-android-apk.mjs
Requires OSS credentials from env vars or `.env.oss.local`. node scripts/deploy-android-release.mjs
Backend deploy: `mvn package`, then SCP the jar to `/opt/yoyuzh/yoyuzh-portal-backend.jar` on the server and restart `my-site-api.service`. No checked-in backend deploy script exists.
## Architecture
### Tech Stack
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript, Vite 6, Tailwind CSS v4, react-router-dom v7 |
| Admin UI | react-admin v5 (lazy-loaded at `/admin/*`) |
| Backend | Spring Boot 3.3.8, Java 17, Maven, Spring Security + JWT |
| ORM | Spring Data JPA / Hibernate |
| DB (prod) | MySQL 8 |
| DB (dev) | H2 (in-file, MySQL compat mode via `application-dev.yml`) |
| File storage | Aliyun OSS (`yoyuzh-files2`, Chengdu region) |
### Frontend Structure (`front/src/`)
- `App.tsx` — router entrypoint
- `lib/` — pure logic modules, each with a co-located `*.test.ts`
- `api.ts` — all backend API calls
- `session.ts` — JWT session management
- `transfer-runtime.ts`, `transfer-protocol.ts` — WebRTC logic
- `files-upload.ts` — upload orchestration
- `pages/` — page-level components (`Files.tsx`, `Transfer.tsx`, `TransferReceive.tsx`, `Login.tsx`, `Overview.tsx`, `FileShare.tsx`)
- `admin/` — react-admin console with custom `data-provider.ts`
- `components/layout/`, `components/ui/` — shared UI primitives
- `auth/AuthProvider.tsx` — auth context
Frontend API proxy: all `/api/*` calls during dev proxy to `VITE_BACKEND_URL` (default `http://localhost:8080`) via `front/vite.config.ts`. Production hardcodes `https://api.yoyuzh.xyz/api` in `front/.env.production`.
### Backend Package Structure (`com.yoyuzh`)
- `auth` — JWT, login, register, refresh token, single-device session enforcement
- `files` — file metadata CRUD, upload/download, share links; `files/storage/` abstracts local vs OSS
- `transfer` — quick-transfer signaling API only (no file content relay)
- `admin` — admin-only user/file management APIs
- `config``SecurityConfig`, CORS, `JwtAuthenticationFilter`
- `common` — shared exceptions and utilities
### Key Design Decisions
- **Single-device login**: `activeSessionId` in user DB record matched against `sid` JWT claim on every request. New login invalidates old device's access token immediately.
- **Quick-transfer**: backend is signaling-only. File bytes travel via WebRTC `DataChannel` between browsers — no server bandwidth consumed. Offline mode uploads to OSS for 7-day retention.
- **File storage abstraction**: `FileContentStorage` interface decouples metadata logic from disk/OSS. Upload flow: `initiate` → direct OSS upload → `complete`. Falls back to proxy upload on failure.
- **Invite-code registration**: single-use codes auto-rotate after consumption; current code displayed in admin console.
### Security Boundaries (`SecurityConfig`)
- Public: `/api/auth/**`, `/api/transfer/**`, `GET /api/files/share-links/{token}`
- Requires auth: `/api/files/**`, `/api/user/**`, `/api/admin/**`
## Testing
Frontend tests live next to the module they test (`src/lib/api.test.ts` beside `src/lib/api.ts`). Run a single test file:
```bash
cd front && node --import tsx --test src/lib/api.test.ts
``` ```
Backend tests mirror the main package structure under `backend/src/test/java/com/yoyuzh/`. Run a single test class: The deploy scripts now prefer the repository root `.env` file and keep `.env.oss.local` only as a legacy fallback.
```bash
cd backend && mvn test -Dtest=ClassName
```
Dev profile (`-Dspring-boot.run.profiles=dev`) uses H2; H2 console available at `http://localhost:8080/h2-console`. ## Repo Conventions
## Codex Agent Roles - Root `.env` is the shared local secrets and deploy-config entrypoint.
- `.env.example` is the template.
Agents are defined in `.codex/agents/` and configured in `.codex/config.toml`. Use the workflow from `AGENTS.md`: - `memory.md` is the main continuity file.
- `docs/agents/` stores only the supplemental handoff doc; `CLAUDE.md` itself stays at the repository root.
| Agent | Role | Mode |
|---|---|---|
| `orchestrator` | Default coordinator, routes to specialists | read-only |
| `planner` | File-level and command plans before code changes | read-only |
| `explorer` | Maps code paths and existing behavior | read-only |
| `implementer` | Owns code edits and nearby test updates | read-write |
| `tester` | Runs repo-backed commands, reports failures | read-only |
| `reviewer` | Reviews diffs for bugs, regressions, test gaps | read-only |
| `deployer` | Builds and publishes frontend to OSS; packages backend jar for SSH delivery | read-write |
Default workflow: orchestrator → planner (if multi-file) → explorer (if behavior unclear) → implementer → tester → reviewer → deployer.
## Known Constraints ## Known Constraints
- VS Code "final field not initialized" errors on backend are Lombok/Java LS false positives — check Lombok extension and annotation processor, not source code. - Frontend production still hardcodes `https://api.yoyuzh.xyz/api` in `front/.env.production`.
- Frontend chunk size warnings from Vite do not block builds or deploys. - Vite build chunk warnings do not currently block release.
- `api.yoyuzh.xyz` has TLS/SNI instability on some networks; this is not a backend code issue. - Backend service metadata and SSH-related secrets have been consolidated into root `.env`; do not echo those values in normal responses.
- Production frontend bundle hardcodes the API base URL — API subdomain issues surface as "network error" or "login failed" on the frontend.
- Server credentials are in local file `账号密码.txt`; never commit or output their contents.

View File

@@ -1,280 +0,0 @@
# 项目交接说明
更新时间2026-03-18
项目根目录:`/Users/mac/Documents/my_site`
## 1. 项目概况
这是一个前后端分离的个人门户项目:
- 前端:`front/`
- 后端:`backend/`
- 线上主站:`https://yoyuzh.xyz`
- 线上 API`https://api.yoyuzh.xyz`
主要功能:
- 登录 / 注册
- 网盘文件列表与最近文件
- 教务相关接口(课表 / 成绩)
## 2. 当前线上架构
当前建议保持的生产架构是:
- `yoyuzh.xyz`:继续走 OSS / ESA负责静态站点
- `api.yoyuzh.xyz`:直接指向后端服务器,不要继续走 ESA 代理
原因:
- 主站静态资源走 ESA 没问题
- API 一旦走 ESA之前出现过
- `525 Origin SSL Handshake Error`
- `ERR_CONNECTION_CLOSED`
- `ERR_EMPTY_RESPONSE`
- 当前最稳方案是“静态站加速API 直连”
## 3. 当前前端生产配置
文件:
- `front/.env.production`
当前应保持为:
```env
VITE_API_BASE_URL="https://api.yoyuzh.xyz/api"
VITE_ROUTER_MODE="hash"
VITE_ENABLE_DEV_LOGIN="false"
```
说明:
- 不要再切回同域 `/api`,除非以后重新正确配置边缘转发
- 目前生产前端已经恢复为直连 `api.yoyuzh.xyz`
## 4. 当前已完成的前端改动
### 4.1 登录页
`front/src/pages/Login.tsx`
- 已替换为模板版登录 / 注册页
- 登录调用:`POST /auth/login`
- 注册调用:`POST /auth/register`
- 登录成功后写入 session 并跳转 `/overview`
### 4.2 网络错误处理
`front/src/lib/api.ts`
- 对网络错误统一包装为更清晰的前端错误
- 登录和只读接口有轻量重试机制
- 当前策略是“尽量兜底,但不要把登录拖到 7-8 秒”
### 4.3 登录成功与总览初始化失败拆分提示
相关文件:
- `front/src/lib/session.ts`
- `front/src/pages/Overview.tsx`
- `front/src/pages/overview-state.ts`
已做:
- 登录成功后会标记一次“post-login pending”
- `/overview` 初始化失败时,会提示“登录已成功,但总览加载失败”
- 避免把 overview 并发初始化失败误判成登录失败
## 5. 当前后端与服务器状态
通过 SSH 已确认:
- `my-site-api.service` 正常运行
- 后端本机 `127.0.0.1:8080` 正常
- 服务器本机直打登录接口返回 `200`
- Nginx 正常反代 `api.yoyuzh.xyz -> 127.0.0.1:8080`
服务器上关键配置:
- Nginx`/etc/nginx/sites-enabled/my-site-api`
- 后端配置:`/opt/yoyuzh/application-prod.yml`
- 服务名:`my-site-api.service`
## 6. 线上排查结论
### 6.1 已确认不是后端业务慢
在服务器本机测试结果:
- `POST http://127.0.0.1:8080/api/auth/login``95ms`
- `POST https://api.yoyuzh.xyz/api/auth/login` 从服务器发起约 `681ms`
所以:
- 后端本身不是“登录 5 秒”的根因
### 6.2 之前登录很慢的主要原因
更像是以下问题叠加:
- 旧 DNS / 旧代理链路未收敛
- API 域名曾被 ESA 代理,导致 TLS / 回源问题
- 浏览器前链路偶发 `ERR_CONNECTION_CLOSED`
### 6.3 当前更可信的状态
服务器日志已经看到真实浏览器请求成功:
- `OPTIONS /api/auth/login` => `200`
- `POST /api/auth/login` => `200`
- `/api/user/profile` => `200`
- `/api/files/recent` => `200`
- `/api/files/list` => `200`
- `/api/cqu/*` => `200`
因此:
- 现在如果仍有个别客户端不稳定,优先怀疑本地 DNS / 浏览器缓存 / 本地网络链路
## 7. DNS / ESA 方面的重要结论
### 7.1 过去踩过的坑
不要再轻易做下面这件事:
-`yoyuzh.xyz/api/*` 通过 ESA 回源到 API
之前已经明确踩到:
- `/api/*` 误回 OSS`403 NonCnameForbidden`
- 回源 HTTPS 握手失败,报 `525 Origin SSL Handshake Error`
### 7.2 当前建议
- `yoyuzh.xyz`:可以继续 ESA
- `api.yoyuzh.xyz`:不要走 ESA 代理
### 7.3 用户侧现象
曾出现:
- 无痕模式能登录
- 本机 `dig` 还查到旧的 `198.18.0.148`
这说明某一阶段存在 DNS 传播不一致。
如果下一个 Codex 遇到“浏览器能用,命令行不行”的情况,先查 DNS 链路,不要直接改代码。
## 8. OSS 前端部署方式
已经写好自动部署脚本:
- `scripts/deploy-front-oss.mjs`
- 配套库:`scripts/oss-deploy-lib.mjs`
- 配置模板:`.env.oss.example`
### 8.1 本地使用方式
先准备:
```bash
cp .env.oss.example .env.oss.local
```
然后填入 OSS 参数。
### 8.2 发布命令
```bash
./scripts/deploy-front-oss.mjs
```
### 8.3 只看将要上传什么
```bash
./scripts/deploy-front-oss.mjs --skip-build --dry-run
```
### 8.4 部署逻辑
脚本会:
- 读取 `.env.oss.local`
- 构建 `front/dist`
- 上传到 OSS
- 自动设置缓存头
缓存策略:
- `index.html` => `no-cache`
- `assets/*` => `public,max-age=31536000,immutable`
## 9. 测试账号
开发测试账号文档:
- `开发测试账号.md`
常用账号:
- `portal-demo / portal123456`
注意:
- 这些开发账号只在特定环境下才会自动初始化
- 如果线上账号密码不对,不要默认认为后端坏了
## 10. SSH 与敏感信息
有 SSH 凭据文件:
- `账号密码.txt`
下一个 Codex 可以读取该文件用于 SSH但不要在普通交互回复里直接回显其中的明文密码。
## 11. 推荐的排查顺序
如果后续又出现“登录失败 / 网络连接异常”,按这个顺序排:
1. 先查前端当前生产包是否正确
-`https://yoyuzh.xyz/``index.html`
- 确认引用的是最新构建产物
2. 再查 API 域名是否直连服务器
- `dig +short api.yoyuzh.xyz`
- `curl -vkI https://api.yoyuzh.xyz/`
3. 再查服务器本机和 systemd
- `systemctl status my-site-api`
- `curl http://127.0.0.1:8080/...`
4. 最后查 Nginx access/error log
- `/var/log/nginx/access.log`
- `/var/log/nginx/error.log`
不要上来就改前端逻辑。
## 12. 当前最重要的改进建议
### 短期建议
- 保持 API 直连,不再给 `api.yoyuzh.xyz` 套 ESA
- 用现有自动部署脚本发布前端
### 中期建议
- 给后端加一个明确的健康检查接口,比如 `/api/healthz`
- 给 Nginx access log 加 upstream timing 和 request id
### 长期建议
- 如果未来还想做同域 `/api`,要单独做一轮边缘转发设计
- 先确保:
- 源站类型正确
- 不会回 OSS
- 不会再发生 `525`
## 13. 给下一个 Codex 的一句话总结
当前项目已经从“链路混乱”恢复到“后端基本正常、主站正常、前端直连 API”的状态。
接手时优先维持现状,不要贸然重新启用 ESA 的 `/api` 回源方案。

View File

@@ -14,7 +14,7 @@
- 用户注册与登录 - 用户注册与登录
- 邀请码注册 - 邀请码注册
- 同账号仅允许一台设备同时登录 - 同账号支持桌面端与移动端同时在线
- 个人资料管理 - 个人资料管理
### 网盘 ### 网盘
@@ -71,6 +71,7 @@
├── backend/ Spring Boot 后端 ├── backend/ Spring Boot 后端
├── front/ React 前端 ├── front/ React 前端
├── docs/ 计划与文档 ├── docs/ 计划与文档
├── docs/agents/ agent 补充交接文档
├── scripts/ 部署与辅助脚本 ├── scripts/ 部署与辅助脚本
├── data/ 本地数据或辅助文件 ├── data/ 本地数据或辅助文件
└── 模板/ 页面参考模板 └── 模板/ 页面参考模板
@@ -88,16 +89,30 @@
### 1. 启动后端 ### 1. 启动后端
先准备根目录环境变量:
```bash
cp .env.example .env
```
填好 `APP_JWT_SECRET` 后,在当前 shell 里加载:
```bash
set -a
source .env
set +a
```
```bash ```bash
cd backend cd backend
APP_JWT_SECRET=<至少32字节的密钥> mvn spring-boot:run mvn spring-boot:run
``` ```
如果要使用本地开发配置: 如果要使用本地开发配置:
```bash ```bash
cd backend cd backend
APP_JWT_SECRET=<至少32字节的密钥> mvn spring-boot:run -Dspring-boot.run.profiles=dev mvn spring-boot:run -Dspring-boot.run.profiles=dev
``` ```
说明: 说明:
@@ -144,6 +159,8 @@ mvn package
## 环境变量 ## 环境变量
推荐把本地密钥和部署配置统一维护在仓库根目录 `.env`,模板在 `.env.example`
### 后端必填 ### 后端必填
```env ```env
@@ -171,7 +188,7 @@ YOYUZH_DOGECLOUD_S3_REGION=automatic
### 前端发布配置 ### 前端发布配置
前端发布脚本会从环境变量或 `.env.oss.local` 读取多吉云 API 凭据,再动态换取临时 S3 密钥。前端静态桶应填写逻辑桶名 `yoyuzh-front`,不要直接把底层 `s3Bucket` 写死到配置里。 前端发布脚本会优先从仓库根目录 `.env` 读取多吉云 API 凭据,再动态换取临时 S3 密钥;旧 `.env.oss.local` 只保留兼容回退读取。前端静态桶应填写逻辑桶名 `yoyuzh-front`,不要直接把底层 `s3Bucket` 写死到配置里。
常用变量: 常用变量:
@@ -188,8 +205,8 @@ YOYUZH_ANDROID_RELEASE_PREFIX=android/releases
参考文件: 参考文件:
- `.env.oss.example` - `.env.example`
- `.env.oss.local` - `.env`
## 部署 ## 部署

View File

@@ -15,17 +15,24 @@
## 启动 ## 启动
推荐先在仓库根目录准备并加载 `.env`
```bash
cp .env.example .env
set -a
source ../.env
set +a
```
默认配置: 默认配置:
```bash ```bash
APP_JWT_SECRET=<至少32字节的随机密钥> \
mvn spring-boot:run mvn spring-boot:run
``` ```
本地联调建议使用 `dev` 环境: 本地联调建议使用 `dev` 环境:
```bash ```bash
APP_JWT_SECRET=<至少32字节的随机密钥> \
mvn spring-boot:run -Dspring-boot.run.profiles=dev mvn spring-boot:run -Dspring-boot.run.profiles=dev
``` ```

View File

@@ -1,6 +0,0 @@
{
"registries": {
"@shadcn": "https://ui.shadcn.com/r/styles/default/{name}.json",
"@react-bits": "https://reactbits.dev/r/{name}.json"
}
}

36
docs/agents/handoff.md Normal file
View File

@@ -0,0 +1,36 @@
# 项目交接补充
更新时间2026-04-06
这份文档只记录补充性的交接和运维提示;一切当前状态仍优先以 `memory.md``docs/architecture.md``docs/api-reference.md` 为准。
## 当前主线
- 产品方向已经从旧教务切到“网盘 + 快传 + 管理台 + Android 壳”
- 前端静态站发布走 `node scripts/deploy-front-oss.mjs`
- Android APK 发包走 `node scripts/deploy-android-apk.mjs`
- 后端交付仍是 `cd backend && mvn package` 后手动上传 jar 并重启服务
## 本地配置整理
- 根目录 `.env` 已收口多吉云 API 凭据、服务器 SSH 信息和常用部署元信息
- `.env.example` 作为模板保留在仓库中
- 旧的 `.env.oss.local` 仅作为兼容回退读取,不再是主入口
## 线上运维关键信息
- 后端服务名:`my-site-api.service`
- 后端 jar 路径:`/opt/yoyuzh/yoyuzh-portal-backend.jar`
- 后端环境变量文件:`/opt/yoyuzh/app.env`
- 后端额外配置文件:`/opt/yoyuzh/application-prod.yml`
- API 域名:`https://api.yoyuzh.xyz`
- 主站域名:`https://yoyuzh.xyz`
## 遇到线上登录/网络异常时先看什么
1. 前端当前生产包是否是最新资源
2. `api.yoyuzh.xyz` 的 DNS / TLS / 反向代理是否正常
3. `my-site-api.service` 是否正常运行
4. 服务端和 Nginx 日志里是否已有真实请求
不要在没有确认链路问题前先改前端业务逻辑。

View File

@@ -410,5 +410,12 @@
1. `memory.md` 1. `memory.md`
2. `docs/architecture.md` 2. `docs/architecture.md`
3. `docs/api-reference.md` 3. `docs/api-reference.md`
4. `backend/src/main/java/com/yoyuzh/config/SecurityConfig.java` 4. `AGENTS.md`
5. 对应业务模块的 `Controller + Service` 5. `CLAUDE.md`
6. `backend/src/main/java/com/yoyuzh/config/SecurityConfig.java`
7. 对应业务模块的 `Controller + Service`
补充说明:
- 根目录 `.env` 现在是本地密钥和部署参数的统一入口
- 额外的交接背景可查看 `docs/agents/handoff.md`

View File

@@ -87,6 +87,7 @@
### 2.3 文档与脚本 ### 2.3 文档与脚本
- `docs/`: 实现计划与补充文档 - `docs/`: 实现计划与补充文档
- `docs/agents/`: 补充性的 agent / handoff 文档;根目录 `CLAUDE.md``AGENTS.md` 仍是入口
- `scripts/`: 前端静态站发布、对象存储迁移和本地辅助脚本 - `scripts/`: 前端静态站发布、对象存储迁移和本地辅助脚本
## 3. 模块划分 ## 3. 模块划分
@@ -402,6 +403,7 @@ Android 壳补充说明:
- 仓库根目录没有 `package.json`,不要在根目录执行 `npm` - 仓库根目录没有 `package.json`,不要在根目录执行 `npm`
- 前端命令只从 `front/package.json` 读取 - 前端命令只从 `front/package.json` 读取
- 后端命令只从 `backend/pom.xml` 读取 - 后端命令只从 `backend/pom.xml` 读取
- 根目录 `.env` 是当前统一的本地密钥与部署配置入口;`.env.example` 是模板,旧 `.env.oss.local` 仅保留兼容回退
- 前端 `npm run lint` 实际是 `tsc --noEmit` - 前端 `npm run lint` 实际是 `tsc --noEmit`
- 后端没有单独 lint 命令 - 后端没有单独 lint 命令
- 本仓库大量使用 LombokVS Code 若出现“final 字段未初始化”之类误报,优先检查 Lombok 扩展、Java Language Server 和 annotation processor - 本仓库大量使用 LombokVS Code 若出现“final 字段未初始化”之类误报,优先检查 Lombok 扩展、Java Language Server 和 annotation processor
@@ -414,9 +416,14 @@ Android 壳补充说明:
2. `docs/architecture.md` 2. `docs/architecture.md`
3. `docs/api-reference.md` 3. `docs/api-reference.md`
4. `AGENTS.md` 4. `AGENTS.md`
5. `CLAUDE.md`
如果要继续某个具体功能,再进入对应模块的: 如果要继续某个具体功能,再进入对应模块的:
- 前端页面文件 - 前端页面文件
- 后端 Controller / Service - 后端 Controller / Service
- 紧邻测试文件 - 紧邻测试文件
如果需要额外的交接背景,再补读:
- `docs/agents/handoff.md`

View File

@@ -33,7 +33,7 @@ Important:
- `npm run lint` is the current TypeScript check because it runs `tsc --noEmit`. - `npm run lint` is the current TypeScript check because it runs `tsc --noEmit`.
- There is no separate ESLint script. - There is no separate ESLint script.
- There is no separate `typecheck` script beyond `npm run lint`. - There is no separate `typecheck` script beyond `npm run lint`.
- OSS publishing uses `scripts/deploy-front-oss.mjs`, which reads credentials from environment variables or `.env.oss.local`. - OSS publishing uses `scripts/deploy-front-oss.mjs`, which reads credentials from environment variables or the repository root `.env` file, with `.env.oss.local` kept only as a legacy fallback.
## Frontend rules ## Frontend rules

View File

@@ -37,7 +37,10 @@
- 2026-04-03 Android 更新链路已改为“APK 存在文件桶独立路径 `android/releases/`,后端 `/api/app/android/latest` 读取 `android/releases/latest.json` 返回带版本号的后端下载地址,`/api/app/android/download` 直接分发 APK 字节流”;这样 App 内检查更新和 Web 下载都不会再误用前端静态桶旧包,也不依赖对象存储预签名下载 - 2026-04-03 Android 更新链路已改为“APK 存在文件桶独立路径 `android/releases/`,后端 `/api/app/android/latest` 读取 `android/releases/latest.json` 返回带版本号的后端下载地址,`/api/app/android/download` 直接分发 APK 字节流”;这样 App 内检查更新和 Web 下载都不会再误用前端静态桶旧包,也不依赖对象存储预签名下载
- 2026-04-03 网盘已新增回收站:`DELETE /api/files/{id}` 现在会把文件或整个目录树软删除进回收站,默认保留 10 天;前端桌面网盘页在左侧目录栏最下方新增“回收站”入口,移动端网盘页头也可进入回收站查看并恢复 - 2026-04-03 网盘已新增回收站:`DELETE /api/files/{id}` 现在会把文件或整个目录树软删除进回收站,默认保留 10 天;前端桌面网盘页在左侧目录栏最下方新增“回收站”入口,移动端网盘页头也可进入回收站查看并恢复
- 2026-04-05 Git 远程已从 GitHub 迁到自建私有 Gitea`https://git.yoyuzh.xyz/yoyuz/my_site.git`;当前本地 `main` 已推到新的 `origin/main` - 2026-04-05 Git 远程已从 GitHub 迁到自建私有 Gitea`https://git.yoyuzh.xyz/yoyuz/my_site.git`;当前本地 `main` 已推到新的 `origin/main`
- 2026-04-05 因为仓库现在是私人仓库,`.gitignore` 已放开 `账号密码.txt``开发测试账号.md``.env.local``.env.*.local``.env.oss.local``front/.env.production` 等私有配置文件,后续可以直接纳入版本控制 - 2026-04-06 已把本地项目密钥和部署元信息统一收口到仓库根目录 `.env`,模板文件改为 `.env.example`;前端 / Android 发布脚本现在优先读取 `.env`,旧 `.env.oss.local` 只作为兼容回退,不再作为主入口
- 2026-04-06 已删除根目录 `账号密码.txt`,服务器 SSH 登录信息改为放在根目录 `.env`
- 2026-04-06 已把补充型 handoff 文档收口到 `docs/agents/handoff.md``CLAUDE.md` 继续保留在根目录作为 agent 入口,额外的 `NEXT_CODEX_HANDOFF.md` 与目录说明文档已删除
- 2026-04-06 已确认前端当前只是在源码层使用 `front/src/components/ui/*` 组件,不依赖根目录 `shadcn` CLI因此已删除根目录 `package.json``package-lock.json``components.json` 和根目录 `node_modules`
- 根目录 README 已重写为中文公开版 GitHub 风格 - 根目录 README 已重写为中文公开版 GitHub 风格
- VS Code 工作区已补 `.vscode/settings.json``.vscode/extensions.json``lombok.config`,并在 `backend/pom.xml` 显式声明了 Lombok annotation processor - VS Code 工作区已补 `.vscode/settings.json``.vscode/extensions.json``lombok.config`,并在 `backend/pom.xml` 显式声明了 Lombok annotation processor
- 进行中: - 进行中:
@@ -75,6 +78,7 @@
- [ ] 前端构建仍有 chunk size warning目前不阻塞发布但后续可以考虑做更细的拆包 - [ ] 前端构建仍有 chunk size warning目前不阻塞发布但后续可以考虑做更细的拆包
- [ ] 线上前端 bundle 当前仍内嵌 `https://api.yoyuzh.xyz/api`API 子域名异常时会直接表现为“网络异常/登录失败” - [ ] 线上前端 bundle 当前仍内嵌 `https://api.yoyuzh.xyz/api`API 子域名异常时会直接表现为“网络异常/登录失败”
- [ ] 当前 Android 工程里的 Google Maven 镜像改动有一部分落在生成/依赖文件中;如果后续升级 Capacitor 或重新 `npm install`,需要重新确认 `front/android/build.gradle``front/android/capacitor-cordova-android-plugins/build.gradle``front/node_modules/@capacitor/android/capacitor/build.gradle` 的仓库源仍指向可访问镜像 - [ ] 当前 Android 工程里的 Google Maven 镜像改动有一部分落在生成/依赖文件中;如果后续升级 Capacitor 或重新 `npm install`,需要重新确认 `front/android/build.gradle``front/android/capacitor-cordova-android-plugins/build.gradle``front/node_modules/@capacitor/android/capacitor/build.gradle` 的仓库源仍指向可访问镜像
- [ ] 根目录目前仍有 `开发测试账号.md``需求文档.md``模板/` 等非运行时资料,后续如需继续瘦身可再决定是否迁入 `docs/` 或单独资料目录
## 关键约束 ## 关键约束
(只写这个任务特有的限制,区别于项目通用规则) (只写这个任务特有的限制,区别于项目通用规则)
@@ -82,6 +86,7 @@
- 前端真实命令以 `front/package.json` 为准;`npm run lint` 实际是 `tsc --noEmit` - 前端真实命令以 `front/package.json` 为准;`npm run lint` 实际是 `tsc --noEmit`
- 后端真实命令以 `backend/pom.xml` / `backend/README.md` 为准;常用的是 `mvn test``mvn package` - 后端真实命令以 `backend/pom.xml` / `backend/README.md` 为准;常用的是 `mvn test``mvn package`
- 修改文件时默认用 `apply_patch` - 修改文件时默认用 `apply_patch`
- 根目录 `.env` 现在是本地密钥、部署参数和服务器 SSH 元信息的统一入口;`.env.example` 是模板,`.env.oss.local` 不再作为主入口
- 已知线上后端服务名是 `my-site-api.service` - 已知线上后端服务名是 `my-site-api.service`
- 已知线上后端运行包路径是 `/opt/yoyuzh/yoyuzh-portal-backend.jar` - 已知线上后端运行包路径是 `/opt/yoyuzh/yoyuzh-portal-backend.jar`
- 已知新服务器公网 IP 是 `1.14.49.201` - 已知新服务器公网 IP 是 `1.14.49.201`
@@ -110,13 +115,14 @@
- Android 调试 APK 当前输出路径:`front/android/app/build/outputs/apk/debug/app-debug.apk` - Android 调试 APK 当前输出路径:`front/android/app/build/outputs/apk/debug/app-debug.apk`
- Android APK 独立发包命令: - Android APK 独立发包命令:
- `node scripts/deploy-android-release.mjs` - `node scripts/deploy-android-release.mjs`
- 服务器登录信息保存在本地 `账号密码.txt`,不要把内容写进文档或对外输出 - 服务器登录信息保存在根目录 `.env`,不要把内容写进文档或对外输出
## 参考资料 ## 参考资料
(相关链接、文档片段、背景资料) (相关链接、文档片段、背景资料)
- 根目录说明: `README.md` - 根目录说明: `README.md`
- 后端说明: `backend/README.md` - 后端说明: `backend/README.md`
- 仓库协作规范: `AGENTS.md` - 仓库协作规范: `AGENTS.md`
- agent / handoff 补充文档: `docs/agents/handoff.md`
- 前端/后端工作区配置: `.vscode/settings.json``.vscode/extensions.json` - 前端/后端工作区配置: `.vscode/settings.json``.vscode/extensions.json`
- Lombok 配置: `lombok.config` - Lombok 配置: `lombok.config`
- 最近关键实现位置: - 最近关键实现位置:

4603
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
{
"devDependencies": {
"shadcn": "^4.1.0"
}
}

View File

@@ -9,13 +9,12 @@ import {
createAwsV4Headers, createAwsV4Headers,
encodeObjectKey, encodeObjectKey,
getCacheControl, getCacheControl,
loadRepoEnv,
normalizeEndpoint, normalizeEndpoint,
parseSimpleEnv,
requestDogeCloudTemporaryS3Session, requestDogeCloudTemporaryS3Session,
} from './oss-deploy-lib.mjs'; } from './oss-deploy-lib.mjs';
const repoRoot = process.cwd(); const repoRoot = process.cwd();
const envFilePath = path.join(repoRoot, '.env.oss.local');
const apkSourcePath = path.join(repoRoot, 'front', 'android', 'app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk'); const apkSourcePath = path.join(repoRoot, 'front', 'android', 'app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk');
function parseArgs(argv) { function parseArgs(argv) {
@@ -24,24 +23,6 @@ function parseArgs(argv) {
}; };
} }
async function loadEnvFileIfPresent() {
try {
const raw = await fs.readFile(envFilePath, 'utf-8');
const values = parseSimpleEnv(raw);
for (const [key, value] of Object.entries(values)) {
if (!process.env[key]) {
process.env[key] = value;
}
}
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
return;
}
throw error;
}
}
function requireEnv(name) { function requireEnv(name) {
const value = process.env[name]; const value = process.env[name];
if (!value) { if (!value) {
@@ -133,7 +114,7 @@ async function uploadFile({
async function main() { async function main() {
const {dryRun} = parseArgs(process.argv.slice(2)); const {dryRun} = parseArgs(process.argv.slice(2));
await loadEnvFileIfPresent(); await loadRepoEnv({repoRoot});
const androidScope = getAndroidReleaseScope(); const androidScope = getAndroidReleaseScope();
if (!androidScope) { if (!androidScope) {

View File

@@ -13,16 +13,15 @@ import {
getFrontendSpaAliasKeys, getFrontendSpaAliasKeys,
getCacheControl, getCacheControl,
getContentType, getContentType,
loadRepoEnv,
listFiles, listFiles,
normalizeEndpoint, normalizeEndpoint,
parseSimpleEnv,
requestDogeCloudTemporaryS3Session, requestDogeCloudTemporaryS3Session,
} from './oss-deploy-lib.mjs'; } from './oss-deploy-lib.mjs';
const repoRoot = process.cwd(); const repoRoot = process.cwd();
const frontDir = path.join(repoRoot, 'front'); const frontDir = path.join(repoRoot, 'front');
const distDir = path.join(frontDir, 'dist'); const distDir = path.join(frontDir, 'dist');
const envFilePath = path.join(repoRoot, '.env.oss.local');
function parseArgs(argv) { function parseArgs(argv) {
return { return {
@@ -31,24 +30,6 @@ function parseArgs(argv) {
}; };
} }
async function loadEnvFileIfPresent() {
try {
const raw = await fs.readFile(envFilePath, 'utf-8');
const values = parseSimpleEnv(raw);
for (const [key, value] of Object.entries(values)) {
if (!process.env[key]) {
process.env[key] = value;
}
}
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
return;
}
throw error;
}
}
function requireEnv(name) { function requireEnv(name) {
const value = process.env[name]; const value = process.env[name];
if (!value) { if (!value) {
@@ -156,7 +137,7 @@ async function uploadSpaAliases({
async function main() { async function main() {
const {dryRun, skipBuild} = parseArgs(process.argv.slice(2)); const {dryRun, skipBuild} = parseArgs(process.argv.slice(2));
await loadEnvFileIfPresent(); await loadRepoEnv({repoRoot});
const apiAccessKey = requireEnv('YOYUZH_DOGECLOUD_API_ACCESS_KEY'); const apiAccessKey = requireEnv('YOYUZH_DOGECLOUD_API_ACCESS_KEY');
const apiSecretKey = requireEnv('YOYUZH_DOGECLOUD_API_SECRET_KEY'); const apiSecretKey = requireEnv('YOYUZH_DOGECLOUD_API_SECRET_KEY');

View File

@@ -299,3 +299,28 @@ export function parseSimpleEnv(rawText) {
return parsed; return parsed;
} }
export async function loadRepoEnv({
repoRoot,
candidateFileNames = ['.env.local', '.env', '.env.oss.local'],
}) {
for (const fileName of candidateFileNames) {
const filePath = path.join(repoRoot, fileName);
try {
const raw = await fs.readFile(filePath, 'utf-8');
const values = parseSimpleEnv(raw);
for (const [key, value] of Object.entries(values)) {
if (!process.env[key]) {
process.env[key] = value;
}
}
} catch (error) {
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
continue;
}
throw error;
}
}
}

View File

@@ -1,2 +0,0 @@
ubuntu@1.14.49.201
)at8^56?mTUf_D