结对第二次作业--编程实现

222100401丁念 2024-03-26 18:00:00
这个作业属于哪个课程2302软件工程社区
这个作业要求在哪里结对第二次作业——编程实现
结对学号222100305庞财莹 222100401丁念
这个作业的目标不限框架编程实现;完成博客撰写
其他参考文献CSDN 、Springboot、Vue

目录

  • 1. 项目地址
  • 1.1 Gitcode仓库地址
  • 1.2 云服务器项目地址
  • 1.3 代码规范地址
  • 2.PSP表格和效能分析
  • 2.1 PSP表格
  • 2.2 效能分析
  • 3.成果展示
  • 3.1 基础框架展示
  • 3.2 选手信息
  • 3.3 每日赛况
  • 3.4 详细赛况
  • 3.5 奖牌榜
  • 3.6 附加功能:首页
  • 3.7 附加功能:了解更多
  • 3.8 附加功能:按赛程、日期、显示时间来筛选赛程信息
  • 3.9 附加功能:切换显示其他比赛阶段赛事详细信息
  • 4. 结对讨论过程描述
  • 4.1 开发初期
  • 4.2 开发中期
  • 4.3 开发后期
  • 5.设计实现过程
  • 5.1 功能结构图
  • 5.2 设计概述
  • 5.2.1 前端部分
  • 5.2.2 后端部分
  • 5.3 遇到的问题及解决思路
  • 5.3.1 前端部分
  • 5.3.2 后端部分
  • 6. 代码说明
  • 6.1 前后端交互接口说明
  • 6.1.1 选手信息接口
  • 6.1.2 每日赛况接口
  • 6.2 前端代码说明
  • 6.2.1封装request请求
  • 6.2.2使用代理处理跨域
  • 6.2.3使用router跳转页面
  • 6.2.4表格部分展示
  • 6.2.5组件化复用代码
  • 6.3 后端代码说明
  • 6.3.1 图片上传服务器
  • 6.3.2 跨域处理
  • 6.3.3 数据处理
  • 7. 心路历程和收获
  • 8.评价队友

1. 项目地址

1.1 Gitcode仓库地址

仓库地址

1.2 云服务器项目地址

网页服务器地址

1.3 代码规范地址

后端代码规范地址

前端代码规范地址

2.PSP表格和效能分析

2.1 PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划
• Estimate• 估计这个任务需要多少时间1010
Development开发
• Analysis• 需求分析 (包括学习新技术)240480
• Design Spec• 生成设计文档6030
• Design Review• 设计复审1010
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)3030
• Design• 具体设计6030
• Coding• 具体编码6001200
• Code Review• 代码复审3030
• Test• 测试(自我测试,修改代码,提交修改)240300
Reporting报告
• Test Repor• 测试报告120180
• Size Measurement• 计算工作量2020
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3060
合计14502380

2.2 效能分析

  • 需求分析(包括学习新技术):本次作业我们决定要以前后端分离SpringBoot + Vue的形式完成,但由于后端同学之前未接触过SpringBoot,比预计多花费了很多时间在后端环境搭建,后端框架学习上。在学习初期,难免会遇到很多配置问题,这无疑很耗时间。在后续实践中,应尽量提早掌握技术,以防出现不可预料的难题。

  • 生成设计文档与具体设计:由于在上次结对作业中,我们就已经 基本完成网页的原型设计,节省了非常多我们设计阶段的时间。故在今后进行开发时,因先经过充分沟通后,合作完成基础的原型设计,这不仅能帮助我们明确具体设计,更有助于后续开发过程中前后端保持一致,提高团队合作效率

  • 具体编码:以前后端分离的形式实现此次作业,由于后端同学对后端知识的不熟悉,具体编码阶段时间长、效率低,对于学习初期的后端同学是正常的。在具体编码过程中,常常会遇到因过于面向实践开发,技术底层原理不扎实而导致的各种困难,在今后,有充足的时间给后端同学对补足这一块的知识,希望在今后的实践中,能熟能生巧,提高编码效率。

3.成果展示

3.1 基础框架展示

  • 参照该网站,我们设计了统一的导航条与页脚。其中导航条显示该赛标志、名称等信息,支持各个页面之间的跳转,放置于页眉处。页脚展示相应的开发者与版权信息。具体显示效果如下图所示:

img


img

3.2 选手信息

  • 采用表格的形式来规范展示该赛事各选手的具体信息,其中包含选手的Country、Athlete、Gender 、DOB,以及国籍图片与选手头像。具体显示效果如下:

img

