二维码在物资管理中的应用以及实现

createh53周前 (12-07)技术教程24

今天为大家分享下在物资管理中,我们如何实现二维码的批量生成以及批量打印,在物资管理中,我们经常需要对物资通过二维码,一码一物的方式进行管理,这样的管理方式非常方便。每个二维码都记录了整个物资设备的生命周期。

业务场景

具体的业务场景可能有如下几个场景:

1.对物资建立电子档案

当物资进场后,我们可以通过物资清单Excel实现数据导入,批量制作二维码标签,贴在对应的物资设备上,一物一码,手机扫码即可查看物资类型,物资名称,物资型号,以及物资的当前使用状态(进场,入库,出库,已使用等等)。

逻辑功能实现:

1.首先提供Excel的数据批量导入功能接口
2.然后对导入的资产数据信息进行落库。
3.开始批量制作二维码标签,二维码的内容为当前每个资产的详细信息以及初始化的状态,比如已检阅状态。app扫码后,会根据自己固定的跳转路由地址,并携带扫码得到的参数,跳转到指定的路由地址。
4.将其制作完毕后,将所有的二维码图片上传至文件服务器minio或者oss,同时给用户返回所有的URL列表,供用户下载打印。

2.支持后台和手机端更新修改物资信息

当二维码标签信息需要修改,例如物资使用状态发生变化时,不需要重新制作打印标签,要么在后台修改对应的二维码中的内容即可;同时也需要支持手机端现场扫码,直接修改内容即可,不用登录后台操作。

逻辑功能实现:

1.提供物资状态或者信息变更接口,管理员扫码后,首先回显物资所有信息,以及当前的状态,
2.管理员可以直接修改当前物资信息和状态,并进行提交。

3.支持物资损坏上报

当物资或者设备出现损坏或者故障的时候,使用者可以通过对该物资或者设备上的二维码进行扫码,选择故障申报,填写故障信息,并上传故障图片,完成申报,形成任务下发,同时系统将向责任人进行提醒通知,维修人员维修完毕后,扫码后,在当前任务下完成故障维修说明,并结束任务,形成任务闭环。

代码实现

引入依赖

今天我给大家做一个实现,具体用到的技术:
SpringBoot+Minio+Hutool

<hutool.version>5.8.15</hutool.version>
引入如下:
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.2</version>
        </dependency>
        <!-- hutool 的依赖配置-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-bom</artifactId>
            <version>${hutool.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.15</version>
        </dependency>
                <!-- 二维码支持包 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.2.0</version>
        </dependency>

配置

qrCode:
  # 二维码中间的logo地址
  logoUrl: 
  # 二维码宽度
  width: 300
  # 二维码高度
  height: 300
  # 获取二维码中的Logo缩放的比例系数,如5表示长宽最小值的1/5
  ratio: 7
minio:
  endpoint: 
  accessKey: 
  secretKey: 
  bucketName: 
  expires: 7200 # 单位秒

文件流工具类

package com.wuk.uaa.utils;

import cn.hutool.core.io.FileUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 文件处理工具类
 */
@Slf4j
@Component
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil {

    /**
     * 获取封装得MultipartFile
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return MultipartFile
     */
    public MultipartFile getMultipartFile(InputStream inputStream, String fileName) {
        FileItem fileItem = createFileItem(inputStream, fileName);
        return new CommonsMultipartFile(fileItem);
    }

    /**
     * FileItem类对象创建
     */
    public FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead;
        byte[] buffer = new byte[8192];
        OutputStream os = null;
        //使用输出流输出输入流的字节
        try {
            os = item.getOutputStream();
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (IOException e) {
            log.error("Stream copy exception", e);
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);
                }
            }
        }

        return item;
    }
}

Minio配置类

