基于.NET7 / Vue2分离式的后端解决方案网站开发

目录

1 AspNetCoreWeb接口开发

1.1 项目创建与介绍

1.1.1创建WebApi项目

1.1.2项目基本介绍

1.2 项目目录结构介绍

​编辑

1.3 Swagger配置和日志功能

1.3.1 接口工具Swagger配置修改

1.3.2 配置log4net日志记录

1.4 连接数据库

1.4.1 分页查询(测试日志功能)

1.5 Jwt权限管理

1.5.1 Jwt配置和注册

1.5.2 测试

2 基于Vue的前端组件页面开发

2.1 环境和配置

2.2 自定义开发前引入

2.2.1 组件开发介绍

2.2.2 前后端联调

2.3 登录模块

2.4 登出

2.5 用户管理模块

2.5.1 用户列表渲染

2.5.2 用户添加

2.5.3 用户编辑

2.5.4 用户搜索

2.5.5 用户删除

2.6 日志管理模块

2.6.1 日志数据渲染

2.6.2 日志搜索与删除

2.7 员工管理

2.8 首页数据展示

3 总结


相关内容:

第一篇:原生JS到Vue前端工程化开发_Maxlec的博客-CSDN博客

第二篇:VueRouter和Vuex插件的使用以及项目的打包部署_Maxlec的博客-CSDN博客

1 AspNetCoreWeb接口开发

1.1 项目创建与介绍

1.1.1创建WebApi项目

添加包映射规则或者直接添加映射格式为" * "。

1.1.2项目基本介绍

我们来看一下项目启动类,从.Net 6开始为我们带来的一种全新的引导程序启动的方式。与之前的拆分成Program.cs和Startup不同,整个引导启动代码都在Program.cs中。

(可忽略)与SpringBoot启动类不同的是,SpringBoot启动类中直接启动已创建好的项目,而.Net项目的启动类需要通过项目生成器创建项目实例,还需要手动通过生成器配置相关功能类,然后通过实例调用方法进行使用。

    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            // 添加业务到容器中
            builder.Services.AddControllers();
            // 配置Swagger
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();
            var app = builder.Build();
            // 配置HTTP请求管道
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }
            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();
            app.Run();
        }
    }

1.2 项目目录结构介绍

如下就是我创建后端WebApi项目的目录结构:

包名 说明
Entity 定义实体类,方便进行数据库表的映射。
Service 编写业务逻辑代码,一般还有一个专门进行数据库读取的,但此项目不那么复杂,在业务类中进行数据库操作后直接进行业务逻辑处理的编写,并返回结果。
Controller 定义WebApi接口,方便接收前端发送的请求和传递参数,并调用项目业务方法进行处理。
Utils 定义和配置相关的工具类,比如Jwt,AutoMapper,Swagger等等。
CfgFile 定义配置文件,比如log4net.Config日志配置文件等。
Result 定义规范的通用的结果集,比如状态码code,响应信息。方便业务处理后统一的收集处理,也方便前端接收的数据之后的处理。加快和规范项目的开发。

1.3 Swagger配置和日志功能

1.3.1 接口工具Swagger配置修改

dotNet网络接口项目已经自带下载配置了Swagger,但只是做了简单的配置,我们需要自己重新配置,增加可扩展性。

如下是Swagger提供的接口文档页面:

首先我们需要自己新建一个类,再通过生成器调用扩展方法执行配置,项目实例调用扩展方法进行使用。

在工具类中定义:

/// <summary>
/// 封装Swagger的配置和使用
/// 配置Swagger以及自定义Swagger描述接口信息
/// </summary>
public static class CustomSwaggerExt
{
    // 自定义Swagger的扩展方法(静态类,静态方法,this=>扩展方法)
    public static void AddSwaggerExt(this WebApplicationBuilder builder)
    {
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen(option =>
        {
            // 文档主题
            option.SwaggerDoc("v1", new OpenApiInfo()
            {
                Title = "后台管理Api文档",
                Version = "v1",
                Description = "文档描述"
            });
​
​
            // xml文档路径
            var file = Path.Combine(AppContext.BaseDirectory, "IMChatAPI.xml");
            // true: 显示控制器接口注释,为库生成XML文档文件
            option.IncludeXmlComments(file, true);
​
​
            // 按接口名称顺序排序
            option.OrderActionsBy(o => o.RelativePath);
​
​
            // 添加安全定义--配置支持token授权机制
            option.AddSecurityDefinition("Bearer",new OpenApiSecurityScheme{
                Description = "请输入token,格式为 Bearer xxx(注意中间空格)",
                Name = "Authorization",
                In = ParameterLocation.Header,
                Type = SecuritySchemeType.ApiKey,
                BearerFormat = "JWT",
                Scheme = "Bearer"
            });
​
​
            // 添加安全要求
            option.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference =  new OpenApiReference()
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    },
​
                    new string[]{}
                }
            });
        });
    }
