小demon之Mybatis-plus代码生成器

380 阅读5分钟

小demon

正常的项目架构

image.png

实际代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--父依赖管理-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.18</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <!--项目的基本信息-->
    <groupId>space.qiushui</groupId>
    <artifactId>generator</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>generator</name>
    <packaging>jar</packaging>
    <description>generator</description>
    <!--版本和属性的集中管理-->
    <properties>
        <java.version>11</java.version>
        <mybatis-plus.version>3.5.4.1</mybatis-plus.version>
    </properties>
    <dependencies>
        <!--web项目必需的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--单元测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--美化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!--提供数据库驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!--Mybatis-plus的依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--代码生成器的依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--模版引擎 替换了默认的 VelocityEngine-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.32</version>
        </dependency>
        <!--引入原因:freemarker使用到了这个依赖对属性的判空操作-->
        <dependency>
            <groupId>org.realityforge.org.jetbrains.annotations</groupId>
            <artifactId>org.jetbrains.annotations</artifactId>
            <version>1.7.0</version>
        </dependency>
        <!--代码生成器globalConfig.enableSwagger会用到 enableSpringdoc不熟没用到-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!--springboot封装的打包插件-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

CodeGenerator.java ——最简单的版本

package space.qiushui.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.*;

public class CodeGenerator {

    private static final String URL = "jdbc:mysql://127.0.0.1:3306/hai?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8";
    private static final String USER_NAME = "root";
    private static final String PASSWORD = "123067";


    /**
     * <p>mybatis-plus官网: <a href="https://baomidou.com/pages/779a6e/">官网</a></p>
     * <p>mybatis-plus配置:<a href="https://baomidou.com/pages/981406/">配置</a></p>
     */
    public static void main(String[] args) {
        //项目路径获取
        String projectPath = System.getProperty("user.dir");

        FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
                // 全局配置
                .globalConfig((scanner, builder) -> builder
                        //不打开生成文件所在文件夹
                        .disableOpenDir()
                        // 文件生成目录 默认情况: win-> D:\   linux-> /temp
                        .outputDir(projectPath + "/src/main/java")
                        //创建者
                        .author("lhl")
                        //创建时间格式
                        .commentDate("yyyy-MM-dd")
                        //开启Swagger 模式 生成API文档
                        .enableSwagger()
                        //swagger在springboot 3.X 以后的替代方案 开启 Springdoc
                        //.enableSpringdoc()
                        //时间策略
                        .dateType(DateType.ONLY_DATE))
                // 包配置 可配置模块 所有文件夹的名称
                .packageConfig((scanner, builder) -> builder
                        //目录结构
                        .parent("space.qiushui.generator")
                )
                // 策略配置
                .strategyConfig((scanner, builder) -> builder
                        // 引用控制台窗口输入表名称
                        .addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
                        //Controller 生成策略
                        .controllerBuilder()
                        //添加注解@RestController
                        .enableRestStyle()
                        .enableFileOverride()

                        //Entity 生成策略
                        .entityBuilder()
                        .enableLombok()
                        //禁用生成 serialVersionUID
                        .disableSerialVersionUID()
                        //开启生成实体时生成字段注解
                        //.enableTableFieldAnnotation()
                        //表映射到实体的命名策略: 默认下划线转驼峰命
                        .naming(NamingStrategy.underline_to_camel)
                        //格式化实体名称 影响controller等其他的命名
                        //.formatFileName("%sDO")

                        //service 生成策略
                        .serviceBuilder()
                        .enableFileOverride()
                        //格式化业务实现名称
                        .formatServiceFileName("%sService")
                        .formatServiceImplFileName("%sServiceImpl")

                        //Mapper 生成策略
                        .mapperBuilder()
                        .enableFileOverride()
                        //开启 @Mapper 注解
                        .mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
                        //启用 BaseResultMap 生成
                        .enableBaseResultMap()
                        //启用 BaseColumnList
                        .enableBaseColumnList()
                        .build())
                //模板引擎配置
                .templateEngine(new FreemarkerTemplateEngine())
                .execute();

    }

