团队作业4——项目冲刺【Alpha阶段】第二次 Scrum Meeting
【Alpha阶段】第二次Scrum Meeting
| 这个作业属于哪个课程 | 软件工程 | 
|---|---|
| 这个作业要求在哪里 | 作业要求 | 
| 这个作业的目标 | 站立式会议+项目燃尽图+成员代码/文档签入记录+每人每日总结 | 
团队队员
| 学号 | 姓名 | 
|---|---|
| 3119005415 | 黄奕威 | 
| 3219005447 | 连婉玲 | 
| 3119005417 | 黄智权 | 
| 3119005431 | 欧智昕 | 
| 3219005448 | 刘淑婷 | 
| 3119005410 | 冯波昌 | 
| 3219005445 | 何婉莹 | 
一、例会图片

二、Burndown Chart

三、代码/文档签入记录




四、项目进度
| 队员 | 昨日已完成任务 | 任务概述 | 今日待完成任务 | 
|---|---|---|---|
| 黄奕威 | 时间安排表 · Issue #1、总结 · Issue #2 | 完成项目时间安排表;完成主界面、用户界面、关卡界面的UI搭建 | 动画完善、UI完善、后台通讯尝试 | 
| 连婉玲 | 总结 · Issue #6 | 完成了用户类的初始设计 | 完善用户类 | 
| 黄智权 | 总结 · Issue #7 | 完成精灵类的总体设计 | 精灵升级策略、精灵技能策略实现;数据库精灵表、技能表的初始化 | 
| 欧智昕 | 总结· Issue #5 | 完成了精灵类的基本设计 | 继续根据需求完善精灵类 | 
| 刘淑婷 | 总结 · Issue #3 | 完成了后台框架的搭建 | 尝试与前端进行接口对接 | 
| 冯波昌 | 总结 · Issue #4 | 完成道具类的初始设计 | 继续完善道具类 | 
| 何婉莹 | 总结 · Issue #8 | 查看接口文档,初步规划接口 | 尝试与前端进行接口对接 | 
五、最新模块代码
日志信息配置(点击查看)
<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="30">
    <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
    <!--变量配置-->
    <Properties>
        <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
        <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
<!--        <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS}{bright,green} [%thread] %-5level %logger{36}   %msg%n" />-->
        <property name="LOG_PATTERN" value="%d %highlight{%-5level}{ERROR=Bright RED, WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White} %style{[%t]}{bright,magenta} %style{%c.%M(%L)}{cyan}: %msg%n"/>
        <!-- 定义日志存储的路径 -->
        <property name="FILE_PATH" value="D:" />
        <property name="FILE_NAME" value="our_land" />
    </Properties>
    <appenders>
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
        </console>
        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
        <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
            <PatternLayout pattern="${LOG_PATTERN}"/>
        </File>
        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
        <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
        <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${LOG_PATTERN}"/>
            <Policies>
                <!--interval属性用来指定多久滚动一次,默认是1 hour-->
                <TimeBasedTriggeringPolicy interval="1"/>
                <SizeBasedTriggeringPolicy size="10MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
            <DefaultRolloverStrategy max="15"/>
        </RollingFile>
    </appenders>
    <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
    <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.mybatis" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </logger>
        <!--监控系统信息-->
        <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
        <Logger name="org.springframework" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="Filelog"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>
