1 Star 0 Fork 4

Km/legendframework-core

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
EPL-1.0

legendframework-core

legendframework-core是一款源自游戏Minecraft中服务端Bukkit的开源的超³微量级插件开发框架 (框架仅占300kb),开发者可以更加快速便捷的开发Minecraft服务端插件,只需要导入maven坐标,即可隐形式的开发插件,并不是在脚手架中开发。如果你熟悉SpringBoot与Mybatis-Plus,那么恭喜你,你可以更容易的上手,因为本框架是这两者的重制简易版,非copy,而是在根据Bukkit开发模式下进行开发出的框架, 我们可以更加便捷的生成Yaml,对接Mysql,且支持yaml与mysql重叠事务,自动注册事件与指令,与更加便捷且优雅的开发指令。简单来说,我们制作一款带有增删改查功能的插件到打包运行,仅需5分钟, legendframework-core远比你想象的更加便捷

Git地址:

https://gitee.com/LegendPlugin/legendframework-core.git

Maven 仓库:

http://www.legendrpg.top/nexus/service/rest/repository/browse/legend/com/legendframework/legendframework-core/

Maven坐标:

<project>
    ...
    <repositories>
            <!-- 先配置私服仓库地址 -->
            <repository>
                <id>legend</id>
                <url>http://www.legendrpg.top/nexus/repository/legend/</url>
            </repository>
        </repositories>
    <dependencies>
        <!-- 导入legendframework-core -->
        <dependency>
          <groupId>com.legendframework</groupId>
          <artifactId>legendframework-core</artifactId>
          <version>1.0.2</version>
        </dependency>
    </dependencies>
    ...
</project>

若需更多支持,请移步legendframework-core交流群

提供的能力

为了帮助一些新手开发者上手更容易,这里需要引入三层架构的思想:

  • Yaml配置文件与Mysql表中存储的数据 >> Entity(实体类)
  • Yaml与Mysql的增删改查操作 >> Dao数据访问层
  • 游戏插件业务逻辑 >> Service业务逻辑层
  • Cmd与Event >> Controller表示层(表示层是玩家与服务端交互的唯一途径)

关于更多三层架构的原理与架构图可以参考: 这个链接

好了,当我们大致了解了三层架构之后可以看一下legendframework-core为我们提供了哪些便捷的能力

  1. IOC核心容器 (用于存储对象的容器,实现自动注入)
  2. Dao自动建库建表
  3. Dao默认增删改查方法
  4. Dao面向对象的条件构造器
  5. 事务控制,支持Yaml与Mysql两种事务
  6. Cmd与Event的自动注册,且指令无需在plugin.yml中配置
  7. 指令定义与指令帮助文档输出

IOC核心容器

简单来说我们可以把IOC就当作一个Map<String,Object>的实例,这个Map存储了所有的可用Bean对象,可以集中存储集中获取,更好的管理我们实现业务所需要用到的各种对象

创建Bean的两种方式

@Bean注解

@Bean("id_prop") 含义为:定义一个名称为:"id_prop" 的key,value是这个方法的返回值,存储到IOC中

注意:@Bean注解必须在类上含有@Component以及其他类似的Bean构建注解的类中才能使用

@Component()
public class Utils {
    
    @Bean("id_prop")
    public Prop xxx(){
        Prop prop = new Prop();
        prop.setName("xxx");
        return prop;
    }
}

当然我们也可以在方法入参,去获取IOC容器中的对象

利用@com.legendframework.core.ioc.annotation.Resource注解,指定从IOC中需要获取的Bean名称的对象

注意 当@Bean不给定一个名称的时候,IOC会自动以这个Bean的全限定类名作为名称进行存储

@Bean
public PlayerInfo ccc(@com.legendframework.core.ioc.annotation.Resource(name = "id_prop") Prop prop){
    PlayerInfo playerInfo = new PlayerInfo();
    playerInfo.setLevel(prop.getShortcutKey());
    return playerInfo;
}
类注解实例化

我们可以通过注解@Component进行标识,框架启动后会将这个类反射实例化出一个对象,并将这个对象存储到IOC中

@Component("utils")
public class Utils {}

当然我们提供的不同的实例化类型,分别用于不同场景

  1. @Service
  2. @EntityDao
  3. @Command
  4. @Event
  5. @MainPlugin

所以后面当我们看到类上有类似这样的注解,不必疑惑,他是一个这个类被存入了IOC中,代表你可以从IOC中取出使用

获取Bean的三种方式

上面说到了IOC中Bean的创建,接下来是Bean的3种取出方式

字段自动注入

@com.legendframework.core.ioc.annotation.Resource注解,必须在类上含有@Component以及其他类似的Bean构建注解的类中才能使用

@Component
public class Utils{
    
    @Resource
    private Prop prop;
    
}
入参自动注入

方法入参中使用@com.legendframework.core.ioc.annotation.Resource注解,必须在类上含有@Component以及其他类似的Bean构建注解的类中,并且方法上有@Bean注解,才能使用

@Component
public class Utils{
    
    @Bean
    public Object getBean(@com.legendframework.core.ioc.annotation.Resource() Prop prop){
        return new String();
    }
    
}
方法取出(非自动)
public void getBean(){
    //其中plugin对象为主类对象
    com.legendframework.core.ioc.BeansFactory beansFactory = plugin.store.getBeansFactory();
    Prop bean = beansFactory.getBean(Prop.class);
}

主类

这里的主类,其实就是对应原生的Bukkit插件开发中的JavaPlugin的子类,只不过legendframework-coreJavaPlugin进行了封装,我们的主类所需要继承的父类不同而已

LegendPlugin

类全限定路径:com.legendframework.core.LegendPlugin

这是一个抽象的插件主类,它底层实现了加载时构建本框架初始化的操作,整个框架的入口在这个类

主类必须要实现的方法

新建一个插件主类,必须要实现以下三个方法,

    /**
     * 插件被启动时执行
     * 执行此方法的时候,框架已经初始化完成,所有可用Bean已经装配完毕
     */
    void start();

    /**
     * 插件被卸载时执行
     */
    void end();

    /**
     * 获取当前插件的根指令
     * 如果这个方法你返回NULL,请务必再你插件的plugin.yml中添加一个项:commands:
     * @return
     */
    String getRootCmd();

