草庐IT

Nacos + Gateway网关搭建微服务

<h3 align="center">慢时光~ 2023-03-28 原文

文章所有代码GtiHub:https://github.com/Tom-shushu/work-study 里面的gateway-server和server1项目

1、Docker 部署 Nacos

数据库准备

新建 "nacos_config" 数据库

在https://github.com/alibaba/nacos/blob/develop/distribution/conf/nacos-mysql.sql 获取最新的SQL脚本并执行(注意版本一定要对应),执行成功后如下图所示:

2、安装Nacos

a.下载镜像

docker pull nacos/nacos-server

b.新建 application.properties文件(注意鉴权那个一定要加-不然会出现权限绕过漏洞 CVE-2021-29441)前几天的护网行动可把人忙坏了!

### 开启鉴权
nacos.core.auth.enabled=true
### 关闭使用user-agent判断服务端请求并放行鉴权的功能
nacos.core.auth.enable.userAgentAuthWhite=false
### 配置自定义身份识别的key(不可为空)和value(不可为空)
nacos.core.auth.server.identity.key=my-nacos
nacos.core.auth.server.identity.value=my-nacos

server.contextPath=/nacos
server.servlet.contextPath=/nacos
server.port=8848

spring.datasource.platform=mysql
db.num=1
## 修改为自己的数据库连接和密码
db.url.0=jdbc:mysql://xxxxxxxxxxx.mysql.rds.aliyuncs.com:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=xxxxxxx
db.password=xxxxxxx

nacos.cmdb.dumpTaskInterval=3600
nacos.cmdb.eventTaskInterval=10
nacos.cmdb.labelTaskInterval=300
nacos.cmdb.loadDataAtStart=false
management.metrics.export.elastic.enabled=false
management.metrics.export.influx.enabled=false
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i
nacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
nacos.naming.distro.taskDispatchThreadCount=1
nacos.naming.distro.taskDispatchPeriod=200
nacos.naming.distro.batchSyncKeyCount=1000
nacos.naming.distro.initDataRatio=0.9
nacos.naming.distro.syncRetryDelay=5000
nacos.naming.data.warmup=true
nacos.naming.expireInstance=true

c.直接启动

docker  run --name nacos-server -p 8848:8848 --privileged=true --restart=always -e MODE=standalone -e PREFER_HOST_MODE=hostname -v /root/nacos/logs:/home/nacos/logs -v /root/application.properties:/home/nacos/conf/application.properties -d nacos/nacos-server

d.测试,注意打开8848安全组

浏览器输入: IP:8848/nacos 默认账号密码: nacos nacos

3、Nginx 代理 接口域名(没有域名的可以跳过)

域名有ssl证书的修改nginx.conf文件,网关我们使用8000端口,现在一次配好

# HTTPS server
    server {
        listen       443 ssl;
        server_name  api.zhouhong.icu;

        charset utf-8;
        ssl_certificate   /usr/local/nginx/ssl/8247790_api.zhouhong.icu.pem;
        ssl_certificate_key /usr/local/nginx/ssl/8247790_api.zhouhong.icu.key;
        ssl_session_timeout 5m;
    	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    	ssl_prefer_server_ciphers on;  
    	## Nacos
        location /nacos {
             proxy_pass   http://127.0.0.1:8848;
        }
    	## 网关
    	location / {
                 proxy_pass   http://127.0.0.1:8000;
        }
    }

没有ssl安全证书的可以如下配置,网关我们使用8000端口,现在一次配好

    server {
        listen       80;
        server_name  api.zhouhong.icu;

        ## Nacos
        location /nacos {
             proxy_pass   http://127.0.0.1:8848;
        }
        ## 网关
        location / {
             proxy_pass   http://127.0.0.1:8000;
        }
    }

测试

浏览器输入: https://api.zhouhong.icu/nacos 或者 http://api.zhouhong.icu/nacos

