# 相亲小程序 · 完整开发规格文档 > **文档用途**:本文档供 AI 编程助手直接执行,包含完整的功能清单、目录结构、数据库设计、API 接口、页面逻辑和开发注意事项。 > **技术栈**:微信小程序原生(微信开发者工具) + Python FastAPI 后端 > **阅读顺序**:先看第 1-3 节了解全局,再按模块逐节实现。 --- ## 目录 1. [项目概览](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#1-%E9%A1%B9%E7%9B%AE%E6%A6%82%E8%A7%88) 2. [目录结构](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#2-%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84) 3. [数据库设计](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#3-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AE%BE%E8%AE%A1) 4. [后端 API 规格](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#4-%E5%90%8E%E7%AB%AF-api-%E8%A7%84%E6%A0%BC) 5. [前端页面规格](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#5-%E5%89%8D%E7%AB%AF%E9%A1%B5%E9%9D%A2%E8%A7%84%E6%A0%BC) 6. [AI 匹配算法](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#6-ai-%E5%8C%B9%E9%85%8D%E7%AE%97%E6%B3%95) 7. [微信能力集成](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#7-%E5%BE%AE%E4%BF%A1%E8%83%BD%E5%8A%9B%E9%9B%86%E6%88%90) 8. [后台管理端](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#8-%E5%90%8E%E5%8F%B0%E7%AE%A1%E7%90%86%E7%AB%AF) 9. [开发注意事项](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#9-%E5%BC%80%E5%8F%91%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9) 10. [环境配置](https://claude.ai/chat/18b08d3b-0364-4e53-804b-61b4cf5b63d4#10-%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE) --- ## 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 表 ```sql 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 表 ```sql 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 表 ```sql 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 表(单向感兴趣记录) ```sql 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 表(双向匹配成功记录) ```sql 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 表 ```sql 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 表 ```sql 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。 **请求体:** ```json { "code": "微信登录临时code", "subscribe_audit": true, // 是否订阅审核通知(首次登录时传) "subscribe_match": true } ``` **响应数据:** ```json { "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 获取当前用户完整信息(含审核状态)。 **响应数据:** ```json { "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(待审核)。 **请求体(所有字段均可选,仅传需要更新的):** ```json { "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 ``` **响应数据(列表项):** ```json { "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 报名活动。 **后端校验(按顺序检查,失败立即返回对应错误):** 1. 活动是否存在且已发布 2. 活动报名是否未截止(`signup_deadline > now`) 3. 活动 `require_audit=1` 时,用户 `audit_status` 须为 2 4. 对应性别名额是否未满 5. 用户是否已报名过该活动(幂等处理:已报名则返回成功) **成功响应:** ```json { "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 信息):** ```json { "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 对某用户表示感兴趣(触发双向检查)。 **请求体:** ```json { "to_user_id": 456, "source": "manual" } ``` **响应数据:** ```json { "is_mutual": true, // 是否产生了双向匹配 "match_id": 789, // 匹配成功时返回 match_id "match_info": { // 匹配成功时返回对方 Level 2 信息 "nickname": "小花", "avatar_url": "https://...", "education": 3, "hobbies": ["旅游"] } } ``` **后端实现要点:** 1. 写入 `match_likes` 表 2. 查询反向是否存在 `match_likes`(`from_user_id=to_user_id, to_user_id=from_user_id`) 3. 若存在,在 `matches` 表创建记录(user_a_id 取较小值) 4. 匹配成功后发送微信订阅消息给双方(异步任务) #### POST /matches/ai-suggest 触发 AI 推荐,返回得分最高的候选人列表(最多5个)。 **响应同 /matches/candidates,但包含详细 match_reasons。** #### GET /matches/my 获取我的匹配成功列表。 **响应数据(列表项):** ```json { "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。 **后端处理:** 1. 接收文件 2. 压缩到 800x800 以内(使用 Pillow) 3. 上传到腾讯云 COS,路径:`avatars/{user_id}/{timestamp}.jpg` 4. 生成模糊版本(Pillow 高斯模糊,radius=15)上传到 COS 5. 更新 users 表 `avatar_url` 和 `avatar_blur_url` **响应:** ```json { "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 数据看板数据。 **响应数据:** ```json { "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 审核用户资料。 **请求体:** ```json { "audit_status": 2, // 2=通过 3=驳回 "audit_remark": "照片不清晰,请重新上传" // 驳回时必填 } ``` **后端操作:** 1. 更新 `audit_status`, `audit_remark`, `audit_time`, `auditor_id` 2. 若用户订阅了审核通知,发送微信订阅消息 #### PUT /admin/users/{id}/ban 封禁/解封用户。 #### GET /admin/activities 活动列表(含草稿)。 #### POST /admin/activities 创建活动。 **请求体(完整字段):** ```json { "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 配置 ```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 全局初始化逻辑 ```javascript // 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) **功能模块:** 1. **活动预告轮播**(头部 swiper) - 展示 `status=1`(报名中)的已发布活动 - 每张卡片显示:封面图、标题、时间、剩余名额(「还剩 N 个名额」) - 点击跳转到活动详情页 2. **公告栏**(轮播或单行滚动) - 展示最新置顶公告标题 - 点击跳转公告详情页 3. **会员展示区**(随机卡片列表) - 调用 `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) **功能模块:** 1. **分类 Tab**:全部 / 户外 / 餐饮 / 文化 / 体育 2. **活动卡片列表**(上拉加载更多) 3. **活动状态标签**:报名中(绿)/ 即将截止(橙)/ 已结束(灰) **活动详情页(activity-detail):** 1. 封面图(全宽展示) 2. 活动基本信息(时间、地点、人数) 3. 名额进度条:`男 15/20 · 女 12/20` 4. 已报名人员预览(模糊头像列表,最多8个异性) 5. 活动详情内容(富文本渲染) 6. **底部报名按钮**,状态: - 未审核通过:「完善资料后可报名」→ 点击跳转资料页 - 可报名:「立即报名」 - 已报名:「已报名 ✓」 - 名额已满:「名额已满」 - 已截止:「报名已截止」 ### 5.5 匹配页(match) **布局:** 1. **顶部 AI 匹配按钮**(醒目展示) 2. **今日推荐列表**(卡片式,每张展示 Level 1 信息) 3. 每张卡片底部:「感兴趣 ♥」和「跳过」按钮 **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) **布局(从上到下):** 1. **用户卡片**:头像 + 昵称 + 审核状态徽章(待审核/已通过/未提交) 2. **资料完整度进度条**:`85% 完整,继续完善提升匹配率 →` 3. **功能菜单列表**: - 编辑资料 → `/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) ```python # 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 登录流程(完整实现) ```python # 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 订阅消息发送 ```python # 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 前端请求封装 ```javascript // 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 待审核队列页面逻辑 这是管理员最高频使用的页面,需优化操作效率: 1. 列表展示待审核用户(按提交时间升序,越早越靠前) 2. 显示已等待时长(超过 4 小时标红提示 SLA 警告) 3. 点击用户展开侧边栏,显示完整资料和头像 4. 侧边栏操作:**通过** 按钮(绿色)+ **驳回** 按钮(红色,点击后弹出输入框填写原因) 5. 操作完成后自动加载下一条 --- ## 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 配置 ```python # 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 文件(后端) ```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 启动命令 ```bash # 安装依赖 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 入口 ```python # 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月_ _如有接口变更,请同步更新本文档对应章节。_