全網最通俗易懂的【短連接二維碼】實戰

昨天的文章推送中有一篇題爲全網最通俗易懂的【短連接】入門, 讓我以爲頗爲有趣好玩,這不正好理論知識學完了,實操代碼擼起來。若是有不瞭解的同窗能夠看看入門那篇的介紹,我這裏直接從實戰提及,代碼中有超過的中文註釋,讓你更容易閱讀理解。話很少說,上代碼!html

效果展現

項目搭建與相關依賴

新建一個普通的maven java 工程,如圖所示java

本身給項目取組名(group)和模塊名(artifact)
至此項目搭建完成,此時咱們爲項目引入一些基本的jar包

<properties>
        <spring.version>5.1.8.RELEASE</spring.version>
        <spring.boot.version>2.1.6.RELEASE</spring.boot.version>
    </properties>

    <dependencies>
        <!-- spring boot依賴,表示這是一個springboot web程序. -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <!-- apache 的工具包. -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
    </dependencies>
複製代碼

如何實現短連接生成功能

集成H2內存數據庫

  1. 添加必要的jar包web

    <!-- 引入H2的相關包. -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
                <version>${spring.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>com.h2database</groupId>
                <artifactId>h2</artifactId>
                <version>RELEASE</version>
                <scope>compile</scope>
            </dependency>
            <!--輔助jar包,用於查看內存數據庫-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <version>${spring.boot.version}</version>
                <optional>true</optional>
            </dependency>
    複製代碼
  2. 在application.properties中添加必要的配置算法

    #h2配置
    spring.jpa.show-sql = true
    spring.jpa.hibernate.ddl-auto = update
    ##數據庫鏈接設置
    spring.datasource.url = jdbc:h2:mem:dbtest
    spring.datasource.username = root
    spring.datasource.password = root
    spring.datasource.driverClassName =org.h2.Driver
    ##數據初始化設置
    #進行該配置後,每次啓動程序,程序都會運行resources/db/schema.sql文件,對數據庫的結構進行操做。
    spring.datasource.schema=classpath:db/schema.sql
    #進行該配置後,每次啓動程序,程序都會運行resources/db/data.sql文件,對數據庫的數據操做。
    #spring.datasource.data=classpath:db/data.sql
    ##h2 web console設置
    spring.datasource.platform=h2
    # 進行該配置後,h2 web consloe 就能夠在遠程訪問了。不然只能在本機訪問。
    spring.h2.console.settings.web-allow-others=true
    #進行該配置,你就能夠經過YOUR_URL/h2訪問h2 web consloe。YOUR_URL是你程序的訪問URl。
    spring.h2.console.path=/h2
    #進行該配置,程序開啓時就會啓動h2 web consloe。固然這是默認的,若是你不想在啓動程序時啓動h2 web consloe,那麼就設置爲false。
    spring.h2.console.enabled=true
    複製代碼
  3. 添加數據庫結構腳本spring

    resource目錄下新建文件夾db,建立文件schema.sql,內容以下sql

    create table if not exists short_link (
            id int not null primary key,
            url varchar(1000),
            create_time DATE );
    
    複製代碼
  4. 測試H2數據庫數據庫

    啓動springboot應用程序,在瀏覽器中輸入http://localhost:2088/h2,能夠打開h2數據庫管理器登陸界面,可以進入以下頁面說明H2集成成功!apache

輸入配置的數據庫信息,點擊登陸,便可打開操做界面:

使用spring data jpa

建立實體與數據庫表的映射對象瀏覽器

@Entity
@Data
@NoArgsConstructor
@ToString
public class ShortLink {
    @Id
    @GeneratedValue
    private long id;

    private String url;
    private Date createTime;

    public ShortLink(String url, Date date){
        this.url = url;
        this.createTime =date;
    }
}
複製代碼

建立DAO數據庫交互層,CrudRepository實現了對 DB 基本的增刪改查方法springboot

/**
 * CrudRepository 實現了對 ShortLink 基本的增刪改查方法
 * @author jiangpeng
 * @date 2019/11/2715:29
 */
public interface ShortLinkRepository extends CrudRepository<ShortLink, Long> {
}
複製代碼

短連接的ID轉換生成器

/**
 * 短連接生成
 * 10進制、62進制互轉
 * @author jiangpeng
 */
@Slf4j
public class ConversionUtils {
    /**
     * 初始化 62 進制數據,索引位置表明字符的數值,好比 A表明10,z表明61等
     */
    private static String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    private static int scale = 62;

    /**
     * 將數字轉爲62進制
     *
     * @param num    Long 型數字
     * @param length 轉換後的字符串長度,不足則左側補0
     * @return 62進制字符串
     */
    public static String encode(long num, int length) {
        StringBuilder sb = new StringBuilder();
        int remainder;
        // id混淆算法
        long snum = num & 0xff000000;
        snum += (num & 0x0000ff00) << 8;
        snum += (num & 0x00ff0000) >> 8;
        snum += (num & 0x0000000f) << 4;
        snum += (num & 0x000000f0) >> 4;

        while (snum > scale - 1) {
            /*
              對 scale 進行求餘,而後將餘數追加至 sb 中,因爲是從末位開始追加的,所以最後須要反轉(reverse)字符串
             */
            remainder = Long.valueOf(snum % scale).intValue();
            sb.append(chars.charAt(remainder));

            snum = snum / scale;
        }

        sb.append(chars.charAt(Long.valueOf(snum).intValue()));
        String value = sb.reverse().toString();
        log.info("encode id: {}", snum);
        return StringUtils.leftPad(value, length, '0');
    }

    /**
     * 62進制字符串轉爲數字
     *
     * @param str 編碼後的62進制字符串
     * @return 解碼後的 10 進制字符串
     */
    public static long decode(String str) {
        /*
          將 0 開頭的字符串進行替換
         */
        str = str.replace("^0*", "");
        long num = 0;
        int index;
        for (int i = 0; i < str.length(); i++) {
            /*
              查找字符的索引位置
             */
            index = chars.indexOf(str.charAt(i));
            /*
              索引位置表明字符的數值
             */
            num += (long) (index * (Math.pow(scale, str.length() - i - 1)));
        }
        // id混淆算法
        long snum = num & 0xff000000;
        snum += (num & 0x00ff0000) >> 8;
        snum += (num & 0x0000ff00) << 8;
        snum += (num & 0x000000f0) >> 4;
        snum += (num & 0x0000000f) << 4;

        return snum;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("62進制:" + encode(1, 5));
        System.out.println("10進制:" + decode("0000G"));
    }
}
複製代碼

能夠執行main方法查看運行結果,比對是否進制轉後仍是原來的值

集成 freeMarker 靜態頁面

引入freeMarker的依賴包

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
複製代碼

freeMarker默認讀取模板文件路徑爲resource/templates目錄下,因此在這個目錄下建立頁面文件 short_link.ftl

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
<h2>短連接轉換</h2>
<form action="shortLink" method="post">
    要轉換的url:<textarea  rows="1" style="width: 432px; height: 43px;" name="url">${url?default('')}</textarea>
    <br/><br/>
    <input type="submit" value="提交"/>
</form>

<#if shortUrl?? && shortUrl != "">
    <a href="${shortUrl}" target="_blank">${shortUrl}</a>
</#if>

</body>
</html>

複製代碼

建立請求Controller

/**
 * 生成短連接請求類
 *
 * @author jiangpeng
 * @date 2019/11/2715:19
 */
@Controller
@RequestMapping("shortLink")
public class ShortLinkController {
    @Autowired
    private ShortLinkRepository shortLinkRepository;

    @GetMapping
    public String shortLink() {
        return "short_link";
    }

    /**
     * 生成短連接
     *
     * @param url 要轉換的url
     * @return short_link.ftl
     */
    @PostMapping
    public String createShortLink(String url, HttpServletRequest request) throws UnknownHostException {
        Instant instant = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant();
        ShortLink shortLink = shortLinkRepository.save(new ShortLink(url, Date.from(instant)));
        String shortStr = ConversionUtils.encode(shortLink.getId(), 4);

        request.setAttribute("url", url);
        request.setAttribute("shortUrl", getServerUrl(request) + "/shortLink/" + shortStr);

        return "short_link";
    }

    /**
     * 解析短連接並跳轉頁面
     *
     * @param shortUrl 短連接參數
     */
    @RequestMapping("/{shortUrl}")
    public void redirectToSourceUrl(@PathVariable("shortUrl") String shortUrl, HttpServletResponse response) throws IOException {
        long id = ConversionUtils.decode(shortUrl);
        Optional<ShortLink> shortLinkOpt = shortLinkRepository.findById(id);
        String url = shortLinkOpt.orElseGet(null).getUrl();
        response.sendRedirect(url);
    }

    /**
     * 獲取當前應用服務器域名和端口
     * @return String
     */
    private String getServerUrl(HttpServletRequest request) throws UnknownHostException {
        StringBuilder sb = new StringBuilder();
        //獲取服務器域名
        String serverName = request.getServerName();
        //獲取服務器端口
        int serverPort = request.getServerPort();
        //獲取服務器IP地址;
        String hostAddress = InetAddress.getByName(request.getServerName()).getHostAddress();

        return sb.append("http://").append(serverName).append(":").append(serverPort).toString();
    }
}

複製代碼

最後在瀏覽器輸入http://localhost:2088/shortLink便可跳轉到對應頁面,以下圖是演示效果

如下是數據庫表中保存的數據,ID是其中的短鏈連接參數生成與轉換的關鍵

如何實現二維碼連接功能

使用zxing生成二維碼

引入zxing 二維碼工具包, 它實現了關於業界二維碼的規範

<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.4.0</version>
</dependency>
複製代碼

二維碼生成工具類

/**
 * 二維碼生成工具類
 * @author jiangpeng
 * @date 2019/11/28 0028
 */
@Slf4j
public class QRCodeUtils {
    /**
     * 生成二維碼
     *
     * @Param Content 二維碼內容
     * @Param outputStream
     */
    public static void QREncode(String content, File logoFile, OutputStream outputStream) throws WriterException,
            IOException {
        // 圖像寬度
        int width = 200;
        // 圖像高度
        int height = 200;
        // 圖像類型
        String format = "png";
        Map<EncodeHintType, Object> hints = new HashMap<>();
        //內容編碼格式
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        // 指定糾錯等級
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        //設置二維碼邊的空度,非負數
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);

        /*
            問題:生成二維碼正常,生成帶logo的二維碼logo變成黑白;  緣由:MatrixToImageConfig默認黑白,須要設置BLACK、WHITE
            解決:https://ququjioulai.iteye.com/blog/2254382
         */
        if (logoFile != null) {
            MatrixToImageConfig matrixToImageConfig = new MatrixToImageConfig(0xFF000001, 0xFFFFFFFF);
            BufferedImage bufferedImage = LogoMatrix(MatrixToImageWriter.toBufferedImage(bitMatrix,
                    matrixToImageConfig),
                    logoFile);
            //輸出帶logo圖片
            ImageIO.write(bufferedImage, format, outputStream);
        } else {
            MatrixToImageWriter.writeToStream(bitMatrix, format, outputStream);
        }
        log.info("二維碼生成成功!");
    }

    /**
     * 識別二維碼
     */
    public static void QRReader(File file) throws IOException, NotFoundException {
        MultiFormatReader formatReader = new MultiFormatReader();
        //讀取指定的二維碼文件
        BufferedImage bufferedImage = ImageIO.read(file);
        BinaryBitmap binaryBitmap =
                new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(bufferedImage)));
        //定義二維碼參數
        Map<DecodeHintType, String> hints = new HashMap<>(8);
        hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
        Result result = formatReader.decode(binaryBitmap, hints);
        //輸出相關的二維碼信息
        log.info("解析結果:" + result.toString());
        log.info("二維碼格式類型:" + result.getBarcodeFormat());
        log.info("二維碼文本內容:" + result.getText());
        bufferedImage.flush();
    }

    /**
     * 二維碼添加logo
     *
     * @param matrixImage 源二維碼圖片
     * @param logoFile    logo圖片
     * @return 返回帶有logo的二維碼圖片
     */
    public static BufferedImage LogoMatrix(BufferedImage matrixImage, File logoFile) throws IOException {
        /*
         * 讀取二維碼圖片,並構建繪圖對象
         */
        Graphics2D g2 = matrixImage.createGraphics();

        int matrixWidth = matrixImage.getWidth();
        int matrixHeight = matrixImage.getHeight();
        /*
         * 讀取Logo圖片
         */
        BufferedImage logo = ImageIO.read(logoFile);

        int logoWidth = matrixWidth / 4;
        int logoHeight = matrixHeight / 4;

        int x = matrixWidth / 10 * 4;
        int y = matrixHeight / 10 * 4;

        //開始繪製圖片
        g2.drawImage(logo, x, y, logoWidth, logoHeight, null);
        BasicStroke stroke = new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        // 設置筆畫對象
        g2.setStroke(stroke);
        //指定弧度的圓角矩形
        RoundRectangle2D.Float round = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 20, 20);
        g2.setColor(Color.white);
        // 繪製圓弧矩形
        g2.draw(round);
        //設置logo 有一道灰色邊框
        BasicStroke stroke2 = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
        // 設置筆畫對象
        g2.setStroke(stroke2);
        RoundRectangle2D.Float round2 = new RoundRectangle2D.Float(x + 2, y + 2, logoWidth - 4, logoHeight - 4, 20, 20);
        g2.setColor(new Color(128, 128, 128));
        // 繪製圓弧矩形
        g2.draw(round2);

        g2.dispose();
        matrixImage.flush();
        return matrixImage;
    }
}
複製代碼

使用 freeMarker 靜態頁面

freeMarker默認讀取模板文件路徑爲resource/templates目錄下,因此在這個目錄下建立頁面文件 qr_code.ftl

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
<h2>二維碼內容生成</h2>
<form action="qrCode" method="post" enctype="multipart/form-data">
    <span>內容:<input type="text" name="content"></span>
    <br/><br/>
    <span>logo:<input type="file" name="logoFile"></span>
    <br/><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

複製代碼

建立請求Controller

/**
 * 生成二維碼請求類
 * @author jiangpeng
 * @date 2019/11/28 0028
 */
@Controller
@RequestMapping("qrCode")
public class QRCodeController {

    /**
     * 跳轉頁面
     * @return
     */
    @GetMapping
    public String qrCode(){
        return "qr_code";
    }

    /**
     * 生成二維碼
     * @param content 內容
     * @param response HttpServletResponse
     */
    @PostMapping
    public void createQrCode(String content, @RequestParam("logoFile") MultipartFile logoFile, HttpServletResponse response) throws Exception {
        File file = !logoFile.isEmpty() ? FileConvertUtils.multipartFileToFile(logoFile): null;
        QRCodeUtils.QREncode(content, file, response.getOutputStream());
    }
}
複製代碼

最後在瀏覽器輸入http://localhost:2088/shortLink便可跳轉到對應頁面,以下圖是演示效果

掃碼關注公衆號,回覆20191128獲取本文全部源碼

寫做不易,若是文章對你有幫助,能否留下腳印留個贊~

☞☞點擊這裏購買雲服務器☜體驗代碼效果☜

相關文章
相關標籤/搜索