React.js 集成 Spring Boot 開發(fā) Web 應(yīng)用

React.js 集成 Spring Boot 開發(fā) Web 應(yīng)用

1. 創(chuàng)建工程

image.png
reakt$ tree .
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── src
    ├── main
    │   ├── kotlin
    │   │   └── com
    │   │       └── reaktboot
    │   │           └── reakt
    │   │               └── ReaktApplication.kt
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── kotlin
            └── com
                └── reaktboot
                    └── reakt
                        └── ReaktApplicationTests.kt

16 directories, 8 files

導(dǎo)入 IDEA 中

image.png

2. 配置數(shù)據(jù)源 application-dev.properties

#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

3. 創(chuàng)建User,Role 表

image.png
package com.reaktboot.reakt.entity
import javax.persistence.*

/**
 * Created by jack on 2017/4/29.
 */
@Entity
class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = -1
    @Column(length = 50, unique = true)
    var username: String = ""
    var password: String = ""
    @ManyToMany(targetEntity = Role::class, fetch = FetchType.EAGER)
    lateinit var roles: Set<Role>

    override fun toString(): String {
        return "User(id=$id, username='$username', password='$password', roles=$roles)"
    }
}

package com.reaktboot.reakt.entity

import javax.persistence.*


/**
 * Created by jack on 2017/4/29.
 */
@Entity
class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = -1
    @Column(length = 50, unique = true)
    var role: String = "ROLE_USER"
}


package com.reaktboot.reakt.dao

import com.reaktboot.reakt.entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param

interface UserDao : JpaRepository<User, Long> {

    @Query("""
         select a from #{#entityName} a where a.username = :username
    """)
    fun findByUsername(@Param("username") username: String): User?
}


package com.reaktboot.reakt.dao

import com.reaktboot.reakt.entity.Role
import org.springframework.data.jpa.repository.JpaRepository

interface RoleDao : JpaRepository<Role, Long> {
}



4. 實(shí)現(xiàn)登陸權(quán)限校驗(yàn)

WebSecurityConfig

package com.reaktboot.reakt

import com.reaktboot.reakt.handler.MyAccessDeniedHandler
import com.reaktboot.reaktservice.MyUserDetailService
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.web.access.AccessDeniedHandler

/**
prePostEnabled :決定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]
secureEnabled : 決定是否Spring Security的保障注解 [@Secured] 是否可用
jsr250Enabled :決定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用.
 */

@Configuration
@EnableWebSecurity
// 開啟 Spring Security 方法級安全
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
class WebSecurityConfig : WebSecurityConfigurerAdapter() {

    @Bean
    fun myAccessDeniedHandler(): AccessDeniedHandler {
        return MyAccessDeniedHandler("/403")
    }

    @Bean
    override fun userDetailsService(): UserDetailsService {
        return MyUserDetailService()
    }

    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http.csrf().disable()
        http.authorizeRequests()
            .antMatchers("/", // 首頁不攔截
                    "/css/**",
                    "/fonts/**",
                    "/js/**",
                    "/images/**" // 不攔截靜態(tài)資源
            ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            //.loginPage("/login")// url 請求路徑,對應(yīng) LoginController 里面的 @GetMapping("/login")
            .usernameParameter("username")
            .passwordParameter("password")
            .defaultSuccessUrl("/main").permitAll()
            .and()
            .exceptionHandling().accessDeniedHandler(myAccessDeniedHandler())
//            .exceptionHandling().accessDeniedPage("/403")
            .and()
            .logout().permitAll()

        http.logout().logoutSuccessUrl("/")

    }

    @Throws(Exception::class)
    override fun configure(auth: AuthenticationManagerBuilder) {
        //AuthenticationManager 使用我們的 lightSwordUserDetailService 來獲取用戶信息
        auth.userDetailsService(userDetailsService())
            .passwordEncoder(passwordEncoder())
    }

    /**
     * 密碼加密算法
     *
     * @return
     */
    @Bean
    fun passwordEncoder(): BCryptPasswordEncoder {
        return BCryptPasswordEncoder();
    }
}

MyUserDetailService

package com.reaktboot.reaktservice