    /**
     * 处理 all 情况
     * @param tables 表名称
     * @return 切割后的List集合
     */
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }

}

升级版(自定义模板和属性)

覆盖原有controller、service和impl,生成通用业务方法 ——第二个版本

    FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
                // 全局配置
                .globalConfig((scanner, builder) -> builder
                        //不打开生成文件所在文件夹
                        .disableOpenDir()
                        // 文件生成目录 默认情况: win-> D:\   linux-> /temp
                        .outputDir(projectPath + "/src/main/java")
                        //创建者
                        .author("lhl")
                        //创建时间格式
                        .commentDate("yyyy-MM-dd")
                        //开启Swagger 模式 生成API文档
                        .enableSwagger()
                        //swagger在springboot 3.X 以后的替代方案 开启 Springdoc
                        //.enableSpringdoc()
                        //时间策略
                        .dateType(DateType.ONLY_DATE))
                // 包配置 可配置模块 所有文件夹的名称
                .packageConfig((scanner, builder) -> builder
                        //目录结构
                        .parent("space.qiushui.generator")
                )
                // 策略配置
                .strategyConfig((scanner, builder) -> builder
                        // 引用控制台窗口输入表名称
                        .addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
                        //Controller 生成策略
                        .controllerBuilder()
                        //添加注解@RestController
                        .enableRestStyle()
                        .enableFileOverride()

                        //Entity 生成策略
                        .entityBuilder()
                        .enableLombok()
                        //禁用生成 serialVersionUID
                        .disableSerialVersionUID()
                        //开启生成实体时生成字段注解
                        //.enableTableFieldAnnotation()
                        //表映射到实体的命名策略: 默认下划线转驼峰命
                        .naming(NamingStrategy.underline_to_camel)
                        //格式化实体名称 影响controller等其他的命名
                        //.formatFileName("%sDO")

                        //service 生成策略
                        .serviceBuilder()
                        .enableFileOverride()
                        //格式化业务实现名称
                        .formatServiceFileName("%sService")
                        .formatServiceImplFileName("%sServiceImpl")

                        //Mapper 生成策略
                        .mapperBuilder()
                        .enableFileOverride()
                        //开启 @Mapper 注解
                        .mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
                        //启用 BaseResultMap 生成
                        .enableBaseResultMap()
                        //启用 BaseColumnList
                        .enableBaseColumnList()
                        .build())
                //模板引擎配置
                .templateEngine(new FreemarkerTemplateEngine())
                //模版配置
                .templateConfig((scanner, builder) -> builder
                        .controller("templates/controllerV2.java")
                        .service("templates/serviceV2.java")
                        .serviceImpl("templates/serviceImplV2.java")
                )
                //自定义模板配置
                .injectionConfig(builder -> {
                    builder.beforeOutputFile(((tableInfo, stringObjectMap) -> {
                                //controller和service是否使用构造起注入
                                stringObjectMap.put("lombok", true);
                                //是否添加校验
                                stringObjectMap.put("validated", true);
                            }))
                            .build();
                })
                .execute();

模板在mybatis-plus-generator生成器中默认设置的路径是:src/main/resources/templates

controllerV2.java 模板

package ${package.Controller};

import org.springframework.web.bind.annotation.*;
<#if restControllerStyle>
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>

<#if swagger>
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
</#if>
<#if lombok>
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
</#if>
<#if validated>
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;
import org.springframework.validation.annotation.Validated;
</#if>
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};

import java.lang.Long;
import java.util.List;

