Rails 集成论坛包括 Discuz 和 Discourse

Terry.Shi · March 17, 2017 · Last by Terry.Shi replied at March 22, 2017 · 7541 hits
Topic has been selected as the excellent topic by the admin.

排版已吐 折腾了好久 踩了好多坑 也很有意思 当然还有写的有问题的地方需要指正

Discuz

主要实现

  • 主站登录,论坛为登录状态
  • 主站未登录去访问论坛,论坛跳转到主站,登陆后跳转回论坛
  • 论坛退出同时主站退出并跳转到主站退出后界面
  • 主站退出同步论坛退出

首先要介绍的是 discuz 的 ucenter 所提供的应用同步原理,我们通过 rails 调用一个登陆接口,这个接口返回用户信息,再以用户 id 为参数,调用同步的接口,这个接口返回一段 js,最后渲染这段 js 来操作浏览器设置 discuz 的 cookie 完成同步,登出同理。根据原理实现方式不尽相同,下面给出一种。

环境:

  • 主应用使用 devise。
  • discuz 3.2

写了一个 docker-compose 来快速搭建https://github.com/terryshi96/docker-for-discuz 源码采用 volume 的方式,方便修改。事实上把 html 里面源码换掉就能起其他 php 程序,然后最好是把 docker 镜像源换为国内的,容器内 apt-get 镜像源也换为国内的。

实现:

  • 添加加一个别人写的 gem ucenter,提供了与 UCenter api 通信的方法 https://github.com/MgaMPKAy/ucenter 这里直接在 gemfile 里面加 ucenter 将用的是另一个包,需要用 git 的方式添加

  • 添加初始化配置 initializers/ucenter.rb 主要提供 4 个参数 appid 是在 ucenter 中配置的应用 id,编码方式,通信用的 api_url,ucenter 中配置的应用的 key

    UCenter.configure do |config|
    config.appid   = UcenterSetting.appid
    config.charset = UcenterSetting.charset
    config.api     = UcenterSetting.api
    config.key     = UcenterSetting.key
    end
    

    这里我是把配置抽离出来的,也可以直接在初始化脚本里写参数。

    appid: 2
    charset: gbk
    api: http://bbs.xxx.com/uc_server
    key: 'xcxcxcx'
    bbs_url: http://bbs.xxx.com
    

    ucenter 的配置 配置之后必然会提醒通信失败,这个不管他。

  • 添加核心文件 app/controllers/ucenter_controller.rb

class UcenterController < ApplicationController
  skip_before_action :authenticate_user!, only: [:syn_out]
  #同步登陆
  def sso
    #连接ucenter
    uc = UCenter.connect
    #检查用户是否存在,不存在注册一个,注册主要用到3个参数 用户名 密码 邮箱,这个自己定
    if uc.user_checkname(current_user.realname)
      ucsuer_register current_user.realname, current_user.created_at, current_user.email
    end
    #调用登录接口 用一个变量保存登录后的返回值
    login_res = uc.user_login current_user.realname, current_user.created_at
    #通过返回值中的id调用同步接口
    syn_res = uc.user_synlogin login_res[:id]
    href = <<HREF
<script language="javascript" type="text/javascript">
function goto_bbs() {
window.location.href = "#{UcenterSetting.bbs_url}"
}
setTimeout('goto_bbs()', 500);
</script>
HREF
    #把返回的js渲染到html 同时加上一段跳转js
    render html: "#{syn_res}#{href}".html_safe
  end
  #同步登出  原理与登录相同
  def syn_out
    if current_user?
    uc = UCenter.connect
    logout_res = uc.get_user current_user.realname
    syn_res = uc.user_synlogout logout_res[:id]
    href = <<HREF
<script language="javascript" type="text/javascript">
function goto_bbs() {
window.location.href = "#{HostSetting.site}/users/sign_out"
}
setTimeout('goto_bbs()', 500);
</script>
HREF
    render html: "#{syn_res}#{href}".html_safe
    else
    redirect_to root_path
    end
  end
end
  • 添加与之对应的路由
get 'ucenter/sso' => 'ucenter#sso'
get 'ucenter/syn_out' => 'ucenter#syn_out'
  • 之后要注意的一点,上面的 register 注册方法,只会在 ucenter 的用户表中创建用户,而不在 discuz 的用户表中,这会导致找不到用户。需要修改 discuz 目录下的 uc_server/model/user.php,大概在 129 行处的 function add_user 函数里添加代码,其中的 discuz 为所连数据库,根据具体情况修改。
