IndexedDB的包装器JsStore - 实现登录功能及事务处理

        JsStore是IndexedDB的包装器。它提供了简单的SQL像api,这是容易学习和使用。         IndexedDb查询可以在web worker内部执行,JsStore通过提供一个单独的worker文件来保持这种功能。

        最近有位叫Pioneer网友一直在问我关于事务的实现方式,关于这点的确在看JsStore时,并没太重视。由于近期疫情放开新冠病毒迅速传播,我也不例外18日中招了。近日身体已基本恢复,利用在家休息期间,了解了JsStore中的transaction事务。对于事务,简单的理解就是,一个事务里的操作,要么全部执行成功,要么全部执行失败。 这里将修改之前IndexedDB写的登录功能,来作为案例讲解,界面细节就不细讲了,有不清楚可以查看之前案例,地址:本地数据库IndexedDB - 学员管理系统之登录(一)_觉醒法师的博客-CSDN博客_indexdb实现管理系统

 一、框架搭建

1.1 项目结构

        由于该Demo主要是以之前 “本地数据库IndexedDB - 学员管理系统之登录(一)” 中代码实现,并且只讲登录功能,以及事务处理,所以有些地方不会做过多细讲,该篇重点会讲到api、db、store几处。

 1.2 路由定义

        路由这块并不复杂,只之前多于部分删除,保留现在Pages中Error、login、menge部分即可。

        Error页面代码如下:

<template>
  <div class="error-box">
      <h3>页面出错了 404 ~</h3>
      <p>当前页面不存在,<span class="blue" @click="backEvent">点击返回</span></p>
  </div>
</template>

<script>
  export default{
    data(){
      return {}
    },
    methods: {
      backEvent(){
        this.$router.go(-1);
      }
    }
  }
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>

         由于该篇不讲主页功能,这里只保留极少代码,以实现登录后跳转到首页即可,mange页面代码:

<template>
<div class="index-wrap">
     Home
</div>
</template>

<script>
export default {
  data () {
    return {

    }
  },
  methods: {
  }
}
</script>

<style lang="scss" scoped>

</style>

        登录页面login样式代码如下:


.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: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","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;
			}
		}
	}
}

        登录页面先实现校验等功能,具体实现待后期再详细讲解,login代码如下:

<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="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>

<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) {

            }
      	});
      },
      /**
       * 重置表单
       * @param {Object} formName
       */
      resetForm(formName) {
      	this.$refs[formName].resetFields();
      }
    }
  }
</script>

<style lang="scss" scoped>
@import './index.scss';
</style>

        路由中配置代码如下: 

import Vue from 'vue'
import Router from 'vue-router'
import { TOKEN } from '@/store/mutationsType'
import Layout from '@/components/Layout'
import Error404 from '@/pages/Error/err404'
import Mange from '@/pages/mange'
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: Mange,
        }
      ]
    },
    {
      path: '/login',
      name: 'Login',
      component: Login,
    },
    {
      path: '*',
      name: 'Error404',
      component: Error404,
    },
  ]
});

//存储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');
    }
  });
});

export default _router;

1.3 store状态管理器

        state.js代码如下:

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

export default state;

        getters.js代码如下:

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

        mutationsType.js代码如下:

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

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

        mutations.js代码如下:

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

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

        在这里tokenIsFailure函数还未实现,所以代码中暂不体现,actions.js代码如下:

import Vue from 'vue'
import { USERINFO, TOKEN } from './mutationsType'
import { Loading } from 'element-ui'
import { tokenIsFailure } from '@/api'

/**
 * 业务层
 */
const actions = {
  /**
   * 重新加载缓存中用户信息
   */
  reloadUserInfo({commit}){
    let token = Vue.ls.get(TOKEN),
        userInfo = Vue.ls.get(USERINFO);
    if(token) {
      commit(TOKEN, token);
    }
    if(userInfo) {
      commit(USERINFO, userInfo);
    }
  },
  /**
   * 检查是否登录
   */
  checkIsLogin({commit}){
    let token = Vue.ls.get(TOKEN);
    return new Promise((resolve, reject) => {
      if(token){
        Vue.ls.set(TOKEN, token, 24 * 60 * 60 * 1000);    //重新计时,缓存1天
        resolve();
      }else{
        reject();
      }
    });
  },
  /**
   * 保存登录信息
   */
  saveLoginInfo({commit}, param){
    if(param['token']) {
      commit(TOKEN, param.token);
      Vue.ls.set(TOKEN, param.token, 24 * 60 * 60 * 1000);
    }
    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;

         store/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
})

        在main.js中引入,代码如下:

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

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


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

        以上操作完成,基本框架已经完成,剩下就是功能实现了。

二、数据库模型

2.1 创建数据库链接

        首先在db目录下创建index.js文件,用来实现数据库连接和增删改查功能,代码如下:

import { Connection } from "jsstore";
import workerInjector from "jsstore/dist/worker_injector";

let connection = new Connection();
connection.addPlugin(workerInjector);

export default connection;

