微信小程序基于vant和springboot实现附件上传和预览

前言

图片上传和预览在移动端应用非常广泛和频繁,vant组件库van-uploader组件已经帮我们实现了大部分功能,但是在系统中频繁使用还是有点麻烦,我们根据自身的业务系统重新封装了一下简化我们的开发。后端使用springboot集成jcifs实现文件管理微服务。

附件上传

附件预览

前端组件

组件介绍

前端只要在视图中使用组件,传入需要的参数即可

businessid业务id,用于把业务单号和附件关联
tmp_id临时业务id,在一开始业务单号未产生的时候和附件关联
name用于把不通类型的附件归类到不同的文件夹
businesstype用于区分同一个业务单号下不通类型的附件组
readonly判断组件是预览还是上传
<bdog-uploader-image      
	id="visit_uploader"
	businessid="{{id}}"
	tmp_id="{{tmp_id}}"
	name="customerVisit"
	businesstype="visit"
    readonly ="{{readonly}}"
/>  

组件js部分代码

const util = require('../../utils/util.js')
var request = require('../../utils/request.js')
var { config } = require('../../utils/config.js')
import Toast from '@vant/weapp/toast/toast';
const app = getApp();

Component({
  properties: {
    businessid: {
      type: String
    },
    tmp_id: {
        type: String
    },
    name: {
        type: String
    },
    businesstype: {
        type: String
    },
    readonly:{
        type:Boolean
    }
  },
  data: {
    fileList: [],
  },
  attached:function(){
    //this.getFileList()
  },
  methods: {
     afterRead(event) {
        Toast.loading({
            duration: 0, // 持续展示 toast
            forbidClick: true,
            message: "上传中"
        })
        var that = this
        const { file } = event.detail
        wx.uploadFile({
          url: config.baseUrl +'/MpAttachment/uploadFile', 
          filePath: file.url,
          name: 'file',
          header: {
            "Content-Type": "multipart/form-data",
            "id":that.data.businessid,
            "tmpId":that.data.tmp_id,
            "name":that.data.name,
            "businesstype":that.data.businesstype,
            "token":app.globalData.userInfo.token,
          },
          success(res) {
            const data = JSON.parse(res.data)
            if(data.code == 200){
                // 上传完成需要更新 fileList
                const { fileList = [] } = that.data;
                const url = config.baseUrl +'/MpAttachment/getImage?id=' + data.data.id
                fileList.push({ ...file, url: url, id: data.data.id })
                that.setData({ fileList })
                Toast.clear();
                Toast({ type: 'success',message: '上传成功',duration:500, })
            }else{
                Toast({ type: 'fail',message: '上传失败',duration:500, })
            }
          },
          fail:function(res){
            Toast({ type: 'fail',message: '上传失败',duration:500, })
          }
        });
      },
      delete(event) {
        Toast.loading({
            duration: 0, // 持续展示 toast
            forbidClick: true,
            message: "删除中"
        })
        var that = this
        var data = {}
        data['id'] = event.detail.file.id
        request.get('/MpAttachment/delete',data)
        .then(function (res) {
          if(res.code == 200){
            const { fileList } = that.data;
            const newFileList = fileList.filter((items) =>{
              return items.id != event.detail.file.id
            })
            that.setData({ fileList : newFileList, })
            Toast.clear();
            Toast({ type: 'success',message: '删除成功',duration:500, })
          }else{
            Toast({ type: 'fail',message: '删除失败',duration:500, })
          }
        }, function (error) {
            Toast({ type: 'fail',message: '删除失败',duration:500, })
        })
      },
      getFileList() {
        var that = this
        var data = {}
        data['businessid'] = that.data.businessid
        data['businesstype'] = that.data.businesstype
        request.get('/MpAttachment/getList',data)
        .then(function (res) {
          if(res.code == 200){
            const fileList = res.data;
            fileList.forEach(function(items){
                items.url = config.baseUrl + '/MpAttachment/getImage?id=' + items.id
                items.type = 'image'
            })
            that.setData({ fileList : fileList, })
          }else{
            Toast({ type: 'fail',message: '附件加载失败',duration:500, })
          }
        }, function (error) {
            Toast({ type: 'fail',message: '附件加载失败',duration:500, })
        })
      }
  }

})

组件视图部分代码

<van-cell title="" >
    <van-uploader
        slot="right-icon"
        file-list="{{ fileList }}"
        max-count="9"
        bind:after-read="afterRead"
        bind:delete="delete"  
        show-upload="{{ !readonly }}"
        deletable="{{ !readonly }}"
    />