可推荐复写父类的方法
    /**
     * 当插件被载入的时调用
     * 该方法比 start() 更早执行
     * 不强制要求实现
     *
     * 框架启动前的初始化操作初始化
     * 或许你能想到实现这个方法后修改一下框架的一些参数,再让框架启动
     */
    void load() {
    }

	/**
     * 配置文件保存路径
     * 希望获取到的是类似:D:/server/plugins/ 这样的绝对目录,就是插件所在目录,当然你可以对其覆写修改
     * @return
     */
    String getSavePath();

	/**
     * 是否自动代替在plugin.yml中写入指令配置
     * @return 默认是true
     */
    default boolean isAutowiredRegister(){
        return true;
    }

    /**
     * 获取根指令配置
     * @return
     */
    com.legendframework.core.cmd.CommandRootConfig getCommandRootConfig();

com.legendframework.core.cmd.CommandRootConfig ,代替了plugin.yml中command的配置

/**
 * 指令相关配置
 *
 * 参考plugin.yml 中 commands: 配置项, 具体类见 {@link org.bukkit.command.Command}
 */
public class CommandRootConfig {

    /**
     * 根指令的介绍
     */
    protected String description;

    /**
     * 当指令管理器没找到用户输入的指令时,向用户提示该信息
     * 注意,该参数与{@link org.bukkit.command.Command#usageMessage} 不同
     */
    protected String usageMessage;

    /**
     * 使用根指令的权限
     */
    private String permission;

    /**
     * 当用户无上方权限时提示该信息
     */
    private String permissionMessage;
}

@MainPlugin注解

插件主类必须加上**@MainPlugin**注解,加入后,可以实现Bean的自动注入,也就是说可以在主类中任意使用IOC容器中的Bean

@MainPlugin
public class TestPlugin extends LegendPlugin {
    
    @Resource
    private Prop prop;

    @Override
    public void start() {
        System.out.println(prop);
    }

    @Override
    public void end() {

    }

    @Override
    public String getRootCmd() {
        return null;
    }
}

配置文件自定义父路径

框架默认的配置文件的绝对路径目录是这样获取的:

//plugin是主类对象
plugin.getDataFolder().getParentFile().getAbsolutePath();

如果你想让配置文件的根目录自定义位置,可以让插件使用者在本插件.jar内中的plugin.yml配置文件中的末尾添加一项:

#声明这个插件加载后,生成的配置文件在什么目录
configPath: "D://ddd/ddd/ddd"

实体类

这里的实体类是指,对数据库表或Yaml文件所映射出来后的一个Java类,这个类封装了我们需要存储的一系列字段

实体类相关注解

@Entity

作用域:类,用于声明这个类在Dao中的基本信息,表名,文件名,存储类型等,这里有提到YamlStorageType的存储类型,这个待会儿说。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {
    /**
     * 实体类存储类型
     * 定义后,该类只按照该值定义的类型来存储
     *
     * DataType.UNDEFINED 用于说明现目前不确定该实体类的存储类型,确切的存储类型由配置文件动态定义
     * 如果在@Entity.type()中提前定义了数据存储类型例如:DataType.MYSQL ,则该实体类会无视外部配置文件的动态配置
     * 每次始终使用实体类@Entity.type()中定义的存储方式
     * @return
     */
    DataType type() default DataType.UNDEFINED;
    /**
     * SQL表名 
     * 如果不指定,默认为本类类名驼峰转下划线,
     * 例如:UserTable -> user_table
     * @return
     */
    String sqlTableName() default "";
    /**
     * yaml文件相对路径
     * 如果 {@link Entity#yamlStorageType()} 取值为: YamlStorageType.MANY_TO_ONE
     * 则该值应该返回一个目录(文件夹)名称,如果为其他,这个值应该为一个".yml" 后缀的文件名称,但是".yml"后缀可以不用填写
     *
     * @return
     */
    String yamlFileName();
    /**
     * Yaml文件的存储类型
     *
     * 单个文件存储
     * 一个对象代表一个Yaml文件
     * ONE_TO_ONE
     *
     * 单个文件存储
     * 一个文件存储一个集合对象
     * ONE_TO_MANY
     *
     * 多个文件存储
     * 多个文件存储,每个文件存储一个对象
     * 指定目录下的所有文件,包括递归层级的文件都会读取
     * MANY_TO_ONE,
     *
     * @return
     */
    YamlStorageType yamlStorageType();
}

@Column

作用域:字段,声明这个字段是否与表进行映射以及自定义映射的名称,与yaml文件中所显示的注释信息

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    /**
     * 字段别名
     * 条件查询会取这个字段名
     */
    String value() default "";
    /**
     * 是否映射到表中
     * @return
     */
    boolean exist() default true;
    
    /**
     * 字段注释
     * value()是一个数组,所对应的就是多行注释
     */
    String[] value() default "";
    /**
     * 是否安装字段类型来输出可选值范围
     *
     * 目前仅支持两种字段类型 Boolean | Enum
     *
     * Enum 类型字段必须实现如下接口
     * @see com.legendframework.core.dao.enums.IEnum
     *
     * @return
     */
    boolean isOutPutOptionalRange() default true;
    /**
     * 排序 值越小越靠前
     *
     * 小于1的值 代表永远排在最后
     * @return
     */
    int sort() default -1;
}

生成的注释示例

#无意义主键
id: "1"
#数据类型
#可选值: UNDEFINED : 不确定的类型 | YAML : yml文件存储 | MYSQL : mysql数据库存储
dataType: "YAML"
#Mysql主机地址
sqlHost: 
#Mysql端口
sqlPort: 
#Mysql数据库名
databaseName: 
#Mysql用户名
username: 
#Mysql密码
password: 

@ToJson

作用域:字段,用于对复杂字段进行JSON序列化与反序列化的存储读取,解决了一对多表的定义

@TableId

作用域:字段,与别名定义, 并且声明这个类的ID字段,实体类必须有一个ID字段,尽管这个ID在你的业务中没有任何作用

三种Yaml文件存储方式

