React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解
项目工程目录
~/easykotlin/reakt$ tree.├── build│ ├── kotlin│ │ ├── compileKotlin│ │ └── compileTestKotlin│ └── kotlin-build│ └── version.txt├── build.gradle├── gradle│ └── wrapper│ ├── gradle-wrapper.jar│ └── gradle-wrapper.properties├── gradlew├── gradlew.bat├── reakt.iml├── reakt.ipr├── reakt.iws├── reakt_main.iml├── reakt_test.iml└── src ├── main │ ├── java │ ├── kotlin │ │ └── com │ │ └── easykotlin │ │ └── reakt │ │ └── ReaktApplication.kt │ └── resources │ ├── application.properties │ ├── static │ └── templates └── test ├── java ├── kotlin │ └── com │ └── easykotlin │ └── reakt │ └── ReaktApplicationTests.kt └── resources24 directories, 14 files
build.gradle
buildscript { ext { kotlinVersion = '1.2.0' springBootVersion = '2.0.0.M7' } repositories { mavenCentral() maven { url "https://repo.spring.io/snapshot" } maven { url "https://repo.spring.io/milestone" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}") classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}") }}apply plugin: 'kotlin'apply plugin: 'kotlin-spring'apply plugin: 'eclipse'apply plugin: 'org.springframework.boot'apply plugin: 'io.spring.dependency-management'group = 'com.easykotlin'version = '0.0.1-SNAPSHOT'sourceCompatibility = 1.8compileKotlin { kotlinOptions.jvmTarget = "1.8"}compileTestKotlin { kotlinOptions.jvmTarget = "1.8"}repositories { mavenCentral() maven { url "https://repo.spring.io/snapshot" } maven { url "https://repo.spring.io/milestone" }}dependencies { compile('org.springframework.boot:spring-boot-starter-actuator') compile('org.springframework.boot:spring-boot-starter-data-jpa') compile('org.springframework.boot:spring-boot-starter-freemarker') compile('org.springframework.boot:spring-boot-starter-security') compile('org.springframework.boot:spring-boot-starter-web') compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}") compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") runtime('mysql:mysql-connector-java') testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('org.springframework.security:spring-security-test')}
ReaktApplication.kt
package com.easykotlin.reaktimport org.springframework.boot.autoconfigure.SpringBootApplicationimport org.springframework.boot.runApplication@SpringBootApplicationclass ReaktApplicationfun main(args: Array) { runApplication (*args)}
后端工程目录
~/easykotlin/reakt$ tree.├── LICENSE├── README.md├── build│ ├── kotlin│ │ ├── compileKotlin│ │ └── compileTestKotlin│ └── kotlin-build│ └── version.txt├── build.gradle├── gradle│ └── wrapper│ ├── gradle-wrapper.jar│ └── gradle-wrapper.properties├── gradlew├── gradlew.bat├── out│ └── production│ ├── classes│ │ ├── META-INF│ │ │ └── reakt_main.kotlin_module│ │ └── com│ │ └── easykotlin│ │ └── reakt│ │ ├── ReaktApplication.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$1.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$1$lambda$1.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$1.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$2$lambda$1.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$2.class│ │ ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2.class│ │ ├── ReaktApplicationKt$main$1.class│ │ ├── ReaktApplicationKt.class│ │ ├── WebSecurityConfig.class│ │ ├── advice│ │ │ └── GlobalExceptionHandlerAdvice.class│ │ ├── controller│ │ │ ├── ApiController.class│ │ │ ├── LoginController.class│ │ │ └── RouterController.class│ │ ├── dao│ │ │ ├── RoleDao.class│ │ │ └── UserDao.class│ │ ├── entity│ │ │ ├── Role.class│ │ │ └── User.class│ │ ├── handler│ │ │ ├── ControllerTools.class│ │ │ └── MyAccessDeniedHandler.class│ │ └── service│ │ └── MyUserDetailService.class│ └── resources│ ├── application-daily.properties│ ├── application-dev.properties│ ├── application-prod.properties│ ├── application.properties│ └── logback-spring.xml├── reakt.iml├── reakt.ipr├── reakt.iws├── reakt_main.iml├── reakt_test.iml└── src ├── main │ ├── java │ ├── kotlin │ │ └── com │ │ └── easykotlin │ │ └── reakt │ │ ├── ReaktApplication.kt │ │ ├── advice │ │ │ └── GlobalExceptionHandlerAdvice.kt │ │ ├── controller │ │ │ ├── ApiController.kt │ │ │ ├── LoginController.kt │ │ │ └── RouterController.kt │ │ ├── dao │ │ │ ├── RoleDao.kt │ │ │ └── UserDao.kt │ │ ├── entity │ │ │ ├── Role.kt │ │ │ └── User.kt │ │ ├── handler │ │ │ └── MyAccessDeniedHandler.kt │ │ ├── security │ │ │ └── WebSecurityConfig.kt │ │ └── service │ │ └── MyUserDetailService.kt │ └── resources │ ├── application-daily.properties │ ├── application-dev.properties │ ├── application-prod.properties │ ├── application.properties │ ├── logback-spring.xml │ ├── static │ └── templates └── test ├── java ├── kotlin │ └── com │ └── easykotlin │ └── reakt │ └── ReaktApplicationTests.kt └── resources45 directories, 58 files
前端Node React 工程部分:
使用 $ nowa init web 命令创建前端 web 工程:
~/easykotlin/reakt/front$ nowa init webWelcome to nowa project generator!I will use this template to generate your project:https://github.com/nowa-webpack/template-uxcore/archive/v5.zipMay I ask you some questions?? Project name reakt? Project description An awesome project? Author name jack? Project version 1.0.0? Project homepage ? Project repository ? Npm registry https://registry.npm.taobao.org? Do you want SPA feature? Yes? Do you want i18n feature? (Y/n) YStart to copy files ...Generate file .editorconfigGenerate file .eslintignoreGenerate file .eslintrc.jsonGenerate file .gitignoreGenerate file abc.jsonGenerate file html/index.htmlGenerate file mock/user/query.jsGenerate file package.jsonGenerate file src/app/app.jsGenerate file src/app/app.lessGenerate file src/app/db.jsGenerate file src/app/routes.jsxGenerate file src/app/util.jsGenerate file src/app/variables.jsGenerate file src/components/search-data/index.jsGenerate file src/components/search-data/SearchData.jsxGenerate file src/components/search-word/index.jsGenerate file src/components/search-word/SearchWord.jsxGenerate file src/i18n/en.jsGenerate file src/i18n/index.jsGenerate file src/i18n/zh-cn.jsGenerate file src/images/README.mdGenerate file src/pages/demo/index.jsGenerate file src/pages/demo/logic.jsGenerate file src/pages/demo/PageDemo.jsxGenerate file src/pages/demo/PageDemo.lessGenerate file src/pages/error/index.jsGenerate file src/pages/error/PageError.jsxGenerate file src/pages/error/PageError.lessGenerate file src/pages/home/index.jsGenerate file src/pages/home/logic.jsGenerate file src/pages/home/PageHome.jsxGenerate file src/pages/home/PageHome.lessGenerate file webpack.config.jsnpm notice created a lockfile as package-lock.json. You should commit this file.npm WARN uxcore-layout@1.0.5 requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself.npm WARN uxcore-button@0.3.12 requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself.npm WARN uxcore-button@0.3.12 requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself.npm WARN uxcore-button@0.3.12 requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself.npm WARN uxcore-transfer@0.3.10 requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself.npm WARN react-slick@0.14.8 requires a peer of react@^0.14.0 || ^15.0.1 but none is installed. You must install peer dependencies yourself.npm WARN react-slick@0.14.8 requires a peer of react-dom@^0.14.0 || ^15.0.1 but none is installed. You must install peer dependencies yourself.npm WARN enzyme@2.9.1 requires a peer of react@0.13.x || 0.14.x || ^15.0.0-0 || 15.x but none is installed. You must install peer dependencies yourself.npm WARN react-test-renderer@15.6.2 requires a peer of react@^15.6.2 but none is installed. You must install peer dependencies yourself.npm WARN react-hammerjs@0.5.0 requires a peer of react@^0.14.3 || ^15.0.0 but none is installed. You must install peer dependencies yourself.added 249 packages in 15.628s
设置 JavaScript 的版本是 ES6
前端工程
~/easykotlin/reakt/front$ nowa serverListening at http://192.168.0.104:3000
~/easykotlin/reakt/front$ nowa serverListening at http://192.168.0.104:3000webpack built 77b5a8beed9790822bea in 12869msHash: 77b5a8beed9790822beaVersion: webpack 1.13.3Time: 12869ms Asset Size Chunks Chunk Names app-zh-cn.js 1.98 MB 0 [emitted] app 1.home-zh-cn.js 641 kB 1 [emitted] home 2.demo-zh-cn.js 641 kB 2 [emitted] demo3.error-zh-cn.js 540 kB 3 [emitted] errorwebpack: bundle is now VALID.
nowa build 之后的默认输出目录在 dist 下面. 我们下面写一个构建脚本,分别拷贝这些 js,css,html 到 Spring Boot 工程的 resource 目录下面:
reakt.sh
#!/usr/bin/env bash#build front js,css,htmlcd ./frontnowa buildcd ../#cp js,css,html to /templates, /statickotlinc -script reakt.kts#gradle bootRungradle bootRun
reakt.kts
import java.io.Fileimport java.io.FileFilterval srcPath = File("./front/dist/")val templatesPath = "src/main/resources/templates/"val jsFile = "src/main/resources/static/js/"val cssPath = "src/main/resources/static/css/"val templatesDir = File("src/main/resources/templates/")val cssDir = File("src/main/resources/static/css/")val jsDir = File("src/main/resources/static/js/")if (!templatesDir.exists()) templatesDir.mkdirs()if (!cssDir.exists()) cssDir.mkdirs()if (!jsDir.exists()) jsDir.mkdirs()srcPath.listFiles().forEach { val fileName = it.name when { fileName.endsWith(".html") -> { println("Copy file: $fileName") val htmlFile = File("$templatesPath$fileName") it.copyTo(target = htmlFile, overwrite = true) replaceJsCssSrc(htmlFile) } fileName.endsWith(".js") -> { println("Copy file: $fileName") it.copyTo(target = File("$jsFile$fileName"), overwrite = true) } fileName.endsWith(".css") -> { println("Copy file: $fileName") it.copyTo(target = File("$cssPath$fileName"), overwrite = true) } }}fun replaceJsCssSrc(htmlFile: File) { val oldJsSrc = """
logback-spring.xml
${FILE_LOG_PATTERN} ${LOG_FILE} ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz ${MAX_SIZE} ${MAX_HISTORY} ${CONSOLE_LOG_PATTERN} utf8
application-dev.properties
spring.application.name=reaktserver.port=8004#mysqlspring.datasource.url=jdbc:mysql://localhost:3306/reakt?useUnicode=true&characterEncoding=UTF8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootspring.datasource.driverClassName=com.mysql.jdbc.Driver# Specify the DBMSspring.jpa.database=MYSQL# Show or not log for each sql queryspring.jpa.show-sql=true# Hibernate ddl auto (create, create-drop, update)spring.jpa.hibernate.ddl-auto=create-drop#spring.jpa.hibernate.ddl-auto=update# stripped before adding them to the entity manager)spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect#logginglogging.level.root=infologging.level.org.springframework.web=infologging.path=${user.home}/logs#logging.file=${spring.application.name}.log#logging.exception-conversion-word=#logging.pattern.console=#logging.pattern.file=logging.file.max-history=30logging.file.max-size=2MB#logging.pattern.level=#logging.pattern.dateformat=#Freemarker# template-loader-path, comma-separated list#spring.freemarker.template-loader-path=classpath:/reakt/dist/spring.freemarker.template-loader-path=classpath:/templates/# suffixspring.freemarker.suffix=.html# static resources path pattern, default is root path: /** , 浏览器请求路径,会映射到spring.resources.static-locations#spring.mvc.static-path-pattern=/reakt/dist/**# if config this key, will overwrite the default Spring Boot Config#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/reakt/dist/#managementmanagement.endpoints.web.enabled=truemanagement.endpoints.enabled-by-default=truemanagement.endpoints.web.base-path=/actuatormanagement.health.db.enabled=truemanagement.endpoint.health.enabled=truemanagement.endpoint.metrics.enabled=truemanagement.endpoint.mappings.enabled=truemanagement.endpoint.info.enabled=truemanagement.endpoint.beans.enabled=truemanagement.endpoint.env.enabled=truemanagement.endpoint.health.show-details=truemanagement.endpoint.logfile.enabled=truemanagement.endpoint.scheduledtasks.enabled=truemanagement.endpoint.sessions.enabled=truemanagement.health.diskspace.enabled=truemanagement.info.git.enabled=true
工程源代码
完整的工程源代码(感觉有所帮助的, 顺手点个 Star 哦 !):
参考文章
React.js and Spring Data REST:
Kotlin 使用命令行执行 kts 脚本: