Redmine源代码分析

一、总体结构

1.1 代码结构

系统的核心代码都放在目录: {安装目录}/apps/redmine/htdocs,这里我们仅仅列出需要关注的文件或文件夹。

./app/ # 此文件夹下存放着完成redmine工作逻辑的核心代码。 代码按照经典的MVC的方式组织。

    ./app/models # 模型文件, 每个文件与数据库中的一张表对应, 负责数据的读取和写入以及数据的基本逻辑处理。

    ./app/controllers # 控制器文件。 将模型和视图连接起来。 页面请求到来后, 由控制器调用模型, 完成数据处理后, 渲染视图文件并呈现给用户。

    ./app/views # 视图文件。

    ./app/helpers # helper 为视图文件提供帮助方法。 一些渲染页面中基本的逻辑可以以函数的形式放在helper中, 供views文件使用。

./config # 系统的基本配置文件存放于此。

    ./config/configuration.yml # 配置系统的邮件发送,字体等信息。

    ./config/database.yml # 配置数据库的连接信息。

    ./config/routes.rb  # 路由信息。 指明了每个请求的url如何与相应的控制器关联.

    ./config/locale # 翻译信息。 Redmine支持多语言, 用户设定不同的语言, 系统就会切换到相应的语言。

./db # 数据库迁移文件存放于此。

./files # redmine票的附件存放于此。 备份redmine数据时, 不仅要备份数据库, 还要备份该文件夹。

./lib # redmine系统使用的众多工具包

./lib/tasks # 此文件夹下存放着redmine的rake文件。 通过这些文件, 我们可以在终端敲入简单的命令,完成复杂的任务。 另外, 我们还可以通过编写rake文件,完成某些任务。

./log # 日志。

./plugins # redmine插件

./public # redmine系统的javascript, css, 图片等文件存放于此

 1.2 响应流程

1

上图简述了Redmine(Rails框架)响应用户请求的过程。

  1. 用户发起HTTP请求。
  2. 系统通过路由规则为请求寻找响应的控制器和具体的action。
  3. 请求被转向相应的控制器和具体的action。
  4. 控制器委托模型获取数据,并完成具体的任务。
  5. 模型查询数据库,获取数据,并返回给控制器。
  6. 控制器用得到的数据渲染视图。
  7. 渲染后的视图,也就是浏览器可识别的代码,被返回至客户端。

下面将简单介绍Redmine的路由、控制器、模型、视图。

二、路由

Redmine路由文件./config/routes.rb。

阅读学习资料后, 路由的机制基本就能搞清楚了。相关内容不多说, 只是通过redmine路由文件,说几个点。

1. root :to => ‘welcome#index’, :as => ‘home’

  • :to => ‘welcome#index’: 说明此路由指向的controller为welcome, action为index。
  • root: root用来表示这个特殊的访问路径“/”。
  • :as => ‘home’: 这个参数可以理解为路由的别名, 另外它会自动生成两个具名路由帮助方法home_url(绝对路径)和home_path(相对路径)。 如果不理解这一点, 在代码中看到home_path这样的方法后, 会十分困惑。

2. match ‘login’, :to => ‘account#login’, :as => ‘signin’, :via => [:get, :post]

  • :via => [:get, :post]: 指定此路由接收什么方法的请求。 在此例中, 用GET和POST方法请求路径‘/login’, 是有响应的。

3. get ‘boards/:board_id/topics/:id’, :to => ‘messages#show’, :as => ‘board_message’

  • get xxxx, 这里表示路由只接收GET请求。
  •  ‘boards/:board_id/topics/:id’, 请求路径响应位置的值:board_id,:id, 会存放在变量params中, 供controller调用。

4.  resources :users

  • 内容较多, 相关知识请阅读参考文献中的‘资源路径’部分

三、控制器

3.1 文件结构

路径: Redmine的控制器文件位于./app/controllers。

下面对控制器的文件结构进行分析。

------- 基类
 application_controller.rb #所有其它的控制器都继承于此。 基类中完成了用户登录验证, 会话超时控制, 当前用户设定, 前端语言种类选定, 基本异常处理(无权限,无视图)等基本功能。
------- 用户个人相关
 account_controller.rb # 账户状态。 登陆,登出,忘记/重设密码,注册用户,是否在线。
 activities_controller.rb # redmine的用户活动历史
 my_controller.rb # redmine的个人信息的设置, 个性化定制工作台。
