人类行为验证处理方案 —— 脱离UI组件库实现登录、注册+表单校验

ops/2024/10/22 13:32:27/

目录

01: 构建登录模块基础UI结构

02: 表单校验实现原理与方案分析

表单校验的实现原理 

自定义表单校验方案分析 

文章中的方案实现

03: 基于 vee-validate 实现普适的表单校验         

04: 什么是人类行为验证?它的目的、实现原理、构建方案分别是什么? 

什么是人类行为验证 

目的 

它的实现原理是什么? 

我们如何在项目中使用它

05: 构建人类行为验证模块

06: 用户登录行为处理 

07: 用户信息获取行为

08: 退出登录操作 

09: token 超时处理 

10: 注册页面基本样式处理 

11: 处理注册行为 

12: 总结


 

01: 构建登录模块基础UI结构

- src/views
- - login-register
- - - components
- - - - header.vue
- - - login
- - - - index.vue
// src/views/login-register/components/header.vue<template><!-- 头部图标:PC端 --><div class="hidden pt-5 h-8 xl:block"><imgv-lazyclass="m-auto"src="https://res.lgdsunday.club/signlogo.png"alt=""/></div><!-- 头部图标:移动端 --><div class="h-[111px] xl:hidden"><imgv-lazyclass="dark:hidden"src="https://res.lgdsunday.club/login-bg.png"alt=""/><imgv-lazyclass="h-5 absolute top-[5%] left-[50%] translate-x-[-50%]"src="https://m.imooc.com/static/wap/static/common/img/logo-small@2x.png"alt=""srcset=""/></div>
</template>
// src/views/login-register/login/index.vue<template><divclass="relative h-screen bg-white dark:bg-zinc-800 text-center xl:bg-zinc-200"><!-- 头部图标:PC端 --><header-vue></header-vue><!-- 表单区 --><divclass="block px-3 mt-4 dark:bg-zinc-800 xl:bg-white xl:w-[388px] xl:dark:bg-zinc-900 xl:m-auto xl:mt-8 xl:py-4 xl:rounded-sm xl:shadow-lg"><h3class="mb-2 font-semibold text-base text-main dark:text-zinc-300 hidden xl:block">账号登录</h3><!-- 表单 --><vee-form @submit="onLoginHandler"><vee-fieldclass="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"name="username":rules="validateUsername"type="text"placeholder="用户名"autocomplete="on"v-model="loginForm.username"/><vee-error-messageclass="text-sm text-red-600 block mt-0.5 text-left"name="username"></vee-error-message><vee-fieldclass="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"name="password":rules="validatePassword"type="password"placeholder="密码"autocomplete="on"v-model="loginForm.password"/><vee-error-messageclass="text-sm text-red-600 block mt-0.5 text-left"name="password"></vee-error-message><div class="pt-1 pb-3 leading-[0px] text-right"><aclass="inline-block p-1 text-zinc-400 text-right dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400 cursor-pointer"@click="onToRegister">去注册</a></div><m-buttonclass="w-full dark:bg-zinc-900 xl:dark:bg-zinc-800":loading="loading":isActiveAnim="false">登录</m-button></vee-form><div class="flex justify-around mt-4"><!-- QQ --><qq-login-vue></qq-login-vue><!-- 微信 --><wx-login-vue></wx-login-vue></div></div><!-- 人类行为验证模块 --><slider-captcha-vuev-if="isSliderCaptchaVisible"@close="isSliderCaptchaVisible = false"@success="onCaptchaSuccess"></slider-captcha-vue></div>
</template><script>
export default {name: 'login'
}
</script><script setup>
import headerVue from '../components/header.vue'
import sliderCaptchaVue from './slider-captcha.vue'
import {Form as VeeForm,Field as VeeField,ErrorMessage as VeeErrorMessage
} from 'vee-validate'
import { validateUsername, validatePassword } from '../validate'
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import { LOGIN_TYPE_USERNAME } from '@/constants'
import qqLoginVue from './qq-login.vue'
import wxLoginVue from './weixin-login.vue'const store = useStore()
const router = useRouter()// 控制 sliderCaptcha 展示
const isSliderCaptchaVisible = ref(false)/*** 登录触发*/
const onLoginHandler = () => {isSliderCaptchaVisible.value = true
}/*** 人类行为验证通过*/
const onCaptchaSuccess = async () => {isSliderCaptchaVisible.value = false// 登录操作onLogin()
}// 登录时的 loading
const loading = ref(false)
// 用户输入的用户名和密码
const loginForm = ref({username: '',password: ''
})
/*** 用户登录行为*/
const onLogin = async () => {loading.value = true// 执行登录操作try {await store.dispatch('user/login', {...loginForm.value,loginType: LOGIN_TYPE_USERNAME})} finally {loading.value = false}router.push('/')
}/*** 进入注册页面*/
const onToRegister = () => {// 配置跳转方式store.commit('app/changeRouterType', 'push')router.push('/register')
}
</script><style lang="scss" scoped></style>

