...
URL参数名 | URL参数值 | 说明 | |
输入参数 | className | AIChatRemoteService | |
methodName | loginIfSmartbiLogged | ||
params | [] | 样例:[] | |
返回值 | retCode | 0 | 非0表示错误 |
result | {\"token\":\"CE3CE70AC12B70052A507D4B560E5374\"} | NLA 的token信息,这个非常重要,后续NLA所有方法需要用到这个token; 非0时是错误信息 |
备注:登陆NLA前需要登陆成功Smartbi(通过账户密码/单点/其他登陆方式)
4.3 查询数据模型清单、推荐问句
URL参数名 | URL参数值 | 说明 | |
输入参数 | className | AIChatRemoteService | |
methodName | getBuildThemes | ||
params | |||
token | loginIfSmartbiLogged方法获取的token | ||
返回值 | retCode | 0 | 参考query方法 |
result | 参考query方法 |
...
<script> window.aiChatbotConfig = { // 配置AIChat的版本号 version: 1, // 配置嵌入的域名 iframeSrc: "http://localhost:3000", // btn: { // 按钮图片可以自定义配置 // backgroundImg: 'http://xxx.png' // } }; </script> <script type="text/javascript" src="http://localhost:3000/smartbi/vision/external.js"></script> |
嵌入后页面展示:
6. Demo代码编写
6.1 demo使用开发框架说明
本次demo,为了更好的说明第三方集成情况。采用完全模拟第三方平台的开发模式。demo应用使用的开发框架如下:
- Java框架 -- Springboot 2
- 前端框架 -- Vue2 、elementui、 jquery、Echarts
- SmartbiV11 -- 非常重要,本次开发对接的API是基于V11版本提供的
6.2 代码结构说明
【代码结构】
@startuml
前端HTML -> JS_API: 调用前端接口
JS_API --> APIController: 调用服务端接口
APIController --> SmartbiAPI: 调用Java远程接口
SmartbiAPI --> Smartbi_Server: 调用Smartbi接口
@enduml
【接口调用时序】
6.2.1 主要代码文件说明
...
文件
...
说明
...
后端Java代码
...
SmartbiAPI.java
...
Smartbi api 接口
...
APIController.java
...
演示程序,提供给前端的接口,实现内容与SmartbiAPI.java对应
...
前端JS
...
api.js
...
前端API接口,与APIController.java 对应
...
components/result-view.js
...
查询结果展示路由组件
...
components/query-tip.js
...
查询条件展示组件
...
components/select-entity.js
...
选择实体展示组件
...
components/result/chart-view.js
...
图形显示组件
...
components/result/table-view.js
...
表格显示组件
...
components/result/text-view.js
...
文字显示组件
...
components/result/select-view.js
...
模糊实体选择组件
...
前端HTML
...
index.html
...
页面文件
6.3 登录
【登录部分】
<el-form :inline="true" :model="form1" class="demo-form-inline">
<el-form-item label="账号:">
<el-input v-model="form1.user" placeholder="账号"></el-input>
</el-form-item>
<el-form-item label="密码:">
<el-input v-model="form1.password" placeholder="密码" show-password></el-input>
</el-form-item>
<el-form-item label="Smartbi地址:">
<el-input v-model="form1.server" placeholder="Smartbi地址" style="width:350px"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="login" :disabled="loginOk">登录</el-button>
</el-form-item>
</el-form>
login: function(server, userName, password, callback) {
$.ajax({
type: "POST",
url: 'smartbi/login',
data: {server: server, userName: userName, password: password},
success: function (data) {
callback(data);
},
error: function (data) {
alert(data.responseJSON.message);
}
});
},
/**
* 登录Smartbi 和 nla
* @param server
* @param userName
* @param password
* @return
*/
@RequestMapping("login")
public boolean login(String server, String userName, String password, HttpSession session) throws RemoteException {
SmartbiAPI smartbi = new SmartbiAPI(server);
try {
boolean rs = smartbi.login(userName, password);
if (rs) {
rs = smartbi.loginNLA();
if (rs) {
saveSmartbi(session, smartbi);
return true;
}
}
} catch (RemoteException e) {
logger.error(e.getMessage(), e);
throw e;
}
throw new RemoteException("登录失败");
}
...
6.4 执行查询
6.4.1 执行查询
...
<result-view ref="resultView" v-if="status==4"></result-view>
...
query() {
this.status = 3;
API.query(this.input1, (data) => {
if (data) {
this.status = 4;
this.$nextTick(() => {
this.$refs.resultView.setResult(data);
})
}
});
}
/**
* 查询
*/
query: function(question, callback) {
$.ajax({
type: "POST",
url: 'smartbi/query',
data: {question: question, datasetId: ""},
success: function (data) {
callback(data);
},
error: function (data) {
alert(data.responseJSON.message);
}
});
},
@RequestMapping("query")
public String query(String question, String datasetId, HttpSession session) throws RemoteException {
SmartbiAPI smartbi = getSmartbi(session);
if (smartbi == null) {
throw new RemoteException("用户未登录!");
} else {
try {
return smartbi.query(question, datasetId);
} catch (RemoteException e) {
logger.error(e.getMessage(), e);
throw e;
}
}
}
/**
* 执行自然语言查询
*
* @param question 问题
* @param datasetId 模型Id
* @return
*/
public String query(String question, String datasetId) throws RemoteException {
List<Object> params = new ArrayList<>();
params.add(question);
params.add(this.nlaToken);
//isMultiRound
params.add(false);
params.add(datasetId);
//reportId
params.add("");
//uuid
params.add("");
//userChoseThemeId -- 交互式问答选择的Id,或者用户强制的模型Id
params.add("");
//isMobileCall
params.add(false);
//getPageBO -- 集成仪表盘使用,是否返回仪表盘定义
params.add(false);
return remoteInvokeEx("AIChatRemoteService", "query", params);
}
6.4.2 结果展示路由
执行 query 方法后,将返回结果设置给 result-view 组件
setResult(result) {
this.result = JSON.parse(result);
this.routerResult();
this.show = true;
},
/**
* 路由,显示结果展示组件
*/
routerResult() {
if (this.result.code == 0) {
// 正常展示结果
if (this.isChartsResult()) {
this.resultComponent = Vue.component('chart-view');
this.resultContent = this.result.result;
} else if (this.isTableResult()) {
this.resultComponent = Vue.component('table-view');
this.resultContent = this.result.result.html;
} else {
this.resultComponent = Vue.component('text-view');
this.resultContent = "异常展示组件 -- 还没有实现!";
}
} else if (this.result.code == 50) {
// 反问,选择实体
this.resultComponent = Vue.component('select-view');
this.resultContent = this.result;
} else if (this.result.code == 10) {
// 元数据
this.resultComponent = Vue.component('text-view');
this.resultContent = "此处是元数据查询,还没有实现!";
} else if (this.result.code == 20) {
// 闲聊
this.resultComponent = Vue.component('text-view');
this.resultContent = "此处是聊天内容,还没有实现!";
} else if (this.result.code == 30) {
// 打开报表
this.resultComponent = Vue.component('text-view');
this.resultContent = "此处是打开报表,还没有实现!";
} else if (this.result.code == 100) {
// 空结果
this.resultComponent = Vue.component('text-view');
this.resultContent = "查询无结果!";
} else if (this.result.code == -10) {
// 如果nl2sql有输出,但smartbi查询数据报错
this.resultComponent = Vue.component('text-view');
this.resultContent = "查询报错了!" + this.result.message;
}
},
6.5 结果展示
6.5.1 表格展示
Vue.component('table-view', {
name: 'TableView',
template: '\
<div class="container">\
<div class="scrollbar" v-html="resultContent" style="max-height: 330px;"></div>\
</div>\
',
props: ["resultContent"]
});
6.5.2 图形展示
var tooltipValueFormatter = function(value) {
let valueStr = String(value);
let isFloat = valueStr.indexOf(".") + 1 > 0;
if (isFloat && typeof(value) == "number") {
return value.toFixed(2);
} else if (isFloat && typeof(value) == "string") {
return Number(value).toFixed(2);
}
return value;
}
Vue.component('chart-view', {
name: 'ChartView',
template: '\
<div class="container">\
<div style="height: 270px; width: 100%; background-color: white;" ref="echartContainerId"></div>\
</div>\
',
props: ["resultContent"],
data() {
return {
myChart: null,
chartType: null,
option: null,
onceShowMaxItem: 7
}
},
watch: {
//queryResult
resultContent: {
handler: function (newValue, oldValue) {
let that = this;
setTimeout(function () {
that.showResult(that.resultContent.portletType, that.resultContent.html);
}, 100);
},
immediate: true
}
},
computed: {
isEmptyData() {
//option不存在不是空数据
if (!this.option) {
return false;
}
let series = this.option.series;
return !series || (series && series.length === 0) || (!series[0].data || (series[0].data && series[0].data.length === 0))
}
},
methods: {
showResult(chartType, echartsOption) {
this.chartType = chartType;
this.option = JSON.parse(echartsOption);
if (this.isEmptyData) {
this.$emit("showEmptyQueryResultView");
return;
}
//该方法可以不需要,主要是对后端返回的option进行加工
this.processOption();
this.$nextTick(() => {
this.myChart = echarts.init(this.$refs.echartContainerId);
this.myChart.setOption(this.option);
});
},
processOption() {
let grid = {
left: '2%',
right: '2%',
bottom: '2%',
top: '7%',
containLabel: true,
show: 'true',
borderWidth: '0'
};
if (this.chartType === "ECHARTS_BAR" || this.chartType === "ECHARTS_LINE" || this.chartType === "ECHARTS_COMBINATION__DUAL") {
let onceShowMaxItem = this.onceShowMaxItem; //一次最多显示多少根柱子,后面运算时用到, 3-7有效,太多没试过,小于3时不太准确
let dataNum = this.option.xAxis.data.length;
let processedEnd = 100;
if (dataNum > onceShowMaxItem) {
processedEnd = 100 * onceShowMaxItem / dataNum;
}
let dataZoom = [
{
// start, end是控制echarts图形的缩放比例,%,通过计算end, 控制最多显示多少根柱子(多少单位的X轴长度)
type: 'inside',
xAxisIndex: [0],
start: 0, end: processedEnd,
//end: 100 / this.option.xAxis.data.length * 6,
zoomOnMouseWheel: false,
zoomLock: true,
},
];
//设置图形可以左右滚动
this.option["dataZoom"] = dataZoom;
//给第一个柱子或线设置显示数值,所有柱子都设置会很挤
//this.option["series"][0]["label"] = label;
//解决Y轴刻度被遮挡
this.option["grid"] = grid;
}
if (this.chartType === "ECHARTS_BAR__HORIZONTAL") {
//解决Y轴刻度被遮挡
this.option["grid"] = grid;
}
if (this.chartType === "ECHARTS_PIE") {
let label = {
normal: {
position: "inside"//此处将展示的文字在内部展示
}
};
//解决饼图标签显示不全
this.option["series"][0]["label"] = label;
}
if (!!this.option.tooltip) {
this.option.tooltip["confine"] = true;
this.option.tooltip["valueFormatter"] = tooltipValueFormatter;
} else {
let tooltip = {
confine: true,
valueFormatter: tooltipValueFormatter
};
this.option["tooltip"] = tooltip;
}
}
},
destroyed() {
if (!!this.myChart) {
this.myChart.dispose();
}
this.myChart = null;
}
});