​
    // 启用配置的扩展方法
    public static void UseSwaggerExt(this WebApplication app)
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
}

在启动配置类中调用就可以了:

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
​
        builder.Services.AddControllers();
        
        // 调用扩展方法
        builder.AddSwaggerExt();
​
        var app = builder.Build();
​
        if (app.Environment.IsDevelopment())
        {
            app.UseSwaggerExt();
        }
​
        app.UseHttpsRedirection();
​
        app.UseAuthorization();
​
​
        app.MapControllers();
​
        app.Run();
    }
}

在控制器对应接口添加注释后显示如下,可以看到自定义配置类的扩展效果:

1.3.2 配置log4net日志记录

和Nlog相似,Log4net可以做到独立的连接数据库,在系统的任何地方都能够使用。

项目运行过程中需要进行监控,日志记录是项目必备的功能。

首先同样我们需要添加log4net的依赖包,以及日志生成器依赖包(用于配置log4net的扩展包Logging):

然后在启动类中通过生成器配置log4net

        // 配置log4net
        builder.Logging.AddLog4Net("CfgFile/log4net.Config");

在项目中新建配置文件夹,在其中添加log4net.config文件,文件属性设置为始终复制(在运行时才会保存到项目文件夹中)。如下就是log4net配置文件格式,包括各种日志输出方式,其中关于数据库存储日志的方式需要自行修改相关的信息:

<?xml version="1.0" encoding="utf-8"?>
<log4net>
    <!-- 控制台日志配置 -->
    <appender name="RollingConsole" type="log4net.Appender.ConsoleAppender">
        <!-- 日志输出格式 -->
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%5level [%thread] (%file:%line) - %message%newline" />
        </layout>
    </appender>
​
    
    <!-- 文件存储日志配置 -->
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender">
        <!-- 保存文件的名称 -->
        <file value="D:\log.log" />
        
        <!-- 追加日志内容 -->
        <appendToFile value="true" />
        
        <!-- 备份时为文件名加后缀-->
        <datePattern value="yyyyMMdd.TXT"/>
        
        <!-- 防止多线程时不能写入日志,线程非安全-->
        <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
        
        <!-- 文件的编码方式 -->
        <param name="Encoding" value="UTF-8"/>
        
        <!-- 每个文件的大小 -->
        <maximumFileSize value="2MB" />
        
        <!-- 保存文件的最大数量 -->
        <maxSizeRollBackups value="2" />
        
        <!-- 日志输出格式 -->
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%level %thread %logger - %message%newline" />
        </layout>
    </appender>
    
    
    <!-- SqlServer数据库存储日志 -->
    <appender name="RollingSqlServer" type="log4net.Appender.ADONetAppender">
        <!--日志缓存写入条数 设置为0时只要有一条就立刻写到数据库 生产环境可改为10-100写入一次-->
        <bufferSize value="1" />
        <!-- 日志数据库连接类型(此处非常重要,写错会导致无法写入数据库) -->
        <connectionType value="System.Data.SqlClient.SqlConnection, System.Data, Version=1.0.5000.0,Culture=neutral,PublicKeyToken=23ec7fc2d6eaa4a5"/>
        <!--日志数据库连接字符串-->
        <connectionString value="DATABASE=xxxx;SERVER=.;UID=sa;PWD=xxxx;Connect Timeout=15;" />
        
        <!--日志数据库脚本-->
        <commandText value="INSERT INTO Log ([Establish],[Thread],[Level],[Logger],[Message]) VALUES (@log_date,@Thread,@Level,@Logger,@Message)" />
