使用spring cloud 和 zuul 进行服务路由_服务路由可以用什么做-程序员宅基地

技术标签: spring  

spring cloud 和 netfix zuul

  1. 网关中可以实现的横切关注点:
    1. 静态路由;
    2. 动态路由;
    3. 验证和授权:
    4. 度量数据收集和日志记录;
  2. 构建zuul:
    1. 导入依赖包;
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-zuul</artifactId>
      </dependency>
    2. 添加注解,有两个注解可用:

      1. @EnableZuulProxy:会自动使用eureka来通过服务ID查询服务,其核心是反向代理,捕获客户端请求然后代表客户端访问远程资源,客户端不知道最终调用的服务;

      2. @EnableZuulServer:不会加载任何zuul反向代理过滤器,也不会使用eureka进行服务发现,当我们构建自己的路由服务而不使用任何zuul预置的功能时使用该注解,如与别的服务发现引擎(如consul)进行集成时使用该注解;

    3. 添加配置与eureka进行通信;

      server:
        port: 5555
      
      eureka:
        instance:
          preferIpAddress: true
        client:
          registerWithEureka: true
          fetchRegistry: true
          serviceUrl:
              defaultZone: http://localhost:8761/eureka/
      
      
    4. 为所有服务调用添加前缀;

      zuul:
        prefix:  /api
  3. 在zuul中配置路由,有几种zuul路由机制,可以通过http://ip:port/routes查看所有的路径;

    1. 通服务发现自动映射路由:不需要通过配置,zuul可以根据其服务ID自动路由请求,如果没有指定任何路由,zuul将根据eureka中的服务ID进行路由;

      1. http://ip:port/服务ID/具体的url请求:端口后面第一个被用于查找具体服务,后面的则是具体的路径;

      2. 关闭个别服务或全部服务的自动路径映射;

        
        zuul:
          #屏蔽全部服务的自动路由,则设置'*'
          ignored-services: '服务名1','服务名2'  
    2. 使用服务发现手动映射路由;

      zuul:
        routes:
          # 服务名: /路径/** 
          organizationservice: /organization/**
      

      自动映射和手动映射的区别:在使用自动映射路由时,zuul只会基于eureka服务ID来公开服务,如果服务实例没有运行,zuul将不会公开该服务的路由,而在没有使用eureka注册服务实例的情况下,手动映射路由仍然会将请求映射到服务ID。

    3. 使用静态URL手动映射路由,可以用了路由不受eureka管理的服务;

      1. 不使用负载均衡:

        zuul:
          routes:
            #服务名
            organizationservice:   #zuul用于在内部识别服务的关键字
              path: /organization/**  #静态路由
              url: http://ip:port   #服务的静态实例,将被直接调用,而不是zuul通过eureka调用

        此时只能有一条路径,而不能使用负载均衡路由到各个实例。

      2. 开启rabbon负载均衡,此时需要禁用掉ribbon和eureka的集成,并列出ribbon进行负载均衡的各个实例;

        #在ribbon中禁用掉eureka支持
        ribbon:
          eureka:
            enabled: false
        zuul:
          routes:
            organizationservice:   
              path: /organization/**  #静态路由
              serviceId: organizationservice  #定义一个服务ID,该服务ID用于在ribbon中查找服务
        organizationservice:  #与serviceId一致
          ribbon:
            listOfServices: http://ip:8081,http://ip:8082  #指定请求会路由到的服务列表

        注意:(1)静态路由并在ribbon中禁用eureka会造成一个问题,那就是禁用了对通过zuul网关运行的所有服务的ribbon支持,意味着eureka会承受跟多的负载,因为zuul无法使用ribbon来缓存服务的查找。

        (2)ribbon不会在每次发出调用时都去调用eureka,它会在本地缓存服务实例的位置,然后定期检查eureka是否有变化,缺少了ribbon,zuul每次需要解析服务的位置时都需要调用eureka来进行定位。
    4. zuul和服务超时,zuul使用netfix的hystrix和ribbon库来防止长时间运行的服务调用影响网关的性能;

      1. hystrix默认的超时间时间为1s,通过以下配置进行更改:

        hystrix:
          command:
            default:
              execution:
                isolation:
                  thread:
                    timeoutInMilliseconds: 2500
            服务ID:   #通过替代defalut来设置某个服务单独的hystrix超时时间
              execution:
                isolation:
                  thread:
                    timeoutInMilliseconds: 3000
      2. ribbon的默认超时时间为5s,可以通过以下配置进行修改:

        #修改某个服务的ribbon超时时间
        服务ID.ribbon.ReadTimeout: 7000  
  4. zuul的真正威力:过滤器;

    1. servlet过滤器和Spring Aspect被本地化为特定服务,而zuul过滤器允许为通过zuul路由的所有服务实现横切关注点;

    2. zuul支持三种类型的过滤器:

      1. 前置过滤器:将请求发送到实际目的地前调用,可以对http请求进行检查和修改,但是前置过滤器不能进行重定向到别的端点或服务;

      2. 后置过滤器:在调用目标服务后来自目标服务的响应会经过该过滤器,可用来检查和修改来自被调用服务的响应,通常用来记录从目标服务返回的响应、处理错误或审核对敏感信息的响应;

      3. 路由过滤器:可以将请求路由到其他服务,路由过滤器不能执行http重定向,而是会终止传入的http请求,然后创建新请求并代表原始调用者调用路由;

    3. 构建前置过滤器,通过扩展ZuulFilter实现一个从zuul流出的每个请求都有关联ID; 

      package com.thoughtmechanix.zuulsvr.filters;
      
      import com.netflix.zuul.ZuulFilter;
      import com.netflix.zuul.context.RequestContext;
      import org.springframework.stereotype.Component;
      
      /**
       * 所有zuul过滤器必须扩展ZuulFilter类
       */
      @Component
      public class TrackingFilter extends ZuulFilter {
      
          /**
           * 定义过滤器类型,前置、后置、路由
           */
          @Override
          public String filterType() {
              return "pre";
          }
      
          /**
           * 定义过滤器执行顺序
           */
          @Override
          public int filterOrder() {
              return 1;
          }
      
          /**
           * 是否要执行过滤器
           */
          public boolean shouldFilter() {
              return true;
          }
      
          /**
           * 每次服务通过过滤器时执行run方法
           */
          public Object run() {
              if (!isCorrelationIdPresent()) {
                  setCorrelationId(generateCorrelationId());
              }
              RequestContext ctx = RequestContext.getCurrentContext();
              return null;
          }
      
          private String getCorrelationId() {
              RequestContext ctx = RequestContext.getCurrentContext();
      
              if (ctx.getRequest().getHeader("tmx-correlation-id") != null) {
                  return ctx.getRequest().getHeader("tmx-correlation-id");
              } else {
                  return ctx.getZuulRequestHeaders().get("tmx-correlation-id");
              }
          }
      
          private void setCorrelationId(String correlationId) {
              RequestContext ctx = RequestContext.getCurrentContext();
              ctx.addZuulRequestHeader("tmx-correlation-id", correlationId);
          }
      
          private boolean isCorrelationIdPresent() {
              return getCorrelationId() != null;
          }
      
          private String generateCorrelationId() {
              return java.util.UUID.randomUUID().toString();
          }
      }

       注意:(1)在一般的spring mvc和spring boot服务中,RequestContext是org.springframework.web.servletsupport.RequestContext类型的,而zuul提供了一个专门的RequestContext(com.netflix.zuul.context.RequestContext),它具有额外几个方法拉访问zuul特定的值。

      (2)zuul不允许直接添加或修改传入请求中的HTTP头部,要向http头部添加请求头,应使用RequestContext 的addZuulRequestHeader()方法,该方法将维护一个单独的HTTP头部映射,当zuul服务器调用目标服务是,包含在ZuulRequestHeader映射中的数据将被合并。
    4. 当zuul调用目标服务时,在目标服务调用中使用前置过滤器添加的关联ID;

      1. 要求:被zuul调用的服务可以很容易访问到关联ID;被调用的服务在调用其他服务时也需要将关联ID传递下去。

      2. 定义servlet过滤器,拦截进入服务的请求,获取头部信息;

        package com.thoughtmechanix.licenses.utils;
        
        import org.springframework.stereotype.Component;
        
        import javax.servlet.Filter;
        import javax.servlet.FilterChain;
        import javax.servlet.FilterConfig;
        import javax.servlet.ServletException;
        import javax.servlet.ServletRequest;
        import javax.servlet.ServletResponse;
        import javax.servlet.http.HttpServletRequest;
        import java.io.IOException;
        
        @Component
        public class UserContextFilter implements Filter {
        
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                    throws IOException, ServletException {
                HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        
                //获取http头部信息,并将其映射到UserContext类(pojo),报错存头部获取的信息,方便之后使用
                UserContextHolder.getContext().setCorrelationId(httpServletRequest.getHeader("tmx-correlation-id"));
                UserContextHolder.getContext().setUserId(httpServletRequest.getHeader("tmx-user-id"));
                UserContextHolder.getContext().setAuthToken(httpServletRequest.getHeader("tmx-auth-token"));
                UserContextHolder.getContext().setOrgId(httpServletRequest.getHeader("tmx-org-id"));
        
                filterChain.doFilter(httpServletRequest, servletResponse);
            }
        
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }
        
            @Override
            public void destroy() {
            }
        }

        UserContextHolder用户保存微服务处理的单个服务客户端请求的HTTP头部信息,通过保存在ThreadLocal中,确保用户请求的线程获取数据;

        package com.thoughtmechanix.licenses.utils;
        
        
        import org.springframework.util.Assert;
        
        public class UserContextHolder {
            private static final ThreadLocal<UserContext> userContext = new ThreadLocal<UserContext>();
        
            public static final UserContext getContext(){
                UserContext context = userContext.get();
        
                if (context == null) {
                    context = createEmptyContext();
                    userContext.set(context);
                }
                return userContext.get();
            }
        
            public static final void setContext(UserContext context) {
                Assert.notNull(context, "Only non-null UserContext instances are permitted");
                userContext.set(context);
            }
        
            public static final UserContext createEmptyContext(){
                return new UserContext();
            }
        }
        

        UserContext简单java对象;

        package com.thoughtmechanix.licenses.utils;
        
        import lombok.Data;
        import org.springframework.stereotype.Component;
        
        @Component
        @Data
        public class UserContext {
        
            private String correlationId= new String();
            private String authToken= new String();
            private String userId = new String();
            private String orgId = new String();
        
        }
      3. 自定义TestTemplate和ClientHttpRequestIntercept,确保关联ID被传播;

        1. 定义拦截器,添加HTTP头部;

          package com.thoughtmechanix.licenses.utils;
          
          import org.springframework.http.HttpHeaders;
          import org.springframework.http.HttpRequest;
          import org.springframework.http.client.ClientHttpRequestExecution;
          import org.springframework.http.client.ClientHttpRequestInterceptor;
          import org.springframework.http.client.ClientHttpResponse;
          
          import java.io.IOException;
          
          public class UserContextInterceptor implements ClientHttpRequestInterceptor {
              /**
               * 该方法在RestTemplate发生实际的HTTP服务调用之前被调用
               */
              @Override
              public ClientHttpResponse intercept(
                      HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
                      throws IOException {
          
                  HttpHeaders headers = request.getHeaders();
                  headers.add("tmx-correlation-id", UserContextHolder.getContext().getCorrelationId());
                  headers.add("tmx-auth-token", UserContextHolder.getContext().getAuthToken());
          
                  return execution.execute(request, body);
              }
          }
        2. 定义RestTemplate  bean,将上述拦截器添加进去;

          package com.thoughtmechanix.licenses;
          
          import com.thoughtmechanix.licenses.utils.UserContextInterceptor;
          import org.springframework.boot.SpringApplication;
          import org.springframework.boot.autoconfigure.SpringBootApplication;
          import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
          import org.springframework.cloud.client.loadbalancer.LoadBalanced;
          import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
          import org.springframework.context.annotation.Bean;
          import org.springframework.web.client.RestTemplate;
          
          import java.util.Collections;
          import java.util.List;
          
          @SpringBootApplication
          @EnableEurekaClient
          @EnableCircuitBreaker
          public class Application {
          
              @LoadBalanced //该注解表明这个restTemplate要使用ribbon
              @Bean
              public RestTemplate getRestTemplate(){
                  RestTemplate template = new RestTemplate();
                  List interceptors = template.getInterceptors();
                  if (interceptors==null){
                      template.setInterceptors(Collections.singletonList(new UserContextInterceptor()));
                  }
                  else{
                      interceptors.add(new UserContextInterceptor());
                      template.setInterceptors(interceptors);
                  }
          
                  return template;
              }
          
              public static void main(String[] args) {
                  SpringApplication.run(Application.class, args);
              }
          }
          
    5. 定义后置过滤器,获取目标请求响应中的关联ID,并将其注入HTTP头部返回给服务调用者(zuul会结束目标http响应,并创建新的http响应返回给调用者,因此需要手动将目标响应中的头信息返回给服务调用者);

      package com.thoughtmechanix.zuulsvr.filters;
      
      
      import com.netflix.zuul.ZuulFilter;
      import com.netflix.zuul.context.RequestContext;
      import org.springframework.stereotype.Component;
      
      @Component
      public class ResponseFilter extends ZuulFilter {
      
          @Override
          public String filterType() {
              return "post";
          }
      
          @Override
          public int filterOrder() {
              return 1;
          }
      
          @Override
          public boolean shouldFilter() {
              return true;
          }
      
          @Override
          public Object run() {
              RequestContext ctx = RequestContext.getCurrentContext();
              //获取原始HTTP请求中传入的关联ID,并将它注入到响应中
              ctx.getResponse().addHeader("tmx-correlation-id", getCorrelationId());
              return null;
          }
      
          public String getCorrelationId() {
              RequestContext ctx = RequestContext.getCurrentContext();
              if (ctx.getRequest().getHeader("tmx-correlation-id") != null) {
                  return ctx.getRequest().getHeader("tmx-correlation-id");
              } else {
                  return ctx.getZuulRequestHeaders().get("tmx-correlation-id");
              }
          }
      }
      
    6. 构建动态路由过滤器,基本用不到,有点复杂,不看也罢,此处只做记录;

      package com.thoughtmechanix.zuulsvr.filters;
      
      
      import com.netflix.zuul.ZuulFilter;
      import com.netflix.zuul.context.RequestContext;
      import com.thoughtmechanix.zuulsvr.model.AbTestingRoute;
      import org.apache.http.Header;
      import org.apache.http.HttpHost;
      import org.apache.http.HttpRequest;
      import org.apache.http.HttpResponse;
      import org.apache.http.client.HttpClient;
      import org.apache.http.client.methods.HttpPatch;
      import org.apache.http.client.methods.HttpPost;
      import org.apache.http.client.methods.HttpPut;
      import org.apache.http.entity.ContentType;
      import org.apache.http.entity.InputStreamEntity;
      import org.apache.http.impl.client.CloseableHttpClient;
      import org.apache.http.impl.client.HttpClients;
      import org.apache.http.message.BasicHeader;
      import org.apache.http.message.BasicHttpRequest;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
      import org.springframework.http.HttpMethod;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.stereotype.Component;
      import org.springframework.util.LinkedMultiValueMap;
      import org.springframework.util.MultiValueMap;
      import org.springframework.web.client.HttpClientErrorException;
      import org.springframework.web.client.RestTemplate;
      
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.InputStream;
      import java.net.URL;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Map;
      import java.util.Random;
      
      @Component
      public class SpecialRoutesFilter extends ZuulFilter {
      
          @Autowired
          RestTemplate restTemplate;
      
          //spring cloud 提供的类,带有用于代理服务请求的辅助方法
          private ProxyRequestHelper helper = new ProxyRequestHelper();
      
          @Override
          public Object run() {
              RequestContext ctx = RequestContext.getCurrentContext();
      
              boolean flag = new Random().nextBoolean();
      
              //根据逻辑条件(此处可根据具体情况进行替代)判断是否进行动态路由
              if (flag) {
                  String route = "路由到服务的完整路径";
                  forwardToSpecialRoute(route);
              }
              return null;
          }
      
          private void forwardToSpecialRoute(String route) {
              RequestContext context = RequestContext.getCurrentContext();
              HttpServletRequest request = context.getRequest();
      
              //创建将发送到服务的所有HTTP请求头部的副本
              MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);
              //创建所有HTTP请求参数的副本
              MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
              String verb =  request.getMethod().toUpperCase();
              //创建将被转发到替代服务的HTTP主体的副本
              InputStream requestEntity =  null;
              try {
                  requestEntity = request.getInputStream();
              } catch (IOException ex) {
                  // no requestBody is ok.
              }
              if (request.getContentLength() < 0) {
                  context.setChunkedRequestBody();
              }
      
              this.helper.addIgnoredHeaders();
              CloseableHttpClient httpClient = null;
              HttpResponse response = null;
      
              try {
                  httpClient = HttpClients.createDefault();
                  //使用forward辅助方法调用替代服务
                  response = forward(httpClient, verb, route, request, headers,
                          params, requestEntity);
                  //通过setResponse辅助方法将服务调用的结果保存会回zuul服务器
                  setResponse(response);
              } catch (Exception ex) {
                  ex.printStackTrace();
      
              } finally {
                  try {
                      assert httpClient != null;
                      httpClient.close();
                  } catch (IOException ignored) {
                  }
              }
          }
      
          private HttpResponse forward(HttpClient httpclient, String verb, String uri,
                                       HttpServletRequest request, MultiValueMap<String, String> headers,
                                       MultiValueMap<String, String> params, InputStream requestEntity)
                  throws Exception {
              URL host = new URL(uri);
              HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol());
      
              HttpRequest httpRequest;
              int contentLength = request.getContentLength();
              InputStreamEntity entity = new InputStreamEntity(requestEntity, contentLength,
                      request.getContentType() != null
                              ? ContentType.create(request.getContentType()) : null);
              switch (verb.toUpperCase()) {
                  case "POST":
                      HttpPost httpPost = new HttpPost(uri);
                      httpRequest = httpPost;
                      httpPost.setEntity(entity);
                      break;
                  case "PUT":
                      HttpPut httpPut = new HttpPut(uri);
                      httpRequest = httpPut;
                      httpPut.setEntity(entity);
                      break;
                  case "PATCH":
                      HttpPatch httpPatch = new HttpPatch(uri);
                      httpRequest = httpPatch;
                      httpPatch.setEntity(entity);
                      break;
                  default:
                      httpRequest = new BasicHttpRequest(verb, uri);
      
              }
              try {
                  httpRequest.setHeaders(convertHeaders(headers));
                  HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest);
      
                  return zuulResponse;
              } finally {
              }
          }
      
          private void setResponse(HttpResponse response) throws IOException {
              this.helper.setResponse(response.getStatusLine().getStatusCode(),
                      response.getEntity() == null ? null : response.getEntity().getContent(),
                      revertHeaders(response.getAllHeaders()));
          }
          
          @Override
          public String filterType() {
              return "route";
          }
      
          @Override
          public int filterOrder() {
              return 1;
          }
      
          @Override
          public boolean shouldFilter() {
              return true;
          }
      
          private Header[] convertHeaders(MultiValueMap<String, String> headers) {
              List<Header> list = new ArrayList<>();
              for (String name : headers.keySet()) {
                  for (String value : headers.get(name)) {
                      list.add(new BasicHeader(name, value));
                  }
              }
              return list.toArray(new BasicHeader[0]);
          }
      
          private HttpResponse forwardRequest(HttpClient httpclient, HttpHost httpHost,
                                              HttpRequest httpRequest) throws IOException {
              return httpclient.execute(httpHost, httpRequest);
          }
      
      
          private MultiValueMap<String, String> revertHeaders(Header[] headers) {
              MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
              for (Header header : headers) {
                  String name = header.getName();
                  if (!map.containsKey(name)) {
                      map.put(name, new ArrayList<String>());
                  }
                  map.get(name).add(header.getValue());
              }
              return map;
          }
      }
      

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41607429/article/details/124073165

