React.js 集成 Spring Boot 开发 Web 应用

React.js 集成 Spring Boot 开发 Web 应用

1. 创建工程

React.js 集成 Spring Boot 开发 Web 应用
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

导入 IDEA 中

React.js 集成 Spring Boot 开发 Web 应用
image.png

2. 配置数据源 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. 创建User,Role 表

React.js 集成 Spring Boot 开发 Web 应用
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. 实现登陆权限校验

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/**" // 不拦截静态资源
            ).permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            //.loginPage("/login")// url 请求路径,对应 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( // 此处为了区分我们本地系统中的 User 实体类,特意列出userdetails 的 User 类的全路径
                username,
                user.password,
                authorities
        )
    }
}

5. 前端使用 React.js 开发: 目录结构

我们使用 nowa:

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

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

PC 前端组件库:
http://uxco.re/components/button/

React.js 集成 Spring Boot 开发 Web 应用
image.png

6. 创建 React 前端工程

前端应用工程目录放到 /Users/jack/KotlinSpringBoot/reakt/src/main/resources 目录下:

React.js 集成 Spring Boot 开发 Web 应用
image.png

前端目录结构如下:

React.js 集成 Spring Boot 开发 Web 应用
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

前端工程应用单独启动:

[email protected]:~/KotlinSpringBoot/reakt/src/main/resources/reakt$ nowa server

Listening at http://192.168.0.104:3000

浏览器访问: http://192.168.0.104:3000

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

React.js 集成 Spring Boot 开发 Web 应用
image.png

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

nowa 使用的体验两大精华地方,

不需要学习webpack, 整个前端开发环境都集成了. react入门的小白最喜欢了, 我学webpack头大死了,到现在也没有搞明白. 用了nowa,我就不需要搞明白了.

掌握了nowa的脚手架模板, 整个开发效率提升2倍.
如果说react将组件的复用提高到极限,减少了重复代码的工作量. nowa的自定义脚手架,则把项目文件的复用便捷性提高到极限, 以前要复制一组文件,然后修改文件名/组件名..等等.

现在:

用 nowa init mod 创建一组函数组件
用nowa init rmod 创建一组react组件,
用nowa init page 创建自己个性化的一组文件,
用nwoa init api 创建api资源模块,

创建好了,直接可以写业务代码,不需要复制粘贴啥的了. 当然mod rmod page api 这几个都是按项目和自己习惯,定义过的模板.

gui版本,我也体验了一下, 管理项目方便了.不用去文件夹里面翻找了.

7. 前后端目录集成

React.js 集成 Spring Boot 开发 Web 应用
image.png
React.js 集成 Spring Boot 开发 Web 应用
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">选项1</Menu.Item>
            <Menu.Item key="setting:2">选项2</Menu.Item>
            <Menu.Item key="setting:3">选项3</Menu.Item>
            <Menu.Item key="setting:4">选项4</Menu.Item>
          </SubMenu>
          <Menu.Item key="alipay">
            <a href="#" target="_blank">关于我们</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/