# 微信小程序会员绑定系统需求规格说明书 ## 文档信息 **项目名称:** 小智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 会员绑定 绑定会员身份,享受专属权益 当前状态: {{memberLevelText}} 到期时间: {{memberInfo.endTime}} 会员绑定 订单号 会员权益 {{item.benefitName}} {{item.benefitDesc}} 查看详细权益说明 > ``` #### 6.2.2 会员中心页面 (pages/member/center/center.wxml) ```xml {{userInfo.nickName}} {{memberLevelText}} {{memberStatusText}} 到期:{{memberInfo.endTime}} 会员有效期 剩余 {{remainingDays}} 天 续费会员 权益说明 绑定记录 我的权益 {{item.benefitName}} {{item.benefitValue}} ``` ### 6.3 组件设计 #### 6.3.1 会员卡片组件 (components/member-card/member-card.wxml) ```xml {{levelText}} {{levelDesc}} 会员ID: {{memberId}} 有效期至: {{endTime}} ``` --- ## 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 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() .eq("openid", openid) .eq("deleted", 0) ); if (memberInfo == null) { // 创建免费用户 memberInfo = createFreeMember(openid); } // 检查会员是否过期 checkMemberExpiry(memberInfo); // 获取会员权益 List 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() .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 getMemberBenefits(String memberLevel) { return memberBenefitConfigMapper.selectList( new QueryWrapper() .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() .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() .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 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 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> 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 benefits = memberService.getMemberBenefits(targetLevel); Map result = Map.of( "memberLevel", targetLevel, "benefits", benefits ); return Result.success(result); } catch (Exception e) { return Result.error("获取权益信息失败: " + e.getMessage()); } } /** * 验证权益 */ @PostMapping("/verify-benefit") public Result> verifyBenefit( @RequestBody BenefitVerifyRequest request, HttpServletRequest httpRequest) { try { String openid = getOpenidFromToken(httpRequest); boolean hasPermission = memberService.verifyBenefit( openid, request.getBenefitCode(), request.getRequestCount() ); Map 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. 点击登录按钮
2. 微信授权成功
3. 获取用户信息 | 登录成功,获取到openid和用户信息 | | TC_LOGIN_002 | 取消授权 | 1. 点击登录按钮
2. 取消微信授权 | 登录失败,提示授权失败 | | TC_LOGIN_003 | 网络异常 | 1. 断网状态下登录
2. 点击登录按钮 | 登录失败,提示网络异常 | #### 8.1.2 会员绑定测试 | 测试用例ID | 测试场景 | 测试步骤 | 预期结果 | |------------|----------|----------|----------| | TC_BIND_001 | 正常绑定VIP | 1. 输入有效VIP订单号
2. 点击绑定按钮 | 绑定成功,会员等级变为VIP | | TC_BIND_002 | 正常绑定SVIP | 1. 输入有效SVIP订单号
2. 点击绑定按钮 | 绑定成功,会员等级变为SVIP | | TC_BIND_003 | 无效订单号 | 1. 输入无效订单号
2. 点击绑定按钮 | 绑定失败,提示订单无效 | | TC_BIND_004 | 空订单号 | 1. 不输入订单号
2. 点击绑定按钮 | 提示请输入订单号 | | TC_BIND_005 | 重复绑定 | 1. 输入已绑定的订单号
2. 点击绑定按钮 | 绑定失败,提示订单已使用 | #### 8.1.3 权益验证测试 | 测试用例ID | 测试场景 | 测试步骤 | 预期结果 | |------------|----------|----------|----------| | TC_BENEFIT_001 | VIP权益验证 | 1. VIP用户访问高级功能
2. 系统验证权益 | 验证通过,允许访问 | | TC_BENEFIT_002 | 免费用户权益验证 | 1. 免费用户访问VIP功能
2. 系统验证权益 | 验证失败,提示升级会员 | | TC_BENEFIT_003 | 过期会员权益验证 | 1. 过期VIP用户访问VIP功能
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 联系方式 - **项目经理**: [姓名] - [邮箱] - [电话] - **技术负责人**: [姓名] - [邮箱] - [电话] - **测试负责人**: [姓名] - [邮箱] - [电话] --- **文档结束** > 📋 本需求规格说明书为微信小程序会员绑定系统的完整技术方案,包含了从需求分析到部署上线的全流程内容。请各相关人员严格按照本文档执行开发和测试工作。