/**
 * <p>
 * ${table.comment!} 前端控制器
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
<#if swagger>
@Api(tags = {"${table.comment!}Controller"})
</#if>
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if lombok>
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
</#if>
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>

    private final ${table.serviceName} service;

<#if swagger>
    @ApiOperation(value = "查询列表")
</#if>
    @GetMapping(value = "/list")
    public List<${entity}> list(${entity} query) {
        List<${entity}> list = service.select${entity}List(query);
        return list;
    }

<#if swagger>
    @ApiOperation(value = "查询详情")
</#if>
    @GetMapping(value = "/{id}")
    public ${entity} get${entity}ById(@PathVariable("id") Long id) {
        return service.select${entity}ById(id);
    }

<#if swagger>
    @ApiOperation(value = "新增")
</#if>
    @PostMapping
    public ${entity} insert${entity}(<#if validated>@Validated(Insert.class)</#if> @RequestBody ${entity} req) {
        return service.insert${entity}(req);
    }

<#if swagger>
    @ApiOperation(value = "修改")
</#if>
    @PutMapping
    public ${entity} update${entity}(<#if validated>@Validated(Update.class)</#if> @RequestBody ${entity} req) {
        return service.update${entity}(req);
    }

<#if swagger>
    @ApiOperation(value = "删除")
</#if>
    @DeleteMapping("/{ids}")
    public List<Long> delete${entity}ByIds(@PathVariable(value = "ids") List<Long> ids) {
        return service.delete${entity}ByIds(ids);
    }
}
</#if>

serviceV2.java 模板

package ${package.Service};

import ${package.Entity}.${entity};
import ${superServiceClassPackage};

import java.lang.Long;
import java.util.List;

/**
 * <p>
 * ${table.comment!} 服务类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {

    /**
     * 查询列表
     *
     * @param query 查询条件封装
     * @return 查询结果
     */
    List<${entity}> select${entity}List(${entity} query);

    /**
     * 通过ID查询结果
     *
     * @param id 主键ID
     * @return 查询结果
     */
    ${entity} select${entity}ById(Long id);

    /**
     * 新增
     *
     * @param req 数据入参
     * @return 操作结果
     */
    ${entity} insert${entity}(${entity} req);

    /**
     * 修改
     *
     * @param req 数据入参
     * @return 操作结果
     */
    ${entity} update${entity}(${entity} req);

    /**
     * 批量删除操作
     *
     * @param ids 主键ID集合
     * @return 操作结果
     */
    List<Long> delete${entity}ByIds(List<Long> ids);
}
</#if>

serviceImplV2.java 模板

package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
<#if table.serviceInterface>
import ${package.Service}.${table.serviceName};
</#if>
import ${superServiceImplClassPackage};
<#if lombok>
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
</#if>
import org.springframework.stereotype.Service;

import java.lang.Long;
import java.util.List;

