疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口

架构师成长+面试必备之 高并发基础书籍 【Netty Zookeeper Redis 高并发实战


前言

Crazy-SpringCloud 微服务脚手架 &视频介绍

Crazy-SpringCloud 微服务脚手架,是为 Java 微服务开发 入门者 准备的 学习和开发脚手架。并配有一系列的使用教程和视频,大致如下:

高并发 环境搭建 图文教程和演示视频,陆续上线:

中间件 链接地址
Linux Redis 安装(带视频) Linux Redis 安装(带视频)
Linux Zookeeper 安装(带视频) Linux Zookeeper 安装, 带视频
Windows Redis 安装(带视频) Windows Redis 安装(带视频)
RabbitMQ 离线安装(带视频) RabbitMQ 离线安装(带视频)
ElasticSearch 安装, 带视频 ElasticSearch 安装, 带视频
Nacos 安装(带视频) Nacos 安装(带视频)

Crazy-SpringCloud 微服务脚手架 图文教程和演示视频,陆续上线:

组件 链接地址
Eureka Eureka 入门,带视频
SpringCloud Config springcloud Config 入门,带视频
spring security spring security 原理+实战
Spring Session SpringSession 独立使用
分布式 session 基础 RedisSession (自定义)
重点: springcloud 开发脚手架 springcloud 开发脚手架
SpingSecurity + SpringSession 死磕 (写作中) SpingSecurity + SpringSession 死磕

小视频以及所需工具的百度网盘链接,请参见 疯狂创客圈 高并发社群 博客

Spring Security 的重要性

在web应用开发中,安全无疑是十分重要的,选择Spring Security来保护web应用是一个非常好的选择。Spring Security 是spring项目之中的一个安全模块,特别是在spring boot项目中,spring security已经默认集成和启动了。

Spring Security 默认为自动开启的,可见其重要性。

如果要关闭,需要在启动类加上,exclude ={SecurityAutoConfiguration} 的配置

@EnableEurekaClient
@SpringBootApplication(scanBasePackages = {
"com.crazymaker.springcloud.user",
"com.crazymaker.springcloud.seckill.remote.fallback",
"com.crazymaker.springcloud.standard"
}, exclude = {SecurityAutoConfiguration.class})

或者

spring:
autoconfigure:
exclude: org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

一般不建议关闭。

Spring Security 核心组件

spring security核心组件有:Userdetails 、Authentication,UserDetailsService、AuthenticationProvider、AuthenticationManager 下面分别介绍。

Authentication

authentication 直译过来是“认证”的意思,在Spring Security 中Authentication用来表示当前用户是谁,一般来讲你可以理解为authentication就是一组用户名密码信息。Authentication也是一个接口,其定义如下:

public interface Authentication extends Principal, Serializable {

Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

接口有4个get方法,分别获取

  • Authorities, 填充的是用户角色信息。

  • Credentials,直译,证书。填充的是密码。

  • Details ,用户信息。

  • Principal 直译,形容词是“主要的,最重要的”,名词是“负责人,资本,本金”。感觉很别扭,所以,还是不翻译了,直接用原词principal来表示这个概念,其填充的是用户名。

    因此可以推断其实现类有这4个属性。

这几个方法作用如下:

  • getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。

  • getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。

  • getDetails: 获取用户的额外信息,(这部分信息可以是我们的用户表中的信息)

  • getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (UserDetails也是一个接口,里边的方法有getUsername,getPassword等)。

  • isAuthenticated: 获取当前 Authentication 是否已认证。

  • setAuthenticated: 设置当前 Authentication 是否已认证(true or false)。

UserDetails

UserDetails,看命知义,是用户信息的意思。其存储的就是用户信息,其定义如下:

public interface UserDetails extends Serializable {

Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}

方法含义如下:

  • getAuthorites:获取用户权限,本质上是用户的角色信息。

  • getPassword: 获取密码。

  • getUserName: 获取用户名。

  • isAccountNonExpired: 账户是否过期。

  • isAccountNonLocked: 账户是否被锁定。

  • isCredentialsNonExpired: 密码是否过期。