智能推荐

基于Kepler.gl 和 Google Earth Engine 卫星数据创建拉伸多边形地图-程序员宅基地

文章浏览阅读965次,点赞18次,收藏21次。现在我们有了 2021 年和 2023 年的 NDVI 数据帧,我们需要从 2021 年的值中减去 2023 年的值以捕获 NDVI 的差异。该数据集包括像素级别的植被值,我们将编写一个自定义函数来根据红色和绿色波段的表面反射率计算 NDVI。在我的上一篇文章中,我演示了如何将单个多边形分割/镶嵌为一组大小均匀的六边形。现在我们有了植被损失数据,让我们使用 Kepler.gl 可视化每个六边形的植被损失。将地图保存为 HTML 文件,在浏览器中打开 HTML 以获得更好的视图。现在我们将调用该函数并使用、

Echarts绘制任意数据的正态分布图_echarts正态分布图-程序员宅基地

文章浏览阅读3.3k次,点赞6次,收藏5次。正态分布,又称高斯分布或钟形曲线,是统计学中最为重要和常用的分布之一。_echarts正态分布图

Android中发送短信等普通方法_android bundle.get("pdus");-程序员宅基地

文章浏览阅读217次。首先要在Mainfest.xml中加入所需要的权限:[html] view plain copyprint?uses-permission android:name="android.permission.SEND_SMS"/> uses-permission android:name="android.permission.READ_SMS"/> _android bundle.get("pdus");