​
        <!--日志时间 -->
        <parameter>
            <parameterName value="@log_date" />
            <dbType value="DateTime" />
            <layout type="log4net.Layout.RawTimeStampLayout" />
        </parameter>
        <!--线程号-->
        <parameter>
            <parameterName value="@Thread" />
            <dbType value="String" />
            <size value="255" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%t" />
            </layout>
        </parameter>
        <!--日志类型-->
        <parameter>
            <parameterName value="@Level" />
            <dbType value="String" />
            <size value="255" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%p" />
            </layout>
        </parameter>
        <!--日志对象名称-->
        <parameter>
            <parameterName value="@Logger" />
            <dbType value="String" />
            <size value="20" />
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%logger" />
            </layout>
        </parameter>
        <!-- 自定义信息 -->
        <parameter>
            <parameterName value="@Message"/>
            <dbType value="String"/>
            <size value="4000"/>
            <layout type="log4net.Layout.PatternLayout">
                <conversionPattern value="%m"/>
            </layout>
        </parameter>
        
    </appender>
​
    
    <!-- 调用appender使其生效 -->
    <root>
        <level value="ALL" />
        <appender-ref ref="RollingConsole" />
        <appender-ref ref="RollingFile" />
        <appender-ref ref="RollingSqlServer"/>
    </root>
</log4net>

因为要向sqlserver中插入数据,所以需要安装对应的依赖包SqlClient(注意config文件中是System还是Microsoft)进行数据库连接:

新建一个LogController类,当控制器被创建时生成日志错误信息,测试log4net配置是否生效:

        public LogController(ILogger<LogController> logger, LogService logService)
        {
            this.logService = logService;
​
            // 测试控制器构造器创建控制器对象时,打印日志
            logger.LogInformation($"{this.GetType().FullName}对象被创建");
            logger.LogDebug($"{this.GetType().FullName}对象被创建");
            logger.LogError($"{this.GetType().FullName}对象被创建");
            logger.LogWarning($"{this.GetType().FullName}对象被创建");
            logger.LogInformation($"{this.GetType().FullName}对象被创建");
            
        }

运行项目,如下图我们可以通过Swagger调试该控制价的接口,当控制器被创建就会将生成的日志插入到对应的数据库表中:

日志生成并插入数据库成功:

在获取日志方法中

1.4 连接数据库

我没有使用EF Core连接数据库和进行数据映射,而是第三方框架SqlSugar,它是一款简单易用的高性能轻量级开源ORM框架。SqlSugar的性能是EF数倍,另外在批量操作和一对多查询上也有不错的SQL优化。(使用方式类似于Mybatis)

ORM框架:ORM(Object Relational Mapping,对象关系映射)是为了解决面向对象与关系数据库存在的互不匹配现象的一种技术。通过使用描述对象和数据库之间映射的元数据将程序中的对象自动持久化到关系数据库中。ORM框架的本质作用是优化和简化编程中操作数据库的代码。

同样的作为第三方框架,我们需要下载安装SqlSugar对应dotNet连接数据库依赖包SqlSugarCore。

然后还需要在配置启动类中通过生成器调用AddTransient配置连接服务并生成SqlSugarClient实例对象,这样就可以随时调用了。当项目启动后,IOC容器会通过反射实现注入实例对象,即注册数据库连接服务和生成SqlSugarClient实例对象。

AddTransient瞬时模式:每次请求,都获取一个新的实例。即使同一个请求获取多次也会是不同的实例。

            #region IOC
                // 配置注册数据库连接服务
                builder.Services.AddTransient<SqlSugarClient>(servicePrivider =>
                {
                    ConnectionConfig connection = new ConnectionConfig()
                    {
                        // 定义数据库连接字符串,连接字符串也可以在项目设置文件中声明,方便更换
                        ConnectionString = 
                            "Data Source=.;Initial Catalog=xxxx;Integrated Security=True;User ID=sa", 
                        // 指定数据库类型
                        DbType = DbType.SqlServer,
                        IsAutoCloseConnection = true,
                        InitKeyType = InitKeyType.Attribute
                    };
​
                    return new SqlSugarClient(connection);
                });