阅读上方注解**@Entity的源码可以得知,目前框架支持Yaml与Mysql两种存储方式,其中Mysql存储对于插件使用者(服主)有些许陌生了,本框架采用隐式配置在正常插件使用中,插件使用者基本上不会感觉到Mysql使用痕迹。然后就是使用最多的Yaml文件存储,基于Bukkit插件开发的惯例,.Yml文件是所有插件使用者(服主)们最熟悉的一个数据结构类型,为了更好的让插件使用者们能够更好的阅读我们插件生成的配置文件,针对Yaml文件存储,提供了三种用于Yaml的存储方式,这里针对 YamlStorageType 枚举类所对应的实际情况说明一下这三种Yaml存储方式**:

  1. YamlStorageType.ONE_TO_ONE

    一个配置文件对应一个实体类对象,多用于存储插件配置信息,示例:

    id: "1"
    levelConfig: 30
    logConfig: DEBUG
    
  2. YamlStorageType.ONE_TO_MANY

    一个配置文件对应多个实体类对象,多用于存储静态化的多行信息,例如,插件道具模板列表,技能列表等,示例:

    1:
      id: "1"
      skillName: "迅捷"
      skillMp: 30
    2:
      id: "2"
      skillName: "狂暴"
      skillMp: 60
    3:
      id: "3"
      skillName: "治疗"
      skillMp: 90
    
  3. YamlStorageType.MANY_TO_ONE

    一个文件目录中的多个配置文件,每个配置文件对应一个实体类对象,一个文件目录就是一个实体类对象集合,多用于玩家动态的数据存储,以及可以实现YamlStorageType.ONE_TO_MANY能实现的所有功能,示例参考YamlStorageType.ONE_TO_ONE

定义实体类

假设定义一个日志配置类:

  1. 创建一个类名为: LogConfig 继承 AbstractEntity 类

    import com.legendframework.core.dao.entity.AbstractEntity;
    public class LogConfig extends AbstractEntity<LogConfig> {
    }
    
  2. 声明**@Entity**注解信息

    定义了这个实体类的yaml文件名称为"logConfig",存储类型是ONE_TO_ONE类型,这里的type值定为 DataType.YAML,意思是存储类型固定为Yaml,不会因为外部的配置动态改变

    import com.legendframework.core.dao.entity.AbstractEntity;
    @Entity(type = DataType.YAML,yamlFileName = "logConfig",yamlStorageType = YamlStorageType.ONE_TO_ONE)
    public class LogConfig extends AbstractEntity<LogConfig> {
    }
    
  3. 定义字段

    由于父类AbstractEntity中声明了id字段,所以子类这里可以不用再次声明

    这里定义了两个字段,并且用**@Column**注解进行标识了字段注释信息与排列顺序

    如果枚举类LoggerLevelEnumcom.esotericsoftware.yamlbeans.extend.IEnum的子类,那么这个枚举字段会被解析自动生成 **”可选值“ **的注释信息,教程这里的枚举类并不是IEnum的子类,这里不做演示

    import com.legendframework.core.dao.entity.AbstractEntity;
    @Entity(type = DataType.YAML,yamlFileName = "logConfig",yamlStorageType = YamlStorageType.ONE_TO_ONE)
    public class LogConfig extends AbstractEntity<LogConfig> {
    
        @Column(
                comment = {
                        "日志级别",
                        "权重: DEBUG > WARN > ERROR > INFO , 如设置为WARN , 则显示WARN及一下级别的日志信息, 不会显示DEBUG级别的信息"
                },
                sort = 2)
        private LoggerLevelEnum logLevel;
    
        @Column(
                comment = {
                        "模板日志输出",
                        "可选值:",
                        "   #SERVICE# : 服务名",
                        "   #LOG_LEVEL# : 日志级别",
                        "   #CLASS_NAME# : 类信息",
                        "   #DATE_TIME# : 日志产生日期",
                        "   #INFO# : 错误信息"
                },
                sort = 3)
        private String templateInfo;
    
    	get与set方法此处省略...
    }
    
  4. 定义默认初始参数

    当插件被服务器启动后,如果第一次加载,框架会自动生成初始数据,前提是你的实体类需要是AbstractEntity的子类并且复写其方法getDefaultEntitys()

    import com.legendframework.core.dao.entity.AbstractEntity;
    @Entity(type = DataType.YAML,yamlFileName = "logConfig",yamlStorageType = YamlStorageType.ONE_TO_ONE)
    public class LogConfig extends AbstractEntity<LogConfig> {
    
        @Column(
                comment = {
                        "日志级别",
                        "权重: DEBUG > WARN > ERROR > INFO , 如设置为WARN , 则显示WARN及一下级别的日志信息, 不会显示DEBUG级别的信息"
                },
                sort = 2)
        private LoggerLevelEnum logLevel;
    
        @Column(
                comment = {
                        "模板日志输出",
                        "可选值:",
                        "   #SERVICE# : 服务名",
                        "   #LOG_LEVEL# : 日志级别",
                        "   #CLASS_NAME# : 类信息",
                        "   #DATE_TIME# : 日志产生日期",
                        "   #INFO# : 错误信息"
                },
                sort = 3)
        private String templateInfo;
        
        /**
         * 获取默认的数据
         * 如果实体类实现了这个接口,会默认将这些数据保存
         * @return
         */
        @Override
    	public List<LogConfig> getDefaultEntitys() {
        	LogConfig log = new LogConfig();
        	log.setId("1");
        	log.setLogLevel(LoggerLevelEnum.DEBUG);
        	log.setTemplateInfo("[#SERVICE#][#LOG_LEVEL#][#CLASS_NAME#][#DATE_TIME#]: #INFO#");
        	return Collections.singletonList(log);
    	}
    
    	get与set方法此处省略...
    }
    
  5. 经过Dao配置后插件自动生成的Yaml文件如下:

    #无意义主键
    id: "1"
    #日志级别
    #权重: DEBUG > WARN > ERROR > INFO , 如设置为WARN , 则显示WARN及一下级别的日志信息, 不会显示DEBUG级别的信息
    logLevel: "DEBUG"
    #模板日志输出
    #可选值:
    #   #SERVICE# : 服务名
    #   #LOG_LEVEL# : 日志级别
    #   #CLASS_NAME# : 类信息
    #   #DATE_TIME# : 日志产生日期
    #   #INFO# : 错误信息
    templateInfo: "[#SERVICE#][#LOG_LEVEL#][#CLASS_NAME#][#DATE_TIME#]: #INFO#"
    

