软件工程实践结对作业二

这个作业属于哪个课程2022年福大-软件工程;软件工程实践-W班
这个作业要求在哪里要求在这里
这个作业的目标以北京冬奥会赛事信息平台为载体,初步接触需求分析,了解NABCD模型在实际中的应用,并初步学会使用一些原型设计工具进行原型设计
结对学号221900231 221900203
其他参考文献见博客末尾

0x01 about 链接

1.1 Gitcode仓库地址

PairProject of 221900203 and 221900231

1.2 服务器链接

Hello,XXIV Winter Olympics!

1.3 代码规范链接


0x02 PSP表格&效能分析

2.1 PSP表格

203同学的PSP表格

PSPPersonal Software Process Stages预估消耗时间(分钟)实际消耗时间(分钟)
计划Planning
•估计这个任务需要多久•Estimate510
开发Development
•需求分析•Analysis1015
•制定代码规范•Deciding Code Style1015
•前端技术学习•Tool Learning150120
•原型改进•Previous Improving5030
•前端编码•Coding14001600
•测试及改进•Testing and Improving400400
•撰写报告•Reporting3050
•计算工作量•Size Measurement53
•事后总结•Postmortem2030
总计20802253

231同学的PSP表格

PSPPersonal Software Process Stages预估消耗时间(分钟)实际消耗时间(分钟)
计划Planning
•估计这个任务需要多久•Estimate510
开发Development
•需求分析•Analysis1015
•制定代码规范•Deciding Code Style1010
•后端技术学习•Learning12001400
•后端编码•Coding240180
•测试及改进•Test and Improving150180
•云服务器部署•Deploying4001000
•撰写报告•Reporting120150
•计算工作量•Size Measurement53
•事后总结•Postmortem2020
总计21602968

2.2 效能分析

  • 203同学:本次结对中,花费时间较多的是编码测试改进模块,与预估出入较大的是编码部分。我负责结对的前端部分,由于之前学过Vue2.0相关知识,所以学习技术环节没有什么时间成本,但由于学艺不精,外加直男审美,编写界面时极其痛苦,导致一直在调样式,超时较多,而且最后界面也不是很好看,后面的测试也是如此,所以如果后面还要继续写前端的话,应该要多练手,不然真正有用上的时候就会感觉有心无力。

  • 231同学:本次作业的效能简直惨不忍睹。由于我的技术栈过于浅薄(真的是书到用时方恨少),在本次结对作业中,为了能完成目标,不得不学习新技术。但由于工期非常紧,只能学习个易入门的技术且只能学个大概。但即使如此,照样也花费了很多时间。另外一个就是部署服务器也花了很多时间,因为之前完全没有接触过linux,其中的很多东西都只能跟着教程做,出问题只能借助搜索引擎的力量,导致效率很低下。综上所述,菜是原罪,应该多抓紧时间学技术,否则遇到这种情况只能感慨自己没用了。


0x03 成果展示

根据原型,我们一共完成了奖牌地图奖牌榜赛程赛况关于冬奥四大栏目

3.0 header and footer

在这里插入图片描述

在这里插入图片描述

3.1 奖牌地图界面

装载所有91个冬奥参赛国家的奖牌信息(包括获奖没获奖的国家/地区信息),没获奖的国家/地区也会有颜色,和其他未参赛国家/地区区分

在这里插入图片描述

这里插播一条地理小常识:南美洲有一块有颜色地区,其实是法国海外属地法属圭亚那

鼠标移动到有数据的国家时,会显示国家/地区名称和他的具体奖牌信息:

在这里插入图片描述

在这里插入图片描述

将鼠标锚定在图例的某个位置,会显示奖牌总数在这个值附近的国家/地区名:

在这里插入图片描述

3.2 奖牌榜界面

奖牌榜显示排名、国家/地区名、金牌数、银牌数、铜牌数、奖牌总数,以分页的形式展示:

在这里插入图片描述

3.3 赛程赛况界面

赛程赛况中又进行了页内导航,分为日期赛程分项赛程大项赛程多选项(混合查询)页面

3.3.1 日期赛程页面

以日历形式展示,点击某一天,就可以查看当天赛程

在这里插入图片描述

在这里插入图片描述

3.3.2 分项赛程页面

以卡片形式展示所有分项(共15个),点击可以查看对应赛程,还可以查看对应比赛的赛况结果(目前只支持单人雪车单人雪橇钢架雪车三个项目的赛果查看):

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.3.3 场馆赛程页面