2021-07-26 WSL2 的安装和联网_wsl2 联网-程序员宅基地

文章浏览阅读2.6k次。0、说明最近在学习 Data Assimilation Research Testbed (DART) 相关内容,其软件是在 Unix/Linux 操作系统下编译和运行的 ,由于我的电脑是 Windows 10 的,DART 推荐可以使用 Windows Subsystem For Linux (WSL) 来创建一个 Windows 下的 Linux 子系统。以下的内容主要介绍如何安装 WSL2,以及 WSL2 的联网。1、如何在 Windows 10 下安装WSL具体的安装流程可以在 microso_wsl2 联网

DATABASE_LINK 数据库连接_添加 database link重复的数据库链接命-程序员宅基地

文章浏览阅读1k次。DB_LINK 介绍在本机数据库orcl上创建了一个prod_link的publicdblink(使用远程主机的scott用户连接),则用sqlplus连接到本机数据库,执行select * from scott.emp@prod_link即可以将远程数据库上的scott用户下的emp表中的数据获取到。也可以在本地建一个同义词来指向scott.emp@prod_link,这样取值就方便多了..._添加 database link重复的数据库链接命

云-腾讯云-实时音视频:实时音视频(TRTC)-程序员宅基地

文章浏览阅读3.1k次。ylbtech-云-腾讯云-实时音视频:实时音视频(TRTC)支持跨终端、全平台之间互通,从零开始快速搭建实时音视频通信平台1.返回顶部 1、腾讯实时音视频(Tencent Real-Time Communication,TRTC)拥有QQ十几年来在音视频技术上的积累,致力于帮助企业快速搭建低成本、高品质音视频通讯能力的完整解决方案。..._腾讯实时音视频 分享链接

