【SpringBoot】SpringBoot2.x整合Shiro(一)
一:什么是ACL和RBAC:
ACL: Access Control List 访问控制列表
以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
优点:简单易用,开发便捷
缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
例子:常见的文件系统权限设计, 直接给用户加权限
RBAC: Role Based Access Control
基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
缺点:开发对比ACL相对复杂
例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security
BAT企业 ACL,一般是对报表系统,阿里的ODPS
总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等
二:Apache Shiro基础知识和架构
Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
shiro包含四大核心模块:身份认证,授权,会话管理和加密
直达Apache Shiro官网 http://shiro.apache.org/introduction.html
什么是身份认证
Authentication,身份证认证,一般就是登录
什么是授权
Authorization,给用户分配角色或者访问某些资源的权限
什么是会话管理
Session Management, 用户的会话管理员,多数情况下是web session
什么是加密
Cryptography, 数据加解密,比如密码加解密等
shiro架构图
三.用户访问Shiro权限控制运行流程
Subject
我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
SecurityManager
安全管理器,Subject的认证和授权都要在安全管理器下进行
Authenticator
认证器,主要负责Subject的认证
Realm
数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息
Authorizer
授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
Cryptography
加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
Cache Manager
缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能
四.Springboot2.x整合Apache Shiro实战
项目环境:
Maven3.5 + Jdk8 + Springboot 2.X + IDEA
相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--注释掉-->
<!--<scope>runtime</scope>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--阿里巴巴druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--spring整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<!-- shiro+redis缓存插件 主要用于CacheManager,以及缓存sessionManage中配置session持久化-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
Shiro各个组件说明
Realm:
realm作用:Shiro 从 Realm 获取安全数据
默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm
两个概念
principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
credential:凭证, 一般就是密码
所以一般我们说 principal + credential 就账号 + 密码
开发中,往往是自定义realm , 即继承 AuthorizingRealm,重写doGetAuthenticationInfo(认证方法)和doGetAuthorizationInfo(授权方法)
当用户登陆的时候会调用 doGetAuthenticationInfo
进行权限校验的时候会调用: doGetAuthorizationInfo
UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo :代表该用户的认证信息
Shiro内置的Filter
核心过滤器类:DefaultFilter, 配置哪个路径对应哪个拦截器进行处理
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
需要认证登录才能访问
user:org.apache.shiro.web.filter.authc.UserFilter
用户拦截器,表示必须存在用户。
anon:org.apache.shiro.web.filter.authc.AnonymousFilter
匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
角色授权拦截器,验证用户是或否拥有角色。
参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个
参数都通过才算通过
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
权限授权拦截器,验证用户是否拥有权限
参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必
须每个参数都通过才算可以
authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
httpBasic 身份验证拦截器。
logout:org.apache.shiro.web.filter.authc.LogoutFilter
退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
port:org.apache.shiro.web.filter.authz.PortFilter
端口拦截器, 可通过的端口。
ssl:org.apache.shiro.web.filter.authz.SslFilter
ssl拦截器,只有请求协议是https才能通过。
Filter的配置路径说明:
/admin/video /user /pub
路径通配符支持 ?、*、**,注意通配符匹配不 包括目录分隔符“/”
心 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配
URL权限采取第一次匹配优先的方式
? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
* : 匹配零个或多个字符串,如 /add* ,匹配 /addtest,但不匹配 /user/1
** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy
例子
/user/**=filter1
/user/add=filter2
请求 /user/add 命中的是filter1拦截器
性能问题:通配符比字符串匹配会复杂点,所以性能也会稍弱,推荐是使用字符串匹配方式
数据加解密器CredentialsMatche
一般会自定义验证规则
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new
HashedCredentialsMatcher();
//散列算法,使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5("xxx"));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
Shiro的缓存模块CacheManager
shiro中提供了对认证信息和授权信息的缓存。
默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的(因为授权的数据量大,每次都要查询数据库,性能受到影响)
AuthenticatingRealm 及 AuthorizingRealm 分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓
存。
Shiro中的SessionManager
用户和程序直接的链接,程序可以根据session识别到哪个用户,和javaweb中的session类似
什么是会话管理器SessionManager
会话管理器管理所有subject的所有操作,是shiro的核心组件,前后端分离的应用中session通常用token代替,用于会话管理
RBAC权限控制架构设计
1.数据库设计:老五张表
用户,角色,权限,用户角色表,角色权限表
2.shiro相关配置
-配置ShiroFilterFactoryBean
配置流程和思路
shiroFilterFactoryBean-》
-SecurityManager-》
-CustomSessionManager
-CustomRealm-》hashedCredentialsMatcher
SessionManager
-DefaultSessionManager: 默认实现,常用于javase
-ServletContainerSessionManager: web环境
-DefaultWebSessionManager:常用于自定义实现(一般用这种)
ShiroConfig配置
@Configuration
public class ShiroConfig { /**
* 配置ShiroFilterFactoryBean
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("执行 ShiroFilterFactoryBean shiroFilter");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); //需要登录的接口:如果访问某个接口,需要登录却没有登录,则调用此接口,如果前端后端不分离,则跳转到html页面
shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //登录成功 跳转url,如果前后端分离,则没这个调用 --这里设置为首页就行了
shiroFilterFactoryBean.setSuccessUrl("/"); //登录成功,但是没有权限,未授权就会调用这个接口,如果不是前后端分离,则跳转到403页面
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); //设置自定义filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter", new CustomRoleFilter()); //shiroFilterFactoryBean绑定自定义的filter
shiroFilterFactoryBean.setFilters(filterMap); //过滤器链的map
//拦截器(过滤器路径,坑一必须要用LinkedhashMap),部分路径无法进行拦截
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); /*
********************************************
通常是配置这些过滤器,也可以用个数据库的动态加载,这些数据
********************************************/
//退出过滤器
filterChainDefinitionMap.put("/logout", "logout"); //匿名可以访问,也就是游客模式
filterChainDefinitionMap.put("/pub/**", "anon"); //登录用户才可以访问
filterChainDefinitionMap.put("/authc/**", "authc"); //管理员角色才可以访问
filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //有编辑权限才可以访问
filterChainDefinitionMap.put("/video/update", "perms[video_update]"); //坑二:过滤器是顺序执行,从上而下,一般来说/** 放到最下面 //authc: url定义必须通过认证才可以访问
//anno: url可以匿名访问
filterChainDefinitionMap.put("/**", "authc"); //配置过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean;
} /**
* 数据域
*
* @return
*/
@Bean
public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); //设置加密器--因为数据库中的密码不是明文存储
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 自定义seesionManager
* -配置session持久化
* @return
*/
@Bean
public SessionManager sessionManager() {
//自定义CustomSessionManager 继承 DefaultWebSessionManager
CustomSessionManager customSessionManager = new CustomSessionManager(); //配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO()); //超时时间,默认 30分钟,会话超时;方法里面的单位是毫秒
customSessionManager.setGlobalSessionTimeout(20000); return customSessionManager;
} /**
* 密码加解密规则 CredentialMatcher-配置在数据域中,用于数据的加解密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //设置散列算法:这里使用MD5算法
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列次数,好比散列两次 相当于md5(md5(x))
hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher;
}
/**
* 配置redisManager
*/
@Bean
public RedisManager getRedisManager() { RedisManager redisManager = new RedisManager(); //默认就是localhost:6379 不写也行
redisManager.setHost("localhost");
redisManager.setPort(6379);
return redisManager;
}
/**
* 配置具体cache实现类RedisCacheManager
* 为什么要使用缓存:
* 缓存组件位于SecurityManager中,在CustomRealm数据域中,由于授权方法中每次都要查询数据库,性能受影响,因此将数据缓存起来,提高查询效率
* 除了使用Redis缓存,还能使用shiro-ehcache
*
* @return
*/
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置过期时间,单位是秒,20s,
redisCacheManager.setExpire(20);
return redisCacheManager;
}
/**
* 自定义session持久化
*
* @return
*/
public RedisSessionDAO redisSessionDAO() { /*
为啥session也要持久化?
重启应用,用户无感知,可以继续以原先的状态继续访问
注意点:
DO对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
*/
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager()); //配置自定义sessionId,shiro自动生成色sessionId不满足条件时可以使用
redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
return redisSessionDAO;
}
/**
* LifecycleBeanPostProcessor
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
} /**
* AuthorizationAttributeSourceAdvisor
* 作用:加入shiro注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
} /**
* DefaultAdvisorAutoProxyCreator
* 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
* 要在LifecycleBeanPostProcessor创建后才可以创建
*
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
自定义Filter==>>>CustomRoleFilter
/**
* 自定义Filter
* 问题:为什么要自定义filter:
* 因为配置role[admin,root] ,只有同时满足admin和root才能够访问,显然这是不合理的
* 实际是超级管理员有全部权限
*/
public class CustomRoleFilter extends AuthorizationFilter { @Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
//获取当前访问路径所有角色的集合
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue; //没有角色显示,可以直接访问
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
} //当前subject是roles中的任意一个,则有权限访问
Set<String> roles = CollectionUtils.asSet(rolesArray); for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return subject.hasAllRoles(roles);
}
}
自定义SessionManager:===>>CustomSessionManager
/** @Discription 自定义SessionMananger 为什么要自定义自定义SessionMananger?
* 原因:
* 因为前后端分离的情况下 不是靠session,而是靠token去交互,因此需要自定义这个sessionId的获取
* 即重写父类的方法(父类是从头中拿sessionId)
*
*/
public class CustomSessionManager extends DefaultWebSessionManager { //这个key 放在请求头中,可以自己定义 ,通常是设置为token或者authorization
private static final String AUTHORIZATION = "token"; public CustomSessionManager() {
super();
} @Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//将ServletRequest转换成HttpServletRequest
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null) {
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
//
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
// //automatically mark it valid here. If it is invalid, the
// //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); //automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
return super.getSessionId(request, response);
}
}
}
自定义数据域 CustomRealm
**
* 自定义的realm 数据域
*/ public class CustomRealm extends AuthorizingRealm { @Autowired
private UserService userService; /**
* 权限校验的时候会调用
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权 doGetAuthorizationInfo"); //从token中获取用户信息,token代表用户输入
// String username = (String) principals.getPrimaryPrincipal();
User newUser = (User) principals.getPrimaryPrincipal(); // 使用原因?
// 授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存 //提高性能的方法1-使用redis缓存:
// 将信息放到缓存,例如redis,但是要设置缓存失效时间,因为可能更新数据库了,但是缓存没有更新
//提高性能的方法2-使用shiro-redis集成的缓存:
// shiro-redis的缓存配置在SecurityManager中
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>(); List<Role> roleList = user.getRoleList(); for (Role role : roleList) {
stringRoleList.add(role.getName()); List<Permission> permissionList = role.getPermissionList(); for (Permission p : permissionList) {
if (p != null) {
stringPermissionList.add(p.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //将用户对应的角色和权限信息 放到权限器中
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
simpleAuthorizationInfo.addRoles(stringRoleList); return simpleAuthorizationInfo;
} /**
* 用户登录的时候会调用
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证 doGetAuthenticationInfo"); //从token中获取用户信息,token代表用户输入
String username = (String) token.getPrincipal(); User user = userService.findAllUserInfoByUsername(username); //取密码
String password = user.getPassword(); if (password == null || "".equals(password)) {
return null;
} return new SimpleAuthenticationInfo(user, password, this.getClass().getName());
}
} /*
* 原有的问题
class java.lang.String must has getter for field: authCacheKey or id\nWe need a
field to identify this Cache Object in Redis. So you need to defined an id field
which you can get unique id to identify this principal. For example, if you use
UserInfo as Principal class, the id field maybe userId, userName, email, etc. For
example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is
authCacheKey or id, that means your principal object has a method called
\"getAuthCacheKey()\" or \"getId()\""
改造原有的逻辑,修改缓存的唯一key doGetAuthorizationInfo 方法
原有:
String username = (String)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(username);
改为
User newUser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); doGetAuthenticationInfo方法
原有:
return new SimpleAuthenticationInfo(username, user.getPassword(),
this.getClass().getName());
改为
return new SimpleAuthenticationInfo(user, user.getPassword(),
this.getClass().getName());
*
*
* */
代码地址:
https://github.com/AlenYang123456/Springboot-Shiro
【SpringBoot】SpringBoot2.x整合Shiro(一)的更多相关文章
- SpringBoot2.x整合Shiro(一)
一:什么是ACL和RBAC: ACL: Access Control List 访问控制列表 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩 ...
- SpringBoot 优雅的整合 Shiro
Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理.借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最 ...
- 在 springboot 中如何整合 shiro 应用 ?
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro. 它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与spring Security 一样都是 ...
- SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
本文源码:GitHub·点这里 || GitEE·点这里 一.Shiro简介 1.基础概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.作为一款安全 ...
- SpringBoot2.x整合Shiro出现cors跨域问题(踩坑记录)
1. Springboot如何跨域? 最简单的方法是: 定义一个配置CorsConfig类即可(是不是简单且无耦合到令人发指) @Configuration public class CorsConf ...
- SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)在pom.xml中添加依赖: <properties> <shi ...
- SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...
- SpringBoot学习:整合shiro(rememberMe记住我后自动登录session失效解决办法)
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 定义一个拦截器,判断用户是通过记住我登录时,查询数据库后台自动登录,同时把用户放入ses ...
- SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)验证码 首先login.jsp里增加了获取验证码图片的标签: <body s ...
随机推荐
- 区分函数防抖&函数节流
1. 概念区分 函数防抖:触发事件后,在n秒内函数只能执行一次,如果触发事件后在n秒内又触发了事件,则会重新计算函数延执行时间. 简单说: 频繁触发, 但只在特定的时间内才执行一次代码,如果特定时间内 ...
- css 超过一行省略号
//超过一行省略号 overflow: hidden; white-space: nowrap; text-overflow: ellipsis; //超过两行省略号 overflow: hidden ...
- 解决.NET Core Ajax请求后台传送参数过大请求失败问题
解决.NET Core Ajax请求后台传送参数过大请求失败问题 今天在项目上遇到一个坑, 在.Net Core中通过ajax向mvc的controller传递对象时,控制器(controller)的 ...
- redis安装以及使用
一.安装 1.源码安装 1.下载redis源码 $ wget http://download.redis.io/releases/redis-4.0.10.tar.gz 2.解压缩 $ tar -zx ...
- FreeBSD csh shell 配置
在/etc/csh.cshrc里面加入: alias ls ls –G, 并重新登录 问:如何让FreeBSD的csh像bash那样按tab列出列出无法补齐的候选文件? 答:标准的方法是按Ctrl+D ...
- Mybatis中由于${}直接注入引发的问题
一.问题引入 我们先来看这段代码,我想从取值为${category}的表中查询全部信息. @Mapper public interface CategoryMapper { @Select(" ...
- 得分(JAVA语言)
package 第三章习题; /* * 给出一个由O和X组成的串(长度为1~80),统计得分. * 每个O得分为目前连续出现的O的个数,X得分为0. * 例如,OOXXOXXOOO的得分为 * ...
- Docker上安装Redis
Docker可以很方便的进行服务部署和管理,下面我们通过docker来搭建Redis的单机模式.Redis主从复制.Redis哨兵模式.Redis-Cluster模式 一.在Docker上安装单机版R ...
- 去空格的四则运算表达式求值-Java
笔记 package com.daidai.day4.demo1; import java.util.ArrayList; import java.util.Arrays; import java.u ...
- PureStudy:学科知识分享——个人网站开发全解
PureStudy:学科知识分享--个人网站开发全解 项目描述 PureStudy,学科知识分享网站. 学生可以使用这个网站,来浏览相应学科的知识点.学习总结,获取相关的资料.此外,他们可以选择上传文 ...