springboot2.2.6 app版本管理,扫描二维码下载app

1、首先在maven中加入生成二维码的一个依赖包:

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>${zxing-core.version}</version>
        </dependency>

这里我用的是3.3.0版本

<zxing-core.version>3.3.0</zxing-core.version>

2、所需的一个工具类QRCodeUtils

import com.google.zxing.*;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.springframework.util.Assert;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Objects;

public class QRCodeUtils {

	private static final String CHARSET = "utf-8";

	private static final String FORMAT_NAME = "JPG";

	// 二维码尺寸
	private static final int QRCODE_SIZE = 750;

	// LOGO宽度
	private static final int WIDTH = 250;

	// LOGO高度
	private static final int HEIGHT = 250;

	/********************************************************************************
	 *
	 *
	 *
	 *********************************************************************************/
	public static void encode(String content, Image src, boolean needCompress, OutputStream output) {
		BufferedImage image = createImage(content);
		// 插入图片
		if(Objects.nonNull(src)){
			insertImage(image, src, needCompress);
		}
		try {
			ImageIO.write(image, FORMAT_NAME, output);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, InputStream inputStream, boolean needCompress, OutputStream output) {
		try {
			encode(content, ImageIO.read(inputStream), needCompress, output);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, File file, boolean needCompress, OutputStream output) {
		try {
			encode(content, ImageIO.read(file), needCompress, output);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, String imgPath, boolean needCompress, OutputStream output) {
		File file = new File(imgPath);
		Assert.isTrue(file.exists(), MessageFormat.format("文件:{0} 不存在",imgPath));
		encode(content, file, needCompress, output);
	}


	public static void encode(String content, Image src, boolean needCompress, File destFile) {
		BufferedImage image = createImage(content);
		// 插入图片
		if(Objects.nonNull(src)){
			insertImage(image, src, needCompress);
		}
		if (!destFile.exists() && !destFile.isDirectory()) {
			destFile.mkdirs();
		}
		try {
			ImageIO.write(image, FORMAT_NAME, destFile);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, InputStream inputStream, boolean needCompress, File destFile) {
		try {
			encode(content, ImageIO.read(inputStream), needCompress, destFile);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, File file, boolean needCompress, File destFile) {
		try {
			encode(content, ImageIO.read(file), needCompress, destFile);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, String imgPath, boolean needCompress, File destFile) {
		File file = new File(imgPath);
		Assert.isTrue(file.exists(), MessageFormat.format("文件:{0} 不存在",imgPath));
		encode(content, file, needCompress, destFile);
	}


	public static void encode(String content, Image src, boolean needCompress, String destPath) {
		encode(content, src, needCompress, new File(destPath));
	}
	public static void encode(String content, InputStream inputStream, boolean needCompress, String destPath) {
		try {
			encode(content, ImageIO.read(inputStream), needCompress, destPath);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, File file, boolean needCompress, String destPath) {
		Assert.isTrue(file.exists(), MessageFormat.format("文件:{0} 不存在",file.getAbsolutePath()));
		try {
			encode(content, ImageIO.read(file), needCompress, destPath);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
	}
	public static void encode(String content, String imgPath, boolean needCompress, String destPath) {
		File file = new File(imgPath);
		encode(content, file, needCompress, destPath);
	}






	public static void encode(String content, Image src, OutputStream output) {
		encode(content, src, true, output);
	}
	public static void encode(String content, InputStream inputStream, OutputStream output) {
		encode(content, inputStream, true, output);
	}
	public static void encode(String content, File file, OutputStream output) {
		encode(content, file, true, output);
	}
	public static void encode(String content, String imgPath, OutputStream output) {
		encode(content, imgPath, true, output);
	}


	public static void encode(String content, Image src, File destFile) {
		encode(content, src, true, destFile);
	}
	public static void encode(String content, InputStream inputStream, File destFile) {
		encode(content, inputStream, true, destFile);
	}
	public static void encode(String content, File file, File destFile) {
		encode(content, file, true, destFile);
	}
	public static void encode(String content, String imgPath, File destFile) {
		encode(content, imgPath, true, destFile);
	}


	public static void encode(String content, Image src, String destPath) {
		encode(content, src, true, destPath);
	}
	public static void encode(String content, InputStream inputStream, String destPath) {
		encode(content, inputStream, true, destPath);
	}
	public static void encode(String content, File file, String destPath) {
		encode(content, file, true, destPath);
	}
	public static void encode(String content, String imgPath, String destPath) {
		encode(content, imgPath, true, destPath);
	}


	/********************************************************************************
	 *
	 *
	 *
	 *********************************************************************************/
	public static String decode(BufferedImage image) {
		if (image == null) {
			return null;
		}
		BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
		BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
		Result result;
		Hashtable hints = new Hashtable();
		hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
		try {
			result = new MultiFormatReader().decode(bitmap, hints);
		} catch (NotFoundException e) {
			throw new BusinessException(e);
		}
		String resultStr = result.getText();
		return resultStr;
	}
	public static String decode(InputStream inputStream){
		BufferedImage image = null;
		try {
			image = ImageIO.read(inputStream);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
		return decode(image);
	}
	public static String decode(File file){
		BufferedImage image = null;
		try {
			image = ImageIO.read(file);
		} catch (IOException e) {
			throw new BusinessException(e);
		}
		return decode(image);
	}
	public static String decode(String path){
		return decode(new File(path));
	}


	private static BufferedImage createImage(String content) {
		Hashtable hints = new Hashtable();
		hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
		hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
		hints.put(EncodeHintType.MARGIN, 1);
		BitMatrix bitMatrix = null;
		try {
			bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);
		} catch (WriterException e) {
			throw new BusinessException(e);
		}
		int width = bitMatrix.getWidth();
		int height = bitMatrix.getHeight();

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
			}
		}
		return image;
	}
	private static void insertImage(BufferedImage source, Image src, boolean needCompress){
		int width = src.getWidth(null);
		int height = src.getHeight(null);
		if (needCompress) { // 压缩LOGO
			//if (width > WIDTH) {
				width = WIDTH;
			//}
			//if (height > HEIGHT) {
				height = HEIGHT;
			//}
			Image image = src.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
			BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
			Graphics g = tag.getGraphics();
			g.drawImage(image, 0, 0, null); // 绘制缩小后的图
			g.dispose();
			src = image;
		}
		// 插入LOGO
		Graphics2D graph = source.createGraphics();
		int x = (QRCODE_SIZE - width) / 2;
		int y = (QRCODE_SIZE - height) / 2;
		graph.drawImage(src, x, y, width, height, null);
		Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
		graph.setStroke(new BasicStroke(3f));
		graph.draw(shape);
		graph.dispose();
	}



	static class BufferedImageLuminanceSource extends LuminanceSource {

		private final BufferedImage image;
		private final int left;
		private final int top;

		public BufferedImageLuminanceSource(BufferedImage image) {
			this(image, 0, 0, image.getWidth(), image.getHeight());
		}

		public BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width, int height) {
			super(width, height);

			int sourceWidth = image.getWidth();
			int sourceHeight = image.getHeight();
			if (left + width > sourceWidth || top + height > sourceHeight) {
				throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
			}

			for (int y = top; y < top + height; y++) {
				for (int x = left; x < left + width; x++) {
					if ((image.getRGB(x, y) & 0xFF000000) == 0) {
						image.setRGB(x, y, 0xFFFFFFFF); // = white
					}
				}
			}

			this.image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY);
			this.image.getGraphics().drawImage(image, 0, 0, null);
			this.left = left;
			this.top = top;
		}

		@Override
		public byte[] getRow(int y, byte[] row) {
			if (y < 0 || y >= getHeight()) {
				throw new IllegalArgumentException("Requested row is outside the image: " + y);
			}
			int width = getWidth();
			if (row == null || row.length < width) {
				row = new byte[width];
			}
			image.getRaster().getDataElements(left, top + y, width, 1, row);
			return row;
		}

		@Override
		public byte[] getMatrix() {
			int width = getWidth();
			int height = getHeight();
			int area = width * height;
			byte[] matrix = new byte[area];
			image.getRaster().getDataElements(left, top, width, height, matrix);
			return matrix;
		}

		@Override
		public boolean isCropSupported() {
			return true;
		}

		@Override
		public LuminanceSource crop(int left, int top, int width, int height) {
			return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height);
		}

		@Override
		public boolean isRotateSupported() {
			return true;
		}

		@Override
		public LuminanceSource rotateCounterClockwise() {
			int sourceWidth = image.getWidth();
			int sourceHeight = image.getHeight();
			AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
			BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY);
			Graphics2D g = rotatedImage.createGraphics();
			g.drawImage(image, transform, null);
			g.dispose();
			int width = getWidth();
			return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), getHeight(), width);
		}

	}

}

