Spring Boot + Spring Cloud 实现权限管理系统 (集成 Shiro 框架)
Apache Shiro
优势特点
它是一个功能强大、灵活的,优秀开源的安全框架。
它可以处理身份验证、授权、企业会话管理和加密。
它易于使用和理解,相比Spring Security入门门槛低。
主要功能
- 验证用户身份
- 用户访问权限控制
- 支持单点登录(SSO)功能
- 可以响应认证、访问控制,或Session事件
- 支持提供“Remember Me”服务
- 。。。
框架体系
Shiro 的整体框架如下图所示:

Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。
它们分别是:
- Authentication(认证):用户身份识别,通常被称为用户“登录”。
- Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至在非web 应用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
除此之外,还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
- Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
- 缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
- 并发:Apache Shiro 支持多线程应用程序的并发特性。
- 测试:支持单元测试和集成测试,确保代码和预想的一样安全。
- “Run As”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
- “Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。
主要流程
在概念层,Shiro 架构包含三个主要的理念:Subject,SecurityManager 和 Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。

- Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
- SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
- Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。
我们需要实现Realms的Authentication 和 Authorization。其中 Authentication 是用来验证用户身份,Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
以上描述摘抄自纯洁的微笑博客文章,更多详情可以参考:
Shiro 官网:http://shiro.apache.org/
纯洁的微笑:http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html
Shiro 集成
下面就来讲解如何在我们的项目里集成 Shiro 框架。
引入依赖
首先上 maven 仓库查找,当前最新的版本是 1.4.0,我们就用这个版本。
kitty-pom/pom.xml 父POM中添加属性和 dependencyManagement 依赖
<shiro.version>1.4.0</shiro.version>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
kitty-admin/pom.xml 添加 dependencies 依赖
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</dependency>
同理,把后续要用到的几个工具包也导入进来。
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>

<!-- commons -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>${commons.lang.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons.fileupload.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons.codec.version}</version>
</dependency>

添加配置
1. 添加配置类
添加配置类,注入自定义的认证过滤器(OAuth2Filter)和认证器(OAuth2Realm),并添加请求路径拦截配置。
ShiroConfig.java

package com.louis.kitty.boot.config; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map; import javax.servlet.Filter; import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import com.louis.kitty.admin.oauth2.OAuth2Filter;
import com.louis.kitty.admin.oauth2.OAuth2Realm; /**
* Shiro 配置
* @author Louis
* @date Sep 1, 2018
*/
@Configuration
public class ShiroConfig { @Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 自定义 OAuth2Filter 过滤器,替代默认的过滤器
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
// 访问路径拦截配置,"anon"表示无需验证,未登录也可访问
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/webjars/**", "anon");
// 查看SQL监控(druid)
filterMap.put("/druid/**", "anon");
// 首页和登录页面
filterMap.put("/", "anon");
filterMap.put("/sys/login", "anon");
// swagger
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/webjars/springfox-swagger-ui/**", "anon");
// 其他所有路径交给OAuth2Filter处理
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
} @Bean
public Realm getShiroRealm(){
OAuth2Realm myShiroRealm = new OAuth2Realm();
return myShiroRealm;
} @Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 注入 Realm 实现类,实现自己的登录逻辑
securityManager.setRealm(getShiroRealm());
return securityManager;
}
}

2. 认证过滤器
拦截除配置成不需认证的请求路径外的请求,都交由这个过滤器处理,负责接收前台带过来的token并封装成对象,如果请求没有携带token,则提示错误。
OAuth2Filter.java

package com.louis.kitty.admin.oauth2; import java.io.IOException; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import com.alibaba.fastjson.JSONObject;
import com.louis.kitty.common.utils.StringUtils;
import com.louis.kitty.core.http.HttpResult;
import com.louis.kitty.core.http.HttpStatus; /**
* Oauth2过滤器
* @author Louis
* @date Sep 1, 2018
*/
public class OAuth2Filter extends AuthenticatingFilter { @Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
// 获取请求token
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
return null;
}
return new OAuth2Token(token);
} @Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return false;
} @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 获取请求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if(StringUtils.isBlank(token)){
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpResult result = HttpResult.error(HttpStatus.SC_UNAUTHORIZED, "invalid token");
String json = JSONObject.toJSONString(result);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
} @Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json; charset=utf-8");
try {
// 处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
HttpResult result = HttpResult.error(HttpStatus.SC_UNAUTHORIZED, throwable.getMessage());
String json = JSONObject.toJSONString(result);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
} /**
* 获取请求的token
*/
private String getRequestToken(HttpServletRequest httpRequest){
// 从header中获取token
String token = httpRequest.getHeader("token");
// 如果header中不存在token,则从参数中获取token
if(StringUtils.isBlank(token)){
token = httpRequest.getParameter("token");
}
return token;
} }

