老实说,一开始觉得这个图片的上传没那么复杂,刚开始自己的构思是前端传来图片文件,后端接收,先将文件存在本地的一个文件里面,然后根据前端传来的图片名称,结合后端所存的图片地址路径,拼凑出一个web访问的url地址,把这个地址存在数据库中,需要回显的时候就从数据库中查询出这个url,结合前端<img></img>标签的src属性将请求后端获取的url地址填上去,就能将图片显示出来。

        起初,是这样子设想,只是落实到代码的时候要考虑的细节就比较多了。现在我只是做的一个头像的更换。这里面的细节还是挺多的。所以在此就记录一下,也方便以后自己查看。

        先看看效果吧,前端写的不太好看,不过也够用了。

        做的是一个个人中心的页面查看修改,包括姓名、邮箱、电话、性别、头像的修改。其他都还好,就是一些String类型的数据交互,重点就在于头像的更换了。实现的效果是点击修改图像弹出选择图片的文件列表,选择完毕之后,左边的头像图片框内会显示选择的图片,右边会出现一个移除图片的按钮,其实这个是文件上传标签<el-upload>文件列表改的一个样式,这个后面会讲。点击移除图片的按钮,之前选择的图片会被移除,重新显示原本的头像,按钮也会消失。当点击提交的时候修改过的姓名、性别、邮箱、电话,会存储在数据库中,头像上传后返回一个url地址,将这个地址存储在数据库中,结合<img>标签就可以显示出头像了。

 

         好吧!本人比较擅长后端,当然了也参考了网上的一些代码,结合自己的理解,暂时就先做成这个样子了,如果有需求还是可以修改。

        话不多说,从后端开始!

        后端的第一件事情就是先设计的数据库中的数据表,当然了,因为这个页面的逻辑没有多复杂,后端逻辑也没有特别麻烦的地方。这里只讲头像上传,和个人信息更新两个方面的,因为这个页面中的数据新增是在别的地方实现的,就不讲了。如果需要了解,也可以私聊我,但是逻辑上也不难处理。

        好了,数据库使用的是mysql,持久层用的mybatis-plus,如果对mysql数据库,和mybatis-plus不太熟悉的,可以先去网上找找资料先学习一下,这里就不讲这些了。先创建一个用户表,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 '账号状态(0正常 1停用)',
	`email` VARCHAR ( 64 ) DEFAULT NULL COMMENT '邮箱',
	`phonenumber` VARCHAR ( 32 ) DEFAULT NULL COMMENT '手机号',
	`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 '创建人的用户id',
	`create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
	`update_by` BIGINT ( 20 ) DEFAULT NULL COMMENT '更新人',
	`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
	`del_flag` INT ( 11 ) DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
PRIMARY KEY ( `id` ) USING BTREE 
) ENGINE = INNODB AUTO_INCREMENT = 8 DEFAULT CHARSET = utf8mb4 ROW_FORMAT = DYNAMIC COMMENT = '用户表';

        都是一些比较普通的字段,没啥特别的。

        表创建好了之后,就创建spring boot项目了。这个如果不了解不会的话,可以看看我上一篇的文章,虽然是写的分页,但是还是比较详细的介绍了spring boot项目的创建,只是将oracle数据库换成mysql就行了。链接地址:http://t.csdn.cn/XrXkq

        接下来就是实体类了,根据表中的字段创建,直接贴代码吧:

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;//账号状态(0正常,1停用)
    private String email;//邮箱
    private String phonenumber;//手机号
    private String sex;//用户性别(0男,1女,2未知)
    private String avatar;//头像
    private String userType;//用户类型(0管理员,1普通用户)
    private Long createBy;//创建人的用户id
    private Date createTime;//创建时间
    private Long updateBy;//更新人
    private Date updateTime;//更新时间
    private Integer delFlag;//删除标志(0代表未删除,1代表已删除)

}

        创建了一个java类User,实现了序列化,其实这里是可以去掉,但是我原本的项目中是需要使用到序列化的,这里也没有影响。就保留了下来,至于那四个注解,前面三个是lombok相关的,不了解的可以去了解一下,可以省下写setter,getter方法,以及无参有参的构造方法,第四个则是mybatis-plus相关的,主要标明对应的是哪个表,value中就写的哪个表。

        创建完实体类,就需要创建相关的controller层,service层,mapper层,我们一层一层解析进去,从controller层开始。这里是前端请求的,后端接收参数的地方。可以先创建一个controller文件包,在包内创建一个PictureController java类,如下代码就是controller层中PictureController类的内容:

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封装类,这个封装类是自己写的,看看代码就懂里面是什么东西了,可以再创建一个result包,将封装类放到里面去:

package com.lhh.managementsystem_h.result;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

/***
 * 响应类,
 * 返回数据使用
 *
 * @author lhh
 */
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> {

    private Integer code;//状态码

    private String message;//提示信息,如果有错误时,前端可以获取该字段进行提示

    private T data;//查询到的结果数据

    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;
    }

}

        重载了三个构造方法,分别是(1)状态码、提示信息;(2)状态码、数据;(3)状态码、提示信息、数据。可以作为返回给前端的封装类,前端拿到了返回信息可以用res.code获取状态码等。

        继续看看uploadImage类,这个是前端传一个图片过来,以二进制文件列表进行接收,然后进行一系列的处理,返回一个url列表。

        红圈中的两句打印看看传过来的参数是什么,其实就只是用来测试,确定没问题了就可以删掉。接下来就是service层了,也就是使用@Resource注入的地方,两个接口调用的方法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列表,用于存在数据库中,或者返回给前端显示图片
     */
    List<String> uploadImage(List<MultipartFile> files);

    /***
     * 根据文件名称删除文件
     * @param fileName 文件名
     * @return
     */
    ResponseResult deleteFile(String fileName);

}

        创建一个名为service的文件包,在service包新建一个java接口AvatarService写了两个抽象方法,代码中都有注释了,应该是比较好理解的。接下来看看他们两个抽象方法的实现类:

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请求域对象
     */
    @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();//获取旧文件名
            //断言,用于调试,如果表达式即oldFileName不为空的话,程序继续执行,否则会抛出异常
            assert oldFileName != null;
            //判断旧文件名中是否有.字符串,如果有的话就从最后一个.截取到最后一个字符,没有.字符就返回取null
            String type = oldFileName.contains(".") ? oldFileName.substring(oldFileName.lastIndexOf(".")) : null;
            //以uuid重新命名避免重复,拼接文件路径,方便前端接收
            String filePath = path + UUID.randomUUID() + type;
            System.out.println(filePath);//打印路径查看
            File filesPath = new File(path);//新建文件操作类
            if (!filesPath.exists()) {//如果是不存在的话
                filesPath.mkdirs();//创建文件路径
            }
            BufferedOutputStream out = null;//字节缓冲流
            try{
                //字节输入流,路径是之前拼凑的路径
                FileOutputStream fileOutputStream = new FileOutputStream(filePath);
                out = new BufferedOutputStream(fileOutputStream);//新建缓冲流对象
                out.write(file.getBytes());//将文件流写入路径之中
                msgs.add(filePath);//将路径增加进列表中并返回
            } catch (FileNotFoundException e) {//文件为空异常抛出
                throw new RuntimeException(e);
            } catch (IOException e) {//运行时异常抛出
                throw new RuntimeException(e);
            }finally {
                if(out != null){
                    out.flush();//把内部缓冲区的数据,刷新到文件中
                    out.close();//释放资源,关闭流
                }
            }
        }

        return msgs;//返回列表
    }

    /***
     * 根据文件路径删除文件
     * @param filePath 文件路径
     * @return boolean类型
     */
    public static boolean deleteFiles(String filePath){
        boolean flag = false;//定义一个boolean变量,设置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/";//图片存放的本地地址
        //调用图片上传方法,也就是upload方法,用于上传图片
        try{
            //传入参数files,path,然后返回新的文件名列表
            imagePath = AvatarServiceImpl.upload(files,path);
            System.out.println("图片上传路径:"+imagePath);//打印图片看看
            for(String img : imagePath){//遍历图片路径
                //按‘/’截取
                String[] split = img.split("/");
                //取出数组中的最后一个元素就是文件名,数组下标从0开始,最后一个就数组长度-1
                String fileName = split[split.length - 1];
                //开始拼凑基础的url路径,request.getScheme=http使用的协议名;
                //request.getServerName() = localhost 服务器名称;
                //request.getServerPort() = 8909,端口号
                //request.getContextPath() = 返回请求中对应context的部分,如果此context是默认context,则返回”“。
                String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath();
                String httpUrl = basePath + "/upload/" + fileName;//这里的"/upload/"是配置的虚拟路径,拼凑出一个可以访问的url
                System.out.println("完整的url路径:"+httpUrl);//打印出来看看,点击一下是否能访问
                imageUrls.add(httpUrl);//将完整的url路径添加进列表中

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if(imagePath.get(0).equals("file_empty")){//如果imagePath列表里面的第一个元素是"file_empty"
            return null;//标识列表是空,那就返回空
        }
        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;//如果文件类型不为空,则继续运行下去,否则抛出异常
        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);//调用删除文件方法删除文件
        if(b){//如果是返回的是真的,即b=ture
            return new ResponseResult(200,"删除成功!!");
        }else {//否则
            return new ResponseResult(302,"删除失败!!");
        }
    }
}

        这里面有四个方法,两个是静态方法,两个是抽象方法的实现;上面都有代码的注释了,如果还是觉得不够清晰的也可以私聊一下我。当然了,这里面的4个方法中,图片的上传的方法是uploadImage,这个方法是返回的一个可供访问的url地址,同样是拼凑出来的,这上面的/upload/的虚拟路径,是在CorsConfig类中实现,该类需要实现一个WegMvcConfigurer类,是用于配置跨域和虚拟路径的方法,跨域如果不清楚的话,就自己找找资料了解一下吧,主要是如果这个各个方面都比较详细的话,那么这篇文章就十分的冗长,而且主题也并没有抓住重点了。好了,先贴下代码看看是如何配置这个虚拟路径的。

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){
        //设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                //是否允许cookie
                .allowCredentials(true)
                //设置允许的请求方式
                .allowedMethods("GET","POST","DELETE","PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry){
        //  /upload/**为前端URL访问路径  后面 file:xxxx为本地磁盘映射
        registry.addResourceHandler("/upload/**") //虚拟url路径
                .addResourceLocations("file:D:/xiangmu/xtgl/image/"); //真实本地路径
    }

}

        虚拟路径的配置是在addResourceHandlers方法中,是用虚拟的url路径代替了本地磁盘的路径,最终可以构建的虚拟的url地址为http://loaclhost:8909/upload/**,该路径可用于存于数据库中,用于前端查询出来回显。

        我们在上传了图片之后,拿到了url的地址,那么就可以和前面修改过的个人信息,如姓名、性别、邮箱、手机号等信息一起提交了后端了。交给后端存储在数据库中。也就是在前面的截图中点击了提交之后,会进行一个接口请求,调用接口存储。代码如下:

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,用于编写用户相关的接口,这里只写了一个用户信息的更新接口UserController,接着业务逻辑是在UserService层的updateUserInfo方法中,这里只是用于接收前端提交的用户数据,真正处理更新业务的还是得在Service层,其实有人喜欢在Controller写业务逻辑,我其实不太能理解,主要是看到了Controller上一堆的代码,看着还是不是那么舒服的,当然了我只是不喜欢在Controller写业务,并不是说不能在Controller上写,每个人都有自己的习惯,以及审美,在Controller写也并不是不可以,就是当我读到你写在Controller层的代码的时候,我可能会崩溃了。

        好像有点扯远了,下面来看看Service层的代码如何处理存的业务逻辑。

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包下创建一个UserService接口,用于扩展用户相关的业务逻辑。接着在该接口写一个updateUserInfo抽象方法,接口写好了,就看看如何去实现这个接口。哦,对了,我忘了这个传递的userInfoDto对象了,这是一个数据传输对象,用于前端和接口数据传输,这是一种设计模式,如果你还是不太了解Dto、Vo的用法和用处,还是得去认真学习一下,这些基本的东西如果不够熟悉掌握的话,你代码的开发就会非常的困难,很多bug你是无法准确定位在哪里的,而我的文章只是针对与一个功能的开发,对于基础的java语法用法等不会涉及太多,还是得自己系统的去学习才行。

        好像又扯远了,看看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;//停用标志

    private String personalName;//姓名

    private String userName;//工号

    private List<Long> roleId;//角色id列表

    private Long userId;//创建用户id

    private String avatar;//创建用户头像

    private String email;//邮箱

    private String sex;//性别

    private String phoneNumber;//手机号

    private String[] avatars;//头像图片列表


}

        与实体类类似,但是属性仅仅是你需要的数据而已,多余的全部都不要展示出来,这样很大程度上还是能避免一些安全问题的。

        来吧,看看是如何实现那个抽象方法的。

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());//设置id
        user.setPersonalName(userInfoDto.getPersonalName());//设置姓名
        user.setEmail(userInfoDto.getEmail());//设置邮箱
        user.setAvatar(userInfoDto.getAvatars()[0]);//设置头像
        user.setSex(userInfoDto.getSex());//设置性别
        user.setPhonenumber(userInfoDto.getPhoneNumber());//设置电话
        user.setUpdateTime(new Date());//设置修改时间
        user.setUpdateBy(userInfoDto.getId());//设置更新人
        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])){//头像也不为空的话,得删除该头像对应的图片
                String imageName = StringUtils.substringAfterLast(user1.getAvatar(), "/");
                //截取文件类型,也就是图片类型.png;.jpeg;.jpg格式的。判断是否有.字符串,有的按最后一个.字符串截取字符
                String type = imageName.contains(".") ? imageName.substring(imageName.lastIndexOf(".")) : null;
                String filePath;//定义文件路径
                assert type != null;//如果文件类型不为空,则继续运行下去,否则抛出异常
                if(type.equals(".png") || type.equals(".jpeg") || type.equals(".jpg")) {//判断文件类型
                    filePath = "D:/xiangmu/xtgl/image/" + imageName;//文件路径则是之前存图片的地方,加上文件名拼凑出完整的文件路径
                    AvatarServiceImpl.deleteFiles(filePath);//调用删除文件方法删除文件
                }
            }
            userMapper.updateUserInfo(user);//更新用户信息
            return new ResponseResult(200,"更新成功!!");

        }else {
            return new ResponseResult(300,"该用户不存在!!");
        }
    }
}

        先新建一个实体类User,然后把UserInfoDto上需要存储的数据全部设置到User类上,这里面比较重要的一点就是,根据前端传过来的id来匹配和构建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);//就像是你调用自己写在mapper中的方法那样,只不过就是现在这个selectOne方法是mybaits-plus自带的,其实这条语句的就是相当于select * from sys_user where id = 1;然后将查询出来的那个结果的用user1给封装起来罢了。

        其实查询这条对应记录的原因除了需要判断这个对应的id是否能够查询出来记录,查询出来的不为空,而且该记录Avatar(头像url数据)也不为空,还有就是传进来的头像url与原来的数据库中记录的不一样的时候,这个时候就要先将原先存储的头像给删除掉,不然一直更新头像,却不删除本地的图片的话,可能本地或者服务器的磁盘会被撑爆的,所以还是得进行删除才行,只允许一个用户只有一个头像存储在本地或者服务器上。

        把与原来不一样的图片的删除了之后,才进行数据库的存储。就是把前端提交的数据更新到数据库里面,先是根据id查询出该用户的数据,如果不是空的才进行的操作(一般情况下查询出来的应该是不会空,因为在进到个人的页面之前是要进行的登录操作的,而个人主页的信息一般是从登录的数据表里面获取到的)。

        写到这里,后端的内容基本上都已经的写完了。其实如果是要进行一个比较大项目的开发,这一点的内容就是沙漠中的一粒沙子,市面上能够上线的APP也好,网页端也好,小程序端也好,都是有比较多的数据表,许多人夜以继日的研发、测试而完成的,一个人开发,需要的时间很长,需要克服的困难也很多。所以在日常的开发中,需要不断的记录,不断积累经验才能比较快速的定位到你程序中存在的问题,也就是我们常说的修复bug。

        个人比较擅长后端,前端对于我来说比较难受的一点就需要不断的调整样式,有时候为了美观,调整了一个布局又弄乱了另外的布局,可能是我对于前端孤陋寡闻了,对于我来说,能够实现功能,而且页面布局不是那么丑陋,有许多的瑕疵我也只能忍受,我似乎并不是很想花费许多的时间在页面样式的修改上,但是一个前端开发的大佬,一定是对于样式的修改布局等有自己的主见看法,是肯为了美观花费上比较长的时间区调整样式的,也会有自己样式布局的积累,有用得上的能够很快得拎出来使用。

        我一个前端菜鸡,能够进行普通的页面开发,就已经算可以的了。只是,如果想要做全栈开发还是前后端的开发都要快速的了解,新的技术得不断的学习,别让自己落后在技术领域的方面。不过好像有点扯远了。下面进行前端页面的开发,先贴个代码吧,个人中心的前端页面代码

<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>手机:<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: {
    //文件上传前,触发函数
    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;
      //文件格式校验
      if(!isJPG){
        this.$message.error('只能上传jpg/jpeg/png图片!!');
        return false;
      }
      //文件大小校验
      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数组中
      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、调用splice方法,移除图片信息
      this.imgUpload.fileList.splice(0,1);
      //3、同时表单置空
      this.form.image = window.sessionStorage.getItem("avatar");
      // 由于上传前校验钩子return false时会调用文件删除钩子,删除前先做判断,若上传成功才可删除
      // 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、调用splice方法,移除图片信息
      //   this.imgUpload.fileList.splice(0,1);
      //   //3、同时表单置空
      //   this.form.image = window.sessionStorage.getItem("avatar");
      // }
    },
    poiImgHandleExceed(){
      this.$message.warning("超过图片上传数量!");
    },
    //点击修改按钮和新增按钮时先调用此删除进行清空图片列表
    resetImgList(){
      this.imgUpload.fileList = [];
      this.imgUpload.re_fileList = [];
    },
    cancel() {
      // 由于图片自动上传,若有已上传图片需要先删除再取消,否则后台会留存图片
      if(this.imgUpload.fileList.length > 0){
        this.$alert('请先移除已上传图片', '警告',{
          confirmButtonText: "确定",
          type: "warning"
        })
        return;
      }
      // 对话框关闭
      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("确定要提交吗?提交后会返回登录界面,请谨慎选择!!","提示",{
        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>

        这个页面上的函数比较多,但是页面布局还是比较简陋,这个页面运行出来是这样的

         当然了,这里只是针对于这个页面的写法,你要是真的想把这整个页面实现出来,你需要先去创建一个vue项目,至于怎么创建怎么做的,网上有许多的教程,这里就不在进行项目创建的教学了,不懂的话还是得去了解的。这个页面使用的element-ui,这个东西有个官网,你可以直接搜索element-ui,里面有需要样式的写法布局等,我也是从这个网站上的一些ui给拼凑出来的。至于前面的ui,姓名、性别、邮箱、手机等自己在上面找的就好了,也可以的直接复制我的。个人觉得这个页面还是简陋而且一般,非常一般,但是我不太想花费更多的时间去弄了,可能我花了时间,但是页面还是不好看就尴尬了。

        ·这个页面重点当然是头像的更换啦。就是<el-upload>这个标签,这上面有非常多的函数调用,至于每一个的函数,代码中有的基本上都已经有写上一些的注释了,比较要注意的还是这个属性:auto-upload:"false",这个属性是用于判断是否自动上传的,false表示不自动,默认是自动上传的,上传的地址在属性action上,这里用#表示上传,等到提交的时候再进行上传,不然的话,一将图片加载上去,就上传到后台的磁盘上的话,这样对磁盘的压力还是太大了,所以必须得先点击提交之后在进行图片的上传。

        除了头像(图片)之外,那些信息都是登录的时候从数据库中获取的显示到该页面上的,因为登录的页面的与这个页面不是同一个页面,需要将个人信息存储在session中,然后再从session中提取出来显示到这个页面上,就像data里面的userInfo属性里面的信息,登录的时候填入该信息的代码如下,获取的头像信息也是从里面获取而来的,想要更换的时候就点击更换头像。

         对于我来说,这个项目已经完成了,只是这个上面还有其他的属性,例如

 :limit = "Number(1)"限制上传一张;file-list上传的文件列表;before-upload上传前的钩子函数,在上传前触发,例如限制大小是5M的话,大于5M是不允许上传的,其他的各种属性可以到官网去了解吧,上面的在element-ui官网上应该有的。这里就不再做展开了,这篇文章已经很长了,也花费了我许多的时间去写。

        这里要写的最后的一点就是选择了图片之后弹出来的文件列表以及关闭的样式,我做的是这个样子的,由于这里知识上传的一张图片,而且图片是直接显示在圆形的框内,所以我把文件列表给删掉,做了一个关闭移除图片的按钮。如下图:

         点击移除图片的按钮,图片会被移除,显示成以前的头像,这个按钮也会跟着一起移除,当在重新点击修改头像的时候,这个移除图片也会重新的出现,不过这个时候点击修改头像需要先将当前添加上去的新图片先移除掉才能重新添加。一整个页面的代码都已经是给出来,就是代码多而且比较杂,不明白都可以评论或者私聊我。

        真的,后端可以说是写的比较详细的了,没讲的在代码上面都有注释了。对于前端,讲的就不那么的详细了,除了自己本身擅长后端之外,前端代码比较多而且比较杂,我怕自己讲出来有许多的错误,到时弄出许多的笑话就不好了。不过,有错误的话,还是很欢迎指正的,也欢迎志同道合的朋友一起讨论。原本想写这篇文章其实还是比较简单的一个原因,就是自己也想记录一下这个功能的开始,或许在很久之后还有用呢。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