从C#.net到RoR - GuruDigger的的迁移经验分享

===广告部分,想看技术部分的可以直接跳过 ===

GuruDigger是一个面向web开发者的社区,能够从用户认证通过的Email 出发,自动爬遍互联网,根据用户在互联网上的活动进行分析,对掌握的每项编程语言技能进行评分和排名:

从C#.net到RoR - GuruDigger的的迁移经验分享_第1张图片


还有头脑风暴板块和Guru燃料功能能够帮助用户实现他的各种好玩,甚至是稀奇古怪的Idea:

从C#.net到RoR - GuruDigger的的迁移经验分享_第2张图片


===广告结束,以下是迁移经验分享部分===

GuruDigger之前是用C#.net开发的,最近迁移到了rails 3(3.0.9),迁移的主要原因:rails生产力高,便于后续维护。

因为是业余时间做的,前后差不多用了2个月的时间,统计了一下,我累计用了120多个小时
另外一个开发者 linjun,因为是第一次接触ruby的关系,包括学习和开发,累计用了200多个小时。

对于后台开发,rails的效率没的说,但是前端还不给力,虽然和2.0 相比, 3.0的jquery + ujs好用了很多,可是很多时间还是花费在了和js/html/css上,这和我们2个开发人员都不擅长前端也有关系,目前用的css都是照扒旧版本的,后来还有专门的前端工程师帮我们改了很多。

1. 用户认证
Rails项目用户认证当然首选 devise,短短的几行配置和代码,就可以搞定一切关于注册,登录,忘记密码等一系列用户认证相关功能,再配合 omniauth,还可以实现Google/Twitter/QQ/新浪微薄等第3方帐号登录。

但是GuruDigger旧版本记录的用户密码是MD5加密,不够安全,所以在迁移的时候对devise做了一个扩展,将旧系统的MD5密码导入数据库(legacy_password_hash),用户在新系统第一次登陆的时候先用旧密码判断一下,进行密码迁移:
module Devise
  module Models
    module DatabaseAuthenticatable      
      def valid_password_with_legacy?(password)
        if self.legacy_password_hash.present?
          if ::Digest::MD5.hexdigest(password).upcase == self.legacy_password_hash
            self.password = password
            self.legacy_password_hash = nil
            self.save!
            return true
          else
            return false
          end
        else
          return valid_password_without_legacy?(password)
        end
      end

      alias_method_chain :valid_password?, :legacy
    end

    module Recoverable
      def reset_password_with_legacy!(new_password, new_password_confirmation)
        self.legacy_password_hash = nil
        reset_password_without_legacy!(new_password, new_password_confirmation)
      end

      alias_method_chain :reset_password!, :legacy
    end
  end
end


2. 权限
使用的是cancan,它能够让你在一个集中的地方定义权限(Ability 类),而不需要分散在controller,views或者数据库查询中添加各种重副代码,比如我们定义了用户只能编辑、删除自己的idea或者project,只用在Ability里面定义如下:
can :manage, [Idea, Project, UserEmail], :user_id => user.id


在Idea Controller里面,就只用cancan提供的helper method load_and_authorize_resource加载当前用户有权限的idea,非常方便:
class IdeasController < ApplicationController
  load_and_authorize_resource
  def update
    @idea.update_attributes!(params[:idea])
  end
end


3. 文本编辑
因为是面向web开发者的社区,所以文本编辑器方面用了和Github一样的markdown语法,在前端编辑器用的是 markitup,在后端markdown render html的功能上,使用了 redcarpet,因为他采用了Ruby wrap C代码,所以性能很不错。但是markdown本身不支持嵌入flash等功能,有时候用户还是需要贴个视频的,所以做了一个扩展支持flash,利用markdown的img标签,先生成一个flash为前缀的地址:
{name:'Flash', key:'F', placeHolder:'![600x450](flash:http://www.youtube.com/v/9I9SkGwJNOA)'}

然后后端用正则表达式转换一下:
class MarkdownExt < Redcarpet
  def to_html
    super.gsub(/<img src="flash:(.*?)" alt="(\d+).(\d+)">/) do |match|
      flash_tag $1, $2, $3
    end
  end

  protected
  def flash_tag(url, width, height)
    #...
  end
end


4. 任务队列
GuruDigger有很多功能需要用到异步的任务队列,比如调用爬虫分析数据,同步到Twitter,发送电子邮件等,我们采用了 resque,它基于Redis,提供了非常方便的任务队列框架,另外还有完善的维护功能,以同步到Twitter为例子,在需要同步的时候,只需要调用Resque的入队列方法(enqueue),而另外写一个perform方法来处理队列内的数据:
module TwitterUpdater
  def self.included(model)
    model.class_eval {
      attr_accessor :update_twitter

      def update_to_twitter(message)
        if self.update_twitter.to_i == 1 && user.twitter_access_token.present?
          Resque.enqueue(TwitterUpdater, user.id, message)
        end
      end
    }
  end

  @queue = :twitter_updater

  def self.perform(user_id, message)
    p "TwitterUpdater processing #{user_id}, #{message}"
    user = User.find(user_id)
    token, secret =  user.twitter_access_token.split(":")
    client = TwitterOAuth::Client.new(
      :consumer_key => TwitterConfig['consumer_key'], :consumer_secret => TwitterConfig['consumer_secret'],
      :token => token, :secret => secret
    )

    if client.authorized?
      client.update(message + " via @GuruDigger")
    else
      user.update_attributes(:twitter_access_token => nil)
    end
  end
end


5. 全文搜索
使用了 sunspot插件,他后台服务器是solr(基于Java的Lucene),solr对于小规模应用来说安装和配置相当简单,并且有很好的扩展性,对于大数据量的全文搜索,可以使用它的cluster功能,默认支持的中文是单字分词,我们可以使用mmseg分词算法: mmseg4j

Sunspot是通过ActiveRecord的create/update/destroy callback来更新/删除索引,只需要在model里面定义需要索引的属性就可以了:
  searchable do
    text :name
    text :introduction
    text :address
  end


如果想让索引的工作变成异步,还可以使用 sunspot_index_queue这个addon,也可以使用前面提到的Resque来自己扩展一下。

6. 其他用到的杂项gem
A. 客户端校验
使用了 client_side_validations插件,好处是只需要在model上定义validate rule,就能够被客户端的js重用,不过和纯客户校验的jquery validate相比,不够灵活。

B. Google Map插件 gmaps4rails

C. 用户头像gravatar显示插件gravatar-ultimate

D. Tag插件acts-as-taggable-on

E. 评论插件acts_as_commentable_with_threading

F. 支付插件activemerchant

===总结===
迁移框架是个体力活,要保证数据正确,功能一致,同时还要加上一些新功能,不过RoR的框架越来越成熟,各种gem,plugin经过这几年的发展,组合和扩展非常方便,完成一个项目很像搭乐高玩具的感觉。同时期待Rails 3.1在前端的改进(coffeescript sass)能够提高前端的开发效率。

你可能感兴趣的:(.net)