02: 表单校验实现原理与方案分析

        在绝大多数的情况下,我们进行登录时,都会通过 UI 组件库 实现表单校验功能。但是在没有 UI 组件库 的情况下,我们应该如何进行表单校验呢?

想要搞明白这一点,我们首先就需要搞明白表单校验的 实现原理。 

表单校验的实现原理 

我们知道,所谓表单校验,指的是:

        1. 在某一个时机下(失去焦点、内容变化)

        2. 检查表单元素中的 value 是否符合某个条件(校验条件)

        3. 如果不符合,则给出对应的提示

根据以上描述,我们所需要关注的,其实就是三点内容:

        1. 监听表单元素的对应时机

        2. 检查内容是否匹配校验条件

        3. 根据检查结果,展示对应提示

自定义表单校验方案分析 

根据以上原理描述,如果我们想要自定义一套表单校验的功能逻辑,是不是就比较简单了:

        1. 创建对应的 field 输入框组件

        2. 该组件中,包含两个元素:

                1. input 输入框

                2. span 表示错误提示

3. 监听 input 输入框的 blur 失去焦点 事件

4. 根据 input 的 value 判断是否满足一个或多个指定的条件(比如:是否为空)

5. 如果不满足,则展示 span 标签,表示错误提示消息

文章中的方案实现

        根据以上描述,我们确实可以实现一个基础的表单校验。但是这样的表单校验组件,很难具有 普适 性,因为实际开发中,表单校验的场景多种多样。比如:国际化处理。 

        把它抽离成一个 通用组件 意义并不大。咱们在文章中,就不会专门去实现这样的一个组件。而是会采用一种更加普适的方式。

        这种方式就是:vee-validate

        vee-validate 是一个 vue 中专门做表单校验的库,该库更加具有 普适 性,也更加适合大家在实际开发中的使用。

03: 基于 vee-validate 实现普适的表单校验         

- src/views/login-register
- - validate.js
// 关键代码import {Form as VeeForm,Field as VeeField,ErrorMessage as VeeErrorMessage
} from 'vee-validate'// 三个组件的使用
// src/views/login-register/validate.js/*** 用户名的表单校验*/
export const validateUsername = (value) => {if (!value) {return '用户名为必填的'}if (value.length < 3 || value.length > 12) {return '用户名应该在 3-12 位之间'}return true
}/*** 密码的表单校验*/
export const validatePassword = (value) => {if (!value) {return '密码为必填的'}if (value.length < 6 || value.length > 12) {return '密码应该在 6-12 位之间'}return true
}/*** 确认密码的表单校验*/
export const validateConfirmPassword = (value, password) => {if (value !== password[0]) {return '两次密码输入必须一致'}return true
}