  • isEnabled: 账户是否可用。

UserDetailsService

提到了UserDetails就必须得提到UserDetailsService, UserDetailsService也是一个接口,且只有一个方法loadUserByUsername,他可以用来获取UserDetails。


package org.springframework.security.core.userdetails; public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

通常在spring security应用中,我们会自定义一个CustomUserDetailsService来实现UserDetailsService接口,并实现其public UserDetails loadUserByUsername(final String login);方法。我们在实现loadUserByUsername方法的时候,就可以通过查询数据库(或者是缓存、或者是其他的存储形式)来获取用户信息,然后组装成一个UserDetails,(通常是一个org.springframework.security.core.userdetails.User,它继承自UserDetails) 并返回。

在实现loadUserByUsername方法的时候,如果我们通过查库没有查到相关记录,需要抛出一个异常来告诉spring security来“善后”。这个异常是org.springframework.security.core.userdetails.UsernameNotFoundException。

AuthenticationProvider

负责真正的验证。

当我们使用 authentication-provider 元素来定义一个 AuthenticationProvider 时,如果没有指定对应关联的 AuthenticationProvider 对象,Spring Security 默认会使用 DaoAuthenticationProvider。DaoAuthenticationProvider 在进行认证的时候需要一个 UserDetailsService 来获取用户的信息 UserDetails,其中包括用户名、密码和所拥有的权限等。所以如果我们需要改变认证的方式,我们可以实现自己的 AuthenticationProvider;如果需要改变认证的用户信息来源,我们可以实现 UserDetailsService。

实现了自己的 AuthenticationProvider 之后,我们可以在配置文件中这样配置来使用我们自己的 AuthenticationProvider。其中 myAuthenticationProvider 就是我们自己的 AuthenticationProvider 实现类对应的 bean。

AuthenticationProvider 接口如下:

package org.springframework.security.authentication;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException; boolean supports(Class<?> var1);
}
  • authenticate 表示认证的动作。

  • supports 表示所支持的 Authentication类型。Authentication 包含很多子类,如果 AbstractAuthenticationToken 。

AbstractAuthenticationToken implements Authentication

还有,可以自定义 Authentication ,比如 本实例所使用的: JwtAuthenticationToken。

AuthenticationManager

认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。

AuthenticationManager 是一个接口,它只有一个方法,接收参数为Authentication,其定义如下:

public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}

AuthenticationManager 的作用就是校验Authentication,如果验证失败会抛出AuthenticationException异常。AuthenticationException是一个抽象类,因此代码逻辑并不能实例化一个AuthenticationException异常并抛出,实际上抛出的异常通常是其实现类,如DisabledException,LockedException,BadCredentialsException等。BadCredentialsException可能会比较常见,即密码错误的时候。

组件比较多,但是如果主要流程理顺了,也比较简单。

Spring Security 实战

搞定两个 AuthenticationProvider:

(1) 从数据库获取用户

首先通过 UserDetailsService 获取 UserDetails,然后 通过 UserDetailsService 装配 DaoAuthenticationProvider

(2) 完成用户的认证

实现一个自己的 JwtAuthenticationProvider,完成用户的认证

(3)定制一个过滤器

(4)完成所有组件的装配

实战1 : UserDetailsService 获取 UserDetails

首先通过 UserDetailsService 获取 UserDetails,然后 通过 UserDetailsService 装配 DaoAuthenticationProvider。

package com.crazymaker.springcloud.user.info.service.impl;

@Slf4j
@Service
public class UserAuthService implements UserDetailsService { private PasswordEncoder passwordEncoder; public UserAuthService() {
//默认使用 bcrypt, strength=10
this.passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
} private UserPO loadFromDB(String username) {
if (null == userDao) {
userDao = CustomAppContext.getBean(UserDao.class);
} List<UserPO> list = userDao.findAllByLoginName(username); if (null == list || list.size() <= 0) {
return null;
}
UserPO userPO = list.get(0);
return userPO;
} @Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException { UserPO userPO = loadFromDB(username); //将salt放到password字段返回
return User.builder()
.username(userPO.getLoginName())
.password(userPO.getPassword())
// .password(SessionConstants.SALT)
//BCrypt.gensalt(); 正式开发时可以调用该方法实时生成加密的salt
// .password(SessionConstants.SALT)
.authorities(SessionConstants.USER_INFO)
.roles("USER")
.build(); } }

实战2: 装配 DaoAuthenticationProvider

在 SecurityConfiguration 配置类中加入如下内容:


@Bean("daoAuthenticationProvider")
protected AuthenticationProvider daoAuthenticationProvider() throws Exception {
//这里会默认使用BCryptPasswordEncoder比对加密后的密码,注意要跟createUser时保持一致
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
daoProvider.setUserDetailsService(userDetailsService());
return daoProvider;
} @Override
protected UserDetailsService userDetailsService() {
return new UserAuthService();
}

实战3: 实现一个自己的 JwtAuthenticationProvider