​
                // 配置业务层
                builder.Services.AddTransient<LogService>();
            #endregion

1.4.1 分页查询(测试日志功能)

在业务层中定义数据库分页查询的操作方法测试SqlSugar是否配置成功。

通过上面的配置后返回连接客户端对象。然后我们可以在业务层通过构造器传递注入SqlSugarClient对象,然后调用其中操作数据库的方法插入数据或者返回数据等。

(可忽略)SpringBoot程序中会自动扫描定义sql语句的接口,然后通过JavaBean自动创建发送语句的平台对象。所以在SpringBoot使用Mybatis操作数据库的操作步骤是,只需要定义对应的mapper方法,然后在业务层或者控制器中调用接口中相应的操作方法即可。不用自己在每个mapper接口的方法中通过使用执行对象调用sql执行方法。

如下就是我们在业务层中使用SqlSugarClient执行对象调用数据库分页查询的sql执行方法,返回json格式的结果集:

    public class LogService
    {
        private readonly SqlSugarClient sqlSugarClient;
​
        // 通过构造器传递注入数据库连接对象
        public LogService(SqlSugarClient sqlSugarClient)
        {
            this.sqlSugarClient = sqlSugarClient;
        }
​
        public IActionResult GetLog(int pageNumber, int pageSize)
        {
            List<Log> logPageList = sqlSugarClient.Queryable<Log>().ToPageList(pageNumber, pageSize);
​
            return new JsonResult(logPageList);
        }
    }

然后同样地,我们可以在新建的LogController控制器中通过构造器传递注入LogService对象,然后调用LogService中定义方法:

不在控制器中直接使用SqlSugarClient执行对象调用执行方法,是因为控制器是与前端交互的接口,需要定义很多接收前端请求的方法,需要经常操作,不能让控制器看起来太臃肿。(但像LogController这种控制器,它本身没有很多的业务,所以也可以直接在控制器中处理,这里通过调用LogService中的执行方法是为了说明前面的代码方式)

public class LogController
{
    private readonly LogService logService;
​
    
    public LogController(ILogger<LogController> logger, LogService logService)
    {
        this.logService = logService;
​
        // 测试控制器构造器创建控制器对象时,打印日志
        logger.LogInformation($"{this.GetType().FullName}对象被创建");
        logger.LogDebug($"{this.GetType().FullName}对象被创建");
        logger.LogError($"{this.GetType().FullName}对象被创建");
        logger.LogWarning($"{this.GetType().FullName}对象被创建");
        logger.LogInformation($"{this.GetType().FullName}对象被创建");
        
    }

​
    /// <summary>
    /// 获取日志记录
    /// </summary>
    /// <param name="pageNumber"></param>
    /// <param name="pageSize"></param>
    /// <returns></returns>
    [HttpGet("{pageNumber:int}/{pageSize:int}")]
    public IActionResult Get(int pageNumber, int pageSize)
    {
        return logService.GetLog(pageNumber, pageSize);
    }
}

同样地,因为业务层LogService也是通过构造器传递注入到控制器中进行调用,所以也需要在配置启动类中进行注册,否则会报错:

// 配置业务层
builder.Services.AddTransient<LogService>();

运行项目,执行查询第6页的3条数据的分页查询方法:

可以看到,成功返回分页数据:

1.5 Jwt权限管理

1.5.1 Jwt配置和注册

首先我们需要在Utils目录下,定义和配置Jwt工具类。

