适合熟悉vue3、nodejs基础者的项目:技术选型与设计方案详解

2025-07-03
来源:万象资讯

[]]

一、技术选型

前端:vue3+

后端:++

数据库:

二、设计方案

大部分网站的登录模块包含两个功能:注册和登录。

注册的主要目的是在系统中设立一个账户以及设置相应的密码,以便于后续的登录过程。然而,为了防止恶意注册行为、便于用户找回密码、以及防止脚本批量注册等情况的发生,大多数注册过程还会涉及邮箱验证和手机号验证等额外功能。

若同学们所创建的网站针对的是普通消费者群体,且该项目具备显著的市场发展潜力,那么在项目开发过程中,应遵循常规的注册流程。

然而,众多后台管理系统主要服务的是特定企业客户,即面向B端用户,这类系统通常无需设置注册环节,只需赋予超级管理员权限,由其负责用户的创建与维护工作。

我研发的演示网站,主要面向企业用户的后台管理需求,旨在提供解决方案。然而,我制作这个演示网站的初衷,是希望同学们都能进行体验和参考。因此,我为该网站设计了两种不同的登录方式:一是简便的注册登录机制;二是通过超级管理员创建用户后,采用的常规登录流程。

为了使注册过程更加简便,我们摒弃了传统的手机号验证方式,因为这种方式可能会让众多嫌麻烦的学生望而却步。同时,使用微信登录还需每年缴纳300元的认证费用。因此,我设计了通过微信扫码跳转至小程序的方法,用户在小程序内完成验证后,即可实现登录。

该方案非常适合那些寻求以低成本、简便方式通过微信进行扫码登录的中小企业、学生以及个人开发者等不同用户群体。

至于登录的过程相当直观,无论采用何种手段进行,其核心都是将前端收集的用户名和密码传递至后端,后端系统会将其与数据库中存储的用户资料进行比对,一旦比对成功,登录操作即告完成;若比对不匹配,则会提示登录失败。

三、小程序扫码登录

小程序的开发过程相对较为繁琐,而且这个方案是博主独立构思的,网络上的相关资料并不多。若只是想要掌握基础的登录技术原理,以便应对企业管理系统的开发需求,那么学习本章节的内容并非必需。

3.1 后端调用小程序官方api生成小程序码

我们通过调用小程序的官方API,生成了带有特定参数的小程序码,其中参数的键值我们定义为cId,之后将会用到。在生成小程序码的过程中,我们能够指定用户扫码后所进入的页面,目前我们设定为“我的”页面。

代码实现:

/** * 根据token获取微信小程序码 参数access_token,即微信的访问令牌。 参数env_version表示环境版本,其值为develop或release。 * */ async getWXACodeUnlimited(access_token: string, env_version: string) { const url = 此链接指向的是微信小程序码生成接口,具体地址为:https://api.weixin.qq.com/wxa/getwxacodeunlimit。; const cId = nanoid() const data = { scene: 'cId=' + cId, page: 'pages/about/about', env_version } const params = { access_token } const result = await this.httpService.request({ url, method: 'POST', responseType: 'arraybuffer', // 指定响应类型为二进制数据 data, params }) return { imgData: result.data, cId } }

代码讲解:

这是官方提供的用于获取小程序码的接口,详细信息可在微信小程序的官方网站上查阅。关于小程序API的应用,博主计划撰写一篇专门的文章进行介绍。

2. cId = ():使用插件生成随机的参数

采用系统自带的调用功能,无论同学们选择何种工具,均不受限制。

3.2 前端请求接口,获取小程序码

3.1章节提供的是小程序码的图像资料以及cId标识,前端随后向相应的后端服务发送请求,目的是获取小程序码图像并将其展示出来。

代码实现:

/** * 获取小程序码 * 返回值-小程序码参数*/ const miniAppSceneCodeUrl = ref('') //带参数的二维码 async function getWXACodeUnlimited() { miniAppSceneCodeUrl.value = '' let imgRs = {} try { imgRs = await getWXACodeUnlimitedApi({ env_version: 'release', }); } catch (e) { imgRs = { success: false } } // 将 Buffer 数据转换为图片 URL // 1. 将数组转换为 Uint8Array const uint8Array = new Uint8Array(imgRs.data.imgData.data);

怎么进入小程序云开发_微信扫码登录实现方案_vue3+nodejs全栈开发

// 2. 将 Uint8Array 转换为 Blob const blob = new Blob([uint8Array], {type: 'image/png'}); // 根据实际图片类型设置 MIME 类型 // 3. 生成图片 URL miniAppSceneCodeUrl.value = URL.createObjectURL(blob); return imgRs.data.cId }

对应的html代码:

<div v-if="miniAppSceneCodeUrl" style=宽度设定为百分之百,展示方式采用弹性布局,内容居中排列,定位属性设置为相对定位。> <img :src="miniAppSceneCodeUrl" style="width: 120px;height: 120px"/> </div>

代码解释:

1.pi:调用后端接口,获取图片信息和cId参数值。

后续步骤中,那三行代码并非必需,因为我的后端发送的图片信息是以二进制形式存在的。如果大家直接传递数据,操作将更为简便,只需在前端将src属性直接赋值,图片即可显示。之所以选择这种方式,是因为我的服务器带宽条件不佳,因此我尽量减少前后端数据交换的体积。

3.:小程序码的url,获取后直接在前端html中显示。

3.3 小程序监听扫码登录行为

用户在扫描小程序码后,便会被带至小程序内部,并直接跳转至“我的”页面;随后,该页面对页面渲染完成后的情况进行了监听,以确认是否传递了参数cId。

若小程序在监听用户跳转时接收到cId,这表明用户正在进行扫码登录操作,此时应将cId记录在用户信息中;反之,若未携带cId,则视为常规跳转,无需进行任何处理。

代码示例:

onLoad(async (option) => { if (option.scene) { const scene = decodeURIComponent(option.scene) const params = parseScene(scene); // 解析为键值对 saveCId(params.cId) } })

代码解释:

1.:小程序端是用开发的,是页面加载的生命周期。

2.. :监听页面加载后,页面携带的参数。

将cId存入数据库的用户信息模块。这一段代码相对较为繁琐,其中包含了众多复杂的逻辑判断。例如,若用户系首次使用小程序,必须先经历小程序为新用户建立档案的过程,随后方可将cId存入数据库的用户信息模块。这部分内容属于小程序功能的开发范畴,不属于本次登录功能的讲解内容,因此不再详细阐述。同学们若想达到相似的效果,只需确保在这一阶段将cId记录至数据库内的用户资料中即可。

3.4 以cId为筛选条件轮询用户信息

在3.2章节中,前端获取小程序码时,该方法不仅提供了cId,具体细节可参考3.2章节所展示的代码。

获取到cId之后,前端便能够以cId作为筛选标准,对用户信息表进行轮询查询。鉴于每个cId都是随机生成的,一旦前端在用户信息表中找到匹配的数据,便表明用户已成功通过扫码登录,此时前端页面即可允许访问,并显示登录成功的提示信息。

代码实现:

const showRefreshCode = ref(false) /** *根据小程序码参数轮询用户信息 */ function getUserInfoBySceneCode(sceneCode) { if (!sceneCode) return let getApiCount = 0 //当前轮询次数 let maxApiCount = 3 //最大的轮询次数 let getApiSuccess = false //轮询是否成功 getUserInfoInterval.value = setInterval(async () => { if (getApiCount >= maxApiCount) { // 超过10次,停止轮询 clearInterval(getUserInfoInterval.value); showRefreshCode.value = true if (getApiCount === maxApiCount && !getApiSuccess) { // 十次轮询都未成功,显示错误信息 console.error('十次轮询均失败:', getApiSuccess); Message.error('扫码登录失败,请重试'); showRefreshCode.value = true; } } getApiCount++ try { const userInfoRs = await getUserInfoBySceneCodeApi({ sceneCode: sceneCode }) if (userInfoRs.success) { getApiSuccess = true Message.success('扫码登录成功') // 登录成功后,维护全局变量 await handleLogIn(userInfoRs) router.push({name: 'home'}) // 成功后停止轮询 clearInterval(getUserInfoInterval.value); } } catch (error) { console.error('根据小程序码参数获取用户信息失败:', error); } }, 3000); // 每3秒轮询一次 }

怎么进入小程序云开发_微信扫码登录实现方案_vue3+nodejs全栈开发

上面代码就是简单的轮询代码,并没有什么技术点需要讲解。

3.5增加小程序码过期机制

前端持续不断地进行请求查询,这样做可能会对系统性能造成影响。因此,有必要设定一个规则,即当轮询达到一定次数后,便判定当前的登录操作已经失效。此时应终止轮询操作,并将已过期的小程序码隐藏起来。当用户选择刷新时,系统将重新启动新一轮的轮询。

在3.4章节中,我们定义了一个变量,它控制着刷新图标的显示。一旦轮询的次数超过了设定的上限,刷新图标便会显现出来,同时也会阻止用户进行扫码操作。这是因为我们已停止了对小程序码中包含的cId参数的轮询,因此再次扫码就失去了原本的意义。

当用户点击刷新图标,则重新获取小程序码,并重新开启轮询。

代码实现:

在前面显示小程序码的html代码中,增加刷新图标的判断:

<div v-if="miniAppSceneCodeUrl" style="width: 100%;display: flex;justify-content: center;position: relative"> <img :src="miniAppSceneCodeUrl" style="width: 120px;height: 120px"/> <div v-if="showRefreshCode" class="refresh-icon"> <Icon style=指针样式:手形;透明度:80%。 @click="loginBySceneCode" type="md-refresh" size="80"/> </div> </div>

代码解释:

1.:是否显示刷新图标

操作方法包括:点击刷新图标,请求小程序码,隐藏刷新图标,以及启动轮询用户信息等步骤。

四、常规的用户+密码登录

除了采用小程序扫码进行身份验证之外,该示例项目还增设了传统的用户名与密码登录途径。这些用户账户是由系统中的超级管理员所设立,并在账户创建完成后,用户需使用预设的密码进行初次登录。鉴于当前示例项目中的角色管理功能尚不健全,因此,所有用户均享有创建新用户的权限。

4.1 账户登录前端实现

账户登录页面的前端结构主要由一个Form表单构成,该表单中设有用户名和密码的输入区域,以及登录功能的按钮。同时,对于用户名和密码的输入框,还特别设定了不能为空的要求。

效果如下:

代码实现:

<Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit"> <FormItem prop="userCode"> <Input v-model="form.userCode" placeholder="请输入用户名"> <span slot="prepend"> <Icon :size="16" type="ios-person"></Icon> </span> </Input> </FormItem> <FormItem prop="password"> <Input type="password" v-model="form.password" password placeholder="请输入密码"> <span slot="prepend"> <Icon :size="14" type="md-lock"></Icon> </span> </Input> </FormItem> <FormItem> <Button @click="handleSubmit" class="lz-btn-primary" long>登录</Button> </FormItem> </Form>

众多UI框架均具备表单验证的功能,这些规则是通过设定来实现的。在示例项目中,仅对必填项进行了验证,而在实际应用中,还需额外添加对输入长度进行检查的规则。

登录按钮功能代码实现:

async function handleSubmit() { const validRs=await loginForm.value.validate() if (validRs) { const data = { userCode: form.value.userCode, password: form.value.password }; const res = await login(data) if (res.success) { // 登录成功后,维护全局变量 await handleLogIn(res) // 路由跳转 router.push({ name: '_home' }) } } }

代码解释:

1.:判断表单校验是否通过,通过校验后,才能执行登录逻辑

在执行调用之后,系统会对接收到的用户名和密码进行合法性验证,这一过程涉及对数据库中的数据进行核对,最终由后端系统给出相应的验证结论。

在操作成功之后,应当对全局变量进行维护。此过程在方法中实现,主要目的是将那些后续操作中频繁需要的关键信息保存在全局状态管理中。具体内容将在下文进行详细阐述。

4..push:上面所有方法都实现后,则跳转到网站首页。

方法代码实现:

/** * 登录时维护全局变量

怎么进入小程序云开发_微信扫码登录实现方案_vue3+nodejs全栈开发

* @param loginRs 登录成功后的返回值 登录响应对象中的访问令牌参数 该参数表示登录响应中的刷新令牌标识符,即refreshTokenId。 * @param loginRs.data 用户信息*/ async handleLogIn(loginRs) { // debugger const {accessToken, refreshTokenId, data} = loginRs console.log('userInfo', data) setToken(accessToken, refreshTokenId) this.token = accessToken // 维护全局用户信息 this.userInfo = data setUserInfoLocal(data) // 维护路由信息 await this.setRouters() },

如果同学们的项目同样引入了身份验证机制,那么请在此处进行相关信息的维护。在我个人的demo项目中,我使用了双因素认证,然而这部分内容对于前端初学者而言可能过于繁复,学习难度较大,不利于持续学习,因此我并不打算在这里详细展开。

如果只是简单的项目,不涉及,则采用下面的代码:

async handleLogIn(loginRs) { // debugger const { data} = loginRs console.log('userInfo', data) // 维护全局用户信息 this.userInfo = data localStorage.setItem('userInfo', JSON.stringify(userInfoData)) // 维护路由信息 await this.setRouters() },

代码解释:

用户信息需保存至全局状态管理,并且必须同步存入缓存,这是因为全局状态管理在页面刷新后信息会失效,而在众多应用场景中,缓存中的信息依然不可或缺。

在从远端获取路由信息并确保这些信息被纳入全局状态管理的过程中,才必须采用这种方法。路由信息只需被存储在全局状态管理中,无需进行缓存处理。这是因为,一旦路由信息是从远端获取的,它通常意味着正在进行权限和路由的管理。路由与权限紧密相关,且这类数据变化较为频繁,不宜直接从缓存中提取。相反,一旦监测到路由信息丢失,就应立即重新获取。若前端负责整个项目的路由管理,无需进行权限控制,那么该方法便无需采用。

4.2 账户登录后端实现

若未进行身份验证,那么用户密码的验证过程将变得极为简单,仅涉及基本的增加、删除、修改和查询操作。后端接口会根据前端传递的用户名,在数据库的用户信息表中检索该用户是否存在,一旦找到,便进行密码匹配,若用户名和密码均无误,则用户登录成功。

代码实现:

async login(entity: { userCode: string, password: string }): Promise<any> { let userData = await this.baseUserModel.findOne({ where: {userCode: entity.userCode} }); if(!userData){ return { success: false, msg: '用户不存在', } } let hasAuth = false if(userData && userData.userCode.trim()==='test'){ // 判断游客用户-test登录 hasAuth = entity.password===this.commonService.getPassCode() }else{ hasAuth = bcrypt.compareSync(entity.password, userData?.password) } if (!hasAuth) { return { success: false, msg: '用户名或密码错误', data:userData } } return { success: true, data: userData, } }

代码解释:

此语法规则依据查询用户信息来确定。若信息不存在,便向前端输出结果;若信息存在,则继续执行后续操作。

在我的项目中设有名为test的特定账户,这一点在项目示例中便能一目了然。该账户的密码系我自行对随机数进行加盐加密所得,其中并无太多专业知识。在大家进行实战项目时,这一环节是可以适当省略的。

在验证输入密码与用户资料中存储的密码是否匹配时,需注意,此过程涉及一个加密解密的插件。若用户信息表内存储的密码未经加密,一旦服务器遭受攻击,潜在损失将极为严重。因此,在用户注册阶段,通常都会采用加密工具对密码进行加密处理,并以此加密后的形式保存。加密后的密码将变成一串难以辨认的字符序列,通过将此序列与输入密码进行比对,即可确认两者是否相符。

总结

分享