展示策略同上

在这里插入图片描述

在这里插入图片描述

3.3.4 组合查询页面

可以在此选择日期项目场馆查询指定赛事。如果不选择其中一项或几项,则该条件不限制。全部都不选时,查询所有赛事:

在这里插入图片描述

在这里插入图片描述

3.4 关于冬奥界面

3.4.1 北京冬奥会介绍页面

简单介绍本次冬奥会的基本情况,并用图表展示赛事构成、金银铜牌归属分布等数据:

在这里插入图片描述

在这里插入图片描述

3.4.2 历届冬奥会介绍页面

罗列了2002年以来的冬奥会举办简介,用户可以点击某一张卡片查看对应冬奥会的赛事介绍百度百科:

在这里插入图片描述


0x04 编码过程实录

4.1 结对分工

由于203同学之前已经比较系统地学习了Vue的有关知识并且已经实战过,所以本次结对前端部分的编码任务自然而然就交给了他。但是,231同学啥也不会,所以只能选择后端。考虑到前端的任务繁重,工作量大,所以231同学还承担部署服务器、最后博客的大部分编写工作。

4.2 技术栈

  • 前端:Vue2.0+axios(前后端交互中介)
  • 后端:Servlet 3

4.3 编码工具

VSCode+IntelliJ IDEA Ultimate

4.4 网页设计思路

在这里插入图片描述

4.5 细说编码中的困难

Barrier 1:如何在Vue中嵌入地图

估计这是前端编码中最大的困难了,因为之前前端实现的时候从来没有遇到过这种需求。一开始两个人都没什么头绪,只能硬着头皮在网上搜罗相关经验,最后还真的找到了。Vue的本质其实是javascript,所以js中支持的语法在Vue中一定适用,所以我们找了如何在js中嵌入可视化地图,然后就搜出了本次作业的重磅友情插件——EChart。不得不叹服Echart强大的数据集成、可视化能力。只需要照着他的模板,剔除不参加显示的国家/地区名,并取得需要显示的国家/地区的奖牌数据,就可以实现我们的目的。(由于本次冬奥会有奖牌的国家只有29个,所以我们决定直接将数据写在js脚本里面)

具体代码会在后面展示

Barrier 2:前端如何获取赛程数据

由于两个人头一次接触前后端分离的编码模式,对于如何接受数据的认识,仅仅停留在理论层面,因此也是一个大麻烦。同样是经过了多方查找,发现其实实现方式或者技术还是挺多的。最后我们使用了axios来充当数据交互的中间媒介,他本质是ajax,不过操纵会更加方便。将基本代码写好之后,只要和后端沟通好数据接口,前台就可以拿到数据信息了。

Barrier 3:后端应该采用什么技术

对于啥也不会的后端同学,由于作业时间很短,所以这个问题非常头疼。最开始打算用Springboot,但是粗浅看了一下,发现东西太多了,如果不系统学,用起来会很乱,于是后来选择了它的先导版本——Servlet。他的职能更倾向于数据处理,所以只需要掌握基本的请求/响应流程就行,对于本次任务的需求足够了,唯一的不足就是代码重复性较高。


0x05 关键代码展示

5.0 项目结构

在这里插入图片描述

5.1 前端关键代码

奖牌地图
引入Echart组件世界地图部分,手动把参赛国家/地区列出,并将获取到的奖牌数据渲染到地图中,最后设置悬停时的显示样式。


import world from '../../static/mapData/world.json'
...

  jsonMap: {
        'world':world,
   },
  worldData :[
        {name: '美国', gold: 8, silver: 10, copper: 7, value: 25,rank: 4},
        ...//此处填充奖牌信息
  ],
  nameMap : {
        'Afghanistan':'阿富汗','Afghanistan':'阿富汗',
        ...//为原json中的英文国家名建立中文映射
  },
  ...//其他非重要代码
  methods:{
    chinaConfigure(area) {
         this.myChart = this.$echarts.init(document.getElementById("map")); //这里是为了获得容器所在位置        
         window.onresize = this.myChart.resize;
         let option={ // 进行相关配置
          backgroundColor: "#E4F3FA",
          tooltip: {
            triggerOn: 'mousemove',
            formatter: function (params){
              console.log(params);
              if(params.data.name!=null){//设置悬停时的显示信息
              return params.data.name+"\n"+"金牌数:"+params.data.gold+
              "\n"+"银牌数:"+params.data.silver+"\n"+"铜牌数:"+params.data.copper+"\n"+"奖牌总数:"+params.data.value+"金牌榜名次:"+params.data.rank
              }
              }
          }, 
          visualMap: {//设置图例
            max: 40,//设置奖牌总数最大值
            calculable: true,
            inRange: {
                color: [ '#ffffbf', '#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']//为图例锚定颜色变换区间,系统会让颜色自然过渡
              }
                  },
        series: [{
        type: 'map',
        map: area,
        data: area=='world'?this.worldData:[],
        nameMap: area=='world'?this.nameMap:{},
    }]
        }
        this.myChart.setOption(option,true);
      },
  }
};