public static class JwtServices
{
    // 定义基本属性配置的内部类,方便修改
    class JwtSettings
    {
        /// <summary>
        /// 颁发者
        /// </summary>
        public string Issuer { get; set; } = "http://localhost:5064";
        /// <summary>
        /// 使用者
        /// </summary>
        public string Audience { get; set; } = "http://localhost:9528";
        /// <summary>
        /// 密钥
        /// </summary>
        public string SecretKey { get; set; } = "123abcdefg123abcdefg123abcdefg";
    }
​
    static readonly JwtSettings jwtSettings = new();
​
    // 定义生成token的方法,在其中初始化一个JwtSecurityToken并返回,后面调用时只需要传入载荷即可
    public static JwtSecurityToken GetToken(Claim[] claims)
    {
        var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
        // 设置的有效时常为1分钟便于测试
        return new JwtSecurityToken(jwtSettings.Issuer, jwtSettings.Audience, claims, DateTime.Now, DateTime.Now.AddMinutes(6), creds);
    }
​
    // 生成规则,需要在配置启动类中进行注册后生效,也可以将这一段直接在配置启动类中进行注册。
    public static IServiceCollection AddJwtAuthentication(this IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        }).AddJwtBearer(o =>
        {
            o.TokenValidationParameters = new TokenValidationParameters
            {
                // 颁发者
                ValidIssuer = jwtSettings.Issuer, 
                // 使用者
                ValidAudience = jwtSettings.Audience, 
                // 颁发者密钥
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)), 
                ValidateIssuerSigningKey = true, //  验证颁发者密钥
                ValidateIssuer = true,  // 验证颁发者
                ValidateLifetime = true,  // 验证使用时限 
                // 缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
                ClockSkew = TimeSpan.FromDays(5) 
            };
        });
        return services;
    }
}

然后需要在配置启动类中进行注册我们配置的token生成规则:

// 配置JWT
builder.Services.AddJwtAuthentication();

还需要进行了权限的认证和授权,说明当用户访问设置权限的接口时需要携带正确的token,比如获取用户信息时。

1.5.2 测试

最后在控制器UserController中定义如下接口进行测试,接收表单,表单中一般有用户的账号和密码。

[HttpPost("user/login")]
public IActionResult Login([FromBody] Login login)
{
    return new JsonResult(userService.GetLoginToken(login));
}

在用户业务层的GetLoginToken方法中,首先通过前面配置的SqlSugar数据库操作工具根据用户账号进行查询,是否包含此用户(这里可以通过判断返回的List对象中的元素个数),判断通过后进行密码校验,都通过后调用JwtServices中的定义的GetToken方法,传入用户的账号作为载荷,尽量不传入密码作为载荷,token的编码可能会被破解。最后封装在结果集中返回。

// 传入账户和密码,生成token
public TokenResult<string> GetLoginToken([FromBody] Login login)
{
    List<User> list = sugarClient.Queryable<User>().Where(it => it.username == login.username).ToList();
    TokenResult<string> result = new TokenResult<string>();
​
    if (list.Count > 0)
    {
        if (list[0].password == login.password)
        {
            string token = new JwtSecurityTokenHandler().WriteToken(JwtServices.GetToken(new Claim[] { new Claim(nameof(login.username), login.username) }));
            returnToken = token;
​
            result.code = 20000;
            result.success = true;
            result.message = "登录成功";
            Dictionary<string, string> headers = new Dictionary<string, string>
        {
            { "token", returnToken }
        };
​
            result.data = headers;
        }
        else
        {
            result.code = 0;
            result.success = false;
            result.message = "密码错误";
        }
    }
    else
    {
        result.code = 0;
        result.success = false;
        result.message = "账号错误";
    }
    return result;
}

如下是通过Swagger文档调试工具调试的结果:(后面进行前端开发时会使用到token)

2 基于Vue的前端组件页面开发

2.1 环境和配置

开发环境

编码工具:VS Code

依赖管理:NPM

前端vue项目是基于Vue-Element-Admin的基础方案进行开发,具体下载和引入可以看《原生JS到Vue前端工程化开发和部署》这篇第十章的讲述,包括下载安装,项目启动,以及项目的理解。

首先看一下该基础方案的开发目录结构:

2.2 自定义开发前引入

2.2.1 组件开发介绍

如下是源代码文件目录,首先App.vue就是vue前端根组件页面,该组件是整个vue单例项目唯一挂载到html页面中的组件,所以我们所有的组件开发都是基于这个根组件。在App组件中定义了一个路由占位符,当点击相关子路由时,会展示与该路由关联的组件,将子组件页面通过App组件挂载渲染到html上,因为App组件已经在main.js执行文件中进行挂载绑定到指定的HTML标签上。这个组件也就可以叫做子组件,需要我们在如下views目录下自定义。

如下是本项目自定义的一些子组件,dashboard下是网站首页,form下是员工管理页面,login下是登录页面,table下index.vue是用户页面,log.vue就是日志查看和管理页面。

2.2.2 前后端联调

前后端联调的第一个问题就是前端的发送网络请求和后端接收处理请求然后响应的问题。前端用的是axios发送异步请求,(也就是ajax的请求框架)。我们直接看一下axios请求的定义。在request.js文件中创建了一个实例,之后的所有请求都有它发送和接收。其中定义了请求路径base,其实就是后端运行的地址和端口。然后下面就是请求拦截器和响应拦截器的定义。

// 创建发送异步请求的实例
const service = axios.create({
  // 拼接开发环境中后端的地址和端口
  baseURL: process.env.VUE_APP_BASE_API, 
  timeout: 5000 // 超时5秒终止请求
})

然后第二就是这个解决同源策略中的跨域问题。因为两个独立项目不同用网络端口,所以涉及到了跨域,需要自行修改一下这个网络安全策略,我是在后端的启动配置类中简单加入中间件进行配置,比如像允许包含所有请求头,所有不同源,所有请求方法的请求访问。

// 中间件解决跨域问题
builder.Services.AddCors(option =>
{
    option.AddPolicy("allcore", coreBuilder =>
    {
        coreBuilder.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod();
    });
});

2.3 登录模块

首先先来看登录组件页面。用户名和密码使用双向数据绑定,并设置默认值。输入数据时在前端做了简单的检验,比如位数校验。

      loginForm: {
        username: 'Maxlec',
        password: '123456'
      },

当我们输入数据后点击登录按钮,则触发处理登录的相关方法,如下:

    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          this.$store.dispatch('user/login', this.loginForm).then((res) => {
                this.$router.push({ path: this.redirect || '/' })
                this.loading = false
                this.$message({
                    message: '登录成功',
                    type: 'success'
                })
          }).catch(() => {
            this.loading = false
          })
        } else {
          console.log('提交失败')
          return false
        }
      })
    }

在方法中通过初步校验后,执行vue状态管dispatch派遣的方式触发和传递相关数据给store中的actions方法和,在actions中调用相应的网络请求接口并传递数据。(因为登录操作相关数据的处理比较繁琐,所以通过vuex状态进行管理),这部分可以看《原生JS到Vue前端工程化开发和部署》这篇第八章。

可以看到,在vuex状态state的action中进行调用了异步请求方法,传递相关参数对象。在响应数据取出token并提交mutations方法修改state数据状态,比如设置保存token到本地。

  // 用户登录,在这发送异步请求
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
            const { data } = response
​
            // 注意加上Bearer,注意空格
            commit('SET_TOKEN', 'Bearer '+data.token) 
            setToken('Bearer '+data.token)
            resolve()
            console.log(data.token)
      }).catch(error => {
            reject(error)
      })
    })
  },

在后端接口调用的处理方法中,在通过账号和密码校验之后,通过JwtService中生成token的方法,使用用户名称作为载荷生成token返回。最后生成token返回后,进入响应拦截器判断请求状态,如果成功响应则继续返回响应数据,在vuex状态管理中保存token。因为要进入首页,进入路由导航守卫的权限认证中,在导航守卫中还需要发送用户信息请求,这个请求需要携带token,成功获取后才能进入首页。

2.4 登出

登出就相对简单一些,当点击登出,直接提交action中的登出方法,在其中把token删除,然后跳转到登录页面即可。

    async logout() {
      await this.$store.dispatch('user/logout')
      this.$router.push(`/login?redirect=${this.$route.fullPath}`)
    }

2.5 用户管理模块

2.5.1 用户列表渲染

从前端组件页面开始梳理当进入用户管理组件页面时,会自动执行组件的周期函数creatd进行数据的请求,默认传递1号页码,最终响应分页数据,赋值给tableData数组,最终渲染指定分页大小的数据到组件页面中对应的属性。分页条通过响应的查询条数进行页码展示。

    created: function() {
      this.fetchData()
    },
    methods: {
      fetchData() {
        getUserList(1).then(response => {
          this.tableData = response.data
          //总记录
          this.total = response.recordCount
          //每页显示条数
          this.pageSize = response.pageSize
​
          this.isShow = true
        })
      },
     }

