团队作业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逻辑较为复杂,精灵对战动画效果实现不佳,需进一步完善。
连婉玲:大家首次合作,配合得尚且不佳,后续需要更多的沟通与交流,以提升之后的任务完成效率。
黄智权:精灵类中关于精灵升级策略、属性提升策略、精灵类继承问题有待解决。
欧智昕:时间管理尤为重要,根据各成员的分工与实际能力,客观分析预计完成时间,合理评估各自的执行力与任务完成效率,以便对各模块进行灵活的人员调整。
刘淑婷:敏捷开发需要定期集结各位成员进行项目进程的商讨,各抒己见、集思广益,充分有效的沟通十分重要,各成员的分工需明确且清晰,讨论确定当日计划,并动手付诸实践。
冯波昌:只要思想不滑坡,办法总比困难多。要沉得住气,静得下心去思考问题,一个人的半途而废会严重影响整个团队的进程。
何婉莹:在代码实现的过程中,难免会遇到自己难以解决的实质性难题,寻求队友的帮助不失为一种好方法。相互交换思考过程,有助于彼此逻辑能力的提升。