35 KiB
35 KiB
解班客小程序架构文档
1. 项目概述
1.1 项目简介
解班客小程序是一个基于微信生态的社交旅行平台,融合了结伴旅行、动物认领、商家服务等核心功能。采用微信小程序原生开发框架,提供流畅的用户体验和丰富的社交功能。
1.2 业务目标
- 社交旅行:为用户提供结伴旅行的平台,增强旅行体验
- 动物认领:创新的动物认领功能,增加用户粘性
- 商家服务:为商家提供服务展示和预订平台
- 用户增长:通过微信生态实现用户快速增长
1.3 技术目标
- 性能优化:快速加载,流畅交互
- 用户体验:符合微信设计规范,操作简单直观
- 功能完整:覆盖核心业务场景
- 扩展性强:支持功能快速迭代和扩展
2. 技术选型
2.1 开发框架
2.1.1 微信小程序原生框架
// 选型理由
{
"框架": "微信小程序原生",
"版本": "最新稳定版",
"优势": [
"官方支持,稳定性高",
"性能最优,启动速度快",
"API完整,功能丰富",
"调试工具完善"
],
"适用场景": [
"复杂业务逻辑",
"高性能要求",
"深度集成微信能力"
]
}
2.1.2 状态管理
// Mobx-miniprogram
{
"库": "mobx-miniprogram",
"版本": "^4.13.2",
"优势": [
"响应式状态管理",
"简单易用",
"性能优秀",
"支持计算属性"
]
}
2.2 UI组件库
2.2.1 Vant Weapp
{
"组件库": "Vant Weapp",
"版本": "^1.11.2",
"优势": [
"组件丰富",
"设计规范",
"文档完善",
"社区活跃"
],
"使用组件": [
"Button", "Cell", "Form",
"Popup", "Dialog", "Toast",
"Tab", "NavBar", "Search"
]
}
2.3 工具库
2.3.1 网络请求
// 自定义HTTP库
{
"库": "自研HTTP库",
"特性": [
"请求拦截器",
"响应拦截器",
"错误处理",
"Loading管理",
"Token自动刷新"
]
}
2.3.2 工具函数
{
"日期处理": "dayjs",
"数据验证": "async-validator",
"图片处理": "自研工具",
"地理位置": "微信API",
"支付": "微信支付API"
}
3. 架构设计
3.1 整体架构
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)
// 页面组件结构
pages/
├── index/ // 首页
├── travel/ // 结伴旅行
│ ├── list/ // 旅行列表
│ ├── detail/ // 旅行详情
│ └── create/ // 创建旅行
├── animal/ // 动物认领
│ ├── list/ // 动物列表
│ ├── detail/ // 动物详情
│ └── adopt/ // 认领页面
├── merchant/ // 商家服务
├── user/ // 用户中心
└── common/ // 通用页面
3.2.2 业务逻辑层 (Business Layer)
// 业务模块结构
business/
├── user/ // 用户业务
│ ├── auth.js // 认证逻辑
│ ├── profile.js // 用户资料
│ └── settings.js // 用户设置
├── travel/ // 旅行业务
│ ├── list.js // 列表逻辑
│ ├── detail.js // 详情逻辑
│ └── booking.js // 预订逻辑
├── animal/ // 动物业务
└── merchant/ // 商家业务
3.2.3 数据管理层 (Data Layer)
// 状态管理结构
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)
// 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)
// 工具函数结构
utils/
├── index.js // 工具入口
├── date.js // 日期工具
├── format.js // 格式化工具
├── validate.js // 验证工具
├── location.js // 位置工具
├── image.js // 图片工具
└── wechat.js // 微信API封装
4. 核心模块设计
4.1 用户模块
4.1.1 用户认证
// 用户认证流程
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 用户状态管理
// 用户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 旅行列表
// 旅行列表组件
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 旅行详情
// 旅行详情页面
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 动物列表
// 动物列表组件
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 支付服务
// 支付服务
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 状态管理架构
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 数据流向
// 数据流管理
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 存储结构
// 存储管理器
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客户端设计
// 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
// 用户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
// 旅行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 代码分包
// 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 资源优化
// 图片懒加载组件
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 数据缓存策略
// 缓存策略管理
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 列表虚拟化
// 虚拟列表组件
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 敏感数据加密
// 数据加密工具
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管理
// 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 请求签名
// 请求签名工具
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 工具函数测试
// 工具函数测试
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 页面测试
// 页面集成测试
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 用户流程测试
// 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 环境配置
// 环境配置
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 构建脚本
{
"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配置
# .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 性能指标收集
// 性能监控工具
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 错误捕获和上报
// 错误监控工具
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流程:自动化构建和部署
- 版本管理:规范的版本发布流程
本小程序架构文档为解班客项目提供了完整的前端架构指导,通过合理的架构设计和技术选型,确保小程序的高性能、高可用性和良好的用户体验,为业务发展提供坚实的技术基础。