从简到繁——SSM个人博客搭建完全记录【5】后台管理系统的后端开发之二(富文本编辑器的图片处理)

前言

  在开发完前台页面之后,一个简单的博客系统就基本开发完成了。尚未完成的部分主要还有三个:富文本编辑器的图片处理、登录、全文检索。还有阅读量的显示等细节部分,比较容易实现。本节完成富文本编辑器的图片处理。

Java操作阿里云oss

  富文本编辑器的图片处理包括两个部分:上传和修改。先来完成图片上传。我没有直接把图片保存在服务器里面,而是选择了阿里云的oss存储服务:对象存储 OSS,价格还是比较低的。首先在maven需要引入oss相关的jar包:



    com.aliyun.oss
    aliyun-sdk-oss
    2.7.0

参考他人代码和api的oss工具类:

package com.vansl.utils;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.*;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import java.io.*;
import java.util.Calendar;
import java.util.List;
import java.util.Properties;
import java.util.Random;

/**
 * @author: vansl
 * @create: 18-5-10 下午4:09
 */
public class AliyunOSSUtil {

    static Logger logger = LogManager.getLogger(AliyunOSSUtil.class);

    private static String END_POINT;            //访问域名
    private static String ACCESS_KEY_ID;        //秘钥id
    private static String ACCESS_KEY_SECRET;    //秘钥key