/**
 * <p>
 * ${table.comment!} 服务实现类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
<#if lombok>
@Slf4j
</#if>
@Service
<#if lombok>
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
</#if>
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()<#if table.serviceInterface>, ${table.serviceName}</#if> {

}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}><#if table.serviceInterface> implements ${table.serviceName}</#if> {

    private final ${table.mapperName} mapper;

    @Override
    public List<${entity}> select${entity}List(${entity} query) {
        return null;
    }

    @Override
    public ${entity} select${entity}ById(Long id) {
        return null;
    }

    @Override
    public ${entity} insert${entity}(${entity} req) {
        return null;
    }

    @Override
    public ${entity} update${entity}(${entity} req) {
        return null;
    }

    @Override
    public List<Long> delete${entity}ByIds(List<Long> ids) {
        return null;
    }
}
</#if>

以上这些模板是放在 /src/main/resource/templates 下的,文件命名如下:

  • controllerV2.java.ftl
  • serviceV2.java.ftl
  • serviceImplV2.java.ftl
    将上面的模板文件代码复制到文件会代码格式缩进有问题里,以绝后患你可以自己调整和我页面上缩进就好,要么就是生成的文件controller、service、impl用快捷键 Ctrl + Alt + LCommand + Option + L

自定义模板和属性 ——暂时的最终版本

    FastAutoGenerator.create(URL, USER_NAME, PASSWORD)
                // 全局配置
                .globalConfig((scanner, builder) -> builder
                        //不打开生成文件所在文件夹
                        .disableOpenDir()
                        // 文件生成目录 默认情况: win-> D:\   linux-> /temp
                        .outputDir(projectPath + "/src/main/java")
                        //创建者
                        .author("lhl")
                        //创建时间格式
                        .commentDate("yyyy-MM-dd")
                        //开启Swagger 模式 生成API文档
                        .enableSwagger()
                        //swagger在springboot 3.X 以后的替代方案 开启 Springdoc
                        //.enableSpringdoc()
                        //时间策略
                        .dateType(DateType.ONLY_DATE))
                // 包配置 可配置模块 所有文件夹的名称
                .packageConfig((scanner, builder) -> builder
                        //目录结构
                        .parent("space.qiushui.generator")
                )
                // 策略配置
                .strategyConfig((scanner, builder) -> builder
                        // 引用控制台窗口输入表名称
                        .addInclude(getTables(scanner.apply("表名(英文逗号分隔/all):")))
                        //Controller 生成策略
                        .controllerBuilder()
                        //添加注解@RestController
                        .enableRestStyle()
                        .enableFileOverride()

                        //Entity 生成策略
                        .entityBuilder()
                        .enableLombok()
                        //禁用生成 serialVersionUID
                        .disableSerialVersionUID()
                        //开启生成实体时生成字段注解
                        //.enableTableFieldAnnotation()
                        //表映射到实体的命名策略: 默认下划线转驼峰命
                        .naming(NamingStrategy.underline_to_camel)
                        //格式化实体名称 影响controller等其他的命名
                        //.formatFileName("%sDO")

                        //service 生成策略
                        .serviceBuilder()
                        .enableFileOverride()
                        //格式化业务实现名称
                        .formatServiceFileName("%sService")
                        .formatServiceImplFileName("%sServiceImpl")

                        //Mapper 生成策略
                        .mapperBuilder()
                        .enableFileOverride()
                        //开启 @Mapper 注解
                        .mapperAnnotation(org.apache.ibatis.annotations.Mapper.class)
                        //启用 BaseResultMap 生成
                        .enableBaseResultMap()
                        //启用 BaseColumnList
                        .enableBaseColumnList()
                        .build())
                //模板引擎配置
                .templateEngine(new FreemarkerTemplateEngine())
                //模版配置
                .templateConfig((scanner, builder) -> builder
                        .controller("templates/controllerV2.java")
                        .service("templates/serviceV2.java")
                        .serviceImpl("templates/serviceImplV2.java")
                )
                //自定义模板配置
                .injectionConfig(builder -> {
                    //自定义文件
                    List<CustomFile> list = new ArrayList<>();
                    CustomFile convert = new CustomFile.Builder()
                            .enableFileOverride()
                            .formatNameFunction((tableInfo -> tableInfo.getEntityName() + "DTO"))
                            .fileName(".java")
                            .templatePath("templates/dto.java.ftl")
                            .packageName("dto")
                            .build();
                    //模板在上面,可以加其他的 covert[MapStruct] 和 DTO 的拆分
                    list.add(convert);
                    
                    builder.customFile(list)
                            .beforeOutputFile(((tableInfo, stringObjectMap) -> {
                                //controller和service是否使用构造起注入
                                stringObjectMap.put("lombok", true);
                                //是否添加校验
                                stringObjectMap.put("validated", true);
                                //dto是否继承实体类
                                stringObjectMap.put("dtoExtends", true);
                                stringObjectMap.put("dtoName", tableInfo.getEntityName() + "DTO");
                                stringObjectMap.put("dtoPackage", "space.qiushui.generator.dto");
                                //无尽想象...
                            }))
                            .build();
                })
                .execute();

具体的模板 dto.java

package ${dtoPackage};

import java.io.Serializable;
<#if springdoc>
import io.swagger.v3.oas.annotations.media.Schema;
<#elseif swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if dtoExtends>
import ${package.Entity}.${entity};
</#if>
<#if entityLombokModel>
    <#if dtoExtends>
import lombok.EqualsAndHashCode;
    </#if>
import lombok.Getter;
import lombok.Setter;
    <#if chainModel>
import lombok.experimental.Accessors;
    </#if>
</#if>

/**
 * <p>
 * ${table.comment!} 数据传输类
 * </p>
 *
 * @author ${author}
 * @since ${date}
 */
