说明

该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成系统开发)。

该系统文章,我会尽量说的非常详细,做到不管新手、老手都能看懂。

说明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理系统。

友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。

qq群:801913255,进群有什么不懂的尽管问,群主都会耐心解答。

有兴趣的朋友,请关注我吧(*^▽^*)。

关注我,学不会你来打我

前言

  随着前后端框架(轮子)的逐渐搭建完成,我们的OverallAuth2.0项目也正式迈入功能开发阶段。

  今天我们的目标是做一个带有认证的用户登录功能。

  看该文章前,说明一点。最好结合我之前的系列文章观看,因为会使用到之前系列文章中的代码。当然有一定基础的码友可自动忽略。

流程图

从流程图上可以看出,本次登录非常简单,它没有过多的业务逻辑,就是一个简单的用户登录验证,成功之后,就能进入系统。至于说登录之后的业务逻辑处理,本篇文章不会涉及(交由之后的系列文章)。

实现功能

  1、用户登录

  2、登录失效处理

  3、异常信息提示

编写后端接口

  这里需要编写用户登录接口,并且返回用户数据。

建立一个用户登录后的返回模型,由于在之前已经创建(LoginOutPut),我们只需在该模型中添加一个UserId即可,代码如下

/// <summary>
/// 登录输出模型
/// </summary>
public class LoginOutPut
{
/// <summary>
/// 用户ID
/// </summary>
public int UserId { get; set; } /// <summary>
/// 用户名
/// </summary>
public string? UserName { get; set; } /// <summary>
/// 密码
/// </summary>
public string? Password { get; set; } /// <summary>
/// Token
/// </summary>
public string? Token { get; set; } /// <summary>
/// Token过期时间
/// </summary>
public string? ExpiresDate { get; set; } }

ISysUserRepository仓储接口中,添加一个根据用户名和密码查找数据的接口

/// <summary>
/// 根据用户名称和密码获取用户信息
/// </summary>
/// <param name="userName">用户名称</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
public SysUser? GetUserMsg(string userName, string password);

SysUserRepository仓储中,实现接口

        /// <summary>
/// 根据用户名称和密码获取用户信息
/// </summary>
/// <param name="userName">用户名称</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
public SysUser? GetUserMsg(string userName, string password)
{
string sql = " select * from Sys_User where UserName =@UserName and Password=@Password";
using var connection = DataBaseConnectConfig.GetSqlConnection();
return connection.QueryFirstOrDefault<SysUser>(sql, new { UserName = userName, Password = password });
}

同理在SysUserService、ISysUserService层中,添加相同接口

ISysUserService

/// <summary>
/// 根据用户名称和密码获取用户信息
/// </summary>
/// <param name="userName">用户名称</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
ReceiveStatus<LoginOutPut> GetUserMsg(string userName, string password);

SysUserService

 /// <summary>
/// 根据用户名称和密码获取用户信息
/// </summary>
/// <param name="userName">用户名称</param>
/// <param name="password">用户密码</param>
/// <returns></returns>
public ReceiveStatus<LoginOutPut> GetUserMsg(string userName, string password)
{
ReceiveStatus<LoginOutPut> receiveStatus = new ReceiveStatus<LoginOutPut>();
List<LoginOutPut> loginResultsList = new List<LoginOutPut>();
if (string.IsNullOrEmpty(userName))
return ExceptionHelper<LoginOutPut>.CustomExceptionData("用户名不能为空!");
if (string.IsNullOrEmpty(password))
return ExceptionHelper<LoginOutPut>.CustomExceptionData("密码不能为空!");
var result = _sysUserRepository.GetUserMsg(userName, password);
if (result == null)
return ExceptionHelper<LoginOutPut>.CustomExceptionData(string.Format("用户【{0}】不存在,或账号密码输入错误", userName));
if (result.IsOpen == false)
return ExceptionHelper<LoginOutPut>.CustomExceptionData(string.Format("用户【{0}】已停用,请开启后再登录", userName)); LoginOutPut loginResults = new LoginOutPut()
{
UserId = result.UserId,
UserName = result.UserName,
Token = string.Empty,
ExpiresDate = string.Empty
};
loginResultsList.Add(loginResults);
receiveStatus.data = loginResultsList;
receiveStatus.msg = "登录成功";
return receiveStatus;
}

上述接口中,我们要验证用户名、密码是否为空,是否正确,用户账号是否启用等。如果验证不通过。我们使用ExceptionHelper异常帮助类,把异常信息,反馈给前端。

如果验证通过,我们需要返回用户名、用户id、token、过期时间给前端。

在SysUserController控制器中,添加如下接口

 /// <summary>
/// 登录
/// </summary>
/// <returns></returns>
[HttpPost]
[AllowAnonymous] // 不验证权限
public ReceiveStatus<LoginOutPut> Login(LoginInput loginModel)
{
var result = _userService.GetUserMsg(loginModel.UserName ?? string.Empty, loginModel.Password ?? string.Empty);
if (result.success)
{
var loginResult = result.data.First();
var tokenResult = JwtPlugIn.BuildToken(loginModel);
loginResult.Token = tokenResult.Token;
loginResult.ExpiresDate = tokenResult.ExpiresDate;
result.data = new List<LoginOutPut>() { loginResult };
}
return result;
}

因为是用户登录接口,所以不需要jwt验证

在用户登录成功后,我们根据用户名、用户密码、jwt配置信息生成token和过期时间。

ps:jwt配置信息请查看之前系列文章

前端结构调整

移动一下文件(不是跟着系列文章的,请忽略,主要是前端结构调整)

把components文件夹下的图片,移动到同src文件夹同级的resources的picture目录下(记住调整图片引用路径)。

把components文件夹下的HelloWorld.vue文件内容,拷贝到views下的framework文件夹中(没有就新建),然后删除components文件夹。

搭建登录界面

在scr文件夹下创建model文件夹,并在下面创建user文件夹,然后再user文件夹下创建一个LoginInput.ts的文件,用于存放字段(model文件夹以后作为存放模型的文件夹)

export interface LoginInput {
//用户名称
UserName: string;
//用户密码
Password: string;
}

接着往下。

在api文件夹下创建user文件夹,并添加index.ts文件内容如下(api文件夹以后作为存放调用后端接口的文件夹)

import { LoginInput } from '@/model/user/LoginInput';
import Http from '../http'; export const login = function(loginForm: LoginInput) {
return Http.post('/api/SysUser/Login', loginForm)
}

该代码主要是调用后端写的登录接口

接着往下。

在views文件夹目录下,创建login文件夹,并添加index.vue文件。内容如下

<template>
<div class="backgroundStyle">
<div class="loginStyle">
<div style="color: rgb(76 104 139)">
<div class="systemTitle">
OverallAuth2.0 权限管理系统
</div>
<div class="systemSubTitle">
简单、易懂、功能强大,欢迎访问使用。
</div>
</div>
<div style="height: calc(100% - 260px)">
<div class="fieldStyle">
<div style="width: 100%; text-align: left; margin-left: 10%">
<el-tag>密码登录</el-tag>
</div>
</div>
<div class="fieldStyle">
<div style="width: 100%">
<el-input
v-model="loginForm.UserName"
style="width: 80%; height: 40px"
placeholder="请输入用户名"
:prefix-icon="User"
/>
</div>
</div>
<div class="fieldStyle">
<div style="width: 100%">
<el-input
v-model="loginForm.Password"
style="width: 80%; height: 40px"
placeholder="请输入密码"
type="password"
show-password
:prefix-icon="Hide"
/>
</div>
</div>
<div class="fieldStyle">
<div style="width: 100%">
<el-input
v-model="code"
style="width: 80%; height: 40px"
placeholder="请输入验证码"
:prefix-icon="Position"
/>
</div>
</div>
<div class="fieldStyle">
<div style="width: 100%">
<el-button
@click="loginClick"
type="primary"
style="width: 80%; height: 50px"
>登录</el-button
>
</div>
</div>
</div>
<div style="height: 60px; text-align: left; margin-left: 10px">
<el-checkbox v-model="isStarted" label="码云是否Star" size="large" />
<div style="color: red; font-size: 12px">
*为了帮助更多的人知道及了解本项目,请帮忙Star。拜谢各位
</div>
</div>
<div class="loginBottomStyle">
<el-divider content-position="left"
><el-icon color="red"><star-filled /></el-icon>特色功能</el-divider
>
<div class="featuresFunction">
<el-tag>可视化权限设计</el-tag>
<el-tag type="success">数据行权限</el-tag>
<el-tag type="warning">数据列权限</el-tag>
<el-tag type="danger">完整流程审批</el-tag>
</div>
</div>
</div>
</div>
</template> <script lang="ts">
import { defineComponent, onMounted, reactive, ref } from "vue";
import { TestAutofac } from "../../api/module/user";
import { User, Hide, Position, StarFilled } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { useRouter } from "vue-router";
import { login } from "@/api/user";
import { LoginInput } from "@/model/user/LoginInput";
import { useUserStore } from "../../store/user";
import { storeToRefs } from "pinia";
export default defineComponent({
setup() {
//初始加载
onMounted(() => {
//TestAutofacMsg();
});
const userStore = useUserStore(); const router1 = useRouter();
const userName = ref("");
const password = ref("");
const code = ref("");
const isStarted = ref<boolean>(false);
//调用接口
const TestAutofacMsg = async () => {
var result = await TestAutofac();
console.log(result);
}; const loginForm = reactive<LoginInput>({
UserName: "张三",
Password: "1",
});
const loginClick = function () {
login(loginForm).then(({ data, code, msg }) => {
setTimeout(() => {
if (code == 200) {
userStore.token = data[0].token.toString();
userStore.expiresDate = data[0].expiresDate;
userStore.userInfo = {
userName: data[0].userName,
userId: data[0].userId,
}; ElMessage({
message: "登录成功",
type: "success",
});
router1.push({ path: "/framework" });
}
}, 1000);
});
}; return {
User,
Hide,
Position,
StarFilled,
userName,
password,
code,
isStarted,
loginClick,
loginForm,
};
},
components: {},
});
</script> <style scoped>
.backgroundStyle {
background-image: url(../../../resources/picture/login.png);
height: calc(100vh);
width: 100%;
background-size: 100% 100%;
display: flex;
} .loginStyle {
width: 23%;
height: 55%;
margin-top: 12%;
margin-left: 10%;
border: 2px solid white;
background-color: white;
border-radius: 10px;
box-shadow: 0px 0px 19px 0px rgba(132, 203, 255, 2.5);
}
.systemTitle {
height: 70px;
font-size: 30px;
justify-content: center;
align-items: center;
display: flex;
}
.systemSubTitle {
display: flex;
height: 30px;
font-size: 14px;
justify-content: center;
border-bottom: 1px solid #e1dede;
} .loginBottomStyle {
height: 100px;
/* font-size: 30px; */
justify-content: center;
align-items: center;
} .fieldStyle {
display: flex;
margin-top: 10px;
} .featuresFunction {
display: flex;
}
.featuresFunction > * {
margin-left: 10px;
}
</style>

修改base-routes.ts文件,添加一下2个菜单。

  {
path: '/framework',
component: Framework,
name: "架构", },
{
path: '/login',
component: Login,
name: "登录页面",
},

调整app.vue

把template中的内容替换成<router-view></router-view>即可,这个调整的原因是完全根据路由来访问界面,配合路由守卫,做到未登录时就进入登录界面的效果。

状态库持久化

这个是本篇文章的重点,它的作用是可以持久化记录登录人员登录信息。为以后验证token过期、获取登录信息做准备。

安装npm install pinia-plugin-persist插件。

并在main.ts中添加引用

import  persist  from 'pinia-plugin-persist'
pinia.use(persist)

这里需要注意的是:pinia.use(persist)一定要在app.use(pinia)前面。

在scr下建立store文件夹,并添加三个文件app.ts、index.ts、user.ts,内容如下

app.ts

import { defineStore } from 'pinia'

export const useAppStore = defineStore({
id: 'app',
state: () => {
return {
tab: true,
logo: true,
level: true,
inverted: false,
routerAlive: true,
collapse: false,
subfield: false,
locale: "zh_CN",
subfieldPosition: "side",
theme: 'light',
breadcrumb: true,
sideWidth: "220px",
sideTheme: 'dark',
greyMode: false,
accordion: true,
tagsTheme: 'concise',
keepAliveList: [],
themeVariable: {
"--global-checked-color": "#5fb878",
"--global-primary-color": "#009688",
"--global-normal-color": "#1e9fff",
"--global-danger-color": "#ff5722",
"--global-warm-color": "#ffb800",
},
}
},
persist: {
enabled: true,
strategies: [
{
// 可以是localStorage或sessionStorage
storage: localStorage,
// 指定需要持久化的属性
paths: ['token', 'expiresDate', 'userInfo']
}
]
},
})

index.ts

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' const store = createPinia();
store.use(piniaPluginPersistedstate); export default store;

user.ts

import { defineStore } from 'pinia'
export const useUserStore = defineStore(
'user', {
state: () => ({
token: '',
expiresDate: '',
userInfo: {},
}), actions: {},
//persist:true
persist: {
enabled: true,
strategies: [
{
// 可以是localStorage或sessionStorage
storage: localStorage,
// 指定需要持久化的属性
paths: ['token','expiresDate','userInfo']
}
]
},
})

这里说明一下,在paths属性中,你可以选择持久化存储数据的字段。我这里选择存储了token,过期时间、用户信息,朋友们可以自行决定。

路由守卫调整

上一篇文章我们讲过,路由守卫的作用,这里就不再赘述。

调整如下

router.beforeEach方法内容变更成一下代码
  NProgress.start();
const userStore = useUserStore();
const endTime = new Date(userStore.expiresDate);
const currentTime = new Date();
to.path = to.path;
if (to.meta.requireAuth && endTime < currentTime) {
router.push('/login')
}
if (to.meta.requireAuth) {
next();
} else if (to.matched.length == 0) {
next({ path: '/login' })
} else {
next();
}

从上述代码可以看出我们同过useUserStore获取了用户登录信息。然后拿到过期时间判断登录是否过期。过期就需要重新登录。

提示信息调整

找到在api文件夹下的http.ts文件,修改如下

结合后端返回code,做出准确提示。

演示地址

登录演示

结语

我们的OverallAuth2.0项目也正式迈入功能开发阶段,可能文章内容逐渐开始复杂化,如果你感兴趣的话,也有跟着博主从0到1搭建权限管理系统的兴趣。

那么请加qq群:801913255,进群有什么不懂的尽管问,群主都会耐心解答。

后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html

前端vue 预览地址:http://139.155.137.144:8881

关注公众号:发送【权限】,获取前后端代码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技术文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

