OTRS 二次開發筆記


公司使用otrs系統處理業務工單,各種事件流。因為是開源免費系統,因此需要在上面做一些功能補充或定制的二次開發。

otrs是什么?###

OTRS 是一個功能強大的工單系統。完美適用於服務台(Help Desk)、IT服務管理、流程管理、機會管理和更多其他領域
它的文檔挺詳細的:http://doc.otrs.com/doc/

  • 使用者看 Admin Manual
  • 開發者看 Developer Manual 和 API Reference

otrs用什么開發的?#

1. 語言 Perl##

為了開發這個花了一個月時間啃完《Perl入門》《Perl進階》。因為PHP的關系,某些語法很容易接受,然而在另一些地方總是覺得很蛋疼。。最讓我無法接受的是Perl的調試,我到現在還不知道除了看apache錯誤日志外怎么更好地調試perl的web應用。php因為是腳本語言,不停地echo,print_r然后打開瀏覽器就可以看到結果。然而perl只能看到一個500 Internal server error. (╯‵□′)╯︵┻━┻

2. 數據庫 Mysql##

otrs支持多個數據庫,一般mysql就好了,沒啥好講的

3. 安裝和環境##

otrs有rpm包傻瓜式安裝,http://ftp.otrs.org/pub/otrs/RPMS/
otrs的源碼安裝的話解壓就可以了。然后自己配置apache安裝perl模塊。配置httpd.conf 修改cgi-bin 目錄為 otrs的cgi-bin目錄。這個配置過程不是很懂,感覺是歪打正着成功的。。。
打開瀏覽器 /otrs/installer.pl 按步驟配置好數據庫和用戶就可以用了。
otrs 有一個定時任務腳本,需要隨時同步更新配置文件,收發郵件等等。因此安裝好了之后不要忘了啟動它。

如何補充otrs的功能?#

1. otrs核心目錄 Kernel##

-- Config 配置目錄,包括菜單,訪問權限的配置
-- Modules 模塊目錄,相當於控制器
-- Output 模板目錄,otrs有一套模板引擎
-- System 系統目錄,各種核心功能驅動器,和數據庫操作模型

弄清楚目錄結構當然是首要的。通過學習開發文檔和查看源碼可以迅速掌握各模塊的功能,加載方法。

2. otrs二次開發##

目前運行的是otrs4.0版本,我做開發測試的是5.0版本。開發上基本沒有差別,只有極個別文件目錄不一樣。

配置文件###

首先我們開發的核心模塊都根據各自角色放在相應的文件夾,然后全部放在Custom目錄下,如圖:

然后在 Kernel/Config 目錄下放置我們的配置文件,主要是添加系統菜單。具體規則可以參考它原有的配置文件來寫,下面是我寫的一個例子:

<ConfigItem Name="Frontend::Module###AgentWSCustom" Required="1" Valid="1">
    <Description Translatable="1">FrontendModuleRegistration for WSCustom module.</Description>
    <Group>WSCustom</Group>
    <SubGroup>Frontend::Agent::ModuleRegistration</SubGroup>
    <Setting>
        <FrontendModuleReg>
            <Title>自定義模塊</Title>
            <Group>users</Group>
            <Description>自定義模塊</Description>
            <NavBarName>自定義</NavBarName>
            <NavBar>
                <Description Translatable="1">自定義首頁</Description>
                <Name Translatable="1">工單查詢</Name>
                <Link>Action=AgentWSCustom</Link>
                <LinkOption></LinkOption>
                <NavBar>自定義</NavBar>
                <Type></Type>
                <Block></Block>
                <AccessKey>w</AccessKey>
                <Prio>100</Prio>
            </NavBar>
            <NavBar>
                <Description Translatable="1">自定義菜單</Description>
                <Type>Menu</Type>
                <Block>ItemArea</Block>
                <Name Translatable="1">自定義</Name>
                <Link>Action=AgentWSCustom</Link>
                <LinkOption>data-id="10086"</LinkOption>
                <NavBar>自定義</NavBar>
                <AccessKey>t</AccessKey>
                <Prio>10100</Prio>
            </NavBar>
            <Loader>
                <CSS>Custom.Agent.WSCustom.css</CSS>
                <JavaScript>Custom.Agent.WSCustom.js</JavaScript>
            </Loader>
        </FrontendModuleReg>
    </Setting>
</ConfigItem>

配置的菜單鏈接是 Action=AgentWSCustom, 因此需要在 Custom/Kernel/Modules/ 下建一個 AgentWSCustom.pm 模塊(類):

