基于uni-app的校园综合服务平台开发实战

发布于:2025-09-02 ⋅ 阅读:(22) ⋅ 点赞:(0)

闪递校园:基于uni-app的校园综合服务平台开发实战

作为一名全栈开发者,我用6个月时间开发了这款校园综合服务平台——闪递校园。本文将详细分享项目从0到1的开发经验,包括技术选型、核心功能实现、踩坑记录以及性能优化等方面的干货内容。

📝 项目背景

在这里插入图片描述

为什么要做这个项目?

在大学期间,我发现校园里存在诸多痛点:

  • 🏃‍♂️ 跑腿需求旺盛:取快递、买外卖、代购等需求量大,但缺乏规范化平台
  • 💬 社交圈子固化:同学之间交流局限,缺乏破冰工具
  • 🎫 信息不对称:演出票务、二手交易信息分散
  • 💰 支付体验差:校园服务多采用现金支付,体验落后

于是我决定开发一款集跑腿服务、社交娱乐、校园电商于一体的综合性平台。

技术选型思考

技术栈 选择 理由
前端框架 uni-app + Vue 2.x 一套代码多端运行,降低开发成本
UI组件库 TuniaoUI 组件丰富,校园风格契合
状态管理 Vuex 用户信息、订单状态等需要全局管理
地图服务 腾讯地图API 国内定位精准,校园场景适配好
支付系统 微信支付 + 余额支付 覆盖主流支付场景
实时通讯 WebSocket 订单状态、聊天消息实时性要求高

🏗️ 项目架构设计

整体架构图

核心模块
跑腿服务
社交圈子
支付系统
实时通讯
用户层
UI组件层
业务逻辑层
数据管理层
网络请求层
后端API

目录结构设计

reWxSchool/
├── pages/              # 主包页面
│   ├── index/         # 首页模块
│   ├── hall/          # 接单大厅
│   ├── circle/        # 校园圈子
│   └── user/          # 用户中心
├── pagesA/            # 分包A - 核心业务
│   ├── order/         # 订单管理
│   ├── chat/          # 聊天系统
│   ├── makeFrend/     # 盲盒交友
│   └── withdraw/      # 钱包系统
├── pagesB/            # 分包B - 扩展功能
│   ├── movie/         # 电影票务
│   ├── secondhand/    # 二手市场
│   └── mbti/          # 性格测试
├── components/        # 公共组件
├── store/            # Vuex状态管理
└── util/             # 工具函数

💡 核心功能实现

1. 复杂支付系统的设计与实现

支付系统是整个平台的核心,我设计了一套完整的支付流程:

1.1 支付组件封装
// components/payBox/payBox.vue
export default {
    data() {
        return {
            paymentInProgress: false,      // 支付进行中标志
            isProcessing: false,           // 处理中标志
            currentOrderId: null,          // 当前订单ID
            paymentSessionId: null,        // 支付会话ID
            retryCount: 0,                 // 重试次数
            paymentTimeout: null           // 支付超时定时器
        }
    },
    
    methods: {
        // 核心支付流程
        async processPayment() {
            if (this.paymentInProgress) {
                this.showToast('支付进行中,请稍候');
                return;
            }

            try {
                this.paymentInProgress = true;
                this.isProcessing = true;
                this.paymentStartTime = Date.now();
                this.retryCount++;
                
                // 设置支付超时(5分钟)
                this.setPaymentTimeout();
                
                // 保存订单(如果还没有订单ID)
                let orderResult;
                if (!this.currentOrderId) {
                    orderResult = await this.saveOrder();
                    this.currentOrderId = orderResult.id || orderResult.payOrderId;
                } else {
                    // 使用现有订单ID,避免重复创建订单
                    orderResult = { 
                        payOrderId: this.currentOrderId, 
                        id: this.currentOrderId 
                    };
                }
                
                // 发起支付
                await this.initiatePayment(orderResult.payOrderId, orderResult.id);
                
            } catch (error) {
                this.handlePaymentError(error);
            } finally {
                this.clearPaymentTimeout();
                // 延迟重置状态,防止快速重复点击
                setTimeout(() => {
                    this.isProcessing = false;
                    this.paymentInProgress = false;
                }, 1000);
            }
        },

        // 微信支付处理
        async wechatPayment(payOrderId, orderId) {
            try {
                const response = await this.$apiHttp.transactions({ payOrderId });
                
                if (response.code !== 0) {
                    throw new Error('交易初始化失败');
                }
                
                await this.requestWechatPayment(response.data, orderId);
                
            } catch (error) {
                console.log('支付失败', orderId);
            }
        },

        // 余额支付处理
        async balancePayment(payOrderId, orderId) {
            try {
                const response = await this.$apiHttp.balancePayment({ payOrderId });
                
                if (response.code === 0) {
                    this.handlePaymentSuccess(orderId);
                } else {
                    this.handlePaymentFailure(response.msg, orderId);
                }
            } catch (error) {
                this.handlePaymentFailure('余额支付失败', orderId);
            }
        }
    }
}
1.2 防重复支付机制

