WeChat applet implements attachment uploading and previewing based on vant and springboot

Preface

Image uploading and previewing are widely and frequently used on mobile devices. The van-uploader component of the vant component library has helped us realize most of the functions. However, it is still a bit troublesome to use frequently in the system. We repackaged and simplified it according to our own business system. Our development. The backend uses springboot to integrate jcifs to implement file management microservices.

Updating files

Attachment preview

Front-end components

Component introduction

The front end only needs to use the component in the view and pass in the required parameters.

businessid Business ID, used to associate the business order number with attachments
tmp_id The temporary business ID is associated with the attachment when the business order number is not generated at the beginning.
name Used to classify different types of attachments into different folders
businesstype Used to distinguish attachment groups of different types under the same business order number
readonly Determine whether the component is previewed or uploaded
<bdog-uploader-image      
	id="visit_uploader"
	businessid="{
   
   {id}}"
	tmp_id="{
   
   {tmp_id}}"
	name="customerVisit"
	businesstype="visit"
    readonly ="{
   
   {readonly}}"
/>  

Component js part code

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, })
        })
      }
  }

})

Component view part code

<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" />

Backend microservices

Backend microservices

Microservices generally include attachment uploading, deletion, image retrieval, list retrieval, and attachment uploading services.

​​​​​​​

 

 Microservice code

@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 file management helper class

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 file

Here we must use jcifs package 2.0 or above. Below 2.0, network disk permission authentication often gets stuck, causing reading or uploading attachments to be extremely slow.

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

Generate files

We categorize each type of files, and separate attachments into a folder every day.

 Table Structure

add watermark

Use cases

//添加水印
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);

Picture help class

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

}

references

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

Guess you like

Origin blog.csdn.net/qq243348167/article/details/126819511