说实话我是真没想到就这么简单的功能我还会踩坑,没办法前端写得烂,发送数据格式不对,后端的图片处理又不对,烦死了,不过还好后面有搞回来了,现在做个简单记录。到这里的话,后面就是如何整合那个Markdown编辑器,然后就是博客表的设计,分页获取数据,之后就是用户权限的设置,现在的话还没有后台管理,不过这个也好办主要是后面的文章审核,管理要用到,总的来说到周末还是有机会完成的,毕竟下个礼拜我要去展示一下我的项目作为期末作业,虽然这个是web的大二学校才刚刚教java,这次交个简单的桌面即可,但是这又不是我,理论上我还可以交安卓,交Flink的作品,但是前者没意思,uniapp香呀,还要啥安卓反正我又不是专业的前端,后者这玩意太抽象,我总不能说让Flink 跑个算法吧。所以web是比较理想的,刚好复习springboot 玩玩前后端分离。
这个其实就是简单的CURD,后端拿到数据,然后更新即可,但是这里刚好涉及到用户验证,也就是token检测,这个可以去看看这篇
SpringBoot+Vue前后端分离实战(用户注册登录)
这个其实是一系列的记录。
这里几个比较重要的数据交互,一个是用户更新后的信息和状态,一个是token。
<template>
<div>
<div class="show">
<form>
<p style="margin-left:16%;margin-top: 3%;height:30px">昵称:<span style="margin-left: 5%"><input ref="username" :maxlength="10" placeholder="修改昵称" class="texti" style="height: 8%" type="text" maxlength="10">input>span>p>
<p style="margin-left:16%;margin-top: 3%;height:30px">性别:<span style="margin-left: 5%"><input v-model="checkedman" ref="man" checked id="man" type="radio" value="1" name="1" />男 <input v-model="checkedman" ref="woman" id="woman" type="radio" value="2" name="1"/>女span>p>
<p style="margin-left:16%;margin-top: 3%;height:30px">邮箱:<span style="margin-left: 5%"><input ref="email" :maxlength="30" id="emailAddress" type="email" placeholder="修改邮箱" style="height: 8%" class="texti">input>span>p>
<p style="margin-left:16%;margin-top: 3%;height:200px">简介:<span style="margin-left: 5%"><textarea ref="jianjie" style=" width: 60%;height: 80%;" class="texti" placeholder="请开始编辑">textarea>span>p>
<el-button style="margin-left: 10%;width: 20%;height: 15%" class="btn" @click="cancle()">取消el-button>
<el-button style="margin-left: 40%;width: 20%;height: 15%" class="btn" @click="submitForm()">确定el-button>
form>
div>
div>
template>
<script>
//还是等跳转到这里的时候再来加载数据好一点
export default {
name: "changeinfo",
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页");
let islogin = localStorage.getExpire("tokenhole")
if(!islogin){
next({
path:'/login'});
}
next();
},
data(){
return{
checkedman:"1",
checkedwoman:""
}
},
methods:{
cancle(){
this.$router.push("/space")
},
changeinfo(){
this.axios({
url: "/boot/space/changeuserinfo",
method: 'post',
headers: {
"token": localStorage.getExpire("tokenhole") },
data: {
name: this.$userinfo.userName,
email: this.$userinfo.userEmail,
sex: this.$userinfo.userSex,
userinfo: this.$userinfo.userInfo
},
}).then(res =>{
let data = res.data
if(data.success == '0'){
alert("数据提交异常请重新提交~")
}
else {
alert("信息修改成功")
this.$router.push("/space")
}
})
}
,
submitForm(){
// 先改变本地的数值,然后上传到服务器
if(this.$refs.email.value!=''){
if(this.$refs.email.value!==this.$userinfo.userEmail){
if(this.$refs.email.value.indexOf("@") >= 0){
this.$userinfo.userEmail = this.$refs.email.value
}
else {
alert("请输入正确的邮箱地址!")
return
}
}
}
if(this.$refs.jianjie.value!=''){
if(this.$refs.jianjie.value!==this.$userinfo.userInfo){
this.$userinfo.userInfo= this.$refs.jianjie.value
}
}
if(this.$refs.username.value!=''){
if(this.$refs.username.value!==this.$userinfo.userName){
this.$userinfo.userName = this.$refs.username.value
}
}
// 由于男女是存储值的,所以还是要判断一下的,默认是男的所以只需要看看是说不是小姐姐就好了
if(this.checkedwoman===2){
this.$userinfo.userSex='女'
}
//这部分是后面和后端打交道的,最后回去space页面
this.changeinfo()
}
}
}
script>
<style scoped>
.show{
margin: 100px auto;
width: 80%;
height: 450px;
border: 5px solid #18a0ec;
transition: all 0.9s;
border-radius: 10px;
}
.show:hover{
box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.4);
margin-top: 90px;
}
.texti{
border: 1px solid #ccc;
padding: 13px 14px;
width: 30%;
font-size: 14px;
font-weight: 300;
border-radius: 5px; /* 边框半径 */
background: white; /* 背景颜色 */
cursor: pointer; /* 鼠标移入按钮范围时出现手势 */
outline: none; /* 不显示轮廓线 */
}
.texti:focus{
border-color: #1e88e1;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
}
textarea {
resize: none;
}
style>
这个前端基本上就是直接硬怼的,没有啥技巧。
那个用户信息是个全局变量
这样当用户信息更新之后能够及时在前端显示。
到了这里就是后端接口
public SpaceMessage ChangeUserInfo(HttpServletRequest request,
@RequestBody Map<String, Object> usermap) throws Exception {
String token = request.getHeader("token");
User user = VerifyUser(token);
String name = (String) usermap.get("name");
String email = (String) usermap.get("email");
String sex = (String) usermap.get("sex");
String userinfo =(String) usermap.get("userinfo");
user.setUserInfo(userinfo);
user.setUserEmail(email);
user.setUserSex(sex);
user.setChengHu(name);
boolean flag = userService.UpdataUser(user);
if(flag)
spaceMessage.setSuccess(1);
else
spaceMessage.setSuccess(0);
return spaceMessage;
}
但是这里的话由于整个项目的分层比较多所以只能把核心的拿过来看看。(项目源码后面会在GitHub开源,这个只是相当于记录)
这个还是比较重要的,后面那个Markdown的图片显示也还用的上。
这里的话你可以选择上传之后服务器返回图片url,但是我这里是选择在前端自己加载了本地的图片(也是把先前的老代码拿了过来改一下)
changepic() {
document.getElementById('file').onchange = function () {
var imgFile = this.files[0];
var fr = new FileReader();
fr.onload = function () {
let showing = document.getElementById('showimg')
let img = fr.result;
this.f64 = img;
this.filename = imgFile.name
showing.src = img;
showing.style.height = "220px";
showing.style.width = "220px";
showing.style.borderRadius="200px"
};
fr.readAsDataURL(imgFile);
}
}
这里要说的就是那个这里还是用 axios发送的那玩意默认是把数据放在 body 了,所以这里封装一个
let param = new FormData();
这样就好了,这样就不会放在那里了,这样后端就好接受了,否则拜拜~
subchangepic(){
if(this.$refs.file.files[0]!=null){
this.f64 = this.$refs.file.files[0]
console.log(this.f64.name)
let param = new FormData();
param.append("file",this.f64);
this.axios({
url: "/boot/space/changepic",
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
"token": localStorage.getExpire("tokenhole"),
},
data: param,
}).then(res=>{
if(res.data.success == '0'){
alert("头像上传失败请检查图片格式")
}
else {
alert("信息修改成功")
this.$router.push("/space")
}
})
}
<template>
<div>
<div class="show">
<div class="show1" >
<img ref="showing" src="" id="showimg" style="margin-left: 1px;margin-top: 3px">
div>
<br>
<input multiple="multiple" id="file" ref="file" @click="changepic(this)" type="file" name="userpic"
style="margin-left: 10%;height: 50px; display:inline-block;width: 50%;">
<button @click="subchangepic()" style="height: 40px;position: relative; margin-left:15%;">确定button>
div>
div>
template>
<script>
//还是等跳转到这里的时候再来加载数据好一点
export default {
name: "changepic",
data(){
return {
filename: null,
f64: null,
loadImage: ""
}
},
beforeRouteEnter: (to, from, next) => {
let islogin = localStorage.getExpire("tokenhole")
if(!islogin){
next({
path:'/login'});
}
next();
},
methods:{
changepic() {
document.getElementById('file').onchange = function () {
var imgFile = this.files[0];
var fr = new FileReader();
fr.onload = function () {
let showing = document.getElementById('showimg')
let img = fr.result;
this.f64 = img;
this.filename = imgFile.name
showing.src = img;
showing.style.height = "220px";
showing.style.width = "220px";
showing.style.borderRadius="200px"
};
fr.readAsDataURL(imgFile);
}
},
cancle(){
this.$router.push("/space")
},
subchangepic(){
if(this.$refs.file.files[0]!=null){
this.f64 = this.$refs.file.files[0]
console.log(this.f64.name)
let param = new FormData();
param.append("file",this.f64);
this.axios({
url: "/boot/space/changepic",
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
"token": localStorage.getExpire("tokenhole"),
},
data: param,
}).then(res=>{
if(res.data.success == '0'){
alert("头像上传失败请检查图片格式")
}
else {
alert("信息修改成功")
this.$router.push("/space")
}
})
}
}
}
}
script>
<style scoped>
button {
margin-left: 70%;
width: 15%;
height: 35px;
border-width: 0px;
border-radius: 3px;
background: #1E90FF;
cursor: pointer;
outline: none;
font-family: Microsoft YaHei;
color: white;
font-size: 17px;
}
.show{
margin: 100px auto;
width: 80%;
height: 450px;
border: 5px solid #18a0ec;
transition: all 0.9s;
border-radius: 10px;
}
.show1{
margin: 50px auto;
width: 222px;
height: 226px;
border: 5px solid #18a0ec;
transition: all 0.9s;
border-radius: 150px;
}
.show1:hover{
box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.4);
margin-top: 90px;
}
.show:hover{
box-shadow: 0px 15px 30px rgba(0, 0, 0, 0.4);
margin-top: 90px;
}
.texti{
border: 1px solid #ccc;
padding: 13px 14px;
width: 30%;
font-size: 14px;
font-weight: 300;
border-radius: 5px; /* 边框半径 */
background: white; /* 背景颜色 */
cursor: pointer; /* 鼠标移入按钮范围时出现手势 */
outline: none; /* 不显示轮廓线 */
}
.texti:focus{
border-color: #1e88e1;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)
}
textarea {
resize: none;
}
style>
这里的话其实没啥就是接受到图片然后保存
@Autowired
UpPhotoNameUtils upPhotoNameUtils;
@PostMapping("/space/changepic")
public SpaceMessage ChangePic(@RequestParam("file") MultipartFile file,HttpServletRequest request) throws Exception{
String userPic = upPhotoNameUtils.SaveUserPic("UserPic", file);
if(userPic==null){
spaceMessage.setSuccess(0);
}
String token = request.getHeader("token");
User user = VerifyUser(token);
user.setUserPic(userPic);
boolean flag = userService.UpdataUser(user);
if(flag){
spaceMessage.setUserPic(user.getUserPic());
spaceMessage.setSuccess(1);
}
else
spaceMessage.setSuccess(0);
return spaceMessage;
}
这里是有一个验证的,就是token验证
但这个不是重点,重点是那个工具类
这个帮助我们保存图片,原则是生成UUID避免图片名字重复,按照年月日生成文件夹保存图片避免触发Linux的那个老问题。
这里的话我搞了个配置
@Data
@Component
@ConfigurationProperties(prefix = "file.upload")
public class PhotosConfig {
private List<String> allowTypes;
}
@Component
public class UpPhotoNameUtils {
@Autowired
PhotosConfig photosConfig;
static String UserPicRoot = "static/";
public String SaveUserPic(String LodPath ,MultipartFile file) {
String contentType = file.getContentType();
//限制文件格式
if(!photosConfig.getAllowTypes().contains(contentType)){
return null;
}
String filename = UUID.randomUUID().toString() + System.currentTimeMillis();
//先创建文件夹
Calendar cad = Calendar.getInstance();
String month = String.valueOf(cad.get(Calendar.MONTH) + 1);
if (month.length() < 2) {
month = "0" + month;
}
String day = String.valueOf(cad.get(Calendar.DAY_OF_MONTH));
try {
UserPicRoot= ResourceUtils.getURL("classpath:").getPath()+UserPicRoot;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
String RevPath = LodPath +"/"+ String.valueOf(cad.get(Calendar.YEAR)) +"/"+ month+"/"+day;
String savePath = UserPicRoot+RevPath;
String realsavePath = savePath.replace('/', '\\').substring(1,savePath.length());
//这个是为了拿到真实的存储地址,返回结果如下:C:\Users\31395\Desktop\WhiteHole\WhiteHoleBoot\target\classes\static\UserPic\2021\12\1
File dirFile = new File(realsavePath);
boolean bFile = dirFile.exists();
if(!bFile){
dirFile.mkdirs();
}
RevPath = RevPath+"/"+ filename + file.getOriginalFilename();
savePath = UserPicRoot+RevPath;
try {
file.transferTo(new File(savePath));
} catch (IOException e) {
e.printStackTrace();
}
return "/" + RevPath;
}
}
这个早就要说了,刚好把这个做了就是一下。
这里的话没啥,直接先看源码就好了,值得一提的是我那个上传博客的页面并没有做好。
<template>
<div style="width: 100%;position: relative">
<div>
<input type="text" name="blogname" placeholder="请输入文章标题" required>
<button type="submit" id="submit" @click="submit">发布文章button>
div>
<br>
<mavon-editor
v-model="content"
ref="md"
@imgAdd="imgAdd"
@change="change"
style="min-height: 800px;width: 100%"
/>
div>
template>
<script>
import {
mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import axios from "axios";
export default {
// 注册
name: 'WirterMarkdown',
components: {
mavonEditor,
},
beforeRouteEnter: (to, from, next) => {
console.log("准备进入个人信息页");
let islogin = localStorage.getExpire("tokenhole")
if(!islogin){
next({
path:'/login'});
}
next();
},
data() {
return {
content:'', // 输入的markdown
html:'', // 及时转的html
}
},
methods: {
imgAdd(pos, $file){
let param = new FormData()
param.append("file",$file)
this.axios({
url: "/boot/boke/bokeImg",
method: "post",
data: param,
headers:{
'Content-Type': 'multipart/form-data',
"token": localStorage.getExpire("tokenhole"),
}
}).then(res=>{
if(res.data.success == 0){
alert("图片上传失败")
return
}
let url = "/boot"+ res.data.bokeImg
this.$refs.md.$img2Url(pos,url)
})
},
// 所有操作都会被解析重新渲染
change(value, render){
//value为编辑器中的实际内容,即markdown的内容,render为被解析成的html的内容
this.html = render;
},
// 提交
submit(){
//点击提交后既可以获取html内容,又可以获得markdown的内容,之后存入到服务端就可以了
console.log(this.content);
console.log(this.html);
}
},
mounted() {
}
}
script>
<style scoped>
#center {
margin-top: 5%;
width: 96%;
height: 96%;
border: 1px;
}
img {
margin: auto;
margin-left: 30%;
height: 40%;
width: 40%;
position: relative;
top: 10%;
}
input {
width: 85%;
height: 30px;
border-width: 2px;
border-radius: 5px;
border-color: #00c4ff;
border-bottom-color: #2C7EEA;
color: #586e75;
font-size: 15px;
}
button {
width: 10%;
height: 35px;
border-width: 0px;
margin-left: 3%;
border-radius: 10px;
background: #1E90FF;
cursor: pointer;
outline: none;
font-family: Microsoft YaHei;
color: white;
font-size: 17px;
}
button:hover {
background-color: #1E90FF;
box-shadow: 0 4px 0 powderblue;
}
style>
@PostMapping("/boke/bokeImg")
public BokeMessage ChangePic(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws Exception{
//这个不需要验证,没必要!
String userPic = upPhotoNameUtils.SaveUserPic("BokeImg", file);
System.out.println("hello");
if(userPic==null){
bokeMessage.setSuccess(0);
}
bokeMessage.setSuccess(1);
bokeMessage.setBokeImg(userPic);
return bokeMessage;
}
图片存在这