说明

该文章是属于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. 四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat

    四,分析Spring Boot底层机制(Tomcat 启动分析+Spring容器初始化+Tomcat如何关联 Spring 容器) 以及个人编写启动 Tomcat @ 目录 四,分析Spring Bo ...

  2. pikachu靶场-验证码

    先打开靶场,然后打开你的十米大砍刀burp,再把浏览器代理给配置好,开搞 1.先随便输入帐号和密码,用burp抓包 2.burp抓到包后用快捷键ctrl+l打开Intruder 3.确定 4.选择cl ...

  3. spark 中怎么像 pandas 里面对时间数据做 resample

    1. 笨办法 pandas Dataframe 可以很容易做时序数据的 resample,按照一定的frequency 聚合数据. 但是spark 中因为没有顺序的概念就不太好做,下面是怎么在spar ...

  4. 注册中心Nacos集群搭建

    一提到注册中心,大家往往想到Zookeeper.或者Eureka.今天我们看看阿里的一款配置中心+注册中心的中间件--Nacos.有了它以后,我们的项目中的配置就可以统一从Nacos中获取了,而且Sp ...

  5. .NET 多版本兼容的精美 WinForm UI控件库

    前言 有粉丝小伙伴在后台留言咨询有没有WinForm 控件库推荐,现在就给安排上. .NET 平台进行 Windows 应用程序开发的我们来说,找一个既美观又实用的 WinForm UI 控件库至关重 ...

  6. 通过C#在Word中插入或删除分节符

    在Word中,分节符是一种强大的工具,用于将文档分成不同的部分,每个部分可以有独立的页面设置,如页边距.纸张方向.页眉和页脚等.正确使用分节符可以极大地提升文档的组织性和专业性,特别是在长文档中,需要 ...

  7. MoCov1: 无监督视觉表征学习的动量对比《Momentum Contrast for Unsupervised Visual Representation Learning》(MoCo、动量对比、动态字典、队列维护、对比损失InfoNCE)

    现在是2024年6月11日,21:49,刚找好要看的论文,师兄推荐的. 先占个坑,明天看,我累了(我没脑子了). 现在是6月12日,15:49,干正事了(又被别人影响了情绪,这不好,希望你改掉,好的, ...

  8. CSS – display, visibility, opacity, transparent 的区别

    前言 要让一个元素"消失", 有 3 种做法. 它们有一点点的不同. 在实战时要清楚什么时候用什么哦. 例子说明 <div class="abc"> ...

  9. 使用pxe安装ARM服务器(鲲鹏920)遇到的坑

    一.关于PXE获取到IP之后无ACK,无法获取引导文件. 目前ARM服务器基本都是使用UEFI的方式进行引导,我们只需要关注EFI方式引导即可,Legacy引导已经随着时代的发展被扫进历史的垃圾桶. ...

  10. 如何安装eNSP

    如何安装eNSP? eNSP是需要三个插件进行辅助的,所以先下三个插件,最后在下eNSP 首先来看看Wireshark的安装 很简单,基本上就直接下一步就行 这里直接下一步 这里要注意,这些要么安装在 ...