继承于 AuthenticationProvider,实现一个自己的 JwtAuthenticationProvider,完成用户的认证

package com.crazymaker.springcloud.standard.security.provider;
//... public class JwtAuthenticationProvider implements AuthenticationProvider { private RedisOperationsSessionRepository sessionRepository;
private CustomedSessionIdResolver httpSessionIdResolver; public JwtAuthenticationProvider(RedisOperationsSessionRepository sessionRepository,
CustomedSessionIdResolver httpSessionIdResolver) {
this.sessionRepository = sessionRepository;
this.httpSessionIdResolver = httpSessionIdResolver;
} @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
DecodedJWT jwt = ((JwtAuthenticationToken) authentication).getToken();
if (jwt.getExpiresAt().before(Calendar.getInstance().getTime())) {
throw new NonceExpiredException("认证过期");
}
String sid = jwt.getSubject();
String otoken = jwt.getToken(); Session session = null; try {
session = sessionRepository.findById(sid);
} catch (Exception e) {
e.printStackTrace();
} if (null == session) {
throw new NonceExpiredException("认证有误,请重新登录");
} String json = session.getAttribute(G_USER);
if (StringUtils.isBlank(json)) {
throw new NonceExpiredException("认证有误,请重新登录");
} UserDTO userDTO = JsonUtil.jsonToPojo(json, UserDTO.class);
if (null == userDTO) {
throw new NonceExpiredException("认证有误");
} String password = userDTO.getPassword(); String username = userDTO.getLoginName();
UserDetails user = User.builder()
.username(username)
.password(password)
.authorities(SessionConstants.USER_INFO)
.build(); String encryptSalt = password;
try {
Algorithm algorithm = Algorithm.HMAC256(encryptSalt);
JWTVerifier verifier = JWT.require(algorithm)
.withSubject(sid).build();
verifier.verify(jwt.getToken());
} catch (Exception e) {
throw new BadCredentialsException("JWT token verify fail", e);
}
JwtAuthenticationToken token =
new JwtAuthenticationToken(user, jwt, user.getAuthorities());
return token;
} @Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(JwtAuthenticationToken.class);
} }

实战4: 装配 AuthenticationManager

认证是由 AuthenticationManager 来管理的,但是真正进行认证的是 AuthenticationManager 中定义的 AuthenticationProvider。AuthenticationManager 中可以定义有多个 AuthenticationProvider。

  @EnableWebSecurity()
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider())
.authenticationProvider(jwtAuthenticationProvider());
}
//.... }

实战5: 定制过滤器,将 AuthenticationManager 用起来

搞得再多,如果不通过过滤器,将 AuthenticationManager 用起来,也是没有用的。

package com.crazymaker.springcloud.standard.security.filter;
//..... public class JwtAuthenticationFilter extends OncePerRequestFilter { private RequestMatcher requiresAuthenticationRequestMatcher;
private List<RequestMatcher> permissiveRequestMatchers;
private AuthenticationManager authenticationManager; private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); //.....
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Authentication authResult = null;
/**
* 场景: 从 zuul 过来,直接带上session 头
*/
if (StringUtils.isNotEmpty(request.getHeader(SessionConstants.SESSION_SEED))) {
request.setAttribute(SessionConstants.SESSION_SEED,
request.getHeader(SessionConstants.SESSION_SEED));
UserDetails userDetails = User.builder()
.username(request.getHeader(SessionConstants.SESSION_SEED))
.password(request.getHeader(SessionConstants.SESSION_SEED))
.authorities(SessionConstants.USER_INFO)
.build();
authResult = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
successfulAuthentication(request, response, filterChain, authResult); filterChain.doFilter(request, response);
return;
} /**
* 正常场景: 单体微服务访问,或者从Zuul过来,没有带 session head
*/
if (!requiresAuthentication(request, response)) { filterChain.doFilter(request, response);
return;
}
AuthenticationException failed = null;
try {
String token = getJwtToken(request);
if (StringUtils.isNotBlank(token)) {
JwtAuthenticationToken authToken = new JwtAuthenticationToken(JWT.decode(token));
DecodedJWT jwt = authToken.getToken(); //将 AuthenticationManager 用起来
authResult = this.getAuthenticationManager().authenticate(authToken);
UserDetails user = (UserDetails) authResult.getPrincipal();
request.setAttribute(SessionConstants.SESSION_SEED, jwt.getSubject());
} else {
failed = new InsufficientAuthenticationException("请求头认证消息为空");
}
} catch (JWTDecodeException e) {
logger.error("JWT format error", e);
failed = new InsufficientAuthenticationException("请求头认证消息格式错误", failed);
} catch (InternalAuthenticationServiceException e) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
failed = e;
} catch (AuthenticationException e) {
// Authentication failed
failed = e;
}
if (authResult != null) {
successfulAuthentication(request, response, filterChain, authResult);
} else if (!permissiveRequest(request)) {
unsuccessfulAuthentication(request, response, failed);
return;
} filterChain.doFilter(request, response);
} protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
failureHandler.onAuthenticationFailure(request, response, failed);
} //.... }

