• royi-vue


    若依(前后端分离版)

    1.1 什么是若依?

    一套权限管理的开源项目

    1. 用,减少自己的工作量
    2. 学习优秀开源项目的底层编程思想,设计思路,提高自己的编程能力

    1.2登录逻辑

    1.2.1 生成验证码

    基本思路

    1+1=?@2

    1+1=?转成图片存入前端,

    2存入redis 前端通过 unid 来拿到后端的值

    http://localhost/dev-api/captcaImage 前端请求

    它做了一个反向管理,进行代理,映射到后端,解决跨域问题

    devServer: {
      host: '0.0.0.0',
          //如果希望外部网络访问就需要制定如下 localhost 是127.0.0.1 专供自己访问,告诉服务器监听0.0.0.0意味着让服务器监听每一个端口
      port: port,
      open: true,
      proxy: {
        // detail: https://cli.vuejs.org/config/#devserver-proxy
        [process.env.VUE_APP_BASE_API]: {
          target: `http://localhost:8080`,
          changeOrigin: true,
          pathRewrite: {
            ['^' + process.env.VUE_APP_BASE_API]: ''
              //将 /dev-api 替换成 ''再映射到 8080 端口
          }
        }
      },
      disableHostCheck: true
        
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    验证码后端逻辑

     @GetMapping("/captchaImage")
        public AjaxResult getCode(HttpServletResponse response) throws IOException
        {
            AjaxResult ajax = AjaxResult.success();
    //        查看验证码的开头是否开启
            boolean captchaOnOff = configService.selectCaptchaOnOff();
            ajax.put("captchaOnOff", captchaOnOff);
            if (!captchaOnOff)
            {
                return ajax;
            }
    
            // 保存验证码信息生成 uuid
            String uuid = IdUtils.simpleUUID();
            String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
    
            String capStr = null, code = null;
            BufferedImage image = null;
    
            // 生成验证码
            if ("math".equals(captchaType))
            {
                String capText = captchaProducerMath.createText();
                capStr = capText.substring(0, capText.lastIndexOf("@"));
                code = capText.substring(capText.lastIndexOf("@") + 1);
                image = captchaProducerMath.createImage(capStr);
            }
            else if ("char".equals(captchaType))
            {
                capStr = code = captchaProducer.createText();
                image = captchaProducer.createImage(capStr);
            }
    		//存入 将这个答案存入 redis
            redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES);
            // 转换流信息写出
            FastByteArrayOutputStream os = new FastByteArrayOutputStream();
            try
            {
                ImageIO.write(image, "jpg", os);
            }
            catch (IOException e)
            {
                return AjaxResult.error(e.getMessage());
            }
    
            ajax.put("uuid", uuid);
            ajax.put("img", Base64.encode(os.toByteArray()));
            return ajax;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    使用kaptcha工具包进行生成

    1.2.2 登录的流程

    1. 校验验证码
    2. 校验用户名与密码
    3. 生成 token

    使用异步任务管理器结合线程池,实现了异步操作日志记录,和业务逻辑实现解耦合

    boolean captchaOnOff = configService.selectCaptchaOnOff();
            // 验证码开关
            if (captchaOnOff)
            {
                validateCaptcha(username, code, uuid);
            }
            // 用户验证
            Authentication authentication = null;
            try
            {
                // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                authentication = authenticationManager
                        .authenticate(new UsernamePasswordAuthenticationToken(username, password));
            }
            catch (Exception e)
            {
                if (e instanceof BadCredentialsException)
                {
                    //异步生成日志 先是生成一个任务丢到线程池内,由线程池分配一个线程让它来执行
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                    throw new UserPasswordNotMatchException();
                }
                else
                {
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                    throw new ServiceException(e.getMessage());
                }
            }
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    //        将每次的登录记录,记录在 login_info数据库 里面
            recordLoginInfo(loginUser.getUserId());
            // 生成token
            return tokenService.createToken(loginUser);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    1.2.3 getInfo

    获取当前用户的角色与权限信息,存储到 vuex 中

    前端

    在 permission 内进行发送请求

    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信息
            // dispatch:含有异步操作,数据提交至 actions ,可用于向后台提交数据
    		//commit:同步操作,数据提交至 mutations ,可用于登录成功后读取用户信息写到缓存里
            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()
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    后端逻辑

    @GetMapping("getInfo")
    public AjaxResult getInfo()
    {
        SysUser user = SecurityUtils.getLoginUser().getUser();
        // 角色集合
        Set<String> roles = permissionService.getRolePermission(user);
        // 权限集合
        Set<String> permissions = permissionService.getMenuPermission(user);//查是什么角色然后拿到权限 如果是 admin 用户直接给所有的权限如果是其它用户就需要查 user_role 与 role 与 user 表一起拿到用户的具体的权限
        AjaxResult ajax = AjaxResult.success();
        ajax.put("user", user);
        ajax.put("roles", roles);
        ajax.put("permissions", permissions);
        return ajax;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.2.4 getRouters

    根据当前权限获取动态路由

    @GetMapping("getRouters")
    public AjaxResult getRouters()
    {
        Long userId = SecurityUtils.getUserId();
        List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
        return AjaxResult.success(menuService.buildMenus(menus));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在 select MenuTreeByUserId 中最后调用了getChildPerms 这个方法,执行递归返回一个多级菜单的对象给前端

    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;
    }
    private void recursionFn(List<SysMenu> list, SysMenu t)
        {
            // 得到子节点列表
            List<SysMenu> childList = getChildList(list, t);
            t.setChildren(childList);
            for (SysMenu tChild : childList)
            {
                if (hasChild(list, tChild))
                {
                    recursionFn(list, tChild);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    1.3用户管理

    1.3.1.list

    流程: 加载 vue 页面->请求后台数据

    @PreAuthorize("@ss.hasPermi('system:user:list')")//对其进行权限控制
    @GetMapping("/list")
    public TableDataInfo list(SysUser user)
    {
        startPage();//在这里用 pageHelper 进行将查询语句拦截进行封装分页
        List<SysUser> list = userService.selectUserList(user);
        return getDataTable(list);
    }
    @Override
    @DataScope(deptAlias = "d", userAlias = "u") //查询的时候给表设置别名
    public List<SysUser> selectUserList(SysUser user)
    {
        return userMapper.selectUserList(user);
    }
    protected void startPage()
        {
            PageDomain pageDomain = TableSupport.buildPageRequest();
            Integer pageNum = pageDomain.getPageNum();
            Integer pageSize = pageDomain.getPageSize();
            if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
            {
                String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
                Boolean reasonable = pageDomain.getReasonable();
                PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
                //这里的 reasonable 是对参数进行逻辑处理,保证参数的正确性
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    1.3.2 treeselect

    1. 查出所有的部门数据

    2. 组装成一个数组

      @GetMapping("/treeselect")
      public AjaxResult treeselect(SysDept dept)
      {
          List<SysDept> depts = deptService.selectDeptList(dept);
          return AjaxResult.success(deptService.buildDeptTreeSelect(depts));
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    1.3.3 查询数据

    // 节点单击事件
    handleNodeClick(data) {
      this.queryParams.deptId = data.id;
      this.getList();
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后发请求给后端,后端返回接口

    1.3.4 新增按钮

    前端

    /** 新增按钮操作 */
    handleAdd() {
      this.reset();
      this.getTreeselect();//getUser 是获取用户后台信息
      getUser().then(response => {
        this.postOptions = response.posts;
        this.roleOptions = response.roles;
        this.open = true;
        this.title = "添加用户";
        this.form.password = this.initPassword;
      });
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    后端

    userId 是如果有 userId 就是修改,如果没有 userId 就是创建

    @PreAuthorize("@ss.hasPermi('system:user:query')")
    @GetMapping(value = { "/", "/{userId}" })
    public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId)
    {
        userService.checkUserDataScope(userId);
        AjaxResult ajax = AjaxResult.success();
        List<SysRole> roles = roleService.selectRoleAll();
        ajax.put("roles", SysUser.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList()));
        ajax.put("posts", postService.selectPostAll());
        if (StringUtils.isNotNull(userId))
        {
            ajax.put(AjaxResult.DATA_TAG, userService.selectUserById(userId));
            ajax.put("postIds", postService.selectPostListByUserId(userId));
            ajax.put("roleIds", roleService.selectRoleListByUserId(userId));
        }
        return ajax;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    @PreAuthorize("@ss.hasPermi('system:user:add')")
        @Log(title = "用户管理", businessType = BusinessType.INSERT)
        @PostMapping
    public AjaxResult add(@Validated @RequestBody SysUser user)
    {
        if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName())))
        {
            return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,登录账号已存在");
        }
        else if (StringUtils.isNotEmpty(user.getPhonenumber())
                 && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
        {
            return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
        }
        else if (StringUtils.isNotEmpty(user.getEmail())
                 && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
        {
            return AjaxResult.error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在");
        }
        user.setCreateBy(getUsername());
        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
        return toAjax(userService.insertUser(user));
    }
    @Override
    @Transactional
    public int insertUser(SysUser user)
    {
        // 新增用户信息
        int rows = userMapper.insertUser(user);
        // 新增用户岗位关联
        insertUserPost(user);
        // 新增用户与角色管理
        insertUserRole(user);
        return rows;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    1.3.5 更改角色

    前端

    /** 修改按钮操作 */
    handleUpdate(row) {
      this.reset();
      this.getTreeselect();
      const userId = row.userId || this.ids;
      getUser(userId).then(response => {
        this.form = response.data;
        this.postOptions = response.posts;
        this.roleOptions = response.roles;
        this.form.postIds = response.postIds;
        this.form.roleIds = response.roleIds;
        this.open = true;
        this.title = "修改用户";
        this.form.password = "";
      });
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
     @PreAuthorize("@ss.hasPermi('system:user:edit')")
       @Log(title = "用户管理", businessType = BusinessType.UPDATE)
       @PutMapping
        public AjaxResult edit(@Validated @RequestBody SysUser user)
        {
            userService.checkUserAllowed(user);
            if (StringUtils.isNotEmpty(user.getPhonenumber())
                    && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user)))
            {
                return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,手机号码已存在");
            }
            else if (StringUtils.isNotEmpty(user.getEmail())
                    && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user)))
            {
                return AjaxResult.error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
            }
            user.setUpdateBy(getUsername());
            return toAjax(userService.updateUser(user));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    数据库:

    1. 修改User
    2. 重新维护它的 user_post 和 user_role
    @Override
    @Transactional 添加了事务如果出现了异常就会直接回滚了
        public int updateUser(SysUser user)
        {
            Long userId = user.getUserId();
            // 删除用户与角色关联
            userRoleMapper.deleteUserRoleByUserId(userId);
            // 新增用户与角色管理
            insertUserRole(user);
            // 删除用户与岗位关联
            userPostMapper.deleteUserPostByUserId(userId);
            // 新增用户与岗位管理
            insertUserPost(user);
            return userMapper.updateUser(user);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.3.6 删除数据

    前端

    /** 删除按钮操作 */
    handleDelete(row) {
      const userIds = row.userId || this.ids;
      this.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?').then(function() {
        return delUser(userIds);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    后端

    /**
     * 删除用户
     */
    @PreAuthorize("@ss.hasPermi('system:user:remove')")
    @Log(title = "用户管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{userIds}")
    public AjaxResult remove(@PathVariable Long[] userIds)
    {
        if (ArrayUtils.contains(userIds, getUserId()))//判断当前用户是否在要删除的用户中,如果在就抛异常
        {
            return error("当前用户不能删除");
        }
        return toAjax(userService.deleteUserByIds(userIds));//开始删除 
    }
    /**
         * 批量删除用户信息
         * 
         * @param userIds 需要删除的用户ID
         * @return 结果
         */
        @Override
        @Transactional
        public int deleteUserByIds(Long[] userIds)
        {
            for (Long userId : userIds)
            {
                checkUserAllowed(new SysUser(userId));
            }
            // 删除用户与角色关联
            userRoleMapper.deleteUserRole(userIds);
            // 删除用户与岗位关联
            userPostMapper.deleteUserPost(userIds);
            return userMapper.deleteUserByIds(userIds);// 这个并不是真正的删除只是逻辑删除,就是在数据库中有一个字段表明它是否被删除了
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    <delete id="deleteUserByIds" parameterType="Long">
       update sys_user set del_flag = '2' where user_id in 
       <foreach collection="array" item="userId" open="(" separator="," close=")">
          #{userId}
          </foreach> 
        <!--> 可以看到其中只是改了一个字段 将 del_flag 置为2<--->
    </delete>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.4 异步任务管理器

    //异步生成日志 先是生成一个任务丢到线程池内,由线程池分配一个线程让它来执行
    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
    throw new UserPasswordNotMatchException();
    
    • 1
    • 2
    • 3
    /**
     * 单例模式属于 饿汉式即上来就创建了一个 对象也不管你用不用
     */
    private AsyncManager(){}
    
    private static AsyncManager me = new AsyncManager();
    
    public static AsyncManager me()
    {
        return me;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    excute 函数需要传入一个 Task 对象 Task 类实现了Runable 接口,是一个任务由线程Thread 去执行

    /**
     * 记录登录信息
     * 
     * @param username 用户名
     * @param status 状态
     * @param message 消息
     * @param args 列表
     * @return 任务task
     */
    public static TimerTask recordLogininfor(final String username, final String status, final String message,
            final Object... args)
    {
        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        return new TimerTask()
        {
            @Override
            public void run()
            {
                String address = AddressUtils.getRealAddressByIP(ip);
                StringBuilder s = new StringBuilder();
                s.append(LogUtils.getBlock(ip));
                s.append(address);
                s.append(LogUtils.getBlock(username));
                s.append(LogUtils.getBlock(status));
                s.append(LogUtils.getBlock(message));
                // 打印信息到日志
                sys_user_logger.info(s.toString(), args);
                // 获取客户端操作系统
                String os = userAgent.getOperatingSystem().getName();
                // 获取客户端浏览器
                String browser = userAgent.getBrowser().getName();
                // 封装对象
                SysLogininfor logininfor = new SysLogininfor();
                logininfor.setUserName(username);
                logininfor.setIpaddr(ip);
                logininfor.setLoginLocation(address);
                logininfor.setBrowser(browser);
                logininfor.setOs(os);
                logininfor.setMsg(message);
                // 日志状态
                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
                {
                    logininfor.setStatus(Constants.SUCCESS);
                }
                else if (Constants.LOGIN_FAIL.equals(status))
                {
                    logininfor.setStatus(Constants.FAIL);
                }
                // 插入数据
                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    封装了登录用户的信息,执行添加操作,这里并不会执行而是将任务交给线程对象来执行.

    异步任务管理器,内部定义了一个线程池,然后根据业务创建添加日志的任务,解耦合,日志全部统一处理

    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService()
    {
        return new ScheduledThreadPoolExecutor(corePoolSize,
                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
        {
            @Override
            protected void afterExecute(Runnable r, Throwable t)
            {
                super.afterExecute(r, t);
                Threads.printException(r, t);
            }
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    1.5 代码自动生成

    分别在admin src 与 ui src 目录下复制粘贴然后直接再 rebuild 一下项目实现生成修改操作

    在这里插入图片描述

    前后端之间使用 jwt 对 token 进行封装,然后这里作个简单的鉴别

    what is jwt JSON WEB TOKEN

    JWT,为了在网络应用环境间传递声明面执行的一种基于 JSON 的开放标准,可以用来认证也可用于加密。

    session

    session 保存在服务端内存中的一个对象,主要用来存储所有访问过该服务商的客户端信息,从而保存用户会话状态,但服务端重启用户信息也就消失

    token

    与 session 的原理大概相同

    流程上是这样的:

    • 用户使用用户名密码来请求服务器
    • 服务器进行验证用户的信息
    • 服务器通过验证发送给用户一个token
    • 客户端存储token,并在每次请求时附送上这个token值
    • 服务端验证token值,并返回数据

    这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持跨域。

    jwt 长什么样

    jwt 本身并不安全,依赖的是 https 协议

    jwt 由三段信息构成 头部,载荷,签证

    三段都会进行 base64 进行加密

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

    签证

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    导入 excel

    pol与easy excel讲解

    常用场景

    1. 将用户信息导出为 excel
    2. 将 excel 表中的信息导入到网站数据库

    使用

     // 1. 创建一个工作薄 03版本 
            Workbook workbook = new HSSFWorkbook();
    //        2. 创建一个工作表
            Sheet sheet = workbook.createSheet("猿创孵化表");
    //        3.创建一个行 0代表第一行
            Row row = sheet.createRow(0);
    //        4.创建一个单元格 0 代表第一个单元格
            Cell cell11 = row.createCell(0);
    //        5. 设置值
            cell11.setCellValue("姓名");
            Cell cell13 = row.createCell(2);
            Cell cell12 = row.createCell(1);
            cell12.setCellValue("班级");
            String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
            cell12.setCellValue(time);
    //        获得输出流
            FileOutputStream fileOutputStream = new FileOutputStream("猿创算法孵化表.xls");
    //        写入位置
            workbook.write(fileOutputStream);
    //        关闭流
            fileOutputStream.close();
            System.out.println("文件生成完毕");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
      // 1. 创建一个工作薄 07 07的使用只有后缀名的不同与类的不同
            Workbook workbook = new XSSFWorkbook();
    //        2. 创建一个工作表
            Sheet sheet = workbook.createSheet("猿创孵化表");
    //        3.创建一个行 0代表第一行
            Row row = sheet.createRow(0);
    //        4.创建一个单元格 0 代表第一个单元格
            Cell cell11 = row.createCell(0);
    //        5. 设置值
            cell11.setCellValue("姓名");
            Cell cell13 = row.createCell(2);
            Cell cell12 = row.createCell(1);
            cell12.setCellValue("班级");
            String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
            cell12.setCellValue(time);
    //        获得输出流
            FileOutputStream fileOutputStream = new FileOutputStream("猿创算法孵化表07.xlsx");
    //        写入位置
            workbook.write(fileOutputStream);
    //        关闭流
            fileOutputStream.close();
            System.out.println("文件生成完毕");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    大数据量时

    03版本的比较快,但不能超过65536行

    07版本的比较慢

    为了解决07版本比较慢的这一点我们可以使用 sxxfworkbook 来加速这一过程,

    它是实现 BigGridDemo 策略的流版本,它允许编写非常大的数据而不用担心内存耗尽

    因为在任何时候,只有可配置的一部分保存在内存中

    注意

    过程中会产生临时文件,需要清理临时文件,默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件

    @Test
    public void testWrite03()throws Exception{
        long begin = System.currentTimeMillis();
        Workbook workbook = new SXSSFWorkbook();
        Sheet sheet = workbook.createSheet("猿创狗仔计划");
        for (int i = 0; i < 65536; i++) {
            Row row = sheet.createRow(i);
            for (int j = 0; j < 10; j++) {
                Cell cell = row.createCell(j);
                cell.setCellValue(j);
            }
        }
        FileOutputStream fileOutputStream = new FileOutputStream("猿创狗仔计划.xlsx");
        workbook.write(fileOutputStream);
    
        fileOutputStream.close();
        ((SXSSFWorkbook) workbook).dispose();
        long end = System.currentTimeMillis();
        System.out.println(end-begin);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    工作中狠一点就是表读过来,存入集合,再遍历放入数据库,再根据数据库中的字段在前端解析

    普通的 poi 太复杂,直接上 easyexcel

    导入依赖

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.0.5</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // 写法2
    String fileName = "Test.xlsx";
    // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
    // 如果这里想使用03 则 传入excelType参数即可
    EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后直接调 api 进行

    下载文件逻辑

    在 rouyi-admin 中的 CommonController 中进行修改,先对后缀名进行合法性验证,然后得到

    try
    {
        if (!FileUtils.checkAllowDownload(fileName))//这里主要对后缀名及非法下载进行处理
        {
            throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
        }
        String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);//这里是合成一个生成后的文件名称
        String filePath = RuoYiConfig.getDownloadPath() + fileName;//通过读取royi的配置文件得到文件应该存放的位置
    
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);//设置响应类型
        FileUtils.setAttachmentResponseHeader(response, realFileName);//设置响应头
        FileUtils.writeBytes(filePath, response.getOutputStream());//这个非常重要大概是通过
        if (delete)
        {
            FileUtils.deleteFile(filePath);
        }
    }
    catch (Exception e)
    {
        log.error("下载文件失败", e);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    导入文件是 SysUserController.java 里面的 importData 方法负责

    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
    {
        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class); //获取这个工具类
        List<SysUser> userList = util.importExcel(file.getInputStream()); // 将文件读入流放入 在内部进行表格的处理数据封装成一个 list 返回 其中已经写入了文件
        String operName = getUsername();
        String message = userService.importUser(userList, updateSupport, operName); //对这个list 进行检索并返回消息有多少条成功或者失败
        return AjaxResult.success(message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果我们需要自定义哪些数据可以被下载我们可以在SecurityConfig中进行配置

    若依的注销在其中也进行了配置

    公共基础

    • 前端获取方法:this.$store.state.user.name

    • 后端获取方法

      • String username = SecurityUtils.getUsername();
        
        • 1

      注解

      restController 注解 controller 会使return 的方法不会返回jsp 页面,或者html,配置的html 解析器不起作用,返回的内容就是return 的内容

      关于translational 注解只能放在 public 方法上,spring 的默认事务规则是当遇到运行异常与error时会回滚

      如果想针对检查异常进行事务回滚,可以在@Transactional注解里使用 rollbackFor属性明确指定异常。

      并且出现了异常不要试图去用catch去捕获

      @Transactional(rollbackFor = Exception.class)
      
      • 1

      responseBody 将 java 对象转换为 json 格式

      @ControllerAdvice 和 @RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
      RestControllerAdvice = ControllerAdvice + ResponseBody

      return new ScheduledThreadPoolExecutor(corePoolSize,
              new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build())
      {
          @Override
          protected void afterExecute(Runnable r, Throwable t)
          {
              super.afterExecute(r, t);
              Threads.printException(r, t);
          }
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      一种新语法即是 new 一个对象的同时,重写它父类的方法

      @Aspect

      @Pointcut("execution(public * com.rest.module..*.*(..))")
      //指定某一个包下的哪一个方法进行切面
      public void getMethods() {
      }
      
      • 1
      • 2
      • 3
      • 4
      // 指定注解
      @Pointcut("@annotation(com.rest.utils.SysPlatLog)")
      //只要加了这个注解我就来切
      public void withAnnotationMethods() {
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      3、Advice,在切入点上执行的增强处理,主要有五个注解:

      @Before 在切点方法之前执行

      ​ @After 在切点方法之后执行

      ​ @AfterReturning 切点方法返回后执行

      @AfterThrowing 切点方法抛异常执行

      @Around 属于环绕增强,能控制切点执行前,执行后

      1. @Resource(name = “captchaProducer”)

        • @Resource默认按byName自动注入。
        • 既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行进行查找,如果找到就注入。
        • 只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性的bean。
        • 只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
      2. @PreAuthorize(“@ss.hasPermi(‘monitor:operlog:list’)”)

        • @preAuthorize指定用户访问这个接口需要哪一部分权限,进行权限的验证
      3. @Value(“spring.port”)可以读取spring 配置文件中配置的值返回的是一个字符串

    • 相关阅读:
      【FreeRTOS】【STM32】06 FreeRTOS的使用-动态创建单任务
      Java swing(GUI) mysql实现的仓库进销存管理系统源码+运行教程
      【HCIA】交换机三层互通及链路可靠性
      【JVM】字节码技术:图解字节码形式下的 方法执行流程
      04、关联关系映射
      MIT 6.S081学习笔记(第〇章)
      npm install 卡在reify:rxjs: timing reifyNode的解决办法
      【Node.js】模块化:
      下载安装 VMware &虚拟机
      Eclipse项目导入笔记大全&踩坑大全
    • 原文地址:https://blog.csdn.net/qq_53008149/article/details/125427802