丰农汇——CodeArts团队实战总结

丰农汇 2024-10-31 22:18:30

丰农汇——CodeArts团队实战总结

这个作业属于哪个课程FZU_SE_teacherW_4
这个作业要求在哪里软工实践——CodeArts团队实战总结
团队名称丰农汇
这个作业的目标开发一个奥运比赛竞猜平台,实现GUI以及基础,附加功能
其他参考文献构建之法

目录

  • 丰农汇——CodeArts团队实战总结
  • 1 项目地址
  • 2 CodeArt 提交日志
  • 3 程序运行环境
  • 3.1 前端部署
  • 3.2 后端部署
  • 4 需求分析
  • 4.1 NABCD模型
  • 4.2 功能描述
  • 4.2.1 用户登录界面
  • 4.2.2 用户基本信息
  • 4.2.2 竞猜功能
  • 4.3 数据库设计
  • 5 功能实现思路
  • 5.1 技术栈
  • 5.1.1 前端技术栈
  • 5.1.2 后端技术栈
  • 5.2 接口设计
  • 5.3 测试方案
  • 5.4 思考问题与解答
  • 5.4.1 用户如何快速知道有竞猜功能并参与竞猜?
  • 5.4.2 如何告知用户竞猜结果?
  • 5.4.3 如何保证一人一号?
  • 6 运行截图说明
  • 6.1 登录注册
  • 6.2 客户端
  • 7 分工和贡献度说明
  • 8 困难与解决方法
  • 9 PSP表格


1 项目地址

截图信息(部分)

img

img

次数统计

学号提交次数
2222003025
2222003016
2222003034
2222004267
2222004174
2221001104
2222004275
2222003323

3 程序运行环境

3.1 前端部署

  • 我们的前端项目使用Vue.js + Vite开发,最后使用Vite打包构建

  • Vite构建项目命令 pnpm vite build

  • 构建好项目之后再把项目放到云服务上的Nginx服务器中

  • 我们的前端项目最后部署在 http://8.130.92.86/

3.2 后端部署

  • 首先在云服务器上安装好Mysql,并且开放3306端口
  • 在SpringBoot项目中连接到云服务器上的Mysql数据库
  • 把SpringBoot项目打成jar包
  • 再把jar包部署到云服务器上
  • 云服务上的jdk版本为jdk1.8

4 需求分析

4.1 NABCD模型

N(Need,需求)

体育迷对于参与奥运赛事的热情和兴趣日益增长,他们渴望能够通过一种互动性强、趣味性高的方式参与到奥运会中。用户需要一个平台,可以让他们预测比赛结果,增加观看比赛的参与感和紧张感。

A(Approach,做法)

开发一个在线竞猜平台,用户可以在上面选择不同的比赛进行预测和下注。
协同使用CodeArts进行团队开发。
前端使用ES6Vue 3PiniaViteAxiosElement-Plus
后端使用SpringbootMybatisPlusMySQLRedis

B (Benefit,好处)

用户可以通过竞猜平台增加观看比赛的乐趣,提高参与度和兴奋感。
平台无需下载,占用内存空间小,运行速度快。
页面简洁,操作简单,对于初次接触的用户十分友好。

C (Competitors,竞争)

我们的主要竞争对手为其他小组的项目。

我方优势

模块清晰,操作简单,界面美观,用户体验好。
无需下载安装,使用方便。

我方劣势

数据主要从官方网站爬取,可能存在数据较少等问题。
我们的平台结构简单,功能较少。

D(Delivery,推广)

可以把平台介绍给身边的同学和朋友等,反馈不错的话再让他们的家人朋友分享给身边的人。
通过微博,b站等社交平台推广,同时给用户提供反馈方式,积极维护提问和评论区。
可以与体育论坛合作,吸引体育迷关注和使用我们的竞猜平台。

4.2 功能描述

功能模块图

img

4.2.1 用户登录界面

用户首先进行注册,设置账号和密码,账号为手机号码。

4.2.2 用户基本信息

  • 显示用户的基本信息(姓名,手机号,地址)
  • 对密码进行修改

4.2.2 竞猜功能

  • 用户可以通过筛选器,选择比赛日期,包括单场的比赛项目、开始时间、截止时间、选手A、选手B的信息

  • 点击竞猜按键可以选择选手进行投票

  • 当竞猜项目结束的时候,会弹窗提示用户已经完成的竞猜项目

4.3 数据库设计

我们设计了4张数据库表,分别是用户表user、竞猜表guess、竞猜投票记录表vote_record、队伍表team,并且我们将数据库部署在服务器上