<#if entityLombokModel>
@Getter
@Setter
    <#if chainModel>
@Accessors(chain = true)
    </#if>
    <#if dtoExtends>
@EqualsAndHashCode(callSuper = true)
    </#if>
</#if>
<#if springdoc>
@Schema(name = "${entity}数据传输类", description = "${table.comment!}")
<#elseif swagger>
@ApiModel(value = "${entity}数据传输类", description = "${table.comment!}")
</#if>
<#if dtoExtends>
public class ${dtoName} extends ${entity} implements Serializable {
<#else>
public class ${dtoName} implements Serializable {
</#if>
    private static final long serialVersionUID = 1L;

<#-- ----------  BEGIN 字段循环遍历  ---------->
<#if dtoExtends>
    <#if springdoc>
    @Schema(description = "拓展字段")
    <#elseif swagger>
    @ApiModelProperty("拓展字段")
    <#else>
    /**
     * 拓展字段示例
     */
    </#if>
    private String extra;
<#else>
<#list table.fields as field>
    <#if field.comment!?length gt 0>
        <#if springdoc>
    @Schema(description = "${field.comment}")
        <#elseif swagger>
    @ApiModelProperty("${field.comment}")
        <#else>
    /**
     * ${field.comment}
     */
        </#if>
    </#if>
    private ${field.propertyType} ${field.propertyName};

</#list>
</#if>
<#------------  END 字段循环遍历  ---------->
<#if !entityLombokModel>
    <#list table.fields as field>
        <#if field.propertyType == "boolean">
            <#assign getprefix="is"/>
        <#else>
            <#assign getprefix="get"/>
        </#if>

    public ${field.propertyType} ${getprefix}${field.capitalName}() {
        return ${field.propertyName};
    }

    <#if chainModel>
    public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    <#else>
    public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
    </#if>
        this.${field.propertyName} = ${field.propertyName};
        <#if chainModel>
        return this;
        </#if>
    }
    </#list>
</#if>
<#if !entityLombokModel>

    @Override
    public String toString() {
        return "${entity}{" +
    <#list table.fields as field>
        <#if field_index==0>
            "${field.propertyName} = " + ${field.propertyName} +
        <#else>
            ", ${field.propertyName} = " + ${field.propertyName} +
        </#if>
    </#list>
        "}";
    }
</#if>
}

备注:文件内容和文件名称要相互映照,因为是有相互引用的,不然会导致错乱哦

项目创建步骤

一切的基础前提:

  • jdk下载:1.8版本或更高,我的是11,并且安装好,配置好环境变量。
  • idea下载(可选):可以用记事本硬写也行,推荐idea。
  • maven下载(可选):配置好环境变量,配置好setting文件,复制一个备份到.m2下。
  • 数据库要有MySQL/H2都行
  • 项目初始化:SpringBoot初始化项目 image.png

  • 替换pom文件和将Generator.java文件,随意放在 /src/main/java 下随便哪个位置都行

  • 修改项目jdk版本和配置maven设置,然后加载依赖就好 image.png

image.png

  • 点击Generator.java中的main方法运行,输入表名,就完成了

功能讲解

官网信息

解释

为什么使用springboot:2.7.18

原因:请看SpringBoot官方文档,而且我建这个 Demon 肯定是拿这个版本相应最新的东西来了解,又没有什么业务和配置啥的,需要妥协版本的。所以想以此作为一个阶段,后面跃升到对应的17和21啊。

image.png

其实还可以适当的看看 mybatis-plus-generatorfreemarker 的源码实现和API,很懒,不想写了,这个demon就到这了啊,使用可以通过自定义属性和自定义文件夹,将通用的功能借助这个现有的代码生成来玩,就好了。拜拜

完结 撒花