kotlin使用spring mvc将接收的字符串生成二维码并响应

简介: 使用kotlin制作一个基于`spring mvc`的小demo:接收请求中的字符串参数,用`zxing`将字符串生成出一个二维码,再返回给前端。可以通过此例来了解kotlin与spring的搭配

kotlin代替java早已有了一定的势头,无论是Intellij IDEA这个如今第一的Java IDE,还是Jetpack Compose Multiplatform这个新兴Android UI都极大的推动了kotlin。此外kotlin的专有生态也在持续发展,像Ktormktor都比较有趣。当然了最重要的还是与以前的scalagroovy一样,作为JVM平台上的语言能完美与java交互,继承java这边庞大而丰富的生态。

在Java最主要的应用场景——web开发,自然可以用kotlin来尝试尝试的,本文使用kotlin制作一个基于spring mvc的小demo:接收请求中的字符串参数,用zxing将字符串生成出一个二维码,然后返回给前端。

首先从kotlin环境构建说起,如果你熟悉这一点,可以直接跳到图像生成,也可以先跳到最后的效果展示先看看样子。

环境构建

Gradle配置

第一步当然就是写好gradle的配置了,写java时这个语言基本都是选groovy这个默认的,没有额外扩展名,直接就是build.gradle。但现在用kotlin了,自然可以尝试尝试对应的build.gradle.kts,其实也就是一些符号的变化,按照惯例写在前面的自然是引入的插件了:

plugins {
    java
    war
    kotlin("jvm") version "1.8.20"
}