3.3 每日赛况

  • 默认展示该赛所有赛程,显示具体比赛时间、名称、阶段,其中实现以日期为单位分组展示,并支持点击查看详细比赛情况。具体显示效果如下:

img

3.4 详细赛况

  • 显示指定赛事的详细信息,包含参赛选手、选手排名、选手国籍、选手年龄、比赛得分、与落后积分,并图文并茂的展示对应选手图像等信息。该比赛前三名会有对应的金、银、铜牌图标显示。具体显示效果如下:

img

3.5 奖牌榜

  • 以表格形式展示各国获奖情况,其中包含奖牌数量排名、国家名称、国家图片、金银铜牌数量以及总奖牌数量。具体显示效果如下:

img

3.6 附加功能:首页

  • 于首页上方,采用了轮播图的形式,展示该赛事的精彩瞬间。

  • 随后图文并茂的展示该赛事的最新新闻,以增加网页趣味。

  • 具体显示效果如下:

img

3.7 附加功能:了解更多

  • 采用图文并茂的形式,生动形象的展示了2024年世界游泳锦标赛的相关信息,以增加网站趣味。具体效果如下图所示:

img

3.8 附加功能:按赛程、日期、显示时间来筛选赛程信息

  • 每日赛况页面,我们利用下拉框组件来支持用户以如下三种方式来筛选赛程信息,方便用户快速定位到想要查看的赛事,增加网站与用户的交互性。

  • 按赛程筛选信息,默认显示各阶段(初赛、半决赛、决赛)赛程信息,具体显示如下:

img

  • 按日期筛选信息,具体显示如下:

img

  • 按显示时间选择信息,支持用户选择按当地、背景时间显示时间信息,具体显示如下:

img

  • 点击Reset按钮,返回默认显示界面,具体显示如下:

img

3.9 附加功能:切换显示其他比赛阶段赛事详细信息

  • 在详细赛况页面,以下拉框形式,支持用户切换当前比赛项目其他赛事阶段的详细赛况信息,具体显示如下:

img

4. 结对讨论过程描述

  • 由于队友有前端开发经验,故我们线下讨论后决定以前后端分离形式完成作业。

4.1 开发初期

以下截图记录了,我们遇到问题,共同讨论到共同解决的过程。

  • 沟通跨域问题

    img


    img


    img

4.2 开发中期

  • 沟通协作时间,提高合作效率

img

  • 沟通前后端数据传输方式

img


img

  • 沟通接口

img


img

  • 沟通传输的数据格式

img

4.3 开发后期

  • 前端开发完成,打包给后端一起部署上服务器

img

5.设计实现过程

5.1 功能结构图

img

5.2 设计概述

5.2.1 前端部分

  1. 技术相关:Vue,axios,element-ui,echarts,vue-awesome-swiper

  2. 实现思路:

    • 使用axios获取接口数据,本次作业封装了request,可以统一处理axios请求。
    • 获取到数据使用element-ui组件库进行设计,列出表格等展示信息。
    • 使用echarts可视化数据。
    • 将多个页面使用到的内容抽离成组件,提高代码复用率,便于维护。

5.2.2 后端部分

  1. 采用技术:Docker、servlet、SpringBoot
  2. 设计思路
    • 数据获取思路:由于本次作业特性,我们这次作业的数据可从官网下载相应的JSON文件,直接把JSON文件当做我们的数据库,进行编程。
    • 数据处理思路:分析相应JSON文件结构,学习FastJson使用方法parseObject、parseObject、parseObject、parseObject等,即可完成数据的层次解析;同时利用JSONArray套JSONObject的数据结构,将所需传输给前端的数据打包。
    • 接口设计思路:每一动作对应于一个接口,并且只对必要的数据进行传输。

5.3 遇到的问题及解决思路

5.3.1 前端部分

  1. 跨域

    问题描述:前端访问后端接口的跨域问题

    是否解决:是

    解决收获:前端配置proxy代理,重写路径。


  1. echarts

    问题描述:echarts柱状图横坐标名字不显示

    是否解决:是

    解决收获:不是因为渲染数据的数组有问题,是因为给的容器宽度不够。


