? ? ?老實(shí)說,一開始覺得這個圖片的上傳沒那么復(fù)雜,剛開始自己的構(gòu)思是前端傳來圖片文件,后端接收,先將文件存在本地的一個文件里面,然后根據(jù)前端傳來的圖片名稱,結(jié)合后端所存的圖片地址路徑,拼湊出一個web訪問的url地址,把這個地址存在數(shù)據(jù)庫中,需要回顯的時候就從數(shù)據(jù)庫中查詢出這個url,結(jié)合前端<img></img>標(biāo)簽的src屬性將請求后端獲取的url地址填上去,就能將圖片顯示出來。
? ? ? ? 起初,是這樣子設(shè)想,只是落實(shí)到代碼的時候要考慮的細(xì)節(jié)就比較多了?,F(xiàn)在我只是做的一個頭像的更換。這里面的細(xì)節(jié)還是挺多的。所以在此就記錄一下,也方便以后自己查看。
? ? ? ? 先看看效果吧,前端寫的不太好看,不過也夠用了。
????????做的是一個個人中心的頁面查看修改,包括姓名、郵箱、電話、性別、頭像的修改。其他都還好,就是一些String類型的數(shù)據(jù)交互,重點(diǎn)就在于頭像的更換了。實(shí)現(xiàn)的效果是點(diǎn)擊修改圖像彈出選擇圖片的文件列表,選擇完畢之后,左邊的頭像圖片框內(nèi)會顯示選擇的圖片,右邊會出現(xiàn)一個移除圖片的按鈕,其實(shí)這個是文件上傳標(biāo)簽<el-upload>文件列表改的一個樣式,這個后面會講。點(diǎn)擊移除圖片的按鈕,之前選擇的圖片會被移除,重新顯示原本的頭像,按鈕也會消失。當(dāng)點(diǎn)擊提交的時候修改過的姓名、性別、郵箱、電話,會存儲在數(shù)據(jù)庫中,頭像上傳后返回一個url地址,將這個地址存儲在數(shù)據(jù)庫中,結(jié)合<img>標(biāo)簽就可以顯示出頭像了。
?
? ? ? ? ?好吧!本人比較擅長后端,當(dāng)然了也參考了網(wǎng)上的一些代碼,結(jié)合自己的理解,暫時就先做成這個樣子了,如果有需求還是可以修改。
? ? ? ? 話不多說,從后端開始!
? ? ? ? 后端的第一件事情就是先設(shè)計(jì)的數(shù)據(jù)庫中的數(shù)據(jù)表,當(dāng)然了,因?yàn)檫@個頁面的邏輯沒有多復(fù)雜,后端邏輯也沒有特別麻煩的地方。這里只講頭像上傳,和個人信息更新兩個方面的,因?yàn)檫@個頁面中的數(shù)據(jù)新增是在別的地方實(shí)現(xiàn)的,就不講了。如果需要了解,也可以私聊我,但是邏輯上也不難處理。
? ? ? ? 好了,數(shù)據(jù)庫使用的是mysql,持久層用的mybatis-plus,如果對mysql數(shù)據(jù)庫,和mybatis-plus不太熟悉的,可以先去網(wǎng)上找找資料先學(xué)習(xí)一下,這里就不講這些了。先創(chuàng)建一個用戶表,sql語句為:
CREATE TABLE `sys_user` (
`id` BIGINT ( 20 ) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`user_name` VARCHAR ( 64 ) NOT NULL DEFAULT 'NULL' COMMENT '用戶名',
`personal_name` VARCHAR ( 64 ) DEFAULT 'NULL' COMMENT '姓名',
`password` VARCHAR ( 64 ) NOT NULL DEFAULT 'NULL' COMMENT '密碼',
`status` CHAR ( 1 ) DEFAULT '0' COMMENT '賬號狀態(tài)(0正常 1停用)',
`email` VARCHAR ( 64 ) DEFAULT NULL COMMENT '郵箱',
`phonenumber` VARCHAR ( 32 ) DEFAULT NULL COMMENT '手機(jī)號',
`sex` CHAR ( 1 ) DEFAULT NULL COMMENT '用戶性別(0男,1女,2未知)',
`avatar` VARCHAR ( 128 ) DEFAULT NULL COMMENT '頭像',
`user_type` CHAR ( 1 ) NOT NULL DEFAULT '1' COMMENT '用戶類型(0管理員,1普通用戶)',
`create_by` BIGINT ( 20 ) DEFAULT NULL COMMENT '創(chuàng)建人的用戶id',
`create_time` DATETIME DEFAULT NULL COMMENT '創(chuàng)建時間',
`update_by` BIGINT ( 20 ) DEFAULT NULL COMMENT '更新人',
`update_time` DATETIME DEFAULT NULL COMMENT '更新時間',
`del_flag` INT ( 11 ) DEFAULT '0' COMMENT '刪除標(biāo)志(0代表未刪除,1代表已刪除)',
PRIMARY KEY ( `id` ) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 8 DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMMENT = '用戶表';
? ? ? ? 都是一些比較普通的字段,沒啥特別的。
? ? ? ? 表創(chuàng)建好了之后,就創(chuàng)建spring boot項(xiàng)目了。這個如果不了解不會的話,可以看看我上一篇的文章,雖然是寫的分頁,但是還是比較詳細(xì)的介紹了spring boot項(xiàng)目的創(chuàng)建,只是將oracle數(shù)據(jù)庫換成mysql就行了。鏈接地址:http://t.csdn.cn/XrXkq。
? ? ? ? 接下來就是實(shí)體類了,根據(jù)表中的字段創(chuàng)建,直接貼代碼吧:
package com.lhh.managementsystem_h.eneity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_user")
public class User implements Serializable {
private static final long serialVersionUID = -40356785423868312L;
@TableId(value = "id",type = IdType.AUTO)
private Long id;//主鍵
private String userName;//用戶名
private String personalName;//工號
private String password;//密碼
private String status;//賬號狀態(tài)(0正常,1停用)
private String email;//郵箱
private String phonenumber;//手機(jī)號
private String sex;//用戶性別(0男,1女,2未知)
private String avatar;//頭像
private String userType;//用戶類型(0管理員,1普通用戶)
private Long createBy;//創(chuàng)建人的用戶id
private Date createTime;//創(chuàng)建時間
private Long updateBy;//更新人
private Date updateTime;//更新時間
private Integer delFlag;//刪除標(biāo)志(0代表未刪除,1代表已刪除)
}
????????創(chuàng)建了一個java類User,實(shí)現(xiàn)了序列化,其實(shí)這里是可以去掉,但是我原本的項(xiàng)目中是需要使用到序列化的,這里也沒有影響。就保留了下來,至于那四個注解,前面三個是lombok相關(guān)的,不了解的可以去了解一下,可以省下寫setter,getter方法,以及無參有參的構(gòu)造方法,第四個則是mybatis-plus相關(guān)的,主要標(biāo)明對應(yīng)的是哪個表,value中就寫的哪個表。
? ? ? ? 創(chuàng)建完實(shí)體類,就需要創(chuàng)建相關(guān)的controller層,service層,mapper層,我們一層一層解析進(jìn)去,從controller層開始。這里是前端請求的,后端接收參數(shù)的地方??梢韵葎?chuàng)建一個controller文件包,在包內(nèi)創(chuàng)建一個PictureController java類,如下代碼就是controller層中PictureController類的內(nèi)容:
package com.lhh.managementsystem_h.controller;
import com.lhh.managementsystem_h.result.ResponseResult;
import com.lhh.managementsystem_h.service.AvatarService;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
@RestController
public class PictureController {
@Resource
private AvatarService avatarService;
@PostMapping(value = "/image/avatar")
public ResponseResult uploadImage(@RequestParam(value = "file") List<MultipartFile> files){
System.out.println(files);
List<String> imagePath = avatarService.uploadImage(files);
return new ResponseResult(200,"上傳成功!!",imagePath);
}
@GetMapping(value = "/image/deleteFile/{fileName}")
public ResponseResult deleteFile(@PathVariable(value = "fileName") String fileName){
System.out.println(fileName);
return avatarService.deleteFile(fileName);
}
}
? ? ? ? 先來看看這兩個接口,返回的都是ResponseResult封裝類,這個封裝類是自己寫的,看看代碼就懂里面是什么東西了,可以再創(chuàng)建一個result包,將封裝類放到里面去:
package com.lhh.managementsystem_h.result;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/***
* 響應(yīng)類,
* 返回數(shù)據(jù)使用
*
* @author lhh
*/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {
private Integer code;//狀態(tài)碼
private String message;//提示信息,如果有錯誤時,前端可以獲取該字段進(jìn)行提示
private T data;//查詢到的結(jié)果數(shù)據(jù)
public ResponseResult(Integer code,String message){
this.code = code;
this.message = message;
}
public ResponseResult(Integer code,T data){
this.code = code;
this.data = data;
}
public ResponseResult(Integer code,String message,T data){
this.code = code;
this.message = message;
this.data = data;
}
}
? ? ? ? 重載了三個構(gòu)造方法,分別是(1)狀態(tài)碼、提示信息;(2)狀態(tài)碼、數(shù)據(jù);(3)狀態(tài)碼、提示信息、數(shù)據(jù)??梢宰鳛榉祷亟o前端的封裝類,前端拿到了返回信息可以用res.code獲取狀態(tài)碼等。
? ? ? ? 繼續(xù)看看uploadImage類,這個是前端傳一個圖片過來,以二進(jìn)制文件列表進(jìn)行接收,然后進(jìn)行一系列的處理,返回一個url列表。
? ? ? ? 紅圈中的兩句打印看看傳過來的參數(shù)是什么,其實(shí)就只是用來測試,確定沒問題了就可以刪掉。接下來就是service層了,也就是使用@Resource注入的地方,兩個接口調(diào)用的方法avatarService.uploadImage()以及avatarService.deleteFile();一個將圖片存儲到本地路徑,一個通過圖片名稱刪除圖片。
? ? ? ? 先來看看service層的接口:
package com.lhh.managementsystem_h.service;
import com.lhh.managementsystem_h.result.ResponseResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface AvatarService {
/***
* 上傳圖片
* @param files 圖片文件列表
* @return 一個url列表,用于存在數(shù)據(jù)庫中,或者返回給前端顯示圖片
*/
List<String> uploadImage(List<MultipartFile> files);
/***
* 根據(jù)文件名稱刪除文件
* @param fileName 文件名
* @return
*/
ResponseResult deleteFile(String fileName);
}
? ? ? ? 創(chuàng)建一個名為service的文件包,在service包新建一個java接口AvatarService寫了兩個抽象方法,代碼中都有注釋了,應(yīng)該是比較好理解的。接下來看看他們兩個抽象方法的實(shí)現(xiàn)類:
package com.lhh.managementsystem_h.service.Impl;
import com.lhh.managementsystem_h.result.ResponseResult;
import com.lhh.managementsystem_h.service.AvatarService;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;
@Service
public class AvatarServiceImpl implements AvatarService {
/**
* Servlet請求域?qū)ο? */
@Resource
private HttpServletRequest request;
/***
* 上傳圖片存儲圖片
* @param files 前端傳來的文件列表
* @param path 路徑
* @return url地址列表
* @throws IOException
*/
public static List<String> upload(List<MultipartFile> files, String path) throws IOException {
List<String> msgs = new ArrayList<>();//用于存放圖片的路徑
if(files.size() < 1){//如果前端提交的列表是空的
msgs.add("file_empty");//列表加入文件的為空的提示,并返回
return msgs;
}
for(MultipartFile file : files){//遍歷文件名稱
String oldFileName = file.getOriginalFilename();//獲取舊文件名
//斷言,用于調(diào)試,如果表達(dá)式即oldFileName不為空的話,程序繼續(xù)執(zhí)行,否則會拋出異常
assert oldFileName != null;
//判斷舊文件名中是否有.字符串,如果有的話就從最后一個.截取到最后一個字符,沒有.字符就返回取null
String type = oldFileName.contains(".") ? oldFileName.substring(oldFileName.lastIndexOf(".")) : null;
//以uuid重新命名避免重復(fù),拼接文件路徑,方便前端接收
String filePath = path + UUID.randomUUID() + type;
System.out.println(filePath);//打印路徑查看
File filesPath = new File(path);//新建文件操作類
if (!filesPath.exists()) {//如果是不存在的話
filesPath.mkdirs();//創(chuàng)建文件路徑
}
BufferedOutputStream out = null;//字節(jié)緩沖流
try{
//字節(jié)輸入流,路徑是之前拼湊的路徑
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
out = new BufferedOutputStream(fileOutputStream);//新建緩沖流對象
out.write(file.getBytes());//將文件流寫入路徑之中
msgs.add(filePath);//將路徑增加進(jìn)列表中并返回
} catch (FileNotFoundException e) {//文件為空異常拋出
throw new RuntimeException(e);
} catch (IOException e) {//運(yùn)行時異常拋出
throw new RuntimeException(e);
}finally {
if(out != null){
out.flush();//把內(nèi)部緩沖區(qū)的數(shù)據(jù),刷新到文件中
out.close();//釋放資源,關(guān)閉流
}
}
}
return msgs;//返回列表
}
/***
* 根據(jù)文件路徑刪除文件
* @param filePath 文件路徑
* @return boolean類型
*/
public static boolean deleteFiles(String filePath){
boolean flag = false;//定義一個boolean變量,設(shè)置flag為false
File file = new File(filePath);//新建文件操作類
//路徑是個文件且不為空的時候時候刪除文件
if(file.isFile() && file.exists()){
flag = file.delete();//刪除文件,返回true
return flag;
}
//刪除失敗時,返回false
return false;
}
@Override
public List<String> uploadImage(List<MultipartFile> files) {
//新建一個存放圖片url的String列表
List<String> imagePath;
List<String> imageUrls = new ArrayList<>();//用于存放url地址
String path = "D:/xiangmu/xtgl/image/";//圖片存放的本地地址
//調(diào)用圖片上傳方法,也就是upload方法,用于上傳圖片
try{
//傳入?yún)?shù)files,path,然后返回新的文件名列表
imagePath = AvatarServiceImpl.upload(files,path);
System.out.println("圖片上傳路徑:"+imagePath);//打印圖片看看
for(String img : imagePath){//遍歷圖片路徑
//按‘/’截取
String[] split = img.split("/");
//取出數(shù)組中的最后一個元素就是文件名,數(shù)組下標(biāo)從0開始,最后一個就數(shù)組長度-1
String fileName = split[split.length - 1];
//開始拼湊基礎(chǔ)的url路徑,request.getScheme=http使用的協(xié)議名;
//request.getServerName() = localhost 服務(wù)器名稱;
//request.getServerPort() = 8909,端口號
//request.getContextPath() = 返回請求中對應(yīng)context的部分,如果此context是默認(rèn)context,則返回”“。
String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
String httpUrl = basePath + "/upload/" + fileName;//這里的"/upload/"是配置的虛擬路徑,拼湊出一個可以訪問的url
System.out.println("完整的url路徑:"+httpUrl);//打印出來看看,點(diǎn)擊一下是否能訪問
imageUrls.add(httpUrl);//將完整的url路徑添加進(jìn)列表中
}
} catch (IOException e) {
throw new RuntimeException(e);
}
if(imagePath.get(0).equals("file_empty")){//如果imagePath列表里面的第一個元素是"file_empty"
return null;//標(biāo)識列表是空,那就返回空
}
System.out.println("imagePath里面的東西是:"+imagePath);
System.out.println("imageUrls里面的東西是:"+imageUrls);
return imageUrls;
}
@Override
public ResponseResult deleteFile(String fileName) {
//截取文件類型,也就是圖片類型.png;.jpeg;.jpg格式的。判斷是否有.字符串,有的按最后一個.字符串截取字符
String type = fileName.contains(".") ? fileName.substring(fileName.lastIndexOf(".")) : null;
String filePath;//定義文件路徑
assert type != null;//如果文件類型不為空,則繼續(xù)運(yùn)行下去,否則拋出異常
if(type.equals(".png") || type.equals(".jpeg") || type.equals(".jpg")) {//判斷文件類型
filePath = "D:/xiangmu/xtgl/image/" + fileName;//文件路徑則是之前存圖片的地方,加上文件名拼湊出完整的文件路徑
}else {
return new ResponseResult(301,"文件類型不支持?。?);
}
boolean b = AvatarServiceImpl.deleteFiles(filePath);//調(diào)用刪除文件方法刪除文件
if(b){//如果是返回的是真的,即b=ture
return new ResponseResult(200,"刪除成功!!");
}else {//否則
return new ResponseResult(302,"刪除失敗??!");
}
}
}
? ? ? ? 這里面有四個方法,兩個是靜態(tài)方法,兩個是抽象方法的實(shí)現(xiàn);上面都有代碼的注釋了,如果還是覺得不夠清晰的也可以私聊一下我。當(dāng)然了,這里面的4個方法中,圖片的上傳的方法是uploadImage,這個方法是返回的一個可供訪問的url地址,同樣是拼湊出來的,這上面的/upload/的虛擬路徑,是在CorsConfig類中實(shí)現(xiàn),該類需要實(shí)現(xiàn)一個WegMvcConfigurer類,是用于配置跨域和虛擬路徑的方法,跨域如果不清楚的話,就自己找找資料了解一下吧,主要是如果這個各個方面都比較詳細(xì)的話,那么這篇文章就十分的冗長,而且主題也并沒有抓住重點(diǎn)了。好了,先貼下代碼看看是如何配置這個虛擬路徑的。
package com.lhh.managementsystem_h.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry){
//設(shè)置允許跨域的路徑
registry.addMapping("/**")
// 設(shè)置允許跨域請求的域名
.allowedOriginPatterns("*")
//是否允許cookie
.allowCredentials(true)
//設(shè)置允許的請求方式
.allowedMethods("GET","POST","DELETE","PUT")
// 設(shè)置允許的header屬性
.allowedHeaders("*")
// 跨域允許時間
.maxAge(3600);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry){
// /upload/**為前端URL訪問路徑 后面 file:xxxx為本地磁盤映射
registry.addResourceHandler("/upload/**") //虛擬url路徑
.addResourceLocations("file:D:/xiangmu/xtgl/image/"); //真實(shí)本地路徑
}
}
? ? ? ? 虛擬路徑的配置是在addResourceHandlers方法中,是用虛擬的url路徑代替了本地磁盤的路徑,最終可以構(gòu)建的虛擬的url地址為http://loaclhost:8909/upload/**,該路徑可用于存于數(shù)據(jù)庫中,用于前端查詢出來回顯。
? ? ? ? 我們在上傳了圖片之后,拿到了url的地址,那么就可以和前面修改過的個人信息,如姓名、性別、郵箱、手機(jī)號等信息一起提交了后端了。交給后端存儲在數(shù)據(jù)庫中。也就是在前面的截圖中點(diǎn)擊了提交之后,會進(jìn)行一個接口請求,調(diào)用接口存儲。代碼如下:
package com.lhh.managementsystem_h.controller;
import com.lhh.managementsystem_h.dto.UserInfoDto;
import com.lhh.managementsystem_h.result.ResponseResult;
import com.lhh.managementsystem_h.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping(value = "/user/updateUserInfo")
public ResponseResult updateUserInfo(@RequestBody UserInfoDto userInfoDto){
return userService.updateUserInfo(userInfoDto);
}
}
? ? ? ? 這是一個Controller,用于編寫用戶相關(guān)的接口,這里只寫了一個用戶信息的更新接口UserController,接著業(yè)務(wù)邏輯是在UserService層的updateUserInfo方法中,這里只是用于接收前端提交的用戶數(shù)據(jù),真正處理更新業(yè)務(wù)的還是得在Service層,其實(shí)有人喜歡在Controller寫業(yè)務(wù)邏輯,我其實(shí)不太能理解,主要是看到了Controller上一堆的代碼,看著還是不是那么舒服的,當(dāng)然了我只是不喜歡在Controller寫業(yè)務(wù),并不是說不能在Controller上寫,每個人都有自己的習(xí)慣,以及審美,在Controller寫也并不是不可以,就是當(dāng)我讀到你寫在Controller層的代碼的時候,我可能會崩潰了。
? ? ? ? 好像有點(diǎn)扯遠(yuǎn)了,下面來看看Service層的代碼如何處理存的業(yè)務(wù)邏輯。
package com.lhh.managementsystem_h.service;
import com.lhh.managementsystem_h.dto.UserInfoDto;
import com.lhh.managementsystem_h.result.ResponseResult;
public interface UserService {
ResponseResult updateUserInfo(UserInfoDto userInfoDto);
}
? ? ? ? 首先在service包下創(chuàng)建一個UserService接口,用于擴(kuò)展用戶相關(guān)的業(yè)務(wù)邏輯。接著在該接口寫一個updateUserInfo抽象方法,接口寫好了,就看看如何去實(shí)現(xiàn)這個接口。哦,對了,我忘了這個傳遞的userInfoDto對象了,這是一個數(shù)據(jù)傳輸對象,用于前端和接口數(shù)據(jù)傳輸,這是一種設(shè)計(jì)模式,如果你還是不太了解Dto、Vo的用法和用處,還是得去認(rèn)真學(xué)習(xí)一下,這些基本的東西如果不夠熟悉掌握的話,你代碼的開發(fā)就會非常的困難,很多bug你是無法準(zhǔn)確定位在哪里的,而我的文章只是針對與一個功能的開發(fā),對于基礎(chǔ)的java語法用法等不會涉及太多,還是得自己系統(tǒng)的去學(xué)習(xí)才行。
? ? ? ? 好像又扯遠(yuǎn)了,看看UserInfoDto吧!!
package com.lhh.managementsystem_h.dto;
import lombok.Data;
import java.util.List;
@Data
public class UserInfoDto {
private Long id;//用戶id
private String status;//停用標(biāo)志
private String personalName;//姓名
private String userName;//工號
private List<Long> roleId;//角色id列表
private Long userId;//創(chuàng)建用戶id
private String avatar;//創(chuàng)建用戶頭像
private String email;//郵箱
private String sex;//性別
private String phoneNumber;//手機(jī)號
private String[] avatars;//頭像圖片列表
}
? ? ? ? 與實(shí)體類類似,但是屬性僅僅是你需要的數(shù)據(jù)而已,多余的全部都不要展示出來,這樣很大程度上還是能避免一些安全問題的。
? ? ? ? 來吧,看看是如何實(shí)現(xiàn)那個抽象方法的。
package com.lhh.managementsystem_h.service.Impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lhh.managementsystem_h.config.SecurityConfig;
import com.lhh.managementsystem_h.dto.UserInfoDto;
import com.lhh.managementsystem_h.eneity.Role;
import com.lhh.managementsystem_h.eneity.User;
import com.lhh.managementsystem_h.eneity.UserRole;
import com.lhh.managementsystem_h.mapper.RoleMapper;
import com.lhh.managementsystem_h.mapper.UserMapper;
import com.lhh.managementsystem_h.mapper.UserRoleMapper;
import com.lhh.managementsystem_h.result.ResponseResult;
import com.lhh.managementsystem_h.service.UserService;
import com.lhh.managementsystem_h.vo.PageVo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public ResponseResult updateUserInfo(UserInfoDto userInfoDto) {
User user = new User();
user.setId(userInfoDto.getId());//設(shè)置id
user.setPersonalName(userInfoDto.getPersonalName());//設(shè)置姓名
user.setEmail(userInfoDto.getEmail());//設(shè)置郵箱
user.setAvatar(userInfoDto.getAvatars()[0]);//設(shè)置頭像
user.setSex(userInfoDto.getSex());//設(shè)置性別
user.setPhonenumber(userInfoDto.getPhoneNumber());//設(shè)置電話
user.setUpdateTime(new Date());//設(shè)置修改時間
user.setUpdateBy(userInfoDto.getId());//設(shè)置更新人
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("id",userInfoDto.getId());
User user1 = userMapper.selectOne(wrapper);//匹配id查詢該id下的用戶信息
if(user1 != null ){//如果是查詢出來不為空,
if(user1.getAvatar() != null && !user1.getAvatar().equals(userInfoDto.getAvatars()[0])){//頭像也不為空的話,得刪除該頭像對應(yīng)的圖片
String imageName = StringUtils.substringAfterLast(user1.getAvatar(), "/");
//截取文件類型,也就是圖片類型.png;.jpeg;.jpg格式的。判斷是否有.字符串,有的按最后一個.字符串截取字符
String type = imageName.contains(".") ? imageName.substring(imageName.lastIndexOf(".")) : null;
String filePath;//定義文件路徑
assert type != null;//如果文件類型不為空,則繼續(xù)運(yùn)行下去,否則拋出異常
if(type.equals(".png") || type.equals(".jpeg") || type.equals(".jpg")) {//判斷文件類型
filePath = "D:/xiangmu/xtgl/image/" + imageName;//文件路徑則是之前存圖片的地方,加上文件名拼湊出完整的文件路徑
AvatarServiceImpl.deleteFiles(filePath);//調(diào)用刪除文件方法刪除文件
}
}
userMapper.updateUserInfo(user);//更新用戶信息
return new ResponseResult(200,"更新成功??!");
}else {
return new ResponseResult(300,"該用戶不存在?。?);
}
}
}
? ? ? ? 先新建一個實(shí)體類User,然后把UserInfoDto上需要存儲的數(shù)據(jù)全部設(shè)置到User類上,這里面比較重要的一點(diǎn)就是,根據(jù)前端傳過來的id來匹配和構(gòu)建sql語句:
? ? ? ? QueryWrapper wrapper = new QueryWrapper();//新建一個sql語句匹配對象
? ? ? ? wrapper.eq("id",userInfoDto.getId());//匹配前端傳過來的id屬性,匹配出來就是sql語句的where id = 1;只不過這是mybatis-plus自帶的條件匹配類,就不用自己再去寫一個xml文件來專門來寫這么一條sql語句了。
? ? ? ? User user1 = userMapper.selectOne(wrapper);//就像是你調(diào)用自己寫在mapper中的方法那樣,只不過就是現(xiàn)在這個selectOne方法是mybaits-plus自帶的,其實(shí)這條語句的就是相當(dāng)于select * from sys_user where id = 1;然后將查詢出來的那個結(jié)果的用user1給封裝起來罷了。
? ? ? ? 其實(shí)查詢這條對應(yīng)記錄的原因除了需要判斷這個對應(yīng)的id是否能夠查詢出來記錄,查詢出來的不為空,而且該記錄Avatar(頭像url數(shù)據(jù))也不為空,還有就是傳進(jìn)來的頭像url與原來的數(shù)據(jù)庫中記錄的不一樣的時候,這個時候就要先將原先存儲的頭像給刪除掉,不然一直更新頭像,卻不刪除本地的圖片的話,可能本地或者服務(wù)器的磁盤會被撐爆的,所以還是得進(jìn)行刪除才行,只允許一個用戶只有一個頭像存儲在本地或者服務(wù)器上。
? ? ? ? 把與原來不一樣的圖片的刪除了之后,才進(jìn)行數(shù)據(jù)庫的存儲。就是把前端提交的數(shù)據(jù)更新到數(shù)據(jù)庫里面,先是根據(jù)id查詢出該用戶的數(shù)據(jù),如果不是空的才進(jìn)行的操作(一般情況下查詢出來的應(yīng)該是不會空,因?yàn)樵谶M(jìn)到個人的頁面之前是要進(jìn)行的登錄操作的,而個人主頁的信息一般是從登錄的數(shù)據(jù)表里面獲取到的)。
? ? ? ? 寫到這里,后端的內(nèi)容基本上都已經(jīng)的寫完了。其實(shí)如果是要進(jìn)行一個比較大項(xiàng)目的開發(fā),這一點(diǎn)的內(nèi)容就是沙漠中的一粒沙子,市面上能夠上線的APP也好,網(wǎng)頁端也好,小程序端也好,都是有比較多的數(shù)據(jù)表,許多人夜以繼日的研發(fā)、測試而完成的,一個人開發(fā),需要的時間很長,需要克服的困難也很多。所以在日常的開發(fā)中,需要不斷的記錄,不斷積累經(jīng)驗(yàn)才能比較快速的定位到你程序中存在的問題,也就是我們常說的修復(fù)bug。
? ? ? ? 個人比較擅長后端,前端對于我來說比較難受的一點(diǎn)就需要不斷的調(diào)整樣式,有時候?yàn)榱嗣烙^,調(diào)整了一個布局又弄亂了另外的布局,可能是我對于前端孤陋寡聞了,對于我來說,能夠?qū)崿F(xiàn)功能,而且頁面布局不是那么丑陋,有許多的瑕疵我也只能忍受,我似乎并不是很想花費(fèi)許多的時間在頁面樣式的修改上,但是一個前端開發(fā)的大佬,一定是對于樣式的修改布局等有自己的主見看法,是肯為了美觀花費(fèi)上比較長的時間區(qū)調(diào)整樣式的,也會有自己樣式布局的積累,有用得上的能夠很快得拎出來使用。
? ? ? ? 我一個前端菜雞,能夠進(jìn)行普通的頁面開發(fā),就已經(jīng)算可以的了。只是,如果想要做全棧開發(fā)還是前后端的開發(fā)都要快速的了解,新的技術(shù)得不斷的學(xué)習(xí),別讓自己落后在技術(shù)領(lǐng)域的方面。不過好像有點(diǎn)扯遠(yuǎn)了。下面進(jìn)行前端頁面的開發(fā),先貼個代碼吧,個人中心的前端頁面代碼
<template>
<div style="height: 82vh">
<h1 style="text-align: center;color: #84c2ef"><i class="el-icon-s-custom"></i> 個人中心</h1>
<div>
<h3 style="text-align: center">工號:{{this.userInfo.username}}</h3>
</div>
<div style="width: 80%;height: 400px;">
<div class="infoUser" style="float: left" >
<el-form label-width="80px" :model="userInfo">
<el-form-item >
<h3>姓名:<el-input v-model="userInfo.personalName" style="width: 200px;"></el-input></h3>
</el-form-item>
<el-form-item >
<h3>性別:<el-radio v-model="userInfo.sex" label="男">男</el-radio>
<el-radio v-model="userInfo.sex" label="女">女</el-radio></h3>
</el-form-item>
<el-form-item >
<h3>郵箱:<el-input v-model="userInfo.email" style="width: 200px;"></el-input></h3>
</el-form-item>
<el-form-item >
<h3>手機(jī):<el-input v-model="userInfo.phoneNumber" style="width: 200px;"></el-input></h3>
</el-form-item>
</el-form>
</div>
<div class="touxiang" style="width: 200px;height: 89px;float: right">
<div class="pic">
<img v-if="form.image" :src="form.image?''+form.image:'@/assets/avatar.gif'" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</div>
<!-- :auto-upload="false" :show-file-list="false" list-type = "picture"-->
<el-upload
action='#'
:limit="Number(1)"
:file-list="imgUpload.fileList"
:headers = "imgUpload.uploadHeaders"
:before-upload = "imgHandleBefore"
:on-success = "imgHandleSuccess"
:on-change = "imgUploadStateChange"
:on-remove = "imgHandleRemove"
:on-exceed = "poiImgHandleExceed"
class="avatar-uploader"
:auto-upload = "false"
:show-file-list="false"
list-type = "picture"
ref="upload"
accept = "image/jpeg,image/png,image/jpg">
<el-button size="small" type="primary">修改頭像</el-button>
<div slot="tip" class="el-upload__tip">只能上傳jpg/png文件,且不超過5MB</div>
<div slot="tip" class="el-upload__tip"><h3>注意!要提交了才能更換??!</h3></div>
</el-upload>
<!-- 文件列表容器 -->
<div style="width:80%;position: center;">
<div class="fileTemp" v-for="file in imgUpload.fileList" :key="file.uid" >
<el-button type="danger"
icon="el-icon-delete"
@click="imgHandleRemove"
size="mini"
circle>移除圖片</el-button>
</div>
</div>
<div style="position: fixed;padding-top: 20%">
<el-button type="primary" @click="updateUserInfo">提交</el-button>
<el-button type="primary" @click="reset">重置</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import ImageApi from "@/api/ImageApi";
import UserApi from "@/api/UserApi";
export default {
name: "",
data () {
return {
userInfo:{
username: window.sessionStorage.getItem('username'),
personalName: window.sessionStorage.getItem('personalName'),
sex:window.sessionStorage.getItem('sex'),
email:window.sessionStorage.getItem('email'),
phoneNumber:window.sessionStorage.getItem('phoneNumber'),
avatars:'',
id: window.sessionStorage.getItem("id")
},
imgUpload:{
// 圖片上傳url
uploadURL: '',
uploadUrls:"http://localhost:8909/image/avatar",
//uploadUrls:"#",
// 圖片列表
fileList: [],
// 圖片回顯列表
re_fileList: [],
// 圖片上傳請求頭
uploadHeaders:{"token": window.sessionStorage.getItem("token")}, //{"tk" : getToken()},
//uploadHeaders:{},
},
form:{
image:[window.sessionStorage.getItem("avatar")]
},
fileLists: []
}
},
methods: {
//文件上傳前,觸發(fā)函數(shù)
imgHandleBefore(file){
const isLimit5M = file.size / 1024 / 1024 < 5;//判斷是是否小于5M
const typeArr = ['image/png','image/jpeg','image/jpg'];//判斷文件格式
const isJPG = typeArr.indexOf(file.type) !== -1;
//文件格式校驗(yàn)
if(!isJPG){
this.$message.error('只能上傳jpg/jpeg/png圖片??!');
return false;
}
//文件大小校驗(yàn)
if(!isLimit5M){
this.$message.error("上傳圖片不能超過5M??!")
return false;
}
},
imgHandleSuccess(response){
if(response.code !== 200){
this.$message.error("圖片上傳失?。?!");
return;
}
// 得到一個圖片信息對象 臨時路徑
const picInfo = {pic: response.data};
//將圖片信息對象,push到fileList數(shù)組中
this.imgUpload.fileList.push(picInfo);
},
imgUploadStateChange(file){
this.fileLists = file
this.imgUpload.fileList.push(file)
// 拼接圖片訪問url到表單中(僅上傳一張,上傳多張需要逗號拼接)
if(this.fileLists.length !== 0){
let url = null;
if(window.createObjectURL != undefined){
url = window.createObjectURL(this.fileLists.raw)
}else if(window.URL != undefined){
url = window.URL.createObjectURL(this.fileLists.raw)
}else if(window.webkitURL != undefined){
url = window.webkitURL.createObjectURL(this.fileLists.raw)
}
this.form.image = url;
}
},
imgHandleRemove(file){
console.log(file)
//console.log(file.response)
// 2、調(diào)用splice方法,移除圖片信息
this.imgUpload.fileList.splice(0,1);
//3、同時表單置空
this.form.image = window.sessionStorage.getItem("avatar");
// 由于上傳前校驗(yàn)鉤子return false時會調(diào)用文件刪除鉤子,刪除前先做判斷,若上傳成功才可刪除
// if(file && file.response.code === 200){
// // 0、獲取將要刪除的圖片的臨時路徑一級文件名稱
// let filePath = "";
// if(file.response !== undefined){
// filePath = file.response.data[0];
// }else {
// filePath = file.url;
// }
// let fileName = filePath.substring(filePath.lastIndexOf("\\") + 1);
// fileName = fileName.substr(fileName.lastIndexOf("/") + 1);
// // 1.刪除單張后臺圖片,deleteFile為后臺圖片接口
// console.log(fileName);
// ImageApi.deleteFile(fileName).then(response => {
// if(response.code !== 200){
// this.$message.error("刪除后臺圖片失敗??!");
// return;
// }
// this.$message.success("刪除圖片成功??!");
// });
// // 2、調(diào)用splice方法,移除圖片信息
// this.imgUpload.fileList.splice(0,1);
// //3、同時表單置空
// this.form.image = window.sessionStorage.getItem("avatar");
// }
},
poiImgHandleExceed(){
this.$message.warning("超過圖片上傳數(shù)量!");
},
//點(diǎn)擊修改按鈕和新增按鈕時先調(diào)用此刪除進(jìn)行清空圖片列表
resetImgList(){
this.imgUpload.fileList = [];
this.imgUpload.re_fileList = [];
},
cancel() {
// 由于圖片自動上傳,若有已上傳圖片需要先刪除再取消,否則后臺會留存圖片
if(this.imgUpload.fileList.length > 0){
this.$alert('請先移除已上傳圖片', '警告',{
confirmButtonText: "確定",
type: "warning"
})
return;
}
// 對話框關(guān)閉
this.open = false;
// 表單清空
this.reset();
},
reset(){
this.userInfo.username = window.sessionStorage.getItem('username'),
this.userInfo.personalName=window.sessionStorage.getItem('personalName'),
this.userInfo.sex=window.sessionStorage.getItem('sex'),
this.userInfo.email=window.sessionStorage.getItem('email'),
this.userInfo.phone=window.sessionStorage.getItem('phoneNumber')
},
updateUserInfo(){
if(this.fileLists.length!==0){
let file = new FormData();
file.append("file",this.fileLists.raw)
console.log(file);
this.$axios.post("http://localhost:8909/image/avatar",file).then(res => {
if(res.data.code === 200){
this.userInfo.avatars = res.data.data
}
});
}
this.userInfo.avatars = this.form.image
this.$confirm("確定要提交嗎?提交后會返回登錄界面,請謹(jǐn)慎選擇?。?,"提示",{
confirmButtonText: "確定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
UserApi.updateUserInfo(this.userInfo).then(res => {
if(res.code === 200){
this.$message.success("更新成功?。?)
window.sessionStorage.clear()
this.$router.push({path: '/login'})
}
})
})
}
}
}
</script>
<style lang="less" scoped>
.touxiang {
display: flex;
.avatar-uploader {
::v-deep .el-upload {
margin-top: 5px;
height: 45px;
display: flex;
flex-direction: column;
align-content: space-between;
}
::v-deep .el-button {
width: 90px;
height: 35px;
font-size: 15px;
}
}
.pic {
margin-right: 20px;
border-radius: 50%;
border: 1px dashed gray;
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
line-height: 80px;
text-align: center;
}
.avatar {
border-radius: 50%;
width: 80px;
height: 80px;
display: block;
}
}
}
.infoUser{
width: 400px;
height: 380px;
padding-left: 6%;
}
// 文件列表容器
.fileTemp {
margin-bottom: 5px;
background-color: rgb(236, 234, 234);
width: 100px;
}
</style>
? ? ? ? 這個頁面上的函數(shù)比較多,但是頁面布局還是比較簡陋,這個頁面運(yùn)行出來是這樣的
? ? ? ? ?當(dāng)然了,這里只是針對于這個頁面的寫法,你要是真的想把這整個頁面實(shí)現(xiàn)出來,你需要先去創(chuàng)建一個vue項(xiàng)目,至于怎么創(chuàng)建怎么做的,網(wǎng)上有許多的教程,這里就不在進(jìn)行項(xiàng)目創(chuàng)建的教學(xué)了,不懂的話還是得去了解的。這個頁面使用的element-ui,這個東西有個官網(wǎng),你可以直接搜索element-ui,里面有需要樣式的寫法布局等,我也是從這個網(wǎng)站上的一些ui給拼湊出來的。至于前面的ui,姓名、性別、郵箱、手機(jī)等自己在上面找的就好了,也可以的直接復(fù)制我的。個人覺得這個頁面還是簡陋而且一般,非常一般,但是我不太想花費(fèi)更多的時間去弄了,可能我花了時間,但是頁面還是不好看就尷尬了。
? ? ? ? ·這個頁面重點(diǎn)當(dāng)然是頭像的更換啦。就是<el-upload>這個標(biāo)簽,這上面有非常多的函數(shù)調(diào)用,至于每一個的函數(shù),代碼中有的基本上都已經(jīng)有寫上一些的注釋了,比較要注意的還是這個屬性:auto-upload:"false",這個屬性是用于判斷是否自動上傳的,false表示不自動,默認(rèn)是自動上傳的,上傳的地址在屬性action上,這里用#表示上傳,等到提交的時候再進(jìn)行上傳,不然的話,一將圖片加載上去,就上傳到后臺的磁盤上的話,這樣對磁盤的壓力還是太大了,所以必須得先點(diǎn)擊提交之后在進(jìn)行圖片的上傳。
? ? ? ? 除了頭像(圖片)之外,那些信息都是登錄的時候從數(shù)據(jù)庫中獲取的顯示到該頁面上的,因?yàn)榈卿浀捻撁娴呐c這個頁面不是同一個頁面,需要將個人信息存儲在session中,然后再從session中提取出來顯示到這個頁面上,就像data里面的userInfo屬性里面的信息,登錄的時候填入該信息的代碼如下,獲取的頭像信息也是從里面獲取而來的,想要更換的時候就點(diǎn)擊更換頭像。
? ? ? ? ?對于我來說,這個項(xiàng)目已經(jīng)完成了,只是這個上面還有其他的屬性,例如
?:limit = "Number(1)"限制上傳一張;file-list上傳的文件列表;before-upload上傳前的鉤子函數(shù),在上傳前觸發(fā),例如限制大小是5M的話,大于5M是不允許上傳的,其他的各種屬性可以到官網(wǎng)去了解吧,上面的在element-ui官網(wǎng)上應(yīng)該有的。這里就不再做展開了,這篇文章已經(jīng)很長了,也花費(fèi)了我許多的時間去寫。
? ? ? ? 這里要寫的最后的一點(diǎn)就是選擇了圖片之后彈出來的文件列表以及關(guān)閉的樣式,我做的是這個樣子的,由于這里知識上傳的一張圖片,而且圖片是直接顯示在圓形的框內(nèi),所以我把文件列表給刪掉,做了一個關(guān)閉移除圖片的按鈕。如下圖:
? ? ? ? ?點(diǎn)擊移除圖片的按鈕,圖片會被移除,顯示成以前的頭像,這個按鈕也會跟著一起移除,當(dāng)在重新點(diǎn)擊修改頭像的時候,這個移除圖片也會重新的出現(xiàn),不過這個時候點(diǎn)擊修改頭像需要先將當(dāng)前添加上去的新圖片先移除掉才能重新添加。一整個頁面的代碼都已經(jīng)是給出來,就是代碼多而且比較雜,不明白都可以評論或者私聊我。文章來源:http://www.zghlxwxcb.cn/news/detail-759556.html
? ? ? ? 真的,后端可以說是寫的比較詳細(xì)的了,沒講的在代碼上面都有注釋了。對于前端,講的就不那么的詳細(xì)了,除了自己本身擅長后端之外,前端代碼比較多而且比較雜,我怕自己講出來有許多的錯誤,到時弄出許多的笑話就不好了。不過,有錯誤的話,還是很歡迎指正的,也歡迎志同道合的朋友一起討論。原本想寫這篇文章其實(shí)還是比較簡單的一個原因,就是自己也想記錄一下這個功能的開始,或許在很久之后還有用呢。文章來源地址http://www.zghlxwxcb.cn/news/detail-759556.html
到了這里,關(guān)于Spring boot + vue 實(shí)現(xiàn)圖片(頭像)的上傳等操作的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!