04: 什么是人类行为验证?它的目的、实现原理、构建方案分别是什么? 

当表单校验完成之后,接下来我们就来处理 人类行为验证 模块。

想要搞清楚 人类行为验证,就需要搞明白三点内容:

        1. 什么是人类行为验证。

        2. 它的目的是什么。

        3. 它的实现原理是什么。

        4. 我们应该如何在项目中使用它。

什么是人类行为验证 

在我们日常使用的应用中,人类行为验证其实已经无处不在了。

比如大家应该都见过如下场景:

以上场景,均属于人类行为验证模块。

目的 

为什么需要有这样的一个东西呢?这样的一个东西对用户而言是非常讨厌的一个操作。

想要搞明白这个问题,大家就需要先搞清楚现在的应用面临的一个问题。

        假如在一个博客系统中,它会根据博客的访问量进行首页排名。假设有一个人,写了一段脚本代码,构建出巨量的 IP 来不断地访问一个指定的博客。这个博客就会被顶到非常靠前的访问位置中。 

        又假如:在某些投票或者砍价的应用中,也有人利用一段脚本代码,伪造出巨量的用户去进行投票或者砍价的行为,这样的投票或者砍价是不是也就失去了原本的意义。

        针对以上这种场景,我们应该如何防止呢?如何能够判断出,当前进行“投票”的操作是 进行的,而不是 机器 进行的呢?

        想要解决这个问题,就需要使用到 人类行为验证 了。

        简单来说,人类行为验证的目的就是:明确当前的操作是人完成的,而非机器。 

它的实现原理是什么? 

想要完成这样的判断,并且让判断准确,其实是非常复杂的:

        人机验证通过对用户的行为数据、设备特征与网络数据构建多维度数据分析,采用完整的可信前端安全方案,保证数据采集的真实性、有效性。 比如以下几个方面(包括但不仅限于):

        1. 浏览器特征检查:所有浏览器都有差异,可以通过各种前端相关手段检查浏览器环境的真实性。

        2. 鼠标事件(click、move、hover、leave)。

        3. 页面窗口(size、scroll、坐标)。

        4. cookie。

通过收集到的多维度数据,分析并建立人类行为模型,以此来判断用户是否是一个机器人。

以这样的滚动为例:

        人进行的拖动拼图和机器进行的拖动拼图,两者的 鼠标行为轨迹 是不同的。这个不同就是区分人和机器的关键。 

我们如何在项目中使用它

目前人类行为验证的实现方案,主要分为两种:

1. 收费平台,年费在几万到几十万不等,有专门的技术人员帮助对接:

        极验

        网易易盾

2. 免费开源,验证的精准度需要看服务端的能力:

        gitee 开源的 SliderCaptcha

我们这里主要是使用这个开源的 SliderCaptcha 实现。

大家在实际项目中可以根据实际情况进行处理。 

05: 构建人类行为验证模块