4、网关服务搭建(代码参考:https://github.com/Tom-shushu/work-study)

1.pom文件(只展示部分,详细信息请到github看代码~)

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>fastjson</artifactId>
                    <groupId>com.alibaba</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>fastjson</artifactId>
                    <groupId>com.alibaba</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

2.配置文件 application.yml

#请求和响应GZIP压缩支持
feign:
  httpclient:
    enabled: false
  okhttp:
    enabled: true
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true
      
spring:
  cloud:
    gateway:
      enabled: true
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
          ## 配置下面这个默认会匹配以服务名为前缀的路由,就不需要在nacos里面配置路由了
          filters:
            - name: RewritePath
              args:
                regexp: "'/' + serviceId + '/(?<remaining>.*)'"
                replacement: "'/' + serviceId + '/${remaining}'"
      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowed-origins: "*"
            allowed-methods: "*"
            allowed-headers: "*"
            allow-credentials: true
      httpclient:
        response-timeout: 60000
        connect-timeout: 60000
    loadbalancer:
      ribbon:
        enabled: false
  application:
    name: gateway
  sleuth:
    enabled: false
    http:
      legacy:
        enabled: true
  main:
    allow-bean-definition-overriding: true
  codec:
    max-in-memory-size: 20MB
management:
  endpoints:
    web:
      exposure:
        include: '*'
        exclude: heapdump,dump,threaddump,configprops,env
  security:
    enabled: false

gate:
  ignore:
    startWith: /auth/jwt,/auth/captcha

ribbon:
  eureka:
    enabled: true
  ReadTimeout: 60000
  ConnectTimeout: 60000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 1
  OkToRetryOnAllOperations: false

hystrix:
  threadpool:
    default:
      coreSize: 1000 ##并发执行的最大线程数,默认10
      maxQueueSize: 1000 ##BlockingQueue的最大队列数
      queueSizeRejectionThreshold: 500 ##即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 10000
            timerDelayInMilliseconds: 10000

3.bootstrap.yml 配置文件

#服务配置
server:
  port: 8000
  max-http-header-size: 10240

spring:
  application:
    name: gateway
  profiles:
    active: @spring.active@
  servlet:
    multipart:
      max-request-size: 100MB
      max-file-size: 100MB

logging:
  file:
    path: logs/
---
spring:
  profiles: local
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 4d7c95f1-21d5-43ad-a420-5eebb2473751
        file-extension: yml
        username: nacos
        password: nacos
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 4d7c95f1-21d5-43ad-a420-5eebb2473751
        username: nacos
        password: nacos
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 07610e11-85ae-49cb-aa46-b8f0802543bd
        file-extension: yml
        username: nacos
        password: nacos
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 07610e11-85ae-49cb-aa46-b8f0802543bd
        username: nacos
        password: nacos
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 01940467-9f25-45e3-b9b8-1ad25c937544
        file-extension: yml
        extension-configs:
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 01940467-9f25-45e3-b9b8-1ad25c937544

4.网关配置类GatewayConfig

@Configuration
public class GatewayConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    @Bean
    public Decoder feignDecoder() {
        return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
    }

    public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
        final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
        return new ObjectFactory<HttpMessageConverters>() {
            @Override
            public HttpMessageConverters getObject() throws BeansException {
                return httpMessageConverters;
            }
        };
    }

    public class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
        GateWayMappingJackson2HttpMessageConverter() {
            List<MediaType> mediaTypes = new ArrayList<>();
            mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
            setSupportedMediaTypes(mediaTypes);
        }
    }
    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {

        return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder().codecs(
                clientCodecConfigurer -> clientCodecConfigurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024)).build());
    }
}

5.过滤器

package com.zhouhong.gatewayserver.core.filter;
/**
 * 全局请求转换
 **/
@Log4j2
@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {

    @Resource
    ServerCodecConfigurer codecConfigurer;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (Objects.equals(exchange.getRequest().getMethod(), HttpMethod.POST)) {
            // 表单传输
            MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
            if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)) {
                return returnMono(chain, exchange);
            }

            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            //放开不进行安全过滤的接口
            boolean checkSign = true;
            for (String notAuthResource : ReleaseConstant.NONE_SECURITY_URL_PATTERNS) {
                AntPathMatcher antPathMatcher = new AntPathMatcher();
                if (antPathMatcher.match(notAuthResource, path)) {
                    checkSign = false;
                }
            }
            ServerRequest serverRequest = ServerRequest.create(exchange, codecConfigurer.getReaders());
            Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                //因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行发挥
                if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)) {
                    JSONObject jsonObject = JSON.parseObject(body);
                    String newBody = null;
                    try {
                        // 如果是1.0版本的请求(body经过base64转码),要将body解码
                        if (jsonObject.containsKey("sign") || jsonObject.containsKey("object")) {
                            newBody = verifySignature(jsonObject.getString("object"));
                        } else {
                            newBody = body;
                        }
                    } catch (Exception e) {
                        return Mono.error(e);
                    }
                    log.info("请求:" + path + " " + body);
                    return Mono.just(newBody);
                }
                return Mono.just(body);
            });
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            headers.remove("Content-Length");
            // 请求中添加TOKEN用户信息
            addTokenInfo(headers, serverRequest);
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
            Mono mono = bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
                return returnMono(chain, exchange.mutate().request(decorator).build());
            }));
            return mono;
        } else {
            //GET 验签
            return returnMono(chain, exchange);
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }

    private Mono<Void> returnMono(GatewayFilterChain chain, ServerWebExchange exchange) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
        }));
    }

    private String verifySignature(String paramStr) {
        return new String(Base64Utils.decodeFromString(paramStr));
    }


    private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {
            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(headers);
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }

    private void addTokenInfo(HttpHeaders headers, ServerRequest request) {
        return;
    }
}
其他代码见github源码~

