docs: add expiry docs and sql script
This commit is contained in:
@@ -0,0 +1,396 @@
|
|||||||
|
# 保质期提醒小程序 - API 文档
|
||||||
|
|
||||||
|
## 基础信息
|
||||||
|
|
||||||
|
**Base URL**: `/api/expiry`
|
||||||
|
|
||||||
|
**认证方式**: JWT Token(通过公共登录接口获取,参见 `docs/common/auth.md`)
|
||||||
|
|
||||||
|
**请求头**:
|
||||||
|
```
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 物品管理
|
||||||
|
|
||||||
|
### 1.1 获取物品列表
|
||||||
|
|
||||||
|
**接口**: `GET /api/expiry/items`
|
||||||
|
|
||||||
|
**Query 参数**:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| status | string | ❌ | 状态筛选:all/expiring/expired/normal/used |
|
||||||
|
| category | string | ❌ | 分类筛选:all/food/medicine/cosmetic/other |
|
||||||
|
| sort | string | ❌ | 排序方式:expiry_date/created_at,默认 expiry_date |
|
||||||
|
| page | int | ❌ | 页码,默认 1 |
|
||||||
|
| page_size | int | ❌ | 每页数量,默认 20,最大 100 |
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"production_date": "2026-02-01",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"shelf_life_days": 37,
|
||||||
|
"quantity": 2,
|
||||||
|
"location": "冰箱",
|
||||||
|
"remark": "",
|
||||||
|
"status": "normal",
|
||||||
|
"days_left": 6,
|
||||||
|
"created_at": "2026-03-01T10:00:00Z",
|
||||||
|
"updated_at": "2026-03-01T10:00:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 15,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**状态说明**:
|
||||||
|
- `normal`: 正常(距离过期 > 7天)
|
||||||
|
- `expiring`: 即将过期(0-7天)
|
||||||
|
- `expired`: 已过期(< 0天)
|
||||||
|
- `used`: 已使用
|
||||||
|
- `discarded`: 已丢弃
|
||||||
|
|
||||||
|
**days_left 计算**:
|
||||||
|
- 正数:距离过期还有 N 天
|
||||||
|
- 0:今天过期
|
||||||
|
- 负数:已过期 N 天
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.2 获取首页汇总
|
||||||
|
|
||||||
|
**接口**: `GET /api/expiry/summary`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"total_items": 15,
|
||||||
|
"expiring_soon": 3,
|
||||||
|
"expired": 2,
|
||||||
|
"normal": 10,
|
||||||
|
"used": 0,
|
||||||
|
"discarded": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
- `total_items`: 当前有效物品总数(不含已使用/已丢弃)
|
||||||
|
- `expiring_soon`: 7天内即将过期的数量
|
||||||
|
- `expired`: 已过期的数量
|
||||||
|
- `normal`: 正常状态的数量
|
||||||
|
- `used`: 已使用的数量
|
||||||
|
- `discarded`: 已丢弃的数量
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.3 添加物品
|
||||||
|
|
||||||
|
**接口**: `POST /api/expiry/items`
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"production_date": "2026-02-01",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"shelf_life_days": 37,
|
||||||
|
"quantity": 2,
|
||||||
|
"location": "冰箱",
|
||||||
|
"remark": "伊利纯牛奶"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| name | string | ✅ | 物品名称,最长 100 字符 |
|
||||||
|
| category | string | ✅ | 分类:food/medicine/cosmetic/other |
|
||||||
|
| production_date | string | ❌ | 生产日期,格式 YYYY-MM-DD |
|
||||||
|
| expiry_date | string | ✅ | 过期日期,格式 YYYY-MM-DD |
|
||||||
|
| shelf_life_days | int | ❌ | 保质期天数 |
|
||||||
|
| quantity | int | ❌ | 数量,默认 1 |
|
||||||
|
| location | string | ❌ | 存放位置,最长 50 字符 |
|
||||||
|
| remark | string | ❌ | 备注,最长 255 字符 |
|
||||||
|
|
||||||
|
**业务逻辑**:
|
||||||
|
1. 如果提供 `production_date` 和 `shelf_life_days`,后端会自动计算 `expiry_date`(如果未提供)
|
||||||
|
2. `expiry_date` 必填,或者 `production_date` + `shelf_life_days` 组合必填
|
||||||
|
3. 初始状态为 `normal`,后端根据 `expiry_date` 自动计算 `days_left`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "添加成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"production_date": "2026-02-01",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"shelf_life_days": 37,
|
||||||
|
"quantity": 2,
|
||||||
|
"location": "冰箱",
|
||||||
|
"remark": "伊利纯牛奶",
|
||||||
|
"status": "normal",
|
||||||
|
"days_left": 6,
|
||||||
|
"created_at": "2026-03-04T10:00:00Z",
|
||||||
|
"updated_at": "2026-03-04T10:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.4 更新物品
|
||||||
|
|
||||||
|
**接口**: `PUT /api/expiry/items/:id`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
- `id`: 物品 ID
|
||||||
|
|
||||||
|
**请求体**: 同添加物品
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "更新成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "牛奶(已开封)",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.5 删除物品
|
||||||
|
|
||||||
|
**接口**: `DELETE /api/expiry/items/:id`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
- `id`: 物品 ID
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "删除成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**: 使用软删除,数据不会真正删除
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1.6 标记物品状态
|
||||||
|
|
||||||
|
**接口**: `POST /api/expiry/items/:id/status`
|
||||||
|
|
||||||
|
**路径参数**:
|
||||||
|
- `id`: 物品 ID
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "used"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**status 可选值**:
|
||||||
|
- `used`: 已使用
|
||||||
|
- `discarded`: 已丢弃
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "标记成功",
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"status": "used",
|
||||||
|
"updated_at": "2026-03-04T10:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 用户设置
|
||||||
|
|
||||||
|
### 2.1 获取用户设置
|
||||||
|
|
||||||
|
**接口**: `GET /api/expiry/settings`
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "success",
|
||||||
|
"data": {
|
||||||
|
"remind_days": [7, 3, 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**说明**: 如果用户未设置,返回默认值 `[7, 3, 1]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2.2 更新用户设置
|
||||||
|
|
||||||
|
**接口**: `POST /api/expiry/settings`
|
||||||
|
|
||||||
|
**请求体**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"remind_days": [10, 5, 2, 1]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**字段说明**:
|
||||||
|
- `remind_days`: 提醒天数数组,最多 5 个值,每个值范围 1-30
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "更新成功",
|
||||||
|
"data": {
|
||||||
|
"remind_days": [10, 5, 2, 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 错误码
|
||||||
|
|
||||||
|
| code | message | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| 0 | success | 成功 |
|
||||||
|
| 400 | 参数错误 | 请求参数不合法 |
|
||||||
|
| 401 | 未授权 | Token 无效或过期 |
|
||||||
|
| 404 | 资源不存在 | 物品不存在 |
|
||||||
|
| 500 | 服务器错误 | 内部错误 |
|
||||||
|
|
||||||
|
**错误响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "物品名称不能为空"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 前端集成示例
|
||||||
|
|
||||||
|
### 4.1 获取首页数据
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 并行请求
|
||||||
|
Promise.all([
|
||||||
|
wx.request({ url: '/api/expiry/summary' }),
|
||||||
|
wx.request({ url: '/api/expiry/items?status=expiring&page_size=10' })
|
||||||
|
]).then(([summary, items]) => {
|
||||||
|
// 渲染首页
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 添加物品
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
wx.request({
|
||||||
|
url: '/api/expiry/items',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
name: '牛奶',
|
||||||
|
category: 'food',
|
||||||
|
expiry_date: '2026-03-10',
|
||||||
|
quantity: 2,
|
||||||
|
location: '冰箱'
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
wx.showToast({ title: '添加成功' });
|
||||||
|
// 刷新列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 标记已使用
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
wx.request({
|
||||||
|
url: `/api/expiry/items/${itemId}/status`,
|
||||||
|
method: 'POST',
|
||||||
|
data: { status: 'used' },
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
wx.showToast({ title: '已标记' });
|
||||||
|
// 刷新列表
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 性能建议
|
||||||
|
|
||||||
|
### 5.1 缓存策略
|
||||||
|
|
||||||
|
- **summary**: 进入首页时刷新,缓存 5 分钟
|
||||||
|
- **items 列表**: 下拉刷新时更新,支持分页加载
|
||||||
|
- **settings**: 登录后缓存,修改时更新
|
||||||
|
|
||||||
|
### 5.2 请求优化
|
||||||
|
|
||||||
|
- 首页使用并行请求 `summary` + `items`
|
||||||
|
- 列表滚动到底部时自动加载下一页
|
||||||
|
- 使用防抖避免频繁请求
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 数据库索引建议
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 用户 + 过期日期(最常用查询)
|
||||||
|
CREATE INDEX idx_user_expiry ON expiry_items(user_id, expiry_date);
|
||||||
|
|
||||||
|
-- 用户 + 分类
|
||||||
|
CREATE INDEX idx_user_category ON expiry_items(user_id, category);
|
||||||
|
|
||||||
|
-- 用户 + 状态
|
||||||
|
CREATE INDEX idx_user_status ON expiry_items(user_id, status);
|
||||||
|
|
||||||
|
-- 软删除
|
||||||
|
CREATE INDEX idx_deleted_at ON expiry_items(deleted_at);
|
||||||
|
```
|
||||||
@@ -0,0 +1,560 @@
|
|||||||
|
# 保质期提醒小程序 - 开发指南
|
||||||
|
|
||||||
|
本文档提供技术实现细节和开发规范。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 技术架构
|
||||||
|
|
||||||
|
### 1.1 整体架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ 微信小程序端 │
|
||||||
|
│ (原生开发) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│ HTTPS
|
||||||
|
│ JWT Token
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ Gin Router │
|
||||||
|
│ (路由层) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ Handler │
|
||||||
|
│ (控制器层) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ Service │
|
||||||
|
│ (业务逻辑层) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ Repository │
|
||||||
|
│ (数据访问层) │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ MySQL │
|
||||||
|
│ (数据存储) │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
internal/expiry/
|
||||||
|
├── handler.go # HTTP 处理器
|
||||||
|
├── service.go # 业务逻辑
|
||||||
|
├── repository.go # 数据访问
|
||||||
|
├── model.go # 数据模型
|
||||||
|
└── dto.go # 数据传输对象(可选)
|
||||||
|
|
||||||
|
docs/expiry/
|
||||||
|
├── PRD.md # 产品需求文档
|
||||||
|
├── API.md # 接口文档
|
||||||
|
├── README.md # 项目说明
|
||||||
|
├── ISSUES.md # 开发任务
|
||||||
|
└── DEVELOPMENT.md # 本文档
|
||||||
|
|
||||||
|
docs/sql/
|
||||||
|
└── expiry.sql # 数据库初始化脚本
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 核心实现
|
||||||
|
|
||||||
|
### 2.1 过期状态计算
|
||||||
|
|
||||||
|
**关键逻辑**:
|
||||||
|
```go
|
||||||
|
// 计算剩余天数
|
||||||
|
func (item *ExpiryItem) CalculateDaysLeft() int {
|
||||||
|
now := time.Now().Truncate(24 * time.Hour)
|
||||||
|
expiry := item.ExpiryDate.Truncate(24 * time.Hour)
|
||||||
|
return int(expiry.Sub(now).Hours() / 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算状态
|
||||||
|
func (item *ExpiryItem) CalculateStatus() string {
|
||||||
|
daysLeft := item.CalculateDaysLeft()
|
||||||
|
if daysLeft < 0 {
|
||||||
|
return "expired"
|
||||||
|
} else if daysLeft <= 7 {
|
||||||
|
return "expiring"
|
||||||
|
}
|
||||||
|
return "normal"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 使用 `Truncate(24 * time.Hour)` 确保只比较日期,忽略时分秒
|
||||||
|
- 状态计算在查询时动态进行,不存储到数据库
|
||||||
|
- 前端根据 `days_left` 显示不同颜色
|
||||||
|
|
||||||
|
### 2.2 自动计算过期日期
|
||||||
|
|
||||||
|
**场景**:用户填写生产日期 + 保质期天数,自动计算过期日期
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (s *ExpiryService) CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, error) {
|
||||||
|
item := &ExpiryItem{
|
||||||
|
UserID: userID,
|
||||||
|
MiniProgramID: req.MiniProgramID,
|
||||||
|
Name: req.Name,
|
||||||
|
Category: req.Category,
|
||||||
|
ProductionDate: req.ProductionDate,
|
||||||
|
Quantity: req.Quantity,
|
||||||
|
Location: req.Location,
|
||||||
|
Remark: req.Remark,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提供了生产日期和保质期天数,自动计算过期日期
|
||||||
|
if req.ProductionDate != nil && req.ShelfLifeDays != nil {
|
||||||
|
expiryDate := req.ProductionDate.AddDate(0, 0, *req.ShelfLifeDays)
|
||||||
|
item.ExpiryDate = expiryDate
|
||||||
|
item.ShelfLifeDays = req.ShelfLifeDays
|
||||||
|
} else if req.ExpiryDate != nil {
|
||||||
|
item.ExpiryDate = *req.ExpiryDate
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("必须提供过期日期或生产日期+保质期天数")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.repo.Create(item); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 汇总统计实现
|
||||||
|
|
||||||
|
**SQL 查询优化**:
|
||||||
|
```go
|
||||||
|
func (r *ExpiryRepository) GetSummary(userID uint) (map[string]int, error) {
|
||||||
|
var result struct {
|
||||||
|
TotalItems int64
|
||||||
|
ExpiringSoon int64
|
||||||
|
Expired int64
|
||||||
|
Normal int64
|
||||||
|
Used int64
|
||||||
|
Discarded int64
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Truncate(24 * time.Hour)
|
||||||
|
sevenDaysLater := now.AddDate(0, 0, 7)
|
||||||
|
|
||||||
|
db := r.db.Model(&ExpiryItem{}).Where("user_id = ?", userID)
|
||||||
|
|
||||||
|
// 总数(不含已使用/已丢弃)
|
||||||
|
db.Where("status NOT IN ?", []string{"used", "discarded"}).Count(&result.TotalItems)
|
||||||
|
|
||||||
|
// 即将过期(7天内)
|
||||||
|
db.Where("expiry_date BETWEEN ? AND ?", now, sevenDaysLater).
|
||||||
|
Where("status NOT IN ?", []string{"used", "discarded"}).
|
||||||
|
Count(&result.ExpiringSoon)
|
||||||
|
|
||||||
|
// 已过期
|
||||||
|
db.Where("expiry_date < ?", now).
|
||||||
|
Where("status NOT IN ?", []string{"used", "discarded"}).
|
||||||
|
Count(&result.Expired)
|
||||||
|
|
||||||
|
// 正常
|
||||||
|
db.Where("expiry_date > ?", sevenDaysLater).
|
||||||
|
Where("status NOT IN ?", []string{"used", "discarded"}).
|
||||||
|
Count(&result.Normal)
|
||||||
|
|
||||||
|
// 已使用
|
||||||
|
db.Where("status = ?", "used").Count(&result.Used)
|
||||||
|
|
||||||
|
// 已丢弃
|
||||||
|
db.Where("status = ?", "discarded").Count(&result.Discarded)
|
||||||
|
|
||||||
|
return map[string]int{
|
||||||
|
"total_items": int(result.TotalItems),
|
||||||
|
"expiring_soon": int(result.ExpiringSoon),
|
||||||
|
"expired": int(result.Expired),
|
||||||
|
"normal": int(result.Normal),
|
||||||
|
"used": int(result.Used),
|
||||||
|
"discarded": int(result.Discarded),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**优化建议**:
|
||||||
|
- 使用索引 `idx_user_expiry (user_id, expiry_date)`
|
||||||
|
- 考虑使用 Redis 缓存(5分钟过期)
|
||||||
|
|
||||||
|
### 2.4 列表查询与筛选
|
||||||
|
|
||||||
|
**Repository 实现**:
|
||||||
|
```go
|
||||||
|
func (r *ExpiryRepository) FindByUser(
|
||||||
|
userID uint,
|
||||||
|
filters map[string]interface{},
|
||||||
|
page, pageSize int,
|
||||||
|
) ([]ExpiryItem, int64, error) {
|
||||||
|
var items []ExpiryItem
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
db := r.db.Model(&ExpiryItem{}).Where("user_id = ?", userID)
|
||||||
|
|
||||||
|
// 状态筛选
|
||||||
|
if status, ok := filters["status"].(string); ok && status != "all" {
|
||||||
|
if status == "expiring" {
|
||||||
|
now := time.Now().Truncate(24 * time.Hour)
|
||||||
|
sevenDaysLater := now.AddDate(0, 0, 7)
|
||||||
|
db = db.Where("expiry_date BETWEEN ? AND ?", now, sevenDaysLater)
|
||||||
|
} else if status == "expired" {
|
||||||
|
now := time.Now().Truncate(24 * time.Hour)
|
||||||
|
db = db.Where("expiry_date < ?", now)
|
||||||
|
} else {
|
||||||
|
db = db.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分类筛选
|
||||||
|
if category, ok := filters["category"].(string); ok && category != "all" {
|
||||||
|
db = db.Where("category = ?", category)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
sort := "expiry_date"
|
||||||
|
if s, ok := filters["sort"].(string); ok {
|
||||||
|
sort = s
|
||||||
|
}
|
||||||
|
db = db.Order(sort + " ASC")
|
||||||
|
|
||||||
|
// 统计总数
|
||||||
|
db.Count(&total)
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
db = db.Offset(offset).Limit(pageSize)
|
||||||
|
|
||||||
|
if err := db.Find(&items).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, total, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 前端实现要点
|
||||||
|
|
||||||
|
### 3.1 API 请求封装
|
||||||
|
|
||||||
|
**utils/request.js**:
|
||||||
|
```javascript
|
||||||
|
const BASE_URL = 'https://your-domain.com/api';
|
||||||
|
|
||||||
|
function request(options) {
|
||||||
|
const token = wx.getStorageSync('token');
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
wx.request({
|
||||||
|
url: BASE_URL + options.url,
|
||||||
|
method: options.method || 'GET',
|
||||||
|
data: options.data || {},
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': token ? `Bearer ${token}` : ''
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
resolve(res.data.data);
|
||||||
|
} else if (res.data.code === 401) {
|
||||||
|
// Token 过期,重新登录
|
||||||
|
wx.navigateTo({ url: '/pages/login/login' });
|
||||||
|
reject(res.data);
|
||||||
|
} else {
|
||||||
|
wx.showToast({
|
||||||
|
title: res.data.message || '请求失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
reject(res.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: reject
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { request };
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 日期格式化
|
||||||
|
|
||||||
|
**utils/date.js**:
|
||||||
|
```javascript
|
||||||
|
// 计算剩余天数颜色
|
||||||
|
function getDaysLeftColor(daysLeft) {
|
||||||
|
if (daysLeft < 0) return '#FF4444'; // 红色:已过期
|
||||||
|
if (daysLeft <= 3) return '#FF8800'; // 橙色:3天内
|
||||||
|
if (daysLeft <= 7) return '#FFBB00'; // 黄色:7天内
|
||||||
|
return '#00CC66'; // 绿色:正常
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
function formatDate(date) {
|
||||||
|
const d = new Date(date);
|
||||||
|
const year = d.getFullYear();
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(d.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算剩余天数文本
|
||||||
|
function getDaysLeftText(daysLeft) {
|
||||||
|
if (daysLeft < 0) return `已过期 ${Math.abs(daysLeft)} 天`;
|
||||||
|
if (daysLeft === 0) return '今天过期';
|
||||||
|
if (daysLeft === 1) return '明天过期';
|
||||||
|
return `还剩 ${daysLeft} 天`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getDaysLeftColor,
|
||||||
|
formatDate,
|
||||||
|
getDaysLeftText
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 分类图标映射
|
||||||
|
|
||||||
|
**utils/category.js**:
|
||||||
|
```javascript
|
||||||
|
const CATEGORY_CONFIG = {
|
||||||
|
food: {
|
||||||
|
name: '食品',
|
||||||
|
icon: '🍎',
|
||||||
|
color: '#FF6B6B'
|
||||||
|
},
|
||||||
|
medicine: {
|
||||||
|
name: '药品',
|
||||||
|
icon: '💊',
|
||||||
|
color: '#4ECDC4'
|
||||||
|
},
|
||||||
|
cosmetic: {
|
||||||
|
name: '化妆品',
|
||||||
|
icon: '💄',
|
||||||
|
color: '#FFB6C1'
|
||||||
|
},
|
||||||
|
other: {
|
||||||
|
name: '其他',
|
||||||
|
icon: '📦',
|
||||||
|
color: '#95A5A6'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCategoryConfig(category) {
|
||||||
|
return CATEGORY_CONFIG[category] || CATEGORY_CONFIG.other;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getCategoryConfig, CATEGORY_CONFIG };
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据库优化
|
||||||
|
|
||||||
|
### 4.1 索引设计
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 用户 + 过期日期(最常用查询)
|
||||||
|
CREATE INDEX idx_user_expiry ON expiry_items(user_id, expiry_date);
|
||||||
|
|
||||||
|
-- 用户 + 分类
|
||||||
|
CREATE INDEX idx_user_category ON expiry_items(user_id, category);
|
||||||
|
|
||||||
|
-- 用户 + 状态
|
||||||
|
CREATE INDEX idx_user_status ON expiry_items(user_id, status);
|
||||||
|
|
||||||
|
-- 软删除
|
||||||
|
CREATE INDEX idx_deleted_at ON expiry_items(deleted_at);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 查询优化建议
|
||||||
|
|
||||||
|
1. **避免全表扫描**:所有查询都带上 `user_id`
|
||||||
|
2. **使用覆盖索引**:常用字段加入索引
|
||||||
|
3. **分页查询**:使用 `LIMIT` 和 `OFFSET`
|
||||||
|
4. **缓存热点数据**:summary 数据使用 Redis 缓存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 安全性考虑
|
||||||
|
|
||||||
|
### 5.1 权限控制
|
||||||
|
|
||||||
|
- 所有接口必须通过 JWT 认证
|
||||||
|
- 查询时必须验证 `user_id`,防止越权访问
|
||||||
|
- 删除操作使用软删除,可恢复
|
||||||
|
|
||||||
|
### 5.2 输入验证
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 验证物品名称
|
||||||
|
if len(req.Name) == 0 || len(req.Name) > 100 {
|
||||||
|
return nil, errors.New("物品名称长度必须在1-100之间")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证分类
|
||||||
|
validCategories := []string{"food", "medicine", "cosmetic", "other"}
|
||||||
|
if !contains(validCategories, req.Category) {
|
||||||
|
return nil, errors.New("无效的分类")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证日期
|
||||||
|
if req.ExpiryDate != nil && req.ExpiryDate.Before(time.Now()) {
|
||||||
|
// 允许添加已过期物品,只是警告
|
||||||
|
// 可根据业务需求调整
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 测试策略
|
||||||
|
|
||||||
|
### 6.1 单元测试
|
||||||
|
|
||||||
|
**测试 Model 层**:
|
||||||
|
```go
|
||||||
|
func TestCalculateDaysLeft(t *testing.T) {
|
||||||
|
item := &ExpiryItem{
|
||||||
|
ExpiryDate: time.Now().AddDate(0, 0, 5),
|
||||||
|
}
|
||||||
|
daysLeft := item.CalculateDaysLeft()
|
||||||
|
assert.Equal(t, 5, daysLeft)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateStatus(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
daysLeft int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{-1, "expired"},
|
||||||
|
{0, "expiring"},
|
||||||
|
{5, "expiring"},
|
||||||
|
{7, "expiring"},
|
||||||
|
{8, "normal"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
item := &ExpiryItem{
|
||||||
|
ExpiryDate: time.Now().AddDate(0, 0, tt.daysLeft),
|
||||||
|
}
|
||||||
|
assert.Equal(t, tt.expected, item.CalculateStatus())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 集成测试
|
||||||
|
|
||||||
|
使用 Postman 或编写测试脚本:
|
||||||
|
```bash
|
||||||
|
# 登录获取 Token
|
||||||
|
TOKEN=$(curl -X POST http://localhost:8080/api/auth/login \
|
||||||
|
-d '{"code":"xxx","mini_program_id":1}' | jq -r '.data.token')
|
||||||
|
|
||||||
|
# 添加物品
|
||||||
|
curl -X POST http://localhost:8080/api/expiry/items \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"quantity": 2
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 获取列表
|
||||||
|
curl -X GET "http://localhost:8080/api/expiry/items?status=expiring" \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 部署建议
|
||||||
|
|
||||||
|
### 7.1 环境变量
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
SERVER_PORT=8080
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASSWORD=your_password
|
||||||
|
DB_NAME=wx_service
|
||||||
|
JWT_SECRET=your_jwt_secret
|
||||||
|
GIN_MODE=release
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 Docker 部署(可选)
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM golang:1.23-alpine AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN go build -o wx_service ./cmd/api
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/wx_service .
|
||||||
|
COPY .env .
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["./wx_service"]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 监控与日志
|
||||||
|
|
||||||
|
### 8.1 日志记录
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 记录关键操作
|
||||||
|
log.Printf("[Expiry] User %d created item: %s", userID, item.Name)
|
||||||
|
log.Printf("[Expiry] User %d deleted item: %d", userID, itemID)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 性能监控
|
||||||
|
|
||||||
|
- 接口响应时间
|
||||||
|
- 数据库查询耗时
|
||||||
|
- 错误率统计
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 常见问题
|
||||||
|
|
||||||
|
### Q1: 时区问题如何处理?
|
||||||
|
|
||||||
|
统一使用 UTC 时间存储,前端根据用户时区显示。Go 中使用 `time.Now().UTC()`。
|
||||||
|
|
||||||
|
### Q2: 如何处理大量过期物品?
|
||||||
|
|
||||||
|
定期清理已过期且标记为 `discarded` 的物品(物理删除),或者归档到历史表。
|
||||||
|
|
||||||
|
### Q3: 如何优化首页加载速度?
|
||||||
|
|
||||||
|
- 使用 Redis 缓存 summary 数据
|
||||||
|
- 首页只加载即将过期的物品(限制数量)
|
||||||
|
- 使用 CDN 加速静态资源
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 后续优化方向
|
||||||
|
|
||||||
|
1. **OCR 识别**:集成 OCR 识别生产日期和保质期
|
||||||
|
2. **推送通知**:使用小程序订阅消息推送临期提醒
|
||||||
|
3. **数据分析**:统计用户消费习惯,提供采购建议
|
||||||
|
4. **社交功能**:家庭成员共享物品列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档维护**: 随着项目迭代,及时更新本文档
|
||||||
@@ -0,0 +1,463 @@
|
|||||||
|
# 保质期提醒小程序 - 开发任务清单
|
||||||
|
|
||||||
|
本文档按优先级和依赖关系拆分开发任务,便于团队协作和进度跟踪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Milestone 1: 数据库与基础架构(优先级:P0)
|
||||||
|
|
||||||
|
### Issue #1: 数据库表设计与初始化
|
||||||
|
**标签**: `database`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 创建 `expiry_items` 表(物品表)
|
||||||
|
- [ ] 创建 `expiry_user_settings` 表(用户设置表)
|
||||||
|
- [ ] 添加必要的索引
|
||||||
|
- [ ] 编写 SQL 初始化脚本 `docs/sql/expiry.sql`
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 表结构符合 PRD 设计
|
||||||
|
- 索引覆盖常用查询场景
|
||||||
|
- 可通过脚本一键初始化
|
||||||
|
|
||||||
|
**SQL 脚本位置**: `/root/wx_service/docs/sql/expiry.sql`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #2: 创建 expiry 模块目录结构
|
||||||
|
**标签**: `backend`, `architecture`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 创建 `internal/expiry` 目录
|
||||||
|
- [ ] 创建 `model.go` - 定义数据模型
|
||||||
|
- [ ] 创建 `repository.go` - 数据访问层
|
||||||
|
- [ ] 创建 `service.go` - 业务逻辑层
|
||||||
|
- [ ] 创建 `handler.go` - HTTP 处理器
|
||||||
|
- [ ] 在 `internal/routes/routes.go` 注册路由
|
||||||
|
|
||||||
|
**目录结构**:
|
||||||
|
```
|
||||||
|
internal/expiry/
|
||||||
|
├── handler.go
|
||||||
|
├── service.go
|
||||||
|
├── model.go
|
||||||
|
└── repository.go
|
||||||
|
```
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 目录结构清晰,符合项目规范
|
||||||
|
- 路由注册成功,可通过 `/api/expiry/healthz` 测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Milestone 2: 后端核心接口(优先级:P0)
|
||||||
|
|
||||||
|
### Issue #3: 实现物品数据模型
|
||||||
|
**标签**: `backend`, `model`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #1, #2
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 定义 `ExpiryItem` 结构体
|
||||||
|
- [ ] 定义 `ExpiryUserSettings` 结构体
|
||||||
|
- [ ] 实现 GORM 模型标签
|
||||||
|
- [ ] 实现 `CalculateDaysLeft()` 方法
|
||||||
|
- [ ] 实现 `CalculateStatus()` 方法
|
||||||
|
|
||||||
|
**核心逻辑**:
|
||||||
|
```go
|
||||||
|
type ExpiryItem struct {
|
||||||
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
|
UserID uint `json:"user_id"`
|
||||||
|
MiniProgramID uint `json:"mini_program_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
ProductionDate *time.Time `json:"production_date"`
|
||||||
|
ExpiryDate time.Time `json:"expiry_date"`
|
||||||
|
ShelfLifeDays *int `json:"shelf_life_days"`
|
||||||
|
Quantity int `json:"quantity" gorm:"default:1"`
|
||||||
|
Location string `json:"location"`
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
Status string `json:"status" gorm:"default:'normal'"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算剩余天数
|
||||||
|
func (item *ExpiryItem) CalculateDaysLeft() int {
|
||||||
|
now := time.Now().Truncate(24 * time.Hour)
|
||||||
|
expiry := item.ExpiryDate.Truncate(24 * time.Hour)
|
||||||
|
return int(expiry.Sub(now).Hours() / 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算状态
|
||||||
|
func (item *ExpiryItem) CalculateStatus() string {
|
||||||
|
daysLeft := item.CalculateDaysLeft()
|
||||||
|
if daysLeft < 0 {
|
||||||
|
return "expired"
|
||||||
|
} else if daysLeft <= 7 {
|
||||||
|
return "expiring"
|
||||||
|
}
|
||||||
|
return "normal"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 模型定义完整,字段类型正确
|
||||||
|
- 辅助方法逻辑正确
|
||||||
|
- 通过单元测试
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #4: 实现物品 Repository 层
|
||||||
|
**标签**: `backend`, `repository`, `P0`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #3
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] `Create(item *ExpiryItem) error` - 创建物品
|
||||||
|
- [ ] `Update(item *ExpiryItem) error` - 更新物品
|
||||||
|
- [ ] `Delete(id, userID uint) error` - 删除物品(软删除)
|
||||||
|
- [ ] `FindByID(id, userID uint) (*ExpiryItem, error)` - 查询单个物品
|
||||||
|
- [ ] `FindByUser(userID uint, filters map[string]interface{}, page, pageSize int) ([]ExpiryItem, int64, error)` - 查询用户物品列表
|
||||||
|
- [ ] `GetSummary(userID uint) (map[string]int, error)` - 获取统计汇总
|
||||||
|
- [ ] `UpdateStatus(id, userID uint, status string) error` - 更新状态
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 所有方法实现完整
|
||||||
|
- 支持分页、筛选、排序
|
||||||
|
- 错误处理完善
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #5: 实现物品 Service 层
|
||||||
|
**标签**: `backend`, `service`, `P0`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #4
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] `CreateItem(userID uint, req CreateItemRequest) (*ExpiryItem, error)` - 创建物品
|
||||||
|
- 验证必填字段
|
||||||
|
- 如果提供生产日期+保质期,自动计算过期日期
|
||||||
|
- 调用 Repository 创建
|
||||||
|
- [ ] `UpdateItem(id, userID uint, req UpdateItemRequest) (*ExpiryItem, error)` - 更新物品
|
||||||
|
- [ ] `DeleteItem(id, userID uint) error` - 删除物品
|
||||||
|
- [ ] `GetItem(id, userID uint) (*ExpiryItem, error)` - 获取单个物品
|
||||||
|
- [ ] `GetItems(userID uint, filters ItemFilters) (*ItemListResponse, error)` - 获取物品列表
|
||||||
|
- 支持状态筛选(all/expiring/expired/normal/used)
|
||||||
|
- 支持分类筛选
|
||||||
|
- 支持排序(expiry_date/created_at)
|
||||||
|
- 计算 days_left
|
||||||
|
- [ ] `GetSummary(userID uint) (*SummaryResponse, error)` - 获取汇总统计
|
||||||
|
- [ ] `UpdateItemStatus(id, userID uint, status string) error` - 更新状态
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 业务逻辑正确
|
||||||
|
- 参数验证完善
|
||||||
|
- 返回数据包含 days_left 计算结果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #6: 实现物品 Handler 层
|
||||||
|
**标签**: `backend`, `handler`, `P0`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #5
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] `GetSummary(c *gin.Context)` - GET /api/expiry/summary
|
||||||
|
- [ ] `GetItems(c *gin.Context)` - GET /api/expiry/items
|
||||||
|
- [ ] `CreateItem(c *gin.Context)` - POST /api/expiry/items
|
||||||
|
- [ ] `UpdateItem(c *gin.Context)` - PUT /api/expiry/items/:id
|
||||||
|
- [ ] `DeleteItem(c *gin.Context)` - DELETE /api/expiry/items/:id
|
||||||
|
- [ ] `UpdateStatus(c *gin.Context)` - POST /api/expiry/items/:id/status
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 所有接口可通过 Postman/curl 测试
|
||||||
|
- 返回格式符合 API 文档
|
||||||
|
- 错误处理完善(400/401/404/500)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #7: 实现用户设置接口
|
||||||
|
**标签**: `backend`, `settings`, `P1`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #2
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] Repository: `GetSettings(userID uint)`, `UpdateSettings(userID uint, remindDays []int)`
|
||||||
|
- [ ] Service: 验证 remind_days 数组(最多5个,范围1-30)
|
||||||
|
- [ ] Handler: `GetSettings(c *gin.Context)`, `UpdateSettings(c *gin.Context)`
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 默认返回 [7,3,1]
|
||||||
|
- 更新后持久化到数据库
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Milestone 3: 小程序前端开发(优先级:P0)
|
||||||
|
|
||||||
|
### Issue #8: 小程序项目初始化
|
||||||
|
**标签**: `frontend`, `setup`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 创建小程序项目
|
||||||
|
- [ ] 配置 `app.json`(页面路由、TabBar)
|
||||||
|
- [ ] 配置 `project.config.json`
|
||||||
|
- [ ] 封装 API 请求工具(支持 JWT Token)
|
||||||
|
- [ ] 实现登录逻辑(复用公共登录接口)
|
||||||
|
|
||||||
|
**TabBar 配置**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/expiry/home/home",
|
||||||
|
"text": "首页",
|
||||||
|
"iconPath": "images/home.png",
|
||||||
|
"selectedIconPath": "images/home-active.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/expiry/list/list",
|
||||||
|
"text": "全部",
|
||||||
|
"iconPath": "images/list.png",
|
||||||
|
"selectedIconPath": "images/list-active.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/expiry/profile/profile",
|
||||||
|
"text": "我的",
|
||||||
|
"iconPath": "images/profile.png",
|
||||||
|
"selectedIconPath": "images/profile-active.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 小程序可正常运行
|
||||||
|
- 登录流程正常
|
||||||
|
- API 请求工具可用
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #9: 首页开发
|
||||||
|
**标签**: `frontend`, `home`, `P0`
|
||||||
|
**预计工时**: 1.5天
|
||||||
|
**依赖**: Issue #6, #8
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 统计卡片组件(总数/即将过期/已过期)
|
||||||
|
- [ ] 即将过期物品列表
|
||||||
|
- [ ] 列表项组件(名称、分类图标、剩余天数、过期日期)
|
||||||
|
- [ ] 浮动添加按钮
|
||||||
|
- [ ] 下拉刷新
|
||||||
|
- [ ] 空状态提示
|
||||||
|
|
||||||
|
**UI 要点**:
|
||||||
|
- 剩余天数颜色:红色(已过期)、橙色(3天内)、黄色(7天内)、绿色(正常)
|
||||||
|
- 分类图标:食品🍎、药品💊、化妆品💄、其他📦
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 数据正确展示
|
||||||
|
- 交互流畅
|
||||||
|
- 空状态友好
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #10: 添加/编辑页开发
|
||||||
|
**标签**: `frontend`, `form`, `P0`
|
||||||
|
**预计工时**: 1.5天
|
||||||
|
**依赖**: Issue #6, #8
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 表单组件(名称、分类、日期、数量、位置、备注)
|
||||||
|
- [ ] 日期选择器(生产日期、过期日期)
|
||||||
|
- [ ] 分类选择器(食品/药品/化妆品/其他)
|
||||||
|
- [ ] 自动计算过期日期(生产日期 + 保质期天数)
|
||||||
|
- [ ] 表单验证
|
||||||
|
- [ ] 提交逻辑(新增/编辑)
|
||||||
|
|
||||||
|
**交互逻辑**:
|
||||||
|
- 如果填写生产日期 + 保质期天数,自动计算过期日期
|
||||||
|
- 过期日期必填
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 表单验证完善
|
||||||
|
- 新增/编辑功能正常
|
||||||
|
- 自动计算逻辑正确
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #11: 全部物品页开发
|
||||||
|
**标签**: `frontend`, `list`, `P0`
|
||||||
|
**预计工时**: 1.5天
|
||||||
|
**依赖**: Issue #6, #8
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 状态筛选 Tabs(全部/即将过期/已过期/正常)
|
||||||
|
- [ ] 分类筛选(全部/食品/药品/化妆品/其他)
|
||||||
|
- [ ] 排序切换(按过期时间/按添加时间)
|
||||||
|
- [ ] 物品列表(复用首页列表项组件)
|
||||||
|
- [ ] 左滑操作(编辑/删除/标记已用)
|
||||||
|
- [ ] 分页加载
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 筛选和排序功能正常
|
||||||
|
- 左滑操作流畅
|
||||||
|
- 分页加载正常
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #12: 个人中心页开发
|
||||||
|
**标签**: `frontend`, `profile`, `P1`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #7, #8
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 用户信息展示(头像、昵称)
|
||||||
|
- [ ] 数据统计(累计添加、已使用、已过期)
|
||||||
|
- [ ] 提醒设置(提前天数)
|
||||||
|
- [ ] 关于小程序
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 用户信息正确展示
|
||||||
|
- 设置可保存
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Milestone 4: 测试与优化(优先级:P1)
|
||||||
|
|
||||||
|
### Issue #13: 后端单元测试
|
||||||
|
**标签**: `backend`, `test`, `P1`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #3-#7
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] Model 层测试(CalculateDaysLeft, CalculateStatus)
|
||||||
|
- [ ] Repository 层测试(使用 sqlmock)
|
||||||
|
- [ ] Service 层测试(业务逻辑验证)
|
||||||
|
- [ ] Handler 层测试(HTTP 请求测试)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 测试覆盖率 > 80%
|
||||||
|
- 所有测试通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #14: 接口集成测试
|
||||||
|
**标签**: `backend`, `test`, `P1`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #6, #7
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 编写 Postman Collection
|
||||||
|
- [ ] 测试所有接口的正常流程
|
||||||
|
- [ ] 测试异常情况(参数错误、权限错误等)
|
||||||
|
- [ ] 性能测试(并发请求)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 所有接口测试通过
|
||||||
|
- 响应时间 < 200ms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #15: 小程序端测试
|
||||||
|
**标签**: `frontend`, `test`, `P1`
|
||||||
|
**预计工时**: 1天
|
||||||
|
**依赖**: Issue #9-#12
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 功能测试(所有页面和交互)
|
||||||
|
- [ ] 兼容性测试(iOS/Android)
|
||||||
|
- [ ] 边界情况测试(网络异常、数据为空等)
|
||||||
|
- [ ] 性能测试(页面加载速度)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 所有功能正常
|
||||||
|
- 无明显性能问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #16: 性能优化
|
||||||
|
**标签**: `optimization`, `P2`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #14, #15
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 后端:添加 Redis 缓存(summary 数据)
|
||||||
|
- [ ] 后端:优化数据库查询(使用索引)
|
||||||
|
- [ ] 前端:列表虚拟滚动(大数据量优化)
|
||||||
|
- [ ] 前端:图片懒加载
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 首页加载时间 < 500ms
|
||||||
|
- 列表滚动流畅
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Milestone 5: 部署上线(优先级:P0)
|
||||||
|
|
||||||
|
### Issue #17: 部署准备
|
||||||
|
**标签**: `deployment`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #13-#15
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 更新 `.env.example`
|
||||||
|
- [ ] 编写部署文档
|
||||||
|
- [ ] 配置 Docker Compose(如需要)
|
||||||
|
- [ ] 配置 Nginx(如需要)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 部署文档完整
|
||||||
|
- 可一键部署
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #18: 生产环境部署
|
||||||
|
**标签**: `deployment`, `P0`
|
||||||
|
**预计工时**: 0.5天
|
||||||
|
**依赖**: Issue #17
|
||||||
|
|
||||||
|
**任务描述**:
|
||||||
|
- [ ] 数据库初始化
|
||||||
|
- [ ] 后端服务部署
|
||||||
|
- [ ] 小程序提审上线
|
||||||
|
- [ ] 监控配置
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 服务稳定运行
|
||||||
|
- 小程序审核通过
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 优先级说明
|
||||||
|
|
||||||
|
- **P0**: 必须完成(MVP 核心功能)
|
||||||
|
- **P1**: 重要(优化和测试)
|
||||||
|
- **P2**: 可选(性能优化)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗓️ 预计时间线
|
||||||
|
|
||||||
|
| Milestone | 预计工时 | 建议时间 |
|
||||||
|
|-----------|---------|---------|
|
||||||
|
| M1: 数据库与基础架构 | 1天 | Day 1 |
|
||||||
|
| M2: 后端核心接口 | 4.5天 | Day 2-5 |
|
||||||
|
| M3: 小程序前端开发 | 5天 | Day 6-10 |
|
||||||
|
| M4: 测试与优化 | 2.5天 | Day 11-12 |
|
||||||
|
| M5: 部署上线 | 1天 | Day 13 |
|
||||||
|
| **总计** | **14天** | **2周** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 备注
|
||||||
|
|
||||||
|
1. 以上工时为单人开发预估,团队协作可并行开发
|
||||||
|
2. 建议先完成 M1+M2,再开始 M3,避免接口变更
|
||||||
|
3. 测试阶段(M4)可与开发阶段(M2+M3)并行进行
|
||||||
|
4. 每个 Issue 完成后需要 Code Review
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
# 保质期提醒小程序 - 产品需求文档 (PRD)
|
||||||
|
|
||||||
|
## 1. 产品概述
|
||||||
|
|
||||||
|
### 1.1 产品定位
|
||||||
|
一款家庭物品保质期管理小程序,帮助用户记录食品、药品、化妆品等物品的保质期,提供临期提醒,减少浪费。
|
||||||
|
|
||||||
|
### 1.2 核心价值
|
||||||
|
- **快速记录**:扫码/手动添加物品保质期
|
||||||
|
- **智能提醒**:临期自动提醒,避免过期浪费
|
||||||
|
- **分类管理**:按类别查看,一目了然
|
||||||
|
- **统计分析**:了解消费习惯,优化采购
|
||||||
|
|
||||||
|
### 1.3 目标用户
|
||||||
|
- 家庭主妇/主夫
|
||||||
|
- 注重健康的年轻人
|
||||||
|
- 有囤货习惯的用户
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. MVP 功能范围
|
||||||
|
|
||||||
|
### 2.1 核心功能(必做)
|
||||||
|
|
||||||
|
#### ✅ 物品管理
|
||||||
|
- 添加物品(名称、分类、生产日期、保质期天数)
|
||||||
|
- 编辑物品
|
||||||
|
- 删除物品
|
||||||
|
- 标记已使用/已丢弃
|
||||||
|
|
||||||
|
#### ✅ 提醒系统
|
||||||
|
- 临期提醒(提前 7 天、3 天、1 天)
|
||||||
|
- 已过期标记
|
||||||
|
- 首页展示即将过期物品列表
|
||||||
|
|
||||||
|
#### ✅ 分类查看
|
||||||
|
- 预设分类:食品、药品、化妆品、其他
|
||||||
|
- 按分类筛选
|
||||||
|
- 按过期时间排序
|
||||||
|
|
||||||
|
#### ✅ 基础统计
|
||||||
|
- 当前物品总数
|
||||||
|
- 即将过期数量(7天内)
|
||||||
|
- 已过期数量
|
||||||
|
|
||||||
|
### 2.2 暂不实现(后续迭代)
|
||||||
|
|
||||||
|
- ❌ 拍照识别生产日期
|
||||||
|
- ❌ 家庭成员共享
|
||||||
|
- ❌ 购物清单联动
|
||||||
|
- ❌ 消费习惯分析
|
||||||
|
- ❌ 推送通知(小程序订阅消息)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 页面结构
|
||||||
|
|
||||||
|
### 3.1 首页 (home)
|
||||||
|
|
||||||
|
**核心目标**:快速查看即将过期物品
|
||||||
|
|
||||||
|
| 元素 | 说明 | 数据来源 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 统计卡片 | 总数/即将过期/已过期 | `GET /expiry/summary` |
|
||||||
|
| 即将过期列表 | 7天内过期的物品 | `GET /expiry/items?status=expiring` |
|
||||||
|
| 快速添加按钮 | 浮动按钮 | 跳转添加页 |
|
||||||
|
| 分类筛选 | 全部/食品/药品/化妆品/其他 | 前端筛选 |
|
||||||
|
|
||||||
|
**列表项展示**:
|
||||||
|
- 物品名称
|
||||||
|
- 分类图标
|
||||||
|
- 剩余天数(红色:已过期,橙色:3天内,黄色:7天内)
|
||||||
|
- 过期日期
|
||||||
|
|
||||||
|
### 3.2 添加/编辑页 (add_edit_item)
|
||||||
|
|
||||||
|
**表单字段**:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| name | string | ✅ | 物品名称 |
|
||||||
|
| category | enum | ✅ | 分类:food/medicine/cosmetic/other |
|
||||||
|
| production_date | date | ❌ | 生产日期 |
|
||||||
|
| expiry_date | date | ✅ | 过期日期 |
|
||||||
|
| shelf_life_days | int | ❌ | 保质期天数(与过期日期二选一) |
|
||||||
|
| quantity | int | ❌ | 数量(默认1) |
|
||||||
|
| location | string | ❌ | 存放位置(冰箱/柜子等) |
|
||||||
|
| remark | string | ❌ | 备注 |
|
||||||
|
|
||||||
|
**交互逻辑**:
|
||||||
|
- 如果填写生产日期 + 保质期天数,自动计算过期日期
|
||||||
|
- 如果直接填写过期日期,则不需要生产日期
|
||||||
|
|
||||||
|
### 3.3 全部物品页 (all_items)
|
||||||
|
|
||||||
|
**核心目标**:查看所有物品,支持筛选和排序
|
||||||
|
|
||||||
|
| 功能 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 状态筛选 | 全部/即将过期/已过期/正常 |
|
||||||
|
| 分类筛选 | 全部/食品/药品/化妆品/其他 |
|
||||||
|
| 排序方式 | 按过期时间/按添加时间 |
|
||||||
|
| 列表展示 | 同首页列表项 |
|
||||||
|
| 左滑操作 | 编辑/删除/标记已用 |
|
||||||
|
|
||||||
|
### 3.4 个人中心 (profile)
|
||||||
|
|
||||||
|
**功能**:
|
||||||
|
- 用户信息(头像、昵称)
|
||||||
|
- 提醒设置(提前天数:7/3/1天)
|
||||||
|
- 数据统计(累计添加、已使用、已过期)
|
||||||
|
- 关于小程序
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据模型
|
||||||
|
|
||||||
|
### 4.1 物品表 (expiry_items)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | bigint | 主键 |
|
||||||
|
| user_id | bigint | 用户ID(关联 users 表) |
|
||||||
|
| mini_program_id | bigint | 小程序ID |
|
||||||
|
| name | varchar(100) | 物品名称 |
|
||||||
|
| category | varchar(20) | 分类:food/medicine/cosmetic/other |
|
||||||
|
| production_date | date | 生产日期(可选) |
|
||||||
|
| expiry_date | date | 过期日期 |
|
||||||
|
| shelf_life_days | int | 保质期天数(可选) |
|
||||||
|
| quantity | int | 数量(默认1) |
|
||||||
|
| location | varchar(50) | 存放位置(可选) |
|
||||||
|
| remark | varchar(255) | 备注(可选) |
|
||||||
|
| status | varchar(20) | 状态:normal/used/expired/discarded |
|
||||||
|
| created_at | timestamp | 创建时间 |
|
||||||
|
| updated_at | timestamp | 更新时间 |
|
||||||
|
| deleted_at | timestamp | 软删除 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
- `idx_user_expiry` (user_id, expiry_date)
|
||||||
|
- `idx_user_category` (user_id, category)
|
||||||
|
- `idx_user_status` (user_id, status)
|
||||||
|
|
||||||
|
### 4.2 用户设置表 (expiry_user_settings)
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| id | bigint | 主键 |
|
||||||
|
| user_id | bigint | 用户ID(唯一) |
|
||||||
|
| remind_days | json | 提醒天数数组 [7,3,1] |
|
||||||
|
| created_at | timestamp | 创建时间 |
|
||||||
|
| updated_at | timestamp | 更新时间 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. API 设计
|
||||||
|
|
||||||
|
### 5.1 物品管理
|
||||||
|
|
||||||
|
#### 获取物品列表
|
||||||
|
```
|
||||||
|
GET /api/expiry/items
|
||||||
|
Query:
|
||||||
|
- status: string (expiring/expired/normal/used/all)
|
||||||
|
- category: string (food/medicine/cosmetic/other/all)
|
||||||
|
- sort: string (expiry_date/created_at)
|
||||||
|
- page: int
|
||||||
|
- page_size: int
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"days_left": 6,
|
||||||
|
"status": "normal",
|
||||||
|
"quantity": 2,
|
||||||
|
"location": "冰箱"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 10,
|
||||||
|
"page": 1,
|
||||||
|
"page_size": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 获取首页汇总
|
||||||
|
```
|
||||||
|
GET /api/expiry/summary
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"total_items": 15,
|
||||||
|
"expiring_soon": 3, // 7天内
|
||||||
|
"expired": 2,
|
||||||
|
"normal": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 添加物品
|
||||||
|
```
|
||||||
|
POST /api/expiry/items
|
||||||
|
Body:
|
||||||
|
{
|
||||||
|
"name": "牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"production_date": "2026-02-01", // 可选
|
||||||
|
"expiry_date": "2026-03-10", // 必填
|
||||||
|
"shelf_life_days": 37, // 可选
|
||||||
|
"quantity": 2,
|
||||||
|
"location": "冰箱",
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "牛奶",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新物品
|
||||||
|
```
|
||||||
|
PUT /api/expiry/items/:id
|
||||||
|
Body: 同添加物品
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 删除物品
|
||||||
|
```
|
||||||
|
DELETE /api/expiry/items/:id
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "删除成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 标记物品状态
|
||||||
|
```
|
||||||
|
POST /api/expiry/items/:id/status
|
||||||
|
Body:
|
||||||
|
{
|
||||||
|
"status": "used" // used/discarded
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "标记成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 用户设置
|
||||||
|
|
||||||
|
#### 获取设置
|
||||||
|
```
|
||||||
|
GET /api/expiry/settings
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"remind_days": [7, 3, 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 更新设置
|
||||||
|
```
|
||||||
|
POST /api/expiry/settings
|
||||||
|
Body:
|
||||||
|
{
|
||||||
|
"remind_days": [7, 3, 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"message": "更新成功"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 用户流程
|
||||||
|
|
||||||
|
### 6.1 新用户流程
|
||||||
|
|
||||||
|
```
|
||||||
|
启动小程序
|
||||||
|
↓
|
||||||
|
微信登录 (wx.login)
|
||||||
|
↓
|
||||||
|
进入首页(空状态)
|
||||||
|
↓
|
||||||
|
点击"添加物品"
|
||||||
|
↓
|
||||||
|
填写表单
|
||||||
|
↓
|
||||||
|
提交成功 → 返回首页
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 日常使用流程
|
||||||
|
|
||||||
|
```
|
||||||
|
打开首页
|
||||||
|
↓
|
||||||
|
查看即将过期物品
|
||||||
|
↓
|
||||||
|
[需要添加新物品]
|
||||||
|
↓
|
||||||
|
点击浮动按钮
|
||||||
|
↓
|
||||||
|
填写表单 → 提交
|
||||||
|
↓
|
||||||
|
[物品已使用]
|
||||||
|
↓
|
||||||
|
左滑 → 标记已用
|
||||||
|
↓
|
||||||
|
[查看所有物品]
|
||||||
|
↓
|
||||||
|
切换到"全部"Tab
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 技术实现要点
|
||||||
|
|
||||||
|
### 7.1 过期状态计算
|
||||||
|
|
||||||
|
**后端统一计算**:
|
||||||
|
- `days_left = expiry_date - today`
|
||||||
|
- `status`:
|
||||||
|
- `days_left < 0`: expired
|
||||||
|
- `0 <= days_left <= 7`: expiring
|
||||||
|
- `days_left > 7`: normal
|
||||||
|
|
||||||
|
### 7.2 提醒逻辑(MVP 暂不实现推送)
|
||||||
|
|
||||||
|
**前端展示提醒**:
|
||||||
|
- 首页红点:有即将过期物品时显示
|
||||||
|
- 列表颜色标记:
|
||||||
|
- 红色:已过期
|
||||||
|
- 橙色:3天内
|
||||||
|
- 黄色:7天内
|
||||||
|
- 绿色:正常
|
||||||
|
|
||||||
|
### 7.3 性能优化
|
||||||
|
|
||||||
|
**缓存策略**:
|
||||||
|
- summary 数据:进入首页时刷新
|
||||||
|
- items 列表:按页加载,支持下拉刷新
|
||||||
|
|
||||||
|
**索引优化**:
|
||||||
|
- 按 user_id + expiry_date 查询(最常用)
|
||||||
|
- 按 user_id + category 筛选
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 待扩展功能(后续版本)
|
||||||
|
|
||||||
|
### V2.0
|
||||||
|
- [ ] 拍照识别生产日期和保质期
|
||||||
|
- [ ] 小程序订阅消息推送
|
||||||
|
- [ ] 常用物品模板(快速添加)
|
||||||
|
|
||||||
|
### V3.0
|
||||||
|
- [ ] 家庭成员共享
|
||||||
|
- [ ] 购物清单联动
|
||||||
|
- [ ] 消费习惯分析
|
||||||
|
|
||||||
|
### V4.0
|
||||||
|
- [ ] 社区分享(保质期知识)
|
||||||
|
- [ ] 积分系统(减少浪费奖励)
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
# 保质期提醒小程序 - 开发文档
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
保质期提醒小程序是一款帮助用户管理家庭物品保质期的工具,支持食品、药品、化妆品等多种物品的记录和提醒。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档索引
|
||||||
|
|
||||||
|
- **[PRD.md](./PRD.md)** - 产品需求文档(功能范围、页面设计、数据模型)
|
||||||
|
- **[API.md](./API.md)** - 接口文档(完整的 API 定义和示例)
|
||||||
|
- **[DEVELOPMENT.md](./DEVELOPMENT.md)** - 开发指南(技术实现细节)
|
||||||
|
- **[ISSUES.md](./ISSUES.md)** - 开发任务清单(按优先级拆分的 Issues)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 数据库初始化
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 创建物品表
|
||||||
|
CREATE TABLE expiry_items (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
mini_program_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
category VARCHAR(20) NOT NULL,
|
||||||
|
production_date DATE,
|
||||||
|
expiry_date DATE NOT NULL,
|
||||||
|
shelf_life_days INT,
|
||||||
|
quantity INT DEFAULT 1,
|
||||||
|
location VARCHAR(50),
|
||||||
|
remark VARCHAR(255),
|
||||||
|
status VARCHAR(20) DEFAULT 'normal',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
deleted_at TIMESTAMP NULL,
|
||||||
|
INDEX idx_user_expiry (user_id, expiry_date),
|
||||||
|
INDEX idx_user_category (user_id, category),
|
||||||
|
INDEX idx_user_status (user_id, status),
|
||||||
|
INDEX idx_deleted_at (deleted_at)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
|
-- 创建用户设置表
|
||||||
|
CREATE TABLE expiry_user_settings (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL UNIQUE,
|
||||||
|
remind_days JSON DEFAULT '[7,3,1]',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 后端开发
|
||||||
|
|
||||||
|
参考现有模块结构(如 `internal/smoke`),创建 `internal/expiry` 目录:
|
||||||
|
|
||||||
|
```
|
||||||
|
internal/expiry/
|
||||||
|
├── handler.go # HTTP 处理器
|
||||||
|
├── service.go # 业务逻辑
|
||||||
|
├── model.go # 数据模型
|
||||||
|
└── repository.go # 数据访问层
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 路由注册
|
||||||
|
|
||||||
|
在 `internal/routes/routes.go` 中注册路由:
|
||||||
|
|
||||||
|
```go
|
||||||
|
expiry := r.Group("/api/expiry")
|
||||||
|
expiry.Use(middleware.AuthMiddleware())
|
||||||
|
{
|
||||||
|
expiry.GET("/summary", expiryHandler.GetSummary)
|
||||||
|
expiry.GET("/items", expiryHandler.GetItems)
|
||||||
|
expiry.POST("/items", expiryHandler.CreateItem)
|
||||||
|
expiry.PUT("/items/:id", expiryHandler.UpdateItem)
|
||||||
|
expiry.DELETE("/items/:id", expiryHandler.DeleteItem)
|
||||||
|
expiry.POST("/items/:id/status", expiryHandler.UpdateStatus)
|
||||||
|
expiry.GET("/settings", expiryHandler.GetSettings)
|
||||||
|
expiry.POST("/settings", expiryHandler.UpdateSettings)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 小程序端开发
|
||||||
|
|
||||||
|
参考 API 文档进行前端开发,主要页面:
|
||||||
|
|
||||||
|
- `pages/expiry/home` - 首页
|
||||||
|
- `pages/expiry/add` - 添加/编辑物品
|
||||||
|
- `pages/expiry/list` - 全部物品列表
|
||||||
|
- `pages/expiry/profile` - 个人中心
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP 功能清单
|
||||||
|
|
||||||
|
### Phase 1: 核心功能(2-3天)
|
||||||
|
- [x] 数据库表设计
|
||||||
|
- [ ] 后端 API 实现
|
||||||
|
- [ ] 物品 CRUD
|
||||||
|
- [ ] 汇总统计
|
||||||
|
- [ ] 用户设置
|
||||||
|
- [ ] 前端页面开发
|
||||||
|
- [ ] 首页(列表 + 统计)
|
||||||
|
- [ ] 添加/编辑页
|
||||||
|
- [ ] 全部物品页
|
||||||
|
|
||||||
|
### Phase 2: 优化完善(1-2天)
|
||||||
|
- [ ] 状态计算逻辑优化
|
||||||
|
- [ ] 列表分页加载
|
||||||
|
- [ ] 左滑操作
|
||||||
|
- [ ] 空状态提示
|
||||||
|
- [ ] 错误处理
|
||||||
|
|
||||||
|
### Phase 3: 测试上线(1天)
|
||||||
|
- [ ] 单元测试
|
||||||
|
- [ ] 接口测试
|
||||||
|
- [ ] 小程序端测试
|
||||||
|
- [ ] 部署上线
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
**后端**:
|
||||||
|
- Go 1.23+
|
||||||
|
- Gin Web Framework
|
||||||
|
- GORM (MySQL)
|
||||||
|
- JWT 认证
|
||||||
|
|
||||||
|
**前端**:
|
||||||
|
- 微信小程序原生开发
|
||||||
|
- WeUI 组件库(可选)
|
||||||
|
|
||||||
|
**数据库**:
|
||||||
|
- MySQL 8.0+
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发规范
|
||||||
|
|
||||||
|
### 1. 代码风格
|
||||||
|
|
||||||
|
遵循项目现有代码风格:
|
||||||
|
- Go: 使用 `gofmt` 格式化
|
||||||
|
- 变量命名:驼峰命名法
|
||||||
|
- 错误处理:统一使用 `common.ErrorResponse`
|
||||||
|
|
||||||
|
### 2. 提交规范
|
||||||
|
|
||||||
|
```
|
||||||
|
feat: 添加物品管理接口
|
||||||
|
fix: 修复过期日期计算错误
|
||||||
|
docs: 更新 API 文档
|
||||||
|
test: 添加物品服务单元测试
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 分支管理
|
||||||
|
|
||||||
|
- `main`: 生产环境
|
||||||
|
- `develop`: 开发环境
|
||||||
|
- `feature/expiry-*`: 功能分支
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 测试数据
|
||||||
|
|
||||||
|
### 测试物品
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "伊利纯牛奶",
|
||||||
|
"category": "food",
|
||||||
|
"expiry_date": "2026-03-10",
|
||||||
|
"quantity": 6,
|
||||||
|
"location": "冰箱"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "感冒灵颗粒",
|
||||||
|
"category": "medicine",
|
||||||
|
"expiry_date": "2027-12-31",
|
||||||
|
"quantity": 1,
|
||||||
|
"location": "药箱"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "雅诗兰黛面霜",
|
||||||
|
"category": "cosmetic",
|
||||||
|
"production_date": "2025-06-01",
|
||||||
|
"shelf_life_days": 1095,
|
||||||
|
"quantity": 1,
|
||||||
|
"location": "梳妆台"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 如何计算过期状态?
|
||||||
|
|
||||||
|
后端统一计算 `days_left = expiry_date - today`,前端根据 `days_left` 显示颜色:
|
||||||
|
- `< 0`: 红色(已过期)
|
||||||
|
- `0-3`: 橙色(即将过期)
|
||||||
|
- `4-7`: 黄色(临期)
|
||||||
|
- `> 7`: 绿色(正常)
|
||||||
|
|
||||||
|
### Q2: 如何处理时区问题?
|
||||||
|
|
||||||
|
统一使用 UTC 时间存储,前端根据用户时区显示。
|
||||||
|
|
||||||
|
### Q3: 软删除如何实现?
|
||||||
|
|
||||||
|
使用 GORM 的 `deleted_at` 字段,查询时自动过滤已删除数据。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 联系方式
|
||||||
|
|
||||||
|
如有问题,请查看:
|
||||||
|
- [Issues](./ISSUES.md) - 开发任务和问题追踪
|
||||||
|
- [API 文档](./API.md) - 接口详细说明
|
||||||
|
- [PRD](./PRD.md) - 产品需求细节
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
-- 保质期提醒小程序 - 数据库初始化脚本
|
||||||
|
-- 执行前请确保已创建数据库并选择正确的数据库
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 1. 物品表 (expiry_items)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS expiry_items (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID(关联 users 表)',
|
||||||
|
mini_program_id BIGINT UNSIGNED NOT NULL COMMENT '小程序ID(关联 mini_programs 表)',
|
||||||
|
name VARCHAR(100) NOT NULL COMMENT '物品名称',
|
||||||
|
category VARCHAR(20) NOT NULL COMMENT '分类:food/medicine/cosmetic/other',
|
||||||
|
production_date DATE DEFAULT NULL COMMENT '生产日期(可选)',
|
||||||
|
expiry_date DATE NOT NULL COMMENT '过期日期',
|
||||||
|
shelf_life_days INT DEFAULT NULL COMMENT '保质期天数(可选)',
|
||||||
|
quantity INT DEFAULT 1 COMMENT '数量',
|
||||||
|
location VARCHAR(50) DEFAULT NULL COMMENT '存放位置(可选)',
|
||||||
|
remark VARCHAR(255) DEFAULT NULL COMMENT '备注(可选)',
|
||||||
|
status VARCHAR(20) DEFAULT 'normal' COMMENT '状态:normal/used/expired/discarded',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
deleted_at TIMESTAMP NULL DEFAULT NULL COMMENT '软删除时间',
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
INDEX idx_user_expiry (user_id, expiry_date) COMMENT '用户+过期日期索引(最常用查询)',
|
||||||
|
INDEX idx_user_category (user_id, category) COMMENT '用户+分类索引',
|
||||||
|
INDEX idx_user_status (user_id, status) COMMENT '用户+状态索引',
|
||||||
|
INDEX idx_deleted_at (deleted_at) COMMENT '软删除索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保质期物品表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 2. 用户设置表 (expiry_user_settings)
|
||||||
|
-- ============================================
|
||||||
|
CREATE TABLE IF NOT EXISTS expiry_user_settings (
|
||||||
|
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL UNIQUE COMMENT '用户ID(唯一)',
|
||||||
|
remind_days JSON DEFAULT '[7,3,1]' COMMENT '提醒天数数组,例如 [7,3,1]',
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||||
|
|
||||||
|
-- 索引
|
||||||
|
INDEX idx_user_id (user_id) COMMENT '用户ID索引'
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='保质期用户设置表';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 3. 插入测试数据(可选)
|
||||||
|
-- ============================================
|
||||||
|
-- 注意:以下测试数据需要根据实际的 user_id 和 mini_program_id 调整
|
||||||
|
|
||||||
|
-- 示例:假设 user_id=1, mini_program_id=1
|
||||||
|
-- INSERT INTO expiry_items (user_id, mini_program_id, name, category, production_date, expiry_date, shelf_life_days, quantity, location, remark) VALUES
|
||||||
|
-- (1, 1, '伊利纯牛奶', 'food', '2026-02-01', '2026-03-10', 37, 6, '冰箱', ''),
|
||||||
|
-- (1, 1, '感冒灵颗粒', 'medicine', NULL, '2027-12-31', NULL, 1, '药箱', ''),
|
||||||
|
-- (1, 1, '雅诗兰黛面霜', 'cosmetic', '2025-06-01', '2028-06-01', 1095, 1, '梳妆台', '');
|
||||||
|
|
||||||
|
-- 插入默认用户设置
|
||||||
|
-- INSERT INTO expiry_user_settings (user_id, remind_days) VALUES
|
||||||
|
-- (1, '[7,3,1]');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 4. 验证表结构
|
||||||
|
-- ============================================
|
||||||
|
-- 查看表结构
|
||||||
|
-- SHOW CREATE TABLE expiry_items;
|
||||||
|
-- SHOW CREATE TABLE expiry_user_settings;
|
||||||
|
|
||||||
|
-- 查看索引
|
||||||
|
-- SHOW INDEX FROM expiry_items;
|
||||||
|
-- SHOW INDEX FROM expiry_user_settings;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 5. 常用查询示例
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 查询用户的所有物品
|
||||||
|
-- SELECT * FROM expiry_items WHERE user_id = 1 AND deleted_at IS NULL;
|
||||||
|
|
||||||
|
-- 查询即将过期的物品(7天内)
|
||||||
|
-- SELECT * FROM expiry_items
|
||||||
|
-- WHERE user_id = 1
|
||||||
|
-- AND deleted_at IS NULL
|
||||||
|
-- AND expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)
|
||||||
|
-- ORDER BY expiry_date ASC;
|
||||||
|
|
||||||
|
-- 查询已过期的物品
|
||||||
|
-- SELECT * FROM expiry_items
|
||||||
|
-- WHERE user_id = 1
|
||||||
|
-- AND deleted_at IS NULL
|
||||||
|
-- AND expiry_date < CURDATE()
|
||||||
|
-- ORDER BY expiry_date DESC;
|
||||||
|
|
||||||
|
-- 统计汇总
|
||||||
|
-- SELECT
|
||||||
|
-- COUNT(*) as total_items,
|
||||||
|
-- SUM(CASE WHEN expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY) THEN 1 ELSE 0 END) as expiring_soon,
|
||||||
|
-- SUM(CASE WHEN expiry_date < CURDATE() THEN 1 ELSE 0 END) as expired,
|
||||||
|
-- SUM(CASE WHEN expiry_date > DATE_ADD(CURDATE(), INTERVAL 7 DAY) THEN 1 ELSE 0 END) as normal
|
||||||
|
-- FROM expiry_items
|
||||||
|
-- WHERE user_id = 1
|
||||||
|
-- AND deleted_at IS NULL
|
||||||
|
-- AND status NOT IN ('used', 'discarded');
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 6. 数据清理(谨慎使用)
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 物理删除已软删除超过30天的记录
|
||||||
|
-- DELETE FROM expiry_items
|
||||||
|
-- WHERE deleted_at IS NOT NULL
|
||||||
|
-- AND deleted_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
|
||||||
|
|
||||||
|
-- 清空测试数据(谨慎使用!)
|
||||||
|
-- TRUNCATE TABLE expiry_items;
|
||||||
|
-- TRUNCATE TABLE expiry_user_settings;
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 7. 性能优化建议
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 定期分析表,优化查询性能
|
||||||
|
-- ANALYZE TABLE expiry_items;
|
||||||
|
-- ANALYZE TABLE expiry_user_settings;
|
||||||
|
|
||||||
|
-- 查看表状态
|
||||||
|
-- SHOW TABLE STATUS LIKE 'expiry_%';
|
||||||
|
|
||||||
|
-- ============================================
|
||||||
|
-- 初始化完成
|
||||||
|
-- ============================================
|
||||||
Reference in New Issue
Block a user