本地数据库IndexedDB - 学员管理系统之登录(一)

news2024/11/20 7:05:20

        IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。

IndexedDB具有以下特点:

  1. 键值对存储:IndexedDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括JS对象。对象仓库中,数据以”键值对“的形式保存,每一个数据记录都对应的主键,主键是独一无二的,不能有重复,否则会抛出错误。
  2. 异步:IndexedDB操作时不会锁死浏览器,用户依然可以进行其他操作,这与LocalStorage形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
  3. 支持事务:IndexedDB支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
  4. 同源限制:IndexedDB受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
  5. 存储空间大:IndexedDB的存储空间比LocalStore大很多,一般来说不少于250MB,甚至没有上限。
  6. 支持二进制存储:IndexedDB不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer对象和Blob对象)。

        

        通过上篇讲解,大家已经熟悉了IndexedDB的一些基本用法。这里将使用IndexedDB写个简单的”学员管理系统(本地版)“,如对IndexedDB基础还不了解的,请翻看上一篇,地址如下:

本地数据库IndexedDB - 初学者(一)_觉醒法师的博客-CSDN博客

        使用的技术框架是Vue.js + element-ui + vuex + axios + sass + indexedDB,系统栏目分类有:管理员列表、年级管理、班级管理、教师列表和学员列表。

        如下图所示:

一、搭建项目

        Vue项目的搭建这里也不再阐述,如有对vue全家桶不了解的同学,可以先阅读以下几篇文章:

Vue.js快速入门之一:安装和配置_觉醒法师的博客-CSDN博客_vue.js配置

Vue.js快速入门之二:使用状态管理工具Vuex_觉醒法师的博客-CSDN博客        

Vue.js快速入门之三:Vue-Router路由_觉醒法师的博客-CSDN博客

Vue.js快速入门之四:axios安装和使用_觉醒法师的博客-CSDN博客_axios怎么安装

二、数据库创建

        Vue项目搭建好后,需要在src目录下创建db目录,用来存储操作IndexedDB数据库文件。比如数据库操作文件、对应表封装类等。
       

2.1 定义表

        虽然IndexedDB不属性于“关系性数据库”,这里还是按“关系性数据库”进行表的定义和关联。

系统管理员表:

字段名类型描述
idintID
namevarchar管理员账号
passwordvarchar密码
phonevarchar手机号
createtimedatetime创建日期
updatetimedatetime更新日期

年级表:

字段名类型描述
idintID
namevarchar年级名称
passwordvarchar密码
floorchar楼层
createtimedatetime创建日期
updatetimedatetime更新日期

班级表:

字段名类型描述
idintID
namevarchar班级名称
teacherChargevarchar班主任
floorchar楼层
gidint关联年级
createtimedatetime创建日期
updatetimedatetime更新日期

教师表:

字段名类型描述
idintID
namevarchar教师姓名
registrationvarchar户籍
addressvarchar现居住地
phonevarchar手机号
gidint关联年级ID
cidschar关联班级ID,通过逗号分隔
birthdaydatetime出生年月日
createtimedatetime创建日期
updatetimedatetime更新日期

学员表:

字段名类型描述
idintID
namevarchar学员姓名
registrationvarchar户籍
addressvarchar现居住地
phonevarchar手机号
gidint关联年级ID
cidint关联班级ID
birthdaydatetime出生年月日
createtimedatetime创建日期
updatetimedatetime更新日期

楼层表:

字段名类型描述
idintID
namevarchar楼栋名称
floorvarchar楼层
createtimedatetime创建日期
updatetimedatetime更新日期

2.2 MD5加密

        由于IndexedDB是浏览器中数据库,当项目运行后可直接查看数据库中的数据,这时相关私密性的内容就需要通过加密进行处理了。所以在创建数据库表前,我们先在src目录下创建utils工具类目录,添加md5.js文件,用来给登录密码进行加密处理。

md5.js代码如下:


/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}


export {
  hex_md5,
  b64_md5,
  str_md5
}

        这里除了直接引用MD5文件外,也可以通过npm或yarn等一些包管理工具,使用命令进行安装,这部分自行选择适合自己项目的方法即可。

2.2 打开数据库

        以上将所有表结构定义好后,咱们可以封装操作数据库文件了。在src目录下创建db目录,然后新建index.js文件,用来定义数据库打开及表结构创建等等。

        在index.js文件中,我们封装一个DBIsLoadSuccess函数,用来判断IndexedDB是否打开成功,因为open请求不会立即打开数据库或者开始一个事务,而是在onsuccess执行时;并且结合Promise,来实现此功能。具体代码如下:

let Database;
let Request = window.indexedDB.open("TestDatabase", 2);
let isError = false;

// 数据库打开失败
Request.onerror = function(event) {
  isError = true;
  throw "Why didn't you allow my web app to use IndexedDB?!";
};

// 数据库打开成功
Request.onsuccess = function(event) {
  isError = false;
  Database = event.target.result;
};


let handle = null;

/**
 * 数据库增删改查 封装
 */