这个差不多就是kotlin的web项目常用的配置,因为多多少少还是可能用到java,打war包的需求基本也都会有,kotlin("jvm") version "1.8.20"则是kotlin的必需,至于这个要追加版本号是因为官方文档就是这样写的。不过这样也会有问题,看似比以前的groovy简单,那如果插件名像maven-publish这样中间带-的怎么引入呢?这种有特殊符号的其实直接用“`”符号包起来就好了,例如下面这样:

plugins {
    kotlin("jvm") version "1.8.20"
    `maven-publish`
    signing
}

然后就是groupversiondescription这些基础属性的设置。

group = "com.lyrieek.gear"
version = "0.0.1"
description = "a demo"

这里我起个包名叫gear,而项目名自然和groovy一样都是在settings.gradle里设置,当然也是要加kts的,文件名应该是settings.gradle.kts

rootProject.name = "Gear Web"

这样就可以将项目名命名为Gear Web了,再然后就是继续在build.gradle.kts里面设置repositories了:

repositories {
    mavenLocal()
    maven { url = uri("https://maven.aliyun.com/repository/google/") }
    maven { url = uri("https://maven.aliyun.com/repository/public/") }
    mavenCentral()
}

众所周知国内一般是需要这个阿里云镜像的,这个设置一般就是这样。然后还可以设置一下JDK的版本,这里我选择用17:

java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

然后便是dependencies

dependencies {
    implementation("jakarta.servlet:jakarta.servlet-api:6.0.0")
    implementation("org.springframework:spring-web:6.0.7")
    implementation("org.springframework:spring-webmvc:6.0.7")
    implementation("org.springframework.security:spring-security-web:6.0.2")
    implementation("org.springframework.security:spring-security-config:6.0.2")

    implementation("com.fasterxml.jackson.core:jackson-databind:2.14.2")
    implementation("com.google.zxing:core:3.5.1")
}

作为web项目,jstl如今可能已经不需要了,但servlet-api还是少不了的。再就是spring mvcspring security基本的几个包。还有现在交互必不可少的jackson,它的annotations就懒得引了,要用也是和java那边一样的。最后就是这个功能的重点,谷歌的zxing,为了轻量这里只需要core部分即可。

类配置

众所周知用xml配置已经过时了,现在又流行回去用配置类了,自然是写个WebConfig先:

import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import java.text.SimpleDateFormat

@Configuration
@EnableWebMvc
@ComponentScan("com.lyrieek.gear")
open class WebConfig : WebMvcConfigurer {

    override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
        configurer.enable()
    }

    override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>>) {
        converters.add(
            MappingJackson2HttpMessageConverter(
                Jackson2ObjectMapperBuilder()
                    .indentOutput(true)
                    .dateFormat(SimpleDateFormat("yyyy-MM-dd")).build()
            )
        )
    }

    override fun addCorsMappings(registry: CorsRegistry) {
        registry.addMapping("/**")
            .allowedOriginPatterns("*")
            .allowCredentials(true)
            .allowedMethods("*")
    }
}

web项目从一开始就要围绕安全来工作,安全配置单独写出来是有必要的,但是不要忘记关掉阻挠前端开发工作的csrf

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain

@Configuration
@EnableWebSecurity
open class WebSecurityConfig {
    @Bean
    open fun filterChain(http: HttpSecurity): SecurityFilterChain {
        return http.cors().and().csrf().disable().build()
    }
}

虽说框架的xml确实都没了,但服务的src\main\webapp\WEB-INF\web.xml由于历史遗留问题,现阶段还没办法不写,配置还不短:

<web-app>
    <display-name>gear</display-name>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.lyrieek.gear.config</param-value>
    </context-param>

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.lyrieek.gear.config</param-value>
        </init-param>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>throwExceptionIfNoHandlerFound</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

到这也就配置完成了。

图像生成

首先就是写个controller

import jakarta.servlet.http.HttpServletResponse
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.util.*

@RestController
@RequestMapping("qrcode")
class QRCodeController {

    @Autowired
    private lateinit var server: QRCodeService

    @RequestMapping
    fun generate(@RequestParam("str") str: String, response: HttpServletResponse) {
        server...
    }

}

其中这个Service需要注意,要用lateinit var修饰,基本上所有的Service都是这样注入的。下面的generate()方法接受str参数,还有response准备将未来生成好的BufferedImage响应回去。

下面就是service的部分,这部分是本文的核心,说来也比较简单,首先写好壳子:

@Service
class QRCodeService {

    private val width = 100
    private val height = 100

    fun generate(content: String): BufferedImage {
        ...
    }
}

二维码的宽高是可以提前都定好的,这个不会变,参数就是controller那边的str,返回就返回一个标准的java.awt.image.BufferedImage,然后就开始写这个方法的主体代码,先准备二维码的参数,zxing需要的参数是一个MutableMap<EncodeHintType, Any>

val encodeHints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java)
encodeHints[EncodeHintType.CHARACTER_SET] = "UTF-8"
encodeHints[EncodeHintType.MARGIN] = 1
val matrix: BitMatrix =
    MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, encodeHints)

一般来说只用的上这两个参数,charset保证中文的编码,margin设置下间距,然后用MultiFormatWriter.encode()就生成二维码的数据矩阵了,接下来就是写入到BufferedImage中:

val image = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY)
val rowPixels = IntArray(width)
var row = BitArray(width)
for (y in 0 until height) {
    row = matrix.getRow(y, row)
    for (x in 0 until width) {
        rowPixels[x] = if (row[x]) 1 else -1
    }
    image.setRGB(0, y, width, 1, rowPixels, 0, width)
}
return image

因为一般是黑白的,所以用BufferedImage.TYPE_BYTE_BINARY色彩模式即可,1就是黑色,-1就是白色。

但如果想生成彩色的二维码,比如说灰底蓝色的二维码,可以改成BufferedImage.TYPE_3BYTE_BGR,然后在填充rowPixels时选择自己想要的颜色:

val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
val rowPixels = IntArray(width)
var row = BitArray(width)
for (y in 0 until height) {
    row = matrix.getRow(y, row)
    for (x in 0 until width) {
        rowPixels[x] = if (row[x]) Color.blue.rgb else Color.gray.rgb
    }
    image.setRGB(0, y, width, 1, rowPixels, 0, width)
}
return image

相对应的controller那边只需要响应BufferedImage就好了:

@RequestMapping
fun generate(@RequestParam("str") str: String, response: HttpServletResponse) {
    response.contentType = "image/jpeg"
    ImageIO.write(server.generate(str), "jpg", response.outputStream)
}

效果展示

image.png

选择BufferedImage.TYPE_3BYTE_BGR通道并设置蓝色效果:
image.png

综合源代码

QRCodeController.kt

import jakarta.servlet.http.HttpServletResponse
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.util.*
import javax.imageio.ImageIO

@RestController
@RequestMapping("qrcode")
class QRCodeController {

    @Autowired
    private lateinit var server: QRCodeService

    @RequestMapping
    fun generate(@RequestParam("str") str: String, response: HttpServletResponse) {
        response.contentType = "image/jpeg"
        ImageIO.write(server.generate(str), "jpg", response.outputStream)
    }

}

QRCodeService.kt

import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.google.zxing.common.BitArray
import com.google.zxing.common.BitMatrix
import org.springframework.stereotype.Service
import java.awt.image.BufferedImage
import java.util.*

@Service
class QRCodeService {

    private val width = 100
    private val height = 100

    fun generate(content: String): BufferedImage {
        val encodeHints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java)
        encodeHints[EncodeHintType.CHARACTER_SET] = "UTF-8"
        encodeHints[EncodeHintType.MARGIN] = 1
        val matrix: BitMatrix =
            MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, encodeHints)
        val image = BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY)
        val rowPixels = IntArray(width)
        var row = BitArray(width)
        for (y in 0 until height) {
            row = matrix.getRow(y, row)
            for (x in 0 until width) {
                rowPixels[x] = if (row[x]) 1 else -1
            }
            image.setRGB(0, y, width, 1, rowPixels, 0, width)
        }
        return image
    }
}

本文写作于2023年5月29日并发布于lyrieek的掘金,于2023年7月17日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
5月前
|
存储 前端开发 Java
揭秘!如何用Spring Boot轻松打造动态二维码生成器?一键解锁无限可能,你的创意将无处不在!
【8月更文挑战第29天】在数字化时代,二维码成为信息快速传递的关键工具,广泛应用于支付、身份验证和产品追溯等场景。本文将指导你如何利用Spring Boot框架和Google的ZXing库,搭建一个动态生成二维码的Web服务。首先,通过Spring Initializr创建项目并配置相关依赖;接着,编写二维码生成逻辑和服务类;最后,在Controller中整合这些功能,提供RESTful接口供外部调用。通过访问`/generate-qrcode?text=你的内容`即可测试API并获取二维码图片。这为开发者提供了强大的工具,未来还可进一步优化存储和提升性能。
212 3
|
3月前
|
JSON 前端开发 Java
Spring MVC——获取参数和响应
本文介绍了如何在Spring框架中通过不同的注解和方法获取URL参数、上传文件、处理cookie和session、以及响应不同类型的数据。具体内容包括使用`@PathVariable`获取URL中的参数,使用`MultipartFile`上传文件,通过`HttpServletRequest`和`@CookieValue`获取cookie,通过`HttpSession`和`@SessionAttribute`获取session,以及如何返回静态页面、HTML代码片段、JSON数据,并设置HTTP状态码和响应头。
77 1
Spring MVC——获取参数和响应
|
3月前
|
JSON 前端开发 Java
Spring Boot框架中的响应与分层解耦架构
在Spring Boot框架中,响应与分层解耦架构是两个核心概念,它们共同促进了应用程序的高效性、可维护性和可扩展性。
76 3
|
3月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
267 2
|
3月前
|
前端开发 Java
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
文章介绍了如何使用SpringBoot创建简单的后端服务器来处理HTTP请求,包括建立连接、编写Controller处理请求,并返回响应给前端或网址。
62 0
学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例
|
5月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
151 3
|
5月前
|
存储 Java Kotlin
Kotlin 字符串教程:深入理解与使用技巧
Kotlin中的字符串用于存储文本,定义时使用双引号包围字符序列,如`var greeting = &quot;Hello&quot;`。Kotlin能自动推断变量类型,但在未初始化时需显式指定类型,如`var name: String`。可通过索引访问字符串元素,如`txt[0]`获取首字符。字符串作为对象,拥有属性和方法,如`length`获取长度,`toUpperCase()`转大写。可使用`compareTo()`比较字符串,`indexOf()`查找子串位置。字符串中嵌入单引号表示文本内的引号,如`&quot;It&#39;s alright&quot;`。使用`+`或`plus()
66 3
|
5月前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
111 0
|
5月前
|
XML 前端开发 Java
Spring MVC接收param参数(直接接收、注解接收、集合接收、实体接收)
Spring MVC提供了灵活多样的参数接收方式,可以满足各种不同场景下的需求。了解并熟练运用这些基本的参数接收技巧,可以使得Web应用的开发更加方便、高效。同时,也是提高代码的可读性和维护性的关键所在。在实际开发过程中,根据具体需求选择最合适的参数接收方式,能够有效提升开发效率和应用性能。
240 2
|
5月前
|
安全 Java Android开发
Kotlin字符串秘籍:解锁高效处理与创意应用,让你的代码闪耀不凡!
【8月更文挑战第2天】Kotlin是一门现代化的静态类型语言,以简洁、安全及强互操作性著称,在Android及服务器端开发中广受好评。本文通过与其他语言对比,深入解析Kotlin中字符串的基础和高级用法。Kotlin简化了字符串拼接,支持直接使用`+`操作符,并引入了直观的字符串模板。它提供了丰富的字符串操作函数,如使用索引范围进行子字符串提取,增强了代码的可读性。Kotlin字符串的不可变性提升了程序稳定性。利用扩展函数特性,可以轻松定制字符串行为,提高代码的模块化和重用性。掌握这些技巧能显著提升开发效率和代码质量。
51 1