说明

该文章是属于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. NSmartProxy:一款.NET开源、跨平台的内网穿透工具

    前言 今天大姚给大家分享一款.NET开源.免费(MIT License).跨平台的内网穿透工具,采用.NET Core的全异步模式打造:NSmartProxy. 内网穿透工具介绍 内网穿透工具是一种能 ...

  2. VUE-局部使用

    目录 VUE-局部使用 快速入门 常用指令 v-for v-bind v-if & v-show v-on v-model vue生命周期 Axios Vue案例 VUE-局部使用 Vue 是 ...

  3. chrome 被hao123 劫持处理

    打开chrome,就进入baidu.com/xxx,烦人,浏览器被劫持了XXXX 查注册表hao123,删除找到的 进入chrome设置,修改主页新标签页 装杀毒软件,查杀病毒 修改chrome名 等 ...

  4. 【YashanDB知识库】YAS-00103 no free block in dictionary cache

    [问题分类]功能使用 [关键字]YAS-00103,no free block in dictionary cache [问题描述]执行union all 太多子查询导致报错,例子如下: [问题原因分 ...

  5. 小tips:CSS实现0.5px的边框的两种方式

    方式一 <style> .border { width: 200px; height: 200px; position: relative; } .border::before { con ...

  6. SoftCLT: 时间序列的软对比学习《Soft Contrastive Learning for Time Series》(时间序列、时序分类任务、软对比学习、实例上软赋值距离差异、数据空间非嵌入空间度量相似性)

    2024年6月25日,10:11,好几天没看论文了,一直在摸鱼写代码(虽然也没学会多少),今天看一篇师兄推荐的. 论文:Soft Contrastive Learning for Time Serie ...

  7. mongodb 中rs.stauts()命令参数解析

    转载请注明出处: rs.status()命令用于获取MongoDB副本集的状态信息.它提供了关于副本集中各个节点的详细信息,包括节点的健康状况.角色.选举状态等. 以下是查看一个mongo集群状态返回 ...

  8. Spring —— 注解开发(依赖注入)

    自动装配   引用类型         简单类型      加载properties文件      

  9. LeetCode 730. Count Different Palindromic Subsequences (区间DP)

    题意 给一个字符串S,求它所有子序列中不同非空回文串的数量.字符串由 'a' 'b' 'c' 'd' 四个字母组成. 由于题目要求的是不同回文串. abba 的回文串子序列为 a,b,aba,abba ...

  10. Perfetto分析进阶

    一.Perfetto介绍 Perfetto是Android Q中引入的全新下一代平台级跟踪工具,为Android.Linux和Chrome平台提供了一种通用的性能检测和跟踪分析工具集.其核心是引入了一 ...