Самый понятный [короткая ссылка QR-код] реальный бой во всей сети

Java

Во вчерашнем пуше статьи была статья под названиемСамая простая и понятная [короткая ссылка] запись во всей сети, Мне становится довольно интересно и весело Дело не только в том, что теоретические знания закончены, а практический код свернут. Если есть студенты, которые этого не знают, вы можете прочитать введение введения.Я начну с реального боя здесь.В коде больше китайских комментариев, что облегчает вам чтение и понимание. Без лишних слов, давайте перейдем к коду!

Показать результаты

Построение проекта и связанные с ним зависимости

Создайте новый нормальный java-проект maven, как показано на рисунке.

Дайте проекту имя группы (группа) и имя модуля (артефакт) самостоятельно
На данный момент проект завершен.В настоящее время мы вводим некоторые основные пакеты 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

    <!-- 引入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. Добавить скрипт структуры базы данных

    существуетresourceСоздайте новую папку в каталогеdb,Создать файлschema.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 прошла успешно!

Введите информацию о настроенной базе данных и нажмите Войти, чтобы открыть рабочий интерфейс:

использовать весенние данные 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Реализован базовый метод добавления, удаления, изменения и запроса БД.

/**
 * CrudRepository 实现了对 ShortLink 基本的增删改查方法
 * @author jiangpeng
 * @date 2019/11/2715:29
 */
public interface ShortLinkRepository extends CrudRepository<ShortLink, Long> {
}

Генератор преобразования идентификаторов для коротких ссылок

/**
 * 短链接生成
 * 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"));
    }
}

Вы можете выполнить основной метод, чтобы просмотреть текущие результаты и сравнить, является ли преобразование исходным значением.

Интеграция статических страниц 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>

Создать контроллер запросов

/**
 * 生成短链接请求类
 *
 * @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Вы можете перейти на соответствующую страницу, как показано ниже, демонстрационный эффект

Ниже приведены данные, сохраненные в таблице базы данных, а идентификатор является ключом к генерации и преобразованию параметров короткой цепи.

Как реализовать функцию ссылки QR-кода

Используйте zxing для генерации QR-кода

Представлен набор инструментов zxing QR code, который реализуетСпецификация отраслевого QR-кода

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

Инструменты генерации QR-кода

/**
 * 二维码生成工具类
 * @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>

Создать контроллер запросов

/**
 * 生成二维码请求类
 * @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, чтобы получить весь исходный код этой статьи.

Писать не легко.Если статья вам помогла, можете оставить след и поставить лайк~

☞☞Нажмите здесь, чтобы купить облачный сервер☜Ощутите эффект кода☜