axios封装前台数据请求及响应结果

请求时,将参数params先封装好,然后接入后台给出的数据接口,由axios传入后台,最后响应一个数据结构(json形式),从中取出自己想要的值。

export default {
  name: 'DailyScheduleMore',
  
  data() {
      return {
          tableData:[

          ],
          date:"date",
          game_kind:"game_kind",
          game_venue:"game_venue",
          currentPage:1,//当前页码
          total:0,//总数据
          pageSize:9, //每页的数据条数
          url:'http://47.97.91.198:8081/Olympics-backend/SearchByMixedConditions',//后台接口
      } 
  },
    mounted(){
        ...
    }, 
    methods: {  
        handleSizeChange(val) {//每页条数改变时触发 选择一页显示多少行 
            console.log(`每页 ${val} 条`);
            this.pageSize = val;
        },
        handleCurrentChange(val) {//当前页改变时触发 跳转其他页
            console.log(`当前页: ${val}`);
            this.currentPage = val;
        },
         
        getlivestockInfo(){
            var that = this;
            var url = this.url;
            var params;
            params = {search:"true",date:""};//请求参数封装(默认值)
            // params.append('pageNum',num1);
            this.$axios.get(url,{params:{
              search:"true",date:this.date,game_kind:this.game_kind,game_venue:this.game_venue
            }}
            )    
            .then(response => {//请求成功
                this.tableData=response.data.matchList;//需事先了解json结构
                that.total=response.data.matchList.length;//便于分页
                console.log(that.tableData.length);
            }).catch(error => {//请求失败
                // console.log('请求失败');
                console.log(error);
            })
        },
    }
}

分页

用到了ElementUIpagination组件,可以自定义每页显示几条数据等个性值。


<!-- 分页器 -->
    <div class="block" style="margin-top:15px;">
        <el-pagination
            	@size-change="handleSizeChange"
            	@current-change="handleCurrentChange"
            	:current-page="currentPage"
            	:page-sizes="[5,10,15]"
            	:page-size="pageSize"
            	layout="total, prev, pager, next, jumper"
            	:total="total">
			    </el-pagination>
    </div>

router的使用

router文件夹下的index.js中配置好路由信息,目的主要是实现页面的正常跳转。


Vue.use(Router)

export default new Router({
  base: '/Olympics_frontend/',
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'report',
      component: report,
      redirect: '/HelloWorld',//奖牌地图做为首页
      children: [
        { 
          name:'HelloWorld',
          path:'/HelloWorld',
          component:HelloWorld
        },
        {
          name:'ScheduleAction',
          path:'/ScheduleAction',
          component:ScheduleAction,
          redirect: '/ScheduleAction/DateSchedule',//界面内分页(二级导航)
          children:[
            {
              name:'DateSchedule',
              path:'/ScheduleAction/DateSchedule',
              component:DateSchedule
            },
           ...//其他路由
    }
  ]
})

5.2 后端关键代码

首先附上整个数据流在请求/响应周期的流向:

在这里插入图片描述

5.2.0 项目结构

在这里插入图片描述

5.2.1 装填数据库

本次作业实际上可以不用后台,因为官方的api是公开的,我们可以根据此来直接获取数据并在前台解析,但是这样子的话组合查询实现就有点困难,所以最后还是选择做后端传数据。
数据我们利用了上一次作业的成果(谁能想到这作业竟是连续剧),先跑了上次的程序在记事本上,然后写程序解析填入数据库。
下面这段代码的作用是解析赛事到数据库:

    ...//数据库连接配置信息

    Class.forName(JDBC_DRIVER);

    // 打开链接
    System.out.println("连接数据库...");
    conn = DriverManager.getConnection(DB_URL, USER, PASS);

    BufferedReader reader;
    try {
        reader = new BufferedReader(new InputStreamReader(new FileInputStream("output.txt"), StandardCharsets.UTF_8));
        String line = reader.readLine();
        int id = 1;
        stmt = conn.prepareStatement("insert into gameData values (?,?,?,?,?)");
        String time = null;
        ..//初始化字段
        while (line != null) {
            if (line.contains("time")) {
                time = line.replaceAll("time:", "").trim();
            } 
               ..//处理空格
                stmt.setInt(1, id);
                id++;
                stmt.setString(2, time);
                //填充字段

                stmt.execute();
            }
            line = reader.readLine();
        }
        reader.close();
        // 完成后关闭
        stmt.close();
        conn.close();
    }
    ...//异常处理