竞猜表guess:

img

队伍表team:

img

用户表user:

img

竞猜投票记录表vote_record:

img

5 功能实现思路

5.1 技术栈

5.1.1 前端技术栈

  • Vue.js

  • Element Plus

  • pnpm包管理

  • Pinia持久化存储

  • Eslint + prettier 代码规范校验

  • husky git提交验证

    5.1.2 后端技术栈

  • SpringBoot框架

  • mysql数据库

  • mybatis持久层框架

  • Jwt 生成和校验Token

5.2 接口设计

共同讨论了接口设计,通过Apifox进行共同设计接口和调试(接口设计地址

登录接口:

为了保证网站资源的安全性,我们需要进行一个简单的认证处理,如果登录成功,后端会返回一串jwt的token串,要求前端每次在请求头携带上token串,后端利用进行拦截器进行请求拦截,解析令牌

  • JwtUtil (Jwt token生成和解析类)

    public class JwtUtil {
        /**
         * 生成jwt
         * 使用Hs256算法, 私匙使用固定秘钥
         *
         * @param secretKey jwt秘钥
         * @param ttlMillis jwt过期时间(毫秒)
         * @param claims    设置的信息
         * @return
         */
        public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
            // 指定签名的时候使用的签名算法,也就是header那部分
            SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    
            // 生成JWT的时间
            long expMillis = System.currentTimeMillis() + ttlMillis;
            Date exp = new Date(expMillis);
    
            // 设置jwt的body
            JwtBuilder builder = Jwts.builder()
                    // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                    .setClaims(claims)
                    // 设置签名使用的签名算法和签名使用的秘钥
                    .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                    // 设置过期时间
                    .setExpiration(exp);
    
            return builder.compact();
        }
    
        /**
         * Token解密
         *
         * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
         * @param token     加密后的token
         * @return
         */
        public static Claims parseJWT(String secretKey, String token) {
            // 得到DefaultJwtParser
            Claims claims = Jwts.parser()
                    // 设置签名的秘钥
                    .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                    // 设置需要解析的jwt
                    .parseClaimsJws(token).getBody();
            return claims;
        }
    
    }
    
  • 拦截器方法实现

        /**
         * 校验jwt
         *
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            //判断当前拦截到的是Controller的方法还是其他资源
            if (!(handler instanceof HandlerMethod)) {
                //当前拦截到的不是动态方法,直接放行
                return true;
            }
    
            //1、从请求头中获取令牌
            String token = request.getHeader(userTokenName);
    
            //2、校验令牌
            try {
                log.info("jwt校验:{}", token);
                Claims claims = JwtUtil.parseJWT(userSecretKey, token);
                Long userId = Long.valueOf(claims.get("userId").toString());
                UserHolder.saveUserId(userId);
                log.info("当前用户id:", userId);
                //3、通过,放行
                return true;
    
            } catch (Exception ex) {
                //4、不通过,响应401状态码
                response.setStatus(401);
                return false;
            }
        }
    
  • 业务层代码(service),我们会在jwt中存放用户id,但我们不会存放敏感信息如密码、电话等(安全性考虑)

        @Override
        public Result<UserLoginVo> login(UserParamsDto userParamsDto) {
    
            User user = userMapper.getUser(userParamsDto);
            if(user == null) {
                return Result.error("用户名或密码输入错误");
            }
    
            //登录成功后,生成jwt令牌
            Map<String, Object> claims = new HashMap<>();
            claims.put("userId", user.getId());
            String token = JwtUtil.createJWT(
                    userSecretKey,
                    userTtl,
                    claims);
    
            UserLoginVo userLoginVo = UserLoginVo.builder()
                    .token(token)
                    .build();
    
            return Result.success(userLoginVo);
        }
    
  • 注册接口:

    前端发送请求到后端,后端获取密码、手机号码。接着后端判断手机号码是否唯一,如果唯一,那么成功注册,否则注册失败,并提示用户相应的信息

    • 业务层代码

         /**
           * 用户注册
           * @param userParamsDto
           * @return
           */
          @Override
          public Result register(UserParamsDto userParamsDto) {
              User userByPhone = userMapper.getUserByPhone(userParamsDto);
              if(userByPhone != null) {
                  return Result.error("用户已经存在,无需注册");
              }
      
              User user = new User();
              BeanUtil.copyProperties(userParamsDto, user);
              user.setCreateTime(LocalDateTime.now());
              user.setUpdateTime(LocalDateTime.now());
              userMapper.insert(user);
      
              return Result.success("注册成功");
          }
      
  • 修改用户信息接口:(略:不展示)

  • 修改用户密码接口:(略:不展示)

  • 投票接口:

    前端传入竞猜比赛id和队伍id,后端接收后写入竞猜记录表,前端为啥不需要传入用户id呢,因为登录后我们将用户id存入token中传回前端,前端每次请求后端接口都会携带token,我们通过拦截器拦截请求,并从请求中获取token解析出用户id存入UserHolder中,UserHolder是我们自己编写的一个工具类,底层是ThreadLocal,所以当业务层需要用户id时直接从UserHolder中获取即可

    • UserHolder工具类

      public class UserHolder {
      
          private static final ThreadLocal<Long> tl = new ThreadLocal<>();
      
          public static void saveUserId(Long id){
              tl.set(id);
          }
      
          public static Long getUserId(){
              return tl.get();
          }
      
          public static void removeUserId(){
              tl.remove();
          }
      }
      
    • 接口层

      @PostMapping("/vote")
      public Result vote(@RequestBody Map map) {
          return guessService.vote(map);
      }
      
    • 业务层(service)

       @Override
          public Result vote(Map map) {
              Long guessId = Long.valueOf(map.get("guessId").toString());
              Long userId = UserHolder.getUserId();
      
              VoteRecord voteRecord = voteRecordMapper.selectByGuessIdAndUserId(guessId, userId);
              if(voteRecord != null) {
                  return Result.error("你已经在本次比赛提交过竞猜,不能重复提交");
              }
              voteRecord = new VoteRecord();
              voteRecord.setGuessId(guessId);
              voteRecord.setTeamId(Long.valueOf(map.get("teamId").toString()));
              voteRecord.setUserId(userId);
      
              voteRecordMapper.insert(voteRecord);
              return Result.success("投票成功");
          }
      
  • 获取竞猜列表接口:

    • 接口层:

      @GetMapping("/list/{date}")
      public Result<List<GuessVo>> getGuessList(@PathVariable("date") String date) {
          return guessService.guessList(date);
      }
      
    • 业务层:将前端通过路径传入的日期参数(如,”0724“)解析出来查询数据库对应的竞猜列表,还需返回每支队伍的名字和已获得的票数

          /**
           * 获取竞猜列表
           * @param date 0727
           */
          @Override
          public Result<List<GuessVo>> guessList(String date) {
              //处理日期
              String month = date.substring(0, 2);
              String day = date.substring(2);
              LocalDate localDate =
                      LocalDate.of(2024, Integer.parseInt(month), Integer.parseInt(day));
              LocalDateTime beginOfDay = LocalDateTime.of(localDate, LocalTime.MIN);
              LocalDateTime endOfDay = LocalDateTime.of(localDate, LocalTime.MAX);
      
              //查询
              List<GuessVo> guessList = guessMapper.selectList(beginOfDay, endOfDay);
      
      
              List<Long> teamAIds = CollUtil.getFieldValues(guessList, "teamaId", Long.class);
              List<Long> teamBIds = CollUtil.getFieldValues(guessList, "teambId", Long.class);
      
      
              List<Team> teamAList = teamMapper.selectByIds(teamAIds);
              List<Team> teamBList = teamMapper.selectByIds(teamBIds);
      
      
      
              Map<Long, Team> mapA = CollUtil.fieldValueMap(teamAList, "id");
              Map<Long, Team> mapB = CollUtil.fieldValueMap(teamBList, "id");
      
              guessList.forEach(
                  item -> {
                      item.setTeamaName(mapA.get(item.getTeamaId()).getRegion());
                      item.setTeambName(mapB.get(item.getTeambId()).getRegion());
                      item.setWinnerTeamName(mapA.get(item.getTeamaId()).getRegion());
      
                      Integer voteA = voteRecordMapper.selectCountByTeamId(item.getTeamaId(), item.getId());
                      Integer voteB = voteRecordMapper.selectCountByTeamId(item.getTeambId(), item.getId());
      
                      item.setTeamaVotes(voteA);
                      item.setTeambVotes(voteB);
                  }
              );
      
              return Result.success(guessList);
          }
      
  • 获取我参与过的竞猜列表接口:

    • 接口层

      @GetMapping("/myList")
      public Result<List<MyGuessVo>> getMyVoteList() {
          return guessService.getMyVoteList();
      }
      
    • 业务层:先从竞猜记录表中获取该用户投过的记录,利用获取的竞猜id返回竞猜赛事基本信息,并且通过判断比赛结束决定是否返回胜者消息和用户是否猜对的信息,至于为何前端不需要传入userId在前面投票接口已经解释过

          @Override
          public Result<List<MyGuessVo>> getMyVoteList() {
      
              Long userId = UserHolder.getUserId();
              List<VoteRecord> list = voteRecordMapper.selectByUserId(userId);
      
              List<MyGuessVo> ans = new ArrayList<>(list.size());
      
              for(int i = 0; i < list.size(); ++i) {
                  MyGuessVo myGuessVo = new MyGuessVo();
                  VoteRecord voteRecord = list.get(i);
                  Long guessId = voteRecord.getGuessId();
      
                  GuessVo guessVo = guessMapper.selectById(guessId);
                  Long teamId = voteRecord.getTeamId();
                  Team team = teamMapper.selectById(teamId);
                  myGuessVo.setVoteTeamName(team.getRegion());
                  myGuessVo.setEndTime(guessVo.getEndTime());
                  myGuessVo.setGameName(guessVo.getGameName());
      
                  if(myGuessVo.getEndTime().isBefore(LocalDateTime.now())) {
                      myGuessVo.setIsCorrect(guessVo.getWinnerTeam().equals(teamId));
                      myGuessVo.setWinnerTeamName(teamMapper.selectById(guessVo.getWinnerTeam()).getRegion());
                  }
                  ans.add(myGuessVo);
              }
      
              return Result.success(ans);
          }
      