import com.reaktboot.reakt.dao.UserDao
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class MyUserDetailService : UserDetailsService {
    val logger = LoggerFactory.getLogger(MyUserDetailService::class.java)

    @Autowired lateinit var userDao: UserDao


    override fun loadUserByUsername(username: String): UserDetails {
        val user = userDao.findByUsername(username) ?: throw  UsernameNotFoundException(username + " not found")

        logger.info("user = {}", user)
        val roles = user.roles
        val authorities = mutableSetOf<SimpleGrantedAuthority>()
        roles.forEach {
            authorities.add(SimpleGrantedAuthority(it.role))
        }
        return org.springframework.security.core.userdetails.User( // 此處為了區(qū)分我們本地系統(tǒng)中的 User 實(shí)體類硝逢,特意列出userdetails 的 User 類的全路徑
                username,
                user.password,
                authorities
        )
    }
}

5. 前端使用 React.js 開發(fā): 目錄結(jié)構(gòu)

我們使用 nowa:

https://nowa-webpack.github.io/

使用文檔:
https://nowa-webpack.github.io/nowa/

PC 前端組件庫:
http://uxco.re/components/button/

image.png

6. 創(chuàng)建 React 前端工程

前端應(yīng)用工程目錄放到 /Users/jack/KotlinSpringBoot/reakt/src/main/resources 目錄下:

image.png

前端目錄結(jié)構(gòu)如下:

image.png
~/KotlinSpringBoot/reakt/src/main/resources/reakt$ ls
abc.json          mock              package-lock.json src
html              node_modules      package.json      webpack.config.js


~/KotlinSpringBoot/reakt/src/main/resources/reakt$ tree src/
src/
├── app
│   ├── app.js
│   ├── app.less
│   ├── db.js
│   ├── util.js
│   └── variables.js
├── components
│   ├── search-data
│   │   ├── SearchData.jsx
│   │   └── index.js
│   └── search-word
│       ├── SearchWord.jsx
│       └── index.js
├── images
│   └── README.md
└── pages
    ├── demo
    │   ├── PageDemo.jsx
    │   ├── PageDemo.less
    │   ├── index.js
    │   └── logic.js
    └── home
        ├── PageHome.jsx
        ├── PageHome.less
        ├── index.js
        └── logic.js

8 directories, 18 files

前端工程應(yīng)用單獨(dú)啟動:

jack@jacks-MacBook-Air:~/KotlinSpringBoot/reakt/src/main/resources/reakt$ nowa server

Listening at http://192.168.0.104:3000

瀏覽器訪問: http://192.168.0.104:3000

可以看到 nowa 集成的 uxcore 的樣板示例工程:

image.png

nowa 使用參考: https://segmentfault.com/a/1190000009088343

nowa 使用的體驗(yàn)兩大精華地方,

不需要學(xué)習(xí)webpack, 整個前端開發(fā)環(huán)境都集成了. react入門的小白最喜歡了, 我學(xué)webpack頭大死了,到現(xiàn)在也沒有搞明白. 用了nowa,我就不需要搞明白了.

掌握了nowa的腳手架模板, 整個開發(fā)效率提升2倍.
如果說react將組件的復(fù)用提高到極限,減少了重復(fù)代碼的工作量. nowa的自定義腳手架,則把項(xiàng)目文件的復(fù)用便捷性提高到極限, 以前要復(fù)制一組文件,然后修改文件名/組件名..等等.

現(xiàn)在:

用 nowa init mod 創(chuàng)建一組函數(shù)組件
用nowa init rmod 創(chuàng)建一組react組件,
用nowa init page 創(chuàng)建自己個性化的一組文件,
用nwoa init api 創(chuàng)建api資源模塊,

創(chuàng)建好了,直接可以寫業(yè)務(wù)代碼,不需要復(fù)制粘貼啥的了. 當(dāng)然mod rmod page api 這幾個都是按項(xiàng)目和自己習(xí)慣,定義過的模板.

gui版本,我也體驗(yàn)了一下, 管理項(xiàng)目方便了.不用去文件夾里面翻找了.

7. 前后端目錄集成

image.png
image.png

Navbar.jsx

import {Component} from 'react';
import './Navbar.less';

const Menu = require('uxcore-menu')
const SubMenu = Menu.SubMenu
const MenuItem = Menu.Item


export default class Navbar extends Component {

  static defaultProps = {}

  static propTypes = {}

  constructor(props) {
    super(props);
    this.state = {
      current: '1'
    }
  }


