草庐IT

阿里云短信服务

一只咸鱼秦 2023-04-24 原文

阿里云短信服务接口(Springboot例)

一、准备工作

1、进入阿里云官网,注册,登录,进入短信服务模块

顶部菜单——产品——热门产品——短信服务

免费开通(学习阶段用免费的,测试用),正式项目里需要花钱购买

2、进入费用页面,充值点测试费用。一条短信几分钱,一般充一块钱,测试就够了。

3、左侧栏——快速学习——API发送测试——绑定测试手机号。学习测试阶段,只能给自己绑定的手机号发短信。给别的手机号发短信,收不到。

测试签名模板,就是短信内容的格式。测试的我们不能自定义,只能用官方提供的。购买之后,我们可以自定义短信模板。

完成操作后,点击“调用API发送短信”,进入控制台。

4、点击页面右侧“获取AK”,获取AccessKey ID和AccessKey Secret,代码里要用到。

点击“继续使用AccessKey”,也可以创建子用户。其实就是主用户和次用户的区别。

5、刚开始是没有AccessKey的,我们新创建一个。

创建好的id为账号,Secret为密码,我们需要记住或者保存下来(后期需要也可以过来查看,但是需要验证码才能获取密码,很麻烦)。建议存起来。

至此,阿里云网站上的操作进行完了。

二、Maven项目中代码

1、回到阿里云短信服务控制台,复制官方提供的依赖。建议使用新版SDK。

<!--阿里云短信服务-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>dysmsapi20170525</artifactId>
            <version>2.0.16</version>
        </dependency>

粘贴依赖到pom.xml里,刷新pom.xml。

2、创建SMSUtils工具类。

@Slf4j
@Component
public class SMSUtils {

    /**
     * 使用AK&SK初始化账号Client
     * @param accessKeyId
     * @param accessKeySecret
     * @return Client
     * @throws Exception
     */

    // 这里填写网站获取的accessKey
    static String accessKeyId = "XXXX";
    static String accessKeySecret = "XXXX";

    public static com.aliyun.dysmsapi20170525.Client createClient(String accessKeyId, String accessKeySecret) throws Exception {
        Config config = new Config()
                // 您的 AccessKey ID
                .setAccessKeyId(accessKeyId)
                // 您的 AccessKey Secret
                .setAccessKeySecret(accessKeySecret);
        // 访问的域名
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new com.aliyun.dysmsapi20170525.Client(config);
    }

    // 生成4位随机数
    public static String smsCode(){
        //  传统方式遍历生成4位随机数
        /*String code = "";
        Random random = new Random();
        // 生成9以内随机数
        for (int i=0;i<=3;i++){
            int randomInt = random.nextInt(9);
            code = code + randomInt;
        }
        String msg = "{\"code\":\""+code+"\"}";
        log.info("msg = "+msg);*/

        // 用自带工具类,生成指定位数的随机码。
        String code = RandomStringUtils.randomNumeric(4);
        String msg = "{\"code\":\""+code+"\"}";
        log.info("msg = "+msg);
        return code;
    }
    
    // 发送验证码之后,返回验证码
    public String sendCode(String phoneNumber) throws Exception{
        //这里的accessKeyId需要我们填入刚刚添加的AccessKey的账号,后面那个参数为密码
        com.aliyun.dysmsapi20170525.Client client = SMSUtils.createClient(accessKeyId, accessKeySecret);
        // 查看发送信息
        String code = smsCode();
        log.info("code = "+code);
        String msg = "{\"code\":\"" + code + "\"}";
        log.info("msg = "+msg);
        //发送
        SendSmsRequest sendSmsRequest = new SendSmsRequest()
                //短信的签名  测试阶段不能改
                .setSignName("阿里云短信测试")
                //短信的模板码  测试阶段不能改
                .setTemplateCode("SMS_154950909")
                // 测试手机号  与绑定的测试手机号一致
                .setPhoneNumbers(phoneNumber)   // 这里手机号只能填18039934120,已经绑定过的测试号。实际开发中可自定义手机号
                //code后面的值为验证码,code的值只支持4-6位纯数字
                // 最终格式: {"code":"1234"}
                .setTemplateParam("{\"code\":\"" + code + "\"}");
        RuntimeOptions runtime = new RuntimeOptions();
        try {
            // 复制代码运行请自行打印 API 的返回值
            SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
            // 响应结果转为json返回
            ObjectMapper objectMapper = new ObjectMapper();
            String respBody = objectMapper.writeValueAsString(sendSmsResponse.body);
            log.info("响应信息为:"+respBody);
        } catch (TeaException error) {
            // 如有需要,请打印 error
            com.aliyun.teautil.Common.assertAsString(error.message);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            // 如有需要,请打印 error
            com.aliyun.teautil.Common.assertAsString(error.message);
        }
        return code;
    }

}