为了防止用户快速点击导致重复扣费,我设计了多重保护机制:

// 多重锁定机制
confirmBalancePayment() {
    // 防止快速点击确认框
    if (this.isProcessing || this.paymentInProgress) {
        return;
    }
    
    uni.showModal({
        title: '确认支付',
        content: '是否使用余额免密支付?',
        success: (res) => {
            if (res.confirm) {
                // 再次检查状态,防止确认框期间状态变化
                if (!this.isProcessing && !this.paymentInProgress) {
                    this.processPayment();
                }
            }
        },
    });
}

2. 实时通讯系统实现

2.1 WebSocket连接管理
// pagesA/chatUser/chat.vue
export default {
    data() {
        return {
            socket: null,
            wsUrl: "wss://school.bitsai.top/school-api/websocket",
            connectionStatus: 'disconnected',
            token: "",
            reconnectAttempts: 0,
            maxReconnectAttempts: 5
        }
    },
    
    methods: {
        // WebSocket连接
        connectWebSocket() {
            // 检查token是否存在
            if (!this.token) {
                console.error('Token不存在,无法建立WebSocket连接');
                this.showErrorMessage('登录信息无效,请重新登录');
                return;
            }
            
            this.socket = uni.connectSocket({
                url: this.wsUrl,
                header: {
                    'token': this.token
                },
                success: () => console.log('WebSocket connection established'),
                fail: (error) => {
                    console.error('WebSocket connection failed', error);
                    this.showErrorMessage('WebSocket连接失败,请稍后重试');
                }
            });

            // 处理连接打开
            this.socket.onOpen(() => {
                console.log('WebSocket connection opened successfully');
                this.connectionStatus = 'connected';
                this.reconnectAttempts = 0; // 重置重连次数
            });

            // 处理接收消息
            this.socket.onMessage((message) => {
                let data = JSON.parse(message.data);
                if (data.type === 0) {
                    this.handleReceivedMessage(data.msg);
                }
            });

            // 处理连接关闭,自动重连(指数退避)
            this.socket.onClose((event) => {
                if (event.code !== 1000) {
                    this.connectionStatus = 'reconnecting';
                    // 确保token存在时才重连
                    if (this.token && this.reconnectAttempts < this.maxReconnectAttempts) {
                        setTimeout(() => {
                            this.reconnectAttempts++;
                            this.connectWebSocket();
                        }, this.getReconnectDelay());
                    } else {
                        this.connectionStatus = 'disconnected';
                    }
                } else {
                    this.connectionStatus = 'disconnected';
                }
            });
        },

        // 指数退避重连延迟
        getReconnectDelay() {
            return Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
        },

        // 消息发送
        handleSend() {
            if (this.chatMsg && !/^\s+$/.test(this.chatMsg)) {
                let messageObj = {
                    botContent: "",
                    userContent: this.chatMsg,
                    isload: true,
                    iserr: false,
                    sendTime: new Date().toISOString()
                };
                this.msgList.push(messageObj);
                this.shouldScrollToBottom = true;

                if (this.socket && this.socket.readyState === 1) {
                    const message = {
                        type: 0,
                        msg: this.chatMsg,
                        data: "Some data",
                        userId: this.receiverId
                    };

                    this.socket.send({
                        data: JSON.stringify(message),
                        success: () => {
                            console.log('Message sent successfully');
                            this.updateMessageStatus('success');
                        },
                        fail: (error) => {
                            console.error('Failed to send message', error);
                            this.updateMessageStatus('error');
                        }
                    });
                } else {
                    console.error('WebSocket is not open');
                    this.updateMessageStatus('error');
                    this.showErrorMessage('无法发送消息,请稍后重试');
                }

                this.chatMsg = ''; // 清空输入框
            }
        }
    }
}

3. 地理位置服务集成