5.3 测试方案

后端人员在编写接口时,运用http client(idea中下载的插件,下载可以直接在idea中完成接口的测试)

img

以用户测试登录接口为例 展现测试代码和测试结果

img

img

该方法十分方便可以直接在idea进行接口测试,对比postman是个更佳选择

5.4 思考问题与解答

5.4.1 用户如何快速知道有竞猜功能并参与竞猜?

用户登录进去后首页就是竞猜功能,直白明了。

5.4.2 如何告知用户竞猜结果?

在竞猜结束后会在,用户界面登录后弹窗通知竞赛结果。

5.4.3 如何保证一人一号?

使用手机号进行登录,确保一人一号

6 运行截图说明

6.1 登录注册

登录,通过账号密码登录

img

注册,通过手机号(用户名)注册,确保一人一号

img

6.2 客户端

竞猜页面,可以看到待竞猜的各种比赛

img

竞猜过程,点击投票按钮可以选择竞猜的队伍,竞猜结束切换到“我的竞猜结果”会有弹窗提示竞猜结果

img

个人页面,可以修改姓名地址等个人信息,还可以修改密码

img


img

7 分工和贡献度说明

学号分工贡献比例
222200302解决小组内出现的小问题,提供接口设计和数据库设计,搭建项目结构(包括依赖、配置)完成本项目业务类编写和配置类编写18.5
222200301完成部分后端数据持久层接口编写9.5
222200303完成部分后端数据持久层接口编写和部分VO类的设计10
222200426完成实体类和VO类的设计,编写博客11.5
222200417完成DTO类的设计和工具类的编写9.5
222100110解决小组内出现的小问题,参与接口设计和数据库设计,搭建前端项目结构,完成前端项目页面渲染,前后端的对接,接口封装18
222200427实现了竞猜界面,对小组进行分工,编写博客12.5
222200332实现了登陆界面和页面弹窗10.5