$this->db->query("INSERT INTO `discuz`.pre_common_member SET uid='$uid', username='$username', password='$password', email='$email', adminid='0', groupid='10', regdate='".$this->base->time."', credits='0', timeoffset='9999'");
$this->db->query("INSERT INTO `discuz`.pre_common_member_status SET uid='$uid', regip='$regip', lastip='$regip', lastvisit='".$this->base->time."', lastactivity='".$this->base->time."', lastpost='0', lastsendmail='0'");
$this->db->query("INSERT INTO `discuz`.pre_common_member_profile SET uid='$uid'");
$this->db->query("INSERT INTO `discuz`.pre_common_member_field_forum SET uid='$uid'");
$this->db->query("INSERT INTO `discuz`.pre_common_member_field_home SET uid='$uid'");
$this->db->query("INSERT INTO `discuz`.pre_common_member_count SET uid='$uid', extcredits1='0', extcredits2='0', extcredits3='0', extcredits4='0', extcredits5='0', extcredits6='0', extcredits7='0', extcredits8='0'");
  • 然后修改 discuz 入口文件 比如 forum.php 在最后加上,判断用户是否登录,未登录则跳转到主站登录页面
if(empty($_G['uid'])) {
header("location: http://bbs.xxx.com/bbs_sign_in");
exit;
}
  • 这个地址对应的动作是 新建的一个控制器里的 app/controller/bbs_sessions_controller.rb, 新建这样一个登录入口是为了不影响主站原来的登录入口,因为通过这个登录入口登录后会进行跳转操作。
class BbsSessionsController < Devise::SessionsController
  respond_to :html, :js

  include Concerns::CommonShare

  def new
    super
  end


  def after_sign_in_path_for(resource)
    ucenter_sso_path
  end
end
  • 在 devise 的路由里添加
get 'bbs_sign_in' => 'bbs_sessions#new'

关于判断登录后跳转地址也可以这样写,意思是如果是来自 bbs_sign_in 的登录请求,返回一个跳转地址是 ucenter/sso。或者其他你们自己的判断登录跳转的方式。

if request.referrer == "#{HostSetting.site}#{bbs_sign_in_path}"
  return ucenter_sso_path
end
  • 注意修改登录页的样式,否则新增的登录页面将没有样式
// --------------- 登录页 -----------//
#users_sessions_new, #bbs_sessions_new //--<- 新增的--//

至此,访问论坛首页,会判断是否登录,未登录会跳转到 主站/bbs_sign_in 页,如果主站未登录将显示,主站登录页,登录后将跳转到/ucenter/sso,执行同步登录操作,之后跳转回论坛并且为登录状态;主站已登录则会直接跳转到/ucenter/sso 进行同步登录。

同步退出

  • 修改 devise 的 sign_out 方式为 get,在 initializer/devise.rb 中
# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :get
  • 把所有退出按钮对应的 link_to 改为 ucenter_syn_out_path, method: :get

  • 把 discuz 中的退出链接改为http://bbs.xxx.com/ucenter/syn_out 主要是这两个文件

    discuz/template/default/search/header.htm
    discuz/template/default/common/header_userstatus.htm
    

    至此,两站的退出全部指向 bbs.xxx.com/ucenter/syn_out,然后这个 action(代码见上文) 会先对论坛进行同步退出操作,之后跳转回主站/users/sign_out,将主站退出,最后跳转到主站退出页面。 最后,不推荐集成 discuz,这个也只是提供一个集成论坛的思路。

Discourse

这个比集成 discuz 少踩了一些坑~~

安装 discourse

  • 首先需要安装 docker 这个自理,注意把镜像源也换为国内的,这个也自理。最好不要用 centos 运行 docker,存储驱动支持的不是很好. docker 环境可能会报错说 docker.io 找不到 做个软链
ln -s /usr/bin/docker /usr/bin/docker.io
  • 然后 github clone 项目初始化
./discourse-setup

之后生成 container/app.yml 这个配置文件,需要对其修改

  • 添加这两行
- "templates/web.china.template.yml" # <-- Added  这样gem源就为ruby-china
-   "templates/web.socketed.template.yml"# <-- Added 用于配置nignx
  • 把端口映射的两行注释掉
# - "80:80"
# - "443:443"
  • 邮件配置是用的 sendcloud
DISCOURSE_SMTP_AUTHENTICATION: login
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none
DISCOURSE_SMTP_ENABLE_START_TLS: false
DISCOURSE_SMTP_ADDRESS: smtpcloud.sohu.com
DISCOURSE_SMTP_PORT: 25
DISCOURSE_SMTP_USER_NAME: your_account
DISCOURSE_SMTP_PASSWORD: "your_password"

邮件的配置会影响到能否发送邮件,而且管理员账户需要通过邮件激活。当然也能手动创建管理员。

cd /var/discourse  (你的discourse目录) 
./launcher enter app 
rake admin:create
  • nignx
