docs: add detailed documentation
这个提交包含在:
父节点
aaed19de05
当前提交
5d06dd52e1
167
README.md
普通文件
167
README.md
普通文件
@ -0,0 +1,167 @@
|
||||
# XuqmGroup Web 前端文档
|
||||
|
||||
> Vue 3.5 · TypeScript · Vite 6 · Element Plus 2.9 · Pinia 3
|
||||
|
||||
## 工程结构
|
||||
|
||||
```
|
||||
XuqmGroup-Web/
|
||||
├── package.json # Yarn Workspace 根配置
|
||||
├── tenant-platform/ # 租户开放平台 :5173
|
||||
└── ops-platform/ # 运营管理平台 :5174
|
||||
```
|
||||
|
||||
## 启动
|
||||
|
||||
```bash
|
||||
cd XuqmGroup-Web
|
||||
yarn install
|
||||
|
||||
# 分别启动(两个终端)
|
||||
yarn workspace tenant-platform dev
|
||||
yarn workspace ops-platform dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 租户开放平台(tenant-platform)`:5173`
|
||||
|
||||
> 用户自助注册、管理应用、配置功能服务的对外平台。
|
||||
|
||||
### 路由结构
|
||||
|
||||
| 路径 | 组件 | 说明 |
|
||||
|------|------|------|
|
||||
| `/login` | LoginView | 登录(图形验证码) |
|
||||
| `/register` | RegisterView | 注册(邮箱验证码 60s 倒计时) |
|
||||
| `/forgot-password` | ForgotPasswordView | 忘记密码 |
|
||||
| `/dashboard` | DashboardView | 控制台首页(需登录) |
|
||||
| `/apps` | AppListView | 应用列表(需登录) |
|
||||
| `/apps/:id` | AppDetailView | 应用详情(需登录) |
|
||||
| `/accounts` | SubAccountView | 子账号管理(需登录) |
|
||||
|
||||
### 认证流程
|
||||
|
||||
```
|
||||
登录 → POST /api/auth/login → 获得 JWT
|
||||
↓
|
||||
Pinia authStore.setToken(jwt)
|
||||
↓
|
||||
localStorage 持久化
|
||||
↓
|
||||
axios 拦截器自动附加 Authorization: Bearer <jwt>
|
||||
↓
|
||||
401 响应 → 清除 Token → 跳转 /login
|
||||
```
|
||||
|
||||
### API 模块(src/api/)
|
||||
|
||||
| 文件 | 接口 | 说明 |
|
||||
|------|------|------|
|
||||
| `auth.ts` | `authApi` | 验证码、注册、登录、找回密码 |
|
||||
| `app.ts` | `appApi` | 应用 CRUD |
|
||||
| `account.ts` | `accountApi` | 子账号管理 |
|
||||
|
||||
### Pinia Store
|
||||
|
||||
**authStore**(`src/stores/auth.ts`)
|
||||
|
||||
```typescript
|
||||
const auth = useAuthStore()
|
||||
|
||||
auth.token // JWT 原始字符串
|
||||
auth.payload.sub // 租户 ID
|
||||
auth.payload.username
|
||||
auth.payload.nickname
|
||||
auth.payload.type // MAIN | SUB
|
||||
|
||||
auth.setToken(jwt) // 登录后调用
|
||||
auth.logout() // 清除并跳转 /login
|
||||
```
|
||||
|
||||
JWT Payload 由 `atob(token.split('.')[1])` 解析,无需额外请求。
|
||||
|
||||
### 应用详情页(AppDetailView)
|
||||
|
||||
- 平台 Tab 切换:`ANDROID` / `IOS` / `HARMONY`
|
||||
- 每个平台下显示 `IM` / `推送` / `版本管理` 三个服务卡片
|
||||
- 支持一键开关服务、复制 secretKey、重新生成 secretKey
|
||||
|
||||
### 子账号管理(SubAccountView)
|
||||
|
||||
创建子账号**两步流程**:
|
||||
1. 输入邮箱 → 发送验证码 → 验证通过(Redis 标记 24h 有效)
|
||||
2. 填写账号信息 → 创建子账号
|
||||
|
||||
---
|
||||
|
||||
## 运营管理平台(ops-platform)`:5174`
|
||||
|
||||
> 内部使用,管理所有租户、查看统计数据。独立账号体系。
|
||||
|
||||
### 路由结构
|
||||
|
||||
| 路径 | 组件 | 说明 |
|
||||
|------|------|------|
|
||||
| `/login` | LoginView | 运营管理员登录 |
|
||||
| `/tenants` | TenantListView | 租户列表(搜索/分页/启用禁用) |
|
||||
| `/statistics` | StatisticsView | 数据统计 |
|
||||
|
||||
### 认证
|
||||
|
||||
`localStorage` 存储 `ops_token`(与租户平台 token 隔离)。
|
||||
API 请求通过 `src/api/client.ts` 中的 axios 实例统一附加,401 跳回 `/login`。
|
||||
|
||||
### 租户管理功能
|
||||
|
||||
- 关键词搜索(用户名/邮箱)
|
||||
- 分页展示(每页 20 条)
|
||||
- 类型标签:主账号(蓝色)/ 子账号(灰色)
|
||||
- 状态标签:正常(绿色)/ 禁用(红色)
|
||||
- 一键启用/禁用
|
||||
|
||||
### 统计卡片
|
||||
|
||||
| 指标 | 接口字段 |
|
||||
|------|---------|
|
||||
| 总租户数 | `totalTenants` |
|
||||
| 今日新增 | `todayNew` |
|
||||
| 活跃应用 | `activeApps` |
|
||||
| 在线用户 | `onlineUsers` |
|
||||
|
||||
---
|
||||
|
||||
## 环境变量
|
||||
|
||||
在各平台根目录创建 `.env.local`:
|
||||
|
||||
```env
|
||||
# tenant-platform
|
||||
VITE_API_BASE_URL=https://api.xuqm.com/api
|
||||
|
||||
# ops-platform
|
||||
VITE_API_BASE_URL=https://api.xuqm.com/api
|
||||
```
|
||||
|
||||
未设置时默认代理到 `http://localhost:8081`(Vite dev server proxy)。
|
||||
|
||||
## 构建部署
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
yarn workspace tenant-platform build
|
||||
yarn workspace ops-platform build
|
||||
|
||||
# 产物在各平台的 dist/ 目录,部署至 Nginx 或 CDN
|
||||
```
|
||||
|
||||
Nginx 配置示例(SPA history 模式):
|
||||
```nginx
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8081/api/;
|
||||
}
|
||||
```
|
||||
279
docs/DEPLOY.md
普通文件
279
docs/DEPLOY.md
普通文件
@ -0,0 +1,279 @@
|
||||
# 部署文档
|
||||
|
||||
## 一、基础设施要求
|
||||
|
||||
| 组件 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| JDK | 21 | GraalVM 或 Eclipse Temurin |
|
||||
| MySQL | 8.0+ | 4 个独立数据库 |
|
||||
| Redis | 7.x | 验证码、会话标记 |
|
||||
| Nginx | 1.24+ | 前端静态 + API 反代 |
|
||||
| Maven | 3.9+ | 后端构建 |
|
||||
| Node.js | 22+ | 前端构建 |
|
||||
|
||||
---
|
||||
|
||||
## 二、数据库初始化
|
||||
|
||||
服务启动时通过 `ddl-auto: update` 自动建表,只需提前创建数据库:
|
||||
|
||||
```sql
|
||||
CREATE DATABASE xuqm_tenant CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE DATABASE xuqm_im CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE DATABASE xuqm_push CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE DATABASE xuqm_update CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、后端构建与启动
|
||||
|
||||
### 构建
|
||||
|
||||
```bash
|
||||
cd XuqmGroup-Server
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
各模块 jar 生成于 `{module}/target/{module}-0.1.0-SNAPSHOT.jar`。
|
||||
|
||||
### 环境变量
|
||||
|
||||
**tenant-service**(:8081)
|
||||
```bash
|
||||
export SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xuqm_tenant?...
|
||||
export SPRING_DATASOURCE_USERNAME=xuqm
|
||||
export SPRING_DATASOURCE_PASSWORD=your_db_password
|
||||
export SPRING_DATA_REDIS_HOST=redis
|
||||
export SPRING_MAIL_USERNAME=noreply@xuqm.com
|
||||
export SPRING_MAIL_PASSWORD=your_smtp_password
|
||||
export JWT_SECRET=your_256bit_secret
|
||||
export OPS_ADMIN_USERNAME=admin
|
||||
export OPS_ADMIN_PASSWORD=your_ops_password
|
||||
```
|
||||
|
||||
**im-service**(:8082)
|
||||
```bash
|
||||
export SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xuqm_im?...
|
||||
export SPRING_DATASOURCE_USERNAME=xuqm
|
||||
export SPRING_DATASOURCE_PASSWORD=your_db_password
|
||||
export SPRING_DATA_REDIS_HOST=redis
|
||||
export JWT_SECRET=your_256bit_secret_im
|
||||
```
|
||||
|
||||
**push-service**(:8083)
|
||||
```bash
|
||||
export SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xuqm_push?...
|
||||
export SPRING_DATASOURCE_USERNAME=xuqm
|
||||
export SPRING_DATASOURCE_PASSWORD=your_db_password
|
||||
export JWT_SECRET=your_256bit_secret_push
|
||||
# 华为推送
|
||||
export HUAWEI_APP_ID=your_huawei_app_id
|
||||
export HUAWEI_APP_SECRET=your_huawei_secret
|
||||
# 小米推送
|
||||
export XIAOMI_APP_SECRET=your_xiaomi_secret
|
||||
# iOS APNs
|
||||
export APNS_KEY_ID=your_key_id
|
||||
export APNS_TEAM_ID=your_team_id
|
||||
export APNS_KEY_PATH=/opt/xuqm/apns_key.p8
|
||||
export APNS_BUNDLE_ID=com.yourcompany.app
|
||||
```
|
||||
|
||||
**update-service**(:8084)
|
||||
```bash
|
||||
export SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/xuqm_update?...
|
||||
export SPRING_DATASOURCE_USERNAME=xuqm
|
||||
export SPRING_DATASOURCE_PASSWORD=your_db_password
|
||||
export JWT_SECRET=your_256bit_secret_update
|
||||
export UPDATE_UPLOAD_DIR=/data/xuqm/update
|
||||
export UPDATE_BASE_URL=https://update.xuqm.com
|
||||
```
|
||||
|
||||
### 启动
|
||||
|
||||
```bash
|
||||
java -jar tenant-service/target/tenant-service-*.jar &
|
||||
java -jar im-service/target/im-service-*.jar &
|
||||
java -jar push-service/target/push-service-*.jar &
|
||||
java -jar update-service/target/update-service-*.jar &
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端构建与部署
|
||||
|
||||
```bash
|
||||
cd XuqmGroup-Web
|
||||
yarn install
|
||||
yarn workspace tenant-platform build # dist/ → /var/www/tenant
|
||||
yarn workspace ops-platform build # dist/ → /var/www/ops
|
||||
```
|
||||
|
||||
### Nginx 配置
|
||||
|
||||
```nginx
|
||||
# 租户开放平台
|
||||
server {
|
||||
listen 80;
|
||||
server_name tenant.xuqm.com;
|
||||
root /var/www/tenant;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8081/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
|
||||
# 运营管理平台
|
||||
server {
|
||||
listen 80;
|
||||
server_name ops.xuqm.com;
|
||||
root /var/www/ops;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:8081/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
}
|
||||
|
||||
# IM WebSocket 反代
|
||||
server {
|
||||
listen 80;
|
||||
server_name im.xuqm.com;
|
||||
|
||||
location /ws/im {
|
||||
proxy_pass http://127.0.0.1:8082;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
|
||||
location /api/im/ {
|
||||
proxy_pass http://127.0.0.1:8082/api/im/;
|
||||
}
|
||||
}
|
||||
|
||||
# 版本管理文件服务
|
||||
server {
|
||||
listen 80;
|
||||
server_name update.xuqm.com;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8084/;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、Docker Compose(可选)
|
||||
|
||||
```yaml
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
MYSQL_USER: xuqm
|
||||
MYSQL_PASSWORD: xuqm_password
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
||||
tenant-service:
|
||||
image: eclipse-temurin:21-jre
|
||||
volumes:
|
||||
- ./XuqmGroup-Server/tenant-service/target:/app
|
||||
command: java -jar /app/tenant-service-0.1.0-SNAPSHOT.jar
|
||||
environment:
|
||||
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/xuqm_tenant?...
|
||||
SPRING_DATA_REDIS_HOST: redis
|
||||
ports:
|
||||
- "8081:8081"
|
||||
depends_on: [mysql, redis]
|
||||
|
||||
im-service:
|
||||
image: eclipse-temurin:21-jre
|
||||
volumes:
|
||||
- ./XuqmGroup-Server/im-service/target:/app
|
||||
command: java -jar /app/im-service-0.1.0-SNAPSHOT.jar
|
||||
ports:
|
||||
- "8082:8082"
|
||||
depends_on: [mysql, redis]
|
||||
|
||||
push-service:
|
||||
image: eclipse-temurin:21-jre
|
||||
volumes:
|
||||
- ./XuqmGroup-Server/push-service/target:/app
|
||||
command: java -jar /app/push-service-0.1.0-SNAPSHOT.jar
|
||||
ports:
|
||||
- "8083:8083"
|
||||
depends_on: [mysql]
|
||||
|
||||
update-service:
|
||||
image: eclipse-temurin:21-jre
|
||||
volumes:
|
||||
- ./XuqmGroup-Server/update-service/target:/app
|
||||
- update_files:/data/xuqm/update
|
||||
command: java -jar /app/update-service-0.1.0-SNAPSHOT.jar
|
||||
environment:
|
||||
UPDATE_UPLOAD_DIR: /data/xuqm/update
|
||||
ports:
|
||||
- "8084:8084"
|
||||
depends_on: [mysql]
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
update_files:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、发版流程汇总
|
||||
|
||||
| 平台 | 步骤 |
|
||||
|------|------|
|
||||
| **后端** | `mvn clean package` → 替换 jar → 重启服务 |
|
||||
| **前端** | `yarn build` → 替换 `dist/` → Nginx 无需重启 |
|
||||
| **Android SDK** | 修改版本号 → `./gradlew publish` → Nexus |
|
||||
| **iOS SDK (SPM)** | 修改版本号 → `git tag x.y.z && git push origin x.y.z` |
|
||||
| **iOS SDK (CocoaPods)** | `pod repo push xuqm-specs XuqmSDK.podspec` |
|
||||
| **RN SDK** | 修改 `package.json` version → `npm publish` |
|
||||
| **Vue3 SDK** | 修改 `package.json` version → `npm run build && npm publish` |
|
||||
| **HarmonyOS SDK** | 修改 `oh-package.json5` version → `ohpm publish` |
|
||||
|
||||
---
|
||||
|
||||
## 七、健康检查
|
||||
|
||||
各服务均暴露 Spring Actuator 端点:
|
||||
|
||||
```bash
|
||||
curl http://localhost:8081/actuator/health
|
||||
curl http://localhost:8082/actuator/health
|
||||
curl http://localhost:8083/actuator/health
|
||||
curl http://localhost:8084/actuator/health
|
||||
```
|
||||
|
||||
正常返回:`{"status":"UP"}`
|
||||
89
docs/PLATFORM_OVERVIEW.md
普通文件
89
docs/PLATFORM_OVERVIEW.md
普通文件
@ -0,0 +1,89 @@
|
||||
# XuqmGroup 平台文档总览
|
||||
|
||||
> 版本:0.1.0 | 最后更新:2026-04-21
|
||||
|
||||
## 仓库索引
|
||||
|
||||
| 仓库 | 语言/框架 | Gogs 地址 | 说明 |
|
||||
|------|-----------|-----------|------|
|
||||
| [XuqmGroup-Server](./server/README.md) | Java 21 / Spring Boot 3.4 | https://xuqinmin.com/xuqinmin12/XuqmGroup-Server | 后端多模块服务 |
|
||||
| [XuqmGroup-Web](./web/README.md) | Vue 3.5 / TypeScript | https://xuqinmin.com/xuqinmin12/XuqmGroup-Web | 租户平台 + 运营平台前端 |
|
||||
| [XuqmGroup-AndroidSDK](./android-sdk/README.md) | Kotlin 2.3 / AGP 9.1 | https://xuqinmin.com/xuqinmin12/XuqmGroup-AndroidSDK | Android SDK |
|
||||
| [XuqmGroup-iOSSDK](./ios-sdk/README.md) | Swift 5.9 / iOS 16+ | https://xuqinmin.com/xuqinmin12/XuqmGroup-iOSSDK | iOS SDK |
|
||||
| [XuqmGroup-RNSDK](./rn-sdk/README.md) | TypeScript / RN 0.76+ | https://xuqinmin.com/xuqinmin12/XuqmGroup-RNSDK | React Native SDK |
|
||||
| [XuqmGroup-Vue3SDK](./vue3-sdk/README.md) | TypeScript / Vue 3.5 | https://xuqinmin.com/xuqinmin12/XuqmGroup-Vue3SDK | Vue3 Web SDK |
|
||||
| [XuqmGroup-HarmonySDK](./harmony-sdk/README.md) | ArkTS / HarmonyOS 5 | https://xuqinmin.com/xuqinmin12/XuqmGroup-HarmonySDK | 鸿蒙 SDK |
|
||||
|
||||
## 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 客户端层 │
|
||||
│ Android SDK iOS SDK RN SDK Vue3 SDK HarmonyOS SDK │
|
||||
└───────────────────────────┬─────────────────────────────────┘
|
||||
│ HTTPS / WSS
|
||||
┌───────────────────────────▼─────────────────────────────────┐
|
||||
│ 服务端层 │
|
||||
│ ┌────────────┐ ┌──────────┐ ┌───────────┐ ┌────────────┐ │
|
||||
│ │tenant-svc │ │im-service│ │push-svc │ │update-svc │ │
|
||||
│ │ :8081 │ │ :8082 │ │ :8083 │ │ :8084 │ │
|
||||
│ └─────┬──────┘ └────┬─────┘ └─────┬─────┘ └──────┬─────┘ │
|
||||
│ └─────────────┴─────────────┴───────────────┘ │
|
||||
│ ↓ JPA / JDBC │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ MySQL 8 (xuqm_tenant / xuqm_im / xuqm_push / │ │
|
||||
│ │ xuqm_update) + Redis 7 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 前端层 │
|
||||
│ 租户开放平台 :5173 运营管理平台 :5174 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 核心概念
|
||||
|
||||
| 概念 | 说明 |
|
||||
|------|------|
|
||||
| **租户 (Tenant)** | 在开放平台注册的主账号,可创建子账号 |
|
||||
| **应用 (App)** | 租户创建的业务应用,具有唯一 appKey/appSecret |
|
||||
| **功能服务 (FeatureService)** | 挂载在 App 下的服务实例(IM / 推送 / 版本管理),按平台(Android / iOS / HarmonyOS)独立开启 |
|
||||
| **IM 账号** | 业务方通过 appKey 在 IM 服务创建的用户,仅存 userId,不存昵称/头像 |
|
||||
| **运营平台** | 内部管理后台,独立账号体系,不与租户共用 |
|
||||
|
||||
## 统一响应格式
|
||||
|
||||
所有 HTTP 接口均返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"status": "0",
|
||||
"data": { ... },
|
||||
"message": "success"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `code` | HTTP 语义码,200 成功,4xx/5xx 失败 |
|
||||
| `status` | `"0"` 成功,`"1"` 业务失败 |
|
||||
| `data` | 业务数据,失败时为 null |
|
||||
| `message` | 错误描述 |
|
||||
|
||||
## 认证方式
|
||||
|
||||
- **租户平台**:`Authorization: Bearer <tenant_jwt>`,由 `POST /api/auth/login` 颁发
|
||||
- **IM 服务**:`Authorization: Bearer <im_jwt>`,由 `POST /api/im/auth/login` 颁发;WebSocket 连接时通过 URL 参数 `?token=<im_jwt>` 传递
|
||||
- **运营平台**:`Authorization: Bearer <ops_jwt>`,由 `POST /api/auth/ops/login` 颁发
|
||||
|
||||
## 发版信息
|
||||
|
||||
| 平台 | Registry | 命令 |
|
||||
|------|----------|------|
|
||||
| npm (RN SDK / Vue3 SDK) | https://nexus.xuqinmin.com/repository/npm-hosted/ | `npm publish` |
|
||||
| Android Maven | https://nexus.xuqinmin.com/repository/android-hosted/ | `./gradlew publish` |
|
||||
| iOS SPM | Git Tag | `git tag x.y.z && git push origin x.y.z` |
|
||||
| iOS CocoaPods | https://xuqinmin.com/xuqinmin12/xuqm-specs | `pod repo push xuqm-specs XuqmSDK.podspec` |
|
||||
| HarmonyOS | ohpm (OpenHarmony Package Manager) | `ohpm publish` |
|
||||
正在加载...
在新工单中引用
屏蔽一个用户