3.1 腾讯地图API封装
// pagesB/utils/power.js
export default {
    methods: {
        // 获取用户定位信息
        Monitor() {
            var QQMapWX = require('@/comne/qqmap-wx-jssdk.js');
            let qqmapsdk = new QQMapWX({
                key: ''
            });
            var _this = this;
            
            uni.getLocation({
                type: 'gcj02',
                success(res) {
                    var latitude = res.latitude;
                    var longitude = res.longitude;
                    
                    // 小程序环境使用腾讯地图SDK
                    // #ifdef MP-WEIXIN
                    qqmapsdk.reverseGeocoder({
                        location: {
                            latitude: latitude,
                            longitude: longitude
                        },
                        success: function(res) {
                            console.log(res.result);
                            uni.setStorageSync('LocalHost', res.result);
                            _this.city = res.result.ad_info.city;
                        }
                    });
                    // #endif
                    
                    // H5环境使用HTTP API
                    // #ifdef H5
                    _this.getH5Loca(latitude, longitude);
                    // #endif
                },
                fail(err) {
                    console.log(err);
                    uni.showModal({
                        title: '提示',
                        content: '请先授权位置信息',
                        success: function(res) {
                            if (res.cancel == false && res.confirm) {
                                uni.openSetting({
                                    success: function(data) {
                                        _this.Monitor();
                                    }
                                });
                            } else {
                                _this.Monitor();
                            }
                        }
                    });
                }
            });
        },
        
        // H5环境地理编码
        getH5Loca(latitude, longitude) {
            let prm = {
                location: {
                    latitude: latitude,
                    longitude: longitude
                },
                key: '',
                output: 'jsonp',
                get_poi: 0,
                coord_type: 5
            };
            let url = `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}`;
            this.$jsonp(url, prm).then(res => {
                uni.setStorageSync('LocalHost', res.result);
                this.city = res.result.ad_info.city;
            });
        }
    }
}

4. AI智能客服集成

// util/request/api.js
coQuanziApi: (data) => {
    return new Promise((resolve, reject) => {
        uni.request({
            url: 'https://api.coze.cn/open_api/v2/chat',
            method: 'POST',
            header: {
                'Authorization': '',
                'Connection': 'keep-alive',
                'Host': 'api.coze.cn',
                'Accept': '*/*',
                'Content-Type': 'application/json',
            },
            data: {
                "conversation_id": data.userId,
                "bot_id": "",
                "user": data.userId,
                "stream": false,
                "query": data.content,
            },
            success: (res) => {
                resolve(res);
            },
            fail: (err) => {
                console.error(err);
                reject(err);
            }
        });
    });
}

🎯 创新功能设计

1. 盲盒交友系统

这是我最引以为豪的创新功能,通过匿名投递和随机抽取的方式,为校园社交带来新的玩法:

核心逻辑:
  1. 投放纸条:用户可以匿名投放个人信息或心情到盲盒中
  2. 随机抽取:其他用户可以随机抽取纸条,如果感兴趣可以发起聊天
  3. 地理限制:仅限同校学生参与,确保社交的真实性
  4. 隐私保护:初期完全匿名,建立信任后才可以选择公开身份
// 盲盒相关API
// 投放纸条
luckyNoteSave: (data) => {
    return postRequest('luckyNote/save', data)
},

// 抽取纸条
luckydrawNote: (data) => {
    return postRequest('luckyNote/drawNote', data)
},

// 查看历史
noteHistoryPage: (data) => {
    return getRequest('luckyNote/noteHistoryPage', data)
}

2. 智能跑腿订单系统

订单状态流转:
骑手接单
骑手取货
骑手送达
用户确认
用户取消
待接单
待取货
配送中
待确认
已完成
已取消
核心API设计:
// 订单相关API
orderSave: (data) => postRequest('order/save', data),          // 创建订单
orderTaking: (data) => postRequest('order/taking', data),      // 骑手接单
orderDelivery: (data) => postRequest('order/delivery', data),  // 骑手取货
orderArrive: (data) => postRequest('order/arrive', data),      // 骑手送达
orderConfirmArrive: (data) => postRequest('order/confirmArrive', data), // 用户确认

🚀 性能优化实践

1. 分包加载策略

通过合理的分包设计,将首屏加载时间从3.2s优化到1.8s:

// pages.json
{
    "pages": [
        // 主包 - 核心页面
        {"path": "pages/index/index"},
        {"path": "pages/hall/index"},
        {"path": "pages/circle/circle"},
        {"path": "pages/user/index"}
    ],
    "subPackages": [
        {
            "root": "pagesA",  // 分包A - 核心业务功能
            "pages": [
                {"path": "order/index"},
                {"path": "chat/chat"},
                {"path": "makeFrend/makeFrend"}
            ]
        },
        {
            "root": "pagesB",  // 分包B - 扩展功能
            "pages": [
                {"path": "movie/index"},
                {"path": "secondhand/index"},
                {"path": "mbti/mbti"}
            ]
        }
    ]
}

2. 请求优化

