feat: 添加设计文件packages
This commit is contained in:
513
packages/design-document/我编写的文档/付费订阅数据库设计.md
Normal file
513
packages/design-document/我编写的文档/付费订阅数据库设计.md
Normal file
@@ -0,0 +1,513 @@
|
||||
订阅相关表设计
|
||||
---
|
||||
## 表结构总览
|
||||
数据库使用 PostgreSQL
|
||||
|
||||
```
|
||||
订阅相关
|
||||
├── subscription_plans (订阅计划表)
|
||||
├── subscriptions (用户订阅表)
|
||||
├── subscription_history (订阅历史记录表)
|
||||
├── payments (支付记录表)
|
||||
└── subscription_features (订阅功能权限表)
|
||||
```
|
||||
## 订阅相关表设计
|
||||
|
||||
### subscription_plans 订阅计划表
|
||||
**表结构**
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| plan_id | BIGSERIAL | PRIMARY KEY | 计划ID,自增 |
|
||||
| plan_code | VARCHAR(50) | NOT NULL, UNIQUE | 计划代码(如:annual_19) |
|
||||
| plan_name | VARCHAR(100) | NOT NULL | 计划名称(如:年付会员) |
|
||||
| plan_type | VARCHAR(20) | NOT NULL, CHECK | 计划类型:monthly/yearly/lifetime |
|
||||
| price | DECIMAL(10, 2) | NOT NULL | 价格(元) |
|
||||
| duration_days | INTEGER | NOT NULL | 订阅时长(天) |
|
||||
| features | JSONB | | 包含的功能列表(JSON格式) |
|
||||
| is_active | BOOLEAN | NOT NULL, DEFAULT true | 是否启用 |
|
||||
| sort_order | INTEGER | DEFAULT 0 | 排序顺序 |
|
||||
| description | TEXT | | 计划描述 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**创建语句**
|
||||
|
||||
```sql
|
||||
CREATE TABLE subscription_plans (
|
||||
plan_id BIGSERIAL PRIMARY KEY,
|
||||
plan_code VARCHAR(50) NOT NULL UNIQUE,
|
||||
plan_name VARCHAR(100) NOT NULL,
|
||||
plan_type VARCHAR(20) NOT NULL,
|
||||
price DECIMAL(10, 2) NOT NULL,
|
||||
duration_days INTEGER NOT NULL,
|
||||
features JSONB,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
sort_order INTEGER DEFAULT 0,
|
||||
description TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT check_plan_type CHECK (plan_type IN ('monthly', 'yearly', 'lifetime')),
|
||||
CONSTRAINT check_price_positive CHECK (price >= 0),
|
||||
CONSTRAINT check_duration_positive CHECK (duration_days > 0)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_subscription_plans_code ON subscription_plans(plan_code);
|
||||
CREATE INDEX idx_subscription_plans_type ON subscription_plans(plan_type);
|
||||
CREATE INDEX idx_subscription_plans_active ON subscription_plans(is_active);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE subscription_plans IS '订阅计划表,定义可购买的订阅套餐';
|
||||
COMMENT ON COLUMN subscription_plans.plan_code IS '计划代码,用于系统识别,如:annual_19';
|
||||
COMMENT ON COLUMN subscription_plans.features IS '包含的功能列表,JSON格式,如:["advanced_analytics", "export_data"]';
|
||||
```
|
||||
|
||||
**示例数据**
|
||||
|
||||
```sql
|
||||
-- 年付19元计划
|
||||
INSERT INTO subscription_plans (plan_code, plan_name, plan_type, price, duration_days, features, description)
|
||||
VALUES (
|
||||
'annual_19',
|
||||
'年付会员',
|
||||
'yearly',
|
||||
19.00,
|
||||
365,
|
||||
'["advanced_analytics", "export_data", "unlimited_accounts", "priority_support"]'::jsonb,
|
||||
'年付19元,解锁高级功能'
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### subscriptions 用户订阅表
|
||||
|
||||
**表结构**
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| subscription_id | BIGSERIAL | PRIMARY KEY | 订阅ID,自增 |
|
||||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID,关联 users 表 |
|
||||
| plan_id | BIGINT | NOT NULL, FOREIGN KEY | 订阅计划ID |
|
||||
| status | VARCHAR(20) | NOT NULL, CHECK | 订阅状态:active/expired/cancelled |
|
||||
| start_date | DATE | NOT NULL | 订阅开始日期 |
|
||||
| end_date | DATE | NOT NULL | 订阅结束日期 |
|
||||
| auto_renew | BOOLEAN | NOT NULL, DEFAULT false | 是否自动续费 |
|
||||
| cancelled_at | TIMESTAMP | | 取消时间 |
|
||||
| cancelled_reason | VARCHAR(255) | | 取消原因 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**创建语句**
|
||||
|
||||
```sql
|
||||
CREATE TABLE subscriptions (
|
||||
subscription_id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
plan_id BIGINT NOT NULL REFERENCES subscription_plans(plan_id),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
auto_renew BOOLEAN NOT NULL DEFAULT false,
|
||||
cancelled_at TIMESTAMP,
|
||||
cancelled_reason VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT check_subscription_status CHECK (status IN ('active', 'expired', 'cancelled', 'pending')),
|
||||
CONSTRAINT check_date_order CHECK (end_date >= start_date)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_subscriptions_user_id ON subscriptions(user_id);
|
||||
CREATE INDEX idx_subscriptions_plan_id ON subscriptions(plan_id);
|
||||
CREATE INDEX idx_subscriptions_status ON subscriptions(status);
|
||||
CREATE INDEX idx_subscriptions_end_date ON subscriptions(end_date);
|
||||
CREATE INDEX idx_subscriptions_user_status ON subscriptions(user_id, status);
|
||||
|
||||
-- 创建触发器自动更新 updated_at
|
||||
CREATE TRIGGER update_subscriptions_updated_at
|
||||
BEFORE UPDATE ON subscriptions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE subscriptions IS '用户订阅表,记录用户当前的订阅状态';
|
||||
COMMENT ON COLUMN subscriptions.status IS '订阅状态:active(活跃)/expired(已过期)/cancelled(已取消)/pending(待支付)';
|
||||
COMMENT ON COLUMN subscriptions.auto_renew IS '是否自动续费,true表示到期自动续费';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### subscription_history 订阅历史记录表
|
||||
|
||||
**表结构**
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| history_id | BIGSERIAL | PRIMARY KEY | 历史记录ID |
|
||||
| subscription_id | BIGINT | NOT NULL, FOREIGN KEY | 订阅ID |
|
||||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID |
|
||||
| plan_id | BIGINT | NOT NULL, FOREIGN KEY | 订阅计划ID |
|
||||
| action | VARCHAR(20) | NOT NULL, CHECK | 操作类型:purchase/renew/cancel/expire |
|
||||
| old_status | VARCHAR(20) | | 原状态 |
|
||||
| new_status | VARCHAR(20) | | 新状态 |
|
||||
| old_end_date | DATE | | 原结束日期 |
|
||||
| new_end_date | DATE | | 新结束日期 |
|
||||
| amount | DECIMAL(10, 2) | | 金额(如果是购买/续费) |
|
||||
| payment_id | BIGINT | FOREIGN KEY | 关联的支付记录ID |
|
||||
| notes | TEXT | | 备注 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
**创建语句**
|
||||
|
||||
```sql
|
||||
CREATE TABLE subscription_history (
|
||||
history_id BIGSERIAL PRIMARY KEY,
|
||||
subscription_id BIGINT NOT NULL REFERENCES subscriptions(subscription_id) ON DELETE CASCADE,
|
||||
user_id BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
plan_id BIGINT NOT NULL REFERENCES subscription_plans(plan_id),
|
||||
action VARCHAR(20) NOT NULL,
|
||||
old_status VARCHAR(20),
|
||||
new_status VARCHAR(20),
|
||||
old_end_date DATE,
|
||||
new_end_date DATE,
|
||||
amount DECIMAL(10, 2),
|
||||
payment_id BIGINT REFERENCES payments(payment_id),
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT check_action_type CHECK (action IN ('purchase', 'renew', 'cancel', 'expire', 'upgrade', 'downgrade'))
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_subscription_history_user_id ON subscription_history(user_id);
|
||||
CREATE INDEX idx_subscription_history_subscription_id ON subscription_history(subscription_id);
|
||||
CREATE INDEX idx_subscription_history_action ON subscription_history(action);
|
||||
CREATE INDEX idx_subscription_history_created_at ON subscription_history(created_at);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE subscription_history IS '订阅历史记录表,记录所有订阅状态变更';
|
||||
COMMENT ON COLUMN subscription_history.action IS '操作类型:purchase(购买)/renew(续费)/cancel(取消)/expire(过期)/upgrade(升级)/downgrade(降级)';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### payments 支付记录表
|
||||
|
||||
**表结构**
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| payment_id | BIGSERIAL | PRIMARY KEY | 支付ID,自增 |
|
||||
| user_id | BIGINT | NOT NULL, FOREIGN KEY | 用户ID |
|
||||
| subscription_id | BIGINT | FOREIGN KEY | 关联的订阅ID |
|
||||
| plan_id | BIGINT | NOT NULL, FOREIGN KEY | 订阅计划ID |
|
||||
| payment_method | VARCHAR(20) | NOT NULL, CHECK | 支付方式:wechat/alipay/apple/google |
|
||||
| payment_channel | VARCHAR(50) | | 支付渠道(如:微信小程序、H5等) |
|
||||
| transaction_id | VARCHAR(100) | UNIQUE | 第三方交易号 |
|
||||
| order_no | VARCHAR(100) | NOT NULL, UNIQUE | 内部订单号 |
|
||||
| amount | DECIMAL(10, 2) | NOT NULL | 支付金额 |
|
||||
| currency | VARCHAR(10) | NOT NULL, DEFAULT 'CNY' | 货币类型 |
|
||||
| status | VARCHAR(20) | NOT NULL, CHECK | 支付状态:pending/success/failed/refunded |
|
||||
| paid_at | TIMESTAMP | | 支付完成时间 |
|
||||
| refunded_at | TIMESTAMP | | 退款时间 |
|
||||
| refund_amount | DECIMAL(10, 2) | | 退款金额 |
|
||||
| refund_reason | VARCHAR(255) | | 退款原因 |
|
||||
| metadata | JSONB | | 额外信息(如:微信支付回调数据) |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**创建语句**
|
||||
|
||||
```sql
|
||||
CREATE TABLE payments (
|
||||
payment_id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(user_id) ON DELETE CASCADE,
|
||||
subscription_id BIGINT REFERENCES subscriptions(subscription_id),
|
||||
plan_id BIGINT NOT NULL REFERENCES subscription_plans(plan_id),
|
||||
payment_method VARCHAR(20) NOT NULL,
|
||||
payment_channel VARCHAR(50),
|
||||
transaction_id VARCHAR(100) UNIQUE,
|
||||
order_no VARCHAR(100) NOT NULL UNIQUE,
|
||||
amount DECIMAL(10, 2) NOT NULL,
|
||||
currency VARCHAR(10) NOT NULL DEFAULT 'CNY',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
paid_at TIMESTAMP,
|
||||
refunded_at TIMESTAMP,
|
||||
refund_amount DECIMAL(10, 2),
|
||||
refund_reason VARCHAR(255),
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT check_payment_method CHECK (payment_method IN ('wechat', 'alipay', 'apple', 'google', 'other')),
|
||||
CONSTRAINT check_payment_status CHECK (status IN ('pending', 'success', 'failed', 'refunded', 'cancelled')),
|
||||
CONSTRAINT check_amount_positive CHECK (amount > 0)
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_payments_user_id ON payments(user_id);
|
||||
CREATE INDEX idx_payments_subscription_id ON payments(subscription_id);
|
||||
CREATE INDEX idx_payments_order_no ON payments(order_no);
|
||||
CREATE INDEX idx_payments_transaction_id ON payments(transaction_id);
|
||||
CREATE INDEX idx_payments_status ON payments(status);
|
||||
CREATE INDEX idx_payments_created_at ON payments(created_at);
|
||||
|
||||
-- 创建触发器自动更新 updated_at
|
||||
CREATE TRIGGER update_payments_updated_at
|
||||
BEFORE UPDATE ON payments
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE payments IS '支付记录表,记录所有支付交易';
|
||||
COMMENT ON COLUMN payments.order_no IS '内部订单号,系统生成,唯一';
|
||||
COMMENT ON COLUMN payments.transaction_id IS '第三方支付平台的交易号';
|
||||
COMMENT ON COLUMN payments.metadata IS '额外信息,JSON格式,存储支付回调数据等';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### subscription_features 订阅功能权限表
|
||||
|
||||
**表结构**
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| feature_id | BIGSERIAL | PRIMARY KEY | 功能ID |
|
||||
| feature_code | VARCHAR(50) | NOT NULL, UNIQUE | 功能代码 |
|
||||
| feature_name | VARCHAR(100) | NOT NULL | 功能名称 |
|
||||
| feature_type | VARCHAR(20) | NOT NULL, CHECK | 功能类型:basic/premium |
|
||||
| description | TEXT | | 功能描述 |
|
||||
| is_active | BOOLEAN | NOT NULL, DEFAULT true | 是否启用 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
**创建语句**
|
||||
|
||||
```sql
|
||||
CREATE TABLE subscription_features (
|
||||
feature_id BIGSERIAL PRIMARY KEY,
|
||||
feature_code VARCHAR(50) NOT NULL UNIQUE,
|
||||
feature_name VARCHAR(100) NOT NULL,
|
||||
feature_type VARCHAR(20) NOT NULL,
|
||||
description TEXT,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT check_feature_type CHECK (feature_type IN ('basic', 'premium'))
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX idx_subscription_features_code ON subscription_features(feature_code);
|
||||
CREATE INDEX idx_subscription_features_type ON subscription_features(feature_type);
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON TABLE subscription_features IS '订阅功能权限表,定义所有可用的功能';
|
||||
COMMENT ON COLUMN subscription_features.feature_code IS '功能代码,用于系统识别,如:advanced_analytics';
|
||||
```
|
||||
|
||||
**示例数据**
|
||||
|
||||
```sql
|
||||
-- 基础功能(免费)
|
||||
INSERT INTO subscription_features (feature_code, feature_name, feature_type, description)
|
||||
VALUES
|
||||
('basic_records', '基础记录', 'basic', '记录持仓和交易'),
|
||||
('basic_stats', '基础统计', 'basic', '查看基础收益统计');
|
||||
|
||||
-- 高级功能(付费)
|
||||
INSERT INTO subscription_features (feature_code, feature_name, feature_type, description)
|
||||
VALUES
|
||||
('advanced_analytics', '高级分析', 'premium', '深度收益分析和图表'),
|
||||
('export_data', '数据导出', 'premium', '导出Excel/CSV数据'),
|
||||
('unlimited_accounts', '无限账户', 'premium', '支持多个券商账户'),
|
||||
('priority_support', '优先支持', 'premium', '优先客服支持');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 订阅系统业务流程
|
||||
|
||||
### 1. 用户购买订阅流程
|
||||
|
||||
```sql
|
||||
-- 步骤1:创建支付记录
|
||||
INSERT INTO payments (user_id, plan_id, payment_method, order_no, amount, status)
|
||||
VALUES (:user_id, :plan_id, 'wechat', :order_no, 19.00, 'pending');
|
||||
|
||||
-- 步骤2:支付成功后,创建订阅
|
||||
INSERT INTO subscriptions (user_id, plan_id, status, start_date, end_date)
|
||||
VALUES (
|
||||
:user_id,
|
||||
:plan_id,
|
||||
'active',
|
||||
CURRENT_DATE,
|
||||
CURRENT_DATE + INTERVAL '365 days'
|
||||
);
|
||||
|
||||
-- 步骤3:更新支付记录
|
||||
UPDATE payments
|
||||
SET status = 'success', paid_at = CURRENT_TIMESTAMP, subscription_id = :subscription_id
|
||||
WHERE payment_id = :payment_id;
|
||||
|
||||
-- 步骤4:记录订阅历史
|
||||
INSERT INTO subscription_history (
|
||||
subscription_id, user_id, plan_id, action,
|
||||
new_status, new_end_date, amount, payment_id
|
||||
)
|
||||
VALUES (
|
||||
:subscription_id, :user_id, :plan_id, 'purchase',
|
||||
'active', CURRENT_DATE + INTERVAL '365 days', 19.00, :payment_id
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 检查用户功能权限
|
||||
|
||||
```sql
|
||||
-- 查询用户是否有某个功能权限
|
||||
SELECT
|
||||
CASE
|
||||
WHEN s.status = 'active' AND s.end_date >= CURRENT_DATE THEN true
|
||||
ELSE false
|
||||
END as has_access,
|
||||
sp.features
|
||||
FROM subscriptions s
|
||||
INNER JOIN subscription_plans sp ON s.plan_id = sp.plan_id
|
||||
WHERE s.user_id = :user_id
|
||||
AND s.status = 'active'
|
||||
AND s.end_date >= CURRENT_DATE
|
||||
ORDER BY s.end_date DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- 或者检查特定功能
|
||||
SELECT
|
||||
CASE
|
||||
WHEN s.status = 'active'
|
||||
AND s.end_date >= CURRENT_DATE
|
||||
AND sp.features @> '["advanced_analytics"]'::jsonb
|
||||
THEN true
|
||||
ELSE false
|
||||
END as has_advanced_analytics
|
||||
FROM subscriptions s
|
||||
INNER JOIN subscription_plans sp ON s.plan_id = sp.plan_id
|
||||
WHERE s.user_id = :user_id
|
||||
ORDER BY s.end_date DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
### 3. 订阅过期处理
|
||||
|
||||
```sql
|
||||
-- 定时任务:检查并更新过期订阅
|
||||
UPDATE subscriptions
|
||||
SET status = 'expired'
|
||||
WHERE status = 'active'
|
||||
AND end_date < CURRENT_DATE;
|
||||
|
||||
-- 记录过期历史
|
||||
INSERT INTO subscription_history (
|
||||
subscription_id, user_id, plan_id, action,
|
||||
old_status, new_status, old_end_date
|
||||
)
|
||||
SELECT
|
||||
subscription_id, user_id, plan_id, 'expire',
|
||||
'active', 'expired', end_date
|
||||
FROM subscriptions
|
||||
WHERE status = 'expired'
|
||||
AND end_date < CURRENT_DATE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM subscription_history
|
||||
WHERE subscription_id = subscriptions.subscription_id
|
||||
AND action = 'expire'
|
||||
AND created_at::date = CURRENT_DATE
|
||||
);
|
||||
```
|
||||
|
||||
### 4. 自动续费处理
|
||||
|
||||
```sql
|
||||
-- 查询即将到期且开启自动续费的订阅
|
||||
SELECT s.*, sp.price, sp.plan_code
|
||||
FROM subscriptions s
|
||||
INNER JOIN subscription_plans sp ON s.plan_id = sp.plan_id
|
||||
WHERE s.status = 'active'
|
||||
AND s.auto_renew = true
|
||||
AND s.end_date BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '3 days';
|
||||
|
||||
-- 执行自动续费(创建新的支付记录和延长订阅)
|
||||
BEGIN;
|
||||
-- 创建支付记录
|
||||
INSERT INTO payments (user_id, plan_id, payment_method, order_no, amount, status)
|
||||
VALUES (:user_id, :plan_id, 'auto_renew', :order_no, :price, 'pending');
|
||||
|
||||
-- 延长订阅
|
||||
UPDATE subscriptions
|
||||
SET end_date = end_date + INTERVAL '365 days',
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE subscription_id = :subscription_id;
|
||||
|
||||
-- 记录历史
|
||||
INSERT INTO subscription_history (...)
|
||||
VALUES (...);
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据一致性**
|
||||
- 订阅状态变更时,必须同时更新 subscriptions 和 subscription_history
|
||||
- 支付成功后才创建订阅,避免未支付就开通
|
||||
|
||||
2. **功能权限检查**
|
||||
- 每次使用功能前都要检查订阅状态和功能权限
|
||||
- 可以缓存用户权限,但需要设置合理的过期时间
|
||||
|
||||
3. **订阅过期处理**
|
||||
- 建议使用定时任务每日检查过期订阅
|
||||
- 过期后立即更新状态,避免用户继续使用付费功能
|
||||
|
||||
4. **自动续费**
|
||||
- 需要在订阅到期前3-7天提醒用户
|
||||
- 自动续费失败时,需要通知用户并更新订阅状态
|
||||
|
||||
5. **退款处理**
|
||||
- 退款时需要更新支付状态和订阅状态
|
||||
- 按比例计算退款金额(已使用天数)
|
||||
|
||||
6. **数据安全**
|
||||
- 支付相关数据需要加密存储
|
||||
- 交易号等敏感信息需要脱敏处理
|
||||
|
||||
7. **持仓自动价格更新功能联动**
|
||||
- 用户购买订阅后,需要批量更新该用户所有持仓的 `auto_price_update = true`
|
||||
- 用户订阅过期或取消后,需要批量更新该用户所有持仓的 `auto_price_update = false`
|
||||
- 建议在订阅状态变更时,同步更新 positions 表的 auto_price_update 字段
|
||||
|
||||
**示例SQL:**
|
||||
```sql
|
||||
-- 用户购买订阅后,启用自动价格更新
|
||||
UPDATE positions
|
||||
SET auto_price_update = true
|
||||
WHERE user_id = :user_id
|
||||
AND asset_type IN ('stock', 'fund', 'bond');
|
||||
|
||||
-- 用户订阅过期后,禁用自动价格更新
|
||||
UPDATE positions
|
||||
SET auto_price_update = false
|
||||
WHERE user_id = :user_id;
|
||||
|
||||
-- 查询付费用户的股票持仓(用于自动价格更新)
|
||||
SELECT
|
||||
p.position_id,
|
||||
p.symbol,
|
||||
p.market,
|
||||
p.asset_type,
|
||||
p.current_price
|
||||
FROM positions p
|
||||
WHERE p.auto_price_update = true
|
||||
AND p.asset_type = 'stock'
|
||||
AND p.status = 'active';
|
||||
```
|
||||
|
||||
93
packages/design-document/我编写的文档/投资记录模块-产品设计.md
Normal file
93
packages/design-document/我编写的文档/投资记录模块-产品设计.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 投资记录模块-产品设计
|
||||
|
||||
## 一、投资记录设计思路
|
||||
职责清晰:系统负责所有可规则化、基于市场公开事件的处理(被动变更);用户只负责输入自己主动发起的行为结果(主动变更)。
|
||||
输入极简:对于任何一笔交易,用户最核心、最确定的输入就是最终的持仓成本和最新的持仓份额。让用户只修改这两项,避免了理解复杂规则(如加权平均计算)的负担,也减少了输入错误的可能性。
|
||||
输入极简:对于任何一笔交易,用户最核心、最确定的输入就是最终的持仓成本和最新的持仓份额。让用户只修改这两项,避免了理解复杂规则(如加权平均计算)的负担,也减少了输入错误的可能性。
|
||||
|
||||
### 1.1 持仓变更设计
|
||||
1. 把所有的表更分为两种:`用户驱动(主动)` 和 `系统驱动(被动)`
|
||||
2. 系统驱动包含:现金分红、送股、拆股、汇率变动等
|
||||
3. 用户驱动包含:初始买入、追加买入、卖出;
|
||||
4. 所有的用户驱动,都只需要变更最终的持仓成本和最新的持仓份额,这两项。避免其他复杂的操作
|
||||
5. 每次用户主动变更,记录成本价和份额的同时,还需要完成如下记录:
|
||||
- 反向计算本次交易股价和份额,并记录;
|
||||
- 统计最新的份额和净值,并记录
|
||||
- 同时引导填下投资复盘和思考。
|
||||
6. 系统驱动的变更:(万一无法实现,可以降级为用户驱动变更)
|
||||
- 分红:收盘后获取每股分红金额,最新成本价 = 原成本价 - 分红,市场价逻辑保持不变(使用不复权的股价)
|
||||
- 送股、拆股等都变更最新的成本价和份额,并记录。
|
||||
|
||||
### 1.2 收益记录设计
|
||||
1. 使用基金净值法(时间加权收益率)来统计收益;
|
||||
2. 每次主动和被动变更,重新计算总体的资产金额和份额,记录到 daily_snapshots 表。
|
||||
3. 忘记记录的情况:如果用户忘记记录当日交易,过一段时间后再来记录,需要删除期间的快照数据,并保留期间的交易数据。然后删除期间的快照数据,重新生成。
|
||||
|
||||
### 1.3 系统驱动变价记录逻辑
|
||||
1. 首先需要有一个表记录所有股票的基本信息,方便用户在输入code或股票名是进行模糊匹配。这个数据因为不常变更,可以放在CDN上。
|
||||
2. 每日定时把所有用户需要自动变价的持仓(股票/基金)汇总,在收盘后查询这些股票的最新市价、市值、市盈率等信息(所有用的是持仓最新价格应该依赖这个表,减少直接查询外部证券接口的次数)
|
||||
3. 更新完股票的最新市价后,在定时更新用户持仓表中的持仓数据、收益数据,对应 positions 和 asset_snapshots 表。
|
||||
|
||||
|
||||
## 二、持仓页面详细设计
|
||||
|
||||
### 页面概述
|
||||
页面整体展示三部分内容:
|
||||
- 资产概览、
|
||||
- 资产和收益图表(累计资产折线图、收益率折线图)、
|
||||
- 持仓百分比图
|
||||
- 我的持仓列表
|
||||
|
||||
### 功能需求
|
||||
#### 资产概览
|
||||
以卡片形式展示资产概览,左上角显示用户昵称,下边显示记账时长(例如 记账:300天),右侧从上到下分别展示总金额(大字红色展示)、上一个交易日收益、累计收益、累计收益率、年化收益率。
|
||||
|
||||
#### 收益图表
|
||||
- 第一个图表:默认展示从记账以来每日总金额的折线图,可以切换时间(近5日、本月、近一个月、近一年、记账以来、自定义)
|
||||
- 第二个图表:默认展示从记账以来每日累计收益率,可以切换不同时间段(近5日、本月、近一个月、近一年、记账以来、自定义)
|
||||
(PS: 你在输出设计稿的时候,需要使用 Echarts 来模拟)
|
||||
|
||||
#### 持仓百分比
|
||||
使用环图,展示各个持仓资产项的金额百分比
|
||||
(PS: 你在输出设计稿的时候,需要使用 Echarts 来模拟)
|
||||
|
||||
#### 我的持仓列表
|
||||
**展示形式**
|
||||
title 左侧展示 “我的持仓”,右侧展示一个 圆形的加号(添加资产项) 按钮,可以添加新的资产项。
|
||||
以卡片形式展示 资产项:
|
||||
- 左侧依次展示:公司简称(大号字体)、股票代码、市场、持股市场、证券公司简称(这一块你要帮我看看如何排列会更好)
|
||||
- 中间展示:持股数、持股时长。
|
||||
- 右侧展示:从上到下依次展示昨日市价、盈亏金额、收益率。
|
||||
- 右小角展示一个 “更新资产”(名称可以你再帮我想一下) 的按钮,点击从底部弹出Popup页(变更资产页)
|
||||
|
||||
**操作形式**
|
||||
- 点击 圆形的“添加资产项”按钮,从底部弹出 新增资产页。
|
||||
- 点击 资产项卡片总的“更新资产”的按钮,从底部弹出 变更资产页
|
||||
|
||||
|
||||
## 三、新增资产页/变更资产页
|
||||
这个页面有两种形态,新增和变更,差别是新增最上面有搜索框,变更没有搜索框。都是冲底部弹出,再上滑,可以变成全屏页面。
|
||||
|
||||
**新增资产页**
|
||||
最上面可以选择资产类型,分别是:股票、基金、现金、其他
|
||||
|
||||
股票:
|
||||
先展示 搜索框,用户可以输入code或股票名称,自动联想,用户选择后确认股票。
|
||||
然后选择券商,下拉列表展示。
|
||||
选择后,之后依次展示股票名称、股票code、市场。
|
||||
之后展示成本价输入框和份数输入框。(输入时要考虑便捷性)
|
||||
|
||||
基金:
|
||||
不展示搜索框,仅展示输入框,需要用户自己输入。
|
||||
之后展示成本价输入框和份数输入框。(输入时要考虑便捷性)
|
||||
|
||||
现金和其他:
|
||||
不展示搜索框,仅展示输入框,需要用户自己输入资产名称。
|
||||
直接输入金额即可。
|
||||
|
||||
确认按钮:点击确认,提交数据到后台,按钮居中展示。
|
||||
|
||||
**变更资产页**
|
||||
最上面不需要在选择资产类型,下边的展示和`新增资产页` 一样。
|
||||
|
||||
|
||||
1319
packages/design-document/我编写的文档/数据库设计.md
Normal file
1319
packages/design-document/我编写的文档/数据库设计.md
Normal file
File diff suppressed because it is too large
Load Diff
28
packages/design-document/我编写的文档/整体产品设计思路.md
Normal file
28
packages/design-document/我编写的文档/整体产品设计思路.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# 整体产品设计思路
|
||||
---
|
||||
## 心智设计
|
||||
让每一笔投资都经过思考
|
||||
### 应用名思考
|
||||
- 投小记-记录、计划、复盘你的投资
|
||||
- 投小记-记录、决策、复盘你的投资
|
||||
|
||||
投小记 - 专业投资记账助手,助您记录每一笔交易、制定投资计划、进行投资复盘,轻松管理股票、基金等资产收益。
|
||||
|
||||
### 页面Title设计
|
||||
每个页面除了标题外,下边附带一句话
|
||||
首页 - 买股票就是买公司
|
||||
交易计划 - 计划你的交易,交易你的计划
|
||||
复盘页 - 回顾过去是为了更好应对将来 ??
|
||||
|
||||
|
||||
## 页面设计原则
|
||||
### 配置方案
|
||||
主题色:`#8b5cf6`,紫色主题
|
||||
|
||||
### 设计原则
|
||||
- 简洁又不失个性
|
||||
- 体现 安静与思考 原则
|
||||
-
|
||||
|
||||
## 想法
|
||||
- 私密分享:可以将自己的交易计划和复盘,通过小程序私密分享-分享给其他人,这样即保障了裂变属性,有增加了隐私安全。
|
||||
6
packages/design-document/我编写的文档/计划模块-产品设计.md
Normal file
6
packages/design-document/我编写的文档/计划模块-产品设计.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# 计划模块-产品设计
|
||||
|
||||
## 二、交易计划模块
|
||||
### 1. 创建计划
|
||||
核心思想:计划应当和估值合并,计划依赖于估值。
|
||||
1. 交易计划列表页:
|
||||
Reference in New Issue
Block a user