首先进入管网下载项目,执行数据库,admin中resources中修改数据库、redis

登陆生成验证码

后端生成表达式,1+1=?@2

1+1=?转成图片,传到前端展示   2存入redis通过key,value进行判断,点击登陆在判断

前端页面全在src的views中,打开login.vue,找到初始化方法

created() {
    this.getCode();
    this.getCookie();
  },
  methods: {
    getCode() {
      getCodeImg().then(res => {
        this.captchaOnOff = res.captchaOnOff === undefined ? true : res.captchaOnOff;
        if (this.captchaOnOff) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;    //key值存入表单,之后再redis找
        }
      });
    },

在src-api中

// 获取验证码
export function getCodeImg() {
  return request({     
    url: '/captchaImage',
    headers: {
      isToken: false
    },
    method: 'get',
    timeout: 20000
  })
}

request引入utils中request.js

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分,具体在配置文件中.evn
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000
})

请求做了一个反向代理,请求前端,映射到后端

在vue.config.js配置文件中,

proxy: {
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        target: `http://localhost:8080`,
        changeOrigin: true,
        pathRewrite: {
        //路径重写  将/dev-api替换为''在映射到8080
          ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      }
    },

后端代码:@GetMapping("/captchaImage")

登陆:login.vue判断逻辑在user.js

actions: {
  // 登录
  Login({ commit }, userInfo) {
    const username = userInfo.username.trim()
    const password = userInfo.password
    const code = userInfo.code
    const uuid = userInfo.uuid
    //实现异步处理
    return new Promise((resolve, reject) => {
      login(username, password, code, uuid).then(res => {
        setToken(res.token)
        commit('SET_TOKEN', res.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

后端登陆流程,校验验证码、校验用户名密码、生成token

public AjaxResult login(@RequestBody LoginBody loginBody)
{
    AjaxResult ajax = AjaxResult.success();
    // 生成令牌  封装方法
    String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
            loginBody.getUuid());
    ajax.put(Constants.TOKEN, token);
    return ajax;
}

验证码判断

public void validateCaptcha(String username, String code, String uuid)
    {
        //获取对应uuid
        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
        //比较输入与redis中存的是否一样
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        //过期抛出异常
        if (captcha == null)
        {
            //记录日志  异步通过线程池记录
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        //验证码错误
        if (!code.equalsIgnoreCase(captcha))
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId)
    {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }
}

获取角色与权限

在permission.js中(每次页面跳转都要进入这个方法里)

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (store.getters.roles.length === 0) {
        // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetInfo').then(() => {
          store.dispatch('GenerateRoutes').then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            router.addRoutes(accessRoutes) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
            store.dispatch('LogOut').then(() => {
              Message.error(err)
              next({ path: '/' })
            })
          })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

后端

/**
 * 获取用户信息
 * 
 * @return 用户信息
 */
@GetMapping("getInfo")
public AjaxResult getInfo()
{
    SysUser user = SecurityUtils.getLoginUser().getUser();
    // 角色集合
    Set<String> roles = permissionService.getRolePermission(user);
    // 权限集合
    Set<String> permissions = permissionService.getMenuPermission(user);
    AjaxResult ajax = AjaxResult.success();
    ajax.put("user", user);
    ajax.put("roles", roles);
    ajax.put("permissions", permissions);
    return ajax;
}

权限对应数据库  sys_role表、sys_user、sys_user_role,获取之后返回给前端,在user.js

// 获取用户信息
GetInfo({ commit, state }) {
  return new Promise((resolve, reject) => {
    getInfo().then(res => {
      const user = res.user
      const avatar = user.avatar == "" ? require("@/assets/images/profile.jpg") : process.env.VUE_APP_BASE_API + user.avatar;
      //有角色
      if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
        commit('SET_ROLES', res.roles)    //存入vuex中
        commit('SET_PERMISSIONS', res.permissions)
      } else {
        commit('SET_ROLES', ['ROLE_DEFAULT'])
      }
      commit('SET_NAME', user.userName)
      commit('SET_AVATAR', avatar)
      resolve(res)
    }).catch(error => {
      reject(error)
    })
  })
},

获取权限,对应路由菜单,对应sys_menu

/**
 * 获取路由信息
 * 
 * @return 路由信息
 */
@GetMapping("getRouters")
public AjaxResult getRouters()
{
    Long userId = SecurityUtils.getUserId();
    List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
    //根据用户查出对应菜单,在生成动态路由
    return AjaxResult.success(menuService.buildMenus(menus));
}

数据库中通过parent_id与menu_id关联,当parent_id为0找不到对应的菜单id则证明没有父节点,若有对应

则为二级菜单

后端通过递归获取菜单

/**
     * 根据父节点的ID获取所有子节点
     * 
     * @param list 分类表
     * @param parentId 传入的父节点ID
     * @return String
     */
    public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
    {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
        {
            SysMenu t = (SysMenu) iterator.next();
            // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
            if (t.getParentId() == parentId)
            {
                recursionFn(list, t);
                //继续从二级菜单找三级
                returnList.add(t);
            }
        }
        return returnList;
    }

返回到侧边栏,Layout-compontents-Sidebar-index.vue

用循环遍历出菜单

<sidebar-item
    v-for="(route, index) in sidebarRouters"
    :key="route.path  + index"
    :item="route"
    :base-path="route.path"
/>

首页内容通过index.js,重定向到新页面

{
  path: '',
  component: Layout,
  redirect: 'index',
  children: [
    {
      path: 'index',
      component: (resolve) => require(['@/views/index'], resolve),
      name: 'Index',
      meta: { title: '首页', icon: 'dashboard', affix: true }
    }
  ]
},

点击用户管理会跳到新页面,对应数据库中菜单表component