export const DBIsLoadSuccess = () => {
  return new Promise((resolve, reject) => {
    //如果数据库打开成功,直接回调resolve函数,并返回实例对象
    if(Database){
      resolve(Database);
    }
    //如果数据库还未打开,添加定时器 进行查询,打开成功后,
    else{
      handle = setInterval(() => {
        if(Database){
          clearInterval(handle);
          resolve(Database);
        }
        //如果数据库打开失败,执行reject函数
        else if(isError){
          clearInterval(handle);
          reject();
        }
      }, 200);
    }
  });
}

2.3 表结构创建

        在onupgradeneeded事件中,允许你在处理函数中更新数据库模式;所以我们在onupgradeneeded执行时,来创建对应的表及表结构。

        在管理员表创建时,需要默认添加一个管理员账号,并且通过hex_md5对密码进行处理后再存储到用户表中。创建管理员账号时,无须添加accesstoken键值对,因为此时用户还未登录,无token信息;如默认添加空值,则索引accesstoken中则会有相应关联值;这里accesstoken索引是用来判断用户是否登录的,是唯一的,只有在登录状态下才会赋值。

        在index.js文件中增加以下代码:

import { hex_md5 } from '@/utils/md5'

//数据库表名
let usersName = "users";        //用户表
let gradeName = "grade";        //年级表
let classifyName = "classify";  //班级表
let studentName = "student";    //学生表
let teacherName = "teacher";    //老师表
let buildingName = "building";  //楼栋

// 执行success前执行函数
Request.onupgradeneeded = function(e){
  let db = e.target.result;

  //用户表
  if(!db.objectStoreNames.contains(usersName)){
    let store = db.createObjectStore(usersName, {keyPath: "id", autoIncrement: true});
    //创建索引 - 用户名
    store.createIndex('name', 'name', {unique: true});
    //创建索引 - 登录token
    store.createIndex('accesstoken', 'accesstoken', {unique: true});
    //添加默认账号
    store.add({
      name: "admin",
      password: hex_md5('123456'),
      phone: "13233332222",
      cratetime: new Date().getTime(),
      updatetime: new Date().getTime()
    });
  }

  //年级表
  if(!db.objectStoreNames.contains(gradeName)){
    let store = db.createObjectStore(gradeName, {keyPath: "id", autoIncrement: true});

    //创建索引 - 年级名称(年级为唯一,故不能重复)
    store.createIndex('name', 'name', {unique: true});
  }

  //班级表
  if(!db.objectStoreNames.contains(classifyName)){
    let store = db.createObjectStore(classifyName, {keyPath: "id", autoIncrement: true});

    //创建索引 - 班级名称
    store.createIndex('name', 'name', {unique: false});
    //创建索引  - 年级名称
    store.createIndex('grade', 'grade', {unique: true});
  }

  //学生表
  if(!db.objectStoreNames.contains(studentName)){
    let store = db.createObjectStore(studentName, {keyPath: "id", autoIncrement: true});

    //创建索引 - 学生姓名
    store.createIndex('name', 'name', {unique: false});
  }

  //老师表
  if(!db.objectStoreNames.contains(teacherName)){
    let store = db.createObjectStore(teacherName, {keyPath: "id", autoIncrement: true});

    //创建索引 - 老师姓名
    store.createIndex('name', 'name', {unique: false});
  }

  //楼栋表
  if(!db.objectStoreNames.contains(buildingName)){
    let store = db.createObjectStore(buildingName, {keyPath: "id", autoIncrement: true});

    //创建索引 - 楼栋名称
    store.createIndex('name', 'name', {unique: true, multiEntry: true});
  }
  //if end
}

注意:onupgradeneeded只会在第一次打开数据库时触发,后期想要触onupgradeneeded监听事件,则需要修改open中第二位参数的版本号。

        我们在项目中引用index.js文件并执行,此时我们F12显示控制台,切换到“Application”,在IndexedDB中,则可以看到TestDatabase数据库了,以及创建相应的表及索引。

        如下图:

三、项目结构 

        在项目中,如下图在src目录下,创建相应的项目文件。

         在router以上部分,会在后面会详细讲解,这里我们先讲下如果定义路由和状态管理仓库。

3.1 vue-ls

        Vue-ls用来控制数据存储在localStorage或者sessionStorage中,并且可以控制其存储时效性。

安装:

npm install vue-ls --save

main.js中引入:

import Storage from 'vue-ls'

Vue.use(Storage, {
  namespace: 'system_',
  name: 'ls',
  storeage: 'local'
});

基本用法如下:

 //存储数据
Vue.ls.set('foo', 'boo', 60 * 60 * 1000); //缓存1小时
Vue.ls.get('foo');
Vue.ls.get('boo', 10); //如果没有获取到boo数据,默认返回10

Vue.ls.on('foo', callback) //监听foo值变化,触发Callback回调函数
Vue.ls.off('foo', callback) //卸载foo监听事件

//移除foo对应缓存数据
Vue.ls.remove('foo');

3.2 store状态管理

        Vuex是一个专为Vue.js应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件状态,并在相应的规则保证状态以一种可预测的方式发生变化。

        什么情况下会使用到Vuex:比如登录的用户信息,接口的统一访问令牌等,会频繁使用和全站通用的数据,可以寄存在状态管理器中。

        现在在store目录创建相应文件:

3.2.1 创建state.js文件

代码如下:

/**
 * 状态,变量库
 */