Dao(数据访问层)

本框架Dao层已经封装了一系列默认的CRUD(增删改查)接口,调用只需要做下Dao与Entity的绑定即可,并且在框架启动后,如果第一次启动,会按需自动创建数据库,以及与实体类中注解@Entity所指定映射的表

默认的CRUD接口

com.legendframework.core.dao.IDao 中定义了很多接口,这些接口均由接口实现我们可以看看都有哪些方法

public interface IDao<T> extends IConfigDao<T> , IReload , CacheFlush{

    /**
     * 获取实体类类型
     * @return
     */
    Class<T> getEntityClass();

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param wrapper 实体对象封装操作类(可以为 null)
     */
    int delete(Wrapper<T> wrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(Collection<? extends Serializable> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(T entity, Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(Collection<? extends Serializable> idList);

    /**
     * 根据 entity 条件,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     * @param throwEx      有多个 result 是否抛出异常
     */
    T selectOne(Wrapper<T> queryWrapper, boolean throwEx);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    long selectCount(Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Page<T> selectPage(Page<T> page, Wrapper<T> queryWrapper);
}

条件构造器

当我们使用Dao进行条件筛选操作的时候,需要传入一个条件构造器com.legendframework.core.dao.wrapper.Wrapper,这是一个接口,本框架默认提供了两种条件构造器的实现类,然后我们可以通过面向对象的方式进行构建条件,下面所有关于条件构造器的案例,均是链式编程

条件构造器接口是指对 条件条件连接 的构造

条件

"=" , "!=" , "<" , ">" , "like" , "IS NOLL" . 等等一系列的条件符号, 表示方式为: Value1 ? Value2 , 其中 ? 则代表条件比较符

条件比较符所提供的所有方法如下:

eq(); //等于
ne(); //不等于
gt(); //大于
ge(); //大于等于
lt(); //小于
le(); //小于等于
in(); //指当前查询的值是否包含给定的一个比较值的集合,同等于SQL语法中的 IN (value1,value2,value3....)
notIn(); //与in()含义相同,不过这个是取反,指当前查询的值是否均不包含在给定一个比较值的集合中
like(); //模糊比较,比较提供的值,是否包含在值中,同等于SQL语法中的 LIKE
notLike(); //模糊比较取反
likeLeft(); //比较是否值是否以某值开头
likeRight(); //比较值只是否以某值结尾
isNull(); //是否为NULL
isNotNull; //是否不为NULL

注:其中方法入参中boolean condition 参数如果为false,则不进行条件的校验

eq (等于)
eq(R column, Object val)
eq(boolean condition, R column, Object val)

​ 例: eq("name", "老王")--->name = '老王'

ne (不等于)
ne(R column, Object val)
ne(boolean condition, R column, Object val)

​ 例: ne("name", "老王")--->name != '老王'

gt (大于)
gt(R column, Object val)
gt(boolean condition, R column, Object val)

​ 例: gt("age", 18)--->age > 18

ge (大于等于)
ge(R column, Object val)
ge(boolean condition, R column, Object val)

​ 例: ge("age", 18)--->age >= 18

lt (小于)
lt(R column, Object val)
lt(boolean condition, R column, Object val)

​ 例: lt("age", 18)--->age < 18

le (小于等于)
le(R column, Object val)
le(boolean condition, R column, Object val)

​ 例: le("age", 18)--->age <= 18

in
in(R column, Object... values)
in(boolean condition, R column, Object... values)

​ 例: in("age", 1, 2, 3)--->age in (1,2,3)

notIn
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)

​ 例: notIn("age", 1, 2, 3)--->age not in (1,2,3)

like
like(R column, Object val)
like(boolean condition, R column, Object val)

​ 例: like("name", "王")--->name like '%王%'

notLike
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)

​ 例: notLike("name", "王")--->name not like '%王%'

likeLeft
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)

​ 例: likeLeft("name", "王")--->name like '%王'

likeRight
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)

​ 例: likeRight("name", "王")--->name like '王%'

isNull
isNull(R column)
isNull(boolean condition, R column)

​ 例: isNull("name")--->name is null

isNotNull
isNotNull(R column)
isNotNull(boolean condition, R column)

​ 例: isNotNull("name")--->name is not null

条件连接

"and" , "or" 连接条件只有这两种,and代表并且(&&),or代表或者(||) , 他们拼接在 条件与条件之间,表示方式:条件1 && 条件2

and (并且)
and()
and(boolean condition)

框架默认是使用的and拼接符,下面这两个调用方式是等价的

​ 例1: eq("id",1).eq("name","老王")--->id = 1 and name = '老王'

​ 例2: eq("id",1).and().eq("name","老王")--->id = 1 and name = '老王'

and嵌套
and(Function<Param, Param> func)
and(boolean condition, Function<Param, Param> func)

这个Function函数式接口中的入参,框架会传递一个新的条件构造器

​ 例: eq("id",1).and(fun -> fun.eq("name","老王").or().eq("name","你儿子")) ---> id = 1 and (name = "老王" or name = "你儿子")

or (或者)
or()
or(boolean condition)

注意事项:

主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

​ 例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'

or嵌套
or(Function<Param, Param> func)
or(boolean condition, Function<Param, Param> func)

​ 例: eq("name","老王").or(fun -> fun.eq("id",1).eq("name","你儿子")) ---> name = "老王" or (id = 1 eq name = "你儿子")

QueryWrapper

这是一个基础的条件构造器, 上面的条件构造案例均是使用的 QueryWrapper 类所写出的案例

LambdaQueryWrapper

通过 QueryWrapper 的使用之后,我们会发现一个问题,就是需要我们主动输入字段名称,这可能会带来一些问题,例如输错,未来字段变更后代码编译是没有报错的,所以有了 LambdaQueryWrapper

两者区别:

//首先从Wrappers类中获取条件构造器
//普通方式
QueryWrapper<T> query();
//lambda方式
LambdaQueryWrapper<T> lambdaQuery();

