当前位置:首页 » 《随便一记》 » 正文

【.NET Web API+Vue3】前后端分离登录案例

18 人参与  2024年02月20日 14:16  分类 : 《随便一记》  评论

点击全文阅读


本案例偏向业务实战,详细原理请根据参考资料、网上检索等自行学习。

.NET 8 后端

项目目录结构

项目的引用依赖链为:WebAPI => Service => Infrastructure => Model

项目目录结构

本案例中,Test 层不会用到。

EF Core 与 Identity

在 Model 层中安装 Microsoft.AspNetCore.Identity.EntityFrameworkCore Nuget 包

在 Model.DbEntities 目录下新建 ApplicationRoleApplicationUserUserInfo 实体类

using Microsoft.AspNetCore.Identity;namespace CampusServicePlatform.Model.DbEntities{    public class ApplicationRole : IdentityRole<Guid>    {    }}
using Microsoft.AspNetCore.Identity;namespace CampusServicePlatform.Model.DbEntities{    public class ApplicationUser : IdentityUser<Guid>    {        public virtual UserInfo? UserInfo { get; set; }    }}
namespace CampusServicePlatform.Model.DbEntities{    public class UserInfo    {        public int Id { get; set; }        public string? Nickname { get; set; }        public string? Avatar { get; set; }        public DateTime? CreatedTime { get; set; }        public Guid UserId { get; set; }        public virtual ApplicationUser? User { get; set; }    }}

在 Infrastructure 层中安装 Microsoft.EntityFrameworkCore.DesignMicrosoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCore.Tools NuGet 包

在 Infrastructure.DbEntityConfigs 目录下新建 ApplicationRoleEntityConfigApplicationUserEntityConfigUserInfoEntityConfig 实体配置类

using CampusServicePlatform.Model.DbEntities;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace CampusServicePlatform.Infrastructure.DbEntityConfigs{    public class ApplicationRoleEntityConfig : IEntityTypeConfiguration<ApplicationRole>    {        public void Configure(EntityTypeBuilder<ApplicationRole> builder)        {        }    }}
using CampusServicePlatform.Model.DbEntities;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace CampusServicePlatform.Infrastructure.DbEntityConfigs{    public class ApplicationUserEntityConfig : IEntityTypeConfiguration<ApplicationUser>    {        public void Configure(EntityTypeBuilder<ApplicationUser> builder)        {        }    }}
using CampusServicePlatform.Model.DbEntities;using Microsoft.EntityFrameworkCore;using Microsoft.EntityFrameworkCore.Metadata.Builders;namespace CampusServicePlatform.Infrastructure.DbEntityConfigs{    public class UserInfoEntityConfig : IEntityTypeConfiguration<UserInfo>    {        public void Configure(EntityTypeBuilder<UserInfo> builder)        {            builder.Property(e => e.UserId).IsRequired();                        // 指定外键            builder.HasOne(e => e.User).WithOne(e => e.UserInfo).HasForeignKey<UserInfo>(e => e.UserId).HasPrincipalKey<ApplicationUser>(e => e.Id);                        // 创建非聚集索引,加快 GUID 列连接查询速度            builder.HasIndex(e => e.UserId).IsUnique().IsClustered(false);        }    }}

在 Infrastructure.DbEntityConfigs 目录下新建 ApplicationDbContext EF Core 数据库上下文

using CampusServicePlatform.Model.DbEntities;using Microsoft.AspNetCore.Identity.EntityFrameworkCore;using Microsoft.EntityFrameworkCore;namespace CampusServicePlatform.Infrastructure.DbEntityConfigs{    public class ApplicationDbContext : IdentityDbContext<ApplicationUser, ApplicationRole, Guid>    {        public virtual DbSet<UserInfo> UserInfos { get; set; }        public ApplicationDbContext() { }        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)        {        }        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)        {            if (!optionsBuilder.IsConfigured)            {                optionsBuilder.UseSqlServer("本地数据库连接字符串");            }        }        protected override void OnModelCreating(ModelBuilder modelBuilder)        {            base.OnModelCreating(modelBuilder);            modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);        }    }}

