Files
server/微信小程序会员绑定系统需求规格说明书.md
2025-11-02 19:34:16 +08:00

1372 lines
41 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微信小程序会员绑定系统需求规格说明书
## 文档信息
**项目名称:** 小智AI微信小程序会员绑定系统
**文档版本:** v1.0
**创建日期:** 2025年9月30日
**项目负责人:** 开发团队
**文档类型:** 需求规格说明书
---
## 1. 项目概述
### 1.1 项目背景
小智AI微信小程序需要建立完善的会员体系通过会员绑定系统实现用户身份验证、会员等级管理和权益分配提升用户体验和产品价值。
### 1.2 项目目标
1. **无缝对接**:实现微信小程序与会员购买系统的无缝对接
2. **等级管理**提供VIP和SVIP两档会员绑定功能
3. **权益匹配**:确保会员权益与购买等级准确匹配
4. **用户体验**:提供流畅的会员绑定和管理体验
### 1.3 项目范围
- 微信小程序会员绑定功能开发
- 会员等级管理系统
- 会员权益验证机制
- 相关数据库设计和API接口开发
---
## 2. 功能需求分析
### 2.1 核心功能模块
#### 2.1.1 用户授权登录模块
**功能描述:** 通过微信授权获取用户身份信息
**主要功能:**
- 微信授权登录
- 获取用户openid
- 用户信息存储
- 登录状态管理
**业务流程:**
```
用户点击登录 → 微信授权 → 获取code → 后端换取openid → 用户信息入库 → 登录成功
```
#### 2.1.2 会员身份验证模块
**功能描述:** 验证用户会员身份和等级
**主要功能:**
- 会员身份验证
- 会员等级识别
- 会员状态查询
- 会员信息同步
**验证逻辑:**
```
输入openid → 查询会员表 → 验证会员状态 → 返回会员等级 → 更新绑定状态
```
#### 2.1.3 会员绑定管理模块
**功能描述:** 管理用户与会员身份的绑定关系
**主要功能:**
- 会员绑定操作
- 绑定状态更新
- 绑定历史记录
- 解绑功能
#### 2.1.4 会员等级管理模块
**功能描述:** 管理不同等级会员的权益和特权
**会员等级定义:**
| 等级 | 名称 | 权益描述 | 有效期 |
|------|------|----------|--------|
| **普通用户** | 免费用户 | 基础功能使用 | 永久 |
| **VIP** | 基础会员 | 高级功能 + 优先支持 | 按购买周期 |
| **SVIP** | 高级会员 | 全功能 + 专属服务 | 按购买周期 |
### 2.2 功能优先级
| 优先级 | 功能模块 | 开发周期 |
|--------|----------|----------|
| **P0** | 用户授权登录 | 1周 |
| **P0** | 会员身份验证 | 1周 |
| **P1** | 会员绑定管理 | 1周 |
| **P1** | 会员等级管理 | 1周 |
| **P2** | 会员权益展示 | 0.5周 |
---
## 3. 技术架构设计
### 3.1 系统架构图
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 微信小程序 │ │ 后端API服务 │ │ 数据库存储 │
│ │ │ │ │ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ 会员绑定页面 │ │◄──►│ │ 会员验证API │ │◄──►│ │ 会员信息表 │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ 会员中心页面 │ │◄──►│ │ 权益查询API │ │◄──►│ │ 绑定关系表 │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │ 权益说明弹窗 │ │◄──►│ │ 绑定管理API │ │◄──►│ │ 用户信息表 │ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 3.2 技术栈选择
**前端技术栈:**
- 微信小程序原生框架
- WXML + WXSS + JavaScript
- 微信小程序API
**后端技术栈:**
- Java Spring Boot
- MyBatis Plus
- MySQL 数据库
- Redis 缓存
**第三方服务:**
- 微信开放平台API
- 微信支付API
---
## 4. 数据库设计
### 4.1 数据表结构
#### 4.1.1 会员信息表 (member_info)
```sql
CREATE TABLE `member_info` (
`member_id` varchar(64) NOT NULL COMMENT '会员ID主键',
`openid` varchar(128) NOT NULL COMMENT '微信用户openid',
`member_level` varchar(16) NOT NULL DEFAULT 'FREE' COMMENT '会员等级FREE-免费VIP-基础会员SVIP-高级会员',
`member_status` varchar(16) NOT NULL DEFAULT 'ACTIVE' COMMENT '会员状态ACTIVE-有效EXPIRED-过期SUSPENDED-暂停',
`start_time` datetime DEFAULT NULL COMMENT '会员开始时间',
`end_time` datetime DEFAULT NULL COMMENT '会员结束时间',
`purchase_order_id` varchar(64) DEFAULT NULL COMMENT '购买订单ID',
`auto_renew` tinyint DEFAULT 0 COMMENT '是否自动续费0-否1-是',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT 0 COMMENT '是否删除0-未删除1-已删除',
PRIMARY KEY (`member_id`),
UNIQUE KEY `uk_openid` (`openid`),
KEY `idx_member_level` (`member_level`),
KEY `idx_member_status` (`member_status`),
KEY `idx_end_time` (`end_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员信息表';
```
#### 4.1.2 会员绑定记录表 (member_bind_log)
```sql
CREATE TABLE `member_bind_log` (
`log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID主键',
`member_id` varchar(64) NOT NULL COMMENT '会员ID',
`openid` varchar(128) NOT NULL COMMENT '微信用户openid',
`bind_type` varchar(16) NOT NULL COMMENT '绑定类型BIND-绑定UNBIND-解绑UPGRADE-升级DOWNGRADE-降级',
`old_level` varchar(16) DEFAULT NULL COMMENT '原会员等级',
`new_level` varchar(16) NOT NULL COMMENT '新会员等级',
`bind_source` varchar(32) DEFAULT NULL COMMENT '绑定来源PURCHASE-购买GIFT-赠送ADMIN-管理员',
`related_order_id` varchar(64) DEFAULT NULL COMMENT '关联订单ID',
`bind_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '绑定时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`log_id`),
KEY `idx_member_id` (`member_id`),
KEY `idx_openid` (`openid`),
KEY `idx_bind_type` (`bind_type`),
KEY `idx_bind_time` (`bind_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员绑定记录表';
```
#### 4.1.3 会员权益配置表 (member_benefit_config)
```sql
CREATE TABLE `member_benefit_config` (
`config_id` int NOT NULL AUTO_INCREMENT COMMENT '配置ID主键',
`member_level` varchar(16) NOT NULL COMMENT '会员等级',
`benefit_code` varchar(32) NOT NULL COMMENT '权益代码',
`benefit_name` varchar(100) NOT NULL COMMENT '权益名称',
`benefit_desc` varchar(500) DEFAULT NULL COMMENT '权益描述',
`benefit_value` varchar(100) DEFAULT NULL COMMENT '权益值',
`is_enabled` tinyint DEFAULT 1 COMMENT '是否启用0-禁用1-启用',
`sort_order` int DEFAULT 0 COMMENT '排序顺序',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`config_id`),
UNIQUE KEY `uk_level_code` (`member_level`, `benefit_code`),
KEY `idx_member_level` (`member_level`),
KEY `idx_benefit_code` (`benefit_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员权益配置表';
```
### 4.2 初始化数据
```sql
-- 插入会员权益配置数据
INSERT INTO `member_benefit_config` (`member_level`, `benefit_code`, `benefit_name`, `benefit_desc`, `benefit_value`, `sort_order`) VALUES
('FREE', 'basic_chat', '基础对话', '每日基础AI对话功能', '50', 1),
('FREE', 'basic_voice', '基础语音', '每日语音识别次数', '20', 2),
('VIP', 'advanced_chat', '高级对话', '无限制AI对话功能', 'unlimited', 1),
('VIP', 'advanced_voice', '高级语音', '无限制语音识别', 'unlimited', 2),
('VIP', 'priority_support', '优先支持', '客服优先响应', 'enabled', 3),
('VIP', 'custom_model', '自定义模型', '使用自定义AI模型', 'enabled', 4),
('SVIP', 'premium_chat', '专属对话', '专属AI模型对话', 'unlimited', 1),
('SVIP', 'premium_voice', '专属语音', '高质量语音合成', 'unlimited', 2),
('SVIP', 'exclusive_support', '专属服务', '一对一专属客服', 'enabled', 3),
('SVIP', 'api_access', 'API访问', '开放API接口调用', 'enabled', 4),
('SVIP', 'data_export', '数据导出', '对话数据导出功能', 'enabled', 5);
```
---
## 5. API接口设计
### 5.1 会员验证相关接口
#### 5.1.1 用户登录接口
**接口地址:** `POST /api/member/login`
**请求参数:**
```json
{
"code": "微信授权code",
"userInfo": {
"nickName": "用户昵称",
"avatarUrl": "头像URL"
}
}
```
**响应数据:**
```json
{
"code": 200,
"message": "登录成功",
"data": {
"openid": "用户openid",
"sessionKey": "会话密钥",
"memberInfo": {
"memberId": "会员ID",
"memberLevel": "VIP",
"memberStatus": "ACTIVE",
"endTime": "2025-12-31 23:59:59"
}
}
}
```
#### 5.1.2 会员信息查询接口
**接口地址:** `GET /api/member/info`
**请求参数:**
```
Header: Authorization: Bearer {token}
```
**响应数据:**
```json
{
"code": 200,
"message": "查询成功",
"data": {
"memberId": "会员ID",
"memberLevel": "VIP",
"memberStatus": "ACTIVE",
"startTime": "2025-01-01 00:00:00",
"endTime": "2025-12-31 23:59:59",
"autoRenew": true,
"benefits": [
{
"benefitCode": "advanced_chat",
"benefitName": "高级对话",
"benefitDesc": "无限制AI对话功能",
"benefitValue": "unlimited"
}
]
}
}
```
#### 5.1.3 会员绑定接口
**接口地址:** `POST /api/member/bind`
**请求参数:**
```json
{
"orderId": "购买订单ID",
"memberLevel": "VIP",
"duration": 12,
"durationUnit": "MONTH"
}
```
**响应数据:**
```json
{
"code": 200,
"message": "绑定成功",
"data": {
"memberId": "会员ID",
"memberLevel": "VIP",
"startTime": "2025-01-01 00:00:00",
"endTime": "2025-12-31 23:59:59"
}
}
```
### 5.2 会员权益相关接口
#### 5.2.1 权益列表查询接口
**接口地址:** `GET /api/member/benefits`
**请求参数:**
```
Query: memberLevel=VIP (可选,不传则返回当前用户等级权益)
```
**响应数据:**
```json
{
"code": 200,
"message": "查询成功",
"data": {
"memberLevel": "VIP",
"benefits": [
{
"benefitCode": "advanced_chat",
"benefitName": "高级对话",
"benefitDesc": "无限制AI对话功能",
"benefitValue": "unlimited",
"isEnabled": true
}
]
}
}
```
#### 5.2.2 权益验证接口
**接口地址:** `POST /api/member/verify-benefit`
**请求参数:**
```json
{
"benefitCode": "advanced_chat",
"requestCount": 1
}
```
**响应数据:**
```json
{
"code": 200,
"message": "验证成功",
"data": {
"hasPermission": true,
"remainingCount": "unlimited",
"resetTime": null
}
}
```
---
## 6. 前端页面设计
### 6.1 页面结构
```
pages/
├── member/
│ ├── bind/
│ │ ├── bind.wxml # 会员绑定页面
│ │ ├── bind.wxss # 样式文件
│ │ ├── bind.js # 逻辑文件
│ │ └── bind.json # 配置文件
│ ├── center/
│ │ ├── center.wxml # 会员中心页面
│ │ ├── center.wxss
│ │ ├── center.js
│ │ └── center.json
│ └── benefits/
│ ├── benefits.wxml # 权益说明页面
│ ├── benefits.wxss
│ ├── benefits.js
│ └── benefits.json
└── components/
├── member-card/ # 会员卡片组件
├── benefit-item/ # 权益项组件
└── login-modal/ # 登录弹窗组件
```
### 6.2 关键页面设计
#### 6.2.1 会员绑定页面 (pages/member/bind/bind.wxml)
```xml
<view class="container">
<!-- 页面标题 -->
<view class="header">
<text class="title">会员绑定</text>
<text class="subtitle">绑定会员身份,享受专属权益</text>
</view>
<!-- 用户信息卡片 -->
<view class="user-card">
<image class="avatar" src="{{userInfo.avatarUrl}}" mode="aspectFill"></image>
<view class="user-info">
<text class="nickname">{{userInfo.nickName}}</text>
<text class="openid">ID: {{userInfo.openid}}</text>
</view>
</view>
<!-- 会员状态 -->
<view class="member-status">
<view class="status-item">
<text class="label">当前状态:</text>
<text class="value {{memberInfo.memberLevel}}">{{memberLevelText}}</text>
</view>
<view class="status-item" wx:if="{{memberInfo.endTime}}">
<text class="label">到期时间:</text>
<text class="value">{{memberInfo.endTime}}</text>
</view>
</view>
<!-- 绑定操作区域 -->
<view class="bind-section">
<view class="section-title">会员绑定</view>
<!-- 订单号输入 -->
<view class="input-group">
<text class="input-label">订单号</text>
<input class="input-field"
placeholder="请输入购买订单号"
value="{{orderId}}"
bindinput="onOrderIdInput" />
</view>
<!-- 绑定按钮 -->
<button class="bind-btn"
bindtap="onBindMember"
disabled="{{!orderId || binding}}">
{{binding ? '绑定中...' : '绑定会员'}}
</button>
</view>
<!-- 权益说明 -->
<view class="benefits-section">
<view class="section-title">会员权益</view>
<view class="benefits-list">
<view class="benefit-item" wx:for="{{benefits}}" wx:key="benefitCode">
<icon class="benefit-icon" type="success" size="16"></icon>
<text class="benefit-name">{{item.benefitName}}</text>
<text class="benefit-desc">{{item.benefitDesc}}</text>
</view>
</view>
<text class="view-more" bindtap="onViewMoreBenefits">查看详细权益说明 ></text>
</view>
</view>
```
#### 6.2.2 会员中心页面 (pages/member/center/center.wxml)
```xml
<view class="container">
<!-- 会员卡片 -->
<view class="member-card {{memberInfo.memberLevel}}">
<view class="card-header">
<image class="avatar" src="{{userInfo.avatarUrl}}" mode="aspectFill"></image>
<view class="member-info">
<text class="nickname">{{userInfo.nickName}}</text>
<view class="member-badge">
<text class="level-text">{{memberLevelText}}</text>
</view>
</view>
<view class="member-status">
<text class="status-text">{{memberStatusText}}</text>
<text class="expire-time" wx:if="{{memberInfo.endTime}}">
到期:{{memberInfo.endTime}}
</text>
</view>
</view>
<view class="card-body">
<view class="progress-section" wx:if="{{memberInfo.memberLevel !== 'FREE'}}">
<text class="progress-label">会员有效期</text>
<view class="progress-bar">
<view class="progress-fill" style="width: {{memberProgress}}%"></view>
</view>
<text class="progress-text">剩余 {{remainingDays}} 天</text>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-item" bindtap="onRenewMember">
<icon class="menu-icon" type="success" size="20"></icon>
<text class="menu-text">续费会员</text>
<icon class="arrow-icon" type="arrow" size="16"></icon>
</view>
<view class="menu-item" bindtap="onViewBenefits">
<icon class="menu-icon" type="info" size="20"></icon>
<text class="menu-text">权益说明</text>
<icon class="arrow-icon" type="arrow" size="16"></icon>
</view>
<view class="menu-item" bindtap="onViewBindLog">
<icon class="menu-icon" type="search" size="20"></icon>
<text class="menu-text">绑定记录</text>
<icon class="arrow-icon" type="arrow" size="16"></icon>
</view>
</view>
<!-- 权益快览 -->
<view class="benefits-preview">
<view class="section-title">我的权益</view>
<view class="benefits-grid">
<view class="benefit-card" wx:for="{{benefits}}" wx:key="benefitCode">
<icon class="benefit-icon" type="{{item.isEnabled ? 'success' : 'cancel'}}" size="24"></icon>
<text class="benefit-name">{{item.benefitName}}</text>
<text class="benefit-value">{{item.benefitValue}}</text>
</view>
</view>
</view>
</view>
```
### 6.3 组件设计
#### 6.3.1 会员卡片组件 (components/member-card/member-card.wxml)
```xml
<view class="member-card {{memberLevel}}">
<view class="card-background">
<image class="bg-pattern" src="/images/member-bg-{{memberLevel}}.png"></image>
</view>
<view class="card-content">
<view class="member-level">
<text class="level-text">{{levelText}}</text>
<text class="level-desc">{{levelDesc}}</text>
</view>
<view class="member-info">
<text class="member-id">会员ID: {{memberId}}</text>
<text class="valid-period" wx:if="{{endTime}}">
有效期至: {{endTime}}
</text>
</view>
</view>
</view>
```
---
## 7. 开发脚本
### 7.1 小程序端脚本
#### 7.1.1 会员绑定页面逻辑 (pages/member/bind/bind.js)
```javascript
// pages/member/bind/bind.js
const app = getApp()
const api = require('../../../utils/api')
Page({
data: {
userInfo: {},
memberInfo: {},
orderId: '',
binding: false,
benefits: [],
memberLevelText: '普通用户'
},
onLoad(options) {
this.initPage()
this.loadMemberInfo()
this.loadBenefits()
},
// 初始化页面
initPage() {
const userInfo = app.globalData.userInfo
if (!userInfo) {
wx.redirectTo({
url: '/pages/login/login'
})
return
}
this.setData({ userInfo })
},
// 加载会员信息
async loadMemberInfo() {
try {
wx.showLoading({ title: '加载中...' })
const res = await api.getMemberInfo()
if (res.code === 200) {
const memberInfo = res.data
const memberLevelText = this.getMemberLevelText(memberInfo.memberLevel)
this.setData({
memberInfo,
memberLevelText
})
}
} catch (error) {
console.error('加载会员信息失败:', error)
wx.showToast({
title: '加载失败',
icon: 'error'
})
} finally {
wx.hideLoading()
}
},
// 加载权益信息
async loadBenefits() {
try {
const res = await api.getMemberBenefits()
if (res.code === 200) {
this.setData({
benefits: res.data.benefits
})
}
} catch (error) {
console.error('加载权益信息失败:', error)
}
},
// 订单号输入
onOrderIdInput(e) {
this.setData({
orderId: e.detail.value.trim()
})
},
// 绑定会员
async onBindMember() {
const { orderId } = this.data
if (!orderId) {
wx.showToast({
title: '请输入订单号',
icon: 'error'
})
return
}
try {
this.setData({ binding: true })
wx.showLoading({ title: '绑定中...' })
const res = await api.bindMember({
orderId: orderId
})
if (res.code === 200) {
wx.showToast({
title: '绑定成功',
icon: 'success'
})
// 刷新会员信息
setTimeout(() => {
this.loadMemberInfo()
}, 1500)
// 清空订单号
this.setData({ orderId: '' })
} else {
wx.showToast({
title: res.message || '绑定失败',
icon: 'error'
})
}
} catch (error) {
console.error('绑定会员失败:', error)
wx.showToast({
title: '绑定失败',
icon: 'error'
})
} finally {
this.setData({ binding: false })
wx.hideLoading()
}
},
// 查看更多权益
onViewMoreBenefits() {
wx.navigateTo({
url: '/pages/member/benefits/benefits'
})
},
// 获取会员等级文本
getMemberLevelText(level) {
const levelMap = {
'FREE': '普通用户',
'VIP': 'VIP会员',
'SVIP': 'SVIP会员'
}
return levelMap[level] || '普通用户'
}
})
```
#### 7.1.2 API工具类 (utils/api.js)
```javascript
// utils/api.js
const config = require('./config')
class ApiService {
constructor() {
this.baseUrl = config.apiBaseUrl
this.token = wx.getStorageSync('token') || ''
}
// 通用请求方法
request(options) {
return new Promise((resolve, reject) => {
wx.request({
url: this.baseUrl + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
'Content-Type': 'application/json',
'Authorization': this.token ? `Bearer ${this.token}` : '',
...options.header
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data)
} else if (res.statusCode === 401) {
// token过期跳转登录
wx.removeStorageSync('token')
wx.redirectTo({
url: '/pages/login/login'
})
reject(new Error('登录已过期'))
} else {
reject(new Error(`请求失败: ${res.statusCode}`))
}
},
fail: (error) => {
reject(error)
}
})
})
}
// 用户登录
login(data) {
return this.request({
url: '/api/member/login',
method: 'POST',
data
})
}
// 获取会员信息
getMemberInfo() {
return this.request({
url: '/api/member/info'
})
}
// 绑定会员
bindMember(data) {
return this.request({
url: '/api/member/bind',
method: 'POST',
data
})
}
// 获取会员权益
getMemberBenefits(memberLevel) {
const url = memberLevel
? `/api/member/benefits?memberLevel=${memberLevel}`
: '/api/member/benefits'
return this.request({
url
})
}
// 验证权益
verifyBenefit(data) {
return this.request({
url: '/api/member/verify-benefit',
method: 'POST',
data
})
}
// 更新token
setToken(token) {
this.token = token
wx.setStorageSync('token', token)
}
}
module.exports = new ApiService()
```
### 7.2 服务端脚本
#### 7.2.1 会员服务类 (MemberService.java)
```java
package com.xiaozhi.plus.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaozhi.plus.entity.MemberInfo;
import com.xiaozhi.plus.entity.MemberBindLog;
import com.xiaozhi.plus.entity.MemberBenefitConfig;
import com.xiaozhi.plus.mapper.MemberInfoMapper;
import com.xiaozhi.plus.mapper.MemberBindLogMapper;
import com.xiaozhi.plus.mapper.MemberBenefitConfigMapper;
import com.xiaozhi.plus.service.MemberService;
import com.xiaozhi.plus.dto.MemberBindRequest;
import com.xiaozhi.plus.dto.MemberInfoResponse;
import com.xiaozhi.plus.utils.IdGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class MemberServiceImpl extends ServiceImpl<MemberInfoMapper, MemberInfo> implements MemberService {
@Autowired
private MemberInfoMapper memberInfoMapper;
@Autowired
private MemberBindLogMapper memberBindLogMapper;
@Autowired
private MemberBenefitConfigMapper memberBenefitConfigMapper;
@Override
public MemberInfoResponse getMemberInfo(String openid) {
MemberInfo memberInfo = memberInfoMapper.selectOne(
new QueryWrapper<MemberInfo>()
.eq("openid", openid)
.eq("deleted", 0)
);
if (memberInfo == null) {
// 创建免费用户
memberInfo = createFreeMember(openid);
}
// 检查会员是否过期
checkMemberExpiry(memberInfo);
// 获取会员权益
List<MemberBenefitConfig> benefits = getMemberBenefits(memberInfo.getMemberLevel());
return MemberInfoResponse.builder()
.memberId(memberInfo.getMemberId())
.memberLevel(memberInfo.getMemberLevel())
.memberStatus(memberInfo.getMemberStatus())
.startTime(memberInfo.getStartTime())
.endTime(memberInfo.getEndTime())
.autoRenew(memberInfo.getAutoRenew())
.benefits(benefits)
.build();
}
@Override
@Transactional
public boolean bindMember(String openid, MemberBindRequest request) {
// 验证订单有效性
if (!validateOrder(request.getOrderId())) {
throw new RuntimeException("订单验证失败");
}
// 获取或创建会员信息
MemberInfo memberInfo = memberInfoMapper.selectOne(
new QueryWrapper<MemberInfo>()
.eq("openid", openid)
.eq("deleted", 0)
);
String oldLevel = null;
if (memberInfo == null) {
memberInfo = new MemberInfo();
memberInfo.setMemberId(IdGenerator.generateMemberId());
memberInfo.setOpenid(openid);
memberInfo.setMemberLevel("FREE");
memberInfo.setMemberStatus("ACTIVE");
memberInfo.setCreateTime(LocalDateTime.now());
} else {
oldLevel = memberInfo.getMemberLevel();
}
// 更新会员信息
memberInfo.setMemberLevel(request.getMemberLevel());
memberInfo.setMemberStatus("ACTIVE");
memberInfo.setStartTime(LocalDateTime.now());
memberInfo.setEndTime(calculateEndTime(request.getDuration(), request.getDurationUnit()));
memberInfo.setPurchaseOrderId(request.getOrderId());
memberInfo.setUpdateTime(LocalDateTime.now());
// 保存会员信息
if (oldLevel == null) {
memberInfoMapper.insert(memberInfo);
} else {
memberInfoMapper.updateById(memberInfo);
}
// 记录绑定日志
recordBindLog(memberInfo, oldLevel, "PURCHASE", request.getOrderId());
return true;
}
@Override
public List<MemberBenefitConfig> getMemberBenefits(String memberLevel) {
return memberBenefitConfigMapper.selectList(
new QueryWrapper<MemberBenefitConfig>()
.eq("member_level", memberLevel)
.eq("is_enabled", 1)
.orderByAsc("sort_order")
);
}
@Override
public boolean verifyBenefit(String openid, String benefitCode, Integer requestCount) {
MemberInfo memberInfo = memberInfoMapper.selectOne(
new QueryWrapper<MemberInfo>()
.eq("openid", openid)
.eq("deleted", 0)
);
if (memberInfo == null) {
return false;
}
// 检查会员状态
if (!"ACTIVE".equals(memberInfo.getMemberStatus())) {
return false;
}
// 检查是否过期
if (memberInfo.getEndTime() != null && memberInfo.getEndTime().isBefore(LocalDateTime.now())) {
return false;
}
// 检查权益配置
MemberBenefitConfig benefitConfig = memberBenefitConfigMapper.selectOne(
new QueryWrapper<MemberBenefitConfig>()
.eq("member_level", memberInfo.getMemberLevel())
.eq("benefit_code", benefitCode)
.eq("is_enabled", 1)
);
return benefitConfig != null;
}
private MemberInfo createFreeMember(String openid) {
MemberInfo memberInfo = new MemberInfo();
memberInfo.setMemberId(IdGenerator.generateMemberId());
memberInfo.setOpenid(openid);
memberInfo.setMemberLevel("FREE");
memberInfo.setMemberStatus("ACTIVE");
memberInfo.setCreateTime(LocalDateTime.now());
memberInfoMapper.insert(memberInfo);
return memberInfo;
}
private void checkMemberExpiry(MemberInfo memberInfo) {
if (memberInfo.getEndTime() != null &&
memberInfo.getEndTime().isBefore(LocalDateTime.now()) &&
!"EXPIRED".equals(memberInfo.getMemberStatus())) {
memberInfo.setMemberStatus("EXPIRED");
memberInfoMapper.updateById(memberInfo);
}
}
private LocalDateTime calculateEndTime(Integer duration, String durationUnit) {
LocalDateTime now = LocalDateTime.now();
switch (durationUnit.toUpperCase()) {
case "DAY":
return now.plusDays(duration);
case "MONTH":
return now.plusMonths(duration);
case "YEAR":
return now.plusYears(duration);
default:
throw new IllegalArgumentException("不支持的时间单位: " + durationUnit);
}
}
private void recordBindLog(MemberInfo memberInfo, String oldLevel, String bindSource, String orderId) {
MemberBindLog bindLog = new MemberBindLog();
bindLog.setMemberId(memberInfo.getMemberId());
bindLog.setOpenid(memberInfo.getOpenid());
bindLog.setBindType(oldLevel == null ? "BIND" : "UPGRADE");
bindLog.setOldLevel(oldLevel);
bindLog.setNewLevel(memberInfo.getMemberLevel());
bindLog.setBindSource(bindSource);
bindLog.setRelatedOrderId(orderId);
bindLog.setBindTime(LocalDateTime.now());
memberBindLogMapper.insert(bindLog);
}
private boolean validateOrder(String orderId) {
// TODO: 实现订单验证逻辑
// 1. 检查订单是否存在
// 2. 检查订单是否已支付
// 3. 检查订单是否已绑定
return true;
}
}
```
#### 7.2.2 会员控制器 (MemberController.java)
```java
package com.xiaozhi.plus.controller;
import com.xiaozhi.plus.service.MemberService;
import com.xiaozhi.plus.dto.MemberBindRequest;
import com.xiaozhi.plus.dto.MemberInfoResponse;
import com.xiaozhi.plus.dto.BenefitVerifyRequest;
import com.xiaozhi.plus.entity.MemberBenefitConfig;
import com.xiaozhi.plus.utils.Result;
import com.xiaozhi.plus.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/member")
public class MemberController {
@Autowired
private MemberService memberService;
@Autowired
private JwtUtil jwtUtil;
/**
* 获取会员信息
*/
@GetMapping("/info")
public Result<MemberInfoResponse> getMemberInfo(HttpServletRequest request) {
try {
String openid = getOpenidFromToken(request);
MemberInfoResponse memberInfo = memberService.getMemberInfo(openid);
return Result.success(memberInfo);
} catch (Exception e) {
return Result.error("获取会员信息失败: " + e.getMessage());
}
}
/**
* 绑定会员
*/
@PostMapping("/bind")
public Result<String> bindMember(@RequestBody MemberBindRequest request, HttpServletRequest httpRequest) {
try {
String openid = getOpenidFromToken(httpRequest);
boolean success = memberService.bindMember(openid, request);
if (success) {
return Result.success("绑定成功");
} else {
return Result.error("绑定失败");
}
} catch (Exception e) {
return Result.error("绑定失败: " + e.getMessage());
}
}
/**
* 获取会员权益
*/
@GetMapping("/benefits")
public Result<Map<String, Object>> getMemberBenefits(
@RequestParam(required = false) String memberLevel,
HttpServletRequest request) {
try {
String targetLevel = memberLevel;
if (targetLevel == null) {
String openid = getOpenidFromToken(request);
MemberInfoResponse memberInfo = memberService.getMemberInfo(openid);
targetLevel = memberInfo.getMemberLevel();
}
List<MemberBenefitConfig> benefits = memberService.getMemberBenefits(targetLevel);
Map<String, Object> result = Map.of(
"memberLevel", targetLevel,
"benefits", benefits
);
return Result.success(result);
} catch (Exception e) {
return Result.error("获取权益信息失败: " + e.getMessage());
}
}
/**
* 验证权益
*/
@PostMapping("/verify-benefit")
public Result<Map<String, Object>> verifyBenefit(
@RequestBody BenefitVerifyRequest request,
HttpServletRequest httpRequest) {
try {
String openid = getOpenidFromToken(httpRequest);
boolean hasPermission = memberService.verifyBenefit(
openid,
request.getBenefitCode(),
request.getRequestCount()
);
Map<String, Object> result = Map.of(
"hasPermission", hasPermission,
"remainingCount", hasPermission ? "unlimited" : "0",
"resetTime", null
);
return Result.success(result);
} catch (Exception e) {
return Result.error("权益验证失败: " + e.getMessage());
}
}
private String getOpenidFromToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
return jwtUtil.getOpenidFromToken(token);
}
throw new RuntimeException("无效的token");
}
}
```
---
## 8. 测试用例设计
### 8.1 功能测试用例
#### 8.1.1 用户登录测试
| 测试用例ID | 测试场景 | 测试步骤 | 预期结果 |
|------------|----------|----------|----------|
| TC_LOGIN_001 | 正常登录 | 1. 点击登录按钮<br>2. 微信授权成功<br>3. 获取用户信息 | 登录成功获取到openid和用户信息 |
| TC_LOGIN_002 | 取消授权 | 1. 点击登录按钮<br>2. 取消微信授权 | 登录失败,提示授权失败 |
| TC_LOGIN_003 | 网络异常 | 1. 断网状态下登录<br>2. 点击登录按钮 | 登录失败,提示网络异常 |
#### 8.1.2 会员绑定测试
| 测试用例ID | 测试场景 | 测试步骤 | 预期结果 |
|------------|----------|----------|----------|
| TC_BIND_001 | 正常绑定VIP | 1. 输入有效VIP订单号<br>2. 点击绑定按钮 | 绑定成功会员等级变为VIP |
| TC_BIND_002 | 正常绑定SVIP | 1. 输入有效SVIP订单号<br>2. 点击绑定按钮 | 绑定成功会员等级变为SVIP |
| TC_BIND_003 | 无效订单号 | 1. 输入无效订单号<br>2. 点击绑定按钮 | 绑定失败,提示订单无效 |
| TC_BIND_004 | 空订单号 | 1. 不输入订单号<br>2. 点击绑定按钮 | 提示请输入订单号 |
| TC_BIND_005 | 重复绑定 | 1. 输入已绑定的订单号<br>2. 点击绑定按钮 | 绑定失败,提示订单已使用 |
#### 8.1.3 权益验证测试
| 测试用例ID | 测试场景 | 测试步骤 | 预期结果 |
|------------|----------|----------|----------|
| TC_BENEFIT_001 | VIP权益验证 | 1. VIP用户访问高级功能<br>2. 系统验证权益 | 验证通过,允许访问 |
| TC_BENEFIT_002 | 免费用户权益验证 | 1. 免费用户访问VIP功能<br>2. 系统验证权益 | 验证失败,提示升级会员 |
| TC_BENEFIT_003 | 过期会员权益验证 | 1. 过期VIP用户访问VIP功能<br>2. 系统验证权益 | 验证失败,提示续费 |
### 8.2 接口测试用例
#### 8.2.1 会员信息查询接口测试
```javascript
// 测试脚本示例
describe('会员信息查询接口测试', () => {
test('正常查询会员信息', async () => {
const response = await request(app)
.get('/api/member/info')
.set('Authorization', 'Bearer valid_token')
.expect(200);
expect(response.body.code).toBe(200);
expect(response.body.data).toHaveProperty('memberId');
expect(response.body.data).toHaveProperty('memberLevel');
});
test('无效token查询', async () => {
const response = await request(app)
.get('/api/member/info')
.set('Authorization', 'Bearer invalid_token')
.expect(401);
expect(response.body.code).toBe(401);
});
});
```
### 8.3 性能测试用例
| 测试项目 | 测试指标 | 预期值 | 测试方法 |
|----------|----------|--------|----------|
| 接口响应时间 | 平均响应时间 | <500ms | JMeter压力测试 |
| 并发处理能力 | 并发用户数 | 1000+ | 并发访问测试 |
| 数据库查询性能 | 查询响应时间 | <100ms | SQL性能测试 |
---
## 9. 部署和运维
### 9.1 部署环境要求
**服务器配置:**
- CPU: 4核心以上
- 内存: 8GB以上
- 存储: 100GB以上SSD
- 网络: 100Mbps以上带宽
**软件环境:**
- 操作系统: CentOS 7.6+
- Java: JDK 1.8+
- 数据库: MySQL 8.0+
- 缓存: Redis 6.0+
- Web服务器: Nginx 1.18+
### 9.2 部署步骤
1. **数据库初始化**
```bash
mysql -u root -p < db/member_tables.sql
```
2. **应用部署**
```bash
# 构建应用
mvn clean package -Dmaven.test.skip=true
# 部署应用
java -jar xiaozhi-server.jar --spring.profiles.active=prod
```
3. **Nginx配置**
```nginx
server {
listen 443 ssl;
server_name api.xiaozhi.com;
location /api/member/ {
proxy_pass http://localhost:8091;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
### 9.3 监控和日志
**监控指标:**
- 接口响应时间
- 错误率统计
- 数据库连接数
- 内存使用率
**日志配置:**
```yaml
logging:
level:
com.xiaozhi.plus.service.MemberService: INFO
file:
name: /var/log/xiaozhi/member.log
max-size: 100MB
max-history: 30
```
---
## 10. 风险评估和应对策略
### 10.1 技术风险
| 风险项目 | 风险等级 | 影响描述 | 应对策略 |
|----------|----------|----------|----------|
| 微信API变更 | 中 | 接口不兼容 | 定期检查API文档及时适配 |
| 数据库性能 | 中 | 查询缓慢 | 优化索引,使用缓存 |
| 并发安全 | 高 | 数据不一致 | 使用分布式锁,事务控制 |
### 10.2 业务风险
| 风险项目 | 风险等级 | 影响描述 | 应对策略 |
|----------|----------|----------|----------|
| 恶意绑定 | 高 | 订单被盗用 | 增强订单验证,限制绑定频率 |
| 权益滥用 | 中 | 超出预期使用 | 设置使用限制,监控异常 |
| 数据泄露 | 高 | 用户信息泄露 | 数据加密,访问控制 |
---
## 11. 项目计划和里程碑
### 11.1 开发计划
| 阶段 | 任务 | 工期 | 负责人 |
|------|------|------|--------|
| **第1周** | 数据库设计和API接口开发 | 5天 | 后端开发 |
| **第2周** | 小程序页面和组件开发 | 5天 | 前端开发 |
| **第3周** | 功能联调和测试 | 5天 | 全员 |
| **第4周** | 部署上线和优化 | 5天 | 运维+开发 |
### 11.2 里程碑
- **M1 (第1周末)**: 后端API开发完成
- **M2 (第2周末)**: 前端页面开发完成
- **M3 (第3周末)**: 功能测试通过
- **M4 (第4周末)**: 正式上线发布
---
## 12. 附录
### 12.1 相关文档
- [微信小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/)
- [微信支付API文档](https://pay.weixin.qq.com/wiki/doc/api/index.html)
- [Spring Boot官方文档](https://spring.io/projects/spring-boot)
### 12.2 联系方式
- **项目经理**: [姓名] - [邮箱] - [电话]
- **技术负责人**: [姓名] - [邮箱] - [电话]
- **测试负责人**: [姓名] - [邮箱] - [电话]
---
**文档结束**
> 📋 本需求规格说明书为微信小程序会员绑定系统的完整技术方案,包含了从需求分析到部署上线的全流程内容。请各相关人员严格按照本文档执行开发和测试工作。