<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Terry.Shi (Terry.shi)</title>
    <link>https://ruby-china.org/Terry.Shi</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>真的有无法使用自动化工具爬取数据的网站吗？</title>
      <description>&lt;p&gt;公司会时不时爬一些竞品网站的产品信息，依赖于爬虫框架，改改频率改改 UA 加个 ip 代理池几乎都顺利能爬到。直到接到爬 &lt;a href="http://www.lulus.com" rel="nofollow" target="_blank" title=""&gt;www.lulus.com&lt;/a&gt; 这家的需求，换过爬虫框架甚至用了 ui 测试框架，调研过一些模拟浏览器访问的服务，都无法正常爬取页面，不是 403 就是人机检测页面，最终用了最蠢的方法：按键精灵，真就开了个 windows 用按键精灵直接去开浏览器然后截屏。。这里可以给个列表页，大家感兴趣的可以爬爬看&lt;a href="https://www.lulus.com/categories/262_475/white-dresses.html" rel="nofollow" target="_blank"&gt;https://www.lulus.com/categories/262_475/white-dresses.html&lt;/a&gt; 。我主要是想请教一下这是什么样的技术组合能够实现这么强的反爬，莫非已经用上了传说中的人工智能来分析请求？&lt;/p&gt;</description>
      <author>Terry.Shi</author>
      <pubDate>Tue, 02 Jun 2020 19:36:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/39929</link>
      <guid>https://ruby-china.org/topics/39929</guid>
    </item>
    <item>
      <title>关于 RoR 内存问题的讨论</title>
      <description>&lt;h2 id="背景情况"&gt;背景情况&lt;/h2&gt;