(系列十二)Vue3+.Net8实现用户登录(超详细登录文档)的更多相关文章

  1. Spring Boot教程(二十二)使用Swagger2构建强大的RESTful API文档(1)

    由于Spring Boot能够快速开发.便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API.而我们构建RESTful API的目的通常都是由于多终端的原因,这 ...

  2. BurpSuite系列(十二)----User options模块(用户选择)

    一.简介   User options模块主要用来配置一些常用的选项. 二.模块说明 User options主要由4个模块组成: 1.Connections 连接 2.SSL 3.Display 4 ...

  3. Web 前端开发精华文章推荐(jQuery、HTML5、CSS3)【系列十二】

    2012年12月12日,[<Web 前端开发人员和设计师必读文章>系列十二]和大家见面了.梦想天空博客关注 前端开发 技术,分享各种增强网站用户体验的 jQuery 插件,展示前沿的 HT ...

  4. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  5. struts2官方 中文教程 系列十二:控制标签

    介绍 struts2有一些控制语句的标签,本教程中我们将讨论如何使用 if 和iterator 标签.更多的控制标签可以参见 tags reference. 到此我们新建一个struts2 web 项 ...

  6. 爬虫系列(十二) selenium的基本使用

    一.selenium 简介 随着网络技术的发展,目前大部分网站都采用动态加载技术,常见的有 JavaScript 动态渲染和 Ajax 动态加载 对于爬取这些网站,一般有两种思路: 分析 Ajax 请 ...

  7. SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据

    原文:SQL Server 2008空间数据应用系列十二:Bing Maps中呈现GeoRSS订阅的空间数据 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft SQL Se ...

  8. SpringBoot系列(十二)过滤器配置详解

    SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...

  9. 学习ASP.NET Core Razor 编程系列十二——在页面中增加校验

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  10. 打开order by的大门,一探究竟《死磕MySQL系列 十二》

    在日常开发工作中,你一定会经常遇到要根据指定字段进行排序的需求. 这时,你的SQL语句类似这样. select id,phone,code from evt_sms where phone like  ...

随机推荐

  1. Go make 介绍

    Go 语言中的 make 函数用于创建和初始化特定类型的对象,主要是用于创建切片(slice).映射(map)和通道(channel).make 函数与 new 函数不同,new 函数是用于分配内存, ...

  2. C++ : 如何用C语言实现C++的虚函数机制?

    前言 在 googletest的源码中,看到gtest-matchers.h 中实现的MatcherBase 类自定义了一个 VTable,这种设计实现了一种类似于C++虚函数的机制.C++中的虚函数 ...

  3. EF Core – Library use EF

    前言 写 Library 有时候会用到 database, 会想用 EF 来维护. 比如 Identity, IdentityServer, OpenIddict, 这些 Library 都有使用到 ...

  4. SpringBoot——整合SSM(主要整合MyBatis)

    基于SpringBoot整合SSM SpringBoot整合Spring(不存在) SpringBoot整合SpringMVC(不存在) SpringBoot整合MyBatis(主要) Spring整 ...

  5. socket close和shutdown的区别,TIME_WAIT和CLOSE_WAIT

    TCP主动关闭连接 appl: close(), --> FIN FIN_WAIT_1 //主动关闭socket方,调用close关闭socket,发FIN               < ...

  6. 系统编程-操作系统概论PART1

    Part1. 计算机的基本组成原理 Part2. 计算机执行原理顶层视图 Part3. 指令 指令周期 取指令和执行指令 指令格式 前面1字节是操作码,代码指令的功能,例如加法功能. 后面3字节用于寻 ...

  7. Android Linux EAS优化-schedtune

    SchedTune SchedTune是一项与CPU调频相关的性能提升技术,它实现为一个cgroup控制器. 这个控制器提供了一个名称为schedtune.boost的配置参数,运行时系统可以使用它来 ...

  8. 2021年9月国产数据库排行榜-墨天轮:达梦奋起直追紧逼OceanBase,openGauss反超PolarDB再升一位

    2021年9月国产数据库排行榜已在墨天轮发布,本月参与排名的数据库总数达到了142个. 一.9月国产数据库流行度排行榜前15名 先来看看排行榜前五名,虽然PingCAP的TiDB分数本月下降31.82 ...

  9. iOS中修饰符常用小结

    1.copy,是复制引用对象地址的深拷贝 a:当修饰不可变类型的属性时,如NSArray.NSDictionary.NSString,用copy,用copy为关键字的话,调用setter方法后.是对赋 ...

  10. 01-react的基本使用

    // 导入react和react-dom包 类似 vue 中的 import vue from 'vue' import react from 'react' // 内部的组件 import reac ...