Skip to content

SpringBoot(Kotlin) + Vue3 (typescript) 脚手架

Notifications You must be signed in to change notification settings

nesteiner/Scaffold

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Scaffold 脚手架

配置项

后端

用户模型接口 com.example.backend.model.User

interface User {
    val id: Long?
    val name: String
    val passwordHash: String
    val roles: List<Role>
    val enabled: Boolean
}

以 Student 为例的配置

数据表定义

object Students: LongIdTable() {
    val name = char("name", 24)
    val grade = char("grade", 8)
    val major = char("major", 16)
    val clazz = char("clazz", 24)
    val institute = char("institute", 32)
    val telephone = char("telephone", 11)
    val email = varchar("email", 24)
    val passwordHash = varchar("passwordHash", 256)
    val cardId = char("cardId", 18)
    val sex = char("sex", 1)
    val enabled = bool("enabled")
}

注册请求模型

class RegisterStudentRequest(
    @NotBlank(message = "name cannot be blank")
    @Length(min = 5, max = 20, message = "length of name must in 5-20")
    val name: String,

    @NotBlank(message = "grade cannot be blank")
    @Length(max = 4, message = "length of grade must less than 4")
    val grade: String,

    @NotBlank(message = "major cannot be blank")
    @Length(max = 20, message = "length of major must less than 20")
    val major: String,

    @NotBlank(message = "clazz cannot be blank")
    @Length(max = 10)
    val clazz: String,

    @NotBlank(message = "institude cannot be blank")
    @Length(max = 30, message = "length of institude must less than 30")
    val institude: String,

    @NotBlank(message = "telephone cannot blank")
    @Length(min = 11, max = 11, message = "length of telephone must be 11")
    val telephone: String,

    @NotBlank(message = "email cannot be blank")
    @Email(message = "email pattern error")
    val email: String,

    @NotBlank(message = "password cannot be blank")
    val passwordHash: String,

    @NotBlank(message = "cardId cannot be blank")
    @Length(max = 18, message = "length of cardId must less than 18")
    val cardId: String,

    @NotBlank(message = "sex cannot be blank")
    @Length(min = 2, max = 2, message = "sex must be 男 or 女")
    val sex: String,
)

更新请求模型

class UpdateStudentRequest(
    @NotNull(message = "id cannot be null")
    val id: Long,

    @NotBlank(message = "name cannot be blank")
    @Length(min = 5, max = 20, message = "length of name must in 5-20")
    val name: String,

    @NotBlank(message = "grade cannot be blank")
    @Length(max = 4, message = "length of grade must less than 4")
    val grade: String,

    @NotBlank(message = "major cannot be blank")
    @Length(max = 20, message = "length of major must less than 20")
    val major: String,

    @NotBlank(message = "clazz cannot be blank")
    @Length(max = 10)
    val clazz: String,

    @NotBlank(message = "institude cannot be blank")
    @Length(max = 30, message = "length of institude must less than 30")
    val institude: String,

    @NotBlank(message = "telephone cannot blank")
    @Length(min = 11, max = 11, message = "length of telephone must be 11")
    val telephone: String,

    @NotBlank(message = "email cannot be blank")
    @Email(message = "email pattern error")
    val email: String,

    @NotBlank(message = "password cannot be blank")
    val passwordHash: String,

    @NotBlank(message = "cardId cannot be blank")
    @Length(max = 18, message = "length of cardId must less than 18")
    val cardId: String,

    @NotBlank(message = "sex cannot be blank")
    @Length(min = 2, max = 2, message = "sex must be 男 or 女")
    val sex: String,
)

安全设置 com.example.backend.configure.WebSecurityConfigure

@Autowired
@Throws(Exception::class)
fun configureGlobal(authenticationManagerBuilder: AuthenticationManagerBuilder,
                    studentService: StudentService,
                    mD5PasswordEncoder: MD5PasswordEncoder) {
    authenticationManagerBuilder
        .userDetailsService(studentService)
        .passwordEncoder(mD5PasswordEncoder)
}

如何定义了多个用户模型,可以这样

@Autowired
@Throws(Exception::class)
fun configureGlobal(authenticationManagerBuilder: AuthenticationManagerBuilder,
                    adminService: AdminService,
                    studentService: StudentService,
                    teacherService: TeacherService,
                    mD5PasswordEncoder: MD5PasswordEncoder) {
    authenticationManagerBuilder
        .userDetailsService(adminService)
        .passwordEncoder(mD5PasswordEncoder)

    authenticationManagerBuilder
        .userDetailsService(studentService)
        .passwordEncoder(mD5PasswordEncoder)

    authenticationManagerBuilder
        .userDetailsService(teacherService)
        .passwordEncoder(mD5PasswordEncoder)
}