&lt;p&gt;看到 &lt;a href="https://ruby-china.org/topics/35236" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/35236&lt;/a&gt;&lt;br&gt;
想到我们也遇到了这样的问题。感觉可以拿出来讨论一下。&lt;br&gt;
下图是一个 rails 项目服务器 14 天的内存增长情况&lt;br&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/f4cde64b-ecf1-4926-90e5-04620edd6c12.png!large" title="" alt=""&gt;&lt;br&gt;
8 核 16G 阿里云服务器 puma 3.10.0 rails 5.05
可以看到在没有部署或者重启的操作下，呈现内存持续增长。&lt;br&gt;
内存持续增长对系统的危害：在内存接近满值的时候会触发操作系统 out of memory 机制，可能导致关键进程被系统杀死 (redis postgresql sidekiq 等) 造成系统异常，此外也会让程序花费大量的时间在 GC 上从而导致程序响应速度降低。&lt;/p&gt;
&lt;h2 id="关于ruby的内存机制"&gt;关于 ruby 的内存机制&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ruby 有自己的内存管理机制，叫做 Ruby Heaps。他独立于操作系统的 System Heap，包含很多 Slots，其中每一个 Slots 指向一个对象。Slots 本身存储于 Ruby Heaps 中，但其指向的对象存储于 System Heap 中。例如一个 Ruby 程序创建了一个 50M 的字符串，这时 Ruby Heaps 中就有一个指向该字符串的 Slot，而真正的字符串存储于 System Heap 中。当这一字符串不再被引用时，Slot 会在下一次内存清理（GC iteration&amp;nbsp;）中被回收，同时存储 50M 字符串的内存也会返回给操作系统。Ruby 会在最开始创建一个最小的 Ruby Heap，此后再必要的时候进行 Ruby Heaps 的创建或销毁。&amp;nbsp;每次创建 Ruby Heap 会是上一个的 1.8 倍（由&amp;nbsp;RUBY_GC_HEAP_GROWTH_FACTOR 控制，这个环境变量值设置越低，意味着我们越要频繁的运行 GC 和请求分配内存。该数值越大，意味着更少的 GC，以及超过我们程序运行所需要的内存。），而当一个 Ruby Heap 中的 Slot 均为空（free Slot）时，该 Ruby Heap 会被释放（内存被操作系统回收）。即 ruby 是通过多个 heaps 来管理内存的，每个 heap 有许多 slot 用来存放对象，只有当一个 heap 的 slot 全部是 free 的时候 ruby 才会把这个 heap 释放掉，把内存交还 os.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ruby 中的常量是永远不会被垃圾回收的，所以如果常量引用了一个对象，那么这个对象也永远不会被垃圾回收，是由于将来&amp;nbsp;可能&amp;nbsp;会使用它们。在 Ruby 中一个对象一旦被全局对象引用，它就不会被垃圾回收。&amp;nbsp;这一原则也适用于常量，全局变量，模块 (modules) 和类 (class)。因此，在全局可访问的任何地方引用对象都要注意这一点。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ruby 会在内存不够时根据 RUBY_GC_HEAP_GROWTH_FACTOR 申请内存，因为分配内存的操作开销很大，Ruby 会把这些分配的内存保持住一段时间。一旦进程将这些内存用尽，那么就再次申请内存。内存会逐渐释放，这一过程很慢。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;综合上述几点：ruby 的程序会释放内存给操作系统，但是如果一个 heap 里存在不会被垃圾回收的对象，那这个 heap 的空间就不会释放给操作系统，同时内存释放很慢，容易发生内存持续上涨的情况。&lt;/p&gt;
&lt;h2 id="处理方向"&gt;处理方向&lt;/h2&gt;
&lt;p&gt;首先明确的是内存占用的多少跟并发量是有直接关系的。但是排除这个因素，对接口内存占用分析能发现，不合理的代码会让内存上升的速度大大加快，整个趋势就是一个持续上涨的趋势。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;添加 puma_worker_killer。这个是最简单有效但是不治本的方法。&lt;/li&gt;
&lt;li&gt;优化代码。不合理的代码会对内存造成极大的负担，如 N+1 的查询，不加 limit 的查询，返回大量无用字段等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="定位问题"&gt;定位问题&lt;/h2&gt;
&lt;p&gt;这里主要用了三个工具（当然不止这些 newrelic 等也都可以）&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;oink&lt;/li&gt;
&lt;li&gt;memory_profiler&lt;/li&gt;
&lt;li&gt;scoutapp
前两个是内存检测的 gem，最后一个是一个三方服务（缺点就是贵，我们用的试用版。。。）&lt;br&gt;
能检测到 n+1 查询
&lt;img src="https://l.ruby-china.com/photo/2018/582dac7d-7d73-46f9-b327-99991a50cd3b.png!large" title="" alt=""&gt;&lt;br&gt;
以及内存占用大的接口&lt;br&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/36b25adc-0164-4eaa-bac2-0b633e1bc4c2.png!large" title="" alt=""&gt;&lt;br&gt;
看哪些接口创建了大量对象&lt;br&gt;
&lt;img src="https://l.ruby-china.com/photo/2018/c6d111cc-202d-4c57-b455-ea0c72b57381.png!large" title="" alt=""&gt;&lt;br&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="优化方式"&gt;优化方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;减少全局对象&lt;/li&gt;
&lt;li&gt;减少创建的对象 &lt;/li&gt;
&lt;li&gt;数据库查询尽量使用 limit 限制查询结果条数 &lt;/li&gt;
&lt;li&gt;避免 n+1 查询&lt;/li&gt;
&lt;li&gt;冻结一个字符串，解释器会认为你不会修改该字符串，并保留它以便重复使用。（在 Ruby 3 中，字符串字面量在所有文件中默认被冻结。）&lt;br&gt;
其他更多优化可以看下面这篇博客&lt;br&gt;
&lt;a href="http://blog.csdn.net/tianxingjian111222/article/details/53906140" rel="nofollow" target="_blank"&gt;http://blog.csdn.net/tianxingjian111222/article/details/53906140&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些权当做抛砖引玉，主要想听听各位大佬的看法。比如 ruby 内存机制上有哪些理解不到位，比如还有什么样的写法会占用大量内存，比如还有哪些能够优化代码的方式，比如 ruby3 在内存优化上会带来哪些惊喜。&lt;/p&gt;