可以进入调用异步网络请求的getUserList方法中查看,可以看到getUserList方法中封装了request.js异步请求配置中的接口方法request。只需要传递请求的路径,请求方法以及请求参数。进入request.js异步网络请求设置文件中,其中定义了异步请求实例定义请求的后台网络地址和端口;定义了请求拦截器用来设置请求头,比如设置请求消息的头字段中携带token。还定义了响应拦截器,当后端返回响应消息时首先被响应拦截器拦截,其作用就是判断响应消息中数据的状态码,比如token正确性,登录状态等。在这些基础上做出成功的反馈和错误的反馈。

import request from '@/utils/request'
​
// 用户数据请求
export function getUserList(pageNumber) {
  return request({
    url: '/user',
    method: 'get',
    params: {pageNumber}
  })
}

当后端接收到前端网络请求时,IIS服务器会根据请求路由和请求方法将请求交给对应的接口方法进行处理。如以上的发送的请求路径为http://localhost:5064/user?pageNumber=1,请求本机的5064端口下user资源,并传递参数pageNumber。可以看看后端网络接口的定义:

        /// <summary>
        /// 分页查询用户数据
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <returns></returns>
        [HttpGet("user")]
        public Result<User> GetUser([FromQuery] int pageNumber)
        {
            return userService.GetUser(pageNumber, 3);
        }

因为每个控制器中会定义很多的接口方法,如果在所有接口方法中写业务处理代码会显得很臃肿,所有后端采用分层思想,即使得接口简洁明了和规范,又能加快开发流程。在进入用户业务层中查看GetUser方法的业务逻辑定义,可以看到使用了SqlSugar中的查询方法进行用户数据的查询,最后根据传递的页码和大小进行分页,最后判断成功查询后,将结果封装在自定义的Result结果集中,此结果集通用,包括状态码,数据记录的个数,分页的页码以及每页的长度,方便前端进行响应消息的判断和分页操作。

        //  获取用户列表
        public Result<User> GetUser(int pageNumber, int pageSize)
        {
            var list = sugarClient.Queryable<User>();
​
            List<User> listPage = list.ToPageList(pageNumber, pageSize);
​
            Result<User> result = new Result<User>();
​
            if(listPage.Count > 0)
            {
                result.code = 20000;
                result.data = listPage;
                result.pageNumber = pageNumber;
                result.pageSize = pageSize;
                result.recordCount = list.Count();
            }
​
            return result;
        }

可以看到浏览器已经成功接收到响应json数据:

2.5.2 用户添加

还是在用户组件页面中,声明了一个浮窗,设置visible为false隐藏,当点击添加时,触发methods中的AddPage方法将visible修改为true,弹出浮窗,输入用户数据,点击确认键后触发doAdd方法就可以将输入的表单数据通过网络异步的方式发送到后端。

// 弹出浮窗
AddPage(){
    this.aflag = true
},
// 确认添加 
doAdd() {
    add(this.aform).then(res=>{
        if(res.data[0].code == 20000){
            this.$message({
                message: '添加成功',
                type: 'success'
            })
        }else{
            this.$message({
                message: '添加失败',
                type: 'error'
            })
        }
    });
},
// 发送异步请求
export function add(data) {
    return request ({
        url: '/user',
        method: 'post',
        data
    })
}

在后台接口方法中调用的业务逻辑处理方法如下,可以看到后端将传输过来的用户信息转换为一个用户对象,我们直接调用父类中SqlSugar的插入方法进行用户数据的插入,并判断插入是否成功,成功后响应结果集封装的成功状态码。

// 添加用户的接口
        /// <summary>
        /// 添加用户
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        [HttpPost("user")]
        public Result<User> PostUser([FromBody] User user)
        {
            return userService.AddUser(user);
        }

// 添加后台管理用户的业务方法
        public Result<User> AddUser(User user)
        {
            int i = insert<User>(user);
​
            // 返回添加结果集
            Result<User> result = new Result<User>();
            if (i != 0)
            {
                result.code = 20000;
            }
​
            return result;
        }

2.5.3 用户编辑

用户编辑与用户添加相同,定义弹窗,只是多了传递当前记录到弹窗中方便用户编辑,当编辑完成点击确认时将表单数据通过Put请求发送到后端进行更新。