------- 处理系统管理页面请求的控制器。
 admin_controller.rb # 系统管理页面首页, 插件信息页面, 系统信息页面。
 custom_fields_controller.rb # 自定义字段管理
 users_controller.rb # 用户管理
 trackers_controller.rb # 跟踪标签管理
 groups_controller.rb # 组管理
 roles_controller.rb # 角色管理
 issue_statuses_controller.rb # 问题状态管理
 workflows_controller.rb # 工作流程管理
 enumerations_controller.rb # 枚举类型管理
 settings_controller.rb # 系统设置
 auth_sources_controller.rb # 验证源(ldap)管理
 project_enumerations_controller.rb # 系统枚举值的管理
------- 问题票数据相关
 --- 问题票核心
 issues_controller.rb # 和问题票数据的增删改查、批量编辑。
 context_menus_controller.rb # 批量编辑问题票/工时记录时, 点击右键弹出哪些菜单。
 --- 问题票查询
 queries_controller.rb # 问题票的自定义查询建立、更新、删除。
 auto_completes_controller.rb # 返回满足查询条件的问题票信息的Hash。问题票关联的查找框,父任务的查找框等会调用此控制器。
 --- 问题票相关属性
 issue_categories_controller.rb # 问题票类型(系统自带的分类字段)的增删改查
 issue_relations_controller.rb # 问题票建立关联,显示关联,删除关联
 versions_controller.rb # 目标版本的增删改查
 journals_controller.rb # 历史注记的增删改查
 timelog_controller.rb # 工时的增删改查, 批量操作, 统计报告。
 attachments_controller.rb # 问题票附件的上传、显示和删除。
 watchers_controller.rb # 跟踪者的增删
 ---问题票的特色展示
 gantts_controller.rb # 甘特图展示问题票信息
 calendars_controller.rb # 日历展示问题票信息
 reports_controller.rb # 问题票的统计报告
------- 项目相关的控制器
 projects_controller.rb # 项目的增删改查, 项目相关属性的设置与查询。
 wiki_controller.rb # Wiki相关
 wikis_controller.rb # wiki起始页的新建与删除
 files_controller.rb # 项目文件管理
 documents_controller.rb # 项目文档管理
 members_controller.rb # 项目成员相关
 ---讨论区相关
 boards_controller.rb # 讨论区的增删改查
 messages_controller.rb # 讨论区内留言的增删改查
 --- 新闻相关
 news_controller.rb # 新闻正文的增删
 comments_controller.eb # 新闻评论/留言的增删
 previews_controller.rb # 新闻和问题票都使用wiki语法编辑。预览控制器为其提供预览服务。
------- 系统其他功能
 mail_handler_controller.rb # 邮件的接收与发送
 welcome_controller.rb # 系统首页
 search_controller.rb # 系统全文搜索
------- 本人未曾关注
 repositories_controller.rb
 sys_controller.rb

 3.2 代码结构

绝大部分控制器都继承自ApplicationController, 而ApplicationController继承自ActionController。ActionController是Rails框架提供的控制器。 请通过学习资料全面了解其特性:http://guides.ruby-china.org/action_controller_overview.html 。

控制器相当于视图和模型的拼接器: 从模型中获取数据, 将数据塞入视图,展现给用户。另外控制器还会用到众多的helper函数, helper相当于一个函数库。而Rails框架的特点是约定大于配置。如果Controller的名字为IssuesController, Action为show, 那么就可能存在模型Issue, 同时相应的视图文件就存放在./app/views/issues/show.html.erb 。  在redmine中, 控制器会自动加载所有的模型,也就是说, 你这个在控制器中直接调用全部模型。 另外控制器还会根据声明加载部分helper模块, helper中封装了众多的函数。

控制器文件都存放在路径./app/controllers/下。打开一个控制器文件, 可以看到如下的代码结构:

 class IssuesController < ApplicationController 
     menu_item :new_issue, :only => [:new, :create]
     default_search_scope :issues
     before_filter :find_issue, :only => [:show, :edit, :update]
     before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
     ……

     def index
         retrieve_query
         ……
     end
     ……
 end

