若依Vue系统中的权限管理部分的功能都集中在了系统管理菜单模块中,如下图所示。其中权限部分主要涉及到了用户管理、角色管理、菜单管理、部门管理这四个部分。

一、若依Vue系统中的权限分类

根据观察,若依Vue系统中的权限分为以下几类

  • 菜单权限:用户登录系统之后能看到哪些菜单
  • 按钮权限:用户在一个页面上能看到哪些按钮,比如新增、删除等按钮
  • 接口权限:用户带着认证信息请求后端接口,是否有权限访问,该接口和前端页面上的按钮一一对应
  • 数据权限:用户有权限访问后端某个接口,但是不同的用户相同的接口相同的入参,根据权限大小不同,返回的结果应当不一样——权限大的能够看到的数据更多。

1.菜单权限

这个比较好理解,拥有不同权限的用户登录系统之后看到的菜单是不一样的,从新建菜单到给一个用户分配菜单权限,上一篇文章已经讲过,不赘述。

用户登录之后会请求后端的com.ruoyi.web.controller.system.SysLoginController#getRouters接口获取登录用户的菜单数据:

  1. select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, ifnull(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
  2. from sys_menu m
  3. left join sys_role_menu rm on m.menu_id = rm.menu_id
  4. left join sys_user_role ur on rm.role_id = ur.role_id
  5. left join sys_role ro on ur.role_id = ro.role_id
  6. left join sys_user u on ur.user_id = u.user_id
  7. where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0 AND ro.status = 0
  8. order by m.parent_id, m.order_num

菜单类型(M目录 C菜单 F按钮);菜单状态(0显示 1隐藏)

这是典型的用户-角色-菜单模型。

前端会根据该接口返回的数据渲染出不同的菜单。

2.按钮权限

新增按钮权限和新增菜单差不多,下图是我在新闻列表页面上新增了一个按钮叫做新闻新增,该按钮的权限分配和菜单的权限分配方法是一样的。

3.接口权限

每一个按钮基本上都会对应着一个后端的接口,前端会根据权限标志显示或者隐藏按钮,但是如果用户不点击按钮,直接通过http请求工具请求后端咋办?所以接口权限也是要有的,该权限和按钮上权限完全一致。

若依系统使用了SpringSecurity框架,接口权限都是基于注解@PreAuthorize实现的,比如,用户管理页面中的修改用户按钮对应的后端接口长这个样子

  1. @PreAuthorize("@ss.hasPermi('system:user:edit')")
  2. @Log(title = "用户管理", businessType = BusinessType.UPDATE)
  3. @PutMapping
  4. public AjaxResult edit(@Validated @RequestBody SysUser user)
  5. {
  6. ...
  7. }

和其对应的前端按钮权限标志一样

如果没有权限访问接口,则会返回类似如下信息:

  1. {
  2. "msg": "请求访问:/system/user/list,认证失败,无法访问系统资源",
  3. "code": 401
  4. }

4.数据权限

用户有权限访问后端某个接口,但是不同的用户相同的接口相同的入参,根据权限大小不同,返回的结果应当不一样——权限大的能够看到的数据更多。

体现在若依Vue系统中,举个例子,现在若以系统中有两个用户,一个是超级管理员admin,一个是普通用户kdyzm

它们两者均有用户管理、菜单管理、角色管理权限,所以它们能够看到相应的菜单并作出相应的操作,比如删除、新增、修改等。这里有个问题,kdyzm应当只能看到一部分数据,而超级管理员应当能够看到所有数据,在若依系统中,是通过对角色数据权限修改进行控制的。

所以,相同的权限,超级管理员能够看到的用户数量和普通用户kdyzm能够看到的用户数量是不一样的。

超级管理员看到的用户管理页面:

普通用户kdyzm看到的用户管理页面:

二、各种类型权限实现原理

1.菜单权限

菜单权限很简单,实际上就是简单的用户-角色-菜单模型,那么菜单是什么时候加载的呢?ruoyi-ui\src\permission.js,加载的逻辑在这个文件中。

permission.js文件中设置了导航守卫,每次路由发生变化的时候就会触发router.beforeEach的回调函数。

  1. router.beforeEach((to, from, next) => {
  2. NProgress.start()
  3. if (getToken()) {
  4. /* has token*/
  5. if (to.path === '/login') {
  6. next({ path: '/' })
  7. NProgress.done()
  8. } else {
  9. if (store.getters.roles.length === 0) {
  10. // 判断当前用户是否已拉取完user_info信息
  11. store.dispatch('GetInfo').then(res => {
  12. // 拉取user_info
  13. const roles = res.roles
  14. store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => {
  15. // 根据roles权限生成可访问的路由表
  16. router.addRoutes(accessRoutes) // 动态添加可访问路由表
  17. next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
  18. })
  19. }).catch(err => {
  20. store.dispatch('LogOut').then(() => {
  21. Message.error(err)
  22. next({ path: '/' })
  23. })
  24. })
  25. } else {
  26. next()
  27. }
  28. }
  29. } else {
  30. // 没有token
  31. if (whiteList.indexOf(to.path) !== -1) {
  32. // 在免登录白名单,直接进入
  33. next()
  34. } else {
  35. next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
  36. NProgress.done()
  37. }
  38. }
  39. })

注意if (store.getters.roles.length === 0) {这段逻辑,可以看出,如果不刷新当前页面,就算给用户添加了新的菜单权限,用户也看不到新的菜单。

2.按钮权限

按钮权限设置上和菜单权限基本上是一样的,是附着于页面中的细粒度权限。按钮权限体现在如果用户没有相应的权限,则看不到相关的按钮。这个是咋实现的呢?

先看下系统管理下的菜单管理中的修改、新增和删除按钮前端vue代码

  1. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  2. <template slot-scope="scope">
  3. <el-button size="mini"
  4. type="text"
  5. icon="el-icon-edit"
  6. @click="handleUpdate(scope.row)"
  7. v-hasPermi="['system:menu:edit']"
  8. >修改</el-button>
  9. <el-button
  10. size="mini"
  11. type="text"
  12. icon="el-icon-plus"
  13. @click="handleAdd(scope.row)"
  14. v-hasPermi="['system:menu:add']"
  15. >新增</el-button>
  16. <el-button
  17. size="mini"
  18. type="text"
  19. icon="el-icon-delete"
  20. @click="handleDelete(scope.row)"
  21. v-hasPermi="['system:menu:remove']"
  22. >删除</el-button>
  23. </template>
  24. </el-table-column>

el-button上有个属性v-hasPermi,这实际上是vue的自定义指令,属性值就是创建按钮的时候定义的那个权限标志。其定义在src/directive/permission/index.js文件

  1. import hasRole from './hasRole'
  2. import hasPermi from './hasPermi'
  3. const install = function(Vue) {
  4. Vue.directive('hasRole', hasRole)
  5. Vue.directive('hasPermi', hasPermi)
  6. }
  7. if (window.Vue) {
  8. window['hasRole'] = hasRole
  9. window['hasPermi'] = hasPermi
  10. Vue.use(install); // eslint-disable-line
  11. }
  12. export default install

其具体实现逻辑就在同目录的hasPermi.js文件中

  1. import store from '@/store'
  2. export default {
  3. inserted(el, binding, vnode) {
  4. const { value } = binding
  5. const all_permission = "*:*:*";
  6. const permissions = store.getters && store.getters.permissions
  7. if (value && value instanceof Array && value.length > 0) {
  8. const permissionFlag = value
  9. const hasPermissions = permissions.some(permission => {
  10. return all_permission === permission || permissionFlag.includes(permission)
  11. })
  12. if (!hasPermissions) {
  13. el.parentNode && el.parentNode.removeChild(el)
  14. }
  15. } else {
  16. throw new Error(`请设置操作权限标签值`)
  17. }
  18. }
  19. }

注意代码 el.parentNode && el.parentNode.removeChild(el),可以看到,如果没有按钮权限,则会将按钮本身从dom中移除。

3.接口权限

接口权限和前端的按钮权限一一对应。为的是防止用户绕过按钮直接请求后端接口获取数据。在若依Vue系统中,是使用SpringSecurity的注解@PreAuthorize实现的。

虽然只是一个注解,但是它是SpringSecurity+JWT集成的结晶~这个之后再细谈。

4.数据权限

数据权限实现的关键在于com.ruoyi.framework.aspectj.DataScopeAspect类。该类是一个切面类,凡是加上com.ruoyi.common.annotation.DataScope注解的方法,在执行的时候都会被它拦截。

该切面定义了五种权限范围

name code desc
DATA_SCOPE_ALL 1 全部数据权限
DATA_SCOPE_CUSTOM 2 自定数据权限
DATA_SCOPE_DEPT 3 部门数据权限
DATA_SCOPE_DEPT_AND_CHILD 4 部门及以下数据权限
DATA_SCOPE_SELF 5 仅本人数据权限

该切面的核心逻辑是“拼SQL”,方法执行之前,会给参数的一个params属性添加一个dataScope键值对,key为"dataScope",值为AND (" + sqlString.substring(4) + ")"样式的一段SQL,这段SQL会根据当前用户所在的部门以及当前用户角色的权限范围发生变化。

  1. StringBuilder sqlString = new StringBuilder();
  2. for (SysRole role : user.getRoles())
  3. {
  4. String dataScope = role.getDataScope();
  5. if (DATA_SCOPE_ALL.equals(dataScope))
  6. {
  7. sqlString = new StringBuilder();
  8. break;
  9. }
  10. else if (DATA_SCOPE_CUSTOM.equals(dataScope))
  11. {
  12. sqlString.append(StringUtils.format(
  13. " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
  14. role.getRoleId()));
  15. }
  16. else if (DATA_SCOPE_DEPT.equals(dataScope))
  17. {
  18. sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
  19. }
  20. else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
  21. {
  22. sqlString.append(StringUtils.format(
  23. " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
  24. deptAlias, user.getDeptId(), user.getDeptId()));
  25. }
  26. else if (DATA_SCOPE_SELF.equals(dataScope))
  27. {
  28. if (StringUtils.isNotBlank(userAlias))
  29. {
  30. sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
  31. }
  32. else
  33. {
  34. // 数据权限为仅本人且没有userAlias别名不查询任何数据
  35. sqlString.append(" OR 1=0 ");
  36. }
  37. }
  38. }

简单来说,这段代码的逻辑就是用户所在的部门权限越高,数据权限范围越大,查出来的结果集将会越大。

DataScope注解分别加到了部门列表查询、角色列表查询、用户列表查询的接口上,很明显,这几个接口需要根据不同的人查出不同的结果。

以用户列表查询为例,执行sql为

  1. <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
  2. select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
  3. left join sys_dept d on u.dept_id = d.dept_id
  4. where u.del_flag = '0'
  5. <if test="userName != null and userName != ''">
  6. AND u.user_name like concat('%', #{userName}, '%')
  7. </if>
  8. <if test="status != null and status != ''">
  9. AND u.status = #{status}
  10. </if>
  11. <if test="phonenumber != null and phonenumber != ''">
  12. AND u.phonenumber like concat('%', #{phonenumber}, '%')
  13. </if>
  14. <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
  15. AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
  16. </if>
  17. <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
  18. AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
  19. </if>
  20. <if test="deptId != null and deptId != 0">
  21. AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
  22. </if>
  23. <!-- 数据范围过滤 -->
  24. ${params.dataScope}
  25. </select>

其中,有这么一段代码

  1. <!-- 数据范围过滤 -->
  2. ${params.dataScope}

实际上DataScopeAspect切面就只干了填充params的dataScope属性这么一件事情。

三、若依Vue系统SpringSecurity+JWT

若依Vue系统中从用户登录到后端接口权限校验,都是基于SpringSecurity+JWT实现的,其中,SpringSecurity是核心,jwt只是为了保证token合法性的一种手段(签名防止篡改)。spring security集成的相关代码在ruoyi-framework模块的com.ruoyi.framework.security包以及com.ruoyi.framework.config.SecurityConfig类中。

SecurityConfig是核心配置类,所有的配置均在该类中。

1.用户登录

用户登录的逻辑在方法com.ruoyi.web.controller.system.SysLoginController#login中,一个典型的登录请求体如下所示

  1. {
  2. "username": "admin",
  3. "password": "admin123",
  4. "code": "0",
  5. "uuid": "a9fdbcbcb28748b796b5b77ad71bbb97"
  6. }

username和password分别是用户名和密码,code为验证码,uuid为验证码的唯一标识。登录成功之后会返回前端一个jwt令牌

  1. {
  2. "msg": "操作成功",
  3. "code": 200,
  4. "token": "eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIzZjRhNjJjLTY5NzMtNDcxZS04ZmU4LWJmYWQ4YzllNWFkMiJ9.9d3iIaNq62CkjTXlxFOQgdDMOAZiu5tAsEn0cEuV23opT6PAqu_CiaN7kQY8_XhlQrHX5RgZ2bH7LpsiKLLcSw"
  5. }

在登录方法中,做了以下几件事情

  • 根据uuid获取redis中的验证码并对请求的验证码做验证
  • 如果验证码没问题,则对用户名和密码进行校验
  • 如果用户名和密码校验成功,则使用token作为key将用户信息保存到redis
  • 使用jwt对token签名并返回前端

在整个过程中,会抛出一些自定义异常,比如

  1. throw new CaptchaExpireException();
  2. throw new CaptchaException();
  3. throw new UserPasswordNotMatchException();
  4. throw new CustomException(e.getMessage());

这些异常最终会被全局异常处理器处理掉:com.ruoyi.framework.web.exception.GlobalExceptionHandler

2.接口权限校验

前端请求完成登录接口之后会将token存储到cookie,key为Admin-Token,value是jwt令牌。登录逻辑:user.actions.Login

之后,每次请求后端接口的时候都会带上Authentication Header

这实际上是通过axios的请求拦截器实现的:详情可见src/utils/request.js文件

带着Authentication Header的请求打到后端的时候会经过过滤器com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter,该过滤器做了以下几件事情

  • 从请求头中取出jwt令牌,并对其进行jwt验签,验签若是成功,则取出原始token
  • 根据token从redis中取出用户数据
  • 将用户信息封装成UsernamePasswordAuthenticationToken对象,并将该对象填充到Spring Security上下文中

填充到SpringSecurity上下文才能让Controller接口上的@PreAuthorize注解发挥作用(存疑,这里若依作者并非使用原生的SpringSecurity提供的spel表达式,也没有用authorities,而是使用了PermissionService类)。

接着,Controller接口正式执行之前会进入com.ruoyi.framework.web.service.PermissionService#hasPermi方法判定权限,这里重新从redis中取出用户数据并进行权限校验,权限校验失败则不再执行接口中逻辑(存疑,这里并没有使用SpringSecurity上下文中的用户数据,那么JwtAuthenticationTokenFilter中的用户信息填充上下文中的代码是干啥用的)。

四、实战

上一篇文章讲解了如何创建一个菜单并创建页面,但是是个空页面

这篇文章将会讲解如何实现增删查改功能。

一切开始之前,新建表news,建表SQL如下

  1. CREATE TABLE `news` (
  2. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  3. `title` varchar(128) NOT NULL COMMENT '新闻标题',
  4. `brief` varchar(256) DEFAULT NULL COMMENT '新闻概述',
  5. `content` text COMMENT '新闻正文',
  6. `create_time` datetime DEFAULT NULL,
  7. `create_by` varchar(64) DEFAULT NULL,
  8. `update_time` datetime DEFAULT NULL,
  9. `update_by` varchar(64) DEFAULT NULL,
  10. `delete_flag` tinyint(1) DEFAULT NULL,
  11. PRIMARY KEY (`id`)
  12. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

1.前端页面修改

可以仿照角色管理的页面写,直接将角色管理页面的代码直接拷贝到news文件中,效果如下

没错,新闻列表的标题,角色管理的页面。。。

之后对页面中元素进行修改,使其和上面创建的表结构一致,修改后的页面样子

这是预期中的样子,但是内容还是角色管理页面的内容。

2.创建按钮权限

上一步已经完成了页面外观的改造,接下来需要修改页面内容了,首先需要把按钮权限给加上

按照这个样子添加按钮权限,之后把权限标志分配到前端页面中

3.使用代码生成代码

在系统工具-代码生成页面中生成news表对应的相关实体类、mapper、xml对象等,可以极大的简化开发过程。

4.准备后端接口

将上一步代码生成器生成的NewsController拿过来改一改就行,修改后的代码如下所示:

  1. package com.ruoyi.web.controller.business;
  2. import com.ruoyi.common.annotation.Log;
  3. import com.ruoyi.common.core.controller.BaseController;
  4. import com.ruoyi.common.core.domain.AjaxResult;
  5. import com.ruoyi.common.core.page.TableDataInfo;
  6. import com.ruoyi.common.enums.BusinessType;
  7. import com.ruoyi.common.utils.SecurityUtils;
  8. import com.ruoyi.common.utils.poi.ExcelUtil;
  9. import com.ruoyi.system.domain.News;
  10. import com.ruoyi.system.mapper.NewsMapper;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.security.access.prepost.PreAuthorize;
  13. import org.springframework.validation.annotation.Validated;
  14. import org.springframework.web.bind.annotation.*;
  15. import java.util.Date;
  16. import java.util.List;
  17. /**
  18. * @author kdyzm
  19. */
  20. @RestController
  21. @RequestMapping("/business/news")
  22. public class NewsController extends BaseController {
  23. @Autowired
  24. private NewsMapper newsMapper;
  25. /**
  26. * 获取新闻列表
  27. */
  28. @PreAuthorize("@ss.hasPermi('business:news:list')")
  29. @GetMapping("/list")
  30. public TableDataInfo list(News post) {
  31. startPage();
  32. List<News> list = newsMapper.selectNewsList(post);
  33. return getDataTable(list);
  34. }
  35. @Log(title = "新闻管理", businessType = BusinessType.EXPORT)
  36. @PreAuthorize("@ss.hasPermi('business:news:export')")
  37. @GetMapping("/export")
  38. public AjaxResult export(News post) {
  39. List<News> list = newsMapper.selectNewsList(post);
  40. ExcelUtil<News> util = new ExcelUtil<>(News.class);
  41. return util.exportExcel(list, "新闻数据");
  42. }
  43. /**
  44. * 根据新闻编号获取详细信息
  45. */
  46. @PreAuthorize("@ss.hasPermi('business:news:query')")
  47. @GetMapping(value = "/{postId}")
  48. public AjaxResult getInfo(@PathVariable Long postId) {
  49. return AjaxResult.success(newsMapper.selectNewsById(postId));
  50. }
  51. /**
  52. * 新增新闻
  53. */
  54. @PreAuthorize("@ss.hasPermi('business:news:add')")
  55. @Log(title = "新闻管理", businessType = BusinessType.INSERT)
  56. @PostMapping
  57. public AjaxResult add(@Validated @RequestBody News post) {
  58. post.setCreateBy(SecurityUtils.getUsername());
  59. post.setCreateTime(new Date());
  60. return toAjax(newsMapper.insertNews(post));
  61. }
  62. /**
  63. * 修改新闻
  64. */
  65. @PreAuthorize("@ss.hasPermi('business:news:update')")
  66. @Log(title = "新闻管理", businessType = BusinessType.UPDATE)
  67. @PutMapping
  68. public AjaxResult edit(@Validated @RequestBody News post) {
  69. post.setUpdateBy(SecurityUtils.getUsername());
  70. return toAjax(newsMapper.updateNews(post));
  71. }
  72. /**
  73. * 删除新闻
  74. */
  75. @PreAuthorize("@ss.hasPermi('business:news:delete')")
  76. @Log(title = "新闻管理", businessType = BusinessType.DELETE)
  77. @DeleteMapping("/{postIds}")
  78. public AjaxResult remove(@PathVariable Long[] postIds) {
  79. return toAjax(newsMapper.deleteNewsByIds(postIds));
  80. }
  81. }

5.修改前端页面请求地址

将生成的代码中的news.js文件放到api目录,并修改其中的接口路径与后端接口地址一一对应。

  1. import request from '@/utils/request'
  2. // 查询角色列表
  3. export function listNews(query) {
  4. return request({
  5. url: '/business/news/list',
  6. method: 'get',
  7. params: query
  8. })
  9. }
  10. // 查询角色详细
  11. export function getNews(roleId) {
  12. return request({
  13. url: '/business/news/' + roleId,
  14. method: 'get'
  15. })
  16. }
  17. // 新增角色
  18. export function addNews(data) {
  19. return request({
  20. url: '/business/news',
  21. method: 'post',
  22. data: data
  23. })
  24. }
  25. // 修改角色
  26. export function updateNews(data) {
  27. return request({
  28. url: '/business/news',
  29. method: 'put',
  30. data: data
  31. })
  32. }
  33. // 删除角色
  34. export function delNews(roleId) {
  35. return request({
  36. url: '/business/news/' + roleId,
  37. method: 'delete'
  38. })
  39. }
  40. // 导出角色
  41. export function exportNews(query) {
  42. return request({
  43. url: '/business/news/export',
  44. method: 'get',
  45. params: query
  46. })
  47. }

然后修改页面中的请求地址使用这里的地址。

五、测试

1.超级管理员测试

超级管理员拥有最大权限,所有权限校验都会跳过对超级管理员的权限校验。这里先使用超级管理员进行测试可以规避权限问题,大体上先看看能否跑的通。

下面是演示超级管理员的CRUD操作。

2.普通用户测试

这里用用户kdyzm进行测试,在测试之前,先看下kdyzm的角色

可以看到该用户是运营角色,那么修改角色权限,只给查询、修改、新增权限,不给导出和删除权限,如下所示

这时候切换登录用户为kdyzm,看看新闻列表页面

可以看到,kdyzm在新闻列表页面中,看不到导出导出按钮和删除按钮,符合预期设想。

好了,若依Vue权限详解部分到此结束了,下一篇文章将会讲解若依代码生成器生成原理和代码分析

我的博客原文地址:若依管理系统RuoYi-Vue(二):权限系统设计详解 ,欢迎大家关注呀

若依管理系统RuoYi-Vue(二):权限系统设计详解的更多相关文章

  1. VUE二 生命周期详解

    vue官网对vue生命周期的介绍 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.销毁等一系列过程,我们称这是Vue的生命周期.通俗说就是Vue实 ...

  2. 转:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权

    原文地址:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法.shiro认证与shiro授权 以下是部分内容,具体见原文. shiro介绍 什么是shiro shiro是Apache ...

  3. Vue.js 数据绑定语法详解

    Vue.js 数据绑定语法详解 一.总结 一句话总结:Vue.js 的模板是基于 DOM 实现的.这意味着所有的 Vue.js 模板都是可解析的有效的 HTML,且通过一些特殊的特性做了增强.Vue ...

  4. HTTPD之二————HTTPD服务详解————httpd的配置文件常见设置

    HTTPD之二----HTTPD服务详解----httpd的配置文件常见设置 HTTP服务器应用 http服务器程序 httpd apache nginx lighttpd 应用程序服务器 IIS,a ...

  5. 数据结构图文解析之:二叉堆详解及C++模板实现

    0. 数据结构图文解析系列 数据结构系列文章 数据结构图文解析之:数组.单链表.双链表介绍及C++模板实现 数据结构图文解析之:栈的简介及C++模板实现 数据结构图文解析之:队列详解与C++模板实现 ...

  6. Oracle权限管理详解

    Oracle权限管理详解 转载--CzmMiao的博客生活 Oracle 权限 权限允许用户访问属于其它用户的对象或执行程序,ORACLE系统提供三种权限:Object 对象级.System 系统级. ...

  7. Java进阶(三十二) HttpClient使用详解

    Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...

  8. Spring Boot 启动(二) 配置详解

    Spring Boot 启动(二) 配置详解 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Boot 配置 ...

  9. Android ADB命令教程二——ADB命令详解

    Android ADB命令教程二——ADB命令详解 转载▼ 原文链接:http://www.tbk.ren/article/249.html       我们使用 adb -h 来看看,adb命令里面 ...

随机推荐

  1. Urlrewrite

    Urlrewrite  地址重写,用户得到的全部都是经过处理后的URL地址 过滤用户的所有请求,符合规则的便对其进行重定向 rule结点中from默认使用的正则表达式来匹配,若用户访问服务器时的URL ...

  2. Flash图解线程池 | 阿里巴巴面试官希望问的线程池到底是什么?

    前言 前几天小强去阿里巴巴面试Java岗,止步于二面. 他和我诉苦自己被虐的多惨多惨,特别是深挖线程和线程池的时候,居然被问到不知道如何作答. 对于他的遭遇,结合他过了一面的那个嘚瑟样,我深表同情(加 ...

  3. Session.invalidate与sessiont.removeAtribute()学习比较

    当浏览器第一次请求时,服务器创建一个session对象,同时生成一个sessionId,并在此次响应中将sessionId 以响应报文的方式传回客户端浏览器内存或以重写url方式送回客户端,来保持整个 ...

  4. 前台console调试技巧

    前台console调试技巧 一.console.log() 二.console.warn() 三.console.dir() 四.console.table() 五.console.assert() ...

  5. Java8中流的性能

    流(Stream)是Java8为了实现最佳性能而引入的一个全新的概念.在过去的几年中,随着硬件的持续发展,编程方式已经发生了巨大的改变,程序的性能也随着并行处理.实时.云和其他一些编程方法的出现而得到 ...

  6. Geotools操作GeoJSON:解析FeatureCollection对象文件

    Geotools操作GeoJSON:解析FeatureCollection对象文件 一.解析FeatureCollection对象文件 1.1 geotools操作GeoJSON过程中的问题及相关源码 ...

  7. 你必须知道的关于操作系统的N个概念!

    本文全部概念都是基于<计算机操作系统教程(第四版)>中的表述归纳而成. 操作系统的任务和功能 操作系统的职能是管理和控制计算机系统中的所有硬件和软件资源,合理地组织计算机流程,并为用户提供 ...

  8. JDK-7新特性,更优雅的关闭流-java try-with-resource语句使用

    前言 公司最近代码质量整改,需要对大方法进行调整,降低到50行以下,对方法的深度进行降低,然后有些文件涉及到流操作,很多try/catch/finally语句,导致行数超出规范值,使用这个语法可以很好 ...

  9. 【noi 2.7_413】Calling Extraterrestrial Intelligence Again(算法效率--线性筛素数+二分+测时)

    题意:给3个数M,A,B,求两个质数P,Q.使其满足P*Q<=M且A/B<=P/Q<=1,并使P*Q最大.输入若干行以0,0,0结尾. 解法:先线性筛出素数表,再枚举出P,二分出对应 ...

  10. 牛客编程巅峰赛S2第7场 - 钻石&王者 A.牛牛的独特子序列 (字符串,二分)

    题意:给你一个字符串,找出一个类似为\(aaabbbccc\)这样的由连续的\(abc\)构成的子序列,其中\(|a|=|b|=|c|\),问字符串中能构造出的子序列的最大长度. 题解:这题刚开始一直 ...