实战6: 配置 HttpSecurity 的过滤机制

还是在 UserWebSecurityConfig 配置文件,将 HttpSecurity 的过滤机制配置起来,完成所有组件的装配。

代码如下:

package com.crazymaker.springcloud.user.info.config;
//... @EnableWebSecurity()
public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter { @Resource
private UserAuthService userAuthService; protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
"/swagger-ui.html",
"/api/user/login/v1",
// "/api/user/add/v1",
// "/api/user/speed/test/v1",
// "/api/user/say/hello/v1",
// "/api/user/*/detail/v1",
"/api/crazymaker/duty/info/user/login")
.permitAll()
.anyRequest().authenticated() // .antMatchers("/image/**").permitAll()
// .antMatchers("/admin/**").hasAnyRole("ADMIN")
.and() .formLogin().disable()
.sessionManagement().disable()
.cors()
.and() .addFilterAfter(new OptionsRequestFilter(), CorsFilter.class)
.apply(new JsonLoginConfigurer<>()).loginSuccessHandler(jsonLoginSuccessHandler())
.and()
.apply(new JwtAuthConfigurer<>()).tokenValidSuccessHandler(jwtRefreshSuccessHandler()).permissiveRequestUrls("/logout")
.and()
.logout()
// .logoutUrl("/logout") //默认就是"/logout"
.addLogoutHandler(tokenClearLogoutHandler())
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler())
.and()
.addFilterBefore(springSessionRepositoryFilter(), SessionManagementFilter.class)
.sessionManagement().disable()
; } @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/api/user/login/v1",
"/v2/api-docs",
"/swagger-resources/configuration/ui",
"/swagger-resources",
"/swagger-resources/configuration/security",
// "/api/user/say/hello/v1",
// "/api/user/add/v1",
// "/api/user/speed/test/v1",
// "/api/user/*/detail/v1",
"/images/**",
"/swagger-ui.html",
"/webjars/**",
"**/favicon.ico",
"/css/**",
"/js/**",
"/api/crazymaker/info/user/login"
); } @Resource
RedisOperationsSessionRepository sessionRepository; @Resource
public CustomedSessionIdResolver httpSessionIdResolver; @DependsOn({"sessionRepository", "httpSessionIdResolver"})
@Bean("jwtAuthenticationProvider")
protected AuthenticationProvider jwtAuthenticationProvider() {
return new JwtAuthenticationProvider(sessionRepository, httpSessionIdResolver);
} public <S extends Session> OncePerRequestFilter springSessionRepositoryFilter() { CustomedSessionRepositoryFilter<? extends Session> sessionRepositoryFilter = new CustomedSessionRepositoryFilter<>(
sessionRepository);
// sessionRepositoryFilter.setServletContext(this.servletContext);
sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
return sessionRepositoryFilter;
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider())
.authenticationProvider(jwtAuthenticationProvider());
} @Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Bean("daoAuthenticationProvider")
protected AuthenticationProvider daoAuthenticationProvider() throws Exception {
//这里会默认使用BCryptPasswordEncoder比对加密后的密码,注意要跟createUser时保持一致
DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
daoProvider.setUserDetailsService(userDetailsService());
return daoProvider;
} @Bean
protected JwtRefreshSuccessHandler jwtRefreshSuccessHandler() {
return new JwtRefreshSuccessHandler();
} @Override
protected UserDetailsService userDetailsService() {
return new UserAuthService();
} @Bean
protected JsonLoginSuccessHandler jsonLoginSuccessHandler() {
return new JsonLoginSuccessHandler(userAuthService);
} @Bean
protected TokenClearLogoutHandler tokenClearLogoutHandler() {
return new TokenClearLogoutHandler(userAuthService);
} @Bean
protected CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "HEAD", "OPTION"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.addExposedHeader(SessionConstants.AUTHORIZATION);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
} }

实战小结

大概通过以上6步,一个集成jwt的springsecurity机制,完整的配置起来了。

