结对作业二

这个作业属于哪个课程 2021春软件工程实践 -W班 (福州大学)
这个作业要求在哪里 结对作业二
结对学号 221801123221801120
这个作业的目标 顶会热词统计项目实现
其他参考文献 《构建之法》、博客园、B站

作业链接

网站链接

网站链接:链接 (账号:admin 密码:admin)

git仓库链接和代码规范链接

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

成品展示

  • 登录
    用户名、密码登录,验证码进行验证。
    img
    img

  • 首页
    搜索界面,可以通过选择“论文题目”、“论文摘要”、“关键词”进行模糊搜索,搜索后可以进入论文列表界面
    img
    img

  • 论文列表
    展示论文列表,用户可以选择“查看”、“编辑”、“删除”等操作。
    img
    分页
    img
    查看即显示论文详细信息,包括论文题目、来源、时间、关键词、论文摘要以及论文链接。
    img
    编辑可对论文具体信息进行修改。
    img
    删除则会删除该论文。
    img

  • 热门分析Top10
    top10的热词通过词云展示,点击热词会进入论文列表界面并展示与该词有关的论文。
    img
    img

  • 趋势
    img
    img

  • 我的收藏(没来得及实现)
    img

  • 注销
    img

结对讨论过程描述

  • 刚看到题目时,最先想直接用servlet的方式来写,前后端不分离,毕竟对框架的接触还比较少,后来觉得用框架会可能会有利于加强效率,所以在讨论之后,选择了springMVC框架,但是由于手生,配置起来也不容易,还是遇到了不少的问题。后来也考虑过springboot框架进行开发,但是在学习其他知识上已经花费了太多的时间,觉得学习成本会负担不起,担心造成高不成低不就的尴尬局面,所以只能硬着头皮写下去。
  • 然后进行数据库设计(虽然后面还改动了),接着就是按原型进行模块划分,然后分工,一个做前端一个后端。遇到问题的时候,会一起解决问题,毕竟有时候当局者迷旁观者清。
  • 前期的进展比较缓慢,主要是花比较多的时间学习哔哩哔哩教程配合Github上的源码案例,毕竟工欲善其事,必先利其器嘛。后期上手之后,效率就跟了上来,虽然期间也会遇到很多bug和问题,但是在讨论之后大多都能得到解决。
  • 因为我们的作息生活比较一致,加上宿舍相邻,所以大多采取线下交流的方式进行结对,线上交流就比较少。
    img
    img

设计实现过程

数据库表:

共有三张表: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

img

设计

  • 登录模块

    • 前端:对数据进行判空验证
    • 后端:读数据进行判空验证及验证码验证,通过设置session来判断用户的登录状态,通过拦截器判断是否放行
  • 顶部栏模块

    • 前端:通过jsp,抽离出可复用的顶部,实现可复用jsp
  • 论文查询模块

    • 前端:抽象出列表项结构,通过jstl与el构建,写了个底部分页器jsp
    • 后端:底层写了分页查询的逻辑,封装通过前端传输的搜索信息和分页器提供的页码数据并返回。
  • 论文编辑模块

    • 前端:点击button,跳出卡片,通过js进行非空验证
    • 后端:写好底层dao进行数据库表的更新,controler逐层调用实现论文edit
  • 论文删除模块(原理同上

  • 词云图、柱状图

    • 前端:应用了highchart模板来处理通过ajax请求后端返回的数据,并以图表的方式呈现。
    • 后端:采用SpringMVC框架,model层设计实体类,dao层实现底层数据库接口,service层调用接口,最后在controller`层调用实现;底层用排序方法取出Top10热词;存入数据库,需要时取出返回数据给前端。

功能结构图

img

代码说明

前端

论文列表展示:使用表格的方式来展示列表,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">&times;</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>

Top10柱状图

var chart = new Highcharts.Chart({
    chart:{
        renderTo:'wordchartDiv',
        type:'column' //显示类型 柱形
    },
    title:{
        text:'Top10' //图表的标题
    },
    xAxis:{
        categories:xtext
    },
    yAxis:{
        title:{
            text:'数量' //Y轴的名称
        },
    },
    series:[{
        name:"keyword"
    }]
});
var x = [];//X轴
var y = [];//Y轴
var xtext = [];//X轴TEXT
var color = ['#21c2ff','#5c9fff','#936dfb','#cf8cfb','#49FBF4','#00ff77','#F774FB','#7D1FFB','#57FBB4','#FBCBF5'];
$.ajax({
    "url": "user/chart",
    "async": false,
    "success": function(data) {
        var json = eval("("+data+")");
        for(var key in json.list){
            json.list[key].y = json.list[key].num; //给Y轴赋值
            xtext = json.list[key].keyword;//给X轴TEXT赋值
            json.list[key].color= color[key];
        }
        chart.series[0].setData(json.list);//数据填充到highcharts上面
    }
});

验证码设计代码:

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对前端这方面比较陌生,但学习能力强态度很认真,完成自己的任务后会主动找我要任务,还能帮我解决一些小问题(困扰很久的小问题,不得不说当局者真的迷,sql单词拼错还不报错)。希望还有机会合作。

posted @ 2021-03-31 22:55  WiLLyy  阅读(125)  评论(6编辑  收藏  举报