Java 之 Spring Boot + Vue + Element UI 前后端分离项目(上-项目搭建) | |
---|---|
Java之Spring Boot+Vue+Element UI前后端分离项目(中-功能完善-实现查询) | |
Java之Spring Boot+Vue+Element UI前后端分离项目(下-功能完善-发布文章-文章搜索) |
源代码下载:https://download.csdn.net/download/qq_44757034/85045367
Java SpringBoot 前后端分离项目高仿CSDN项目源代码(前端Vue+Element UI 后端Java的SpringBoot+Myabtis,数据库Mysql)
安装富文本编辑框
npm install vue-quill-editor //富文本编辑器
npm install quill //依赖项
<template>
<div style="width: 90%; background-color: #99a9bf;margin: auto;top: 0px;left: 0px">
<div style="width:80%;background-color: white;margin: auto;">
<div style="clear: both"></div>
<el-input style="margin-top: 50px;width: 80%" v-model="title" placeholder="请输入文章标题"></el-input>
<div style="text-align: left;width: 80%;margin: auto;margin-top: 30px ">
上传文章缩略图:
<el-upload
class="avatar-uploader"
action="http://localhost:9090/image"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon" style="border-style: solid;border-radius: 15px"></i>
</el-upload>
</div>
<div style="clear: both"></div>
<div style="margin-top: 20px;float:left; margin-left: 130px ">
是否原创:
<el-switch
v-model="checked"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</div>
<div style="clear: both"></div>
<el-input style="margin-top: 50px; margin-bottom: 50px;width: 80%"
type="textarea"
:rows="10"
placeholder="文章摘要"
v-model="abstract_text">
</el-input>
<div style="width: 80%;margin: auto">
<span style="text-align: left;display: block">文章内容:</span>
<quill-editor
ref="myQuillEditor"
v-model="content"
:options="editorOption"
style="height: 500px"
/>
</div>
<el-button type="primary" @click="submit" style="margin-top:150px">提交</el-button>
</div>
<div style="margin-top: 100px"></div>
</div>
</template>
<script>
import 'quill/dist/quill.core.css' //引入富文本
import 'quill/dist/quill.snow.css' //引入富文本
import 'quill/dist/quill.bubble.css' //引入富文本
import {quillEditor} from 'vue-quill-editor' //引入富文本
export default {
components: {
quillEditor
},
name: "Write",
data() {
return {
abstract_text: '',
title: "",
checked: false,
is_original: this.checked ? "是" : "否",
imageUrl: "",
thumbnail: "",
content: '<h2>请输入文章内容</h2>',
//富文本编辑器配置
editorOption: {},
blogType : "",
typeName: "选择文章类型",
typeId : ""
}
},
created() {//编写构造函数
this.$axios.get("http://localhost:9090/verify/")
.then((res) => {
}).catch(() => {
this.$router.push({path: "/Login"})
this.$message.error("没有登录请登录后发布文章!");
});
//获取顶部分类信息
this.$axios.get('http://localhost:9090/blogType/queryBlogType')
.then(response => (
this.blogType = response.data
)).catch(function (error) { // 请求失败处理
console.log(error);
});
},
methods: {
submit() {
this.$axios.post('http://localhost:9090/blogging/save', {
title: this.title,
abstract_text: this.abstract_text,
thumbnail: this.thumbnail,
context: this.content,
is_original: this.checked ? "是" : "否",
typeId : this.typeId,
}).then((res) => {
this.$router.push("/");
this.$message.success("文章发布成功!");
}).catch(() => {
this.$message.error("文章发布失败!");
});
},
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
this.thumbnail = "http://localhost:9090/img/" + res;
},
selectType(typename,id) {
this.typeName = typename;
this.typeId = id;
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
}
}
}
</script>
<style scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import 'default-passive-events'
import './http';
Vue.config.productionTip = false
Vue.use(ElementUI)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
访问:http://localhost:8080/#/Write
在BlogController当中创建对应的上传图片的接口
@PostMapping("image")
public ResponseEntity<String> uploadImage(@RequestParam("file") MultipartFile file,HttpServletRequest request) throws IOException {
String name = file.getOriginalFilename();
IdWorker idWorker = new IdWorker(new Random().nextInt(10), 1);
long l = idWorker.nextId();
name = l+name;
String property = System.getProperty("user.dir");
file.transferTo(new File(System.getProperty("user.dir")+"\\src\\main\\webapp\\img\\"+name));
return ResponseEntity.ok(name);
}
<el-upload
class="avatar-uploader"
action="http://localhost:9090/blog/image"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon" style="border-style: solid;border-radius: 15px"></i>
</el-upload>
上传图片成功
<template>
<div style="width: 90%; background-color: #99a9bf;margin: auto;top: 0px;left: 0px">
<div style="width:80%;background-color: white;margin: auto;">
<div style="clear: both"></div>
<el-input style="margin-top: 50px;width: 80%" v-model="title" placeholder="请输入文章标题"></el-input>
<div style="text-align: left;width: 80%;margin: auto;margin-top: 30px ">
上传文章缩略图:
<el-upload
class="avatar-uploader"
action="http://localhost:9090/blog/image"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon" style="border-style: solid;border-radius: 15px"></i>
</el-upload>
</div>
<div style="clear: both"></div>
<div style="margin-top: 20px;float:left; margin-left: 130px ">
是否原创:
<el-switch
v-model="checked"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</div>
<div style="clear: both"></div>
<el-input style="margin-top: 50px; margin-bottom: 50px;width: 80%"
type="textarea"
:rows="10"
placeholder="文章摘要"
v-model="abstract_text">
</el-input>
<div style="width: 80%;margin: auto">
<span style="text-align: left;display: block">文章内容:</span>
<quill-editor
ref="myQuillEditor"
v-model="content"
:options="editorOption"
style="height: 500px"
/>
</div>
<el-button type="primary" @click="submit" style="margin-top:150px">提交</el-button>
</div>
<div style="margin-top: 100px"></div>
</div>
</template>
<script>
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import {quillEditor} from 'vue-quill-editor'
export default {
components: {
quillEditor
},
name: "Write",
data() {
return {
abstract_text: '',
title: "",
checked: false,
is_original: this.checked ? "是" : "否",
imageUrl: "",
thumbnail: "",
content: '<h2>请输入文章内容</h2>',
//富文本编辑器配置
editorOption: {},
blogType : "",
typeName: "选择文章类型",
typeId : ""
}
},
created() {//编写构造函数
},
methods: {
submit() {
this.$http.post('/blog/save', {
title: this.title,
abstract_text: this.abstract_text,
thumbnail: this.thumbnail,
context: this.content,
is_original: this.checked ? "是" : "否",
typeId : this.typeId,
}).then((res) => {
this.$router.push("/");
this.$message.success("文章发布成功!");
}).catch(() => {
this.$message.error("文章发布失败!");
});
},
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
this.thumbnail = "http://localhost:9090/img/" + res;
},
selectType(typename,id) {
this.typeName = typename;
this.typeId = id;
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
}
}
}
</script>
<style scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>
https://code100.blog.csdn.net/article/details/123302546
getInfo() {
this.$http.get('blog/queryBlogByPage?title=' + this.title + '&page=' + this.page + '&rows=' + this.rows)
.then(response => (
this.info = response.data,
this.total = this.info.total,
this.totalPage = this.info.totalPage,
this.items = this.info.items
)).catch(function (error) { // 请求失败处理
console.log(error);
});
},
getInfo() {
this.$http.get('/blog/queryBlogArticleById?id=' + this.id )
.then(response => (
this.info = response.data,
this.title = this.info.title
)).catch(function (error) { // 请求失败处理
console.log(error);
});
},
selectBlog() {
this.page = 1;
this.rows = 10;
let startTime = (new Date(((this.value1+"").split(",")[0]))).getTime();
let endTime = (new Date(((this.value1+"").split(",")[1]))).getTime();
this.startBlogTime = startTime;
this.endBlogTime = endTime;
this.getInfo();
},
like(){
this.$http.get('blog/blogLikeId?id=' + this.id );
this.getInfo();
},
package cn.itbluebox.springbootcsdn.service.Impl;
import cn.itbluebox.springbootcsdn.domain.Consumer;
import cn.itbluebox.springbootcsdn.enums.ExceptionEnum;
import cn.itbluebox.springbootcsdn.exception.BlException;
import cn.itbluebox.springbootcsdn.mapper.ConsumerMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class ConsumerService {
@Autowired
private ConsumerMapper consumerMapper;
public Boolean checkData(String data, Integer type) {
Consumer consumer = new Consumer();
//判断数据类型
switch (type) {
case 1:
consumer.setEmail(data);
break;
case 2:
consumer.setPhone(Long.parseLong(data));
break;
default:
return null;
}
return consumerMapper.selectCount(consumer) == 0;
}
public Consumer queryUser(String email, String password) {
// 查询
Consumer consumer = new Consumer();
consumer.setEmail(email);
consumer.setPassword(password);
Consumer consumer1 = this.consumerMapper.selectOne(consumer);
// 校验用户名
if (consumer1 == null) {
return null;
}
// 用户名密码都正确
return consumer1;
}
public String saveConsumer(Consumer consumer) {
int insert = consumerMapper.insert(consumer);
if (insert != 1) {
throw new BlException(ExceptionEnum.CONSUMER_SAVE_ERROR);
}
return insert + "";
}
public Consumer queryConsumerById(Long id) {
Consumer consumer = new Consumer();
consumer.setId(id);
Consumer consumer1 = consumerMapper.selectOne(consumer);
consumer1.setPassword("");
return consumer1;
}
}
package cn.itbluebox.springbootcsdn.properties;
import cn.itbluebox.springbootcsdn.utils.RsaUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;
@ConfigurationProperties(prefix = "sc.jwt")
@Data
@Slf4j
public class JwtProperties {
private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
private PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private String cookieName;
private Integer cookieMaxAge;
// private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
/**
* @PostContruct:在构造方法执行之后执行该方法
*/
@PostConstruct
public void init(){
try {
File pubKey = new File(pubKeyPath);
File priKey = new File(priKeyPath);
if (!pubKey.exists() || !priKey.exists()) {
// 生成公钥和私钥
RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
}
// 获取公钥和私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
} catch (Exception e) {
log.error("初始化公钥和私钥失败!", e);
throw new RuntimeException();
}
}
}
package cn.itbluebox.springbootcsdn.web;
import cn.itbluebox.springbootcsdn.enums.ExceptionEnum;
import cn.itbluebox.springbootcsdn.exception.BlException;
import cn.itbluebox.springbootcsdn.properties.JwtProperties;
import cn.itbluebox.springbootcsdn.service.Impl.AuthService;
import cn.itbluebox.springbootcsdn.utils.CookieUtils;
import cn.itbluebox.springbootcsdn.utils.JwtUtils;
import cn.itbluebox.springbootcsdn.utils.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
@EnableConfigurationProperties(JwtProperties.class)
@Slf4j
public class AuthController {
@Autowired
private AuthService authService;
@Autowired
private JwtProperties prop;
/**
* 登录授权
*
* @return
*/
@GetMapping("accredit")
public ResponseEntity<Cookie> authentication(
@RequestParam("email") String email,
@RequestParam("password") String password,
HttpServletRequest request,
HttpServletResponse response) {
// 登录校验
System.out.println(" 登录校验 登录校验 登录校验 登录校验");
String token = authService.authentication(email, password);
if (StringUtils.isBlank(token)) {
log.info("用户授权失败");
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
Cookie cookie = CookieUtils.setGetCookie(request, response, prop.getCookieName(), token, prop.getCookieMaxAge(), true);
return ResponseEntity.ok(cookie);
}
@GetMapping("verify")
public ResponseEntity<UserInfo> verify(
@CookieValue("SC_TOKEN") String token,
HttpServletRequest request,
HttpServletResponse response
) {
//解析token
System.out.println("解析token解析token解析token解析token解析token");
try {
UserInfo userInfo = JwtUtils.getUserInfo(prop.getPublicKey(), token);
//刷新token,重新生成token
String newToken = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());
//写回cookie
CookieUtils.setCookie(request, response, prop.getCookieName(), newToken, prop.getCookieMaxAge(), true);
//返回用户信息
return ResponseEntity.ok(userInfo);
} catch (Exception e) {
//token以过期,或者token篡改
throw new BlException(ExceptionEnum.UN_AUTHORIZED);
}
}
}
<template>
<div>
<el-card class="box-card" style="width:30%;margin: auto;height: 550px;margin-top: 100px">
<div slot="header" class="clearfix">
<span>登录博客</span>
</div>
<div>
<el-input v-model="email" placeholder="邮箱" style="margin-top: 50px"></el-input>
<el-input v-model="password" placeholder="密码" style="margin-top: 50px"></el-input>
<el-button type="primary" @click="login" style="margin-top: 80px">登录</el-button>
</div>
<div style="float: right">
<el-button @click="register" style="margin-top: 80px">注册账号</el-button>
</div>
</el-card>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
email: "",
password: "",
users: {
id: "",
name: "",
image: "",
email: "",
}
}
},
created() {//编写构造函数
this.$http.get("/verify/")
.then((res) => {
this.$router.push({path: "/"})
this.$message.success("已经登录成功请执行其他操作!");
}).catch(() => {
});
},
methods: {
login() {
this.$http.get("/accredit?email=" + this.email + "&password=" + this.password)
.then((res) => {
console.log(res.data.value);
this.$http.get("/verify/")
.then(({data}) => {
this.users = data;
sessionStorage.setItem('userId',this.users.id);
});
this.$message.success("登录成功!");
this.$router.push("/");
}).catch(() => {
this.$message.error("登录失败!");
});
},
register() {
this.$router.push("/Register");
},
},
watch: {
page: function () {
this.getInfo();
}
}
}
</script>
<style scoped>
</style>
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Article from '@/components/Article'
import Write from '@/components/Write'
import Login from '@/components/Login'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/Article',
name: 'Article',
component: Article
},
{
path: '/Write',
name: 'Write',
component: Write
},
{
path: '/Login',
name: 'Login',
component: Login
},
]
})
访问页面:http://localhost:8080/#/Login
<el-button @click="goToWrite">写文章</el-button>
goToWrite() {
this.$router.push("/Write");
},
没有登录跳转回登录页面
created() {//编写构造函数
this.$http.get("verify/")
.then((res) => {
}).catch(() => {
this.$router.push({path: "/Login"})
this.$message.error("没有登录请登录后发布文章!");
});
},
package cn.itbluebox.springbootcsdn.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "blog")
public class Blog {
private Long id; //文章id
private String title; //文章标题
private String abstract_text; //文章内容
private String thumbnail; //缩略图
private Date create_time; //创建时间
private Long like_count; //点赞数量
private Long view_count; //浏览量
private Long consumer_id; //用户ID
private String type_id; //类型
private Long blog_article_id; //博客文章ID
@Transient
private String context;
@Transient
private Date last_update_time; //更新时间
@Transient
private Character is_original;
@Transient //Transient声明当前字段不是数据对应的字段
private Long[] typeId;
}
@PostMapping("save")
public ResponseEntity<String> saveBlogging(
@RequestBody Blog blog,
@CookieValue("SC_TOKEN") String token,
HttpServletRequest request, HttpServletResponse response
){
UserInfo userInfo = JwtUtils.getUserInfo(prop.getPublicKey(), token);
//刷新token,重新生成token
String newToken = JwtUtils.generateToken(userInfo, prop.getPrivateKey(), prop.getExpire());
//写回cookie
CookieUtils.setCookie(request, response, prop.getCookieName(), newToken, prop.getCookieMaxAge(), true);
//返回用户信息
blog.setConsumer_id(userInfo.getId());
String blog_code = blogService.saveBlogging(blog);
return ResponseEntity.ok(blog_code);
}
String saveBlogging(Blog blog);
实现类当中的方法
```java
@Transactional
@Override
public String saveBlogging(Blog blog) {
//先插入博客的文章部分
long l = new IdWorker(new Random().nextInt(10), 1).nextId();
String ls = (l + "");
ls = ls.substring(5, ls.length());
BlogArticle blogArticle = new BlogArticle();
blogArticle.setId(Long.parseLong(ls));
blogArticle.setContext(blog.getContext());
blogArticle.setLast_update_time(new Date());
blogArticle.setIs_original(blog.getIs_original());
//插入博客文章的代码
int insert2 = blogArticleMapper.insertSql(blogArticle);
if (insert2 != 1) {
throw new BlException(ExceptionEnum.BLOG_SAVE_ERROR);
}
//插入博客
blog.setCreate_time(new Date());
blog.setLike_count(1L);
blog.setView_count(1L);
blog.setBlog_article_id(blogArticle.getId());
int insert1 = blogMapper.insert(blog);
if (insert1 != 1) {
throw new BlException(ExceptionEnum.BLOG_SAVE_ERROR);
}
return "success";
}
@Insert("insert into blog_article VALUES (#{id},#{context},#{last_update_time},#{is_original})")
int insertSql(BlogArticle blogArticle);
运行测试
<el-row >
<el-col :span="16" style="padding-left: 200px;padding-right: 200px;">
<div>
<el-input v-model="title" placeholder="请输入内容"></el-input>
</div>
</el-col>
<el-col :span="8">
<div>
<el-button type="primary" round @click="getInfo()" style="margin-left: -550px;" >搜索</el-button>
</div>
</el-col>
</el-row>
输入内容
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://code100.blog.csdn.net/article/details/123456044
内容来源于网络,如有侵权,请联系作者删除!