在windows+IDEA环境下,使用gradle将springboot打包,制作成镜像发布到docker上并部署到本地k8s访问web应用。
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("plugin.jpa") version "1.6.10"
id("org.springframework.boot") version "2.6.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
kotlin("plugin.allopen") version "1.4.32"
kotlin("kapt") version "1.4.32"
}
group = "com.xun"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
}
dependencies {
kapt("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "junit")
exclude(module = "mockito-core")
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("com.ninja-squad:springmockk:3.0.1")
}
allOpen{
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
tasks.withType {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType {
useJUnitPlatform()
}
FROM java
MAINTAINER [email protected]
VOLUME /tmp
ADD /build/libs/springbootkotlin1-0.0.1-SNAPSHOT.jar myspringbootkotlinapp.jar
ENTRYPOINT ["java","-jar","myspringbootkotlinapp.jar"]
启动docker,启动minikube
minikube start
在Dockerfile所在目录下运行shell,制作镜像
docker build -t myspringbootkotlinapp .
docker images
docker tag myspringbootkotlinapp sunzhexun/myspringbootkotlinapp
docker login
docker push sunzhexun/myspringbootkotlinapp
kubectl create deployment myspringbootkotlinapp --image=sunzhexun/myspringbootkotlinapp --dry-run=client -o=yaml > deployment.yaml
echo --- >> deployment.yaml
kubectl create service clusterip myspringbootkotlinapp --tcp=8080:8080 --dry-run=client -o=yaml >> deployment.yaml
kubectl apply -f deployment.yaml
kubectl get all
创建容器可能需要几秒到几分钟,可以另起一个终端,查看dashboard
minikube dashboard
kubectl port-forward svc/myspringbootkotlinapp 8080:8080
localhost:8080
此时就可以本地访问我们的springboot应用啦
此springboot项目代码(使用的是kotlin+jpa)
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("plugin.jpa") version "1.6.10"
id("org.springframework.boot") version "2.6.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.10"
kotlin("plugin.spring") version "1.6.10"
kotlin("plugin.allopen") version "1.4.32"
kotlin("kapt") version "1.4.32"
}
group = "com.xun"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
}
dependencies {
kapt("org.springframework.boot:spring-boot-configuration-processor")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(module = "junit")
exclude(module = "mockito-core")
}
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("com.ninja-squad:springmockk:3.0.1")
}
allOpen{
annotation("javax.persistence.Entity")
annotation("javax.persistence.Embeddable")
annotation("javax.persistence.MappedSuperclass")
}
tasks.withType {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "1.8"
}
}
tasks.withType {
useJUnitPlatform()
}
实体类
import java.time.LocalDateTime
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.Id
import javax.persistence.ManyToOne
@Entity
class Article(
var title: String,
var headline: String,
var content: String,
@ManyToOne var author: User,
var slug: String = title.toSlug(),
var addedAt: LocalDateTime = LocalDateTime.now(),
@Id @GeneratedValue var id: Long? = null)
@Entity
class User(
var login: String,
var firstname: String,
var lastname: String,
var description: String? = null,
@Id @GeneratedValue var id: Long? = null)
@Entity
class Product(
var productName: String,
var categoryId: Int,
@Id @GeneratedValue var id: Long? = null)
@Entity
class Category(
var categoryName:String,
@Id @GeneratedValue var id: Long? = null)
持久层
import com.xun.springbootkotlin1.Article
import org.springframework.data.repository.CrudRepository
interface ArticleRepository: CrudRepository<Article, Long> {
fun findBySlug(slug: String): Article?
fun findAllByOrderByAddedAtDesc(): Iterable<Article>
}
import com.xun.springbootkotlin1.Category
import org.springframework.data.repository.CrudRepository
interface CategoryRepository:CrudRepository<Category,Long> {
}
import com.xun.springbootkotlin1.Product
import org.springframework.data.repository.CrudRepository
interface ProductRepository:CrudRepository<Product,Long> {
}
import com.xun.springbootkotlin1.User
import org.springframework.data.repository.CrudRepository
interface UserRepository : CrudRepository<User, Long> {
fun findByLogin(login: String): User?
}
初始化Entity数据
import com.xun.springbootkotlin1.repository.ArticleRepository
import com.xun.springbootkotlin1.repository.CategoryRepository
import com.xun.springbootkotlin1.repository.ProductRepository
import com.xun.springbootkotlin1.repository.UserRepository
import org.springframework.boot.ApplicationRunner
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class BlogConfiguration {
@Bean
fun databaseInitializer(userRepository: UserRepository,
articleRepository: ArticleRepository,
productRepository: ProductRepository,
categoryRepository: CategoryRepository
) = ApplicationRunner {
val authorInfo = userRepository.save(User("author", "喆勋", "孙"))
articleRepository.save(Article(
title = "标题2",// 必须是数字/英文结尾?
headline = "头条新闻",
content = "上海昨天新增24例本土新冠肺炎确诊患者,新增734例本土无症状感染者",
author = authorInfo
))
articleRepository.save(Article(
title = "标题1",
headline = "头条新闻",
content = "山西吕梁商代墓出土特殊形制青铜短剑",
author = authorInfo
))
productRepository.save(Product(
productName = "iphone13",
categoryId = 1
))
productRepository.save(Product(
productName = "HUAWEI mate 40 pro",
categoryId = 1
))
productRepository.save(Product(
productName = "西湖醋鱼",
categoryId = 2
))
productRepository.save(Product(
productName = "北京烤鸭",
categoryId = 2
))
productRepository.save(Product(
productName = "四川麻婆豆腐",
categoryId = 2
))
productRepository.save(Product(
productName = "NIKE",
categoryId = 3
))
categoryRepository.save(Category(
categoryName = "数码产品",
id = 1
))
categoryRepository.save(Category(
categoryName = "食品",
id = 2
))
categoryRepository.save(Category(
categoryName = "运动服装",
id = 3
))
}
}
@ConstructorBinding
@ConfigurationProperties("blog")
data class BlogProperties(var title: String, val banner: Banner) {
data class Banner(val title: String? = null, val content: String)
}
HtmlController
@Controller
class HtmlController(private val repository: ArticleRepository, private val properties: BlogProperties) {
@GetMapping("/")
fun blog(model: Model): String {
model["title"]=properties.title
model["banner"] = properties.banner
model["articles"] = repository.findAllByOrderByAddedAtDesc().map { it.render() }
return "blog"
}
@GetMapping("/article/{slug}")
fun article(@PathVariable slug: String, model: Model): String {
val article = repository
.findBySlug(slug)
?.render()
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "This article does not exist")
model["title"] = article.title
model["article"] = article
return "article"
}
fun Article.render() = RenderedArticle(
slug, title, headline, content, author, addedAt.format()
)
data class RenderedArticle(
val slug: String,
val title: String,
val headline: String,
val content: String,
val author: User,
val addedAt: String
)
}
HttpController.kt
@RestController
@RequestMapping("/api/article")
class ArticleController(private val repository: ArticleRepository){
@GetMapping("/")
fun findAll() = repository.findAllByOrderByAddedAtDesc()
@GetMapping("/{slug}")
fun findOne(@PathVariable slug:String)=repository.findBySlug(slug)?:
throw ResponseStatusException(HttpStatus.NOT_FOUND,"This article does not exist")
}
@RestController
@RequestMapping("/api/user")
class UserController(private val repository: UserRepository){
@GetMapping("/")
fun findAll() = repository.findAll()
@GetMapping("/{login}")
fun findOne(@PathVariable login:String)=repository.findByLogin(login)?:
throw ResponseStatusException(HttpStatus.NOT_FOUND,"This user does not exist")
}
@RestController
@RequestMapping("/api/product")
class ProductController(private val repository: ProductRepository){
@GetMapping("/")
fun findAll() = repository.findAll()
}
@RestController
@RequestMapping("/api/category")
class CategoryController(private val repository:CategoryRepository){
@GetMapping("/")
fun findAll() = repository.findAll()
}
Extensions.kt
import java.time.LocalDateTime
import java.time.format.DateTimeFormatterBuilder
import java.time.temporal.ChronoField
import java.util.*
fun LocalDateTime.format() = this.format(englishDateFormatter)
private val daysLookup = (1..31).associate { it.toLong() to getOrdinal(it) }
private val englishDateFormatter = DateTimeFormatterBuilder()
.appendPattern("yyyy-MM-dd")
.appendLiteral(" ")
.appendText(ChronoField.DAY_OF_MONTH, daysLookup)
.appendLiteral(" ")
.appendPattern("yyyy")
.toFormatter(Locale.ENGLISH)
private fun getOrdinal(n: Int) = when {
n in 11..13 -> "${n}th"
n % 10 == 1 -> "${n}st"
n % 10 == 2 -> "${n}nd"
n % 10 == 3 -> "${n}rd"
else -> "${n}th"
}
fun String.toSlug() = toLowerCase()
.replace("\n", " ")
.replace("[^a-z\\d\\s]".toRegex(), " ")
.split(" ")
.joinToString("-")
.replace("-+".toRegex(), "-")
@SpringBootApplication
@EnableConfigurationProperties(BlogProperties::class)
class Springbootkotlin1Application
fun main(args: Array<String>) {
runApplication<Springbootkotlin1Application>(*args){
setBannerMode(Banner.Mode.OFF)
}
}
blog.mustache
{{> header}}
<h1>{{title}}h1>
<div class="articles">
{{#banner.title}}
<section>
<header class="banner">
<h2 class="banner-title">{{banner.title}}h2>
header>
<div class="banner-content">
{{banner.content}}
div>
section>
{{/banner.title}}
{{#articles}}
<section>
<header class="article-header">
<h2 class="article-title">
<a href="/article/{{slug}}">{{title}}a>
h2>
<div class="article-meta">By
<strong>{{author.firstname}}strong>, on <strong>{{addedAt}}strong>
div>
header>
<div class="article-description">
{{headline}}
div>
section>
{{/articles}}
div>
<br><br><br>
<div class="title"><strong>网上商城strong>div>
<div class="hyperlink">
<a href="/api/product/">商品列表a>
<a href="/api/category/">类型列表a>
div>
{{> footer}}
header.mustache
<html>
<head>
<title>{{title}}title>
head>
<body>
article.mustache
{{> header}}
<section class="article">
<header class="article-header">
<h1 class="article-title">{{article.title}}h1>
<p class="article-meta">By
<strong>{{article.author.firstname}}strong>, on
<strong>{{article.addedAt}}strong>
p>
header>
<div class="article-description">
{{article.headline}}
{{article.content}}
div>
section>
{{> footer}}
footer.mustache
body>
html>
blog.title=Blog
blog.banner.title=Warning
blog.banner.content=The blog will be down tomorrow.
rootProject.name = "springbootkotlin1"
Building web applications with Spring Boot and Kotlin
Spring Boot Kubernetes