1655 lines
35 KiB
Markdown
1655 lines
35 KiB
Markdown
|
|
# 解班客小程序架构文档
|
|||
|
|
|
|||
|
|
## 1. 项目概述
|
|||
|
|
|
|||
|
|
### 1.1 项目简介
|
|||
|
|
解班客小程序是一个基于微信生态的社交旅行平台,融合了结伴旅行、动物认领、商家服务等核心功能。采用微信小程序原生开发框架,提供流畅的用户体验和丰富的社交功能。
|
|||
|
|
|
|||
|
|
### 1.2 业务目标
|
|||
|
|
- **社交旅行**:为用户提供结伴旅行的平台,增强旅行体验
|
|||
|
|
- **动物认领**:创新的动物认领功能,增加用户粘性
|
|||
|
|
- **商家服务**:为商家提供服务展示和预订平台
|
|||
|
|
- **用户增长**:通过微信生态实现用户快速增长
|
|||
|
|
|
|||
|
|
### 1.3 技术目标
|
|||
|
|
- **性能优化**:快速加载,流畅交互
|
|||
|
|
- **用户体验**:符合微信设计规范,操作简单直观
|
|||
|
|
- **功能完整**:覆盖核心业务场景
|
|||
|
|
- **扩展性强**:支持功能快速迭代和扩展
|
|||
|
|
|
|||
|
|
## 2. 技术选型
|
|||
|
|
|
|||
|
|
### 2.1 开发框架
|
|||
|
|
|
|||
|
|
#### 2.1.1 微信小程序原生框架
|
|||
|
|
```javascript
|
|||
|
|
// 选型理由
|
|||
|
|
{
|
|||
|
|
"框架": "微信小程序原生",
|
|||
|
|
"版本": "最新稳定版",
|
|||
|
|
"优势": [
|
|||
|
|
"官方支持,稳定性高",
|
|||
|
|
"性能最优,启动速度快",
|
|||
|
|
"API完整,功能丰富",
|
|||
|
|
"调试工具完善"
|
|||
|
|
],
|
|||
|
|
"适用场景": [
|
|||
|
|
"复杂业务逻辑",
|
|||
|
|
"高性能要求",
|
|||
|
|
"深度集成微信能力"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.1.2 状态管理
|
|||
|
|
```javascript
|
|||
|
|
// Mobx-miniprogram
|
|||
|
|
{
|
|||
|
|
"库": "mobx-miniprogram",
|
|||
|
|
"版本": "^4.13.2",
|
|||
|
|
"优势": [
|
|||
|
|
"响应式状态管理",
|
|||
|
|
"简单易用",
|
|||
|
|
"性能优秀",
|
|||
|
|
"支持计算属性"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 UI组件库
|
|||
|
|
|
|||
|
|
#### 2.2.1 Vant Weapp
|
|||
|
|
```javascript
|
|||
|
|
{
|
|||
|
|
"组件库": "Vant Weapp",
|
|||
|
|
"版本": "^1.11.2",
|
|||
|
|
"优势": [
|
|||
|
|
"组件丰富",
|
|||
|
|
"设计规范",
|
|||
|
|
"文档完善",
|
|||
|
|
"社区活跃"
|
|||
|
|
],
|
|||
|
|
"使用组件": [
|
|||
|
|
"Button", "Cell", "Form",
|
|||
|
|
"Popup", "Dialog", "Toast",
|
|||
|
|
"Tab", "NavBar", "Search"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 工具库
|
|||
|
|
|
|||
|
|
#### 2.3.1 网络请求
|
|||
|
|
```javascript
|
|||
|
|
// 自定义HTTP库
|
|||
|
|
{
|
|||
|
|
"库": "自研HTTP库",
|
|||
|
|
"特性": [
|
|||
|
|
"请求拦截器",
|
|||
|
|
"响应拦截器",
|
|||
|
|
"错误处理",
|
|||
|
|
"Loading管理",
|
|||
|
|
"Token自动刷新"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2.3.2 工具函数
|
|||
|
|
```javascript
|
|||
|
|
{
|
|||
|
|
"日期处理": "dayjs",
|
|||
|
|
"数据验证": "async-validator",
|
|||
|
|
"图片处理": "自研工具",
|
|||
|
|
"地理位置": "微信API",
|
|||
|
|
"支付": "微信支付API"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 架构设计
|
|||
|
|
|
|||
|
|
### 3.1 整体架构
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "小程序架构"
|
|||
|
|
A[用户界面层 UI Layer]
|
|||
|
|
B[业务逻辑层 Business Layer]
|
|||
|
|
C[数据管理层 Data Layer]
|
|||
|
|
D[服务层 Service Layer]
|
|||
|
|
E[工具层 Utils Layer]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "外部服务"
|
|||
|
|
F[后端API]
|
|||
|
|
G[微信API]
|
|||
|
|
H[第三方服务]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
A --> B
|
|||
|
|
B --> C
|
|||
|
|
B --> D
|
|||
|
|
D --> F
|
|||
|
|
D --> G
|
|||
|
|
D --> H
|
|||
|
|
B --> E
|
|||
|
|
C --> E
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 分层架构详解
|
|||
|
|
|
|||
|
|
#### 3.2.1 用户界面层 (UI Layer)
|
|||
|
|
```javascript
|
|||
|
|
// 页面组件结构
|
|||
|
|
pages/
|
|||
|
|
├── index/ // 首页
|
|||
|
|
├── travel/ // 结伴旅行
|
|||
|
|
│ ├── list/ // 旅行列表
|
|||
|
|
│ ├── detail/ // 旅行详情
|
|||
|
|
│ └── create/ // 创建旅行
|
|||
|
|
├── animal/ // 动物认领
|
|||
|
|
│ ├── list/ // 动物列表
|
|||
|
|
│ ├── detail/ // 动物详情
|
|||
|
|
│ └── adopt/ // 认领页面
|
|||
|
|
├── merchant/ // 商家服务
|
|||
|
|
├── user/ // 用户中心
|
|||
|
|
└── common/ // 通用页面
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.2 业务逻辑层 (Business Layer)
|
|||
|
|
```javascript
|
|||
|
|
// 业务模块结构
|
|||
|
|
business/
|
|||
|
|
├── user/ // 用户业务
|
|||
|
|
│ ├── auth.js // 认证逻辑
|
|||
|
|
│ ├── profile.js // 用户资料
|
|||
|
|
│ └── settings.js // 用户设置
|
|||
|
|
├── travel/ // 旅行业务
|
|||
|
|
│ ├── list.js // 列表逻辑
|
|||
|
|
│ ├── detail.js // 详情逻辑
|
|||
|
|
│ └── booking.js // 预订逻辑
|
|||
|
|
├── animal/ // 动物业务
|
|||
|
|
└── merchant/ // 商家业务
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.3 数据管理层 (Data Layer)
|
|||
|
|
```javascript
|
|||
|
|
// 状态管理结构
|
|||
|
|
store/
|
|||
|
|
├── index.js // Store入口
|
|||
|
|
├── user.js // 用户状态
|
|||
|
|
├── travel.js // 旅行状态
|
|||
|
|
├── animal.js // 动物状态
|
|||
|
|
└── common.js // 通用状态
|
|||
|
|
|
|||
|
|
// 本地存储管理
|
|||
|
|
storage/
|
|||
|
|
├── index.js // 存储管理器
|
|||
|
|
├── user.js // 用户数据
|
|||
|
|
├── cache.js // 缓存管理
|
|||
|
|
└── config.js // 配置数据
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.4 服务层 (Service Layer)
|
|||
|
|
```javascript
|
|||
|
|
// API服务结构
|
|||
|
|
services/
|
|||
|
|
├── http.js // HTTP客户端
|
|||
|
|
├── user.js // 用户API
|
|||
|
|
├── travel.js // 旅行API
|
|||
|
|
├── animal.js // 动物API
|
|||
|
|
├── merchant.js // 商家API
|
|||
|
|
├── payment.js // 支付API
|
|||
|
|
└── upload.js // 文件上传
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3.2.5 工具层 (Utils Layer)
|
|||
|
|
```javascript
|
|||
|
|
// 工具函数结构
|
|||
|
|
utils/
|
|||
|
|
├── index.js // 工具入口
|
|||
|
|
├── date.js // 日期工具
|
|||
|
|
├── format.js // 格式化工具
|
|||
|
|
├── validate.js // 验证工具
|
|||
|
|
├── location.js // 位置工具
|
|||
|
|
├── image.js // 图片工具
|
|||
|
|
└── wechat.js // 微信API封装
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 核心模块设计
|
|||
|
|
|
|||
|
|
### 4.1 用户模块
|
|||
|
|
|
|||
|
|
#### 4.1.1 用户认证
|
|||
|
|
```javascript
|
|||
|
|
// 用户认证流程
|
|||
|
|
class AuthService {
|
|||
|
|
// 微信登录
|
|||
|
|
async wxLogin() {
|
|||
|
|
try {
|
|||
|
|
// 1. 获取微信授权码
|
|||
|
|
const { code } = await wx.login();
|
|||
|
|
|
|||
|
|
// 2. 获取用户信息
|
|||
|
|
const userInfo = await this.getUserProfile();
|
|||
|
|
|
|||
|
|
// 3. 后端验证登录
|
|||
|
|
const result = await api.user.login({
|
|||
|
|
code,
|
|||
|
|
userInfo
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 4. 保存用户信息
|
|||
|
|
await this.saveUserInfo(result);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error('登录失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户资料
|
|||
|
|
async getUserProfile() {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
wx.getUserProfile({
|
|||
|
|
desc: '用于完善用户资料',
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.1.2 用户状态管理
|
|||
|
|
```javascript
|
|||
|
|
// 用户Store
|
|||
|
|
import { observable, action, computed } from 'mobx-miniprogram';
|
|||
|
|
|
|||
|
|
export const userStore = observable({
|
|||
|
|
// 用户信息
|
|||
|
|
userInfo: null,
|
|||
|
|
token: '',
|
|||
|
|
isLogin: false,
|
|||
|
|
|
|||
|
|
// 用户设置
|
|||
|
|
settings: {
|
|||
|
|
notifications: true,
|
|||
|
|
location: true,
|
|||
|
|
privacy: 'public'
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 计算属性
|
|||
|
|
get isVip() {
|
|||
|
|
return this.userInfo?.vipLevel > 0;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 动作
|
|||
|
|
setUserInfo: action(function(userInfo) {
|
|||
|
|
this.userInfo = userInfo;
|
|||
|
|
this.isLogin = true;
|
|||
|
|
}),
|
|||
|
|
|
|||
|
|
setToken: action(function(token) {
|
|||
|
|
this.token = token;
|
|||
|
|
}),
|
|||
|
|
|
|||
|
|
logout: action(function() {
|
|||
|
|
this.userInfo = null;
|
|||
|
|
this.token = '';
|
|||
|
|
this.isLogin = false;
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 旅行模块
|
|||
|
|
|
|||
|
|
#### 4.2.1 旅行列表
|
|||
|
|
```javascript
|
|||
|
|
// 旅行列表组件
|
|||
|
|
Component({
|
|||
|
|
data: {
|
|||
|
|
travelList: [],
|
|||
|
|
loading: false,
|
|||
|
|
hasMore: true,
|
|||
|
|
page: 1,
|
|||
|
|
filters: {
|
|||
|
|
city: '',
|
|||
|
|
date: '',
|
|||
|
|
type: ''
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
lifetimes: {
|
|||
|
|
attached() {
|
|||
|
|
this.loadTravelList();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
// 加载旅行列表
|
|||
|
|
async loadTravelList(refresh = false) {
|
|||
|
|
if (this.data.loading) return;
|
|||
|
|
|
|||
|
|
this.setData({ loading: true });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const page = refresh ? 1 : this.data.page;
|
|||
|
|
const result = await api.travel.getList({
|
|||
|
|
page,
|
|||
|
|
...this.data.filters
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const travelList = refresh
|
|||
|
|
? result.list
|
|||
|
|
: [...this.data.travelList, ...result.list];
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
travelList,
|
|||
|
|
hasMore: result.hasMore,
|
|||
|
|
page: page + 1,
|
|||
|
|
loading: false
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
this.setData({ loading: false });
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '加载失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 筛选
|
|||
|
|
onFilter(e) {
|
|||
|
|
const filters = e.detail;
|
|||
|
|
this.setData({ filters });
|
|||
|
|
this.loadTravelList(true);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 下拉刷新
|
|||
|
|
onRefresh() {
|
|||
|
|
this.loadTravelList(true);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 上拉加载
|
|||
|
|
onLoadMore() {
|
|||
|
|
if (this.data.hasMore) {
|
|||
|
|
this.loadTravelList();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 4.2.2 旅行详情
|
|||
|
|
```javascript
|
|||
|
|
// 旅行详情页面
|
|||
|
|
Page({
|
|||
|
|
data: {
|
|||
|
|
travelId: '',
|
|||
|
|
travelDetail: null,
|
|||
|
|
loading: true,
|
|||
|
|
joined: false,
|
|||
|
|
participants: []
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
onLoad(options) {
|
|||
|
|
this.setData({ travelId: options.id });
|
|||
|
|
this.loadTravelDetail();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 加载旅行详情
|
|||
|
|
async loadTravelDetail() {
|
|||
|
|
try {
|
|||
|
|
const result = await api.travel.getDetail(this.data.travelId);
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
travelDetail: result.travel,
|
|||
|
|
participants: result.participants,
|
|||
|
|
joined: result.joined,
|
|||
|
|
loading: false
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
this.setData({ loading: false });
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '加载失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 加入旅行
|
|||
|
|
async joinTravel() {
|
|||
|
|
try {
|
|||
|
|
await api.travel.join(this.data.travelId);
|
|||
|
|
|
|||
|
|
this.setData({ joined: true });
|
|||
|
|
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '加入成功',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 刷新参与者列表
|
|||
|
|
this.loadTravelDetail();
|
|||
|
|
} catch (error) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: error.message || '加入失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 动物认领模块
|
|||
|
|
|
|||
|
|
#### 4.3.1 动物列表
|
|||
|
|
```javascript
|
|||
|
|
// 动物列表组件
|
|||
|
|
Component({
|
|||
|
|
data: {
|
|||
|
|
animalList: [],
|
|||
|
|
categories: [],
|
|||
|
|
selectedCategory: '',
|
|||
|
|
loading: false
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
lifetimes: {
|
|||
|
|
attached() {
|
|||
|
|
this.loadCategories();
|
|||
|
|
this.loadAnimalList();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
// 加载动物分类
|
|||
|
|
async loadCategories() {
|
|||
|
|
try {
|
|||
|
|
const categories = await api.animal.getCategories();
|
|||
|
|
this.setData({ categories });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('加载分类失败', error);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 加载动物列表
|
|||
|
|
async loadAnimalList() {
|
|||
|
|
this.setData({ loading: true });
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await api.animal.getList({
|
|||
|
|
category: this.data.selectedCategory
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
animalList: result.list,
|
|||
|
|
loading: false
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
this.setData({ loading: false });
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '加载失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 切换分类
|
|||
|
|
onCategoryChange(e) {
|
|||
|
|
const category = e.detail;
|
|||
|
|
this.setData({ selectedCategory: category });
|
|||
|
|
this.loadAnimalList();
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 认领动物
|
|||
|
|
async adoptAnimal(e) {
|
|||
|
|
const animalId = e.currentTarget.dataset.id;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await api.animal.adopt(animalId);
|
|||
|
|
|
|||
|
|
wx.showToast({
|
|||
|
|
title: '认领成功',
|
|||
|
|
icon: 'success'
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 刷新列表
|
|||
|
|
this.loadAnimalList();
|
|||
|
|
} catch (error) {
|
|||
|
|
wx.showToast({
|
|||
|
|
title: error.message || '认领失败',
|
|||
|
|
icon: 'error'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.4 支付模块
|
|||
|
|
|
|||
|
|
#### 4.4.1 支付服务
|
|||
|
|
```javascript
|
|||
|
|
// 支付服务
|
|||
|
|
class PaymentService {
|
|||
|
|
// 微信支付
|
|||
|
|
async wxPay(orderInfo) {
|
|||
|
|
try {
|
|||
|
|
// 1. 创建支付订单
|
|||
|
|
const paymentData = await api.payment.createOrder(orderInfo);
|
|||
|
|
|
|||
|
|
// 2. 调用微信支付
|
|||
|
|
const result = await this.requestPayment(paymentData);
|
|||
|
|
|
|||
|
|
// 3. 支付成功处理
|
|||
|
|
await this.handlePaymentSuccess(result);
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error('支付失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 调用微信支付API
|
|||
|
|
requestPayment(paymentData) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
wx.requestPayment({
|
|||
|
|
timeStamp: paymentData.timeStamp,
|
|||
|
|
nonceStr: paymentData.nonceStr,
|
|||
|
|
package: paymentData.package,
|
|||
|
|
signType: paymentData.signType,
|
|||
|
|
paySign: paymentData.paySign,
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 支付成功处理
|
|||
|
|
async handlePaymentSuccess(result) {
|
|||
|
|
// 更新订单状态
|
|||
|
|
await api.payment.confirmPayment(result);
|
|||
|
|
|
|||
|
|
// 更新本地状态
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 数据架构
|
|||
|
|
|
|||
|
|
### 5.1 状态管理架构
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "Store架构"
|
|||
|
|
A[RootStore]
|
|||
|
|
B[UserStore]
|
|||
|
|
C[TravelStore]
|
|||
|
|
D[AnimalStore]
|
|||
|
|
E[CommonStore]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "页面组件"
|
|||
|
|
F[Page Components]
|
|||
|
|
G[Custom Components]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "本地存储"
|
|||
|
|
H[Storage Manager]
|
|||
|
|
I[Cache Manager]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
A --> B
|
|||
|
|
A --> C
|
|||
|
|
A --> D
|
|||
|
|
A --> E
|
|||
|
|
|
|||
|
|
F --> A
|
|||
|
|
G --> A
|
|||
|
|
|
|||
|
|
A --> H
|
|||
|
|
A --> I
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 数据流设计
|
|||
|
|
|
|||
|
|
#### 5.2.1 数据流向
|
|||
|
|
```javascript
|
|||
|
|
// 数据流管理
|
|||
|
|
class DataFlow {
|
|||
|
|
// 数据获取流程
|
|||
|
|
async fetchData(type, params) {
|
|||
|
|
// 1. 检查缓存
|
|||
|
|
const cached = await this.checkCache(type, params);
|
|||
|
|
if (cached && !this.isExpired(cached)) {
|
|||
|
|
return cached.data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 请求API
|
|||
|
|
const data = await this.requestAPI(type, params);
|
|||
|
|
|
|||
|
|
// 3. 更新缓存
|
|||
|
|
await this.updateCache(type, params, data);
|
|||
|
|
|
|||
|
|
// 4. 更新Store
|
|||
|
|
this.updateStore(type, data);
|
|||
|
|
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓存管理
|
|||
|
|
async checkCache(type, params) {
|
|||
|
|
const key = this.generateCacheKey(type, params);
|
|||
|
|
return await storage.get(key);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async updateCache(type, params, data) {
|
|||
|
|
const key = this.generateCacheKey(type, params);
|
|||
|
|
await storage.set(key, {
|
|||
|
|
data,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
expiry: this.getCacheExpiry(type)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.3 本地存储设计
|
|||
|
|
|
|||
|
|
#### 5.3.1 存储结构
|
|||
|
|
```javascript
|
|||
|
|
// 存储管理器
|
|||
|
|
class StorageManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.prefix = 'jiebanke_';
|
|||
|
|
this.version = '1.0.0';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 用户数据存储
|
|||
|
|
async setUserData(data) {
|
|||
|
|
await this.set('user_data', {
|
|||
|
|
...data,
|
|||
|
|
version: this.version,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 缓存数据存储
|
|||
|
|
async setCacheData(key, data, expiry = 3600000) {
|
|||
|
|
await this.set(`cache_${key}`, {
|
|||
|
|
data,
|
|||
|
|
expiry: Date.now() + expiry,
|
|||
|
|
version: this.version
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 基础存储方法
|
|||
|
|
async set(key, value) {
|
|||
|
|
try {
|
|||
|
|
await wx.setStorage({
|
|||
|
|
key: this.prefix + key,
|
|||
|
|
data: value
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('存储失败', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async get(key) {
|
|||
|
|
try {
|
|||
|
|
const result = await wx.getStorage({
|
|||
|
|
key: this.prefix + key
|
|||
|
|
});
|
|||
|
|
return result.data;
|
|||
|
|
} catch (error) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 网络架构
|
|||
|
|
|
|||
|
|
### 6.1 HTTP客户端设计
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// HTTP客户端
|
|||
|
|
class HttpClient {
|
|||
|
|
constructor() {
|
|||
|
|
this.baseURL = 'https://api.jiebanke.com';
|
|||
|
|
this.timeout = 10000;
|
|||
|
|
this.interceptors = {
|
|||
|
|
request: [],
|
|||
|
|
response: []
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 请求拦截器
|
|||
|
|
addRequestInterceptor(interceptor) {
|
|||
|
|
this.interceptors.request.push(interceptor);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 响应拦截器
|
|||
|
|
addResponseInterceptor(interceptor) {
|
|||
|
|
this.interceptors.response.push(interceptor);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 发送请求
|
|||
|
|
async request(config) {
|
|||
|
|
// 应用请求拦截器
|
|||
|
|
for (const interceptor of this.interceptors.request) {
|
|||
|
|
config = await interceptor(config);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await this.wxRequest(config);
|
|||
|
|
|
|||
|
|
// 应用响应拦截器
|
|||
|
|
for (const interceptor of this.interceptors.response) {
|
|||
|
|
response = await interceptor(response);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return response;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw this.handleError(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 微信请求封装
|
|||
|
|
wxRequest(config) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
wx.request({
|
|||
|
|
url: this.baseURL + config.url,
|
|||
|
|
method: config.method || 'GET',
|
|||
|
|
data: config.data,
|
|||
|
|
header: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
...config.headers
|
|||
|
|
},
|
|||
|
|
timeout: this.timeout,
|
|||
|
|
success: resolve,
|
|||
|
|
fail: reject
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 6.2 API服务设计
|
|||
|
|
|
|||
|
|
#### 6.2.1 用户API
|
|||
|
|
```javascript
|
|||
|
|
// 用户API服务
|
|||
|
|
class UserAPI {
|
|||
|
|
constructor(http) {
|
|||
|
|
this.http = http;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 用户登录
|
|||
|
|
async login(data) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: '/user/login',
|
|||
|
|
method: 'POST',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取用户信息
|
|||
|
|
async getProfile() {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: '/user/profile',
|
|||
|
|
method: 'GET'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新用户信息
|
|||
|
|
async updateProfile(data) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: '/user/profile',
|
|||
|
|
method: 'PUT',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 6.2.2 旅行API
|
|||
|
|
```javascript
|
|||
|
|
// 旅行API服务
|
|||
|
|
class TravelAPI {
|
|||
|
|
constructor(http) {
|
|||
|
|
this.http = http;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取旅行列表
|
|||
|
|
async getList(params) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: '/travel/list',
|
|||
|
|
method: 'GET',
|
|||
|
|
data: params
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取旅行详情
|
|||
|
|
async getDetail(id) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: `/travel/${id}`,
|
|||
|
|
method: 'GET'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建旅行
|
|||
|
|
async create(data) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: '/travel',
|
|||
|
|
method: 'POST',
|
|||
|
|
data
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加入旅行
|
|||
|
|
async join(id) {
|
|||
|
|
return await this.http.request({
|
|||
|
|
url: `/travel/${id}/join`,
|
|||
|
|
method: 'POST'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 性能优化
|
|||
|
|
|
|||
|
|
### 7.1 启动性能优化
|
|||
|
|
|
|||
|
|
#### 7.1.1 代码分包
|
|||
|
|
```javascript
|
|||
|
|
// app.json 分包配置
|
|||
|
|
{
|
|||
|
|
"pages": [
|
|||
|
|
"pages/index/index",
|
|||
|
|
"pages/user/index"
|
|||
|
|
],
|
|||
|
|
"subPackages": [
|
|||
|
|
{
|
|||
|
|
"root": "packages/travel",
|
|||
|
|
"name": "travel",
|
|||
|
|
"pages": [
|
|||
|
|
"list/index",
|
|||
|
|
"detail/index",
|
|||
|
|
"create/index"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"root": "packages/animal",
|
|||
|
|
"name": "animal",
|
|||
|
|
"pages": [
|
|||
|
|
"list/index",
|
|||
|
|
"detail/index",
|
|||
|
|
"adopt/index"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"preloadRule": {
|
|||
|
|
"pages/index/index": {
|
|||
|
|
"network": "all",
|
|||
|
|
"packages": ["travel"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 7.1.2 资源优化
|
|||
|
|
```javascript
|
|||
|
|
// 图片懒加载组件
|
|||
|
|
Component({
|
|||
|
|
properties: {
|
|||
|
|
src: String,
|
|||
|
|
placeholder: String
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
data: {
|
|||
|
|
loaded: false,
|
|||
|
|
error: false
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
lifetimes: {
|
|||
|
|
attached() {
|
|||
|
|
this.observer = wx.createIntersectionObserver(this);
|
|||
|
|
this.observer.relativeToViewport().observe('.lazy-image', (res) => {
|
|||
|
|
if (res.intersectionRatio > 0) {
|
|||
|
|
this.loadImage();
|
|||
|
|
this.observer.disconnect();
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
detached() {
|
|||
|
|
if (this.observer) {
|
|||
|
|
this.observer.disconnect();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
loadImage() {
|
|||
|
|
const img = wx.createImage();
|
|||
|
|
img.onload = () => {
|
|||
|
|
this.setData({ loaded: true });
|
|||
|
|
};
|
|||
|
|
img.onerror = () => {
|
|||
|
|
this.setData({ error: true });
|
|||
|
|
};
|
|||
|
|
img.src = this.properties.src;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 7.2 运行时性能优化
|
|||
|
|
|
|||
|
|
#### 7.2.1 数据缓存策略
|
|||
|
|
```javascript
|
|||
|
|
// 缓存策略管理
|
|||
|
|
class CacheStrategy {
|
|||
|
|
constructor() {
|
|||
|
|
this.strategies = {
|
|||
|
|
// 用户数据 - 长期缓存
|
|||
|
|
user: {
|
|||
|
|
expiry: 24 * 60 * 60 * 1000, // 24小时
|
|||
|
|
storage: 'local'
|
|||
|
|
},
|
|||
|
|
// 旅行列表 - 短期缓存
|
|||
|
|
travelList: {
|
|||
|
|
expiry: 5 * 60 * 1000, // 5分钟
|
|||
|
|
storage: 'memory'
|
|||
|
|
},
|
|||
|
|
// 动物列表 - 中期缓存
|
|||
|
|
animalList: {
|
|||
|
|
expiry: 30 * 60 * 1000, // 30分钟
|
|||
|
|
storage: 'local'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取缓存策略
|
|||
|
|
getStrategy(type) {
|
|||
|
|
return this.strategies[type] || {
|
|||
|
|
expiry: 5 * 60 * 1000,
|
|||
|
|
storage: 'memory'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查缓存是否过期
|
|||
|
|
isExpired(cacheData, type) {
|
|||
|
|
const strategy = this.getStrategy(type);
|
|||
|
|
return Date.now() - cacheData.timestamp > strategy.expiry;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 7.2.2 列表虚拟化
|
|||
|
|
```javascript
|
|||
|
|
// 虚拟列表组件
|
|||
|
|
Component({
|
|||
|
|
properties: {
|
|||
|
|
items: Array,
|
|||
|
|
itemHeight: Number,
|
|||
|
|
containerHeight: Number
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
data: {
|
|||
|
|
visibleItems: [],
|
|||
|
|
scrollTop: 0,
|
|||
|
|
startIndex: 0,
|
|||
|
|
endIndex: 0
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
observers: {
|
|||
|
|
'items, containerHeight, itemHeight': function() {
|
|||
|
|
this.updateVisibleItems();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
methods: {
|
|||
|
|
// 更新可见项目
|
|||
|
|
updateVisibleItems() {
|
|||
|
|
const { items, itemHeight, containerHeight } = this.properties;
|
|||
|
|
const { scrollTop } = this.data;
|
|||
|
|
|
|||
|
|
const visibleCount = Math.ceil(containerHeight / itemHeight);
|
|||
|
|
const startIndex = Math.floor(scrollTop / itemHeight);
|
|||
|
|
const endIndex = Math.min(startIndex + visibleCount + 1, items.length);
|
|||
|
|
|
|||
|
|
const visibleItems = items.slice(startIndex, endIndex).map((item, index) => ({
|
|||
|
|
...item,
|
|||
|
|
index: startIndex + index,
|
|||
|
|
top: (startIndex + index) * itemHeight
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
this.setData({
|
|||
|
|
visibleItems,
|
|||
|
|
startIndex,
|
|||
|
|
endIndex
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 滚动事件
|
|||
|
|
onScroll(e) {
|
|||
|
|
const scrollTop = e.detail.scrollTop;
|
|||
|
|
this.setData({ scrollTop });
|
|||
|
|
this.updateVisibleItems();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 8. 安全架构
|
|||
|
|
|
|||
|
|
### 8.1 数据安全
|
|||
|
|
|
|||
|
|
#### 8.1.1 敏感数据加密
|
|||
|
|
```javascript
|
|||
|
|
// 数据加密工具
|
|||
|
|
class CryptoUtil {
|
|||
|
|
constructor() {
|
|||
|
|
this.algorithm = 'AES-256-GCM';
|
|||
|
|
this.keyLength = 32;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成密钥
|
|||
|
|
generateKey() {
|
|||
|
|
const array = new Uint8Array(this.keyLength);
|
|||
|
|
wx.getRandomValues(array);
|
|||
|
|
return Array.from(array).map(b => b.toString(16).padStart(2, '0')).join('');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 加密数据
|
|||
|
|
encrypt(data, key) {
|
|||
|
|
try {
|
|||
|
|
const jsonString = JSON.stringify(data);
|
|||
|
|
const encrypted = this.aesEncrypt(jsonString, key);
|
|||
|
|
return encrypted;
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error('加密失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 解密数据
|
|||
|
|
decrypt(encryptedData, key) {
|
|||
|
|
try {
|
|||
|
|
const decrypted = this.aesDecrypt(encryptedData, key);
|
|||
|
|
return JSON.parse(decrypted);
|
|||
|
|
} catch (error) {
|
|||
|
|
throw new Error('解密失败');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 8.1.2 Token管理
|
|||
|
|
```javascript
|
|||
|
|
// Token管理器
|
|||
|
|
class TokenManager {
|
|||
|
|
constructor() {
|
|||
|
|
this.tokenKey = 'access_token';
|
|||
|
|
this.refreshTokenKey = 'refresh_token';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存Token
|
|||
|
|
async saveToken(tokenData) {
|
|||
|
|
await storage.set(this.tokenKey, {
|
|||
|
|
token: tokenData.accessToken,
|
|||
|
|
expiry: Date.now() + tokenData.expiresIn * 1000
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
await storage.set(this.refreshTokenKey, tokenData.refreshToken);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取Token
|
|||
|
|
async getToken() {
|
|||
|
|
const tokenData = await storage.get(this.tokenKey);
|
|||
|
|
|
|||
|
|
if (!tokenData) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否过期
|
|||
|
|
if (Date.now() > tokenData.expiry) {
|
|||
|
|
return await this.refreshToken();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return tokenData.token;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 刷新Token
|
|||
|
|
async refreshToken() {
|
|||
|
|
try {
|
|||
|
|
const refreshToken = await storage.get(this.refreshTokenKey);
|
|||
|
|
|
|||
|
|
if (!refreshToken) {
|
|||
|
|
throw new Error('Refresh token not found');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const result = await api.user.refreshToken(refreshToken);
|
|||
|
|
await this.saveToken(result);
|
|||
|
|
|
|||
|
|
return result.accessToken;
|
|||
|
|
} catch (error) {
|
|||
|
|
// 刷新失败,清除所有Token
|
|||
|
|
await this.clearTokens();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清除Token
|
|||
|
|
async clearTokens() {
|
|||
|
|
await storage.remove(this.tokenKey);
|
|||
|
|
await storage.remove(this.refreshTokenKey);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 接口安全
|
|||
|
|
|
|||
|
|
#### 8.2.1 请求签名
|
|||
|
|
```javascript
|
|||
|
|
// 请求签名工具
|
|||
|
|
class RequestSigner {
|
|||
|
|
constructor(secretKey) {
|
|||
|
|
this.secretKey = secretKey;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成签名
|
|||
|
|
generateSignature(params, timestamp, nonce) {
|
|||
|
|
// 1. 参数排序
|
|||
|
|
const sortedParams = this.sortParams(params);
|
|||
|
|
|
|||
|
|
// 2. 构建签名字符串
|
|||
|
|
const signString = this.buildSignString(sortedParams, timestamp, nonce);
|
|||
|
|
|
|||
|
|
// 3. 生成签名
|
|||
|
|
const signature = this.hmacSha256(signString, this.secretKey);
|
|||
|
|
|
|||
|
|
return signature;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 参数排序
|
|||
|
|
sortParams(params) {
|
|||
|
|
return Object.keys(params)
|
|||
|
|
.sort()
|
|||
|
|
.reduce((result, key) => {
|
|||
|
|
result[key] = params[key];
|
|||
|
|
return result;
|
|||
|
|
}, {});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 构建签名字符串
|
|||
|
|
buildSignString(params, timestamp, nonce) {
|
|||
|
|
const paramString = Object.keys(params)
|
|||
|
|
.map(key => `${key}=${params[key]}`)
|
|||
|
|
.join('&');
|
|||
|
|
|
|||
|
|
return `${paramString}×tamp=${timestamp}&nonce=${nonce}&secret=${this.secretKey}`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 9. 测试架构
|
|||
|
|
|
|||
|
|
### 9.1 单元测试
|
|||
|
|
|
|||
|
|
#### 9.1.1 工具函数测试
|
|||
|
|
```javascript
|
|||
|
|
// 工具函数测试
|
|||
|
|
describe('DateUtil', () => {
|
|||
|
|
test('formatDate should format date correctly', () => {
|
|||
|
|
const date = new Date('2023-12-25');
|
|||
|
|
const formatted = DateUtil.formatDate(date, 'YYYY-MM-DD');
|
|||
|
|
expect(formatted).toBe('2023-12-25');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('isValidDate should validate date correctly', () => {
|
|||
|
|
expect(DateUtil.isValidDate('2023-12-25')).toBe(true);
|
|||
|
|
expect(DateUtil.isValidDate('invalid-date')).toBe(false);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// API服务测试
|
|||
|
|
describe('UserAPI', () => {
|
|||
|
|
let userAPI;
|
|||
|
|
let mockHttp;
|
|||
|
|
|
|||
|
|
beforeEach(() => {
|
|||
|
|
mockHttp = {
|
|||
|
|
request: jest.fn()
|
|||
|
|
};
|
|||
|
|
userAPI = new UserAPI(mockHttp);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('login should call correct endpoint', async () => {
|
|||
|
|
const loginData = { code: 'test-code' };
|
|||
|
|
const expectedResponse = { token: 'test-token' };
|
|||
|
|
|
|||
|
|
mockHttp.request.mockResolvedValue(expectedResponse);
|
|||
|
|
|
|||
|
|
const result = await userAPI.login(loginData);
|
|||
|
|
|
|||
|
|
expect(mockHttp.request).toHaveBeenCalledWith({
|
|||
|
|
url: '/user/login',
|
|||
|
|
method: 'POST',
|
|||
|
|
data: loginData
|
|||
|
|
});
|
|||
|
|
expect(result).toEqual(expectedResponse);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.2 集成测试
|
|||
|
|
|
|||
|
|
#### 9.2.1 页面测试
|
|||
|
|
```javascript
|
|||
|
|
// 页面集成测试
|
|||
|
|
describe('Travel List Page', () => {
|
|||
|
|
let page;
|
|||
|
|
|
|||
|
|
beforeEach(() => {
|
|||
|
|
page = new TravelListPage();
|
|||
|
|
// Mock API responses
|
|||
|
|
jest.spyOn(api.travel, 'getList').mockResolvedValue({
|
|||
|
|
list: [
|
|||
|
|
{ id: 1, title: 'Test Travel 1' },
|
|||
|
|
{ id: 2, title: 'Test Travel 2' }
|
|||
|
|
],
|
|||
|
|
hasMore: false
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('should load travel list on page load', async () => {
|
|||
|
|
await page.onLoad();
|
|||
|
|
|
|||
|
|
expect(api.travel.getList).toHaveBeenCalled();
|
|||
|
|
expect(page.data.travelList).toHaveLength(2);
|
|||
|
|
expect(page.data.loading).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('should handle filter changes', async () => {
|
|||
|
|
const filters = { city: 'Beijing', date: '2023-12-25' };
|
|||
|
|
|
|||
|
|
await page.onFilter({ detail: filters });
|
|||
|
|
|
|||
|
|
expect(api.travel.getList).toHaveBeenCalledWith(
|
|||
|
|
expect.objectContaining(filters)
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 9.3 端到端测试
|
|||
|
|
|
|||
|
|
#### 9.3.1 用户流程测试
|
|||
|
|
```javascript
|
|||
|
|
// E2E测试
|
|||
|
|
describe('User Journey', () => {
|
|||
|
|
test('complete travel booking flow', async () => {
|
|||
|
|
// 1. 用户登录
|
|||
|
|
await page.goto('/pages/user/login');
|
|||
|
|
await page.tap('.login-btn');
|
|||
|
|
await page.waitFor('.user-info');
|
|||
|
|
|
|||
|
|
// 2. 浏览旅行列表
|
|||
|
|
await page.goto('/pages/travel/list');
|
|||
|
|
await page.waitFor('.travel-item');
|
|||
|
|
|
|||
|
|
// 3. 查看旅行详情
|
|||
|
|
await page.tap('.travel-item:first-child');
|
|||
|
|
await page.waitFor('.travel-detail');
|
|||
|
|
|
|||
|
|
// 4. 加入旅行
|
|||
|
|
await page.tap('.join-btn');
|
|||
|
|
await page.waitFor('.success-toast');
|
|||
|
|
|
|||
|
|
// 5. 验证结果
|
|||
|
|
const joinedStatus = await page.$('.joined-status');
|
|||
|
|
expect(joinedStatus).toBeTruthy();
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 10. 部署架构
|
|||
|
|
|
|||
|
|
### 10.1 构建配置
|
|||
|
|
|
|||
|
|
#### 10.1.1 环境配置
|
|||
|
|
```javascript
|
|||
|
|
// 环境配置
|
|||
|
|
const config = {
|
|||
|
|
development: {
|
|||
|
|
apiBaseURL: 'https://dev-api.jiebanke.com',
|
|||
|
|
debug: true,
|
|||
|
|
logLevel: 'debug'
|
|||
|
|
},
|
|||
|
|
testing: {
|
|||
|
|
apiBaseURL: 'https://test-api.jiebanke.com',
|
|||
|
|
debug: true,
|
|||
|
|
logLevel: 'info'
|
|||
|
|
},
|
|||
|
|
production: {
|
|||
|
|
apiBaseURL: 'https://api.jiebanke.com',
|
|||
|
|
debug: false,
|
|||
|
|
logLevel: 'error'
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 获取当前环境配置
|
|||
|
|
function getConfig() {
|
|||
|
|
const env = process.env.NODE_ENV || 'development';
|
|||
|
|
return config[env];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = getConfig();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 10.1.2 构建脚本
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"scripts": {
|
|||
|
|
"dev": "cross-env NODE_ENV=development miniprogram-cli dev",
|
|||
|
|
"build:test": "cross-env NODE_ENV=testing miniprogram-cli build",
|
|||
|
|
"build:prod": "cross-env NODE_ENV=production miniprogram-cli build",
|
|||
|
|
"preview": "miniprogram-cli preview",
|
|||
|
|
"upload": "miniprogram-cli upload",
|
|||
|
|
"test": "jest",
|
|||
|
|
"lint": "eslint . --ext .js",
|
|||
|
|
"lint:fix": "eslint . --ext .js --fix"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 10.2 CI/CD流程
|
|||
|
|
|
|||
|
|
#### 10.2.1 GitHub Actions配置
|
|||
|
|
```yaml
|
|||
|
|
# .github/workflows/miniprogram.yml
|
|||
|
|
name: MiniProgram CI/CD
|
|||
|
|
|
|||
|
|
on:
|
|||
|
|
push:
|
|||
|
|
branches: [ main, develop ]
|
|||
|
|
pull_request:
|
|||
|
|
branches: [ main ]
|
|||
|
|
|
|||
|
|
jobs:
|
|||
|
|
test:
|
|||
|
|
runs-on: ubuntu-latest
|
|||
|
|
steps:
|
|||
|
|
- uses: actions/checkout@v3
|
|||
|
|
|
|||
|
|
- name: Setup Node.js
|
|||
|
|
uses: actions/setup-node@v3
|
|||
|
|
with:
|
|||
|
|
node-version: '16'
|
|||
|
|
cache: 'npm'
|
|||
|
|
|
|||
|
|
- name: Install dependencies
|
|||
|
|
run: npm ci
|
|||
|
|
|
|||
|
|
- name: Run tests
|
|||
|
|
run: npm test
|
|||
|
|
|
|||
|
|
- name: Run linting
|
|||
|
|
run: npm run lint
|
|||
|
|
|
|||
|
|
build-and-deploy:
|
|||
|
|
needs: test
|
|||
|
|
runs-on: ubuntu-latest
|
|||
|
|
if: github.ref == 'refs/heads/main'
|
|||
|
|
|
|||
|
|
steps:
|
|||
|
|
- uses: actions/checkout@v3
|
|||
|
|
|
|||
|
|
- name: Setup Node.js
|
|||
|
|
uses: actions/setup-node@v3
|
|||
|
|
with:
|
|||
|
|
node-version: '16'
|
|||
|
|
cache: 'npm'
|
|||
|
|
|
|||
|
|
- name: Install dependencies
|
|||
|
|
run: npm ci
|
|||
|
|
|
|||
|
|
- name: Build for production
|
|||
|
|
run: npm run build:prod
|
|||
|
|
|
|||
|
|
- name: Upload to WeChat
|
|||
|
|
run: npm run upload
|
|||
|
|
env:
|
|||
|
|
WECHAT_APPID: ${{ secrets.WECHAT_APPID }}
|
|||
|
|
WECHAT_PRIVATE_KEY: ${{ secrets.WECHAT_PRIVATE_KEY }}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 11. 监控与分析
|
|||
|
|
|
|||
|
|
### 11.1 性能监控
|
|||
|
|
|
|||
|
|
#### 11.1.1 性能指标收集
|
|||
|
|
```javascript
|
|||
|
|
// 性能监控工具
|
|||
|
|
class PerformanceMonitor {
|
|||
|
|
constructor() {
|
|||
|
|
this.metrics = {};
|
|||
|
|
this.init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init() {
|
|||
|
|
// 监听页面性能
|
|||
|
|
wx.onAppRoute((res) => {
|
|||
|
|
this.trackPagePerformance(res);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 监听网络请求
|
|||
|
|
this.interceptNetworkRequests();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 页面性能追踪
|
|||
|
|
trackPagePerformance(route) {
|
|||
|
|
const startTime = Date.now();
|
|||
|
|
|
|||
|
|
// 页面加载完成后记录
|
|||
|
|
setTimeout(() => {
|
|||
|
|
const loadTime = Date.now() - startTime;
|
|||
|
|
this.recordMetric('page_load_time', {
|
|||
|
|
route: route.path,
|
|||
|
|
loadTime,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
});
|
|||
|
|
}, 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 网络请求拦截
|
|||
|
|
interceptNetworkRequests() {
|
|||
|
|
const originalRequest = wx.request;
|
|||
|
|
|
|||
|
|
wx.request = (options) => {
|
|||
|
|
const startTime = Date.now();
|
|||
|
|
|
|||
|
|
const originalSuccess = options.success;
|
|||
|
|
const originalFail = options.fail;
|
|||
|
|
|
|||
|
|
options.success = (res) => {
|
|||
|
|
const duration = Date.now() - startTime;
|
|||
|
|
this.recordMetric('api_request', {
|
|||
|
|
url: options.url,
|
|||
|
|
method: options.method,
|
|||
|
|
duration,
|
|||
|
|
status: res.statusCode,
|
|||
|
|
success: true
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (originalSuccess) {
|
|||
|
|
originalSuccess(res);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
options.fail = (err) => {
|
|||
|
|
const duration = Date.now() - startTime;
|
|||
|
|
this.recordMetric('api_request', {
|
|||
|
|
url: options.url,
|
|||
|
|
method: options.method,
|
|||
|
|
duration,
|
|||
|
|
success: false,
|
|||
|
|
error: err.errMsg
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (originalFail) {
|
|||
|
|
originalFail(err);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return originalRequest(options);
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录指标
|
|||
|
|
recordMetric(type, data) {
|
|||
|
|
if (!this.metrics[type]) {
|
|||
|
|
this.metrics[type] = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.metrics[type].push(data);
|
|||
|
|
|
|||
|
|
// 定期上报
|
|||
|
|
this.reportMetrics();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上报指标
|
|||
|
|
async reportMetrics() {
|
|||
|
|
if (Object.keys(this.metrics).length === 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await api.analytics.reportMetrics(this.metrics);
|
|||
|
|
this.metrics = {}; // 清空已上报的指标
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('指标上报失败', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 11.2 错误监控
|
|||
|
|
|
|||
|
|
#### 11.2.1 错误捕获和上报
|
|||
|
|
```javascript
|
|||
|
|
// 错误监控工具
|
|||
|
|
class ErrorMonitor {
|
|||
|
|
constructor() {
|
|||
|
|
this.errors = [];
|
|||
|
|
this.init();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
init() {
|
|||
|
|
// 全局错误监听
|
|||
|
|
wx.onError((error) => {
|
|||
|
|
this.captureError('global_error', error);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 未处理的Promise拒绝
|
|||
|
|
wx.onUnhandledRejection((res) => {
|
|||
|
|
this.captureError('unhandled_rejection', res.reason);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// HTTP错误监听
|
|||
|
|
this.interceptHttpErrors();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 捕获错误
|
|||
|
|
captureError(type, error) {
|
|||
|
|
const errorInfo = {
|
|||
|
|
type,
|
|||
|
|
message: error.message || error,
|
|||
|
|
stack: error.stack,
|
|||
|
|
timestamp: Date.now(),
|
|||
|
|
userAgent: wx.getSystemInfoSync(),
|
|||
|
|
route: getCurrentPages().pop()?.route
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.errors.push(errorInfo);
|
|||
|
|
|
|||
|
|
// 立即上报严重错误
|
|||
|
|
if (this.isCriticalError(error)) {
|
|||
|
|
this.reportErrors();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 判断是否为严重错误
|
|||
|
|
isCriticalError(error) {
|
|||
|
|
const criticalKeywords = ['network', 'payment', 'auth'];
|
|||
|
|
const message = (error.message || error).toLowerCase();
|
|||
|
|
|
|||
|
|
return criticalKeywords.some(keyword =>
|
|||
|
|
message.includes(keyword)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 上报错误
|
|||
|
|
async reportErrors() {
|
|||
|
|
if (this.errors.length === 0) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await api.analytics.reportErrors(this.errors);
|
|||
|
|
this.errors = []; // 清空已上报的错误
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('错误上报失败', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 12. 总结
|
|||
|
|
|
|||
|
|
### 12.1 架构优势
|
|||
|
|
|
|||
|
|
#### 12.1.1 技术优势
|
|||
|
|
- **原生性能**:使用微信小程序原生框架,性能最优
|
|||
|
|
- **开发效率**:组件化开发,代码复用率高
|
|||
|
|
- **用户体验**:符合微信设计规范,用户学习成本低
|
|||
|
|
- **生态集成**:深度集成微信生态,功能丰富
|
|||
|
|
|
|||
|
|
#### 12.1.2 业务优势
|
|||
|
|
- **快速迭代**:模块化架构,支持功能快速开发和上线
|
|||
|
|
- **数据驱动**:完善的数据收集和分析体系
|
|||
|
|
- **用户增长**:利用微信社交关系链,促进用户增长
|
|||
|
|
- **商业变现**:多样化的商业模式支持
|
|||
|
|
|
|||
|
|
### 12.2 扩展性设计
|
|||
|
|
|
|||
|
|
#### 12.2.1 功能扩展
|
|||
|
|
- **插件化架构**:支持功能模块插件化扩展
|
|||
|
|
- **配置化管理**:业务规则配置化,灵活调整
|
|||
|
|
- **API版本管理**:支持API平滑升级
|
|||
|
|
- **多端适配**:架构支持多端扩展
|
|||
|
|
|
|||
|
|
#### 12.2.2 性能扩展
|
|||
|
|
- **分包加载**:支持功能分包,按需加载
|
|||
|
|
- **缓存策略**:多级缓存,提高响应速度
|
|||
|
|
- **虚拟化列表**:支持大数据量列表展示
|
|||
|
|
- **图片优化**:懒加载和压缩优化
|
|||
|
|
|
|||
|
|
### 12.3 运维保障
|
|||
|
|
|
|||
|
|
#### 12.3.1 监控体系
|
|||
|
|
- **性能监控**:全方位性能指标监控
|
|||
|
|
- **错误监控**:实时错误捕获和告警
|
|||
|
|
- **用户行为分析**:用户行为数据收集和分析
|
|||
|
|
- **业务指标监控**:关键业务指标实时监控
|
|||
|
|
|
|||
|
|
#### 12.3.2 质量保障
|
|||
|
|
- **自动化测试**:单元测试、集成测试、E2E测试
|
|||
|
|
- **代码质量**:ESLint代码规范检查
|
|||
|
|
- **CI/CD流程**:自动化构建和部署
|
|||
|
|
- **版本管理**:规范的版本发布流程
|
|||
|
|
|
|||
|
|
本小程序架构文档为解班客项目提供了完整的前端架构指导,通过合理的架构设计和技术选型,确保小程序的高性能、高可用性和良好的用户体验,为业务发展提供坚实的技术基础。
|