Skip to content

居美家房产中介平台 - 项目计划文档

📌 项目概述

项目名称

居美家房产中介平台

客户方

居美家房产中介公司

项目背景

为居美家房产中介公司开发一套房源租售管理系统,支持微信小程序和移动H5端,方便经纪人发布房源,用户浏览查找房源,管理员统一管理平台数据。

项目范围

  • 服务区域: 青岛市北区
  • 小程序类型: 个人小程序
  • 服务器: 阿里云服务器 + 宝塔面板
  • 腾讯地图密钥: PVZBZ-DB46Z-WFEXI-ZCKQA-HZBWS-BLBF2

项目目标

  • 提供便捷的房源发布和管理功能
  • 为用户提供优质的房源浏览和搜索体验
  • 实现高效的房源信息展示
  • 降低运营成本,提高工作效率

设计参考

  • 房源信息参考:安居客房源详情页
  • UI设计风格:安居客APP(现代简约,符合中国人使用习惯)

🏗️ 技术架构

前端技术栈

  • 框架: uni-app (主要适配微信小程序,兼容H5)
  • 核心库: Vue 3
  • UI组件库: uni-ui
  • 地图服务: 腾讯地图 API
  • 状态管理: Pinia
  • 网络请求: uni.request 封装

后端技术栈

  • 框架: ThinkPHP 8.0
  • 语言: PHP 8.1+
  • 架构模式: MVC (Model-View-Controller)
  • ORM: ThinkPHP ORM (支持链式操作)
  • 数据库: MySQL 5.7
  • 缓存: Redis (支持多种缓存驱动)
  • Web服务器: Nginx + PHP-FPM
  • 文件存储: 本地文件系统
  • 依赖管理: Composer
  • 特性:
    • 容器管理和依赖注入
    • 中间件机制
    • 验证器
    • ORM模型关联
    • RESTful路由

开发模式

  • 个人开发
  • 快速迭代
  • 无预算约束(使用免费/开源方案)

👥 用户角色

1. 普通用户

  • 浏览房源列表
  • 查看房源详情
  • 搜索房源
  • 地图找房
  • 联系经纪人

2. 经纪人

  • 登录账号
  • 发布房源(无需审核,直接上线)
  • 管理自己的房源
  • 编辑/删除/上下架房源
  • 申请添加小区

3. 管理员

  • 最高权限
  • 查看所有房源
  • 用户管理(审核经纪人申请)
  • 小区管理(审核小区申请)
  • 数据统计

🎯 核心功能模块

模块一:用户模块

1.1 用户认证

  • 小程序端: 微信授权登录
  • H5端: 邮箱验证码登录(QQ邮箱SMTP)
  • 自动获取用户基本信息

邮箱验证码规则

  • 验证码有效期: 5分钟
  • 同一邮箱发送间隔: 60秒

1.2 角色管理

  • 默认注册为普通用户
  • 用户申请成为经纪人(任何人均可申请)
  • 管理员后台简单审核(信息登记审核,无需上传资质证明)

经纪人申请字段

  • 真实姓名(必填)
  • 联系电话(必填)
  • 所属公司(必填)
  • 备注说明(选填)

1.3 个人中心

  • 用户信息展示
  • 我的房源(经纪人)
  • 浏览历史
    • 记录用户浏览过的房源
    • 未登录用户通过JWT中的anonymous_id标识
    • 登录用户关联到账号(user_id)
    • 登录时自动迁移匿名浏览历史到用户账号
    • 统一使用JWT Token维护用户身份状态
  • 设置与退出

模块二:房源模块

2.1 房源列表(首页)

展示方式

  • 瀑布流/卡片式布局
  • 缩略图 + 关键信息
  • Tab页切换(二手房/租房)

内容信息

  • 房源图片(首图)
  • 标题
  • 价格(租金/售价)
  • 面积、户型
  • 小区名称
  • 楼层信息
  • 房源标签
  • 经纪人信息

交互功能

  • 下拉刷新
  • 上拉加载更多
  • Tab分类切换(二手房/租房)
  • 排序选项:
    • 最新发布(默认)
    • 价格升序/降序
    • 面积大小
    • 浏览量排序(热门房源)

2.2 房源详情

图片视频区

  • 轮播图展示(支持图片、视频)
  • 支持全屏预览
  • 图片计数显示

基本信息

  • 价格(大字突出显示)
  • 面积
  • 户型(X室X厅X卫)
  • 楼层(X/共X层)
  • 朝向
  • 装修情况
  • 房源标签(经纪人手动选择,支持多选和新增)

小区信息

  • 小区名称
  • 详细地址
  • 建筑年代
  • 物业费
  • 配套设施(自动获取周边:地铁站、学校、医院、商场等)

地图位置

  • 嵌入腾讯地图
  • 显示小区位置
  • 周边设施标注

房源描述

  • 经纪人填写的文字描述

经纪人信息

  • 头像、昵称
  • 在售房源数量

操作按钮

  • 联系经纪人(点击后拨打用户表中的手机号)
  • 分享房源(支持多种方式):
    • 分享到微信好友
    • 分享到朋友圈
    • 生成海报图片
    • 复制链接

2.3 发布房源

Step 1: 选择小区

  • 搜索小区名称
  • 显示候选列表
  • 若找不到,可申请添加小区

Step 2: 房源类型

  • 二手房 / 租房

Step 3: 上传媒体

  • 上传房源图片(最多9张,单张最大5MB)
  • 上传视频(1个,可选,最大100MB,时长不限)
  • 设置封面图

Step 4: 填写信息

  • 标题
  • 价格
  • 面积
  • 户型(室、厅、卫)
  • 楼层 / 总楼层
  • 朝向(东、南、西、北、南北、东西等)
  • 装修情况(毛坯、简装、精装、豪装)
  • 房源标签(多选,可新增)
  • 房源描述

Step 5: 提交发布

  • 直接发布,无需审核
  • 自动上架

2.4 我的房源

  • 已发布房源列表
  • 房源状态标识(上架中/已下架)
  • 快捷操作:编辑、删除、上/下架
  • 数据统计展示:
    • 总房源数
    • 上架中数量
    • 已下架数量
    • 总浏览量

重要说明

  • 房源永久有效,除非手动下架或删除
  • 已下架房源:
    • 普通用户可查看但标注下架状态
    • 不会出现在搜索结果中
    • 不会出现在列表筛选中

模块三:小区模块

3.1 小区数据来源

数据积累策略

  1. 完全依赖经纪人申请和管理员添加逐步积累
  2. 使用腾讯地图POI搜索获取小区信息
  3. 自动获取:名称、地址、经纬度、周边配套
  4. 管理员手动补充:建筑年代、物业费等
  5. 初始数据库为空,随使用逐步完善

3.2 小区管理(管理员)

添加小区

  • 输入小区名称
  • 调用腾讯地图API搜索(限定青岛市北区)
  • 选择正确的POI结果
  • 自动填充:
    • 小区名称
    • 详细地址
    • 经纬度
  • 手动填写:
    • 建筑年代
    • 物业费
    • 备注信息

周边配套自动获取

  • 添加小区时调用腾讯地图周边搜索API获取一次
  • 存储到数据库,定期更新(管理员手动触发或定时任务)
  • 获取附近500米内:
    • 地铁站
    • 公交站
    • 学校
    • 医院
    • 商场/超市
    • 银行
  • 存储为JSON格式

小区列表

  • 查看所有小区
  • 编辑小区信息
  • 删除小区(需检查是否有关联房源)

3.3 小区申请(经纪人)

  • 经纪人发布房源时,若小区不存在
  • 填写小区申请表单:
    • 小区名称(必填)
    • 详细地址(必填)
    • 小区照片(必填,至少1张)
    • 建筑年代(必填)
    • 物业费(必填)
    • 备注说明(选填)
  • 提交后等待管理员审核
  • 审核通过后可继续发布房源

