草庐IT

11. SpringCloud之集成Oauth2.0权限校验之密码模式

天还下着毛毛雨 2023-03-28 原文

1、Oauth2.0密码模式

在SpringCloud项目里 ,Oauth2.0密码模式 校验权限的总体流程还是 一样的。

image.png

只不过使用 密码模式获取 token,也就是说在获取 token 过程中必须带上用户的用户名和密码,获取到 的 token 是跟用户绑定的。

2、认证服务器搭建

客户端信息和用户信息 既可以存在内存里,也可以存在 数据库里, 存在内存的方式 我们已经在 上一篇 客户端模式 演示过了,接下来 密码模式就 看下 如何在数据库 存储。

2.1、表结构

2.2.1、Oauth2.0表结构:

可以直接从官网上扒下来,是Oauth2.0用来权限校验 预设的表。主要用来存token,授权码和客户端信息。

1、用户token表

CREATE TABLE `oauth_access_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(48) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  `authentication` blob,
  `refresh_token` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

2、客户端信息表

CREATE TABLE `oauth_client_details` (
  `client_id` varchar(48) NOT NULL COMMENT '客户端ID,主要用于标识对应的应用',
  `resource_ids` varchar(256) DEFAULT NULL,
  `client_secret` varchar(256) DEFAULT NULL COMMENT '客户端秘钥,BCryptPasswordEncoder加密',
  `scope` varchar(256) DEFAULT NULL COMMENT '对应的范围',
  `authorized_grant_types` varchar(256) DEFAULT NULL COMMENT '认证模式',
  `web_server_redirect_uri` varchar(256) DEFAULT NULL COMMENT '认证后重定向地址',
  `authorities` varchar(256) DEFAULT NULL,
  `access_token_validity` int(11) DEFAULT NULL COMMENT '令牌有效期',
  `refresh_token_validity` int(11) DEFAULT NULL COMMENT '令牌刷新周期',
  `additional_information` varchar(4096) DEFAULT NULL,
  `autoapprove` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

插入 micro-order 客户端记录, 密钥为 123456的 {bcrypt}+ bcrypt加密后的 密文

INSERT INTO `oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('micro-order', 'micro-order', '{bcrypt}$2a$10$8HBBphskF43dijSHs8KQg./BWUnxeqRaFr0jbDwCcqJo0FNM6YZn2', 'all,read,writr,aa', 'client_credentials,refresh_token,password', NULL, 'oauth2', NULL, NULL, NULL, NULL);

3、客户端token