代码可大致分为两部分, 第一部分为声明代码, 第二部分为Action实现代码。 对于Rails新手来说, 声明区域中的代码理解起来有难度, 而Action区域一般都是纯粹业务逻辑的实现。

3.2.1 声明代码
  • 引入Helper文件
helper :custom_fields
include CustomFieldsHelper

上方两行代码引入了位于./app/helpers/custom_fields_heler.rb中的CustomFieldsHelper模块, 而模块中定义的所有函数都可以被当前Controller调用了。

  • 过滤器

通过过滤器, 我们可以在控制器动作之前、之后或者前后运行一些方法。下面列出redmine中常用的过滤器

before_filter :xxx, :only/except=>[action1, action2…]
skip_before_filter :yyy

如果设置了before_filter, 那么每个Action执行前都会先运行xxx方法。当然可以通过:only=>,:except=>来限定过滤器作用于哪些Action。 skip_before_filter的作用是跳过过滤器,使其失效。

  • 定义类属性
class_attribute :xxx

上边的代码使得相应的类(A)有了一个静态变量, 在类内可通过self.xxx的方式引用; 在类外,可通过A.xxx的方式引用。

  • 布局layout
layout 'admin'

layout相当于整个系统页面的大框架, 框架确定了每个模块的在页面上的摆放形式和摆放位置。 Redmine系统的整个界面有两大摆放方式,相应地也就有两大layout: admin和base。

Layout文件的存放路径为./app/views/layouts/

  • 异常处理
rescue_from ::Unauthorized, :with => :deny_access

这个配置用来进行异常处理。异常发生时,会被 rescue_from 捕获,异常对象会传入处理代码。处理异常的代码可以是方法,也可以是 Proc 对象,由 :with 选项指定。也可以不用 Proc 对象,直接使用块。

  • menu_item

用来指定当前页面中,哪个项目按钮被按下。

  • 特殊登陆验证
accept_rss_auth :xxx,:yyy
accept_api_auth :xxx,:yyy

用来指定哪些Action可以通过RSS/REST API验证的方式访问。

  • 搜索区域
default_search_scope :xxx

设置Redmine的文本搜索功能的搜索目标。 xxx可以是news,messages, documents, wiki_pages, issues

  • model_object

Redmine控制器中为find_model_object函数指定模型的方法。

3.2.2 Action代码

Action中都是业务逻辑,没太多可总结的。这里把常用的几个方法记录于下。

1. flash

flash.now[:error] #当前Action中显示错误提示。

flash.discard[:error] # 忽略所有错误提示。

flash.keep[:error] # 到下一个Action中,错误提示依然有效。

2. 渲染视图

控制器渲染的视图可以用render显式指定,也可以按照默认的约定。Render的使用方法请参考资料:http://www.cnblogs.com/wangyuyu/archive/2013/08/03/3235597.html

四、模型与数据表

数据库的表结构由位于./db/migrate目录下的迁移文件决定; 而系统对于数据的操作则由./app/models/下的模型文件决定。

 4.1 文件结构

 
 ------ # 系统功能相关
 auth_source.rb # 验证源 -> 表 auth_sources
 auth_source_ldap.rb # ldap验证源, 继承自auth_source.rb中的类->表 auth_sources
 enumeration.rb # 系统枚举常量 –> 表 enumerations
 custom_field.rb # 自定义字段 ->表custom_fields。
 custom_value.rb # 自定义字段值 -> 表 custom_values
 custom_field_value.rb # 将custom_field,issue, custom_value联系起来 -> 无表关联。
 principal.rb # group和use的基类
 group.rb # 系统分组 –> 表groups_users
 group_custom_field.rb # 分组的自定义字段 -> 继承自custom_field
 mail_handler.rb # 用于接收邮件 -> 无表关联
 mailer.rb # 发送邮件 –> 无表关联
 query.rb # 自定义查询 –> 表 queries
 role.rb # 成员角色 –> 表roles
 setting.rb # 系统设置 –> 表settings
 workflow_permission.rb # 工作流权字段限配置, 继承于workflow_rule
 workflow_rule.rb # 工作流程 -> workflows
 workflow_transition.rb # 工作流程状态切换权限配置, 继承于workflow_rule
--- # 用户信息
 token.rb # 用户标识(例如rest api键值, rss键值)
 user.rb # 用户账户记录-> 表users
 user_custom_field.rb # 用户自定义字段, 继承于custom_field
 user_preference.rb # 用户偏好设置 -> 表user_preferences
