前言
前言
服务端GitHub地址:https://github.com/griii/Hak-Server
小游戏网址:https://www.guorii.cn/hak
一、引入lombok
pom依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
注意,lombok引入的版本过低会和maven打包版本不匹配出现无法打包的问题:
将lombok改为新版即可解决。
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
idea配置
在Setting-Plugins中下载安装lombok相关支持即可
@Date、@Slf4j使用
在entity类上添加
@Date注解,lombok将自动生成getter、setter方法
@Data
public abstract class PlayerPeople implements IPlayer {
private double x;
private double y;
private int uid;
private int token;
private String name;
private boolean move = false;
private double angle;
private float speed = 30;
private long time;
二、AOP配置
pom文件引入AOP
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
配置AOP类
先创建LogInstructAspect类,这里的@Slf4j注解由lombok提供,将会自动注入一个log对象
@Aspect
@Slf4j
@Component
public class LogInstructAspect {
}
加入切点
@Pointcut("execution(* com.guorui.hak.entity.instruct..*.*(..))")
public void instructLog(){
}
切点匹配语法:
*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
由于需要方法参数来输出,因此使用@Around环绕切面
@Around("instructLog()")
public void aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
StringBuilder logInfor = new StringBuilder();
joinPoint.proceed();//先执行
//System.out.println("切面执行...");
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof Instruct){
Instruct ins = (Instruct)arg;
logInfor.append("用户: [" + ins.getRoomId() + ":" + ins.getUid() + "]" + "执行:" + ((Instruct) arg).getOrder());
}else if(arg instanceof PlayerPeople){
log.info(logInfor + "用户状态:{}",arg.toString());
}
}
}
但是这里出现了一个很严重的问题:由于之前netty的handler并没有作为Spring的组件,因此后续的MsgManage以及Strategy都无法正常调用。
之后将整个netty注册流程均以组件的形式运行,并且把策略的初始化方式交由Spring的IOC实现,通过注入ApllicationContext获取。
@Component
public class MsgManage{
@Autowired
private ApplicationContext applicationContext;
private static final String STRATEGY = "Strategy";
public void addInstruct(Instruct instruct) throws NoSuchMethodException {
//instructs.offer(instruct);
//对指令解析order后调用对应Player的对应逻辑函数
PlayerPeople player = Room.players.get(instruct.getUid()+"");
if (player == null){
System.out.println("没有找到该用户!");
return;
}
String order = instruct.getOrder();
try {
//通过Spring的BeanFactory获取
String strategyName = order + STRATEGY;
System.out.println(applicationContext.getBean("moveStrategy"));
InstructStrategy is = (InstructStrategy) applicationContext.getBean(order + STRATEGY);
//InstructStrategy is = (InstructStrategy)(Class.forName("com.guorui.hak.entity.instruct.strategy.impl." + strategyName).getConstructor().newInstance());
is.instructOrder(instruct,player);
//player.getClass().getMethod(order, Instruct.class).invoke(player,instruct);
}catch (Exception e){
e.printStackTrace();
System.out.println("指令调用失败!该用户没有使用该指令的权限!");
}
}
}
原先的策略的调用是通过反射获取构造器每次新建一个对象来调用,一方面客户端需要注重于构造策略,另一方面每次都需要构造出一个新的实例(原先应当用单例模式更好一些),但使用Spring就摈弃了这些劣势,这个改动非常好的体现了Spring的IOC的好处。
测试后切面日志成功执行
但显然,这里输出的信息太过繁琐了,主要是用户状态过多,先重写用户的toString方法
@Override
public String toString() {
return "[" +
String.format("%5.2f",x) + "," +
String.format("%5.2f",y) + "]" + "{" +
(move?"T":"F") + "," +
String.format("a:%3.1f",angle) +
",s:" + speed +
'}';
}
这样就简洁多了。
另外,没有配置输出到日志文件中持久化,因此还需要配置logback
配置logback的xml文件
在resource目录下创建logback-spring.xml文件
先配置一个简单的
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/HakLogHome" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level- %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/HakLog%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1000MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
成功输出在txt文件中了,但是SpringBoot启动时的日志也输出在文件中了,应当配置使得玩家的操作日志输出与其他日志隔离,仅输出在一个文件中。
<appender name="PLAYER_INSTRUCT" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/HakPlayerInstructLog%d{yyyy-MM-dd}.log</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} - %msg%n</pattern>
</encoder>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1000MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 默认控制台日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!-- 玩家指令专属的日志输出 -->
<logger name="com.guorui.hak.config.aspectConfig.LogInstructAspect" level="INFO" additivity="false">
<appender-ref ref="PLAYER_INSTRUCT" />
</logger>
成功输出了简洁明了并且与其他日志隔离的日志文件,并且控制台不再输出该日志。