说明

该文章是属于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. 总结:redis 突然变慢

    用户量暴增,无法下单,凌晨的夜,静悄悄... 经过查找发现Redis. 获取不到连接资源,并且集群中的单台 Redis 连接量很高. 大量的流量没了 Redis 的缓存响应,直接打到了 MySQL,最 ...

  2. Docker 知识梳理及其安装使用

    Docker 介绍 Docker 是一个强大的工具,用于高效开发.打包和部署应用程序.Docker 是一种容器管理服务.Docker 于 2013 年发布.它是开源的,可用于 Windows.macO ...

  3. 在stable diffussion中完美修复AI图片

    无论您的提示和模型有多好,一次性获得完美图像的情况很少见. 修复小缺陷的不可或缺的方法是图像修复(inpainting).在这篇文章中,我将通过一些基本示例来介绍如何使用图像修复来修复缺陷. 需要的软 ...

  4. Java怎么把多个对象的list的数据合并

    1.示例一:创建几个包含Person对象的List,并将它们合并成一个新的List 在Java中,将多个对象的List合并通常涉及到遍历这些List并将它们的元素添加到一个新的List中.这里,我将给 ...

  5. Angular 16+ 高级教程 – Angular 和其它技术方案的比较

    前言 上一篇我有提到 Angular 适合用于哪些项目,但讲的太含糊,什么大中小项目的...这篇我将更具体的去讲解,Angular 的定位,还有它和其它方案的优缺点. Web 技术可以用来做许许多多不 ...

  6. CSS – 管理

    前言 CSS 有好几种写法. 它们最终出来的效果是一样的, 区别只是在你如何 "写" 和 "读" 或者说开发和维护. 这已经不是如何"实现" ...

  7. CSS – 屏幕, 打印, 分辨率, 物理像素, 逻辑像素, Retina, DPI, PPI 是什么?

    前言 之前就有写过关于 Retina 和 Responsive Image 响应式图片 (responsive image) Retina 显示屏 但写的很烂, 这篇从新整理一下. 参考: 掌握web ...

  8. 支持Docker的底层技术(Namespaces?CGroups?UnionFS?Docker 架构?)(二)

    上一篇介绍了Docker基本概念,这一篇介绍下支持Docker的底层技术 Docker 本质就是宿主机的一个特殊进程,Docker 是通过 namespace 实现资源隔离,通过cgroup 实现资源 ...

  9. 微信小程序点击按钮进行页面跳转

    下面是wxml代码 <button type="primary" bindtap="go">跳转到list页面</button> 下面是 ...

  10. 《Vue.js 设计与实现》读书笔记 - 第7章、渲染器的设计

    第7章.渲染器的设计 7.1 渲染器与响应系统的结合 渲染器需要有跨平台的能力. 在浏览器端会渲染为真实的 DOM 元素. const { effect, ref } = VueReactivity ...