分为前端和后端小组,分别有110和302同学担任组长,他们两位对此次作业贡献度较大,解决了组员的很多问题

8 困难与解决方法

学号遇到的困难解决方法
222200427郭翔宇1. 使用 git push 时,出现了报错无法提交
2.时间紧迫,且是第一次经历一天时间做个小项目,组内同学掌握的技术水平差别较大,很慌
1. 发现是ESLint 被集成到了 Git 钩子(hooks)中,在提交时候会判断js的语法正确性,查看错误日志,重新修改不符合规范代码,使得符合规范
2.幸好两个小组长开发经验比较丰富,且善于指导组内编程较不熟练的同学,还是能及时完成任务
222100110吴宇航1.我在前端项目中向后端发送请求时出现了跨域问题
2.后端的多个接口在一些输入下都会返回500状态码(服务器内部异常),导致前端无法正确获取数据。
3.使用vite构建项目之后再部署到Nginx服务上之后有一个问题,刷新页面,会提示404(页面没有找到),这种情况应该是页面刷新没有找到根路径导致的,或者当前的路径确实不存在(由于这是vue的路由,实际并没有这个路径)
1.与后端及时沟通,让后端运行请求的跨域访问
2.及时联系后端,并且对这些接口进行联调
3.vite打包项目时base路径要设置为 / ,不要设置为 ./ (相对路径),同时Nginx服务器的配置也要加上一行 try_files $uri $uri/ /index.html; (没有找到文件或目录时再尝试访问index.html),通过这两个配置就解决了刷新页面提示404的问题了。
222200302方金田1.如何保证系统安全性(认证安全)
2.后端传给前端的日期格式前端无法正常解析
1.利用Jwt生成令牌,用户访问需要携带令牌访问(JWT使用数字签名或加密算法对数据进行验证和防篡改,确保信息在传输过程中的安全性和完整性‌,并且使用JSON格式,易于解析和使用,这使得其在网络传输中更加高效‌)
2. @JsonFormat注解:在传入前端实体类的时间字段上加上注解
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
222200417林宇涛对java语言不熟悉使用AI辅助工具,用gpt生成代码
222200303石江强使用 git clone和push时提示没有权限直接在华为云代码仓库上传文件
222200426徐俊杰要准确理解每个类的功能和它们之间的关系比较困难,特别是在没有详细需求文档的情况下。与其他人尤其的设计者交流,理解设计
222200301王珺琨项目使用的技术栈与之前接触的不同,完成较为吃力通过阅读官方文档、教程和示例代码,向团队成员请教,进行实际编码练习慢慢的渐入佳境
222200332康思梦登录页面背景图片无法显示调整图像大小