- src/views/login-register/login
- - slider-captcha.vue
// src/views/login-register/login/slider-captcha.vue<template><divclass="fixed top-[20%] left-[50%] translate-x-[-50%] w-[340px] h-[270px] text-sm bg-white dark:bg-zinc-800 rounded border border-zinc-200 dark:border-zinc-900 shadow-3xl"><div class="flex items-center h-5 text-left px-1 mb-1"><span class="flex-grow dark:text-zinc-200">请完成安全验证</span><m-svg-iconname="refresh"fillClass="fill-zinc-900 dark:fill-zinc-200"class="w-3 h-3 p-0.5 rounded-sm duration-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-900"@click="onReset"></m-svg-icon><m-svg-iconname="close"fillClass="fill-zinc-900 dark:fill-zinc-200"class="ml-2 w-3 h-3 p-0.5 rounded-sm duration-300 cursor-pointer hover:bg-zinc-200 dark:hover:bg-zinc-900"@click="onClose"></m-svg-icon></div><div id="captcha"></div></div>
</template><script>
const EMITS_CLOSE = 'close'
const EMITS_SUCCESS = 'success'
</script><script setup>
import '@/vendor/SliderCaptcha/slidercaptcha.min.css'
import '@/vendor/SliderCaptcha/longbow.slidercaptcha.min.js'
import { getCaptcha } from '@/api/sys'
import { onMounted } from 'vue'const emits = defineEmits([EMITS_CLOSE, EMITS_SUCCESS])let captcha = null
onMounted(() => {captcha = sliderCaptcha({// 渲染位置id: 'captcha',// 用户拼图成功之后的回调async onSuccess(arr) {const res = await getCaptcha({behavior: arr})if (res) {emits(EMITS_SUCCESS)}},// 用户拼图失败之后的回调onFail() {console.log('onFail')},// 默认的验证方法,咱们不在此处进行验证,而是选择在用户拼图成功之后进行验证,所以此处永远返回为 trueverify() {return true}})
})/*** 重置*/
const onReset = () => {captcha.reset()
}/*** 关闭*/
const onClose = () => {emits(EMITS_CLOSE)
}
</script>
// index.html<!-- iconfont 在线图标,主要用于 sliderCaptcha --><link rel="stylesheet"href="https://at.alicdn.com/t/font_3042963_nv614canpao.css?spm=a313x.7781069.1998910419.47&file=font_3042963_nv614canpao.css" />

使用: 

// src/views/login-register/login/index.vue<template><!-- 人类行为验证模块 --><slider-captcha-vuev-if="isSliderCaptchaVisible"@close="isSliderCaptchaVisible = false"@success="onCaptchaSuccess"></slider-captcha-vue>
</template>
<script setup>
import sliderCaptchaVue from './slider-captcha.vue'// 控制 sliderCaptcha 展示
const isSliderCaptchaVisible = ref(false)/*** 登录触发*/
const onLoginHandler = () => {isSliderCaptchaVisible.value = true
}/*** 人类行为验证通过*/
const onCaptchaSuccess = async () => {isSliderCaptchaVisible.value = false// 登录操作onLogin()
}
</script>

06: 用户登录行为处理 

// src/views/login-register/login/index.vueconst store = useStore()
const router = useRouter()// 登录时的 loading
const loading = ref(false)
// 用户输入的用户名和密码
const loginForm = ref({username: '',password: ''
})
/*** 用户登录行为*/
const onLogin = async () => {loading.value = true// 执行登录操作try {await store.dispatch('user/login', {...loginForm.value,loginType: LOGIN_TYPE_USERNAME})} finally {loading.value = false}router.push('/')
}

        我们希望把所有登录逻辑都放入 vuex 中。这是一种比较常见的封装方式。token 的处理、用户信息的处理、退出登录的处理、刷新 token,都可以在一块完成。 

- src/store/modules
- - user.js
// src/store/modules/user.jsimport { loginUser, getProfile, registerUser } from '@/api/sys'
import md5 from 'md5'
import { message } from '@/libs'
import { LOGIN_TYPE_OAUTH_NO_REGISTER_CODE } from '@/constants'export default {namespaced: true,state: () => ({// 登录之后的 tokentoken: '',// 获取用户信息userInfo: {}}),mutations: {/*** 保存 token*/setToken(state, newToken) {state.token = newToken},/*** 保存用户信息*/setUserInfo(state, newInfo) {state.userInfo = newInfo}},actions: {/*** 注册*/async register(context, payload) {const { password } = payload// 注册return await registerUser({...payload,password: password ? md5(password) : ''})},/*** 登录*/async login(context, payload) {const { password } = payloadconst data = await loginUser({...payload,password: password ? md5(password) : ''})// QQ 扫码登录,用户未注册if (data.code === LOGIN_TYPE_OAUTH_NO_REGISTER_CODE) {return data.code}context.commit('setToken', data.token)context.dispatch('profile')},/*** 获取用户信息*/async profile(context) {const data = await getProfile()context.commit('setUserInfo', data)// 欢迎message('success',`欢迎您 ${data.vipLevel? '尊贵的 VIP' + data.vipLevel + ' 用户 ' + data.nickname: data.nickname} `,6000)},/*** 退出登录*/logout(context) {context.commit('setToken', '')context.commit('setUserInfo', {})// 退出登录之后,重新刷新下页面,// 因为对于前台项目而言,用户是否登录(是否为 VIP)看到的数据可能不同location.reload()}}
}
// 注意:在 src/store/index.js 中进行注册
npm i md5

07: 用户信息获取行为

企业级项目中常见的传递 token 方式:在 axios 请求头中

// src/utils/request.jsconst service = axios.create({baseURL: import.meta.env.VITE_BASE_API,timeout: 5000
})// 请求拦截器
service.interceptors.request.use((config) => {// config.headers.icode = '你需要在这里填入你的 icode'if (store.getters.token) {// 如果token存在 注入tokenconfig.headers.Authorization = `Bearer ${store.getters.token}`}return config // 必须返回配置},(error) => {return Promise.reject(error)}
)
// 代码在上一小节 src/store/modules/user.js 中。

08: 退出登录操作 

// 代码在上一小节 src/store/modules/user.js 中。
// src/views/layout/components/header/header-my.vue<script setup>
import { confirm } from '@/libs'/*** menu Item 点击事件,也可以根据其他的 key 作为判定,比如 name*/
const onItemClick = (path) => {// 有路径则进行路径跳转if (path) {// 配置跳转方式store.commit('app/changeRouterType', 'push')router.push(path)return}// 无路径则为退出登录confirm('您确定要退出登录吗?').then(() => {// 退出登录不存在跳转路径store.dispatch('user/logout')})
}
</script>

09: token 超时处理 

通常情况下 token 均具备时效性。在本文章中,token 失效后,服务端会返回 401.

当服务端返回 401 时,表示 token 超时,则需要重新登录。

对应的操作可以在 axios 的响应式拦截器中进行

// src/utils/request.jsimport axios from 'axios'
import store from '@/store'
import { message as $message } from '@/libs'const service = axios.create({baseURL: import.meta.env.VITE_BASE_API,timeout: 5000
})// 请求拦截器
service.interceptors.request.use((config) => {// config.headers.icode = '你需要在这里填入你的 icode'if (store.getters.token) {// 如果token存在 注入tokenconfig.headers.Authorization = `Bearer ${store.getters.token}`}return config // 必须返回配置},(error) => {return Promise.reject(error)}
)// 响应拦截器
service.interceptors.response.use((response) => {const { success, message, data } = response.data//   要根据success的成功与否决定下面的操作if (success) {return data} else {$message('warn', message)// TODO:业务错误return Promise.reject(new Error(message))}},// code 非 200 时,回调函数。(error) => {// 处理 token 超时问题if (error.response &&error.response.data &&error.response.data.code === 401) {// TODO: token超时store.dispatch('user/logout')}$message('error', error.response.data.message)// TODO: 提示错误消息return Promise.reject(error)}
)export default service

10: 注册页面基本样式处理 

- src/views/login-register
- - register
- - - index.vue
<template><divclass="relative h-screen bg-white dark:bg-zinc-800 text-center xl:bg-zinc-200"><!-- 头部图标 --><header-vue></header-vue><!-- 表单区 --><divclass="block px-3 mt-4 dark:bg-zinc-800 xl:bg-white xl:w-[388px] xl:dark:bg-zinc-900 xl:m-auto xl:mt-8 xl:py-4 xl:rounded-sm xl:shadow-lg"><h3class="mb-2 font-semibold text-base text-main dark:text-zinc-300 hidden xl:block">注册账号</h3><!-- 表单 --><vee-form @submit="onRegister"><!-- 用户名 --><vee-fieldclass="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"name="username"type="text"placeholder="用户名"autocomplete="on":rules="validateUsername"v-model="regForm.username"/><vee-error-messageclass="text-sm text-red-600 block mt-0.5 text-left"name="username"></vee-error-message><!-- 密码 --><vee-fieldclass="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"name="password"type="password"placeholder="密码"autocomplete="on":rules="validatePassword"v-model="regForm.password"/><vee-error-messageclass="text-sm text-red-600 block mt-0.5 text-left"name="password"></vee-error-message><!-- 确认密码 --><vee-fieldclass="dark:bg-zinc-800 dark:text-zinc-400 border-b-zinc-400 border-b-[1px] w-full outline-0 pb-1 px-1 text-base focus:border-b-main dark:focus:border-b-zinc-200 xl:dark:bg-zinc-900"name="confirmPassword"type="password"placeholder="确认密码"autocomplete="on"rules="validateConfirmPassword:@password"v-model="regForm.confirmPassword"/><vee-error-messageclass="text-sm text-red-600 block mt-0.5 text-left"name="confirmPassword"></vee-error-message><div class="pt-1 pb-3 leading-[0px] text-right"><div class="mb-2"><aclass="inline-block p-1 text-zinc-400 text-right dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400 cursor-pointer"target="__black"@click="onToLogin">去登录</a></div><div class="text-center"><aclass="text-zinc-400 dark:text-zinc-600 hover:text-zinc-600 dark:hover:text-zinc-400 text-sm duration-400"href="https://m.imooc.com/newfaq?id=89"target="__black">注册即同意《慕课网注册协议》</a></div></div><m-buttonclass="w-full dark:bg-zinc-900 xl:dark:bg-zinc-800":isActiveAnim="false":loading="loading">立即注册</m-button></vee-form></div></div>
</template><script setup>
import headerVue from '../components/header.vue'
import {Form as VeeForm,Field as VeeField,ErrorMessage as VeeErrorMessage,defineRule
} from 'vee-validate'
import {validateUsername,validatePassword,validateConfirmPassword
} from '../validate'
import { LOGIN_TYPE_USERNAME } from '@/constants'
import { ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter, useRoute } from 'vue-router'const store = useStore()
const router = useRouter()
const route = useRoute()/*** 插入规则*/
defineRule('validateConfirmPassword', validateConfirmPassword)/*** 进入登录页面*/
const onToLogin = () => {// 配置跳转方式store.commit('app/changeRouterType', 'push')router.push('/login')
}// 数据源
const regForm = ref({username: '',password: '',confirmPassword: ''
})
// loading
const loading = ref(false)
console.log(route)
/*** 触发注册*/
const onRegister = async () => {loading.value = truetry {const payload = {username: regForm.value.username,password: regForm.value.password}// 触发注册,携带第三方数据await store.dispatch('user/register', {...payload,...route.query})// 注册成功,触发登录await store.dispatch('user/login', {...payload,loginType: LOGIN_TYPE_USERNAME})} finally {loading.value = false}router.push('/')
}
</script><style lang="scss" scoped></style>

确认密码 要关联到 密码,这样的关联操作 需要进行一个单独的注册。

// src/views/login-register/validate.js/*** 确认密码的表单校验*/
export const validateConfirmPassword = (value, password) => {if (value !== password[0]) {return '两次密码输入必须一致'}return true
}// register/index.vue 中使用代码import { defineRule } from 'vee-validate'
import { validateConfirmPassword } from '../validate'/*** 插入规则*/
defineRule('validateConfirmPassword', validateConfirmPassword)<vee-field      placeholder="确认密码"autocomplete="on"rules="validateConfirmPassword:@password"
/>

11: 处理注册行为 

// src/store/modules/user.jsexport default {……actions: {……/*** 注册*/async register(context, payload) {const { password } = payload// 注册return await registerUser({...payload,password: password ? md5(password) : ''})},}
}
// src/views/login-register/register/index.vue
// 代码在上一小节中

12: 总结

在本篇文章中,我们主要处理了两块内容:

1. 人类行为验证

        1. 是什么

        2. 目的

        3. 实现原理

        4. 构建方案

2. 表单验证原理 以及在实际开发中 通过 vee-validate 实现表单验证功能

登录处理完成之后,接下来我们就需要处理用户的信息展示和修改了。

在用户信息展示和修改中,我们将接触到新的通用组件和图片裁剪、上传的概念。 


http://www.ppmy.cn/ops/45505.html

相关文章

vue实现左侧拖拽拉伸,展开收起

需求&#xff1a;1.左侧是个树形结构&#xff0c;有的文字过长展示不全&#xff0c;想通过拖拽显示全部的数据 2.展开收起 实现图中效果 <div class"catalog-drag"><svg t"1687228434888" class"icon" viewBox"0 0 1…

【香橙派 AIpro】新手保姆级开箱教程:Linux镜像+vscode远程连接

香橙派 AIpro 开发板 AI 应用部署测评 写在最前面一、开发板概述官方资料试用印象适用场景 二、详细开发前准备步骤1. 环境准备2. 环境搭建3. vscode安装ssh插件4. 香橙派 AIpro 添加连接配置5. 连接香橙派 AIpro6. SSH配置 二、详细开发步骤1. 登录 juypter lab2. 样例运行3. …

蓝桥杯-班级活动

题目描述 小明的老师准备组织一次班级活动。班上一共有 ( n ) 名&#xff08;( n ) 为偶数&#xff09;同学&#xff0c;老师想把所有的同学进行分组&#xff0c;每两名同学一组。为了公平&#xff0c;老师给每名同学随机分配了一个 ( n ) 以内的正整数作为 id&#xff0c;第 …

Lua 基础 03 常用函数

Lua 基础相关知识 第三期 字符串 格式化字符串 string.format 通常字符串的连接可以使用 .. 符号&#xff0c;不过当字符串比较长&#xff0c;这样的连接方式就很繁琐&#xff0c;这时可以使用 string.format 进行格式化。 常用的格式控制符&#xff1a; %s 接收一个字符串…

C语言:结构体(详细讲解)

目录 结构体类型的声明 结构声明 描述一个学生 结构体的创建和初始化​编辑 结构体的⾃引⽤ 结构体的特殊声明 结构体内存对⻬ 对⻬规则 练习1 练习2 练习3 练习4-结构体嵌套问题 为什么会存在内存对⻬ 修改默认对⻬数 结构体的传参 结构体实现位段 什么是位段…

uni-app App端实现文字语音播报(Ba-TTS)

前言 最近在遇到消息提示语音播放出来&#xff0c;查了一圈文档发现并没有自带api 后面想起支付宝收钱播报&#xff0c;不受限与系统环境和版本环境&#xff08;后面查阅他是音频实现的&#xff09; 如果是由安卓端需要语音播放功能-直接使用Ba-TTs救急&#xff08;需要付费2…

VSCODE 常用快捷键

快捷按键 注释 CTRL /CTRL KSHIFT ALT A取消注释 CTRL /CTRL KSHIFT ALT A搜索文件 Ctrl P移动到某一行 Ctrl g打开一个新窗口 Ctrl Shift N关闭窗口 Ctrl Shift W新建文件 Ctrl N文件间切换 Ctrl Tab全部文件搜索 Ctrl Shift F全屏 F11 打开文件出现中文乱码 文件右下角…

C++第二十二弹---vector深度剖析及模拟实现(下)

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、容量操作 2、内容修改操作 3、打印函数 4、迭代器失效 4.1、什么是迭代器失效 4.2、哪些操作会引起迭代器失效 总结 1、容量操作 size()…