3、写发送短信的controller。调用短信服务后,我们将返回的验证码保存到redis里,并设置时长,比如一分钟有效,并设置一个是否过期的key,如果有未过期的验证码,不能重复发送短信。

RedisPrfix.java

接口中的常量都是静态常量,所以我们可以定义一些常用的前缀、常量到里面。

public interface RedisPrfix {
    // 接口中的数据都为静态常量  public static
    String SESSION_KEY = "session_";
    // 是否有未过期验证码的key
    String TOEKN_TIMEOUT = "timeout_";
    // 保存验证码的key
    String TOKEN_CODEKEY = "phone_";
    // 编解码格式
    String CHARACTERENCODING = "UTF-8";
}

smsController.java

发送验证码的控制器。

@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@JsonInclude(JsonInclude.Include.NON_NULL)  // 只要json中不为空的数据
public class MsgVo implements Serializable {   // 用来接收手机号+验证码
    @JsonProperty("phone")
    private String phone;
    @JsonProperty("captcha")
    private String captcha;
}
@RestController
@RequestMapping("/sms")
@Slf4j
public class SmsController {
    // reids模板注入
    @Autowired
    private RedisTemplate redisTemplate;
    // 阿里发验证码工具类
    @Autowired
    private SMSUtils smsUtils;

    /* 通过MsgVo对象来接收手机号、验证码。发送验证码,并将其存入redis
         1、redis中定义一个timeout_phone,记录该手机号是否存在未过期验证码。
         有效期一分钟,一分钟之内不允许重复发送验证码
         每次发送新的验证码,更新timeout_phone为true
         每次判断timeout_phone这个键是否存在,如果存在,则不能重复发送验证码
         2、redis有定义一个“phone”_+phone的键,值为新获取的验证码
         有效期十分钟,十分钟内该验证码有效
     */

    // 前端点击“发送验证码”,就请求这个方法
    @PostMapping("/captchas")
    public void captchas(@RequestBody MsgVo msgVo){
        // redis操作对象
        ValueOperations valueOperations = redisTemplate.opsForValue();

        // 获取手机号
        String phone = msgVo.getPhone();
        log.info("phone = "+phone);

        // 每次发送验证码之前,检验该手机号是否存在没有过期的验证码
        if (redisTemplate.hasKey(RedisPrfix.TOEKN_TIMEOUT+phone)){
            // 如果存在,则提示不能重复发送  抛出异常,方法中止步
            throw new RuntimeException("提示:不能重复发送");
        }

        // 如果不存在未过期验证码,则发送新验证码,并存入redis
        try {
            // 获取发送的验证码
            String code = smsUtils.sendCode(phone);
            // 验证码放入redis  有效期600秒,即十分钟
            valueOperations.set(RedisPrfix.TOKEN_CODEKEY+phone,code, Duration.ofSeconds(60*10));
            // 更改未过期验证码的redis  有效期60秒,即一分钟
            valueOperations.set(RedisPrfix.TOEKN_TIMEOUT+phone,"true",Duration.ofSeconds(60*2));
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("短信发送失败!");
        }

    }

}