</van-cell>
<van-toast id="van-toast" />

后端微服务

后端微服务

微服务总归包含了附件上传、删除、获取图片、获取列表、附件上传个服务

​​​​​​​

 

 微服务代码

@RestController
@RequestMapping("/MpAttachment")
@Api(tags = { Swagger2Config.TAG_MpAttachment })
public class MpAttachmentController implements ServletContextAware {

    protected HttpServletRequest request;
    protected HttpServletResponse response;
    protected HttpSession session;
    protected ServletContext servletContext;
    String FileConnect ="/";
    @Autowired
    protected UserService userService;
    @Autowired
    @Qualifier("dispJdbcTemplate")
    protected JdbcTemplate dispJdbcTemplate;
    @Autowired
    protected MpAttachmentService mpAttachmentService;


    @ApiOperation(value = "获取列表", notes = "")
    @GetMapping(value="/getList")
    public Result getList(@ApiParam(value = "businessid" , required=true ) @RequestParam String businessid,
                          @ApiParam(value = "businesstype" , required=false ) @RequestParam String businesstype) throws ParseException {
        List list =  mpAttachmentService.getViewList(businessid,businesstype);
        return Result.success(list,"成功!");
    }

    @CrossOrigin
    @ApiOperation(value = "附件上传", notes = "")
    @PostMapping("/uploadFile")
    public Result uploadFile(@RequestParam("file") MultipartFile file, @RequestHeader("name") String name, @RequestHeader String id, @RequestHeader String tmpId, @RequestHeader String businesstype) {
        if (file.isEmpty()) {
            return Result.failed("上传文件为空");
        }
        String uuid = UUID.randomUUID().toString();
        // 获取文件名
        String fileName = file.getOriginalFilename();
        String newFileName = uuid + "."+ fileName.split("\\.")[1];
        MpAttachment attachment = new MpAttachment();
        attachment.setBusinessid(id);
        attachment.setTmp_businessid(tmpId);
        attachment.setBusinesstype(businesstype);
        attachment.setFilename(fileName);
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyMMdd");
        String uploadPath = name + FileConnect + LocalDate.now().format(fmt);
        attachment.setFilepath(uploadPath + FileConnect + newFileName);

        try {
            //文件上传
            SmbFileUtils.save(file.getBytes(),uploadPath,newFileName);
            attachment.setCreatetime(DateUtils.getNow());
            attachment.setId(UUID.randomUUID().toString());
            mpAttachmentService.add(attachment);
            return Result.success(mpAttachmentService.getView(attachment.getId()),"成功!");
        } catch (IOException e) {
            e.printStackTrace();
            return Result.failed("文件上传失败");
        }
    }

    @CrossOrigin
    @ApiOperation(value = "附件上传并添加水印", notes = "")
    @PostMapping("/uploadImageFile")
    public Result uploadImageFile(@RequestParam("file") MultipartFile file, @RequestHeader("name") String name, @RequestHeader String id, @RequestHeader String tmpId, @RequestHeader String businesstype) {
        User user = userService.findCueernt();
        if (file.isEmpty()) {
            return Result.failed("上传文件为空");
        }
        String uuid = UUID.randomUUID().toString();
        // 获取文件名
        String fileName = file.getOriginalFilename();
        String newFileName = uuid + "."+ fileName.split("\\.")[1];
        MpAttachment attachment = new MpAttachment();
        attachment.setBusinessid(id);
        attachment.setTmp_businessid(tmpId);
        attachment.setBusinesstype(businesstype);
        attachment.setFilename(fileName);
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyMMdd");
        String uploadPath = name + FileConnect + LocalDate.now().format(fmt);
        attachment.setFilepath(uploadPath + FileConnect + newFileName);

        try {
            //添加水印
            InputStream input = new ByteArrayInputStream((file.getBytes()));
            /**给图片添加文字水印**/
            ArrayList<String> watermarkList =new ArrayList<String>();
            watermarkList.add("现场拍照[客户照片]");
            watermarkList.add(user.getName() +" " + DateUtils.dateToStr(new Date(),"yyyy-MM-dd HH:mm"));
            InputStream output = ImageWatermarkUtils.markImageByText(watermarkList,input,fileName.split("\\.")[1]);
            //文件上传
            SmbFileUtils.save(FileUtils.StreamToByte(output),uploadPath,newFileName);
            attachment.setCreatetime(DateUtils.getNow());
            attachment.setId(UUID.randomUUID().toString());
            mpAttachmentService.add(attachment);
            return Result.success(mpAttachmentService.getView(attachment.getId()),"成功!");
        } catch (IOException e) {
            e.printStackTrace();
            return Result.failed("文件上传失败");
        }
    }