const state = {
  /**
   * 访问令牌
   */
  token: "",
  /**
   * 用户信息
   */
  userInfo: null
}

export default state;

3.2.2 创建mutationsType.js文件

代码如下:

/**
 * 用户信息
 */
export const USERINFO = "USERINFO";

/**
 * 访问令牌
 */
export const TOKEN = "TOKEN";

3.2.3 创建mutations.js文件

代码如下:

import { USERINFO, TOKEN } from './mutationsType'

const mutations = {
  /**
   * 修改访问令牌信息
   */
  [TOKEN](state, param){
    state.token = param;
  },
  /**
   * 修改用户信息
   */
  [USERINFO](state, param){
    state.userInfo = param;
  }
}
export default mutations;

3.2.4 创建getters.js文件

代码如下:

const getters = {
  /**
   * 用户信息
   */
  userInfo(state){
    return state.userInfo;
  },
  /**
   * 访问令牌
   */
  accessToken(state){
    return state.token;
  }
}
export default getters;

3.2.5 创建actions.js文件

代码如下:

import Vue from 'vue'
import { USERINFO, TOKEN } from './mutationsType'

/**
 * 业务层
 */
const actions = {
  /**
   * 检查是否登录
   */
  checkIsLogin(){
    let token = Vue.ls.get(TOKEN);
    return new Promise((resolve, reject) => {
      if(token){
        resolve();
      }else{
        reject();
      }
    });
  },
  /**
   * 保存登录信息
   */
  saveLoginInfo({commit}, param){
    if(param['token']) {
      commit(TOKEN, param.token);
      Vue.ls.set(TOKEN, param.token);
    }
    if(param['userinfo']) {
      commit(USERINFO, param.userinfo);
      Vue.ls.set(USERINFO, param.userinfo);
    }
  },
  /**
   * 退出登录
   */
  exitLogin({commit}, param){
    commit(TOKEN, '');
    commit(USERINFO, '');
    Vue.ls.remove(TOKEN);
    Vue.ls.remove(USERINFO);
  }
}

export default actions;

3.2.6 创建index.js文件

代码如下:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

Vue.use(Vuex);

export default new Vuex.Store({
  state,
  getters,
  actions,
  mutations
})

3.2.7 main.js中引入 

代码如下:

import Vue from 'vue'
import App from './App'
import elementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'element-ui/lib/theme-chalk/base.css'
import Storage from 'vue-ls'
import store from '@/store/index'

Vue.use(elementUI);

Vue.use(Storage, {
  namespace: 'system_',
  name: 'ls',
  storeage: 'local'
});

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

        这里store本地状态管理器中文件就创建和定义完了,后续可直接使用this.$store进行调用即可。

3.3 router路由定义

        在定义路由前,先在pages目录下,对应创建所有页面文件,如管理员列表页、年级列表页、班级列表页、教师列表页、学员列表页,以及错误页和登录页。

        另外,我们需要在components组件中,新建Layout.vue文件,用来定义菜单和内容区域,作为公共部分。代码如下:

html代码部分:

<template>
  <div class="layout-wrap">
    <el-container class="container">
      <el-aside width="200px" class="aside-box">
        <div class="title">
          <h3>学员管理系统</h3>
        </div>
        <el-menu default-active="1" class="el-menu-vertical" :router="true">
          <el-menu-item index="1" :route="{path: '/sys/mange'}">
            <i class="el-icon-s-custom"></i>
            <span slot="title">管理员列表</span>
          </el-menu-item>
          <el-menu-item index="2" :route="{path: '/sys/grade'}">
            <i class="el-icon-house"></i>
            <span slot="title">年级管理</span>
          </el-menu-item>
          <el-menu-item index="3" :route="{path: '/sys/classify'}">
            <i class="el-icon-collection-tag"></i>
            <span slot="title">班级管理</span>
          </el-menu-item>
          <el-menu-item index="4" :route="{path: '/sys/teacher'}">
            <i class="el-icon-user"></i>
            <span slot="title">教师列表</span>
          </el-menu-item>
          <el-menu-item index="5" :route="{path: '/sys/student'}">
            <i class="el-icon-user"></i>
            <span slot="title">学员列表</span>
          </el-menu-item>
        </el-menu>
        <div class="btn-box">
          <el-button type="danger" size="mini" @click="logoutEvent">退出登录</el-button>
        </div>
      </el-aside>
      <el-main>
        <transition name="el-zoom-in-center">
          <router-view />
        </transition>
      </el-main>
    </el-container>
  </div>
</template>

JS部分:

<script>
export default {
  name: 'Layout',
  data () {
    return { }
  },
  methods: {
    /**
     * 退出登录
     */
    logoutEvent(){
      
    }
  }
}
</script>

样式部分:

<style lang="scss" scoped>
 .container{ height: 100vh; }

  .el-menu{ border-right: 0; }

  .el-menu-vertical{ height: 100%; }

  .aside-box{
    position: relative;
    padding-top: 80px;
    border-right: 1px solid #e6e6e6;

    .title{
      width: 100%;
      padding: 30px 0;
      text-align: center;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 10;

      h3{
        font-size: 20px;
        color: #409EFF;
      }
    }
  }
</style>

        待这些页页创建成功后,我们将在router/index.js中引入这些模块,用来定义页面跳转路由路径,代码如下:

import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/components/Layout'
import Error404 from '@/pages/Error/err404'
import Index from '@/pages/index'
import Student from '@/pages/student'
import Mange from '@/pages/mange'
import Grade from '@/pages/grade'
import Classify from '@/pages/classify'
import Teacher from '@/pages/teacher'
import Login from '@/pages/login'
import store from '@/store'

Vue.use(Router);

let _router = new Router({
  routes: [
    {
      path: '/',
      name: "Home",
      component: Layout,
      redirect: '/sys/index',
      children: [
        {
          path: '/sys/index',
          name: 'Index',
          component: Index,
        },
        {
          path: '/sys/student',
          name: 'Student',
          component: Student,
        },
        {
          path: '/sys/mange',
          name: 'Mange',
          component: Mange,
        },
        {
          path: '/sys/grade',
          name: 'Grade',
          component: Grade,
        },
        {
          path: '/sys/classify',
          name: 'Classify',
          component: Classify,
        },
        {
          path: '/sys/teacher',
          name: 'Teacher',
          component: Teacher,
        }
      ]
    },
    {
      path: '/login',
      name: 'Login',
      component: Login,
    },
    {
      path: '*',
      name: 'Error404',
      component: Error404,
    },
  ]
});

_router.beforeEach((toRoute, fromRoute, next) => {
  next();
});

export default _router;

        此时,我们可以点击页面左侧菜单,进行页面跳转了。关于登录功能,和登录页面跳转,将会在后面继续讲解。

四、登录功能

4.1 权限校验

        在前面我们定义好了路由相关跳转路径,那我们如何跳转到登录页呢。此时大家可以往前翻看“3.2.5 创建actions.js文件”中,定义了checkIsLogin函数,用来判断用户是否登录了。在里我们则可以对router/index.js中的 路由卫士进行 稍微调整即可,代码如下:

_router.beforeEach((toRoute, fromRoute, next) => {
  store.dispatch('checkIsLogin').then(() => {
    next();
  }).catch(() => {
    if(toRoute.path=='/login'){
      next();
    }else{
      next('/login');
    }
  });
});

        通过执行checkIsLogin函数,来判断用户是否已登录,如登录则直接跳转到下一路由页面中,否则跳转到登录页,完成登录后,则可以正常访问系统页面。为什么要在此判断呢,因为所有页面的访问都要经过“路由卫士”,系统登录是有时效性的,一旦超时token则会自动失效;所以当token失败时,用户点击下一步操作,则会自动跳转到登录页面。

        另外,由于这里引用了Promise,所以会出现以下错误:

vue-router Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current        

        这时按以下方法修改即可:

//存储push
let originPush=Router.prototype.push
let originReplace=Router.prototype.replace

//重写
Router.prototype.push=function(location,resole,reject){
    if(resole&&reject){
        originPush.call(this,location,resole,reject)
    }else{
        originPush.call(this,location,()=>{},()=>{})
    }
}
Router.prototype.replace=function(location,resole,reject){
    if(resole&&reject){
        originReplace.call(this,location,resole,reject)
    }else{
        originReplace.call(this,location,()=>{},()=>{})
    }
}

_router.beforeEach((toRoute, fromRoute, next) => {
  store.dispatch('checkIsLogin').then(() => {
    next();
  }).catch(() => {
    if(toRoute.path=='/login'){
      next();
    }else{
      next('/login');
    }
  });
});

    

4.2 登录页面

        此时开始着手完成登录页部分的代码,这里直接贴代码了。

html部分:

<template>
  <div class="login-box">
  	<h3>学员管理系统</h3>
  	<h4><span>———</span> <span class="tit">安全登录</span> <span>———</span></h4>
  	<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
  		<el-form-item label="用户名" prop="username">
  			<el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
  		</el-form-item>
  		<el-form-item label="密码" prop="password">
  			<el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
  		</el-form-item>
  		<el-form-item>
  			<el-button type="primary" :disabled="disabledButton" @click="submitForm('ruleForm')">登录</el-button>
  			<el-button :disabled="disabledButton" @click="resetForm('ruleForm')">重置</el-button>
  		</el-form-item>
  	</el-form>
  </div>
</template>

js部分:

<script>
  export default {
    data(){
      var validateUsername = (rule, value, callback) => {
      	if (value === '') {
      		callback(new Error('请输入用户名'));
      	} else {
      		callback();
      	}
      };
      var validatePass = (rule, value, callback) => {
      	if (value === '') {
      		callback(new Error('请输入密码'));
      	} else {
      		if (this.ruleForm.checkPass !== '') {
      			this.$refs.ruleForm.validateField('checkPass');
      		}
      		callback();
      	}
      };
      return {
        disabledButton: false,    //是否禁用按钮
      	ruleForm: {
      		username: '',
      		password: '',
      	},
      	rules: {
      		username: [
      			{ validator: validateUsername, trigger: 'blur' }
      		],
      		password: [
      			{ validator: validatePass, trigger: 'blur' }
      		]
      	},
      }
    },
    methods: {
      /**
       * 提交数据
       * @param {Object} formName
       */
      submitForm(formName) {
      	this.$refs[formName].validate((valid) => {
      		if (valid) {
      			console.log(this.ruleForm);
      		} else {
      			console.log('error submit!!');
      			return false;
      		}
      	});
      },
      /**
       * 重置表单
       * @param {Object} formName
       */
      resetForm(formName) {
      	this.$refs[formName].resetFields();
      }
    }
  }