// 等价示例:
query().eq("id", value);
lambdaQuery().eq(Entity::getId, value);

其中,所代替 QueryWrapper 的第一个参数,是传入 LambdaQueryWrapper 泛型中的类型,然后通过这个类型利用Jdk8新特性::选择相应的get方法,框架会自动解析这个get方法,获取这个字段名称

获取条件构造器实例化对象

直接使用com.legendframework.core.dao.wrapper.defaults.Wrappers这个类的静态方法即可获取实例

//获取QueryWrapper条件构造器
QueryWrapper<T> queryWrapper = com.legendframework.core.dao.wrapper.defaults.Wrappers.query();
//获取LambdaQueryWrapper条件构造器
LambdaQueryWrapper<T> queryWrapper = com.legendframework.core.dao.wrapper.defaults.Wrappers.lambdaQuery();

分页

通过传入Page对象,指定页码与每页显示条目数,即可分页

/**
 * 根据 entity 条件,查询全部记录(并翻页)
 *
 * @param page         分页查询条件
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
Page<T> selectPage(Page<T> page, Wrapper<T> queryWrapper);

Page类

public class Page<T> implements Serializable {
    /*页码(1开始)*/
    private long currentPage
    /*每页条目数*/
    private long itemNum;
    /*总页数*/
    private long totalPage;
    /*总记录数*/
    private long totalSum;
    /*分页后数据*/
    private List<T> list;
    
    getset.....
}   

定义Dao

Dao的定义非常的简单,只需要创建一个Dao接口,继承IDao即可,并且在IDao的T泛型中填入我们实体类的类名即可

最关键的一点是,需要在我们定义的Dao上加入**@EntityDao**注解,这样才能让这个Dao生效且自动生成CRUD接口方法

import com.legendframework.core.dao.annotation.EntityDao;
import com.legendframework.core.dao.IDao;
@EntityDao
public interface LogConfigDao extends IDao<LogConfig> {
}

使用Dao

我们使用上方定义的插件主类来使用这个Dao,通过 @Resource 注解就可以把这个 LogConfigDao 注入进去然后使用

@MainPlugin
public class TestPlugin extends LegendPlugin {

    @Resource
    private LogConfigDao logConfigDao;

    @Override
    public void start() {
        //这里就可以调用dao接口中的方法,选择你要调用的方法进行使用了
        LambdaQueryWrapper<LogConfig> warpper = Wrappers.lambdaQuery();
        LogConfig logConfig = logConfigDao.selectOne(warpper.eq(LogConfig::getLogLevel, LoggerLevelEnum.DEBUG),false);
    }
	........
}

Service (业务逻辑层)

Service默认提供了一系列对Dao层接口的封装接口,可以理解为Service内包装了一个Dao,在三层架构中,每层各司其职,不越界,低耦合高内聚的思想,Dao层只处理数据读写的逻辑,其他业务相关的逻辑由Service来处理,与用户交互的逻辑由Controller来处理 , Controller负责组装调用Service

Service提供的默认接口

public interface IService<T> extends IConfigService<T> , IReload {

    /**
     * 插入一条记录(选择字段,策略插入)
     *
     * @param entity 实体对象
     */
    boolean save(T entity);
    
    /**
     * 插入一条具有默认数据的记录 (主动触发)
     *
     * 如果这个ID的记录不存在,则获取去获取这个实体类的默认配置,然后进行赋值ID插入
     *
     * 适用于玩家初次进入服务器,进而生成玩家配置文件
     *
     * @param id
     * @return
     */
    boolean saveDefault(Serializable id);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    boolean removeById(Serializable id);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体包装类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    boolean remove(Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表
     */
    boolean removeByIds(Collection<? extends Serializable> idList);

    /**
     * 根据 ID 选择修改
     *
     * @param entity 实体对象
     */
    boolean updateById(T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象
     */
    boolean update(T entity, Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T getById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表
     */
    List<T> listByIds(Collection<? extends Serializable> idList);

    /**
     * 根据 Wrapper,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    default T getOne(Wrapper<T> queryWrapper) {
        return getOne(queryWrapper, false);
    }

    /**
     * 根据 Wrapper,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     * @param throwEx      有多个 result 是否抛出异常
     */
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);

    /**
     * 根据 Wrapper,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    Object getObj(Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    long count(Wrapper<T> queryWrapper);

    /**
     * 查询列表
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    List<T> list(Wrapper<T> queryWrapper);

    /**
     * 翻页查询
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    Page<T> page(Page<T> page, Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类 {@link com.legendframework.core.dao.wrapper.defaults.QueryWrapper}
     */
    List<Object> listObjs(Wrapper<T> queryWrapper);
}

定义Service的两种方式

这里只说明定义方式,调用方式与调用IOC容器中Bean的方式一致,注解注入即可

BaseService

public abstract class BaseService<T,D extends IDao<T>> implements IService<T> {...}
  1. 让自己的Service去继承BaseService,泛型1的参数填写为对应的实体类,泛型2参数填写为对应的Dao接口

    public class PropService extends BaseService<Prop,PropDao> {...}
    
  2. 最重要的一步,给自己的Service类上加上**@Service**注解

    @Service
    public class PropService extends BaseService<Prop,PropDao> {...}
    

VagueService

这是一个单泛型的Service,只需要开发者传入一个实体类即可使用增删改查的所有功能,内部封装了获取Dao的操作

public abstract class VagueService<T> extends BaseService<T, IDao<T>> {...}

同样记住加上**@Service**注解

@Service
public class PropService extends VagueService<Prop> {...}

事务

可以在**@Service所标识类中的任意方法中使用@Transactional**注解即可,对整个方法的调用链进行相应逻辑的事务控制,支持事务嵌套,支持Yaml与Mysql的事务合并,其中Yaml的事务隔离级别为:串行化

@Service
public class PropService extends VagueService<Prop> {
    @Transactional
    public void account(){
        Prop prop = getOne();
        prop.setName("修改值1");
        updateById(prop);
        //制造异常
        throw new RuntimeException("手动异常");
    }
}

注意:在方法中调用本类的带有**@Transactional**的方法,这时这个被调用的方法的事务注解将不会生效,具体原因是动态代理的问题,这里就不进阶讲解了,大家知道有这一回事就好

@Transactional

框架支持4种事务类型

  • REQUIRED

    支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择

  • SUPPORTS

    支持当前事务,如果当前没有事务,就以非事务方式执行

  • REQUIRES_NEW

    新建事务,如果当前存在事务,把当前事务挂起

  • NOT_SUPPORTED

    以非事务方式执行操作,如果当前存在事务,就把当前事务挂起


事件

原生的Bukkit事件注册需要手动在主类中进行注册,现框架为您省略了这一步操作,你只需要在事件的类上加上**@Event**注解即可自动注册事件

@Event
public class MyPlayerJoinEvent implements Listener {
    /**
     * 玩家进入服务器事件
     * @param e
     */
    @EventHandler
    public void event(PlayerJoinEvent e){
        e.setJoinMessage("你好,欢迎进入服务器!");
    }
}

指令

相比事件,指令就没这么简单就能讲完了,我们先来看下原生的指令定义:

public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if (args.length == 0){
        //当前输入的是根指令
        return true;
    }
    if (args.length == 1){
        if (StringUtils.equals(args[0],"info")){
            //xxx info
            //查看玩家个人信息
        } else if (StringUtils.equals(args[0], "reload")) {
            //xxx reload
            //重载插件
        }
        return true;
    }
    if (args.length == 2){
        if (StringUtils.equals(args[0],"info")){
            //xxx info <玩家名称>
            //查看指定玩家的个人信息
            String name = args[1];
        }
        return true;
    }
    return false;
}

由于原生的指令,是在一个**onCommand()**方法中进行判断处理插件的指令,如果是指令少还好,如果指令一多,复杂的if,else条件就会让人看得眼花缭乱,可读性极低,修改BUG,每次都要阅读代码很久才能定位问题,并且每次都要处理各种各样的参数转换,校验等操作。来看看本框架是如何定义指令的

@Command(cmd = "my",title = "我的指令")
public class MyCommand extends BaseCommand {
    