3、创建配置文件、并将二维码中间的图片命名为qr-code-center.png放到resource目录下的file文件夹下

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * QRCodeProperty
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/5/7 15:53
 * @author: 
 */
@Component
@ConfigurationProperties(prefix = "qr-code")
@Getter
@Setter
public class QRCodeProperty {

    /**
     * 二维码解析地址,一般要配置成公网可访问的二维码解析地址
     */
    private String parseUrl;

    /**
     * 二维码是否需要压缩
     */
    private boolean needCompress;

}

qr-code:
    parse-url: http://localhost:8980/api/appVersion/qrCode
    need-compress: true

在这里插入图片描述

4、创建实体类AppVersion.java,及其AppVersionMapper.javaIAppVersionService.javaAppVersionServiceImpl.javaAppVersionApi.javaAppVersionMapper.xml

下面仅展示AppVersion.javaAppVersionServiceImpl.javaAppVersionApi.java

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.Date;

/**
 * AppVersion
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/5/7 14:14
 * @author: 
 */
@Getter
@Setter
@Accessors(chain = true)
public class AppVersion {

    /**
     * 主键
     */
    private Long id;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 更新内容
     */
    private String updateContent;

    /**
     * app版本号,设定为不可编辑
     */
    private String appVersion;

    /**
     * apk文件大小
     */
    private Long appSize;