在 WebAPI.Program.cs 中配置 DbContext 与 Identity

此处的数据库连接字符串获取不再赘述,详情请移步另一篇文章查看:https://blog.csdn.net/Felix61Felix/article/details/134634047。

var builder = WebApplication.CreateBuilder(args);// ...builder.Services.AddDbContext<ApplicationDbContext>(options =>{    string? connectionString = builder.Configuration.GetConnectionString("Default");    options.UseSqlServer(connectionString);});builder.Services    .AddIdentity<ApplicationUser, ApplicationRole>(options =>    {        // 在这里仅要求简单的限制,即密码长度为 6,其他限制请自行配置        options.Password.RequiredLength = 6;        options.Password.RequireDigit = false;        options.Password.RequireLowercase = false;        options.Password.RequireUppercase = false;        options.Password.RequireNonAlphanumeric = false;    })    .AddEntityFrameworkStores<ApplicationDbContext>()    .AddDefaultTokenProviders();// ...var app = builder.Build();

进入程序包管理控制台,将解决方案的启动项目、程序包管理控制台的默认项目都切换成 Infrastructure

将程序包管理控制台的默认项目切换成 Infrastructure

解决方案的启动项目切换成 Infrastructure

在程序包管理控制台中依次输入 EF Core 迁移命令:Add-Migration InitialCreate -OutputDir DatabaseEntityConfig/_MigrationsUpdate-database,以生成数据库

JWT

在 Infrastructure 层中新建 JwtHelper

