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", "中文是必要的");
OTRS 二次开发笔记的更多相关文章
- 【基于spark IM 的二次开发笔记】第一天 各种配置
[基于spark IM 的二次开发笔记]第一天 各种配置 http://juforg.iteye.com/blog/1870487 http://www.igniterealtime.org/down ...
- phpcms二次开发笔记
phpcms二次开发笔记 --soulsjie 以下载的全新的phpcms搭建一个新的站点为例,讲解如何利用phpcms进行二次开发 一.下载和安装phpcms http://www.phpcms.c ...
- 研究QGIS二次开发笔记(一)
为了在QT程序中嵌入一个地图,最终选择了QGIS来干这件事.选型阶段真是呵呵.我折腾的是QGIS2.4.0. 首先,到官方网站下载安装QGIS.如果你跟我一样懒的话,可能希望下载一个已经编译好的win ...
- RTX二次开发笔记2
问题一:关于DLL文件的引用 在安装文件夹内 APIObject.dll==>RTXSAPI.dll 服务器API接口 RTXCAPI.DLL ==> 客户端API接口 问题二:RTX二次 ...
- (dede)织梦系统二次开发笔记
(dede)织梦系统二次开发记录 --soulsjie 一.模板常用文件说明 模板文件都在文件夹templets下,我们以默认模板(default)为例,对模板文件结构进行分析: 首页模板文件目录 \ ...
- 派胜OA二次开发笔记(1)重写主界面
最近从派胜OA 2018 升级到 2019,为了二次开发方便,索性花了两天,反向分析 PaiOA 2019 主界面程序,重写大部分代码,方便对菜单权限进行控制. 主界面/core/index.aspx ...
- phpcms v9二次开发笔记
phpcms是基于MVC结构的. 安装: 下载phpcms_v9.5.9_UTF8.zip:新建目录phpcms,将压缩包里install_package目录下所有文件复制到phpcms目录.浏览器输 ...
- discuz二次开发笔记(二)------跳转函数运用
前几天在增加修改功能时,突然用到一个提示函数,有点不理解,看了他的由来后果断做下笔记,感觉这在以后的开发中肯定还是要用的上的.有些地方不是很理解,在以后慢慢纠正.查补. Htm页面中用的js跳转: $ ...
- prestashop二次开发 笔记(支付插件)
//主函数 public function __construct() { $this->name = 'CilPay'; //模块名称 $this->display ...
随机推荐
- HTML5 学习记录——2
20150826 1.声明文档类型 <!DOCTYPE> 声明HTML是用什么版本写的. 常用声明; 2.HYML头部元素 <head> <title> 定义 ...
- python中zip()函数基本用法
zip()函数接受一系列可迭代对象作为参数,将不同对象中相对应的元素打包成一个元组(tuple),返回由这些元组组成的list列表,如果传入的参数的长度不等,则返回的list列表的长度和传入参数中 ...
- 【leetcode刷题笔记】Longest Consecutive Sequence
Given an unsorted array of integers, find the length of the longest consecutive elements sequence. F ...
- 【遍历二叉树】05二叉树的层次遍历II【Binary Tree Level Order Traversal II】
就把vector改成用栈类存放层次遍历的一层的序列 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ...
- Android Studio & Butter Knife —— 快速开发
Butter Knife是一个Android的注解框架,可以帮助用户快速完成视图.资源与对象的绑定,完成事件的监听.(也就是少写findViewById()) 具体的介绍可以参考官方主页: http: ...
- bzoj 2342: 双倍回文 回文自动机
题目大意: 定义双倍回文串的左一半和右一半均是回文串的长度为4的倍数的回文串 求一个给定字符串中最长的双倍回文串的长度 题解: 我们知道可以简单地判定以某一点结尾的最长回文串 我们知道可以简单地判定以 ...
- Java应用中使用ShutdownHook友好地清理现场、退出JVM的2种方法
Runtime.getRuntime().addShutdownHook(shutdownHook); 这个方法的含义说明: 这个方法的意思就是在jvm中增加一个关闭的钩子,当jv ...
- lvs+keepalived和haproxy+heartbeat区别
最近一直在看一些高可用性的负载均衡方案,当然那些f5之类的硬件设备是玩不起也接触不到了.只能看这些for free的开源方案. 目前使用比较多的就是标题中提到的这两者,其实lvs和haproxy都是实 ...
- TModJS:目录
ylbtech-TModJS:目录 1.返回顶部 1. https://github.com/aui/tmodjs 2. https://www.npmjs.com/package/tmodjs 3. ...
- 《Kubernetes权威指南第2版》学习(三)RC学习
1 RC文件介绍: kind: ReplicationController,表示是一个RC: spec.selector: RC的Pod标签(Label)选择器,监控和管理拥有这些标签的Pod实例, ...