&lt;p&gt;其他关于 ruby 内存的讨论贴   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Ruby Uses Memory&lt;br&gt;
&lt;a href="https://ruby-china.org/topics/25790" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/25790&lt;/a&gt;&lt;br&gt;
&lt;/li&gt;
&lt;li&gt;关于 Ruby 内存使用的一些优化和探索&lt;br&gt;
&lt;a href="https://ruby-china.org/topics/27057" rel="nofollow" target="_blank"&gt;https://ruby-china.org/topics/27057&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>Terry.Shi</author>
      <pubDate>Wed, 14 Mar 2018 17:31:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/35238</link>
      <guid>https://ruby-china.org/topics/35238</guid>
    </item>
    <item>
      <title>Ruby 现在是不是很尴尬</title>
      <description>&lt;p&gt;公司不愿意用 ruby 说根本招不到人。程序员不愿意学 ruby 说根本找不到工作。我是挺喜欢 ruby 的，对 ruby 不火感到很遗憾。&lt;/p&gt;</description>
      <author>Terry.Shi</author>
      <pubDate>Fri, 05 Jan 2018 16:07:39 +0800</pubDate>
      <link>https://ruby-china.org/topics/34832</link>
      <guid>https://ruby-china.org/topics/34832</guid>
    </item>
    <item>
      <title>集成论坛包括 Discuz 和 Discourse</title>
      <description>&lt;p&gt;排版已吐
折腾了好久 踩了好多坑 也很有意思 当然还有写的有问题的地方需要指正&lt;/p&gt;
&lt;h2 id="Discuz"&gt;Discuz&lt;/h2&gt;&lt;h3 id="主要实现"&gt;主要实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;主站登录，论坛为登录状态&lt;/li&gt;
&lt;li&gt;主站未登录去访问论坛，论坛跳转到主站，登陆后跳转回论坛 &lt;/li&gt;
&lt;li&gt; 论坛退出同时主站退出并跳转到主站退出后界面 &lt;/li&gt;
&lt;li&gt;主站退出同步论坛退出&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;首先要介绍的是 discuz 的 ucenter 所提供的应用同步原理，我们通过 rails 调用一个登陆接口，这个接口返回用户信息，再以用户 id 为参数，调用同步的接口，这个接口返回一段 js，最后渲染这段 js 来操作浏览器设置 discuz 的 cookie 完成同步，登出同理。根据原理实现方式不尽相同，下面给出一种。&lt;/p&gt;
&lt;h3 id="环境："&gt;环境：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;主应用使用 devise。&lt;/li&gt;
&lt;li&gt;discuz 3.2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;写了一个 docker-compose 来快速搭建&lt;a href="https://github.com/terryshi96/docker-for-discuz" rel="nofollow" target="_blank"&gt;https://github.com/terryshi96/docker-for-discuz&lt;/a&gt;
源码采用 volume 的方式，方便修改。事实上把 html 里面源码换掉就能起其他 php 程序，然后最好是把 docker 镜像源换为国内的，容器内 apt-get 镜像源也换为国内的。&lt;/p&gt;
&lt;h3 id="实现："&gt;实现：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;添加加一个别人写的 gem ucenter，提供了与 UCenter api 通信的方法
&lt;a href="https://github.com/MgaMPKAy/ucenter" rel="nofollow" target="_blank"&gt;https://github.com/MgaMPKAy/ucenter&lt;/a&gt;  这里直接在 gemfile 里面加 ucenter 将用的是另一个包，需要用 git 的方式添加&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;添加初始化配置 initializers/ucenter.rb  主要提供 4 个参数 appid 是在 ucenter 中配置的应用 id，编码方式，通信用的 api_url,ucenter 中配置的应用的 key&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;UCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appid&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UcenterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appid&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UcenterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charset&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UcenterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UcenterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;key&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我是把配置抽离出来的，也可以直接在初始化脚本里写参数。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;appid: 2
charset: gbk
api: http://bbs.xxx.com/uc_server
key: 'xcxcxcx'
bbs_url: http://bbs.xxx.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ucenter 的配置 配置之后必然会提醒通信失败，这个不管他。&lt;/p&gt;
&lt;h2 id=""&gt;&lt;img src="https://l.ruby-china.com/photo/2017/114a1bf1709863a3448de52310cc1c73.png!large" title="" alt=""&gt;&lt;/h2&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;添加核心文件  app/controllers/ucenter_controller.rb&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UcenterController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;skip_before_action&lt;/span&gt; &lt;span class="ss"&gt;:authenticate_user!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:syn_out&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;#同步登陆&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sso&lt;/span&gt;
    &lt;span class="c1"&gt;#连接ucenter&lt;/span&gt;
    &lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;
    &lt;span class="c1"&gt;#检查用户是否存在,不存在注册一个,注册主要用到3个参数 用户名 密码 邮箱,这个自己定&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_checkname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;ucsuer_register&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="c1"&gt;#调用登录接口 用一个变量保存登录后的返回值&lt;/span&gt;
    &lt;span class="n"&gt;login_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_login&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;created_at&lt;/span&gt;
    &lt;span class="c1"&gt;#通过返回值中的id调用同步接口&lt;/span&gt;
    &lt;span class="n"&gt;syn_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_synlogin&lt;/span&gt; &lt;span class="n"&gt;login_res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;HREF&lt;/span&gt;&lt;span class="sh"&gt;
