├── .gitignore ├── .gitmodules ├── README.md ├── build.gradle ├── doc ├── changelog.md ├── db │ ├── expr.cn.md │ ├── expr.md │ ├── getting_started.cn.md │ ├── getting_started.md │ ├── query.cn.md │ ├── query.md │ ├── query_builder.cn.md │ └── query_builder.md ├── deploy.cn.md ├── deploy.md ├── es │ ├── Entity.md │ ├── EsDocRepository.md │ ├── EsManager.md │ ├── EsQueryBuilder.md │ ├── getting_started.md │ └── img │ │ ├── complex-agg-compare.png │ │ ├── complex-search-compare.png │ │ └── search-compare.png ├── http │ ├── api_doc.cn.md │ ├── controller.cn.md │ ├── controller.md │ ├── cookie.cn.md │ ├── cookie.md │ ├── flow.cn.md │ ├── flow.md │ ├── getting_started.cn.md │ ├── getting_started.md │ ├── img │ │ ├── jetty-idea-run.png │ │ ├── php-index.png │ │ ├── php-view.png │ │ └── yapix.png │ ├── jetty_server.cn.md │ ├── jetty_server.md │ ├── jphp.cn.md │ ├── request.cn.md │ ├── request.md │ ├── response.cn.md │ ├── response.md │ ├── route.cn.md │ ├── route.md │ ├── session.cn.md │ ├── session.md │ ├── upload.cn.md │ └── upload.md └── orm │ ├── benchmark │ ├── code_add.md │ ├── code_delete.md │ ├── code_getDepWithEmps.md │ ├── code_getEmpsByConditionIf.md │ ├── code_getEmpsByIds.md │ ├── code_model.md │ ├── code_update.md │ ├── code_updateEmpOnDynFields.md │ ├── img │ │ ├── 6077bfac6be12.png │ │ ├── 6077bfbbc0dc6.png │ │ ├── 6077bfd9c9520.png │ │ ├── 6077bff47755e.png │ │ ├── 6077c08d57841.png │ │ ├── 6077c0981c0eb.png │ │ ├── 6077c0c3ab9e8.png │ │ ├── 6077c0ce79345.png │ │ ├── 6077c139b5960.png │ │ ├── 6077c15079643.png │ │ ├── 6077c15fc9f9d.png │ │ ├── 6077c16ded504.png │ │ ├── 6077c18017f46.png │ │ ├── 6077c19e43fe7.png │ │ ├── 6077c1a98f781.png │ │ ├── 6077c1c088904.png │ │ ├── 6077c1d234d6f.png │ │ ├── 6077c1de61b60.png │ │ ├── 6077c1f1ba059.png │ │ ├── 6077c2052f6c4.png │ │ ├── 6077c2129b0c2.png │ │ ├── 60790aecb5bb9.png │ │ ├── 60790afe9f191.png │ │ ├── 60790b10b2c28.png │ │ ├── 60790b1f350e7.png │ │ ├── 60790b3076858.png │ │ ├── 60790b466ae99.png │ │ ├── 60790b5ad0bb4.png │ │ ├── 60790b7c3038a.png │ │ ├── 60790b872267e.png │ │ ├── 60790b9b83cda.png │ │ ├── 60790bacc9dea.png │ │ ├── 60790bcd07362.png │ │ ├── 60790bd9eb306.png │ │ ├── 60790beb40d0a.png │ │ ├── 60790bf79b7e2.png │ │ ├── 60790c102c4d0.png │ │ ├── 60790c2509043.png │ │ ├── 60790c449efb9.png │ │ ├── 60790c4e87cf8.png │ │ ├── 60790c5f75e94.png │ │ ├── 60790c6f11687.png │ │ └── 607917878ca90.png │ ├── result_round1.md │ ├── result_round2.md │ └── sence.md │ ├── cbrelation.cn.md │ ├── cbrelation.md │ ├── getting_started.cn.md │ ├── getting_started.md │ ├── jphp.cn.md │ ├── model.cn.md │ ├── model.md │ ├── relation.cn.md │ ├── relation.md │ ├── using.cn.md │ ├── using.md │ ├── validation.cn.md │ └── validation.md ├── gradle.properties ├── img ├── actiondetail.png ├── actionedit.png ├── actionindex.png ├── actionnew.png ├── runserver.png ├── webpage.png └── webview.png ├── install.sh ├── jkmvc-example ├── Dockerfile ├── build.gradle ├── src │ ├── main │ │ ├── kotlin │ │ │ └── net │ │ │ │ └── jkcode │ │ │ │ └── jkmvc │ │ │ │ └── example │ │ │ │ ├── controller │ │ │ │ ├── UserController.kt │ │ │ │ └── WelcomeController.kt │ │ │ │ └── model │ │ │ │ └── UserModel.kt │ │ ├── resources │ │ │ ├── auth.yaml │ │ │ ├── cookie.properties │ │ │ ├── dataSources.yaml │ │ │ ├── example.mysql.sql │ │ │ ├── http.yaml │ │ │ ├── jetty.yaml │ │ │ ├── routes.yaml │ │ │ ├── session.properties │ │ │ └── upload.properties │ │ └── webapp │ │ │ ├── META-INF │ │ │ └── MANIFEST.MF │ │ │ ├── WEB-INF │ │ │ └── web.xml │ │ │ ├── index.html │ │ │ ├── index.jsp │ │ │ └── user │ │ │ ├── detail.jsp │ │ │ ├── edit.jsp │ │ │ ├── index.jsp │ │ │ ├── login.jsp │ │ │ └── new.jsp │ └── test │ │ └── kotlin │ │ └── net │ │ └── jkcode │ │ └── jkmvc │ │ └── tests │ │ └── ExampleTests.kt └── start-jetty.sh ├── jkmvc-http ├── build.gradle ├── src │ ├── main │ │ ├── kotlin │ │ │ └── net │ │ │ │ └── jkcode │ │ │ │ └── jkmvc │ │ │ │ └── http │ │ │ │ ├── HttpParamMap.kt │ │ │ │ ├── HttpRequest.kt │ │ │ │ ├── HttpResponse.kt │ │ │ │ ├── HttpState.kt │ │ │ │ ├── IHttpRequest.kt │ │ │ │ ├── InnerHttpRequest.kt │ │ │ │ ├── Interceptor.kt │ │ │ │ ├── JkFilter.kt │ │ │ │ ├── MultipartRequest.kt │ │ │ │ ├── PartFile.kt │ │ │ │ ├── UploadFileUtil.kt │ │ │ │ ├── _Request.kt │ │ │ │ ├── controller │ │ │ │ ├── Controller.kt │ │ │ │ ├── ControllerClass.kt │ │ │ │ ├── ControllerClassLoader.kt │ │ │ │ ├── IController.kt │ │ │ │ ├── IControllerClass.kt │ │ │ │ ├── IControllerClassLoader.kt │ │ │ │ └── MethodRouteDetector.kt │ │ │ │ ├── handler │ │ │ │ ├── HttpRequestHandler.kt │ │ │ │ └── IHttpRequestHandler.kt │ │ │ │ ├── jphp │ │ │ │ ├── JkmvcHttpExtension.kt │ │ │ │ ├── PHttpRequest.kt │ │ │ │ ├── PHttpResponse.kt │ │ │ │ └── PhpView.kt │ │ │ │ ├── router │ │ │ │ ├── ARoute.kt │ │ │ │ ├── HttpMethod.kt │ │ │ │ ├── IRoute.kt │ │ │ │ ├── IRouter.kt │ │ │ │ ├── Route.kt │ │ │ │ ├── RouteException.kt │ │ │ │ ├── RouteResult.kt │ │ │ │ └── Router.kt │ │ │ │ ├── session │ │ │ │ ├── Auth.kt │ │ │ │ ├── IAuth.kt │ │ │ │ ├── IAuthUserModel.kt │ │ │ │ ├── SessionAuth.kt │ │ │ │ ├── TokenAuth.kt │ │ │ │ └── token │ │ │ │ │ ├── ITokenManager.kt │ │ │ │ │ └── TokenManager.kt │ │ │ │ ├── util │ │ │ │ ├── AllPagination.kt │ │ │ │ ├── IPagination.kt │ │ │ │ └── Pagination.kt │ │ │ │ └── view │ │ │ │ ├── IView.kt │ │ │ │ ├── VelocityView.kt │ │ │ │ └── View.kt │ │ └── resources │ │ │ ├── JPHP-INF │ │ │ └── sdk │ │ │ │ └── php │ │ │ │ └── jkmvc │ │ │ │ └── http │ │ │ │ ├── HttpRequest.php │ │ │ │ └── HttpResponse.php │ │ │ ├── META-INF │ │ │ └── services │ │ │ │ └── php.runtime.ext.support.Extension │ │ │ ├── auth.yaml │ │ │ ├── cookie.properties │ │ │ ├── http.yaml │ │ │ ├── jphp │ │ │ ├── callController.php │ │ │ ├── controller │ │ │ │ └── Test.php │ │ │ └── views │ │ │ │ └── login.php │ │ │ ├── redis.yaml │ │ │ ├── routes.yaml │ │ │ ├── session.properties │ │ │ └── upload.properties │ └── test │ │ ├── kotlin │ │ └── net │ │ │ └── jkcode │ │ │ └── jkmvc │ │ │ └── tests │ │ │ ├── ControllerTests.kt │ │ │ ├── TemplateTests.kt │ │ │ └── ViewTests.kt │ │ └── resources │ │ ├── test.ftl │ │ ├── test.php │ │ └── test.vm ├── test-freemarker.out ├── test-jphp.out └── test-velocity.out ├── jkmvc-orm ├── build.gradle └── src │ ├── main │ ├── kotlin │ │ └── net │ │ │ └── jkcode │ │ │ └── jkmvc │ │ │ ├── db │ │ │ ├── ClosableDataSource.kt │ │ │ ├── Db.kt │ │ │ ├── DbColumn.kt │ │ │ ├── DbColumnLogicType.kt │ │ │ ├── DbConfig.kt │ │ │ ├── DbException.kt │ │ │ ├── DbFunction.kt │ │ │ ├── DbMeta.kt │ │ │ ├── DbResultRow.kt │ │ │ ├── DbResultRowIterator.kt │ │ │ ├── DbResultSet.kt │ │ │ ├── DbResultSetIterator.kt │ │ │ ├── DbTable.kt │ │ │ ├── DbType.kt │ │ │ ├── IDataSourceFactory.kt │ │ │ ├── IDb.kt │ │ │ ├── IDbIdentifierQuoter.kt │ │ │ ├── IDbMeta.kt │ │ │ ├── IDbValueQuoter.kt │ │ │ ├── _Connection.kt │ │ │ ├── sharding │ │ │ │ ├── ShardingDataSourceFactory.kt │ │ │ │ └── ShardingDb.kt │ │ │ └── single │ │ │ │ ├── BaseDataSourceFactory.kt │ │ │ │ ├── BaseSingleDb.kt │ │ │ │ ├── DruidDataSourceFactory.kt │ │ │ │ ├── DruidSingleDb.kt │ │ │ │ ├── HikariDataSourceFactory.kt │ │ │ │ └── HikariSingleDb.kt │ │ │ ├── es │ │ │ ├── AggExpr.kt │ │ │ ├── EntityTypeAdapterFactory.kt │ │ │ ├── EsDocRepository.kt │ │ │ ├── EsException.kt │ │ │ ├── EsManager.kt │ │ │ ├── EsQueryBuilder.kt │ │ │ ├── EsScrollCollection.kt │ │ │ ├── NestedAggregation.kt │ │ │ ├── ScrollSearchResult.kt │ │ │ ├── _Jest.kt │ │ │ └── annotation │ │ │ │ ├── EsDoc.kt │ │ │ │ └── EsId.kt │ │ │ ├── model │ │ │ ├── EmptyOrmMeta.kt │ │ │ ├── GeneralModel.kt │ │ │ ├── GeneralOrmMeta.kt │ │ │ └── GeneralOrmQueryBuilder.kt │ │ │ ├── orm │ │ │ ├── DbKey.kt │ │ │ ├── IEntitiableOrm.kt │ │ │ ├── IJavaOrmMeta.kt │ │ │ ├── IOrm.kt │ │ │ ├── IOrmEntity.kt │ │ │ ├── IOrmMeta.kt │ │ │ ├── IOrmPersistent.kt │ │ │ ├── IOrmRelated.kt │ │ │ ├── IOrmValid.kt │ │ │ ├── Orm.kt │ │ │ ├── OrmCacheMeta.kt │ │ │ ├── OrmEntity.kt │ │ │ ├── OrmException.kt │ │ │ ├── OrmMeta.kt │ │ │ ├── OrmPersistent.kt │ │ │ ├── OrmQueryBuilder.kt │ │ │ ├── OrmQueryBuilderListener.kt │ │ │ ├── OrmRelated.kt │ │ │ ├── OrmValid.kt │ │ │ ├── PkEmptyRule.kt │ │ │ ├── PkGenerator.kt │ │ │ ├── SelectColumnList.kt │ │ │ ├── _Orm.kt │ │ │ ├── jphp │ │ │ │ ├── JkmvcOrmExtension.kt │ │ │ │ ├── PDb.kt │ │ │ │ ├── PModel.kt │ │ │ │ └── PQueryBuilder.kt │ │ │ ├── prop │ │ │ │ ├── OrmListPropDelegater.kt │ │ │ │ ├── OrmMapPropDelegater.kt │ │ │ │ ├── OrmPropDelegater.kt │ │ │ │ └── OrmSetPropDelegater.kt │ │ │ ├── relation │ │ │ │ ├── BelongsToRelation.kt │ │ │ │ ├── CbRelation.kt │ │ │ │ ├── HasNRelation.kt │ │ │ │ ├── HasNThroughRelation.kt │ │ │ │ ├── ICbRelation.kt │ │ │ │ ├── IRelation.kt │ │ │ │ ├── Relation.kt │ │ │ │ └── RelationConditions.kt │ │ │ └── serialize │ │ │ │ ├── OrmConverter.kt │ │ │ │ ├── OrmEntityFstSerializer.kt │ │ │ │ └── _Json.kt │ │ │ ├── query │ │ │ ├── CompiledSql.kt │ │ │ ├── DbCondition.kt │ │ │ ├── DbExpr.kt │ │ │ ├── DbLimit.kt │ │ │ ├── DbQueryBuilder.kt │ │ │ ├── DbQueryBuilderAction.kt │ │ │ ├── DbQueryBuilderDecoration.kt │ │ │ ├── DbQueryBuilderQuoter.kt │ │ │ ├── DbQueryPart.kt │ │ │ ├── DbQueryPartGroup.kt │ │ │ ├── DbQueryPartSimple.kt │ │ │ ├── DbQueryPartTemplate.kt │ │ │ ├── DecorationPartType.kt │ │ │ ├── ICompiledSql.kt │ │ │ ├── IDbQuery.kt │ │ │ ├── IDbQueryBuilder.kt │ │ │ ├── IDbQueryBuilderAction.kt │ │ │ ├── IDbQueryBuilderDecoration.kt │ │ │ ├── IDbQueryBuilderQuoter.kt │ │ │ ├── IDbQueryPart.kt │ │ │ ├── InsertData.kt │ │ │ └── SqlAction.kt │ │ │ └── util │ │ │ ├── ITreeNode.kt │ │ │ ├── ModelGenerator.kt │ │ │ ├── TreeJsonFactory.kt │ │ │ └── TreeNode.kt │ └── resources │ │ ├── JPHP-INF │ │ └── sdk │ │ │ └── php │ │ │ └── jkmvc │ │ │ └── orm │ │ │ ├── Db.php │ │ │ ├── Model.php │ │ │ └── QueryBuilder.php │ │ ├── META-INF │ │ └── services │ │ │ └── php.runtime.ext.support.Extension │ │ ├── dataSources.yaml │ │ ├── db-function.yaml │ │ ├── db-meta.yaml │ │ ├── db.yaml │ │ ├── es.yaml │ │ ├── fst-serializer.yaml │ │ ├── orm.properties │ │ ├── services │ │ └── php.runtime.ext.support.Extension │ │ └── validation-query.properties │ └── test │ ├── kotlin │ └── net │ │ └── jkcode │ │ └── jkmvc │ │ └── tests │ │ ├── DbQueryBuilderTests.kt │ │ ├── DbTests.kt │ │ ├── EntityTests.kt │ │ ├── JavaOrmTests.java │ │ ├── JphpTests.kt │ │ ├── ModelGeneratorTests.kt │ │ ├── OrmTests.kt │ │ ├── ShardingDbTest.kt │ │ ├── entity │ │ ├── Game.kt │ │ ├── MessageEntity.kt │ │ ├── Player.kt │ │ └── RecentOrder.kt │ │ ├── es │ │ ├── EsAggregationTests.kt │ │ ├── EsDocRepositoryTests.kt │ │ ├── EsQueryBuilderTests.kt │ │ ├── EsTests.kt │ │ ├── GsonTests.kt │ │ └── JavaEsAggregationTests.java │ │ └── model │ │ ├── AddressModel.kt │ │ ├── JavaUserModel.java │ │ ├── MessageModel.kt │ │ ├── ParcelModel.kt │ │ └── UserModel.kt │ └── resources │ ├── dataSource-shardorder.yaml │ ├── orm.php │ ├── test.mysql.sql │ ├── test.oracle.sql │ └── test.sqlserver.sql ├── jkmvc-server-jetty ├── build.gradle └── src │ └── main │ ├── kotlin │ └── net │ │ └── jkcode │ │ └── jkmvc │ │ └── server │ │ ├── JettyServer.kt │ │ └── JettyServerLauncher.kt │ └── resources │ └── jetty.yaml ├── jkmvc-tag ├── build.gradle └── src │ ├── main │ ├── kotlin │ │ └── net │ │ │ └── jkcode │ │ │ └── jkmvc │ │ │ └── tags │ │ │ └── form │ │ │ ├── BaseBoundTag.kt │ │ │ ├── BaseInputTag.kt │ │ │ ├── BaseMultiCheckedTag.kt │ │ │ ├── BaseSingleCheckedTag.kt │ │ │ ├── ButtonTag.kt │ │ │ ├── CheckboxTag.kt │ │ │ ├── CheckboxesTag.kt │ │ │ ├── ErrorsTag.kt │ │ │ ├── FormTag.kt │ │ │ ├── HiddenInputTag.kt │ │ │ ├── HtmlTag.kt │ │ │ ├── IdGenerator.kt │ │ │ ├── InputTag.kt │ │ │ ├── ItemsTag.kt │ │ │ ├── LabelTag.kt │ │ │ ├── MessageTag.kt │ │ │ ├── OptionTag.kt │ │ │ ├── OptionsTag.kt │ │ │ ├── PasswordInputTag.kt │ │ │ ├── RadioButtonTag.kt │ │ │ ├── RadioButtonsTag.kt │ │ │ ├── SelectTag.kt │ │ │ └── TextareaTag.kt │ └── resources │ │ ├── META-INF │ │ └── jkmvc-form.tld │ │ └── tag.properties │ └── test │ └── kotlin │ └── net │ └── jkcode │ └── jkmvc │ └── tags │ └── form │ └── TagTests.kt ├── jkmvc.ipr ├── jkmvc.iws ├── publish.sh └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | jkmvc.iml 2 | .idea/ 3 | build/ 4 | target/ 5 | .gradle 6 | *.log 7 | out/ 8 | upload/ 9 | logs/ 10 | tmp/ 11 | gradle/ 12 | gradlew 13 | gradlew.bat 14 | jkguard 15 | jkmvc-benchmark 16 | jkbenchmark 17 | jphp-java-ext 18 | jkutil 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jkguard"] 2 | path = jkguard 3 | url = git@github.com:shigebeyond/jkguard.git 4 | [submodule "jkmvc-benchmark"] 5 | path = jkmvc-benchmark 6 | url = git@github.com:shigebeyond/jkmvc-benchmark.git 7 | [submodule "jkbenchmark"] 8 | path = jkbenchmark 9 | url = git@github.com:shigebeyond/jkbenchmark.git 10 | [submodule "jphp-java-ext"] 11 | path = jphp-java-ext 12 | url = git@github.com:shigebeyond/jphp-java-ext.git 13 | [submodule "jkutil"] 14 | path = jkutil 15 | url = git@github.com:shigebeyond/jkutil.git -------------------------------------------------------------------------------- /doc/db/expr.cn.md: -------------------------------------------------------------------------------- 1 | # Db表达式 2 | 3 | 见类 `DbExpr` 4 | 5 | ``` 6 | data class DbExpr(public val exp:CharSequence /* 表达式, 可以是 String | DbQueryBuilder */, 7 | public val alias:String?, /* 别名 */ 8 | public val expQuoting:Boolean = (exp !is IDbQueryBuilder) /* 是否转义exp, 只要不是子查询, 默认都转 */ 9 | ) : CharSequence by "" 10 | ``` 11 | 12 | 主要有两个作用: 13 | 14 | 1. 带别名 15 | 16 | `DbQueryBuilder().select(DbExpr("username", "u"), DbExpr("password", "p")).from("user");` 17 | 18 | 2. 控制是否转义 19 | 20 | `DbQueryBuilder().select("username", DbExpr("COUNT(`id`)", "total_posts", false))` 21 | 22 | 23 | -------------------------------------------------------------------------------- /doc/db/expr.md: -------------------------------------------------------------------------------- 1 | # Db Expression 2 | 3 | see class `DbExpr` 4 | 5 | ``` 6 | data class DbExpr(public val exp:CharSequence /* Expression: String | DbQueryBuilder */, 7 | public val alias:String?, /* alias */ 8 | public val expQuoting:Boolean = (exp !is IDbQueryBuilder) /* Whether `exp` is quoting, If `exp` is not sub query, default value is true */ 9 | ) : CharSequence by "" 10 | ``` 11 | 12 | It has 2 uses: 13 | 14 | 1. With alias 15 | 16 | `DbQueryBuilder().select(DbExpr("username", "u"), DbExpr("password", "p")).from("user");` 17 | 18 | 2. Controller quoting expression 19 | 20 | `DbQueryBuilder().select("username", DbExpr("COUNT(`id`)", "total_posts", false))` 21 | 22 | 23 | -------------------------------------------------------------------------------- /doc/db/query.cn.md: -------------------------------------------------------------------------------- 1 | # 构建查询 2 | 3 | 有2种方式来构建查询 4 | 5 | 1. [Db](getting_started.cn.md) 方法 6 | 7 | ``` 8 | Db::execute() 9 | Db::queryValue() 10 | Db::queryRow() 11 | Db::queryRows() 12 | ``` 13 | 14 | 2. [query builder](query_builder.cn.md) 方法 15 | 16 | 查询构建器,是通过提供类sql的一系列方法,来帮助开发者快速构建原生sql,可兼容不同的数据库(如mysql/oracle)。 17 | 18 | -------------------------------------------------------------------------------- /doc/db/query.md: -------------------------------------------------------------------------------- 1 | # Making Queries 2 | 3 | There are two different ways to make queries. 4 | 5 | 1. [Db](getting_started.md) methods 6 | 7 | ``` 8 | Db::execute() 9 | Db::queryValue() 10 | Db::queryRow() 11 | Db::queryRows() 12 | ``` 13 | 14 | 2. [query builder](query_builder.md) methods 15 | 16 | `DbQueryBuilder` provides methods similar to sql syntax, and generates real sql adapted to different databases(eg. mysql/oracle). 17 | 18 | -------------------------------------------------------------------------------- /doc/deploy.cn.md: -------------------------------------------------------------------------------- 1 | # 部署 2 | 3 | ## 1 设置生成环境 4 | 5 | vim src/main/resources/jkapp.yaml 6 | 7 | ``` 8 | # 环境:prod/dev/test 9 | environment=dev 10 | ``` 11 | ## 2 设置生成库 12 | 13 | vim src/main/resources/dataSources.yaml 14 | 15 | 修改你的生成库的连接配置 16 | 17 | ## 3 设置生产环境的上传目录与域名 18 | 19 | vim src/main/resources/upload.properties 20 | 21 | ``` 22 | # 上传文件的保存目录,末尾不要带/ 23 | uploadRootDirectory=upload 24 | # 编码 25 | encoding=gbk 26 | # 上传文件的域名 27 | uploadDomain=http://localhost:8081/jkmvc/upload 28 | ``` 29 | 30 | ## 4 设置你的controller包 31 | 32 | vim src/main/resources/http.yaml 33 | 34 | ``` 35 | # 是否调试 36 | debug: true 37 | # 静态文件的扩展名 38 | staticFileExts: gif|jpg|jpeg|png|bmp|ico|svg|swf|js|css|eot|ttf|woff 39 | # controller类所在的包路径 40 | controllerPackages: 41 | - net.jkcode.jkmvc.example.controller 42 | # 视图目录, 根目录为webapp 43 | viewDir: 44 | ``` -------------------------------------------------------------------------------- /doc/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy 2 | 3 | ## 1 Setting up a production environment 4 | 5 | vim src/main/resources/jkapp.yaml 6 | 7 | ``` 8 | # environment: prod/dev/test 9 | environment=dev 10 | ``` 11 | ## 2 Setting up the production database 12 | 13 | vim src/main/resources/dataSources.yaml 14 | 15 | Write your database configure 16 | 17 | ## 3 Setting up the production upload directory and domain 18 | 19 | vim src/main/resources/upload.properties 20 | 21 | ``` 22 | # upload directory, where the uploaded file save, without postfix "/" 23 | uploadRootDirectory=/var/www/upload 24 | encoding=gbk 25 | # domain to visit uploaded file 26 | uploadDomain=http://localhost:8081/jkmvc/upload 27 | ``` 28 | 29 | ## 4 Setting up the controller classes's package 30 | 31 | vim src/main/resources/http.yaml 32 | 33 | ``` 34 | debug: true 35 | # static file extension 36 | staticFileExts: gif|jpg|jpeg|png|bmp|ico|svg|swf|js|css|eot|ttf|woff 37 | # controller classes's package paths 38 | controllerPackages: 39 | - net.jkcode.jkmvc.example.controller 40 | # view directory 41 | viewDir: 42 | ``` -------------------------------------------------------------------------------- /doc/es/Entity.md: -------------------------------------------------------------------------------- 1 | # 实体类及注解 2 | 实体类, 主要用于映射物理的es索引, 以方便用面向对象的API来读写索引 3 | 4 | ## demo 5 | 6 | ```kotlin 7 | @EsDoc("message_index", "_doc") 8 | open class MessageEntity: OrmEntity() { 9 | 10 | // 代理属性读写 11 | @EsId 12 | public var id:Int by property() // 消息id 13 | 14 | public var fromUid:Int by property() // 发送人id 15 | 16 | public var toUid:Int by property() // 接收人id 17 | 18 | public var created:Long by property() // 接收人id 19 | 20 | public var content:String by property() // 消息内容 21 | 22 | override fun toString(): String { 23 | return "MessageEntity(" + toMap() + ")" 24 | } 25 | 26 | } 27 | ``` 28 | 29 | 1. 实体类, 我们继承了db orm中OrmEntity类体系, 主要是方便db orm与es orm相互调用, 也可统一2类存储实现上的实体类 30 | 31 | 2. 两个注解: 32 | 33 | 2.1 `@EsDoc` 作用在类,标记实体类为文档对象,一般有3个属性 34 | ```kotlin 35 | annotation class EsDoc( 36 | public val index: String, // 索引名 37 | public val type: String = "_doc", // 类型 38 | public val esName: String = "default" // es配置名 39 | ) 40 | ``` 41 | 42 | 2.2 `@EsId` 作用在成员变量,标记一个字段作为_id主键 43 | 44 | 这2个注解是非常精简的, 仅仅是关注索引名与_id主键, 没有过多关注索引存储(如分片数/副本数)与字段元数据(字段类型/分词器)等等, 这些都是在框架之外由运维人员自行维护的, 从而达到简化代码的目的. 45 | 46 | 仅在kotlin中有效 47 | 48 | 如果在java中, 请使用注解`@JestId`, 这是在jest中定义的, 详见`io.searchbox.annotations.JestId` 49 | -------------------------------------------------------------------------------- /doc/es/img/complex-agg-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/es/img/complex-agg-compare.png -------------------------------------------------------------------------------- /doc/es/img/complex-search-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/es/img/complex-search-compare.png -------------------------------------------------------------------------------- /doc/es/img/search-compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/es/img/search-compare.png -------------------------------------------------------------------------------- /doc/http/api_doc.cn.md: -------------------------------------------------------------------------------- 1 | # 生成api文档 2 | 3 | 我们使用idea插件`jk-yapix`, 根据controller类的javadoc中导出api接口信息 4 | 5 | ## 1 controller类注释格式 6 | 1. 请求参数格式为 `@param 参数名*:类型=默认值`,其中`*`表示必填 7 | 2. 返回值(响应)格式为`@return [类名]`,响应数据结构优先取方法注释中`@return`链接的类,如果没有再取方法返回值类型; 8 | 9 | 例子:参考[UserController.kt](/jkmvc-example/src/main/kotlin/net/jkcode/jkmvc/example/controller/UserController.kt) 10 | ```kotlin 11 | /** 12 | * 详情页 13 | * @param id:int=0 用户id 14 | * @return [UserModel] 15 | */ 16 | public fun detail() 17 | ``` 18 | 19 | ## 2 插件安装与使用 20 | 1. 安装 21 | 参考 [jk-yapix](https://plugins.jetbrains.com/plugin/19338-jk-yapix) 22 | 23 | 2. 使用 24 | ![](img/yapix.png) 25 | 26 | 3. 我们访问yapi后台即可看到文档 27 | 28 | -------------------------------------------------------------------------------- /doc/http/cookie.cn.md: -------------------------------------------------------------------------------- 1 | # cookie 2 | 3 | 主要是注意修改cookie的域名配置 4 | 5 | vim src/main/resources/cookie.properties 6 | 7 | ``` 8 | expiry = 604800 9 | path = / 10 | domain = localhost 11 | # 仅用于https/ssl 12 | secure = false 13 | # 禁止js脚本读取到cookie, 防止XSS攻击 14 | httponly = false 15 | ``` -------------------------------------------------------------------------------- /doc/http/cookie.md: -------------------------------------------------------------------------------- 1 | # cookie 2 | 3 | Remember to config `domain` 4 | 5 | vim src/main/resources/cookie.properties 6 | 7 | ``` 8 | expiry = 604800 9 | path = / 10 | domain = localhost 11 | # only use https/ssl 12 | secure = false 13 | # prevent js from reading cookie, defend XSS attack 14 | httponly = false 15 | ``` 16 | -------------------------------------------------------------------------------- /doc/http/flow.cn.md: -------------------------------------------------------------------------------- 1 | # 请求处理流程 2 | 3 | 每个应用都遵守同样的请求处理流程: 4 | 5 | 应用入口是 `net.jkcode.jkmvc.http.JkFilter`, 他的实现只是简单的调用 `HttpHandler.handle(req as HttpServletRequest, res as HttpServletResponse)` 6 | 7 | 现在,让我们看看 `net.jkcode.jkmvc.http.HttpHandler#handle()`的实现 8 | 9 | 1. 创建 [HttpRequest](request.cn.md) 对象 10 | 2. 创建 [HttpResponse](response.cn.md) 对象 11 | 3. 调用 `req.parseRoute()` 来解析uri,及其相应的controller与action 12 | 4. 创建 [Controller](controller.cn.md) 对象 13 | 5. 调用 Controller 对象的 action 方法 14 | -------------------------------------------------------------------------------- /doc/http/flow.md: -------------------------------------------------------------------------------- 1 | # Request Flow 2 | 3 | Every application follows the same flow: 4 | 5 | Application's entry is `net.jkcode.jkmvc.http.JkFilter`, and nd it just call `HttpHandler.handle(req as HttpServletRequest, res as HttpServletResponse)` 6 | 7 | Now, let's read `net.jkcode.jkmvc.http.HttpHandler#handle()` 8 | 9 | 1. create [HttpRequest](request.md) object 10 | 2. create [HttpResponse](response.md) object 11 | 3. call `req.parseRoute()` to parse uri and its corresponding controller and action. 12 | 4. create [Controller](controller.md) object 13 | 5. call Controller object's action method 14 | -------------------------------------------------------------------------------- /doc/http/img/jetty-idea-run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/http/img/jetty-idea-run.png -------------------------------------------------------------------------------- /doc/http/img/php-index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/http/img/php-index.png -------------------------------------------------------------------------------- /doc/http/img/php-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/http/img/php-view.png -------------------------------------------------------------------------------- /doc/http/img/yapix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/http/img/yapix.png -------------------------------------------------------------------------------- /doc/http/jetty_server.cn.md: -------------------------------------------------------------------------------- 1 | # jetty server 2 | 3 | ## server配置 4 | vim src/main/resources/jetty.yaml 5 | 6 | ``` 7 | maxThreads: 0 # io线程数, 如果为0则取Runtime.getRuntime().availableProcessors()*8 8 | #host: localhost # The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0 then bind to all interfaces. 9 | port: 8080 10 | # 如果 jkmvc-example 是根目录, 则不需要 jkmvc-example/ 目录前缀 11 | webDir: jkmvc-example/src/main/webapp 12 | contextPath: /jkmvc-example 13 | logDir: logs 14 | tempDir: tmp 15 | ``` 16 | 17 | ## 启动server 18 | 启动主类 `net.jkcode.jkmvc.server.JettyServerLauncher` 19 | 20 | 如果你在idea中通过`Run/Debug Configurations` -> `Application`启动, 请注意指定`use classpath of module` 21 | ![](img/jetty-idea-run.png) -------------------------------------------------------------------------------- /doc/http/jetty_server.md: -------------------------------------------------------------------------------- 1 | # jetty server 2 | 3 | ## Server configuration 4 | vim src/main/resources/jetty.yaml 5 | 6 | ``` 7 | maxThreads: 0 # io thread num 8 | #host: localhost # The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0 then bind to all interfaces. 9 | port: 8080 10 | webDir: jkmvc-example/src/main/webapp 11 | contextPath: /jkmvc-example 12 | logDir: logs 13 | tempDir: tmp 14 | ``` 15 | 16 | ## Start server 17 | Run the main class `net.jkcode.jkmvc.server.JettyServerLauncher` 18 | 19 | If you run through `Run/Debug Configurations` -> `Application` in idea, you must config `use classpath of module` 20 | ![](img/jetty-idea-run.png) -------------------------------------------------------------------------------- /doc/http/request.cn.md: -------------------------------------------------------------------------------- 1 | # 请求对象 2 | 3 | jkmvc的请求类是 `net.jkcode.jkmvc.http.HttpRequest` 4 | 5 | 他是继承并代理 `javax.servlet.http.HttpServletRequest` 6 | 7 | 因此,你可以使用从 `HttpServletRequest` 继承的方法 8 | 9 | 下面仅列出请求类特有的属性与方法 10 | 11 | 1. 路由相关的属性与方法 12 | 13 | 属性/方法 | 作用 14 | --- | --- 15 | controller: String | 获得当前controller 16 | action: String | 获得当前action 17 | route: Route | 当前匹配的路由规则 18 | routeParams: Map | 当前匹配的路由参数 19 | parseRoute(): Boolean | 解析路由 20 | 21 | 22 | 2. 检查 `http method` 的方法 23 | 24 | 属性/方法 | 作用 25 | --- | --- 26 | isAjax: Boolean | 是否ajax请求 27 | isGet: Boolean | 是否get请求 28 | isMultipartContent: Boolean | 是否 multipart 请求 29 | isPost: Boolean | 是否post请求 30 | isOptions: Boolean | 是否是options请求 31 | isUpload: Boolean | 是否上传文件的请求 32 | 33 | 3. 获得get/post/路由参数的方法 34 | 35 | 方法 | 作用 36 | --- | --- 37 | contains(key: String): Boolean | 检查是否有get/post/路由参数 38 | isEmpty(key: String): Boolean | 检查get/post/路由参数是否为空 39 | getParameter(key: String): String? | 获得get/post/路由参数值 40 | get(key: String, defaultValue: T?): T? | 获得get/post/路由参数值,注:调用时需明确指定返回类型,来自动转换参数值为指定类型 41 | getBoolean(key: String, defaultValue: Boolean? = null): Boolean? | 获得boolean类型的get/post/路由参数值 42 | getDate(key: String, defaultValue: Date? = null): Date? | 获得Date类型的get/post/路由参数值 43 | getDouble(key: String, defaultValue: Double? = null): Double? | 获得double类型的get/post/路由参数值 44 | getFloat(key: String, defaultValue: Float? = null): Float? | 获得float类型的get/post/路由参数值 45 | getInt(key: String, defaultValue: Int? = null): Int? | 获得int类型的get/post/路由参数值 46 | getLong(key: String, defaultValue: Long? = null): Long? | 获得long类型的get/post/路由参数值 47 | getShort(key: String, defaultValue: Short? = null): Short? | 获得short类型的get/post/路由参数值 48 | 49 | 4. 获得上传文件的方法 50 | 51 | 方法 | 作用 52 | --- | --- 53 | getPartFile(name: String): File? | 获得某个上传文件 54 | storePartFileAndGetRelativePath(name: String): String | 保存某个上传文件, 并返回其相对路径 55 | 56 | 5. 文件路径与url的相互转换 57 | 58 | 方法 | 作用 59 | --- | --- 60 | getFileRelativePath(file: String): String | 获得指定文件的相对路径 61 | getUploadUrl(relativePath: String): String | 获得上传文件的url -------------------------------------------------------------------------------- /doc/http/response.cn.md: -------------------------------------------------------------------------------- 1 | # 响应对象 2 | 3 | jkmvc的响应类是 `net.jkcode.jkmvc.http.HttpResponse` 4 | 5 | 他是继承并代理 `javax.servlet.http.HttpServletResponse` 6 | 7 | 因此,你可以使用从 `HttpServletResponse` 继承的方法 8 | 9 | 下面仅列出响应类特有的属性与方法 10 | 11 | 1. 渲染的方法 12 | 13 | 方法 | 作用 14 | --- | --- 15 | setStatus(status: Int): Unit | 设置响应状态码 16 | renderView(view: View): Unit | 响应视图 17 | renderHtml(content: String): Unit | 响应html 18 | renderText(content: String): Unit | 响应文本 19 | renderJson(content: Any): Unit | 响应json 20 | renderXml(content: Any): Unit | 响应xml 21 | renderJs(content: Any): Unit | 响应js 22 | renderFile(file: File): Unit | 响应文件 23 | renderFile(file: String): Unit | 响应文件 24 | 25 | 2. 操作缓存的方法 26 | 27 | 方法 | 作用 28 | --- | --- 29 | setCache(expires: Long): HttpResponse | 设置响应缓存 30 | getCache(): String? | 获得缓存时间 31 | 32 | 3. 操作cookie的方法 33 | 34 | 方法 | 作用 35 | --- | --- 36 | deleteCookie(name: String): HttpResponse | 删除cookie 37 | setCookie(name: String, value: String, expiry: Int? = null): HttpResponse | 设置cookie值 38 | setCookies(data: Map, expiry: Int? = null): HttpResponse | 设置cookie值 -------------------------------------------------------------------------------- /doc/http/response.md: -------------------------------------------------------------------------------- 1 | # Response object 2 | 3 | Response class is `net.jkcode.jkmvc.http.HttpResponse`, which is implements `javax.servlet.http.HttpServletResponse` 4 | 5 | So, you can call all methods that inherited from `HttpServletResponse` 6 | 7 | Here is a list of the properties and methods special in `HttpResponse` 8 | 9 | 1. Render methods 10 | 11 | method | usage 12 | --- | --- 13 | setStatus(status: Int): Unit | set response status code 14 | renderView(view: View): Unit | render view 15 | renderHtml(content: String): Unit | render html 16 | renderText(content: String): Unit | render text 17 | renderJson(content: Any): Unit | render json 18 | renderXml(content: Any): Unit | render xml 19 | renderJs(content: Any): Unit | render js 20 | renderFile(file: File): Unit | render file 21 | renderFile(file: String): Unit | render file 22 | 23 | 2. Cache Manipulating methods 24 | 25 | method | usage 26 | --- | --- 27 | setCache(expires: Long): HttpResponse | set cache time 28 | getCache(): String? | get cache time 29 | 30 | 3. Cookie Manipulating methods 31 | 32 | method | usage 33 | --- | --- 34 | deleteCookie(name: String): HttpResponse | delete cookie 35 | setCookie(name: String, value: String, expiry: Int? = null): HttpResponse | set a cookie 36 | setCookies(data: Map, expiry: Int? = null): HttpResponse | set multiple cookies -------------------------------------------------------------------------------- /doc/http/session.cn.md: -------------------------------------------------------------------------------- 1 | # 会话 2 | 3 | ## 会话配置 4 | 5 | vim src/main/resources/session.properties 6 | 7 | ``` 8 | # 认证处理的类型: 1 session: 使用session来保存登录用户 2 token: 使用token来保存登录用户 9 | authType = session 10 | # token缓存方式, 仅在 authType = token 时有效 11 | tokenCache = jedis 12 | # 用户模型的类,必须是实现 IAuthUserModel 13 | userModel = net.jkcode.jkmvc.example.model.UserModel 14 | # 用户名字段 15 | usernameField = username 16 | # 密码字段 17 | passwordField = password 18 | # 密码加密的盐 19 | passwordSalt = .$%^#*!)06zth 20 | ``` 21 | 22 | 其中`userModel`是开发者自己实现的用户模型类, 表示当前会话中登录的用户 23 | 24 | ## UserModel实现 25 | 必须实现接口`IAuthUserModel`, 参考demo 26 | 27 | ``` 28 | class UserModel(id:Int? = null): Orm(id), IAuthUserModel { 29 | // 伴随对象就是元数据 30 | companion object m: OrmMeta(UserModel::class, "用户模型", "user", "id"){} 31 | 32 | // 代理属性读写 33 | public var id:Int by property() // 用户编号 34 | 35 | public var username:String by property() // 用户名 36 | 37 | public var password:String by property() // 密码 38 | 39 | public var name:String by property() // 中文名 40 | 41 | public var age:Int by property() // 年龄 42 | 43 | public var avatar:String by property() // 头像 44 | 45 | } 46 | ``` -------------------------------------------------------------------------------- /doc/http/session.md: -------------------------------------------------------------------------------- 1 | # Session 2 | 3 | ## Session configuration 4 | 5 | vim src/main/resources/session.properties 6 | 7 | ``` 8 | # Auth type: 1 session: use session to store login user 2 token: use token to store user 9 | authType = session 10 | # Token cache type, only use when authType = token 11 | tokenCache = jedis 12 | # UserModel class,must implements IAuthUserModel 13 | userModel = net.jkcode.jkmvc.example.model.UserModel 14 | # username field name 15 | usernameField = username 16 | # password field name 17 | passwordField = password 18 | # password salt 19 | passwordSalt = .$%^#*!)06zth 20 | ``` 21 | 22 | `userModel` is UserModel class, which represents the logined user, and you should define it yourself. 23 | 24 | ## UserModel class 25 | must implements`IAuthUserModel`, there is a demo: 26 | 27 | ``` 28 | class UserModel(id:Int? = null): Orm(id), IAuthUserModel { 29 | // orm meta 30 | companion object m: OrmMeta(UserModel::class, "User model", "user", "id"){} 31 | 32 | // delegate property access 33 | public var id:Int by property() // 用户编号 34 | 35 | public var username:String by property() // 用户名 36 | 37 | public var password:String by property() // 密码 38 | 39 | public var name:String by property() // 中文名 40 | 41 | public var age:Int by property() // 年龄 42 | 43 | public var avatar:String by property() // 头像 44 | 45 | } 46 | ``` -------------------------------------------------------------------------------- /doc/http/upload.cn.md: -------------------------------------------------------------------------------- 1 | # 上传文件 2 | 3 | jkmvc对上传包servlet3.0的`javax.servlet.http.Part`进行了二次封装,并提供了便捷的api来处理上传文件。 4 | 5 | ## 1 上传配置 6 | 7 | vim src/main/resources/upload.properties 8 | 9 | ``` 10 | # 上传文件的保存目录,末尾不要带/ 11 | uploadRootDirectory=/var/www/upload 12 | # 编码 13 | encoding=gbk 14 | # 禁止上传的文件扩展名, 以逗号分隔 15 | forbiddenExt = jsp,jspx,exe,sh,php,py 16 | # 访问上传文件的域名 17 | uploadDomain=http://localhost:8081/jkmvc/upload 18 | ``` 19 | 20 | 配置项 | 作用 21 | --- | --- 22 | uploadRootDirectory | 上传的根目录,由jkmvc接收的上传文件都保存到该目录下,同时为了能访问这些文件,你需要基于该目录建立http文件服务器 23 | uploadDomain | 访问上传文件的域名,结合它可以获得访问上传文件的url 24 | 25 | ## 2 处理上传文件 26 | 27 | ### 2.1 上传的表单 28 | 29 | 表单用 `enctype="multipart/form-data"` 来修饰 30 | 31 | ``` 32 |
" method="post" enctype="multipart/form-data"> 33 |
34 | 35 | 36 |
37 | 38 |
39 | ``` 40 | 41 | ### 2.2 接收上传文件 42 | 43 | ``` 44 | /** 45 | * 上传头像 46 | */ 47 | public fun uploadAvatar() 48 | { 49 | // 查询单个用户 50 | val id: Int = req["id"]!! 51 | val user = UserModel(id) 52 | if(!user.isLoaded()){ 53 | res.renderHtml("用户[" + req["id"] + "]不存在") 54 | return 55 | } 56 | 57 | // 检查并处理上传文件 58 | if(req.isUpload){ // 检查上传请求 59 | user.avatar = req.storePartFileAndGetRelativePath("avatar") 60 | user.update() 61 | } 62 | 63 | // 重定向到详情页 64 | redirect("user/detail/$id"); 65 | } 66 | ``` 67 | 68 | ## 3 下载文件 69 | 70 | 文件上传后,当然需要被下载。文件下载有2种方式 71 | 72 | ### 3.1 java提供的下载 73 | 74 | 直接在Controller中调用`res.renderFile(file: File)` 来向浏览器响应文件 75 | 76 | ### 3.2 文件服务器提供的下载 77 | 78 | 我们可以使用apache/nginx来提供文件下载服务,直接指定服务目录为上传目录 79 | 80 | ``` 81 | location ~ \.(gif|jpg|jpeg|.js|.css)$ { 82 | root /var/www/upload; 83 | index index.html index.htm; 84 | } 85 | 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- /doc/orm/benchmark/code_delete.md: -------------------------------------------------------------------------------- 1 | # 场景delete代码 2 | 3 | jkorm很简单 4 | 5 | mybatis很啰嗦, 多了dao与mapper.xml, 烦 6 | 7 | => mybatis分层多两层, 代码是jkorm的两倍 8 | 9 | ## 生成sql 10 | 1 jkorm 生成sql 11 | ``` 12 | SELECT * FROM `employee` `employee` WHERE `id` = 1 LIMIT 1 13 | DELETE `employee` FROM `employee` `employee` WHERE `id` = 1 14 | ``` 15 | 16 | 2 mybatis 生成sql 17 | ```` 18 | ==> Preparing: select * from employee where id = ? 19 | ==> Parameters: 1(Integer) 20 | <== Columns: id, title, email, gender, dep_id 21 | <== Row: 1, Mr vTmu8, Mr tOrDJ@qq.com, 男, 0 22 | <== Total: 1 23 | 24 | ==> Preparing: delete from employee where id=? 25 | ==> Parameters: 1(Integer) 26 | <== Updates: 1 27 | ``` 28 | 29 | ## jkorm 30 | JkormBenchmarkPlayer 31 | 32 | ``` 33 | /** 34 | * 删除 35 | */ 36 | public fun delete(i: Int): Int { 37 | val emp = Employee.findByPk(i) 38 | if (emp != null) 39 | emp.delete() 40 | 41 | return 2 42 | } 43 | ``` 44 | 45 | ## mybatis 46 | 47 | ### 1 调用层 48 | MybatisBenchmarkPlayer 49 | ``` 50 | /** 51 | * 删除 52 | */ 53 | public fun delete(i: Int): Int { 54 | // 先查后删 55 | val emp = empDao.getEmpById(i) 56 | if (emp != null) 57 | empDao.delEmpById(i); 58 | session.commit() 59 | 60 | return 2 61 | } 62 | ``` 63 | 64 | ### 2 dao层 65 | 66 | EmployeeDao 67 | ``` 68 | // update中用到过的select, 不重复写 69 | 70 | /** 71 | * 删 72 | * @param id 73 | * @return 74 | */ 75 | Long delEmpById(Integer id); 76 | ``` 77 | 78 | ### 3 mapper.xml层 79 | 80 | EmployeeMapper.xml 81 | ``` 82 | // update中用到过的select, 不重复写 83 | 84 | 85 | delete from employee where id=#{id} 86 | 87 | ``` -------------------------------------------------------------------------------- /doc/orm/benchmark/code_getEmpsByIds.md: -------------------------------------------------------------------------------- 1 | # 场景getEmpsByIds代码 2 | 3 | jkorm很简单 4 | 5 | mybatis很啰嗦, 多了dao与mapper.xml, 烦 6 | 7 | => mybatis分层多两层, 代码是jkorm的两倍 8 | 9 | ## 生成sql 10 | 1 jkorm 生成sql 11 | ``` 12 | SELECT * FROM `employee` `employee` WHERE `id` IN (185, 837, 248) 13 | ``` 14 | 15 | 2 mybatis 生成sql 16 | ``` 17 | ==> Preparing: select * from employee where id in( ? , ? , ? ) 18 | ==> Parameters: 353(Integer), 973(Integer), 724(Integer) 19 | <== Total: 0 20 | ``` 21 | 22 | 23 | ## jkorm 24 | JkormBenchmarkPlayer 25 | 26 | ``` 27 | /** 28 | * 多id查询(循环多id拼where in) 29 | */ 30 | public fun getEmpsByIds(i: Int): Int { 31 | val ids = listOf(randomInt(1000), randomInt(1000), randomInt(1000)) 32 | val emps = Employee.queryBuilder().where("id", ids).findModels() 33 | return 1 34 | } 35 | 36 | ``` 37 | 38 | ## mybatis 39 | 40 | ### 1 调用层 41 | MybatisBenchmarkPlayer 42 | ``` 43 | /** 44 | * 多id查询(循环多id拼where in) 45 | */ 46 | public fun getEmpsByIds(i: Int): Int { 47 | val ids = listOf(randomInt(1000), randomInt(1000), randomInt(1000)) 48 | val emps = empDao.getEmpsByConditionForeach(ids) 49 | 50 | return 1 51 | } 52 | ``` 53 | 54 | ### 2 dao层 55 | 56 | EmployeeDao 57 | ``` 58 | /** 59 | * 多id查询(循环多id拼where in) 60 | * @param ids 61 | * @return 62 | */ 63 | List getEmpsByConditionForeach(@Param("ids") List ids); 64 | ``` 65 | 66 | ### 3 mapper.xml层 67 | 68 | EmployeeMapper.xml 69 | ``` 70 | 88 | ``` -------------------------------------------------------------------------------- /doc/orm/benchmark/code_update.md: -------------------------------------------------------------------------------- 1 | # 场景update代码 2 | 3 | jkorm很简单 4 | 5 | mybatis很啰嗦, 多了dao与mapper.xml, 烦 6 | 7 | => mybatis分层多两层, 代码是jkorm的两倍 8 | 9 | ## 生成sql 10 | 1 jkorm 生成sql 11 | ``` 12 | SELECT * FROM `employee` `employee` WHERE `id` = 1 LIMIT 1 13 | UPDATE `employee` `employee` SET `title` = 'Miss zJxbk' WHERE `id` = 1 14 | ``` 15 | 16 | 2 mybatis 生成sql 17 | ``` 18 | ==> Preparing: select * from employee where id = ? 19 | ==> Parameters: 1(Integer) 20 | <== Columns: id, title, email, gender, dep_id 21 | <== Row: 1, Mr tOrDJ, Mr tOrDJ@qq.com, 男, 0 22 | <== Total: 1 23 | 24 | ==> Preparing: update employee set title=?, email=?, gender=? where id=? 25 | ==> Parameters: Mr vTmu8(String), Mr tOrDJ@qq.com(String), 男(String), 1(Integer) 26 | <== Updates: 1 27 | ``` 28 | 29 | ## jkorm 30 | JkormBenchmarkPlayer 31 | 32 | ``` 33 | /** 34 | * 更新 35 | */ 36 | public fun update(i: Int): Int { 37 | val emp = Employee.findByPk(i)!! 38 | val isMan = emp.gender == "男" 39 | emp.title = (if (isMan) "Mr " else "Miss ") + randomString(5) 40 | emp.update() 41 | return 2 42 | } 43 | ``` 44 | 45 | ## mybatis 46 | 47 | ### 1 调用层 48 | MybatisBenchmarkPlayer 49 | ``` 50 | /** 51 | * 更新 52 | */ 53 | public fun update(i: Int): Int { 54 | val emp: Employee = empDao.getEmpById(i); 55 | val isMan = emp.gender == "男" 56 | emp.title = (if (isMan) "Mr " else "Miss ") + randomString(5); 57 | empDao.updateEmp(emp) 58 | session.commit() 59 | 60 | return 2 61 | } 62 | ``` 63 | 64 | ### 2 dao层 65 | 66 | EmployeeDao 67 | ``` 68 | /** 69 | * 查单个 70 | * @param id 71 | * @return 72 | */ 73 | Employee getEmpById(Integer id); 74 | 75 | /** 76 | * 改 77 | * @param emp 78 | * @return 79 | */ 80 | Long updateEmp(Employee emp); 81 | EmployeeDao 82 | ``` 83 | 84 | ### 3 mapper.xml层 85 | 86 | EmployeeMapper.xml 87 | ``` 88 | 91 | 92 | 93 | update employee 94 | set title=#{title}, email=#{email}, gender=#{gender} 95 | where id=#{id} 96 | 97 | ``` -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077bfac6be12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077bfac6be12.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077bfbbc0dc6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077bfbbc0dc6.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077bfd9c9520.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077bfd9c9520.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077bff47755e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077bff47755e.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c08d57841.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c08d57841.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c0981c0eb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c0981c0eb.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c0c3ab9e8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c0c3ab9e8.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c0ce79345.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c0ce79345.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c139b5960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c139b5960.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c15079643.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c15079643.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c15fc9f9d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c15fc9f9d.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c16ded504.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c16ded504.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c18017f46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c18017f46.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c19e43fe7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c19e43fe7.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c1a98f781.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c1a98f781.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c1c088904.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c1c088904.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c1d234d6f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c1d234d6f.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c1de61b60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c1de61b60.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c1f1ba059.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c1f1ba059.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c2052f6c4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c2052f6c4.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/6077c2129b0c2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/6077c2129b0c2.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790aecb5bb9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790aecb5bb9.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790afe9f191.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790afe9f191.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b10b2c28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b10b2c28.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b1f350e7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b1f350e7.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b3076858.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b3076858.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b466ae99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b466ae99.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b5ad0bb4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b5ad0bb4.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b7c3038a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b7c3038a.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b872267e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b872267e.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790b9b83cda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790b9b83cda.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790bacc9dea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790bacc9dea.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790bcd07362.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790bcd07362.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790bd9eb306.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790bd9eb306.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790beb40d0a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790beb40d0a.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790bf79b7e2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790bf79b7e2.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c102c4d0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c102c4d0.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c2509043.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c2509043.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c449efb9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c449efb9.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c4e87cf8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c4e87cf8.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c5f75e94.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c5f75e94.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/60790c6f11687.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/60790c6f11687.png -------------------------------------------------------------------------------- /doc/orm/benchmark/img/607917878ca90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/doc/orm/benchmark/img/607917878ca90.png -------------------------------------------------------------------------------- /doc/orm/cbrelation.cn.md: -------------------------------------------------------------------------------- 1 | # 基于回调的关联关系 2 | 3 | 基于db实现的关联关系, 是通过拼接主键外键条件的联查sql, 来查询关联对象的 4 | 5 | 而基于回调实现的关联关系, 是使用回调来查询关联对象的. 6 | 7 | Jkmvc的ORM模块,支持2种回调实现的关联关系:`hasMany`, `hasOne` 8 | 9 | ## 1 hasMany 有多个的关系 10 | 11 | `hasMany`关系,如 `Post` 模型从属于`User`模型(就是一个文章从属于一个用户)。从用户角度,`User` 模型有多个`Post`模型(就是一个用户有多个文章),我们可以这样定义`hasMany`关系: 12 | 13 | ``` 14 | hasMany("posts" /* 关系名 */, UserModel::id /* 主模型的主键的getter */, PostModel::userId /* 从对象的外键的getter */){ // 批量获取关联对象的回调 15 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 16 | } 17 | ``` 18 | 让我们来看看方法定义: 19 | 20 | ``` 21 | fun IOrmMeta.cbHasMany(name: String, pkGetter: (M)->K, fkGetter: (R)->K, relatedSupplier:(List) -> List): IOrmMeta 22 | ``` 23 | 24 | 接上面的例子,`User`模型的的关系定义如下 25 | 26 | ``` 27 | hasMany("posts", UserModel::id, PostModel::userId){ 28 | // 自定义的查询, 可以是rpc 29 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 30 | } 31 | ``` 32 | 33 | 以后,你就可以通过`user["posts"]`来访问`Post`模型(就是某用户的文章啦) 34 | 35 | ## 2 hasOne 有一个的关系 36 | 37 | `hasOne`关系基本等同于`hasMany`关系。只是`hasOne`关系是一对一的,而`hasMany`是一对多。如果一个用户只有一个文章,则我们的代码是这样的: 38 | 39 | ``` 40 | hasOne("story", UserModel::id, PostModel::userId){ 41 | // 自定义的查询, 可以是rpc 42 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 43 | } 44 | ``` 45 | 46 | # 关联对象查询 47 | 48 | 使用 `OrmQueryBuilder.with()` 来联查关联对象 49 | 50 | ``` 51 | // 联查一对一关联对象 52 | val post = PostModel.queryBuilder() 53 | .with("author") 54 | .where("id", "=", 20) 55 | .findModel(); 56 | 57 | // ------------------------------- 58 | // 联查一对多关联对象 59 | val user = UserModel.queryBuilder() 60 | .with("posts") 61 | .where("id", "=", 20) 62 | .findModel(); 63 | ``` -------------------------------------------------------------------------------- /doc/orm/cbrelation.md: -------------------------------------------------------------------------------- 1 | # Relations using callback 2 | 3 | Relations using db query related objects with sql 4 | 5 | Relations using callback query related objects using callback 6 | 7 | Jkmvc ORM supports 2 types of callback relations: `hasMany`, `hasOne`. 8 | 9 | ## 1 hasMany 10 | 11 | The standard `hasMany` relation, eg: a post belongs to a user. From the user's perspective, a user has many posts. A hasMany relation is defined below: 12 | 13 | ``` 14 | hasMany("posts" /* relation name */, UserModel::id /* this model's primary key getter */, PostModel::userId /* related model's foreign key getter */){ // callback to query related objects 15 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 16 | } 17 | ``` 18 | Let's have a look at the method definition 19 | 20 | ``` 21 | fun IOrmMeta.cbHasMany(name: String, pkGetter: (M)->K, fkGetter: (R)->K, relatedSupplier:(List) -> List): IOrmMeta 22 | ``` 23 | Again, for our user and post example, this would look like the following in the user model: 24 | 25 | ``` 26 | hasMany("posts", UserModel::id, PostModel::userId){ 27 | // you can also call rpc 28 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 29 | } 30 | ``` 31 | 32 | Using the above, the posts could be access using `user["posts"]`. 33 | 34 | ## 2 hasOne 35 | 36 | A `hasOne` relation is almost identical to a `hasMany` relation. In a `hasOne` relation, there can be 1 and only 1 relation (rather than 1 or more in a hasMany). If a user can only have one post or story, rather than many then the code would look like this: 37 | 38 | ``` 39 | hasOne("story", UserModel::id, PostModel::userId){ 40 | // you can also call rpc 41 | PostModel.queryBuilder().where("user_id", "IN", it).findModels() 42 | } 43 | ``` 44 | 45 | # Query related object 46 | 47 | use `OrmQueryBuilder.with()` to query related object 48 | 49 | ``` 50 | // Query 1:1 related object, it will merge into 1 sql 51 | val post = PostModel.queryBuilder() 52 | .with("author") 53 | .where("id", "=", 20) 54 | .findModel(); 55 | 56 | // ------------------------------- 57 | // Query 1:N related objects, it will split into 2 sql 58 | val user = UserModel.queryBuilder() 59 | .with("posts") 60 | .where("id", "=", 20) 61 | .findModel(); 62 | ``` -------------------------------------------------------------------------------- /doc/orm/getting_started.cn.md: -------------------------------------------------------------------------------- 1 | # ORM 2 | 3 | ORM:对象与关系映射, 是在数据库与你的应用之间架设了一层。 你可以用面向对象的方式,来操作数据库。通俗一点说,在数据库里面有一个表,那么在应用程序里就有一个类与这个表相对应,类中的成员变量名与表的列名一一对应,该类的实例对应表中的一行数据。 4 | 5 | Jkmvc提供一个强大的ORM模块,它采用的是`active record`设计模式,同时它可以自行维护表的列信息,无需手动配置。 6 | 7 | ORM 允许你像操作java对象一样,操作数据库。一旦你定义好元数据, ORM 就可以让你在不用写一句sql的前提下,从数据库中读写数据。 8 | 9 | 通过定义好模型之间的关联关系,ORM 会大量减少增删改查的代码。所有关联关系都会被自动处理,你只需要像访问普通对象属性一样,去访问关联数据。 10 | 11 | ## 开始 12 | 13 | ## 1 添加依赖 14 | 1. gradle 15 | ``` 16 | compile "net.jkcode.jkmvc:jkmvc-orm:1.9.0" 17 | ``` 18 | 19 | 2. maven 20 | ``` 21 | 22 | net.jkcode.jkmvc 23 | jkmvc-orm 24 | 1.9.0 25 | 26 | ``` 27 | 28 | ## 2 配置 29 | 在使用 ORM 之前,你必须先定义好数据库配置: 30 | 31 | vim src/main/resources/dataSources.yaml 32 | 33 | ``` 34 | # 数据库名 35 | default: 36 | driverClassName: com.mysql.jdbc.Driver 37 | url: jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8 38 | username: root 39 | password: root 40 | ``` 41 | 42 | Java 使用的是驼峰命名,因此 Jkmvc 会根据上面的 `columnUnderline` and `columnUpperCase` 配置项,在 ORM 对象与数据库字段名之间,自动转换命名。 43 | 44 | vim src/main/resources/db.yaml 45 | 46 | ``` 47 | # 是否调试 48 | debug: true 49 | # 分库的数据库名, 会使用 sharding-jdbc 50 | shardingDbs: shardorder 51 | # 字段名是下划线命名的数据库名, 用于逗号分隔 52 | columnUnderlineDbs: default,test 53 | # 字段名全大写的数据库名, 用逗号分隔 54 | columnUpperCaseDbs: 55 | ``` 56 | 57 | 现在你就可以创建 [模型](model.cn.md),并[使用 ORM](using.cn.md)。 58 | -------------------------------------------------------------------------------- /doc/orm/getting_started.md: -------------------------------------------------------------------------------- 1 | # ORM 2 | 3 | ORM:Object Relational Mapper, is the layer that sits between your database and your application. You can manipulate database in the Object-Oriented Programming way. 4 | 5 | Jkmvc provides a powerful ORM module that uses the active record pattern and database introspection to determine a model's column information. 6 | 7 | The ORM allows for manipulation and control of data within a database as though it was a java object. Once you define the meta data, ORM allows you to pull data from your database, manipulate the data in any way you like, and then save the result back to the database without the use of SQL. 8 | 9 | By creating relationships between models that follow convention over configuration, much of the repetition of writing queries to create, read, update, and delete information from the database can be reduced or entirely removed. All of the relationships can be handled automatically by the ORM library and you can access related data as standard object properties. 10 | 11 | ## Getting started 12 | 13 | ## 1 Add dependency 14 | 1. gradle 15 | ``` 16 | compile "net.jkcode.jkmvc:jkmvc-orm:1.9.0" 17 | ``` 18 | 19 | 2. maven 20 | ``` 21 | 22 | net.jkcode.jkmvc 23 | jkmvc-orm 24 | 1.9.0 25 | 26 | ``` 27 | 28 | ## 2 Configure 29 | Before we use ORM, we must define database configuration. 30 | 31 | vim src/main/resources/dataSources.yaml 32 | 33 | ``` 34 | # database name 35 | default: 36 | driverClassName: com.mysql.jdbc.Driver 37 | url: jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8 38 | username: root 39 | password: root 40 | ``` 41 | 42 | Java use camel-case naming, so jkmvc will transform between ORM object's property name and table's column name, according to configuration item `columnUnderline` and `columnUpperCase` 43 | 44 | vim src/main/resources/db.yaml 45 | 46 | ``` 47 | debug: true 48 | # sharding database names, it uses sharding-jdbc 49 | shardingDbs: shardorder 50 | # Column name is underlined for these database names 51 | columnUnderlineDbs: default,test 52 | # Column name is all uppercase for these database names 53 | columnUpperCaseDbs: 54 | ``` 55 | 56 | You can now create your [model](model.md) and [use ORM](using.md). 57 | -------------------------------------------------------------------------------- /doc/orm/jphp.cn.md: -------------------------------------------------------------------------------- 1 | # 概述 2 | jkmvc 整合jphp技术, 支持同构异语言(java/php)及相互调用, 以便支持更多的动态性, 可以用php来写db代码或模型代码, 主要是为了方便php controller调用。 3 | 4 | 一般而言, 整合jphp(动态语言)给java平台添加动态性的动机, 主要是用在网关或视图引擎上, 特别是网关上的路由、转发、聚合服务、熔断降级限流等的动态修改, 代码修改无须重启java服务, 同时php也会编译为字节码来保证性能, 另外php从语法、学习成本、使用成本、web应用、流行度、招聘等都是较好选择, 因此该整合技术是兼顾了效率与性能的较"实惠"的技术。 5 | 6 | # 使用 7 | 8 | 1. db操作 9 | ```php 10 | query("select * from user"); 15 | echo "查找所有用户\n"; 16 | var_dump($users); 17 | $uid = 0; 18 | if($users){ 19 | $uid = $users[0]['id']; 20 | echo "更新用户: $uid\n"; 21 | $db->execute("update user set age = age + 1 where id = ?", [$uid]); 22 | } 23 | ``` 24 | 25 | 2. model操作 26 | ```php 27 | use php\jkmvc\orm\Model; 28 | $uid = 1; 29 | $model = new Model("net.jkcode.jkmvc.tests.model.UserModel"); 30 | 31 | echo "创建用户: $uid\n"; 32 | $model->id = 10; 33 | $model->age = 10; 34 | $model->username = 'shi'; 35 | $model->password = 'shi'; 36 | $model->name = 'shi'; 37 | $model->create(); 38 | 39 | echo "加载用户: $uid\n"; 40 | $model->load($uid); 41 | echo "$model \n"; 42 | 43 | echo "查找用户: $uid\n"; 44 | $user = $model->find($uid); 45 | var_dump($user); 46 | ``` 47 | 48 | 3. QueryBuilder调用 49 | 3.1 从 Db 中引用 QueryBuilder 50 | ```php 51 | $uid = 1; 52 | echo "查找用户: $uid\n"; 53 | $qb = $db->queryBuilder(); 54 | $user = $qb->table("user", null)->where('id', '=', $uid)->findRow(); 55 | var_dump($user); 56 | ``` 57 | 58 | 3.2 从 Model 中引用 QueryBuilder 59 | ```php 60 | echo "查找用户: $uid\n"; 61 | $qb = $model->queryBuilder(); 62 | $user = $qb->with('home')->where('user.id', '=', $uid)->findModel(); 63 | echo "$user \n"; 64 | ``` -------------------------------------------------------------------------------- /doc/orm/model.cn.md: -------------------------------------------------------------------------------- 1 | # 模型 2 | 3 | ## 1 创建模型 4 | 5 | 假如你要创建表 `user` 相关的模型,只需要按照以下的语法来创建类 `UserModel`: 6 | 1. 继承类 `net.jkcode.jkmvc.orm.Orm` 7 | 2. 定义伴随对象为元数据 8 | 3. 使用`property()`来定义代理属性 9 | 10 | ``` 11 | class UserModel(id:Int? = null): Orm(id) { 12 | // 伴随对象就是元数据 13 | companion object m: OrmMeta(UserModel::class /* 模型类 */, "User Model" /* 模型名 */, "user" /* 表名 */, "id" /* 表主键 */){} 14 | 15 | // 代理属性读写 16 | public var id:Int by property() // 用户编号 17 | 18 | public var username:String by property() // 用户名 19 | 20 | public var password:String by property() // 密码 21 | } 22 | ``` 23 | 24 | ## 2 元数据 25 | 26 | ORM 的元数据,就是模型相关的数据库信息,包含以下内容:数据库名, 表名, 主键等等。 27 | 28 | ORM 的元数据是用类 `net.jkcode.jkmvc.orm.OrmMeta` 来表示的,它有以下的属性: 29 | 1. `model`: 模型类 30 | 2. `label`: 模型名, 默认值是模型名 31 | 3. `table`: 表名, 默认值是模型名 32 | 4. `primaryKey`: 主键, 默认值是 `id` 33 | 5. `dbName`:数据库名, 定义在配置文件`dataSources.yaml` 中, 默认值是 `default` 34 | 35 | 当你创建 `net.jkcode.jkmvc.orm.OrmMeta` 对象时,你必须传递上述的属性,就如下面的代码: 36 | 37 | ``` 38 | OrmMeta(UserModel::class /* 模型类 */, "User Model" /* 模型名 */, "user" /* 表名 */, "id" /* 表主键 */, "default" /* 数据库名 */){} 39 | ``` 40 | 41 | ## 3 为模型绑定元数据 42 | 43 | 对每个模型,定义伴随对象为元数据 44 | 45 | ``` 46 | companion object m: OrmMeta(UserModel::class, "User Model", "user", "id"){} 47 | ``` 48 | 49 | ## 4 自动生成模型代码 50 | 51 | Jkmvc提供了`net.jkcode.jkmvc.util.ModelGenerator` 来自动生成模型代码 52 | 53 | ``` 54 | val generator = ModelGenerator("/home/shi/code/java/jkmvc/jkmvc-example/src/main/kotlin" /* 源码目录 */, "net.jkcode.jkmvc.example.model" /* 包路径 */ "default" /* 数据库名 */, "shijianhang" /* 作者 */) 55 | generator.genenateModelFile("UserModel" /* 模型类名 */, "用户模型" /* 模型名 */, "user" /* 表名 */) 56 | ``` 57 | 58 | 它会根据指定的数据库与指定的表,来生成模型代码。其中根据`db.yaml`中的配置项 `columnUnderline` 和 `columnUpperCase` 来将数据库的字段名转换为对象属性名。 59 | 60 | 生成模型代码如下: 61 | 62 | ``` 63 | class UserModel(id:Int? = null): Orm(id) { 64 | // 伴随对象就是元数据 65 | companion object m: OrmMeta(UserModel::class /* 模型类 */, "User Model" /* 模型名 */, "user" /* 表名 */, "id" /* 表主键 */){} 66 | 67 | // 代理属性读写 68 | public var id:Int by property() // 用户编号 69 | 70 | public var username:String by property() // 用户名 71 | 72 | public var password:String by property() // 密码 73 | } 74 | ``` -------------------------------------------------------------------------------- /doc/orm/validation.cn.md: -------------------------------------------------------------------------------- 1 | # 校验 2 | 3 | Orm模型与[Validation](../common/validation/validation.cn.md)库紧密集成,该库提供了异常类`net.jkcode.jkmvc.validator.ValidateException`,来帮助您快速处理基本CRUD操作的验证错误。 4 | 5 | ## 1 定义校验规则 6 | 7 | 验证规则是定义在`OrmMeta::rules`属性中。 这个属性包含多个字段的规则,每个规则都是由`Validation.execute(exp:String, value:Any, binds:Map)`来执行。 8 | 9 | 每个规则是`net.jkcode.jkmvc.orm.RuleValidator`对象,它有2个属性: 10 | 1. `label`:字段中文名 11 | 2. `rule`:验证表达式, 可包含多个, 用空格分割 12 | 13 | 有两种方法来定义规则 14 | 1.重写`OrmMeta :: rules`属性 15 | 16 | ``` 17 | public override val rules: MutableMap = hashMapOf( 18 | "userId" to RuleValidator("用户", "notEmpty"), 19 | "age" to RuleValidator( "年龄", "digit() between(1,120)") 20 | ) 21 | ``` 22 | 23 | 2. 调用 `OrmMeta::addRule(field: String, label:String, rule: String?)` 方法来添加单个规则 24 | 25 | ``` 26 | // 添加标签 + 规则 27 | addRule("name", "姓名", "notEmpty"); 28 | addRule("age", "年龄", "digit() between(1,120)"); 29 | ``` 30 | 31 | ## 2执行验证 32 | 33 | Jkmvc通过`Orm.validate()`方法执行验证。 34 | 35 | 它会遍历`OrmMeta::rules`中的每个字段的规则,并使用`Validation.execute(exp:String, value:Any, binds:Map)`方法来对字段值执行规则 36 | 37 | 该方法有3个实际参数: 38 | 1. `exp`: 字段规则 39 | 2. `value`: 字段值 40 | 3. `binds`: 其他字段的值 41 | 42 | ## 3自动验证 43 | 44 | 当调用`Orm.validate()`/`Orm::save()`/`Orm::update()`方法时,模型都会自动调用`Orm.validate()`来验证自己的数据。 因此当发现模型的数据无效时,会抛出校验异常`net.jkcode.jkmvc.validator.ValidateException`。 45 | 46 | ``` 47 | public fun create() 48 | { 49 | try 50 | { 51 | val user = UserModel() 52 | user.username = 'invalid username'; 53 | user.save(); 54 | } 55 | catch (e: ValidateException) 56 | { 57 | // 处理校验异常 58 | } 59 | } 60 | ``` -------------------------------------------------------------------------------- /doc/orm/validation.md: -------------------------------------------------------------------------------- 1 | # Validation 2 | 3 | Orm models are tightly integrated with the [Validation](../common/validation/validation.md) library which comes with a very flexible `net.jkcode.jkmvc.validator.ValidateException` that helps you quickly handle validation errors from basic CRUD operations. 4 | 5 | ## 1 Defining Rules 6 | 7 | Validation rules are defined in the `OrmMeta::rules` property. This property is the rules for each field to be executed by `Validation.execute(exp:String, value:Any, binds:Map)` method. 8 | 9 | Each rule is `net.jkcode.jkmvc.orm.RuleValidator` object, which has 2 properties: 10 | 1. `label`: A label is a human-readable version of the field name. 11 | 2. `rule`: A validation expressions, split by space 12 | 13 | There are 2 way to define rules 14 | 1. override `OrmMeta::rules` property 15 | 16 | ``` 17 | public override val rules: MutableMap = hashMapOf( 18 | "userId" to RuleValidator("Id label", "notEmpty"), 19 | "age" to RuleValidator( "Age label", "digit() between(1,120)") 20 | ) 21 | ``` 22 | 23 | 2. call `OrmMeta::addRule(field: String, label:String, rule: String?)` method to add rule 24 | 25 | ``` 26 | // add label and rule for field 27 | addRule("name", "Name label", "notEmpty"); 28 | addRule("age", "Age label", "digit() between(1,120)"); 29 | ``` 30 | 31 | ## 2 Execute validation 32 | 33 | Jkmvc execute validation by `Orm.validate() ` method. 34 | 35 | It will visit each field's rule in `OrmMeta::rules`, and execute the rule on the field's value using `Validation.execute(exp:String, value:Any, binds:Map)` method. 36 | 37 | The method has actual parameter: 38 | 1. `exp`: the field's rule 39 | 2. `value`: the field's value 40 | 3. `binds`: the other fields' values 41 | 42 | ## 3 Automatic Validation 43 | 44 | All models automatically validate their own data by calling `Orm.validate()` when `Orm::save()`, `Orm::update()`, or `Orm::create()` is called. Because of this, you should always expect these methods to throw an `net.jkcode.jkmvc.validator.ValidateException` when the model's data is invalid. 45 | 46 | ``` 47 | public fun create() 48 | { 49 | try 50 | { 51 | val user = UserModel() 52 | user.username = 'invalid username'; 53 | user.save(); 54 | } 55 | catch (e: ValidateException) 56 | { 57 | // handle exception 58 | } 59 | } 60 | ```ivew -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | env=dev 2 | version=4.8.1 3 | jvm_version=1.8 4 | kotlin_version=1.3.21 5 | coroutines_version=0.22.5 6 | dokka_version=0.9.9 7 | gretty_version=2.0.0 8 | netty_version=4.1.36.Final 9 | asynchttpclient_version=2.10.1 10 | jetty_version=9.3.6.v20151106 11 | jetty_jsp_version=9.2.9.v20150224 12 | undertow_version=1.4.14.Final 13 | jkutil_version=1.9.0 14 | jkguard_version=1.9.0 15 | jphp_java_ext_version=0.9.2 16 | org.gradle.daemon=true # 加快编译 17 | org.gradle.jvmargs=-Xdebug -Xrunjdwp:transport=dt_socket,address=5005,suspend=n,server=y 18 | org.gradle.parallel=true 19 | org.gradle.configureondemand=true 20 | -------------------------------------------------------------------------------- /img/actiondetail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/actiondetail.png -------------------------------------------------------------------------------- /img/actionedit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/actionedit.png -------------------------------------------------------------------------------- /img/actionindex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/actionindex.png -------------------------------------------------------------------------------- /img/actionnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/actionnew.png -------------------------------------------------------------------------------- /img/runserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/runserver.png -------------------------------------------------------------------------------- /img/webpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/webpage.png -------------------------------------------------------------------------------- /img/webview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shigebeyond/jkmvc/4b233a0522501f01158e1c0725da138af1c53597/img/webview.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 设置生产环境 3 | sed -i 's/env=dev/env=pro/g' gradle.properties 4 | 5 | # 安装到本地库 6 | #gradle install -x test 7 | gradle :jkmvc-orm:install -x test 8 | gradle :jkmvc-http:install -x test 9 | gradle :jkmvc-server-jetty:install -x test 10 | gradle :jkmvc-tag:install -x test 11 | 12 | # 恢复开发环境 13 | sed -i 's/env=pro/env=dev/g' gradle.properties 14 | -------------------------------------------------------------------------------- /jkmvc-example/Dockerfile: -------------------------------------------------------------------------------- 1 | # 先执行 gradle build -x test -Pall, 后执行: sudo docker build -t jkmvceg .; sudo docker run -d --network host --name jkmvceg jkmvceg 2 | # 访问 http://localhost:8080/ 3 | 4 | # 基础镜像 5 | FROM java 6 | 7 | # 描述 8 | MAINTAINER jkmvceg 9 | 10 | # 复制文件 11 | # 由于add/copy的文件必须使用上下文目录的内容 12 | # COPY build/app/* /opt/xx/ -- wrong: 会将子目录中所有文件复制到/opt/jkmvceg/,从而错误的去掉子目录那层 13 | COPY build/app/*.war /opt/jkmvceg/ 14 | COPY build/app/start-jetty.sh /opt/jkmvceg/ 15 | COPY build/app/javax.servlet-api-3.1.0.jar /opt/jkmvceg/ 16 | COPY build/app/conf /opt/jkmvceg/conf 17 | 18 | # 暴露端口, 跟jetty.yaml端口一样 19 | EXPOSE 8080 20 | 21 | # 启动命令, 要一直运行,否则命令结束会导致容器结束 22 | # CMD ["/bin/sh", "-c", "while true; do sleep 100; done"] # 让进程一直跑, 否则容器会exit 23 | ENTRYPOINT /opt/jkmvceg/start-jetty.sh 24 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/kotlin/net/jkcode/jkmvc/example/controller/WelcomeController.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.example.controller 2 | 3 | import net.jkcode.jkmvc.http.controller.Controller 4 | 5 | /** 6 | * 主页 7 | */ 8 | class WelcomeController: Controller() { 9 | 10 | /** 11 | * 主页 12 | */ 13 | public fun index() { 14 | val content = "hello world
用户管理" 15 | res.renderHtml(content); 16 | } 17 | 18 | /** 19 | * 显示jsp视图 20 | * render jsp view 21 | */ 22 | public fun jsp(){ 23 | res.renderView(view("index" /* view file */, mapOf("name" to "shijianhang") /* view data */)) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /jkmvc-example/src/main/kotlin/net/jkcode/jkmvc/example/model/UserModel.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.example.model 2 | 3 | import net.jkcode.jkmvc.http.session.IAuthUserModel 4 | import net.jkcode.jkmvc.orm.OrmMeta 5 | import net.jkcode.jkmvc.orm.Orm 6 | 7 | /** 8 | * 用户模型 9 | * 10 | * @ClassName: UserModel 11 | * @Description: 12 | * @author shijianhang<772910474@qq.com> 13 | * @date 2017-09-29 6:56 PM 14 | */ 15 | class UserModel(id:Int? = null): Orm(id), IAuthUserModel { 16 | // 伴随对象就是元数据 17 | companion object m: OrmMeta(UserModel::class, "用户模型", "user", "id"){} 18 | 19 | // 代理属性读写 20 | public var id:Int by property() // 用户编号 21 | 22 | public var username:String by property() // 用户名 23 | 24 | public var password:String by property() // 密码 25 | 26 | public var name:String by property() // 中文名 27 | 28 | public var age:Int by property() // 年龄 29 | 30 | public var avatar:String by property() // 头像 31 | 32 | } -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/auth.yaml: -------------------------------------------------------------------------------- 1 | # 认证机制 2 | session: net.jkcode.jkmvc.http.session.SessionAuth 3 | token: net.jkcode.jkmvc.http.session.TokenAuth 4 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/cookie.properties: -------------------------------------------------------------------------------- 1 | expiry = 604800 2 | path = / 3 | domain = localhost 4 | # 仅用于https/ssl 5 | secure = false 6 | # 禁止js脚本读取到cookie, 防止XSS攻击 7 | httponly = false -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/dataSources.yaml: -------------------------------------------------------------------------------- 1 | # 不同数据库的配置 2 | # mysql, 命名: 小写+下划线 3 | default: 4 | # 主库 5 | master: 6 | driverClassName: com.mysql.jdbc.Driver 7 | url: jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf-8 8 | username: root 9 | password: root 10 | # 多个从库, 可省略 11 | slaves: 12 | - 13 | driverClassName: com.mysql.jdbc.Driver 14 | url: jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=utf-8 15 | username: root 16 | password: root 17 | 18 | # sql server, 命名: 小写+下划线 19 | #default: 20 | # master: 21 | # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver 22 | # url: jdbc:sqlserver://192.168.0.56:1433;databaseName=tempdb 23 | # username: sa 24 | # password: '111111' 25 | 26 | # oracle, 命名: 大写+下划线 27 | #default: 28 | # master: 29 | # driverClassName: oracle.jdbc.driver.OracleDriver 30 | # url: jdbc:oracle:thin:@localhost:ORCL 31 | # # schema - oracle的概念,代表一组数据库对象,在 Db.tables 中延迟加载表字段时,用来过滤 DYPT 库的表,可省略,默认值=username 32 | # # schema: admin 33 | # username: admin 34 | # password: 123456 35 | # # 其他属性,格式如:a=1;b=2 36 | # connectionProperties: oracle.jdbc.V8Compatible=true 37 | # # 查询超时(秒) 38 | # queryTimeOut: 0 39 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/example.mysql.sql: -------------------------------------------------------------------------------- 1 | # 用户表 2 | CREATE TABLE IF NOT EXISTS `user` ( 3 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户id', 4 | `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', 5 | `password` varchar(50) NOT NULL DEFAULT '' COMMENT '密码', 6 | `name` varchar(50) NOT NULL DEFAULT '' COMMENT '中文名', 7 | `age` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '年龄', 8 | `avatar` varchar(250) DEFAULT NULL COMMENT '头像', 9 | PRIMARY KEY (`id`) 10 | )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户'; 11 | 12 | # 地址表 13 | CREATE TABLE IF NOT EXISTS `address` ( 14 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址id', 15 | `user_id` int(11) unsigned NOT NULL COMMENT '用户id', 16 | `addr` varchar(50) NOT NULL DEFAULT '' COMMENT '地址', 17 | `tel` varchar(50) NOT NULL DEFAULT '' COMMENT '电话', 18 | `is_home` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否是家庭住址', 19 | PRIMARY KEY (`id`) 20 | ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='地址'; 21 | 22 | # 包裹表 23 | CREATE TABLE IF NOT EXISTS `parcel` ( 24 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '包裹id', 25 | `sender_id` int(11) unsigned NOT NULL COMMENT '寄件人id', 26 | `receiver_id` int(11) unsigned NOT NULL COMMENT '收件人id', 27 | `content` varchar(50) NOT NULL DEFAULT '' COMMENT '寄件内容', 28 | PRIMARY KEY (`id`) 29 | ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8 COMMENT='包裹'; -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/http.yaml: -------------------------------------------------------------------------------- 1 | # 是否调试 2 | debug: true 3 | # 静态文件的扩展名 4 | # static file extension 5 | staticFileExts: gif|jpg|jpeg|png|bmp|ico|svg|swf|js|css|eot|ttf|woff 6 | # controller类所在的包路径 7 | # controller classes's package paths 8 | controllerPackages: 9 | - net.jkcode.jkmvc.example.controller 10 | # 视图目录, 根目录为webapp 11 | # view directory 12 | viewDir: -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/jetty.yaml: -------------------------------------------------------------------------------- 1 | maxThreads: 0 # io线程数, 最小是4, 如果为0则取Runtime.getRuntime().availableProcessors()*8 2 | #host: localhost # The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0 then bind to all interfaces. 3 | port: 8080 4 | # 如果 jkmvc-example 是根目录, 则不需要 jkmvc-example/ 目录前缀 5 | webDir: jkmvc-example/src/main/webapp 6 | contextPath: /jkmvc-example 7 | logDir: logs 8 | tempDir: tmp -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/routes.yaml: -------------------------------------------------------------------------------- 1 | #路由规则 2 | default: 3 | # url正则 | url pattern 4 | regex: (/(/)?)? 5 | # 参数子正则 | param pattern 6 | # paramRegex: 7 | # id: \d+ 8 | # 默认参数值 | default param 9 | defaults: 10 | controller: welcome 11 | action: index -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/session.properties: -------------------------------------------------------------------------------- 1 | # 注:我们的会话依赖于用户模型,但是我们不能事先确定用户模型,因此将用户模型及相关项做成配置 2 | 3 | # 认证处理的类型: 1 session: 使用session来保存登录用户 2 token: 使用token来保存登录用户 4 | authType = session 5 | # token缓存方式, 仅在 authType = token 时有效 6 | tokenCache = jedis 7 | # 用户模型的类,必须是实现 IAuthUserModel 8 | userModel = net.jkcode.jkmvc.example.model.UserModel 9 | # 用户名字段 10 | usernameField = username 11 | # 密码字段 12 | passwordField = password 13 | # 密码加密的盐 14 | passwordSalt = .$%^#*!)06zth 15 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/resources/upload.properties: -------------------------------------------------------------------------------- 1 | # 上传文件的保存目录,末尾不要带/ 2 | uploadRootDirectory=upload 3 | # 编码 4 | encoding=gbk 5 | # 禁止上传的文件扩展名, 以逗号分隔 6 | forbiddenExt = jsp,jspx,exe,sh,php,py 7 | # 上传文件的域名 8 | uploadDomain=http://localhost:8080/jkmvc-example/upload 9 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/webapp/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | jkmvc 9 | net.jkcode.jkmvc.http.JkFilter 10 | 11 | true 12 | 13 | 14 | 15 | jkmvc 16 | /* 17 | 18 | 19 | 20 | 21 | default 22 | 23 | / 24 | 25 | 26 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jkmvc 6 | 7 | 8 | Hello $name
9 | 10 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/webapp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" contentType="text/html; charset=UTF-8" 2 | pageEncoding="UTF-8"%> 3 | 4 | 5 | 6 | 7 | jkmvc 8 | 9 | 10 | Hello <%= request.getAttribute("name") %>
11 | 12 | -------------------------------------------------------------------------------- /jkmvc-example/src/main/webapp/user/new.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="net.jkcode.jkmvc.http.HttpRequest,net.jkcode.jkmvc.example.model.UserModel" pageEncoding="UTF-8"%> 2 | <% HttpRequest req = HttpRequest.current(); %> 3 | 4 | 5 | 6 | 7 | todolist 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
新建用户
17 | 18 | 19 |
" method="post"> 20 |
21 | 22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /jkmvc-example/src/test/kotlin/net/jkcode/jkmvc/tests/ExampleTests.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests 2 | 3 | import net.jkcode.jkmvc.server.JettyServer 4 | import net.jkcode.jkmvc.util.ModelGenerator 5 | import org.junit.Test 6 | 7 | 8 | class ExampleTests { 9 | 10 | // right: 直接启动 JettyServerLauncher 类, 并设置 module 为 jkmvc-example_main 11 | @Test 12 | fun testJettyServer() { 13 | // wrong: 无法加载 jkmvc-example/src/main/webapp 14 | JettyServer().start() 15 | } 16 | 17 | @Test 18 | fun testCodeModel() { 19 | val generator = ModelGenerator("/home/shi/code/java/jkmvc/jkmvc-example/src/main/kotlin" /* 源码目录 */, "net.jkcode.jkmvc.example.model" /* 包路径 */, "default" /* 数据库名 */, "shijianhang" /* 作者 */) 20 | //generator.genenateModelFile("UserModel" /* 模型类名 */, "用户模型" /* 模型名 */, "user" /* 表名 */) 21 | //generator.genenateModelFile("AddressModel" /* 模型类名 */, "地址模型" /* 模型名 */, "address" /* 表名 */) 22 | generator.genenateModelFile("ParcelModel" /* 模型类名 */, "包裹模型" /* 模型名 */, "parcel" /* 表名 */) 23 | } 24 | } 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /jkmvc-example/start-jetty.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | JAVA_VERSION=`java -fullversion 2>&1 | awk -F[\"\.] '{print $2$3$4}' |awk -F"_" '{print $1}'` 3 | if [ $JAVA_VERSION -lt 180 ]; then 4 | echo "Error: Java version should >= 1.8.0 " 5 | exit 1 6 | fi 7 | 8 | cd `dirname $0` 9 | DIR=`pwd` 10 | 11 | WAR=`ls | grep .war` 12 | # 去掉.war后缀, 即可工程名 13 | PRO=${WAR%%.war} 14 | 15 | if [ ! -d $PRO ]; then 16 | mkdir $PRO 17 | cd $PRO 18 | echo "解押"$WAR 19 | #unzip ../$WAR 20 | jar -xvf ../$WAR 21 | 22 | # 移动servlet.jar 23 | mv ../javax.servlet-api-3.1.0.jar WEB-INF/lib/ 24 | fi 25 | 26 | cd $DIR 27 | 28 | # 将 jetty.yaml 中的 webDir 配置项修改为当前项目路径 29 | #sed -i "s/webDir: .*src\/main\/webapp/webDir: $PRO/g" $PRO/WEB-INF/classes/jetty.yaml 30 | sed -i "s/webDir: .*src\/main\/webapp/webDir: $PRO/g" conf/jetty.yaml 31 | 32 | echo "启动jetty" 33 | JAVA_OPTS="-Djava.net.preferIPv4Stack=true -server -Xms1g -Xmx1g -XX:MetaspaceSize=128m -Djava.util.concurrent.ForkJoinPool.common.parallelism=32" 34 | #JAVA_OPTS="-Djava.net.preferIPv4Stack=true -server" 35 | 36 | JAVA_DEBUG_OPTS="" 37 | if [ "$1" = "debug" ]; then 38 | JAVA_DEBUG_OPTS=" -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=n " 39 | fi 40 | 41 | SERVER_CLASS='net.jkcode.jkmvc.server.JettyServerLauncher' 42 | 43 | java $JAVA_OPTS $JAVA_DEBUG_OPTS -cp $DIR/conf:$DIR/$PRO/WEB-INF/classes:$DIR/$PRO/WEB-INF/lib/* $SERVER_CLASS 44 | -------------------------------------------------------------------------------- /jkmvc-http/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies{ 2 | // other project 3 | compile project(':jkmvc-orm') 4 | if(env == 'dev') 5 | compile project(':jkguard') 6 | else 7 | compile "net.jkcode:jkguard:$jkguard_version" 8 | 9 | // 加密 10 | compile "commons-codec:commons-codec:1.5" 11 | 12 | // upload 13 | compile 'servlets.com:cos:05Nov2002' 14 | 15 | // 模板引擎 16 | // velocity 17 | compile 'org.apache.velocity:velocity:1.7' 18 | // freemarker 19 | compile "org.freemarker:freemarker:2.3.16" 20 | 21 | // servlet 22 | compile "javax.servlet:javax.servlet-api:3.1.0" // 因为不引入 war 插件, 因此不能使用功能 providedCompile 23 | runtime "javax.servlet.jsp:jsp-api:2.2.1-b03" 24 | runtime "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1" 25 | compile "jstl:jstl:1.2" 26 | 27 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 28 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/HttpParamMap.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http 2 | 3 | import net.jkcode.jkutil.common.any 4 | import net.jkcode.jkutil.common.decorateCollection 5 | import net.jkcode.jkutil.common.decorateSet 6 | import org.apache.commons.collections.keyvalue.DefaultMapEntry 7 | 8 | /** 9 | * http参数哈希 10 | * 11 | * @author shijianhang<772910474@qq.com> 12 | * @date 2019-11-20 21:20:33 13 | */ 14 | class HttpParamMap(public val params: Map>): Map by params as Map{ 15 | 16 | /** 17 | * 是否包含值 18 | */ 19 | public override fun containsValue(value: String?): Boolean { 20 | return params.any{ k, v -> 21 | v.contains(value) 22 | } 23 | } 24 | 25 | /** 26 | * 获得值 27 | */ 28 | public override fun get(key: String): String? { 29 | return params.get(key)?.first() 30 | } 31 | 32 | /** 33 | * 获得值的集合 34 | */ 35 | public override val values: Collection 36 | get() = decorateCollection(params.values){ v -> 37 | v.first() 38 | } 39 | 40 | /** 41 | * 获得实体的集合 42 | */ 43 | public override val entries: Set> 44 | get() = decorateSet(params.entries){ e -> 45 | DefaultMapEntry(e.key, e.value.first()) as Map.Entry 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/HttpState.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http 2 | 3 | import net.jkcode.jkmvc.http.controller.Controller 4 | import net.jkcode.jkmvc.http.jphp.PHttpRequest 5 | import net.jkcode.jkmvc.http.jphp.PHttpResponse 6 | import net.jkcode.jkutil.ttl.HttpRequestScopedTransferableThreadLocal 7 | import net.jkcode.jkutil.ttl.SttlCurrentHolder 8 | import net.jkcode.jphp.ext.getPropJavaValue 9 | import php.runtime.memory.ObjectMemory 10 | 11 | /** 12 | * http状态 13 | */ 14 | data class HttpState( 15 | public val req: HttpRequest, // 请求 16 | public val res: HttpResponse, // 响应 17 | public val controller: Any? // 控制器,兼容java、jphp 18 | ) { 19 | 20 | companion object: SttlCurrentHolder(HttpRequestScopedTransferableThreadLocal()) // http请求域的可传递的 ThreadLocal 21 | { 22 | /** 23 | * 通过java controller,来设置当前http状态 24 | */ 25 | fun setCurrentByController(controller: Controller) { 26 | setCurrent(HttpState(controller.req, controller.res, controller)) 27 | } 28 | 29 | /** 30 | * 通过php controller,来设置当前http状态 31 | */ 32 | fun setCurrentByController(controller: ObjectMemory) { 33 | val preq = controller.getPropJavaValue("req") as PHttpRequest 34 | val pres = controller.getPropJavaValue("res") as PHttpResponse 35 | setCurrent(HttpState(preq.request, pres.response, controller)) 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/IHttpRequest.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http 2 | 3 | import net.jkcode.jkutil.common.generateId 4 | import javax.servlet.DispatcherType 5 | import javax.servlet.http.HttpServletRequest 6 | import javax.servlet.http.HttpServletRequestWrapper 7 | 8 | /** 9 | * jkmvc特有的http请求基类 10 | * 11 | * @author shijianhang<772910474@qq.com> 12 | * @date 4/15/2020 7:58 PM 13 | */ 14 | abstract class IHttpRequest(req: HttpServletRequest): HttpServletRequestWrapper(req){ 15 | 16 | /** 17 | * 请求标识, 调试用 18 | */ 19 | public val id: Long by lazy { 20 | generateId("HttpRequest") 21 | } 22 | 23 | /** 24 | * 是否内部请求 25 | */ 26 | public val isInner: Boolean 27 | get() = dispatcherType == DispatcherType.INCLUDE || dispatcherType == DispatcherType.FORWARD 28 | 29 | /** 30 | * 原始的请求 = 非 IHttpRequest 的请求 31 | */ 32 | public val originalRequest: HttpServletRequest 33 | get() { 34 | var result = request 35 | while(result != null && result is IHttpRequest){ 36 | result = result.request 37 | } 38 | return result as HttpServletRequest 39 | } 40 | 41 | /** 42 | * 发起servlet/jsp 43 | */ 44 | val originalServletPath: String 45 | //get() = (request as HttpServletRequest).servletPath 46 | get() = originalRequest.servletPath 47 | 48 | public override fun toString(): String { 49 | return "${javaClass.simpleName}{id=$id, servletPath=$servletPath, originalServletPath=$originalServletPath}" 50 | } 51 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/Interceptor.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http 2 | 3 | import net.jkcode.jkutil.interceptor.IRequestInterceptor 4 | 5 | // http请求处理的拦截器, 注: 拦截前已做好路由解析 6 | typealias IHttpRequestInterceptor = IRequestInterceptor -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/controller/IController.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.controller 2 | 3 | import net.jkcode.jkmvc.http.HttpRequest 4 | import net.jkcode.jkmvc.http.HttpResponse 5 | import net.jkcode.jkmvc.http.view.View 6 | import net.jkcode.jkutil.collection.LazyAllocatedMap 7 | import java.io.Writer 8 | import javax.servlet.ServletOutputStream 9 | 10 | /** 11 | * 控制器 12 | * 13 | * @author shijianhang 14 | * @date 2016-10-8 下午8:02:47 15 | * 16 | */ 17 | interface IController{ 18 | 19 | /** 20 | * 请求对象 21 | */ 22 | var req: HttpRequest 23 | 24 | /** 25 | * 响应对象 26 | */ 27 | var res: HttpResponse 28 | 29 | /** 30 | * 响应的writer 31 | */ 32 | val writer: Writer 33 | get() = res.prepareWriter() 34 | 35 | /** 36 | * 响应的output 37 | */ 38 | val out: ServletOutputStream 39 | get() = res.outputStream 40 | 41 | /** 42 | * 视图模型 43 | */ 44 | val vm:MutableMap 45 | 46 | /** 47 | * 视图 48 | * @param file 视图文件 49 | * @param data 视图变量 50 | * @return 视图 51 | */ 52 | fun view(file:String, data:Map = LazyAllocatedMap()): View 53 | 54 | /** 55 | * 视图 56 | * @param data 视图变量 57 | * @return 视图 58 | */ 59 | fun view(data:Map = LazyAllocatedMap()): View 60 | 61 | /** 62 | * 重定向到指定url 63 | * @param uri 64 | */ 65 | fun redirect(uri: String, data:Map = emptyMap()) 66 | 67 | 68 | /** 69 | * 渲染结果 70 | */ 71 | fun renderResult(result: Any?) 72 | 73 | /** 74 | * 前置处理 75 | */ 76 | fun before(){} 77 | 78 | /** 79 | * 后置处理 80 | * 因为这是请求的最后处理(包含异常处理), 你最好不要再往上抛异常 81 | * 82 | * @param result action方法执行结果 83 | * @param t action方法执行抛出的异常 84 | * @return 85 | */ 86 | fun after(result: Any?, t: Throwable? = null): Any? { 87 | // 处理异常 88 | if(t != null){ 89 | t.printStackTrace() 90 | return null 91 | } 92 | 93 | // 处理结果 94 | return result 95 | } 96 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/controller/IControllerClass.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.controller 2 | 3 | import java.lang.reflect.Method 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * 封装Controller类 8 | * 方便访问其构造函数与所有的action方法 9 | * Created by shi on 4/26/17. 10 | */ 11 | interface IControllerClass{ 12 | 13 | /** 14 | * controller类 15 | */ 16 | val clazz: KClass<*>; 17 | 18 | /** 19 | * 所有action方法 20 | */ 21 | val actions: Map; 22 | 23 | /** 24 | * 获得action方法 25 | * @return 26 | */ 27 | fun getActionMethod(name:String): Method? { 28 | return actions.get(name); 29 | } 30 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/controller/IControllerClassLoader.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.controller 2 | 3 | /** 4 | * 加载Controller类 5 | * 6 | * @author shijianhang 7 | * @date 2016-10-8 下午8:02:47 8 | */ 9 | interface IControllerClassLoader { 10 | 11 | /** 12 | * 获得controller类 13 | * @param controller名 14 | * @return 15 | */ 16 | fun get(name: String): ControllerClass? 17 | 18 | /** 19 | * 获得所有的controller类 20 | * 21 | * @return 22 | */ 23 | fun getAll(): Collection 24 | } 25 | -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/controller/MethodRouteDetector.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.controller 2 | 3 | import net.jkcode.jkmvc.http.router.Route 4 | import net.jkcode.jkmvc.http.router.Router 5 | import java.lang.reflect.Method 6 | import net.jkcode.jkmvc.http.router.ARoute 7 | import net.jkcode.jkmvc.http.router.route 8 | 9 | /** 10 | * 方法级路由的检测器 11 | * 主要是检测方法上的路由注解 12 | * 13 | * 14 | * @author shijianhang 15 | * @date 2016-10-6 上午12:01:17 16 | */ 17 | open class MethodRouteDetector { 18 | 19 | /** 20 | * 检测路由注解 21 | * @param name 路由名 22 | * @parma method 方法 23 | */ 24 | public fun detect(controller:String, action: String, method: Method) { 25 | // 获得注解 26 | val annotation = method.route 27 | if(annotation == null) 28 | return 29 | // 获得正则 30 | val regex = annotation.regex.trim() 31 | // 只有正则不为空才会添加路由, 否则都交给默认路由处理(默认路由最后需要校验方法) 32 | if(regex.isEmpty()) 33 | return 34 | 35 | val route = Route(regex, annotation.method, controller, action) 36 | Router.addRoute("$controller#$action", route) 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/handler/IHttpRequestHandler.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.handler 2 | 3 | import net.jkcode.jkmvc.http.IHttpRequestInterceptor 4 | import java.util.concurrent.CompletableFuture 5 | import javax.servlet.ServletRequest 6 | import javax.servlet.ServletResponse 7 | import javax.servlet.http.HttpServletRequest 8 | import javax.servlet.http.HttpServletResponse 9 | 10 | /** 11 | * http请求处理者 12 | * 13 | * @author shijianhang 14 | * @date 2016-10-6 上午9:27:56 15 | * 16 | */ 17 | interface IHttpRequestHandler { 18 | 19 | /** 20 | * http请求处理的拦截器 21 | */ 22 | val interceptors: List 23 | 24 | /** 25 | * 处理请求 26 | * 27 | * @param HttpServletRequest req 28 | * @param HttpServletResponse res 29 | * @return 30 | */ 31 | fun handle(request: HttpServletRequest, response: HttpServletResponse): CompletableFuture<*> 32 | 33 | } 34 | -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/jphp/JkmvcHttpExtension.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.jphp 2 | 3 | import php.runtime.env.CompileScope 4 | import php.runtime.ext.support.Extension 5 | import org.develnext.jphp.zend.ext.ZendExtension 6 | 7 | class JkmvcHttpExtension : Extension() { 8 | 9 | companion object { 10 | const val NS = "php\\jkmvc\\http" 11 | } 12 | 13 | override fun getStatus(): Status { 14 | return Status.EXPERIMENTAL 15 | } 16 | 17 | override fun getRequiredExtensions(): Array? { 18 | return arrayOf( 19 | ZendExtension::class.java.getName() 20 | ) 21 | } 22 | 23 | override fun getPackageNames(): Array { 24 | return arrayOf("jkmvc\\http") 25 | } 26 | 27 | override fun onRegister(scope: CompileScope) { 28 | registerClass(scope, PHttpRequest::class.java) 29 | registerClass(scope, PHttpResponse::class.java) 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/jphp/PhpView.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.jphp 2 | 3 | import net.jkcode.jkmvc.http.HttpRequest 4 | import net.jkcode.jkmvc.http.HttpResponse 5 | import net.jkcode.jkmvc.http.view.View 6 | import net.jkcode.jphp.ext.JphpLauncher 7 | 8 | /** 9 | * php模板视图 10 | * 放在web根目录下的html文件 11 | * 12 | * @author shijianhang<772910474@qq.com> 13 | * @date 8/25/17 9:49 AM 14 | */ 15 | class PhpView(req: HttpRequest /* 请求对象 */, res: HttpResponse /* 响应对象 */, file:String/* 视图文件 */, vm: MutableMap /* 视图模型 */): View(req, res, file, vm) { 16 | 17 | /** 18 | * 渲染php模板 19 | */ 20 | override fun render() { 21 | JphpLauncher.run(path + ".php", vm, res.outputStream) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/ARoute.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | import net.jkcode.jkguard.IMethodMeta 4 | import net.jkcode.jkutil.common.getCachedAnnotation 5 | import java.lang.reflect.Method 6 | 7 | /** 8 | * 路由注解 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2020-1-6 6:04 PM 11 | */ 12 | @Target(AnnotationTarget.FUNCTION) 13 | @Retention(AnnotationRetention.RUNTIME) 14 | annotation class ARoute( 15 | public val regex: String = "", // url正则 16 | public val method: HttpMethod = HttpMethod.ALL // http方法 17 | ) 18 | 19 | /** 20 | * 获得路由注解 21 | */ 22 | public val Method.route: ARoute? 23 | get(){ 24 | return getCachedAnnotation() 25 | } 26 | 27 | /** 28 | * 获得路由注解 29 | */ 30 | public val IMethodMeta.route: ARoute? 31 | get(){ 32 | return getAnnotation() 33 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/HttpMethod.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | /** 4 | * http方法 5 | * @author shijianhang<772910474@qq.com> 6 | * @date 2020-1-6 6:04 PM 7 | */ 8 | enum class HttpMethod { 9 | 10 | ALL, GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; 11 | 12 | /** 13 | * 匹配方法 14 | * @param method 15 | * @return 16 | */ 17 | public fun match(method: HttpMethod): Boolean { 18 | return this == HttpMethod.ALL || this == method 19 | } 20 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/IRoute.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | /** 4 | * 路由正则处理 5 | * 6 | * 1 编译正则 7 | * 将简化的路由正则,转换为完整的正则:主要是将<参数>替换为子正则 8 | * 9 | * 10 | * // 将 (\/(\/)?)? 编译为 /([^\/]+)(\/([^\/]+)\/(\d+)?)?/ 11 | * // 其中参数对子正则的映射关系保存在 paramGroupMapping 中 12 | * val route = Route( 13 | * "(\/(\/)?)?", 14 | * mapOf( 15 | * "controller" to "[a-z]+", 16 | * "action" to "[a-z]+", 17 | * "id" to "\d+", 18 | * ) 19 | * ); 20 | * 21 | * 22 | * 2 匹配路由正则 23 | * 24 | * route.mathes('welcome/index'); 25 | * 26 | */ 27 | interface IRoute{ 28 | 29 | /** 30 | * 原始正则: (\/(\/)?)? 31 | */ 32 | val regex:String 33 | 34 | /** 35 | * 参数的子正则 36 | */ 37 | val paramRegex:Map 38 | 39 | /** 40 | * 参数的默认值 41 | */ 42 | val defaults:Map? 43 | 44 | /** 45 | * http方法 46 | */ 47 | val method: HttpMethod 48 | 49 | /** 50 | * controller 51 | * 仅当方法级注解路由时有效 52 | */ 53 | val controller: String? 54 | 55 | /** 56 | * action 57 | * 仅当方法级注解路由时有效 58 | */ 59 | val action: String? 60 | 61 | /** 62 | * 检查uri是否匹配路由正则 63 | * 64 | * @param uri 65 | * @param method 66 | * @return 匹配的路由参数,如果为null,则没有匹配 67 | */ 68 | fun match(uri: String, method: HttpMethod):Map?; 69 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/IRouter.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | /** 4 | * 路由参数 + 路由规则 5 | */ 6 | typealias ParamsAndRoute = Pair, Route> 7 | 8 | /** 9 | * 路由器 10 | * 1 加载路由规则 11 | * 2 解析路由:匹配规则 12 | * 13 | * @author shijianhang 14 | * @date 2016-10-6 上午12:01:17 15 | * 16 | */ 17 | interface IRouter 18 | { 19 | /** 20 | * 添加路由 21 | * @param name 路由名 22 | * @parma route 路由对象 23 | */ 24 | fun addRoute(name:String, route: Route): Router 25 | 26 | /** 27 | * 解析路由:匹配规则 28 | * @param uri 29 | * @param method 30 | * @return [路由参数, 路由规则] 31 | */ 32 | fun parse(uri: String, method: HttpMethod): RouteResult?; 33 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/RouteException.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | import net.jkcode.jkutil.common.JkException 4 | 5 | /** 6 | * 路由异常 7 | */ 8 | class RouteException : JkException { 9 | 10 | public constructor(cause: Throwable) : super(cause) { 11 | } 12 | 13 | public constructor(message: String, cause: Throwable? = null) : super(message, cause) { 14 | } 15 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/router/RouteResult.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.router 2 | 3 | /** 4 | * 路由匹配结果 5 | * @author shijianhang<772910474@qq.com> 6 | * @date 2020-04-08 6:00 PM 7 | */ 8 | class RouteResult( 9 | public val params: Map, // 路由实参 10 | public val route: Route // 路由 11 | ){ 12 | /** 13 | * controller 14 | */ 15 | public val controller: String 16 | get() = if(route.isMethodLevel) 17 | route.controller!! // 方法级注解路由 18 | else 19 | params["controller"]!! // 全局配置路由 20 | 21 | /** 22 | * action 23 | */ 24 | public val action: String 25 | get() = if(route.isMethodLevel) 26 | route.action!! // 方法级注解路由 27 | else 28 | params["action"]!! // 全局配置路由 29 | 30 | override fun toString(): String { 31 | return "controller=[$controller], action=[$action]" 32 | } 33 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/session/IAuth.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.session 2 | 3 | /** 4 | * 认证用户 5 | * 1 登录 6 | * 2 注销 7 | * 3 密码加密 8 | * 9 | * @author shijianhang 10 | * @create 2017-09-19 下午11:35 11 | **/ 12 | interface IAuth { 13 | 14 | /** 15 | * 获得当前登录用户 16 | * @return 17 | */ 18 | public fun getCurrentUser(): IAuthUserModel? 19 | 20 | /** 21 | * 登录验证 22 | * 23 | * @param username 用户名 24 | * @param password 密码 25 | * @param withs 联查的关联对象名 26 | * @return Orm? 27 | */ 28 | public fun login(username:String, password:String, withs: Array = emptyArray()): IAuthUserModel? 29 | 30 | /** 31 | * 注销登录 32 | */ 33 | public fun logout() 34 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/session/IAuthUserModel.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.session 2 | 3 | import net.jkcode.jkutil.common.Config 4 | import net.jkcode.jkmvc.orm.IOrm 5 | import org.apache.commons.codec.digest.DigestUtils 6 | 7 | /** 8 | * 会话相关的用户模型接口 9 | * 如需引入会话,要实现该接口,并在 session.properties 将该实现类赋值给 userModel 项 10 | * 11 | * @author shijianhang 12 | * @create 2017-09-19 下午11:35 13 | */ 14 | interface IAuthUserModel : IOrm { 15 | 16 | companion object{ 17 | /** 18 | * 会话配置 19 | */ 20 | protected val sessionConfig: Config = Config.instance("session"); 21 | } 22 | 23 | /** 24 | * 加密字符串,用于加密密码 25 | * 26 | * @param str 27 | * @return 28 | */ 29 | fun hash(str: String): String { 30 | return DigestUtils.md5Hex(str + sessionConfig["salt"]); 31 | } 32 | 33 | /** 34 | * create前置处理 35 | */ 36 | override fun beforeCreate(){ 37 | // 加密密码 38 | val field:String = sessionConfig["passwordField"]!! 39 | this[field] = hash(this[field]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/session/SessionAuth.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.session 2 | 3 | import net.jkcode.jkmvc.http.HttpRequest 4 | import javax.servlet.http.HttpSession 5 | 6 | /** 7 | * 基于session的认证用户 -- 使用session来保存登录用户 8 | * 1 登录 9 | * 2 注销 10 | * 11 | * @author shijianhang 12 | * @create 2017-10-04 下午3:40 13 | **/ 14 | class SessionAuth : Auth() { 15 | 16 | /** 17 | * 获得当前会话 18 | * 19 | * @param create 如果没有会话, 是否创建新的会话 20 | * @return 21 | */ 22 | private fun getSession(create:Boolean = true): HttpSession? { 23 | return HttpRequest.current().getSession(create); 24 | } 25 | 26 | /** 27 | * 获得当前登录用户 28 | * @return 29 | */ 30 | public override fun getCurrentUser(): IAuthUserModel?{ 31 | // 从session中读取登录用户 32 | return getSession(false)?.getAttribute("user") as IAuthUserModel? 33 | } 34 | 35 | /** 36 | * 登录后的处理 37 | * @param user 38 | */ 39 | protected override fun afterLogin(user: IAuthUserModel) { 40 | // 保存登录用户到session中 41 | getSession(true)!!.setAttribute("user", user); 42 | } 43 | 44 | /** 45 | * 注销登录 46 | */ 47 | public override fun logout(){ 48 | getSession(false)?.invalidate(); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/session/TokenAuth.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.session 2 | 3 | import net.jkcode.jkmvc.http.HttpRequest 4 | import net.jkcode.jkmvc.http.session.token.ITokenManager 5 | import net.jkcode.jkmvc.http.session.token.TokenManager 6 | import net.jkcode.jkutil.ttl.AllRequestScopedTransferableThreadLocal 7 | 8 | /** 9 | * 基于token认证用户 -- 使用token来保存登录用户的状态 10 | * 1 登录 11 | * 2 注销 12 | * 13 | * @author shijianhang 14 | * @create 2017-10-04 下午3:40 15 | **/ 16 | class TokenAuth : Auth() { 17 | 18 | /** 19 | * 登录用户缓存 20 | */ 21 | protected val users: AllRequestScopedTransferableThreadLocal = AllRequestScopedTransferableThreadLocal { 22 | // 获得当前token 23 | val token = getToken() 24 | if (token == null) 25 | null 26 | else // 根据token获得用户 27 | tokenManager.getUser(token)?.component1() 28 | } 29 | 30 | /** 31 | * token管理器 32 | */ 33 | protected val tokenManager: ITokenManager = TokenManager 34 | 35 | /** 36 | * 获得当前会话的token 37 | */ 38 | public fun getToken(): String? { 39 | // web请求? 40 | val req = HttpRequest.currentOrNull() 41 | if(req == null) 42 | return null 43 | 44 | // 先找请求参数 45 | val token = req.getParameter("token") 46 | if(!token.isNullOrEmpty()) 47 | return token 48 | 49 | // 后找请求头 50 | return req.getHeader("token") 51 | } 52 | 53 | /** 54 | * 获得当前登录用户 55 | * @return 56 | */ 57 | public override fun getCurrentUser(): IAuthUserModel?{ 58 | return users.get() 59 | } 60 | 61 | /** 62 | * 登录后的处理 63 | * @param user 64 | */ 65 | protected override fun afterLogin(user: IAuthUserModel) { 66 | //生成登录token 67 | val token = tokenManager.createToken(user) 68 | HttpRequest.currentOrNull()?.setAttribute("token", token); 69 | } 70 | 71 | /** 72 | * 注销登录 73 | */ 74 | public override fun logout(){ 75 | // 获得当前token 76 | val token = getToken() 77 | if(token == null) 78 | return 79 | 80 | // 删除token 81 | tokenManager.deleteToken(token) 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/session/token/ITokenManager.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.session.token 2 | 3 | import net.jkcode.jkmvc.http.session.IAuthUserModel 4 | 5 | /** 6 | * 管理会话的token 7 | * 8 | * @ClassName: TokenManager 9 | * @Description: 10 | * @author shijianhang<772910474@qq.com> 11 | * @date 2017-10-03 11:21 AM 12 | */ 13 | interface ITokenManager { 14 | /** 15 | * 为指定用户创建一个token 16 | * 17 | * @param user 指定用户 18 | * @return 生成的token 19 | */ 20 | fun createToken(user: IAuthUserModel): String 21 | 22 | /** 23 | * 获得token相关的用户 24 | * 25 | * @param token 26 | * @return [用户, tokenId] 27 | */ 28 | fun getUser(token: String): Pair? 29 | 30 | /** 31 | * 清除token 32 | * 33 | * @param token 34 | */ 35 | fun deleteToken(token: String) 36 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/util/AllPagination.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.util 2 | 3 | /** 4 | * 全量数据的分页处理,主要用于内存分页 5 | * 6 | * @Description: 7 | * @author shijianhang<772910474@qq.com> 8 | * @date 2017-09-21 4:40 PM 9 | */ 10 | class AllPagination( 11 | public val items:List, /* 全量数据 */ 12 | reqPage:Int = getRequestPage() /* 当前页码 */, 13 | pageSize:Int = config["pageSize"]!! /* 每页的记录数 */ 14 | ) : Pagination(items.size, reqPage, pageSize) { 15 | 16 | /** 17 | * 当前页的数据 18 | */ 19 | public val pageItems:List by lazy{ 20 | items.subList(startIndex, endIndex) 21 | } 22 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/util/IPagination.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.util 2 | 3 | /** 4 | * 分页处理 5 | * 6 | * @ClassName: IPagination 7 | * @Description: 8 | * @author shijianhang<772910474@qq.com> 9 | * @date 2017-09-21 5:08 PM 10 | */ 11 | interface IPagination { 12 | /** 13 | * 每页的记录数 14 | */ 15 | val pageSize:Int; 16 | 17 | /** 18 | * 当前页码 19 | */ 20 | val page:Int; 21 | 22 | /** 23 | * 记录总数 24 | */ 25 | val total:Int; 26 | 27 | /** 28 | * 页码总数 29 | */ 30 | val totalPages:Int; 31 | 32 | /** 33 | * 开始位置 34 | */ 35 | val startIndex:Int; 36 | 37 | /** 38 | * 结束位置 39 | */ 40 | val endIndex:Int; 41 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/util/Pagination.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.util 2 | 3 | import net.jkcode.jkutil.common.Config 4 | import net.jkcode.jkmvc.http.HttpRequest 5 | 6 | /** 7 | * 分页处理,主要用于数据库分页 8 | * 9 | * @Description: 10 | * @author shijianhang<772910474@qq.com> 11 | * @date 2017-09-21 4:40 PM 12 | */ 13 | open class Pagination( 14 | public override val total:Int /* 记录总数 */, 15 | protected val reqPage:Int = getRequestPage() /* 请求页码 */, 16 | public override val pageSize:Int = config["pageSize"]!! /* 每页的记录数 */ 17 | ) : IPagination { 18 | 19 | companion object { 20 | /** 21 | * 分页配置 22 | */ 23 | val config: Config = Config.instance("pagination"); 24 | 25 | /** 26 | * 获得页码的请求参数 27 | */ 28 | public fun getRequestPage():Int{ 29 | return HttpRequest.current().get(config["pageParameterName"]!!, 1)!! // 默认为第一页 30 | } 31 | } 32 | 33 | /** 34 | * 页码总数 35 | */ 36 | public override val totalPages:Int = maxOf((total + pageSize - 1) / pageSize, 1) // 最少1页 37 | 38 | /** 39 | * 当前页码 40 | */ 41 | public override val page:Int = minOf(totalPages, maxOf(reqPage, 1)) 42 | 43 | /** 44 | * 开始位置 45 | */ 46 | public override val startIndex:Int = pageSize * (page - 1) 47 | 48 | /** 49 | * 结束位置 50 | */ 51 | public override val endIndex:Int = minOf(total, pageSize * page) 52 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/kotlin/net/jkcode/jkmvc/http/view/IView.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.http.view 2 | 3 | import net.jkcode.jkmvc.http.HttpRequest 4 | import net.jkcode.jkmvc.http.HttpResponse 5 | 6 | /** 7 | * 视图 8 | * 9 | * @author shijianhang 10 | * @date 2016-10-21 下午3:14:54 11 | */ 12 | interface IView 13 | { 14 | /** 15 | * 请求对象 16 | */ 17 | val req: HttpRequest 18 | 19 | /** 20 | * 响应对象 21 | */ 22 | val res: HttpResponse 23 | 24 | /** 25 | * 视图文件 26 | */ 27 | val file:String 28 | 29 | /** 30 | * 视图文件路径 31 | */ 32 | val path:String 33 | 34 | /** 35 | * 视图模型 36 | */ 37 | var vm:MutableMap 38 | 39 | /** 40 | * 设置视图模型 41 | * @param key 42 | * @param value 43 | * @return 44 | */ 45 | operator fun set(key:String, value:Any?): View; 46 | 47 | /** 48 | * 合并视图模型 49 | * @param vm 50 | * @return 51 | */ 52 | fun mergeVm(vm: Map) 53 | 54 | /** 55 | * 渲染视图 56 | */ 57 | public fun render(); 58 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/resources/JPHP-INF/sdk/php/jkmvc/http/HttpRequest.php: -------------------------------------------------------------------------------- 1 | req; 20 | $req = HttpRequest::current(); 21 | echo 'controller#action: '. $req->controller() . '-' . $req->action() . "\n"; 22 | echo 'uri: ' . $req->method() . '-' . $req->uri() . "\n"; 23 | echo 'routeUri: ' . $req->method() . '-' . $req->routeUri() . "\n"; 24 | echo 'param: ' . $req->param('name') . "\n"; 25 | echo 'routeParam: ' . $req->param('id') . "\n"; 26 | echo 'query: ' . $req->query() . "\n"; 27 | echo 'sessionId: ' . $req->sessionId() . "\n"; 28 | } 29 | 30 | /** 31 | * http://localhost:8080/jkmvc-example/$test/login 32 | */ 33 | function login(){ 34 | $this->view('login', ['msg' => '请输入登录账户和密码']); 35 | } 36 | } -------------------------------------------------------------------------------- /jkmvc-http/src/main/resources/redis.yaml: -------------------------------------------------------------------------------- 1 | default: 2 | address: 127.0.0.1:6379 # 可多个节点, 使用逗号分隔, 选择db加/ 3 | password: 4 | dbname: 5 | # 序列器类型 6 | serializer: fst 7 | -------------------------------------------------------------------------------- /jkmvc-http/src/main/resources/routes.yaml: -------------------------------------------------------------------------------- 1 | #路由规则 2 | default: 3 | # url正则 | url pattern 4 | regex: (/(/)?)? 5 | # 参数子正则 | param pattern 6 | # paramRegex: 7 | # id: \d+ 8 | # 默认参数值 | default param 9 | defaults: 10 | controller: welcome 11 | action: index -------------------------------------------------------------------------------- /jkmvc-http/src/main/resources/session.properties: -------------------------------------------------------------------------------- 1 | # 注:我们的会话依赖于用户模型,但是我们不能事先确定用户模型,因此将用户模型及相关项做成配置 2 | 3 | # 认证处理的类型: 1 session: 使用session来保存登录用户 2 token: 使用token来保存登录用户 4 | authType = token 5 | # token缓存方式, 仅在 authType = token 时有效 6 | tokenCacheType = jedis 7 | # 用户模型的类,必须是实现 IAuthUserModel 8 | userModel = xxx.xxx.xxx 9 | # 用户名字段 10 | usernameField = username 11 | # 密码字段 12 | passwordField = password 13 | # 密码加密的盐 14 | passwordSalt = .$%^#*!)06zth` -------------------------------------------------------------------------------- /jkmvc-http/src/main/resources/upload.properties: -------------------------------------------------------------------------------- 1 | # 上传文件的保存目录,末尾不要带/ 2 | # war运行 3 | #uploadRootDirectory=upload 4 | # idea运行 5 | uploadRootDirectory=src/main/webapp/upload 6 | # 编码 7 | encoding=gbk 8 | # 禁止上传的文件扩展名, 以逗号分隔 9 | forbiddenExt = jsp,jspx,exe,sh,php,py 10 | # 上传文件的域名 11 | uploadDomain=http://localhost:8080/jkmvc/upload 12 | -------------------------------------------------------------------------------- /jkmvc-http/src/test/kotlin/net/jkcode/jkmvc/tests/ViewTests.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests 2 | 3 | import net.jkcode.jkutil.common.Config 4 | import org.apache.velocity.VelocityContext 5 | import org.apache.velocity.io.VelocityWriter 6 | import org.apache.velocity.runtime.RuntimeInstance 7 | import org.junit.Test 8 | import java.io.BufferedWriter 9 | import java.io.OutputStreamWriter 10 | import java.util.* 11 | 12 | 13 | class ViewTests{ 14 | 15 | @Test 16 | fun testVelocity(){ 17 | // 构建属性 18 | val props = Properties() 19 | props.setProperty("input.encoding", "UTF-8") 20 | props.setProperty("output.encoding", "UTF-8") 21 | props.setProperty("file.resource.loader.path", "/home/shi/code/java/jkmvc/jkmvc-example/src/main/webapp") 22 | 23 | // 单例用 Velocity 24 | // val instance = Velocity(); 25 | // 非单例用 RuntimeInstanc:同时使用多个Velocity 26 | val instance = RuntimeInstance() 27 | instance.init(props) 28 | 29 | // 获得模板文件 30 | val template = instance.getTemplate("webapp/index.html", "UTF-8"); 31 | 32 | // 构建上下文:要渲染的数据 33 | val context = VelocityContext() 34 | context.put("name", "shi"); 35 | 36 | // 渲染模板 37 | var vwriter: VelocityWriter? = null 38 | val writer = BufferedWriter(OutputStreamWriter(System.out)); 39 | try { 40 | vwriter = VelocityWriter(writer) 41 | template.merge(context, vwriter) // 合并上下文,根据数据渲染并输出 42 | vwriter.flush() 43 | } finally { 44 | writer.close() 45 | if (vwriter != null) 46 | vwriter.recycle(null) 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /jkmvc-http/src/test/resources/test.ftl: -------------------------------------------------------------------------------- 1 | <#list 0..9999 as i> 2 | Hello ${name} 3 | <#list friends as f> 4 | <#if i % 2 == 0> 5 | -${f} 6 | <#else> 7 | +${f} 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /jkmvc-http/src/test/resources/test.php: -------------------------------------------------------------------------------- 1 | 8 | * @date 2019-08-23 5:16 PM 9 | */ 10 | object DbConfig { 11 | /** 12 | * 公共配置 13 | */ 14 | public val config: Config = Config.instance("db", "yaml") 15 | 16 | /** 17 | * 是否调试 18 | */ 19 | public val debug: Boolean = config.getBoolean("debug", false)!!; 20 | 21 | /** 22 | * 分库的数据库名 23 | */ 24 | public val shardingDbs: List = config.getString("shardingDbs", "")!!.split(','); 25 | 26 | /** 27 | * 是否分库 28 | */ 29 | public fun isSharding(db: String): Boolean { 30 | return shardingDbs.contains(db) 31 | } 32 | 33 | /** 34 | * 字段有下划线 35 | */ 36 | public val columnUnderlineDbs: List = config.get("columnUnderlineDbs", "")!!.split(',') 37 | 38 | /** 39 | * 是否字段有下划线 40 | */ 41 | public fun isColumnUnderline(db: String): Boolean { 42 | return columnUnderlineDbs.contains(db) 43 | } 44 | 45 | /** 46 | * 字段全大写 47 | */ 48 | public val columnUpperCaseDbs: List = config.get("columnUpperCaseDbs", "")!!.split(',') 49 | 50 | /** 51 | * 是否字段全大写 52 | */ 53 | public fun isColumnUpperCase(db: String): Boolean { 54 | return columnUpperCaseDbs.contains(db) 55 | } 56 | 57 | /** 58 | * 自定义db实现类 59 | */ 60 | public val customDbClass: Class<*>? by lazy{ 61 | val clazz: String? = config["customDbClass"] 62 | if(clazz == null) 63 | null 64 | else 65 | Class.forName(clazz) 66 | } 67 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/DbException.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | import net.jkcode.jkutil.common.JkException 4 | 5 | /** 6 | * db异常 7 | */ 8 | class DbException : JkException { 9 | 10 | public constructor(cause: Throwable) : super(cause) { 11 | } 12 | 13 | public constructor(message: String, cause: Throwable? = null) : super(message, cause) { 14 | } 15 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/DbResultRow.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | import java.util.* 4 | import kotlin.collections.HashMap 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * 结果集的一行 9 | * 生命周期只在转换结果集阶段, 不能被外部引用 10 | * 11 | * @author shijianhang<772910474@qq.com> 12 | * @date 2019-11-21 4:27 PM 13 | */ 14 | class DbResultRow(public val rs: DbResultSet): Iterable> { 15 | 16 | /** 17 | * 迭代器 18 | */ 19 | override fun iterator(): Iterator> { 20 | return DbResultRowIterator(this) 21 | } 22 | 23 | /** 24 | * 通过inline指定的值类型, 来获得结果集的单个值 25 | * @param column 26 | * @return 27 | */ 28 | public inline operator fun get(column: String): T?{ 29 | return get(rs.findColumn(column)) // mysql jdbc就是这么实现的, 参考 com.mysql.jdbc.ResultSetImpl.getString(java.lang.String) 30 | } 31 | 32 | /** 33 | * 通过inline指定的值类型, 来获得结果集的单个值, 超出列报错 34 | * @param i 35 | * @return 36 | */ 37 | public inline operator fun get(i: Int): T?{ 38 | return get(i, T::class) as T? 39 | } 40 | 41 | /** 42 | * 通过inline指定的值类型, 来获得结果集的单个值, 超出列返回null 43 | * @param i 44 | * @return 45 | */ 46 | public inline fun getOrNull(i: Int): T?{ 47 | if(i <= 0 && i > rs.columnCount) 48 | return null 49 | 50 | return get(i) 51 | } 52 | 53 | /** 54 | * 获得当前行的单个值 55 | * @param i 56 | * @param clazz 57 | * @return 58 | */ 59 | public fun get(i: Int, clazz: KClass<*>? = null): Any?{ 60 | return rs.get(i, clazz) 61 | } 62 | 63 | /** 64 | * 转换为map 65 | * @param convertingColumn 是否转换字段名 66 | * @param to 67 | * @return 68 | */ 69 | public fun toMap(convertingColumn: Boolean = false, to: MutableMap = HashMap()): Map{ 70 | forEach { (col, value) -> 71 | val key = if(convertingColumn) rs.db.column2Prop(col) else col 72 | val nullInvalid = value == null && to is Hashtable // 不允许null的情况 73 | if(!nullInvalid) 74 | to[key] = value; 75 | } 76 | return to 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/DbResultRowIterator.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | /** 4 | * 结果集一行的值迭代器 5 | */ 6 | class DbResultRowIterator(public val row: DbResultRow): Iterator> { 7 | 8 | /** 9 | * 当前序号 10 | * rs.getObject(i)获取列值时,下标是从“1”开始 11 | */ 12 | protected var _curr = 0 13 | 14 | override fun hasNext(): Boolean { 15 | return _curr < row.rs.columnCount 16 | } 17 | 18 | override fun next(): Pair { 19 | val rs = row.rs 20 | // rs.getObject(i)获取列值时,下标是从“1”开始 21 | if(_curr++ >= rs.columnCount) 22 | throw IndexOutOfBoundsException("Index exceed column count") 23 | 24 | val label: String = rs.metaData.getColumnLabel(_curr); // 字段名 25 | val value: Any? = rs.getValue(_curr) // 字段值 26 | return label to value 27 | 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/DbResultSetIterator.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | /** 4 | * 结果集的迭代器 5 | */ 6 | class DbResultSetIterator(public val rs: DbResultSet): Iterator { 7 | 8 | override fun hasNext(): Boolean { 9 | return rs.next() 10 | } 11 | 12 | override fun next(): DbResultRow { 13 | return DbResultRow(rs) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/DbType.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | /** 4 | * 数据库类型 5 | * 6 | * @author shijianhang 7 | * @date 2016-10-8 下午8:02:47 8 | */ 9 | public enum class DbType { 10 | // 名字长的放前面,以便根据driverClass来获得db类型时,能更好的匹配 11 | Postgresql, 12 | SqlServer, 13 | Oracle, 14 | Hsqldb, 15 | Sqlite, 16 | Ingres, 17 | Mysql, 18 | DB2, 19 | H2 20 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/IDataSourceFactory.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | import net.jkcode.jkutil.scope.ClosingOnShutdown 4 | import net.jkcode.jkutil.common.getOrPutOnce 5 | import java.util.concurrent.ConcurrentHashMap 6 | import javax.sql.DataSource 7 | 8 | /** 9 | * 数据源工厂 10 | * 11 | * @author shijianhang 12 | * @date 2016-10-8 下午8:02:47 13 | */ 14 | abstract class IDataSourceFactory : ClosingOnShutdown() { 15 | 16 | /** 17 | * 缓存数据源 18 | */ 19 | private val dataSources: ConcurrentHashMap = ConcurrentHashMap(); 20 | 21 | /** 22 | * 获得数据源 23 | * 跨线程跨请求, 全局共有的数据源 24 | * @param name 数据源名 或 配置 25 | * @return 26 | */ 27 | public fun getDataSource(name: CharSequence): ClosableDataSource { 28 | return dataSources.getOrPutOnce(name){ 29 | ClosableDataSource(buildDataSource(name)) // 可关闭 30 | } 31 | } 32 | 33 | /** 34 | * 构建数据源 35 | * @param name 数据源名 或 配置 36 | * @return 37 | */ 38 | public abstract fun buildDataSource(name: CharSequence): DataSource 39 | 40 | /** 41 | * 关闭所有数据源 42 | */ 43 | public override fun close(){ 44 | for((name, dataSource) in dataSources){ 45 | (dataSource as AutoCloseable).close() 46 | } 47 | dataSources.clear(); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/IDbValueQuoter.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db 2 | 3 | import net.jkcode.jkutil.common.iteratorArrayOrCollection 4 | import net.jkcode.jkutil.common.joinToString 5 | 6 | /** 7 | * Db值转义器 8 | * 9 | * @ClassName: IDbValueQuoter 10 | * @Description: 11 | * @author shijianhang<772910474@qq.com> 12 | * @date 2018-11-21 7:28 PM 13 | */ 14 | interface IDbValueQuoter { 15 | 16 | /** 17 | * 转义值 18 | * 19 | * @param value 字段值, 可以是值数组 20 | * @return 21 | */ 22 | fun quote(value:Any?):String { 23 | // 1 多值, 一般是 where in 的值 24 | val itr = value?.iteratorArrayOrCollection() 25 | if(itr != null){ 26 | return itr.joinToString(", ", "(", ")") { 27 | quoteSingleValue(it) 28 | } 29 | } 30 | 31 | // 2 两值, 一般是 where between 的值 32 | if (value is Pair<*, *>) 33 | return quoteSingleValue(value.first) + " AND " + quoteSingleValue(value.second) 34 | 35 | // 3 单值 36 | return quoteSingleValue(value) 37 | } 38 | 39 | /** 40 | * 转义单个值 41 | * 42 | * @param value 字段值 43 | * @return 44 | */ 45 | fun quoteSingleValue(value: Any?): String 46 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/sharding/ShardingDataSourceFactory.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db.sharding 2 | 3 | import net.jkcode.jkmvc.db.IDataSourceFactory 4 | import java.io.File 5 | import javax.sql.DataSource 6 | 7 | /** 8 | * 数据源工厂 9 | * 使用 sharding-jdbc 10 | * 11 | * @author shijianhang 12 | * @date 2019-8-23 8:02:47 13 | */ 14 | object ShardingDataSourceFactory : IDataSourceFactory() { 15 | 16 | /** 17 | * 构建数据源 18 | * @param name 数据源名, 对应配置文件 dataSource-$name.yaml 19 | * @return 20 | */ 21 | override fun buildDataSource(name: CharSequence): DataSource { 22 | val config = Thread.currentThread().contextClassLoader.getResourceAsStream("dataSource-$name.yaml") 23 | return io.shardingjdbc.core.api.ShardingDataSourceFactory.createDataSource(config.readBytes()) 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/single/BaseDataSourceFactory.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db.single 2 | 3 | import com.alibaba.druid.pool.DruidDataSource 4 | import net.jkcode.jkmvc.db.IDataSourceFactory 5 | import net.jkcode.jkutil.common.Config 6 | import javax.sql.DataSource 7 | 8 | abstract class BaseDataSourceFactory: IDataSourceFactory(){ 9 | 10 | /** 11 | * 构建数据源 12 | * @param name 数据源名 13 | * @return 14 | */ 15 | override fun buildDataSource(name: CharSequence): DataSource { 16 | val config: Config = Config.instance("dataSources.$name", "yaml") 17 | return buildDataSource(config) 18 | } 19 | 20 | /** 21 | * 构建数据源 22 | * @param config 数据源和配置 23 | * @return 24 | */ 25 | public abstract fun buildDataSource(config: Config): DataSource 26 | 27 | /** 28 | * 获得校验sql 29 | * 30 | * @param driverClass 31 | * @return 32 | */ 33 | public fun getValidationQuery(driverClass:String):String{ 34 | val config = Config.instance("validation-query") 35 | return config[driverClass]!! 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/single/DruidSingleDb.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db.single 2 | 3 | import net.jkcode.jkmvc.db.ClosableDataSource 4 | 5 | /** 6 | * 单机的db 7 | * 使用 druid 来获得连接 8 | * 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2019-08-23 10:31 AM 11 | */ 12 | class DruidSingleDb(name:String /* 标识 */) : BaseSingleDb(name) { 13 | 14 | /** 15 | * 获得数据源 16 | * @param name 数据源名 17 | * @return 18 | */ 19 | protected override fun getDataSource(name: String): ClosableDataSource { 20 | return DruidDataSourceFactory.getDataSource(name); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/single/HikariDataSourceFactory.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db.single 2 | 3 | import com.zaxxer.hikari.HikariDataSource 4 | import net.jkcode.jkutil.common.Config 5 | import javax.sql.DataSource 6 | 7 | /** 8 | * Hikari数据源工厂 9 | * 参考 10 | * https://www.jianshu.com/p/5b2646ed7bc7 11 | * https://www.cnblogs.com/wangcp-2014/p/12155991.html 12 | * 13 | * @author shijianhang 14 | * @date 2021-3-8 下午8:02:47 15 | */ 16 | object HikariDataSourceFactory: BaseDataSourceFactory() { 17 | 18 | /** 19 | * 构建数据源 20 | * @param config 数据源和配置 21 | * @return 22 | */ 23 | public override fun buildDataSource(config: Config): DataSource { 24 | val ds = HikariDataSource() 25 | 26 | // 基本属性 url、user、password 27 | ds.setJdbcUrl(config["url"]) 28 | ds.setUsername(config["username"]) 29 | ds.setPassword(config["password"]) 30 | val driverClass: String = config["driverClassName"]!! 31 | ds.setDriverClassName(driverClass) 32 | 33 | ds.setConnectionTestQuery(config.get("validationQuery", getValidationQuery(driverClass))) 34 | ds.setConnectionTimeout(config.getLong("connectionTimeOut", 30000)!!) // 等待连接的超时(毫秒), 缺省:30秒 35 | ds.setIdleTimeout(config.getLong("idleTimeout", 600000)!!) // 连接空闲的最大时长(毫秒), 缺省:10分钟 36 | ds.setMaxLifetime(config.getLong("maxLifetime", 1800000)!! ) // 连接的生命时长(毫秒),超时且没被使用则被释放(retired),缺省:30分钟 37 | ds.setMaximumPoolSize(config.getInt("maxPoolSize", 10)!!) // 最大连接数, 默认10, 推荐的公式:((core_count * 2) + effective_spindle_count) 38 | ds.setMinimumIdle(config.getInt("minIdle", 10)!!) // 最小空闲数 默认 10 39 | 40 | return ds; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/db/single/HikariSingleDb.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.db.single 2 | 3 | import net.jkcode.jkmvc.db.ClosableDataSource 4 | 5 | /** 6 | * 单机的db 7 | * 使用 Hikari 来获得连接 8 | * 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2019-08-23 10:31 AM 11 | */ 12 | class HikariSingleDb(name:String /* 标识 */) : BaseSingleDb(name) { 13 | 14 | /** 15 | * 获得数据源 16 | * @param name 数据源名 17 | * @return 18 | */ 19 | protected override fun getDataSource(name: String): ClosableDataSource { 20 | return HikariDataSourceFactory.getDataSource(name); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/es/EntityTypeAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.es 2 | 3 | import com.google.gson.Gson 4 | import com.google.gson.TypeAdapter 5 | import com.google.gson.TypeAdapterFactory 6 | import com.google.gson.reflect.TypeToken 7 | import com.google.gson.stream.JsonReader 8 | import com.google.gson.stream.JsonWriter 9 | import net.jkcode.jkmvc.orm.IOrmEntity 10 | 11 | /** 12 | * gson类型适配器, 主要针对IOrmEntity 13 | * 14 | * @author shijianhang 15 | * @date 2021-4-21 下午5:16:59 16 | * 17 | */ 18 | class EntityTypeAdapterFactory(protected val mapTypeAdapter: TypeAdapter>) : TypeAdapterFactory { 19 | 20 | override fun create(gson: Gson, typeToken: TypeToken): TypeAdapter? { 21 | val rawType = typeToken.rawType as Class 22 | if (!IOrmEntity::class.java.isAssignableFrom(rawType)) 23 | return null 24 | 25 | return Adapter(rawType) 26 | } 27 | 28 | private inner class Adapter(public val clazz: Class) : TypeAdapter() { 29 | 30 | override fun read(`in`: JsonReader): T? { 31 | val map = mapTypeAdapter.read(`in`) as Map? 32 | if(map == null) 33 | return null 34 | 35 | val item = clazz.newInstance() as T 36 | (item as IOrmEntity).fromMap(map) 37 | return item 38 | } 39 | 40 | override fun write(out: JsonWriter, item: T?) { 41 | if (item == null) { 42 | out.nullValue() 43 | return 44 | } 45 | 46 | val map = (item as IOrmEntity).toMap() as HashMap 47 | mapTypeAdapter.write(out, map) 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/es/EsException.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.es 2 | 3 | import io.searchbox.client.JestResult 4 | import net.jkcode.jkutil.common.JkException 5 | 6 | /** 7 | * es操作异常 8 | * 9 | * @author shijianhang 10 | * @date 2021-4-21 下午5:16:59 11 | * 12 | */ 13 | class EsException : JkException { 14 | 15 | /** 16 | * 错误的文档 17 | */ 18 | public var result: JestResult? 19 | protected set 20 | 21 | 22 | public constructor(message: String, failedDocuments: JestResult? = null) : super(message) { 23 | this.result = failedDocuments 24 | } 25 | 26 | public constructor(cause: Throwable, failedDocuments: JestResult? = null) : super(cause) { 27 | this.result = failedDocuments 28 | } 29 | 30 | public constructor(message: String, cause: Throwable? = null, failedDocuments: JestResult? = null) : super(message, cause) { 31 | this.result = failedDocuments 32 | } 33 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/es/NestedAggregation.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.es 2 | 3 | import com.google.gson.JsonObject 4 | import io.searchbox.core.search.aggregation.AggregationField 5 | import io.searchbox.core.search.aggregation.Bucket 6 | 7 | /** 8 | * 嵌套文档的聚合 9 | * jest没有对应的类, 因此实现一个 10 | * 仅仅做嵌套文档聚合的中转 11 | * 12 | * @author shijianhang 13 | * @date 2021-4-21 下午5:16:59 14 | */ 15 | class NestedAggregation(name: String, bucket: JsonObject) : Bucket(name, bucket, bucket.getAsJsonPrimitive(AggregationField.DOC_COUNT.toString()).asLong /* doc_count 属性 */) { 16 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/es/annotation/EsDoc.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.es.annotation 2 | 3 | import net.jkcode.jkutil.common.getCachedAnnotation 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * es文档注解 8 | * 9 | * @author shijianhang 10 | * @date 2021-4-21 下午5:16:59 11 | */ 12 | @Retention(AnnotationRetention.RUNTIME) 13 | @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) 14 | annotation class EsDoc( 15 | public val index: String, // 索引名 16 | public val type: String = "_doc", // 类型 17 | public val esName: String = "default" // es配置名 18 | ) 19 | 20 | /** 21 | * 从类中获得 @EsDoc 注解 22 | */ 23 | public val KClass.esDoc: EsDoc? 24 | get() { 25 | return getCachedAnnotation() 26 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/es/annotation/EsId.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.es.annotation 2 | 3 | import net.jkcode.jkutil.common.getCachedAnnotation 4 | import kotlin.reflect.KClass 5 | import kotlin.reflect.KProperty1 6 | import kotlin.reflect.full.memberProperties 7 | 8 | /** 9 | * es id的注解 10 | * 用于表示 _id 的字段 11 | * 12 | * @author shijianhang 13 | * @date 2021-4-21 下午5:16:59 14 | */ 15 | @Target(AnnotationTarget.PROPERTY) 16 | @Retention(AnnotationRetention.RUNTIME) 17 | annotation class EsId 18 | 19 | /** 20 | * 从属性中获得 @EsId 注解 21 | */ 22 | public val KProperty1<*, *>.esId: EsId? 23 | get() { 24 | return getCachedAnnotation() 25 | } 26 | 27 | /** 28 | * 从类中获得有 @EsId 注解的属性 29 | */ 30 | public val KClass.esIdProp: KProperty1? 31 | get() { 32 | return this.memberProperties.firstOrNull { 33 | it.esId != null 34 | } 35 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/model/EmptyOrmMeta.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.model 2 | 3 | import net.jkcode.jkmvc.orm.IOrm 4 | import net.jkcode.jkmvc.orm.OrmException 5 | import net.jkcode.jkmvc.orm.OrmMeta 6 | import net.jkcode.jkmvc.orm.OrmQueryBuilder 7 | import kotlin.reflect.KClass 8 | 9 | /** 10 | * 空的元数据 11 | * 实例化时还是需要的元数据 EmptyOrmMeta, 但他不是真正的元数据,只为了在 KClass.modelRowTransformer 中使用 12 | * 真正的元数据设置是在 GeneralOrmQueryBuilder.findRow()/findRows() 调用 GeneralModel.delaySetMeta() 13 | * 14 | * @author shijianhang<772910474@qq.com> 15 | * @date 2018-12-17 3:38 PM 16 | */ 17 | open class EmptyOrmMeta(model: KClass /* 模型类 */): OrmMeta(model, "?", "?", "?", checkingTablePrimaryKey = false){ 18 | 19 | /** 20 | * 禁用 queryBuilder() 21 | * 22 | * @param convertingValue 查询时是否智能转换字段值 23 | * @param convertingColumn 查询时是否智能转换字段名 24 | * @param withSelect with()联查时自动select关联表的字段 25 | * @return 26 | */ 27 | public override fun queryBuilder(convertingValue: Boolean, convertingColumn: Boolean, withSelect: Boolean): OrmQueryBuilder { 28 | throw OrmException("class [EmptyOrmMeta] does not support method [queryBuilder()]") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/model/GeneralOrmMeta.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.model 2 | 3 | import net.jkcode.jkmvc.orm.* 4 | import kotlin.reflect.KClass 5 | 6 | /** 7 | * 通用模型的元数据 8 | * 改写 queryBuilder(), 返回 GeneralOrmQueryBuilder 9 | * 10 | * @author shijianhang<772910474@qq.com> 11 | * @date 2018-12-17 3:38 PM 12 | */ 13 | open class GeneralOrmMeta(label: String, // 模型中文名 14 | table: String, // 表名 15 | primaryKey:DbKeyNames = DbKeyNames("id"), // 主键 16 | cacheMeta: OrmCacheMeta? = null, // 缓存配置 17 | dbName: String = "default" // 数据库名 18 | ): OrmMeta(GeneralModel::class, label, table, primaryKey, cacheMeta, dbName){ 19 | 20 | public constructor( 21 | label: String, // 模型中文名 22 | table: String, // 表名,假定model类名, 都是以"Model"作为后缀 23 | primaryKey: String, // 主键 24 | cacheMeta: OrmCacheMeta? = null, // 缓存配置 25 | dbName: String = "default" // 数据库名 26 | ) : this(label, table, DbKeyNames(primaryKey), cacheMeta, dbName) 27 | 28 | /** 29 | * 改写 queryBuilder(), 返回 GeneralOrmQueryBuilder 30 | * 31 | * @param convertingValue 查询时是否智能转换字段值 32 | * @param convertingColumn 查询时是否智能转换字段名 33 | * @param withSelect with()联查时自动select关联表的字段 34 | * @return 35 | */ 36 | public override fun queryBuilder(convertingValue: Boolean, convertingColumn: Boolean, withSelect: Boolean): OrmQueryBuilder { 37 | return GeneralOrmQueryBuilder(this, convertingValue, convertingColumn, withSelect); 38 | } 39 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/model/GeneralOrmQueryBuilder.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.model 2 | 3 | import net.jkcode.jkmvc.db.DbResultRow 4 | import net.jkcode.jkmvc.db.IDb 5 | import net.jkcode.jkmvc.orm.IOrmMeta 6 | import net.jkcode.jkmvc.orm.OrmMeta 7 | import net.jkcode.jkmvc.orm.OrmQueryBuilder 8 | 9 | /** 10 | * 通用模型的查询构建器 11 | * 改写 OrmQueryBuilder, 在查询并创建 GeneralModel 后才能由 GeneralOrmQueryBuilder 设置其 ormMeta, 即调用 GeneralModel.delaySetMeta(ormMeta) 12 | * 因为创建过程在 KClass.modelRowTransformer(), 无 ormMeta 参数, 只能使用默认构造函数, 给默认参数 EmptyOrmMeta 13 | * 14 | * @author shijianhang<772910474@qq.com> 15 | * @date 2018-12-17 3:38 PM 16 | */ 17 | class GeneralOrmQueryBuilder(ormMeta: OrmMeta, // orm元数据 18 | convertingValue: Boolean = false, // 查询时是否智能转换字段值 19 | convertingColumn: Boolean = false, // 查询时是否智能转换字段名 20 | withSelect: Boolean = true // with()联查时自动select关联表的字段 21 | ): OrmQueryBuilder(ormMeta, convertingValue, convertingColumn, withSelect){ 22 | 23 | /** 24 | * 包装行转换函数 25 | * @param transform 行转换函数, 在 findRows()/findRow() 中使用 26 | * @return 27 | */ 28 | protected fun wrapRowTransform(transform: (DbResultRow) -> T): (DbResultRow) -> T { 29 | return { row -> 30 | val item = transform.invoke(row) 31 | if(item != null && item is GeneralModel) 32 | item.delaySetMeta(ormMeta) 33 | item 34 | } 35 | } 36 | 37 | /** 38 | * 查找一个: select ... limit 1语句 39 | * 40 | * @param params 参数 41 | * @param transform 行转换函数 42 | * @return 单个数据 43 | */ 44 | public override fun findRow(params: List<*>, db: IDb, transform: (DbResultRow) -> T): T?{ 45 | return super.findRow(params, db, wrapRowTransform(transform) /* 包装行转换函数 */) 46 | } 47 | 48 | /** 49 | * 查找多个: select 语句 50 | * 51 | * @param params 参数 52 | * @param transform 行转换函数 53 | * @return 列表 54 | */ 55 | public override fun findRows(params: List<*>, db: IDb, transform: (DbResultRow) -> T): List{ 56 | return super.findRows(params, db, wrapRowTransform(transform) /* 包装行转换函数 */) 57 | } 58 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/IEntitiableOrm.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | import net.jkcode.jkutil.common.getInterfaceGenricType 4 | 5 | /** 6 | * 实体化的IOrm 7 | * 可与实体类相互转换 8 | * 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2019-09-12 9:26 AM 11 | */ 12 | interface IEntitiableOrm: IOrm { 13 | 14 | /** 15 | * 从其他实体对象中设置字段值 16 | * 对于关联对象字段值的设置: 只考虑一对一的关联对象, 不考虑一对多的关联对象 17 | * 18 | * @param from 19 | */ 20 | public fun fromEntity(from: IOrmEntity){ 21 | for(column in ormMeta.propsAndRelations) { 22 | val value:Any? = from[column] 23 | if(value is IOrmEntity){ // 如果是IOrmEntity,则为关联对象 24 | val realValue = getRelatedOrNew(column) // 创建关联对象 25 | (realValue as IEntitiableOrm<*>).fromEntity(value) // 递归设置关联对象的字段值 26 | }else 27 | set(column, value) 28 | } 29 | } 30 | 31 | /** 32 | * 转为实体对象 33 | * 对于关联对象字段值的设置: 只考虑一对一的关联对象, 不考虑一对多的关联对象 34 | * 35 | * @return 36 | */ 37 | public fun toEntity(): E{ 38 | // 获得实体类: 当前类实现 IEntitiableOrm 接口时, 指定的泛型类型 39 | val entityClass = this.javaClass.getInterfaceGenricType(IEntitiableOrm::class.java)!! 40 | //val entityClass = ormMeta.entityClass!! 41 | // 创建实体 42 | val entity = entityClass.newInstance() as E 43 | 44 | // 1 转关联对象 45 | for((name, relation) in ormMeta.relations){ 46 | val value: Any? = this[name] 47 | if(value != null){ 48 | entity[name] = when(value){ 49 | is Collection<*> -> (value as Collection>).toEntities() // 有多个 50 | is IEntitiableOrm<*> -> value.toEntity() // 有一个 51 | else -> value 52 | } 53 | } 54 | } 55 | 56 | // 2 转当前对象:由于关联对象联查时不处理null值, 因此关联对象会缺少null值的字段,这里要补上 57 | for(prop in ormMeta.props) 58 | entity[prop] = this[prop] 59 | 60 | return entity 61 | } 62 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/IOrm.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | /** 4 | * ORM总接口 5 | * 6 | * @author shijianhang 7 | * @date 2016-10-10 上午12:52:34 8 | * 9 | */ 10 | interface IOrm : IOrmRelated { 11 | 12 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/IOrmValid.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | import net.jkcode.jkutil.validator.ModelValidateResult 4 | import net.jkcode.jkutil.validator.ValidateResult 5 | 6 | /** 7 | * ORM之数据校验 8 | * 9 | * @author shijianhang 10 | * @date 2016-10-10 上午12:52:34 11 | * 12 | */ 13 | interface IOrmValid : IOrmEntity { 14 | 15 | /** 16 | * 校验数据 17 | * @return 18 | */ 19 | fun validate(): ModelValidateResult?; 20 | 21 | /** 22 | * 校验数据 23 | */ 24 | fun validateOrThrow() { 25 | validate()?.getOrThrow() 26 | } 27 | 28 | /** 29 | * 标记字段为脏 30 | * @param column 字段名 31 | * @param flag 是否脏 32 | */ 33 | fun setDirty(column: String, flag: Boolean = true) 34 | 35 | /** 36 | * 检查字段是否为脏 37 | * @param column 字段名 38 | */ 39 | fun isDirty(column: String): Boolean 40 | 41 | /** 42 | * 检查字段是否为脏 43 | * @param key 字段名 44 | */ 45 | public fun isDirty(key: DbKeyNames): Boolean { 46 | return key.columns.any { column -> 47 | isDirty(column) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/OrmCacheMeta.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | /** 4 | * orm缓存的元数据 5 | * 6 | * @author shijianhang 7 | * @date 2020-3-10 上午12:52:34 8 | */ 9 | class OrmCacheMeta( 10 | public val cacheType: String= "lru", // 缓存类型, 如 lru/jedis 11 | public val withs: Array = emptyArray(), // 联查对象属性 12 | public val initAll: Boolean = false // 一开始就缓存全部数据 13 | ) { 14 | 15 | /** 16 | * 模型元数据 17 | */ 18 | public lateinit var ormMeta:IOrmMeta 19 | 20 | /** 21 | * 是否联查某个关联属性 22 | * @param name 23 | * @return 24 | */ 25 | public fun hasWithRelation(name: String): Boolean { 26 | return withs.contains(name) 27 | } 28 | 29 | /** 30 | * 是否联查某个关联属性 31 | * @param relatedMeta 关联模型的元数据 32 | * @return 33 | */ 34 | public fun hasWithRelation(relatedMeta: OrmMeta): Boolean { 35 | // 遍历每个关联关系, 匹配关联模型的元数据 36 | for((name, relation) in ormMeta.relations){ 37 | // 匹配关联模型的元数据 + 连带缓存了该关联对象 38 | if(relation.ormMeta == relatedMeta && hasWithRelation(name)) 39 | return true 40 | } 41 | return false 42 | } 43 | 44 | /** 45 | * 对query builder应用联查 46 | * @param query 47 | */ 48 | public fun applyQueryWiths(query: OrmQueryBuilder) { 49 | if(withs.isEmpty()) 50 | return 51 | 52 | // fix bug: 不能联查下一级的 53 | //query.withs(*withs) 54 | query.selectWiths(*build2LevelWiths().toTypedArray()) 55 | } 56 | 57 | /** 58 | * 构建2级的联查对象属性, 其结果用在 OrmQueryBuilder.selectWiths(...) 59 | * TODO: 支持多级联查 60 | */ 61 | protected fun build2LevelWiths(): List { 62 | return withs.map { name -> 63 | val relation = ormMeta.getRelation(name)!! 64 | val nextOrmMeta = relation.ormMeta 65 | val nextCacheMeta = nextOrmMeta.cacheMeta 66 | if(nextCacheMeta?.withs.isNullOrEmpty()){ // 无下一级联查 67 | name 68 | }else{ // 有下一级联查 69 | // name to nextCacheMeta!!.withs.toList() // 如果name是一对多的关系,会自动select *, 否则不会, 因此需要指定name对应模型的字段 70 | // name to nextOrmMeta.props + nextCacheMeta!!.withs 71 | name to listOf("*") + nextCacheMeta!!.withs // 简写 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/OrmException.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | import net.jkcode.jkutil.common.JkException 4 | 5 | /** 6 | * orm操作异常 7 | * 8 | * @author shijianhang 9 | * @date 2016-10-21 下午5:16:59 10 | * 11 | */ 12 | class OrmException : JkException { 13 | 14 | public constructor(cause: Throwable) : super(cause) { 15 | } 16 | 17 | public constructor(message: String, cause: Throwable? = null) : super(message, cause) { 18 | } 19 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/OrmQueryBuilderListener.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | /** 4 | * OrmQueryBuilder的事件监听器 5 | * 例子 6 | * 1. 查询前置处理: 对某个模型类的 queryBuilder 做全局的配置, 如添加全局的where条件 7 | * 2. 更新后置处理: 关联对象的增删改, 需要通知主对象, 以便刷新主对象的缓存 8 | * 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2020-04-22 2:49 PM 11 | */ 12 | abstract class OrmQueryBuilderListener { 13 | 14 | /** 15 | * 查询前置处理 16 | */ 17 | open fun beforeFind(query: OrmQueryBuilder){} 18 | 19 | /** 20 | * 查询后置处理 21 | */ 22 | open fun afterFind(query: OrmQueryBuilder){} 23 | 24 | /** 25 | * 更新前置处理 26 | */ 27 | open fun beforeExecute(query: OrmQueryBuilder){} 28 | 29 | /** 30 | * 更新后置处理 31 | */ 32 | open fun afterExecute(query: OrmQueryBuilder){} 33 | 34 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/OrmValid.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | import net.jkcode.jkutil.collection.FixedKeyMapFactory 4 | import net.jkcode.jkutil.validator.ModelValidateResult 5 | 6 | 7 | /** 8 | * ORM之数据校验+格式化 9 | * 10 | * @author shijianhang 11 | * @date 2016-10-10 上午12:52:34 12 | * 13 | */ 14 | abstract class OrmValid : IOrm, OrmEntity() { 15 | 16 | /** 17 | * 改写 OrmEntity 中的 data属性 18 | * 最新的字段值:<字段名 to 最新字段值> 19 | */ 20 | protected override val _data: FixedKeyMapFactory.FixedKeyMap by lazy{ 21 | ormMeta.dataFactory.createMap() 22 | } 23 | 24 | /** 25 | * 变化的字段值:<字段名 to 原始字段值> 26 | * 一般只读,lazy创建,节省内存 27 | */ 28 | protected val _dirty: MutableMap by lazy { 29 | HashMap() 30 | } 31 | 32 | /** 33 | * 设置对象字段值 34 | * 支持记录变化的字段名 + 原始值 35 | * 36 | * @param column 字段名 37 | * @param value 字段值 38 | */ 39 | public override operator fun set(column: String, value: Any?) { 40 | if (!hasColumn(column)) 41 | throw OrmException("Model class ${this.javaClass} has no property: $column"); 42 | 43 | // 记录变化的字段名 + 原始值 44 | if(!_dirty.containsKey(column) 45 | //&& value != _data[column]) 46 | && !equalsValue(_data[column], value)) 47 | _dirty[column] = _data[column]; 48 | 49 | super.set(column, value) 50 | } 51 | 52 | /** 53 | * 标记字段为脏 54 | * @param column 字段名 55 | * @param flag 是否脏 56 | */ 57 | public override fun setDirty(column: String, flag: Boolean){ 58 | // 标记为脏 59 | if(flag) { 60 | if (_dirty.containsKey(column)) 61 | _dirty[column] = _data[column]; 62 | return 63 | } 64 | 65 | // 标记不脏 66 | _dirty.remove(column) 67 | } 68 | 69 | /** 70 | * 检查字段是否为脏 71 | * @param column 字段名 72 | */ 73 | public override fun isDirty(column: String): Boolean { 74 | return _dirty.containsKey(column) 75 | } 76 | 77 | /** 78 | * 校验数据 79 | * @return 80 | */ 81 | public override fun validate(): ModelValidateResult? { 82 | return ormMeta.validate(this) 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/PkEmptyRule.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | /** 4 | * 检查主键为空的规则 5 | * 6 | * @author shijianhang 7 | * @date 2020-2-1 12:00 PM 8 | */ 9 | class PkEmptyRule(public var rule: Int = 0) { 10 | 11 | companion object{ 12 | 13 | // 允许数字0 14 | @JvmStatic 15 | public val ALLOW_NUMBER_0: Int = 0 16 | 17 | // 允许空字符串 18 | @JvmStatic 19 | public val ALLOW_STRING_EMPTY: Int = 1 20 | 21 | @JvmStatic 22 | public val default = PkEmptyRule(0) 23 | } 24 | 25 | /** 26 | * 允许位为true 27 | * @param bit 28 | */ 29 | public fun allow(bit: Int) { 30 | rule = rule or (1 shl bit) 31 | } 32 | 33 | /** 34 | * 是否允许位为true 35 | * @param bit 36 | * @return 37 | */ 38 | public fun isAllow(bit: Int): Boolean { 39 | return rule and (1 shl bit) > 0 40 | } 41 | 42 | /** 43 | * 检查主键值是否为空 44 | * @param pk 主键值 45 | * @return 46 | */ 47 | public fun isEmpty(pk: Any?): Boolean { 48 | return when(pk){ 49 | null -> true 50 | is Int -> !isAllow(ALLOW_NUMBER_0) && pk == 0 // 数字0 51 | is String -> !isAllow(ALLOW_STRING_EMPTY) && pk.isBlank() // 空字符串 52 | is DbKey<*> -> pk.columns.any(::isEmpty) // 复合主键中的任一字段值不能为空 53 | else -> false 54 | } 55 | } 56 | 57 | 58 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/PkGenerator.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm 2 | 3 | import net.jkcode.jkutil.common.generateId 4 | import net.jkcode.jkutil.common.generateUUID 5 | 6 | /** 7 | * 主键生成器 8 | * @author shijianhang<772910474@qq.com> 9 | * @date 2019-06-04 9:49 AM 10 | */ 11 | public enum class PkGenerator { 12 | 13 | // 异常数 14 | SNOWFLAKE { 15 | override fun generate(ormMeta: OrmMeta): Any = generateId(ormMeta.name) 16 | }, 17 | // 异常比例 18 | UUID { 19 | override fun generate(ormMeta: OrmMeta): Any = generateUUID() 20 | }; 21 | 22 | /** 23 | * 生成主键值 24 | * @param ormMeta orm的元数据 25 | * @return 26 | */ 27 | public abstract fun generate(ormMeta: OrmMeta): Any 28 | 29 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/jphp/JkmvcOrmExtension.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.jphp 2 | 3 | import net.jkcode.jkmvc.db.Db 4 | import net.jkcode.jkmvc.orm.Orm 5 | import net.jkcode.jkmvc.query.DbQueryBuilder 6 | import php.runtime.env.CompileScope 7 | import php.runtime.ext.support.Extension 8 | import org.develnext.jphp.zend.ext.ZendExtension 9 | 10 | class JkmvcOrmExtension : Extension() { 11 | 12 | companion object { 13 | const val NS = "php\\jkmvc\\orm" 14 | } 15 | 16 | override fun getStatus(): Status { 17 | return Status.EXPERIMENTAL 18 | } 19 | 20 | override fun getRequiredExtensions(): Array? { 21 | return arrayOf( 22 | ZendExtension::class.java.getName() 23 | ) 24 | } 25 | 26 | override fun getPackageNames(): Array { 27 | return arrayOf("jkmvc\\orm") 28 | } 29 | 30 | override fun onRegister(scope: CompileScope) { 31 | registerWrapperClass(scope, Db::class.java, PDb::class.java) 32 | registerWrapperClass(scope, Orm::class.java, PModel::class.java) 33 | registerWrapperClass(scope, DbQueryBuilder::class.java, PQueryBuilder::class.java) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/prop/OrmListPropDelegater.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.prop 2 | 3 | import net.jkcode.jkmvc.orm.IOrmEntity 4 | import java.io.Serializable 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KProperty 7 | 8 | /** 9 | * orm列表属性代理 10 | */ 11 | object OrmListPropDelegater: ReadWriteProperty, Serializable { 12 | // 获得属性 13 | public override operator fun getValue(thisRef: IOrmEntity, property: KProperty<*>): Any? { 14 | return thisRef.getOrPut(property.name){ 15 | //LinkedList() 16 | emptyList() 17 | } 18 | } 19 | 20 | // 设置属性 21 | public override operator fun setValue(thisRef: IOrmEntity, property: KProperty<*>, value: Any?) { 22 | thisRef[property.name] = value 23 | } 24 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/prop/OrmMapPropDelegater.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.prop 2 | 3 | import net.jkcode.jkmvc.orm.DbKeyNames 4 | import net.jkcode.jkmvc.orm.IOrmEntity 5 | import java.io.Serializable 6 | import java.util.HashSet 7 | import java.util.concurrent.ConcurrentHashMap 8 | import kotlin.properties.ReadWriteProperty 9 | import kotlin.reflect.KProperty 10 | 11 | 12 | /** 13 | * orm map属性代理 14 | * TODO: 只是简单将list转为map, 只读 15 | */ 16 | class OrmMapPropDelegater protected constructor (public val keys: DbKeyNames): ReadWriteProperty, Serializable { 17 | 18 | companion object{ 19 | 20 | /** 21 | * 单例池: <类 to 单例> 22 | */ 23 | private val insts: ConcurrentHashMap = ConcurrentHashMap(); 24 | 25 | /** 26 | * 获得单例 27 | */ 28 | public fun instance(keys: DbKeyNames): OrmMapPropDelegater { 29 | return insts.getOrPut(keys){ 30 | OrmMapPropDelegater(keys) 31 | } 32 | } 33 | } 34 | 35 | // 获得属性 36 | public override operator fun getValue(thisRef: IOrmEntity, property: KProperty<*>): Any? { 37 | val list: List? = thisRef[property.name] 38 | if(list == null) 39 | return emptyMap() 40 | 41 | return (list as List).associateBy { item -> 42 | keys.columns.joinToString("::") { key -> 43 | item[key] 44 | } 45 | } 46 | } 47 | 48 | // 设置属性 49 | public override operator fun setValue(thisRef: IOrmEntity, property: KProperty<*>, value: Any?) { 50 | val list = (value as Map?)?.mapTo(HashSet()) { it.value } 51 | thisRef[property.name] = list 52 | } 53 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/prop/OrmPropDelegater.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.prop 2 | 3 | import net.jkcode.jkmvc.orm.IOrmEntity 4 | import java.io.Serializable 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KClass 7 | import kotlin.reflect.KProperty 8 | 9 | /** 10 | * orm普通属性代理 11 | */ 12 | object OrmPropDelegater: ReadWriteProperty, Serializable { 13 | // 获得属性 14 | public override operator fun getValue(thisRef: IOrmEntity, property: KProperty<*>): Any? { 15 | val value: Any? = thisRef[property.name] 16 | // int转bool 17 | if(value != null && Boolean::class == (property.returnType.classifier as KClass<*>) && value !is Boolean){ 18 | return when(value) { 19 | is Int -> value > 0 20 | is Long -> value > 0 21 | is Short -> value > 0 22 | is Byte -> value > 0 23 | is Float -> value > 0 24 | is Double -> value > 0 25 | else -> throw IllegalArgumentException("Cannot auto convert [$value] into Boolean") 26 | } 27 | } 28 | 29 | // 其他 30 | return value 31 | } 32 | 33 | // 设置属性 34 | public override operator fun setValue(thisRef: IOrmEntity, property: KProperty<*>, value: Any?) { 35 | thisRef[property.name] = value 36 | } 37 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/prop/OrmSetPropDelegater.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.prop 2 | 3 | import net.jkcode.jkmvc.orm.IOrmEntity 4 | import java.io.Serializable 5 | import kotlin.properties.ReadWriteProperty 6 | import kotlin.reflect.KProperty 7 | 8 | 9 | /** 10 | * orm set属性代理 11 | * TODO: 只是简单将list转为Set, 只读 12 | */ 13 | object OrmSetPropDelegater: ReadWriteProperty, Serializable { 14 | // 获得属性 15 | public override operator fun getValue(thisRef: IOrmEntity, property: KProperty<*>): Any? { 16 | val list: List? = thisRef[property.name] 17 | if(list == null) 18 | return emptySet() 19 | 20 | return list.toSet() 21 | } 22 | 23 | // 设置属性 24 | public override operator fun setValue(thisRef: IOrmEntity, property: KProperty<*>, value: Any?) { 25 | // set也是集合, 不用转了 26 | thisRef[property.name] = value 27 | } 28 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/relation/ICbRelation.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.relation 2 | 3 | import net.jkcode.jkmvc.orm.IOrm 4 | 5 | /** 6 | * 通过回调动态获得对象的关联关系 7 | * 回调可以是rpc 8 | * 9 | * @author shijianhang<772910474@qq.com> 10 | * @date 2020-7-9 7:13 PM 11 | */ 12 | interface ICbRelation { 13 | 14 | /** 15 | * 关系名 16 | */ 17 | val name: String 18 | 19 | /** 20 | * 是否一对一 21 | */ 22 | val one2one: Boolean 23 | 24 | /** 25 | * 主模型的主键的getter 26 | */ 27 | val pkGetter: (M)->K 28 | 29 | /** 30 | * 从对象的外键的getter 31 | */ 32 | val fkGetter: (R)->K 33 | 34 | /** 35 | * 批量获取关联对象的回调 36 | */ 37 | val relatedSupplier:(List) -> List 38 | 39 | /** 40 | * 查询关联对象 41 | * 自动根据关联关系,来构建查询条件 42 | * 43 | * @param item Orm对象 44 | * @return 45 | */ 46 | fun findRelated(item: M): R? 47 | 48 | /** 49 | * 查询关联对象 50 | * 自动根据关联关系,来构建查询条件 51 | * 52 | * @param orm Orm对象或列表 53 | * @return 54 | */ 55 | fun findAllRelated(orm: Any): List{ 56 | return when(orm){ 57 | is IOrm -> findAllRelated(orm) 58 | is Collection<*> -> findAllRelated(orm as Collection) 59 | else -> throw IllegalArgumentException("对relation.findAllRelated(参数)方法,其参数必须是Orm对象或Orm列表") 60 | } 61 | } 62 | 63 | /** 64 | * 查询关联对象 65 | * 自动根据关联关系,来构建查询条件 66 | * 67 | * @param item Orm对象 68 | * @return 69 | */ 70 | fun findAllRelated(item: M): List 71 | 72 | /** 73 | * 查询关联对象 74 | * 自动根据关联关系,来构建查询条件 75 | * 76 | * @param items Orm列表 77 | * @return 78 | */ 79 | fun findAllRelated(items: Collection): List 80 | 81 | /** 82 | * 批量设置关系的属性值 83 | * 84 | * @param items 本模型对象 85 | * @param relatedItems 关联模型对象 86 | */ 87 | fun batchSetRelationProp(items: List, relatedItems: List) 88 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/relation/RelationConditions.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.relation 2 | 3 | import net.jkcode.jkmvc.orm.OrmQueryBuilder 4 | 5 | /** 6 | * 关联关系的联查条件 7 | * @author shijianhang<772910474@qq.com> 8 | * @date 2020-06-26 9:52 AM 9 | */ 10 | data class RelationConditions( 11 | public val conditions :Map, // 查询条件 12 | public var queryAction: ((query: OrmQueryBuilder, lazy: Boolean)->Unit)? // 查询对象的回调函数 13 | ){ 14 | companion object{ 15 | 16 | /** 17 | * 空条件 18 | */ 19 | val EmptyConditions = RelationConditions(emptyMap(), null) 20 | } 21 | 22 | public val size: Int 23 | get() = conditions.size + if(queryAction == null) 0 else 1 24 | 25 | /** 26 | * 添加回调函数 27 | * 即合并2个回调函数 28 | * @param nextAction 下个回调函数 29 | */ 30 | public fun addQueryAction(nextAction: (query: OrmQueryBuilder, lazy: Boolean)->Unit){ 31 | if(queryAction == null) { 32 | queryAction = nextAction 33 | return 34 | } 35 | 36 | // 合并2个回调函数 37 | val preAction = queryAction 38 | queryAction = { query, lazy -> 39 | preAction!!(query, lazy) 40 | nextAction(query, lazy) 41 | } 42 | } 43 | 44 | /** 45 | * 对query builder应用联查 46 | * @param query 47 | * @param lazy 是否延迟加载, 分2条sql, 否则同一条sql 48 | */ 49 | public fun applyQuery(query: OrmQueryBuilder, lazy: Boolean) { 50 | if(lazy) 51 | query.wheres(conditions) 52 | else 53 | query.ons(conditions, false) 54 | 55 | queryAction?.invoke(query, lazy) 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/orm/serialize/OrmEntityFstSerializer.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.orm.serialize 2 | 3 | import net.jkcode.jkmvc.orm.IOrm 4 | import net.jkcode.jkmvc.orm.OrmEntity 5 | import net.jkcode.jkutil.collection.FixedKeyMapFactory 6 | import org.nustaq.serialization.FSTBasicObjectSerializer 7 | import org.nustaq.serialization.FSTClazzInfo 8 | import org.nustaq.serialization.FSTObjectInput 9 | import org.nustaq.serialization.FSTObjectOutput 10 | import org.nustaq.serialization.serializers.FSTMapSerializer 11 | 12 | /** 13 | * ORM之实体对象的序列器 14 | * @author shijianhang 15 | * @date 2016-10-10 上午12:52:34 16 | */ 17 | class OrmEntityFstSerializer : FSTBasicObjectSerializer() { 18 | 19 | /** 20 | * map的序列器 21 | */ 22 | protected val mapSerializer: FSTMapSerializer = FSTMapSerializer() 23 | 24 | /** 25 | * 写 26 | */ 27 | public override fun writeObject(out: FSTObjectOutput, toWrite: Any, clzInfo: FSTClazzInfo, referencedBy: FSTClazzInfo.FSTFieldInfo, streamPosition: Int) { 28 | val entity = toWrite as OrmEntity 29 | // 写 OrmEntity._data 30 | var data = entity.getData() 31 | if(data is FixedKeyMapFactory.FixedKeyMap) // Orm._data is FixedKeyMap 32 | data = HashMap(data) 33 | // 写 Orm.loaded 34 | if(entity is IOrm) 35 | data["_loaded"] = entity.loaded 36 | mapSerializer.writeObject(out, data, clzInfo, referencedBy, streamPosition) 37 | } 38 | 39 | /** 40 | * 读 41 | */ 42 | public override fun instantiate(objectClass: Class<*>, `in`: FSTObjectInput, serializationInfo: FSTClazzInfo, referencee: FSTClazzInfo.FSTFieldInfo, streamPosition: Int): Any { 43 | val entity = objectClass.newInstance() as OrmEntity 44 | // 读 OrmEntity._data 45 | val data = mapSerializer.instantiate(HashMap::class.java, `in`, serializationInfo, referencee, streamPosition) as HashMap 46 | // 读 Orm.loaded 47 | if(entity is IOrm) 48 | entity.loaded = data.remove("_loaded") as Boolean? 49 | ?: false 50 | entity.getData().putAll(data) 51 | return entity 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/query/DbCondition.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.query 2 | 3 | /** 4 | * Db条件, 带参数, 专门用于 DbQueryBuilderDecoration.andWhereCondition()/orWhereCondition(), 用来表示不转义不拼接的条件表达式, 生成sql时原样输出 5 | * 6 | * 7 | * @author shijianhang 8 | * @create 2017-11-19 下午1:47 9 | **/ 10 | data class DbCondition(public val exp:String, // 条件表达式 11 | public val params: List<*> = emptyList() // 参数 12 | ) : CharSequence by exp { 13 | 14 | /** 15 | * 转字符串 16 | * @return 17 | */ 18 | public override fun toString(): String { 19 | return exp 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/query/DecorationPartType.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.query 2 | 3 | /** 4 | * 修饰子句的类型 5 | * @author shijianhang 6 | * @date 2016-10-10 7 | */ 8 | enum class DecorationPartType { 9 | WHERE, 10 | GROUP_BY, 11 | HAVING, 12 | ORDER_BY, 13 | LIMIT 14 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/query/IDbQueryPart.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.query 2 | 3 | import net.jkcode.jkmvc.db.IDb 4 | 5 | /** 6 | * sql子句的模拟构建 7 | * 1 结构 8 | * 每个子句(如where xxx and yyy/group by xxx, yyy)包含多个子表达式(如where可以有多个条件子表达式, 如name="shi", age=1), 每个子表达式有多个元素组成(如name/=/"shi") 9 | * 每个元素有对应的处理函数 10 | * T是子表达式的类型 11 | * 2 compile()为什么需要 DbQueryBuilderDecoration 参数 12 | * 考虑到 DbQueryBuilderDecoration 对象的克隆, 如果使用 DbQueryBuilderDecoration 对象的方法(如 this::quoteColumn, 可省略 DbQueryBuilderDecoration 参数), 13 | * 而不是类的方法(如 DbQueryBuilderDecoration::quoteColumn), 则克隆 DbQueryBuilderDecoration 对象时中`clauses`属性还是引用旧的 DbQueryBuilderDecoration 对象, 14 | * 进而导致新的 DbQueryBuilderDecoration 对象不能正确的编译sql 15 | * 16 | * @author shijianhang 17 | * @date 2016-10-13 18 | */ 19 | interface IDbQueryPart { 20 | 21 | /** 22 | * 编译多个子表达式 23 | * 24 | * @param query 查询构建器 25 | * @param db 数据库连接 26 | * @param sql 保存编译sql 27 | */ 28 | fun compile(query: DbQueryBuilderDecoration, db: IDb, sql:StringBuilder); 29 | 30 | /** 31 | * 清空 32 | * @return 33 | */ 34 | fun clear() 35 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/query/SqlAction.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.query 2 | 3 | /** 4 | * sql的动作类型 5 | * @author shijianhang 6 | * @date 2016-10-10 7 | */ 8 | public enum class SqlAction { 9 | SELECT{ // 查 10 | override val template: DbQueryPartTemplate = DbQueryPartTemplate("SELECT FROM ") 11 | }, 12 | INSERT{ // 增 13 | override val template: DbQueryPartTemplate = DbQueryPartTemplate("INSERT INTO () ") // quoteColumn() 默认不加(), quote() 默认加() 14 | }, 15 | UPDATE{ // 改 16 | override val template: DbQueryPartTemplate = DbQueryPartTemplate("UPDATE SET ") 17 | }, 18 | DELETE{ // 删 19 | override val template: DbQueryPartTemplate = DbQueryPartTemplate("DELETE FROM ") 20 | }; 21 | 22 | abstract val template: DbQueryPartTemplate; 23 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/kotlin/net/jkcode/jkmvc/util/ITreeNode.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.util 2 | 3 | /** 4 | * 树形节点,泛型T是id/pid的类型 5 | * 6 | * @author shijianhang<772910474@qq.com> 7 | * @date 2019-06-26 17:09:27 8 | */ 9 | abstract class ITreeNode { 10 | 11 | companion object { 12 | 13 | /** 14 | * 构建树形节点 15 | * 16 | * @param ids 节点id迭代器,用来标识节点顺序 17 | * @param nodes 节点哈希: 18 | * @return 19 | */ 20 | public fun buildTreeNodes(ids:Iterator, nodes: Map>): List> { 21 | val result = ArrayList>() 22 | // 按顺序迭代节点 23 | for(id in ids){ 24 | val node = nodes[id]!! 25 | if(node.isPidEmpty()){ // 无父节点:收集为根节点 26 | result.add(node) 27 | }else{ // 有父节点:构建父子关系 28 | val parent = nodes[node.pid]!! 29 | parent.addChild(node) 30 | } 31 | } 32 | 33 | return result 34 | } 35 | 36 | } 37 | /** 38 | * 节点id 39 | */ 40 | public abstract val id: T 41 | 42 | /** 43 | * 父节点id 44 | */ 45 | public open val pid: T? = null 46 | 47 | /** 48 | * 子节点 49 | */ 50 | public abstract val children: MutableList> 51 | 52 | /** 53 | * 检查父节点id是否为空 54 | */ 55 | public fun isPidEmpty(): Boolean { 56 | return pid == null 57 | || pid is Int && pid == 0 // 0 58 | || pid is String && pid == "" // 空字符串 59 | } 60 | 61 | /** 62 | * 添加子节点 63 | */ 64 | public fun addChild(child: ITreeNode): ITreeNode { 65 | children.add(child) 66 | return this 67 | } 68 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/JPHP-INF/sdk/php/jkmvc/orm/Db.php: -------------------------------------------------------------------------------- 1 | 物理类型, 用于在生成建表sql时确定字段类型 8 | logicalType2PhysicalType: 9 | boolean: number(1,0) 10 | int: number(10,0) 11 | bigint: number(19,0) 12 | double: double scale 13 | float: float 14 | date: date 15 | time: date 16 | timestamp: date 17 | varchar: long 18 | char: char(1) 19 | bigint: number(19,0) 20 | blob: blob 21 | clob: clob 22 | #mysql 23 | Mysql: 24 | dropTableSql: DROP TABLE IF EXISTS ; 25 | createTableSql: CREATE TABLE
(\n\t )\n ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; 26 | # 目前只支持新建列, 对已有列不做修改/删除, 防止不小心丢了数据无法恢复 27 | alterTableSql: ALTER TABLE
ADD COLUMN ; 28 | columnSql: 29 | # 逻辑类型 -> 物理类型, 用于在生成建表sql时确定字段类型 30 | logicalType2PhysicalType: 31 | boolean: bit 32 | int: integer 33 | decimal: null 34 | double: double scale 35 | float: float 36 | date: date 37 | time: time 38 | timestamp: datetime(6) 39 | varchar: varchar 40 | char: char(1) 41 | bigint: bigint 42 | binary: binary 43 | blob: longblob 44 | clob: longtext 45 | #sql server 46 | SqlServer: 47 | dropTableSql: 48 | createTableSql: 49 | columnSql: 50 | # 逻辑类型 -> 物理类型, 用于在生成建表sql时确定字段类型 51 | logicalType2PhysicalType: 52 | boolean: bit 53 | int: int 54 | bigint: bigint 55 | float: float 56 | date: date 57 | time: time 58 | timestamp: datetime2 59 | char: char(1) 60 | varchar: varchar 61 | blob: varbinary(MAX) 62 | clob: varchar(MAX) -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/db.yaml: -------------------------------------------------------------------------------- 1 | # 是否调试 2 | debug: true 3 | # 分库的数据库名, 会使用 sharding-jdbc 4 | # sharding database names, it uses sharding-jdbc 5 | shardingDbs: shardorder 6 | # 字段名是下划线命名的数据库名, 用于逗号分隔 7 | # Column name is underlined for these database names 8 | columnUnderlineDbs: default,test 9 | # 字段名全大写的数据库名, 用逗号分隔 10 | # Column name is all uppercase for these database names 11 | columnUpperCaseDbs: 12 | # 自定义db实现类, 默认是 net.jkcode.jkmvc.db.single.DruidSingleDb 13 | #customDbClass: net.jkcode.jkmvc.db.single.HikariSingleDb -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/es.yaml: -------------------------------------------------------------------------------- 1 | default: # es配置名, 可有多个配置 2 | esUrl: http://localhost:9200 # es server地址, 多个用逗号分割 3 | maxTotal: 100 # 连接池中总的最大连接数 4 | maxTotalPerRoute: 100 # 每个路由(host+port)的最大连接数 -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/fst-serializer.yaml: -------------------------------------------------------------------------------- 1 | # fst自定义序列器 2 | # key是被序列化的类名, value是序列器的类名 3 | net.jkcode.jkmvc.orm.OrmEntity: net.jkcode.jkmvc.orm.serialize.OrmEntityFstSerializer -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/orm.properties: -------------------------------------------------------------------------------- 1 | # 对外键字段, 如果是空字符串转为null 2 | foreignPropEmptyToNull = false 3 | # 创建时间属性名 4 | createdDateProp = 5 | # 创建人id属性名 6 | createdByProp = 7 | # 创建人名属性名 8 | createdByNameProp = 9 | # 修改时间属性名 10 | modifiedDateProp = 11 | # 修改人id属性名 12 | modifiedByProp = 13 | # 修改人名属性名 14 | modifiedNameByProp = -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/services/php.runtime.ext.support.Extension: -------------------------------------------------------------------------------- 1 | net.jkcode.jkmvc.http.jphp.JkmvcHttpExtension -------------------------------------------------------------------------------- /jkmvc-orm/src/main/resources/validation-query.properties: -------------------------------------------------------------------------------- 1 | #hsqldb 2 | org.hsqldb.jdbcDriver=select 1 from INFORMATION_SCHEMA.SYSTEM_USERS 3 | #Oracle 4 | oracle.jdbc.driver.OracleDriver=select 1 from dual 5 | #DB2 6 | com.ibm.db2.jcc.DB2Driver=select 1 from sysibm.sysdummy1 7 | #mysql 8 | com.mysql.jdbc.Driver=select 1 9 | org.gjt.mm.mysql.Driver=select 1 10 | #microsoft sql 11 | com.microsoft.sqlserver.jdbc.SQLServerDriver=select 1 12 | #postgresql 13 | org.postgresql.Driver=select version(); 14 | #ingres 15 | com.ingres.jdbc.IngresDriver=select 1 16 | #derby 17 | org.apache.derby.jdbc.ClientDriver=values 1 18 | #H2 19 | org.h2.Driver=select 1 -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/JavaOrmTests.java: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests; 2 | 3 | import net.jkcode.jkmvc.tests.model.JavaUserModel; 4 | 5 | public class JavaOrmTests { 6 | 7 | public static void main(String[] args){ 8 | JavaUserModel user = JavaUserModel.ormMeta.queryBuilder() 9 | .with("home") 10 | .with("addresses") 11 | .where("name", "shi") 12 | .findModel(JavaUserModel.class); 13 | System.out.println(user); 14 | System.out.println(user.getHome()); 15 | System.out.println(user.getAddresses()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/JphpTests.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests 2 | 3 | import net.jkcode.jphp.ext.JphpLauncher 4 | import org.junit.Test 5 | 6 | class JphpTests { 7 | 8 | @Test 9 | fun testJphpLauncher(){ 10 | val lan = JphpLauncher 11 | val ret = lan.run("src/test/resources/orm.php") 12 | println("----> $ret") 13 | } 14 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/entity/Game.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.entity 2 | 3 | import net.jkcode.jkmvc.orm.OrmEntity 4 | 5 | /** 6 | * 比赛 7 | */ 8 | public class Game : OrmEntity() { 9 | 10 | public var id: Int by property() 11 | 12 | public var title: String by property() 13 | 14 | public var score: Int by property() 15 | 16 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/entity/MessageEntity.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.entity 2 | 3 | import net.jkcode.jkmvc.es.annotation.EsDoc 4 | import net.jkcode.jkmvc.es.annotation.EsId 5 | import net.jkcode.jkmvc.orm.OrmEntity 6 | 7 | /** 8 | * 消息实体 9 | * 实体类与模型类分离 10 | * 实体类直接继承 OrmEntity, 不继承 IOrm 或 Orm 11 | * 模型类直接继承 实体类, 同时继承 IOrm, 不直接继承 Orm 12 | * @author shijianhang<772910474@qq.com> 13 | * @date 2019-06-27 2:53 PM 14 | */ 15 | @EsDoc("message_index", "_doc") 16 | open class MessageEntity: OrmEntity() { 17 | 18 | // 代理属性读写 19 | @EsId 20 | public var id:Int by property() // 消息id 21 | 22 | public var fromUid:Int by property() // 发送人id 23 | 24 | public var toUid:Int by property() // 接收人id 25 | 26 | public var created:Long by property() // 接收人id 27 | 28 | public var content:String by property() // 消息内容 29 | 30 | override fun toString(): String { 31 | return "MessageEntity(" + toMap() + ")" 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/entity/Player.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.entity 2 | 3 | import net.jkcode.jkmvc.es.annotation.EsDoc 4 | import net.jkcode.jkmvc.es.annotation.EsId 5 | import net.jkcode.jkmvc.orm.OrmEntity 6 | 7 | /** 8 | * 运动员 9 | */ 10 | @EsDoc(index = "player_index") 11 | public class Player: OrmEntity() { 12 | 13 | @EsId 14 | public var id: Int by property() 15 | 16 | public var name: String by property() // 姓名 17 | 18 | public var age: Int by property() // 年龄 19 | 20 | public var salary: Int by property() // 工资 21 | 22 | public var team: String by property() // 队伍 23 | 24 | public var position: String by property() // 职位:前锋/后卫 25 | 26 | public var games: List by property() // 比赛 27 | 28 | 29 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/entity/RecentOrder.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.entity 2 | 3 | import net.jkcode.jkmvc.es.annotation.EsDoc 4 | import net.jkcode.jkmvc.es.annotation.EsId 5 | import net.jkcode.jkmvc.orm.OrmEntity 6 | 7 | /** 8 | * 最近订单 9 | */ 10 | @EsDoc(index = "recent_order_index") 11 | public class RecentOrder: OrmEntity() { 12 | 13 | @EsId 14 | public var id: Long by property() 15 | 16 | public var cargoId: Long by property() // 货物id 17 | 18 | public var driverUserName: String by property() // 司机名 19 | 20 | public var loadAddress: String by property() // 21 | 22 | public var searchable: Boolean by property() // 是否可搜索 23 | 24 | public var companyId: Int by property() // 公司id 25 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/es/GsonTests.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.es 2 | 3 | import com.google.gson.FieldNamingPolicy 4 | import com.google.gson.GsonBuilder 5 | import net.jkcode.jkmvc.tests.entity.MessageEntity 6 | import net.jkcode.jkutil.common.randomInt 7 | import org.junit.Test 8 | 9 | class GsonTests { 10 | 11 | @Test 12 | fun testMap() { 13 | val gsonBuilder = GsonBuilder() 14 | // es字段命名为: 下划线 15 | // 生成的json中的字段名, 都是下划线的 16 | gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 17 | val gson = gsonBuilder.create() 18 | val e = mapOf("name" to "shi", "age" to 1) 19 | val json = gson.toJson(e) 20 | println(json) 21 | 22 | val e2 = gson.fromJson(json, HashMap::class.java) 23 | println(e2) 24 | } 25 | 26 | @Test 27 | fun testEntity() { 28 | val gsonBuilder = GsonBuilder() 29 | // es字段命名为: 下划线 30 | // 生成的json中的字段名, 都是下划线的 31 | gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 32 | val gson = gsonBuilder.create() 33 | val e = buildEntity(1) 34 | val json = gson.toJson(e.toMap()) 35 | println(json) 36 | 37 | val e2 = gson.fromJson(json, MessageEntity::class.java) 38 | println(e2) 39 | } 40 | 41 | private fun buildEntity(i: Int): MessageEntity { 42 | val e = MessageEntity() 43 | e.id = i 44 | e.fromUid = randomInt(10) 45 | e.toUid = randomInt(10) 46 | e.content = if(i % 2 == 0) "welcome $i" else "Goodbye $i" 47 | e.created = i * 60L 48 | return e 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/model/AddressModel.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.model 2 | 3 | import net.jkcode.jkmvc.orm.Orm 4 | import net.jkcode.jkmvc.orm.OrmMeta 5 | 6 | /** 7 | * 地址模型 8 | */ 9 | class AddressModel(id:Int? = null): Orm(id) { 10 | // 伴随对象就是元数据 11 | // company object is ormMeta data for model 12 | companion object m: OrmMeta(AddressModel::class){ 13 | init { 14 | // 添加标签 + 规则 15 | // add label and rule for field 16 | addRule("userId", "用户", "notEmpty"); 17 | addRule("addr", "地址", "notEmpty"); 18 | addRule("tel", "电话", "notEmpty digit"); 19 | 20 | // 添加关联关系 21 | // add relaction for other model 22 | belongsTo("user", UserModel::class, "user_id") 23 | } 24 | 25 | // 重写规则 26 | /*public override val rules: MutableMap = hashMapOf( 27 | "userId" to RuleValidator("用户", "notEmpty"), 28 | "age" to RuleValidator( "年龄", "between(1,120)") 29 | )*/ 30 | } 31 | 32 | // 代理属性读写 33 | // delegate property 34 | public var id:Int by property(); 35 | 36 | public var userId:Int by property(); 37 | 38 | public var addr:String by property(); 39 | 40 | public var tel:String by property(); 41 | 42 | public var isHome:Int by property(); 43 | 44 | // 关联用户:一个地址从属于一个用户 45 | public var user:UserModel by property() 46 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/model/MessageModel.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.model 2 | 3 | import net.jkcode.jkmvc.model.GeneralModel 4 | import net.jkcode.jkmvc.orm.IEntitiableOrm 5 | import net.jkcode.jkmvc.orm.IOrm 6 | import net.jkcode.jkmvc.orm.OrmMeta 7 | import net.jkcode.jkmvc.tests.entity.MessageEntity 8 | 9 | /** 10 | * 消息模型 11 | * 实体类与模型类分离 12 | * 实体类直接继承 OrmEntity, 不继承 IOrm 或 Orm 13 | * 模型类直接继承 实体类, 同时继承 IOrm, 不直接继承 Orm 14 | * 15 | * @ClassName: MessageModel 16 | * @Description: 17 | * @author shijianhang<772910474@qq.com> 18 | * @date 2019-06-27 14:51:51 19 | */ 20 | class MessageModel: MessageEntity(), IOrm by GeneralModel(m), IEntitiableOrm { 21 | 22 | // 伴随对象就是元数据 23 | companion object m: OrmMeta(MessageModel::class, "消息", "message", "id"){} 24 | 25 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/kotlin/net/jkcode/jkmvc/tests/model/ParcelModel.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tests.model 2 | 3 | import net.jkcode.jkmvc.orm.Orm 4 | import net.jkcode.jkmvc.orm.OrmMeta 5 | 6 | /** 7 | * 包裹模型 8 | * 9 | * @ClassName: ParcelModel 10 | * @Description: 11 | * @author shijianhang<772910474@qq.com> 12 | * @date 2017-11-24 09:42:34 13 | */ 14 | class ParcelModel(id:Int? = null): Orm(id) { 15 | // 伴随对象就是元数据 16 | companion object m: OrmMeta(ParcelModel::class, "包裹模型", "parcel", "id"){ 17 | 18 | init { 19 | // 添加标签 + 规则 20 | // add label and rule for field 21 | addRule("sender_id", "寄件人", "notEmpty"); 22 | addRule("receiver_id", "收件人", "notEmpty"); 23 | addRule("content", "内容", "notEmpty"); 24 | 25 | // 添加关联关系 26 | // add relaction for other model 27 | belongsTo("sender", UserModel::class, "sender_id") // 寄件人 28 | belongsTo("receiver", UserModel::class, "receiver_id") // 收件人 29 | } 30 | 31 | } 32 | 33 | // 代理属性读写 34 | public var id:Int by property() // 包裹id 35 | 36 | public var senderId:Int by property() // 寄件人id 37 | 38 | public var receiverId:Int by property() // 收件人id 39 | 40 | public var content:String by property() // 寄件内容 41 | 42 | } -------------------------------------------------------------------------------- /jkmvc-orm/src/test/resources/orm.php: -------------------------------------------------------------------------------- 1 | query("select * from user"); 10 | var_dump($users); 11 | 12 | $uid = 0; 13 | if($users){ 14 | echo "查找最大的用户id\n"; 15 | $uid = $db->query("select max(id) as mid from user")[0]['mid']; 16 | echo "最大的用户id: $uid\n"; 17 | 18 | echo "更新用户: $uid\n"; 19 | $db->execute("update user set age = age + 1 where id = ?", [$uid]); 20 | } 21 | 22 | // model操作 23 | $model = new Model("net.jkcode.jkmvc.tests.model.UserModel"); 24 | echo "创建用户: $uid\n"; 25 | $model->id = $uid + 1; 26 | $age = mt_rand(0, 10); 27 | $model->age = $age; 28 | $name = 'shi'.$age; 29 | $model->username = $name; 30 | $model->password = $name; 31 | $model->name = $name; 32 | $model->create(); 33 | 34 | echo "加载用户: $uid\n"; 35 | $model->load($uid); 36 | echo "$model \n"; 37 | 38 | echo "查找用户: $uid\n"; 39 | $user = $model->find($uid); 40 | echo "$user \n"; 41 | 42 | // db query builder 43 | echo "查找用户: $uid\n"; 44 | $qb = $db->queryBuilder(); 45 | $user = $qb->table("user")->where('id', '=', $uid)->findRow(); 46 | var_dump($user); 47 | 48 | // orm query builder 49 | echo "查找用户: $uid\n"; 50 | $qb = $model->queryBuilder(); 51 | $user = $qb->with('home')->where('user.id', '=', $uid)->findModel(); 52 | echo "$user \n"; 53 | 54 | echo "更新用户: $uid\n"; 55 | $user->age = $user->age + 2; 56 | $user->save(); 57 | -------------------------------------------------------------------------------- /jkmvc-orm/src/test/resources/test.mysql.sql: -------------------------------------------------------------------------------- 1 | -- 用户表 2 | CREATE TABLE IF NOT EXISTS `user` ( 3 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户编号', 4 | `name` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名', 5 | `age` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '年龄', 6 | `avatar` varchar(250) DEFAULT NULL COMMENT '头像', 7 | PRIMARY KEY (`id`) 8 | )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户'; 9 | 10 | -- 地址表 11 | CREATE TABLE IF NOT EXISTS `address` ( 12 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址编号', 13 | `user_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '用户编号', 14 | `addr` varchar(50) NOT NULL DEFAULT '' COMMENT '地址', 15 | `tel` varchar(50) NOT NULL DEFAULT '' COMMENT '电话', 16 | `is_home` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '是否是家庭住址', 17 | PRIMARY KEY (`id`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='地址'; 19 | 20 | -- 包裹表 21 | CREATE TABLE `parcel` ( 22 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '包裹id', 23 | `sender_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '寄件人id', 24 | `receiver_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '收件人id', 25 | `content` varchar(50) NOT NULL DEFAULT '' COMMENT '寄件内容', 26 | PRIMARY KEY (`id`) 27 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='包裹'; 28 | 29 | -- 消息表 30 | CREATE TABLE `message` ( 31 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '消息id', 32 | `from_uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发送人id', 33 | `to_uid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '接收人id', 34 | `content` varchar(50) NOT NULL DEFAULT '' COMMENT '消息内容', 35 | `created` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '接收人id', 36 | PRIMARY KEY (`id`) 37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='消息'; 38 | -------------------------------------------------------------------------------- /jkmvc-orm/src/test/resources/test.sqlserver.sql: -------------------------------------------------------------------------------- 1 | -- 用户表 2 | create table "user" 3 | ( 4 | id int identity(1,1), 5 | name varchar(50), 6 | avatar varchar(50) default '', 7 | age int default 0, 8 | created int default 0 9 | ); 10 | 11 | -- 地址表 12 | create table address 13 | ( 14 | id int identity(1,1), 15 | user_id int default 0, 16 | addr varchar(50), 17 | tel varchar(50) default '', 18 | is_home int default 0 19 | ); 20 | 21 | -- 包裹表 22 | create table parcel 23 | ( 24 | id int identity(1,1), 25 | sender_id int default 0 , 26 | receiver_id int default 0 , 27 | content varchar(50) default '' 28 | ); 29 | 30 | -- 消息表 31 | create table "message" 32 | ( 33 | id int identity(1,1), 34 | from_uid int default 0 , 35 | to_uid int default 0 , 36 | content varchar(50) default '' 37 | ); -------------------------------------------------------------------------------- /jkmvc-server-jetty/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies{ 2 | // other project 3 | compile project(':jkmvc-http') 4 | 5 | // jetty 6 | compile "org.eclipse.jetty:jetty-server:${jetty_version}" 7 | compile "org.eclipse.jetty:jetty-servlet:${jetty_version}" 8 | compile "org.eclipse.jetty:jetty-webapp:${jetty_version}" 9 | compile "org.eclipse.jetty:jetty-servlets:${jetty_version}" 10 | compile "org.eclipse.jetty:jetty-jsp:${jetty_jsp_version}" 11 | //compile "org.eclipse.jetty:apache-jstl:${jetty_jsp_version}" 12 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /jkmvc-server-jetty/src/main/kotlin/net/jkcode/jkmvc/server/JettyServerLauncher.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.server 2 | 3 | import net.jkcode.jkmvc.server.JettyServer 4 | 5 | /** 6 | * jetty server启动器 7 | * 8 | * 启动时报错: `java.lang.NoClassDefFoundError: javax/servlet/ServletRequest` 9 | * 原因: gradle的 war 插件自动将 javax.servlet-api 弄成 providedCompile, 你就算在工程的build.gradle 改为 compile 也没用 10 | * fix: project structure -> modules -> 选中 JettyServerLauncher 应用的工程 -> depencies -> 选中 Gradle: javax.servlet:javax.servlet-api:3.1.0 包, 将 scope 由 provided 改为 compile 11 | * 12 | * @author shijianhang<772910474@qq.com> 13 | * @date 2019-03-29 5:01 PM 14 | */ 15 | object JettyServerLauncher { 16 | 17 | @JvmStatic 18 | fun main(args: Array) { 19 | JettyServer().start() 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /jkmvc-server-jetty/src/main/resources/jetty.yaml: -------------------------------------------------------------------------------- 1 | maxThreads: 0 # io线程数, 如果为0则取Runtime.getRuntime().availableProcessors()*8 2 | #host: localhost # The network interface this connector binds to as an IP address or a hostname. If null or 0.0.0.0 then bind to all interfaces. 3 | port: 8080 4 | webDir: webapp 5 | contextPath: / 6 | logDir: logs 7 | tempDir: tmp 8 | -------------------------------------------------------------------------------- /jkmvc-tag/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies{ 2 | // other project 3 | compile project(':jkmvc-http') 4 | } 5 | 6 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/BaseInputTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | /** 4 | * 输入控件基类 5 | * @author shijianhang<772910474@qq.com> 6 | * @date 2019-12-24 10:08 AM 7 | */ 8 | abstract class BaseInputTag( 9 | tag: String, // 标签名 10 | hasBody: Boolean // 是否有标签体 11 | ): HtmlTag(tag, hasBody, IdGenerator.ByName) { 12 | 13 | public var onfocus: String? by property() 14 | 15 | public var onblur: String? by property() 16 | 17 | public var onchange: String? by property() 18 | 19 | public var accesskey: String? by property() 20 | 21 | public var readonly: Boolean by property() 22 | 23 | // 对 33 | tag.value = value 34 | tag.setLabel(toDisplayString(label)) 35 | // 等于父组件的值即选中 36 | if(selectTag.isBoundValueEquals(value)) 37 | tag.selected = true 38 | tag.writeTag(writer) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/PasswordInputTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | import javax.servlet.jsp.JspWriter 4 | 5 | /** 6 | * 密码框 7 | * 8 | * @author shijianhang<772910474@qq.com> 9 | * @date 2019-12-20 18:57:59 10 | */ 11 | class PasswordInputTag : InputTag("password") { 12 | 13 | /** 14 | * 是否显示密码 15 | */ 16 | var isShowPassword = false 17 | 18 | override fun beforeWriteTag(writer: JspWriter) { 19 | super.beforeWriteTag(writer) 20 | 21 | if(!isShowPassword) 22 | value = "" 23 | } 24 | 25 | override fun reset() { 26 | super.reset() 27 | 28 | isShowPassword = false 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/RadioButtonTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | /** 4 | * 单选框 5 | * 6 | * @author shijianhang<772910474@qq.com> 7 | * @date 2019-12-24 10:08 AM 8 | */ 9 | class RadioButtonTag : BaseSingleCheckedTag("radio") { 10 | } 11 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/RadioButtonsTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | /** 4 | * 多选控件 5 | * type=radio 6 | * @author shijianhang<772910474@qq.com> 7 | * @date 2019-12-24 10:08 AM 8 | */ 9 | class RadioButtonsTag : BaseMultiCheckedTag("radio") { 10 | } 11 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/SelectTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | import javax.servlet.jsp.JspWriter 4 | 5 | /** 6 | * 下拉框 7 | * @author shijianhang<772910474@qq.com> 8 | * @date 2019-12-24 10:43 AM 9 | */ 10 | class SelectTag: BaseInputTag("select", true){ 11 | 12 | // 可见选项的数目 13 | public var size: String? by property() 14 | 15 | // 是否多选 16 | public var multiple: String? by property() 17 | 18 | override fun beforeWriteTag(writer: JspWriter) { 19 | // 根据绑定值类型是否 20 | if (multiple == null && boundType != null && typeRequiresMultiple(boundType!!)) 21 | multiple = "true" 22 | } 23 | 24 | /** 25 | * Returns '`true`' for arrays, [Collections][Collection] 26 | * and [Maps][Map]. 27 | */ 28 | protected fun typeRequiresMultiple(type: Class<*>): Boolean { 29 | return type.isArray || Collection::class.java.isAssignableFrom(type) || Map::class.java.isAssignableFrom(type) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/kotlin/net/jkcode/jkmvc/tags/form/TextareaTag.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | import javax.servlet.jsp.JspWriter 4 | 5 | /** 6 | * 文本域 7 | * @author shijianhang<772910474@qq.com> 8 | * @date 2019-12-24 10:43 AM 9 | */ 10 | class TextareaTag: BaseInputTag("textarea", true){ 11 | 12 | public var rows: String? by property() 13 | 14 | public var cols: String? by property() 15 | 16 | public var onselect: String? by property() 17 | 18 | override fun beforeWriteTag(writer: JspWriter) { 19 | if(value == null && boundValue != null) 20 | value = boundValue 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /jkmvc-tag/src/main/resources/tag.properties: -------------------------------------------------------------------------------- 1 | # 请求中存储错误的属性名 2 | requestErrorAttrName=error -------------------------------------------------------------------------------- /jkmvc-tag/src/test/kotlin/net/jkcode/jkmvc/tags/form/TagTests.kt: -------------------------------------------------------------------------------- 1 | package net.jkcode.jkmvc.tags.form 2 | 3 | import net.jkcode.jkutil.common.getAccessibleField 4 | import net.jkcode.jkutil.common.getProperty 5 | import org.apache.taglibs.standard.tag.common.fmt.MessageSupport 6 | import org.junit.Test 7 | 8 | /** 9 | * 10 | * @author shijianhang<772910474@qq.com> 11 | * @date 2020-04-17 10:17 AM 12 | */ 13 | class TagTests { 14 | 15 | @Test 16 | fun testProp(){ 17 | //val p = MessageTag::class.getProperty("var") 18 | //val p = org.apache.taglibs.standard.tag.rt.fmt.MessageTag::class.getProperty("var") 19 | val p = MessageSupport::class.getProperty("var") // 私有属性不能访问 20 | println(p) 21 | } 22 | 23 | @Test 24 | fun testField(){ 25 | val varField = MessageSupport::class.java.getAccessibleField("var")!! 26 | val scopeField = MessageSupport::class.java.getAccessibleField("scope")!! 27 | println(varField) 28 | println(scopeField) 29 | } 30 | } -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 设置生产环境 3 | sed -i 's/env=dev/env=pro/g' gradle.properties 4 | 5 | # 发布到本地库, 用于检查 6 | gradle publishToMavenLocal -x test 7 | 8 | # 发布到指定的maven仓库 9 | gradle publish -x test 10 | 11 | # 恢复开发环境 12 | sed -i 's/env=pro/env=dev/g' gradle.properties -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'jkmvc-orm', 'jkmvc-http', 'jkmvc-tag', 'jkmvc-example', 'jkmvc-server-jetty' 2 | 3 | rootProject.name = 'jkmvc' 4 | 5 | // 添加子项目 6 | if(env == 'dev') { 7 | include "jkutil", "jkguard", "jphp-java-ext" 8 | 9 | include "jkbenchmark-test" 10 | project(':jkbenchmark-test').projectDir = new File('jkbenchmark/jkbenchmark-test') 11 | 12 | include "orm-benchmark" 13 | project(':orm-benchmark').projectDir = new File('jkmvc-benchmark/orm/jkorm') 14 | 15 | include "http-benchmark" 16 | project(':http-benchmark').projectDir = new File('jkmvc-benchmark/http/jkhttp') 17 | 18 | } 19 | --------------------------------------------------------------------------------