UI(点击查看)
// 冒险页面
export default class Adventure extends Component {
  render () {
    return (
      <div className='adventure-container flex-start-stretch-col'>
        <div className='main-header flex-end-center'>
          <Link to='/user'>
            <Block
              img={require('../../assets/images/icons/avatar.jpg').default}
              size='small'
            />
          </Link>
        </div>
        <div className='adventure-content'>
          {adventure.map(item => (
            <Block
              className="adv-item"
              img={require('../../assets/images/icons/adventure.jpg').default}
              key={item.id}
              size='big'
              text={item.name}
            ></Block>
          ))}
        </div>
        <StoryPanel />
      </div>
    )
  }
}
// 主页模块
function Home () {
  let [isClick, setClick] = useState(false)
  if (isClick) {
    return (
      <div className='home-container flex-around-center-col'>
        <div className='title'>@信安1班——红橙黄绿青蓝紫队</div>
        <LoginPanel />
      </div>
    )
  } else {
    return (
      <div className='home-container flex-around-center-col'>
        <div className='title'>@信安1班——红橙黄绿青蓝紫队</div>
        <Button onClick={() => setClick(!isClick)}>
          <a>进入游戏</a>
        </Button>
      </div>
    )
  }
}
用户类初步实现(点击查看)
public class User {
    private int id;
    private int progress; //进度
    private String user_name;
    private String password;
    private String email;
    private ArrayList<Integer> spirits_bag; //精灵背包
    private ArrayList<Integer> props_bag; //道具背包
}
项目框架搭建(点击查看)
public class WebLogAspect {
    private static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
    @Pointcut("@annotation(ruangong.our_land.aspect.WebLog)")
    public void doWebLog(){
    }
    @Before("doWebLog()")
    public void before(JoinPoint joinPoint) throws ClassNotFoundException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //注解描述信息
        String description = getAspectLogDescription(joinPoint);
        logger.info("====================start=================");
        logger.info("URL         :{}",request.getRequestURL().toString());
        logger.info("Description             :{}",description);
        logger.info("HTTP Method    :{}",request.getMethod());
        logger.info("Class Method  :{}.{}",joinPoint.getSignature().getDeclaringTypeName(),joinPoint.getSignature().getName());
        logger.info("IP    :{}",request.getRemoteAddr());
        logger.info("Request Arags:    {}",new Gson().toJson(joinPoint.getArgs()));
    }
    @After("doWebLog()")
    public void after(){
        logger.info("===============end====================");
    }
    @Around("doWebLog()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object result = proceedingJoinPoint.proceed();
        return result;
    }
    public String getAspectLogDescription(JoinPoint joinPoint) throws ClassNotFoundException {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Class<?> target = Class.forName(targetName);
        Method[] methods = target.getMethods();
        StringBuilder builder = new StringBuilder();
        for (Method method:methods){
            if (method.getName().equals(methodName)){
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length==args.length){
                    builder.append(method.getAnnotation(WebLog.class).message());
                    break;
                }
            }
        }
        return builder.toString();
    }
    @AfterReturning(pointcut = "doWebLog()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        logger.info("Response Args:      {}",new Gson().toJson(result));
    }
    @AfterThrowing(pointcut = "doWebLog()",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint,Exception exception){
        logger.info("Exception e:      {}",exception.getMessage());
    }
}
精灵类实现(点击查看)
package ruangong.our_land.model.spirit;
import lombok.Data;
import lombok.Getter;
import org.springframework.lang.NonNull;
import ruangong.our_land.model.helper.ObjectHelper;
import ruangong.our_land.model.spirit.monster.Monster;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.util.HashMap;
import java.util.Map;
/**
 * 精灵类,包括boss、用户初始和野怪
 *
 * @author HuangZhiquan
 * @author Wizardk
 * @Description 精灵类
 * @date Created in 2021-11-02 22:57
 * @Modified By
 */
