Skip to content

Commit 782c164

Browse files
lqs469JacksonTian
authored andcommitted
feat: search in loacl, google, baidu (#107)
feat: search local and ut test: inprove test coverage fix: baidu sit search, return redirect, default google serach fix: service/search test: service.search.test.js test: rm unreach code
1 parent dbf1ef2 commit 782c164

File tree

10 files changed

+307
-14
lines changed

10 files changed

+307
-14
lines changed

app/controller/search.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,20 @@ class SearchController extends Controller {
66
async index() {
77
let q = this.ctx.query.q;
88
q = encodeURIComponent(q);
9-
this.ctx.redirect(`https://www.google.com.hk/#hl=zh-CN&q=site:cnodejs.org+${q}`);
9+
if (q.length === 0) {
10+
return this.ctx.redirect('/');
11+
}
12+
13+
switch (this.config.search) {
14+
case 'google':
15+
return this.ctx.redirect(`https://www.google.com.hk/#hl=zh-CN&q=site:cnodejs.org+${q}`);
16+
case 'baidu':
17+
return this.ctx.redirect(`https://www.baidu.com/s?wd=site:cnodejs.org+${q}`);
18+
case 'local':
19+
return await this.ctx.render('search/index', await this.service.search.searchLocal(this.ctx.query, q));
20+
default:
21+
return this.ctx.redirect('/');
22+
}
1023
}
1124
}
1225

app/controller/user.js

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,12 @@ class UserController extends Controller {
252252
async toggleStar() {
253253
const { ctx, service } = this;
254254
const user_id = ctx.request.body.user_id;
255-
const user = await service.user.getUserById(user_id);
256-
if (!user) {
257-
ctx.body = { status: 'failed', message: '用户不存在' };
258-
return;
259-
}
260255

256+
const user = await service.user.getUserById(user_id);
261257
user.is_star = !user.is_star;
262258
await user.save();
263-
264259
ctx.body = { status: 'success' };
260+
return;
265261
}
266262

267263
async block() {

app/public/stylesheets/style.less

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ a.user_avatar:hover {
530530
.user .user_avatar {
531531
width: 32px;
532532
height: 32px;
533+
display: inline-block;
534+
overflow: hidden;
533535
}
534536

535537
.user .user_name {
@@ -834,7 +836,7 @@ textarea#title {
834836
font-weight: 700;
835837

836838
img {
837-
vertical-align: initial;
839+
vertical-align: initial;
838840
}
839841
}
840842

@@ -1318,4 +1320,4 @@ textarea.editor {
13181320

13191321
.github-login-btn {
13201322
display: inline-block;
1321-
}
1323+
}

app/service/search.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
3+
const Service = require('egg').Service;
4+
5+
class SearchService extends Service {
6+
constructor(app) {
7+
super(app);
8+
this.limit = this.config.list_topic_count;
9+
}
10+
11+
/*
12+
* 根据关键字查询本地数据库
13+
*/
14+
async searchLocal(query, keyword) {
15+
const { tab } = query;
16+
const page = parseInt(query.page) || 1;
17+
let users = [];
18+
let topics = [];
19+
let data = [];
20+
let count = this.limit;
21+
22+
switch (tab) {
23+
case 'user':
24+
[ data, count ] = await this.searchUser(keyword, page);
25+
break;
26+
case 'topic':
27+
[ data, count ] = await this.searchTopic(keyword, page);
28+
break;
29+
default:
30+
[ users, topics ] = await this.searchUserAndTopic(keyword, page);
31+
}
32+
33+
const pages = Math.ceil(count / this.limit);
34+
return {
35+
keyword,
36+
data,
37+
users,
38+
topics,
39+
current_page: parseInt(query.page) || 1,
40+
tab,
41+
pages,
42+
base: '/search?q=' + keyword,
43+
};
44+
}
45+
46+
queryFactory(keyword, searchKey, page) {
47+
const opt = { skip: (page - 1) * this.limit, limit: this.limit, sort: '-create_at' };
48+
return [
49+
{ [searchKey]: { $regex: new RegExp(keyword, 'i') } },
50+
opt,
51+
];
52+
}
53+
54+
/*
55+
* 根据关键字查找用户列表
56+
* @param {String} keyword 关键字, {Number} page 第几页
57+
* @return {Promise[data, count]} 承载用户列表, 查询总数的 Promise 对象
58+
*/
59+
searchUser(keyword, page) {
60+
const searchQuery = this.queryFactory(keyword, 'name', page);
61+
return Promise.all([
62+
this.service.user.getUsersByQuery(...searchQuery),
63+
this.service.user.getCountByQuery(...searchQuery),
64+
]);
65+
}
66+
67+
/*
68+
* 根据关键字查找帖子列表
69+
* @param {String} keyword 关键字, {Number} page 第几页
70+
* @return {Promise[data, count]} 承载帖子列表, 查询总数的 Promise 对象
71+
*/
72+
searchTopic(keyword, page) {
73+
const searchQuery = this.queryFactory(keyword, 'title', page);
74+
return Promise.all([
75+
this.service.topic.getTopicsByQuery(...searchQuery),
76+
this.service.topic.getCountByQuery(...searchQuery),
77+
]);
78+
}
79+
80+
/*
81+
* 根据关键字查找用户和帖子列表
82+
* @param {String} keyword 关键字, {Number} page 第几页
83+
* @return {Promise[data, count]} 承载用户列表, 帖子列表的 Promise 对象
84+
*/
85+
searchUserAndTopic(keyword, page) {
86+
const userQuery = this.queryFactory(keyword, 'name', page);
87+
const topicQuery = this.queryFactory(keyword, 'title', page);
88+
return Promise.all([
89+
this.service.user.getUsersByQuery(...userQuery),
90+
this.service.topic.getTopicsByQuery(...topicQuery),
91+
]);
92+
}
93+
}
94+
95+
module.exports = SearchService;

app/service/user.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ class UserService extends Service {
8383
return this.ctx.model.User.find(query, '', opt).exec();
8484
}
8585

86+
/*
87+
* 获取关键词能搜索到的用户数量
88+
* @param {String} query 搜索关键词
89+
*/
90+
getCountByQuery(query) {
91+
return this.ctx.model.User.count(query).exec();
92+
}
93+
8694
/*
8795
* 根据查询条件,获取一个用户
8896
* @param {String} name 用户名

app/view/search/index.html

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<%- include('../sidebar.html') %>
2+
3+
<% if (tab === 'user') { users = data }%>
4+
<% if (tab === 'topic') { topics = data }%>
5+
<div id='content'>
6+
<div class='panel'>
7+
<div class='header'>
8+
<ul class='breadcrumb'>
9+
<li><a href='/'>主页</a><span class='divider'>/</span></li>
10+
<li class='active'><a href='<%= base %>'>搜索</a></li>
11+
</ul>
12+
</div>
13+
<div class='inner'>
14+
<% if (typeof users !== 'undefined' && users.length > 0) { %>
15+
<% users.map(user => { %>
16+
<%- include('../user/user.html', { user: user }) %>
17+
<% }) %>
18+
<% if (!tab) { %>
19+
<% var user_url = base + (base.indexOf('?') < 0 ? '?' : '&') + 'tab=user'; %>
20+
<div>
21+
<a href="<%= user_url %>">查看更多用户 ...</a>
22+
</div>
23+
<% } %>
24+
<% } else if (!tab) { %>
25+
<p>没有找到用户</p>
26+
<% } %>
27+
<% if (!tab) { %><hr /><% } %>
28+
<% if (typeof topics !== 'undefined' && topics.length > 0) { %>
29+
<div id="topic_list">
30+
<% topics.map(topic => { %>
31+
<%- include('../topic/abstract.html', { topic: topic }) %>
32+
<% }) %>
33+
</div>
34+
<% if (!tab) { %>
35+
<% var topic_url = base + (base.indexOf('?') < 0 ? '?' : '&') + 'tab=topic'; %>
36+
<div>
37+
<a href="<%= topic_url %>">查看更多帖子 ...</a>
38+
</div>
39+
<% } %>
40+
<% } else if (!tab) { %>
41+
<p>没有找到帖子</p>
42+
<% } %>
43+
44+
<% if (tab) { %>
45+
<div class='pagination' current_page='<%= current_page %>'>
46+
<ul>
47+
<% var base_url = base + (base.indexOf('?') < 0 ? '?' : '&')
48+
+ 'tab=' + (typeof tab !== 'undefined' ? tab : '') + '&page='; %>
49+
<% if (current_page == 1) { %>
50+
<li class='disabled'><a>«</a></li>
51+
<% } else { %>
52+
<li><a href="<%= base_url %>1">«</a></li>
53+
<% } %>
54+
55+
<%
56+
var page_start = current_page - 2 > 0 ? current_page - 2 : 1;
57+
var page_end = page_start + 4 >= pages ? pages : page_start + 4;
58+
%>
59+
60+
<% if (page_start > 1) { %>
61+
<li><a>...</a></li>
62+
<% } %>
63+
64+
<% for(var i = page_start; i <= page_end; i++) { %>
65+
<% if (i === current_page) { %>
66+
<li class='disabled'><a><%= i %></a></li>
67+
<% } else { %>
68+
<li><a href='<%= base_url + i %>'><%= i %></a></li>
69+
<% } %>
70+
<% } %>
71+
72+
<% if (page_end < pages ) { %>
73+
<li><a>...</a></li>
74+
<% } %>
75+
76+
<% if (current_page == pages) { %>
77+
<li class='disabled'><a>»</a></li>
78+
<% } else { %>
79+
<li><a href='<%= base_url + pages %>'>»</a></li>
80+
<% } %>
81+
</ul>
82+
</div>
83+
<% } %>
84+
</div>
85+
</div>
86+
</div>
87+
<script>
88+
$('#q').val('<%- keyword %>');
89+
$(document).ready(function () {
90+
var $nav = $('.pagination');
91+
var current_page = $nav.attr('current_page');
92+
if (current_page) {
93+
$nav.find('li').each(function () {
94+
var $li = $(this);
95+
var $a = $li.find('a');
96+
if ($a.html() == current_page) {
97+
$li.addClass('active');
98+
$a.removeAttr('href');
99+
}
100+
});
101+
}
102+
});
103+
</script>

config/config.default.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,7 @@ module.exports = appInfo => {
158158
// 每个 IP 每天可创建用户数
159159
config.create_user_per_ip = 1000;
160160

161+
config.search = 'google'; // 'google', 'baidu', 'local'
162+
161163
return config;
162164
};

test/app/controller/search.test.js

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
'use strict';
22

3-
const { app } = require('egg-mock/bootstrap');
3+
const { app, assert } = require('egg-mock/bootstrap');
44

55
describe('test/app/controller/search.test.js', () => {
6-
it('search should ok', async () => {
7-
await app.httpRequest().get('/search').expect(302);
6+
it('search null should redirect index', async () => {
7+
const res = await app.httpRequest().get('/search?q=');
8+
assert(res.status === 302);
9+
app.config.search = 'bing';
10+
const res1 = await app.httpRequest().get('/search?q=test');
11+
assert(res1.status === 302);
12+
});
13+
14+
it('search google should ok', async () => {
15+
app.config.search = 'google';
16+
const res = await app.httpRequest().get('/search?q=test');
17+
assert(res.status === 302);
18+
assert(res.headers.location.indexOf('https://www.google.com.hk/') > -1);
19+
});
20+
21+
it('search baidu ok', async () => {
22+
app.config.search = 'baidu';
23+
const res = await app.httpRequest().get('/search?q=test');
24+
assert(res.status === 302);
25+
assert(res.headers.location.indexOf('https://www.baidu.com/') > -1);
26+
});
27+
28+
it('search local ok', async () => {
29+
app.config.search = 'local';
30+
const res = await app.httpRequest().get('/search?q=test');
31+
assert(res.status === 200);
32+
const res1 = await app.httpRequest().get('/search?q=test&tab=user');
33+
assert(res1.status === 200);
34+
const res2 = await app.httpRequest().get('/search?q=test&tab=topic');
35+
assert(res2.status === 200);
836
});
937
});

test/app/controller/user.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,8 @@ describe('test/app/controller/user.test.js', () => {
220220

221221

222222
it('should POST /user/set_star ok', async () => {
223-
const result = await handleAdminPost('/user/set_star', { user_id: user._id });
224-
assert(result.is_star === true);
223+
const res = await handleAdminPost('/user/set_star', { user_id: user._id });
224+
assert(res.is_star === true);
225225
});
226226

227227
it('should POST /user/cancel_star ok', async () => {

test/app/service/search.test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const { app, assert } = require('egg-mock/bootstrap');
4+
5+
describe('test/app/service/search.test.js', () => {
6+
const keyword = `keyword_${Date.now()}`;
7+
const page = 1;
8+
let ctx;
9+
10+
before(async function() {
11+
ctx = app.mockContext();
12+
const loginname = keyword;
13+
const email = `${loginname}@test.com`;
14+
const user = await ctx.service.user.newAndSave(loginname, loginname, 'pass', email, 'avatar_url', 'active');
15+
assert(user.loginname === loginname);
16+
const userId = user._id;
17+
const title = keyword;
18+
const content = 'hello world';
19+
const tab = 'share';
20+
const topic = await ctx.service.topic.newAndSave(title, content, tab, userId);
21+
assert(topic.title === title);
22+
assert(topic.content === content);
23+
assert(topic.tab === tab);
24+
assert(topic.author_id === userId);
25+
});
26+
27+
it('searchUser should ok', async () => {
28+
const [ data, count ] = await ctx.service.search.searchUser(keyword, page);
29+
assert(data[0].loginname === keyword);
30+
assert(count === 1);
31+
});
32+
33+
it('searchTopic should ok', async () => {
34+
const [ data, count ] = await ctx.service.search.searchTopic(keyword, page);
35+
assert(data[0].title === keyword);
36+
assert(count === 1);
37+
});
38+
39+
it('searchUserAndTopic should ok', async () => {
40+
const [ user, topic ] = await ctx.service.search.searchUserAndTopic(keyword, page);
41+
assert(user.length === 1);
42+
assert(user[0].name === keyword);
43+
assert(topic.length === 1);
44+
assert(topic[0].title === keyword);
45+
});
46+
});

0 commit comments

Comments
 (0)