快照平面文件自定义字段映射
字段映射
SIS 框架支持在逐条记录将传入数据添加到 Blackboard 之前对其进行自定义。当 学生信息系统 中的数据不符合 Blackboard 数据要求,并且由于 学生信息系统 令人望而却步,或者数据所代表的信息被其他系统使用时,这非常有用。本文档介绍如何使用“字段映射”选项,特别是如何使用自定义脚本字段映射选项。
这种自定义传入数据的功能称为字段映射,可在用户界面中的集成的“高级配置”菜单下访问。

高级配置屏幕提供了 Blackboard 中该对象支持的对象列表。这些与集成类型对齐的内容因集成类型而异。

在与集成类型(本例中为课程)相关的 Learn 对象上选择“字段映射”时,系统将引导您进入集成的“字段映射”页面。

除了有关字段的有用信息(如插入是否必填或是否要求唯一)外,您还可以选择几种设置来控制集成如何管理传入数据,以及是否选择在传入对象之间进行映射。

自定义脚本和字段映射
在您希望提供字段映射的字段中,您可以选择使用其他传入字段。例如:假设传入的课程 ID 不适合您在 Blackboard 中使用,您可以将其映射到课程外部键。由于本教程是使用自定义脚本来执行映射,因此您需要从“源字段”下拉菜单中选择“使用自定义脚本”。
自定义脚本基于 JavaScript。所有传入的数据都会映射到一个内部对象模型,该模型能够访问每个数据元素的数据,并随后利用自定义 JavaScript 来使用或进行操作。
在 JavaScript switch 语句中使用整数比较器。如果必须使用字符串或字符作为比较器,请使用 if/else 语句而不是 switch 语句。
Mozilla Developer Network https://developer.mozilla.org/en-US/...ript/Reference 和 World Wide Web Consortium (W3C) http://www.w3schools.com/jsref/ 提供了两个优秀的 JavaScript 参考。

选择“使用自定义脚本”可为您提供一个文本区域,用于输入您的 JavaScript 代码。

在此文本区域中,您将输入自定义脚本以编辑传入数据。
Blackboard 内文档
有关自定义字段映射脚本的文档,请参阅从 Blackboard 管理员面板的顶级学生信息系统集成页面链接的示例文档页面。

示例文档链接可打开一个页面,其中包含多个指向 SIS 集成框架文档的链接,这些文档涵盖所有集成类型。快照平面文件集成类型和自定义字段映射上下文中感兴趣的两个文档分别通过数据字典链接:快照平面文件集成和自定义字段映射脚本示例。

自定义脚本
对每种集成类型的对象模型的访问都是不同的,并基于集成类型数据规范的底层数据结构。通过对集成数据规范的了解,可以推导出访问所有集成对象的模式。
在快照平面文件中,这种模式基于“data”根对象,因此我们可以得出以下模式:
data.getValue(<FlatFileDataObject>);
其中 <FlatFileDataObject> 替换为数据馈送中的标头元素名称。
例如:
data.getValue("firstname");
data.getValue("lastname");
data.getValue("course_name");
此外,如果数据馈送采用“Y”或“N”,则必须有条件地将脚本结果分别替换为布尔值 true 或 false。例如:
var avail_ind = false;
if (data.getValue("available_ind").toUpperCase() == 'Y') {
avail_ind=true;
avail_ind;
或者使用采用三元运算符的语句编写:
条件 ?如果条件为 true,则执行:如果条件为 false,则执行:
例如:
data.getValue("available_ind").toUpperCase( )=='Y' ? avail_ind = true : avail_ind = false;
avail_ind;
上述内容可以进一步简化为三元语句,因为它是脚本最后一行表达式的最终值,作为保存在 Blackboard 中的数据点返回。
所以我们可以使用:
data.getValue("available_ind").toUpperCase() == 'Y' ? avail_ind = true : avail_ind = false;
并获得相同的结果。
由于自定义脚本按记录处理,如果执行计算密集型脚本(如加密),则集成性能可能会受到影响。还请注意,不支持访问外部数据,您只能对现有的馈送数据或计算得出的数据执行操作。
调试脚本
调试脚本相对容易,因为您可能遇到的错误或数据问题都会被记录下来。
例如:
假设对“Available”字段开发自定义脚本:
data.getValue("available_ind");
可能会成功运行,但如果数据馈送中没有传递该值,脚本将在日志中产生以下条目:
为不可为 null 属性返回的 Null 值:isAvailable。
属性数据无效:isAvailable。值:null。使用默认黑板值。
其中 as(假设馈送中提供了 available_ind):
data.getValue("available_ind");
可能会成功运行,但是会在日志中生成以下条目:
attribute: isAvailable 的值类型无效。值:y
属性数据无效:isAvailable。值:Y。使用默认黑板值。
这是因为我们需要有条件地将传入数据更改为预期的布尔值 - 在本例中为 “TRUE”,因此我们可以将脚本更改为:
data.getValue("available_ind") == 'y'?true:false;
但为了安全起见,我们希望捕捉大小写字母的比较,因此我们对脚本进行了调整:
data.getValue("available_ind").toUpper() == 'Y' ? true : false;
这将导致实际的脚本错误并记录到日志中:
属性 isAvailable 的脚本执行错误。
blackboard.platform.script.ScriptingException: javax.script.ScriptException: sun.org.mozilla.javascript.internal.EcmaError: ReferenceError: "toUpper" is not defined. (<Unknown Source>#2) in <Unknown Source> at line number 2
这是因为 JavaScript 没有 toUpper(),但它确实有 toUpperCase()所以编辑脚本......
data.getValue("available_ind").toUpperCase() =='Y' ? true : false;
现在脚本将成功运行。
帮助程序脚本:登录自定义脚本
Blackboard 提供辅助脚本,用于提供常用功能,例如脚本日志记录。向脚本添加以下内容将会在集成日志中包含输出内容:
helper.logError(msg)
helper.logWarning(msg)
helper.logInfo(msg)
helper.logDebug(msg)
根据日志记录级别的集成配置,helper.log* 方法的输出将在日志中可见。
其他可用的帮助程序脚本(如从示例文档页面链接的自定义字段映射脚本示例页面中所述)包括:
helper.getBatchUid( String id )- 这样就构建了一个标识符,该标识符前缀为特定于 SIS 集成的批处理 Uid 前缀,该映射在该 SIS 集成上运行。在生成“唯一”ID 时应使用该前缀,因为该前缀有助于避免 ID 冲突。helper.getHelper( String helperName )- 返回特定于 SIS 集成类型的帮助程序对象,其中包含适用于该集成类型的帮助程序方法。每个 SIS 集成类型的 JavaDocs 中都将提供这些帮助程序的文档。helper.skipAttribute()- 这会返回一个值,当从映射脚本返回该值时,会导致所映射的字段被跳过(不更改)。helper.skipAttributeIfNull( Object value )- 如果传入的值为空,则返回值与 skipAttribute 相同。如果它不为空,则返回该值。helper.skipRecord()- 这会返回一个值,当从映射脚本返回该值时,会导致当前正在处理的整个记录被跳过。helper.skipRecordIfNull( Object value )- 如果传入的值为空,则返回值与 skipRecord 相同。如果它不为空,则返回该值。
继续使用我们的示例脚本,我们将添加一条信息日志消息,说明 Blackboard 中使用的值来自自定义脚本、插入数据引用的基础数据对象以及使用的计算值。我们还将添加一条日志信息,说明由于缺乏合适的可用脚本而出现的错误情况。
重新访问上面的 isAvailable 脚本,假设课程数据馈送中的AVAILABLE_IND设置不一致 - 这意味着它并不总是存在于数据馈送中 - 并且您想在 AVAILABLE_IND 的值不存在时将其设置为 true,或者以其他方式进行适当的评估?脚本现在变为:
var outInd = false;
var inInd = data.getValue("available_ind")
if (inInd == "") {
helper.logInfo("INCOMING AVAILABLE_IND UNDEFINED for "+data.getValue("course_id")+": setting isAvailable to true");
outInd=true;
} else {
data.getValue("available_ind").toUpperCase()=='Y'?outInd=true:outInd=false;
}
helper.logInfo("OUTGOING AVAILABLE_IND: ["+outInd+"]");
outInd;
日志现在显示:
2013 年 5 月 31 日 下午 4:03:56 - 课程 [创建/更新, testCourse1]
TEST_COURSE_1的传入AVAILABLE_IND未定义:设置 isAvailable 为 true
传出AVAILABLE_IND:[true]
课程 'testCourse1' 已成功处理。
testCourse1|TEST_COURSE_1|Test Course 1|
...
剩余日志条目
...
字段映射成功。
示例
课程名称
密码
电子邮件
课程名称:添加学期和年份
使用案例
希望课程显示包含课程开设的学期和年份。目前,SIS 提供的数据并未将此信息附加到 course_name 中。课程使用课程名称显示。例如:

先决条件
SIS 将学期和年份作为课程 ID 字符串的一部分,并提供必填的课程名称字段。
例如:
external_course_key|course_id|course_name|available_ind
ARTHIST.202.01|36202010114|Art History 202: Renaissance Architecture|Y
在上面的示例中,course_id由以下部分组成:
部门:36
课程:202
该部分:01
学期月:01
年份:14
要求
1. 学期由指定月份决定。例如:
01 = 冬季
04 = 春季
06 = 夏季
09 = 秋季
2. 在课程名称后加上以编程方式导出的学期和年份,中间用空格隔开,并用括号“()”括起来。例如:
艺术史 202:文艺复兴时期的建筑(2014 年冬季)
3. 如果未提供合适的学期,则忽略附加信息。
后置条件
1. 如果course_id提供合适的学期/年标识符,则在 Blackboard 中创建或更新课程记录之前,其course_name数据将附加为 (学期 YEAR)。
2. course_id中没有合适学期/年标识的课程将不会更改其course_name。
脚本
课程名称在学生信息系统数据馈送course_name字段中提供,所需的学期信息以数字格式包含在学生信息系统数据馈送元素course_id字段中。为了满足要求,我们需要
a) 确定年份,以及
b) 通过映射到范围来确定学期,然后正确格式化结果数据。
为此,我们将提供 javascript 函数,用于返回课程名称显示所需的格式,并将这些函数的输出置入适当的格式中。
注意
HTMLGoodies (http://www.htmlgoodies.com/beyond/ja...d-classes.html) 和万维网联盟 (W3C) (http://www.w3schools.com/js/js_functions.asp) 网站上提供了编写 javascript 函数的教程。
a) 确定年份。
给定course_id在字符序列的末尾包含一个两个数字的年份,我们可以编写一个函数来拉取并返回最后两个字符:
function courseYear(crn) {
return crn.substring(9);
}
b) 确定术语。
course_id还为我们提供了确定学期的数据,我们可以使用 JavaScript 子字符串函数来提取 MM 数据并将其映射到代表学期标签的月份范围 - 例如:09=秋季,01=冬季,04=春季,06=夏季
注意
这是一个简单的情况 - 数据还可以提供开始日期,这些日期可以与course_id结合使用,以有条件地确定学期。
function getTerm(crn) {
var termCode=crn.substring(7, 9);
var term="";
if (termCode == "01") {
term="Winter";
} else if (termCode == "04") {
term="Spring";
} else if (termCode == "06") {
term="Summer";
} else if (termCode == "09") {
term="Fall";
} else {
term="";
}
return term;
}
function getYear(crn) {
return crn.substring(9);
}
因为我们在这里处理的是整数,所以在编写 getTerm 函数时可以使用 switch 语句,而不是上面那个使用 if/else 的语句...
function getTerm(crn) {
var termCode = parseInt(crn.substring(7,9));
var term = "";
switch (termCode)
{
case 09: term ="Fall"; break;
case 01: term ="Winter"; break;
case 04: term ="Spring"; break;
case 06: term ="Summer"; break;
default: term ="";
}
return termString ;
}
注意
switch 语句之所以有效,只是因为我们能够为比较器生成一个整数。
现在我们有了确定学期和年份的函数,让我们编写附加 course_name 的脚本:
function getTerm(crn) {
var termCode=crn.substring(7, 9);
var term="";
if (termCode == "01") {
term="Winter";
} else if (termCode == "04") {
term="Spring";
} else if (termCode == "06") {
term="Summer";
} else if (termCode == "09") {
term="Fall";
} else {
term="";
}
return term;
}
function getYear(crn) {
return crn.substring(9);
}
var crn = data.getValue("course_id");
var year = getYear(crn);
var term = getTerm(crn);
var courseName = data.getValue("course_name");
var newCourseName = "";
if (term!="") {
newCourseName = courseName + " (" + term + " 20" + year + ")";
} else {
newCourseName = courseName;
}
helper.logInfo("INCOMING COURSE_NAME " + data.getValue("course_name"));
helper.logInfo("INCOMING COURSE CRN " + crn);
helper.logInfo("INCOMING COURSE YEAR " + year);
helper.logInfo("INCOMING COURSE TERM " + term);
helper.logInfo("INCOMING COURSE_NAME " + data.getValue("course_name"));
helper.logInfo("OUT NEW NAME " + newCourseName);
newCourseName;
在“课程名称”的“课程”字段映射中输入上述内容,并使用集成的上传文件选项手动上传(存储)上述前提条件中的课程馈送示例,您将看到以下数据发布到我们示例课程的日志中:

