React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解

来源:互联网 发布:p2p网络摄像机说明书 编辑:程序博客网 时间:2024/06/07 06:16

React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解

image.png

image.png

项目工程目录

~/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<String>) {    runApplication<ReaktApplication>(*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 工程:

image.png

~/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

image.png

前端工程

image.png

~/easykotlin/reakt/front$ nowa serverListening at http://192.168.0.104:3000

image.png

~/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 目录下面:

image.png

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 = """<script src="/"""    val oldJsSrcParticular = """<script src="//"""    val newJsSrc = """<script src="/js/"""    val oldCssSrc = """<link rel="stylesheet" href="/"""    val newCssSrc = """<link rel="stylesheet" href="/css/"""    var lines = StringBuilder()    htmlFile.readLines().forEach {        var line = it        if (line.contains(oldJsSrc) && !line.contains(oldJsSrcParticular)) {            line = line.replace(oldJsSrc, newJsSrc)        } else if (line.contains(oldCssSrc)) {            line = line.replace(oldCssSrc, newCssSrc)        }        lines.append(line + "\n")    }    htmlFile.writeText(lines.toString())}

image.png

image.png

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?><configuration>    <springProperty scope="context"                    name="logging.file"                    source="logging.file"/>    <springProperty scope="context"                    name="logging.path"                    source="logging.path"/>    <springProperty scope="context"                    name="logging.level.root"                    source="logging.level.root"/>    <springProperty scope="context"                    name="spring.application.name"                    source="spring.application.name"/>    <springProperty scope="context"                    name="logging.file.max-size"                    source="logging.file.max-size"/>    <springProperty scope="context"                    name="logging.file.max-history"                    source="logging.file.max-history"/>    <property name="LOG_FILE"              value="${logging.path:-.}/${logging.file:-${spring.application.name:-spring}.log}"/>    <property name="MAX_SIZE"              value="${logging.file.max-size:-10MB}"/>    <property name="MAX_HISTORY"              value="${logging.file.max-history:-0}"/>    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>    <conversionRule conversionWord="wex"                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>    <conversionRule conversionWord="wEx"                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>    <property name="CONSOLE_LOG_PATTERN"              value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>    <property name="FILE_LOG_PATTERN"              value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>    <logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>    <logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>    <logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>    <logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>    <logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>    <logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>    <logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>    <logger name="org.hibernate.SQL" level="DEBUG"/>    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>    <!--myibatis log configure-->    <logger name="com.apache.ibatis" level="TRACE"/>    <logger name="java.sql.Connection" level="DEBUG"/>    <logger name="java.sql.Statement" level="DEBUG"/>    <logger name="java.sql.PreparedStatement" level="DEBUG"/>    <!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->    <appender name="FILE"              class="ch.qos.logback.core.rolling.RollingFileAppender">        <encoder>            <pattern>${FILE_LOG_PATTERN}</pattern>        </encoder>        <file>${LOG_FILE}</file>        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>            <maxFileSize>${MAX_SIZE}</maxFileSize>            <maxHistory>${MAX_HISTORY}</maxHistory>        </rollingPolicy>    </appender>    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">        <encoder>            <pattern>${CONSOLE_LOG_PATTERN}</pattern>            <charset>utf8</charset>        </encoder>    </appender>    <root level="${logging.level.root}">        <appender-ref ref="CONSOLE"/>        <appender-ref ref="FILE"/>    </root></configuration>

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 哦 !):

https://github.com/EasyKotlin/reakt

参考文章

React.js and Spring Data REST:

https://spring.io/guides/tutorials/react-and-spring-data-rest/

Kotlin 使用命令行执行 kts 脚本: http://www.jianshu.com/p/5848fbb73227

http://start.spring.io/

原创粉丝点击