首页 文章 精选 留言 我的

精选列表

搜索[mybatis],共3678篇文章
优秀的个人博客,低调大师

java B2B2C springmvc mybatis电子商务平台源码-Spring Cloud Security

一、SpringCloud Security简介Spring Cloud Security提供了一组原语,用于构建安全的应用程序和服务,而且操作简便。可以在外部(或集中)进行大量配置的声明性模型有助于实现大型协作的远程组件系统,通常具有中央身份管理服务。它也非常易于在Cloud Foundry等服务平台中使用。在Spring Boot和Spring Security OAuth2的基础上,可以快速创建实现常见模式的系统,如单点登录,令牌中继和令牌交换。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三 功能: 从Zuul代理中的前端到后端服务中继SSO令牌资源服务器之间的中继令牌使Feign客户端表现得像OAuth2RestTemplate(获取令牌等)的拦截器在Zuul代理中配置下游身份验证 二、SpringCloud Security知识点 封装顺序是这样的:spring security及其各个模块=》spring cloud security=》spring boot autoconfigure的security部分,比如autoconfigure模块有个spring security的sso,是对spring security在oath2下的封装 spring oauth2 authorizeserver,resourceserver,认证服务器和资源服务器是怎么交互的,token_key,/oauth/token/ refresh/token resourceserver就是业务系统,oauth client 就是边缘业务系统,比如直接面向用户的UI系统,或者UI系统直接调用的API接口这一层; JWT和OAuth2的区别?看到好多人在比较这两个东西,现在终结这个问题:JWT只是OAuth2中的token的一种类型。 jwt client(Resource Server或者Zuul等),使用jwt的业务系统在解码acces_token的时候,需要一个toke_key,这个token-key就是JWT Client应用启动的时候从Auth Server拉取的,接口是token/token_key 单点登录这种功能,好多javaee容器都自带的,像webspherespring Security完全将认证和授权分开了,资源只需要声明自己是需要认证的,需要什么样的权限,只管跟当前用户要access_token。 授权的四种方式任意,只要拿到token就可以去让资源去认证。 边缘服务器需要开启@EnableOAuth2Sso,但是边缘服务器也是一个ResourceServer,边缘服务器如果是zuul的话,就不是一个ResourceServer了,只需要添加@EnableOAuth2Sso注解就可以了; client_credentials模式下spring boot不会帮助spring Security构建ClientCredentialsResourceDetails 对象,需要开发者自己创建 favicon.icon,记得把这个东西过滤掉,奶奶的 在其中一个边缘服务上,您可能需要启用单点登录。 @EnableOAuthSso,只需要在边缘服务器指定没用来引导未登录的用户登录系统。@EnableOAuthSso将允许您将未经认证的用户的自动重定向转向授权服务器,他们将能够登录EnableOAuth2Client,在中间中继的时候用ClientCredentialsTokenEndpointFilter,AS设置了allowFormAuthenticationForClients才会有,详情看这里面的AuthorizationServerSecurityConfigurer#configure(HttpSecurity http)逻辑,这点非常重要,ClientCredentialsTokenEndpointFilter是用来验证clientid和client_secret的,使用clientid和client_secret换取下一步的东西; TokenGranter,AuthorizationCodeTokenGranter,ClientCredentialsTokenGranter,RefreshTokenGranter,ImplicitTokenGranter,ResourceOwnerPasswordTokenGranter TokenServices分为两类,一个是用在AuthenticationServer端,AuthorizationServerTokenServices,ResourceServer端有自己的tokenServices接口, BearerTokenExtractor,从其可以看出,token的获取顺序,Header,parameters(get/post) spring security 保护自己的配置,作为ResourceServer的权限配置和作为AuthorizationServer的配置都是在不同的地方 An OAuth 2 authentication token can contain two authentications: one for the client(OAuth2 Client) and one for the user. Since some OAuth authorization grants don’t require user authentication, the user authentication may be null. jwt是不需要存储access_toen的,jwt的机制就是将所有的信息都存在了token里面,从JwtTokenStore也可以看出来 OAuth2AuthenticationManager是密切与token认证相关的,而不是与获取token密切相关的。这是真正认证的地方,一会重点debug,resourceIds 每一个ResourceServer在配置的时候, ResourceServerConfiguration,需要配置一个resourceID,一个ResourceServer只能配置一个 oauth/token = 先验证的是clientid和clientsecret,接着在验证username和userpassword,都是用的ProvideManager,两次验证是两个不同的请求,oauth2客户端会使用RestTemplate请求服务器的接口 ClientCredentialsTokenEndpointFilter用来验证clientId和clientsecret的: OAuth2ClientAuthenticationProcessingFilter:OAuth2客户端用来从OAuth2认证服务器获取access token,也可以从OAuth2认证服务器加载authentication对象到OAuth2客户端的SecurityContext对象中;里面调用OAuth2AuthenticationManager#authenticate()方法使用DefaultTokenServices ,DefaultTokenServices 使用JwtTokenStore,JwtTokenStore使用JwtAccessTokenConverter来将JWT解密成Auth对象。 来从AuthServer请求授权信息accessToken被解密成如下的JWT对象: {“alg”:”RS256”,”typ”:”JWT”} {“exp”:1503758022,”user_name”:”admin”,”authorities”:[“ROLE_TRUSTED_CLIENT”,”ROLE_ADMIN”,”ROLE_USER”],”jti”:”d56f43d2-6c4a-46cf-85f3-050ee195a2bd”,”client_id”:”confidential”,”scope”:[“read”]} [128 crypto bytes] AbstractSecurityInterceptor#befroeInvaction 是ResourceServer获取认证信息的地方 只要是需要验证token有效性的都需要jwt.key-uri的配置 AffirmativeBased值得debugTIPS @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/favicon.ico"); } @Bean @Override protected UserDetailsService userDetailsService(){ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("user").password("password").roles("USER").authorities("USER").build()); manager.createUser(User.withUsername("admin").password("password").roles("USER", "ADMIN", "TRUSTED_CLIENT").authorities("USER").build()); return manager; } 这两种方式有差别,牵扯到UsernamePasswordAuthenticationFilter和ClientCredentialsTokenEndpointFilter security: oauth2: client: client-id: confidential client-secret: secret access-token-uri: http://localhost:8080/oauth/token user-authorization-uri: http://localhost:8080/oauth/authorize use-current-uri: true resource: jwt: key-uri: http://localhost:8080/oauth/token_key filter-order: 3 client里面的配置最终是用来生成OAuth2ProtectedResourceDetails的bean的,参看OAuth2ProtectedResourceDetailsConfiguration 整体代码结构如下:资料和源码来源

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

java B2B2C springmvc mybatis电子商务平台源码-------zuul网关实现

一、简介 在Springcloud中用zuul来实现网关功能,客户端的请求首先经过负载均衡Ngnix,再到达服务网关(zuul集群),然后再到具体的服务。Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/server1转发到到server1服务。zuul默认和Ribbon结合实现了负载均衡的功能。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三 二、搭建 首先是POM文件 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> 然后在applicaton类加上注解@EnableZuulProxy,开启zuul的功能 @SpringBootApplication @EnableZuulProxy @EnableEurekaClient @RefreshScope public class HfzZuulApplication { public static void main(String[] args) { SpringApplication.run(HfzZuulApplication.class, args); } } yml配置如下 eureka: client: serviceUrl: defaultZone: http://name:pass@IP/eureka/ instance: ip-address: Ip地址 prefer-ip-address: true server: port: 8769 spring: application: name: service-zuul sleuth: sampler: percentage: 1.0 cloud: config: discovery: enabled: true service-id: CONFIG-SERVER label: master profile: dev name: hfz-zuul username: name password: pass 以上是在项目中配置的,为了使项目更加灵活,所以将路由的配置放在github上,这样可以动态读取 zuul: routes: api-a: path: /api-a/** serviceId: service-ribbon api-b: path: /api-b/** serviceId: service-feign 以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务; 三、服务过滤 zuul不仅可以路由,并且还能通过过滤来拦截一些服务,可以用来做安全验证。 public class MyFilter extends ZuulFilter{ private static Logger log = LoggerFactory.getLogger(MyFilter.class); @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if(accessToken == null) { log.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); }catch (Exception e){} return null; } log.info("ok"); return null; } } filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre:路由之前routing:路由之时post: 路由之后error:发送错误调用filterOrder:过滤的顺序shouldFilter:逻辑判断,是否要过滤run:过滤器的具体逻辑控制接下来就可以测试访问了。 技术架构图如下:资料和源码来源

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

