从零开始写项目第三篇【在线聊天和个人收藏夹】
在线聊天
在浏览网页的时候无意发现了弹幕这个玩意,于是我们简单去探究了一下弹幕其实是怎么产生的。
后来就接触到了“推送”这么一个概念,然后发现了goEasy这个服务商。
http://goeasy.io/cn/started
goEasy能够将我们的数据实时进行推送,使用起来也是很方便的。
使用了goEasy以后,我们就可以实现实时推送了。那么就剩下弹幕是怎么弄的了。后来又去找到了JS的组件:
http://yaseng.org/jquery.barrager.js/
这个组件可以帮我们很方便地生成弹幕…
<script type="text/javascript">
/*使用goEasy的推送功能*/
var goEasy = new GoEasy({
appkey: 'xxxxx'
});
/*按回车的时候会推送*/
$("#inputText").keydown(function () {
if (event.keyCode == 13) {
goEasy.publish({
channel: 'demo_channel',
message: $("#inputText").val()
});
//推送完清空输入框
$("#inputText").val("");
}
});
/*单击按钮的时候会推送*/
$("#btn").click(function () {
goEasy.publish({
channel: 'demo_channel',
message: $("#inputText").val()
});
});
/*接收数据、数据形式是弹幕*/
goEasy.subscribe({
channel: 'demo_channel',
onMessage: function (message) {
var txt = message.content;
var item = {
img: '${request.contextPath}/imgs/favicon.ico', //图片
info: $("#userNickname").val() +":"+ txt, //文字
href: 'www.baidu.com', //链接
close: true, //显示关闭按钮
speed: 8, //延迟,单位秒,默认8
bottom: 500, //距离底部高度,单位px,默认随机
color: '#fff', //字体颜色颜色,默认白色
old_ie_color: '#fff' //ie低版兼容色,不能与网页背景相同,默认黑色
};
$('body').barrager(item);
}
});
</script>
实现效果:
该功能主要是使用到了两个组件,现在的功能实现非常简单,后面有需要再补充进去吧。
个人收藏夹
由于经常用到的网址越来越多,经常需要找好一阵子才能找到我想要去的网站。即使浏览器有收藏夹这么一个功能…
还是需要去找找找找的,因此我想自己建一个以关键搜索来跳转页面的功能。因为我的桌面版就是使用Rolan这么一个软件,输入关键字,就可以帮我打开软件了,很方便。
于是想起我学过Lucene这么一个全文搜索工具的,后来又发现了Elasticsearch是Lucene的子代,功能更强大也是一种比较新的技术了。后来我就去花了几天去学习Elasticsearch,找到了不少的资料。
学习Elasticsearch的过程已经在相关的博文中写过了,这里就不一一赘述了。
值得注意一点的是:当创建Elasticsearch的Client的时候,一定要加入嗅探这么一个配置:
.put("client.transport.sniff", true)
否则Elasticsearch在使用的时候会非常非常卡,简直令人怀疑人生。
自动补齐
经常上baidu、google的时候,当我们输入部分关键字的时候,那么会在下拉框中自动提示信息的。这个是怎么实现的呢???
Elasticsearch有suggest这么一个自动提示的功能,参考博文:
http://blog.csdn.net/gumpeng/article/details/50346631
http://www.tcao.net/article/86.html
http://blog.csdn.net/liyantianmin/article/details/60778273
具体的操作:
在建立Mapping的时候创建自动补齐的这么一个字段,设置以下的Mapping
//自动补全属性--------
.startObject("suggestName")
.field("type", "completion")
.field("analyzer", "standard")
.field("payloads", "true")
.endObject()
//自动补全属性结束--------
当用户加入新的索引记录的时候,自动补齐字段就是我们的关键字段
public static String String2JSON(String... strings) throws IOException {
String suggestName = strings[2];
if (suggestName.length() > 0) {
}
XContentBuilder builder = jsonBuilder()
.startObject()
.field("userId", strings[0])
.field("webSiteAddr", strings[1])
.field("webSiteName", strings[2])
//自动补全字段
.field("suggestName", strings[2])
.endObject();
return builder.string();
}
那么在查询的时候就非常好办了,首先查询Elasticserach所有的记录,再与提示字段来进行匹配,查询出来的数据就可以与关键字段相关了。
/**
* 根据“命名”查询出数据(自动补全)
*
* @param client
* @param indexName
* @param condition 查询条件
*/
public static List<String> queryIndexByConditionCompletion(TransportClient client, String indexName, String condition) {
String field = "suggestName";
//先查询所有结果再与提示字段的数据进行匹配
SearchRequestBuilder req = client.prepareSearch(indexName);
req.setQuery(QueryBuilders.matchAllQuery());
CompletionSuggestionBuilder csfb = new CompletionSuggestionBuilder(field).field(field).text(condition).size(100);
req.addSuggestion(csfb);
SearchResponse suggestResponse = req.execute().actionGet();
//遍历返回的结果、装载到集合中
List<String> lists = new ArrayList<String>();
List<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> results = suggestResponse.getSuggest().getSuggestion(field).getEntries();
for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> op : results) {
List<? extends Suggest.Suggestion.Entry.Option> options = op.getOptions();
for (Suggest.Suggestion.Entry.Option pp : options) {
lists.add(pp.getText().toString());
}
}
return lists;
}
对于下拉框提示的是通过JS+CSS来进行展示出来的,我是参考了一篇博文,对其进行修改的:
那篇博文现在找不着了…我就贴出我的代码吧:
<!--下拉框提示、请求后台-->
<script type="text/javascript">
$(function () {
//实现搜索输入框的输入提示js类
function oSearchSuggest(searchFuc) {
var input = $('#condition');
var suggestWrap = $('#gov_search_suggest');
var key = "";
var init = function () {
input.bind('keyup', sendKeyWord);
input.bind('blur', function () {
setTimeout(hideSuggest, 100);
})
}
var hideSuggest = function () {
suggestWrap.hide();
}
//发送请求,根据关键字到后台查询
var sendKeyWord = function (event) {
//键盘选择下拉项
if (suggestWrap.css('display') == 'block' && event.keyCode == 38 || event.keyCode == 40) {
var current = suggestWrap.find('li.hover');
if (event.keyCode == 38) {
if (current.length > 0) {
var prevLi = current.removeClass('hover').prev();
if (prevLi.length > 0) {
prevLi.addClass('hover');
input.val(prevLi.html());
}
} else {
var last = suggestWrap.find('li:last');
last.addClass('hover');
input.val(last.html());
}
} else if (event.keyCode == 40) {
if (current.length > 0) {
var nextLi = current.removeClass('hover').next();
if (nextLi.length > 0) {
nextLi.addClass('hover');
input.val(nextLi.html());
}
} else {
var first = suggestWrap.find('li:first');
first.addClass('hover');
input.val(first.html());
}
}
//输入字符
} else {
var valText = $.trim(input.val());
if (valText == '' || valText == key) {
return;
}
searchFuc(valText);
key = valText;
}
}
//请求返回后,执行数据展示
this.dataDisplay = function (data) {
if (data.length <= 0) {
suggestWrap.hide();
return;
}
//往搜索框下拉建议显示栏中添加条目并显示
var li;
var tmpFrag = document.createDocumentFragment();
suggestWrap.find('ul').html('');
for (var i = 0; i < data.length; i++) {
li = document.createElement('LI');
li.innerHTML = data[i];
tmpFrag.appendChild(li);
}
suggestWrap.find('ul').append(tmpFrag);
suggestWrap.show();
//为下拉选项绑定鼠标事件
suggestWrap.find('li').hover(function () {
suggestWrap.find('li').removeClass('hover');
$(this).addClass('hover');
}, function () {
$(this).removeClass('hover');
}).bind('click', function () {
input.val(this.innerHTML);
suggestWrap.hide();
});
}
init();
}
//实例化输入提示的JS,参数为进行查询操作时要调用的函数名
var searchSuggest = new oSearchSuggest(sendKeyWordToBack);
//这是一个模似函数,实现向后台发送ajax查询请求,并返回一个查询结果数据,传递给前台的JS,再由前台JS来展示数据。本函数由程序员进行修改实现查询的请求
//参数为一个字符串,是搜索输入框中当前的内容
<!--搜索数据-->
function sendKeyWordToBack(keyword) {
var obj = {
"condition": $("#condition").val()
};
$.ajax({
type: "POST",
url: path + "/favorites/querySiteCompletion.do",
data: obj,
dataType: "json",
success: function (data) {
console.log(data);
var aData = [];
for (var i = 0; i < data.length; i++) {
//以下为根据输入返回搜索结果的模拟效果代码,实际数据由后台返回
if (data[i] != "") {
aData.push(data[i]);
}
}
//将返回的数据传递给实现搜索输入框的输入提示js类
searchSuggest.dataDisplay(aData);
}
});
}
});
</script>
HTML和CSS,我用得是Bootstrap的。
<!--搜索网站-->
<div class="row " style="position: absolute; margin:0px auto;
text-align:center; ">
<div class="col-lg-6 gover_search">
<div class="input-group gover_search_form clearfix">
<input class="form-control input-lg " type="text" placeholder="请输入您自定义的网站名..." id="condition">
<span class="input-group-btn">
<button class="btn btn-primary btn-lg" type="button" id="search">搜索</button>
</span>
<div class="search_suggest" id="gov_search_suggest">
<ul>
</ul>
</div>
</div>
</div>
</div>
<!--搜索网站-->
<div class="row " style="position: absolute; margin:0px auto;
text-align:center; ">
<div class="col-lg-6 gover_search">
<div class="input-group gover_search_form clearfix">
<input class="form-control input-lg " type="text" placeholder="请输入您自定义的网站名..." id="condition">
<span class="input-group-btn">
<button class="btn btn-primary btn-lg" type="button" id="search">搜索</button>
</span>
<div class="search_suggest" id="gov_search_suggest">
<ul>
</ul>
</div>
</div>
</div>
</div>
实现效果:
增加网站
在增加网站的时候,我们希望以单一的名字来进行添加,因为我们是通过命名来进行搜索对应的网站的。
所以在添加网站的时候需要先判断有没有该命名才能继续添加、如果存在命名了,就不让它添加了。
代码如下:
/**
* 插入数据进索引库中,在插入之前先判断该“命名”索引是否存在
*
* @param client
* @param indexName
* @param type
* @param json
* @param webSiteName
* @return
*/
public static String insertIndexData(TransportClient client, String indexName, String type, String json, String webSiteName) {
//在插入之前、查看一下有没有该数据、如果有就不允许插入了【本站只允许命名是唯一的】
List<String> list = queryIndexByCondition(client, indexName, type, webSiteName);
if (list != null && list.size() > 0) {
return "hasWebSiteName";
} else {
client.prepareIndex(indexName, type).setSource(json).get();
return "success";
}
}
删除与查询网站
添加完网站之后,我们可以查询当前用户所有的网站,并可以删除
查询用户所有的网站我还使用到了Elasticsearch的分页功能:
/**
* 根据用户id查询出索引记录、并对其进行分页
*
* @param client
* @param indexName
* @param type
* @param userId
* @param currentPage
* @return
*/
public static Map<String, String> queryIndexById(TransportClient client, String indexName, String type, String userId, Integer currentPage) {
QueryBuilder query = QueryBuilders.matchQuery("userId", userId);
//接收返回的数据
Map<String, String> map = new HashMap();
//计算出开始取的页数
int startIndex = (currentPage - 1) * EsUtilsPro.PAGE_SIZE;
//查询出总记录数
Long totalRecordCount = ElasticsearchUtils.queryTotalCount(client, indexName, type, userId);
//查询出总页数
Long totalPageCount = totalRecordCount % EsUtilsPro.PAGE_SIZE == 0 ? totalRecordCount / EsUtilsPro.PAGE_SIZE : totalRecordCount / EsUtilsPro.PAGE_SIZE + 1;
SearchResponse response = client.prepareSearch(indexName).setTypes(type).setQuery(query).setFrom(startIndex).setSize(EsUtilsPro.PAGE_SIZE).execute()
.actionGet();
SearchHits hits = response.getHits();
//返回一个最高匹配那个对象就好了。
if (hits.totalHits() > 0) {
for (SearchHit hit : hits) {
map.put(hit.getId(), hit.getSourceAsString());
}
}
map.put("currentPage", String.valueOf(currentPage));
map.put("totalRecordCount", String.valueOf(totalRecordCount));
map.put("totalPageCount", String.valueOf(totalPageCount));
return map;
}
查询总记录数
/**
* 根据用户id查询该用户拥有的总记录数
*
* @param client
* @param indexName
* @param type
* @param userId
* @return
*/
public static Long queryTotalCount(TransportClient client, String indexName, String type, String userId) {
QueryBuilder query = QueryBuilders.matchQuery("userId", userId);
SearchResponse response = client.prepareSearch(indexName).setTypes(type).setQuery(query).execute().actionGet();
return response.getHits().totalHits();
}
使用Map集合的原因是因为我们要在查询的基础上实现删除的功能,要想删除就必须知道当前记录的id,因此就使用到了Map集合了。
<!--根据Id查询索引记录-->
<script>
$(function () {
$("#queryById").click(function () {
var currentPage = $("#currentPage").val();
if (currentPage == null || currentPage == "") {
currentPage = 1;
}
$.ajax({
url: path + "/favorites/querySiteById.do",
type: "post",
data: {userId: $("#userId").val(), currentPage: currentPage},
success: function (responseText) {
//返回的是一个Map集合,我们遍历Map集合,
for (var index in responseText) {
//获取Map的value,index就代表Map的key
var jsonObj = responseText[index]
if (index.length > 16) {
//获取JSON对象
var data = eval("(" + jsonObj + ")");
$("#manageSiteContent").before("<tr><td><input type='text' value=" + data.webSiteAddr + " ></input></td><td><input type='text' value=" + data.webSiteName + " ></input></td><td><a href='${request.contextPath}/favorites/deleteSiteById.do?indexId=" + index + "'>删除</a></td></tr>");
} else {
if (index == "currentPage") {
$("#currentPage").val(jsonObj);
} else if (index == "totalPageCount") {
$("#totalPageCount").val(jsonObj);
} else {
$("#totalRecordCount").val(jsonObj);
}
}
}
createPage($("#currentPage").val(), $("#totalPageCount").val(), $("#totalRecordCount").val());
},
error: function () {
sweetAlert("系统错误了");
}
});
});
});
</script>
生成分页的样式、并指定当前页数(i就作为当前的页数)
<!--生成分页样式-->
<script>
function createPage(totalPageCount) {
for (var i = 1; i <= totalPageCount; i++) {
$(".pagination").append("<li><a href='#' onclick='queryPage(" + i + ")'>" + i + "</a></li>");
}
}
</script>
异步查询分页数据、在查询之前先把原有的记录删除掉。
<!--异步查询分页数据-->
<script>
function queryPage(currentPage) {
//把原有的数据删除掉
$("#manageSiteTr").siblings().empty();
$.ajax({
url: path + "/favorites/querySiteById.do",
type: "post",
data: {userId: $("#userId").val(), currentPage: currentPage},
success: function (responseText) {
//返回的是一个Map集合,我们遍历Map集合,
for (var index in responseText) {
//获取Map的value,index就代表Map的key
var jsonObj = responseText[index]
if (index.length > 16) {
//获取JSON对象
var data = eval("(" + jsonObj + ")");
$("#manageSiteContent").before("<tr><td><input type='text' value=" + data.webSiteAddr + " ></input></td><td><input type='text' value=" + data.webSiteName + " ></input></td><td><a href='${request.contextPath}/favorites/deleteSiteById.do?indexId=" + index + "'>删除</a></td></tr>");
} else {
if (index == "currentPage") {
$("#currentPage").val(jsonObj);
} else if (index == "totalPageCount") {
$("#totalPageCount").val(jsonObj);
} else {
$("#totalRecordCount").val(jsonObj);
}
}
}
},
error: function () {
sweetAlert("系统错误了");
}
});
}
</script>
最终的效果如下:
如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y