54 KiB
相亲小程序 · 完整开发规格文档
文档用途:本文档供 AI 编程助手直接执行,包含完整的功能清单、目录结构、数据库设计、API 接口、页面逻辑和开发注意事项。
技术栈:微信小程序原生(微信开发者工具) + Python FastAPI 后端
阅读顺序:先看第 1-3 节了解全局,再按模块逐节实现。
目录
1. 项目概览
1.1 产品定位
线下相亲活动组织工具 + AI 辅助匹配平台。核心流程:
用户注册 → 填写资料 → 提交审核 → 审核通过 → 报名活动 / 参与匹配
1.2 技术栈总览
| 层 | 技术 | 说明 |
|---|---|---|
| 小程序前端 | 微信小程序原生(WXML/WXSS/JS) | 使用微信开发者工具开发 |
| 后端框架 | Python 3.11 + FastAPI | RESTful API,async 支持 |
| 数据库 | MySQL 8.0 | 主数据库 |
| 缓存 | Redis 7.x | 会话、频控、热数据缓存 |
| ORM | SQLAlchemy 2.x (async) | 数据库操作 |
| 数据库迁移 | Alembic | 版本管理 |
| 文件存储 | 腾讯云 COS | 头像/活动图片存储 |
| 消息推送 | 微信订阅消息 | 审核结果、匹配成功通知 |
| 后台管理 | Vue3 + Element Plus(独立项目) | 管理员操作界面 |
1.3 用户角色
- 普通用户:微信登录,填写资料,报名活动,参与匹配
- 管理员:后台审核资料,管理活动,查看数据看板
- 超级管理员:系统配置,管理员账号管理
2. 目录结构
2.1 小程序前端结构
miniprogram/
├── app.js # 全局入口,处理登录态初始化
├── app.json # 全局配置(TabBar、页面注册)
├── app.wxss # 全局样式
├── project.config.json # 项目配置
│
├── pages/
│ ├── home/ # 首页
│ │ ├── home.js
│ │ ├── home.wxml
│ │ ├── home.wxss
│ │ └── home.json
│ ├── activity/ # 活动列表页
│ │ ├── activity.js
│ │ ├── activity.wxml
│ │ ├── activity.wxss
│ │ └── activity.json
│ ├── activity-detail/ # 活动详情+报名页
│ │ └── ...
│ ├── match/ # 匹配主页
│ │ └── ...
│ ├── profile/ # 我的页面
│ │ └── ...
│ ├── profile-edit/ # 资料填写/编辑
│ │ └── ...
│ ├── audit-status/ # 审核状态追踪页
│ │ └── ...
│ ├── my-activities/ # 我报名的活动列表
│ │ └── ...
│ ├── my-matches/ # 我的匹配记录
│ │ └── ...
│ └── announcement/ # 公告详情页
│ └── ...
│
├── components/
│ ├── member-card/ # 会员卡片组件(首页随机展示用)
│ ├── activity-card/ # 活动卡片组件
│ ├── match-card/ # 匹配候选人卡片组件
│ ├── audit-badge/ # 认证/审核状态徽章
│ ├── progress-bar/ # 资料完整度进度条
│ └── empty-state/ # 空状态占位组件
│
└── utils/
├── request.js # 统一 HTTP 请求封装(含 token 刷新)
├── auth.js # 登录态管理
├── upload.js # 图片上传到 COS 封装
└── constants.js # 全局常量(API_BASE_URL 等)
2.2 后端结构
backend/
├── main.py # FastAPI 入口
├── requirements.txt
├── .env # 环境变量(不提交 git)
├── alembic.ini
├── alembic/
│ └── versions/ # 数据库迁移文件
│
├── app/
│ ├── __init__.py
│ ├── config.py # 配置读取(从 .env)
│ ├── database.py # 数据库连接、Session 工厂
│ ├── dependencies.py # FastAPI 依赖注入(current_user 等)
│ │
│ ├── models/ # SQLAlchemy ORM 模型
│ │ ├── __init__.py
│ │ ├── user.py
│ │ ├── activity.py
│ │ ├── registration.py
│ │ ├── match.py
│ │ └── announcement.py
│ │
│ ├── schemas/ # Pydantic 请求/响应模型
│ │ ├── __init__.py
│ │ ├── user.py
│ │ ├── activity.py
│ │ ├── match.py
│ │ └── common.py
│ │
│ ├── routers/ # 路由模块
│ │ ├── __init__.py
│ │ ├── auth.py # 登录/刷新 token
│ │ ├── users.py # 用户信息
│ │ ├── activities.py # 活动相关
│ │ ├── matches.py # 匹配相关
│ │ ├── announcements.py
│ │ ├── upload.py # 文件上传
│ │ └── admin/ # 后台接口(需 admin 权限)
│ │ ├── users.py
│ │ ├── activities.py
│ │ ├── matches.py
│ │ └── dashboard.py
│ │
│ ├── services/ # 业务逻辑层
│ │ ├── auth_service.py
│ │ ├── match_service.py # AI 匹配核心算法
│ │ ├── notify_service.py # 微信订阅消息发送
│ │ └── cos_service.py # 腾讯云 COS 操作
│ │
│ └── utils/
│ ├── security.py # JWT 生成/验证
│ └── wx_api.py # 微信 API 调用封装(code2session 等)
3. 数据库设计
所有表均包含
created_at DATETIME和updated_at DATETIME(自动维护)。
字符集:utf8mb4,排序规则:utf8mb4_unicode_ci。
3.1 users 表
CREATE TABLE users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
openid VARCHAR(64) NOT NULL UNIQUE COMMENT '微信 openid',
unionid VARCHAR(64) COMMENT '微信 unionid(可选)',
-- 基础信息(用户填写)
nickname VARCHAR(50) COMMENT '昵称',
real_name VARCHAR(20) COMMENT '真实姓名(审核可见)',
gender TINYINT COMMENT '1=男 2=女',
birth_year SMALLINT COMMENT '出生年份',
city VARCHAR(30) COMMENT '所在城市',
height SMALLINT COMMENT '身高(cm)',
education TINYINT COMMENT '1=高中 2=大专 3=本科 4=硕士 5=博士',
job_industry VARCHAR(50) COMMENT '行业',
job_company VARCHAR(100) COMMENT '单位(仅匹配成功后展示)',
income_range TINYINT COMMENT '月收入段:1=<5k 2=5-10k 3=10-20k 4=20k+',
marriage_status TINYINT DEFAULT 1 COMMENT '1=未婚 2=离异 3=丧偶',
-- 性格与兴趣
personality_tags JSON COMMENT '性格标签数组,如["开朗","细心"]',
hobbies JSON COMMENT '爱好数组,如["旅游","读书","健身"]',
-- 价值观问卷答案
value_answers JSON COMMENT '价值观问卷,格式: {"q1":2,"q2":1,...}',
-- 择偶偏好(用于匹配过滤)
prefer_age_min SMALLINT COMMENT '期望对方最小出生年份(越大越年轻)',
prefer_age_max SMALLINT COMMENT '期望对方最大出生年份',
prefer_city VARCHAR(30) COMMENT '期望城市,null=不限',
prefer_education TINYINT COMMENT '期望最低学历,null=不限',
-- 自我介绍
self_intro TEXT COMMENT '自我介绍(500字内)',
-- 头像
avatar_url VARCHAR(500) COMMENT 'COS 头像 URL',
avatar_blur_url VARCHAR(500) COMMENT '模糊头像 URL(AI 生成或后端处理)',
-- 状态
audit_status TINYINT DEFAULT 0 COMMENT '0=未提交 1=待审核 2=审核通过 3=审核驳回',
audit_remark VARCHAR(500) COMMENT '驳回原因',
audit_time DATETIME COMMENT '审核时间',
auditor_id INT UNSIGNED COMMENT '审核人 admin ID',
-- 账号状态
is_active TINYINT DEFAULT 1 COMMENT '1=正常 0=封禁',
ban_reason VARCHAR(200),
-- 统计
profile_completeness TINYINT DEFAULT 0 COMMENT '资料完整度 0-100',
-- 微信订阅消息
subscribe_audit TINYINT DEFAULT 0 COMMENT '是否订阅审核结果通知',
subscribe_match TINYINT DEFAULT 0 COMMENT '是否订阅匹配成功通知',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_audit_status (audit_status),
INDEX idx_gender_city (gender, city)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 activities 表
CREATE TABLE activities (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL COMMENT '活动标题',
description TEXT COMMENT '活动详情(富文本/Markdown)',
category VARCHAR(20) COMMENT '类型:outdoor/dining/culture/sport/other',
location VARCHAR(200) COMMENT '活动地点',
cover_image VARCHAR(500) COMMENT '封面图 COS URL',
start_time DATETIME NOT NULL COMMENT '活动开始时间',
end_time DATETIME NOT NULL COMMENT '活动结束时间',
signup_deadline DATETIME COMMENT '报名截止时间',
capacity_male SMALLINT DEFAULT 0 COMMENT '男性名额(0=不限)',
capacity_female SMALLINT DEFAULT 0 COMMENT '女性名额(0=不限)',
-- 匹配窗口
match_window_hours INT DEFAULT 48 COMMENT '活动结束后开放匹配的小时数',
-- 状态
status TINYINT DEFAULT 0 COMMENT '0=草稿 1=报名中 2=报名截止 3=进行中 4=已结束',
is_published TINYINT DEFAULT 0 COMMENT '是否在小程序展示',
-- 报名要求
require_audit TINYINT DEFAULT 1 COMMENT '是否要求资料审核通过才能报名',
created_by INT UNSIGNED COMMENT '创建人 admin ID',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_published (status, is_published),
INDEX idx_start_time (start_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.3 registrations 表
CREATE TABLE registrations (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
activity_id INT UNSIGNED NOT NULL,
status TINYINT DEFAULT 1 COMMENT '1=待确认 2=已确认 3=已取消 4=已签到',
checked_in_at DATETIME COMMENT '签到时间',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_activity (user_id, activity_id),
INDEX idx_activity_id (activity_id),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (activity_id) REFERENCES activities(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.4 match_likes 表(单向感兴趣记录)
CREATE TABLE match_likes (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
from_user_id INT UNSIGNED NOT NULL COMMENT '发起感兴趣的用户',
to_user_id INT UNSIGNED NOT NULL COMMENT '被感兴趣的用户',
source VARCHAR(20) DEFAULT 'manual' COMMENT 'manual=手动 ai=AI推荐',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_like (from_user_id, to_user_id),
INDEX idx_to_user (to_user_id),
FOREIGN KEY (from_user_id) REFERENCES users(id),
FOREIGN KEY (to_user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.5 matches 表(双向匹配成功记录)
CREATE TABLE matches (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_a_id INT UNSIGNED NOT NULL COMMENT '较小 user_id(保证唯一性)',
user_b_id INT UNSIGNED NOT NULL COMMENT '较大 user_id',
match_score DECIMAL(5,2) COMMENT 'AI 匹配分 0-100',
match_type VARCHAR(20) DEFAULT 'manual' COMMENT 'manual/ai',
source_activity_id INT UNSIGNED COMMENT '来源活动(如果有)',
-- 双方是否查看了对方信息
a_viewed TINYINT DEFAULT 0,
b_viewed TINYINT DEFAULT 0,
matched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_match (user_a_id, user_b_id),
INDEX idx_user_a (user_a_id),
INDEX idx_user_b (user_b_id),
FOREIGN KEY (user_a_id) REFERENCES users(id),
FOREIGN KEY (user_b_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.6 announcements 表
CREATE TABLE announcements (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT NOT NULL COMMENT 'Markdown 内容',
is_pinned TINYINT DEFAULT 0 COMMENT '是否置顶',
is_published TINYINT DEFAULT 1,
created_by INT UNSIGNED,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.7 admins 表
CREATE TABLE admins (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(128) NOT NULL COMMENT 'bcrypt hash',
role TINYINT DEFAULT 1 COMMENT '1=普通管理员 2=超级管理员',
is_active TINYINT DEFAULT 1,
last_login DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. 后端 API 规格
4.1 通用规范
Base URL(开发): http://localhost:8000/api/v1
Base URL(生产): https://your-domain.com/api/v1
认证方式: Bearer Token(JWT)
Content-Type: application/json
统一响应格式:
{
"code": 0, // 0=成功,非0=错误
"message": "ok",
"data": {} // 业务数据
}
错误码约定:
1001 - token 无效或过期
1002 - 权限不足
1003 - 资源不存在
1004 - 参数校验失败
1005 - 业务逻辑错误(message 中包含具体原因)
4.2 认证接口
POST /auth/wx-login
微信登录,通过 code 换取系统 JWT token。
请求体:
{
"code": "微信登录临时code",
"subscribe_audit": true, // 是否订阅审核通知(首次登录时传)
"subscribe_match": true
}
响应数据:
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 7200,
"user_id": 123,
"audit_status": 0,
"is_new_user": true
}
后端实现要点:
- 调用微信
code2session接口获取openid - 用
openid查用户,不存在则自动创建 - 返回 JWT,payload 包含
user_id,openid,exp
POST /auth/refresh
刷新 token(在 access_token 快过期时调用)。
4.3 用户接口
GET /users/me
获取当前用户完整信息(含审核状态)。
响应数据:
{
"id": 123,
"nickname": "小明",
"gender": 1,
"birth_year": 1995,
"city": "北京",
"height": 175,
"education": 3,
"job_industry": "互联网",
"personality_tags": ["开朗", "细心"],
"hobbies": ["旅游", "读书"],
"self_intro": "...",
"avatar_url": "https://...",
"audit_status": 2,
"audit_remark": null,
"profile_completeness": 85
}
PUT /users/me
更新当前用户信息。提交后若 audit_status 为 0 或 3,自动置为 1(待审核)。
请求体(所有字段均可选,仅传需要更新的):
{
"nickname": "小明",
"gender": 1,
"birth_year": 1995,
"city": "北京",
"height": 175,
"education": 3,
"job_industry": "互联网",
"job_company": "某公司",
"income_range": 3,
"personality_tags": ["开朗"],
"hobbies": ["旅游"],
"value_answers": {"q1": 2, "q2": 1, "q3": 3, "q4": 2},
"prefer_age_min": 1990,
"prefer_age_max": 2000,
"prefer_city": "北京",
"self_intro": "..."
}
后端实现要点:
- 更新后重新计算
profile_completeness(见计算规则 §6) - 若
audit_status为 2(已通过),用户修改资料后状态重置为 1(待审核)
POST /users/submit-audit
提交资料审核申请(状态从0/3变为1)。需要头像已上传。
GET /users/public/{user_id}
获取其他用户的公开信息(根据匹配阶段返回不同字段,见信息分级规则)。
信息分级规则(后端强制执行):
- 未匹配:返回 nickname, birth_year范围, city, personality_tags, avatar_blur_url
- 单向感兴趣(我对他/她表示过兴趣):额外返回 avatar_url, education, hobbies
- 双向匹配成功:额外返回 job_industry, job_company, self_intro, income_range
4.4 活动接口
GET /activities
获取活动列表。
Query 参数:
status: 1=报名中 4=已结束 (可选,不传返回所有已发布)
page: 页码,默认1
page_size: 每页数量,默认10
响应数据(列表项):
{
"id": 1,
"title": "春日户外约会",
"category": "outdoor",
"cover_image": "https://...",
"start_time": "2025-04-20T14:00:00",
"end_time": "2025-04-20T18:00:00",
"signup_deadline": "2025-04-19T23:59:59",
"location": "北京朝阳公园",
"status": 1,
"capacity_male": 20,
"capacity_female": 20,
"registered_male": 15,
"registered_female": 12,
"is_registered": false, // 当前用户是否已报名
"can_register": true // 是否可以报名(考虑审核状态、名额等)
}
GET /activities/{id}
获取活动详情,包含已报名人员的模糊头像列表(最多展示8个异性)。
POST /activities/{id}/register
报名活动。
后端校验(按顺序检查,失败立即返回对应错误):
- 活动是否存在且已发布
- 活动报名是否未截止(
signup_deadline > now) - 活动
require_audit=1时,用户audit_status须为 2 - 对应性别名额是否未满
- 用户是否已报名过该活动(幂等处理:已报名则返回成功)
成功响应:
{
"registration_id": 456,
"status": 1,
"message": "报名成功,等待确认"
}
DELETE /activities/{id}/register
取消报名(活动开始前24小时内不可取消)。
4.5 匹配接口
GET /matches/candidates
获取当日推荐候选人列表(异性,每天最多返回10条,Redis 缓存当日推荐列表)。
Query 参数:
source: manual(手动浏览) | ai(AI推荐)
响应数据(列表项,Level 1 信息):
{
"user_id": 456,
"nickname": "小花",
"birth_year_range": "1993-1997", // 模糊处理,5年范围
"city": "北京",
"personality_tags": ["温柔", "活泼"],
"avatar_blur_url": "https://...", // 模糊头像
"match_score": 87.5, // AI 推荐时返回
"match_reasons": ["同样喜欢旅游", "生活节奏相近"] // AI 推荐时返回
}
后端实现要点:
- 过滤已点过"不感兴趣"的用户
- 过滤已互相匹配成功的用户
- 今日已展示过的用户不重复出现(Redis
SET记录)
POST /matches/like
对某用户表示感兴趣(触发双向检查)。
请求体:
{
"to_user_id": 456,
"source": "manual"
}
响应数据:
{
"is_mutual": true, // 是否产生了双向匹配
"match_id": 789, // 匹配成功时返回 match_id
"match_info": { // 匹配成功时返回对方 Level 2 信息
"nickname": "小花",
"avatar_url": "https://...",
"education": 3,
"hobbies": ["旅游"]
}
}
后端实现要点:
- 写入
match_likes表 - 查询反向是否存在
match_likes(from_user_id=to_user_id, to_user_id=from_user_id) - 若存在,在
matches表创建记录(user_a_id 取较小值) - 匹配成功后发送微信订阅消息给双方(异步任务)
POST /matches/ai-suggest
触发 AI 推荐,返回得分最高的候选人列表(最多5个)。
响应同 /matches/candidates,但包含详细 match_reasons。
GET /matches/my
获取我的匹配成功列表。
响应数据(列表项):
{
"match_id": 789,
"matched_at": "2025-04-15T20:30:00",
"other_user": {
"user_id": 456,
"nickname": "小花",
"avatar_url": "https://...", // 匹配成功后展示真实头像
"education": 3,
"city": "北京",
"hobbies": ["旅游"]
}
}
GET /matches/{match_id}/detail
获取匹配详情(Level 3 完整信息,仅匹配双方可访问)。
4.6 公告接口
GET /announcements
获取公告列表(置顶的排在前面)。
page, page_size 参数同活动接口
GET /announcements/{id}
获取公告详情。
4.7 上传接口
POST /upload/avatar
上传用户头像。
请求:multipart/form-data,字段名 file,支持 jpg/png,最大 5MB。
后端处理:
- 接收文件
- 压缩到 800x800 以内(使用 Pillow)
- 上传到腾讯云 COS,路径:
avatars/{user_id}/{timestamp}.jpg - 生成模糊版本(Pillow 高斯模糊,radius=15)上传到 COS
- 更新 users 表
avatar_url和avatar_blur_url
响应:
{
"avatar_url": "https://cos.../avatars/123/1713200000.jpg",
"avatar_blur_url": "https://cos.../avatars/123/1713200000_blur.jpg"
}
POST /upload/activity-cover
上传活动封面图(需 admin token)。
4.8 后台管理接口
所有 /admin/* 接口需要 admin 角色的 JWT token。
POST /admin/auth/login
管理员账号密码登录,返回 JWT。
GET /admin/dashboard
数据看板数据。
响应数据:
{
"total_users": 1250,
"active_users_7d": 380,
"pending_audit": 23,
"total_activities": 45,
"ongoing_activities": 3,
"total_matches": 890,
"match_rate": 0.312,
"user_trend": [
{"date": "2025-04-01", "count": 12},
...
],
"match_trend": [...]
}
GET /admin/users
用户列表,支持搜索和过滤。
Query: keyword(昵称/真实姓名), audit_status, gender, page, page_size
GET /admin/users/{id}
获取用户完整信息(含 job_company 等敏感字段)。
PUT /admin/users/{id}/audit
审核用户资料。
请求体:
{
"audit_status": 2, // 2=通过 3=驳回
"audit_remark": "照片不清晰,请重新上传" // 驳回时必填
}
后端操作:
- 更新
audit_status,audit_remark,audit_time,auditor_id - 若用户订阅了审核通知,发送微信订阅消息
PUT /admin/users/{id}/ban
封禁/解封用户。
GET /admin/activities
活动列表(含草稿)。
POST /admin/activities
创建活动。
请求体(完整字段):
{
"title": "春日约会",
"description": "Markdown 内容",
"category": "outdoor",
"location": "北京朝阳公园",
"cover_image": "https://...",
"start_time": "2025-04-20T14:00:00",
"end_time": "2025-04-20T18:00:00",
"signup_deadline": "2025-04-19T23:59:59",
"capacity_male": 20,
"capacity_female": 20,
"match_window_hours": 48,
"require_audit": true,
"is_published": false
}
PUT /admin/activities/{id}
修改活动(已结束的活动不可修改)。
PUT /admin/activities/{id}/publish
发布/下架活动(is_published 切换)。
GET /admin/matches
匹配记录列表,支持按用户ID、日期过滤。
GET /admin/system/config
获取系统配置(AI 权重、每日推荐上限等)。
PUT /admin/system/config
更新系统配置。
5. 前端页面规格
5.1 app.json 配置
{
"pages": [
"pages/home/home",
"pages/activity/activity",
"pages/match/match",
"pages/profile/profile",
"pages/activity-detail/activity-detail",
"pages/profile-edit/profile-edit",
"pages/audit-status/audit-status",
"pages/my-activities/my-activities",
"pages/my-matches/my-matches",
"pages/announcement/announcement"
],
"tabBar": {
"color": "#999999",
"selectedColor": "#E8593C",
"borderStyle": "white",
"list": [
{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "assets/icons/home.png",
"selectedIconPath": "assets/icons/home-active.png"
},
{
"pagePath": "pages/activity/activity",
"text": "活动",
"iconPath": "assets/icons/activity.png",
"selectedIconPath": "assets/icons/activity-active.png"
},
{
"pagePath": "pages/match/match",
"text": "匹配",
"iconPath": "assets/icons/match.png",
"selectedIconPath": "assets/icons/match-active.png"
},
{
"pagePath": "pages/profile/profile",
"text": "我的",
"iconPath": "assets/icons/profile.png",
"selectedIconPath": "assets/icons/profile-active.png"
}
]
},
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "相遇",
"navigationBarTextStyle": "black"
}
}
5.2 app.js 全局初始化逻辑
// app.js 核心逻辑(伪代码描述)
App({
globalData: {
userInfo: null,
token: null,
baseUrl: 'https://your-domain.com/api/v1'
},
onLaunch() {
// 1. 检查本地 token
const token = wx.getStorageSync('token');
if (token) {
this.globalData.token = token;
// 2. 验证 token 有效性(调用 /users/me)
// 3. 若失败则重新登录
} else {
this.wxLogin();
}
},
async wxLogin() {
// 1. wx.login() 获取 code
// 2. 调用后端 /auth/wx-login
// 3. 存储 token 到 globalData 和 storage
// 4. 若是新用户,跳转到资料填写页
}
});
5.3 首页(home)
功能模块:
-
活动预告轮播(头部 swiper)
- 展示
status=1(报名中)的已发布活动 - 每张卡片显示:封面图、标题、时间、剩余名额(「还剩 N 个名额」)
- 点击跳转到活动详情页
- 展示
-
公告栏(轮播或单行滚动)
- 展示最新置顶公告标题
- 点击跳转公告详情页
-
会员展示区(随机卡片列表)
- 调用
GET /matches/candidates?source=manual - 展示 Level 1 信息:模糊头像、昵称、城市、性格标签
- 点击卡片跳转到对应用户的公开页(弹出半屏 modal)
- 调用
页面加载逻辑:
onLoad:
1. 检查登录态,未登录则调用 app.wxLogin()
2. 并发请求:GET /activities?status=1&page_size=5 和 GET /announcements?page_size=3
3. 展示轮播和公告
onPullDownRefresh:
刷新活动和会员列表
5.4 活动页(activity)
功能模块:
- 分类 Tab:全部 / 户外 / 餐饮 / 文化 / 体育
- 活动卡片列表(上拉加载更多)
- 活动状态标签:报名中(绿)/ 即将截止(橙)/ 已结束(灰)
活动详情页(activity-detail):
- 封面图(全宽展示)
- 活动基本信息(时间、地点、人数)
- 名额进度条:
男 15/20 · 女 12/20 - 已报名人员预览(模糊头像列表,最多8个异性)
- 活动详情内容(富文本渲染)
- 底部报名按钮,状态:
- 未审核通过:「完善资料后可报名」→ 点击跳转资料页
- 可报名:「立即报名」
- 已报名:「已报名 ✓」
- 名额已满:「名额已满」
- 已截止:「报名已截止」
5.5 匹配页(match)
布局:
- 顶部 AI 匹配按钮(醒目展示)
- 今日推荐列表(卡片式,每张展示 Level 1 信息)
- 每张卡片底部:「感兴趣 ♥」和「跳过」按钮
AI 匹配按钮逻辑:
点击 → 调用 POST /matches/ai-suggest
→ 展示匹配结果(含匹配分数和共同点)
→ 用户可逐个查看并点击感兴趣
点击「感兴趣」逻辑:
1. 调用 POST /matches/like
2. 若 is_mutual=true:
- 弹出「匹配成功!」动画弹窗
- 展示对方 Level 2 信息
- 提示「可在「我的匹配」中查看完整信息」
3. 若 is_mutual=false:
- 卡片消失,显示「已发送喜欢」
用户信息弹窗(点击卡片展开):
- 展示 Level 1 信息(未点感兴趣前)
- 展示 Level 2 信息(已点感兴趣后)
5.6 我的页面(profile)
布局(从上到下):
- 用户卡片:头像 + 昵称 + 审核状态徽章(待审核/已通过/未提交)
- 资料完整度进度条:
85% 完整,继续完善提升匹配率 → - 功能菜单列表:
- 编辑资料 →
/profile-edit - 审核状态 →
/audit-status - 我的活动 →
/my-activities - 匹配记录 →
/my-matches - 隐私设置(暂时展示,功能后续迭代)
- 编辑资料 →
审核状态页(audit-status)展示:
状态为 0(未提交): 引导填写资料并提交
状态为 1(待审核): 展示进度提示「预计 4 小时内完成审核」
状态为 2(已通过): 绿色勾,展示通过时间
状态为 3(驳回) : 红色提示,展示驳回原因,「重新提交」按钮
5.7 资料填写页(profile-edit)
分步骤填写(Step Indicator 展示进度):
- Step 1:基本信息(性别、出生年、城市、身高、学历)
- Step 2:职业信息(行业、公司名称)
- Step 3:性格与兴趣(标签多选,爱好最多选8个)
- Step 4:价值观问卷(4道场景题,单选)
- Step 5:择偶偏好(年龄范围、城市、学历要求)
- Step 6:头像上传 + 自我介绍
每步保存逻辑:
- 每步完成后立即调用
PUT /users/me保存(非全部填完才保存) - 允许用户跳步,但提交审核时校验必填字段
头像上传:
wx.chooseMedia → 压缩 → 上传到后端 /upload/avatar
→ 后端处理并存 COS → 返回 URL → 本地展示
价值观问卷题目(4 题):
Q1. 周末你更倾向于?
A. 宅在家休息看剧 B. 约三五好友聚餐 C. 户外运动或旅行
Q2. 婚后与父母同住这件事,你的态度是?
A. 希望独立居住 B. 可以接受偶尔同住 C. 愿意长期同住
Q3. 对于两人的经济管理方式,你倾向于?
A. 各自管理各自的 B. 合并统一管理 C. 部分共用部分独立
Q4. 关于要孩子这件事?
A. 明确想要孩子 B. 顺其自然 C. 目前不想要
6. AI 匹配算法
6.1 完整实现(match_service.py)
# app/services/match_service.py
from typing import List, Dict
import math
from app.models.user import User
# 各维度权重(可从数据库 system_config 表读取,实现动态调参)
WEIGHTS = {
"age": 0.20,
"hobbies": 0.18,
"values": 0.18,
"education": 0.14,
"activity": 0.12, # 用户活跃度
"location": 0.10,
"height": 0.08,
}
def calculate_match_score(user_a: User, user_b: User) -> Dict:
"""
计算两个用户的匹配分数(0-100)。
user_a 是发起匹配的用户,user_b 是候选人。
返回: {"score": 87.5, "reasons": ["同样喜欢旅游"]}
"""
scores = {}
reasons = []
# 1. 年龄契合度(基于对方的择偶偏好)
age_score = _age_score(user_a, user_b)
scores["age"] = age_score
# 2. 兴趣重叠度(Jaccard 相似度)
hobby_score, hobby_overlap = _hobby_score(user_a, user_b)
scores["hobbies"] = hobby_score
if hobby_overlap:
reasons.append(f"都喜欢{hobby_overlap[0]}")
# 3. 价值观相似度(问卷答案加权欧氏距离)
value_score, value_reason = _value_score(user_a, user_b)
scores["values"] = value_score
if value_reason:
reasons.append(value_reason)
# 4. 学历背景(符合对方偏好加分)
scores["education"] = _education_score(user_a, user_b)
# 5. 活跃度(最近 7 天有登录记录)
scores["activity"] = _activity_score(user_b)
# 6. 地理位置(同城 100,同省 60,跨省 20)
location_score = _location_score(user_a, user_b)
scores["location"] = location_score
if location_score == 100:
reasons.append("同城")
# 7. 身高偏好
scores["height"] = _height_score(user_a, user_b)
# 加权求和
total = sum(scores[dim] * WEIGHTS[dim] for dim in scores)
return {
"score": round(total, 1),
"reasons": reasons[:3] # 最多返回3条理由
}
def _age_score(user_a: User, user_b: User) -> float:
"""检查 user_b 的年龄是否在 user_a 的择偶年龄偏好范围内"""
if not user_b.birth_year:
return 50.0
if user_a.prefer_age_min and user_b.birth_year < user_a.prefer_age_min:
return 0.0
if user_a.prefer_age_max and user_b.birth_year > user_a.prefer_age_max:
return 0.0
# 在偏好范围内,越接近中间值分越高
if user_a.prefer_age_min and user_a.prefer_age_max:
mid = (user_a.prefer_age_min + user_a.prefer_age_max) / 2
diff = abs(user_b.birth_year - mid)
half_range = (user_a.prefer_age_max - user_a.prefer_age_min) / 2
return max(0, 100 - (diff / half_range) * 50)
return 80.0
def _hobby_score(user_a: User, user_b: User):
"""Jaccard 相似度计算兴趣重叠"""
hobbies_a = set(user_a.hobbies or [])
hobbies_b = set(user_b.hobbies or [])
if not hobbies_a or not hobbies_b:
return 30.0, []
intersection = hobbies_a & hobbies_b
union = hobbies_a | hobbies_b
jaccard = len(intersection) / len(union)
return round(jaccard * 100, 1), list(intersection)
def _value_score(user_a: User, user_b: User):
"""价值观问卷:对应题目答案相同则加分"""
answers_a = user_a.value_answers or {}
answers_b = user_b.value_answers or {}
if not answers_a or not answers_b:
return 50.0, None
total_q = 4 # 总题目数
matched = 0
for q in ["q1", "q2", "q3", "q4"]:
if answers_a.get(q) == answers_b.get(q):
matched += 1
score = (matched / total_q) * 100
reason = None
if matched >= 3:
reason = "生活节奏相近"
elif matched >= 2:
reason = "价值观较为一致"
return round(score, 1), reason
def _education_score(user_a: User, user_b: User) -> float:
"""user_b 的学历是否达到 user_a 的偏好"""
if not user_a.prefer_education:
return 80.0
if not user_b.education:
return 40.0
if user_b.education >= user_a.prefer_education:
return 100.0
# 每差一级扣 25 分
diff = user_a.prefer_education - user_b.education
return max(0, 100 - diff * 25)
def _activity_score(user_b: User) -> float:
"""基于最近活跃时间评分"""
from datetime import datetime, timedelta
if not user_b.updated_at:
return 30.0
days_inactive = (datetime.now() - user_b.updated_at).days
if days_inactive <= 3:
return 100.0
elif days_inactive <= 7:
return 80.0
elif days_inactive <= 30:
return 50.0
return 20.0
def _location_score(user_a: User, user_b: User) -> float:
if not user_a.city or not user_b.city:
return 50.0
if user_a.city == user_b.city:
return 100.0
# 简单实现:同省判断(实际可接入城市数据库)
# 此处以城市名前两字作为省份简单判断
if user_a.city[:2] == user_b.city[:2]:
return 60.0
return 20.0
def _height_score(user_a: User, user_b: User) -> float:
"""简单身高偏好:女性用户对男性 175+ 额外加分"""
if not user_b.height:
return 50.0
if user_a.gender == 2 and user_b.gender == 1: # 女看男
if user_b.height >= 175:
return 100.0
elif user_b.height >= 170:
return 70.0
return 40.0
return 70.0 # 其他情况默认中等分
def get_ai_candidates(
current_user: User,
all_candidates: List[User],
limit: int = 5,
diversity_ratio: float = 0.3
) -> List[Dict]:
"""
获取 AI 推荐候选人列表。
diversity_ratio: 多样性候选人比例(避免推荐列表同质化)
"""
scored = []
for candidate in all_candidates:
result = calculate_match_score(current_user, candidate)
scored.append({
"user": candidate,
"score": result["score"],
"reasons": result["reasons"]
})
scored.sort(key=lambda x: x["score"], reverse=True)
# 主列表:高分候选
main_count = math.ceil(limit * (1 - diversity_ratio))
main_list = scored[:main_count]
# 多样性候选:从 main_count+1 到 main_count+10 中随机取
import random
diverse_pool = scored[main_count:main_count + 10]
diverse_count = limit - main_count
diverse_list = random.sample(diverse_pool, min(diverse_count, len(diverse_pool)))
return main_list + diverse_list
def calculate_profile_completeness(user: User) -> int:
"""
计算用户资料完整度(0-100)。
各字段权重不同,越重要的字段权重越高。
"""
fields = {
"nickname": 5,
"gender": 5,
"birth_year": 5,
"city": 5,
"height": 3,
"education": 5,
"job_industry": 5,
"personality_tags": 8, # 至少3个
"hobbies": 8, # 至少3个
"value_answers": 10, # 4题全答完
"avatar_url": 15, # 头像权重最高
"self_intro": 8,
"prefer_age_min": 3,
"prefer_city": 3,
"prefer_education": 3,
"job_company": 4,
"income_range": 5,
}
total_weight = sum(fields.values())
earned = 0
for field, weight in fields.items():
val = getattr(user, field, None)
if field == "personality_tags":
if val and len(val) >= 3:
earned += weight
elif val and len(val) >= 1:
earned += weight * 0.5
elif field == "hobbies":
if val and len(val) >= 3:
earned += weight
elif val and len(val) >= 1:
earned += weight * 0.5
elif field == "value_answers":
if val and len(val) >= 4:
earned += weight
elif val and len(val) >= 2:
earned += weight * 0.5
elif val:
earned += weight
return round((earned / total_weight) * 100)
7. 微信能力集成
7.1 登录流程(完整实现)
# app/utils/wx_api.py
import httpx
from app.config import settings
async def code2session(code: str) -> dict:
"""
调用微信 code2session 接口获取 openid
返回: {"openid": "xxx", "session_key": "xxx"}
"""
url = "https://api.weixin.qq.com/sns/jscode2session"
params = {
"appid": settings.WX_APPID,
"secret": settings.WX_SECRET,
"js_code": code,
"grant_type": "authorization_code"
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
data = resp.json()
if "errcode" in data and data["errcode"] != 0:
raise ValueError(f"微信登录失败: {data.get('errmsg')}")
return data
7.2 订阅消息发送
# app/services/notify_service.py
import httpx
from app.config import settings
# 需在微信公众平台申请的订阅消息模板 ID
TEMPLATE_AUDIT_RESULT = "your_audit_template_id" # 审核结果通知模板
TEMPLATE_MATCH_SUCCESS = "your_match_template_id" # 匹配成功通知模板
async def get_access_token() -> str:
"""获取微信接口调用凭证(需加 Redis 缓存,有效期 2 小时)"""
url = "https://api.weixin.qq.com/cgi-bin/token"
params = {
"grant_type": "client_credential",
"appid": settings.WX_APPID,
"secret": settings.WX_SECRET
}
async with httpx.AsyncClient() as client:
resp = await client.get(url, params=params)
return resp.json()["access_token"]
async def send_audit_result(openid: str, passed: bool, remark: str = ""):
"""发送审核结果订阅消息"""
access_token = await get_access_token()
url = f"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}"
payload = {
"touser": openid,
"template_id": TEMPLATE_AUDIT_RESULT,
"miniprogram_state": "formal",
"lang": "zh_CN",
"data": {
"thing1": {"value": "资料审核结果通知"},
"phrase2": {"value": "审核通过" if passed else "审核未通过"},
"thing3": {"value": remark or ("恭喜,您的资料已通过审核!" if passed else "请根据提示修改后重新提交")},
}
}
async with httpx.AsyncClient() as client:
await client.post(url, json=payload)
async def send_match_success(openid: str, other_nickname: str):
"""发送匹配成功订阅消息"""
access_token = await get_access_token()
url = f"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={access_token}"
payload = {
"touser": openid,
"template_id": TEMPLATE_MATCH_SUCCESS,
"page": "pages/my-matches/my-matches", # 点击消息跳转页
"miniprogram_state": "formal",
"data": {
"name3": {"value": other_nickname},
"thing4": {"value": "你们互相感兴趣,匹配成功啦!快去查看吧"},
}
}
async with httpx.AsyncClient() as client:
await client.post(url, json=payload)
7.3 前端请求封装
// utils/request.js
const app = getApp();
const request = (options) => {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync('token');
wx.request({
url: `${app.globalData.baseUrl}${options.url}`,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
},
success(res) {
if (res.statusCode === 401) {
// Token 失效,重新登录
wx.removeStorageSync('token');
app.wxLogin();
reject(new Error('登录已过期'));
return;
}
if (res.data.code !== 0) {
wx.showToast({ title: res.data.message || '请求失败', icon: 'none' });
reject(res.data);
return;
}
resolve(res.data.data);
},
fail(err) {
wx.showToast({ title: '网络异常,请重试', icon: 'none' });
reject(err);
}
});
});
};
export default request;
8. 后台管理端
后台管理使用独立的 Vue3 + Element Plus 项目(非小程序)。以下是关键页面规格。
8.1 页面列表
| 路由 | 页面 | 核心功能 |
|---|---|---|
/login |
管理员登录 | 账号密码登录 |
/dashboard |
数据看板 | 用户趋势、匹配率、审核积压量图表 |
/users |
用户管理 | 搜索、筛选、查看详情 |
/users/audit |
待审核队列 | 重点页面,快速审核 |
/activities |
活动列表 | 增删改查、发布/下架 |
/activities/new |
创建活动 | 表单 |
/matches |
匹配记录 | 查看所有匹配,可按用户筛选 |
/announcements |
公告管理 | 增删改查 |
/settings |
系统设置 | AI 权重调参 |
8.2 待审核队列页面逻辑
这是管理员最高频使用的页面,需优化操作效率:
- 列表展示待审核用户(按提交时间升序,越早越靠前)
- 显示已等待时长(超过 4 小时标红提示 SLA 警告)
- 点击用户展开侧边栏,显示完整资料和头像
- 侧边栏操作:通过 按钮(绿色)+ 驳回 按钮(红色,点击后弹出输入框填写原因)
- 操作完成后自动加载下一条
9. 开发注意事项
9.1 微信小程序开发注意事项
【安全域名配置】
- 微信公众平台 → 开发管理 → 开发设置 → 服务器域名
- request 合法域名:填入后端服务器域名(必须 HTTPS)
- uploadFile 合法域名:填入腾讯云 COS 域名
- 开发阶段可在微信开发者工具中勾选「不校验合法域名」
【登录流程注意】
- wx.login() 返回的 code 有效期为 5 分钟,只能使用一次
- 不要在前端存储 session_key,只存系统颁发的 JWT token
- JWT 推荐有效期 7 天,结合 refresh token 机制
【图片上传注意】
- wx.chooseMedia 替代已废弃的 wx.chooseImage(基础库 2.10.0+)
- 上传前在前端压缩(quality 参数),减少上传时间
- 上传到后端后由后端再次压缩,避免大图存入 COS
【分包加载(后续优化)】
- 当小程序代码超过 1.5MB 时需分包
- 匹配相关页面可放入子包
【wx.request 并发限制】
- 微信限制同时最多 10 个并发请求
- 首页多接口并发请求时注意控制数量
9.2 后端 FastAPI 开发注意事项
【异步数据库操作】
- 使用 SQLAlchemy 2.x async 引擎,配合 asyncmy 驱动
- pip install sqlalchemy[asyncio] asyncmy
- 所有数据库操作使用 async with AsyncSession 上下文
【JWT 配置】
- SECRET_KEY 使用 openssl rand -hex 32 生成,存入 .env
- 算法推荐 HS256
- payload 中仅存 user_id(不存敏感信息)
【CORS 配置】
- 本地开发:allow_origins=["*"]
- 生产环境:allow_origins=["https://your-admin-domain.com"]
- 微信小程序请求不受 CORS 限制,但后台管理 Vue 项目会受影响
【频率限制】
- 匹配接口(/matches/like):同一用户每分钟最多 20 次,Redis 计数
- 登录接口:同一 IP 每分钟最多 10 次
【图片处理】
- pip install Pillow
- 头像模糊处理:ImageFilter.GaussianBlur(radius=15)
- 上传前校验文件类型(检查文件头 magic bytes,而非仅看后缀名)
【双向匹配并发安全】
- 两个用户同时互相点感兴趣时可能产生竞态条件
- matches 表有 UNIQUE KEY (user_a_id, user_b_id),用数据库唯一约束保证
- 插入时使用 INSERT IGNORE 或捕获 IntegrityError
【订阅消息异步发送】
- 不要在请求链路内同步发送微信消息(影响响应速度)
- 使用 FastAPI BackgroundTasks 异步发送
- 或接入 Celery + Redis 任务队列(生产环境推荐)
9.3 数据库注意事项
【JSON 字段使用】
- MySQL 5.7+ 支持 JSON 类型
- personality_tags、hobbies、value_answers 使用 JSON 字段
- SQLAlchemy 中使用 JSON 类型映射
【时区处理】
- MySQL 服务器时区设置为 Asia/Shanghai
- 所有 DATETIME 字段存储本地时间(简化处理)
- 前端展示时注意格式化
【索引设计】
- 高频查询:users(gender, city, audit_status),建复合索引
- match_likes(from_user_id, to_user_id) 已有 UNIQUE 约束(自带索引)
- activities(status, is_published, start_time) 建复合索引
【Alembic 迁移】
- 每次修改 models 后运行:alembic revision --autogenerate -m "描述"
- 应用迁移:alembic upgrade head
- 永远不要在生产环境直接修改表结构
9.4 腾讯云 COS 配置
# app/services/cos_service.py
from qcloud_cos import CosConfig, CosS3Client
from app.config import settings
def get_cos_client():
config = CosConfig(
Region=settings.COS_REGION,
SecretId=settings.COS_SECRET_ID,
SecretKey=settings.COS_SECRET_KEY
)
return CosS3Client(config)
def upload_file(local_path: str, cos_key: str) -> str:
"""上传文件到 COS,返回公开访问 URL"""
client = get_cos_client()
client.upload_file(
Bucket=settings.COS_BUCKET,
LocalFilePath=local_path,
Key=cos_key,
)
return f"https://{settings.COS_BUCKET}.cos.{settings.COS_REGION}.myqcloud.com/{cos_key}"
# pip install cos-python-sdk-v5
10. 环境配置
10.1 .env 文件(后端)
# 微信小程序
WX_APPID=your_wx_appid
WX_SECRET=your_wx_app_secret
# JWT
JWT_SECRET_KEY=your_super_secret_key_generated_by_openssl
JWT_ALGORITHM=HS256
JWT_EXPIRE_HOURS=168 # 7天
# 数据库
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=matchmaking
DB_USER=root
DB_PASSWORD=your_db_password
# Redis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DB=0
# 腾讯云 COS
COS_REGION=ap-beijing
COS_BUCKET=your-bucket-name
COS_SECRET_ID=your_secret_id
COS_SECRET_KEY=your_secret_key
# 微信订阅消息模板 ID
WX_TEMPLATE_AUDIT=your_audit_template_id
WX_TEMPLATE_MATCH=your_match_template_id
# 系统配置
DAILY_RECOMMEND_LIMIT=10 # 每日推荐上限
AUDIT_SLA_HOURS=4 # 审核 SLA 小时数
10.2 requirements.txt
fastapi==0.111.0
uvicorn[standard]==0.29.0
sqlalchemy[asyncio]==2.0.30
asyncmy==0.2.9
alembic==1.13.1
pydantic==2.7.1
pydantic-settings==2.2.1
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
httpx==0.27.0
Pillow==10.3.0
python-multipart==0.0.9
redis==5.0.4
cos-python-sdk-v5==1.9.30
python-dotenv==1.0.1
10.3 启动命令
# 安装依赖
pip install -r requirements.txt
# 初始化数据库(首次)
alembic upgrade head
# 创建初始管理员账号(运行一次)
python scripts/create_admin.py --username admin --password your_password
# 启动开发服务器
uvicorn main:app --reload --host 0.0.0.0 --port 8000
# 生产环境启动(多 worker)
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
10.4 main.py 入口
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.routers import auth, users, activities, matches, announcements, upload
from app.routers.admin import users as admin_users, activities as admin_activities
from app.routers.admin import matches as admin_matches, dashboard
app = FastAPI(title="相亲小程序 API", version="1.0.0")
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境改为具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# 注册路由
app.include_router(auth.router, prefix="/api/v1/auth", tags=["认证"])
app.include_router(users.router, prefix="/api/v1/users", tags=["用户"])
app.include_router(activities.router, prefix="/api/v1/activities", tags=["活动"])
app.include_router(matches.router, prefix="/api/v1/matches", tags=["匹配"])
app.include_router(announcements.router, prefix="/api/v1/announcements", tags=["公告"])
app.include_router(upload.router, prefix="/api/v1/upload", tags=["上传"])
# 后台路由
app.include_router(dashboard.router, prefix="/api/v1/admin/dashboard", tags=["后台-看板"])
app.include_router(admin_users.router, prefix="/api/v1/admin/users", tags=["后台-用户"])
app.include_router(admin_activities.router, prefix="/api/v1/admin/activities", tags=["后台-活动"])
app.include_router(admin_matches.router, prefix="/api/v1/admin/matches", tags=["后台-匹配"])
@app.get("/health")
async def health_check():
return {"status": "ok"}
附录:功能清单总览(MVP 版本)
小程序前端(9 个页面)
| 页面 | 功能 | 状态 |
|---|---|---|
| 首页 | 活动预告轮播、公告、会员展示 | MVP |
| 活动列表 | 分类浏览、上拉加载 | MVP |
| 活动详情 | 详情展示、报名/取消报名 | MVP |
| 匹配页 | 候选人浏览、感兴趣/跳过、AI推荐 | MVP |
| 我的 | 个人信息、功能入口 | MVP |
| 资料编辑 | 6步填写、头像上传 | MVP |
| 审核状态 | 状态展示、驳回原因 | MVP |
| 我的活动 | 报名活动列表 | MVP |
| 我的匹配 | 匹配成功列表、查看详情 | MVP |
后端 API(28 个接口)
| 模块 | 接口数 |
|---|---|
| 认证(登录、刷新) | 2 |
| 用户(获取、更新、提交审核、公开信息) | 4 |
| 活动(列表、详情、报名、取消) | 4 |
| 匹配(候选人、感兴趣、AI推荐、我的匹配、详情) | 5 |
| 公告(列表、详情) | 2 |
| 上传(头像、活动封面) | 2 |
| 后台-看板 | 1 |
| 后台-用户管理(列表、详情、审核、封禁) | 4 |
| 后台-活动管理(列表、创建、修改、发布) | 4 |
| 合计 | 28 |
文档版本:v1.0 · 生成时间:2025年4月
如有接口变更,请同步更新本文档对应章节。