Shiro

1.权限管理概述

2.Shiro权限框架
  2.1 概念
  2.2 Apache Shiro 与Spring Security区别

3.Shiro认证
  3.1 基于ini认证
  3.2 自定义Realm --认证

4.Shiro授权
  4.1 基于ini授权
  4.2 自定义realm – 授权

5.项目集成shiro 认证-授权注意点
  5.1 认证
  5.2 授权
  5.3 注解@RequiresPermissions()
  5.4 标签式权限验证

6.Shiro加密

7.Shiro缓存

权限管理概述

RBAC(Role Based Access Control) :某个用户拥有什么角色,被允许做什么事情(权限)

用户登录—>分配角色---->(权限关联映射)---->鉴权(拥有什么什么权限)

熟悉框架流程

  • 概念 能做什么
  • 架构是怎样
  • 代码怎么实现
  • 项目运用

Shiro权限框架

  • 概念: Apache Shiro 是一个强大且易用的 Java 安全框架
  • 能做什么:Shiro可以帮我们完成 :认证授权、加密、会话管理、与 Web 集成、缓存等。
  • 架构是怎样的

主要认识:
  • Subject(用户):当前的操作用户 获取当前用户Subject currentUser = SecurityUtils.getSubject()
  • SecurityManager(安全管理器):Shiro的核心,负责与其他组件进行交互,实现 subject 委托的各种功能
  • Realms(数据源) :Realm会查找相关数据源,充当与安全管理间的桥梁,经过Realm找到数据源进行认证,授权等操作
  • Authenticator(认证器): 用于认证,从 Realm 数据源取得数据之后进行执行认证流程处理。
  • Authorizer(授权器):用户访问控制授权,决定用户是否拥有执行指定操作的权限。
  • SessionManager (会话管理器):支持会话管理
  • CacheManager (缓存管理器):用于缓存认证授权信息
  • Cryptography(加密组件):提供了加密解密的工具包


Apache Shiro 与Spring Security区别

Shiro::用于中小型项目比较常见,简单易上手,可以支持多种环境Shiro 可以不跟任何的框架或者容器绑定,可独立运行
Spring Security:一般多用于spring环境,中大型项目,更强大Spring Security 则必须要有Spring环境

Shiro认证

基于ini认证

1.新建项目 --导入依赖--新建 配置文件ini2. shiro帮我们创建用户创建令牌 ,类比页面传入的账号密码密码错误--  IncorrectCredentialsException账号错误--  UnknownAccountException源码分析--

  • 导入依赖
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.5.2</version>
</dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.22</version><scope>provided</scope>
</dependency>
  • 编写ini,shiro默认支持的是ini配置的方式(只是演示) shiro-au.ini,真实项目使用xml
#用户的身份、凭据
[users]
zhangsan=555
xiaoluo=666
  • 使用 Shiro 相关的 API 完成身份认证
@Test
public void testLogin(){//创建Shiro的安全管理器,是shiro的核心DefaultSecurityManager securityManager = new DefaultSecurityManager();//加载shiro.ini配置,得到配置中的用户信息(账号+密码)IniRealm iniRealm = new IniRealm("classpath:shiro-au.ini");securityManager.setRealm(iniRealm);//把安全管理器注入到当前的环境中SecurityUtils.setSecurityManager(securityManager);//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断Subject subject = SecurityUtils.getSubject();System.out.println("认证状态:"+subject.isAuthenticated());//创建令牌(携带登录用户的账号和密码)UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","666");//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)subject.login(token);System.out.println("认证状态:"+subject.isAuthenticated());//登出//subject.logout();//System.out.println("认证状态:"+subject.isAuthenticated());
}

自定义Realm–认证

模拟

@Getter
@Setter
public class Employee {private String username;private String password;}

思路:

//继承 AuthorizingRealm
//参数的token ,subject进行登录匹配传入的token : UsernamePasswordToken
1.获取用户名方式一:将token强转为UsernamePasswordToken-->getUsername()方式二:通过token.getPrincipal()    2.以用户名为条件,查询mysql数据库,得到用户对象(用户名/密码)模拟从数据库查询的对象 ------->自己new一个3.将用户对象封装成认证info对象返回//存在两种可能1.用户对象为null-----> return null2.不为空 ---->封装数据 return new SimpleAuthenticationInfo(有3个参数 1.employee ,  //从数据库查询出来需要进行密码匹配的用户对象2.employee.getPassword(),  //匹配对象密码3.super.getName()  //指定realm的名称 ,可自定义)

实现:

public class Realm extends AuthorizingRealm {//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//1.获取登录用户名String username = (String) token.getPrincipal();//2.以用户名为条件查询mysql数据库,得到用户对象//模拟数据Employee employee = new Employee();employee.setUsername("xiaoluo");employee.setPassword("123");//封装成一个认证info对象//判断用户是否为空if (employee!=null){return new SimpleAuthenticationInfo(employee,employee.getPassword(),super.getName());}return null;}
}
@Test
public void testLogin(){//创建Shiro的安全管理器,是shiro的核心DefaultSecurityManager securityManager = new DefaultSecurityManager();//自定义Realm,查出用户信息Realm realm = new Realm();securityManager.setRealm(realm);//把安全管理器注入到当前的环境中SecurityUtils.setSecurityManager(securityManager);//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断Subject subject = SecurityUtils.getSubject();System.out.println("认证状态:"+subject.isAuthenticated());//创建令牌(携带登录用户的账号和密码)UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","123");//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)subject.login(token);System.out.println("认证状态:"+subject.isAuthenticated());//登出//subject.logout();//System.out.println("认证状态:"+subject.isAuthenticated());
}

Shiro授权

基于ini授权

  • 登录
  • 分配
  • 鉴权
    权限表达式   资源:操作admin=*:*  //超级管理员emp=employee:*
//判断是否有某个角色
subject.hasRole("role");
//用户拥有所有指定角色返回true
subject.hasAllRoles(Arrays.aList("role1","role2"));
//判断用户是否有指定角色,将结果返回,封装到boolean数组中
boolean[] booleans=subject.hasRoles(Arrays.aList("role1","role2"));
//check开头的是没有返回值,当没有权限时就会抛出异常
subject.checkRole("role"); //判断用户是否有某个权限
subject.isPermitted("权限表达式")
boolean[] booleans=subject.isPermitted("权限表达式1","权限表达式2")

自定义realm — 授权

思路:

1.获取当前登录用户的id/name//获取当前登录用户对象 ,登录传过来的第一个参数方式一 :principals.getPrimaryPrincipal();方式二:SecurityUtils.getSubject().getPrincipal();2.以用户id作为条件查询mysql数据,查询该用户拥有角色/权限//假装查数据//List<String> roles= roleService.queryByEmployee(Employee.getId());//List<String>    //List<String> roles= permissionService.queryByEmployee(Employee.getId());
= permissionService.queryByEmployee(Employee.getId());3.将角色与权限封装到授权info对象中 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRoles(roles) //添加角色info.addStringPermission(permission);//添加权限

实现:

 //授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//获取当前用户对象Employee employee = (Employee)principalCollection.getPrimaryPrincipal();//以用户id查询数据库,判断该角色/用户是否拥有权限  模拟List<String> roles= Arrays.asList("seller");List<String> permissions= Arrays.asList("customer:list","customer:save");//封装到info对象SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRoles(roles);info.addStringPermissions(permissions);return info;}