4、普通业务服务(服务名称:server1)

1.bootstrap.yml 配置文件

server:
  port: 8080
  max-http-header-size: 10240
spring:
  jackson:
    time-zone: GMT+8
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: @spring.active@
  application:
    name: server1

logging:
  file:
    path: ./logs/

log:
  path: ./logs/

---
spring:
  profiles: local
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 4d7c95f1-21d5-43ad-a420-5eebb2473751
        file-extension: yml
        username: nacos
        password: nacos
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 4d7c95f1-21d5-43ad-a420-5eebb2473751
        username: nacos
        password: nacos
---
spring:
  profiles: dev
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 07610e11-85ae-49cb-aa46-b8f0802543bd
        file-extension: yml
        username: nacos
        password: nacos
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 07610e11-85ae-49cb-aa46-b8f0802543bd
        username: nacos
        password: nacos
---
spring:
  profiles: prod
  cloud:
    nacos:
      config:
        server-addr: https://api.zhouhong.icu
        namespace: 01940467-9f25-45e3-b9b8-1ad25c937544
        file-extension: yml
        extension-configs:
      discovery:
        server-addr: https://api.zhouhong.icu
        namespace: 01940467-9f25-45e3-b9b8-1ad25c937544

这里只是为了测试简单写了两个controller方法

/**
     * 服务名称和接口第一段字符匹配
     * @return
     */
    @PostMapping("/server1/test")
    public String theSameServer() {
        return "你好呀( ⊙ o ⊙ )!";
    }

    /**
     * 服务名称和接口第一段字符不匹配
     * @return
     */
    @PostMapping("/serverNotSame/test")
    public String gettoken() {
        return "你好呀( ⊙ o ⊙ )!";
    }

5、综合测试

A.部署说明:

1.项目中我按照正常的公司开发形式,指定了 本地(local)、测试/开发(dev)、生产(prod)环境,搭建阔以按照自己的需求进行取舍;

2.本次测试我用测试环境进行演示、大家需要在nacos上建立与环境对应的命名空间(local、dev、prod),并且需要在网关和自己业务服务里面指定对应的环境和nacos配置;

3.之前说过nacos开启了用户鉴权,所以需要在discovery 和 config 下面配置登录时的用户名以及密码

4.本项目实现的是根据服务名称自动匹配接口前缀,至于根据nacos路由列表匹配大家可以自行百度。

B.部署

1.打包:需要选择对应的环境,我这里使用测试环境(dev)如图所示:

2.需要打包网关和业务服务两个服务,最终会得到 gateway.jar 和 server1.jar两个jar包,然后上传服务器

3.部署(我这里为了方便就不需要进行docker部署了)直接使用 java -jar XXX.jar 进行简单部署了

部署成功后在nocos控制台查看服务是否注册成功

如果服务列表为这个熊样,就说明部署成功,接下来就可以测试啦

4.接口测试

使用PostMan进行接口调用
A 调用和服务名称相匹配的那个接口 https://api.zhouhong.icu/server1/test

如图所示调用成功(因为yml里面那个配置,它会自动根据服务名称 server1 进行匹配)
B 调用与服务名称不匹配的那个接口

拦截成功