课程名称现在将显示从course_id派生的学期和年份。
密码
使用案例
您使用 LDAP 对 Blackboard 用户进行身份验证,但 Blackboard 需要密码才能创建用户帐户,并且您的 学生信息系统 不提供密码。您需要为每个用户创建一个随机密码。
先决条件
数据源提供用户基本信息,可用于创建密码。例如:名字和姓氏
要求
密码应为名字+姓氏+随机数的组合
必须能够指定随机化的范围
后置条件
为用户创建密码 例如:
对于用户 Barney Rubble,密码可能是 barneyrubble102464
示例数据
external_person_key|user_id|passwd|firstname|lastname|email|system_role
testPerson1|aanderson_test|changeme|Alpha|Anderson||none
testPerson2|bvonbrown_test|changeme|Beta|Von Brown||none
testPerson3|ddavis_test|changeme|Delta|Davis!||none
testPerson4|ggardner_test|changeme|Gamma|G'Ardner||none
脚本
function rand (min, max) {
var argc = arguments.length;
if (argc = = = 0) {
min = 0;
max = 2147483647;
}
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var password = "";
var regex = new RegExp(" ", 'g');
helper.logInfo("INCOMING PASSWORD: " + data.getValue("passwd"));
helper.logInfo("INCOMING FIRSTNAME: " + data.getValue("firstname"));
helper.logInfo("INCOMING LASTNAME: " + data.getValue("lastname"));
password = (data.getValue ("firstname") + data.getValue ("lastname") + rand () + rand ()) .toUpperCase ();
password = password.replace(regex, '');
helper.logInfo("GENERATED PASSWORD: " + password);
password;
在密码的用户字段映射中输入上述内容,并手动上传(存储)上述示例数据中的用户(人员)信息馈送示例,您将看到以下数据发布到日志中:

电子邮件
注意
要正确验证电子邮件可能很复杂,有些人可能会认为,永远都不应该用简单的正则表达式或 javascript 来验证电子邮件。下面是一个高级示例,虽然可能正确匹配了 95% 或更多通过的电子邮件地址,但可能会遗漏一些边缘示例,因此与其说是电子邮件验证练习,不如说是一个示例,说明如何在自定义字段映射脚本中构建电子邮件地址和使用错误捕获。
使用案例
您的 学生信息系统 不存储 学生 电子邮件帐户,也不存储个人电子邮件帐户,但不存储由您的 机构 颁发的电子邮件帐户 - 您希望 Blackboard 中的电子邮件是 机构 颁发的电子邮件帐户。
先决条件
SIS 数据馈送中存在生成所需电子邮件地址本地部分所需的数据。例如:名字和姓氏
要求
机构颁发的电子邮件帐户遵循 [email protected]其中“-yy”代表学生的预期毕业年份。这些元素在 SIS 馈送中通过以下数据传递:
FIRSTNAME - 学生的名字
MIDDLENAME - 学生的中间名
LASTNAME - 学生的姓氏
OTHERNAME - 毕业年份
可接受的电子邮件地址的本地部分应只包含字母数字、“-”、“_”、“.”和撇号。例如:Gamma.G'[email protected]
后置条件
将生成格式正确的电子邮件地址并将其传递给 Blackboard。
示例数据
external_person_key|user_id|passwd|firstname|lastname|email|system_role
testPerson1|aanderson_test|changeme|Alpha|Anderson||none
testPerson2|bvonbrown_test|changeme|Beta|Von Brown||none
testPerson3|ddavis_test|changeme|Delta|Davis!||none
testPerson4|eedwards_test|changeme|E...nstitution.edu|none
testPerson5|ggardner_test|changeme|Gamma|G'Ardner||none
脚本
var emailAddress = "";
var instDomain = "institution.edu";
function buildIt() {
var fname = data.getValue("firstname");
var lname = data.getValue("lastname");
var regex = new RegExp(" ", 'g');
emailAddress = fname +"."+lname+"@"+instDomain;
emailAddress=emailAddress.toLowerCase();
emailAddress = emailAddress.replace(regex, '');
}
function validateIt(eAddress) {
var emailRegEx = /^([a-zA-Z0-9_\.\-\'])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
if (!emailRegEx.test(eAddress))
throw new Error("Email Validator Error: Cannot Validate Email Address");
}
emailAddress = data.getValue("email");
if (emailAddress == "" || emailAddress == null) {
buildIt();
} else if ( emailAddress.indexOf(instDomain) === -1) {
buildIt()
}
try {
validateIt(emailAddress);
} catch (err) {
helper.logError("INCOMING EMAIL ADDRESS: " + emailAddress);
helper.logError("INCOMING FIRSTNAME: " + data.getValue("firstname"));
helper.logError("INCOMING LASTNAME: " + data.getValue("lastname"));
helper.logError(err +" for User (" + data.getValue("user_id") +", Email (" + emailAddress + ").No Email Address Saved for this user. )");
emailAddress="";
}
helper.logInfo("emailAddress: " + emailAddress);
emailAddress;
在密码的用户字段映射中输入上述内容,并手动上传(存储)上述示例数据中的用户(人员)馈送示例,您会看到日志中发布了一个错误。
这是因为根据我们的正则表达式,电子邮件地址不能包含感叹号。
请注意,由于创建或更新用户记录不需要有效的电子邮件地址,因此用户仍然是创建的。
将 Delta Davis 的条目从:
testPerson4|ddavis_test|changeme|Delta|Davis!||none
更改为
testPerson4|ddavis_test|changeme|Delta|Davis||none
删除 Delta 姓氏字段中的“!”并重新上传文件可消除错误并使用新电子邮件地址更新 Delta 的记录。
出错时跳过记录或属性
帮助程序提供了两种额外的方法,允许更好地控制脚本中的处理:
helper.skipAttribute() - 跳过似乎设置不正确的非关键属性
helper.skipRecord() - 跳过整条记录并转到下一条记录
当电子邮件地址无法验证时,上述电子邮件脚本会记录错误,但仍会继续创建记录。如果我们不想创建空邮件地址的用户,而是想记录验证错误并转到下一条记录,则可以在脚本末尾根据电子邮件地址的条件添加 helper.skipRecord()。例如:
而不是使用 emailAddress=“” 关闭脚本;用以下方式关闭它:
(emailAddress=="") ? helper.skipRecord(): emailAddress;
这将根据 emailAddress 的内容有条件地跳过记录,当验证失败时,我们将 emailAddress 设置为空字符串。
将 helper.skipRecord() 方法添加到脚本中,并将 Delta Davis 的条目从:
testPerson4|ddavis_test|changeme|Delta|Davis||none
更改为
testPerson4|ddavis_test|changeme|Delta|Davis!||none
手动上传(存储)修订后的用户(人员)馈送示例(带感叹号),您将看到发布到日志中的数据。

请注意,该错误已记录在案,并在日志末尾注明了因映射脚本和返回结果的属性而跳过的记录。
有条件地应用 helper.skipAttribute() 将产生类似的影响,脚本将处理馈送数据,并根据调用 helper.skipAttribute() 的最后一行中是否存在条件,集成将抛出错误并跳过记录,或者如果属性不是必需的,映射配置将控制并按配置处理属性。