2.2 定义数据表

        在db目录下创建service.js文件,在这里定义数据中使用到的数据表,数据库初始化函数,以及版本迭代等功能操作,代码如下:

import connection from './index.js';
import { DATA_TYPE } from "jsstore";

const getDatabase = () => {
	//用户表
	const UserTable = {
		name: "Users",
		columns: {
			id: { primaryKey: true, autoIncrement: true },
			username: { notNull: true, dataType: DATA_TYPE.String },
			password: { notNull: true, dataType: DATA_TYPE.String },
			role: { notNull: true, dataType: DATA_TYPE.Number },
			token: { notNull: false, dataType: DATA_TYPE.String },
			createtime: { notNull: true, dataType: DATA_TYPE.DateTime },
			updatetime: { notNull: true, dataType: DATA_TYPE.DateTime },
		}
	}

	const dataBase = {
			name: "demo_manage",
			tables: [UserTable],
			version: 1
	};
	return dataBase;
}

export const initJsStore = async () => {
	const dataBase = await getDatabase();
	return await connection.initDb(dataBase);
}

2.3 定义实例操作对象

        在db/model目录下,创建user.js文件,用来定义用户数据表模型;由于登录功能需要使用事务来实现,这里暂不展示,待后续再讲解。代码如下:

import connection from "@/db/index.js";
import { hex_md5 } from '@/utils/md5'

export class UserService {
  constructor(){
    this.tableName = "Users";
  }

  /**
   * 获取所有用户信息
   * @param {Object} token
   */
  getUsers(){
    return connection.select({
      from: this.tableName
    });
  }

  /**
   * 判断是否存在token
   * @param {Object} token
   */
  hasToken(token){
    return connection.select({
      from: this.tableName,
      where: {
        token
      }
    })
  }

  /**
   * 退出
   * @param {Object} token
   */
  logout(token){
    return connection.update({
            in: this.tableName,
            set: {
              token: null
            },
            where: {
              token
            }
          });
  }

  /**
   * 登录
   * @param {Object} params
   */
  login(params){
    
  }

  /**
   * 添加用户数据
   * @param {Object} data
   */
  insertUser(data){
    //增加创建和更新时间
    Object.assign(data, {
      createtime: new Date(),
      updatetime: new Date()
    });
    //加密密码
    if('undefined'!==typeof data['password']){
      data['password'] = hex_md5(data.password);
    }
    //if end
    return connection.insert({
      into: this.tableName,
      values: [data]
    });
  }

}

        后面登录功能实现,将全部通过user.js模型中函数实现。

2.4 api接口

        这里虽然无后台服务接口调用,但为方便使用user实例,还是通过api方式实现功能调用。代码方式如下:

import { UserService } from '@/db/model/user'

// 实例Users对象
const Users = new UserService();

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

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

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

/**
 * 添加用户信息
 */
export const addUserInfo = param => {
  return Users.insertUser(param);
}

/**
 * 获取用户列表
 */
export const userAllList = keyword => {
  return Users.getUsers(keyword);
}

 2.5 连接数据库

        在App.vue中,实现数据库的初始化,并且当数据库连接成功后,判断用户是否存在,不存在则创建一个默认用户,代码如下:

<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
  import { mapGetters } from 'vuex'
  import { initJsStore } from '@/db/service.js'
  import { addUserInfo, userAllList } from '@/api'
  export default {
    name: 'App',
    data(){
      return {}
    },
    created() {
      this.$store.dispatch('reloadUserInfo');
      //初始化数据库
      initJsStore().then(() => {
        //判断用户是否存在,不存在默认添加一个
        userAllList().then(res => {
          if(res.length==0){
            //添加管理员
            addUserInfo({
              username: "admin",
              password: "123456",
              role: 1
            });
          }
        })
      });
    }
  }
</script>

<style>
  * {
    margin: 0;
    padding: 0;
  }

  html,
  body {
    width: 100%;
    height: 100%;
  }

  #app {
    width: 100%;
    font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    font-size: 12px;
    color: #2c3e50;
  }
</style>

三、功能实现

3.1 检测token是否存在

        在2.3中的代码,已实现了hasToken函数,在2.4中的代码,api接口函数已定义了tokenIsFailure()函数,此时我们修改actions.js中的checkIsLogin()函数即可。代码如下:

checkIsLogin({commit}){
	let token = Vue.ls.get(TOKEN);
	return new Promise((resolve, reject) => {
		if(token){
            //判断数据库中token是否存在
			tokenIsFailure(token).then(() => {
				Vue.ls.set(TOKEN, token, 24 * 60 * 60 * 1000);
				resolve();
			}).catch(e => {
				commit(TOKEN, '');
				Vue.ls.remove(TOKEN);
				reject();
			});
		}else{
			reject();
		}
	});
}

        这样一来,当数据库中的token因某些原因失效后,即可将localStorge中缓存及时清除,并在路由跳转时,及时跳转到登录页面。

 

3.2 登录功能 - 事务处理

        用户的登录功能相对较为复杂,完成登录这一步,需要实现以下几个步骤:

  1. 查询用户 - 判断用户名是否存在,
  2. 判断输入的密码是否正确
  3. 生成token,并将token保存到相对用户信息中
  4. 返回登录成功的用户信息和token信息

        此时,我们在db目录中创建transaction.js文件,代码发如下:

import { randomStrName } from '@/utils/utils'

const userTableName = "Users";

/**
 * 登录并生成token信息
 * @param {Object} ctx
 */
async function login2accesstoken(ctx) {

  ctx.start(); // 开始事务

  //1.获取用户信息
  const userRes = await ctx.select({
    from: userTableName,
    where: {
      username: ctx.data.username
    }
  });

  let userinfo;
  //用户存在
  if(userRes.length==1){
    userinfo = userRes[0];
    ctx.setResult('userinfo', userinfo);
  }
  //用户不存在
  else{
    ctx.setResult('code', -1);
    ctx.setResult('msg', "用户名不存在");
    ctx.abort();
    return;
  }

  //2.判断密码是否正确
  if(userinfo.password!=ctx.data.password){
    ctx.setResult('code', -1);
    ctx.setResult('msg', "密码错误");
    ctx.abort();
    return;
  }else{
    //删除密码信息
    delete userinfo.password;
  }

  //3.生成token
  const accesstoken = randomStrName(60);

  //4.存入token 信息
  ctx.update({
    in: userTableName,
    set: {
      token: accesstoken
    },
    where: {
      id: userinfo.id
    }
  });

  //5.返回结果信息
  ctx.setResult('code', 0);
  ctx.setResult('msg', "登录成功");
  ctx.setResult('data', {
    accesstoken,
    userinfo
  });
}

// 将函数 login2accesstoken 赋值到window对象上。
window.login2accesstoken = login2accesstoken;

        如上代码,我们按照实现步骤,依次完成用户查询、账号和密码校验、token生成和保存,以及登录成功后数据返回一系列操作。

        此时,我们可以完成user.js中的login()函数了,首先是将transaction.js文件引入,代码如下:

import connection from "@/db/index.js";
import { hex_md5 } from '@/utils/md5'
import '../transaction.js'

export class UserService {
  constructor(){
    this.tableName = "Users";
  }

  /**
   * 获取所有用户信息
   * @param {Object} token
   */
  getUsers(){
    return connection.select({
      from: this.tableName
    });
  }

  /**
   * 判断是否存在token
   * @param {Object} token
   */
  hasToken(token){
    return connection.select({
      from: this.tableName,
      where: {
        token
      }
    })
  }

  /**
   * 退出
   * @param {Object} token
   */
  logout(token){
    return connection.update({
            in: this.tableName,
            set: {
              token: null
            },
            where: {
              token
            }
          });
  }

  /**
   * 登录
   * @param {Object} params
   */
  login(params){
    return connection.transaction({
					tables: [this.tableName],
					method: "login2accesstoken",
					data: {
						username: params.username,
						password: hex_md5(params.password)
					}
				});
  }

  /**
   * 添加用户数据
   * @param {Object} data
   */
  insertUser(data){
    //增加创建和更新时间
    Object.assign(data, {
      createtime: new Date(),
      updatetime: new Date()
    });
    //加密密码
    if('undefined'!==typeof data['password']){
      data['password'] = hex_md5(data.password);
    }
    //if end
    return connection.insert({
      into: this.tableName,
      values: [data]
    });
  }

}

        将transaction.js中定义的login2accesstoken()函数,赋值到transaction的method参数上即可。

3.3 实现登录功能

        通过以上功能实现,用户模型中已完成登录接口功能,在2.4的代码中,我们也实现登录接口函数定义,这里我们直接使用loginInfo()函数即可。我们找到登录页并打开,代码如下:

submitForm(formName) {
	this.$refs[formName].validate((valid) => {
		if (valid) {
            //调用登录函数,实现用户登录
			loginInfo(this.ruleForm).then(res => {
				if(res.code==0){
                    //缓存用户信息
					this.$store.dispatch('saveLoginInfo', {
						token: res.data.accesstoken,
						userinfo: res.data.userinfo
					})
					this.$message.success(res.msg);
                    //跳转到首页
					setTimeout(() => {
						this.$router.push('/');
					}, 200);
				}else{
					this.$message.error(res.msg);
					this.resetForm('ruleForm');
				}
			}).catch(e => {
				this.$message.error(e.message);
				this.resetForm('ruleForm');
			});

		} else {
			console.log('error submit!!');
			return false;
		}
	});
}

四、属性

         JsStore在事务api中接受一个方法名,该方法名通过上下文调用。

        这个上下文所包含的属性如下:

start启动事务
select查询数据表
count统计查询数据总条数
update更新数据表信息
remove移除
insert插入
setResult setResult接受键和值。setResult用于保存事务完成时返回的值。事务返回一个对象,该对象是键和值的形式,使用setResult设置。
abortAbort用于中止事务。
getResultgetResult用于获取setResult设置的值。
data在事务API中作为数据传递的值。

        这里通过事务实现的功能相对较为简单,大家可以了解下JsStore官网提供的案例。希望此篇对大家有所帮助。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值