有关Nacos + Gateway网关搭建微服务的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. ruby-on-rails - 与 ActiveMerchant 一起使用的最佳支付网关是什么? - 2

    我需要使用ActiveMerchant库在我们的一个Rails应用程序中设置支付解决方案。尽管这个问题非常主观,但人们对主要网关(BrainTree、Authorize.net等)的体验如何?它必须:处理定期付款。有能力记入个人帐户。能够取消付款。有办法存储用户的付款详细信息(例如Authotize.netsCIM)。干杯 最佳答案 ActiveMerchant很棒,但在过去一年左右的时间里,我在使用它时发现了一些问题。首先,虽然某些网关可能会得到“支持”——但并非所有功能都包含在内。查看功能矩阵以确保完全支持您选择的网关-http

  3. ruby-on-rails - Ruby 中的存储库或网关模式 - 2

    如何在Ruby中实现存储库或网关模式?我来自C#世界,我通常抽象出我的数据访问,但是使用ActiveRecord作为Ruby中的默认数据访问机制,如何实现这一点并不明显。我通常在C#中做的是使用抽象接口(interface),然后为ECFustomerRepository、NHibernateCustomerRepository和InMemoryCustomerRepository以及依赖具体实现在这种情况下我注入(inject)了匹配的具体实现。那么现在,Ruby方式是什么?!据我所知,在动态语言中你不需要像DI(依赖注入(inject))这样的东西。而且Ruby具有强大的语言特性,

  4. Spring Cloud Gateway 服务网关的部署与使用详细介绍 - 2

    为什么需要服务网关传统的单体架构中只需要开放一个服务给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,如果没有网关,客户端只能在本地记录每个微服务的调用地址,当需要调用的微服务数量很多时,它需要了解每个服务的接口,这个工作量很大。有了网关之后,网关作为系统的唯一流量入口,封装内部系统的架构,所有请求都先经过网关,由网关将请求路由到合适的微服务。使用网关的好处1)简化客户端的工作。网关将微服务封装起来后,客户端只需同网关交互,而不必调用各个不同服务;(2)降低函数间的耦合度。一旦服务接口修改,只需修改网关的路由策略,不必修改每个调用该函数的客户端,从而减少了程序间的耦合性(3)解放开发

  5. LinuxGUI自动化测试框架搭建(二十二)-框架主入口main.py设计&log日志调用 - 2

    (二十二)-框架主入口main.py设计&log日志调用和生成1测试目的2测试需求3需求分析4详细设计4.1新建存放日志目录log4.1.1配置config.py中写入log的目录4.2`baseInfo.py`中加入日志4.3`test_gedit.py`中加入日志4.4主函数入口main.py中调用日志5调用日志主函数main.py源码6`baseInfo.py`源码7`test_gedit.py`源码8运行效果9目前框架结构1测试目的组织运行所有的测试用例,并调用日志模块,便于问题定位。

  6. 基于ActiveMQ搭建MQTT服务备忘(二):webapp集成 - 2

    (1)为什么写这个话题(Why)读万卷书不如行千里路。这次搭建MQTT服务,遇到了一些误解,特此记录备忘。主要包括:(1)服务(Broker)的账户管理与网页管理平台的账户(2)与web应用的集成(Spring系)(2)ActiveMQ版本选择因为JAVA环境是JDK8,所以按兼容性考虑选择了ActiveMQ5.15的最后版本5.15.15。如果你是JDK11则可考虑ActiveMQ的最新版本5.17或5.18。ActiveMQ支持MQTTv3.1.1andv3.1。(3)ActiveMQ与web应用的集成主要介绍与Spring系的webapp集成(SpringBoot和SpringMVC)。

  7. 【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程 - 2

    这篇文章,主要介绍如何使用SpringCloud微服务组件从0到1搭建一个微服务工程。目录一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件(2)微服务依赖1.2、搭建注册中心(1)引入依赖(2)配置文件(3)启动类1.3、搭建配置中心(1)引入依赖(2)配置文件(3)启动类1.4、搭建API网关(1)引入依赖(2)配置文件(3)启动类1.5、搭建服务提供者(1)引入依赖(2)配置文件(3)启动类1.6、搭建服务消费者(1)引入依赖(2)配置文件(3)启动类1.7、运行测试一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件这里主要是使用的SpringCloudNetflix

  8. ruby-on-rails - Puma 和 Nginx 502 Bad Gateway 错误(Ubuntu 服务器 14.04) - 2

    我需要部署我的Rails应用程序,所以我从这里开始执行了所有步骤,https://www.digitalocean.com/community/tutorials/how-to-deploy-a-rails-app-with-puma-and-nginx-on-ubuntu-14-04但是在教程结束时,我得到了这个错误-->“502BadGateway”编辑现在的错误消息-->“很抱歉,出了点问题。”但是Nginx错误输出是相同的,我检查了puma错误消息,但它们只是记录它何时启动以及何时正常停止。位于app_directory/log下的Rails日志不产生任何输出。puma-man

  9. ruby-on-rails - HttpPlatformHandler HTTP 错误 502.3 - 网关错误 - 2

    我正在尝试在IIS上运行Rails,我遵循了提到的步骤here经过斯科特·汉塞尔曼。这里有一些可能有用的信息:Rails版本:5.1.4ruby版本:2.3.3按照设置步骤操作后,我解决了这个问题HTTPError502.3-BadGatewayTherewasaconnectionerrorwhiletryingtoroutetherequest.Mostlikelycauses:TheCGIapplicationdidnotreturnavalidsetofHTTPerrors.Aserveractingasaproxyorgatewaywasunabletoprocessther

  10. ruby-on-rails - activemerchant Paypal 网关配置 - 2

    我正在尝试借助railscasts教程配置我的paypal网关和activemerchant,但我有点困惑,因为网关信息已更改。这是教程中的旧配置:gateway=ActiveMerchant::Billing::PaypalGateway.new(login:"...",password:"...",signature:"...")在我的PaypalSandbox帐户中,我只有这个:端点:“...”客户ID:“...”secret:“……”什么是正确的配置? 最佳答案 您的网关需要的是经典凭据。为了获得这些,您必须首先创建一个Pa

随机推荐