devise配置登录超时和单处登陆

1.配置用户超时
<Project>/config/initializers/devise.rb
编辑 # config.timeout_in = 30.minutes 这一行,修改为想要配置的时间

<Project>/app/models/user.rb
配置devise :database_authenticatable, :registerable,这一行,加上 :timeoutable

2.设置用户单处登陆
原理:devise在用户登陆后会设置users表中的current_sign_in_at字段为最后一次登陆的时间,用户每次登陆后在session中保存current_sign_in_at字段,在application中校验session中current_sign_in_at字段,如果为空或者小于current_user对象的current_sign_in_at则强制注销
1) 在application_controller基类中定义after_sign_in_path_for方法在当中设置session对象

  def after_sign_in_path_for(resource)
    session[:current_sign_in_at] = current_user.current_sign_in_at
    root_path
  end

2) 定义has_signed取代:authenticate_user!判断用户登陆

  def has_signed
    unless user_signed_in?
      redirect_to root_path
    else
      if session[:current_sign_in_at] == nil or session[:current_sign_in_at] < current_user.current_sign_in_at
        redirect_to destroy_user_session_path
      end
    end
  end

3)在需要判断用户登陆的controller中添加before_action :has_signed过滤器。

rails判断浏览器head设置多语言

给rails系统加i18n,根据浏览器的头去识别用户语言。记录一下相关实现:

1. 添加i18n配置
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.i18n.available_locales = [:en, :cn]
config.i18n.default_locale = :cn

2. 获取语言设置

# app/controller/application_controller.rb
before_action :set_locale
# set local {{{
def set_locale
  I18n.locale = get_locale
end
# }}}

# locale {{{
def get_locale
  locale = I18n.default_locale

  if params[:locale]
    locale = params[:locale].downcase
  elsif cookies[:locale]
    locale = cookies[:locale]
  elsif locale = get_accept_language
    locale
  end
  # set cookies
  if cookies[:locale] != locale
    cookies[:locale] = locale
  end

  locale
end

def get_accept_language
  begin
    first_lang = request.env['HTTP_ACCEPT_LANGUAGE'].split(';').first
    default_lang = first_lang[0,2].downcase

    if default_lang == 'zh'
      first_lang[3,2].downcase
    else
      default_lang
    end
  rescue
    nil
  end
end
# }}}

说明:locale的值优先从url地址读取,次之从cookie,最后从浏览器头判断。英文浏览器的头为Accept-Language:en-US,en;,取en即前两位。中文浏览器头为Accept-Language:zh-CN,zh;,取cn即3-5位,转为小写。

3.编辑语言文件
# config/locales/en.yml
en:
 hello: "Hello world"
# config/locales/cn.yml
cn:
 hello: "你好"

4. 在模板文件中应用
# app/views/home/index.html.erb
<p><%= t('hello'); %></p>
<ul>
  <% I18n.available_locales.each.each do |locale| %>
    <li><%= link_to locale, params.merge(locale: locale) %></li>
  <% end %>
</ul>
说明:点击生成的链接可以手动切换语言,测试的时候方便一些。

devise使用ajax登录

devise默认已经有较好的ajax支持了,只需要简单的配置一下即可使用ajax登录/注册。

1. 修改config/application.rb加下以下配置,启用json输出。

# devise respond_to json
config.to_prepare do
  DeviseController.respond_to :html, :json
end

2. 修改form标签,添加data-remote, data-type,设置id

<%= form_for(resource, :as => resource_name, :url => session_path(resource_name), :remote => true,
 html: {id: 'ajax_user_signin', data: {type: :json}}) do |f| %>

3. 设置ajax头

$.ajaxSetup({
  beforeSend: function(xhr){
    var token;
    token = $('meta[name="csrf-token"]').attr('content');
    if (token) {
      xhr.setRequestHeader('X-CSRF-Token', token);
    }
  }
});

4. js处理代码

$('#ajax_user_signin').on('ajax:complete', function(e, xhr, type){
  if (type === 'success') {
    location.href = '/';
  } else {
    try {
      alert(xhr.responseJSON.error);
      // this.reset();
    } catch (e$) {
      e = e$;
    }
  }
});

关于注册:
注册的方法和登录基本相同,给form_for添加data-remote、data-type和id等属性。需要注意的是注册返回的表单验证是多项错误,使用xhr.resonseJSON.errors获取错误集合。

rails使用devise做登录验证

使用devise这个gem只需要简单几步就能做好用户注册登录等功能,能节省大量时间和精力。

