node.js+vue.js搭建程序设计类课程教学辅助系统 (2)

然后路由文件以下面的形式编写:

const knowledgePointDao = require('../dao/knowledgePointDao.js'); /** * 返回某门课的全部知识点,按章节分类 */ exports["GET /course/:c_id/knowledge_point"] = async (ctx, next) => { let res = await knowledgePointDao.getPontsOrderBySection(ctx.params.c_id); ctx.onSuccess(res); } //返回某位学生知识点答题情况 exports["GET /user/:u_id/course/:c_id/knowledge_point/condition"]=async(ctx,next)=>{ let {u_id,c_id}=ctx.params; let res = await knowledgePointDao.getStudentCondition(u_id,c_id); ctx.onSuccess(res); } b、权限验证

  权限管理是一个系统最重要的部分之一,目前主流的方式为基于角色的权限管理, 一个用户对应多个角色,每个角色对应多个权限(本系统中每个用户对应一个身份,每个身份对应多个角色)。我们的系统如何实现的呢?先从登录开始说起,本系统抛弃了传统的cookie,session模式,使用json web token(JWT)来做身份认证,用户登录后返回一个token给客户端,代码如下所示:

//生成随机盐值 let str = StringHelper.getRandomString(0, 10); //使用该盐值生成token let token = jwt.sign({ u_id: userInfo.u_id, isRememberMe }, str, { expiresIn: isRememberMe ? config.longTokenExpiration:config.shortTokenExpiration }); //token-盐值存入redis,如想让该token过期,redis中清楚该token键值对即可 await RedisHelper.setString(token, str, 30 * 24 * 60 * 60); res.code = 1; res.info = '登录成功'; res.data = { u_type: userInfo.u_type, u_id: userInfo.u_id, token };

以后每次客户端请求都要在header中设置该token,然后每次服务端收到请求都先验证是否拥有权限,验证代码使用router.use(auth),挂载到koa-router中,这样每次在进入具体的路由前都要先执行auth方法进行权限验证,主要验证代码逻辑如下:

/** * 1 验证成功 * 2 登录信息无效 401 * 3 已登录,无操作权限 403 * 4 token已过期 */ let verify = async (ctx) => { let token = ctx.headers.authorization; if (typeof (token) != 'string') { return 2; } let yan = await redisHelper.getString(token); if (yan == null) { return 2; } let data; try { data = jwt.verify(token, yan); } catch (e) { return 2; } if (data.exp * 1000 < Date.now()) { return 4; } //判断是否需要刷新token,如需要刷新将新token写入响应头 if (!data.isRememberMe && (data.exp * 1000 - Date.now()) < 30 * 60 * 1000) { //token有效期不足半小时,重新签发新token给客户端 let newYan = StringHelper.getRandomString(0, 10); let newToken = jwt.sign({ u_id: data.u_id, isRememberMe:false }, newYan, { expiresIn: config.shortTokenExpiration }); // await redisHelper.deleteKey(token); await redisHelper.setString(newToken, newYan,config.shortTokenExpiration); ctx.response.set('new-token', newToken); ctx.response.set('Access-Control-Expose-Headers','new-token'); } //获取用户信息 let userInfoKey = data.u_id + '_userInfo'; let userInfo = await redisHelper.getString(userInfoKey); if (userInfo == null || Object.keys(userInfo).length != 3) { userInfo = await mysqlHelper.first(`select u_id,u_type,j_id from user where u_id=?`, data.u_id); await redisHelper.setString(userInfoKey, JSON.stringify(userInfo), 24 * 60 * 60); }else{ userInfo = JSON.parse(userInfo); } ctx.userInfo = userInfo; //更新用户上次访问时间 mysqlHelper.execute(`update user set last_login_time=? where u_id=?`,Date.now(),userInfo.u_id); //管理员拥有全部权限 if (userInfo.u_type == 0) { return 1; } //获取该用户类型权限 let authKey = userInfo.j_id + '_authority'; let urls = await redisHelper.getObject(authKey); // let urls = null; if (urls == null) { urls = await mysqlHelper.row(` select b.r_id,b.url,b.method from jurisdiction_resource a inner join resource b on a.r_id = b.r_id where a.j_id=? `, userInfo.j_id); let temp = {}; urls.forEach(item => { temp[item.url + item.method] = true; }) await redisHelper.setObject(authKey, temp); urls = temp; } //判断是否拥有权限 if (urls.hasOwnProperty(ctx._matchedRoute.replace(config.url_prefix, '') + ctx.method)) { return 1; } else { return 3; } }

根据用户id获取用户身份id,根据用户身份id从redis中获取拥有的权限,如为null,从mysql数据库中拉取,并存入redis中,然后判断是否拥有要访问的url权限。

c、数据持久化

  本系统中使用mysql存储数据,redis做缓存,由于当时操作库不支持promise,故对它两做了个promise封装,方便代码中调用,参见:MysqlHelper,RedisHelper.js。

2、前端

  前端使用vue-cli构建vue项目,主要用到了vue-router,element-ui,axios这三个组件。

a、路由组织

  单页应用需要前端自己组织路由。本系统将路由分成了三个部分:公共,管理端,学生端。index.js如下:

export default new Router({ mode: 'history', base: '/app/', routes: [{ path: '', name: 'indexPage', component: IndexPage }, { path: '/about', name: 'about', component: About }, Admin, Client, Public, { path: '*', name: "NotFound", component: NotFound } ] })

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspzjf.html