目的:使用这个框架主要就是为了解决高并发环境下登陆操作对数据库及服务器的压力,同时能保证安全性;

  1. 加载时,SpringSecurity定义拦截器和添加两个Fitler;
  2. 登陆时,登陆成功,通过传入的信息(例如:用户名+密码)authenticationManager.authenticate()进行认证得到Authentication;
  3. 认证成功JWT根据规则生成Token(Bearer空格 + Token) 存到Header;认证失败直接抛出提示;
  4. 执行操作时鉴权。拿到Header,如果没拿到就放行了;
  5. 拿到了后,先判断Token是否失效,然后解析出用户名,角色,执行SecurityContextHolder.getContext().setAuthentication(UsernamePasswordAuthenticationToken);再次设置认证信息;没设置成功报403,最后放行。


pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.2</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

User.java

import lombok.Getter;
import lombok.Setter;
import lombok.ToString; import javax.persistence.*; @Entity
@Table(name = "jd_user")
@Getter
@Setter
@ToString
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id; @Column(name = "username")
private String username; @Column(name = "password")
private String password; @Column(name = "role")
private String role;
}

  

JwtUser.java
import lombok.AllArgsConstructor;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection;
import java.util.Collections; @AllArgsConstructor
@ToString
public class JwtUser implements UserDetails { private Integer id;
private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities; // 写一个能直接使用user创建jwtUser的构造器
public JwtUser(User user) {
id = user.getId();
username = user.getUsername();
password = user.getPassword();
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
} @Override
public String getPassword() {
return password;
} @Override
public String getUsername() {
return username;
} @Override
public boolean isAccountNonExpired() {
return true;
} @Override
public boolean isAccountNonLocked() {
return true;
} @Override
public boolean isCredentialsNonExpired() {
return true;
} @Override
public boolean isEnabled() {
return true;
} }

  

LoginUser.java
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter; @Getter
@Setter
public class LoginUser { private String username;
private String password;
private Integer rememberMe; }

  

UserRepository.java
import com.hz.entity.User;
import org.springframework.data.repository.CrudRepository; public interface UserRepository extends CrudRepository<User, Integer> {
User findByUsername(String username);
}

  

UserDetailsServiceImpl.java
import com.hz.entity.JwtUser;
import com.hz.entity.User;
import com.hz.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; /**
* Created by echisan on 2018/6/23
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService { @Autowired
private UserRepository userRepository; @Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findByUsername(s);
return new JwtUser(user);
} }

  

JwtTokenUtils.java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date;
import java.util.HashMap; /**
* Created by echisan on 2018/6/23
*/
public class JwtTokenUtils { public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer "; private static final String SECRET = "jwtsecretdemo";
private static final String ISS = "echisan"; // 角色的key
private static final String ROLE_CLAIMS = "rol"; // 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L; // 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 604800L; // 创建token
public static String createToken(String username,String role, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
HashMap<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
} // 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
} // 获取用户角色
public static String getUserRole(String token){
return (String) getTokenBody(token).get(ROLE_CLAIMS);
} // 是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
} private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}

  

SecurityConfig.java
import com.hz.exception.JWTAccessDeniedHandler;
import com.hz.exception.JWTAuthenticationEntryPoint;
import com.hz.filter.JWTAuthenticationFilter;
import com.hz.filter.JWTAuthorizationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; /**
* Created by echisan on 2018/6/23
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService; @Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, "/tasks/**").hasRole("ADMIN")
// 测试用资源,需要验证了的用户才能访问
.antMatchers("/tasks/**").authenticated()
// 其他都放行了
.anyRequest().permitAll()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
.accessDeniedHandler(new JWTAccessDeniedHandler()); //添加无权限时的处理
} @Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}

  

JWTAuthenticationFilter.java(校验)
import com.hz.entity.JwtUser;
import com.hz.model.LoginUser;
import com.hz.utils.JwtTokenUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; /**
* Created by echisan on 2018/6/23
*/
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private ThreadLocal<Integer> rememberMe = new ThreadLocal<>();
private AuthenticationManager authenticationManager; public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
} @Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException { // 从输入流中获取到登录的信息
try {
LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class);
rememberMe.set(loginUser.getRememberMe());
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>())
);
} catch (Exception e) {
e.printStackTrace();
return null;
}
} // 成功验证后调用的方法
// 如果验证成功,就生成token并返回
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException { JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
System.out.println("jwtUser:" + jwtUser.toString());
boolean isRemember = rememberMe.get() == 1; String role = "";
Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
for (GrantedAuthority authority : authorities){
role = authority.getAuthority();
} String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role, isRemember);
// String token = JwtTokenUtils.createToken(jwtUser.getUsername(), false);
// 返回创建成功的token
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
} @Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().write("authentication failed, reason: " + failed.getMessage());
}
}

  