@Data
public abstract class Spirit {
    /**
     * 精灵的最高等级
     */
    @Max(value = 100, message = "等级最高为100")
    public static final int MAX_LEVEL = 100;
    /**
     * 精灵名
     */
    @NonNull
    @Getter
    private final String name;
    /**
     * 精灵的id
     */
    @NonNull
    @Getter
    private final String id;
    /**
     * 精灵是否稀有(是:1,否:0)
     * 精灵稀有与否,关系到精灵捕捉时的成功率
     */
    @NonNull
    @Getter
    private int isRare;
    /**
     * 等级
     */
    @Getter
    @Min(value = 1, message = "等级最低为1")
    private int level;
    /**
     * 血量
     */
    @Getter
    @Min(value = 0)
    private int blood;
    /**
     * 攻击力
     */
    @Getter
    @Min(value = 0)
    private static int attack;
    /**
     * 防御力
     */
    @Getter
    @Min(value = 0)
    private static int defence;
    /**
     * 速度
     */
    @Getter
    @Min(value = 0)
    private static int speed;
    /**
     * 精灵类型(初始、野怪或boss)
     */
    private String type;
    /**
     * 精灵属性(水、火、草)
     */
    private String nature;
    /**
     * 存放精灵的4个技能
     */
    @NonNull
    private Map<String, Skill> skillMap;
    //构造方法
    public Spirit(String name, String id, int level, int blood, int attack,
                  int defense, int speed,String type,String nature,int isRare) {
        this.name = name;
        this.id = id;
        this.level = level;
        this.blood = blood;
        this.attack = attack;
        this.defence = defense;
        this.speed = speed;
        this.type = type;
        this.nature = nature;
        this.isRare = isRare;
        setSkills();
    }
    private void setSkills() {
        if (skillMap == null) {
            Skill[] skills = ObjectHelper.requireNonNull(initSkills());
            this.skillMap = new HashMap<>(4);
            for (Skill skill : skills) {
                skillMap.put(skill.name, skill);
            }
        }
    }
    /**
     * 初始化技能
     * @return 返回技能列表
     */
    protected abstract Skill[] initSkills();
    /**
     * 根据技能名获取相应技能
     *
     * @param skillName 技能名
     * @return 返回对应的技能类对象
     */
    public Skill getSkills(String skillName) {
        String skill = ObjectHelper.requireNonNull(skillName, "skillName");
        if (skillMap == null) {
            return null;
        }
        if (!skillMap.containsKey(skill)) {
            throw new IllegalArgumentException("The skill \" " + skill + " \" not found! Please recheck!");
        }
        return skillMap.get(skill);
    }
    /**
     * 升级,使level+1
     */
    protected void levelUp() {
        if (this instanceof Monster && this.level < MAX_LEVEL) {
            this.level++;
        }
    }
    public void setBlood(int blood) {
        this.blood = ObjectHelper.verifyNonZeroPositive(blood, "blood");
    }
    public void setAttack(int attack) {
        this.attack = ObjectHelper.verifyNonZeroPositive(attack, "attack");
    }
    public void setDefence(int defense) {
        this.defence = ObjectHelper.verifyNonZeroPositive(defense, "defense");
    }
    public void setSpeed(int speed) {
        this.speed = ObjectHelper.verifyNonZeroPositive(speed, "speed");
    }
    /**
     * 技能类
     */
    @Data
    public static class Skill {
        //技能基本属性
        /**
         * 技能名
         */
        @NonNull
        @Getter
        String name;
        /**
         * 技能描述
         */
        @NonNull
        @Getter
        String description;
        /**
         * 技能使用次数
         */
        @Getter
        int times = 0;
        /**
         * 技能类型(伤害型或提升型)
         * 伤害型:基础伤害值
         * 提升型:提升精灵属性(攻击、防御、速度)
         */
        @NonNull
        @Getter
        String type;
        /**
         * 技能伤害(若技能为伤害型),用于精灵对战
         */
        @Getter
        int hurt;
        //提升型技能的构造方法
        public Skill(String name, String descrp,String type) {
            this.name = ObjectHelper.requireNonNull(name, "name");
            this.description = ObjectHelper.requireNonNull(descrp, "descrp");
            this.type = ObjectHelper.requireNonNull(type, "type");
        }
        //伤害型技能的构造方法
        public Skill(String name, String descrp,String type,int hurt) {
            this.name = ObjectHelper.requireNonNull(name, "name");
            this.description = ObjectHelper.requireNonNull(descrp, "descrp");
            this.type = ObjectHelper.requireNonNull(type, "type");
            this.hurt = hurt;
        }
        /**
         * 技能效果:
         * 伤害型:更新技能伤害值(用于对战)
         * 提升型:更新精灵属性(仅限于对战)
         */
        public void skillEffect(){
            if(this.type.equals("伤害型")){
                //当前伤害值 = 伤害值 * 攻击力
                this.hurt = this.hurt * attack;
            }else if(this.type.equals("提升型")){
                //通过判断技能描述,来判断是提升攻击力、防御力还是速度
                //开始为简单起见,假设属性提升按+1的方式提升
                if(this.description.contains("攻击力")){
                    attack = attack + 1;
                }else if(this.description.contains("防御力")){
                    defence = defence + 1;
                }else if(this.description.contains("速度")){
                    speed = speed + 1;
                }
            }
        }
        public void setTimes(int times) {
            this.times = ObjectHelper.verifyNonZeroPositive(times, "times");
        }
    }
}
六、遇到的困难
- 
UI逻辑较为复杂,精灵对战动画效果实现不佳,需进一步完善。
 - 
精灵类中关于精灵升级策略、属性提升策略、精灵类继承问题有待解决。
 - 
接口逻辑还需进一步理清。
 - 
以矩阵形式随机生成野怪地图较难。
 
七、每人每日总结
黄奕威:UI逻辑较为复杂,精灵对战动画效果实现不佳,需进一步完善。
连婉玲:大家首次合作,配合得尚且不佳,后续需要更多的沟通与交流,以提升之后的任务完成效率。
黄智权:精灵类中关于精灵升级策略、属性提升策略、精灵类继承问题有待解决。
欧智昕:时间管理尤为重要,根据各成员的分工与实际能力,客观分析预计完成时间,合理评估各自的执行力与任务完成效率,以便对各模块进行灵活的人员调整。
刘淑婷:敏捷开发需要定期集结各位成员进行项目进程的商讨,各抒己见、集思广益,充分有效的沟通十分重要,各成员的分工需明确且清晰,讨论确定当日计划,并动手付诸实践。
冯波昌:只要思想不滑坡,办法总比困难多。要沉得住气,静得下心去思考问题,一个人的半途而废会严重影响整个团队的进程。
何婉莹:在代码实现的过程中,难免会遇到自己难以解决的实质性难题,寻求队友的帮助不失为一种好方法。相互交换思考过程,有助于彼此逻辑能力的提升。
                
            
        
浙公网安备 33010602011771号