&amp;lt;script language="javascript" type="text/javascript"&amp;gt;
function goto_bbs() {
window.location.href = "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;UcenterSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bbs_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
}
setTimeout('goto_bbs()', 500);
&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;HREF&lt;/span&gt;
    &lt;span class="c1"&gt;#把返回的js渲染到html 同时加上一段跳转js&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;syn_res&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_safe&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="c1"&gt;#同步登出  原理与登录相同&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;syn_out&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user?&lt;/span&gt;
    &lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UCenter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;
    &lt;span class="n"&gt;logout_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_user&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realname&lt;/span&gt;
    &lt;span class="n"&gt;syn_res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user_synlogout&lt;/span&gt; &lt;span class="n"&gt;logout_res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;HREF&lt;/span&gt;&lt;span class="sh"&gt;
&amp;lt;script language="javascript" type="text/javascript"&amp;gt;
function goto_bbs() {
window.location.href = "&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;HostSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;site&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/users/sign_out"
}
setTimeout('goto_bbs()', 500);
&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;HREF&lt;/span&gt;
    &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;html: &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;syn_res&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html_safe&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;root_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;添加与之对应的路由&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get 'ucenter/sso' =&amp;gt; 'ucenter#sso'
get 'ucenter/syn_out' =&amp;gt; 'ucenter#syn_out'
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;之后要注意的一点，上面的 register 注册方法，只会在 ucenter 的用户表中创建用户，而不在 discuz 的用户表中，这会导致找不到用户。需要修改 discuz 目录下的 uc_server/model/user.php，大概在 129 行处的 function add_user 函数里添加代码，其中的 discuz 为所连数据库，根据具体情况修改。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$this-&amp;gt;db-&amp;gt;query("INSERT INTO `discuz`.pre_common_member SET uid='$uid', username='$username', password='$password', email='$email', adminid='0', groupid='10', regdate='".$this-&amp;gt;base-&amp;gt;time."', credits='0', timeoffset='9999'");
$this-&amp;gt;db-&amp;gt;query("INSERT INTO `discuz`.pre_common_member_status SET uid='$uid', regip='$regip', lastip='$regip', lastvisit='".$this-&amp;gt;base-&amp;gt;time."', lastactivity='".$this-&amp;gt;base-&amp;gt;time."', lastpost='0', lastsendmail='0'");
$this-&amp;gt;db-&amp;gt;query("INSERT INTO `discuz`.pre_common_member_profile SET uid='$uid'");
$this-&amp;gt;db-&amp;gt;query("INSERT INTO `discuz`.pre_common_member_field_forum SET uid='$uid'");
$this-&amp;gt;db-&amp;gt;query("INSERT INTO `discuz`.pre_common_member_field_home SET uid='$uid'");
$this-&amp;gt;db-&amp;gt;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'");
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;然后修改 discuz 入口文件 比如 forum.php 在最后加上，判断用户是否登录，未登录则跳转到主站登录页面&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if(empty($_G['uid'])) {
header("location: http://bbs.xxx.com/bbs_sign_in");
exit;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这个地址对应的动作是 新建的一个控制器里的 app/controller/bbs_sessions_controller.rb, 新建这样一个登录入口是为了不影响主站原来的登录入口，因为通过这个登录入口登录后会进行跳转操作。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BbsSessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SessionsController&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:js&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Concerns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommonShare&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;


  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;after_sign_in_path_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ucenter_sso_path&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在 devise 的路由里添加&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get 'bbs_sign_in' =&amp;gt; 'bbs_sessions#new'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关于判断登录后跳转地址也可以这样写，意思是如果是来自 bbs_sign_in 的登录请求，返回一个跳转地址是 ucenter/sso。或者其他你们自己的判断登录跳转的方式。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;referrer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;HostSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;site&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;bbs_sign_in_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ucenter_sso_path&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;注意修改登录页的样式，否则新增的登录页面将没有样式&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// --------------- 登录页 -----------//
#users_sessions_new, #bbs_sessions_new //--&amp;lt;- 新增的--//
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，访问论坛首页，会判断是否登录，未登录会跳转到 主站/bbs_sign_in 页，如果主站未登录将显示，主站登录页，登录后将跳转到/ucenter/sso，执行同步登录操作，之后跳转回论坛并且为登录状态；主站已登录则会直接跳转到/ucenter/sso 进行同步登录。&lt;/p&gt;
&lt;h4 id="同步退出"&gt;同步退出&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;修改 devise 的 sign_out 方式为 get，在 initializer/devise.rb 中&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The default HTTP method used to sign out a resource. Default is :delete.
config.sign_out_via = :get
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;把所有退出按钮对应的 link_to 改为 ucenter_syn_out_path, method: :get&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;把 discuz 中的退出链接改为&lt;a href="http://bbs.xxx.com/ucenter/syn_out" rel="nofollow" target="_blank"&gt;http://bbs.xxx.com/ucenter/syn_out&lt;/a&gt; 主要是这两个文件&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;discuz/template/default/search/header.htm
discuz/template/default/common/header_userstatus.htm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，两站的退出全部指向 bbs.xxx.com/ucenter/syn_out，然后这个 action(代码见上文) 会先对论坛进行同步退出操作，之后跳转回主站/users/sign_out，将主站退出，最后跳转到主站退出页面。
最后，不推荐集成 discuz，这个也只是提供一个集成论坛的思路。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Discourse"&gt;Discourse&lt;/h2&gt;
&lt;p&gt;这个比集成 discuz 少踩了一些坑~~&lt;/p&gt;
&lt;h3 id="安装discourse"&gt;安装 discourse&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先需要安装 docker 这个自理，注意把镜像源也换为国内的，这个也自理。最好不要用 centos 运行 docker，存储驱动支持的不是很好.
docker 环境可能会报错说 docker.io 找不到
做个软链&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ln -s /usr/bin/docker /usr/bin/docker.io
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;然后 github clone 项目初始化 &lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./discourse-setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后生成 container/app.yml 这个配置文件，需要对其修改&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;添加这两行&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- "templates/web.china.template.yml" # &amp;lt;-- Added  这样gem源就为ruby-china
-   "templates/web.socketed.template.yml"# &amp;lt;-- Added 用于配置nignx
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;把端口映射的两行注释掉&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# - "80:80"
# - "443:443"
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;邮件配置是用的 sendcloud&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;邮件的配置会影响到能否发送邮件，而且管理员账户需要通过邮件激活。当然也能手动创建管理员。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /var/discourse  （你的discourse目录） 
./launcher enter app 
rake admin:create
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;nignx&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server {    
     listen 80; listen [::]:80;     
    server_name forum.example.com;  # &amp;lt;-- 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;        
 } }
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;启动 discourse&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./launcher start app
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="Discourse端所需配置"&gt;Discourse 端所需配置&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2017/7dc81083befa25125778d3c20f61407c.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2017/50053786e61c130ddc6f32806b387ab9.png!large" title="" alt=""&gt;
&lt;img src="https://l.ruby-china.com/photo/2017/c111a2b29aae00b082304ba9b0c6157a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;discourse 需要的配置，可以直接在管理界面搜索关键字，就能显示相应配置。这里我们配置了 API KEY，启用 sso 并添加 sso secret 和 sso url，修改登出跳转 url。&lt;/p&gt;
&lt;h3 id="Rails端实现"&gt;Rails 端实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;首先还是引入 gem，这个 gem 里提供了大量操作 discourse 的方法，我们用到的只是其中很小一部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem  'discourse_api' 
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;同样配置也都被我提取出来了。主要参数是 sso secret ,论坛 url，论坛 sso 登录 url,api_key，管理员邮箱&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;secret: 'xxxxxxxxx'
host: http://bbs.xxx.com
sso_url: http://bbs.xxx.com/session/sso_login
api_key: xxxxxxxxxx
admin_email: xxx@qq.com
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;添加核心文件 app/controllers/discourse_sso_controller.rb&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DiscourseSsoController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sso&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;
      &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secret&lt;/span&gt;
      &lt;span class="c1"&gt;#解析sso登录请求&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DiscourseApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SingleSignOn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;#判断是否是管理员&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin_email&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
        &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="c1"&gt;#登录用户信息设置&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;realname&lt;/span&gt;
      &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;external_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;
      &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sso_secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;sso&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sso_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c1"&gt;#如果主站未登录,跳转到登录页&lt;/span&gt;
      &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;bbs_sign_in_path&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;添加路由 这里就是之前 discourse 中配置的 sso url&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get 'discourse/sso' =&amp;gt; 'discourse_sso#sso'
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;类似之前集成 discuz，这里我们也添加一个登录，登出入口，app/controller/bbs_sessions_controller.rb。然后把 devise 登出方式改为 get。还有要注意修改样式文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BbsSessionsController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Devise&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SessionsController&lt;/span&gt;
  &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="ss"&gt;:html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:js&lt;/span&gt;

  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Concerns&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;CommonShare&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="c1"&gt;#删除session操作&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;添加 devise 路由&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;get 'bbs_sign_in' =&amp;gt; 'bbs_sessions#new'
get 'bbs_sign_out' =&amp;gt; 'bbs_sessions#destroy'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 bbs_sign_in 是论坛所使用的登录页，需要单独为这个登录添加登录跳转，跳转回论坛&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;referrer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;HostSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;site&lt;/span&gt;&lt;span class="si"&gt;}#{&lt;/span&gt;&lt;span class="n"&gt;bbs_sign_in_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;bbs_sign_out 是论坛退出后的跳转地址，即论坛退出后操作主站也退出&lt;/p&gt;

&lt;p&gt;至此，访问 discourse 会跳转到主站/discourse/sso 请求 sso 登录，如果主站已登录则会同步论坛登录并跳转回论坛；如果主站未登录则会跳转到/bbs_sign_in 这个论坛登录入口，登陆后跳转到论坛页面，discourse 重新执行 sso 登录。然后再论坛端退出，会先退出论坛然后跳转主站/bbs_sign_out 同步主站退出。&lt;/p&gt;
&lt;h5 id="主站退出同步论坛退出"&gt;主站退出同步论坛退出&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;只需要修改 devise 原来的退出方法 app/controllers/users/sessions_controller.rb, 这样主站退出前会先调用 discourse 的退出 API&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
  &lt;span class="k"&gt;begin&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DiscourseApi&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;DiscourseSetting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_key&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;by_external_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log_out&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;
  &lt;span class="k"&gt;ensure&lt;/span&gt;
  &lt;span class="c1"&gt;#删除sessions操作&lt;/span&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此集成 Discourse 算全部结束  撒花&lt;/p&gt;</description>
      <author>Terry.Shi</author>
      <pubDate>Fri, 17 Mar 2017 18:45:49 +0800</pubDate>
      <link>https://ruby-china.org/topics/32569</link>
      <guid>https://ruby-china.org/topics/32569</guid>
    </item>
  </channel>
</rss>