认证设置 com.example.backend.controller.AuthenticationController

@RestController
@Validated
class AuthenticationController {
    @Autowired
    lateinit var jwtTokenUtil: JwtTokenUtil

    @Autowired
    lateinit var userDetailsService: StudentService

    @PostMapping("/authenticate", params = ["type"])
    @Throws(LoginException::class)
    fun createToken(@RequestBody @Valid request: LoginRequest, @RequestParam("type") type: String, result: BindingResult): ResponseEntity<LoginResponse> {
        authenticate(request.username, request.passwordHash, type)
        val userDetails = userDetailsService.loadUserByUsername(request.username)
        val token = jwtTokenUtil.generateToken(userDetails)
        return ResponseEntity.ok(LoginResponse(token))
    }

    @Throws(LoginException::class)
    fun authenticate(username: String, password: String, type: String) {
        try {
            var user: UserDetails? = null
            if (type == "student") {
                user = userDetailsService.loadUserByUsername(username)
            } else {
                throw LoginException("no such user type", ErrorStatus.NoSuchUser)
            }
          
            if (password != user.password) {
                throw LoginException("password error", ErrorStatus.UserNamePasswordError)
            } else {
                val authentication: Authentication = UsernamePasswordAuthenticationToken(user, null, user.authorities)
                SecurityContextHolder.getContext().authentication = authentication
            }
        } catch (exception: DisabledException) {
            throw LoginException("user diabled", ErrorStatus.UserDisabled)
        } catch (exception: BadCredentialsException) { // this is for catching UsernameNotfoundException
            throw LoginException("in AuthenticationController: no such user or password error", ErrorStatus.UserNamePasswordError)
        }
    }
}

需要修改的地方,在 authenticate 方法中

var user: UserDetails? = null
if (type == "student") {
    user = userDetailsService.loadUserByUsername(username)
} else {
    throw LoginException("no such user type", ErrorStatus.NoSuchUser)
}

过滤器设置 com.example.backend.filter.AuthenticationFIlter

@Autowired
@Autowired
lateinit var studentService: StudentService
@Autowired
lateinit var adminService: AdminService
doFilterInternal
val services = listOf(studentService, adminService)

查询设置 com.example.backend.controller.AdminController

@GetMapping("/user", params = ["type"])
fun findAll(@RequestParam("type") type: String, @RequestParam("size", defaultValue = "10") size: Int, @RequestParam("page", defaultValue = "0") page: Int): Response<Page<out User>> {
    if (type == "student") {
        return Response.Ok("all students", studentService.findAll(PageRequest.of(page, size)))
    } else {
        return Response.Err("unknown type", Page.empty())
    }
}

登录请求 com.example.backend.request.LoginRequest

class LoginRequest(
    @NotBlank(message = "username cannot be blank")
    @Length(min = 5, message = "username length must greater than 5")
    val username: String,

    @NotBlank(message = "password cannot be blank")
    val passwordHash: String
)

数据库地址 resources/application.propertiees

spring:
  datasource:
    url: "jdbc:mysql://localhost:3306/scaffold"
    driver-class-name: "com.mysql.cj.jdbc.Driver"
    username: "steiner"
    password: "mysql30449030"
  exposed:
    generate-ddl: true
    show-sql: true

开放 url 与 roles 设置 resources/application.properties

open.urls=/authenticate,/student/register
open.roles=student,admin

前端

Page 返回结果

  • content: 数组数据
  • totalPages: 所有分页的数量

用户注册流程

  1. ?是否直接开放注册
  2. 用户添加操作由 Admin 操作 ?

Nginx 配置

user steiner;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    # default_type  application/octet-stream;
    default_type application/json;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        # location / {
        #     root   /usr/share/nginx/html;
        #     index  index.html index.htm;
        # }

        location / {
            root /home/steiner/workspace/sayhello/frontend/dist;
            index index.html;
            try_files $uri $uri/ /index.html;
        }

        location /api {
            proxy_pass http://localhost:8082/api;
            add_header Access-Control-Allow-Origin * always;
            add_header Access-Control-Allow-Methods * always;
            add_header Access-Control-Allow-Headers * always;

            if ($request_method = 'OPTIONS') {
               add_header Access-Control-Allow-Origin * always;
               add_header Access-Control-Allow-Methods * always;
               add_header Access-Control-Allow-Headers * always;
               return 204;     
            }
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

About

SpringBoot(Kotlin) + Vue3 (typescript) 脚手架

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published