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

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

《React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解》 image.png
《React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解》 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
        └── resources

24 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.8
compileKotlin {
    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.reakt

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class ReaktApplication

fun 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
        └── resources

45 directories, 58 files

前端Node React 工程部分:

使用 $ nowa init web 命令创建前端 web 工程:

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

~/easykotlin/reakt/front$ nowa init web

Welcome to nowa project generator!
I will use this template to generate your project:
https://github.com/nowa-webpack/template-uxcore/archive/v5.zip
May 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) Y



Start to copy files ...

Generate file .editorconfig
Generate file .eslintignore
Generate file .eslintrc.json
Generate file .gitignore
Generate file abc.json
Generate file html/index.html
Generate file mock/user/query.js
Generate file package.json
Generate file src/app/app.js
Generate file src/app/app.less
Generate file src/app/db.js
Generate file src/app/routes.jsx
Generate file src/app/util.js
Generate file src/app/variables.js
Generate file src/components/search-data/index.js
Generate file src/components/search-data/SearchData.jsx
Generate file src/components/search-word/index.js
Generate file src/components/search-word/SearchWord.jsx
Generate file src/i18n/en.js
Generate file src/i18n/index.js
Generate file src/i18n/zh-cn.js
Generate file src/images/README.md
Generate file src/pages/demo/index.js
Generate file src/pages/demo/logic.js
Generate file src/pages/demo/PageDemo.jsx
Generate file src/pages/demo/PageDemo.less
Generate file src/pages/error/index.js
Generate file src/pages/error/PageError.jsx
Generate file src/pages/error/PageError.less
Generate file src/pages/home/index.js
Generate file src/pages/home/logic.js
Generate file src/pages/home/PageHome.jsx
Generate file src/pages/home/PageHome.less
Generate file webpack.config.js
npm 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

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

前端工程

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

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

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

~/easykotlin/reakt/front$ nowa server
Listening at http://192.168.0.104:3000
webpack built 77b5a8beed9790822bea in 12869ms
Hash: 77b5a8beed9790822bea
Version: webpack 1.13.3
Time: 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]  demo
3.error-zh-cn.js   540 kB       3  [emitted]  error
webpack: bundle is now VALID.

nowa build 之后的默认输出目录在 dist 下面. 我们下面写一个构建脚本,分别拷贝这些 js,css,html 到 Spring Boot 工程的 resource 目录下面:

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

reakt.sh

#!/usr/bin/env bash
#build front js,css,html
cd ./front
nowa build
cd ../
#cp js,css,html to /templates, /static
kotlinc -script reakt.kts
#gradle bootRun
gradle bootRun

reakt.kts

import java.io.File
import java.io.FileFilter

val 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())
}

《React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解》 image.png
《React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解》 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=reakt
server.port=8004
#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/reakt?useUnicode=true&characterEncoding=UTF8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# Specify the DBMS
spring.jpa.database=MYSQL
# Show or not log for each sql query
spring.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
#logging
logging.level.root=info
logging.level.org.springframework.web=info
logging.path=${user.home}/logs
#logging.file=${spring.application.name}.log
#logging.exception-conversion-word=
#logging.pattern.console=
#logging.pattern.file=
logging.file.max-history=30
logging.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/
# suffix
spring.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/
#management
management.endpoints.web.enabled=true
management.endpoints.enabled-by-default=true
management.endpoints.web.base-path=/actuator
management.health.db.enabled=true
management.endpoint.health.enabled=true
management.endpoint.metrics.enabled=true
management.endpoint.mappings.enabled=true
management.endpoint.info.enabled=true
management.endpoint.beans.enabled=true
management.endpoint.env.enabled=true
management.endpoint.health.show-details=true
management.endpoint.logfile.enabled=true
management.endpoint.scheduledtasks.enabled=true
management.endpoint.sessions.enabled=true
management.health.diskspace.enabled=true
management.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/

    原文作者:一个会写诗的程序员
    原文地址: https://www.jianshu.com/p/77c36bb123de
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