具体,请关注 Java 高并发研习社群博客园 总入口


最后,介绍一下疯狂创客圈:疯狂创客圈,一个Java 高并发研习社群博客园 总入口

疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战


疯狂创客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战

Java 面试题 一网打尽**


spring security 原理+实战的更多相关文章

  1. Spring Security原理分析:系列集合

    Spring Security 工作原理概览:https://blog.csdn.net/u012702547/article/details/89629415 spring security执行原理 ...

  2. Spring Security原理与应用

    Spring Security是什么 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置 ...

  3. spring security原理

    spring security通过一系列过滤器实现其功能,入口过滤器如下(web.xml): <filter> <filter-name>springSecurityFilte ...

  4. spring security原理-学习笔记1-整体概览

    整体概述 运行时环境 Spring Security 3.0需要Java 5.0 Runtime Environment或更高版本. 核心组件 SecurityContextHolder,Securi ...

  5. spring security原理-学习笔记2-核心组件

    核心组件 AuthenticationManager,ProviderManager和AuthenticationProvider AuthenticationManager只是一个接口,实际中是如何 ...

  6. Spring Security原理篇(一) 启动原理

    1.概述 spring security有参考的中文翻译文档https://springcloud.cc/spring-security-zhcn.html 在学习spring security的时候 ...

  7. spring security 简介+实战

    过滤器链: 依赖: security 功能列表: 一.登录验证.权限验证 1.1 httpbasic验证 1.2form验证 建立数据需要遵循RBAC模型 用户表要参考UserDetail创建 实例类 ...

  8. 214. Spring Security:概述

    前言 在之前介绍过了Shiro之后,有好多粉丝问SpringSecurity在Spring Boot中怎么集成.这个系列我们就和大家分享下有关这方面的知识. 本节大纲 一.什么是SpringSecur ...

  9. Spring Security编程模型

    1.采用spring进行权限控制 url权限控制 method权限控制 实现:aop或者拦截器(本质就是之前之后进行控制)--------------------proxy就是 2.权限模型: 本质理 ...

随机推荐

  1. centos7.6安装python3.7

    1.安装python3.7后,需要: yum install libffi-devel -y 然后再到python源码目录再make install 重新编译一下. 否则pip安装一些包时会安装不上, ...

  2. ASI和AFN的区别

    ASI总结 发送请求的2个对象 1.发送GET请求 ASIHttpRequest 2.发送POST请求 ASIFormDataRequest 二发送请求 1.同步请求 startSynchronous ...

  3. ajax规范写法

    $.ajax({ url: "http://1.1.1.1/api/v2/user/inviters", //接口 type: "post", //GET或PO ...

  4. 华为云北京四业务,访问北京一OBS桶,配置指南

    [摘要] 华为云跨数据中心,从北京四访问北京一的OBS桶里面的数据.免去数据迁移的麻烦 1      驱动力 跨region访问OBS桶里面的数据时.如果不走云连接,一个OBS桶域名对应的IP地址,是 ...

  5. luogu P4943 密室 |最短路

    题目描述 密室被打开了. 哈利与罗恩进入了密室,他们发现密室由n个小室组成,所有小室编号分别为:1,2,...,n.所有小室之间有m条通道,对任意两个不同小室最多只有一条通道连接,而每通过一条通道都需 ...

  6. luogu P3807 【模板】卢卡斯定理

    求 C(n,n+m)%p C(m,n)%p=C(m%p,n%p)*C(m/p,n/p) #include<cstdio> #include<cstring> #include& ...

  7. git 提交代码步骤

    拉取服务器代码,避免覆盖他人代码 git pull 查看当前项目中有哪些文件被修改过 git status 提交代码至缓存 git add . 将代码提交到本地仓库中 git commit -m “提 ...

  8. SDK版本管理

    在编写API时,有些API被废弃.如何在使用者调用该API时就报出已经被废弃呢? 方法如下: 1.在OC中 在@interface里将要废弃的方法引用后边加上 __attribute__((depre ...

  9. CSUOJ2078-查找第k大(读入挂)

    查找第k大 Submit Page Output 对于每组数据,输出第k大的数 Sample Input 1 6 2 1 2 3 4 5 6 Sample Output 5 Hint #include ...

  10. TypeScript - 类型声明、枚举、函数、接口

    目录  可定义的类型  类型声明  枚举  函数  接口 可定义的类型 以下所写的并不代表typescript的数据类型,而是在使用过程中可以用作定义的类型 number : 数值类型: string ...