Files
jiebanke/docs/小程序架构文档.md

35 KiB
Raw Blame History

解班客小程序架构文档

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}&timestamp=${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流程:自动化构建和部署
  • 版本管理:规范的版本发布流程

本小程序架构文档为解班客项目提供了完整的前端架构指导,通过合理的架构设计和技术选型,确保小程序的高性能、高可用性和良好的用户体验,为业务发展提供坚实的技术基础。