持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情
前言
这部分的内容可能比较多,因此比较难一次性说清楚,那么咱们本次的任务就是说完成,咱们整个项目里面最重要的部分之一的用户登录注册,首先咱们的前端还是Vue + ElementUI。后端是咱们的微服务架构,咱们要在这个场景下完成一个完整的登录注册+第三方的验证登录,方便用户的体验。同时咱们为了实现多端的一个功能实现,所以的话,咱们的就是说使用这个token去完成。
那么咱们现在要完成的就是咱们最最基本的登录和注册模块。那么现在最先需要完成的就是在咱们的平台本身的登录与注册模块,这个非常重要,因为这个每做好的话,咱们的这个Auto2.0结合第三方的登录也是很难做好的。因为逻辑其实都是一样的,就是细节可能不一样。
由于时间和联调的问题,那么咱们这个也是分好几个part,咱们尽可能详细一点,先把前端的事情说清楚,然后咱们再把后端的事情说清楚。
然后咱们的这个博文是分这几个part,前端的页面,基本的用户登录,注册,黑名单安全机制。Auto2.0 微信,QQ登录,账号绑定。之后的话,咱们的这个部分就完成了,之后的话其实就很简单了,其他的内容我可能连博客都懒得写了,当然除了用户聊天模块,我还会好好写,因为这个也是咱们的一个大头。
页面
我们先来看看最基本的前端页面
前端验证码
咱们的这个验证码的话是前端来生成的,一方面是减少咱们的这个后端的压力,减少这个请求次数。这个咋说呢,其实对于不知道的用户,会老老实实去填写,对于懂的用户,会抓包的同学,用户账号密码不对还是过不了,欸,没啥用,你说过拦截器是吧,不好意思,只要请求到了服务器基本上都会浪费资源,区别是啥,前者校验验证码,不过就不查询,后者直接查,对于恶意脚本来说,会把压力给我mysql,对于这个恶意脚本的话,我们直接在网关,或者是拦截器,搞个黑名单,直接拉黑ip但是存在多个用户用一个ip的情况,会误杀这个太直接不太好,或者搞一个特征请求,只要你不知道我请求的特征,你的脚本模拟不出来,并且此ip不满足特征请求的情况下的次数过多,基本上是脚本,直接拉黑即可。
那么回到咱们这个,这个的前端验证码的话,其实就是一个组件。
<template>
<div class="s-canvas">
<canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
</div>
</template>
<script>
export default {
name: "SIdentify",
props: {
identifyCode: {
type: String,
default: '1234'
},
fontSizeMin: {
type: Number,
default: 25
},
fontSizeMax: {
type: Number,
default: 30
},
backgroundColorMin: {
type: Number,
default: 255
},
backgroundColorMax: {
type: Number,
default: 255
},
colorMin: {
type: Number,
default: 0
},
colorMax: {
type: Number,
default: 160
},
lineColorMin: {
type: Number,
default: 100
},lineColorMax: {
type: Number,
default: 255
},
dotColorMin: {
type: Number,
default: 0
},
dotColorMax: {
type: Number,
default: 255
},
contentWidth: {
type: Number,
default: 112
},
contentHeight: {
type: Number,
default: 31
}
},
methods: {
// 生成一个随机数
randomNum(min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
// 生成一个随机的颜色
randomColor(min, max) {
let r = this.randomNum(min, max)
let g = this.randomNum(min, max)
let b = this.randomNum(min, max)
return 'rgb(' + r + ',' + g + ',' + b + ')'
},
drawPic() {
let canvas = document.getElementById('s-canvas')
let ctx = canvas.getContext('2d')
ctx.textBaseline = 'bottom'
// 绘制背景
ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax)
ctx.fillRect(0, 0, this.contentWidth, this.contentHeight)
// 绘制文字
for (let i = 0; i < this.identifyCode.length; i++) {
this.drawText(ctx, this.identifyCode[i], i)
}
this.drawLine(ctx)
this.drawDot(ctx)
},
drawText(ctx, txt, i) {
ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax)
ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei'
let x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1))
let y = this.randomNum(this.fontSizeMax, this.contentHeight - 5)
var deg = this.randomNum(-45, 45)
// 修改坐标原点和旋转角度
ctx.translate(x, y)
ctx.rotate(deg * Math.PI / 180)
ctx.fillText(txt, 0, 0)
// 恢复坐标原点和旋转角度
ctx.rotate(-deg * Math.PI / 180)
ctx.translate(-x, -y)
},
drawLine(ctx) {
// 绘制干扰线
for (let i = 0; i < 5; i++) {
ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax)
ctx.beginPath()
ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight))
ctx.stroke()
}
},
drawDot(ctx) {
// 绘制干扰点
for (let i = 0; i < 80; i++) {
ctx.fillStyle = this.randomColor(0, 255)
ctx.beginPath()
ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI)
ctx.fill()
}
}
},
watch: {
identifyCode() {
this.drawPic()
}
},
mounted() {
this.drawPic()
}
}
</script>
<style scoped>
.s-canvas {
height: 38px;
}
.s-canvas canvas{
margin-top: 1px;
margin-left: 8px;
}
</style>
哪里要用,哪里导入。
登录页面
咱们来一个一个看看。
Login
<template>
<div>
<vue-particles
class="login-background"
color="#97D0F2"
:particleOpacity="0.7"
:particlesNumber="200"
shapeType="circle"
:particleSize="4"
linesColor="#97D0F2"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.8"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push">
</vue-particles>
<el-tabs v-model="activeName" class="login-bok">
<el-tab-pane label="本账号登录" name="first">
<div style="width: 80%;height: 80%;margin: 100px auto">
<loginby-user-name></loginby-user-name>
</div>
</el-tab-pane>
<el-tab-pane label="第三方登录" name="second">
<login-third></login-third>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import loginbyUserName from "./loginbyUserName";
import LoginThird from "./LoginThird";
export default {
components:{
loginbyUserName,
LoginThird
},
name: "login",
data() {
return {
activeName: 'first'
};
},
}
</script>
<style scoped>
.login-bok{
width: 30%;
height: 450px;
margin: 0 auto;
border: 1px solid #DCDFE6;
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 30px #DCDFE6;
}
.login-background {
width: 80%;
margin: 0 auto;
height: 500px; /**宽高100%是为了图片铺满屏幕 */
z-index: 0;
position: absolute;
}
</style>
这个的话是咱们的主页面。 然后是咱们的平台登录。
LoginbyUserName
<template>
<div>
<el-form :model="formLogin" :rules="rules" ref="ruleForm" label-width="0px" >
<el-form-item prop="username">
<el-input v-model="formLogin.username" placeholder="账号">
<i slot="prepend" class="el-icon-s-custom"/>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="密码" v-model="formLogin.password">
<i slot="prepend" class="el-icon-lock"/>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-row :span="24">
<el-col :span="12">
<el-input v-model="formLogin.code" auto-complete="off" placeholder="请输入验证码" size=""></el-input>
</el-col>
<el-col :span="12">
<div class="login-code" @click="refreshCode">
<!--验证码组件-->
<s-identify :identifyCode="identifyCode"></s-identify>
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<div class="login-btn">
<el-button type="primary" @click="submitForm()" style="margin-left: auto;width: 35%">登录</el-button>
<el-button type="primary" @click="goRegister" style="margin-left: 27%;width: 35%" >注册</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script>
import SIdentify from "../../components/SIdentify/SIdentify";
export default {
name: "loginbyUserName",
components: { SIdentify },
data() {
return{
formLogin: {
username: "",
password: "",
code: ""
},
identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容
identifyCode: '',
// 校验
rules: {
username:
[
{ required: true, message: "请输入用户名", trigger: "blur" }
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" }
],
code: [
{ required: true, message: "请输入验证码", trigger: "blur" }
]
}
}
},
mounted () {
// 初始化验证码
this.identifyCode = ''
this.makeCode(this.identifyCodes, 4)
},
methods:{
refreshCode () {
this.identifyCode = ''
this.makeCode(this.identifyCodes, 4)
},
makeCode (o, l) {
for (let i = 0; i < l; i++) {
this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]
}
},
randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
submitForm(){
if (this.formLogin.code.toLowerCase() !== this.identifyCode.toLowerCase()) {
this.$message.error('请填写正确验证码')
this.refreshCode()
}
else {
//这边后面做一个提交,服务器验证,通过之后获得token
sessionStorage.setItem("loginToken","123456")
console.log("code is right")
this.$router.push("/userinfo")
console.log(this.formLogin.password)
console.log(this.formLogin.username)
}
},
goRegister(){
this.$router.push("/register")
}
},
}
</script>
<style scoped>
</style>
第三方登录
<template>
<div>
<el-tabs v-model="activeName" class="login-bok">
<el-tab-pane label="微信" name="first">
<div style="margin: 50px auto">
<loginby-wx></loginby-wx>
</div>
</el-tab-pane>
<el-tab-pane label="QQ" name="second">
<loginby-q-q></loginby-q-q>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import LoginbyQQ from "./LoginbyQQ";
import LoginbyWx from "./LoginbyWx";
export default {
components:{
LoginbyQQ,
LoginbyWx,
},
name: "LoginThird",
data() {
return {
activeName: 'first'
};
},
}
</script>
<style scoped>
.login-bok{
margin: 0 auto;
text-align: center;
}
</style>
然后里面有两个导航,一个是QQ的一个是微信的,我们这边推荐的其实还是使用第三方登录,并不是说减缓我们的服务器压力,其实这玩意只会增加压力,但是我们可以拿到更多的数据,后面还可以基于微信,或者QQ给我们提供的一些数据,来做更加细致的推荐。
这个里展示QQ的,微信的现在的页面是一样的,现在没啥区别,到咱们后面做Auto2.0的时候才有,这个到时候细说。
<template>
<div>
<el-image
style="width: 60%; height: 60%;margin: 0 auto"
src="static/image/QQ.jpg"
fit="fit"></el-image>
<p>
<el-tooltip content="勾选同意后将自动创建账号并进入账号设置绑定页面" placement="bottom" effect="light">
<el-checkbox v-model="checked" label="1">使用QQ登录时,自动创建账号</el-checkbox>
</el-tooltip>
</p>
</div>
</template>
<script>
export default {
name: "LoginbyQQ",
data () {
return {
checked: true
};
},
}
</script>
<style scoped>
</style>
注册页面
这个注册的话,就是单纯的在我们的平台进行注册,这里的区别就是多了一个邮箱验证。然后在这里做接口幂等。
<template>
<div>
<vue-particles
class="login-background"
color="#97D0F2"
:particleOpacity="0.7"
:particlesNumber="200"
shapeType="circle"
:particleSize="4"
linesColor="#97D0F2"
:linesWidth="1"
:lineLinked="true"
:lineOpacity="0.8"
:linesDistance="150"
:moveSpeed="3"
:hoverEffect="true"
hoverMode="grab"
:clickEffect="true"
clickMode="push">
</vue-particles>
<el-container>
<div style="width: 30%;margin: 0 auto">
<el-form :model="formRegister" :rules="rules" ref="formRegister" label-width="0px" >
<el-form-item prop="nickname">
<el-input v-model="formRegister.nickname" placeholder="昵称">
<i slot="prepend" class="el-icon-user"/>
</el-input>
</el-form-item>
<el-form-item prop="username">
<el-input v-model="formRegister.username" placeholder="账号">
<i slot="prepend" class="el-icon-s-custom"/>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="密码" v-model="formRegister.password">
<i slot="prepend" class="el-icon-lock"/>
</el-input>
</el-form-item>
<el-form-item>
<el-input type="phone" placeholder="手机(选填,建议填写)" v-model="formRegister.phone">
<i slot="prepend" class="el-icon-phone"/>
</el-input>
</el-form-item>
<el-form-item prop="email">
<el-input type="email" placeholder="邮箱(必填)" v-model="formRegister.email">
<i slot="prepend" class="el-icon-s-promotion"/>
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-row :span="24">
<el-col :span="12">
<el-input v-model="formRegister.code" auto-complete="off" placeholder="请输入验证码" size=""></el-input>
</el-col>
<el-col :span="12">
<div class="login-code" @click="refreshCode">
<!--验证码组件-->
<s-identify :identifyCode="identifyCode"></s-identify>
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item prop="emailCode">
<el-row :span="24">
<el-col :span="12">
<el-input v-model="formRegister.emailCode" auto-complete="off" placeholder="请输入邮箱验证码" size=""></el-input>
</el-col>
<el-col :span="12">
<div class="login-code" @click="refreshCode">
<!--发送邮箱验证码-->
<el-button @click="getEmailCode()" :disabled="!show"
style="margin-left: 26%;width: 70%"
type="primary"
>
<span v-show="show">发送验证码</span>
<span v-show="!show" class="count">{{count}} s</span>
</el-button>
</div>
</el-col>
</el-row>
</el-form-item>
<el-form-item>
<div class="login-btn">
<el-button type="primary" @click="goLogin" style="margin-left: auto;width: 35%">登录</el-button>
<el-button type="primary" @click="submitForm()" style="margin-left: 27%;width: 35%" >注册</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-container>
</div>
</template>
<script>
import SIdentify from "../../components/SIdentify/SIdentify";
export default {
name: "register",
components: { SIdentify },
data() {
return{
TIME_COUNT:60,
show: true,
count: 60,
formRegister: {
nickname: "",
phone: "",
username: "",
password: "",
code: "",
email:"",
emailCode:""
},
identifyCodes: '1234567890abcdefjhijklinopqrsduvwxyz',//随机串内容
identifyCode: '',
// 校验
rules: {
phone: [
{ min: 11, max: 11, message: '请输入11位手机号码', trigger: 'blur' },
{
pattern: /^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\d{8}$/,
message: '请输入正确的手机号码'
}
],
nickname:
[
{ required: true, message: "请输入用户昵称", trigger: "blur" }
],
username:
[
{ required: true, message: "请输入账号(用于登录)", trigger: "blur" }
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" }
],
code: [
{ required: true, message: "请输入验证码", trigger: "blur" }
],
emailCode: [
{ required: true, message: "请输入邮箱验证码", trigger: "blur" }
]
}
}
},
mounted () {
// 初始化验证码
this.identifyCode = ''
this.makeCode(this.identifyCodes, 4)
},
methods:{
getEmailCode () {
const that = this
if (this.formRegister.email === '') {
this.$message.error('请先输入邮箱再点击获取验证码')
} else {
let regEmail = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
if (!regEmail.test(this.formRegister.email)) {
this.$message({showClose: true, message: '请输入格式正确有效的邮箱号!', type: 'error'})
} else {
// 这部分是发送邮箱验证码的玩意
// 验证码倒计时
if (!this.timer) {
this.show = false
this.timer = setInterval(() => {
if (this.count > 0 && this.count <= this.TIME_COUNT) {
this.count--
} else {
this.show = true
clearInterval(this.timer)
this.timer = null
}
}, 1000)
}
}
}
},
refreshCode () {
this.identifyCode = ''
this.makeCode(this.identifyCodes, 4)
},
makeCode (o, l) {
for (let i = 0; i < l; i++) {
this.identifyCode += this.identifyCodes[this.randomNum(0, this.identifyCodes.length)]
}
},
randomNum (min, max) {
return Math.floor(Math.random() * (max - min) + min)
},
submitForm(){
let flag = true;
if (this.formRegister.code.toLowerCase() !== this.identifyCode.toLowerCase()) {
this.$message.error('请填写正确验证码');
this.refreshCode();
flag=false;
}
else if(!this.formRegister.emailCode){
this.$message.error('请填写邮箱验证码');
this.refreshCode();
flag=false;
}
else if(!this.formRegister.email){
this.$message.error('已填写邮箱请勿删除或修改邮箱,恶意操作将在120分钟内禁止注册!');
this.refreshCode();
flag=false;
}
if(flag){
//这边后面做一个提交,提交对于消息
}
},
goLogin(){
this.$router.push("/login")
}
},
}
</script>
<style scoped>
.login-background {
width: 80%;
margin: 0 auto;
height: 500px; /**宽高100%是为了图片铺满屏幕 */
z-index: 0;
position: absolute;
}
</style>
总结
这个的话就是咱们的前端的登录了,那么后面咱们开始填写逻辑。之后咱们聊聊Auto2.0 现在的话,咱们这个要申请,咱们还没搞,所以没办法。加上时间关系,咱们现在就先这样。