OAuth2Token.java

package com.louis.kitty.admin.oauth2; import org.apache.shiro.authc.AuthenticationToken; /**
* 自定义 token 对象
* @author Louis
* @date Sep 1, 2018
*/
public class OAuth2Token implements AuthenticationToken {
private static final long serialVersionUID = 1L; private String token; public OAuth2Token(String token){
this.token = token;
} @Override
public String getPrincipal() {
return token;
} @Override
public Object getCredentials() {
return token;
}
}

3. 逻辑认证器
逻辑认证器是认证和授权的主体逻辑,主要包含两部分。
doGetAuthenticationInfo:实现自己的登录验证逻辑,这里主要是认证 token。
doGetAuthorizationInfo:实现接口授权逻辑,收集权限标识或角色,用来判定接口是否可以访问
OAuth2Realm.java

package com.louis.kitty.admin.oauth2; import java.util.Set; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import com.louis.kitty.admin.model.SysUser;
import com.louis.kitty.admin.model.SysUserToken;
import com.louis.kitty.admin.sevice.SysUserService;
import com.louis.kitty.admin.sevice.SysUserTokenService; /**
* 认证Realm实现
* @author Louis
* @date Sep 1, 2018
*/
@Component
public class OAuth2Realm extends AuthorizingRealm { @Autowired
SysUserService sysUserService;
@Autowired
SysUserTokenService sysUserTokenService; @Override
public boolean supports(AuthenticationToken token) {
return token instanceof OAuth2Token;
} /**
* 授权(接口保护,验证接口调用权限时调用)
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user = (SysUser)principals.getPrimaryPrincipal();
// 用户权限列表,根据用户拥有的权限标识与如 @permission标注的接口对比,决定是否可以调用接口
Set<String> permsSet = sysUserService.findPermissions(user.getUsername());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permsSet);
return info;
} /**
* 认证(登录时调用)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String token = (String) authenticationToken.getPrincipal();
// 根据accessToken,查询用户token信息
SysUserToken sysUserToken = sysUserTokenService.findByToken(token);
if(sysUserToken == null || sysUserToken.getExpireTime().getTime() < System.currentTimeMillis()){
// token已经失效
throw new IncorrectCredentialsException("token失效,请重新登录");
}
// 查询用户信息
SysUser user = sysUserService.findById(sysUserToken.getUserId());
// 账号被锁定
if(user.getStatus() == 0){
throw new LockedAccountException("账号已被锁定,请联系管理员");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, token, getName());
return info;
}
}

4. 完善登录接口
完善登录逻辑,在用户密码匹配成功之后,创建并保存token,最后将token返回给前台,以后请求带上token。
SysLoginController.java

/**
* 登录接口
*/
@PostMapping(value = "/sys/login")
public HttpResult login(@RequestBody LoginBean loginBean) throws IOException {
String username = loginBean.getUsername();
String password = loginBean.getPassword(); // 用户信息
SysUser user = sysUserService.findByUserName(username); // 账号不存在、密码错误
if (user == null) {
return HttpResult.error("账号不存在");
} if (!match(user, password)) {
return HttpResult.error("密码不正确");
} // 账号锁定
if (user.getStatus() == 0) {
return HttpResult.error("账号已被锁定,请联系管理员");
} // 生成token,并保存到数据库
SysUserToken data = sysUserTokenService.createToken(user.getUserId());
return HttpResult.ok(data);
} /**
* 验证用户密码
* @param user
* @param password
* @return
*/
public boolean match(SysUser user, String password) {
return user.getPassword().equals(PasswordUtils.encrypte(password, user.getSalt()));
}

SysUserTokenServiceImpl.java,生成并保存token,这里把token保存在数据库,也可以选择保存在redis或session。

@Override
public SysUserToken createToken(long userId) {
// 生成一个token
String token = TokenGenerator.generateToken();
// 当前时间
Date now = new Date();
// 过期时间
Date expireTime = new Date(now.getTime() + EXPIRE * 1000);
// 判断是否生成过token
SysUserToken sysUserToken = findByUserId(userId);
if(sysUserToken == null){
sysUserToken = new SysUserToken();
sysUserToken.setUserId(userId);
sysUserToken.setToken(token);
sysUserToken.setLastUpdateTime(now);
sysUserToken.setExpireTime(expireTime);
// 保存token,这里选择保存到数据库,也可以放到Redis或Session之类可存储的地方
save(sysUserToken);
} else{
sysUserToken.setToken(token);
sysUserToken.setLastUpdateTime(now);
sysUserToken.setExpireTime(expireTime);
// 如果token已经生成,则更新token的过期时间
update(sysUserToken);
}
return sysUserToken;
}

登录测试
登录 Swagger: localhost:8088/swagger-ui.html
用户名:admin 密码: admin

登录成功之后,会返回token,如下图所示。

登录成功之后,一般的逻辑是调到主页,这里我们可以继续访问一个接口当作登录成功之后的跳转(如 /dept/findTree,不用传参方便)。
然后我们就会发现调用失败,甚至打断点到目标接口代码,连接口代码都没有进来,根本没有调用到findTree接口。
这是必然的,因为引入乐Shiro之后便有了权限认证,如果访问请求没有携带token是不能通过验证的,具体解决方案参加下面的登录流程。
登录流程
为了帮助大家理解 shiro 的工作流程,这里对使用了 shiro 以后,我们项目的登录流程做一下简单的说明。
我们开启Debug模式,给登录接口及过滤器和认证器都打上断点,调用登录接口,跟着代码移动的脚步来了解整个登录的流程。
首先代码来到了我们调用的接口: login

成功验证用户密码,即将生成和保存token

根据条件生成或更新token,成功后登录接口会将token返回给前台,前台会带上token进入登录验证

登录接口返回之后就已经登录成功了,按照一般逻辑,这时就会跳转到主页了,我们这边没有页面,就通过访问接口来模拟吧。
我们访问Swagger里 dept/findTree 接口,获取机构数据,这个接口不用传参,比较方便。
结果发现访问没有访问正常结果,甚至debug发现连对应的后台接口代码都没有进去。那是因为加了shiro以后,访问除配置放过外的接口都是需要验证的。
我们直接在浏览器访问:http://localhost:8088/dept/findTree,发现代码来到了我们在过滤器设置的断点里边。

因为我们访问接口的时候,没有把刚才登录成功之后返回的token信息携带过来,所以在过滤器里验证token失败,返回"invalid token" 提示

果然,在代码执行完毕之后,页面得到 “invalid token” 的提示,那我们要继续访问还得带上token才行。

那怎样才能让 swagger 发送请求的时候把 token 也带过去呢,我们这样处理。
修改 Swagger 配置,添加请求头参数,用来传递 token。

SwaggerConfig.java

package com.louis.kitty.boot.config;
import java.util.ArrayList;
import java.util.List; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration
@EnableSwagger2
public class SwaggerConfig { @Bean
public Docket createRestApi(){
// 添加请求参数,我们这里把token作为请求头部参数传入后端
ParameterBuilder parameterBuilder = new ParameterBuilder();
List<Parameter> parameters = new ArrayList<Parameter>();
parameterBuilder.name("token").description("令牌")
.modelRef(new ModelRef("string")).parameterType("header").required(false).build();
parameters.add(parameterBuilder.build());
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.any()).paths(PathSelectors.any())
.build().globalOperationParameters(parameters);
// return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo())
// .select()
// .apis(RequestHandlerSelectors.any())
// .paths(PathSelectors.any()).build();
} private ApiInfo apiInfo(){
return new ApiInfoBuilder()
.title("Kitty API Doc")
.description("This is a restful api document of Kitty.")
.version("1.0")
.build();
} }

