# kuafu **Repository Path**: yixiyun-tech/kuafu ## Basic Information - **Project Name**: kuafu - **Description**: 夸父 Java MVC开发框架,提供极简快速开发。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 1 - **Created**: 2021-05-13 - **Last Updated**: 2025-01-02 ## Categories & Tags **Categories**: webframework **Tags**: None ## README # KuaFu MVC 开发框架 ## 介绍 夸父 Java Web MVC开发框架,提供极简快速开发。 ## 框架要求 - JDK11 推荐[AdoptOpenJDK](https://adoptopenjdk.net/) - Mysql数据库 (目前Sql组件还没适配其他数据库,后面会继续完善) ## 安装教程 1. 新建一个maven项目 2. pom.xml中引入框架jar包 ```xml tech.yixiyun.framework kuafu-mvc 0.0.7 jar ``` 3. pom.xml中还需要配置下build ```xml src/main/resources **/*.* org.apache.maven.plugins maven-compiler-plugin 3.8.1 你的jdk版本,不低于11 你的jdk版本,不低于11 UTF-8 -parameters ``` 4. 创建一个类,写一个main方法,启动项目 ```java public class Main { public static void main(String[] args) { TomcatStarter.start(); } } ``` ## 快速开始 1. 创建一个Controller类 ```java import tech.yixiyun.framework.kuafu.controller.BaseController; import tech.yixiyun.framework.kuafu.view.View; /** * 演示Demo * @author Yixiyun * @version 1.0 * @date 2021-05-16 15:00 */ public class DemoController extends BaseController { public View add(){ return json("name", "zhangsan", "age", 14); } } ``` 重启下应用,访问 localhost:80/demo/add,就会看到浏览器显示一个json结构的数据了 2. 按照正常的web项目结构,在webapp文件夹下,创建一个hello.jsp 文件 ```jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> Demo Hello Kuafu ``` 3. 到DemoController中写一个新方法 ```java public View hello() { return jsp("hello"); } ``` 重新下应用,访问 localhost:80/demo/hello,就会发现页面被打开了 4. 框架会自动识别Controller组件,识别的依据就是类上或父类身上是否有@Controller注解。识别为Controller组件后,框架会继续分析它的方法,**只要方法是public修饰的,并且返回类型是View的,框架就会自动生成路由映射**。 路由对应的url生成规则是: 1. 如果方法上有@Route注解,就以注解的值作为url 2. 否则就以 “/基础路径 / 方法名” 作为url。基础路径由@Controller注解决定,BaseController类上有一个默认的@Controller("(a)(!Controller)") (这是一个特殊语法,更多说明请查看@Controller注解的注释),代表类名去掉"Controller"单词后,剩下字符串的第一个字符小写。所以DemoController因为继承了BaseController,它的基础路径就是 "demo"。它的 add方法映射的路由url 就是 "/demo/add",浏览器访问“/demo/add”就会由这个方法处理 5. 在刚写的hello方法上加一个@Route("/") ```java @Route("/") public View hello() { return jsp("hello"); } ``` 现在重启下应用,访问 localhost:80/,就会发现,hello页面被打开了 (注意webapp目录下不要存在index.jsp、index.html等欢迎页) 6. 在 DemoController 类上加一个 @Controller("abc") ```java @Controller("abc") public class DemoController extends BaseController { ... } ``` 访问 localhost:80/abc/add,就会发现页面显示json数据了 7. BaseController 提供了很多方便的的方法,例如 - json(key,value, key,value...) 生成一个json结果的响应,json结果根据传入的键值对生成。 - json(data) 由传入的一个对象生成json响应 - jsonSuccess(data) 生成一个{state: SUCCESS, data: data}结构的json响应 - jsonFail(msg) 生成一个 {state: ERROR, msg: msg}结构的json响应 - jsp(path) 跳到一个jsp页面,只需要传入jsp在webapp文件夹中的相对路径,可以忽略 .jsp后缀 - dispatch(path) 转向到一个地址 - redirect(path) 重定向到一个地址 - text(content) 返回一个普通文本响应 - ftl(path) 跳到一个freemarker页面 - setAttr(key,value) 向request中绑定数据 - image(byte[]) 返回一个图片 - download(filename, byte[]) 返回一个文件下载流 - getRequest() 获取请求对象 - getResponse() 获取响应对象 - getSession() 获取session对象 8. 写一个Service 业务类 ```java /** * 演示Service * @author Yixiyun * @version 1.0 * @date 2021-05-15 13:22 */ public class DemoService extends BaseService { public void add() { LOGGER.info("执行了add方法"); } } ``` 9. 任何类身上有@Service注解,或者父类身上有@Service注解,就会被识别为Service组件。 注意这里我们使用了**LOGGER** 日志工具类,它基于log4j2实现,做了优化,无需每个类声明一个静态变量,都用它提供的静态方法记录即可。在框架的默认配置中,它只显示info以上级别的信息,并且输出记录到控制台以及日志文件中。后面会讲到如何修改配置 10. 在DemoController中注入DemoService实例 ```java /** * 演示Demo * @author Yixiyun * @version 1.0 * @date 2021-05-16 15:00 @Controller("abc") */ public class DemoController extends BaseController { private DemoService demoService; public View add() { //在这里调用一下demoService的方法 demoService.add(); return json("name", "zhangsan", "age", 14); } ... } ``` 11. 重新在浏览器访问下 demo/add,就会发现控制台打印了日志 12. 只要是框架组件,就会自动识别为Bean,并自动注入,并默认以单例模式实例化。 如果其他类想被纳入Bean管理,只需要类身上加上@Bean注解,注解可以控制实例化方式和是否懒加载。之后就可以通过BeanContext获取实例了。它也会被其他Bean类自动执行依赖注入。 13. 接下来开始熟悉配置相关的,我们首先尝试修改一下tomcat的启动端口 1. 在resources文件夹下新建一个名为 app-config.json 的文件 2. 然后在文件中,按照 json 的写法写上 ```json { "system": { "server": { "port":8080 } } } ``` 3. 重启应用,就会发现控制台提示 > ******** ^^^ 应用启动成功 ^^^ 启动地址:http://localhost:8080/ ******** 14. 对于一个项目来说,一般会有两个配置文件,一个是生产环境的,一个是开发环境的。刚才那个 app-config.json 正常来说对应的就是生产环境配置文件,当然你也可以两个环境下都用它。那么如果你想为开发环境另弄一个配置文件怎么办呢?只需要新建一个 app-config-dev.json 配置文件就行了,项目打包时,注意把它移除。一旦框架检测到有 app-config-dev.json,就会优先加载它,忽略 app-config.json. 接下来你我们尝试配置一个开发环境的配置文件,在这个配置里,我们干两个事,一个是tomcat端口改为9090,另一个是将日志的记录级别改为DEBUG,并且让日志不输出到文件,只在控制台打印。 ```json { "system": { "server": { "port":8080 }, "run": { "logger": { "configuration": { "loggers": { "logger": [{ "name": "kuafuLogger", "level": "debug", "additivity": "false", "AppenderRef": [ {"ref": "dev_console"} ] }] } } } } } } ``` 15. 你可能会觉得这个日志的配置怎么这么麻烦,这个是log4j2 日志框架官方提供的 json 配置方式,框架本身提供的预配置 其实已经不需要怎么改了,如果就是有特别需要,请自行查阅 log4j2 官方文档进行自定义配置,但一定要注意 ** logger的name 一定不要改名字,除非你不想用框架提供的LOGGER工具类 ** 16. 回到配置文件上,你可能会有疑问,配置为什么要这么写,规则是什么?其实很简单,框架提供了一个预配置 default-config.json 文件,你只需要按照预配置的 json结构,在你的配置文件中去重写你想改变的配置项即可。框架会用 你的配置去覆盖 预配置。下面是default-config.json 的内容 ```json { "system": { //server相关配置 "server": { "port": 80, "contextPath": "/", "connectionTimeout": "60000", //接收到请求后,等待处理的超时时间 "listen": "*", //如果服务器有多个网卡,而应用只需要监听某一个网卡的请求,就在这里配置上网卡的hostname或者ip。*代表服务器所有网卡 "compress": "off", //是否对文本进行压缩,三个值可用,on、off、force(强制) "protocol": "HTTP/1.1", //暂时支持 HTTP/1.1 、AJP/1.3两种协议 HTTP/2目前未看到明显性能提升,且要求较多 "errorPages": { //错误页面,一般只对嵌入式服务器有效 "500": "/500.ftl", //发生500时显示的页面 "404": "/404.ftl" //发生404时显示的页面 } }, //启动阶段 "boot": { "tomcat": { //针对Tomcat内嵌服务器的启动阶段的一些配置 "scanJars": false, //tomcat启动时是否扫描所有jar包,用于TLD和web-fragment扫描,这会增加启动时间 "uriEncoding": "UTF-8", "minThread": 10, //tomcat处理请求的最少线程数 "maxThread": 200, //tomcat处理请求的并发线程数,超过的请求会被放入等待队列中 "maxWaitCount": 100 //最多可等待的请求数,超过后会拒绝请求 }, "preHandler": "" //前置处理器,在启动工作开始前执行的工作 }, //运行阶段 "run": { //开发模式还是生产模式 "mode": "dev", //是否启用热加载,需要配合jrebel使用 "hotdeploy": false, //是否启用应用监控,用于实时查看服务器和应用的运行状态 "monitor": { "enable": true }, //请求相关 "request": { //是否支持跨域 "cors": true, //可以请求的资源路径 "resources": { //只有匹配这些规则的请求,才会被框架处理,否则交由Servlet容器处理,语法同Filter的url-pattern语法 "include": ["/*"], //在include的基础上,匹配这些规则的请求,将跳过拦截器处理,直接请求。语法同Filter的url-pattern语法 "exclude": ["*.ico"] }, "session": { "manager": "tech.yixiyun.framework.kuafu.controller.session.ServletSessionManager" //用于获取Session实例的类,需要实现ISessionManager接口 }, "upload": { "singleMaxSize": 51200, //上传的单文件最大大小,单位K,默认50M "totalMaxSize": 51200, //上传时单次请求最大大小,单位K,默认50M "savePath": "/upload/{date:yyyyMMdd}/{uuid}.{suffix}", //上传文件的默认保存位置,以webroot位置为基准 "notAllowSuffix": [ //不允许上传的文件类型 //不允许上传的文件类型 ".exe", ".bat", ".sh", ".dll", ".jsp", ".php", ".jar", ".class", ".js", ".asp", ".jspx", ".html", ".htm", ".shtml", ".ftl", ".py" ] } }, //模板相关 "template": { "freemarker": { //freemarker模板, "config": { //配置项参考文档:https://freemarker.apache.org/docs/api/freemarker/template/Configuration.html#setSetting-java.lang.String-java.lang.String- "ContentType": "text/html; charset=UTF-8", "TemplatePath": "[/,classpath:templates/]", "locale": "zh_CN", "template_exception_handler": "rethrow", "date_format": "yyyy-MM-dd", "time_format": "HH:mm", "datetime_format": "yyyy-MM-dd HH:mm", "template_update_delay": "2000",//单位毫秒,默认5000,开发环境设置低一点,生产环境建议设高 "default_encoding": "UTF-8", "number_format": "0.###", "incompatible_improvements": "2.3.30" } } }, //响应相关 "response": { }, //数据库相关 "db": { //事务相关 "transaction": { //自动开启事务的方法名前缀 "autoOpenPrefix": ["add","modify","del","update","do","save", "create", "alter", "insert"], //默认的事务隔离级别,参考TransactionLevel枚举类中的定义 "defaultLevel": "REPEATABLE_READ", //事务执行超时警告,一旦一个事务执行时间超过设定的毫秒值,就打印警告语句 "timeoutWarning": 1000 } }, //日志相关 "logger": { "default": "kuafuLogger", //默认使用的记录器 "configuration": { //配置参考log4j官网,链接http://logging.apache.org/log4j/2.x/manual/configuration.html#JSON "status": "error", "name": "kuafu", "appenders": { "appender": [ { "type": "Console", "name": "console", "PatternLayout": { "pattern": "%-d{MM-dd HH:mm:ss} [%p]-[%C{1}.%M()]: %m %n" } }, { //开发环境用的打印,可以显示颜色 "type": "Console", "name": "dev_console", "PatternLayout": { "pattern": "%-d{MM-dd HH:mm:ss} [%highlight{%-5level}{INFO=Magenta, TRACE=White, DEBUG=Blue}]-[%highlight{%C{1}.%M()}{INFO=Magenta, TRACE=White, DEBUG=Blue}]: %highlight{%m%n}{INFO=Magenta, TRACE=White, DEBUG=Blue}" } }, { "type": "RollingFile", "name": "file", "fileName": "logs/run.log", //日志文件保存路径 "filePattern" : "logs/%d{MM-dd}_%i.log.gz", //分割后的命名规则 "PatternLayout": { "pattern": "%-d{MM-dd HH:mm:ss} [%p]-[%C{1}.%M()]: %m %n" }, "Policies": { "CronTriggeringPolicy": { "schedule": "0 0 0 * * ? *", //日志每天零点分割一次 "evaluateOnStartup": true }, "SizeBasedTriggeringPolicy": { "size": "60M" } //单个日志文件超过60M也分割一次 }, "DefaultRolloverStrategy": { "max": 10 //所有的日志文件最多保留10个 } } ] }, "loggers": { "logger": [{ "name": "kuafuLogger", "level": "info", "additivity": "false", "AppenderRef": [ //默认会向这两个地方写入,上线后,可以不向console写入 { "ref": "console" }, { "ref": "file" } ] }], "root": { "level": "info", "AppenderRef": { "ref": "console" } } } } } }, "stop": { //系统停止运行阶段 } } } ``` 17. 你应该会注意到,框架依赖的配置都写在了 system 配置项下,为了和你的项目相关的配置区分开,极力建议你,不要把你的 项目相关的配置写在 system 下,而应该另外建一个 键,例如 app 或者 project ```json { "system": { //这里都是框架依赖的配置项 }, "app": { //这里写与你项目相关的配置,例如数据库配置、缓存配置等等 } } ``` 18. 为了方便你从配置文件中读取配置,这里提供了一个AppConfig工具类,它可以根据 key (大部分配置项的key路径我们都在ConfigKey中定义了) 从 所有生效的配置文件中提取配置数据,并转成对应结构,例如 ```java List prefixs = AppConfig.getAsStringList("system.run.db.autoOpenPrefix"); Integer port = AppConfig.getAsInt("system.server.port"); HashMap errorMap = AppConfig.getAsStringMap(ConfigKey.SERVER_ERRORPAGES); ``` 19. 接下来我们创建一个实体类 ```java package tech.yixiyun.demo.user; import tech.yixiyun.framework.kuafu.domain.BaseDomain; import java.util.Date; /** * 用户实体类 * * @author Yixiyun * @version 1.0 * @date 2021-05-17 14:20 */ public class User extends BaseDomain { //唯一标识 private Integer id; //姓名 private String name; //爱好 private String favors[]; //性别,true代表男,false代表女 private Boolean gender; //出生年月日 private java.sql.Date birthday; //记录的创建时间 private Date createTime; //getter setter 这里我就不写了,你一定要写上 } ``` 20. 创建完,我们来看一下Controller方法如何自动解析和转换请求参数。回到 hello.jsp 中,我们构建一个表单。 ```jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> Demo
``` 21. 可以看出,这个表单最终会提交到 demo/add 这个地址,所以回到 DemoController 的add方法,我们改动一下,把接收到的user对象返回给浏览器 ```java public class DemoController extends BaseController { private DemoService demoService; public View add(User user) { demoService.add(); return json(user); } public View hello() { return jsp("hello"); } } ``` 22. 现在可以尝试访问 /demo/hello 打开表单页,然后输入一些信息,点击提交看一下效果了,正常来说你应该能看到一个json结构的字符串,里面正是你提交的数据。如果失败了,请检查下pom.xml 中编译插件是否配置上了 -parameters 23. 框架会自动解析请求参数,根据请求参数名和方法参数名进行匹对处理。同时,**框架会根据请求的Content-Type,自动判断该如何转化参数,所以即使使用 axios 等库,以application-json形式发起请求,无需任何注解,框架也可正常解析请求参数 ** 24. 接下来我们尝试把数据存储到数据库中,首先我们注册一个数据源,框架提供了阿里的Druid数据源支持,所以这里以注册Druid数据源为例: 1. 在maven中添加druid依赖和mysql依赖 ```xml com.alibaba druid ${druid.version} mysql mysql-connector-java ${mysql.version} ``` 2. 在app-config-dev.json 配置文件中添加数据源配置 ```json { "app": { "datasource": { //数据源配置,注意值都是string类型,不能用其他类型,具体配置项参考 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8 "name": "main", //数据源标识 "url": "jdbc:mysql://你的数据库连接?useSSL=false&autoReconnect=true&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai", "username": "数据库用户名", "password": "数据库密码", "keepAlive": "true" } } } ``` 3. 写一个**IDataSourceProvider**实现类 ```java public class DataSourceProvider implements IDataSourceProvider { @Override public DataSourceDefinition[] get() { DataSourceDefinition[] definitions = {new DruidDataSourceDefinition("app.datasource")}; return definitions; } } ``` 25. 完成以上步骤,数据源就完成注册了。接着我们来根据Domain类,自动生成表结构,打开DemoController类,写一个方法 ```java /** * 根据Domain类生成或者更新表结构 * @return */ public View generateTable() { DbKit.createOrAlterAllSingleTable(); return jsonSuccess(); } ``` 然后我们重启系统,通过浏览器访问 /demo/generateTable,返回{state:"SUCCESS"} 就代表执行成功,打开数据库我们应该就可以看到创建成功的表了。 26. 接着我们尝试向数据库添加一条数据,回到DemoService 中的add方法,我们将它改动一下: ```java public void add(User user) { LOGGER.info("执行了add方法"); insertOne(user); } ``` 然后改一下DemoController的add方法 ```java public View add(User user) { demoService.add(user); return json(user); } ``` 然后我们访问一下 /demo/hello,在页面填写一些数据,点击提交试试效果吧 27.