package com.wuk.uaa.config;

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class MinIoClientConfig {

    @Value("${minio.endpoint}")
    private String endpoint;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;

    /**
     * 注入minio 客户端
     * @return
     */
    @Bean
    public MinioClient minioClient(){
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

Minio工具类

package com.wuk.uaa.utils;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.List;

@Component
@Slf4j
public class MinioUtil {

    private static final String FILE_NAME_PATTERN = "{}_{}";

    @Value("${minio.expires}")
    private Integer fileExpires;

    @Resource
    private MinioClient minioClient;

    /**
     * description: 判断bucket是否存在
     * @param bucketName 桶名称
     * @return: bool值
     */
    public boolean bucketExists(String bucketName) throws Exception{
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * description: 创建bucket
     * @param bucketName 桶名称
     */
    public void createBucket(String bucketName) throws Exception {
        boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (!isExist) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * description: 获取全部bucket
     */
    public List<Bucket> getAllBuckets() throws Exception{
        return minioClient.listBuckets();
    }

    /***
     * @author: wuk
     * @date: 2023/7/11 11:05
     * @description: 上传文件并返回文件名称
     * * @return: java.lang.String 返回
     */
    public String upload(MultipartFile file, String bucketName) throws Exception {
        //判断桶是否存在
        boolean b = this.bucketExists(bucketName);
        if (!b) {
            this.createBucket(bucketName);
        }
        // 上传文件的原始文件名
        String originalFilename = file.getOriginalFilename();
        // 文件大小
        long fileSize = file.getSize();
        log.info("上传文件的原始文件名:{},文件大小:{},文件类型为:{}", originalFilename, fileSize, file.getContentType());
        // 文件名:原始文件名_日期
        String fileName = StrUtil.format(FILE_NAME_PATTERN,DateUtils.dateTimeNow(),originalFilename);
        log.info("格式化后的文件名:{}", fileName);
        try {
            minioClient.putObject(PutObjectArgs
                    .builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(file.getInputStream(), fileSize, -1)
                    .contentType(file.getContentType()).build());
        } catch (Exception e) {
            log.info("上传文件失败!{}",e.getMessage());
        }
        // 返回可访问的图片链接
        String fileUrl = getFileUrl(bucketName, fileName, fileExpires);
        log.info("上传的图片链接为:{}",fileUrl);
        return fileUrl;
    }

    /**
     * description: 获取文件url并设置过期时间
     * @param bucketName 桶名称
     * @param fileName   文件名称
     * @param expires    过期时间(不指定默认为7200秒)
     * @return: url 文件路径
     */
    public String getFileUrl(String bucketName, String fileName, Integer expires) throws Exception {
        BucketExistsArgs bucketArgs = BucketExistsArgs.builder().bucket(bucketName).build();
        boolean bucketExists = minioClient.bucketExists(bucketArgs);
        if (ObjectUtil.isNull(expires)) {
            expires = fileExpires;
        }
        String url = "";
        if (bucketExists) {
            GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(fileName)
                    .expiry(expires)
                    .build();
            url = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
        }
        return url;
    }

    /**
     * description: 下载文件
     * @param bucketName 桶名称
     * @param fileName   文件名称
     * @return: byte[]  文件数组
     */
    public byte[] download(String bucketName, String fileName) throws Exception{
        BucketExistsArgs bucketArgs = BucketExistsArgs.builder().bucket(bucketName).build();
        boolean bucketExists = minioClient.bucketExists(bucketArgs);
        log.info("bucketExists:{}", bucketExists);
        InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc;
        while((rc=stream.read(buff, 0, 100))>0) {
            baos.write(buff, 0, rc);
        }
        return baos.toByteArray();
    }
}

QRCodeUtils工具类

package com.wuk.uaa.utils;

import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.wuk.uaa.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @BelongsProject: base-uaa-api
 * @BelongsPackage: com.wuk.uaa.utils
 * @Author: wuk
 * @Date: 2023/7/10 8:47
 * @Description:
 */
@Slf4j
@Component
public class QRCodeUtils {

    @Value("${qrCode.logoUrl}")
    private String logoUrl;
    @Value("${qrCode.width}")
    private Integer width;
    @Value("${qrCode.height}")
    private Integer height;
    @Value("${qrCode.ratio}")
    private Integer ratio;
    @Value("${minio.bucketName}")
    private String bucketName;
    @Resource
    private MinioUtil minioUtil;
    @Resource
    private FileUtils fileUtils;


    public QrConfig getQrConfig(){
        try {
            // 将URL转为BufferedImage
            BufferedImage bufferedImage = ImageIO.read(new URL(logoUrl));
            // 二维码自定义参数对象
            QrConfig qrConfig = QrConfig.create();
            // 设置二维码的宽度
            qrConfig.setWidth(width);
            // 设置二维码的高度
            qrConfig.setHeight(height);
            // 设置二维码中LOGO图片
            qrConfig.setImg(bufferedImage);
            // 设置二维码中的Logo缩放的比例系数,如4表示长宽最小值的1/4
            qrConfig.setRatio(ratio);
            qrConfig.setCharset(StandardCharsets.UTF_8);
            qrConfig.setErrorCorrection(ErrorCorrectionLevel.H);
            return qrConfig;
        }catch (Exception ex){
            ex.printStackTrace();
            log.error("错误信息为:{}",ex.getMessage());
            throw new ServiceException("LOGO的Url地址无法解析");
        }
    }

    /**
     * 将文件对象列表上传至文件服务器,然后得到所有的二维码标签图片URL
     */
    public List<String> getOrCodeUrlList(List<?> contents) throws Exception{
        List<String> fileUrls = new ArrayList<>();
        for (Object content : contents) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            QrCodeUtil
                    .generate(JsonUtils.toJsonString(content),
                            getQrConfig(), ImgUtil.IMAGE_TYPE_JPG, outputStream);
            InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            //文件上传并获取地址
            fileUrls.add(minioUtil.upload(fileUtils.getMultipartFile(inputStream, IdUtil.getSnowflake().nextIdStr()+"."+ImgUtil.IMAGE_TYPE_JPG), bucketName));
        }
        return fileUrls;
    }
}

测试Controller

package com.wuk.uaa.controller;

import com.wuk.uaa.core.controller.BaseController;
import com.wuk.uaa.core.domain.R;
import com.wuk.uaa.entity.vo.QRCodeVo;
import com.wuk.uaa.entity.vo.SysUserImportVo;
import com.wuk.uaa.excel.ExcelResult;
import com.wuk.uaa.service.ISysUserService;
import com.wuk.uaa.utils.ExcelUtil;
import com.wuk.uaa.utils.QRCodeUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.util.List;

/**
 * @BelongsProject: base-oauth-api
 * @BelongsPackage: com.longi.baseoauthapi.controller
 * @Author: wuk
 * @Date: 2023/7/7 14:21
 * @Description: 二维码应用
 */
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/tdCode")
public class QRCodeController extends BaseController {

    @Resource
    private QRCodeUtils qrCodeUtils;

    @Resource
    private ISysUserService userService;

    @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public R<QRCodeVo> importData(@RequestPart("file") MultipartFile file) throws Exception {
        ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(),
                SysUserImportVo.class,true);
        List<SysUserImportVo> userImportVos = result.getList();
        List<String> qrCodeUrlList = qrCodeUtils.getOrCodeUrlList(userImportVos);
        QRCodeVo qrCodeVo = new QRCodeVo();
        qrCodeVo.setAnalysis(result.getAnalysis());
        qrCodeVo.setUrlList(qrCodeUrlList);
        //保存业务库
        userService.insertISUser(userImportVos);
        return R.ok(qrCodeVo);
    }


}

结果:

二维码的批量生成就为大家分享到这里,上述的代码是完整的,可以直接用做生产,希望对大家有所帮助,希望大家多多关注点赞支持!!!