</script>

样式部分:

.login-box{
	width: 600px;
	height: 390px;
	padding: 50px 70px;
	box-sizing: border-box;
	box-shadow: 0 0 10px rgba(0, 0, 0, .1);
	border-radius: 10px;
	overflow: hidden;
	position: absolute;
	left: 50%;
	top: 50%;
	margin-left: -300px;
	margin-top: -200px;
	z-index: 10;

	h3, h4{
		font-family: "Microsoft YaHei","微软雅黑",Arial,sans-serif;
		text-align: center;
	}

	h3{
		font-size: 26px;
		color: #409eff;
	}

	h4{
		font-size: 14px;
		color: #999999;
		font-weight: normal;
		padding: 10px 0 40px;

		span{
			display: inline-block;
			vertical-align: middle;
			&.tit{
				padding: 0 26px;
			}
		}
	}
}

登录页界面如下图:

4.3 添加打开事务功能

        在2.2中我们完成了“数据库打开”操作文件的代码,此时需要在内部添加两个打开事务的执行函数,以及一个读写的常量值(IndexedDB中有对应常量值,但已废弃,这里自己定义即可),在db/index.js文件中增加代码如下:

/**
 * 返回是只读或读写模式
 */
export const CONST_READ = {
  READONLY: "readonly",
  READWRITE: "readwrite"
}

/**
 * 打开索引用户名游标
 */
export const openTransactionIndex = (storeName, indexName, mode) => {
  if(!storeName){
    throw '请指定打开事务的表名!';
  }
  if(!indexName){
    throw '请指定需查询索引名!';
  }
  mode = mode || CONST_READ.READONLY;
  //开启事务
   let transaction = Database.transaction([storeName], mode);
   //连接对象仓库
   let store = transaction.objectStore(storeName);
   //获取对应索引
   let index = store.index(indexName);
   //返回游标
   let cursor = index.openCursor();
   return {
     store,
     index,
     cursor
   }
}

4.4 api请求定义

        在src目录下新建api/index.js文件,用来定义接口请求功能函数。我们先在db/model目录下创建user.js,用来操作管理员表的增删改查;创建好后,先定义好login和ogout函数,用来处理登录和退出功能,代码如下:

/**
 * 通过索引获取对应数据
 */
export const  login = data => {}

/**
 * 退出登录
 */
export const logout = token => {}

        在db/model/user.js创建好后,我们可以在api/index.js中定义登录和退出接口请求了,代码如下:

import { login, logout } from '@/db/model/user'

/**
 * 登录
 */
export const loginInfo = (params) => {
  return login(params);
}

/**
 * 退出
 */
export const logoutInfo = params => {
  return logout(params);
}

4.5 登录和退出业务功能

        在4.3中,我们在db/index.js中添加了添加了处理索引事务函数,我们将其引入到db/model/user.js表中,用来实现登录用户查询和accesstoken信息查询。

        另外,用户密码保存时是通过md5加密处理的,所以登录时匹配用户密码时,也需要使用到md5处理函数,这里也需要引入。

        用户登录成功时候,需要生成accesstoken随机字符串,这里咱们把这类功能函数放在utils/utils.js工具类中;还有在所有数据请求成功后,需要返回一个统一的数据格式JSON文件,我们也在工具类文件中定义个rJson函数,工具类文件代码如下:

/**
 * 随机生成字符串
 * @param {*} _len
 */
export const randomStrName = _len => {
  let _string = 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ1234567890',
      _returnStr = '';
  _len = 'number'===typeof _len ? _len : 50;
  for(var i = 0; i < _len; i++){
    _returnStr += _string.charAt( Math.floor(Math.random() * (_string.length-2)) )
  }
  return _returnStr;
}

/**
 * 返回数据对象
 * @param code 状态码, 0 表示错误, 1表示正常
 * @param data 返回数据集
 * @param msg 返回提示信息
 */
export const rJson = (code, data, msg) => {
  code = code || '0';
  data = data || null;
  msg = msg || '';

  return {
    code,
    data,
    msg
  }
}

        以上准备工具做好后,我们还要在user.js文件中定义storeName,指定当前文件操作的表。login和logout功能函数实现上,使用Promise来异步操作,这样使回调数据更为灵活点。

        代码如下:

import { openTransactionIndex, CONST_READ } from '@/db'
import { rJson, randomStrName } from '@/utils/utils'
import { hex_md5 } from '@/utils/md5'

let storeName = 'users';

/**
 * 通过索引获取对应数据
 */
export const  login = data => {
  return new Promise((resolve, reject) => {
    
  });
}

/**
 * 退出登录
 */
export const logout = token => {
   return new Promise((resolve, reject) => {
       
   });
}