    @Cmd(value = "info",title = "查看玩家信息",desc = "",isAsyn = true)
    public void info(@CmdParam(
            title = "玩家",
            desc = "如果不传入参数则查询自己信息",
            demo = "196",
            required = false) Player player){
        if (player == null) {
            //查询自己的信息
        }else {
            //查询指定玩家信息
        }
    }
    
    @Cmd(value = "reload",title = "重载插件")
    public void reload(){
        System.out.println("重载插件...");
    }
}

接下来对框架的指令类进行详解

@Command

每个指令类都必须有一个**@Command**注解,用于将Bean存入IOC中,并且定义这个指令的前缀,标题等信息

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Command {
    /**Bean名称*/
    String value() default "";
    /**
     * 指令前缀
     * 设置了这个参数的指令类中的指令方法的cmd前缀都会加上这个参数的值
     * @return
     */
    String cmd();
    /**指令类标题*/
    String title();
}

指令树形结构

框架的指令文档输出,是根据指令树形结构来生成的,我们如果需要用到指令的文档输出,就必须要了解树形结构,从下图中可以看出,根指令为唯一,往下延申,指令类可以有多个子指令,并且子指令可以被多个父指令所拥有,每一个指令类中的指令方法都代表一个指令,指令类代表一个指令方法的集合

<img alt="image description">

@CommandParentNode

/**
 * 指令帮助的树形结构节点注解
 * 使用它框架能帮助您实现指令help的树形图
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CommandParentNode {
    /**父节点*/
    Class<? extends BaseCommand>[] value();
}
  1. 根指令的定义方式,使用**@Command指定cmd()**参数为空串,根指令的cmd()参数必须是空串

    则这个指令类中定义的所有方法的指令均为:/根指令 ?? ?? ??。其中/根指令 代表你在主类中定义的getRootCmd()值,?? 代表你方法注解@Cmd定义的指令路径

    @Command(title = "根指令" , cmd = "")
    public class MyRootCommand extends BaseCommand {...}
    
  2. 子指令定义的方式

    使用注解CommandParentNode , 指定自己的父指令是哪些,可以有多个

    @Command(title = "一级指令" , cmd = "one")
    @CommandParentNode(MyRootCommand.class)
    public class MyPropCommand extends BaseCommand {...}
    

相关注解

指令定义

@Cmd

把指令类当作一个指令的集合,那么这个类中的被**@Cmd**标识的方法就是这个集合中的一个指令

@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Cmd {
    /**指令*/
    String[] value();

    /**指令标题信息*/
    String title() default "";

    /**指令描述信息*/
    String desc() default "";

    /**是否异步执行*/
    boolean isAsyn() default true;
 }

为了帮助更好的理解指令的路径拼接,举两个例子:

例1:

  1. 假设插件的主类中定义的根指令**getRootCmd()**为:test

  2. 定义一个根指令的指令类,根指令的cmd参数必须是空串,代表访问到这个指令类中方法的路径为:/test ??

    @Command(title = "根指令" , cmd = "")
    public class MyRootCommand extends BaseCommand {
        @Cmd(value="cmd1",title="指令1")
        public void cmd1(){...}
    }
    
  3. 那么输入 /test cmd1 就可以执行 指令1

例2:

  1. 例1的基础上在新增一个指令类,作为根指令的子指令,这个子指令指定根指令的指令类为父指令

    @Command(title = "玩家相关指令" , cmd = "player")
    @CommandParentNode(MyRootCommand.class)
    public class MyPlayerCommand extends BaseCommand {
        @Cmd(value="info",title="查看玩家信息")
        public void cmd1(){...}
    }
    
  2. 那么输入/test player info 就可以执行info指令

@OnlyOp

作用在方法上

这个指令只能由Op身份的玩家执行

@Cmd(value="test",title="测试指令")
@OnlyOp
public void test(){...}
@OnlyPlayer

作用在方法上

这个指令只能由玩家执行

@Cmd(value="test",title="测试指令")
@OnlyPlayer
public void test(){...}
@OnlyServer

作用在方法上

这个指令只能由服务器执行

@Cmd(value="test",title="测试指令")
@OnlyServer
public void test(){...}
@Permission