------ # 项目数据相关
 project.rb # 项目记录 -> 表projects
 project_custom_field.rb # 项目的自定义字段, 继承自custom_field
--- # 问题票数据
 issue.rb # 问题票 –> 表 issues
 issue_category.rb # 问题类别 -> 表issue_categories
 issue_custom_field.rb # 问题票自定义字段 -> 继承自custom_field
 issue_observer.rb # 问题观察类, 监视issues记录的创建,并执行相应的行为 ->无表关联
 issue_priority.rb # 问题优先级, 继承自enumeration
 issue_priority_custom_field.rb # 问题优先级的自定义字段, 继承自custom_field
 issue_query.rb # 问题票查询, 继承自query
 issue_relation.rb # 问题票关联 -> 表issue_relations
 issue_status.rb # 问题票状态 -> 表 issue_statuses
 journal.rb # 问题票历史注记
 journal_detail.rb # 历史注记详细条目(各个字段变更)的详细历史信息
 journal_observer.rb # 历史注记的监视器。每当有新的注记建立, 发送通知邮件。 ->无表关联
 attachment.rb # 附件文件 ->表attachments
 tracker.rb # 跟踪标签 -> 表trackers
 version.rb # 问题票版本信息 ->表 versions
 version_custom_field.rb # 版本的自定义字段, 继承于custom_field
 watcher.rb # 跟踪者 -> 表watchers
--- # 讨论区
 board.rb # 讨论区 ->表boards
 message.rb # 讨论区留言 –> 表 messages
 message_observer.rb # 讨论区留言监视器, 留言建立后发送通知邮件 -> 无表关联
--- # 新闻
 news.rb # 新闻条目 –> 表news
 news_observer.rb # 新闻监视器。新闻新建后向用户发送邮件 -> 无表关联
 comment.rb # 新闻的评论/留言 ->表comments
 comment_observer.rb # Observer类, 监视comments记录添加的行为。无表关联。
--- # 文档
 document.rb # 文档 -> 表 documents
 document_category.rb # -> 无表关联。
 document_category_custom_field.rb # -> 无表关联。
 document_observer.rb # -> 无表关联。
--- #wiki
 wiki.rb # wiki -> 表wikis
 wiki_content.rb # wiki 记录 –> 表wiki_contents
 wiki_content_observer.rb # wiki_content 监视器, 新建或更新后发送通知邮件 -> 无表关联
 wiki_page.rb # wiki页 -> 表wiki_pages
 wiki_redirect.rb # wiki重定向 -> 表wiki_redirects
--- # 工时
 time_entry.rb # 工时记录 -> 表time_entries
 time_entry_activity.rb # 工时的类型, 继承于enumeration
 time_entry_activity_custom_field.rb #工时的类型的自定义字段, 继承于custom_field
 time_entry_custom_field.rb # 工时的自定义字段, 继承于custom_field
 time_entry_query.rb # 工时的查询, 继承于query
--- # 配置
 enabled_module.rb # 项目启用的模块 -> 表enabled_modules
 member.rb # 项目成员 -> 表members
 member_role.rb # 成员的角色 –> member_roles

 4.2 代码结构

关于Rails框架的模型, 请阅读参考资料, 资料基本覆盖了代码所涉及的全部知识点:

http://guides.ruby-china.org/active_record_basics.html
http://guides.ruby-china.org/active_record_migrations.html
http://guides.ruby-china.org/active_record_validations.html
http://guides.ruby-china.org/active_record_callbacks.html
http://guides.ruby-china.org/association_basics.html
http://guides.ruby-china.org/active_record_querying.html

模型的代码和控制器的代码一样, 可分为两个部分:模型声明和模型方法的实现。

4.2.1 模型声明

1. 模型关联

belongs_to :xxx, 一对一关系, 声明所在的模型属于另一个模型(xxx)。

has_many :xxx, 关联建立两个模型之间的一对多关系。在 belongs_to 关联的另一端经常会使用这个关联。 表示模型的实例有一个或多个另一模型(xxx)的实例。

2. 数据验证

validates_presence_of :xxx, xxx字段的数据必须存在。

validates_length_of :xxx, :maximum => 255, xxx字段的数据长度必须小于255