5.3.2 后端部分

  1. SpringBoot环境配置
    问题描述:SpringBoot与IDEA等版本不匹配
    是否解决:是
    解决收获:我们在开发过程中,经常会遇到环境配置的问题,当我们遇到此类问题时不仅要充分利用CSDN等网站,寻求经验贴,更要注重官方文档的阅读,许多官方文档中都会说明各个版本配置要求。

  1. 后端图片传输
    问题描述:后端需要将类似选手头像等图片传输给前端
    是否解决:是
    解决收获:经讨论后,我们决定把选手头像等图片上传至服务器,后端通过传送相应图片url,让前端使用相应图片。在开发过程中,很多东西都是边学边实践,经历过后,后端小知识点加一。

  1. 跨域问题
    问题描述:前端访问后端接口的跨域问题
    是否解决:是
    解决收获:在搜集了多种跨域解决方法后,我发现Spring 从4.2版本后开始支持 @CrossOrigin 注解实现跨域,这在一定程度上简化了我们实现跨域访问的开发成本,在需要跨域访问的方法或者类上加上这个注解便大功告成。故在SpringBoot中,善用注解,能大大提高开发效率。

6. 代码说明

6.1 前后端交互接口说明

6.1.1 选手信息接口

  • 前端请求

    前端统一封装了接口,只需要写路径和方法即可

    export function getAll() {
        return request.request({
            method: "get",
            url: `/players`
        })
       } 
    
  • 后端接口

    @RestController
    @CrossOrigin(origins = "*")
    public class PlayersController {
    
      @RequestMapping("/api/players")
      public JSONArray players(){
          Lib lib = new Lib();
          return lib.showplayers();
      }
    }
    
  • 后端返回效果

    [
      {
          "PhotoPath": "http://120.26.225.240:8089/uploadimg/c71d6025-9088-4aae-b276-a9416f1cd115.png",
          "CountryImagePath": "http://120.26.225.240:8089/uploadimg/Austria.png",
          "FirstName": "Alexander",
          "DOB": "1999-03-07",
          "Country": "Austria",
          "LastName": "HART",
          "Gender": "Male",
          "Sports": "DV"
      },
     ...
    ]
    

6.1.2 每日赛况接口

  • 前端请求

      //获取赛程
       export function getSchedule() {
        return request.request({
            method: "get",
            url: `/schedule`
        })
               
       } 
    
  • 后端接口

      @RequestMapping("/api/schedule")
      public JSONArray schedule(){
          Lib lib = new Lib();
          return lib.showpschedule();
      }
    
      @RequestMapping("/api/scheduletime")
      @ResponseBody
      public JSONArray schedule(@RequestBody Map name){
          Lib lib = new Lib();
          return lib.showpscheduletime(name.get("Date").toString());
      }
    
      @RequestMapping("/api/scheduletype")
      @ResponseBody
      public JSONArray scheduletype(@RequestBody Map name){
          Lib lib = new Lib();
          return lib.showpscheduletype(name.get("PhaseName").toString());
      }
    
      @RequestMapping("/api/schedulelocal")
      @ResponseBody
      public JSONArray schedulelocal(){
          Lib lib = new Lib();
          return lib.showpschedulelocal();
      }
    
      @RequestMapping("/api/scheduleday")
      @ResponseBody
      public JSONArray scheduleday(){
          Lib lib = new Lib();
          return lib.showpscheduleday();
      }
    
  • 后端返回效果

  • 以/api/schedule为例,后端返回按日期分组的JSONArray。

    [
      [
          {
              "SportCode": "DV",
              "DisciplineId": "108c795d-5e4f-4dc6-acea-0bc70bfd1928",
              "DisciplineName": "Women 1m Springboard",
              "Time": "10:00:00",
              "PhaseName": "Preliminaries",
              "Date": "2024-01-18"
          },
          ...
      ]
      ...
    ]
    

6.2 前端代码说明

6.2.1封装request请求

  • 利用axios和后端进行交互,封装request,可以统一封装接口
    import axios from "axios";
    // 创建axios,赋给变量service
    const request = axios.create({
      baseURL:  'http://120.26.225.240:8089/api',
        timeout: 15000,   // 超时
    });
    
    //添加请求拦截器
    request.interceptors.request.use(
        function (config) {
            config.headers['Access-Control-Allow-Origin'] = '*';
            //在发送请求之前做一些事情
            return config;
        },
        function (error) {
            //请求错误做些什么
            return Promise.reject(error);
        }
    );
    //添加响应拦截器
    request.interceptors.response.use(
        function (response) {
            //使用响应数据返回响应;
            const data = response;
            // 如果data.code
            if (data) {
                return Promise.resolve(data.data);
            }
            return response;
        },
        function (error) {
            //使用响应错误返回
            return Promise.reject(error);
        }
    );
    // 将service 导出
    export default request;
    