随便推点

用c语言写个日历表_农历库c语言-程序员宅基地

文章浏览阅读534次,点赞10次,收藏8次。编写一个完整的日历表需要处理许多细节,包括公历和农历之间的转换、节气、闰年等。运行程序后,会输出指定年份的日历表。注意,这个程序只是一个简单的示例,还有很多可以改进和扩展的地方,例如添加节气、节日等。_农历库c语言

FL Studio21.1.1.3750中文破解百度网盘下载地址含Crack补丁_fl studio 21 注册机-程序员宅基地

文章浏览阅读1w次,点赞28次,收藏27次。FL Studio21.1.1.3750中文破解版是最优秀、最繁荣的数字音频工作站 (DAW) 之一,日新月异。它是一款录音机和编辑器,可让您不惜一切代价制作精美的音乐作品并保存精彩的活动画廊。为方便用户,FL Studio 21提供三种不同的版本——Fruity 版、Producer 版和签名版。所有这些版本都是独一无二的,同样具有竞争力。用户可以根据自己的需要选择其中任何一种。FL Studio21.1.1.3750中文版可以说是一站式综合音乐制作单位,可以让您录制、作曲、混音和编辑音乐。_fl studio 21 注册机

冯.诺伊曼体系结构的计算机工作原理是,冯 诺依曼型计算机的工作原理是什么...-程序员宅基地