1. 安装devise
# 编辑Gemfile加入以下内容
gem 'devise'
#安装gem
bundle install
#初始化devise
rails g devise:install
#初始化model为user
rails g devise user
#初始化视图
rails g devise:views
# 创建数据库
rake db:migrate

2. 使用
# 创建一个空的controller
rails g controller home index --no-helper --no-assets
# 编辑config/routes.rb将home index设为首页
root 'home#index'
# 编辑app/views/home/index.html.erb加入以下内容

<h1>Home#index</h1>
<% if user_signed_in? %>
  hello, <%= current_user.username %><br />
  <%= link_to('登出', destroy_user_session_path, :method => :delete) %>
<% else %>
  <%= link_to('注册', new_registration_path(:user)) %>
  <%= link_to('登录', new_session_path(:user)) %>
<% end %>

此时访问http://localhost:3000就可以使用注册登录功能了。
另附两个有用到的链接:
1. 添加用户名登录 How To: Allow users to sign in using their username or email address
2. 添加管理员角色 How To: Add an Admin Role

吐槽QQ空间

自从装了QQ,总习惯点开QQ空间,久之成瘾。最近改版之后弄得很郁闷,每次打开照片预览,“猜你喜欢”都正好挡住照片评论。

吐槽豆瓣4则

经常到豆瓣听民谣和DJ录音,用了大半年,有几点比较不爽,吐槽之

1. 奇葩的音乐人搜索
在music.douban.com上面的搜索,输入“宋冬野”,自动完成的提示里面有音乐人的小站,搜索的结果却另一翻模样。这个自动完成的功能是新加的,之前在这个位置搜不出音乐人。

如果你想搜索音乐人,得点到音乐人页面,拖到第二屏,用藏着的一个搜索框

2. 同一个页面多个专辑的播放不能互斥。
同一个页面,点开了一张专辑的歌曲,再播放另一张专辑的歌曲时。先前的播放不能自动停止。

3. 总是飘着的分享框
鼠标移动到播放列表中的rss图标,会出现一个悬浮的分享框。鼠标移到悬浮框,往右移出去,悬浮框会一直存在。在停止一首歌然后中键滚动的时候,肯定多出这个家伙在你眼前。打酱油的前端呀!

4. 分享的播放链接
分享的播放链接大约是一个这样的地址,http://site.douban.com/qikeqike8/room/1968340/?s=396923 会播放id为396923的歌曲。当一个页面列有大量歌曲的情况下,不知道当前播放的是哪一首。知道有个东西叫锚点么?不知道把页面滑动到正在播放的歌曲位置么?

用http代理屏蔽isp广告

最近装了“*城宽带”,发现打开网站时还会弹出广告。打电话给客服沟通,客服表示不知道他们有广告,声称是我本人电脑设置有问题。很无耐,写一代理用来屏蔽广告。

下载地址:http://pan.baidu.com/share/link?shareid=3835585554&uk=590772501#dir/path=%2Fpub%2Fother下载adbreak.zip
使用方法:
1. 解压软件包,点击adbreak.bat
软件启动后如下图:

2. 设置浏览器代理脚本配置,以IE为例。(firefox使用autoproxy、chrome使用proxy-switchysharp将指定网址的代理定向到 8337端口,使用goagent也可以直接修改GoAgent Pac文件,参照adbreak.pac)
工具->Internet选项->链接->局域网设置 勾选 “使用自动脚本配置” 在下面的“地址”这一栏填入 http://127.0.0.1:8337/break.pac

3. 设置屏蔽的网址:
编辑break.pac脚本,在 disable_url 中添加要屏蔽的网址。默认带的几个网址是用来屏蔽“*城宽带”这边的几个广告的。

4. 如何知道广告的网址?
首先建议使用抓包工具,如果没有抓包工具可以使用adbreak自带的代理来查看地址。
设置方法:将第2条中的设置地址改为http://127.0.0.1:8337/proxy.pac。
这时再用浏览器上网,所有的请求是走的adbreak自带的http代理访问,会将网址打印在地址栏中。使用浏览器浏览网页,弹出广告后,查看adbreak中的地址列表,找出广告地址。(你访问网址被拦截后的第一条)
由于cmd自带的缓冲区显示内容有限,需要将修改缓冲区大小,以显示足够多的请求地址。
设置方法:鼠右键点击adbreak标题栏,选择属性->布局 “将屏幕缓冲区大小”中的“高度”设为 5000

