结对作业二
这个作业属于哪个课程 | 2021春软件工程实践 -W班 (福州大学) |
---|---|
这个作业要求在哪里 | 结对作业二 |
结对学号 | 221801123、221801120 |
这个作业的目标 | 顶会热词统计项目实现 |
其他参考文献 | 《构建之法》、博客园、B站 |
作业链接
网站链接
网站链接:链接 (账号:admin 密码:admin)
git仓库链接和代码规范链接
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 25 |
• Estimate | • 估计这个任务需要多少时间 | 30 | 25 |
Development | 开发 | 1680 | 2645 |
• Analysis | • 需求分析 (包括学习新技术) | 30 | 25 |
• Design Spec | • 生成设计文档 | 20 | 20 |
• Design Review | • 设计复审 | 10 | 15 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 15 |
• Design | • 具体设计 | 20 | 20 |
• Coding | • 具体编码 | 1500 | 2400 |
• Code Review | • 代码复审 | 30 | 30 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 120 |
Reporting | 报告 | 55 | 60 |
• Test Repor | • 测试报告 | 30 | 30 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 15 | 20 |
合计 | 1765 | 2730 |
成品展示
-
登录
用户名、密码登录,验证码进行验证。
-
首页
搜索界面,可以通过选择“论文题目”、“论文摘要”、“关键词”进行模糊搜索,搜索后可以进入论文列表界面
-
论文列表
展示论文列表,用户可以选择“查看”、“编辑”、“删除”等操作。
分页
查看即显示论文详细信息,包括论文题目、来源、时间、关键词、论文摘要以及论文链接。
编辑可对论文具体信息进行修改。
删除则会删除该论文。
-
热门分析Top10
top10的热词通过词云展示,点击热词会进入论文列表界面并展示与该词有关的论文。
-
趋势
-
我的收藏(没来得及实现)
注销
结对讨论过程描述
- 刚看到题目时,最先想直接用servlet的方式来写,前后端不分离,毕竟对框架的接触还比较少,后来觉得用框架会可能会有利于加强效率,所以在讨论之后,选择了springMVC框架,但是由于手生,配置起来也不容易,还是遇到了不少的问题。后来也考虑过springboot框架进行开发,但是在学习其他知识上已经花费了太多的时间,觉得学习成本会负担不起,担心造成高不成低不就的尴尬局面,所以只能硬着头皮写下去。
- 然后进行数据库设计(虽然后面还改动了),接着就是按原型进行模块划分,然后分工,一个做前端一个后端。遇到问题的时候,会一起解决问题,毕竟有时候当局者迷旁观者清。
- 前期的进展比较缓慢,主要是花比较多的时间学习新知识,毕竟工欲善其事,必先利其器嘛。后期上手之后,效率就跟了上来,虽然期间也会遇到很多bug和问题,但是在讨论之后大多都能得到解决。
- 因为我们的作息生活比较一致,加上宿舍相邻,所以大多采取线下交流的方式进行结对,线上交流就比较少。
设计实现过程
数据库表:
共有三张表:thesis(论文列表)、thesis_keyword(关键词表)、user(用户)
table: thesis
columns:
id int
meet varchar(6)
year varchar(6)
title text
author text
keyword text
abstract_content text
link text
table: thesis_keyword
columns:
id int
tid int
keyword varchar(100)
table: user
columns:
id int
name varchar(128)
password varchar(128)
项目框架:
采用SpringMVC + jsp + JQuery
设计
-
登录模块
- 前端:对数据进行判空验证
- 后端:读数据进行判空验证及验证码验证,通过设置
session
来判断用户的登录状态,通过拦截器判断是否放行
-
顶部栏模块
- 前端:通过jsp,抽离出可复用的顶部,实现可复用jsp
-
论文查询模块
- 前端:抽象出列表项结构,通过jstl与el构建,写了个底部分页器jsp
- 后端:底层写了分页查询的逻辑,封装通过前端传输的搜索信息和分页器提供的页码数据并返回。
-
论文编辑模块
- 前端:点击button,跳出卡片,通过js进行非空验证
- 后端:写好底层dao进行数据库表的更新,controler逐层调用实现论文edit
-
论文删除模块(原理同上
-
词云图、柱状图
- 前端:应用了
highchart
模板来处理通过ajax请求后端返回的数据,并以图表的方式呈现。 - 后端:采用
SpringMVC框架,
model层设计实体类,
dao层实现底层数据库接口,
service层调用接口,最后在
controller`层调用实现;底层用排序方法取出Top10热词;存入数据库,需要时取出返回数据给前端。
- 前端:应用了
功能结构图
代码说明
前端
论文列表展示:使用表格的方式来展示列表,c:forEach的方法来获取数据并显示
<tbody>
<c:forEach items="${pageBean.records}" var="thesis">
<tr>
<td hidden>${thesis.id}</td>
<td>${thesis.title}</td>
<td>${thesis.meet}</td>
<td>${thesis.year}</td>
<td hidden>${thesis.keyword}</td>
<td hidden>${thesis.abstractContent}</td>
<td hidden>${thesis.link}</td>
<td>
<button class="btn-info" onclick="LookCard(this)">查看</button>
<button class="btn-info" onclick="EditCard(this);">编辑</button>
<button class="btn-danger" onclick="DeleteCard(this);">删除</button>
</td>
</tr>
</c:forEach>
</tbody>
论文具体信息展示:使用bootstrap的卡片样式,用卡片的形式展示除论文的详细信息。
<div class="lookCard card-show col-lg-6">
<div class="card">
<div class="card-header">
论文信息
<span class="close">×</span>
</div>
<div class="card-body pre-scrollable">
<dl>
<dt><span class="buleFont">*</span>论文题目</dt>
<dd><textarea id="thesis_view_title" class="eidtText" name="title" readonly></textarea></dd>
<dt><span class="buleFont">*</span>来源</dt>
<dd><textarea id="thesis_view_meet" class="eidtText" name="meet" readonly></textarea></dd>
<dt><span class="buleFont">*</span>时间</dt>
<dd><textarea id="thesis_view_year" class="eidtText" name="year" readonly></textarea></dd>
<dt><span class="buleFont">*</span>关键词</dt>
<dd><textarea id="thesis_view_keyword" class="eidtText bigText" name="keyword" readonly></textarea></dd>
<dt><span class="buleFont">*</span>论文摘要</dt>
<dd><textarea id="thesis_view_abstractContent" class="eidtText bigText" name="abstract" readonly></textarea></dd>
<dt><span class="buleFont">*</span>论文链接</dt>
<dd><textarea id="thesis_view_link" class="eidtText" name="link" readonly></textarea></dd>
</dl>
</div>
<div class="card-footer"></div>
</div>
</div>
验证码设计代码:
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);
int width=70, height=25;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
g.setColor(getRandColor(200,250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman",Font.PLAIN,18));
g.setColor(getRandColor(160,200));
for (int i=0;i<155;i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x,y,x+xl,y+yl);
}
String sRand="";
for (int i=0;i<4;i++){
String rand=String.valueOf(random.nextInt(10));
sRand+=rand;
g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));
g.drawString(rand,13*i+6,16);
}
后端
底层分页
public PageBean<T> pageSearch(int pageCode, int pageSize, int pageNumber,
String where, List<Object> params, HashMap<String, String> orderbys) {
String whereSql = (DataUtil.isValid(where)) ? where : "";
StringBuilder sqlBuilder = new StringBuilder(sql).append(" ").append(whereSql);
StringBuilder countSqlBuilder = new StringBuilder(countSql).append(" ").append(whereSql);
PageBean<T> pageBean = null;
if (DataUtil.isValid(orderbys)) {
//设置排序
sqlBuilder.append(" order by ");
for (String key : orderbys.keySet()) {
sqlBuilder.append(key).append(" ").append(orderbys.get(key)).append(",");
}
sqlBuilder.deleteCharAt(sql.length() - 1);
}
//设置分页
int begin = (pageCode - 1) * pageSize;
sqlBuilder.append(" limit ").append(begin).append(", ").append(pageSize);
//设置参数
if (DataUtil.isValid(params)) {
Object[] paramsArray = params.toArray();
pageBean = new PageBean<T>(jdbcTemplate.query(sqlBuilder.toString(), paramsArray, rowMapper), pageSize, pageCode,
((BigInteger) jdbcTemplate.queryForObject(countSqlBuilder.toString(), paramsArray, BigInteger.class)).intValue(), pageNumber);
} else {
pageBean = new PageBean<T>(jdbcTemplate.query(sqlBuilder.toString(), rowMapper), pageSize, pageCode,
((BigInteger) jdbcTemplate.queryForObject(countSqlBuilder.toString(), BigInteger.class)).intValue(), pageNumber);
}
return pageBean;
}
Top10数据输出
public void chart(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String sql = "select keyword, count(keyword) num from thesis_keyword " +
"group by keyword order by count(keyword) desc limit 10";
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
Map<String, Object> map = new HashMap<String, Object>();
map.put("list", list);
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
String s = gson.toJson(map);
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
PrintWriter out=response.getWriter();
out.print(s);
}
论文列表显示、查询
public String list(String pn, String title, String abstractContent, String keyword, Model model) {
int pageCode = DataUtil.getPageCode(pn);
String where = " where 1 = 1 ";
List<Object> params = new ArrayList<Object>(1);
if (DataUtil.isValid(title)) {
where += "and title like '%" + title + "%'";
}else if (DataUtil.isValid(keyword)) {
where += "and keyword like '%" + keyword + "%'";
}else if (DataUtil.isValid(abstractContent)) {
where += "and abstract_content like '%" + abstractContent + "%'";
}
PageBean<Thesis> pageBean = thesisService.pageSearch(pageCode, pageSize, pageNumber, where, null, null);
model.addAttribute("pageBean", pageBean);
return "user/thesis_list";
}
论文删除、编辑
public void delete(@PathVariable String tid, HttpServletResponse response) {
response.setContentType("text/html;charset=UTF-8");
JSONObject json = new JSONObject();
thesisService.delete(tid);
json.addElement("result", "1").addElement("message", "删除成功");
DataUtil.writeJSON(json, response);
System.out.println(json);
}
/**
* 论文编辑
*/
@RequestMapping("/edit")
@ResponseBody
public void edit(String id, String title, String meet, String year, String keyword, String abstractContent
, String link, HttpServletResponse response) {
JSONObject json = new JSONObject();
if(!DataUtil.isValid(id, title, meet, year, keyword, abstractContent, link)) {
json.addElement("result", "0").addElement("message", "格式非法");
}else {
thesisService.update(id, title, meet, year, keyword, abstractContent, link);
json.addElement("result", "1").addElement("message", "保存成功");
}
DataUtil.writeJSON(json, response);
}
登录验证
public String doLogin(String username, String password, String verify, Model model, HttpServletRequest request) {
if (!DataUtil.isValid(username, password)) {
//提示信息
model.addAttribute("error","用户名密码不能为空");
//跳转页面
return "login";
} else if(!DataUtil.checkVerify(verify,request.getSession())){
//验证码不正确
//提示信息
model.addAttribute("error","验证码错误");
//跳转页面
return "login";
} else {
User user = userService.login(username, password);
if(user != null) {
//登陆成功
String id = String.valueOf(user.getId());
HttpSession session = request.getSession();
SessionContainer.loginUsers.put(String.valueOf(id), session);
request.getSession().setAttribute("user",user);
return "redirect:/user/index";
} else{
//登陆失败
model.addAttribute("error","账号或密码不正确");
//跳转页面
return "login";
}
}
}
登录拦截
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String path = request.getServletPath();
String contextPath = request.getContextPath();
HttpSession session = request.getSession();
//如果是去向用户页面
if (path.indexOf("user") != -1) {
User user = (User) session.getAttribute("user");
if (user == null) {
response.sendRedirect(contextPath + "/login");
return false;
} else if (session.getAttribute("force") != null) {
/*//此账号已在其它地方登录
response.sendRedirect(contextPath + "/valid");
session.invalidate();*/
return false;
}
return true;
}
return true;
}
心路历程和收获
221801120(唐凯秦):
心路历程:
刚看到题目具体要求的时候,还是感觉有点束手无策,因为以前没有做什么大项目的经历,一时间竟不知该从何入手。有想过要选择用框架来做,应该会减少开发时间,但是又担心学习成本过大,在JavaEE课程的启发下,选择使用springMVC框架,以及用jsp嵌套html来实现前端界面。因为缺乏知识和经验,所以刚开始的几天项目都没有什么实质性的进展,想先对基本知识进行大致的学习,然后再进行开发。
开发过程中也遇到了不少问题,比如在前端开发中,原本是使用原生的组件进行开发,效率会比较低,后来发现了可以使用bootstrap库,虽然有的样式很好用,但是引入bootstrap后发现里面的一些样式和自己设计的样式会发生冲突,这种错误不太好找,所以找错误、解决错误也花了好多时间。(想设计出一个好看的界面可太不容易了/(ㄒoㄒ)/~~)收获:
这次项目也使用GitHub,感觉使用起来更熟练了,以后的团队协作应该会更加得心应手。再者就是积累了开发项目的经历(项目虽然不是很大),但是多少也是一份不错的经验,能够完成心里也是很有成就感的。这次项目也让我对MVC设计模式有了更深的体会,低耦合、高复用、可维护,感觉项目清晰了好多。然后就是对html、css、js的使用更加灵活了,原生开发还是挺累的,感觉脑里有东西,但手上就是写不出来,对细节的要求好高。或许使用框架会很好的提高效率(还是水平有限呐)。接触的东西越多,越感觉自己会的太少,要学习的东西还是太多了(头秃警告)。
221801123(武雍易):
心路历程:拿到题目的时候,是打算直接写servlet不用框架(主要也是框架掌握的不够熟练,写了一段时间,越写越觉得原生servlet的弊端,越能感受为什么会有后来的框架,所以后面使用SpringMVC框架,对servlet进行封装,简化了结对后端的开发,但是这框架的配置也是一个问题。加上中途的一次团队Github实训,团队后端使用的是SpringBoot框架,对比SpringMVC确实是有很大的好处,实现了自动配置,降低了项目搭建的复杂度。所以其实在实训后有想过在换成SpringBoot框架,但是奈何感觉时间成本我已经负担不起了,就只能继续闷头SpringMVC。加上没有前后端分离,前端使用的还是jsp,前后端都不熟练情况,这次的任务蛮重的。
收获:从原生Servlet一个功能一个Servlet到反射多个功能到一个Servlet,再到使用框架后多个功能一个Controller,自动装配让我感受到SpringMVC框架的优势所在。以前解析json的例子很少,自己基本没有尝试过,这次收获良多。还收获到了ajax的原理(以前属实没有前后端一起开发过。深深感受到后端配jsp的恶心,java后端来将html套成jsp页面,出错率较高,修改问题时需要双方协同开发,开发效率是真的低(或许我们能力也有限),这时前后端分离的优势就显而易见了。使用Github进行项目管理更加熟练。用的多自然学到的也多这个没毛病,就是有点顶。
评价结对队友
KQ(221801120)对YY(221801123)的评价:
这次结对体验总体来说还是很不错的,YY同学是个很有主意的人,在我还在束手无策、不知如何下手的时候,他能给我带来很多的建议和启发。他的学习能力和应变能力也很强,遇到bug时会耐下心来,仔细钻研,但是又不会一味地死磕下去,轻重缓急合理调控,可以让我们的项目可以有惊无险地完成下去。在一起讨论的过程也很愉快,在一起头脑风暴,感觉没什么问题是解决不了的,赞!!!
YY(221801123)对KQ(221801120)的评价:
这是第二次与KQ同学合作,虽然KQ对前端这方面比较陌生,但学习能力强态度很认真,完成自己的任务后会主动找我要任务,还能帮我解决一些小问题(困扰很久的小问题)。希望还有机会合作。