Skip to content

参照 江南一点雨 的 VBlog,加上自己的理解写了自己的VBlog,使用Vue3 setup + SpringBoot

Notifications You must be signed in to change notification settings

nesteiner/VBlog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

My Own VBlog

介绍

这个项目参考了 江南一点雨 大佬的 VBlog 项目,原先后端都抄了一遍,抄明白后,自己用 Kotlin 写了自己的后端 我并没有使用 mybatis 来作为 ORM ,而是使用 JPA 作为与数据库的交互,主要是因为 mybatis 抄的时候发现自己用的是 Kotlin ,里面的构造方法要求 完整,而原来的项目中数据库表的定义与数据库模型的定义有很大的不同,所以我决定自己改写一遍,不用 Java 前端也是抄的,不过使用了 Vue3 和 Typescript 语法,编辑器插件使用了其他的插件 总结下来,这个项目我觉得非常适合供人学习,我的代码尽量写的简洁,没有冗余代码,欢迎大家 star

注意,这个项目还只是个演示项目,后续需要增加用户注册功能

技术栈

前端

  • Vue3 + setup
  • Element Plus
  • scratch components (自己写的)

后端

  • SpringBoot 3.0
  • Spring Security
  • Spring Data JPA
  • Spring Validation
  • MySQL
  • Kotlin

Start Up

配置项

backend/src/main/resources 目录下有 application.yml 文件, 修改他的内容

server:
  port: 8082
  servlet:
    context-path: /api

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/vblog
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: steiner
    password: your password
  jpa:
    hibernate:
      ddl-auto: none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        format_sql: true

jwt:
  secret: iot.technology

open:
  urls:
    - /authenticate
    - /image/**
  roles:
    - user
    - admin

file:
  storage:
    url: /home/steiner/workspace/VBlog/storage

你需要修改他的

  • spring.datasource.url 数据库地址
  • spring.datasource.username 数据库用户名
  • spring.datasource.password 数据库密码
  • spring.jpa-hibernate.ddl-auto 应用启动时对数据库的行为, 首先把他设置成 =create= ,下一次运行的时候记得改回来
  • file.storage.url 图片存储路径

开启 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/VBlog/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;
#    }
#}

}

这里你需要更改 location / 这里的 root 选项,把他改为 前端的 dist 目录

后端

backend 目录下,输入

./gradlew test --tests "com.backend.backend.BackendApplicationTests.clearAndInjectData"
./gradlew build -x test

然后,运行

java -jar build/libs/backend-0.0.1-SNAPSHOT.jar

注意,使用的java版本要大于等于17

前端

进入前端目录,执行 yarn build

实例

登录页

images/实例/2023-04-25_22-18-37_screenshot.png

文章列表

images/实例/2023-04-25_22-25-40_screenshot.png

添加文章

images/实例/2023-04-25_22-21-19_screenshot.png

修改文章

images/实例/2023-04-25_22-24-23_screenshot.png

栏目管理

images/实例/2023-04-25_22-25-04_screenshot.png

用户设置(管理员页面)

images/实例/2023-04-25_22-26-09_screenshot.png

添加用户(管理员页面)

images/实例/2023-06-29_21-41-37_screenshot.png

更改头像(管理员页面)

images/实例/2023-06-29_21-43-09_screenshot.png 点击头像即可更改

Progress

Frontend

  • [X] 主页
  • [X] 删除失败
  • [X] 删除和显示不同步
  • [X] 草稿箱功能
  • [X] 回收站功能 v-if ??
  • [X] 删除操作
  • [X] 栏目管理
  • [X] 请求时 state = ?
  • [X] 添加 Category
  • [X] 用户管理
  • [X] 用户管理 + 用户 enabled
  • [X] Update Roles: Unable to locate constructor for embeddable : com.example.backend.model.UserRole$UPK
  • [X] Update Roles: Duplicate entry ‘2-1’ for key ‘PRIMARY’

Backend

  • [X] Article state 添加一个字段 state = DELETED/3, state = DUSTBIN/2

PROBLEM

  • [X] enabledChange(user.enabled) ?

Feature

  • [X] add user
  • [X] user enabled
  • [X] article shorcut

未解决的问题

我的 ArticleRepository 是这样的

@Repository
interface ArticleRepository: JpaRepository<Article, Long> {
    @Query("select new com.example.backend.model.ArticleShortcut(a.id, a.title, a.summary, a.category, a.author, a.publishDate, a.editTime, a.state, a.tags) from Article a where a.state = ?1 and a.author.id = ?2 order by a.editTime desc")
    fun findAllByState(state: Int, uid: Long, pageable: Pageable): Page<ArticleShortcut>

    @Query("select a.tags from Article a where a.state = :state and a.author.id = :uid")
    fun findTest(@Param("state") state: Int, @Param("uid") uid: Long): List<List<Tag>>
}

这是 ArticleShortcut

class ArticleShortcut(
    val id: Long,
    val title: String,
    val summary: String,
    val category: Category,
    val author: User,
    val publishDate: Timestamp,
    val editTime: Timestamp,
    val state: Int,
    val tags: List<Tag>
)

and I found that the findAllByState failed

images/未解决的问题/2023-07-02_22-09-54_screenshot.png

when I remove the `tags` filed in the `ArticleShortcut`, everything is ok but when I try to fetch the tags stand alone, the code is ok

images/未解决的问题/2023-07-02_22-10-50_screenshot.png

and this is my Article with @ManyToMany field

@Entity(name = "Article")
class Article(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myid")
    @GenericGenerator(name = "myid", strategy = "com.example.backend.generator.ManualInsertGenerator")
    val id: Long?,

    @Column(length = 255, nullable = false)
    var title: String,

    @Lob
    @Column(columnDefinition = "text", nullable = false)
    var markdownContent: String,

    @Lob
    @Column(columnDefinition = "text", nullable = false)
    var htmlContent: String,

    @Lob
    @Column(columnDefinition = "text", nullable = false)
    var summary: String,

    @OneToOne
    @JoinColumn(name = "cid", referencedColumnName = "id")
    var category: Category,

    @ManyToOne
    @JoinColumn(name = "aid", referencedColumnName = "id")
    val author: User,

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    val publishDate: Timestamp,

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    var editTime: Timestamp,

    @Column(nullable = false)
    var state: Int,

    @Column(nullable = false)
    val pageView: Int,

    @ManyToMany
    @JoinTable(
        name = "ArticleTag",
        joinColumns = [JoinColumn(name = "articleid", referencedColumnName = "id")],
        inverseJoinColumns = [JoinColumn(name = "tagid", referencedColumnName = "id")]
    )
    var tags: List<Tag>,
) {
    companion object {
        const val SCRATCH = 0
        const val PUBLISHED = 1
        const val DUSTBIN = 2
        const val DELETED = 3
    }
}

你可以在 在这里 查看完整聊天

About

参照 江南一点雨 的 VBlog,加上自己的理解写了自己的VBlog,使用Vue3 setup + SpringBoot

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published