控制器<AgentWSCustom.pm>##

package Kernel::Modules::AgentWSCustom;

use strict;
use warnings;

use POSIX;
use Encode;

use Kernel::Language qw(Translatable);

# Frontend modules are not handled by the ObjectManager.
our $ObjectManagerDisabled = 1;

sub new {
    my ( $Type, %Param ) = @_;

    # allocate new hash for object
    my $Self = {%Param};
    bless ($Self, $Type);

    return $Self;
}

sub Run {
    my ( $Self, %Param ) = @_;
    my %Data = ();
    
    # store last queue screen
    $Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
        SessionID => $Self->{SessionID},
        Key       => 'LastScreenOverview',
        Value     => $Self->{RequestedURL},
    );
    # Kernel modules
    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
    my $LayoutObject     = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
    my $ParamObject  = $Kernel::OM->Get('Kernel::System::Web::Request');
    my $TimeObject = $Kernel::OM->Get('Kernel::System::Time');
    # Custom module
    my $WSCustom = $Kernel::OM->Get('Kernel::System::WSCustom');
    # do something
    my $Tickets= $WSCustom->GetTicketsList();
    
    # result overview
    $LayoutObject->Block(
        Name => 'OverviewResult',
        Data => {},
    );
    
    for my $t (@{$Tickets}) {
        # output the result data
        $LayoutObject->Block(
            Name => 'OverviewResultRow',
            Data => {
                %{$t},
            },
        );
    }
    # build output
    my $Output = $LayoutObject->Header();
    $Output   .= $LayoutObject->NavigationBar();
    $Data{hello} = "Hello Xxq, Bye!";
    $Output   .= $LayoutObject->Output(
        Data    => \%Data,
        TemplateFile => 'AgentWSCustom',
    );
    $Output   .= $LayoutObject->Footer();
    return $Output;
}

控制器的核心是 Run() ,基本結構是先加載各種系統組件,然后操作數據庫模型,在這里我們是 my $Tickets= $WSCustom->GetTicketsList(); 。因此需要在 Custom/Kernel/System/ 添加一個名為 WSCustom.pm 模塊文件。
模板輸出是 $LayoutObject 控制, Block() 方法可以渲染一個模板塊,一般配合循環輸出列表或表格。Output() 方法接收綁定到模板的變量Data,和模板文件名TemplateFile。因此需要在 Custom/Kernel/Output/HTML/Templates/Standard 目錄下添加一個名為 AgentWSCustom.tt 的模板文件(根據版本不同,這個路徑會不一樣)。

數據庫模型<WSCustom.pm>##

package Kernel::System::WSCustom;

use strict;
use warnings;

# list your object dependencies (e.g. Kernel::System::DB) here
our @ObjectDependencies = (
    'Kernel::System::DB',
);

sub new {
    my ( $Type, %Param ) = @_;
    
    # allocate new hash for object
    my $Self = {};
    bless ($Self, $Type);
    
    return $Self;
}

sub GetTicketsList {
    my ( $Self, %Param ) = @_;
    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
    # id,工單號,創建時間,修改(完成)時間,工單狀態ID
    my $sql = " SELECT t.id,t.tn,t.create_time,t.change_time,t.ticket_state_id state_id, ";
    # 隊列,類型,狀態,服務類型
    $sql .= " q.name queue,tt.name type,ts.name state,serv.name service, ";
    # 客戶,派發人
    $sql .= " cc.name customer,CONCAT(u.last_name,u.first_name) creater FROM ticket t ";
    $sql .= " LEFT JOIN queue q on t.queue_id=q.id ";
    $sql .= " LEFT JOIN ticket_type tt on t.type_id=tt.id ";
    $sql .= " LEFT JOIN ticket_state ts ON t.ticket_state_id=ts.id ";
    $sql .= " LEFT JOIN service serv ON t.service_id=serv.id ";
    $sql .= " LEFT JOIN customer_company cc ON t.customer_id=cc.customer_id";
    $sql .= " LEFT JOIN users u ON t.create_by=u.id ";
    $sql .= " WHERE t.create_time_unix BETWEEN ? AND ? ORDER BY t.id ASC ";
    $DBObject->Prepare(
        SQL => $sql,
        Bind => [ \$Param{from}, \$Param{to} ]
    );
    my @res;
    while (my @Row = $DBObject->FetchrowArray()) {
        my %row;
        $row{id} = $Row[0];
        $row{tn} = $Row[1];
        $row{create} = $Row[2];
        $row{change} = $Row[3];
        $row{state_id} = $Row[4];
        $row{queue} = $Row[5];
        $row{type} = $Row[6];
        $row{state} = $Row[7];
        $row{service} = $Row[8];
        $row{customer} = $Row[9];
        $row{creater} = $Row[10];
        push @res, \%row;
    }
    return \@res;
}