validate :xxx, xxx是自定义的数据验证函数。

validates_uniqueness_of :xxx, xxx的唯一性验证。

before_validation :xxx, 在验证数据前执行xxx方法。

3. 添加新方法

acts_as_event {options}  为相应的类添加event_datetime, event_title, event_description, event_author, event_type, event_date, event_group, event_url,recipients方法。

acts_as_activity_provider 为活动模块(用户活动, 项目活动)注册被认为是活动的事件。这个类方法的详细信息请参考./lib/redmine/plugin.rb中方法activity_provider的注释。

acts_as_tree, 使得模型实例能够构造成树状结构。 具体说明请阅读, . lib/plugins/acts_as_tree/README

acts_as_list, 使得模型实例能够构造成列表结构。 具体说明请阅读, . lib/plugins/acts_as_list/README

scope :xxx,  作用域把常用的查询定义成方法,在关联对象或模型上调用。

4.类变量

cattr_accessor :xxx , xxx相当于Java中类的静态方法, 在类中通过@@xxx的方法访问, 类外通过A.xxx的方法访问(A为类名)。

5.动作钩子方法

before_save :xxx, 模型保存数据前执行xxx方法。

after_destroy :xxx, 模型删除数据后执行xxx方法。
4.2.2 模型方法

大多是业务逻辑,暂时没什么可写。

 五、 视图

Rails框架中, 视图是控制器用来渲染前端的模板, 是将运算数据生动呈现给用户的关键。Rails学习指南用三篇文章讲解视图:

《Action View基础》 http://guides.ruby-china.org/action_view_overview.html
《Rails布局和视图渲染》http://guides.ruby-china.org/layouts_and_rendering.html
《Action View表单帮助方法》 http://guides.ruby-china.org/form_helpers.html

redmine的代码中, 视图和控制器几乎是一一对应的, 视图仅仅比控制器多出一个文件夹./app/views/layout, 而这个文件夹中存放着的,是整个页面的大布局。 仅凭上述三篇文章,便可阅读绝大部分视图代码。 这里不再多述

六、Rake任务

Rails框架中, 我们通过rake文件可以完成一系列任务。这些复杂的任务, 通过简单地键入rake xxxx便可执行。而Redmine系统则使用rake管理常见的任务, 例如数据库迁移, 发送问题票过期通知邮件等等。 学习资料: http://guides.ruby-china.org/command_line.html#rake

6.1 Redmine Rake

系统默认的rake文件都存在于./lib/tasks文件夹下,本人使用最多的就是rake redmine:plugins:migrate RAILS_ENV=production, 它的作用是完成插件数据库的迁移。使用命令rake –T就能看到所有的rake任务,以及对rake任务简单的说明。

6.2 自定义Rake

我们也可以自己编写自定义rake. 自定义的rake文件可以放在./lib/tasks中, 也可以放在插件的相应目录中./plugins/xxx(plugins_name)/lib/tasks。 推荐将其放在插件的目录中, 这样便于管理。

编写rake文件, 首先在./lib/tasks或插件的相应路径下新建一个文件,文件名以.rake为后缀。添加如下内容:

namespace :redmine do
    task :send_reminders => :environment do
        options = {}
        options[:days] = ENV['days'].to_i if ENV['days']
        ……Rake任务代码
    end
end
  • namespace :redmine do …… end 表示名字空间, 我们可以通过名字空间访问到相应的任务。
  • task :send_reminders => :environment do … end 表示建立一个名为send_reminders的rake任务。 通过 => :environment, 我们便可以在rake任务中使用redmine的类与模块。
  • options[:days] = ENV[‘days’].to_i if ENV[‘days’], 其中, ENV[‘days’]中保存了通过命令行的days=7传递进来的参数。

执行命令: rake redmine:send_reminders days=7 RAILS_ENV=production, 其中redmine为名字空间, send_reminders为任务名, days=7为向rake任务传递的参数, RAILS_ENV为系统环境变量。

 

 

 

 

 

 

 

 

 

    • 我直接用普通的文本编辑器, 学习redmine用普通的文本编辑器应该就够了吧,至少我当初觉得还行。js, php, python, ruby我一般都用文本编辑器,不过程序太大之后确实不太好hold。。你如果遇到好的工具也给我推荐下吧。。