文章浏览阅读1.3k次。冯诺依曼计算机工作原理冯 诺依曼计算机工作原理的核心是 和 程序控制世界上不同型号的计算机,就其工作原理而言,一般都是认为冯 诺依曼提出了什么原理冯 诺依曼原理中,计算机硬件系统由那五大部分组成的 急急急急急急急急急急急急急急急急急急急急急急冯诺依曼结构计算机工作原理的核心冯诺依曼结构和现代计算机结构模型 转载重学计算机组成原理 一 冯 诺依曼体系结构从冯.诺依曼的存储程序工作原理及计算机的组成来..._简述冯诺依曼计算机结构及工作原理

四国军棋引擎开发(2)简单的事件驱动模型下棋-程序员宅基地

文章浏览阅读559次。这次在随机乱下的基础上加上了一些简单的处理,如进营、炸棋、吃子等功能,在和敌方棋子产生碰撞之后会获取敌方棋子大小的一些信息,目前采用的是事件驱动模型,当下完一步棋界面返回结果后会判断是否触发了相关事件,有事件发生则处理相关事件,没有事件发生则仍然是随机下棋。1.事件驱动模型首先定义一个各种事件的枚举变量,目前的事件有工兵吃子,摸暗棋,进营,明确吃子,炸棋。定义如下:enum MoveE..._军棋引擎

STL与泛型编程-第一周笔记-Geekband-程序员宅基地

文章浏览阅读85次。1, 模板观念与函数模板简单模板: template< typename T > T Function( T a, T b) {… }类模板: template struct Object{……….}; 函数模板 template< class T> inline T Function( T a, T b){……} 不可以使用不同型别的..._geekband 讲义

vb.net正则表达式html,VB.Net常用的正则表达式(实例)-程序员宅基地

文章浏览阅读158次。"^\d+$"  //非负整数(正整数 + 0)"^[0-9]*[1-9][0-9]*$"  //正整数"^((-\d+)|(0+))$"  //非正整数(负整数 + 0)"^-[0-9]*[1-9][0-9]*$"  //负整数"^-?\d+$"    //整数"^\d+(\.\d+)?$"  //非负浮点数(正浮点数 + 0)"^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0..._vb.net 正则表达式 取html中的herf