这个项目参考了 江南一点雨 大佬的 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
在 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
图片存储路径
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
- [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’
- [X] Article state 添加一个字段 state = DELETED/3, state = DUSTBIN/2
- [X] enabledChange(user.enabled) ?
- [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
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
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
}
}
你可以在 在这里 查看完整聊天