就是一些純粹的數據讀取,沒啥好說的。

模板文件<AgentWSCustom.tt>###

<div class="custom-container">
	<div class="">
	<form action="[% Env("CGIHandle") %]" method="get">
		<input type="hidden" name="Action" value="[% Env("Action") %]" id="SearchAction"/>
        <input type="hidden" name="Subaction" value="Search"/>
		<div class="ws-form-control"><label><span class="ws-form-label">開始:</span><input type="text" name="from" value="[% Data.from %]" placeholder="yyyy-mm-dd" /></label></div>
		<div class="ws-form-control"><label><span class="ws-form-label">結束:</span><input type="text" name="to" value="[% Data.to %]" placeholder="yyyy-mm-dd" /></label></div>
		<div class="ws-form-control">
			<button class="ws-form-btn" name="subAction" value="search">查詢</button>
			<button class="ws-form-btn" name="subAction" value="export">導出</button>
		</div>
		<div class="ws-form-control ws-tips">Tips:由於工單數量較多,查詢比較耗時。日期跨度建議在60天以內。</div>
	</form>
	</div>
	<div class="result">
[% RenderBlockStart("OverviewResult") %]
        <div class="Header">
            <h2>[% Translate("結果列表") | html %]</h2>
        </div>

        <div class="Content">
            <table class="DataTable" summary="工單列表">
                <thead>
                    <tr>
                        <th>[% Translate("工單號") | html %]</th>
                        <th>[% Translate("隊列") | html %]</th>
                        <th>[% Translate("服務種類") | html %]</th>
                        <th>[% Translate("服務類型") | html %]</th>
                        <th>[% Translate("客戶") | html %]</th>
                        <th>[% Translate("工單狀態") | html %]</th>
                        <th>[% Translate("派發人") | html %]</th>
                        <th>[% Translate("處理人") | html %]</th>
                        <th>[% Translate("創建時間") | html %]</th>
                        <th>[% Translate("關閉時間") | html %]</th>
                        <th>[% Translate("總時長") | html %]</th>
                    </tr>
                </thead>
                <tbody>
[% RenderBlockStart("NoDataFoundMsg") %]
                    <tr>
                        <td colspan="4">
                        [% Translate("No result found.") | html %]
                        </td>
                    </tr>
[% RenderBlockEnd("NoDataFoundMsg") %]
[% RenderBlockStart("OverviewResultRow") %]
                    <tr class="">
                        <td>
                        	<a href="[% Env("Baselink") %]Action=AgentTicketZoom;TicketID=[% Data.id | uri %]" title="" class="MasterActionLink">[% Data.tn | html %]</a>
                        </td>
                        <td>[% Data.queue | html %]</td>
                        <td>[% Translate(Data.type) | html %]</td>
                        <td>[% Data.service | html %]</td>
                        <td>[% Data.customer | html %]</td>
                        <td>[% Translate(Data.state) | html %]</td>
                        <td>[% Data.creater | html %]</td>
                        <td>[% Translate(Data.handler) | html %]</td>
                        <td>[% Data.create | html %]</td>
                        <td>[% Data.close | html %]</td>
                        <td>[% Data.totalTime | html %]</td>
                    </tr>
[% RenderBlockEnd("OverviewResultRow") %]
                </tbody>
            </table>
        </div>
[% RenderBlockEnd("OverviewResult") %]
	</div>
</div>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">
$(function(){
	console.log('It works!!!');
});
</script>
[% END %]

模板里比較重要的是Translate() 方法,另外 JSOnDocumentComplete 支持你直接插入JS代碼。

靜態css 和 js

頁面引入的靜態css,js 需要在config的xml文件中配置,然后放入目錄 var/httpd/htdocs/skins/Agent/default/css/var/httpd/htdocs/js/,如圖:

到這里我的自定義模塊大功告成。

3. 亂碼的坑##

之前嘗試在控制器中綁定中文字符串,在頁面輸出總是亂碼。后來無意之中在控制器先對中文字符串解碼 Encode.decode("utf8", "中文字符"), 然后完美解決,包括導出到csv,excel文件。

import Encode;
$ZhStr = decode("utf8", "中文是必要的");


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM