store(商城项目)Springboot+springmvc+ajax+mybatis(五)

21. 用户-上传头像-持久层

(a) 规划所需要执行的SQL语句

在持久层处理“上传头像”时,也只是将数据表中的“头像”字段的值进行更新即可,至于值应该是多少,不由持久层来决定,只需要解决“更新头像的值”的功能即可,所以,需要执行的SQL语句大致是:

update t_user set avatar=?, modified_user=?, modified_time=? where uid=?

在执行更新之前,依然检查用户数据是否存在及状态,在持久层并不需要开发新的功能!

(b) 接口与抽象方法

UserMapper接口中添加:

Integer updateAvatarByUid(
    @Param("uid") Integer uid, 
    @Param("avatar") String avatar, 
    @Param("modifiedUser") String modifiedUser, 
    @Param("modifiedTime") Date modifiedTime
);

(c) 配置映射

UserMapper.xml中配置映射:

<!-- 修改某用户的头像 -->
<!-- Integer updateAvatarByUid(
        @Param("uid") Integer uid, 
        @Param("avatar") String avatar, 
        @Param("modifiedUser") String modifiedUser, 
        @Param("modifiedTime") Date modifiedTime) -->
<update id="updateAvatarByUid">
    UPDATE
        t_user
    SET
        avatar=#{avatar},
        modified_user=#{modifiedUser},
        modified_time=#{modifiedTime}
    WHERE
        uid=#{uid}
</update>

UserMapperTests中测试:

@Test
public void updateAvatarByUid() {
    Integer uid = 18;
    String avatar = "jgfbjghgkuygv";
    String modifiedUser = "密码管理员";
    Date modifiedTime = new Date();
    Integer rows = mapper.updateAvatarByUid(uid, avatar, modifiedUser, modifiedTime);
    System.err.println("rows=" + rows);
}

22. 用户-上传头像-业务层

(a) 规划可能出现的异常

在执行更新头像之前,应该检查用户数据是否存在及状态是否正常,可能抛出UserNotFoundException,在执行更新时,可能抛出UpdateException

(b) 业务接口及抽象方法

IUserService接口中添加抽象方法:

/**
 * 修改头像
 * @param uid 用户的id
 * @param avatar 新的头像路径
 * @param username 用户名
 */
void changeAvatar(Integer uid, String avatar, String username);

(c) 实现抽象方法

UserServiceImpl中实现以上抽象方法,实现过程可参考“修改密码”,将其简化即可:

@Override
public void changeAvatar(Integer uid, String avatar, String username) {
    // 基于参数uid调用userMapper的findByUid()查询用户数据
    User result = userMapper.findByUid(uid);
    // 判断查询结果是否为null
    if (result == null) {
        // 是:抛出UserNotFoundException
        throw new UserNotFoundException(
            "修改用户头像失败!用户数据不存在!");
    }

    // 判断查询结果中的isDelete是否为1
    if (result.getIsDelete() == 1) {
        // 是:抛出UserNotFoundException
        throw new UserNotFoundException(
            "修改用户头像失败!用户数据不存在!");
    }

    // 调用userMapper的updateAvatarByUid()执行更新密码,并获取返回的受影响的行数
    Integer rows = userMapper.updateAvatarByUid(uid, avatar, username, new Date());
    // 判断受影响的行数是否不为1
    if (rows != 1) {
        // 是:UpdateException
        throw new UpdateException(
            "修改用户头像失败!执行更新用户头像时出现未知错误!请联系系统管理员!");
    }
}

UserServiceTests中测试:

@Test
public void changeAvatar() {
    try {
        Integer uid = 1800;
        String avatar = "1234";
        String username = "密码管理员";
        service.changeAvatar(uid, avatar, username);
        System.err.println("OK.");
    } catch (ServiceException e) {
        System.err.println(e.getClass().getName());
        System.err.println(e.getMessage());
    }
}

23. 用户-上传头像-控制器层

(a) 处理新创建的异常

此次控制器层需要自行抛出异常!在处理上传的过程中,可能出现相关错误,这些错误都应该使用抛出异常的方式来表示,另外,再在统一处理异常的代码中进行处理!

如果客户端上传的文件为空,则抛出FileEmptyException

如果客户端上传的文件大小超出了限制,则抛出FileSizeException

如果客户端上传的文件类型超出了限制,则抛出FileTypeException

在调用transferTo()方法保存客户端上传的文件时,该方法还会抛出IllegalStateExceptionIOException,但是,并不适合在控制器中将这2种异常直接抛出,交给统一处理异常的代码进行处理,因为后续其它的功能也许也会出现IOException,处理方式可能应该不同!所以,在调用tranferTo()方法时,应该使用try...catch语法捕获这2种异常,并在处理时抛出自定义异常FileUploadStateExceptionFileUploadIOException

另外,还应该创建以上异常类的基类FileUploadException

这些异常应该创建在cn.tedu.store.controller.ex包中,其中,FileUploadException是另5个异常的基类,它应该继承自RuntimeException

所以,现在需要创建6个异常类,其继承结构是:

RuntimeException
    FileUploadException
        FileEmptyException
        FileSizeException
        FileTypeException
        FileUploadStateException
        FileUploadIOException

在处理异常的方法之前,先将方法之前的注解改为:

@ExceptionHandler({ServiceException.class, FileUploadException.class})

即:使得该方法能够处理ServiceExceptionFileUploadException这2大类型的异常!

然后,再在处理异常的方法中,添加更多的else if语句进行处理!

(b) 设计所需要处理的请求

请求路径:
请求参数:
请求方式:
响应结果:JsonResult<?>

(c) 处理请求

关于处理上传的基本代码如下:

@PostMapping("avatar/change")
public JsonResult<String> changeAvatar(
    MultipartFile file, HttpSession session) throws Exception {
    // 日志
    System.err.println("UserController.changeAvatar()");
    // 判断文件是否为空
    boolean isEmpty = file.isEmpty();
    System.err.println("\tisEmpty=" + isEmpty);
    // 获取文件大小
    long size = file.getSize();
    System.err.println("\tsize=" + size);
    // 获取文件的MIME类型
    String contentType = file.getContentType();
    System.err.println("\tcontentType=" + contentType);
    // 获取原始文件名(客户端设备中的文件名)
    String originalFilename 
        = file.getOriginalFilename();
    System.err.println("\toriginalFilename=" + originalFilename);
    // 将文件上传到哪个文件夹
    String parent = session
        .getServletContext().getRealPath("upload");
    System.err.println("\tupload path=" + parent);
    File dir = new File(parent);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    // 保存上传的文件时使用的文件名
    String filename = "" 
            + System.currentTimeMillis() 
            + System.nanoTime();
    String suffix = "";
    int beginIndex = originalFilename
            .lastIndexOf(".");
    if (beginIndex >= 1) {
        suffix = originalFilename
            .substring(beginIndex);
    }
    String child = filename + suffix;
    // 将客户端上传的文件保存到服务器端
    File dest = new File(parent, child);
    file.transferTo(dest);

    // 将保存的文件的路径记录到数据库中
    String avatar = "/upload/" + child;
    System.err.println("\tavatar path=" + avatar);
    Integer uid = getUidFromSession(session);
    String username = getUsernameFromSession(session);
    userService.changeAvatar(uid, avatar, username);
    // 返回
    return new JsonResult<>(OK, avatar);
}

24. 用户-上传头像-前端页面

------------------------------------

附1:关于文件上传

在实现“文件上传”时,如果使用传统的表单提交方式,首先,必须使用<input type="file" />类型的控件,用于客户端浏览并选择所需要上传的文件,并且,在<form>标签中,必须配置method="post"enctype="multipart/form-data"!为了保证表单能提交,还应该在<form>标签中设置action属性,在上传控件中添加name属性,并保证按钮是submit类型的!

所以,在传统方式提交时,上传所需的最小化代码:

<form action="/users/avatar/change" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传" />
</form>

在服务器端,就需要使用控制器接收上传的请求,则在控制器类中添加处理请求的方法:

@PostMapping("avatar/change")
public JsonResult<Void> changeAvatar(MultipartFile file) throws Exception {
    // 将客户端上传的文件保存到服务器端
    File dest = new File("d:/chengheng/1.png");
    file.transferTo(dest);
    return null;
}

在处理上传时,需要注意几个常规问题:

  1. 可以在处理请求的方法中添加HttpServletRequest参数,调用其getServletContext().getRealPath()方法获取某个文件夹的实际路径,该文件夹应该是文件上传到的位置;

  2. 应该自行制定策略,保证上传的文件不会覆盖此前上传的文件;

  3. 调用MultipartFilegetOriginalFilename()可以获取原始文件名,从中得到原始文件的扩展名,用于保存上传的文件时指定扩展名。

SpringBoot框架设置了默认上传文件的限制,如果需要自定义限制值,可以在application.properties中进行配置!根据使用不同的SpringBoot框架的版本,对应的设置值不一样

Spring Boot 1.3.x and earlier

multipart.maxFileSize
multipart.maxRequestSize

Spring Boot 1.4.x and 1.5.x

spring.http.multipart.maxFileSize
spring.http.multipart.maxRequestSize

Spring Boot 2.x

spring.servlet.multipart.maxFileSize
spring.servlet.multipart.maxRequestSize

如果,直接在启动类的声明之前添加@Configuration注解,并在启动类的内部添加方法进行配置:

@Bean
public MultipartConfigElement getMultipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();

    factory.setMaxFileSize(DataSize.ofMegabytes(100));
    factory.setMaxRequestSize(DataSize.ofMegabytes(100));

    return factory.createMultipartConfig();
}

在服务器端的控制器处理上传时,关于MultipartFile接口的API有:

  • String getOriginalFilename():获取客户端上传的文件的原始文件名;

  • boolean isEmpty():判断上传的文件是否为空,如果在表单中没有选择文件,或选择的文件是0字节则视为空,如果为空,则返回true,如果不为空,则为false

  • long getSize():获取文件的大小,以字节为单位;

  • String getContentType():获取文件的MIME类型;

  • InputStream getInputStream():获取文件的输入流,通常用于自定义处理客户端提交的文件的数据,该方法不可以与tranferTo()同时使用;

  • void transferTo(File dest):保存客户端上传的文件,该方法不可以与getInputStream()同时使用;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

饭九钦vlog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值