xiangqinxiaochengxu/相亲小程序开发详情.md
2026-04-17 10:49:14 +08:00

54 KiB
Raw Permalink Blame History

相亲小程序 · 完整开发规格文档

文档用途:本文档供 AI 编程助手直接执行包含完整的功能清单、目录结构、数据库设计、API 接口、页面逻辑和开发注意事项。
技术栈:微信小程序原生(微信开发者工具) + Python FastAPI 后端
阅读顺序:先看第 1-3 节了解全局,再按模块逐节实现。


目录

  1. 项目概览
  2. 目录结构
  3. 数据库设计
  4. 后端 API 规格
  5. 前端页面规格
  6. AI 匹配算法
  7. 微信能力集成
  8. 后台管理端
  9. 开发注意事项
  10. 环境配置

1. 项目概览

1.1 产品定位

线下相亲活动组织工具 + AI 辅助匹配平台。核心流程:

用户注册 → 填写资料 → 提交审核 → 审核通过 → 报名活动 / 参与匹配

1.2 技术栈总览

技术 说明
小程序前端 微信小程序原生WXML/WXSS/JS 使用微信开发者工具开发
后端框架 Python 3.11 + FastAPI RESTful APIasync 支持
数据库 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 DATETIMEupdated_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 '模糊头像 URLAI 生成或后端处理)',
    
    -- 状态
    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 TokenJWT
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 查用户,不存在则自动创建
  • 返回 JWTpayload 包含 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

报名活动。

后端校验(按顺序检查,失败立即返回对应错误):

  1. 活动是否存在且已发布
  2. 活动报名是否未截止(signup_deadline > now
  3. 活动 require_audit=1 时,用户 audit_status 须为 2
  4. 对应性别名额是否未满
  5. 用户是否已报名过该活动(幂等处理:已报名则返回成功)

成功响应:

{
    "registration_id": 456,
    "status": 1,
    "message": "报名成功,等待确认"
}

DELETE /activities/{id}/register

取消报名活动开始前24小时内不可取消


4.5 匹配接口

GET /matches/candidates

获取当日推荐候选人列表异性每天最多返回10条Redis 缓存当日推荐列表)。

Query 参数:

source: manual手动浏览 | aiAI推荐

响应数据列表项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": ["旅游"]
    }
}

后端实现要点:

  1. 写入 match_likes
  2. 查询反向是否存在 match_likesfrom_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

获取我的匹配成功列表。

响应数据(列表项):

{
    "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_urlavatar_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": "照片不清晰,请重新上传"  // 驳回时必填
}

后端操作:

  1. 更新 audit_status, audit_remark, audit_time, auditor_id
  2. 若用户订阅了审核通知,发送微信订阅消息

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

功能模块:

  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

# 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 待审核队列页面逻辑

这是管理员最高频使用的页面,需优化操作效率:

  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 配置

# 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

后端 API28 个接口)

模块 接口数
认证(登录、刷新) 2
用户(获取、更新、提交审核、公开信息) 4
活动(列表、详情、报名、取消) 4
匹配候选人、感兴趣、AI推荐、我的匹配、详情 5
公告(列表、详情) 2
上传(头像、活动封面) 2
后台-看板 1
后台-用户管理(列表、详情、审核、封禁) 4
后台-活动管理(列表、创建、修改、发布) 4
合计 28

文档版本v1.0 · 生成时间2025年4月
如有接口变更,请同步更新本文档对应章节。