    @CrossOrigin
    @ApiOperation(value = "base64附件上传", notes = "")
    @PostMapping("/base64UploadFile")
    public Result base64UploadFile(@RequestBody String base64Image, @RequestHeader("fileName") String fileName, @RequestHeader("name") String name, @RequestHeader String id, @RequestHeader String tmpId, @RequestHeader String businesstype) throws UnsupportedEncodingException {
        String uuid = UUID.randomUUID().toString();
        base64Image = java.net.URLDecoder.decode(base64Image,"UTF-8");
        fileName = java.net.URLDecoder.decode(fileName,"UTF-8");
        id = java.net.URLDecoder.decode(id,"UTF-8");
        String newFileName = uuid + "."+ fileName.split("\\.")[1];
        MpAttachment attachment = new MpAttachment();
        attachment.setBusinessid(id);
        attachment.setTmp_businessid(tmpId);
        attachment.setBusinesstype(businesstype);
        attachment.setFilename(fileName);
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyMMdd");
        String uploadPath = name + FileConnect + LocalDate.now().format(fmt);
        attachment.setFilepath(uploadPath + FileConnect + newFileName);

        try {

            byte[] imageByte = ImageUtils.base64ImageToByte(base64Image);
            SmbFileUtils.save(imageByte,uploadPath,newFileName);
            attachment.setCreatetime(DateUtils.getNow());
            attachment.setId(UUID.randomUUID().toString());
            mpAttachmentService.add(attachment);
            return Result.success(mpAttachmentService.getView(attachment.getId()),"成功!");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.failed("文件上传失败");
        }
    }

    @ApiOperation(value = "获取图片", notes = "")
    @GetMapping(value="/getImage", produces = {MediaType.IMAGE_PNG_VALUE})
    public BufferedImage getImage(@ApiParam(value = "id" , required=true ) @RequestParam String id) throws IOException {
        MpAttachment attachment = mpAttachmentService.get(id);
        if(attachment !=null)
        {
            InputStream imageInputStream =  SmbFileUtils.getFile(attachment.getFilepath());
            return ImageIO.read(imageInputStream);
        }
        return null;
    }

    @ApiOperation(value = "删除", notes = "")
    @GetMapping(value="/delete")
    public Result delete(@ApiParam(value = "id" , required=true ) @RequestParam String id) {
        MpAttachment attachment = mpAttachmentService.get(id);
        try {
            SmbFileUtils.delete(attachment.getFilepath());
            int result = mpAttachmentService.delete(id);
            if(result >0){
                return Result.success(attachment,"删除成功!");
            }else {
                return Result.success(attachment,"删除失败!");
            }

        } catch (Exception e) {
            e.printStackTrace();
            return Result.failed("失败");
        }

    }


    @ModelAttribute
    public void setReqAndRes(HttpServletRequest request, HttpServletResponse response){
        this.request = request;
        this.response = response;
        this.session = request.getSession();
    }
    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
}

jcifs文件管理帮助类

package com.brickdog.common.utils;


import jcifs.CIFSContext;
import jcifs.CIFSException;
import jcifs.context.SingletonContext;
import jcifs.smb.*;

import java.io.*;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;


public class SmbFileUtils {
    static String ip = "127.0.0.1";
    static String domain = "127.0.0.1/upload";
    static String userName = "admin";
    static String password = "admin";

    static void SmbFileUtils(){

    }
    //根据账号密码登录
    private static CIFSContext withNTLMCredentials(CIFSContext ctx) {
        return ctx.withCredentials(new NtlmPasswordAuthenticator(domain,
                userName, password));
    }