说明:程序使用nodejs运行,启用后占用 8337、8338 两个端口,8337端口有break.pac和proxy.pac两个文件。
使用break.pac文件时,当弹出ISP的广告时,break.pac脚本识别到广告地址将这个请求定向到8337端口,这时原请求地址被附加在广告请求地址的Referer中,代理读取referer地址,将请求重定向到原地址。
使用proxy.pac时,请求请重定向到8338端口,这时所请求的文件通过adbreak从网络获取,可以得到每一个请求的地址。用来从地址列表中找出广告的地址。

附adbreak的coffee源代码:

http = require 'http'
url = require 'url'

http.createServer (req, res)->
  request_url = req.url
  request_pac = request_url.match /\/((?:break|proxy).pac)((?:\?.*)?)/

  if request_pac isnt null
    console.log "[#{req.method}] #{request_url}"
    res.writeHead 200, 'Content-Type': 'application/x-ns-proxy-autoconfig'
    pac = require('fs').createReadStream request_pac[1]
    pac.pipe res
    pac.on 'end', ->
      res.end()
  else
    referer = req.headers.referer
    if referer
      console.log "[#{req.method}] -referer- #{request_url}"
      res.writeHead 302, 'Location': referer
    else
      res.writeHead 200, 'Content-Type': 'text/plain'
      console.log "[#{req.method}] -disable- #{request_url}"
      res.write 'no referer'

    res.end()

  return

.listen 8337, '0.0.0.0'
console.log 'start break server by http://127.0.0.1:8337'

http.createServer (req, res)->
  # 请求地址
  request_url = req.url
  # 请求记录
  console.log "[#{req.method}] #{request_url}"
  # 请求数据
  post_data = ''

  request_option = url.parse request_url

  request_option.method = req.method
  request_option.headers = req.headers

  # 接收数据
  req.on 'data', (chunk)->
    post_data += chunk
    return
  # 结束请求
  .on 'end', ->
    if request_option.method is 'POST' then proxy_request.end post_data else proxy_request.end()
    return
  # 开始代理请求
  proxy_request = http.request request_option, (result)->
    # header
    headers = result.headers
    # statusCode
    statusCode = result.statusCode
    # 写入头
    res.writeHead statusCode, headers
    # 写入代理数据
    result.on 'data', (chunk)->
      res.write chunk

      return
    # 数据结束
    result.on 'end', ->
      res.end()

      return

    return
  # 错误处理
  .on 'error', (error)->
    res.end "remote http.request error#{error}"
    return

  return

.listen 8338, '0.0.0.0'
console.log 'start proxy server by http://127.0.0.1:8338'

附使用截图:
“*城宽带”拦截网络访问弹自家广告抓包截图:

可以看到访问www.jd.com被强制转向到了count.chanet.com.cn上面,后面连续跳转了多个广告请求,还有骗京东自家的推广点击的。在第11个请求时,被定向到了京东首页。

代理的拦截效果截图:

访问优酷,被重定向到了ye87.net

ye87.net被代理拦截重新定向到了youku

adbreak打印的重定向信息

关于chrome浏览器移动web开发

之前预览移动页面,使用一款名为User-Agent Switcher的插件。这两天用chrome开发者工具修改设置时看到新版的chrome自带设置项可十分方便的预览移动页面。设置方法如下:

1. 打开chrome开发者工具,点右下角设置

2. 在设置页面切换到Overrides选项卡,设置User Agent和Device metrics两项即可。

还可以使用Chrome结合ADBPlugin调试安卓手机上的网页,具体信息参照:https://developers.google.com/chrome-developer-tools/docs/remote-debugging

玩家角度看《我叫MT》怎么做用户激励、付费激励

前段时间玩了下这款游戏,个人感觉游戏在用户活跃和付费激励方面设计得很好,从一个玩家的角度来说下这款游戏。

直接上图说明
1. 主界面

游戏主界面,大家能看到界面中两个红色中间带N的圆点。这两个点会一大一小的闪动,让你非常有想点击的欲望。

2. 用户激励
2.1 每天登陆送赠送

赠送的50符石(约相当于RMB 5元价值),体力用完后可点击恢复120点体力,8.8W金币,惊喜礼包一般是南瓜花或南瓜。

1.2 点斗积分

玩家达到35级以后,可以参加战斗系统,5场全胜,可以得230符石、若干金币,5场全输可以得160符石奖励。胜利以后声望增加,可以升级爵位,可以增加体力、领导力上限,每天可领取一定数量符石。

2.3活动副本

循环活动副本,出主角卡、南瓜、小金龙。主角卡用来升级打怪,南瓜可以给主角升级,小金龙可以卖金币。