4.6 实现登录功能

        现在各项关系的文件都创建好了,并且已关联上,咱们可以找到“4.2 登录页面”的js部分,引入api/index.js中的loginInfo,来实现登录数据上传并校验。另外,之前在“3.2.5 创建actions.js文件”中,定义了登录保存用户信息业务函数saveLoginInfo,在登录成功后,需调用此函数将用户信息和accesstoken保存为全局变量中,以及缓存本地存储中。打开pages/login/index.vue文件,代码如下:

<script>
  import { loginInfo } from '@/api'
  export default {
    data(){
      // ...

      return {
        disabledButton: false,    //是否禁用按钮
      	ruleForm: {
      		username: '',
      		password: '',
      	},
      	rules: {
      		username: [
      			{ validator: validateUsername, trigger: 'blur' }
      		],
      		password: [
      			{ validator: validatePass, trigger: 'blur' }
      		]
      	},
      }
    },
    methods: {
      /**
       * 提交数据
       */
      submitForm(formName) {
      	this.$refs[formName].validate((valid) => {
      		if (valid) {
                loginInfo(this.ruleForm).then(res => {
                  if(res.code==1){
                    //缓存数据
                    this.$store.dispatch('saveLoginInfo', {
                      token: res.data.accesstoken,
                      userinfo: res.data.userinfo
                    })
                  }
                  this.$message.success(res.msg);
                  this.$router.push('/');
                }).catch(e => {
                  this.$message.error(e.msg);
                  this.resetForm('ruleForm');
                });
      		} else {
      			return false;
      		}
      	});
      },

      // ...
    }
  }
</script>

        这里输入用户名和密码进行登录,是没有返回数据的,因为我们还没实现数据库查询功能,现在大家打开db/model/user.js文件,实现login函数部分代码。

        在前面“2.3 表结构创建”中,有创建name和accesstoken两个索引;又在“4.3 添加事务功能”中新增了事务创建相关功能函数,这里咱们就可以用上了。首先登录用户名是唯一的,这里通过name索引进行匹配查询,这里openTransactionIndex函数获取相应处理对象。

        代码如下:

export const  login = data => {
  return new Promise((resolve, reject) => {
    //打开事务
    let { index, store } = openTransactionIndex(storeName, 'name', CONST_READ.READWRITE);
    //通过用户名,获取用户数据
    let indexData = index.get(data.username);

    indexData.onerror = function(e){
      reject(
        rJson(0, e, '查询出错了~')
      );
    }

    indexData.onsuccess = function(e){
      let result = e.target.result;
      if(result){
        //判断密码是否一致
        if(result.password==hex_md5(data.password)){
          let accesstoken = randomStrName(80);
          //记录token值
          result['accesstoken'] = accesstoken;
          //保存token
          store.put(result);
          //返回结果数据
          resolve(
            rJson(1, {
              accesstoken,
              userinfo: {
                id: result.id,
                username: result.name,
                phone: result.phone
              }
            }, '登录成功~')
          )
        }else{
          reject(
            rJson(0, null, '密码错误~')
          )
        }
      }else{
        reject(
          rJson(0, null, '用户名错误~')
        )
      }
      //if end
    }

  });
}

        以上功能完成后,我们就可以实现登录了,这时我们在登录界面输入用户名和密码,则可以跳转到首页了。在“2.3 表结构创建”中,默认添加了admin账号,暂时可以通过这个账号登录系统,登录后界面如下:

4.7 实现退出功能

        在“3.3 router路由定义”中的JS部分,我们调用下退出接口,完成退出功能,并通过“3.2.5 创建actions.js文件”中定义的exitLogin函数,清除本地缓存数据。代码如下:

<script>
import { logoutInfo } from '@/api'
import { TOKEN } from '@/store/mutationsType'
export default {
  name: 'Layout',
  data () {
    return { }
  },
  methods: {
    /**
     * 退出登录
     */
    logoutEvent(){
      this.$confirm('确认好退出登录吗?', '提示', {
        confirmButtonText: '退出',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        logoutInfo(this.$ls.get(TOKEN)).then(res => {
          this.$store.dispatch('exitLogin');
          this.$message.success(res.msg);
          this.$router.push('/login');
        }).catch(e => {
          this.$message.error(e.msg);
          this.$router.push('/login');
        })
      }).catch(() => {
        this.$message({
          type: 'info',
          message: '已取消退出'
        })
      })
    }
  }
}
</script>

        同样,现在点击退出按钮不会得到响应,需要将db/model/user.js中的logout功能实现后,才能真正实现退出功能。

        代码如下:

export const logout = token => {
   return new Promise((resolve, reject) => {
      //打开事务
      let {index} = openTransactionIndex(storeName, 'accesstoken', CONST_READ.READWRITE);
      //通过token,获取对应用户数据
      let indexData = index.get(token);

      indexData.onerror = function(e){
       reject(
         rJson(0, e, '查询出错了~')
       );
      }

      indexData.onsuccess = function(e){
        let result = e.target.result,
            source = e.target.source;
          if(result){
            let store = source.objectStore;
            //判断token信息是否匹配
            if(result.accesstoken == token){
              //删除accesstoken
              delete result['accesstoken'];
              //更新数据
              store.put(result);
              resolve(
                rJson(1, null, '退出成功~')
              );
            }else{
              reject(
                rJson(0, null, '登录失效~')
              )
            }
          } else{
            reject(
              rJson(0, null, '未查询到登录信息~')
            )
          }

      }
   });
}

        这里我们在登录状态下,点击退出按钮,退出成功后就会直接跳转到登录界面了。