模块四:搜索模块

4.1 关键词搜索

  • 搜索框(支持小区名)
  • 直接搜索小区名称,显示该小区的所有房源
  • 搜索历史记录
  • 热门搜索推荐(可选)
  • 搜索结果列表

4.2 条件筛选

筛选条件

  • 房源类型:二手房/租房
  • 价格区间:自定义/预设区间
  • 面积区间:自定义/预设区间
  • 户型:1室、2室、3室、4室、5室+
  • 小区筛选:输入小区名称搜索
  • 朝向:东、南、西、北、南北、东西
  • 装修:毛坯、简装、精装、豪装
  • 房源标签:多选标签筛选

筛选交互

  • 筛选条件浮层
  • 已选条件标签展示
  • 清空筛选
  • 确认筛选

模块五:管理后台

5.1 数据统计(首页)

实时统计数据

  • 房源总数
  • 在售房源数
  • 用户总数
  • 经纪人数量

今日数据

  • 今日新增房源
  • 今日新增用户
  • 今日浏览量

周期对比

  • 本周数据对比(与上周)
  • 本月数据对比(与上月)

待处理事项

  • 待审核经纪人申请
  • 待审核小区申请

5.2 房源管理

  • 查看所有房源列表
  • 筛选条件(状态、类型、经纪人、时间)
  • 查看房源详情
  • 编辑房源(管理员可编辑所有房源)
  • 删除违规房源
  • 批量操作(可选)

5.3 用户管理

用户列表

  • 所有用户列表
  • 用户角色筛选
  • 查看用户详情
  • 禁用/启用用户

经纪人审核

  • 待审核经纪人列表
  • 查看申请信息
  • 通过/拒绝申请

5.4 小区管理

小区列表

  • 所有小区列表
  • 搜索小区
  • 编辑小区信息
  • 删除小区

小区申请审核

  • 待审核小区申请
  • 查看申请详情
  • 通过/拒绝申请
  • 通过后自动创建小区

5.5 标签管理

  • 房源标签列表
  • 新增标签
  • 编辑标签
  • 删除标签(检查是否被房源使用)
  • 标签统一管理,方便后续搜索和筛选

💾 数据库设计

表结构设计

1. 用户表 (users)

sql
CREATE TABLE `users` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `openid` VARCHAR(100) COMMENT '微信openid(小程序)',
  `email` VARCHAR(100) COMMENT '邮箱(H5)',
  `password` VARCHAR(255) COMMENT '密码(H5邮箱登录用)',
  `phone` VARCHAR(20) COMMENT '手机号',
  `nickname` VARCHAR(50) COMMENT '昵称',
  `avatar` VARCHAR(255) COMMENT '头像',
  `role` ENUM('user', 'agent', 'admin') DEFAULT 'user' COMMENT '角色',
  `status` TINYINT DEFAULT 1 COMMENT '状态 1:正常 0:禁用',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY `idx_openid` (`openid`),
  UNIQUE KEY `idx_email` (`email`),
  KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

2. 房源表 (properties)