    static{
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Properties properties = new Properties();
        try(InputStream in =loader.getResourceAsStream("config/aliyunOSS.properties");){
            properties.load(in);
            END_POINT= properties.getProperty("END_POINT");                //访问域名
            ACCESS_KEY_ID=properties.getProperty("ACCESS_KEY_ID");         //秘钥id
            ACCESS_KEY_SECRET=properties.getProperty("ACCESS_KEY_SECRET"); //秘钥key
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获得oss连接对象
     * @return oss连接对象
     */
    private static OSSClient initOssClient(){
        return new OSSClient(END_POINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
    }

    /**
     * 创建bucket并赋予权限
     *
     * @param bucketName buket名
     * @param authc      访问权限(1,2,3)(私有,公共读,公共读写)
     */
    public static void createBucket(String bucketName, int authc) {
        //创建OSS客户端
        OSSClient ossClient = initOssClient();

        CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
        switch (authc) {
            case 1:
                createBucketRequest.setCannedACL(CannedAccessControlList.Private);
                break;
            case 2:
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
                break;
            case 3:
                createBucketRequest.setCannedACL(CannedAccessControlList.PublicReadWrite);
                break;
            default:
                createBucketRequest.setCannedACL(CannedAccessControlList.Private);
                break;
        }
        ossClient.createBucket(createBucketRequest);
        // 关闭OSS连接
        ossClient.shutdown();
    }

    /**
     * 删除bucket
     *
     * @param bucketName bucket名
     */
    public void deleteBucket( String bucketName) {
        //创建OSS客户端
        OSSClient ossClient = initOssClient();

        ossClient.deleteBucket(bucketName);
        // 关闭OSS连接
        ossClient.shutdown();
    }

    /**
     * 创建文件夹
     *
     * @param bucketName bucket名
     * @param folder     模拟文件夹名
     */
    public static void createFolder(String bucketName, String folder) {
        //创建OSS客户端
        OSSClient ossClient = initOssClient();
        //上传空文件以创建目录
        ossClient.putObject(bucketName, folder+"/", new ByteArrayInputStream(new byte[0]));
        OSSObject object = ossClient.getObject(bucketName, folder);
        String fileDir = object.getKey();
        logger.info("文件夹创建成功:"+fileDir);
        // 关闭OSS连接
        ossClient.shutdown();
    }

    /**
     * 删除文件夹
     *
     * @param bucketName bucket名
     * @param folder     模拟文件夹名
     */
    public static void deleteFolder(String bucketName, String folder) {
        //创建OSS客户端
        OSSClient ossClient = initOssClient();
        //需要递归删除子文件夹
        ObjectListing objectListing = ossClient.listObjects(bucketName, folder);
        List sums = objectListing.getObjectSummaries();
        for (OSSObjectSummary s : sums) {
            if(!s.getKey().equals(folder)){
                deleteFolder(bucketName,s.getKey());
            }
        }
        ossClient.deleteObject(bucketName, folder);
        logger.info("删除文件夹成功:"+ folder);
        // 关闭OSS连接
        ossClient.shutdown();
    }

    /**
     * 上传文件
     *
     * @param file 文件对象
     * @param bucketName bucket名
     * @param folder  模拟文件夹名
     * @return 文件访问url
     */
    public static String upload(File file, String bucketName, String folder) {
        if (file == null) {
            return null;
        }
        //创建OSS客户端
        OSSClient ossClient = initOssClient();

        try(
            //文件流
            InputStream inputStream = new FileInputStream(file);
         ){
            //文件名
            String fileName = file.getName();
            //文件的后缀名
            String fileExtension = fileName.substring(fileName.lastIndexOf("."));
            //文件大小
            Long fileSize = file.length();
            //创建上传文件的Metadata
            ObjectMetadata metadata = new ObjectMetadata();
            //上传文件的长度
            metadata.setContentLength(inputStream.available());
            //指定该object被下载时的网页的缓存行为
            metadata.setCacheControl("no-cache");
            //指定该object下设置Header
            metadata.setHeader("Pragma","no-cache");
            //指定该object被下载时的内容编码方式
            metadata.setContentEncoding("utf-8");
            //文件的MIME,定义文件的类型及网页编码,默认值application/octet-stream
            metadata.setContentType(getContentType(fileExtension));
            //指定该Object被下载时的名称
            metadata.setContentDisposition("filename/filesize=" + fileName + "/" + fileSize + "Byte.");
            // 生成新的文件名
            String newFilename=generateFilename(fileExtension);
            //上传文件   (上传文件流的形式)
            ossClient.putObject(bucketName, folder+"/"+newFilename, inputStream, metadata);

            String fileUrl = "https://"+bucketName+"."+END_POINT+"/"+folder+"/"+newFilename;
            return fileUrl;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 关闭OSS连接
            ossClient.shutdown();
        }
        return null;
    }

    /**
     * 生成随机文件名
     *
     * @param fileExtension 文件后缀
     * @return 生成的文件名
     */
    public static String generateFilename(String fileExtension){
        String randomFilename = "";

        //生成五位随机数
        Random random = new Random();
        int randomNum = (int)(random.nextDouble()*90000)+10000;

        //返回时间戳+随机数作为文件名
        randomFilename = String.valueOf(System.currentTimeMillis())+randomNum+fileExtension;

        return randomFilename;
    }

    /**
     * 通过文件名判断并获取文件的contentType
     *
     * @param fileExtension 文件后缀
     * @return 文件contentType
     */
    public static  String getContentType(String fileExtension){
        if(".bmp".equalsIgnoreCase(fileExtension)) {
            return "image/bmp";
        }
        if(".gif".equalsIgnoreCase(fileExtension)) {
            return "image/gif";
        }
        if(".jpeg".equalsIgnoreCase(fileExtension) || ".jpg".equalsIgnoreCase(fileExtension)  || ".png".equalsIgnoreCase(fileExtension) ) {
            return "image/jpeg";
        }
        if(".html".equalsIgnoreCase(fileExtension)) {
            return "text/html";
        }
        if(".txt".equalsIgnoreCase(fileExtension)) {
            return "text/plain";
        }
        if(".vsd".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.visio";
        }
        if(".ppt".equalsIgnoreCase(fileExtension) || "pptx".equalsIgnoreCase(fileExtension)) {
            return "application/vnd.ms-powerpoint";
        }
        if(".doc".equalsIgnoreCase(fileExtension) || "docx".equalsIgnoreCase(fileExtension)) {
            return "application/msword";
        }
        if(".xml".equalsIgnoreCase(fileExtension)) {
            return "text/xml";
        }
        //默认返回类型
        return "image/jpeg";
    }

    /**
     * 删除OSS文件
     *
     * @param bucketName bucket名
     * @param folder    模拟文件夹名
     * @param fileName  文件名(可以包含路径)
     */
    public static void deleteFile(String bucketName,String folder,String fileName){
        //创建OSS客户端
        OSSClient ossClient = initOssClient();

        ossClient.deleteObject(bucketName,folder+"/"+fileName);
        logger.debug("删除"+bucketName+"下的文件:"+folder+"/"+fileName+"成功!");
        // 关闭OSS连接
        ossClient.shutdown();
    }

}
阿里云域名和秘钥配置文件格式如下,放在config文件夹下:
#阿里云OSS配置
#访问域名
END_POINT=
#秘钥id
ACCESS_KEY_ID=
#秘钥key
ACCESS_KEY_SECRET=

代码测试成功,上传、访问、删除都没有问题。

图片上传

然后是负责图片上传处理的Controller。在这之前我修改了图片上传的请求路径,在富文本编辑器页面插入如下代码:

//修改请求路径
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function(action) {
    if (action == 'uploadimage' ){
        return '/ued/image';
    } else if(action == 'uploadscrawl') {
        return '/ued/scrawl';
    } else if(action == 'uploadvideo'){
        return '/ued/video';
    }else {
        return this._bkGetActionUrl.call(this, action);
    }
}

同时修改上传时的请求参数以判断用户、文章等信息以确定放在哪个文件夹之下:

//自定义上传请求参数
ue.execCommand('serverparam', {
    'userId':1,
    //'blogId': window.editBlogInfo.blogId
});

  但在新建博客时由于没有blogId信息,所以无法确定放在哪个文件夹里。暂时没有想到比较好的解决办法,所以我干脆把同一用户的所有图片都放在一个文件夹下面了。Controller类代码:

package com.vansl.controller;

import com.baidu.ueditor.ActionEnter;
import com.vansl.utils.AliyunOSSUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: vansl
 * @create: 18-4-10 下午10:23
 */

@Controller
@RequestMapping("/ued")
public class UeditorController {

    @GetMapping(value="")
    public void config(HttpServletRequest request, HttpServletResponse response) {
        response.setContentType("application/json");
        String rootPath = request.getSession().getServletContext().getRealPath("/");

        try {
            String exec = new ActionEnter(request, rootPath).exec();
            PrintWriter writer = response.getWriter();
            writer.write(exec);
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @PostMapping("/image")
    @ResponseBody
    public Map upload(String userId,/*String blogId,*/MultipartFile upfile) throws IOException {
        //返回上传结果(url等)
        Map result = new HashMap<>();

        try {
            if(upfile!=null&&upfile.getOriginalFilename()!=""){
                //获取文件名
                String fileName=upfile.getOriginalFilename();
                //上传到阿里云oss
                String url=AliyunOSSUtil.upload(upfile.getInputStream(),fileName,"vanslblog",userId);
                //返回上传结果
                result.put("state","SUCCESS");
                result.put("url",url);
            }
        }catch (Exception e){
            result.put("state","FAILED");
            e.printStackTrace();
        }finally {
            return result;
        }
    }
}

  测试成功,图片上传和回显都没有问题。

图片修改

  然后是图片变化的监测,包括删除图片和直接删除文章。思路是:

  1. 正则提取文章中的图片路径;
  2. 和原文章进行对比筛选要删除的图片;
  3. 调用ossUtil删除服务器上的图片。

  需要说明的是,当同一文章下的图片保存为一个文件夹时,删除文章的操作原本是不需要对比直接删除整个文件夹就可以了,但如前面所说,考虑到当新建文章时上传图片无法确定上传到哪个文件夹下,所以干脆就把所有图片都放到一个文件夹下了。另外,当用户新建文章时上传了图片却没有上传文章时,上传的图片就会浪费存储空间,但由于难以判断用户未上传文章,所以在这种情况下没有去删除这些图片。这些实现应该都不是最优解,但暂时没有想到其他较好的办法。

  我使用了一个Spring拦截器来进行处理图片变化,需要拦截updateBlog、deleteBlog两个方法。

  由于ServletRequest的请求数据流被设置成只能被读取一次,而Spring拦截器做不到传递对象,所以还需要加一个filter把request对象用HttpServletRequestWrapper包装起来,使请求数据能被重复读取。这个filter的实现参考这篇文章即可:ervletRequest中getReader()和getInputStream()只能调用一次的解决办法。

  拦截器HandlerInterceptor的代码如下:

package com.vansl.interceptor;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.vansl.dao.BlogDao;
import com.vansl.utils.AliyunOSSUtil;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author: vansl
 * @create: 18-5-15 下午5:54
 *
 * 处理博客更新时的图片变化
 */
public class BlogImgInterceptor implements HandlerInterceptor {

    @Autowired
    BlogDao blogDao;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {

        //拦截updateBlog、deleteBlog两个请求
        if(request.getMethod().equals("PUT")||request.getMethod().equals("DELETE")){
            //从请求url中提取博客id
            String uri=request.getRequestURI();
            Integer blogId=Integer.valueOf(uri.substring(uri.lastIndexOf("/")+1));
            //将请求体的json字符串转换为json对象
            JSONObject requestData=JSON.parseObject(IOUtils.toString(request.getReader()));
            //从请求体中提取userId
            String userId=String.valueOf(requestData.get("userId"));
            //原博客内容
            String origin=blogDao.selectContentByBlogId(blogId);
            //提取原博客中的图片url
            HashSet originUrl=getImageUrl(origin,"");

            //如果是删除操作则直接删除所有图片
            if(request.getMethod().equals("DELETE")){
                //遍历原博客的所有图片url
                for (String imgUrl:originUrl) {
                    String fileName=imgUrl.substring(imgUrl.lastIndexOf("/")+1);
                    AliyunOSSUtil.deleteFile("vanslblog",userId,fileName);
                }
            //否则如果是更新操作,对比之后删除
            }else{
                //提取现博客中的图片url
                HashSet currentUrl=getImageUrl((String)requestData.get("content"),"");
                //遍历原博客的所有图片url
                for (String imgUrl:originUrl) {
                    //如果现博客不含该url则调用删除
                    if (!currentUrl.contains(imgUrl)){
                        System.out.println(imgUrl.substring(imgUrl.lastIndexOf("/")+1)  );
                        String fileName=imgUrl.substring(imgUrl.lastIndexOf("/")+1);
                        AliyunOSSUtil.deleteFile("vanslblog",userId,fileName);
                    }
                }
            }
        }
        return true;
    }

    public HashSet getImageUrl(String content,String regex){
        HashSet result=new HashSet<>();
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(content);
        while (matcher.find()) {
            result.add(matcher.group(1));
        }
        return  result;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
    }

}

你可能感兴趣的:(SSM)