首页 文章 精选 留言 我的

精选列表

搜索[API集成],共10000篇文章
优秀的个人博客,低调大师

图解kubernetes中的api多版本中反序列化与转换

在之前的文章中分析过kubernetes是如何进行多版本管理中提到了一个关键的设计解码器, 负责将请求对象反序列化成一个具体的数据模型,今天一起来了解下其内部是如何实现多版本管理、转换的设计要点 1.版本化管理的关键设计 1.1 从拓扑转换到星状转换 在通常的web开发中更多的时候,大家都是断代向前兼容更新,大多数情况下当版本更新之后会独立演进,如果要在多版本之间转换通常则会出现如下的情况 如果我们要为每个版本都去适配其他所有的版本,则复杂度会指数级上升,而在kubernetes中则通过一个内部版本的设计来进行解决,内部版本是一个稳定的版本,所有的版本都只针对目标版本来进行转换的实现,而不关注其他版本 1.2 兼容设计之转换 那如果谋个版本需要独立的演进,或者增设一些新的字段,修改字段名称等破坏性更新的时候,则就需要一种转换机制,负责在当前版本和内部版本之间来进行字段或者数据的转换 1.3 转换的最终之反射 转换其实核心目标是完成从目标对象的字段中获取数据,然后经过一系列操作最终为目标对象的对应的字段进行赋值操作,要完成该操作,则就需要借助反射来实现,通过枚举字段,来获取对应的转换函数,执行转换函数,完成目标赋值 2. 关键设计的实现 为了实现上面的方案,kubernetes中设计了如下组件:Scheme(负责各个版本的注册和管理)、Convert(转换实现)、Serializer(实现对应版本的反序列化),让我们依次看下其关键设计 2.1 Convert Convert实现从一个目标对象到另外一个目标对象的转换, 为了实现这种转换,kubernetes里面主要是借助反射和不同版本的转换函数来共同完成 1.首先我们通过目标函数来获取对应的属性字段,然后针对该字段进行计算赋值操作 2.如果发现对应的字段需要来转换,则会调用对应的转换函数来进行赋值操作,如果不需要转换则会直接通过反射来进行赋值 2.2 外部版本到内部版本的转换 在构建rest接口的时候,每个rest接口都会持有一个生成当前版本对象的构造函数,当请求进入之后,会首先通过目标版本获取对应的decoder decoder会利用当前的GroupVersionKind来进行第一步解析,首先将字节数组解析成当前版本,然后在解析成目标对象之后,又会根据目标版本进行转换 2.3 Scheme Scheme负责各个资源版本的统一注册和管理,为其他组件提供根据GVK来获取对应的资源对象,也提供通过资源对象获取版本等操作,内部还持有convert对象,包装了源对象到目标对象的转换操作 Scheme对象是一个复合的数据结构,其实现了多种结果,诸如typer、defaulter、creater等,很多地方都是通过直接传递scheme来进行对应的参数的填充, 其内部关键数据结构如下 GVK与资源类型的映射,以及当前资源类型支持哪些GVK gvkToType map[schema.GroupVersionKind]reflect.Type typeToGVK map[reflect.Type][]schema.GroupVersionKind 则创建对象的时候可以直接通过New来实例化对应的对象 func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) { if t, exists := s.gvkToType[kind]; exists { // 利用反射来创建对象 return reflect.New(t).Interface().(Object), nil } // 省略相关代码 } 默认初始化函数 defaulterFuncs map[reflect.Type]func(interface{}) 根据对应的类型来完成初始化操作 func (s *Scheme) Default(src Object) { if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok { fn(src) } } 提供转换函数注册接口, 注册到convert中 func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error { return s.converter.RegisterUntypedConversionFunc(a, b, fn) } 3.学习总结 实现上无疑是复杂的作为一个工业设计有很多需要care的边缘情况,剖丝抽茧每个人看到的都不一样,这可能就是源码阅读的乐趣,从上面可以看到核心其实就三个点:将HTTP数据反序列化成为当前URL的资源对象,然后目标资源对象进行初始化默认值函数的执行,最后通过convert来处理不同版本之间的差异,最后统一操作内部版本,好了今天就到这了,希望对大家有所帮助 > 微信号:baxiaoshi2020 > 关注公告号阅读更多源码分析文章 > 更多文章关注 www.sreguide.com > 本文由博客一文多发平台 OpenWrite 发布

优秀的个人博客,低调大师

Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

文章首发于公众号《程序员果果》地址:https://mp.weixin.qq.com/s/QO5L1-RCR-jmIuHlZIfkqQ 简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念。本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用。 快速上手 之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不了解 Spring Security 先移步到 Spring Boot Security 详解。 建表 DROP TABLE IF EXISTS `user`; DROP TABLE IF EXISTS `role`; DROP TABLE IF EXISTS `user_role`; DROP TABLE IF EXISTS `role_permission`; DROP TABLE IF EXISTS `permission`; CREATE TABLE `user` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `role` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE `user_role` ( `user_id` bigint(11) NOT NULL, `role_id` bigint(11) NOT NULL ); CREATE TABLE `role_permission` ( `role_id` bigint(11) NOT NULL, `permission_id` bigint(11) NOT NULL ); CREATE TABLE `permission` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `url` varchar(255) NOT NULL, `name` varchar(255) NOT NULL, `description` varchar(255) NULL, `pid` bigint(11) NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO user (id, username, password) VALUES (1,'user','e10adc3949ba59abbe56e057f20f883e'); INSERT INTO user (id, username , password) VALUES (2,'admin','e10adc3949ba59abbe56e057f20f883e'); INSERT INTO role (id, name) VALUES (1,'USER'); INSERT INTO role (id, name) VALUES (2,'ADMIN'); INSERT INTO permission (id, url, name, pid) VALUES (1,'/user/hi','',0); INSERT INTO permission (id, url, name, pid) VALUES (2,'/admin/hi','',0); INSERT INTO user_role (user_id, role_id) VALUES (1, 1); INSERT INTO user_role (user_id, role_id) VALUES (2, 1); INSERT INTO user_role (user_id, role_id) VALUES (2, 2); INSERT INTO role_permission (role_id, permission_id) VALUES (1, 1); INSERT INTO role_permission (role_id, permission_id) VALUES (2, 1); INSERT INTO role_permission (role_id, permission_id) VALUES (2, 2); 项目结构 resources |___application.yml java |___com | |____gf | | |____SpringbootJwtApplication.java | | |____config | | | |____.DS_Store | | | |____SecurityConfig.java | | | |____MyFilterSecurityInterceptor.java | | | |____MyInvocationSecurityMetadataSourceService.java | | | |____MyAccessDecisionManager.java | | |____entity | | | |____User.java | | | |____RolePermisson.java | | | |____Role.java | | |____mapper | | | |____PermissionMapper.java | | | |____UserMapper.java | | | |____RoleMapper.java | | |____utils | | | |____JwtTokenUtil.java | | |____controller | | | |____AuthController.java | | |____filter | | | |____JwtTokenFilter.java | | |____service | | | |____impl | | | | |____AuthServiceImpl.java | | | | |____UserDetailsServiceImpl.java | | | |____AuthService.java 关键代码 pom.xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</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.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> application.yml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-security-jwt?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: root SecurityConfig @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { //校验用户 auth.userDetailsService( userDetailsService ).passwordEncoder( new PasswordEncoder() { //对密码进行加密 @Override public String encode(CharSequence charSequence) { System.out.println(charSequence.toString()); return DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()); } //对密码进行判断匹配 @Override public boolean matches(CharSequence charSequence, String s) { String encode = DigestUtils.md5DigestAsHex(charSequence.toString().getBytes()); boolean res = s.equals( encode ); return res; } } ); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //因为使用JWT,所以不需要HttpSession .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS).and() .authorizeRequests() //OPTIONS请求全部放行 .antMatchers( HttpMethod.OPTIONS, "/**").permitAll() //登录接口放行 .antMatchers("/auth/login").permitAll() //其他接口全部接受验证 .anyRequest().authenticated(); //使用自定义的 Token过滤器 验证请求的Token是否合法 http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); http.headers().cacheControl(); } @Bean public JwtTokenFilter authenticationTokenFilterBean() throws Exception { return new JwtTokenFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } JwtTokenUtil /** * JWT 工具类 */ @Component public class JwtTokenUtil implements Serializable { private static final String CLAIM_KEY_USERNAME = "sub"; /** * 5天(毫秒) */ private static final long EXPIRATION_TIME = 432000000; /** * JWT密码 */ private static final String SECRET = "secret"; /** * 签发JWT */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(16); claims.put( CLAIM_KEY_USERNAME, userDetails.getUsername() ); return Jwts.builder() .setClaims( claims ) .setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME ) ) .signWith( SignatureAlgorithm.HS512, SECRET ) .compact(); } /** * 验证JWT */ public Boolean validateToken(String token, UserDetails userDetails) { User user = (User) userDetails; String username = getUsernameFromToken( token ); return (username.equals( user.getUsername() ) && !isTokenExpired( token )); } /** * 获取token是否过期 */ public Boolean isTokenExpired(String token) { Date expiration = getExpirationDateFromToken( token ); return expiration.before( new Date() ); } /** * 根据token获取username */ public String getUsernameFromToken(String token) { String username = getClaimsFromToken( token ).getSubject(); return username; } /** * 获取token的过期时间 */ public Date getExpirationDateFromToken(String token) { Date expiration = getClaimsFromToken( token ).getExpiration(); return expiration; } /** * 解析JWT */ private Claims getClaimsFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey( SECRET ) .parseClaimsJws( token ) .getBody(); return claims; } } JwtTokenFilter @Component public class JwtTokenFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; /** * 存放Token的Header Key */ public static final String HEADER_STRING = "Authorization"; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = request.getHeader( HEADER_STRING ); if (null != token) { String username = jwtTokenUtil.getUsernameFromToken(token); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(token, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( request)); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); } } AuthServiceImpl @Service public class AuthServiceImpl implements AuthService { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public String login(String username, String password) { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password ); Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = userDetailsService.loadUserByUsername( username ); String token = jwtTokenUtil.generateToken(userDetails); return token; } } 关键代码就是这些,其他类代码参照后面提供的源码地址。 验证 登录,获取token curl -X POST -d "username=admin&password=123456" http://127.0.0.1:8080/auth/login 返回 eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ 不带token访问资源 curl -X POST -d "name=zhangsan" http://127.0.0.1:8080/admin/hi 返回,拒绝访问 { "timestamp": "2019-03-31T08:50:55.894+0000", "status": 403, "error": "Forbidden", "message": "Access Denied", "path": "/auth/login" } 携带token访问资源 curl -X POST -H "Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTU1NDQ1MzUwMX0.sglVeqnDGUL9pH1oP3Lh9XrdzJIS42VKBApd2nPJt7e1TKhCEY7AUfIXnzG9vc885_jTq4-h8R6YCtRRJzl8fQ" -d "name=zhangsan" http://127.0.0.1:8080/admin/hi 返回正确 hi zhangsan , you have 'admin' role 源码 https://github.com/gf-huanchupk/SpringBootLearning/tree/master/springboot-jwt

优秀的个人博客,低调大师

Java8的stream API与 C#的 LINQ 拓展方法对比

为方便初学 Java8/C# 集合操作的人,特意写下这篇文章. 前期准备 C#版 java版 单集合 分类筛选 计数(Count) Date time1 = convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1)); //0 Long count1 = list1.stream().filter(o -> o.getBirthday().equals(time1)).count(); int count1 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).Count(); long count2 = list1.Where(o => o.Birthday.Equals(new DateTime(1990, 1, 1)) && o.Sex == Sex.Male).LongCount(); /* 0 0 */ 分组(GroupBy) Map<Sex, List<Person>> group1 = list1.stream().collect(Collectors.groupingBy(Person::getSex)); Iterator it = group1.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Sex, List<Person>> groupByItem = (Map.Entry) it.next(); Sex sex = groupByItem.getKey(); out.println(sex); groupByItem.getValue().forEach(person -> { out.println(new Gson().toJson(person)); }); } /* 输出结果: Male {"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"Male"} Female {"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"Female"} X {"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"} */ var group1 = list1.GroupBy(o => o.Sex); //当我们使用 GroupBy() 扩展方法时,使用了延迟执行。 这意味着,当你遍历集合的时候,下一个要出现的项目可能会或者可能不会被加载。 这是一个很大的性能改进,但它会引起有趣的副作用。 list1.RemoveAll(o => o.Sex == Sex.X);//定义 groupby 集合后对原集合进行修改,会发现group1里面已经没了 Sex=X的分组 foreach (var groupByItem in group1) { Sex sex = groupByItem.Key; System.Console.WriteLine(sex); foreach (Person person in groupByItem) { System.Console.WriteLine(JsonConvert.SerializeObject(person)); } } /* 输出结果: {"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2} Male {"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1} Female {"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":2} Male {"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":1} */ //该 ToLookup() 方法创建一个类似 字典(Dictionary ) 的列表List, 但是它是一个新的 .NET Collection 叫做 lookup。 Lookup,不像Dictionary, 是不可改变的。 这意味着一旦你创建一个lookup, 你不能添加或删除元素。 var group2 = list1.ToLookup(o => o.Sex); foreach (var groupByItem in group2) { Sex sex = groupByItem.Key; foreach (Person person in groupByItem) { System.Console.WriteLine(sex); System.Console.WriteLine(JsonConvert.SerializeObject(person)); } } /* 输出结果: {"Height":165,"Weight":50,"Birthday":"1981-01-01T00:00:00","Hobbies":["吃飯","逛街"],"Identifier":"1","Address":"北京","Sex":3} {"Height":170,"Weight":50,"Birthday":"1982-02-01T00:00:00","Hobbies":["吃飯","看電影"],"Identifier":"2","Address":"北京","Sex":3} */ 与此对比,stream没有RemoveAll的操作 匹配的第一项(findFirst/First,FirstOrDefault) Person after90 = list1.stream() .filter(o -> o.getBirthday().after(convertLocalDateToTimeZone(LocalDate.of(1990, 1, 1)))) .findFirst() .orElse(null); // null var after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).First();//如果结果为空,将会导致异常,所以一般极少使用该方法 //An unhandled exception of type 'System.InvalidOperationException' occurred in System.Linq.dll: 'Sequence contains no elements' after90 = list1.Where(o => o.Birthday >= new DateTime(1990, 1, 1)).FirstOrDefault(); var after00 = list1.Where(o => o.Birthday >= new DateTime(2000, 1, 1)).FirstOrDefault(); 遍历(ForEach) list1.stream().forEach(o -> { //在ForEach當中可對集合進行操作 o.setSex(Sex.X); }); list1.forEach(o -> { out.println(new Gson().toJson(o)); }); /* {"height":165,"weight":50,"identifier":"1","address":"北京","birthday":"Jan 1, 1981 12:00:00 AM","hobbies":["吃飯","逛街"],"sex":"X"} {"height":170,"weight":50,"identifier":"2","address":"北京","birthday":"Feb 1, 1982 12:00:00 AM","hobbies":["吃飯","看電影"],"sex":"X"} {"height":170,"weight":50,"identifier":"3","address":"北京","birthday":"Mar 1, 1983 12:00:00 AM","hobbies":["吃飯","上網"],"sex":"X"} */ list1.ForEach(item => { //在ForEach當中可對集合進行操作 item.Sex = Sex.X; }); list1.ForEach(item => { System.Console.WriteLine(JsonConvert.SerializeObject(item)); }); 极值Max/Min //IntStream的max方法返回的是OptionalInt,要先判断有没有值再读取值.isPresent=false 时直接getAsInt会报错.mapToLong,mapToDouble同理 OptionalInt maxHeightOption = list1.stream().mapToInt(Person::getHeight).max(); //字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。 //当集合为长度0的集合时会返回起始值Integer.MIN_VALUE,起始值也不能乱传,个中缘由我暂不清楚 int maxHeight = list1.stream().mapToInt(Person::getHeight).reduce(Integer.MIN_VALUE, Integer::max); out.println(maxHeight); if (maxHeightOption.isPresent()) { maxHeight = maxHeightOption.getAsInt(); out.println(maxHeight); } //mapToInt参数的2种写法都一样,我比较喜欢以下写法,但是 idea 会报 warning OptionalInt minWeightOption = list1.stream().mapToInt(o -> o.getHeight()).min(); int minWeight = list1.stream().mapToInt(o -> o.getHeight()).reduce(Integer.MAX_VALUE, Integer::min); int maxHeight = list1.Select(o => o.Height).Max(); //同 list1.Max(o => o.Height); int minWeight = list1.Min(o => o.Weight); 跳过(skip/Skip),截取(limit/Take) //skip和 limit参数都是long, 这个要注意 list1.stream().skip(1L).limit(2L); 排序 去重复(Distinct) list1.stream().map(Person::getIdentifier).distinct(); list1.Select(o=>o.Identifier).Distinct(); list1.Skip(1).Take(2); 升序(sort/OrderBy) out.println("------------------------------------|升序|------------------------------------"); list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday)).collect(Collectors.toList()); out.println(new Gson().toJson(list1)); list1 = list1.stream().sorted((left, right) -> left.getBirthday().compareTo(right.getBirthday())).collect(Collectors.toList()); out.println(new Gson().toJson(list1)); //升序 list1 = list1.OrderBy(o => o.Birthday).ToList(); 降序(sort/OrderByDescending) out.println("------------------------------------|降序|------------------------------------"); list1 = list1.stream().sorted(Comparator.comparing(Person::getBirthday).reversed()).collect(Collectors.toList()); out.println(new Gson().toJson(list1)); list1 = list1.stream().sorted((left, right) -> right.getBirthday().compareTo(left.getBirthday())).collect(Collectors.toList()); out.println(new Gson().toJson(list1)); //降序 list1 = list1.OrderByDescending(o => o.Birthday).ToList(); 多集合 交集 list1 ∩ list2 out.println("------------------------------------|交集 list1 ∩ list2|------------------------------------"); list1.stream().filter(o -> list2.contains(o)).collect(Collectors.toList()); //连接,下面表示把 list1和 list2当中相同身份证号的取出来,生成一个新的集合 //实际上, join 有另外的用法,类似 sqlserver 里面的多表连接,将不同数据源结合到一起,生成新的数据结构 var intersect = list1.Join(list2, o => o.Identifier, o => o.Identifier, (a, b) => a).ToList(); //交集 list1 ∩ list2 intersect = list1.Intersect(list2).ToList(); 并集list1 ∪ list2 out.println("------------------------------------|并集list1 ∪ list2 |------------------------------------"); list1.addAll(list2); //并集list1 ∪ list2 var union = list1.Union(list2).ToList(); 差集list1 - list2 out.println("------------------------------------|差集list1 - list2|------------------------------------"); list1.stream().filter(item1 -> !list2.contains(item1)).collect(Collectors.toList()); //差集list1 - list2 var except = list1.Except(list2).ToList(); 数据结构转换 out.println("------------------------------------|数据结构转换|------------------------------------"); List<Person> list3 = list1.stream().filter(o -> true).collect(Collectors.toList()); ArrayList<Person> list4 = list1.stream().filter(o -> true).collect(Collectors.toCollection(ArrayList::new)); Set<Person> list5 = list1.stream().filter(o -> true).collect(Collectors.toSet()); Object[] list6 = list1.stream().toArray(); Person[] list7 = list1.stream().toArray(Person[]::new); //数据结构转换 list1.ToArray(); //注意如果 key 重复,ToDictionary会导致出错 list1.ToDictionary(o => o.Identifier, o => o); list1.ToHashSet();

优秀的个人博客,低调大师

通过open API迅速搭建基于应用分组+报警模板的基本监控体系

背景 云监控的功能庞杂,涉及云产品众多, 对于企业用户来说,面对茫茫多的云上资源,建立监控体系时可能第一时间感到无从下手,本文将通过云监控的openAPI,利用 "应用分组" + "报警模板" 的功能,迅速为海量的资源搭建起基本的监控体系。 前置条件 首先你需要有一个阿里云账号 准备好ak (access_key_id, access_key_secret) 建议使用子账号,安全性更好。(参见RAM访问控制) 使用子账号时确保已经授权了AliyunCloudMonitorFullAccess 开始搭建监控体系啦 1. 设置联系人和联系人组 手动通过云监控控制台创建联系人和联系人组,设置正确的邮件,手机信息以便接受报警通知。 假设联系人组为"ops_group" String contactGroups = "ops_group"; logger.i

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册