接收验证码,比对验证码,登录控制器。

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Autowired
    private IUserService userService;
    @Autowired
    private RedisTemplate redisTemplate;

    /* 用户登录
        点击登录之后,根据该手机号在redis里查询真实的验证码,与用户输入的验证码对比。
        若不对,则抛异常。若对,在数据库查询该手机号是否已存在。
        如果不存在,则视为注册,添加用户。并在map存入<"token",sessionId>
        如果存在,直接在map存入,<"token",sessionId>。
        并在redis存入该用户信息,key=sessionId,value=User
        后续可以根据sessionId查询登录用户的信息
     */
    @PostMapping("/tokens")  // MsgVo = phone + captcha,接收前台对象
    public Map<String,Object> tokens(@RequestBody MsgVo msgVo, HttpSession session){
        // redis操作对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        // map集合,保存返回值
        Map<String,Object> resultMap = new HashMap<>();
        // 获取手机号和验证码
        String phone = msgVo.getPhone();
        String code = msgVo.getCaptcha();
        log.info("phone = "+phone+"  captcha = "+code);

        // 获取redis中保存的验证码,先判断是否存在,如果存在,则对比用户输入的验证码
        if (!redisTemplate.hasKey(RedisPrfix.TOKEN_CODEKEY+phone)){
            // 如果不存在该key,则验证码过期了。
            throw new RuntimeException("验证码过期了,请重新发送!");
        }
        // 如果没过期,进行对比
        String realCode = (String) valueOperations.get(RedisPrfix.TOKEN_CODEKEY+phone);
        if (!StringUtils.equals(code,realCode)){
            // 空字符串与非空字符串比较,也是false,所以不进行输入验证码的非空判断
            throw new RuntimeException("验证码输入错误!");
            // 抛出异常后,程序终止
        }

        // 如果验证码输入正确,判断是否是第一次登录
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq("phone",phone);
        User user = userService.getOne(queryWrapper);
        log.info(String.valueOf(user));
        // 如果用户不存在,执行注册方法。如果存在,直接跳过,进行下一步
        if(ObjectUtils.isEmpty(user)){    // 为什么空user不能赋值?不能调用getClass方法?
            User newuser = new User();
            newuser.setPhone(phone);
            newuser.setName(phone);  // 初始用户名为手机号
            newuser.setAvatar("http://t14.baidu.com/it/u=4235480105,3516779440&fm=224&app=112&f=JPEG?w=400&h=400");  // 初始头像
            newuser.setCreatedAt(LocalDateTime.now());  // 创建时间
            newuser.setPhoneLinked(true);  // 是否绑定手机号
            newuser.setWechatLinked(false);  // 是否绑定微信
            newuser.setIntro("这个人很懒,什么也没留下");  // 个人简介
            newuser.setOpenid(null);   // 微信OpenId
            newuser.setUpdatedAt(LocalDateTime.now());  // 更新时间
            newuser.setFollowingCount(0);  //关注数
            newuser.setFollowersCount(0);  // 粉丝数
            // 调用insert方法,注册
            userService.save(newuser);
        }
        // 保存用户登录标记
        String token = session.getId();  // 获取sessionId
        resultMap.put("token",token);  // 前台根据token从后台获取user
        // 将sessionId=user保存到redis  登录信息保存1天
        valueOperations.set(RedisPrfix.SESSION_KEY+token,user, Duration.ofSeconds(60*60*24*3));
        log.info("生成token:"+token);

        // 返回
        return resultMap;
    }

    // 查询已登录用户信息
    @GetMapping("/user")
    @RequriedToken  // 自定义注解,只要加了这个注解,使用拦截器,直接拿到用户信息。传入的参数token,也可以用过req.getParameter(“token”)获取
    public User loginUser(HttpServletRequest request){
     // 无注解方式   User user = (User) redisTemplate.opsForValue().get(RedisPrfix.SESSION_KEY+token);
        User user = (User) request.getAttribute("user");
        log.info("登录用户信息:"+user);
        return user;
    }

    // 注销,退出登录。从redis删除用户信息
    @DeleteMapping("/tokens")
    public void exist(String token){
        log.info("要删除的token:"+token);
        redisTemplate.delete(RedisPrfix.SESSION_KEY+token);
    }

三、PS

ps1:Springboot集成redis

pom.xml依赖:
 <!--springboot跟redis关联的一个依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--redis数据库连接池的相关依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
application.yml:

spring:
  application:
    name: sms-api-module
  cloud:
    nacos:
      server-addr: 192.168.58.16:8848
  datasource:
    url: jdbc:mysql://192.168.58.16:3306/yingxue?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver
  redis:
    host: 192.168.58.16    #redis主机的ip
    port: 6379   #redis端口号
    jedis:
      pool:
        max-active: 500
        max-idle: 50
        min-idle: 10
        max-wait: 30000
server:
  port: 8074