3. 用户付费激励
3.1 体力购买

用户可用体力约为120点,副本每次消耗约6-8点,每隔几分钟可以回复1点,花费符石可1次购买120点体力,没有充值过的帐号每天仅可购买1次,充值后购买次数可增加,后期升级每天需大量体力,很多用户会充值。

3.2 战斗加速

左下角的"X2"代表当前游戏进行速度,默认是X1速度非常慢。X2后游戏进行速度会加快,可节省大量时间。普通用户可以X2,充值以后可以到X3。

3.3 月卡会员赠送

充值30块,你能得到什么,300符石?再送你3000符石(人民币300元),是不是感觉很超值?充30块钱月卡,一次获得等价符石,每天再送你100符石。刺激冲动型用户的付费欲望。

4. 卡牌进化系统

卡牌进化的过程如下:
1) 白色卡牌 升到25级 一套绿装  进化到绿色
2) 绿色卡牌 升到30级 一套蓝装  进化到蓝色
3) 蓝色卡牌 升到60级 一套精英蓝 进化到金边
4) 金边卡牌 此时不升级 一张蓝色卡牌 进化到金边+1
5) 金边卡牌+1 升到65级 两张蓝色卡牌 进化到金边+2
6) 金边卡牌+2 升级到70级 一张蓝色金边卡牌 进化到金边+3
7) 金边卡牌+3 升级到75级 一张紫色金边卡牌 进化到紫色
8) 紫色卡牌 此时不升级 一套紫装 进化到紫色金边
9) 依次+1 +2 +3,后面的没玩过。
说明:75级蓝卡+3升紫色,需要的金边卡牌是需要打副本掉落,或打碎片(掉率不高,且需要集齐80-120个)兑换到紫卡,升到75级,用一套紫装升到金边。一张紫卡的成本非常高,后期+1, +2, +3需要大量紫卡,如果玩家后期想要玩下去必须要充值。

5. 不定期活动
不定期会开放各种活动,上线送金币、符石,增加活动副本次数,增加卡牌掉率。你是玩家遇上这样的机会,还不得泡在线上。体力用完了,普通帐户只能买1次怎么办?亲,考虑下掏钱吧!

6. 总结:
6.1 这款游戏在吸引用户付费方面做了很多设计,原本与人民币等价兑换的符石,在游戏中普通玩家也可大量获得。针对普通玩家,使用购买体力、加速等方式让用户掏小钱(5块15块的)。针对想深入一点的玩家,月卡只需30块就可以多得300块钱的符石。让用户付出很少的代价就可以获得极大的心理价值反馈。针对高端玩家,后期紫卡升级可是无底洞,几千上万都填不满,煤老板有地方消费了。

6.2 游戏中金币系统设计存在比较大的问题,没有一个很明显的获取方式。活动副本的小金龙掉率低,而且掉的钱很快消耗掉。大量金币基本靠活动赠送,不知是有意为之还是存在bug。后期可以减少活动赠送,在商城里加一个符石购买金币,让用户掏钱。哈哈!

6.3 因为不存在用户关系,用户基本处于单人游戏环境。当进行到后期需要大量充值情况下,游戏对玩家吸引力减弱,缺乏一个长期可持续的游戏模式。

6.4 装备系统刚出来,自己还没刷出来过,冒似没掉率很低,不知又是一个吸金大坑还是鸡肋设计。

关于截图的一点总结

1. 去除滚动条
aau创建webform可以去掉滚动条,但测试的时候发现如果网页中对html设置了overflow-y:scroll;滚动条还是会显示出来,页面的doctype好象也可以影响滚动条的显示。最终使用代码将所以滚动条显示出来,在截图的时候裁掉滚动条。
计算滚动条代码:

var js = /**
    document.documentElement.style.overflowY='scroll';
**/
wb.doScript(js);

var scrollWidth = wb.document.documentElement.offsetWidth  - wb.body.clientWidth;

2. 触发layzload加载的图片
很多网站对图片使用了懒加载,图片显示不全,强制设置滚动条到底部可以解决大部份情况。
js添加一行
window.scrollTo(0, 100000);

3. 多用win.delay
最开始在使用setPos修改webform的宽度之后,因为我设置的宽度大于body的宽度截图出现头部居中主体内容未居中的情况,尝试在调用setPos以后调用win.delay(20)问题就解决了。在设置scroll的值触发图片懒加载以后也需要暂停一下让图片加载完成,不过要根据实际网页的情况去设置。