API管理器设计:
// util/request/api.js
const apiManager = {
    // 用户相关
    login: (data) => postRequest('api/login', data),
    getUserData: () => getRequest('user/data'),
    
    // 订单相关
    orderList: (data) => getRequest('order/pageList', data),
    orderSave: (data) => postRequest('order/save', data),
    
    // 支付相关
    transactions: (data) => getRequest('pay/transactions', data),
    balancePayment: (data) => getRequest('pay/balancePayment', data),
    
    // 聊天相关
    getChatList: (data) => getRequest('chat/getChatList', data),
    chatPageList: (data) => getRequest('chat/message/pageList', data)
}

3. 状态管理优化

// store/index.js
const store = new Vuex.Store({
    state: {
        // 持久化用户信息
        vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {
            name: '图鸟'
        },
        // 导航栏配置
        vuex_custom_nav_bar: true,
        vuex_status_bar_height: 0,
        vuex_custom_bar_height: 0,
    },
    mutations: {
        // 通用状态更新
        $tStore(state, payload) {
            let nameArr = payload.name.split('.');
            let saveKey = '';
            let len = nameArr.length;
            
            if (len >= 2) {
                // 支持多层级状态更新
                let obj = state[nameArr[0]];
                for (let i = 1; i < len - 1; i++) {
                    obj = obj[nameArr[i]];
                }
                obj[nameArr[len - 1]] = payload.value;
                saveKey = nameArr[0];
            } else {
                state[payload.name] = payload.value;
                saveKey = payload.name;
            }

            // 自动持久化到本地存储
            saveLifeData(saveKey, state[saveKey]);
        }
    }
})

🐛 踩坑记录与解决方案

1. 微信支付回调问题

问题: 微信支付成功后,有时候回调不及时,导致订单状态更新延迟。

解决方案:

// 增加支付状态轮询机制
requestWechatPayment(paymentData, orderId) {
    return new Promise((resolve, reject) => {
        uni.requestPayment({
            ...paymentData,
            success: () => {
                // 支付成功后启动状态轮询
                this.startPaymentPolling(orderId);
                this.handlePaymentSuccess(orderId);
            },
            fail: (error) => {
                this.handlePaymentFailure('微信支付失败', orderId);
            }
        });
    });
},

// 支付状态轮询
startPaymentPolling(orderId) {
    const pollInterval = setInterval(async () => {
        try {
            const result = await this.$apiHttp.orderDetail(orderId);
            if (result.data.payStatus === 'PAID') {
                clearInterval(pollInterval);
                this.updateOrderStatus('paid');
            }
        } catch (error) {
            console.error('轮询支付状态失败', error);
        }
    }, 2000);
    
    // 30秒后停止轮询
    setTimeout(() => clearInterval(pollInterval), 30000);
}

2. WebSocket断线重连问题

问题: 用户切换应用或网络波动时,WebSocket连接容易断开,重连策略不够完善。

解决方案:

// 改进的重连机制
connectWebSocket() {
    // ... 连接逻辑
    
    this.socket.onClose((event) => {
        if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {
            this.connectionStatus = 'reconnecting';
            
            // 指数退避策略
            const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
            
            setTimeout(() => {
                this.reconnectAttempts++;
                console.log(`${this.reconnectAttempts}次重连尝试`);
                this.connectWebSocket();
            }, delay);
        } else {
            this.connectionStatus = 'disconnected';
            if (this.reconnectAttempts >= this.maxReconnectAttempts) {
                this.showErrorMessage('连接失败次数过多,请检查网络后手动重试');
            }
        }
    });
}

3. 地理位置权限问题

问题: 用户首次使用时拒绝地理位置权限,导致功能异常。

解决方案:

Monitor() {
    uni.getLocation({
        type: 'gcj02',
        success(res) {
            // 定位成功处理
        },
        fail(err) {
            console.log(err);
            // 优雅的权限引导
            uni.showModal({
                title: '定位权限',
                content: '为了给您提供更好的校园服务,需要获取您的位置信息',
                confirmText: '去设置',
                cancelText: '手动选择',
                success: function(res) {
                    if (res.confirm) {
                        // 引导用户去设置页面
                        uni.openSetting({
                            success: function(data) {
                                if (data.authSetting['scope.userLocation']) {
                                    _this.Monitor(); // 重新获取定位
                                }
                            }
                        });
                    } else {
                        // 提供手动选择城市的备选方案
                        uni.navigateTo({
                            url: '/pagesA/school/school'
                        });
                    }
                }
            });
        }
    });
}

🎉 结语

闪递校园这个项目让我对全栈开发有了更深入的理解,也让我意识到技术服务于业务、业务服务于用户的重要性。虽然项目还有很多可以优化的地方,但它已经成为我技术成长路上的一个重要里程碑。

希望这篇文章能够对正在学习 uni-app 开发或者想要做校园服务类项目的同学有所帮助。如果你有任何问题或建议,欢迎在评论区交流讨论!


网站公告

今日签到

点亮在社区的每一天
去签到