需求来源: 微信小程序获取授权码code, 通过授权码code, 获取微信用户信息(比如openid,unionId), 并记录登录状态(比如token信息的获取);
原本打算使用Spring Security中OAuth2.0的机制 实现用小程序登录,发现小程序再已经获取授权码code登录流程和Spring Security中OAuth 2.0登录的流程有点不一样,所以直接使用spring security的Filter进行处理;

Spring Security中的OAuth 2.0 授权码模式:

获取授权码code部分已经由小程序做过了, 现在我们无需再自己的服务oauth2去获取code,而是要直接去认证获取我们所需要的access_token 信息;

小程序已经持有了code,它依然需要将code传递给后端服务器来执行后面的流程。那么我们能不能利用图2中第3个调用redirectUri的步骤呢?换个角度来看问题第三方就是小程序反正它也是将一个code传递给了后端服务器,只要返回登录状态就行了,反正剩下的登录流程都跟小程序无关。我觉得它是可以的。在Spring Security中我们可以使用code通过tokenUri来换取token。那么在微信小程序登录流程中,code最终换取的只是登录态,没有特定的要求。但是后端肯定需要去获取用户的一些信息,比如openId,用户微信信息之类的。总之要根据微信平台提供的API来实现。通过改造tokenUri和userInfoUri可以做到这一点。