4.8 校验缓存数据中的登录信息

        我们又要回到 “ 3.2.5 创建actions.js文件 ”中,为什么当初在定里定义函数判断token失效,而不是直接放到 路由卫生中获取token,直接进行判断呢;因为这里除了要校验本地是否缓存token信息,同时也要判断数据库中token是否存在或失效,话不多说,直接上代码。

        在db/model/user.js中添加checkToken函数,代码如下:

/**
 * 校验token是否存在
 */
export const checkToken = token => {

}

        在api/index.js中添加tokenIsFailure函数,代码如下:

import { login, logout, checkToken } from '@/db/model/user'

/**
 * 判断数据库中token是否失效
 */
export const tokenIsFailure = token => {
  return checkToken(token);
}

        store/actions.js文件中的checkIsLogin进行改造,代码如下:

checkIsLogin(){
    let token = Vue.ls.get(TOKEN);
    return new Promise((resolve, reject) => {
      if(token){
        tokenIsFailure(token).then(() => {
          resolve();
        }).catch(() => {
          commit(TOKEN, '');
          Vue.ls.remove(TOKEN);
          reject();
        });
      }else{
        reject();
      }
    });
  }

        当读取到本地缓存的token信息后,还需要进行数据库校验;如果数据库中不存在,则会直接跳转到登录页,并清除本地缓存的token信息。

        另外,我们之前在“2.2 打开数据库”中定义过DBIsLoadSuccess函数,因为IndexedDB的open()函数不是执行后就返回实例对象的,所有刚进入系统,很有可能会遇到实例对象为空的情况。这时我们需要特别小心,在调用事务前,先判断数据库实例对象是否存在。

        接下来让我们完成db/model/user.js中的checkToken函数功能,代码如下:

export const checkToken = token => {
  return new Promise((resolve, reject) => {
    //判断数据库是否打开
    DBIsLoadSuccess().then(() => {
      //打开事务
      let {index} = openTransactionIndex(storeName, 'accesstoken');
      //通过token,查询数据
      let indexKey = index.getKey(token);
      indexKey.onerror = function(e){
        reject(
          rJson(0, e, '查询出错了~')
        );
      }

      indexKey.onsuccess = function(e){
        let result = e.target.result;
        if(result){
          resolve(
            rJson(1, e, '校验成功~')
          )
        }else{
          reject(
            rJson(0, e, '登录失效~')
          );
        }

      }
    }).catch(() => {
      reject(
        rJson(0, e, '数据库打开失败~')
      );
    })

  });
}

        这里我们先手动清除数据表中的accesstoken,然后点击任意页面跳转,此时会发现页面直接跳转到登录页了。

        正常情况下,在登录时创建的token,是带有时效的,这块这里就不细讲,大家可以通过自己的理解,去完善此部分。

        至此,该篇内容已讲解完了,该系统其他功能完善请看后续篇幅。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

插入排序图解

七大排序之插入排序 文章目录七大排序之插入排序前言一、直接插入排序1.1 算法图解1.2 算法稳定性1.3 插入排序和选择排序相比到底优在哪&#xff1f;二、折半插入排序总结前言 博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&#xff1a;Killing Vibe的博客 欢迎大…

springboot:实现文件上传下载实时进度条功能【附带源码】

0. 引言 记得刚入行的时候&#xff0c;做了一个文件上传的功能&#xff0c;因为上传时间较久&#xff0c;为了用户友好性&#xff0c;想要添加一个实时进度条&#xff0c;显示进度。奈何当时技术有限&#xff0c;查了许久也没用找到解决方案&#xff0c;最后不了了之。 近来偶…

全网最全面的pytest测试框架进阶-conftest文件重写采集和运行测试用例的hook函数

【文章末尾有.......】 使用pytest不仅仅局限于进行单元测试&#xff0c;作为底层模块可扩展性强&#xff0c;有必要理解其运行机制&#xff0c;便于进行二次开发扩展&#xff0c;通过文档的学习很容易理解。 构建一个简单的测试脚本 import pytest import requestsdef add(…

Hive数据倾斜常见场景及解决方案(超全!!!)

Hive数据倾斜常见问题和解决方案 文章目录 前言、一、Explain二、数据倾斜&#xff08;常见优化&#xff09;前言 Hive数据倾斜是面试中常问的问题&#xff0c;这里我们需要很熟练地能举出常见的数据倾斜的例子并且给出解决方案。 一、Explain 我们可以通过sql语句前面加expa…

公众号网课查题搭建方法

公众号网课查题搭建方法 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;…

QLC 闪存给主控带来了很大的难题?

前言 世界各大主流闪存厂商&#xff0c;如美光、海力士、铠侠和长江存储积极致力于QLC的研发&#xff0c;并相继推出了QLC SSD 产品。随着技术的不断进步&#xff0c;人们普遍担心的QLC擦写寿命少正逐渐被改善。QLC SSD 成本是最大的优势&#xff0c;不指望说替代 TLC SSD&…

408 | 【2011年】计算机统考真题 自用回顾知识点整理

