Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c97a24a

Browse files
author
jack
committedDec 16, 2017
# React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解
> Reakt , an example of using React.js 集成 Kotlin Spring Boot 开发 Web 应用实例 ![image.png](http://upload-images.jianshu.io/upload_images/1233356-26fb99ae56602d38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![image.png](http://upload-images.jianshu.io/upload_images/1233356-9cd9497e9e4d24a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 项目工程目录 ``` ~/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 ```groovy 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 ```kotlin 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 工程: ![image.png](http://upload-images.jianshu.io/upload_images/1233356-13c660b76e8a5a4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ``` ~/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 [email protected] requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@>=0.13.0 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@^0.14.0 || ^15.0.1 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react-dom@^0.14.0 || ^15.0.1 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of [email protected] || 0.14.x || ^15.0.0-0 || 15.x but none is installed. You must install peer dependencies yourself. npm WARN [email protected] requires a peer of react@^15.6.2 but none is installed. You must install peer dependencies yourself. npm WARN [email protected] 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](http://upload-images.jianshu.io/upload_images/1233356-3bac4bac5a306a87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 前端工程 ![image.png](http://upload-images.jianshu.io/upload_images/1233356-fa79b6bfe5d81204.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ``` ~/easykotlin/reakt/front$ nowa server Listening at http://192.168.0.104:3000 ``` ![image.png](http://upload-images.jianshu.io/upload_images/1233356-0a25aeb42c5970cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ``` ~/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 目录下面: ![image.png](http://upload-images.jianshu.io/upload_images/1233356-7e20e0591689bf40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) reakt.sh ```shell #!/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 ```kotlin 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()) } ``` ![image.png](http://upload-images.jianshu.io/upload_images/1233356-906664e2c22dc448.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![image.png](http://upload-images.jianshu.io/upload_images/1233356-f09edb2bd05252a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) logback-spring.xml ```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/
1 parent cb89a2f commit c97a24a

File tree

1 file changed

+654
-1
lines changed

1 file changed

+654
-1
lines changed
 

‎README.md

Lines changed: 654 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,654 @@
1-
# reakt
1+
# React.js 集成 Kotlin Spring Boot 开发 Web 应用实例详解
2+
3+
> Reakt , an example of using React.js 集成 Kotlin Spring Boot 开发 Web 应用实例
4+
5+
6+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-26fb99ae56602d38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
7+
8+
9+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-9cd9497e9e4d24a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
10+
11+
12+
项目工程目录
13+
```
14+
~/easykotlin/reakt$ tree
15+
.
16+
├── build
17+
│   ├── kotlin
18+
│   │   ├── compileKotlin
19+
│   │   └── compileTestKotlin
20+
│   └── kotlin-build
21+
│   └── version.txt
22+
├── build.gradle
23+
├── gradle
24+
│   └── wrapper
25+
│   ├── gradle-wrapper.jar
26+
│   └── gradle-wrapper.properties
27+
├── gradlew
28+
├── gradlew.bat
29+
├── reakt.iml
30+
├── reakt.ipr
31+
├── reakt.iws
32+
├── reakt_main.iml
33+
├── reakt_test.iml
34+
└── src
35+
├── main
36+
│   ├── java
37+
│   ├── kotlin
38+
│   │   └── com
39+
│   │   └── easykotlin
40+
│   │   └── reakt
41+
│   │   └── ReaktApplication.kt
42+
│   └── resources
43+
│   ├── application.properties
44+
│   ├── static
45+
│   └── templates
46+
└── test
47+
├── java
48+
├── kotlin
49+
│   └── com
50+
│   └── easykotlin
51+
│   └── reakt
52+
│   └── ReaktApplicationTests.kt
53+
└── resources
54+
55+
24 directories, 14 files
56+
57+
```
58+
59+
60+
61+
62+
63+
64+
65+
66+
67+
68+
build.gradle
69+
70+
```groovy
71+
buildscript {
72+
ext {
73+
kotlinVersion = '1.2.0'
74+
springBootVersion = '2.0.0.M7'
75+
}
76+
repositories {
77+
mavenCentral()
78+
maven { url "https://repo.spring.io/snapshot" }
79+
maven { url "https://repo.spring.io/milestone" }
80+
}
81+
dependencies {
82+
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
83+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
84+
classpath("org.jetbrains.kotlin:kotlin-allopen:${kotlinVersion}")
85+
}
86+
}
87+
88+
apply plugin: 'kotlin'
89+
apply plugin: 'kotlin-spring'
90+
apply plugin: 'eclipse'
91+
apply plugin: 'org.springframework.boot'
92+
apply plugin: 'io.spring.dependency-management'
93+
94+
group = 'com.easykotlin'
95+
version = '0.0.1-SNAPSHOT'
96+
sourceCompatibility = 1.8
97+
compileKotlin {
98+
kotlinOptions.jvmTarget = "1.8"
99+
}
100+
compileTestKotlin {
101+
kotlinOptions.jvmTarget = "1.8"
102+
}
103+
104+
repositories {
105+
mavenCentral()
106+
maven { url "https://repo.spring.io/snapshot" }
107+
maven { url "https://repo.spring.io/milestone" }
108+
}
109+
110+
111+
dependencies {
112+
compile('org.springframework.boot:spring-boot-starter-actuator')
113+
compile('org.springframework.boot:spring-boot-starter-data-jpa')
114+
compile('org.springframework.boot:spring-boot-starter-freemarker')
115+
compile('org.springframework.boot:spring-boot-starter-security')
116+
compile('org.springframework.boot:spring-boot-starter-web')
117+
compile("org.jetbrains.kotlin:kotlin-stdlib-jre8:${kotlinVersion}")
118+
compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
119+
runtime('mysql:mysql-connector-java')
120+
testCompile('org.springframework.boot:spring-boot-starter-test')
121+
testCompile('org.springframework.security:spring-security-test')
122+
}
123+
124+
```
125+
126+
ReaktApplication.kt
127+
128+
```kotlin
129+
package com.easykotlin.reakt
130+
131+
import org.springframework.boot.autoconfigure.SpringBootApplication
132+
import org.springframework.boot.runApplication
133+
134+
@SpringBootApplication
135+
class ReaktApplication
136+
137+
fun main(args: Array<String>) {
138+
runApplication<ReaktApplication>(*args)
139+
}
140+
141+
```
142+
143+
144+
后端工程目录
145+
146+
```
147+
~/easykotlin/reakt$ tree
148+
.
149+
├── LICENSE
150+
├── README.md
151+
├── build
152+
│   ├── kotlin
153+
│   │   ├── compileKotlin
154+
│   │   └── compileTestKotlin
155+
│   └── kotlin-build
156+
│   └── version.txt
157+
├── build.gradle
158+
├── gradle
159+
│   └── wrapper
160+
│   ├── gradle-wrapper.jar
161+
│   └── gradle-wrapper.properties
162+
├── gradlew
163+
├── gradlew.bat
164+
├── out
165+
│   └── production
166+
│   ├── classes
167+
│   │   ├── META-INF
168+
│   │   │   └── reakt_main.kotlin_module
169+
│   │   └── com
170+
│   │   └── easykotlin
171+
│   │   └── reakt
172+
│   │   ├── ReaktApplication.class
173+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$1.class
174+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$1$lambda$1.class
175+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$1.class
176+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$2$lambda$1.class
177+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2$2.class
178+
│   │   ├── ReaktApplicationKt$main$1$$special$$inlined$bean$2.class
179+
│   │   ├── ReaktApplicationKt$main$1.class
180+
│   │   ├── ReaktApplicationKt.class
181+
│   │   ├── WebSecurityConfig.class
182+
│   │   ├── advice
183+
│   │   │   └── GlobalExceptionHandlerAdvice.class
184+
│   │   ├── controller
185+
│   │   │   ├── ApiController.class
186+
│   │   │   ├── LoginController.class
187+
│   │   │   └── RouterController.class
188+
│   │   ├── dao
189+
│   │   │   ├── RoleDao.class
190+
│   │   │   └── UserDao.class
191+
│   │   ├── entity
192+
│   │   │   ├── Role.class
193+
│   │   │   └── User.class
194+
│   │   ├── handler
195+
│   │   │   ├── ControllerTools.class
196+
│   │   │   └── MyAccessDeniedHandler.class
197+
│   │   └── service
198+
│   │   └── MyUserDetailService.class
199+
│   └── resources
200+
│   ├── application-daily.properties
201+
│   ├── application-dev.properties
202+
│   ├── application-prod.properties
203+
│   ├── application.properties
204+
│   └── logback-spring.xml
205+
├── reakt.iml
206+
├── reakt.ipr
207+
├── reakt.iws
208+
├── reakt_main.iml
209+
├── reakt_test.iml
210+
└── src
211+
├── main
212+
│   ├── java
213+
│   ├── kotlin
214+
│   │   └── com
215+
│   │   └── easykotlin
216+
│   │   └── reakt
217+
│   │   ├── ReaktApplication.kt
218+
│   │   ├── advice
219+
│   │   │   └── GlobalExceptionHandlerAdvice.kt
220+
│   │   ├── controller
221+
│   │   │   ├── ApiController.kt
222+
│   │   │   ├── LoginController.kt
223+
│   │   │   └── RouterController.kt
224+
│   │   ├── dao
225+
│   │   │   ├── RoleDao.kt
226+
│   │   │   └── UserDao.kt
227+
│   │   ├── entity
228+
│   │   │   ├── Role.kt
229+
│   │   │   └── User.kt
230+
│   │   ├── handler
231+
│   │   │   └── MyAccessDeniedHandler.kt
232+
│   │   ├── security
233+
│   │   │   └── WebSecurityConfig.kt
234+
│   │   └── service
235+
│   │   └── MyUserDetailService.kt
236+
│   └── resources
237+
│   ├── application-daily.properties
238+
│   ├── application-dev.properties
239+
│   ├── application-prod.properties
240+
│   ├── application.properties
241+
│   ├── logback-spring.xml
242+
│   ├── static
243+
│   └── templates
244+
└── test
245+
├── java
246+
├── kotlin
247+
│   └── com
248+
│   └── easykotlin
249+
│   └── reakt
250+
│   └── ReaktApplicationTests.kt
251+
└── resources
252+
253+
45 directories, 58 files
254+
255+
```
256+
257+
前端Node React 工程部分:
258+
259+
使用 $ nowa init web 命令创建前端 web 工程:
260+
261+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-13c660b76e8a5a4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
262+
263+
264+
```
265+
~/easykotlin/reakt/front$ nowa init web
266+
267+
Welcome to nowa project generator!
268+
I will use this template to generate your project:
269+
https://github.com/nowa-webpack/template-uxcore/archive/v5.zip
270+
May I ask you some questions?
271+
272+
? Project name reakt
273+
? Project description An awesome project
274+
? Author name jack
275+
? Project version 1.0.0
276+
? Project homepage
277+
? Project repository
278+
? Npm registry https://registry.npm.taobao.org
279+
? Do you want SPA feature? Yes
280+
? Do you want i18n feature? (Y/n) Y
281+
282+
283+
284+
Start to copy files ...
285+
286+
Generate file .editorconfig
287+
Generate file .eslintignore
288+
Generate file .eslintrc.json
289+
Generate file .gitignore
290+
Generate file abc.json
291+
Generate file html/index.html
292+
Generate file mock/user/query.js
293+
Generate file package.json
294+
Generate file src/app/app.js
295+
Generate file src/app/app.less
296+
Generate file src/app/db.js
297+
Generate file src/app/routes.jsx
298+
Generate file src/app/util.js
299+
Generate file src/app/variables.js
300+
Generate file src/components/search-data/index.js
301+
Generate file src/components/search-data/SearchData.jsx
302+
Generate file src/components/search-word/index.js
303+
Generate file src/components/search-word/SearchWord.jsx
304+
Generate file src/i18n/en.js
305+
Generate file src/i18n/index.js
306+
Generate file src/i18n/zh-cn.js
307+
Generate file src/images/README.md
308+
Generate file src/pages/demo/index.js
309+
Generate file src/pages/demo/logic.js
310+
Generate file src/pages/demo/PageDemo.jsx
311+
Generate file src/pages/demo/PageDemo.less
312+
Generate file src/pages/error/index.js
313+
Generate file src/pages/error/PageError.jsx
314+
Generate file src/pages/error/PageError.less
315+
Generate file src/pages/home/index.js
316+
Generate file src/pages/home/logic.js
317+
Generate file src/pages/home/PageHome.jsx
318+
Generate file src/pages/home/PageHome.less
319+
Generate file webpack.config.js
320+
npm notice created a lockfile as package-lock.json. You should commit this file.
321+
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.
322+
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.
323+
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.
324+
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.
325+
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.
326+
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.
327+
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.
328+
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.
329+
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.
330+
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.
331+
332+
added 249 packages in 15.628s
333+
```
334+
335+
336+
设置 JavaScript 的版本是 ES6
337+
338+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-3bac4bac5a306a87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
339+
340+
341+
342+
前端工程
343+
344+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-fa79b6bfe5d81204.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
345+
346+
```
347+
~/easykotlin/reakt/front$ nowa server
348+
Listening at http://192.168.0.104:3000
349+
```
350+
351+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-0a25aeb42c5970cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
352+
353+
354+
```
355+
~/easykotlin/reakt/front$ nowa server
356+
Listening at http://192.168.0.104:3000
357+
webpack built 77b5a8beed9790822bea in 12869ms
358+
Hash: 77b5a8beed9790822bea
359+
Version: webpack 1.13.3
360+
Time: 12869ms
361+
Asset Size Chunks Chunk Names
362+
app-zh-cn.js 1.98 MB 0 [emitted] app
363+
1.home-zh-cn.js 641 kB 1 [emitted] home
364+
2.demo-zh-cn.js 641 kB 2 [emitted] demo
365+
3.error-zh-cn.js 540 kB 3 [emitted] error
366+
webpack: bundle is now VALID.
367+
368+
```
369+
370+
371+
nowa build 之后的默认输出目录在 dist 下面. 我们下面写一个构建脚本,分别拷贝这些 js,css,html 到 Spring Boot 工程的 resource 目录下面:
372+
373+
374+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-7e20e0591689bf40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
375+
376+
377+
reakt.sh
378+
379+
```shell
380+
#!/usr/bin/env bash
381+
#build front js,css,html
382+
cd ./front
383+
nowa build
384+
cd ../
385+
#cp js,css,html to /templates, /static
386+
kotlinc -script reakt.kts
387+
#gradle bootRun
388+
gradle bootRun
389+
390+
```
391+
392+
393+
reakt.kts
394+
395+
```kotlin
396+
import java.io.File
397+
import java.io.FileFilter
398+
399+
val srcPath = File("./front/dist/")
400+
401+
val templatesPath = "src/main/resources/templates/"
402+
val jsFile = "src/main/resources/static/js/"
403+
val cssPath = "src/main/resources/static/css/"
404+
405+
val templatesDir = File("src/main/resources/templates/")
406+
val cssDir = File("src/main/resources/static/css/")
407+
val jsDir = File("src/main/resources/static/js/")
408+
409+
if (!templatesDir.exists()) templatesDir.mkdirs()
410+
if (!cssDir.exists()) cssDir.mkdirs()
411+
if (!jsDir.exists()) jsDir.mkdirs()
412+
413+
srcPath.listFiles().forEach {
414+
val fileName = it.name
415+
when {
416+
fileName.endsWith(".html") -> {
417+
println("Copy file: $fileName")
418+
419+
val htmlFile = File("$templatesPath$fileName")
420+
it.copyTo(target = htmlFile, overwrite = true)
421+
replaceJsCssSrc(htmlFile)
422+
}
423+
fileName.endsWith(".js") -> {
424+
println("Copy file: $fileName")
425+
it.copyTo(target = File("$jsFile$fileName"), overwrite = true)
426+
}
427+
fileName.endsWith(".css") -> {
428+
println("Copy file: $fileName")
429+
it.copyTo(target = File("$cssPath$fileName"), overwrite = true)
430+
}
431+
}
432+
}
433+
434+
435+
436+
437+
fun replaceJsCssSrc(htmlFile: File) {
438+
val oldJsSrc = """<script src="/"""
439+
val oldJsSrcParticular = """<script src="//"""
440+
val newJsSrc = """<script src="/js/"""
441+
442+
val oldCssSrc = """<link rel="stylesheet" href="/"""
443+
val newCssSrc = """<link rel="stylesheet" href="/css/"""
444+
445+
var lines = StringBuilder()
446+
htmlFile.readLines().forEach {
447+
var line = it
448+
if (line.contains(oldJsSrc) && !line.contains(oldJsSrcParticular)) {
449+
line = line.replace(oldJsSrc, newJsSrc)
450+
} else if (line.contains(oldCssSrc)) {
451+
line = line.replace(oldCssSrc, newCssSrc)
452+
}
453+
454+
lines.append(line + "\n")
455+
}
456+
457+
htmlFile.writeText(lines.toString())
458+
}
459+
460+
```
461+
462+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-906664e2c22dc448.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
463+
464+
465+
![image.png](http://upload-images.jianshu.io/upload_images/1233356-f09edb2bd05252a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
466+
467+
468+
469+
logback-spring.xml
470+
471+
```xml
472+
<?xml version="1.0" encoding="UTF-8"?>
473+
<configuration>
474+
<springProperty scope="context"
475+
name="logging.file"
476+
source="logging.file"/>
477+
478+
<springProperty scope="context"
479+
name="logging.path"
480+
source="logging.path"/>
481+
482+
<springProperty scope="context"
483+
name="logging.level.root"
484+
source="logging.level.root"/>
485+
486+
<springProperty scope="context"
487+
name="spring.application.name"
488+
source="spring.application.name"/>
489+
490+
<springProperty scope="context"
491+
name="logging.file.max-size"
492+
source="logging.file.max-size"/>
493+
494+
<springProperty scope="context"
495+
name="logging.file.max-history"
496+
source="logging.file.max-history"/>
497+
498+
<property name="LOG_FILE"
499+
value="${logging.path:-.}/${logging.file:-${spring.application.name:-spring}.log}"/>
500+
501+
502+
<property name="MAX_SIZE"
503+
value="${logging.file.max-size:-10MB}"/>
504+
505+
506+
<property name="MAX_HISTORY"
507+
value="${logging.file.max-history:-0}"/>
508+
509+
510+
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
511+
<conversionRule conversionWord="wex"
512+
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
513+
<conversionRule conversionWord="wEx"
514+
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
515+
516+
<property name="CONSOLE_LOG_PATTERN"
517+
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}}"/>
518+
<property name="FILE_LOG_PATTERN"
519+
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}}"/>
520+
521+
<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
522+
<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
523+
<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
524+
<logger name="org.apache.sshd.common.util.SecurityUtils" level="WARN"/>
525+
<logger name="org.apache.tomcat.util.net.NioSelectorPool" level="WARN"/>
526+
<logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="ERROR"/>
527+
<logger name="org.hibernate.validator.internal.util.Version" level="WARN"/>
528+
529+
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
530+
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
531+
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG"/>
532+
<logger name="org.hibernate.SQL" level="DEBUG"/>
533+
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG"/>
534+
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG"/>
535+
536+
<!--myibatis log configure-->
537+
<logger name="com.apache.ibatis" level="TRACE"/>
538+
<logger name="java.sql.Connection" level="DEBUG"/>
539+
<logger name="java.sql.Statement" level="DEBUG"/>
540+
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
541+
542+
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
543+
544+
<appender name="FILE"
545+
class="ch.qos.logback.core.rolling.RollingFileAppender">
546+
<encoder>
547+
<pattern>${FILE_LOG_PATTERN}</pattern>
548+
</encoder>
549+
<file>${LOG_FILE}</file>
550+
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
551+
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
552+
<maxFileSize>${MAX_SIZE}</maxFileSize>
553+
<maxHistory>${MAX_HISTORY}</maxHistory>
554+
</rollingPolicy>
555+
</appender>
556+
557+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
558+
<encoder>
559+
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
560+
<charset>utf8</charset>
561+
</encoder>
562+
</appender>
563+
564+
<root level="${logging.level.root}">
565+
<appender-ref ref="CONSOLE"/>
566+
<appender-ref ref="FILE"/>
567+
</root>
568+
</configuration>
569+
570+
```
571+
572+
application-dev.properties
573+
574+
```
575+
spring.application.name=reakt
576+
server.port=8004
577+
#mysql
578+
spring.datasource.url=jdbc:mysql://localhost:3306/reakt?useUnicode=true&characterEncoding=UTF8&useSSL=false
579+
spring.datasource.username=root
580+
spring.datasource.password=root
581+
spring.datasource.driverClassName=com.mysql.jdbc.Driver
582+
# Specify the DBMS
583+
spring.jpa.database=MYSQL
584+
# Show or not log for each sql query
585+
spring.jpa.show-sql=true
586+
# Hibernate ddl auto (create, create-drop, update)
587+
spring.jpa.hibernate.ddl-auto=create-drop
588+
#spring.jpa.hibernate.ddl-auto=update
589+
# stripped before adding them to the entity manager)
590+
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
591+
#logging
592+
logging.level.root=info
593+
logging.level.org.springframework.web=info
594+
logging.path=${user.home}/logs
595+
#logging.file=${spring.application.name}.log
596+
#logging.exception-conversion-word=
597+
#logging.pattern.console=
598+
#logging.pattern.file=
599+
logging.file.max-history=30
600+
logging.file.max-size=2MB
601+
#logging.pattern.level=
602+
#logging.pattern.dateformat=
603+
#Freemarker
604+
# template-loader-path, comma-separated list
605+
#spring.freemarker.template-loader-path=classpath:/reakt/dist/
606+
spring.freemarker.template-loader-path=classpath:/templates/
607+
# suffix
608+
spring.freemarker.suffix=.html
609+
# static resources path pattern, default is root path: /** , 浏览器请求路径,会映射到spring.resources.static-locations
610+
#spring.mvc.static-path-pattern=/reakt/dist/**
611+
# if config this key, will overwrite the default Spring Boot Config
612+
#spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/reakt/dist/
613+
#management
614+
management.endpoints.web.enabled=true
615+
management.endpoints.enabled-by-default=true
616+
management.endpoints.web.base-path=/actuator
617+
management.health.db.enabled=true
618+
management.endpoint.health.enabled=true
619+
management.endpoint.metrics.enabled=true
620+
management.endpoint.mappings.enabled=true
621+
management.endpoint.info.enabled=true
622+
management.endpoint.beans.enabled=true
623+
management.endpoint.env.enabled=true
624+
management.endpoint.health.show-details=true
625+
management.endpoint.logfile.enabled=true
626+
management.endpoint.scheduledtasks.enabled=true
627+
management.endpoint.sessions.enabled=true
628+
management.health.diskspace.enabled=true
629+
management.info.git.enabled=true
630+
631+
```
632+
633+
634+
# 工程源代码
635+
636+
完整的工程源代码(感觉有所帮助的, 顺手点个 Star 哦 !):
637+
638+
https://github.com/EasyKotlin/reakt
639+
640+
641+
642+
# 参考文章
643+
644+
React.js and Spring Data REST:
645+
646+
https://spring.io/guides/tutorials/react-and-spring-data-rest/
647+
648+
Kotlin 使用命令行执行 kts 脚本: http://www.jianshu.com/p/5848fbb73227
649+
650+
http://start.spring.io/
651+
652+
653+
654+

0 commit comments

Comments
 (0)
Please sign in to comment.