CREATE TABLE `oauth_client_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication_id` varchar(48) NOT NULL,
  `user_name` varchar(256) DEFAULT NULL,
  `client_id` varchar(256) DEFAULT NULL,
  PRIMARY KEY (`authentication_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

4、授权码表

CREATE TABLE `oauth_code` (
  `code` varchar(256) DEFAULT NULL,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

5、刷新token表

CREATE TABLE `oauth_refresh_token` (
  `token_id` varchar(256) DEFAULT NULL,
  `token` blob,
  `authentication` blob
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

6、这个表不知道干嘛

CREATE TABLE `oauth_approvals` (
  `userId` varchar(256) DEFAULT NULL,
  `clientId` varchar(256) DEFAULT NULL,
  `scope` varchar(256) DEFAULT NULL,
  `status` varchar(10) DEFAULT NULL,
  `expiresAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `lastModifiedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

2.2.2、用户角色表

用户信息包括角色权限 和我们的业务有关,由我们自己创建, 只需要向认证服务器框架提供 获取用户 信息 的 方法即可。

经典 用户 , 角色,用户_角色 关系表

1、用户表

最重要是用户名和密码

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

插入用户信息,用户名为 admin,密码为123456的 {bcrypt} + bcrypt加密后的密文

INSERT INTO `user`(`id`, `username`, `password`) VALUES (1, 'admin', '{bcrypt}$2a$10$8HBBphskF43dijSHs8KQg./BWUnxeqRaFr0jbDwCcqJo0FNM6YZn2');

2、角色表

CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

插入角色 ROLE_ADMIN,注意一定要 ROLE_ 为前缀。不然会报错

INSERT INTO `role`(`id`, `name`) VALUES (1, 'ROLE_ADMIN');

3、用户角色关系表

CREATE TABLE `user_role` (
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2、pom依赖

和客户端模式的依赖相同,由于需要 与数据库交互, 多引入了 数据持久化框 之类的依赖

<dependencies>
            <!-- 注册到注册中心 -->
     <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 认证服务相关 -->
     <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-data</artifactId>
    </dependency>
    <!--数据库操作相关, 认证服务器 需要存储token,获取用户信息,客户端信息,这里采用myql存储 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

2.3、代码配置

2.3.1、认证服务配置

@Configuration
// 开启认证服务器
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private UserServiceDetail userServiceDetail;

    @Autowired
    private ClientDetailsService clientDetailsService;

    static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);

    @Bean
    public TokenStore tokenStore() {
        return new JdbcTokenStore(dataSource);
    }

    @Bean // 声明 ClientDetails实现,用数据库存储,需要配置数据源对象
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // redisTokenStore
//        endpoints.tokenStore(new MyRedisTokenStore(redisConnectionFactory))
//                .authenticationManager(authenticationManager)
//                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

        // 存数据库
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userServiceDetail);

        // 配置tokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        //支持refreshtoken
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds(60 * 5);
        //重复使用
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setRefreshTokenValiditySeconds(60 * 10);
        endpoints.tokenServices(tokenServices);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 允许表单认证
        security.allowFormAuthenticationForClients()
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

}

2.3.2、安全配置

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    // 密码加密方式
    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager manager = super.authenticationManagerBean();
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().anyRequest()
                .and()
                .authorizeRequests()
//                .antMatchers("/oauth/**").permitAll();
                .antMatchers("/").authenticated();

/*        http.csrf().disable().exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .authorizeRequests().
                antMatchers("/favicon.ico").permitAll()
                .antMatchers("/oauth/**").permitAll()
                .antMatchers("/login/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .httpBasic().disable();*/
        http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic();  //拦截所有请求 通过httpBasic进行认证

    }
}

2.3.3、UserServiceDetail

定义了如何获取用户信息,比如用户名,密码,角色权限等,这里我们用jpa从数据库查询,也就是user,role,user_role根据用户名查询出 用户信息 交由权限框架 校验。

@Service
public class UserServiceDetail implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

2.3.4、token校验接口

下游服务 调认证服务器的这个接口来验证token是否有效,token有效的话 则返回 认证后的用户信息, 让下游服务 做更细致的权限控制,比如方法级别的控制。

@Slf4j
@RestController
@RequestMapping("/security")
public class SecurityController {

    @RequestMapping(value = "/check", method = RequestMethod.GET)
    public Principal getUser(Principal principal) {
        log.info(principal.toString());
        return principal;
    }
}

2.4、配置文件

spring.application.name=oauth-pwd-server
#spring.cloud.controller.uri= http://localhost:9009/
server.port=9052
#eureka.client.service-url.defaultZone=http://localhost:9001/eureka/
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:9001/eureka/
# 数据源配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://*****:3306/oauth2.0?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456

logging.level.org.springframework.security=debug

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.security.basic.enabled=true

2.5、启动类

@SpringBootApplication
// 认证服务器本身也作为一种 受保护资源
@EnableResourceServer
public class OauthPwdServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(OauthPwdServerApplication.class, args);
    }
}

3、向认证服务器获取token

获取token的 接口仍然是 /oauth/token

3.1、Authorization请求头

在密码模式中 ,Authorization请求头需要填写 加密后的 客户端id和客户端密钥, 只能 用来校验 客户端 是否合法。

用postman,选择Basic Auth加密方式

image.png

这里的客户端id和 密码对应 我们一开始 保存到 oauth_client_details 表里的客户端记录

数据库里存的密文, 页面上填明文。

image.png

之后就可以看到在请求头里 多出了 客户端id和客户端密钥 生成的密钥

image.png

3.2、body参数

  • grant_type :认证类型, 密码模式 填 password
  • username : 用户名,user表里的username
  • password :密码,user表里的密码
  • scope : all

用户名和密码对应 user表里的 用户名和密码

数据库里存的密文, 页面上填明文。

image.png
image.png

最后调用该接口, 可以成功获取到token,证明 可以认证服务器 密码 模式 已经搭建成功。

4、资源服务器(下游服务)搭建

SpringCloud项目中的需要权限校验的下游服务 就是 对应 oauth2.0的模型中, 受保护的资源服务(Resource Server),需要进行权限校验后才能 正常访问。

我们把之前的micro-order服务改成 资源服务器,让他的接口需要权限校验。

之后 调用micro-order服务 就需要传有效的token才能访问。

4.1、pom依赖

新增oauth2.0权限校验 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

4.2、代码配置

4.2.1、客户端配置

/*
  鉴权过滤器
* @  OAuth2AuthenticationProcessingFilter
*
* */
@EnableOAuth2Client
@EnableConfigurationProperties
@Configuration
public class OAuth2ClientConfig {


    @Bean
    @ConfigurationProperties(prefix = "security.oauth2.client")
    public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
        return new ClientCredentialsResourceDetails();
    }

//    @Bean
    public RequestInterceptor oauth2FeignRequestInterceptor(ClientCredentialsResourceDetails clientCredentialsResourceDetails) {
        return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails);
    }

    @Bean
    public OAuth2RestTemplate clientCredentialsRestTemplate() {
        return new OAuth2RestTemplate(clientCredentialsResourceDetails());
    }
}

4.2.2、刷新token配置

public class RefreshTokenAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {

    @Autowired
    private ClientCredentialsResourceDetails clientCredentialsResourceDetails;

    private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();

    @Autowired
    RestTemplate restTemplate;

    private static String oauth_server_url = "http://oauth-pwd-server/oauth/token";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        try {
            //解析异常,如果是401则处理
            ResponseEntity<?> result = exceptionTranslator.translate(authException);
            if (result.getStatusCode() == HttpStatus.UNAUTHORIZED) {
                MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
                formData.add("client_id", clientCredentialsResourceDetails.getClientId());
                formData.add("client_secret", clientCredentialsResourceDetails.getClientSecret());
                formData.add("grant_type", clientCredentialsResourceDetails.getGrantType());
                formData.add("scope", String.join(",", clientCredentialsResourceDetails.getScope()));
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                Map map = restTemplate.exchange(oauth_server_url, HttpMethod.POST,
                        new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
                //如果刷新异常
                if (map.get("error") != null) {
                    // 返回指定格式的错误信息
                    response.setStatus(401);
                    response.setHeader("Content-Type", "application/json;charset=utf-8");
                    response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
                    response.getWriter().flush();
                    //如果是网页,跳转到登陆页面
                    //response.sendRedirect("login");
                } else {
                    //如果刷新成功则存储cookie并且跳转到原来需要访问的页面

                    for (Object key : map.keySet()) {
                        response.addCookie(new Cookie(key.toString(), map.get(key).toString()));
                    }
                    request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
//                    response.sendRedirect(request.getRequestURI());

                    //将access_token保存
                }
            } else {
                //如果不是401异常,则以默认的方法继续处理其他异常
                super.commence(request, response, authException);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

4.2.3、资源服务器配置

@Configuration
@EnableResourceServer
//启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

//    @Autowired
//    private TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        // 配置order访问控制,必须认证后才可以访问
        http.authorizeRequests()
                .antMatchers("/order/**").authenticated();
    }

    /*
    * 把token验证失败后,重新刷新token的类设置到 OAuth2AuthenticationProcessingFilter
    * token验证过滤器中
    * */
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
//        resources.authenticationEntryPoint(new RefreshTokenAuthenticationEntryPoint());
//        resources.tokenStore(tokenStore);
    }
}

4.3、配置文件

新增权限校验相关配置

# 配置 认证服务器 校验token的接口地址就行
security.oauth2.resource.user-info-uri=http://127.0.0.1:9052/security/check
security.oauth2.resource.prefer-token-info=false

4.4、测试下游服务 是否鉴权成功

利用 从认证服务器获取到的token,放到Authorization请求头里, 加前缀 bearer + 空格 +token,去直接请求 micro-order 的以下接口( 从网关路由到该服务的该接口也可以, 只要网关不过滤掉 Authorization 请求信息就行)。

@RequestMapping("/order")
@RestController
@RefreshScope
public class ConfigController {

    @Value("${username}")
    private String username;
    
    // 方法级别控制,必须是ROLE_ADMIN 角色才能访问
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/getUsername")
    public String getUsername(HttpServletRequest request) {
        System.out.println(request.getHeader("Authorization"));
        return username;
    }
}

1、输入正确的token

image.png

发现是可以调用成功的

2、如果不输入token,报401未授权

image.png

3、输入错误的token,报401,token非法

image.png

至此, 资源服务器 确实 是 有权限校验的, 说明搭建成功。

5、认证服务器和下游系统权限校验流程

加上网关zuul, 大概的校验流程就是这样的:


image.png
  1. zuul 携带 token 请求下游系统,被下游系统 filter 拦截

  2. 下游系统过滤器根据配置中的 user-info-uri 请求到认证服务器

  3. 请求到认证服务器被 filter 拦截进行 token 校验,把 token 对应的用户、和权限从数据库

查询出来封装到 Principal .

  1. 认证服务器 token 校验通过后过滤器放行执行 security/check 接口,把 principal 对象返回

  2. 下游系统接收到 principal 对象后就知道该 token 具备的权限了,就可以进行相应用户对

应的 token 的权限执行

有关11. SpringCloud之集成Oauth2.0权限校验之密码模式的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

  5. ruby-on-rails - 我如何将 Hoptoad 与 DelayedJob 和 DaemonSpawn 集成? - 2

    我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  8. ruby - rbenv 安装 ruby​​ 校验和不匹配 osx - 2

    我已经在mountainlion上成功安装了rbenv和ruby​​build。运行rbenvinstall1.9.3-p392结束于:校验和不匹配:ruby-1.9.3-p392.tar.gz(文件已损坏)预期f689a7b61379f83cbbed3c7077d83859,得到1cfc2ff433dbe80f8ff1a9dba2fd5636它正在下载的文件看起来没问题,如果我使用curl手动下载文件,我会得到同样不正确的校验和。有没有人遇到过这个?他们是如何解决的? 最佳答案 tl:博士;使用浏览器从http://ftp.rub

  9. jenkins部署1--jenkins+gitee持续集成 - 2

    前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon

  10. ruby - rbenv 安装权限被拒绝 - 2

    大家好,我正在尝试设置一个开发环境,并且我一直在关注以下教程:Linktotutorial我做得不是很好,除了最基本的版本控制内容外,我对终端命令没有任何实际经验。我点击了第一个链接并尝试运行source~/.bash_profile我得到了错误;mkdir:/usr/local/rbenv/shims:权限被拒绝mkdir:/usr/local/rbenv/versions:权限被拒绝现在每次我加载终端时都会出现错误。bash_profile的内容;exportPATH=/usr/local/rbenv/bin:$PATHexportRBENV_ROOT=/usr/local/rbe

随机推荐