(73)java Spring Cloud+Spring boot+mybatis企业快速开发架构之Spring Cloud服务限流详解

高并发系统中有三把利器用来保护系统:缓存、降级和限流。限流的目的是为了保护系统不被大量请求冲垮,通过限制请求的速度来保护系统。在电商的秒杀活动中,限流是必不可少的一个环节。 推荐分布式架构源码地址 限流的方式也有多种,可以在 Nginx 层面限流,也可以在应用当中限流,比如在 API 网关中。 限​流算法 常见的限流算法有:令牌桶、漏桶。计数器也可以进行限流实现。 1)令牌桶 令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。可以控制流量也可以控制并发量,假如我们想要控制 API 网关的并发量最高为 1000,可以创建一个令牌桶,以固定的速度往桶里添加令牌,超出了 1000 则不添加。 当一个请求到达之后就从桶中获取一个令牌,如果能获取到令牌就可以继续往下请求,获取不到就说明令牌不够,并发量达到了最高,请求就被拦截。 2)漏桶 漏桶是一个固定容量的桶,按照固定的速率流出,可以以任意的速率流入到漏桶中,超出了漏桶的容量就被丢弃,总容量是不变的。但是输出的速率是固定的,无论你上面的水流入的多快,下面的出口只有这么大,就像水坝开闸放水一样,如图 1 所示。 单节点限流 单节点限流指的是只对这个节点的并发量进行控制,相对于集群限流来说单节点限流比较简单,稳定性也好,集群限流需要依赖第三方中间件来存储数据,单节点限流数据存储在本地内存中即可,风险性更低。 从应用的角度来说单节点的限流就够用了,如果我们的应用有 3 个节点,总共能扛住 9000 的并发,那么单个节点最大能扛住的量就是 3000,只要单个节点扛住了就没什么问题了。 我们可以用上面讲的令牌桶算法或者漏桶算法来进行单节点的限流操作,算法的实现可以使用 Google Guava 中提供的算法实现类。实际使用中令牌桶算法更适合一些,当然这个得参考业务需求,之所以选择令牌桶算法是因为它可以处理突发的流量,漏桶算法就不行,因为漏桶的速率是固定的。 首先需要依赖 Guava,其实也可以不用,在 Spring Cloud 中好多组件都依赖了 Guava,如果你的项目是 Spring Cloud 技术栈的话可以不用自己配置,间接就已经依赖了,代码如下所示。 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency> 创建一个限流的过滤器,order 返回 0,执行优先级第一,代码如下所示。 public class LimitFilter extends ZuulFilter { public static volatile RateLimiter rateLimiter = RateLimiter.create(100.0); public LimitFilter() { super(); } @Override public boolean shouldFilter() { return true; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public Object run() { // 总体限流rateLimiter.acquire(); return null; } } 注册限流过滤器,代码如下所示。 @Bean public LimitFilter limitFilter() { return new LimitFilter(); } 上面的方案有一个致命的问题就是速率值是写死的,往往我们需要根据服务器的配置以及当时的并发量来设置一个合理的值,那么就需要速率这个值能够实时修改,并且生效,这时配置中心又派上用场了。 添加 Apollo 的配置,代码如下所示。 @Data @Configuration public class BasicConf { @Value("${limitRate:10}") private double limitRate; } 有一个问题是当这个值修改的时候需要重新初始化 RateLimiter,在配置类中实现修改回调的方法代码如下所示 @Data @Configuration public class BasicConf { @Value("${limitRate:10}") private double limitRate; @ApolloConfig private Config config; @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("limitRate")) { // 更 新 RateLimiter LimitFilter.rateLimiter = RateLimiter.create(config.getDoubleProperty("limitRate", 10.0)); } } } 我们可以用 ab 来测试一下接口,代码如下所示。 ab -n 1000 -c 30 http://192.168.10.170:2103/fsh-house/house/1 Benchmarking 192.168.10.170 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: 192.168.10.170 Server Port: 2103 Document Path: /fsh-house/house/1 Document Length: 796 bytes Concurrency Level: 30 Time taken for tests: 98.989 seconds Complete requests: 1000 Failed requests: 6 (Connect: 0, Receive: 0, Length: 6, Exceptions: 0) Total transferred: 1001434 bytes HTML transferred: 833434 bytes Requests per second: 10.10 [#/sec] (mean) Time per request: 2969.679 [ms] (mean) Time per request: 98.989 [ms] (mean, across all concurrent requests) Transfer rate: 9.88 [Kbytes/sec] received -n 1000 表示总共请求 1000 次 -c 30 表示并发数量 我们可以看到执行完这 1000 次请求总共花费了 98 秒,Time taken for tests 就是请求所花费的总时间,这是在限流参数为 10 的情况下,我们还可以把参数调到 100 然后测试一下,代码如下所示。 ab -n 1000 -c 30 http://192.168.10.170:2103/fsh-house/house/1 Benchmarking 192.168.10.170 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: 192.168.10.170 Server Port: 2103 Document Path: /fsh-house/house/1 Document Length: 7035 bytes Concurrency Level: 30 Time taken for tests: 9.061 seconds Complete requests: 1000 Failed requests: 30 (Connect: 0, Receive: 0, Length: 30, Exceptions: 0) Total transferred: 7015830 bytes HTML transferred: 6847830 bytes Requests per second: 110.37 [#/sec] (mean) Time per request: 271.821 [ms] (mean) Time per request: 9.061 [ms] (mean, across all concurrent requests) Transfer rate: 756.17 [Kbytes/sec] received 限流的参数调大后,请求 9 秒就完成了,这就证明我们的限流操作起作用了。 集群限流 集群限流可以借助Redis 来实现,至于实现的方式也有很多种,下面我们介绍一种比较简单的限流方式。 我们可以按秒来对并发量进行限制,比如整个集群中每秒只能访问 1000 次。我们可以利用计数器来判断,Redis 的 key 为当前秒的时间戳,value 就是访问次数的累加,当次数超出了我们限制的范围内,直接拒绝即可。需要注意的是集群中服务器的时间必须一致才能没有误差,下面我们来看代码。 首先在我们的 API 网关中集成 Redis 的操作,我们引入 Spring Data Redis 来操作 Redis,代码如下所示。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 属性配置文件中配置 Redis 的连接信息 spring.redis.host=192.168.10.47 spring.redis.port=6379 集成后就直接可以使用 RedisTemplate 来操作 Redis 了,这里配置了一个 RedisTemplate,key 为 String 类型,value 为 Long 类型,用来计数,代码如下所示。 @Configuration public class RedisConfig { @Bean(name = "longRedisTemplate") public RedisTemplate<String, Long> redisTemplate(RedisConnectionFactory jedisConnectionFactory) { RedisTemplate<String, Long> template = new RedisTemplate<String, Long>(); template.setConnectionFactory(jedisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericToStringSerializer<Long>(Long.class)); template.setValueSerializer(new GenericToStringSerializer<Long>(Long.class)); return template; } } 在之前的限流类的配置中增加集群限流的速率配置,代码如下所示。 @Data @Configuration public class BasicConf { @Value("${clusterLimitRate:10}") private double clusterLimitRate; } 接下来我们改造之前单体限流用的过滤器 LimitFilter,采用 Redis 来进行限流操作,代码如下所示。 public class LimitFilter extends ZuulFilter { private Logger log = LoggerFactory.getLogger(LimitFilter.class); public static volatile RateLimiter rateLimiter = RateLimiter.create(100); @Autowired @Qualifier("longRedisTemplate") private RedisTemplate<String, Long> redisTemplate; @Autowired private BasicConf basicConf; public LimitFilter() { super(); } @Override public boolean shouldFilter() { return true; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); Long currentSecond = System.currentTimeMillis() / 1000; String key = "fsh-api-rate-limit-" + currentSecond; try { if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().set(key, 0L, 100, TimeUnit.SECONDS); } int rate = basicConf.getClusterLimitRate(); // 当集群中当前秒的并发量达到了设定的值, 不进行处理 // 注意集群中的网关与所在服务器时间必须同步 if (redisTemplate.opsForValue().increment(key, 1) > rate) { ctx.setSendZuulResponse(false); ctx.set("isSuccess", false); ResponseData data = ResponseData.fail("当前负载太高,请稍后重试", ResponseCode.LIMIT_ERROR_CODE.getCode()); ctx.setResponseBody(JsonUtils.toJson(data)); ctx.getResponse().setContentType("application/json;charset=utf-8"); return null; } } catch (Exception e) { log.error("集群限流异常", e); // Redis 挂掉等异常处理,可以继续单节点限流 // 单节点限流 rateLimiter.acquire(); } return null; } } 我们来看看 run 方法里面的逻辑,首先我们是获取了当前时间的时间戳然后转换成秒,定义了一个 Redis 的 key。判断这个 key 是否存在,不存在则插入一个,初始值为 0,然后通过 increment 来为这个 key 累加计数,并获取累加之后的值,increment 是原子性的,不会有并发问题,如果当前秒的数量超出了我们设定的值那就说明当前的并发量已经达到了极限值,然后直接拒绝请求。 这里还需要进行异常处理,前文推荐用单节点限流的方式来进行就是因为集群性质的限流需要依赖第三方中间件,如果中间件挂了,那么就会影响现有的业务,这里需要处理的是如果操作 Redis 出异常了怎么办? 首先是进行集群的限流,如果 Redis 出现挂了之类的问题,捕获到异常之后立刻启用单节点限流,进行双重保护。当然必须有完整的监控系统,当 Redis 出现问题之后必须马上处理。Redis 在生产环境中必须用集群,当然集群也有可能会出问题,所以单节点限流是一种比较好的方案。 具体服务限流 前面我们学习了如何进行单节点的限流和集群的限流。虽然抗住了整体的并发量,但是会有一个弊端,如果这些并发量都是针对一个服务的,那么这个服务还是会扛不住的,针对具体的服务做具体的限制才是最好的选择。 基于前面的基础,要针对具体的服务做限制是比较简单的事情,针对单节点限流我们的做法如下。 之前是用一个 RateLimiter 来防止整体的并发量,针对具体服务的前期是需要知道当前的请求会被转发到哪个服务里去,知道了这个我们只需要为每个服务创建一个 RateLimiter,不同的服务用不同的 RateLimiter 就可以实现具体服务的限制了。 集群的限流是通过时间的秒作为 key 来计数实现的,如果是针对具体的服务,只需要把服务名称加到 key 中就可以了,即一个服务就是一个 key,限流的操作自然而然是针对具体的服务。 可以用 Zuul 提供的 Route Filter 来做,在 Route Filter 中可以直接获取当前请求是要转发到哪个服务,代码如下所示。 RequestContext ctx = RequestContext.getCurrentContext(); Object serviceId = ctx.get("serviceId"); serviceId 就是 Eureka 中注册的服务名称。 具体接口限流 即使我们做了整体的集群限流,如果某个服务的具体限流持续并发量很大且是同一个接口,那么还会影响到其他接口的使用,华章所有的资源都被这一个接口占用了,其他的接口请求过来只能等待或者抛弃,所以我们需要将限流做得更细,可以针对具体的 API 接口进行并发控制。 具体的接口控制并发量我们将这个控制放到具体的服务中,之所以不放到 API 网关去做控制是因为 API 的量太大了,如果统一到 API 网关来控制那么需要配置很多 API 的并发量信息,如果放到具体的服务上,我们可以通过注解的方式在接口的方法上做文章,添加一个注解就可以实现并发控制,还可以结合我们的 Apollo 来做动态修改,当然也可以在 API 网关做,笔者推荐在具体的服务上做。 首先我们定义一个注解,用来标识某个接口需要进行并发控制,这个注解是通用的,可以放在公共的库中。在注解中定义一个 confKey,这个 key 对应的是 Apollo 中的配置 key,也就是说我们这个并发的数字不写死,而是通过 Apollo 来做关联,到时候可以动态修改,实时生效,代码如下所示。 /** * 对 API 进行访问速度限制 * 限制的速度值在Apollo配置中通过 key 关联 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiRateLimit { /** * Apollo配置中的 key * * @return */ String confKey(); } 接下来我们定义一个启动监听器,这个也是通用的,可以放在公共库中。这个启动监听器的主要作用就是扫描所有的 API 接口类,也就是我们的 Controller。 获取 Controller 中所有加了 ApiRateLimit 注解的信息,然后进行初始化操作,控制并发我们这里用 JDK 自带的 Semaphore 来实现,当然你也可以用之前讲的 RateLimiter,代码如下所示。 @Component public class InitApiLimitRateListener implements ApplicationContextAware { public void setApplicationContext(ApplicationContext ctx) throws BeansException { Environment environment = ctx.getEnvironment(); String defaultLimit = environment.getProperty("open.api.defaultLimit"); Object rate = defaultLimit == null ? 100 : defaultLimit; ApiLimitAspect.semaphoreMap.put("open.api.defaultLimit", new Semaphore(Integer.parseInt(rate.toString()))); Map<String, Object> beanMap = ctx.getBeansWithAnnotation(RestController.class); Set<String> keys = beanMap.keySet(); for (String key : keys) { Class<?> clz = beanMap.get(key).getClass(); String fullName = beanMap.get(key).getClass().getName(); if (fullName.contains("EnhancerBySpringCGLIB") || fullName.contains("$$")) { fullName = fullName.substring(0, fullName.indexOf("$$")); try { clz = Class.forName(fullName); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } Method[] methods = clz.getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(ApiRateLimit.class)) { String confKey = method.getAnnotation(ApiRateLimit.class).confKey(); if (environment.getProperty(confKey) != null) { int limit = Integer.parseInt(environment.getProperty(confKey)); ApiLimitAspect.semaphoreMap.put(confKey, new Semaphore(limit)); } } } } } } 上面的代码就是初始化的整个逻辑,在最开始的时候就是获取 open.api.defaultLimit 的值,那么这个值会配置在 Apollo 中,如果没有则给予一个默认值。open.api.defaultLimit 是考虑到并不是所有的接口都需要配置具体的限制并发的数量,所以给了一个默认的限制,也就是说没有加 ApiRateLimit 注解的接口就用这个默认的并发限制。 拿到所有的 Controller 类的信息,通过判断类上是否有 RestController 注解来确定这就是一个接口,然后获取类中所有的方法,获取方法上有 ApiRateLimit 注解的 key,通过 key获取配置的值,然后 new 一个 Semaphore 存入控制并发的切面的 map 中,切面下面会定义。 通过切面来对访问的接口进行并发控制,当然也可以用拦截器、过滤器之类的,切面也是共用的,可以放公共库中,代码如下所示。 /** * 具体 API 并发控制 */ @Aspect @Order(value = Ordered.HIGHEST_PRECEDENCE) public class ApiLimitAspect { public static Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<String, Semaphore>(); @Around("execution(*com.biancheng.*.*.controller.*.*(..))") public Object around(ProceedingJoinPoint joinPoint) { Object result = null; Semaphore semap = null; Class<?> clazz = joinPoint.getTarget().getClass(); String key = getRateLimitKey(clazz, joinPoint.getSignature().getName()); if (key != null) { semap = semaphoreMap.get(key); } else { semap = semaphoreMap.get("open.api.defaultLimit"); } try { semap.acquire(); result = joinPoint.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } finally { semap.release(); } return result; } private String getRateLimitKey(Class<?> clazz, String methodName) { for (Method method : clazz.getDeclaredMethods()) { if (method.getName().equals(methodName)) { if (method.isAnnotationPresent(ApiRateLimit.class)) { String key = method.getAnnotation(ApiRateLimit.class).confKey(); return key; } } } return null; } } 整个切面中的代码量不多,但是作用非常大,所有接口的请求都将会经过它,这是一个环绕通知。 第一行是一个 ConcurrentHashMap,用来存储我们之前在监听器里面初始化好的 Semaphore 对象,需要重点关注的是 around 中的逻辑。 首先获取当前访问的目标对象以及方法名称,通过 getRateLimitKey 获取当前访问的方法是否有限制并发的 key,通过 key 从 semaphoreMap 中获取对应的 Semaphore 对象做并发限制。 配置进行并发控制的切面,代码如下所示。 @Configuration public class BeanConfig { /** * 具体的 API 并发控制 * * @return */ @Bean public ApiLimitAspect apiLimitAspect() { return new ApiLimitAspect(); } } 到这里整个限制的流程就结束了。启动服务,可以用并发测试工具 Apache ab 来测试效果。将并发数量配置为 1,测试请求 1000 次看需要多长时间,然后调大并发数量,再次请求,虽然比较耗时,但我们可以发现并发配置数量越小的耗时时间越长,这就证明并发控制生效了。 目前没有加我们自定义的注解,所有的接口都是用默认的并发控制数量,如果我们想对某个接口单独做并发控制,只需要在方法上加上 ApiRateLimit 注解即可,具体代码如下所示。 /** * 获取房产信息 * * @param houseId 房产编号 * @return */ @ApiRateLimit(confKey = "open.api.hosueInfo") @GetMapping("/{houseId}") public ResponseData hosueInfo(@PathVariable("houseId") Long houseId, HttpServletRequest request) { String uid = request.getHeader("uid"); System.err.println("===" + uid); return ResponseData.ok(houseService.getHouseInfo(houseId)); } ApiRateLimit 中配置的 confKey 要和 Apollo 配置中的 key 对应才行。目前限流的信号量对象是在启动时进行初始化的,如果需要实现在 Apollo 中动态新增或者修改配置也能生效的话,需要对配置的修改进行监听,然后动态创建信号量对象添加到 semaphoreMap 中。

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

java B2B2C springmvc mybatis电子商务平台源码-消息队列之RocketMQ

RocketMQ出自阿里公司的开源产品,用 Java 语言实现,在设计时参考了 Kafka,并做出了自己的一些改进,消息可靠性上比 Kafka 更好。RocketMQ在阿里集团被广泛应用在订单,交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。 需要JAVA Spring Cloud大型企业分布式微服务云构建的B2B2C电子商务平台源码 RocketMQ的主要特性有: 是一个队列模型的消息中间件,具有高性能、高可靠、高实时、分布式特点;Producer、Consumer、队列都可以分布式;Producer向一些队列轮流发送消息,队列集合称为Topic,Consumer如果做广播消费,则一个consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个topic对应的队列集合;能够保证严格的消息顺序;提供丰富的消息拉取模式;高效的订阅者水平扩展能力;实时的消息订阅机制;亿级消息堆积能力;较少的依赖; RocketMQ的优点有:单机支持 1 万以上持久化队列;RocketMQ 的所有消息都是持久化的,先写入系统 PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据;模型简单,接口易用(JMS 的接口很多场合并不太实用);性能非常好,可以大量堆积消息在broker中;支持多种消费,包括集群消费、广播消费等。各个环节分布式扩展设计,主从HA; RocketMQ的缺点有: 支持的客户端语言不多,目前是java及c++,其中c++不成熟;RocketMQ社区关注度及成熟度也不及前两者;没有web管理界面,提供了一个CLI(命令行界面)管理工具带来查询、管理和诊断各种问题;没有在消息队列的核心部分实现JMS等接口; 大型企业分布式互联网电子商务平台,推出PC+微信+APP+云服务的云商平台系统,其中包括B2B、B2C、C2C、O2O、新零售、直播电商等子平台,框架源码下载地址

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

java B2B2C springmvc mybatis多租户电子商城系统--Zuul的高可用

Zuul的高可用非常关键,因为外部请求到后端服务的流量都会经过Zuul。故而在生产环境下一般都需要部署高可用的Zuul以避免单点故障。愿意了解源码的朋友直接求求交流分享技术:二一四七七七五六三三一Zuul客户端也注册到EurekaServer上这种情况下,Zuul的高可用非常简单,只须将多个Zuul节点注册到EurekaServer上,就可实现Zuul的高可用。此时Zuul的高可用与其他微服务的高可用没什么区别。如下图,当Zuul客户端也注册到EurekaServer上时,只须部署多个Zuul节点即可实现其高可用。Zuul客户端会自动从EurekaServer中查询ZuulServer的列表,并使用Ribbon负责均衡地请求Zuul集群。 二Zuul客户端未注册到EurekaServer上现实中,这种场景更多,例如,Zuul客户端是一个手机APP——不可能让所有的手机终端都注册到EurekaServer上。这种情况下,可借助一个额外的负载均衡器来实现Zuul的高可用,例如Nginx、HAProxy、F5等。如下图,Zuul客户端请求发送到负载均衡器,负载均衡器将请求转发到其代理的其中一个Zuul节点。这样,就可以实现Zuul的高可用。 整体代码结构如下:资料和源码来源

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

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

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

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

用户登录
用户注册