server {    
     listen 80; listen [::]:80;     
    server_name forum.example.com;  # <-- change this     
    location / {             
    proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;    
    proxy_set_header Host $http_host;         
    proxy_http_version 1.1;            
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;            
    proxy_set_header X-Forwarded-Proto $scheme;        
 } }
  • 启动 discourse
./launcher start app

Discourse 端所需配置

discourse 需要的配置,可以直接在管理界面搜索关键字,就能显示相应配置。这里我们配置了 API KEY,启用 sso 并添加 sso secret 和 sso url,修改登出跳转 url。

Rails 端实现

  • 首先还是引入 gem,这个 gem 里提供了大量操作 discourse 的方法,我们用到的只是其中很小一部分。
gem  'discourse_api' 
  • 同样配置也都被我提取出来了。主要参数是 sso secret ,论坛 url,论坛 sso 登录 url,api_key,管理员邮箱
secret: 'xxxxxxxxx'
host: http://bbs.xxx.com
sso_url: http://bbs.xxx.com/session/sso_login
api_key: xxxxxxxxxx
admin_email: [email protected]
  • 添加核心文件 app/controllers/discourse_sso_controller.rb
class DiscourseSsoController < ApplicationController
  def sso
    if current_user
      secret = DiscourseSetting.secret
      #解析sso登录请求
      sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret)
      #判断是否是管理员
      if DiscourseSetting.admin_email == current_user.email
        sso.admin = true
      end
      #登录用户信息设置
      sso.email = current_user.email
      sso.name = current_user.realname
      str = current_user.email
      sso.username = "#{str.split(pattern='@').first}_#{current_user.id}"
      sso.external_id = current_user.id
      sso.sso_secret = secret
      redirect_to sso.to_url(DiscourseSetting.sso_url)
    else
      #如果主站未登录,跳转到登录页
      redirect_to bbs_sign_in_path
    end
  end
end
  • 添加路由 这里就是之前 discourse 中配置的 sso url
get 'discourse/sso' => 'discourse_sso#sso'
  • 类似之前集成 discuz,这里我们也添加一个登录,登出入口,app/controller/bbs_sessions_controller.rb。然后把 devise 登出方式改为 get。还有要注意修改样式文件。
class BbsSessionsController < Devise::SessionsController
  respond_to :html, :js

  include Concerns::CommonShare

  def new
    super
  end

  def destroy
    #删除session操作
    super
  end

end
  • 添加 devise 路由
get 'bbs_sign_in' => 'bbs_sessions#new'
get 'bbs_sign_out' => 'bbs_sessions#destroy'

其中 bbs_sign_in 是论坛所使用的登录页,需要单独为这个登录添加登录跳转,跳转回论坛

if request.referrer == "#{HostSetting.site}#{bbs_sign_in_path}"
  return DiscourseSetting.host
end

bbs_sign_out 是论坛退出后的跳转地址,即论坛退出后操作主站也退出

至此,访问 discourse 会跳转到主站/discourse/sso 请求 sso 登录,如果主站已登录则会同步论坛登录并跳转回论坛;如果主站未登录则会跳转到/bbs_sign_in 这个论坛登录入口,登陆后跳转到论坛页面,discourse 重新执行 sso 登录。然后再论坛端退出,会先退出论坛然后跳转主站/bbs_sign_out 同步主站退出。

主站退出同步论坛退出
  • 只需要修改 devise 原来的退出方法 app/controllers/users/sessions_controller.rb, 这样主站退出前会先调用 discourse 的退出 API
def destroy
  begin
    client = DiscourseApi::Client.new(DiscourseSetting.host)
    client.api_key = DiscourseSetting.api_key
    str = current_user.email
    client.api_username = "#{str.split(pattern='@').first}_#{current_user.id}"
    user = client.by_external_id(current_user.id)
    client.log_out(user["id"]) if user
  ensure
  #删除sessions操作
  super
  end
end

至此集成 Discourse 算全部结束 撒花

先顶再看

huacnlee mark as excellent topic. 20 Mar 10:20

Discourse 1.6+ 有启动向导了,手动创建管理员也没法跳过。

run:
  - exec: echo "Beginning of custom commands"
  ## If you want to set the 'From' email address for your first registration, uncomment and change:
  ## After getting the first signup email, re-comment the line. It only needs to run once.
  - exec: rails r "SiteSetting.has_login_hint=false"
  - exec: rails r "SiteSetting.wizard_enabled=false"
  - exec: echo "End of custom commands"

在 app.yml 的 hook 里还是可以禁用相关的设置的。这和管理面板里操作的一样。

SSO 过去的用户名也会被洗一次,有些符号或者中文会很惨...

最后,Discourse 现在支持 SAML SSO 了。https://github.com/discourse/discourse-saml

用户名我是格式化成邮箱前缀加 id 应该不会出现中文 SAML SSO 还需学习

You need to Sign in before reply, if you don't have an account, please Sign up first.