    /**
     * apk文件下载地址
     */
    private String downloadUrl;

    /**
     * 二维码地址
     */
    private String qrCodeUrl;

}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.text.MessageFormat;
import java.util.Objects;
import java.util.UUID;

/**
 * AppVersionServiceImpl
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/5/7 14:29
 * @author: 
 */
@Service
public class AppVersionServiceImpl implements IAppVersionService {

    private final static String BASE_APP_FOLDER = "app";

    @Autowired
    private IFileService fileService;

    @Autowired
    private AppVersionMapper appVersionMapper;

    @Autowired
    private QRCodeProperty qrCodeProperty;

    /**
     * getFolder
     * @desc: apk文件、二维码存放文件夹
     * @author: 
     * @createTime: 2020/5/7 14:41
     * @param appVersion
     * @return: java.lang.String
     */
    private static String getFolder(AppVersion appVersion){
        return BASE_APP_FOLDER + File.separator + appVersion.getAppVersion();
    }

    @Override
    public AppVersion selectById(long id) {
        return appVersionMapper.selectById(id);
    }

    @Override
    public AppVersion selectLast() {
        return appVersionMapper.selectLast();
    }

    @Override
    public long insert(AppVersion appVersion, MultipartFile multipartFile) {

        //TODO 1、先检查版本号是否已存在、略......

        //2、存储上传的apk文件,并设置appSize、downloadUrl
        String folder = getFolder(appVersion);
        UploadResult uploadResult = fileService.upload(multipartFile, folder);
        appVersion.setAppSize(uploadResult.getFileSize()).setDownloadUrl(uploadResult.getDownloadUrl());


        //3、设置qrCodeUrl、保存appVersion、生成二维码
        String qrCodePath = folder +  File.separator + UUID.randomUUID().toString().replaceAll("-","") + ".png";
        appVersion.setQrCodeUrl(FileUtils.getDownloadUrl(qrCodePath));
        appVersionMapper.insert(appVersion);

        QRCodeUtils.encode(
                MessageFormat.format("{0}?id={1}", qrCodeProperty.getParseUrl(), appVersion.getId()),
                AppVersionServiceImpl.class.getClassLoader().getResourceAsStream("file/qr-code-center.png"),
                qrCodeProperty.isNeedCompress(),
                fileService.getAbsolutePath(qrCodePath)
        );

        return appVersion.getId();
    }

    @Override
    public int update(AppVersion appVersion, MultipartFile multipartFile) {
        //1、如果更新了apk文件则:
        if(Objects.nonNull(multipartFile)){
            String folder = getFolder(appVersion);
            UploadResult uploadResult = fileService.upload(multipartFile, folder);
            appVersion.setAppSize(uploadResult.getFileSize()).setDownloadUrl(uploadResult.getDownloadUrl());
            //此时无需再重新生成二维码
        }
        return appVersionMapper.update(appVersion);
    }

    @Override
    public int delete(long id) {
        return appVersionMapper.delete(id);
    }

}

import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Objects;

/**
 * AppVersionApi
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/5/7 16:25
 * @author: 
 */
@Controller
@RequestMapping("/api/appVersion")
public class AppVersionApi {

    @Autowired
    private IAppVersionService appVersionService;

    @PostMapping("/insert")
    @ResponseBody
    public AjaxResult<Long> insert(
            @RequestParam("file") MultipartFile file,
            @RequestParam("appVersion") String _appVersion
    ) {
        AppVersion appVersion = JSON.parseObject(_appVersion,AppVersion.class);
        return AjaxResult.success(appVersionService.insert(appVersion, file));
    }

    @PostMapping("/update")
    @ResponseBody
    public AjaxResult<Integer> update(
            @RequestParam(value = "file", required = false) MultipartFile file,
            @RequestParam("appVersion") String _appVersion
    ) {
        AppVersion appVersion = JSON.parseObject(_appVersion,AppVersion.class);
        return AjaxResult.success(appVersionService.update(appVersion, file));
    }

    @PostMapping("/delete")
    @ResponseBody
    public AjaxResult<Integer> delete(long id) {
        int res = appVersionService.delete(id);
        return AjaxResult.success(res);
    }

    @GetMapping("/qrCode")
    public String qrCode(@RequestParam(value="id", required = false) Long id, Model model){
        AppVersion appVersion = Objects.isNull(id) ? appVersionService.selectLast() : appVersionService.selectById(id);
        String title, error = null;
        if(Objects.isNull(appVersion)){
            title = "错误";
            error = "此二维码链接已失效";
        }else {
            title = "下载";
        }
        model.addAttribute("title", title);
        model.addAttribute("error", error);
        model.addAttribute("appVersion", appVersion);
        return "qrCode";
    }

}

至于qrCode页面就自己设计了
备注:上面使用的IFileService、FileUtils均在之前的博客springboot2.2.6文件上传、下载及文件超出大小限制的处理能找到

相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页