选择题 T3&#xff1a;循环队列 不同指针指向&#xff0c;队列判空/判满条件 1. rear:指向队尾元素 front:指向队头元素前一个位置 &#xff08;1&#xff09;牺牲一个存储空间 &#xff08;2&#xff09;判空条件&#xff1a;front rear &#xff08;3&#xff0…

【RHCSA】管理Linux的联网

目录 rhel8与旧版本的区别 NetworkManager的特点 配置网络 (1)使用P命令配置临时生效的网络连接 (2)修改配置文件&#xff0c;前提是需要有network服务[不推荐] (3)nmcli(命令行工具) 网络测试命令 Ⅰ、使用ping命令测试网络的连通性 Ⅱ、使用tracepath命令跟踪并显示网…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java危险品运输车辆信息管理系统b2z1o

大学毕业设计&#xff0c;一般都是自己或者几个同学一起弄&#xff0c;lunwen都是去&#xff0c;百度&#xff0c;图书馆找很多资料参考&#xff0c;&#xff08;就是把里面都了&#xff0c;自己再按照各个意思重新表达&#xff09;&#xff0c;但是前提&#xff0c;提纲要想好…

【附源码】计算机毕业设计SSM微课程服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

神经网络芯片的单片机,什么是神经网络芯片

1、神经网络做图像分类一定要用到gpu吗&#xff1f; GPU最大的价值一直是“accelerating”(加速)&#xff0c;GPU不是取代CPU&#xff0c;而是利用GPU的并行计算架构&#xff0c;来将并行计算的负载放到GPU上来处理从而极大的提升处理速度。GPU本质上在异构计算架构上属于协处…

Hello Word你真的理解了么?今天教我的表弟,有些感悟

&#x1f36c;博主介绍 &#x1f468;‍&#x1f393; 博主主页&#xff1a;喵的主页 ✨主攻领域&#xff1a;【大数据】【java】【python】【面试分析】 Hello world1. 编写程序2. 打开命令行3. 运行 .class 文件4. 排查错误1. 编写程序 是不是都忘了我们初学时是打开记事本的…

9-1 Kubernetes二进制部署的Prometheus实现服务发现

文章目录前言创建用户复制Token配置文件全局配置Master节点发现Node节点发现Namespace Pod发现自定义Pod发现前言 在上一章节介绍了 8-5 在Prometheus实现Kubernetes-apiserver及Coredns服务发现 基于K8s集群内部安装的Prometheus&#xff0c;添加服务发现时更加方便。Prometh…

二叉树遍历原理 | 深度优先-广度优先 | 栈-队列

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 14天阅读挑战赛 文章目录二叉树遍历原理队列和栈区别深度优先遍历(DFS)前序遍历(根-左-右)中序遍历(左-根-右)后序遍历(左-右-根)广度优先遍历(BFS)逐层遍历(上-下 | …

软件研发人效提升之道,法,术,器,势

在华为的寒气传递之前&#xff0c;笔者已经在思考和实战如何提高研发人效。目的目标很明确: 提高软件研发人效&#xff0c;所谓的软件人效&#xff0c;简单粗暴的定义就是以最低单位人均成本&#xff0c;快速&#xff0c;高质量&#xff0c;高频率&#xff0c;安全地交付软件产…

C++笔记之bitset使用

C++笔记之bitset使用 文章目录 C++笔记之bitset使用0.进制介绍1.cppreference2.常规使用3.用法总结3.1.bitset是什么3.2.使用方法3.3.相关使用函数3.4.转换函数0.进制介绍 1.cppreference

C语言高级教程-C语言数组(五):二维(多维)数组初始化和基于数组的综合实例->帽子选购问题

C语言高级教程-C语言数组&#xff08;五&#xff09;&#xff1a;二维&#xff08;多维&#xff09;数组初始化和基于数组的综合实例->帽子选购问题一、本文的编译环境二、二维数组的初始化三、三维数组的初始化四、使用for循环求三维数组元素值的和4.1、for循环求数组元素值…

行业周期分析的主要内容,怎么分析行业生命周期

如何分析经济周期&#xff1f; 很多人认为经济周期分析很难&#xff0c;很复杂。但是作为一个投资者&#xff0c;必须了解一定的经济周期分析原理。所以今天康少就用一张图来简单讲解下经济周期的分析。 一、经济周期判断1、经济趋向繁荣&#xff1a;普通股收益将大幅提高&am…

第07篇:巧用Spring类型转换, ConverterFormatter知识点学习。

公众号: 西魏陶渊明 CSDN: https://springlearn.blog.csdn.net 天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄&#xff01; 文章目录一、前言1.1 类型转换1.2 格式化输出二、Converter 类型转换2.1 Converter2.1.1 接口定义2.1.2 接口功能2.2 ConverterFactory2.2.1 接口定义…

java8特性,lambda表达式,简写的演变及应用

&#x1f36c;博主介绍 &#x1f468;‍&#x1f393; 博主主页&#xff1a;chad_chang的主页 ✨主攻领域&#xff1a;【大数据】【java】【python】【面试分析】 文章目录lambda表达式1.1.简介1.1.1.什么是Lambda&#xff1f;1.1.2.为什么使用Lambda1.1.3.Lambda对接口的要求1…