JWTAuthorizationFilter(鉴权)
import com.hz.exception.TokenIsExpiredException;
import com.hz.utils.JwtTokenUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections; /**
* Created by echisan on 2018/6/23
*/
public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
} @Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException { String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (TokenIsExpiredException e) {
//返回json形式的错误信息
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
response.getWriter().flush();
return;
}
super.doFilterInternal(request, response, chain);
} // 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
boolean expiration = JwtTokenUtils.isExpiration(token);
if (expiration) {
throw new TokenIsExpiredException("token超时了");
} else {
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
}
return null;
}
}

 异常处理1:TokenIsExpiredException.java

/**
* @description: 自定义异常
*/
public class TokenIsExpiredException extends Exception { public TokenIsExpiredException() {
} public TokenIsExpiredException(String message) {
super(message);
} public TokenIsExpiredException(String message, Throwable cause) {
super(message, cause);
} public TokenIsExpiredException(Throwable cause) {
super(cause);
} public TokenIsExpiredException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

异常处理2:

JWTAuthenticationEntryPoint.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* @description:没有携带token或者token无效
*/
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + authException.getMessage();
response.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}
 异常处理3:JWTAccessDeniedHandler.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; /**
* @description:没有访问权限
*/
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
String reason = "统一处理,原因:" + e.getMessage();
httpServletResponse.getWriter().write(new ObjectMapper().writeValueAsString(reason));
}
}
AuthController.java
import com.hz.entity.User;
import com.hz.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; /**
* Created by echisan on 2018/6/23
*/
@RestController
@RequestMapping("/auth")
public class AuthController { @Autowired
private UserRepository userRepository; @Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder; @PostMapping("/register")
public String registerUser(@RequestParam(value = "username") String username, @RequestParam(value = "password") String password) {
User user = new User();
user.setUsername(username);
user.setPassword(bCryptPasswordEncoder.encode(password));
user.setRole("ROLE_USER");
User save = userRepository.save(user);
return save.toString();
}
}

  

TaskController.java
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; /**
* Created by echisan on 2018/6/23
*/
@RestController
@RequestMapping("/tasks")
public class TaskController { @GetMapping
public String listTasks(){
return "任务列表";
} @PostMapping
@PreAuthorize("hasRole('ADMIN')")
public String newTasks(){
return "创建了一个新的任务";
} @PutMapping("/{taskId}")
public String updateTasks(@PathVariable("taskId")Integer id){
return "更新了一下id为:"+id+"的任务";
} @DeleteMapping("/{taskId}")
public String deleteTasks(@PathVariable("taskId")Integer id){
return "删除了id为:"+id+"的任务";
}
}

  application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useSSL=false
spring.datasource.username=root
spring.datasource.password=root spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

测试:postman

注册URL : http://localhost:8080/auth/register?username=admin1&password=admin1

说明:这一步没有什么重要作用,只是向数据库中注册了一条用户信息;

登陆URL:http://localhost:8080/login(注意Body — raw 手动填写JSON)

说明:Header中加入token

操作的URL:http://localhost:8080/tasks/11

说明:先Authorization的TYPE为NO AUTH(不加token),如图所示,403异常

操作的URL:http://localhost:8080/tasks/11

说明:把Authorization的TYPE为Bearer Auth,如图所示,访问正常