2.5.4 用户搜索

用户搜索是在页面中定义了输入用户姓名进行查询的模块,输入用户姓名后点击查询触发定义的搜索方法,方法中调用异步请求方法,并传递输入的姓名作为参数传递给后端接口方法,后端通过条件查询获取用户数据,判断成功查询结果后,将查询的数据封装在结果集中返回。

        /// <summary>
        /// 搜索用户
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        [HttpGet("searchUser")]
        public Result<User> searchUser([FromQuery] string username)
        {
            var list = sugarClient.Queryable<User>().Where(it => it.username == username).ToList();
​
            Result<User> result = new Result<User>();
​
            if(list.Count > 0)
            {
                result.code = 20000;
                result.data = list;
                result.pageNumber = 1;
                result.recordCount = list.Count();
            }
​
            return result;
        }

2.5.5 用户删除

用户删除较为简单,当点击删除按钮时,传递当前行的用户id给发送Delete异步请求的方法,最后后端根据id进行用户记录的删除。

2.6 日志管理模块

2.6.1 日志数据渲染

在前面已经配置好了log4net日志。所以我们可以通过sqlsugar查询数据库中的数据并展示在前端页面上。

如下是后台定义的方法,返回了一个自定义的结果集,此结果集通用,包括状态码,已经数据记录个数,分页的页码以及每页的长度,方便前端进行分页操作和响应消息的判断。

        public Result<Log> GetLog(int pageNumber, int pageSize)
        {
​
            var list = sugarClient.Queryable<Log>();
​
            List<Log> listPage = list.ToPageList(pageNumber, pageSize);
​
            Result<Log> result = new Result<Log>();
​
            if(listPage.Count > 0)
            {
                result.code = 20000;
                result.data = listPage;
                result.pageNumber = pageNumber;
                result.pageSize = pageSize;
                result.recordCount = list.Count();
            }
​
            return result;
        }

在控制器中的接口方法中调用,接口方法只接收分页的页码,然后默认每页10条数据,然后将结果集进行返回。

        /// <summary>
        /// 获取日志记录
        /// </summary>
        /// <param name="pageNumber"></param>
        /// <returns></returns>
        [HttpGet("log")]
        public Result<Log> Get([FromQuery] int pageNumber)
        {
            return logService.GetLog(pageNumber, 10);
        }

在前端日志组件中,定义了表单查询,以及表格展示,还要分页条。当我们点击日志管理时,会在组件的creatd周期函数中进行数据的请求,默认传递1号页码,最终响应十条数据渲染到组件页面中。

      created: function() {
        this.fetchData()
      },
      methods: {
        fetchData() {
          getLogList(1).then(response => {
            this.tableData = response.data
            //总记录
            this.total = response.recordCount
            //每页显示条数
            this.pageSize = response.pageSize
  
            this.isShow = true
          })
        }
      }

最终将响应消息中的data数据赋给tableData,根据绑定的对应属性成功进行渲染。

2.6.2 日志搜索与删除

日志搜索同用户搜索,只不过日志搜索定义的是根据日志等级进行搜索。日志搜索是在页面中定义了输入日志等级进行查询的模块,输入日志等级后点击查询触发定义的搜索方法,方法中调用异步请求方法,并传递输入的等级作为参数传递给后端接口方法,后端通过条件查询获取日志记录,判断成功查询结果后,将查询的数据封装在结果集中返回。

日志删除与用户删除一样。

2.7 员工管理

员工管理可以根据用户管理进行操作。

2.8 首页数据展示

在数据表已经定义了汽车销售量,汽车销售额,以及订单数据等。通过前端发送axios异步请求获取相应数据,然后通过echarts渲染为图表数据。

如以汽车月销售量与月销售额通过echarts图表展示,首先在组件的周期函数mounted函数中调用echarts获取dom元素绘制图生成图表的方法。在组件周期函数mounted中,当组件页面挂载渲染完毕执行,这样就可以在其中获取DOM元素将图表绑定渲染到对应的标签元素中进行展示。

3 总结

整个项目功能还很简单,所有还会进行后续的开发直至成为完善的功能。相关问题可以私信。