using CampusServicePlatform.Model.DbEntities;using Microsoft.AspNetCore.Identity;using Microsoft.Extensions.Configuration;using Microsoft.IdentityModel.Tokens;using System.IdentityModel.Tokens.Jwt;using System.Security.Claims;using System.Text;namespace CampusServicePlatform.Infrastructure{    public class JwtHelper    {        private readonly IConfiguration _configuration;        private readonly UserManager<ApplicationUser> _userManager;        public JwtHelper(IConfiguration configuration, UserManager<ApplicationUser> userManager)        {            _configuration = configuration;            _userManager = userManager;        }        public string GenerateJwtToken(ApplicationUser? user, UserInfo? userInfo, IList<string>? roles)        {            var claims = new List<Claim>            {                new Claim(ClaimTypes.Name, user.UserName),                new Claim("avatar", userInfo.Avatar),                new Claim("nickname", userInfo.Nickname)            };            foreach (var role in roles)            {                claims.Add(new Claim(ClaimTypes.Role, role));            }            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value));            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256Signature);            var jwtSecurityToken = new JwtSecurityToken(                claims: claims,                // 过期时间,单位是“分”                expires: DateTime.Now.AddMinutes(60 * 24 * 7),                notBefore: DateTime.Now,                issuer: _configuration.GetSection("Jwt:Issuer").Value,                audience: _configuration.GetSection("Jwt:Audience").Value,                signingCredentials: signingCredentials                );            var jwtToken = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);            return jwtToken;        }        public async Task<bool> CheckJwtToken(string? jwtToken)        {            var jwtTokenHandler = new JwtSecurityTokenHandler();            var issuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration.GetSection("Jwt:Key").Value));            var validationParameters = new TokenValidationParameters            {                ValidateIssuerSigningKey = true,                IssuerSigningKey = issuerSigningKey,                ValidateIssuer = true,                ValidIssuer = _configuration.GetSection("Jwt:Issuer").Value,                ValidateAudience = true,                ValidAudience = _configuration.GetSection("Jwt:Audience").Value,                ClockSkew = TimeSpan.Zero            };            var principal = jwtTokenHandler.ValidateToken(jwtToken, validationParameters, out var securityToken);            if (principal.Identity?.IsAuthenticated != true)            {                return false;            }            return true;        }    }}

在 WebAPI 层中编写 JWT 的配置文件

{  "Jwt": {    "Key": "自行设置密钥,推荐写 GUID 值",    "Issuer": "签发者,推荐写 API 地址",    "Audience": "接收者,推荐写前端地址"  }}

在 WebAPI.Program.cs 中配置 JWT

var builder = WebApplication.CreateBuilder(args);// ...builder.Services    .AddAuthentication(options =>    {        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;    })    .AddJwtBearer(options =>    {        options.TokenValidationParameters = new TokenValidationParameters        {            ValidateActor = true,            ValidateIssuer = true,            ValidateAudience = true,            RequireExpirationTime = true,            ValidateIssuerSigningKey = true,            ValidIssuer = builder.Configuration.GetSection("Jwt:Issuer").Value,            ValidAudience = builder.Configuration.GetSection("Jwt:Audience").Value,            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection("Jwt:Key").Value))        };    });builder.Services.AddScoped<JwtHelper>();// ...var app = builder.Build();

Service

在 Service 层新建 IAccountService 接口

using CampusServicePlatform.Model.DTO;namespace CampusServicePlatform.Service{    public interface IAccountervice    {        Task<bool> CheckJwtToken(string? jwtToken);        Task<string?> SignIn(UserRequest userRequest);        Task<bool> SingUp(UserRequest userRequest);    }}

在 Service 层新建 AccountService 实现类

using CampusServicePlatform.Infrastructure;using CampusServicePlatform.Infrastructure.DbEntityConfigs;using CampusServicePlatform.Model.DbEntities;using CampusServicePlatform.Model.DTO;using CampusServicePlatform.Model.Enum;using Microsoft.AspNetCore.Identity;using Microsoft.EntityFrameworkCore;namespace CampusServicePlatform.Service{    public class AccountService : IAccountervice    {        private readonly ApplicationDbContext _context;        private readonly UserManager<ApplicationUser> _userManager;        private readonly RoleManager<ApplicationRole> _roleManager;        private readonly JwtHelper _jwtHelper;        private readonly StringHelper _stringHelper;        public AccountService(ApplicationDbContext context, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, JwtHelper jwtHelper, StringHelper stringHelper)        {            _context = context;            _userManager = userManager;            _roleManager = roleManager;            _jwtHelper = jwtHelper;            _stringHelper = stringHelper;        }        public async Task<bool> SingUp(UserRequest userRequest)        {            var user = new ApplicationUser            {                UserName = userRequest.UserName            };            var result = await _userManager.CreateAsync(user, userRequest.Password);            if (!result.Succeeded)            {                throw new ApplicationException(_stringHelper.ListItemToString(result.Errors, "Description"));            }            if (userRequest.Roles.Count == 0)            {                userRequest.Roles.Add(RoleEnum.Normal.ToString());            }            result = await _userManager.AddToRolesAsync(user, userRequest.Roles);            if (!result.Succeeded)            {                throw new ApplicationException(_stringHelper.ListItemToString(result.Errors, "Description"));            }            var userInfo = new UserInfo            {                UserId = user.Id,                Avatar = "favicon.ico",                Nickname = user.UserName,                CreatedTime = DateTime.Now,            };            _context.UserInfos.Add(userInfo);            await _context.SaveChangesAsync();            return true;        }        public async Task<string?> SignIn(UserRequest userRequest)        {            var user = await _userManager.FindByNameAsync(userRequest.UserName);            if (user == null)            {                throw new KeyNotFoundException("用户名或密码错误");            }            var result = await _userManager.CheckPasswordAsync(user, userRequest.Password);            if (!result)            {                throw new KeyNotFoundException("用户名或密码错误");            }            var userInfo = await _context.UserInfos.SingleOrDefaultAsync(e => e.UserId == user.Id);            var roles = await _userManager.GetRolesAsync(user);            var jwtToken = _jwtHelper.GenerateJwtToken(user, userInfo, roles);            return jwtToken;        }        public Task<bool> CheckJwtToken(string? jwtToken)        {            return _jwtHelper.CheckJwtToken(jwtToken);        }    }}

在 Infrastructure 层新建 StringHelper 辅助类

namespace CampusServicePlatform.Infrastructure{    public class StringHelper    {        public string ListItemToString<T>(IEnumerable<T> collection, string? propertyName = null)        {            if (collection == null)            {                throw new ArgumentNullException(nameof(collection));            }            if (string.IsNullOrEmpty(propertyName))            {                if (typeof(T) == typeof(string))                {                    return string.Join(";", collection.Cast<string>());                }                else                {                    throw new ArgumentException("属性名不能为空", nameof(propertyName));                }            }            var property = typeof(T).GetProperty(propertyName);            if (property == null)            {                throw new ArgumentException($"对象不存在名为 {propertyName} 的属性");            }            return string.Join(";", collection.Select(item => property.GetValue(item)));        }    }}

在 WebAPI.Program.cs 中注入 Service 和辅助类

var builder = WebApplication.CreateBuilder(args);// 3...builder.Services.AddScoped<StringHelper>();builder.Services.AddScoped<IAccountervice, AccountService>();// ...var app = builder.Build();

Web API

在 WebAPI.Controllers 目录下新建 AccountController 控制器

using CampusServicePlatform.Model.Attributes;using CampusServicePlatform.Model.DTO;using CampusServicePlatform.Service;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;namespace CampusServicePlatform.WebAPI.Controllers{    [Route("api/[controller]")]    [ApiController]    public class AccountController : ControllerBase    {        private readonly IAccountervice _accountService;        public AccountController(IAccountervice accountService)        {            _accountService = accountService;        }        [HttpPost("sign-up")]        [Transactional]        public async Task<IActionResult> SignUp(UserRequest userRequest)        {            await _accountService.SingUp(userRequest);            return Ok(new ApiResponse            {                Message = "注册成功"            });        }        [HttpPost("sign-in")]        public async Task<IActionResult> SignIn(UserRequest userRequest)        {            var jwtToken = await _accountService.SignIn(userRequest);            return Ok(new ApiResponse            {                Message = "登录成功",                Data = new                {                    JwtToken = jwtToken                }            });        }        [HttpGet("check-jwt-token")]        [Authorize]        public async Task<IActionResult> CheckJwtToken()        {            var jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Substring("Bearer ".Length).Trim();            var result = await _accountService.CheckJwtToken(jwtToken);            if (!result)            {                throw new ApplicationException("无效的Token");            }            return Ok(new ApiResponse            {                Message = "Token验证成功"            });        }    }}

在 Model.DTO 目录下新建 ApiResponseUserRequest 数据传输类

namespace CampusServicePlatform.Model.DTO{    public class ApiResponse    {        public int Code { get; set; } = 200;        public object? Data { get; set; }        public string? Message { get; set; }    }}
namespace CampusServicePlatform.Model.DTO{    public class UserRequest    {        public string? UserName { get; set; }        public string? Password { get; set; }        public IList<string>? Roles { get; set; } = new List<string>();    }}

在 WebAPI 层新建最终一致性事务操作 Filter异常处理 Filter,详情请移步另一篇文章查看:https://blog.csdn.net/Felix61Felix/article/details/134773734。

Vue3+Vite 前端

视图创建

关于动态路由的生成以及视图的创建规则,请移步另一篇文章查看:https://blog.csdn.net/Felix61Felix/article/details/134753420。

登录表单

<template>    <el-card v-loading="userFriendlyTips.isLoading">        <h2>登录</h2>        <el-form @keyup.enter.native.prevent="handleSignIn" label-position="top">            <el-form-item label="账号">                <el-input v-model="formData.userName" />            </el-form-item>            <el-form-item label="密码">                <el-input v-model="formData.password" type="password" autocomplete="new-password" />            </el-form-item>            <el-form-item>                <el-link @click="routePush(getPath('忘记密码'))" type="primary" :underline="false">忘记密码?</el-link>            </el-form-item>            <el-form-item>                <el-button @click="handleSignIn" type="primary" plain class="w-100">登录</el-button>            </el-form-item>            <el-form-item>                <el-button @click="routePush(getPath('注册'))" plain class="w-100">注册</el-button>            </el-form-item>        </el-form>    </el-card></template><script setup lang="ts">import { routeBack, routePush, getPath } from '@/router'import { ref, onBeforeMount } from 'vue'import { signIn } from '@/utils/accountHelper'import { useAccountStore } from '@/stores/useAccountStore'import { getQuery, routeReplace } from '@/router'import { ElNotification } from 'element-plus'import { userFriendlyTips as _userFriendlyTips } from '@/utils/renderHelper'const userFriendlyTips = ref({ ..._userFriendlyTips })const formData = ref({    userName: '',    password: ''})const from = ref('')onBeforeMount(() => {    if (useAccountStore().getJwtToken()) {        routeBack()    }    const query = getQuery()    from.value = query.from})const handleSignIn = async () => {    try {        userFriendlyTips.value.isLoading = true        await signIn(formData.value)        ElNotification({            title: '登录成功',            message: '七天内将自动登录本站!',            type: 'success'        })        if (from.value) {            routeReplace(from.value)        } else {            routeReplace(getPath('主页'))        }    } catch (error) {    } finally {        userFriendlyTips.value.isLoading = false    }}</script><style scoped lang="scss">.el-form-item__content {    justify-content: space-between !important;}</style>

注册表单

<template>    <el-card v-loading="userFriendlyTips.isLoading">        <h2>注册</h2>        <el-form @keyup.enter.native.prevent="handleSignUp" label-position="top">            <el-form-item label="账号" required="true">                <el-input v-model="formData.userName" />            </el-form-item>            <el-form-item label="密码" required="true">                <el-input v-model="formData.password" type="password" autocomplete="new-password" />            </el-form-item>            <el-form-item label="确认密码" required="true">                <el-input v-model="formData.confirmPassword" type="password" />            </el-form-item>            <el-form-item>                <el-link @click="routePush(getPath('登录'))" type="primary" :underline="false">已有账号?</el-link>            </el-form-item>            <el-form-item>                <el-button @click="handleSignUp" type="primary" plain class="w-100">注册</el-button>            </el-form-item>        </el-form>    </el-card></template><script setup lang="ts">import { routePush, getPath, routeReplace } from '@/router'import { ref } from 'vue'import { userFriendlyTips as _userFriendlyTips } from '@/utils/renderHelper'import { ElNotification } from 'element-plus'import { signUp, signIn } from '@/utils/accountHelper'const userFriendlyTips = ref({ ..._userFriendlyTips })const formData = ref({    userName: '',    password: '',    confirmPassword: ''})const handleSignUp = async () => {    try {        userFriendlyTips.value.isLoading = true        if (!(await signUp(formData.value))) {            return        }        await signIn(formData.value)        ElNotification({            title: '注册成功',            message: '七天内将自动登录本站!',            type: 'success'        })        routeReplace(getPath('主页'))    } catch (error) {    } finally {        userFriendlyTips.value.isLoading = false    }}</script>

个人主页

请自行设置跳转测试(按钮、URL编写等)。

<template>    <h1>个人主页</h1>    <h3>昵称:{{ userInfo?.nickname }}</h3>    <h3>头像:{{ userInfo?.avatar }}</h3></template><script setup lang="ts">import { useAccountStore } from '@/stores/useAccountStore'import { computed } from '@vue/reactivity'const userInfo = computed(() => {    return useAccountStore().userInfo})</script>

utils/accountHelper

import { useAccountStore } from '@/stores/useAccountStore'import httpRequester from '@/http-requester'export const signIn = async (formData: any) => {  const response = await httpRequester.post('/api/account/sign-in', formData)  const data = response.data  const jwtToken = data.jwtToken  useAccountStore().setJwtToken(jwtToken)}export const signOut = () => {  useAccountStore().setJwtToken('')}export const checkJwtToken = async () => {  const response: any = await httpRequester.get('/api/account/check-jwt-token')  if (response.code !== 200) {    signOut()  }  return response.code}export const signUp = async (formData: any) => {  const response: any = await httpRequester.post('/api/account/sign-up', formData)  if (response.code !== 200) {    return false  }  return true}

utils/codeHelper

export const deepEncodeURI = (obj: any) => {  for (let prop in obj) {    if (typeof obj[prop] === 'object') {      deepEncodeURI(obj[prop])    } else {      obj[prop] = encodeURIComponent(obj[prop])    }  }  return obj}export const deepDecodeURI = (obj: any) => {  for (let prop in obj) {    if (typeof obj[prop] === 'object') {      deepDecodeURI(obj[prop])    } else {      obj[prop] = decodeURIComponent(obj[prop])    }  }  return obj}

utils/objectHelper

export const isEmpty = (e: any): boolean => {  if (e === null || e === undefined) {    return false  }  if (typeof e === 'boolean') {    return e  }  if (typeof e === 'string' && e.trim() === '') {    return true  }  if ((typeof e === 'number' && isNaN(e)) || e === 0) {    return true  }  if (Array.isArray(e) && JSON.stringify(e) === '[]' && e.length === 0) {    return true  }  if ((typeof e === 'object' && JSON.stringify(e) === '{}') || Object.keys(e).length === 0) {    return true  }  return false}

utils/renderHelper

// 用户友好提示export const userFriendlyTips = {  isLoading: false,  isEmpty: true}// 分页参数export const pagination = {  pageSize: 20,  pageCount: 1,  currentPage: 1}

http-requester/index

import _axios from 'axios'import { ElMessage } from 'element-plus'const env = import.meta.env.MODEconst baseURL = import.meta.env.VITE_API_BASE_URLconst axios = _axios.create({  baseURL: baseURL,  timeout: 10000,  headers: {    'Content-Type': 'application/json',    'Cache-Control': 'max-age=120'  }})axios.interceptors.request.use(  (config) => {    const jwtToken = localStorage.getItem('jwtToken')    if (jwtToken) {      config.headers.Authorization = `Bearer ${jwtToken}`    }    return config  },  (error) => {    return Promise.reject(error)  })axios.interceptors.response.use(  (_response) => {    const response = _response?.data || null    if (env === 'development') {      const code = response?.code || _response?.status || 200      const message = response?.message || _response?.statusText || '请求成功'      ElMessage.success({        message: `【${code}】${message}`,        grouping: true      })    } else if (env === 'production') {      const message = response?.message      if (message) {        ElMessage.success({          message: `${message}`,          grouping: true        })      }    }    return response  },  (error) => {    const _response = error.response || null    const response = _response?.data || null    if (env === 'development') {      const code = response?.code || _response?.status || 400      const message = response?.message || _response?.statusText || '请求失败'      ElMessage.error({        message: `【${code}】${message}`,        grouping: true      })    } else if (env === 'production') {      const message = response?.message || '服务器异常,请稍后重试'      ElMessage.error({        message: `${message}`,        grouping: true      })    }    return response  })const httpRequester = {  get: (url: string, params?: any) => {    return axios.get(url, { params })  },  post: (url: string, data?: any) => {    return axios.post(url, data || {})  },  put: (url: string, data?: any) => {    return axios.put(url, data || {})  },  delete: (url: string, data?: any) => {    return axios.delete(url, data || {})  }}export default httpRequester

router/index

关于动态路由的生成以及视图的创建规则,请移步另一篇文章查看:https://blog.csdn.net/Felix61Felix/article/details/134753420。

import { createRouter, createWebHashHistory, useRoute } from 'vue-router'import { elMenuActiveStore } from '@/stores/elMenuActiveStore'import type { RouteRecordRaw } from 'vue-router'import { ElMessage } from 'element-plus'import { checkJwtToken } from '@/utils/accountHelper'import { isEmpty } from '@/utils/objectHelper'import { deepDecodeURI, deepEncodeURI } from '@/utils/codeHelper'const pageModules = import.meta.glob('../views/**/page.ts', {  eager: true,  import: 'default'})const componentModules = import.meta.glob('../views/**/index.vue', {  eager: true,  import: 'default'})const routes = Object.entries(pageModules).map(([pagePath, config]) => {  const path = pagePath.replace('../views', '').replace('/page.ts', '') || '/'  const name = path.split('/').filter(Boolean).join('-') || 'index'  const compoentPath = pagePath.replace('page.ts', 'index.vue')  return {    path,    name,    component: componentModules[compoentPath],    meta: config  }})const routeMap: Map<string, RouteRecordRaw> = new Map().set('404', {  path: '/:catchAll(.*)',  component: () => import('@/components/ly-components/LyNotFound.vue'),  meta: {    title: '404'  }})routes.forEach((route: any) => {  const title = route.meta.title  routeMap.set(title, route)})const router = createRouter({  history: createWebHashHistory(import.meta.env.BASE_URL),  routes: Array.from(routeMap.values())})const baseTitle = 'XX网'// 路由守卫 => 路由切换之前router.beforeEach(async (to, from, next) => {  // 需要查询参数的页面  if (to.meta.hasQuery && isEmpty(to.query)) {    return next({ path: from.fullPath })  }  // 需要验证的页面  if (to.meta.hasAuthorize) {    const jwtToken = localStorage.getItem('jwtToken')    if (!jwtToken) {      ElMessage.warning({        message: '请先登录',        grouping: true      })      return next({        path: getPath('登录'),        query: deepEncodeURI({          from: to.fullPath        })      })    } else {      const code = await checkJwtToken()      if (code !== 200) {        ElMessage.warning({          message: '身份验证失败,请重新登录',          grouping: true        })        return next({          path: getPath('登录'),          query: deepEncodeURI({            from: to.fullPath          })        })      }    }  }  // 设置页面标题  if (to.meta.title) {    document.title = `${to.meta.title} - ${baseTitle}`  } else {    document.title = baseTitle  }  // el-menu 高亮  elMenuActiveStore().elMenuActive = to.path  // 切换路由  return next()})export default routerexport const routePush = (path: string, query?: any) => {  router.push({ path, query: deepEncodeURI(query) })}export const routeReplace = (path: any) => {  router.replace(path ?? '/')}export const routeBack = () => {  router.go(-1)}export const routeForward = () => {  router.go(1)}export const openUrl = (url: string, target: string = '_blank') => {  window.open(url, target)}export const getPath = (title: string): string => {  return routeMap.get(title)?.path || '/'}export const getRoute = () => {  return useRoute()}export const getQuery = () => {  return deepDecodeURI(getRoute().query)}

stores/useAccountStore

import { computed, ref } from 'vue'import { defineStore } from 'pinia'import type JwtTokenPayload from '@/types/jwtTokenPayload'import type UserInfo from '@/types/userInfo'export const useAccountStore = defineStore('account', () => {  const _jwtToken = ref()  const getJwtToken = () => {    return Object.freeze(_jwtToken.value)  }  const setJwtToken = (jwtToken: string) => {    _jwtToken.value = jwtToken    if (jwtToken) {      localStorage.setItem('jwtToken', jwtToken)    } else {      localStorage.removeItem('jwtToken')    }  }  const userInfo = computed((): UserInfo | null => {    if (!_jwtToken.value) {      return null    }    const jwtTokenPayload: JwtTokenPayload = JSON.parse(atob(_jwtToken.value.split('.')[1]))    return Object.freeze({      nickname: jwtTokenPayload.nickname,      avatar: jwtTokenPayload.avatar    })  })  return { setJwtToken, getJwtToken, userInfo }})

App.vue

import { onMounted } from 'vue'import { useAccountStore } from '@/stores/useAccountStore'onMounted(async () => {  getAccount()})const getAccount = async () => {  const jwtToken = localStorage.getItem('jwtToken') || ''  if (jwtToken) {    useAccountStore().setJwtToken(jwtToken)    ElNotification({      title: '自动登录',      message: `${useAccountStore().userInfo?.nickname} 你好,欢迎回来!?`,      type: 'success'    })  } else {    ElNotification({      title: '提示',      message: '登录以解锁更多功能!',      type: 'info'    })  }}

整体流程图

整体流程图

参考资料

[1] 杨中科. ASP.NET Core技术内幕与项目实战:基于DDD与前后端分离[M]. 北京: 人民邮电出版社, 2022.

[2] 杨中科. .NET 6教程,.Net Core 2022视频教程,杨中科主讲[Z/OL]. https://www.bilibili.com/video/BV1pK41137He?p=144. 2020.

[3] Foad Alavi. Authenticating Web API Using ASP .Net Identity and JSON Web Tokens (JWT)[Z/OL]. https://www.youtube.com/watch?v=99-r3Y48SYE/. 2023.


点击全文阅读


本文链接:http://zhangshiyu.com/post/69196.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1