重启代码,发现接口页面已经多了token请求参数了。

我们先调用登录接口,拿到返回的token之后,把token复制过来一起发送过去。
继续用 amdin 用户登录,获得返回 token

携带 token 再次访问 findTree 接口。

代码进入过滤器,发现 token 已经成功传过来了,往下执行 executeLogin 继续登录流程。

上面方法调用下面的接口,尝试从请求头或请求参数中获取token。

父类的 executeLogin 方法调用 createToken 创建 token,然后使用 Subject 进行登录。

过滤器的 createToken 方法返回我们自定义的 token 对象。

Subject 调用 SecurityManager 继续进行登录流程。

看下面的调用栈截图,经过系列操作之后,终于来到了我们的 OAuth2Realm,这里有我们的登录和授权逻辑。

来到 OAuth2Realm 的 doGetAuthenticationInfo 方法,将前台传递的token跟后台存储的做比对,比对成功继续往下走。

验证成功之后,代码终于来到了我们的目标接口,成功的完成了调用。

继续往前,放行代码,代码执行完毕,调用界面成功的返回了结果。

我们不传 token 或者传一个不存在的 token 试试。

发现代码在过滤器验证的时候没有通过,返回 “Token 失效” 提示。

接口响应结果,提示 “token失效,请重新登录”。

最后注意:加了Shiro之后每次调试接口都需要传递token,对我们开发来说也是麻烦,如有需要可以通过以下方法取消验证。
在 ShiroConfig 配置类中,把接口路径映射到 anon 过滤器,调试时就不需要 token 验证了。