小程序实现Filter 过滤器链来完成, 基于ProxyFilter代理
默认的过滤器顺序列表
| order | 过滤器名称 |
|---|---|
| 100 | ChannelProcessingFilter |
| 200 | ConcurrentSessionFilter |
| 300 | SecurityContextPersistenceFilter |
| 400 | LogoutFilter |
| 500 | X509AuthenticationFilter |
| 600 | RequestHeaderAuthenticationFilter |
| 700 | CasAuthenticationFilter |
| 800 | UsernamePasswordAuthenticationFilter |
| 900 | OpenIDAuthenticationFilter |
| 1000 | DefaultLoginPageGeneratingFilter |
| 1100 | DigestAuthenticationFilter |
| 1200 | BasicAuthenticationFilter |
| 1300 | RequestCacheAwareFilter |
| 1400 | SecurityContextHolderAwareRequestFilter |
| 1500 | RememberMeAuthenticationFilter |
| 1600 | AnonymousAuthenticationFilter |
| 1700 | SessionManagementFilter |
| 1800 | ExceptionTranslationFilter |
| 1900 | FilterSecurityInterceptor |
| 2000 | SwitchUserFilter |
@Slf4j
public class LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Resource
private SecurityConfigProperties securityConfigProperties;
@Resource
private JwtConfigProperties jwtConfigProperties;
private Class<?> requestDataType;
public LoginAuthenticationFilter(AuthenticationManager authenticationManager,String loginPath) {
super(new AntPathRequestMatcher(loginPath));
super.setAuthenticationManager(authenticationManager);
}
@Override
public void afterPropertiesSet() {
Assert.notNull(securityConfigProperties.getRequestDataTypeName(), "登录请求数据类型必须被设定");
try {
this.requestDataType = Class.forName(securityConfigProperties.getRequestDataTypeName());
} catch (ClassNotFoundException var2) {
Assert.notNull(this.requestDataType, "登录请求数据类型必须是有效的类型");
}
super.afterPropertiesSet();
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
ServletInputStream inputStream = request.getInputStream();
// String requestBody = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
Object requestData = new ObjectMapper().readValue(inputStream, this.requestDataType);
if (requestData != null) {
if (requestData instanceof Map) {
// 可以扩展不同的参数类型走不同的认证处理器; 比如走jwt的时候,会走JwtAuthenticationToken相关的认证处理器JwtAuthenticationProvider
}
// 此处不同引入的模块走自己的登录认证Provider
Authentication auth = this.getAuthenticationManager().authenticate((Authentication)requestData);
if (auth != null) {
UserDetails userDetail = (UserDetails)auth.getDetails();
request.setAttribute(jwtConfigProperties.getUserKey(), userDetail.getUsername());
return auth;
} else {
return null;
}
} else {
throw new RuntimeException("无授权用户信息");
}
}
@Override
public void destroy() {
if (log.isInfoEnabled()) {
log.info("正在注销......");
}
}
}
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userService;
@Resource
private JwtConfigProperties jwtConfigProperties;
@Resource
private TokenParser tokenParser;
private List<RequestMatcher> permissiveRequestMatchers;
@Autowired
private JwtTokenService jwtTokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
if (log.isDebugEnabled()) {
log.debug("开始对请求:{}进行认证", requestURI);
}
if (!this.requireAuthentication(request)) {
filterChain.doFilter(request, response);
return;
}
// 获取 认证头
String authorizationHeader = request.getHeader(jwtConfigProperties.getAuthorizationHeaderName());
if (!checkIsTokenAuthorizationHeader(authorizationHeader)) {
log.debug("获取到认证头Authorization的值:[{}]但不是我们系统中登录后签发的。", authorizationHeader);
filterChain.doFilter(request, response);
return;
}
String token = JwtTokenOperator.getToken(request, jwtConfigProperties.getAuthorizationHeaderName(), jwtConfigProperties.getTokenHeaderPrefix());
JwtAuthInfo authInfo = tokenParser.parse(token, jwtConfigProperties.getTokenType());
if (authInfo == null) {
writeJson(response, "认证token不合法");
return;
}
if (authInfo.getRefreshToken()) {
writeJson(response, "认证token不合法,请勿直接用刷新token认证");
return;
}
String userKey = authInfo.getUserKey();
UserDetails user = this.userService.loadUserByUsername(userKey);
if (ObjectUtil.isEmpty(user)) {
writeJson(response, "用户不存在,无效令牌");
return;
}
if (authInfo.getExpirationTime() < System.currentTimeMillis()) {
// 令牌失效自动刷新令牌
handleTokenExpired(response, request);
}
// 构建认证对象
JwtAuthenticationToken jwtAuthToken = new JwtAuthenticationToken(user, authInfo, user.getAuthorities());
request.setAttribute(jwtConfigProperties.getSignatureKey(), userKey);
jwtAuthToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
SecurityContextHolder.getContext().setAuthentication(jwtAuthToken);
filterChain.doFilter(request, response);
}
/**
* 請求認證
* @param request
* @return
*/
private boolean requireAuthentication(HttpServletRequest request) {
String authHeader = request.getHeader(jwtConfigProperties.getAuthorizationHeaderName());
if (authHeader == null) {
if (log.isDebugEnabled()) {
log.debug("请求头中不包含令牌信息,所以将跳过认证");
}
return false;
}
if (CollectionUtil.isNotEmpty(this.permissiveRequestMatchers)) {
Iterator var2 = this.permissiveRequestMatchers.iterator();
while(var2.hasNext()) {
RequestMatcher matcher = (RequestMatcher)var2.next();
boolean isPermissiveUrl = matcher.matches(request);
if (isPermissiveUrl) {
if (log.isDebugEnabled()) {
log.debug("请求:{}为特权请求,将跳过认证", request.getRequestURI());
}
return false;
}
}
}
return true;
}
@Override
protected void initFilterBean() throws ServletException {
super.initFilterBean();
if (CollectionUtil.isNotEmpty(jwtConfigProperties.getPermissiveRequestUrls())) {
this.permissiveRequestMatchers = new ArrayList(jwtConfigProperties.getPermissiveRequestUrls().size());
Iterator var1 = jwtConfigProperties.getPermissiveRequestUrls().iterator();
while(var1.hasNext()) {
String url = (String)var1.next();
AntPathRequestMatcher matcher = new AntPathRequestMatcher(url);
this.permissiveRequestMatchers.add(matcher);
}
}
}
@Override
protected String getAlreadyFilteredAttributeName() {
if (log.isDebugEnabled()) {
log.debug("正在检查时否已经拦截过滤过");
}
String name = this.getClass().getName();
return name + ".FILTERED";
}
/**
* 判断是否是系统中登录后签发的token
*
* @param authorizationHeader
* @return
*/
private boolean checkIsTokenAuthorizationHeader(String authorizationHeader) {
if (StringUtils.isBlank(authorizationHeader)) {
return false;
}
if (!StringUtils.startsWith(authorizationHeader, jwtConfigProperties.getTokenHeaderPrefix())) {
return false;
}
return true;
}
/**
* 处理token过期情况
*
* @param response
* @param request
* @return
* @throws IOException
*/
private void handleTokenExpired(HttpServletResponse response, HttpServletRequest request) throws IOException, ServletException {
// 获取刷新 token
String refreshTokenHeader = request.getHeader(jwtConfigProperties.getRefreshHeaderName());
// 检测 refresh-token 是否是我们系统中签发的
if (!checkIsTokenAuthorizationHeader(refreshTokenHeader)) {
log.debug("获取到刷新认证头:[{}]的值:[{}]但不是我们系统中登录后签发的。", jwtConfigProperties.getRefreshHeaderName(), refreshTokenHeader);
writeJson(response, "token过期了,refresh token 不是我们系统签发的");
return;
}
String referToken = JwtTokenOperator.getToken(request,jwtConfigProperties.getRefreshHeaderName(),jwtConfigProperties.getTokenHeaderPrefix());
JwtAuthInfo authInfo = this.tokenParser.parse(referToken, jwtConfigProperties.getTokenType());
if (authInfo == null) {
writeJson(response, "refresh token不合法");
return;
}
// 判断 refresh-token 是否过期
if (authInfo.getExpirationTime() < System.currentTimeMillis()) {
writeJson(response, "refresh token 过期了");
return;
}
if (authInfo.getRefreshToken()) {
writeJson(response, "refresh token不合法,请勿直接用认证token刷新令牌");
return;
}
// 重新签发 token
authInfo.setEffectiveTime(0L);
String userToken = jwtTokenService.generateUserToken(authInfo);
// 刷新 refresh token, 刷新token,提供刷新token接口获取
// authInfo.setEffectiveTime(0L);
// String refreshToken = jwtTokenService.generateRefreshUserToken(authInfo);
response.addHeader(jwtConfigProperties.getAuthorizationHeaderName(), jwtConfigProperties.getTokenHeaderPrefix() + userToken);
// response.addHeader(jwtConfigProperties.getRefreshHeaderName(), jwtConfigProperties.getTokenHeaderPrefix() + refreshToken);
}
private void writeJson(HttpServletResponse response, String msg) throws IOException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().println(JSONUtil.parse(CommonResult.buildFailure(StatusEnum.UNAUTHORIZED, msg)));
}
}
@Service
public class JwtTokenService {
@Resource
private JwtConfigProperties jwtConfigProperties;
private static String SECURITY_NAMESPACE_PREFIX = "security-auth";
public String generateUserToken(JwtAuthInfo auth) {
String token = this._generateUserToken(auth);
return token;
}
public JwtAuthInfo parseToken(String token) {
if (StringUtils.isNotBlank(jwtConfigProperties.getTokenHeaderPrefix()) && token.startsWith(jwtConfigProperties.getTokenHeaderPrefix())) {
token = token.substring(jwtConfigProperties.getTokenHeaderPrefix().length());
}
return (JwtAuthInfo) TokenParserRegistrar.getInstance().getParser(jwtConfigProperties.getTokenType()).parse(token, jwtConfigProperties.getSignatureKey(), jwtConfigProperties.getTokenType());
}
public String generateRefreshUserToken(JwtAuthInfo auth) {
long startTime = auth.getEffectiveTime();
if (startTime <= 0L) {
startTime = System.currentTimeMillis();
auth.setEffectiveTime(startTime);
auth.setIssueTime(startTime);
long expireTime = startTime + jwtConfigProperties.getRefreshTokenExpiredSecond();
auth.setExpirationTime(expireTime);
}
auth.setRefreshToken(true);
return generateCommonToken(auth, jwtConfigProperties.getAlgorithmName(), jwtConfigProperties.getSignatureKey());
}
private String _generateUserToken(JwtAuthInfo auth) {
long startTime = auth.getEffectiveTime();
if (startTime <= 0L) {
startTime = System.currentTimeMillis();
auth.setEffectiveTime(startTime);
auth.setIssueTime(startTime);
long expireTime = startTime + jwtConfigProperties.getTokenExpireSecond();
auth.setExpirationTime(expireTime);
}
auth.setRefreshToken(false);
String token = generateCommonToken(auth, jwtConfigProperties.getAlgorithmName(), jwtConfigProperties.getSignatureKey());
this.cacheToken(auth, token);
return token;
}
private void cacheToken(JwtAuthInfo jwtAuthInfo,String token) {
//String cacheKey = this.buildTokenCacheKey(userId, tokenId);
//this.redisTemplate.opsForValue().set(cacheKey, token);
//this.redisTemplate.expire(cacheKey, jwtConfigProperties.getRefreshInterval() + 5000L, TimeUnit.MILLISECONDS);
RedisUtils.setObject(buildTokenCacheKey(jwtAuthInfo), token, Integer.parseInt(String.valueOf(jwtConfigProperties.getTokenExpireSecond())));
}
private String buildTokenCacheKey(JwtAuthInfo jwtAuthInfo) {
return String.join(":",
SECURITY_NAMESPACE_PREFIX,
jwtAuthInfo.getApplicationKey(),
jwtAuthInfo.getUserKey());
}
public String fetchToken(JwtAuthInfo jwtAuthInfo) {
String cacheKey = this.buildTokenCacheKey(jwtAuthInfo);
String cachedToken = RedisUtils.getObject(cacheKey, String.class);
return cachedToken;
}
public String generateCommonToken(AuthInfo authInfo, String algorithm, String signatureKey) {
JwtAuthInfo jwtAuthInfo = (JwtAuthInfo)authInfo;
Map<String, Object> header = new HashMap();
header.put("alg", jwtConfigProperties.getAlgorithmName());
header.put("typ", "JWT");
JwtBuilder builder = Jwts.builder().setHeader(header).setHeaderParam("refresh_token", authInfo.getRefreshToken()).setSubject(jwtAuthInfo.getApplicationKey()).setId(jwtAuthInfo.getUserKey()).setIssuer(jwtAuthInfo.getIssuer()).setIssuedAt(new Date(jwtAuthInfo.getIssueTime())).setExpiration(new Date(jwtAuthInfo.getExpirationTime())).setNotBefore(new Date(jwtAuthInfo.getEffectiveTime())).setAudience(jwtAuthInfo.getTokenUser());
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forName(algorithm);
builder.signWith(signatureAlgorithm, signatureKey);
return builder.compact();
}
}
@ConditionalOnProperty(name="security.enable",havingValue="true")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private SecurityConfigProperties securityConfigProperties;
@Resource
private JwtConfigProperties jwtConfigProperties;
@Resource
private UserDetailsService userDetailsService;
// 处理业务的认证管理器
@Resource
private List<CustomerAuthenticationProvider> authenticationProviderList;
@Resource
private CustomerAuthenticationSuccessHandler customerAuthenticationSuccessHandler;
@Resource
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
for (String url : securityConfigProperties.getUrls()) {
registry.antMatchers(url).permitAll();
}
//允许跨域请求的OPTIONS请求
registry.antMatchers(HttpMethod.OPTIONS).permitAll();
//任何请求需要身份认证
registry.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/**/*.js").permitAll()
.antMatchers("/**/*.css").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/**/*.html").permitAll()
.antMatchers("/**/*.ftl").permitAll()
.antMatchers(jwtConfigProperties.getRefreshTokenUrl()).permitAll()
.anyRequest()
// 允许认证过的用户访问
.authenticated()
// 关闭跨站请求防护及不使用session
.and()
.csrf()
.disable()
// //因为使用JWT,所以不需要HttpSession
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// 自定义权限拒绝处理类
.and()
//允许配置错误处理
.exceptionHandling()
.accessDeniedHandler(restfulAccessDeniedHandler())
.authenticationEntryPoint(restAuthenticationEntryPoint())
// 自定义权限拦截器JWT过滤器
.and()
.addFilterAt(loginAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)//登录Filter
// 在指定的Filter类之前添加过滤器, 使用自定义的 Token过滤器 验证请求的Token是否合法
.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 省份认证接口
* @param auth 用来配置认证管理器AuthenticationManager
* 装配自定义的Provider
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// jwt; 多个就配置多个
for (CustomerAuthenticationProvider customerAuthenticationProvider : authenticationProviderList) {
auth.authenticationProvider(customerAuthenticationProvider);
}
// 此处自定义userDetailsService;
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
/**
* 强散列哈希加密实现
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public LoginAuthenticationFilter loginAuthenticationFilter() throws Exception{
LoginAuthenticationFilter loginAuthenticationFilter = new LoginAuthenticationFilter(authenticationManagerBean(), securityConfigProperties.getLoginPath());
// 自定义实现login success
loginAuthenticationFilter.setAuthenticationSuccessHandler(customerAuthenticationSuccessHandler);
return loginAuthenticationFilter;
}
@Bean
public RestfulAccessDeniedHandler restfulAccessDeniedHandler() {
return new RestfulAccessDeniedHandler();
}
@Bean
public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
return new RestAuthenticationEntryPoint();
}
@Slf4j
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
log.info(request.getRequestURI() + authException.getMessage());
response.getWriter().println(JSONUtil.parse(CommonResult.buildFailure(StatusEnum.UNAUTHORIZED)));
response.getWriter().flush();
}
}
@Slf4j
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
log.info(request.getRequestURI() + e.getMessage());
response.getWriter().println(JSONUtil.parse(CommonResult.buildFailure(StatusEnum.NO_OPERATION_PERMISSION)));
response.getWriter().flush();
}
}
jwt配置
@Component
@Data
public class JwtConfigProperties {
@Value("${jwt.token-type:jwt}")
private String tokenType;
@Value("${jwt.authorization-header-name:Authorization}")
private String authorizationHeaderName;
// jwt 放开的请求
@Value("#{'${jwt.permissive-request-urls:login}'.split(',')}")
private List<String> permissiveRequestUrls;
@Value("${jwt.signature-algorithm:HS256}")
private String algorithmName;
@Value("${jwt.token-header-prefix:Bearer }")
private String tokenHeaderPrefix = "Bearer ";
@Value("${jwt.signature-key:xxxxx}")
private String signatureKey;
@Value("${jwt.token-expire-second:300000}")
private Long tokenExpireSecond = 300000L;
@Value("${jwt.refresh-token-url:/**/token/refresh}")
private String refreshTokenUrl;
@Value("${jwt.refresh-token-expired-second:400000}")
private Long refreshTokenExpiredSecond= 86400000L;
@Value("${jwt.refresh-header-name:Refresh-Token}")
private String refreshHeaderName;
@Value("${jwt.user-key:userKey}")
private String userKey;
}
@Getter
@Setter
@Component
public class SecurityConfigProperties {
@Value("#{'${security.url.ignored:login}'.split(',')}")
private List<String> urls = new ArrayList<>();
@Value("${security.url.login.path:wx/login}")
private String loginPath;
@Value("${security.url.login.request-data-type}")
private String requestDataTypeName;
}
#####设置认证类
@Getter
@Setter
public class DefaultMiniprogramAuthenticationData extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
/**小程序访问Code*/
private String accessCode;;
/**小程序会话key*/
private String sessionKey;
/**小程序openId*/
private String openId;
/**小程序unionId*/
private String unionId;
public DefaultMiniprogramAuthenticationData() {
super(Collections.emptyList());
}
public DefaultMiniprogramAuthenticationData(MiniprogramUserDetail user, String sessionKey) {
super(Collections.emptyList());
super.setDetails(user);
super.setAuthenticated(true);
this.sessionKey = sessionKey;
}
@Override
public Object getPrincipal() {
return this.getDetails();
}
@Override
public Object getCredentials() {
return this.accessCode;
}
}
@Component
@Slf4j
public class MiniprogramAuthenticationProvider implements CustomerAuthenticationProvider {
@Autowired
private ProgramWechatUserManager programWechatUserManager;
@Autowired
private MiniprogramAccountService miniprogramAccountService;
@Autowired
private ApplicationProperties applicationProperties;
@Autowired
private ProgramConfigProperties programConfigProperties;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
log.info("MiniprogramAuthenticationProvider");
if (!(authentication instanceof DefaultMiniprogramAuthenticationData)) {
return null;
}
DefaultMiniprogramAuthenticationData authenticationToken = (DefaultMiniprogramAuthenticationData)authentication;
String accessCode = authenticationToken.getAccessCode();
try {
UserinfoResponse userInfo = programWechatUserManager.getUserInfo(accessCode);
if (userInfo == null) {
if (log.isErrorEnabled()) {
log.error("使用访问令牌{}无法获取到用户信息", accessCode);
}
throw new BadCredentialsException("无法识别用户");
} else {
String sessionKey = userInfo.getSessionKey();
String openId = userInfo.getOpenid();
String unionId = userInfo.getUnionid();
DefaultMiniprogramAuthenticationData authenticationProgramToken = new DefaultMiniprogramAuthenticationData(new MiniprogramUserDetail(miniprogramAccount), sessionKey);
authenticationToken.setUnionId(unionId);
authenticationToken.setOpenId(openId);
return authenticationProgramToken;
}
} catch (Exception var10) {
throw new RuntimeException(var10);
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(DefaultMiniprogramAuthenticationData.class);
}
}
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我已经从我的命令行中获得了一切,所以我可以运行rubymyfile并且它可以正常工作。但是当我尝试从sublime中运行它时,我得到了undefinedmethod`require_relative'formain:Object有人知道我的sublime设置中缺少什么吗?我正在使用OSX并安装了rvm。 最佳答案 或者,您可以只使用“require”,它应该可以正常工作。我认为“require_relative”仅适用于ruby1.9+ 关于ruby-主要:Objectwhenrun
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano
我使用Ember作为我的前端和GrapeAPI来为我的API提供服务。前端发送类似:{"service"=>{"name"=>"Name","duration"=>"30","user"=>nil,"organization"=>"org","category"=>nil,"description"=>"description","disabled"=>true,"color"=>nil,"availabilities"=>[{"day"=>"Saturday","enabled"=>false,"timeSlots"=>[{"startAt"=>"09:00AM","endAt"=>
我想获取模块中定义的所有常量的值:moduleLettersA='apple'.freezeB='boy'.freezeendconstants给了我常量的名字:Letters.constants(false)#=>[:A,:B]如何获取它们的值的数组,即["apple","boy"]? 最佳答案 为了做到这一点,请使用mapLetters.constants(false).map&Letters.method(:const_get)这将返回["a","b"]第二种方式:Letters.constants(false).map{|c
我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是