mybatis-plus:
  mapper-locations: classpath:com/qhx/mapper/xml/*Mapper.xml
redisConfig.java

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        StringRedisSerializer stringRedisSerializer= new StringRedisSerializer();
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        return redisTemplate;
    }
}

控制器使用的时候:
    // reids模板注入
    @Autowired
    private RedisTemplate redisTemplate;

ps2:自定义注解

//1、自定义一个接口先:RequriedToken.java
   
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@Target(ElementType.METHOD) // 加载方法上
public @interface RequriedToken {
}


// 2、拦截器赋予其功能,如果控制器方法上有这个注解,自动执行操作。
// 此例:如果控制器上有@RequriedToken注解,自动获取请求携带的token参数,自动查询redis里保存的登录用户信息,并存入request作用域。用户无需主动接收token(参数里可以不写,但是要有HttpServletRequest req),通过req作用域获取登录用户的信息   TokenInterceptor.java

@Component
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private RedisTemplate redisTemplate;

    // 前置拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // token:xxx
        // tokenKey:user
        // 获取当前请求上是否有RequeriedToken注解
        boolean requiredToken = ((HandlerMethod)handler).getMethod().isAnnotationPresent(RequriedToken.class);
        System.out.println(("是否存在注解:" + requiredToken));
        // 如果存在该注解,则在req作用中取
        if (requiredToken){
            // 获取token信息
            String token = request.getParameter("token");
            System.out.println(("收到的token:" + token));
            // 判断token是否为空
            if (StringUtils.isEmpty(token)){
                // 如果token为空,则存入token为空user为空,提前放行
                request.setAttribute("user",null);
                request.setAttribute("token",null);
                //return true;   // 这里该返回还是抛异常
                throw new RuntimeException("无效token");
            }
            // 根据token获取用户信息
            // redis操作对象
            User user = (User) redisTemplate.opsForValue().get(RedisPrfix.SESSION_KEY+token);
            // 如果不存在该用户信息,抛异常
            if (ObjectUtils.isEmpty(user)){
                throw new RuntimeException("令牌无效!无效token");
            }
            // 将用户保存到req作用域
            request.setAttribute("token",token);
            request.setAttribute("user",user);
        }
        return true;
    }
}

// 3、config里配置该拦截器。
@Configuration
@Slf4j
public class MyInterceptorConfig implements WebMvcConfigurer {
	// 注意,由于Spring工厂的加载机制,拦截器先于Bean的创建。所以此处要先进行Bean的实例化,在进行拦截器的执行。否则拦截器里注入的Bean(包括redisTemplate等)不起效果,会报错。
    @Bean
    public TokenInterceptor getTokenInterceptor(){
        return new TokenInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加TokenInterceptor到注册机里,拦截所有请求,也可以排除静态资源或某些请求。
        registry.addInterceptor(getTokenInterceptor()).addPathPatterns("/**");
    }
}

ps3:阿里云短信流控

获取短信验证码时,突然某次提示短信发送失败,查了发现错误信息如下:
{“Message”:“触发小时级流控Permits:5”,“RequestId”:“818EC3A3-A165-4501-9C6E-7EFCF145F579”,“Code”:“isv.BUSINESS_LIMIT_CONTROL”}
短信发送失败,原因:触发小时级流控Permits:5

解决:原来阿里云短信发送有默认的频率限制:
短信验证码 :使用同一个签名,对同一个手机号码发送短信验证码,支持1条/分钟,5条/小时 ,累计10条/天
短信通知: 使用同一个签名和同一个短信模板ID,对同一个手机号码发送短信通知,支持50条/日
{
// 添加TokenInterceptor到注册机里,拦截所有请求,也可以排除静态资源或某些请求。
registry.addInterceptor(getTokenInterceptor()).addPathPatterns(“/**”);
}
}


ps3:阿里云短信流控

> 获取短信验证码时,突然某次提示短信发送失败,查了发现错误信息如下:
> {“Message”:“触发小时级流控Permits:5”,“RequestId”:“818EC3A3-A165-4501-9C6E-7EFCF145F579”,“Code”:“isv.BUSINESS_LIMIT_CONTROL”}
> 短信发送失败,原因:触发小时级流控Permits:5
>
> 解决:原来阿里云短信发送有默认的频率限制:
> 短信验证码 :使用同一个签名,对同一个手机号码发送短信验证码,支持1条/分钟,5条/小时 ,累计10条/天
> 短信通知: 使用同一个签名和同一个短信模板ID,对同一个手机号码发送短信通知,支持50条/日

有关阿里云短信服务的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  6. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  7. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  8. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  9. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

  10. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

随机推荐