6.2.2使用代理处理跨域

  • const { defineConfig } = require('@vue/cli-service')
    
    module.exports = defineConfig({
      transpileDependencies: true,
      lintOnSave:false,
      devServer: {             
        port: 8080,
        proxy: {                 //设置代理
            '/api': {              //设置拦截器 
                target: 'http://120.26.225.240:8089',     //代理的目标地址
                changeOrigin: true,          
                pathRewrite: {                   //路径重写
                    '/api': ''                     //选择忽略拦截器里面的内容
                }
            }
        },
          host: '0.0.0.0',
          client: {
           
          },
          headers: {
            'Access-Control-Allow-Origin': '*',
          }  
    }
    })
    

6.2.3使用router跳转页面

  • //带参数跳转
    handleSelect(key) {
            if(this.activeIndex != key)
             this.$router.push(
                {
                  name:key,
                }
              )
            else{
              alert('当前已经是目标页面')
            }
          },
    
    //在router.js定义各个路径情况,此处展示奖牌榜的
    import Medal from '../views/Medal.vue'
    Vue.use(VueRouter)
    const routes = [
      {
        path: '/',
        redirect: "/home",
      },
      {
        path: "/medal",
        name: 'Medal',
        component: Medal
      }
    ]
    
    const router = new VueRouter({
      base: process.env.BASE_URL,
      routes
    })
    export default router
    

6.2.4表格部分展示

  •   //使用element-ui的table组件,自定义列,加入选手国家图片等
      <el-table-column
          label="Country"
          width="180">
          <template slot-scope="scope">
          <div  class="tem"> 
            <img :src="scope.row.CountryImagePath" alt="" class="countryPic">
            <span class="country">{{scope.row.Country }}</span>
            </div>
          </template>
        </el-table-column>
    

6.2.5组件化复用代码

  •  <TopBar :index="index"/>//将导航栏抽离成组件,在需要的页面使用
     <Buttom/>//抽离底部
    

6.3 后端代码说明

6.3.1 图片上传服务器

  • 处理上传的handler

    @RestController
    public class UploadHandler {
      private Logger logger = LoggerFactory.getLogger(UploadHandler.class);
      @Autowired
      private UploadService uploadService;
    
      @PostMapping("/uploadimg")
      public String uploadimg(HttpServletRequest request) throws IOException, ServletException {
          // handler调用文件上传的service 得到文件的虚拟路径
          String filepath = uploadService.uploadImg(request);
          return filepath;
      }
    }
    
  • 处理文件上传的service,在service中将上传的文件夹和映射的文件夹都放在配置文件中

    @Service
    public class UploadService {
      private Logger logger = LoggerFactory.getLogger(UploadService.class);
      // 文件的真实路径
      @Value("${file.uploadFolder}")
      private String realBasePath;
      @Value("${file.accessPath}")
      private String accessPath;
    
      public String uploadImg(HttpServletRequest request) throws IOException, ServletException {
          InputStream inputStream = request.getInputStream();
          //获取请求头中Contect-Type的值
          // 图片后缀
          String imgSuffix = "png";
          Enumeration enumeration=request.getHeaderNames();
          while(enumeration.hasMoreElements()) {
              String name=(String)enumeration.nextElement();
              if(name.equals("content-type")){
                  String value=request.getHeader(name);
                  imgSuffix = value.split("/")[1];
                  logger.info("header中" + name + " " + value);
                  logger.info("文件后缀:" + imgSuffix);
              }
          }
          // 文件唯一的名字
          String fileName = UUID.randomUUID().toString() + "." +imgSuffix;
          Date todayDate = new Date();
          SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
          String today = dateFormat.format(todayDate);
          // 域名访问的相对路径(通过浏览器访问的链接-虚拟路径)
          String saveToPath = accessPath + today + "/";
          // 真实路径,实际储存的路径
          String realPath = realBasePath + today + "/";
          // 储存文件的物理路径,使用本地路径储存
          String filepath = realPath + fileName;
          logger.info("上传图片名为:" + fileName+"--虚拟文件路径为:" + saveToPath +"--物理文件路径为:" + realPath);
          // 判断有没有对应的文件夹
          File destFile = new File(filepath);
          if (!destFile.getParentFile().exists()) {
              destFile.getParentFile().mkdirs();
          }
          // 输出流 输出到文件
          OutputStream outputStream = new FileOutputStream(destFile);
          // 缓冲区
          byte[] bs = new byte[1024];
          int len = -1;
          while ((len = inputStream.read(bs)) != -1) {
              outputStream.write(bs,0,len);
          }
          inputStream.close();
          outputStream.close();
          // 返回虚拟路径,给链接访问
          return saveToPath+fileName;
      }
    }
    
  • 设置虚拟路径,访问绝对路径下资源

    @Configuration
    public class UploadConfig implements WebMvcConfigurer{
      @Value("${file.staticAccessPath}")
      private String staticAccessPath;
      @Value("${file.uploadFolder}")
      private String uploadFolder;
      @Override
      public void addResourceHandlers(ResourceHandlerRegistry registry) {
          registry.addResourceHandler(staticAccessPath).addResourceLocations("file:" + uploadFolder);
      }
    }
    