作用在方法上

声明玩家执行这个指令所需的权限

如果**@Permissionvalue()不填权限默认为: 根指令 + 指令类指令 + 方法指令 如果当前方法的@Cmd**定义了有多条指令,则默认取第一条解析为权限 即为完整指令,空格替换为'.'

/**
 * 例根指令为 'test'
 */
@Command(title = "玩家相关指令" , cmd = "player")
@CommandParentNode(MyRootCommand.class)
public class MyPlayerCommand extends BaseCommand {
    @Cmd(value="info",title="查看信息")
    @Permission
    public void info(@CmdParam(title="玩家名称") String name){...}
    
    @Cmd(value="give",title="给予玩家物品")
    @Permission("abc.give")
    public void give(){...}
}

经过框架解析后

执行info所需的权限为:test.player.info

执行give所需的权限为:abc.give

参数定义

@CmdParam

作用在方法,字段

声明指令方法中的参数

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CmdParam {
    /**参数标题信息*/
    String title() default "";

    /**参数描述信息*/
    String desc() default "";

    /**参数示例*/
    String demo() default "";

    /**是否必须*/
    boolean required() default true;
}

来个例子帮助理解:

/**
 * 例根指令为 'test'
 */
@Command(title = "玩家相关指令" , cmd = "player")
@CommandParentNode(MyRootCommand.class)
public class MyPlayerCommand extends BaseCommand {
    
    @Cmd(value="upExp",title="给一个玩家提升经验值")
    @OnlyOp
    @Permission
    public void upExp(
        @CmdParam(title="给予的经验") Long exp,
        @CmdParam(title="玩家名称",desc="不传默认为自己",required=false) String name
    ){...}
    
}

/test player upExp 经验值 玩家名称/test player upExp 经验值 均可以执行到这个指令方法

**注意: **上面的例子中第二个参数,**required()**设置的false,代表这个参数非必填。

**required()**生效有两个条件:

1. 这个参数在参数末尾
2. 这个参数后面的参数的required()值均为false
@CmdEntityParam

在指令类的每个方法,因为有**@CmdParam**注解的存在,会显得每个方法的参数非常的臃肿,可读性低,分不清入参与代码块,为了提高代码的简洁度与可读性,我们想将这么多的入参全部封装到一个Bean中时,就需要用到这个注解

public class PlayerUpExpParam {
    @CmdParam(title="给予的经验") 
    private Long exp;
    
    @CmdParam(title="玩家名称",desc="不传默认为自己",required=false) 
    private String name;
    
    getset...
}
/**
 * 例根指令为 'test'
 */
@Command(title = "玩家相关指令" , cmd = "player")
@CommandParentNode(MyRootCommand.class)
public class MyPlayerCommand extends BaseCommand {
    
    @Cmd(value="upExp",title="给一个玩家提升经验值")
    @OnlyOp
    @Permission
    public void upExp(@CmdCmdEntityParam PlayerUpExpParam param){...}
    
}

调用路径与**@CmdParam**的例子一致:/test player upExp 经验值 玩家名称/test player upExp 经验值 均可以执行到这个指令方法

参数限制

@Size

作用在方法入参与字段上,用于限制一个字符串,数值类型的大小

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Size {

    /**最小值*/
    long min() default 0;

    /**最大值*/
    long max() default Long.MAX_VALUE;

    /**超过范围后的提示内容*/
    String message() default "取值范围有误";
}
@CollectionSize

作用在方法入参与字段上,用于限制一个集合的大小

@Target({ElementType.FIELD,ElementType.PARAMETER})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CollectionSize {

    /**最大值*/
    int max() default Integer.MAX_VALUE;

    /**超过范围后的提示内容*/
    String message() default "集合大小超过了给定的最大值";
}

异常处理

@CmdSuccessHandle

作用在方法,类上

指令执行成功处理器注解,当指令方法执行成功后,会自动执行com.legendframework.core.cmd.BaseCommand#successHandle()方法

如果作用在类上,说明这个指令类的所有指令方法执行成功后都会执行**successHandle()**方法

@CmdFailHandle

作用在方法,类上

指令执行失败处理器注解,当指令方法异常后,会自动执行com.legendframework.core.cmd.BaseCommand#failHandle()方法

如果作用在类上,说明这个指令类的所有指令方法执行异常后都会执行**failHandle()**方法

@CmdHandle

作用在方法,类上,使用这个注解,相当于同时使用了**@CmdSuccessHandle@CmdFailHandle**

内置参数解析

不使用注解就会自动注入的数据类型:

  • org.bukkit.command.CommandSender
  • org.bukkit.command.Command

例:

@Cmd(value="upExp",title="给一个玩家提升经验值")
@OnlyOp
@Permission
public void upExp(CommandSender sender , @CmdCmdEntityParam PlayerUpExpParam param , Command cmd){...}

如果声明了这两个参数,框架会自动将这个两个内置类型,注入到指令方法中

@CmdParam注解所支持的数据类型有:

  • Byte
  • Short
  • Integer
  • Long
  • Float
  • Double
  • Boolean
  • Enum
  • String
  • Offline
  • Player

当然你也可以自定义参数解析,新建一个类实现com.legendframework.core.cmd.paramer.converter.ParamerConverter接口,并且将类上加上**@Component**注解

例:

@Component
public class PlayerParamerConverter implements ParamerConverter<Player> {
    /**
     * 校验这个行参是否符合这个转换器
     * @return
     */
    @Override
    public boolean check(Class<?> cls) {
        return Player.class == cls;
    }
    /**
     * 转换类型
     *
     * 不要以 {@link IParameter} 为需转换的类型
     * 真实的需要转换类型取 type , 因为考虑到泛型List等情况
     * {@link IParameter} 作用是取其注解进行扩展功能
     *
     * @param sender 发送者
     * @param message 消息
     * @param type 需要转换的类
     * @param parameter parameter
     * @return
     */
    @Override
    public Player castParameter(CommandSender sender, String message, Class<Player> type, IParameter parameter) {
        return Bukkit.getPlayer(message);
    }
}

BaseCommand

