1. UniApp登录注册模块基础搭建
第一次接触UniApp的登录注册开发时,我踩过不少坑。记得当时为了一个表单验证折腾到凌晨三点,现在回想起来其实有更优雅的实现方式。下面分享我从实战中总结的完整方案。
先来看基础结构搭建。在UniApp项目中,登录注册模块通常需要三个核心文件:
login.vue:登录页面register.vue:注册页面api/login.js:接口请求封装
登录页面基础代码:
<template> <view class="login-container"> <image class="logo" src="/static/logo.png"></image> <view class="form-group"> <input v-model="form.username" placeholder="请输入用户名" class="input-field" /> </view> <view class="form-group"> <input v-model="form.password" placeholder="请输入密码" password class="input-field" /> </view> <button @click="handleLogin" class="login-btn">登录</button> <view class="footer"> <text @click="goRegister">没有账号?立即注册</text> </view> </view> </template> <script> export default { data() { return { form: { username: '', password: '' } } }, methods: { handleLogin() { if(!this.form.username || !this.form.password) { uni.showToast({ title: '请填写完整信息', icon: 'none' }) return } // 调用登录接口 this.$api.login(this.form).then(res => { uni.switchTab({ url: '/pages/home/index' }) }) }, goRegister() { uni.navigateTo({ url: '/pages/auth/register' }) } } } </script>这个基础版本已经实现了:
- 双绑定的表单数据
- 基本的非空验证
- 页面跳转逻辑
- 接口调用框架
但实际企业级应用还需要更多优化,接下来我会逐步完善安全性和用户体验。
2. 表单验证的进阶实现
早期我做表单验证都是写一堆if-else,后来发现维护起来简直是噩梦。现在推荐使用vuelidate或vee-validate这类专业验证库。以vee-validate为例:
安装配置:
npm install vee-validate@next在main.js中全局引入:
import { Field, Form, ErrorMessage, defineRule } from 'vee-validate' import { required, email, min } from '@vee-validate/rules' // 定义规则 defineRule('required', required) defineRule('email', email) defineRule('min', min) // 注册组件 Vue.component('VField', Field) Vue.component('VForm', Form) Vue.component('ErrorMessage', ErrorMessage)改造后的登录表单:
<template> <VForm @submit="handleLogin"> <view class="form-group"> <VField name="username" rules="required|min:4" v-model="form.username" > <input placeholder="请输入用户名" class="input-field" /> </VField> <ErrorMessage name="username" class="error-msg" /> </view> <view class="form-group"> <VField name="password" rules="required|min:6" v-model="form.password" > <input placeholder="请输入密码" password class="input-field" /> </VField> <ErrorMessage name="password" class="error-msg" /> </view> <button type="submit" class="login-btn">登录</button> </VForm> </template>这样改造后:
- 验证规则可复用
- 错误提示自动处理
- 支持多规则组合
- 验证逻辑与业务代码解耦
对于注册表单,验证会更复杂,通常需要:
- 密码强度校验
- 两次密码一致性校验
- 手机号格式校验
- 图形验证码校验
可以扩展自定义规则:
defineRule('passwordMatch', (value, [target]) => { return value === target }) defineRule('phone', (value) => { return /^1[3-9]\d{9}$/.test(value) })3. 接口安全防护策略
去年我们项目遭遇过一次撞库攻击,让我深刻意识到接口安全的重要性。以下是几个关键防护措施:
3.1 请求参数加密
前端加密方案:
// utils/crypto.js import CryptoJS from 'crypto-js' const SECRET_KEY = 'your-secret-key' export function encryptData(data) { return CryptoJS.AES.encrypt( JSON.stringify(data), SECRET_KEY ).toString() } export function decryptData(ciphertext) { const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY) return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)) }接口调用示例:
import { encryptData } from '@/utils/crypto' this.$api.login({ data: encryptData(this.form) })3.2 防重放攻击
通过timestamp+nonce方案:
// request拦截器示例 const nonce = () => Math.random().toString(36).substr(2) const timestamp = () => Math.floor(Date.now() / 1000) uni.addInterceptor('request', { invoke(args) { args.header = { ...args.header, 'X-Timestamp': timestamp(), 'X-Nonce': nonce(), 'X-Signature': generateSignature(args) } } })3.3 限流策略
后端接口应配置:
- 同一IP登录失败次数限制
- 验证码请求频率限制
- 敏感操作二次验证
4. 验证码系统实现
验证码是防止机器人的有效手段,常见的有:
4.1 图形验证码
前端实现:
<template> <view class="captcha-group"> <input v-model="form.captcha" placeholder="请输入验证码" /> <image :src="captchaUrl" @click="refreshCaptcha" class="captcha-img" /> </view> </template> <script> export default { data() { return { captchaUrl: '', captchaId: '' } }, mounted() { this.refreshCaptcha() }, methods: { refreshCaptcha() { this.$api.getCaptcha().then(res => { this.captchaUrl = res.data.image this.captchaId = res.data.id }) } } } </script>后端生成示例:
const svgCaptcha = require('svg-captcha') router.get('/captcha', (req, res) => { const captcha = svgCaptcha.create({ size: 4, noise: 3, color: true }) // 存储到redis,5分钟过期 redis.set(`captcha:${captcha.text}`, 1, 'EX', 300) res.send({ code: 200, data: { image: captcha.data, id: uuid() } }) })4.2 短信验证码
安全注意事项:
- 设置发送间隔(60秒)
- 每日发送上限
- IP限制
- 验证码有效期(5分钟)
实现方案:
// 发送短信验证码 async sendSmsCode(phone) { // 校验发送间隔 const lastSend = await redis.get(`sms:${phone}:last`) if (lastSend && Date.now() - lastSend < 60000) { throw new Error('操作过于频繁') } // 生成6位随机码 const code = Math.random().toString().substr(2, 6) // 存储验证码 await redis.set(`sms:${phone}`, code, 'EX', 300) await redis.set(`sms:${phone}:last`, Date.now(), 'EX', 60) // 调用短信服务商API await smsProvider.send(phone, `您的验证码是:${code}`) }5. 第三方登录集成
现代应用通常需要集成微信、Apple等第三方登录。以微信登录为例:
5.1 前端实现
<template> <button open-type="getPhoneNumber" @getphonenumber="onGetPhoneNumber"> 微信一键登录 </button> </template> <script> export default { methods: { async onGetPhoneNumber(e) { if (e.detail.errMsg.includes('ok')) { const { encryptedData, iv } = e.detail const res = await this.$api.wxLogin({ code: this.wxCode, encryptedData, iv }) // 登录成功处理 } } } } </script>5.2 后端解密流程
const WXBizDataCrypt = require('./WXBizDataCrypt') async function decryptWxData(appId, sessionKey, encryptedData, iv) { const pc = new WXBizDataCrypt(appId, sessionKey) return pc.decryptData(encryptedData, iv) } router.post('/wx-login', async (ctx) => { const { code, encryptedData, iv } = ctx.request.body // 获取session_key const wxRes = await axios.get( `https://api.weixin.qq.com/sns/jscode2session?appid=${appId}&secret=${appSecret}&js_code=${code}&grant_type=authorization_code` ) // 解密用户数据 const userInfo = decryptWxData( appId, wxRes.data.session_key, encryptedData, iv ) // 处理用户登录/注册 // ... })6. 性能优化实践
在大用户量场景下,登录模块需要特别注意性能:
6.1 接口优化
- 使用Redis缓存用户信息
- 数据库查询添加索引
- 接口响应时间监控
// Redis缓存示例 async function getUserById(id) { const cacheKey = `user:${id}` let user = await redis.get(cacheKey) if (!user) { user = await db.collection('users').findOne({ _id: id }) await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600) } return user }6.2 前端优化
- 图片资源压缩
- 组件懒加载
- 请求合并
<script> // 按需加载验证码组件 const Captcha = () => import('@/components/Captcha') export default { components: { Captcha } } </script>7. 错误处理与监控
完善的错误处理能极大提升用户体验:
7.1 错误分类处理
// 统一错误处理中间件 app.use(async (ctx, next) => { try { await next() } catch (err) { const status = err.status || 500 const message = err.message || '服务异常' // 记录错误日志 logger.error(`[${status}] ${message}`, { url: ctx.url, body: ctx.request.body, stack: err.stack }) // 返回错误信息 ctx.status = status ctx.body = { code: status, message, timestamp: Date.now() } } })7.2 前端错误收集
// 全局错误监听 uni.onError((error) => { uni.request({ url: '/log/error', method: 'POST', data: { msg: error.message, stack: error.stack, page: getCurrentPages().pop().route } }) }) // API错误统一处理 const errorHandler = (error) => { const status = error.response?.status const messageMap = { 400: '请求参数错误', 401: '登录已过期', 403: '没有权限', 404: '资源不存在', 500: '服务器错误' } uni.showToast({ title: messageMap[status] || '网络错误', icon: 'none' }) if (status === 401) { store.dispatch('logout') } return Promise.reject(error) }8. 多端适配技巧
UniApp的优势在于多端兼容,但各平台有差异:
8.1 平台条件编译
<template> <view> <!-- #ifdef MP-WEIXIN --> <button open-type="getUserInfo">微信登录</button> <!-- #endif --> <!-- #ifdef APP-PLUS --> <button @click="appleLogin">Apple登录</button> <!-- #endif --> </view> </template>8.2 样式适配方案
.login-btn { /* 基础样式 */ padding: 12px 0; /* 小程序特有样式 */ /* #ifdef MP */ border-radius: 0; /* #endif */ /* APP特有样式 */ /* #ifdef APP-PLUS */ border-radius: 20px; /* #endif */ }9. 持续优化方向
登录注册模块需要持续迭代优化:
- 生物识别认证:Face ID/Touch ID集成
- 风险控制:异常登录检测
- 数据分析:登录漏斗分析
- 无密码登录:WebAuthn标准实现
- 多因素认证:短信+邮件+OTP组合
// WebAuthn示例 async function registerBiometric(user) { const options = { challenge: randomString(), rp: { name: "My App" }, user: { id: user.id, name: user.email, displayName: user.name }, pubKeyCredParams: [ { type: "public-key", alg: -7 } // ES256 ] } const credential = await navigator.credentials.create({ publicKey: options }) // 存储凭证 await db.collection('credentials').insert({ userId: user.id, credential }) }