Spring Boot + Spring Cloud 实现权限管理系统 (集成 Shiro 框架)的更多相关文章
- Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理
本文是接着上篇博客写的:Spring boot 入门(三):SpringBoot 集成结合 AdminLTE(Freemarker),利用 generate 自动生成代码,利用 DataTable 和 ...
- Spring Boot 入门(五):集成 AOP 进行日志管理
本篇文章是接着 Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理写的,按照前面几篇博客的教程,可以搭建一个简单的项目,主要包含了 Pagehelper+MyBatis 分页 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十一):集成 Shiro 框架
Apache Shiro 优势特点 它是一个功能强大.灵活的,优秀开源的安全框架. 它可以处理身份验证.授权.企业会话管理和加密. 它易于使用和理解,相比Spring Security入门门槛低. 主 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(七):集成 Druid 数据源
数据库连接池负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个:释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏 ...
- 新书上线:《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》,欢迎大家买回去垫椅子垫桌脚
新书上线 大家好,笔者的新书<Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统>已上线,此书内容充实.材质优良,乃家中必备垫桌脚 ...
- Spring Boot 2.X(十八):集成 Spring Security-登录认证和权限控制
前言 在企业项目开发中,对系统的安全和权限控制往往是必需的,常见的安全框架有 Spring Security.Apache Shiro 等.本文主要简单介绍一下 Spring Security,再通过 ...
- [权限管理系统(四)]-spring boot +spring security短信认证+redis整合
[权限管理系统]spring boot +spring security短信认证+redis整合 现在主流的登录方式主要有 3 种:账号密码登录.短信验证码登录和第三方授权登录,前面一节Sprin ...
- Spring Boot 入门(六):集成 treetable 和 zTree 实现树形图
本篇文章是接着Spring Boot 入门(五):集成 AOP 进行日志管理写的,主要集成了树形图,在部门列表或者权限列表中,树形图经常被用上.主要是根据相应的 API 凭借 html 字符串 1.t ...
- Spring -> Spring Boot > Spring Cloud
这几天刚刚上班,公司用的是Spring Cloud,接触不多.我得赶快学起来. 想学习就必须得知道什么是微服务,什么是Spring Boot,什么是Spring Cloud,以及两者之间有什么关系? ...
随机推荐
- AndroidImageSlider第一张图闪过的问题解决
1. AndroidImageSlider的使用: 参考源码:https://github.com/daimajia/AndroidImageSlider 当然网上介绍使用方法的很多,搜一搜. 2. ...
- python 正则表达式规则收集
python正则表达式基本元字符 . 通配符,匹配所有字符 ^abc 匹配以abc开始的字符串 abc$ 匹配以abc结尾的字符串 [abc] 匹配字符集合 [A-Z0-9] 匹配字符范围 ...
- 学习笔记50—多重假设检验与Bonferroni校正、FDR校正
总结起来就三句话: (1)当同一个数据集有n次(n>=2)假设检验时,要做多重假设检验校正 (2)对于Bonferroni校正,是将p-value的cutoff除以n做校正,这样差异基因筛选的p ...
- 学习笔记9—python数据表的合并(join(), merge()和concat())
merage# pandas提供了一个类似于关系数据库的连接(join)操作的方法<Strong>merage</Strong>,可以根据一个或多个键将不同DataFrame中 ...
- 使用http://start.spring.io/ 生成工程
今天学习spring-cloud,无意中发现一个spring提供的构建工程的页面,想记录下,发现有个博客写的很好就直接抄过来了. 原文链接: https://blog.csdn.net/u01050 ...
- (转)Linux下设置和查看环境变量
原文地址:<Linux下设置和查看环境变量> Linux的变量种类 按变量的生存周期来划分,Linux变量可分为两类: 1. 永久的:需要修改配置文件,变量永久生效. 2. 临时的:使用e ...
- Jedis与Lua脚本结合
使用Lua脚本的好处 1.减少网络开销:可以将多个请求通过脚本的形式一次发送,减少网络时延和请求次数. 2.原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入.因此在编 ...
- redis安装配置:inux系统为centOS 64位
下载Redis-4.0.6.tar.gz包 我下载的到自己的默认目录/root/software/下 1. 然后解压到这个目录下 /usr/local/src/ 解压命令: tar -xzf redi ...
- ado.net常用操作
目录 一.ADO.NET概要 二.ADO.NET的组成 三.Connection连接对象 3.1.连接字符串 3.1.1.SQL Server连接字符串 3.1.2.Access连接字符串 3.1.3 ...
- android -------- Eclipse下的NDK配置环境
NDK 全称是Native Development Kit,是一个让开发人员在Android应用中嵌入使用本地代码编写的组件的工具集 原生开发工具包 (NDK) 是一组可让您在 Android 应用中 ...