  handleClick(e) {
    console.log('click ', e);
    this.setState({
      current: e.key,
    });
  }


  render() {

    return (
      <div>
        <Menu onClick={this.handleClick.bind(this)} selectedKeys={[this.state.current]} mode="horizontal">
          <Menu.Item key="brand" className = 'brand-style'>
            <h3>Reakt</h3>
          </Menu.Item>
          <Menu.Item key="mail">
            <i className="kuma-icon kuma-icon-email"/>首頁
          </Menu.Item>
          <Menu.Item key="app">
            <i className="kuma-icon kuma-icon-wangwang"/>快速開始
          </Menu.Item>
          <SubMenu title={<span><i className="kuma-icon kuma-icon-setting"/>博客文章</span>}>
            <Menu.Item key="setting:1">選項(xiàng)1</Menu.Item>
            <Menu.Item key="setting:2">選項(xiàng)2</Menu.Item>
            <Menu.Item key="setting:3">選項(xiàng)3</Menu.Item>
            <Menu.Item key="setting:4">選項(xiàng)4</Menu.Item>
          </SubMenu>
          <Menu.Item key="alipay">
            <a href="#" target="_blank">關(guān)于我們</a>
          </Menu.Item>
        </Menu>

        <Menu
              className="kuma-menu-none-border menu-style"
              defaultOpenKeys={['sub1']}
              selectedKeys={[this.state.current]}
              mode="inline">

          <SubMenu key={"sub1"} title={<span><i className="kuma-icon kuma-icon-email"/><span>Kotlin</span></span>}>
            <MenuItem key={11}>Java</MenuItem>
            <MenuItem key={12}>Scala </MenuItem>
            <MenuItem key={13}>Groovy</MenuItem>
          </SubMenu>

          <SubMenu key={"sub2"}
                   title={<span><i className="kuma-icon kuma-icon-wangwang"/><span>Spring Boot</span></span>}>
            <MenuItem key={21}>Spring MVC</MenuItem>
            <MenuItem key={22}>WebFlux</MenuItem>
            <MenuItem key={23}>Security</MenuItem>
            <MenuItem key={23}>JPA</MenuItem>
          </SubMenu>

          <SubMenu key={"sub3"} title={<span><i className="kuma-icon kuma-icon-wangwang"/><span>React </span></span>}>
            <MenuItem key={31}>Node.js</MenuItem>
            <MenuItem key={32}>Reflux</MenuItem>
            <MenuItem key={33}>ES6</MenuItem>
          </SubMenu>

        </Menu>
      </div>
    );
  }
}

參考文檔: https://spring.io/guides/tutorials/react-and-spring-data-rest/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姨拥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子渠鸽,更是在濱河造成了極大的恐慌叫乌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徽缚,死亡現(xiàn)場離奇詭異憨奸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)凿试,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門排宰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來似芝,“玉大人,你說我怎么就攤上這事额各」酰” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵虾啦,是天一觀的道長麻诀。 經(jīng)常有香客問我,道長傲醉,這世上最難降的妖魔是什么蝇闭? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮硬毕,結(jié)果婚禮上呻引,老公的妹妹穿的比我還像新娘。我一直安慰自己吐咳,他們只是感情好逻悠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著韭脊,像睡著了一般童谒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沪羔,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天饥伊,我揣著相機(jī)與錄音,去河邊找鬼蔫饰。 笑死琅豆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篓吁。 我是一名探鬼主播茫因,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼杖剪!你這毒婦竟也來了节腐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤摘盆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后饱苟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孩擂,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年箱熬,在試婚紗的時候發(fā)現(xiàn)自己被綠了类垦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狈邑。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蚤认,靈堂內(nèi)的尸體忽然破棺而出米苹,到底是詐尸還是另有隱情,我是刑警寧澤砰琢,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布蘸嘶,位于F島的核電站,受9級特大地震影響陪汽,放射性物質(zhì)發(fā)生泄漏训唱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一挚冤、第九天 我趴在偏房一處隱蔽的房頂上張望况增。 院中可真熱鬧,春花似錦训挡、人聲如沸澳骤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽为肮。三九已至,卻和暖如春表悬,著一層夾襖步出監(jiān)牢的瞬間弥锄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工蟆沫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留籽暇,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓饭庞,卻偏偏與公主長得像戒悠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子舟山,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容