前言
说实话我是真没想到就这么简单的功能我还会踩坑,没办法前端写得烂,发送数据格式不对,后端的图片处理又不对,烦死了,不过还好后面有搞回来了,现在做个简单记录。到这里的话,后面就是如何整合那个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='女'
}
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());
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;
}
}
效果
Markdown 图片上传
前端
这个早就要说了,刚好把这个做了就是一下。 这里的话没啥,直接先看源码就好了,值得一提的是我那个上传博客的页面并没有做好。
<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:'',
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){
this.html = render;
},
submit(){
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;
}
效果
图片存在这
|