5.2.2 解决跨域造成数据无法传输的问题

由于前后端采取的策略不一致,这会造成所谓的跨域问题,如果不处理,会造成500错误,需要设置一下:


//设置跨域问题及编码问题
        response.setContentType("application/json;charset=utf-8");//以json格式传输响应
        response.setCharacterEncoding("UTF-8");//设置中文编码格式,解决乱码问题
        response.setHeader("Access-Control-Allow-Origin","*");//设置消息头,防止拦截
        response.addHeader("Access-Control-Allow-Headers","Origin, X-requested-with, Content-Type, Accept");
        response.setHeader("Access-Control-Allow-Methods","GET,POST");//请求类型:GET,POST

5.2.3 数据库连接

专门写了一个类处理数据库连接:(这里其实更好的方式应该是把连接类设为单例,这样可以保证资源不被盲目创建,保证数据安全)


public class DBConnectionImpl implements DBConnection{//实现接口,让代码更加规范
    public Connection conn = null;
    private static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String DB_URL = "jdbc:mysql://47.97.91.198:3306/beijingOlympics?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PASS = "zxc010320";
    @Override
    public void connectToDatabase() {
        try {
            Class.forName(JDBC_DRIVER);

            // 打开链接
            System.out.println("连接数据库...");
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
        }//数据库的关闭写在Servlet层处理完后
        catch (SQLException | ClassNotFoundException e)
        {
            e.printStackTrace();
        }
    }
}

5.2.4 查询数据

使用Preparedstatement+占位符的方式构建查询语句,将Servlet传来的条件填入占位符后完成查询(其实使用Mybatis更好)


public class DateSearchDaoImpl implements DateSearchDao{
    //本层真正与数据库交互
    @Override
    public List<GameData> searchDateScheduleOnDatabase(String date) {
        ...

        try {
            PreparedStatement stmt = dbConnection.conn.prepareStatement("select * from gamedata where date=?");
            stmt.setString(1,date);
            ResultSet resultSet = stmt.executeQuery();

            while (resultSet.next())//将数据填入Bean对象
            {
                GameData gameData = new GameData();
                gameData.setDate(resultSet.getString("date"));
                gameData.setTime(resultSet.getString("time"));
                //...
                resultList.add(gameData);
            }
        } ...//处理异常
        try {
            dbConnection.conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return resultList;
    }

}

5.2.5 封装json对象

使用第三方库封装json对象,只需把Bean中的数据以此装入即可(这里还需装入total字段,便于前端显示时分页)


     FourRoundsDetailService fourRoundsDetailService = new FourRoundsDetailServiceImpl();
     List<FourRoundsData> resultList=fourRoundsDetailService.searchFourRoundsResult(competition);

    int resultNumber = resultList.size();

     JSONObject resultAll = new JSONObject();
     JSONObject resultMain;

     JSONArray resultArray = new JSONArray();
     resultAll.put("total",resultNumber);

    //装载JSON
     for (FourRoundsData fourRoundsData : resultList) {
         resultMain = new JSONObject();//每次都需要初始化,JSONObject对象内容无法被覆盖
         resultMain.put("rank",fourRoundsData.getRank());
         //...

         resultArray.put(resultMain);
     }

     resultAll.put("resultList",resultArray);

     PrintWriter writer = response.getWriter();
     writer.print(resultAll);

5.2.6 如何发现前端请求

在这里我们做了一个约定:用一个字段来标记。在前台的消息中加入search字段,后台servlet层读到请求信息中的search字段后做个判断再处理:


  String search = request.getParameter("search");
  ...
  if (search.equals("true"))
  {
      ...//操纵数据库
  }


0x06 新手部署云服务器

之前从来没接触过服务器部署,全都是localhost产品。借助此次作业,粗浅地跟着视频学习了一下部署。
使用的是阿里云ECS,采用最低的配置进行部署,系统为LinuxCent OS 8.5
由于部署过程非常繁琐,所以这里只是粗略地讲下我们的部署过程。

6.0 工具准备

XShell,Xftp,前者用于命令输入,后者用于传输文件

6.1 下载JDK

Oracle官网下载linux的JDK,然后传输到云服务器上。传输完成后,设置环境变量并生效,输入java -version命令,有版本信息出来就代表成功:

在这里插入图片描述

6.2 下载tomcat

从官网下载tomcat,我们在此下了和本地一个版本号的tomcat(服务器其实就是一个啥也没有的远程电脑),解压后进入bin目录,输入指令./start.sh指令可以查看是否成功安装。

在这里插入图片描述

最后还需要设置tomcat的端口并开放,防止端口冲突。

6.3 安装MySQL

这是部署中最麻烦的一个环节。首先也是先下载linux的MySQL安装包,然后将他传输到云服务器。
接下来解压,以此按照指令下载commonlibsclientserver。完成后对端口进行开放,并修改数据库密码,在Navicat中检查是否连接成功:

在这里插入图片描述

6.4 打包本地项目,投到云服务器

maven打包后台程序,用run npm build指令打包前台程序。生成后,后端为war包,前端程序在dist文件夹下。
然后将程序放入tomcatwebapps下。(war包拖入此会自动被解压)

在这里插入图片描述

6.* 完成部署

输入http://ip地址:端口号/运行目录就可以正常访问。

在这里插入图片描述


0x07 结对过程

7.1 分工情况

  • 203同学:前端框架搭建编写、样式调整、部分博客撰写
  • 231同学:后端数据操作、云服务器部署、博客大体撰写

7.2 结对记录

本次结对可以线下开展,这为团队协作提供了便利。结对过程主要以线下交流为准,辅以少量的线上资料共享。

在这里插入图片描述

在这里插入图片描述


0x08 心得体会

  • 本次作业暴露出的最大一个问题是技术栈浅薄,因此会造成工作量加倍,完成任务时会非常痛苦,因此应该抓紧时间学习相应技术
  • 其实在写博客的时候有发现项目存在的不足,如导航栏出现了滚轮(没部署前是没有的)、页面存在两个滚轴、页脚留白过多等等问题,这些在后续应该继续改进、界面不太美观等等
  • 虽然完成的很痛苦,但是学到了技术的相当多东西,对于个人的成长是有帮助的,是一次很好的锻炼机会
  • 第一次尝试前后端分离开发,充分感受到了这种开发模式的好处。当没有涉及到数据交互时,前后端的耦合度极低,大大方便了协调开发,开发起来比较有条理

0x09 队友互评

  • 203同学评231同学:虽然231同学在本次作业前没有接触过后端的相关知识,但是学习能力比较强,对新事物上手速度快,写的后端代码整体bug很少,方便了前台编程。同时231同学愿意主动分担任务,让本次我们的工作量得到了很好的平衡,是一个可靠的队友。

  • 231同学评203同学:203同学有一定的技术栈,项目经历比较广,这为本次作业的完成提供了便利,同时他有很多的好点子,在项目中得到了较多的应用。203同学动手能力强,抗压能力也较强,是一个好队友。


0x0A 补充:数据库设计

本次作业涉及的数据虽然很多,但是数据结构很简单,后端建立了赛事信息赛况(部分比赛)这两张实表,以及一张虚的空表,用以处理不满足条件的数据请求。
由于我们制作的赛况不能在一维尺度(即一行)上展现,所以无法实现用外键连接,上面提到的表全是独立的。

在这里插入图片描述

gamedata表:

在这里插入图片描述

在这里插入图片描述

mensskeleton表(其他已完成赛况的表结构同此):

在这里插入图片描述

在这里插入图片描述


0x0B 参考文献

B站:Servlet+Http+Tomcat入门教程
B站:阿里云部署vue+springboot
CSDN:Linux部署Tomcat报错:Could not contact localhost:8005.
博客园:js中实现世界地图数据可视化
RUNOOB:vue.js中使用axios


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值