SpringBoot+JPA+SpringSeurity+JWT的更多相关文章

  1. Springboot Jpa: [mysql] java.sql.SQLException: Duplicate entry 'XXX' for key 'PRIMARY'

    前言 1.问题背景 偶尔会出现登录请求出错的情况,一旦失败就会短时间内再也登录不上,更换浏览器或者刷新可能会暂时解决这个问题. 项目运行日志如下: 2022-07-21 09:43:40.946 DE ...

  2. 补习系列(19)-springboot JPA + PostGreSQL

    目录 SpringBoot 整合 PostGreSQL 一.PostGreSQL简介 二.关于 SpringDataJPA 三.整合 PostGreSQL A. 依赖包 B. 配置文件 C. 模型定义 ...

  3. 【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础授权权限

    上一篇<[原]无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限>介绍了实现Shiro的基础认证.本篇谈谈实现 ...

  4. 【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限

    开发环境搭建参见<[原]无脑操作:IDEA + maven + SpringBoot + JPA + Thymeleaf实现CRUD及分页> 需求: ① 除了登录页面,在地址栏直接访问其他 ...

  5. 带着新人学springboot的应用08(springboot+jpa的整合)

    这一节的内容比较简单,是springboot和jpa的简单整合,jpa默认使用hibernate,所以本质就是springboot和hibernate的整合. 说实话,听别人都说spring data ...

  6. SpringBoot系列 - 集成JWT实现接口权限认证

    会飞的污熊 2018-01-22 16173 阅读 spring jwt springboot RESTful API认证方式 一般来讲,对于RESTful API都会有认证(Authenticati ...

  7. springboot+jpa+mysql+redis+swagger整合步骤

    springboot+jpa+MySQL+swagger框架搭建好之上再整合redis: 在电脑上先安装redis: 一.在pom.xml中引入redis 二.在application.yml里配置r ...

  8. springboot+jpa+mysql+swagger整合

    Springboot+jpa+MySQL+swagger整合 创建一个springboot web项目 <dependencies> <dependency>      < ...

  9. SpringBoot JPA + H2增删改查示例

    下面的例子是基于SpringBoot JPA以及H2数据库来实现的,下面就开始搭建项目吧. 首先看下项目的整体结构: 具体操作步骤: 打开IDEA,创建一个新的Spring Initializr项目, ...

随机推荐

  1. Mac 系统下如何显示和隐藏文件

    苹果Mac OS X操作系统下,隐藏文件是否显示有很多种设置方法,最简单的要算在Mac终端输入命令.显示/隐藏Mac隐藏文件命令如下(注意其中的空格并且区分大小写): 第一种方式: 显示Mac隐藏文件 ...

  2. 搭建Linux服务器

    工欲善其事必先利其器, 虚拟机:百度云链接地址:https://pan.baidu.com/s/1_nWQh3WKF7xLs5-nmbZ8lA   (Vmware 12 ) Linux 7:百度云链接 ...

  3. 快速删除XMind指定层级的方法

    在使用xmind梳理知识点的时候,因为长期积累,单个文件的节点数可能超过1000个,层级可能超过6层.但在我们做文件分享时,可能只需要提供3层的思维导图,这时候就需要对子节点进行删除.原始的方法,就是 ...

  4. Centos-获取远程主机对应端口信息-telnet

    telnet 通过 telnet协议与远程主机通信或者获取远程主机对应端口信息 格式 telnet URL/IP port

  5. Go map相关

    map Go语言中的map是一种无序的,基于key-value的数据解构,在Go语言中map是引用类型,因此必须初始化后才能使用. 以下示例将展示如何声明一个map类型,以及如何简单使用. 需要注意的 ...

  6. MyEclpse 2015在线安装Gradle插件图解

    MyEclpse 2015 安装Gradle插件 安装方式:在线安装 一.如何获得Gradle插件在线安装地址 Gradle插件最新在线安装地址可在如下网址中查找: https://github.co ...

  7. Jetson AGX Xavier/ubuntu查找文件

    用以下命令查找文件 sudo updatedb locate xxx #xxx是文件名 如果找不到命令,则需要安装mlocate sudo apt-get install mlocate

  8. [C#.NET 拾遗补漏]09:数据标注与数据校验

    数据标注(Data Annotation)是类或类成员添加上下文信息的一种方式,在 C# 通常用特性(Attribute)类来描述.它的用途主要可以分为下面这三类: 验证 Validation:向数据 ...

  9. CN1,CN2 GT和CN2 GIA的区别

    用一句话来概括,CN1主要定位于承载普通质量的互联网业务,而CN2则定位于承载企业VPN业务.中国电信的自营业务及高质量的互联网业务,CN2 GIA又比GT要好一些. 顺序:CN2 GIA>CN ...

  10. linxu 命令

    top | grep java 统计 java 进程使用的资源比率 nohub java -jar test.war & 后台运行 test.war 程序,标准输出到 test.war 程序目 ...