9 PSP表格

  • 222200427郭翔宇

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

    PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
    Planning计划6070
    • Estimate• 估计这个任务需要多少时间6070
    Development开发750800
    • Analysis• 需求分析 (包括学习新技术)150160
    • Design Spec• 生成设计文档5055
    • Design Review• 设计复审5055
    • Coding Standard• 代码规范 (为目前的开发制定合适的规范)2020
    • Design• 具体设计8085
    • Coding• 具体编码250270
    • Code Review• 代码复审5060
    • Test• 测试(自我测试,修改代码,提交修改)10095
    Reporting报告9096
    • Test Report• 测试报告3035
    • Size Measurement• 计算工作量3032
    • Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3029
    合计900966
  • 222200302方金田

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

    PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
    Planning计划3035
    • Estimate• 估计这个任务需要多少时间3035
    Development开发370405
    • Analysis• 需求分析 (包括学习新技术)6065
    • Design Spec• 生成设计文档3035
    • Design Review• 设计复审2025
    • Coding Standard• 代码规范 (为目前的开发制定合适的规范)1510
    • Design• 具体设计5055
    • Coding• 具体编码120140
    • Code Review• 代码复审3025
    • Test• 测试(自我测试,修改代码,提交修改)4550
    Reporting报告2530
    • Test Report• 测试报告1015
    • Size Measurement• 计算工作量55
    • Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划1010
    合计425470
  • 222200426徐俊杰

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

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

    PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
    Planning计划3040
    • Estimate• 估计这个任务需要多少时间3040
    Development开发375403
    • Analysis• 需求分析 (包括学习新技术)5055
    • Design Spec• 生成设计文档55
    • Design Review• 设计复审108
    • Coding Standard• 代码规范 (为目前的开发制定合适的规范)1012
    • Design• 具体设计200210
    • Coding• 具体编码5060
    • Code Review• 代码复审2528
    • Test• 测试(自我测试,修改代码,提交修改)2525
    Reporting报告1520
    • Test Repor• 测试报告58
    • Size Measurement• 计算工作量55
    • Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划57
    合计420463
  • 222200303石江强

    PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
    Planning计划2020
    • Estimate• 估计这个任务需要多少时间2020
    Development开发650630
    • Analysis• 需求分析 (包括学习新技术)120100
    • Design Spec• 生成设计文档2020
    • Design Review• 设计复审2020
    • Coding Standard• 代码规范 (为目前的开发制定合适的规范)3020
    • Design• 具体设计6050
    • Coding• 具体编码350370
    • Code Review• 代码复审2020
    • Test• 测试(自我测试,修改代码,提交修改)3030
    Reporting报告6070
    • Test Repor• 测试报告3030
    • Size Measurement• 计算工作量1020
    • Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划2020
    合计730720
...全文
140 回复 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

239

社区成员

发帖
与我相关
我的任务
社区管理员
  • FZU_SE_teacherW
  • 助教赖晋松
  • D's Honey
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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