    //保存文件
    public static String save(byte[] byteArr, String url,String fileName) throws IOException {
        InputStream in = new ByteArrayInputStream(byteArr);
        String status = "";
        try {
            CIFSContext context = withNTLMCredentials(SingletonContext.getInstance());
            SmbFileWriter.createDirectory("smb://" + domain  +"/" + url, context);
            boolean result = SmbFileWriter.writeSmbFile(in, "smb://" + domain  +"/" + url +"/" + fileName, context);
            status = "success";
        } catch (Exception e) {
            e.printStackTrace();
            status = "error";
        } finally {
            in.close();
            return status;
        }
    }
    //获取文件
    public static  InputStream getFile(String filePath) throws IOException {
        String url = "smb://" + domain + "/" + filePath;

        try {
            CIFSContext context = withNTLMCredentials(SingletonContext.getInstance());
            SmbFileReader reader = new SmbFileReader();
            byte[] byteArr = reader.readSmbFile(url, context);
            InputStream input = new ByteArrayInputStream(byteArr);
            return  input;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    //删除文件
    public static String delete(String filePath) throws IOException {
        String status = "";
        String url = "smb://" + domain + "/" + filePath;

        try {
            CIFSContext context = withNTLMCredentials(SingletonContext.getInstance());
            SmbFile file = new SmbFile(url, context);
            if (file.exists()) {
                file.delete();
                status = "success";
            }
        } catch (Exception e) {
            e.printStackTrace();
            status = "error";
        }
        return status;
    }

    static class SmbFileReader {
        public byte[] readSmbFile(String path, CIFSContext context) throws IOException {
            try  {
                SmbFile smbFile = new SmbFile(path, context);
                long fileSize = smbFile.length();
                if (fileSize > Integer.MAX_VALUE) {
                    System.out.println("file too big...");
                    return null;
                }
                InputStream fi = smbFile.getInputStream();
                byte[] buffer = new byte[(int) fileSize];
                int offset = 0;
                int numRead = 0;
                while (offset < buffer.length
                        && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {
                    offset += numRead;
                }
                // 确保所有数据均被读取
                if (offset != buffer.length) {
                    throw new IOException("Could not completely read file "
                            + smbFile.getName());
                }
                fi.close();
                return buffer;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    static class SmbFileWriter {
        static boolean writeSmbFile(String source, String target, CIFSContext context) throws IOException {
            if (StrUtils.isEmpty(source) || StrUtils.isEmpty(target)) {
                return false;
            }
            return writeSmbFile(Files.newInputStream(Paths.get(source)),
                    target, context);
        }

        static boolean writeSmbFile(InputStream in, String target, CIFSContext context) throws IOException {
            if (Objects.nonNull(in) && StrUtils.isNotEmpty(target)) {
                try (SmbFile file = new SmbFile(target, context)) {
                    try (SmbFile parent = new SmbFile(file.getParent(), context)) {
                        if (!parent.exists()) {
                            createDirectory(file.getParent(), context);
                        }
                        if (!file.exists()) {
                            file.createNewFile();
                        }
                    }
                    try (OutputStream os = file.getOutputStream()) {
                        byte[] bytes = new byte[1024];
                        while (in.read(bytes) != -1) {
                            os.write(bytes);
                        }
                        return true;
                    }
                }finally {
                    in.close();
                }
            }
            return false;
        }

        static SmbFile createDirectory(String targetDir, CIFSContext context) throws MalformedURLException,
                CIFSException, MalformedURLException {
            try (SmbFile dir = new SmbFile(targetDir, context)) {
                if (!dir.exists()) {
                    dir.mkdir();
                }
                return dir;
            }
        }
    }
}

pom文件

这边jcifs包我们一定要使用2.0以上的,2.0以下经常会出现网盘权限认证卡住导致读取或者上传附件特别慢

<dependency>
    <groupId>eu.agno3.jcifs</groupId>
    <artifactId>jcifs-ng</artifactId>
    <version>2.1.3</version>
</dependency>

生成文件

我们对每类文件进行文件加归类,每天一个文件夹分开存放附件

 表结构

添加水印

使用案列

//添加水印
InputStream input = new ByteArrayInputStream((file.getBytes()));
/**给图片添加文字水印**/
ArrayList<String> watermarkList =new ArrayList<String>();
watermarkList.add("现场拍照[客户照片]");
watermarkList.add(user.getName() +" " + DateUtils.dateToStr(new Date(),"yyyy-MM-dd HH:mm"));
InputStream output = ImageWatermarkUtils.markImageByText(watermarkList,input,fileName.split("\\.")[1]);
//文件上传
SmbFileUtils.save(FileUtils.StreamToByte(output),uploadPath,newFileName);

图片帮助类

package com.brickdog.common.utils;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;

/**
 * 图片添加水印工具类
 * 文字水印 图片水印 利用jdk ,不依赖第三方
 */
public class ImageWatermarkUtils {

    static final String NEW_IMAGE_NAME_PRE_STR = "_water";
    // 水印透明度
    private static float alpha = 0.5f;
    // 水印文字字体
    private static Font font = new Font("宋体", Font.BOLD, 12);
    // 水印文字颜色
    private static Color color = Color.white;


    /**
     * 给图片添加水印、可设置水印图片旋转角度
     *
     * @param iconPath   水印图片路径
     * @param srcImgPath 源图片路径
     * @param targerPath 目标图片路径
     * @param degree     水印图片旋转角度
     */
    public static void markImageByIcon(String iconPath, String srcImgPath, String targerPath, Integer degree) {
        OutputStream os = null;
        try {
            if (StrUtils.isBlank(targerPath)) {
                targerPath = srcImgPath.substring(0, srcImgPath.lastIndexOf(".")) + NEW_IMAGE_NAME_PRE_STR + srcImgPath.substring(srcImgPath.lastIndexOf("."));
            }
            Image srcImg = ImageIO.read(new File(srcImgPath));
            BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null), BufferedImage.TYPE_INT_RGB);
            // 得到画笔对象
            // Graphics g= buffImg.getGraphics();
            Graphics2D g = buffImg.createGraphics();

            // 设置对线段的锯齿状边缘处理
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);

            g.drawImage(srcImg.getScaledInstance(srcImg.getWidth(null), srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0, null);

            if (null != degree) {
                // 设置水印旋转
                g.rotate(Math.toRadians(degree),
                        (double) buffImg.getWidth() / 2, (double) buffImg
                                .getHeight() / 2);
            }
            // 水印图象的路径 水印一般为gif或者png的,这样可设置透明度
            ImageIcon imgIcon = new ImageIcon(iconPath);
            // 得到Image对象。
            Image img = imgIcon.getImage();
            float alpha = 1f; // 透明度
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
            /**
             * 以下一算水印图位置,右下角
             */
            int width = srcImg.getWidth(null);
            int height = srcImg.getHeight(null);
            int iconWidth = img.getWidth(null);
            int iconHeight = img.getHeight(null);
            int x = width - iconWidth;
            int y = height - iconHeight;
            x = x < 0 ? 0 : x;
            y = y < 0 ? 0 : y;
            // 表示水印图片的位置
            g.drawImage(img, x, y, null);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
            g.dispose();
            os = new FileOutputStream(targerPath);
            // 生成图片
            ImageIO.write(buffImg, "JPG", os);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != os)
                    os.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 给图片添加水印文字、可设置水印文字的旋转角度
     *
     */
    public static InputStream markImageByText(ArrayList<String> watermarkList, InputStream imageInputStream,String formatName) {

        try {

            // 1、源图片
            Image srcImg = ImageIO.read(imageInputStream);
            int srcImgWidth = srcImg.getWidth(null);
            int srcImgHeight = srcImg.getHeight(null);
            BufferedImage buffImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB);

            // 2、得到画笔对象
            Graphics2D g = buffImg.createGraphics();
            // 3、设置对线段的锯齿状边缘处理
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(
                    srcImg.getScaledInstance(srcImg.getWidth(null),
                            srcImg.getHeight(null), Image.SCALE_SMOOTH), 0, 0, null);
            // 4、设置黑色遮罩
            int rowHeight = 20;
            int padding = 6;
            int height = rowHeight * watermarkList.size() + padding;
            int x = padding;
            int y = srcImgHeight - height;
            g.setColor(Color.black);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.3f));
            g.fillRect(0,y,srcImgWidth,height);

            // 5、设置水印文字颜色
            g.setColor(color);
            // 6、设置水印文字Font
            g.setFont(font);
            // 7、设置水印文字透明度
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 1f));
            // 8、第一参数->设置的内容,后面两个参数->文字在图片上的坐标位置(x,y)
            for(int i=0;i<watermarkList.size();i++)
            {
               g.drawString(watermarkList.get(i), x, y + rowHeight);
               y =y+rowHeight;
            }
            // 9、释放资源
            g.dispose();
            // 10、生成图片
            ByteArrayOutputStream os = new ByteArrayOutputStream();

            ImageIO.write(buffImg, formatName, os);
            InputStream  outStream = new ByteArrayInputStream(os.toByteArray());

            return  outStream;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {

            } catch (Exception e) {
                e.printStackTrace();
            }
            try {

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return  null;
    }

    /**
     * 获取水印文字总长度
     *
     * @param waterMarkContent
     *            水印的文字
     * @param g
     * @return 水印文字总长度
     */
    private static int getWatermarkLength(String waterMarkContent, Graphics2D g) {
        return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length());
    }

}

参考文献

https://github.com/codelibs/jcifs
https://github.com/xuanyiying/jcifs-ng-smb2-demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

搬砖狗-小强

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

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

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

打赏作者

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

抵扣说明:

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

余额充值