sql
CREATE TABLE `properties` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `property_no` VARCHAR(20) NOT NULL COMMENT '房源编号 如:FY202510290001',
  `user_id` INT UNSIGNED NOT NULL COMMENT '发布者ID',
  `community_id` INT UNSIGNED NOT NULL COMMENT '小区ID',
  `title` VARCHAR(100) NOT NULL COMMENT '标题',
  `type` ENUM('sale', 'rent') NOT NULL COMMENT '类型 sale:二手房 rent:租房',
  `price` DECIMAL(10,2) NOT NULL COMMENT '价格(元/月)',
  `area` DECIMAL(6,2) NOT NULL COMMENT '面积(㎡)',
  `room` TINYINT NOT NULL COMMENT '室',
  `hall` TINYINT NOT NULL COMMENT '厅',
  `toilet` TINYINT NOT NULL COMMENT '卫',
  `floor` TINYINT NOT NULL COMMENT '楼层',
  `total_floor` TINYINT NOT NULL COMMENT '总楼层',
  `orientation` VARCHAR(20) COMMENT '朝向',
  `decoration` VARCHAR(20) COMMENT '装修情况',
  `tags` VARCHAR(500) COMMENT '标签(逗号分隔)',
  `images` TEXT COMMENT '图片(JSON数组)',
  `thumbnail` VARCHAR(255) COMMENT '缩略图(第一张图自动生成)',
  `video` VARCHAR(255) COMMENT '视频',
  `description` TEXT COMMENT '描述',
  `views` INT DEFAULT 0 COMMENT '浏览次数',
  `status` TINYINT DEFAULT 1 COMMENT '状态 1:上架 0:下架',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY `idx_property_no` (`property_no`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_community_id` (`community_id`),
  KEY `idx_type` (`type`),
  KEY `idx_status` (`status`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_views` (`views`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源表';

3. 小区表 (communities)

sql
CREATE TABLE `communities` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(100) NOT NULL COMMENT '小区名称',
  `address` VARCHAR(255) NOT NULL COMMENT '详细地址(青岛市北区)',
  `lat` DECIMAL(10,7) NOT NULL COMMENT '纬度',
  `lng` DECIMAL(10,7) NOT NULL COMMENT '经度',
  `build_year` VARCHAR(10) COMMENT '建筑年代',
  `property_fee` VARCHAR(50) COMMENT '物业费',
  `facilities` JSON COMMENT '周边配套设施',
  `status` TINYINT DEFAULT 1 COMMENT '状态 1:正常 0:禁用',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小区表(青岛市北区)';

4. 小区申请表 (community_applications)

sql
CREATE TABLE `community_applications` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `user_id` INT UNSIGNED NOT NULL COMMENT '申请人ID',
  `name` VARCHAR(100) NOT NULL COMMENT '小区名称',
  `address` VARCHAR(255) NOT NULL COMMENT '地址',
  `images` TEXT COMMENT '小区照片(JSON数组)',
  `build_year` VARCHAR(10) NOT NULL COMMENT '建筑年代',
  `property_fee` VARCHAR(50) NOT NULL COMMENT '物业费',
  `lat` DECIMAL(10,7) COMMENT '纬度',
  `lng` DECIMAL(10,7) COMMENT '经度',
  `remark` VARCHAR(255) COMMENT '备注',
  `status` ENUM('pending', 'approved', 'rejected') DEFAULT 'pending' COMMENT '状态',
  `reject_reason` VARCHAR(255) COMMENT '拒绝原因',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `idx_user_id` (`user_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小区申请表';

5. 经纪人申请表 (agent_applications)

sql
CREATE TABLE `agent_applications` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `user_id` INT UNSIGNED NOT NULL COMMENT '申请人ID',
  `real_name` VARCHAR(50) NOT NULL COMMENT '真实姓名',
  `phone` VARCHAR(20) NOT NULL COMMENT '联系电话',
  `company` VARCHAR(100) NOT NULL COMMENT '所属公司(必填)',
  `remark` VARCHAR(255) COMMENT '备注',
  `status` ENUM('pending', 'approved', 'rejected') DEFAULT 'pending' COMMENT '状态',
  `reject_reason` VARCHAR(255) COMMENT '拒绝原因',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  KEY `idx_user_id` (`user_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='经纪人申请表';

6. 标签表 (tags)

sql
CREATE TABLE `tags` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(50) NOT NULL COMMENT '标签名称',
  `sort` INT DEFAULT 0 COMMENT '排序',
  `status` TINYINT DEFAULT 1 COMMENT '状态 1:启用 0:禁用',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源标签表';

7. 邮箱验证码表 (email_codes)

sql
CREATE TABLE `email_codes` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `email` VARCHAR(100) NOT NULL COMMENT '邮箱',
  `code` VARCHAR(10) NOT NULL COMMENT '验证码',
  `expire_time` TIMESTAMP NOT NULL COMMENT '过期时间',
  `is_used` TINYINT DEFAULT 0 COMMENT '是否已使用 1:已使用 0:未使用',
  `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  KEY `idx_email` (`email`),
  KEY `idx_expire_time` (`expire_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邮箱验证码表';

8. 浏览历史表 (view_history)

sql
CREATE TABLE `view_history` (
  `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `user_id` INT UNSIGNED DEFAULT NULL COMMENT '用户ID(登录用户)',
  `anonymous_id` VARCHAR(100) DEFAULT NULL COMMENT '匿名ID(未登录用户,从JWT中提取)',
  `property_id` INT UNSIGNED NOT NULL COMMENT '房源ID',
  `view_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '浏览时间',
  KEY `idx_user_id` (`user_id`),
  KEY `idx_anonymous_id` (`anonymous_id`),
  KEY `idx_property_id` (`property_id`),
  KEY `idx_view_time` (`view_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='浏览历史表';

🔌 接口设计 (RESTful API)

API 基础信息

  • Base URL: https://api.fangyou.com/v1
  • 认证方式: JWT Token(统一认证体系)
  • 请求格式: JSON
  • 响应格式: JSON

JWT 认证策略

Token 类型

1. 匿名 Token(未登录用户)

  • 首次访问公开接口时自动分配
  • 有效期:30天
  • 用于追踪匿名用户浏览历史
json
{
  "type": "anonymous",
  "anonymous_id": "anon_20251029123456_6720abc123_a1b2c3d4e5f6",
  "iat": 1698566400,
  "exp": 1701158400
}

2. 用户 Token(已登录用户)

  • 登录成功后生成
  • 有效期:7天
  • 包含用户身份和权限信息
json
{
  "type": "user",
  "user_id": 123,
  "role": "agent",
  "email": "user@example.com",
  "iat": 1698566400,
  "exp": 1699171200
}

接口认证规则

接口类型认证要求Token 类型说明
公开接口可选anonymous/user无 Token 时自动分配匿名 Token
需登录接口必须user匿名 Token 会返回 401
管理员接口必须user + admin 角色需验证角色权限

Token 传递方式

http
GET /api/properties/123
Authorization: Bearer {token}

自动 Token 分配机制

  • 客户端首次请求公开接口时,请求头可不带 Token
  • 服务端检测到无有效 Token,自动生成匿名 Token
  • 响应中返回新 Token,客户端保存后用于后续请求

登录后数据迁移

  • 用户登录时,服务端自动将匿名 Token 对应的浏览历史迁移到用户账号
  • 返回新的用户 Token,客户端替换原匿名 Token

统一响应格式

json
{
  "code": 200,
  "message": "success",
  "data": {},
  "token": "新Token(仅在首次访问或Token更新时返回)"
}

接口列表

1. 用户模块 (/api/auth)

1.1 微信登录
POST /api/auth/wx-login
参数: { code: string }
返回: { token: string, userInfo: object }
1.2 邮箱验证码登录(H5)
POST /api/auth/email-login
参数: { email: string, code: string }
返回: { token: string, userInfo: object }
1.3 发送邮箱验证码
POST /api/auth/send-email-code
参数: { email: string }
返回: { success: boolean, message: string }
限制: 同一邮箱60秒内只能发送一次
1.4 获取当前 Token 信息
GET /api/auth/token-info
Headers: Authorization: Bearer {token}
返回: { 
  type: string (anonymous/user),
  user_id: number (仅user类型),
  anonymous_id: string (仅anonymous类型),
  role: string (仅user类型)
}
1.5 获取用户信息
GET /api/user/info
Headers: Authorization: Bearer {token}
认证要求: 必须登录(user token)
返回: { userInfo: object }
1.6 申请成为经纪人
POST /api/user/apply-agent
参数: { 
  real_name: string (必填),
  phone: string (必填),
  company: string (必填),
  remark: string (选填)
}
返回: { application_id: number }

2. 房源模块 (/api/properties)

2.1 房源列表
GET /api/properties
参数:
  - page: number (页码)
  - pageSize: number (每页数量)
  - type: string (sale/rent)
  - sort: string (latest/price_asc/price_desc/area)
返回: { list: array, total: number, page: number }
2.2 房源详情
GET /api/properties/:id
认证要求: 可选(无token时自动分配匿名token)
返回: { 
  property: object,
  token: string (首次访问时返回新的匿名token)
}
说明: 自动记录浏览历史(已登录用户关联user_id,未登录用户关联anonymous_id)
2.3 发布房源
POST /api/properties
认证要求: 必须登录(user token)
参数: {
  community_id, title, type, price, area,
  room, hall, toilet, floor, total_floor,
  orientation, decoration, images, video, description
}
返回: { property_id: number }
2.4 编辑房源
PUT /api/properties/:id
认证要求: 必须登录(user token)
参数: { ... 同发布房源 }
返回: { success: boolean }
2.5 删除房源
DELETE /api/properties/:id
认证要求: 必须登录(user token)
返回: { success: boolean }
2.6 上下架房源
PUT /api/properties/:id/status
认证要求: 必须登录(user token)
参数: { status: number }
返回: { success: boolean }
2.7 我的房源
GET /api/my/properties
认证要求: 必须登录(user token)
参数:
  - page: number
  - pageSize: number
  - status: number (可选)
返回: { list: array, total: number }
2.8 浏览历史
GET /api/my/view-history
认证要求: 可选(支持匿名和登录用户)
返回: { 
  list: array (最近50条浏览记录),
  total: number
}
说明: 
  - 已登录用户: 返回user_id关联的浏览历史
  - 匿名用户: 返回anonymous_id关联的浏览历史
  - 登录后会自动迁移匿名浏览历史

3. 小区模块 (/api/communities)

3.1 小区列表
GET /api/communities
参数:
  - page: number
  - pageSize: number
返回: { list: array, total: number }
3.2 搜索小区
GET /api/communities/search
参数: { keyword: string }
返回: { list: array }
3.3 申请添加小区
POST /api/communities/apply
认证要求: 必须登录(user token)
参数: { 
  name: string (必填),
  address: string (必填),
  images: array (必填,小区照片),
  build_year: string (必填),
  property_fee: string (必填),
  lat: number (选填),
  lng: number (选填),
  remark: string (选填)
}
返回: { application_id: number }
3.4 腾讯地图POI搜索
GET /api/map/poi
参数: { keyword: string }
说明: 默认限定青岛市北区范围
返回: { list: array }

4.1 综合搜索
GET /api/search
参数: { keyword: string, page, pageSize }
返回: { list: array, total: number }
4.2 条件筛选
POST /api/search/filter
参数: {
  type: string,
  price_min: number,
  price_max: number,
  area_min: number,
  area_max: number,
  room: array,
  district: string,
  orientation: string,
  decoration: string,
  page: number,
  pageSize: number
}
返回: { list: array, total: number }

5. 上传模块 (/api/upload)

5.1 上传图片
POST /api/upload/image
认证要求: 必须登录(user token)
Content-Type: multipart/form-data
参数: { file: File }
返回: { url: string }
5.2 上传视频
POST /api/upload/video
认证要求: 必须登录(user token)
Content-Type: multipart/form-data
参数: { file: File }
返回: { url: string }

6. 管理后台 (/api/admin)

所有管理后台接口均需要:认证要求: 必须登录(user token) + admin 角色

6.1 数据统计
GET /api/admin/stats
返回: {
  // 实时统计
  total_properties: number,
  online_properties: number,
  total_users: number,
  total_agents: number,
  
  // 今日数据
  today_properties: number,
  today_users: number,
  today_views: number,
  
  // 周期对比
  week_comparison: {
    properties: { current: number, last: number, change: number },
    users: { current: number, last: number, change: number },
    views: { current: number, last: number, change: number }
  },
  month_comparison: {
    properties: { current: number, last: number, change: number },
    users: { current: number, last: number, change: number },
    views: { current: number, last: number, change: number }
  },
  
  // 待处理
  pending_agents: number,
  pending_communities: number
}
6.2 所有房源
GET /api/admin/properties
参数: { page, pageSize, status, type, user_id, start_time, end_time }
返回: { list: array, total: number }
6.3 用户列表
GET /api/admin/users
参数: { page, pageSize, role, status }
返回: { list: array, total: number }
6.4 审核经纪人
PUT /api/admin/agents/:id
参数: { status: 'approved'|'rejected', reject_reason }
返回: { success: boolean }
6.5 小区管理
GET /api/admin/communities
POST /api/admin/communities
PUT /api/admin/communities/:id
DELETE /api/admin/communities/:id
6.6 审核小区申请
GET /api/admin/community-applications
PUT /api/admin/community-applications/:id
参数: { status: 'approved'|'rejected', reject_reason }
6.7 标签管理
GET /api/admin/tags                    # 标签列表
POST /api/admin/tags                   # 新增标签
PUT /api/admin/tags/:id                # 编辑标签
DELETE /api/admin/tags/:id             # 删除标签

📁 项目目录结构

fangyou/

├── client/                          # uni-app 前端项目
│   ├── pages/
│   │   ├── index/                  # 首页(房源列表)
│   │   │   ├── index.vue
│   │   │   └── components/
│   │   │       ├── PropertyCard.vue
│   │   │       └── FilterBar.vue
│   │   │
│   │   ├── property/               # 房源详情
│   │   │   └── detail.vue
│   │   │
│   │   ├── publish/                # 发布房源
│   │   │   ├── index.vue
│   │   │   ├── select-community.vue
│   │   │   └── form.vue
│   │   │
│   │   ├── search/                 # 搜索
│   │   │   ├── index.vue
│   │   │   └── filter.vue
│   │   │
│   │   ├── map/                    # 地图找房
│   │   │   └── index.vue
│   │   │
│   │   ├── my/                     # 个人中心
│   │   │   ├── index.vue
│   │   │   ├── properties.vue      # 我的房源
│   │   │   ├── apply-agent.vue     # 申请经纪人
│   │   │   └── settings.vue
│   │   │
│   │   ├── auth/                   # 登录
│   │   │   ├── login.vue
│   │   │   └── phone-login.vue
│   │   │
│   │   └── admin/                  # 管理后台
│   │       ├── index.vue           # 数据统计
│   │       ├── properties.vue      # 房源管理
│   │       ├── users.vue           # 用户管理
│   │       ├── agents.vue          # 经纪人审核
│   │       ├── communities.vue     # 小区管理
│   │       └── community-apply.vue # 小区申请审核
│   │
│   ├── components/                 # 全局组件
│   │   ├── ImageUpload.vue        # 图片上传
│   │   ├── VideoUpload.vue        # 视频上传
│   │   ├── CommunitySelector.vue  # 小区选择器
│   │   ├── MapSelector.vue        # 地图选点
│   │   └── Empty.vue              # 空状态
│   │
│   ├── api/                        # API封装
│   │   ├── request.js             # 请求封装
│   │   ├── auth.js                # 用户相关
│   │   ├── property.js            # 房源相关
│   │   ├── community.js           # 小区相关
│   │   ├── search.js              # 搜索相关
│   │   ├── upload.js              # 上传相关
│   │   └── admin.js               # 管理后台
│   │
│   ├── utils/                      # 工具函数
│   │   ├── auth.js                # 登录鉴权
│   │   ├── validator.js           # 表单验证
│   │   ├── format.js              # 格式化
│   │   ├── map.js                 # 地图工具
│   │   └── constants.js           # 常量配置
│   │
│   ├── store/                      # 状态管理 (Pinia)
│   │   ├── index.js
│   │   ├── user.js                # 用户状态
│   │   ├── property.js            # 房源状态
│   │   └── app.js                 # 应用状态
│   │
│   ├── static/                     # 静态资源
│   │   ├── images/
│   │   └── icons/
│   │
│   ├── uni_modules/                # uni-ui组件
│   │
│   ├── App.vue                     # 应用入口
│   ├── main.js                     # 入口文件
│   ├── manifest.json               # 应用配置
│   ├── pages.json                  # 页面配置
│   └── uni.scss                    # 全局样式

├── server/                          # ThinkPHP 8 后端项目
│   ├── app/
│   │   ├── controller/             # 控制器
│   │   │   ├── Auth.php           # 用户认证
│   │   │   ├── Property.php       # 房源管理
│   │   │   ├── Community.php      # 小区管理
│   │   │   ├── Search.php         # 搜索
│   │   │   ├── Upload.php         # 文件上传
│   │   │   ├── Tag.php            # 标签管理
│   │   │   ├── ViewHistory.php    # 浏览历史
│   │   │   └── Admin.php          # 管理后台
│   │   │
│   │   ├── model/                  # 模型
│   │   │   ├── User.php
│   │   │   ├── Property.php
│   │   │   ├── Community.php
│   │   │   ├── CommunityApplication.php
│   │   │   ├── AgentApplication.php
│   │   │   ├── Tag.php
│   │   │   ├── EmailCode.php
│   │   │   └── ViewHistory.php
│   │   │
│   │   ├── service/                # 业务逻辑层
│   │   │   ├── AuthService.php
│   │   │   ├── JwtService.php     # JWT Token生成与解析(支持匿名/用户Token)
│   │   │   ├── PropertyService.php
│   │   │   ├── CommunityService.php
│   │   │   ├── MapService.php     # 腾讯地图API
│   │   │   ├── EmailService.php   # 邮件服务(QQ邮箱SMTP)
│   │   │   ├── ImageService.php   # 图片处理(缩略图生成)
│   │   │   └── UploadService.php
│   │   │
│   │   ├── middleware/             # 中间件
│   │   │   ├── Auth.php           # JWT认证(必须用户Token)
│   │   │   ├── OptionalAuth.php   # 可选认证(自动分配匿名Token)
│   │   │   ├── Role.php           # 角色权限
│   │   │   └── Cors.php           # 跨域
│   │   │
│   │   ├── validate/               # 验证器
│   │   │   ├── PropertyValidate.php
│   │   │   ├── CommunityValidate.php
│   │   │   └── UserValidate.php
│   │   │
│   │   ├── common.php              # 公共函数
│   │   ├── event.php               # 事件定义
│   │   └── provider.php            # 服务提供
│   │
│   ├── config/                     # 配置文件
│   │   ├── app.php                # 应用配置
│   │   ├── database.php           # 数据库配置
│   │   ├── cache.php              # 缓存配置(Redis)
│   │   ├── filesystem.php         # 文件系统
│   │   ├── jwt.php                # JWT配置(自定义)
│   │   ├── map.php                # 腾讯地图配置(自定义)
│   │   ├── email.php              # 邮件配置(自定义,QQ邮箱SMTP)
│   │   └── route.php              # 路由配置
│   │
│   ├── route/                      # 路由目录
│   │   ├── api.php                # API路由
│   │   └── app.php                # 应用路由
│   │
│   ├── public/                     # 公开目录(web根目录)
│   │   ├── index.php              # 入口文件
│   │   ├── .htaccess              # Apache重写规则
│   │   ├── robots.txt
│   │   └── uploads/               # 上传文件存储
│   │       ├── images/
│   │       └── videos/
│   │
│   ├── runtime/                    # 运行时目录
│   │   ├── cache/                 # 缓存目录
│   │   ├── log/                   # 日志目录
│   │   └── temp/                  # 临时目录
│   │
│   ├── vendor/                     # Composer依赖
│   │
│   ├── think                       # 命令行工具
│   ├── composer.json               # Composer配置
│   ├── .env                        # 环境变量
│   └── .example.env                # 环境变量示例

├── database/                        # 数据库
│   ├── migrations/                 # 迁移文件
│   │   └── init.sql               # 初始化SQL
│   └── seeds/                      # 种子数据
│       └── admin_seed.sql         # 管理员账号

├── docs/                           # 文档
│   ├── api.md                     # API文档
│   ├── database.md                # 数据库设计文档
│   └── deployment.md              # 部署文档

├── .gitignore
├── README.md
└── 项目计划.md                     # 本文档

🔧 ThinkPHP 8 技术要点

安装与初始化

bash
# 使用Composer创建ThinkPHP 8项目
composer create-project topthink/think server

# 安装ThinkPHP8扩展包
cd server
composer require topthink/think-orm
composer require topthink/think-cache
composer require topthink/think-filesystem
composer require firebase/php-jwt        # JWT认证
composer require phpmailer/phpmailer     # 邮件发送(QQ邮箱SMTP)
composer require intervention/image      # 图片处理(缩略图生成)

核心特性应用

1. 路由定义 (route/api.php)

php
use think\facade\Route;

// RESTful 资源路由
Route::resource('properties', 'Property');

// 自定义路由组
Route::group('api', function() {
    // 用户认证
    Route::post('auth/login', 'Auth/login');
    Route::post('auth/register', 'Auth/register');
    
    // 房源相关 (需要认证)
    Route::group(function() {
        Route::get('properties', 'Property/index');
        Route::get('properties/:id', 'Property/read');
        Route::post('properties', 'Property/save');
        Route::put('properties/:id', 'Property/update');
        Route::delete('properties/:id', 'Property/delete');
    })->middleware(['Auth']);
    
    // 管理后台 (需要管理员权限)
    Route::group('admin', function() {
        Route::get('stats', 'Admin/stats');
        Route::get('properties', 'Admin/properties');
    })->middleware(['Auth', 'Role:admin']);
});

2. 模型定义 (app/model/Property.php)

php
namespace app\model;

use think\Model;

class Property extends Model
{
    // 表名
    protected $name = 'properties';
    
    // 自动时间戳
    protected $autoWriteTimestamp = true;
    protected $createTime = 'create_time';
    protected $updateTime = 'update_time';
    
    // JSON字段
    protected $json = ['images', 'facilities'];
    
    // 类型转换
    protected $type = [
        'price' => 'float',
        'area' => 'float',
        'status' => 'integer',
    ];
    
    // 关联小区
    public function community()
    {
        return $this->belongsTo(Community::class);
    }
    
    // 关联用户
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    
    // 搜索器:类型
    public function searchTypeAttr($query, $value)
    {
        if ($value) {
            $query->where('type', $value);
        }
    }
    
    // 搜索器:价格区间
    public function searchPriceAttr($query, $value)
    {
        if (isset($value[0]) && isset($value[1])) {
            $query->whereBetween('price', $value);
        }
    }
}

3. 控制器 (app/controller/Property.php)

php
namespace app\controller;

use app\BaseController;
use app\model\Property as PropertyModel;
use app\validate\PropertyValidate;
use think\Request;

class Property extends BaseController
{
    // 房源列表
    public function index(Request $request)
    {
        $page = $request->param('page', 1);
        $pageSize = $request->param('pageSize', 10);
        
        $list = PropertyModel::with(['community', 'user'])
            ->withSearch(['type', 'price'], $request->param())
            ->where('status', 1)
            ->order('create_time desc')
            ->paginate([
                'list_rows' => $pageSize,
                'page' => $page
            ]);
            
        return json([
            'code' => 200,
            'message' => 'success',
            'data' => $list
        ]);
    }
    
    // 房源详情
    public function read($id)
    {
        $property = PropertyModel::with(['community', 'user'])
            ->find($id);
            
        if (!$property) {
            return json(['code' => 404, 'message' => '房源不存在']);
        }
        
        // 浏览量+1
        $property->inc('views')->update();
        
        return json([
            'code' => 200,
            'data' => $property
        ]);
    }
    
    // 发布房源
    public function save(Request $request)
    {
        $data = $request->post();
        
        // 验证
        $validate = new PropertyValidate();
        if (!$validate->check($data)) {
            return json(['code' => 400, 'message' => $validate->getError()]);
        }
        
        // 获取当前用户ID
        $data['user_id'] = $request->userId;
        
        $property = PropertyModel::create($data);
        
        return json([
            'code' => 200,
            'message' => '发布成功',
            'data' => ['property_id' => $property->id]
        ]);
    }
}

4. 验证器 (app/validate/PropertyValidate.php)

php
namespace app\validate;

use think\Validate;

class PropertyValidate extends Validate
{
    protected $rule = [
        'title' => 'require|max:100',
        'type' => 'require|in:sale,rent',
        'price' => 'require|float|>:0',
        'area' => 'require|float|>:0',
        'room' => 'require|integer|>=:1',
        'hall' => 'require|integer|>=:0',
        'community_id' => 'require|integer',
    ];
    
    protected $message = [
        'title.require' => '请填写房源标题',
        'title.max' => '标题最多100个字符',
        'type.require' => '请选择房源类型',
        'type.in' => '房源类型错误',
        'price.require' => '请填写价格',
        'price.float' => '价格格式错误',
    ];
}

5. 中间件 (app/middleware/Auth.php)

php
namespace app\middleware;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class Auth
{
    public function handle($request, \Closure $next)
    {
        $token = $request->header('authorization');
        
        if (!$token) {
            return json(['code' => 401, 'message' => '未登录']);
        }
        
        // 去除 Bearer 前缀
        $token = str_replace('Bearer ', '', $token);
        
        try {
            $key = config('jwt.key');
            $decoded = JWT::decode($token, new Key($key, 'HS256'));
            
            // 将用户ID注入到request中
            $request->userId = $decoded->user_id;
            
        } catch (\Exception $e) {
            return json(['code' => 401, 'message' => 'Token无效']);
        }
        
        return $next($request);
    }
}

6. 服务层 (app/service/MapService.php)

php
namespace app\service;

use think\facade\Cache;

class MapService
{
    private $key;
    
    public function __construct()
    {
        $this->key = config('map.tencent_key');
    }
    
    // POI搜索
    public function searchPoi($keyword, $city = '')
    {
        $url = "https://apis.map.qq.com/ws/place/v1/search";
        $params = [
            'keyword' => $keyword,
            'boundary' => "region({$city},0)",
            'key' => $this->key,
        ];
        
        // 缓存5分钟
        $cacheKey = 'map_poi_' . md5($keyword . $city);
        
        return Cache::remember($cacheKey, function() use ($url, $params) {
            $response = file_get_contents($url . '?' . http_build_query($params));
            return json_decode($response, true);
        }, 300);
    }
}

配置文件

config/database.php

php
return [
    'default' => 'mysql',
    'connections' => [
        'mysql' => [
            'type' => 'mysql',
            'hostname' => env('database.hostname', '127.0.0.1'),
            'database' => env('database.database', 'fangyou'),
            'username' => env('database.username', 'root'),
            'password' => env('database.password', ''),
            'hostport' => env('database.hostport', '3306'),
            'charset' => 'utf8mb4',
            'prefix' => '',
        ],
    ],
];

config/cache.php (Redis)

php
return [
    'default' => 'redis',
    'stores' => [
        'redis' => [
            'type' => 'redis',
            'host' => env('redis.host', '127.0.0.1'),
            'port' => env('redis.port', 6379),
            'password' => env('redis.password', ''),
            'select' => 0,
            'timeout' => 0,
            'expire' => 0,
            'persistent' => false,
            'prefix' => 'fangyou:',
        ],
    ],
];

config/jwt.php (自定义)

php
return [
    'key' => env('jwt.key', 'your-secret-key'),
    'user_ttl' => 86400 * 7, // 用户Token有效期: 7天
    'anonymous_ttl' => 86400 * 30, // 匿名Token有效期: 30天
    'alg' => 'HS256',
];

config/map.php (自定义)

php
return [
    'tencent_key' => env('map.tencent_key', ''),
    'default_city' => '青岛',
    'default_district' => '市北区',
];

config/email.php (自定义)

php
return [
    'host' => env('email.host', 'smtp.qq.com'),
    'port' => env('email.port', 587),
    'username' => env('email.username', ''),
    'password' => env('email.password', ''),  // QQ邮箱授权码
    'from_email' => env('email.from_email', ''),
    'from_name' => env('email.from_name', '居美家'),
    'code_expire' => 300,  // 验证码有效期(秒) - 5分钟
    'send_interval' => 60,  // 发送间隔(秒) - 60秒
];

.env 环境配置

ini
APP_DEBUG = true

[DATABASE]
HOSTNAME = 127.0.0.1
DATABASE = fangyou
USERNAME = root
PASSWORD = 
HOSTPORT = 3306

[REDIS]
HOST = 127.0.0.1
PORT = 6379
PASSWORD = 

[JWT]
KEY = your-secret-key-change-in-production

[MAP]
TENCENT_KEY = PVZBZ-DB46Z-WFEXI-ZCKQA-HZBWS-BLBF2

[EMAIL]
HOST = smtp.qq.com
PORT = 587
USERNAME = your-qq-email@qq.com
PASSWORD = your-qq-authorization-code
FROM_EMAIL = your-qq-email@qq.com
FROM_NAME = 居美家

常用命令

bash
# 启动开发服务器
php think run

# 清除缓存
php think clear

# 生成控制器
php think make:controller Property

# 生成模型
php think make:model Property

# 生成中间件
php think make:middleware Auth

# 生成验证器
php think make:validate Property

📅 开发计划

Phase 1: 项目初始化(1-2天)

  • [x] 项目需求整理
  • [ ] 项目结构搭建
  • [ ] 开发环境配置
  • [ ] 数据库设计与创建
  • [ ] Git仓库初始化

Phase 2: 核心功能开发(7-10天)

Week 1: 基础模块

Day 1-2: 用户模块

  • [ ] 微信登录(小程序)
  • [ ] 邮箱验证码登录(H5)
  • [ ] QQ邮箱SMTP服务集成
  • [ ] JWT认证中间件
  • [ ] 用户信息管理

Day 3-5: 房源列表与详情

  • [ ] 房源列表页面
  • [ ] 房源详情页面
  • [ ] 房源数据接口
  • [ ] 图片上传功能

Day 6-7: 发布房源

  • [ ] 小区选择功能
  • [ ] 发布房源表单
  • [ ] 图片/视频上传
  • [ ] 发布房源接口

Week 2: 完善功能

Day 8-9: 小区管理

  • [ ] 腾讯地图API集成
  • [ ] POI搜索功能
  • [ ] 小区管理(管理端)
  • [ ] 小区申请流程

Day 10-11: 搜索功能

  • [ ] 关键词搜索
  • [ ] 条件筛选
  • [ ] 搜索结果页

Day 12-13: 我的房源

  • [ ] 我的房源列表
  • [ ] 编辑房源
  • [ ] 删除房源
  • [ ] 上下架功能

Phase 3: 管理后台(3-4天)

Day 14-15: 管理功能

  • [ ] 管理后台首页(数据统计)
  • [ ] 房源管理
  • [ ] 用户管理
  • [ ] 经纪人审核

Day 16-17: 小区与标签管理

  • [ ] 小区列表与编辑
  • [ ] 小区申请审核
  • [ ] 标签管理功能
  • [ ] 权限控制完善

Phase 4: 进阶功能(3-5天)

Day 18: 浏览历史与分享功能

  • [ ] 浏览历史记录功能(基于JWT匿名Token)
  • [ ] 登录后数据迁移机制
  • [ ] 房源分享功能(微信/海报/链接)

Day 19-20: 优化与完善

  • [ ] 缩略图自动生成功能
  • [ ] 房源编号生成逻辑
  • [ ] 列表分页优化
  • [ ] 缓存策略(Redis)
  • [ ] 错误提示优化(Toast/Modal)
  • [ ] 数据统计优化(周期对比)

Day 21: 初始数据准备

  • [ ] 预设房源标签数据
  • [ ] 预设装修类型选项
  • [ ] 预设朝向选项
  • [ ] 管理员账号数据

Day 22: 测试与修复

  • [ ] 功能测试
  • [ ] 浏览历史测试
  • [ ] 分享功能测试
  • [ ] Bug修复
  • [ ] 性能优化

Phase 5: 部署上线(1-2天)

Day 23-24: 部署

  • [ ] 服务器环境配置
  • [ ] 代码部署
  • [ ] 小程序提审
  • [ ] H5域名配置
  • [ ] 数据备份方案

🎨 UI设计要点

设计风格

  • 现代简约风格
  • 参考安居客APP
  • 符合中国用户使用习惯
  • 清晰的信息层级

色彩方案

  • 主色调:推荐蓝色系(#1890FF)或绿色系(#52C41A)
  • 辅助色:灰色系
  • 强调色:橙红色(价格等重要信息)

组件规范

  • 遵循uni-ui设计规范
  • 统一的间距、圆角、阴影
  • 良好的点击反馈
  • 友好的空状态

🔒 安全性考虑

1. 用户认证

  • JWT Token认证
  • Token过期机制
  • 刷新Token策略

2. 数据安全

  • SQL注入防护(预处理语句)
  • XSS攻击防护
  • 敏感信息加密存储
  • 手机号脱敏显示

3. 接口安全

  • 接口鉴权
  • 请求频率限制
  • 图片上传类型/大小限制
  • 防止恶意上传

4. 权限控制

  • 用户只能操作自己的房源
  • 管理员权限验证
  • 角色权限中间件

⚡ 性能优化

1. 前端优化

  • 图片懒加载
  • 列表虚拟滚动(长列表)
  • 路由懒加载
  • 静态资源CDN

2. 后端优化

  • 数据库索引优化
  • Redis缓存热门数据
  • 图片压缩与缩略图
  • 分页查询

3. 图片优化

  • 房源第一张图自动生成缩略图
  • 缩略图用于列表展示,提高加载速度
  • 原图用于详情页查看
  • 暂不做图片压缩和水印处理

📊 数据统计

已实现统计

  • 房源浏览量(每个房源)
  • 今日新增房源
  • 今日新增用户
  • 今日浏览量
  • 本周/本月数据对比
  • 经纪人房源数统计

进阶统计(后期扩展)

  • 用户行为分析
  • 热门房源排行
  • 搜索热词
  • 转化率统计
  • 经纪人业绩排名

🚀 后期扩展功能

Phase N+1: 增值功能

  • [ ] 房源推广(置顶、精选)
  • [ ] VIP经纪人
  • [ ] 在线咨询/聊天
  • [ ] 预约看房
  • [ ] 房源收藏功能
  • [ ] 房源比对
  • [ ] VR看房
  • [ ] 消息通知(看房提醒)
  • [ ] 地图找房功能

Phase N+2: 运营功能

  • [ ] 分享裂变
  • [ ] 优惠券系统
  • [ ] 积分系统
  • [ ] 推荐算法
  • [ ] SEO优化

📝 注意事项

开发注意

  1. 服务范围:仅针对青岛市北区
  2. 不需要审核机制:经纪人发布房源直接上线
  3. 小区数据积累:完全依赖经纪人申请和管理员添加
  4. 无需支付功能:免费使用模式
  5. 个人小程序:功能受限但满足需求
  6. 本地存储文件:图片和视频存储在服务器本地
  7. 文件大小限制
    • 图片单张最大5MB
    • 视频最大100MB,时长不限
  8. 邮箱登录:H5端使用邮箱验证码登录(QQ邮箱SMTP)
  9. 管理员权限:可编辑所有房源
  10. 房源编号:自动生成格式如 FY202510290001
  11. 缩略图生成:上传房源图片时,第一张图自动生成缩略图
  12. 浏览历史与JWT认证
    • 统一使用JWT Token认证体系
    • 未登录用户自动分配匿名Token(anonymous_id)
    • 登录用户使用用户Token(user_id)
    • 登录时自动迁移匿名浏览历史
  13. 房源有效期:永久有效,除非手动下架或删除
  14. 下架房源:用户可查看但不可搜索
  15. 错误提示:优先使用Toast轻提示,根据场景选择Modal
  16. 初始数据:预设房源标签、装修类型、朝向等基础数据
  17. JWT认证架构
    • 去除Session机制,统一使用JWT
    • 公开接口支持匿名访问(自动分配临时Token)
    • 需登录接口验证用户Token
    • 管理员接口验证Token + 角色
    • 无状态设计,易于扩展
  18. ThinkPHP特性使用
    • 充分利用ORM链式操作
    • 使用验证器进行数据验证
    • 使用中间件进行权限控制(Auth/OptionalAuth/Role)
    • 使用模型关联简化查询
    • 使用Service层处理JWT逻辑

测试要点

  1. 小程序各机型适配(个人小程序限制)
  2. H5不同浏览器兼容
  3. 邮箱验证码发送与验证测试
  4. 图片/视频上传限制测试(5MB/100MB)
  5. 缩略图自动生成测试
  6. 房源编号生成测试
  7. 标签管理功能测试
  8. JWT认证机制测试
    • 匿名Token自动分配测试
    • 用户Token登录认证测试
    • Token过期处理测试
    • 登录后数据迁移测试(匿名→用户)
  9. 浏览历史测试
    • 未登录用户浏览记录(anonymous_id)
    • 登录用户浏览记录(user_id)
    • 登录后历史记录合并测试
  10. 分享功能测试(微信/海报/链接)
  11. 数据统计测试(周期对比)
  12. 下架房源显示逻辑测试
  13. 大数据量性能测试
  14. 权限控制测试
    • 公开接口(OptionalAuth中间件)
    • 需登录接口(Auth中间件)
    • 管理员接口(Role中间件)
  15. 阿里云+宝塔环境部署测试

运维要点

  1. 定期数据备份
  2. 日志记录与监控
  3. 服务器资源监控
  4. 图片存储空间管理

📞 联系方式

项目负责人

  • 开发者:[张江]
  • 联系方式:[待填写]

客户方

  • 公司名称:居美家房产中介公司
  • 联系人:[待填写]
  • 联系方式:[待填写]

📄 附录

相关链接

版本历史

  • v1.0.0 (2025-10-29):初始版本,完成项目计划
  • v1.1.0 (2025-10-29):需求细化,明确所有功能点和技术细节
  • v1.2.0 (2025-10-29):完善所有细节需求,补充数据统计、图片处理、浏览历史等功能
  • v1.3.0 (2025-10-29):重大更新:采用统一JWT认证方案,去除Session机制
    • 实现匿名Token自动分配机制
    • 支持登录后数据自动迁移
    • 新增OptionalAuth中间件
    • 完善JWT认证架构设计文档

📋 需求确认总结

以下是与客户确认的所有关键信息:

✅ 第一轮确认事项

  1. 服务区域:青岛市北区
  2. 小区数据:完全依赖经纪人申请和管理员添加逐步积累
  3. 经纪人审核:只需简单的信息登记审核,无需上传资质证明
  4. 文件限制
    • 图片:单张最大5MB
    • 视频:最大100MB,时长无限制
  5. H5登录:邮箱验证码登录(QQ邮箱SMTP),验证码有效期5分钟,发送间隔60秒
  6. 管理员账号:直接在数据库插入
  7. 房源编辑权限:管理员可以编辑所有房源,经纪人只能编辑自己的
  8. 服务器:阿里云服务器 + 宝塔面板
  9. 预计用户数:青岛市北区人数规模
  10. 腾讯地图密钥:PVZBZ-DB46Z-WFEXI-ZCKQA-HZBWS-BLBF2
  11. 房源状态:目前只有"上架/下架"两种状态
  12. 小程序类型:个人小程序
  13. 区域筛选:通过小区名称搜索筛选
  14. 经纪人申请:所属公司必填,任何人都可申请成为经纪人
  15. 房源标签
    • 经纪人手动选择,可多选
    • 支持新增标签
    • 后台统一管理标签
    • 标签可用于搜索和筛选
  16. 联系方式:点击"联系"按钮后弹出拨号,显示用户表中的手机号
  17. 数据库名称:fangyou
  18. 图片存储:server/public/uploads/images/
  19. 我的房源统计:展示总房源数、上架数、下架数、总浏览量
  20. 开发方式:uni-app同步开发小程序和H5,功能完全一致
  21. 首页展示:Tab页切换二手房/租房,默认排序为最新发布

✅ 第二轮补充确认

  1. 数据统计
    • 今日新增房源、今日新增用户、今日浏览量
    • 本周/本月数据对比
  2. 分享功能:支持微信好友、朋友圈、生成海报、复制链接
  3. 排序方式:最新发布(默认)、价格升降序、面积、浏览量(热门)
  4. 周边配套:添加小区时获取一次存入数据库,定期更新
  5. 图片处理
    • 暂不做压缩、水印处理
    • 房源第一张图自动生成缩略图
    • 列表展示使用缩略图
  6. 浏览历史(已更新为JWT方案)
    • 未登录用户通过JWT中的anonymous_id标识
    • 登录用户关联到账号(user_id)
    • 登录时自动迁移匿名浏览历史
    • 统一使用JWT Token维护用户身份状态
  7. 下架房源
    • 普通用户可查看但标注下架状态
    • 不在搜索结果和列表筛选中出现
  8. 小区申请:需提供小区照片、建筑年代、物业费等
  9. 初始数据:预设房源标签、装修类型、朝向等基础数据
  10. 房源有效期:永久有效,除非手动下架或删除
  11. 地图找房:暂不开发,列入后期扩展
  12. 错误提示:优先Toast轻提示,根据场景选择Modal
  13. 数据导出:暂不需要
  14. 房源编号:需要编号,格式如 FY202510290001
  15. 小区表简化:移除省市区字段,项目只针对青岛市北区

✅ 第三轮架构优化确认

  1. JWT统一认证方案
    • 去除Session机制,统一使用JWT Token
    • 未登录用户自动分配匿名Token(30天有效期)
    • 登录用户使用用户Token(7天有效期)
    • 公开接口使用OptionalAuth中间件(自动分配匿名Token)
    • 需登录接口使用Auth中间件(必须用户Token)
    • 管理员接口使用Auth+Role中间件
  2. 数据迁移机制:登录时自动将匿名Token关联的浏览历史迁移到用户账号
  3. 浏览历史存储:数据库字段从session_id改为anonymous_id
  4. 无状态架构:服务端无需存储Session,易于水平扩展
  5. 前端Token管理:统一管理一个Token,自动保存服务端返回的新Token

🔐 JWT 统一认证方案详细设计

方案概述

本项目采用统一 JWT 认证体系,去除传统 Session 机制,实现无状态认证架构。所有用户(包括未登录的匿名用户)均使用 JWT Token 标识身份。

Token 类型设计

1. 匿名 Token(Anonymous Token)

用途:未登录用户的临时身份标识

生成时机

  • 用户首次访问公开接口时
  • 没有携带 Token 或 Token 无效时

Payload 结构

json
{
  "type": "anonymous",
  "anonymous_id": "anon_20251029123456_6720abc123_a1b2c3d4e5f6",
  "iat": 1698566400,
  "exp": 1701158400
}

特性

  • 有效期:30 天
  • 自动分配,无需用户操作
  • 用于追踪匿名用户行为(浏览历史等)
  • 登录后自动失效,数据迁移到用户账号

2. 用户 Token(User Token)

用途:已登录用户的身份认证

生成时机

  • 用户登录成功后
  • Token 刷新时

Payload 结构

json
{
  "type": "user",
  "user_id": 123,
  "role": "agent",
  "email": "user@example.com",
  "iat": 1698566400,
  "exp": 1699171200
}

特性

  • 有效期:7 天
  • 包含用户 ID、角色等关键信息
  • 支持权限验证
  • 退出登录时客户端删除即可

中间件设计

1. OptionalAuth(可选认证中间件)

适用场景:公开接口,支持匿名和登录用户访问

工作流程

1. 检查请求头中的 Token
   ├─ 有 Token
   │  ├─ 解析成功 → 注入用户信息到 request
   │  └─ 解析失败 → 生成新的匿名 Token
   └─ 无 Token → 生成新的匿名 Token

2. 注入信息到 request:
   - userId (已登录) 或 anonymousId (匿名)
   - tokenType ('user' 或 'anonymous')
   - newToken (如果生成了新 Token)

3. 继续执行业务逻辑

应用接口

  • 房源列表
  • 房源详情
  • 搜索接口
  • 浏览历史

2. Auth(强制认证中间件)

适用场景:需要登录的接口

工作流程

1. 检查请求头中的 Token
   └─ 无 Token → 返回 401 未登录

2. 解析 Token
   └─ 解析失败 → 返回 401 Token无效

3. 验证 Token 类型
   ├─ type === 'user' → 通过验证
   └─ type === 'anonymous' → 返回 401 请先登录

4. 注入用户信息到 request:
   - userId
   - userRole
   - tokenType

5. 继续执行业务逻辑

应用接口

  • 发布房源
  • 编辑房源
  • 我的房源
  • 申请经纪人
  • 文件上传

3. Role(角色权限中间件)

适用场景:管理员接口

前置要求:必须先经过 Auth 中间件

工作流程

1. 从 request 获取 userRole
   └─ 不存在 → 返回 401 未登录

2. 验证角色权限
   ├─ role === 'admin' → 通过验证
   └─ role !== 'admin' → 返回 403 无权限

3. 继续执行业务逻辑

应用接口

  • 所有 /api/admin/* 接口

核心服务实现

JwtService.php

php
namespace app\service;

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

class JwtService
{
    /**
     * 生成匿名 Token
     */
    public function generateAnonymousToken()
    {
        $payload = [
            'type' => 'anonymous',
            'anonymous_id' => $this->generateAnonymousId(),
            'iat' => time(),
            'exp' => time() + config('jwt.anonymous_ttl'),
        ];
        
        return JWT::encode($payload, config('jwt.key'), 'HS256');
    }
    
    /**
     * 生成用户 Token
     */
    public function generateUserToken($user)
    {
        $payload = [
            'type' => 'user',
            'user_id' => $user->id,
            'role' => $user->role,
            'email' => $user->email,
            'iat' => time(),
            'exp' => time() + config('jwt.user_ttl'),
        ];
        
        return JWT::encode($payload, config('jwt.key'), 'HS256');
    }
    
    /**
     * 解析 Token
     */
    public function decode($token)
    {
        try {
            return JWT::decode($token, new Key(config('jwt.key'), 'HS256'));
        } catch (\Exception $e) {
            return null;
        }
    }
}

数据迁移机制

登录时自动迁移匿名数据

场景:用户从匿名状态登录后,需要将之前的浏览历史等数据关联到用户账号

实现

php
// AuthController::login()
public function login()
{
    // 1. 验证登录信息...
    $user = User::where('email', $email)->find();
    
    // 2. 生成用户 Token
    $token = $this->jwtService->generateUserToken($user);
    
    // 3. 迁移匿名数据
    $oldToken = $this->getToken();
    if ($oldToken) {
        $payload = $this->jwtService->decode($oldToken);
        
        if ($payload && $payload->type === 'anonymous') {
            // 迁移浏览历史
            ViewHistory::where('anonymous_id', $payload->anonymous_id)
                ->where('user_id', null)
                ->update([
                    'user_id' => $user->id,
                    'anonymous_id' => null
                ]);
        }
    }
    
    // 4. 返回新 Token
    return json([
        'code' => 200,
        'data' => [
            'token' => $token,
            'userInfo' => $user
        ]
    ]);
}

前端集成方案

Token 管理(utils/auth.js)

javascript
const TOKEN_KEY = 'token';

export function getToken() {
  return uni.getStorageSync(TOKEN_KEY) || '';
}

export function setToken(token) {
  if (token) {
    uni.setStorageSync(TOKEN_KEY, token);
  }
}

export function removeToken() {
  uni.removeStorageSync(TOKEN_KEY);
}

请求拦截(api/request.js)

javascript
import { getToken, setToken, removeToken } from '@/utils/auth';

function request(options) {
  const token = getToken();
  
  return new Promise((resolve, reject) => {
    uni.request({
      ...options,
      header: {
        'Content-Type': 'application/json',
        'Authorization': token ? `Bearer ${token}` : '',
        ...options.header
      },
      success: (res) => {
        // 自动保存服务端返回的新 Token(匿名 Token)
        if (res.data.token) {
          setToken(res.data.token);
        }
        
        if (res.data.code === 200) {
          resolve(res.data);
        } else if (res.data.code === 401) {
          // Token 过期或无效
          removeToken();
          // 可选:跳转到登录页
          reject(res.data);
        } else {
          reject(res.data);
        }
      },
      fail: reject
    });
  });
}

登录流程

javascript
// 登录
export function login(email, code) {
  return request({
    url: '/api/auth/email-login',
    method: 'POST',
    data: { email, code }
  }).then(res => {
    // 保存用户 Token(覆盖匿名 Token)
    setToken(res.data.token);
    
    // 服务端已自动迁移匿名浏览历史
    
    return res.data.userInfo;
  });
}

// 退出登录
export function logout() {
  removeToken();
  // 下次访问公开接口时会自动获得新的匿名 Token
}

路由配置示例

php
// route/api.php
use think\facade\Route;

// 公开接口(OptionalAuth 中间件)
Route::group('api', function() {
    Route::get('properties', 'Property/index');
    Route::get('properties/:id', 'Property/read');
    Route::get('search', 'Search/index');
    Route::get('my/view-history', 'Property/viewHistory');
})->middleware(['OptionalAuth']);

// 需登录接口(Auth 中间件)
Route::group('api', function() {
    Route::post('properties', 'Property/save');
    Route::put('properties/:id', 'Property/update');
    Route::delete('properties/:id', 'Property/delete');
    Route::get('my/properties', 'Property/myProperties');
})->middleware(['Auth']);

// 管理员接口(Auth + Role 中间件)
Route::group('api/admin', function() {
    Route::get('stats', 'Admin/stats');
    Route::get('properties', 'Admin/properties');
    Route::get('users', 'Admin/users');
})->middleware(['Auth', 'Role:admin']);

// 认证接口(无需中间件)
Route::post('api/auth/email-login', 'Auth/emailLogin');
Route::post('api/auth/wx-login', 'Auth/wxLogin');

方案优势

  1. 统一架构:所有认证逻辑统一使用 JWT,代码更简洁
  2. 无状态设计:服务端无需存储 Session,易于水平扩展
  3. 自动分配:匿名用户无感知获得 Token,提升体验
  4. 数据连续性:登录后自动迁移匿名数据,无缝衔接
  5. 灵活权限:通过中间件组合实现多层次权限控制
  6. 易于维护:前端只需管理一个 Token,逻辑清晰

安全考虑

  1. Token 签名:使用 HS256 算法签名,防止伪造
  2. 过期时间
    • 匿名 Token:30 天(较长,减少重新分配)
    • 用户 Token:7 天(适中,兼顾安全和体验)
  3. HTTPS:生产环境必须使用 HTTPS 传输
  4. 密钥管理:JWT 密钥存储在环境变量中,不提交代码库
  5. Token 刷新:Token 过期后需要重新登录(简化版,后期可实现刷新机制)

文档更新日期:2025年10月29日

最后更新于:

基于 MIT 许可发布