public class ShiroDemo {@Testpublic void testLogin(){//创建Shiro的安全管理器,是shiro的核心DefaultSecurityManager securityManager = new DefaultSecurityManager();//加载shiro.ini配置,得到配置中的用户信息(账号+密码)IniRealm iniRealm = new IniRealm("classpath:shiro-author.ini");//自定义Realm,查出用户信息Realm realm = new Realm();securityManager.setRealm(realm);//把安全管理器注入到当前的环境中SecurityUtils.setSecurityManager(securityManager);//无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断Subject subject = SecurityUtils.getSubject();System.out.println("认证状态:"+subject.isAuthenticated());//创建令牌(携带登录用户的账号和密码)UsernamePasswordToken token = new UsernamePasswordToken("xiaoluo","12");//执行登录操作(将用户的和 ini 配置中的账号密码做匹配)subject.login(token);System.out.println("认证状态:"+subject.isAuthenticated());//登出//subject.logout();//System.out.println("认证状态:"+subject.isAuthenticated());//判断用户是否有某个角色System.out.println("hr:"+subject.hasRole("hr"));System.out.println("seller:"+subject.hasRole("seller"));//是否同时拥有多个角色System.out.println("是否同时拥有role1和role2:"+subject.hasAllRoles(Arrays.asList("hr", "seller")));boolean[] booleans = subject.hasRoles(Arrays.asList("hr", "seller"));//System.out.println(booleans);//check开头的是没有返回值的,当没有权限时就会抛出异常subject.checkRole("seller");//判断用户是否有某个权限System.out.println("user:delete:"+subject.isPermitted("user:delete"));subject.checkPermission("customer:list");}
}
 //如果用户是超级管理员 //设置超级管理员角色info.addRole("admin")//设置超级管理员权限
info.addStringPermission("*:*");

项目集成shiro 认证-授权注意点

认证

1.添加对应依赖
2.配置代理过滤器为什么不用配置拦截器?Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用
3.创建shiro.xml
4.配置shiro过滤器<!--引用指定的安全管理器--><!--shiro默认的登录地址是/login.jsp 现在要指定我们自己的登录页面地址--><!--路径对应的规则-->
5.配置安全管理器  DefaultWebSecurityManager
6.修改LoginController
7.配置自定义Realm
8.将自定义Realm交给容器管理


shiro中的过滤器

过滤器的名称 Java
anon org.apache.shiro.web. lter.authc.AnonymousFilter
authc org.apache.shiro.web. lter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter
roles org.apache.shiro.web. lter.authz.RolesAuthorizationFilter
perms org.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter
user org.apache.shiro.web. lter.authc.UserFilter
logout org.apache.shiro.web. lter.authc.LogoutFilter
port org.apache.shiro.web. lter.authz.PortFilter
rest org.apache.shiro.web. lter.authz.HttpMethodPermissionFilter
ssl org.apache.shiro.web. lter.authz.SslFilter
  • anon: 匿名处理过滤器,即不需要登录即可访问;一般用于静态资源过滤;/static/**=anon
  • authc: 表示需要认证(登录)才能使用;(放最后) /**=authc
  • logout: 注销过滤器 /logout=logout
  • roles: 角色授权过滤器,验证用户是否拥有资源角色; /employee/input=perms["user:update"]

授权

  • 没有权限的异常 :org.apache.shiro.authz.UnauthorizedException
shiro注解鉴权操作方式:
类比RBAC:1.自定义权限注解2.将注解贴在请求映射方法上面3.将注解标注的权限表达式加载到数据库中4.将这些表达式根据用户角色进行权限分配5.当用户登录之后,访问某个请求映射方法时,先经过权限拦截器,进行鉴权操作1.获取当前登录用户权限表达式集合2.获取当前请求映射方法头顶上权限表达式3.判断用户权限表达式集合中是否包含该表达式

Shiro 权限验证三种方式

  • 编程式
  • 注解式
  • 页面标签式
1.编程式Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("hr")) {//有权限
} else {//无权限
}
--------------------------------------2.注解式
@RequiresRoles("admin")
@RequiresPermissions("user:create")
public void hello() {//有权限
}
----------------------------------------3.页面标签式<@shiro.hasPermission name="employee:list"><!-- 有权限 -->
</@shiro.hasRole>

授权步骤

1.贴注解
2.开启 Shiro 注解扫描器
3.查询数据库真实数据 自定义realm

注解@RequiresPermissions()

  • value属性: 这个属性是一个数组
  • Logical.AND: 必须同时拥有value配置所有权限才允许访问
  • Logical.OR:只需要拥有value配置所有权限中一个即可允许访问
约定:权限表达式,第一值权限表达式,第二值为权限名称@RequiresPermissions(value={"employee:list","员工列表"},logical=Logical.OR)逻辑为OR,value满足其中一个条件成立

标签式权限验证


拓展FreeMarker标签

默认的freemarker是不支持shiro标签,所以需要做功能拓展----------------   public class ShiroFreeMarkerConfig extends FreeMarkerConfigurer {@Overridepublic void afterPropertiesSet() throws IOException, TemplateException {//继承之前的属性配置,这不能省super.afterPropertiesSet();Configuration cfg = this.getConfiguration();cfg.setSharedVariable("shiro", new ShiroTags());//注册shiro 标签}
}
--------------------------让之前的配置文件中的FreeMarkerConfigurer修改为自定义的ShiroFreeMarkerConfig类<!-- 注册 FreeMarker 配置类 --><bean class="cn.k.shiro.ShiroFreeMarkerConfig"><!-- 配置 FreeMarker 的文件编码 --><property name="defaultEncoding" value="UTF-8" /><!-- 配置 FreeMarker 寻找模板的路径 --><property name="templateLoaderPath" value="/WEB-INF/views/" /><property name="freemarkerSettings"><props><!-- 兼容模式 ,配了后不需要另外处理空值问题,时间格式除外 --><prop key="classic_compatible">true</prop></props></property></bean>

使用shiro标签

  • authenticated 标签:已认证通过的用户。
<@shiro.authenticated> </@shiro.authenticated>
  • notAuthenticated 标签:未认证通过的用户。
<@shiro.notAuthenticated></@shiro.notAuthenticated>
  • principal 标签 :输出当前用户信息,通常为登录帐号信息
<@shiro.principal property="name" /> //对应name属性
  • hasRole 标签:验证当前用户是否拥有该角色
<@shiro.hasRole name="admin">我是管理员</@shiro.hasRole>
  • hasAnyRoles 标签:验证当前用户是否拥有这些角色中的任何一个,角色之间逗号分隔
<@shiro.hasAnyRoles name="admin,user,hr">Hello admin</@shiro.hasAnyRoles>
  • hasPermission 标签:验证当前用户是否拥有该权限
<@shiro.hasPermission name="department:delete">删除</@shiro.hasPermission>

Shiro加密

MD5

//参数1 :原文,参数2:盐 ,参数3:散列次数(加密次数)
Md5Hash hash=new Md5Hash("1","kent",3);

盐一般要求是固定长度的字符串,且每个用户的盐不同。

一般盐的选择的是用户的唯一数据(账号名等),盐是要求不能改变的,不然下次加密结果就对应不上了

Shiro缓存

当我们登录时,授权信息是要从数据库中查询的,如果每次刷新刷新都需要获取你到底有没有权限,对性能影响不好,用户登录后,授权信息一般很少改动,所以,我们可以将第一次授权后,将信息存在缓存中,下次直接再缓存中获取,就很好的避免了多次访问数据库

Shiro没有实现自己的缓存机制,只提供了支持缓存的API接口,这里使用的是EhCache

mybatis 一级  二级缓存------百度(ing)补充

集成EhCache

第三方EhCache1.配置缓存管理器并引用缓存管理器<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><!--注册自定义数据源--><property name="realm" ref="employeeRealm"/><!--注册缓存管理器--><property name="cacheManager" ref="cacheManager"/>
</bean><!-- 缓存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!-- 设置配置文件 --><property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>----------------------------------------2.添加缓存配置文件  shiro-ehcache.xml<ehcache><defaultCachemaxElementsInMemory="1000"eternal="false"timeToIdleSeconds="600"timeToLiveSeconds="600"memoryStoreEvictionPolicy="LRU"></defaultCache>
</ehcache>

配置缓存文件属性说明

  • maxElementsInMemory: 缓存对象最大个数。
  • eternal:对象是否永久有效 ,一般为false
  • timeToIdleSeconds: 对象空闲时间
  • timeToLiveSeconds:对象存活时间
  • memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。
  • LFU(较少使用,意思是一直以来最少被使用的,缓存的元素有一个hit 属性(命中率),hit 值最小的将会被清出缓存)默认

拓展 统一全局异常

@ControllerAdvice 控制器功能增强注解

1.在进入请求映射方法之前做功能增强,经典用法:date日期格式化
2.在进入请求映射方法之后做功能增强,经典用法:统一异常处理 3.处理异常的方法,方法需要贴ExceptionHandler注解

配置文件

配置代理过滤器

<filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

配置shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"><!--引入自定义Realm--><bean id="employeeRealm" class="cn.k.shiro.EmployeeRealm"></bean><!--配置安全管理器--><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="employeeRealm"/><!--注册缓存管理器--><property name="cacheManager" ref="cacheManager"/></bean><!--配置shiro过滤器--><bean id="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><!--引用指定的安全管理器--><property name="securityManager" ref="securityManager"/><!--shiro默认的登录地址是/login.jsp 现在要指定我们自己的登录页面地址--><property name="loginUrl" value="/login.html"/><!--路径对应的规则--><property name="filterChainDefinitions"><value>/userLogin=anon/css/**=anon/js/**=anon/img/**=anon/upload/**=anon/userLogout=logout/**=authc</value></property></bean><!--开启 Shiro 注解扫描器--><!-- <aop:config/> 会扫描配置文件中的所有advisor,并为其创建代理 --><aop:config/><!-- Pointcut advisor通知器, 会匹配所有加了shiro权限注解的方法 --><bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager"/></bean><!-- 缓存管理器 --><bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"><!-- 设置配置文件 --><property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/></bean></beans>

关联mvc.xml

<import resource="classpath:shiro.xml"/>

loginController

@RequestMapping("/userLogin")@ResponseBodypublic JsonResult login(String username, String password){try {UsernamePasswordToken token = new UsernamePasswordToken(username, password);SecurityUtils.getSubject().login(token);return new JsonResult();} catch (UnknownAccountException e) {return new JsonResult(false, "账号不存在");} catch (IncorrectCredentialsException e) {return new JsonResult(false, "密码错误");} catch (Exception e) {e.printStackTrace();return new JsonResult(false, "登录异常,请联系管理员");}}

关联mvc.xml

<import resource="classpath:shiro.xml"/>

loginController

@RequestMapping("/userLogin")@ResponseBodypublic JsonResult login(String username, String password){try {UsernamePasswordToken token = new UsernamePasswordToken(username, password);SecurityUtils.getSubject().login(token);return new JsonResult();} catch (UnknownAccountException e) {return new JsonResult(false, "账号不存在");} catch (IncorrectCredentialsException e) {return new JsonResult(false, "密码错误");} catch (Exception e) {e.printStackTrace();return new JsonResult(false, "登录异常,请联系管理员");}}

Shiro框架-史上详解相关推荐

  1. java集合框架史上最详解(list set 以及map)

    title: Java集合框架史上最详解(list set 以及map) tags: 集合框架 list set map 文章目录 一.集合框架总体架构 1.1 集合框架在被设计时需满足的目标 1.2 ...

  2. spring框架 AOP核心详解

    AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子. 一 AOP的基本概念 (1)Asp ...

  3. pythonmessage用法_django 消息框架 message使用详解

    前言 在网页应用中,我们经常需要在处理完表单或其它类型的用户输入后,显示一个通知信息给用户. 对于这个需求,Django提供了基于Cookie或者会话的消息框架messages,无论是匿名用户还是认证 ...

  4. 定时任务框架APScheduler学习详解

    定时任务框架APScheduler学习详解 APScheduler简介 在平常的工作中几乎有一半的功能模块都需要定时任务来推动,例如项目中有一个定时统计程序,定时爬出网站的URL程序,定时检测钓鱼网站 ...

  5. java集合框架的结构_集合框架(Collections Framework)详解及代码示例

    简介 集合和数组的区别: 数组存储基础数据类型,且每一个数组都只能存储一种数据类型的数据,空间不可变. 集合存储对象,一个集合中可以存储多种类型的对象.空间可变. 严格地说,集合是存储对象的引用,每个 ...

  6. Android系统(96)---Android 数据交换解析框架Gson使用详解

    Android 数据交换解析框架Gson使用详解 Json 是一种文本形式的数据交换格式,比 xml 更为轻量.Json 的解析和生成的方式很多,在 Android 平台上最常用的类库有 Gson 和 ...

  7. python安装robotframework报错_Python3+RIDE+RobotFramework自动化测试框架搭建过程详解

    Python2.7已于2020年1月1日开始停用,之前RF做自动化都是基于Python2的版本. 没办法,跟随时代的脚步,我们也不得不升级以应用新的控件与功能. 升级麻烦,直接全新安装. 一.Pyth ...

  8. Android自动化大讲堂34--终极自动化框架UIAutomator使用详解

    <深入理解Android自动化测试> 又双叒叕重印咯!!! 无以为报,只能改版得更漂亮一点来答谢各位的厚爱! 好了,废话少说,咱们开始吧! 终极自动化框架UIAutomator使用详解 注 ...

  9. Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721)

    Shiro反序列化漏洞利用详解(Shiro-550+Shiro-721) Shiro简介 Apache Shiro 是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能,Shiro ...

  10. java reflections_Java反射框架Reflections示例详解

    MAVEN 坐标 org.reflections reflections 0.9.10 Reflections 的作用 Reflections通过扫描classpath,索引元数据,并且允许在运行时查 ...

最新文章

  1. 在腾讯,如何做 Code Review?
  2. python apktool_Python使用ApkTool和子进程反编译APK
  3. cstring 比较_属牛人和属蛇人姻缘婚配关系比较和谐
  4. hdu3449 有依赖的背包问题
  5. Html5本地存储和本地数据库
  6. 北大OJ百练——4075:矩阵旋转(C语言)
  7. ThinkPHP+AJAX三级联动
  8. afterclass_通过beforeClass和afterClass设置增强Spring Test Framework
  9. 【计蒜客信息学模拟赛1月月赛 - D】Wish(dp计数,数位)
  10. About The Quaternion 有关四元数
  11. NOIP2018 模拟 Problem A 解题报告 求子序列
  12. oracle主键函数,oracle 主键+1 或 -1 的函数
  13. ajax传参的3种形式
  14. Autoleaders控制组——叶睿 第三次任务
  15. Nebula Graph|信息图谱在携程酒店的应用
  16. php thinkadmin自定义一个弹出弹窗批量操作功能
  17. R 数据的导入和导出
  18. Centos安装RabbitMQ超详细(必须收藏)
  19. 乐视电商云的整体架构与技术实现
  20. 洗扑克牌 (乱数排序)

热门文章

  1. windows系统c语言编译器安装
  2. DDoS是什么意思?
  3. 360浏览器集成IE8内核
  4. 关于win11没有ie 11的问题
  5. SAO Utils 添加白色参考线
  6. 超实用的桌面收纳盒!
  7. Unity 动态修改鼠标指针
  8. pytorch拟合函数
  9. 变化世界中的军事地质学
  10. 如何建立一个Java商城系统?