6.3.2 跨域处理

  • 添加CrossOrigin注释
    @RestController
    @CrossOrigin(origins = "*")
    public class PlayersController {
    }
    

6.3.3 数据处理

  • 以处理选手信息数据为例
    public JSONArray showplayers(){
          //获取按国籍分类的选手数组
          JSONArray jsonArray = JSONArray.parseArray(getJsonStr("src/datas/athletes.json"));
          JSONArray allplayers = new JSONArray();
          for(int i = 0; i < 5; i++){
              //获取第i个国家的信息
              JSONObject jsonObject = jsonArray.getJSONObject(i);
              //获取国籍信息
              String country = jsonObject.getString("CountryName");
              String countryImagePath = "http://120.26.225.240:8089/uploadimg/" + country + ".png";
              //获取多位选手信息
              JSONArray Participations = jsonObject.getJSONArray("Participations");
              for(int j = 0;j < Participations.size(); j++){
                  JSONObject player = new JSONObject();
                  JSONObject athlete = Participations.getJSONObject(j);
                  String firstName = athlete.getString("PreferredFirstName");
                  String lastName = athlete.getString("PreferredLastName");
                  String dob = athlete.getString("DOB").substring(0,10);
                  String gender = (athlete.getInteger("Gender") == 0 ? "Male" : "Female");
                  String sports =athlete.getString("Sports");
                  String  photoPath = "http://120.26.225.240:8089/uploadimg/" + athlete.getString("ScoreboardPhotoId") + ".png";
                  if (athlete.getString("ScoreboardPhotoId") == null){
                      photoPath = null;
                  }
                  //获取每位选手所需传输数据
                  player.put("Country",country);
                  player.put("FirstName",firstName);
                  player.put("LastName",lastName);
                  player.put("Gender",gender);
                  player.put("DOB",dob);
                  player.put("Sports",sports);
                  player.put("PhotoPath",photoPath);
                  player.put("CountryImagePath",countryImagePath);
                  allplayers.add(player);
              }
          }
          return allplayers;
      }
    

7. 心路历程和收获

222100401丁念:在本次作业中,我负责后端与服务器部署部分,由于之前没有接触过后端开发,在本次作业发布之前就抓紧去学习了SpringBoot相关知识,尽管如此,此次作业还是困难重重,配置问题、跨域问题、部署问题等等,都使本就不富裕的作业完成时间变得更加紧巴巴。好在,最后也算顺利地交上了一个满意的答卷。此次作业,丰富了我的实践开发经验,不仅让我接触学习了SpringBoot等知识,更让我明白在团队写作中,沟通的重要性。尽管此次我与我的队友是舍友,沟通非常便携,充分,但意外还会发生,我们仍然还是出现了因为信息不一致而导致的返工现象。在此次作业中,我收获良多,我想这次经历也为我在后续团队作业中打下一个很好的基础。
222100305庞财莹:在本次作业中,我负责前端内容的编写。我使用了一些提高开发效率的工具,比如使用element-ui编写页面,可以让自适应更好且避免了写原生内容的繁杂,抽离可复用的内容,提高代码复用率和可维护性。在本次结对作业中,我意识到了有一些内容要提前沟通完成,比如接口的数据结构等等,这样的提高两个人的开发效率,更快更好地实现页面功能。

8.评价队友

222100401丁念:在本次作业中,队友有着丰富的前端开发经历,给我安全感满满!并且,能在我后端学习时,非常明确的告诉我前端需要什么,我需要完成哪些事,这大大提高了我的学习与编码效率。队友的工作步调与我非常一致,我们合作的非常舒服,希望下次还能与队友共同合作。
222100305庞财莹:队友虽然没有开发过后端的内容,但是学习能力非常强,解决问题也很积极迅速,可以很快的一起寻找问题的出处,共同解决问题。让我们的开发非常顺利的进行,非常愉快地合作写完了此次作业。

...全文
46 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

122

社区成员

发帖
与我相关
我的任务
社区描述
FZU-SE
软件工程 高校
社区管理员
  • LinQF39
  • 助教-吴可仪
  • 一杯时间
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