com.legendframework.core.cmd.BaseCommand 是框架定义的一个实现指令树形结构,且封装了指令帮助输出抽象逻辑,我们开发者定义的所有指令类都应该是BaseCommand的子类

异常处理接口

/**
 * 通一的执行成功处理器
 * 当某指令方法上被标识了@CmdSuccessHandle时,会触发该处理器
 *
 * @param sender 指令发送者
 * @param method 执行成功的指令方法
 * @param params 执行这个指令方法所传入的参数
 */
void successHandle(CommandSender sender , Method method , Object... params);

/**
 * 当方法执行异常的处理器,方法执行异常时触发
 *
 * @param sender 指令发送者
 * @param e 异常信息
 * @param method 执行失败的指令方法
 */
void failHandle(CommandSender sender ,Throwable e , Method method);

/**
 * 当指令参数的个数正确,但与方法所匹配的类型不一致时,会调用该处理器
 *
 * 例如:参数类型为int类型, 玩家输入的这个参数为 "abc" ,则会调用该处理器
 *
 * @param sender 指令发送者
 * @param e 异常信息
 * @param method 封装入参失败的指令方法
 * @param parameter 导致参数错误的这个参数,它可能是一个入参 {@link Parameter} 也可能是一个字段 {@link Field}
 * @param param 导致错误方法实参
 * @param index 参数的索引
 */
void paramsErrorHandle(CommandSender sender , Throwable e, Method method , IParameter parameter , Object param, int index);

抽象的指令文档

其实,写指令最没有技术含量又最麻烦的其实就是指令文档了,原生Bukkit插件的指令开发,更改了指令名称,相关参数都会去手动修改指令文档的输出,为了解决这些问题,整个BaseCommand中的代码,有四分之三都是在描述指令文档,现在分别介绍指令文档:

看到这里,大家都知道每一个BaseCommand的子类都是一个指令类,这个指令类中有N个指令,是封装了N个指令的集合,意味着,我每个BaseCommand的子类都可以修改其父类的默认方法,实现帮助文档的自定义化

默认help指令

这是每一个指令类都会带有一个help指令,这个指令是BaseCommand提供的,help指令会输出当前这个指令类下的所有子指令的help()指令,以及自身定义的指令集合

具体内容如下:

/**
 * 为子类提供的help公用方法
 *
 * @param page 页码
 * {@link Cmd} 中的指令前会拼接子类 {@link Command#cmd()} 参数
 * <p>
 * 例如有两个子类 分别为:PlayerCmd ,ServerCmd
 * 其类上的 {@link Command#cmd()} 参数分别为 'p' , 's'
 * 那么这两个子类的help指令的调用为:
 * 1. PlayerCmd : '/根指令 p help' or '/根指令 p'
 * 2. ServerCmd : '/根指令 s help' or '/根指令 s'
 * 执行的help指令会进入到不同的子类对象的该方法 help() 中 , 子类可以复写这个方法
 */
@Cmd({"help", ""})
@CmdFailHandle
@Permission()
public void help(
        CommandSender sender,
        org.bukkit.command.Command command,
        @CmdParam(title = "页码", desc = "数字", demo = "1", required = false) Integer page
) {
    page = page == null ? 1 : page;
    Command thisCommand = getThisCommandAnnotation();
    List<CmdHelp> cmdHelps = getHelps();
    try {
        CmdHelp help = createCmdhelp(
                BaseCommand.class.getMethod("help", CommandSender.class, org.bukkit.command.Command.class, Integer.class),
                thisCommand,
                this
        );
        //分页且渲染
        renderingHelp(sender, command, help, PageUtil.page(cmdHelps, page, helpItemNum()));
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }
}

注:如要复写这个help()方法,需要自行加上@Cmd()等一系列的注解,注解不会继承

建议覆写的方法

这些方法均提供了默认实现,如果你需要自定义,可以进行覆写

    /**
     * 获取指令帮助的每页显示条目数
     */
    int helpItemNum();

	/**
     * 渲染指令help前缀
     *
     * @param sender      指令执行者
     * @param command     org.bukkit.command.Command 指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpPrefix(CommandSender sender, Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage);

	/**
     * 展示指令帮助的内容部分
	 *
     * 无需关注分页逻辑,只需要自定渲染风格
     *
     * @param sender      指令执行者
     * @param command     org.bukkit.command.Command 指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpBody(CommandSender sender, Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage);

	/**
     * 渲染指令help后缀
     *
     * @param sender      指令执行者
     * @param command     指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpSuffix(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage);

在执行help()指令后,框架会自动获取相应页码的指令,然后依次调用前缀,内容,后缀方法,开发者可以自定义其渲染风格

/**
 * 默认渲染风格
 *
 * @param cmdHelpPage 需要渲染的指令帮助列表
 */
@Override
public void renderingHelp(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage) {
    if (cmdHelpPage.getTotalSum() == 0) {
        sender.sendMessage("当前注册的指令: [" + command.getName() + "] , 没有绑定任何子指令");
        return;
    } else if (cmdHelpPage.getList().size() == 0) {
        sender.sendMessage("当前页没有任何数据");
    }
    renderingHelpPrefix(sender, command, help, cmdHelpPage);
    renderingHelpBody(sender, command, help, cmdHelpPage);
    renderingHelpSuffix(sender, command, help, cmdHelpPage);
}

注:如需在指令参数中插入空格,或传入集合到指令方法参数中,需要用到'{}'符号,例如:

/test player {这是一句 含有空 格 的话 这段话 如果指令 方法中的参数是集合 这段话会以空格进行 分割 填充到集合中}
Eclipse Public License - v 1.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. "Contributor" means any person or entity that distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 3. REQUIREMENTS A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: a) it complies with the terms and conditions of this Agreement; and b) its license agreement: i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. When the Program is made available in source code form: a) it must be made available under this Agreement; and b) a copy of this Agreement must be included with each copy of the Program. Contributors may not remove or alter any copyright notices contained within the Program. Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.

简介

Bukkit插件开发框架 展开 收起
EPL-1.0
取消

发行版

暂无发行版

贡献者 (1)

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/a990903/legendframework-core.git
git@gitee.com:a990903/legendframework-core.git
a990903
legendframework-core
legendframework-core
master

搜索帮助