初始化

This commit is contained in:
jimlee
2023-01-10 17:11:39 +08:00
parent 8030d31753
commit aab0134c8e
28 changed files with 1175 additions and 0 deletions
+155
View File
@@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/>
</parent>
<artifactId>temp-clipboard</artifactId>
<groupId>com.pomelotea</groupId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>consoleApp</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
<kotlin.version>1.7.0</kotlin.version>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<outputDirectory>./target</outputDirectory>
<classifier>executable</classifier>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>1.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>com.parkingwang</groupId>
<artifactId>okhttp3-loginterceptor</artifactId>
<version>0.5</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,23 @@
package com.pomelotea
import org.slf4j.LoggerFactory
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import java.text.SimpleDateFormat
import java.util.*
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-07-15 10:53
* 启动入口
**/
@SpringBootApplication
open class BootApplication {
}
private val logger = LoggerFactory.getLogger("APPLICATION-STARTER")
fun main(args: Array<String>) {
SpringApplication.run(BootApplication::class.java, *args)
logger.info("临时粘贴板服务启动成功:{}", SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(Date()))
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,66 @@
package com.pomelotea.api
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.time.Duration
import javax.servlet.http.HttpServletRequest
/**
*
* @version 0.0.1
* @author jimlee
* date 2022-11-02 10:29
*
**/
@RestController
class HealthCodeController {
val json = """
{
"success": true,
"errorCode": null,
"errorMsg": null,
"result": "{\"reason\":\"0\",\"zlbLogoSize\":30,\"vaccinated\":\"3\",\"modifiedTimes\":0,\"logoUrl\":\"//img.alicdn.com/imgextra/i1/O1CN01RTcWEm1mriBsqhQ3Q_!!6000000005008-2-tps-516-516.png\",\"uid\":2945226479,\"descEn\":\"Green Code: It gives you greenlight at public venues and major checkpoints in Zhejiang;Be attentive to your health condition;the code color might change when your health status or local regulations change.\",\"descCn\":\"绿码:凭此码可在浙江省范围内通行,请主动出示,配合检查;并做好自身防护工作,码颜色将根据您的申报由当地政府按照相关政策动态更新,出行前请仔细检查您的健康。\",\"logoSize\":30,\"vaccinatedSwitch\":true,\"name\":\"李建明\",\"healthRecord\":{\"colorCss\":\"#27AE64\",\"qrCode\":\"V0dinga7e710d462f82d242e2feadba6d9041e\",\"feControlModel\":{\"tips\":\"\",\"enTips\":\"\"},\"stateCouncilBarCode\":\"d8cbdc8eb52a2fcc9bc6864bccd8f917\",\"eHealthCode\":\"\",\"qrColor\":\"green\",\"extInfo\":\"{\\\"nucleicData\\\":{\\\"nucleicDescriptionEn\\\":\\\"d\\\",\\\"nucleicDescriptionCn\\\":\\\"天内\\\",\\\"countDownDescriptionEn\\\":\\\"3 Days To Maturity\\\",\\\"nucleicStatusCn\\\":\\\"阴性\\\",\\\"countDownDescriptionCn\\\":\\\"距离3天到期还剩\\\",\\\"nucleicStatusEn\\\":\\\"Neg\\\",\\\"expireSpecVal\\\":\\\"168\\\",\\\"showCompareTime\\\":true,\\\"lessthenDayNumbersDisplayAsHours\\\":2,\\\"compareType\\\":\\\"report\\\",\\\"checkTime\\\":\\\"2022-10-30 10:48:06\\\",\\\"hourValue\\\":67.0,\\\"nucleicDayEn\\\":\\\"≤3\\\",\\\"timeInterval\\\":\\\"72\\\",\\\"nucleicDayCn\\\":3,\\\"countDown\\\":17608,\\\"compareTime\\\":\\\"2022-10-30 15:21:44\\\",\\\"reportTime\\\":\\\"2022-10-30 15:21:44\\\",\\\"recentNoNucleicDay\\\":3}}\"},\"descCss\":\"#333333\",\"config\":\"{\\\"travelCardUrl\\\":\\\"alipays://platformapi/startapp?appId=2019011763060066&page=pages%2Fjourney-webview%2Fjourney-webview\\\",\\\"travelCardUrlH5\\\":\\\"https://xc.caict.ac.cn/?code=152&phone={phone}&t={timestamp}&ad=1\\\"}\"}",
"arguments": null
}
""".trimIndent()
@PostMapping("/unAuthLwp/queryHealthInfoByAuthCode")
fun queryHealthInfoByAuthCode(@RequestParam args: String, request: HttpServletRequest): String? {
val client = OkHttpClient()
.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.callTimeout(Duration.ofSeconds(10))
.build()
val headerList: MutableList<String> = ArrayList()
val reqHeaders = request.headerNames
for (header in reqHeaders) {
headerList.add(header)
headerList.add(request.getHeader(header))
}
headerList.add("Cookie")
headerList.add(request.cookies.toString())
val headers: Headers = Headers.headersOf(*headerList.toTypedArray())
val request = Request.Builder()
.url("https://healthcode.dingtalk.com/unAuthLwp/queryHealthInfoByAuthCode")
.post(
FormBody.Builder().add("args", args).build()
)
.headers(headers)
.build()
val response = client.newCall(request).execute()
val bodyString: String? = response.body?.string()
println(bodyString)
return json
}
}
@@ -0,0 +1,83 @@
//package com.pomelotea.api
//
//import org.springframework.web.bind.annotation.CookieValue
//import org.springframework.web.bind.annotation.GetMapping
//import org.springframework.web.bind.annotation.PathVariable
//import org.springframework.web.bind.annotation.PutMapping
//import org.springframework.web.bind.annotation.RequestBody
//import org.springframework.web.bind.annotation.RequestMapping
//import org.springframework.web.bind.annotation.RestController
//import org.springframework.web.servlet.view.RedirectView
//import java.nio.charset.StandardCharsets
//import java.nio.file.Files
//import java.nio.file.Paths
//import java.util.concurrent.atomic.AtomicInteger
//import javax.servlet.http.Cookie
//import javax.servlet.http.HttpServletRequest
//import javax.servlet.http.HttpServletResponse
//
///**
// *
// * @version 0.0.1
// * @author jimlee
// * date 2022-08-04 12:53
// *
// **/
//@RestController
//class TempClipboardController {
//
// @GetMapping("/clipboard")
// fun getValue(@CookieValue(value = "clipId", defaultValue = "-1") clipId: Int): String {
// val tempData = clipMap[clipId] ?: return ""
// return if (tempData.type == Type.String) {
// tempData.value ?: ""
// } else {
// Files.readAllBytes(Paths.get("./temp/$clipId")).toString(StandardCharsets.UTF_8)
// }
// }
//
// @GetMapping("/c/{clipId}")
// fun redirectToHome(@PathVariable clipId: Int,
// request: HttpServletRequest,
// response: HttpServletResponse): RedirectView {
// response.addCookie(Cookie("clipId", clipId.toString()))
// return RedirectView("https://c.pomelotea.com", true, false)
// }
//
// @PutMapping("/clipboard")
// fun putValue(@RequestBody putDataRequest: PutDataRequest,
// @CookieValue(value = "clipId", defaultValue = "-1") clipId: Int): Int? {
// if (putDataRequest.value == null) {
// return null
// }
//
// val id: Int = if (clipId != -1) clipId else idInteger.incrementAndGet()
// clipMap[id] = if (putDataRequest.value.length > 10240) {
// Files.write(Paths.get("./temp/$id"), putDataRequest.value.toByteArray(StandardCharsets.UTF_8))
// TempData(Type.File)
// } else {
// TempData(Type.String, putDataRequest.value)
// }
// return id
// }
//
// companion object {
// private val clipMap: MutableMap<Int, TempData> = HashMap()
// private val idInteger: AtomicInteger = AtomicInteger(0)
// }
//}
//
//data class PutDataRequest(
// val id: Int? = null,
// val value: String? = null
//)
//
//data class TempData(
// val type: Type,
// val value: String? = null
//)
//
//enum class Type {
// String,
// File
//}
BIN
View File
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
server:
port: 8081
spring:
servlet:
multipart:
max-request-size: 100MB
max-file-size: 100MB
application:
name: 临时粘贴板
+300
View File
@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>临时粘贴板</title>
<link rel="stylesheet" type="text/css" href="./waves.min.css?v=0.7.2">
<style>
body {
width: 100%;
margin: 5px -10px;
padding: 0 5px 0 14px;
position: absolute;
}
/* 输入框 */
.dzm-textarea {
padding: 5px;
border: 1px solid red;
width: 98%;
border-radius: 3px;
position: fixed;
}
/* 输入框为空时显示 placeholder */
.dzm-textarea:empty:before {
content: attr(placeholder);
color: red;
}
/* 输入框获取焦点时移除 placeholder */
.dzm-textarea:focus:before {
content: none;
}
.textcopy {
position: fixed;
height: 40px;
width: 40px;
top: 50%;
right: 1%;
transform: translateX(-50%) translateY(-50%);
}
.textcopy a,
.textcopy a:hover {
background: #01BCFF;
margin: 5px;
right: 10px;
}
.btn {
color: #fff;
}
</style>
<script type="text/javascript" src="./jquery.min.js"></script>
<script type="text/javascript" src="./waves.min.js?v=0.7.1"></script>
</head>
<body>
<!-- textarea -->
<div oninput="textInput()" id="textarea" class="dzm-textarea" contenteditable="true" placeholder="请输入内容"
style="resize: both; overflow: auto;"></div>
<label for="copy_content"></label><input id="copy_content" type="text" value="" style="position: fixed;top: -100px;z-index: -10;"/>
<div class="textcopy">
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="copyText()">复制</a>
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="pasteText()">粘贴</a>
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="openUrl()">打开</a>
</div>
<script>
Waves.attach('.flat-buttons', ['waves-button']);
Waves.attach('.float-buttons', ['waves-button', 'waves-float']);
Waves.attach('.float-button-light', ['waves-button', 'waves-float', 'waves-light']);
$(document).on('ready', function () {
Waves.init();
Waves.attach('.drag-ripple', 'waves-block', true);
Waves.attach('#bg-pattern', null, true);
});
const textarea = document.querySelector(".dzm-textarea")
function copyText() {
let nowValue = textarea.innerText;
if (nowValue === "") {
nowValue = document.querySelector("#textarea > img").src
// base64 image
if (nowValue.startsWith("data")) {
let type = nowValue.substring(5, nowValue.indexOf(';'))
let clipboardItemInput
switch (type) {
case "image/png":
clipboardItemInput = new ClipboardItem({
"image/png": nowValue.slice(23),
});
break;
case "image/jpeg":
clipboardItemInput = new ClipboardItem({
"image/jpeg": nowValue.slice(23),
});
break;
case "image/gif":
clipboardItemInput = new ClipboardItem({
"image/gif": nowValue.slice(23),
});
break;
case "application/pdf":
clipboardItemInput = new ClipboardItem({
"application/pdf": nowValue.slice(23),
});
break;
case "application/vnd.ms-excel":
clipboardItemInput = new ClipboardItem({
"application/vnd.ms-excel": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.presentationml.presentation": nowValue.slice(23),
});
break;
case "application/vnd.ms-powerpoint":
clipboardItemInput = new ClipboardItem({
"application/vnd.ms-powerpoint": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": nowValue.slice(23),
});
break;
case "application/msword":
clipboardItemInput = new ClipboardItem({
"application/msword": nowValue.slice(23),
});
break;
}
if (clipboardItemInput != null) {
navigator.clipboard.write([clipboardItemInput]);
}
} else {
const selection = window.getSelection();
if (selection.rangeCount > 0) selection.removeAllRanges();
const range = document.createRange();
range.selectNode(document.querySelector("#textarea > img")); //传入dom
selection.addRange(range);
document.execCommand("copy"); //copy是复制
selection.removeAllRanges(); //清除缓存
}
} else {
//获取要赋值的input的元素
var inputElement = document.getElementById("copy_content");
// 设置只读,否则移动端会弹出软键盘
inputElement.setAttribute("readonly", "readonly");
//给input框赋值
inputElement.value = textarea.innerText;
//选中input框的内容
inputElement.select();
inputElement.setSelectionRange(0, 9999999999999);
// 执行浏览器复制命令
// document.execCommand("Copy");
navigator.clipboard.writeText(textarea.innerText)
}
}
function pasteText() {
navigator.clipboard.readText()
.then(text => {
textarea.innerText = text
})
}
//
// function appendText() {
// navigator.clipboard.readText()
// .then(text => {
// textarea.innerText = textarea.innerText + text
// })
// }
function openUrl() {
window.open(textarea.innerText)
}
let lastUpdate = null
let lastValue = ""
let saveInterval
window.onload = function () {
loadValue()
// 监听页面状态
document.addEventListener('visibilitychange', function () {
// 页面状态变化为可见时触发
if (document.visibilityState === 'hidden') {
if (lastUpdate != null && Date.now() - lastUpdate > 5000) {
clearInterval(saveInterval)
}
} else if (document.visibilityState === 'visible') {
loadValue()
saveInterval = setInterval(() => {
saveValue()
}, 1000)
}
}
)
saveInterval = setInterval(() => {
saveValue()
}, 1000)
}
function saveValue() {
let nowValue = textarea.innerText;
if (nowValue === "") {
nowValue = textarea.innerHTML
}
if (lastValue !== nowValue && lastUpdate != null && Date.now() - lastUpdate > 3000) {
lastValue = nowValue
var httpRequest = new XMLHttpRequest();// new对象
httpRequest.open('PUT', '/clipboard', true);// get表示请求方式、url请求的地址
httpRequest.setRequestHeader("Content-type", "application/json");//post方式需要设置请求头
var reqParam = {
'value': nowValue,
}
httpRequest.send(JSON.stringify(reqParam));//发送请求 将请求体写在send中
// 返回处理
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
//success
}
};
}
}
function loadValue() {
var httpRequest = new XMLHttpRequest();// new对象
httpRequest.open('GET', window.origin + "/clipboard", true);// get表示请求方式、url请求的地址
httpRequest.send();// 发送请求
// 返回处理
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
if (httpRequest.responseText.startsWith("<img")) {
textarea.innerHTML = httpRequest.responseText
} else {
textarea.innerText = httpRequest.responseText.replaceAll("\n", "")
}
}
}
}
function textInput() {
lastUpdate = Date.now()
}
document.querySelector('#textarea').addEventListener('paste', function (e) {
var cbd = e.clipboardData;
var ua = window.navigator.userAgent;
// 如果是 Safari 直接 return
if (!(e.clipboardData && e.clipboardData.items)) {
return;
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) {
return;
}
for (var i = 0; i < cbd.items.length; i++) {
var item = cbd.items[i];
if (item.kind === "file") {
var blob = item.getAsFile();
if (blob.size === 0) {
return;
}
var reader = new FileReader();
var imgs = new Image();
imgs.file = blob;
reader.onload = (function (aImg) {
return function (e) {
aImg.src = e.target.result;
// chrome下有Bug显示两张图片,此处为兼容处理
let imgary = $('#textarea > img')
if (imgary.length > 1) {
imgary.eq(-1).remove()
}
};
})(imgs);
reader.readAsDataURL(blob);
document.querySelector("#textarea").innerHTML = ""
document.querySelector('#textarea').appendChild(imgs);
lastUpdate = Date.now()
}
}
}, false);
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
/*!
* Waves v0.7.2
* http://fian.my.id/Waves
*
* Copyright 2014 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE
*/.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:100px;height:100px;margin-top:-50px;margin-left:-50px;opacity:0;background:rgba(0,0,0,.2);background:-webkit-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:-o-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:-moz-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);-webkit-transition:all .5s ease-out;-moz-transition:all .5s ease-out;-o-transition:all .5s ease-out;transition:all .5s ease-out;-webkit-transition-property:-webkit-transform,opacity;-moz-transition-property:-moz-transform,opacity;-o-transition-property:-o-transform,opacity;transition-property:transform,opacity;-webkit-transform:scale(0) translate(0,0);-moz-transform:scale(0) translate(0,0);-ms-transform:scale(0) translate(0,0);-o-transform:scale(0) translate(0,0);transform:scale(0) translate(0,0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background:rgba(255,255,255,.4);background:-webkit-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:-o-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:-moz-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%)}.waves-effect.waves-classic .waves-ripple{background:rgba(0,0,0,.2)}.waves-effect.waves-classic.waves-light .waves-ripple{background:rgba(255,255,255,.4)}.waves-notransition{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.waves-button,.waves-circle{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle,#fff 100%,#000 100%)}.waves-button,.waves-button:hover,.waves-button:visited,.waves-button-input{white-space:nowrap;vertical-align:middle;cursor:pointer;border:none;outline:0;color:inherit;background-color:transparent;font-size:1em;line-height:1em;text-align:center;text-decoration:none;z-index:1}.waves-button{padding:.85em 1.1em;border-radius:.2em}.waves-button-input{margin:0;padding:.85em 1.1em}.waves-input-wrapper{border-radius:.2em;vertical-align:bottom}.waves-input-wrapper.waves-button{padding:0}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%}.waves-float{-webkit-mask-image:none;-webkit-box-shadow:0 1px 1.5px 1px rgba(0,0,0,.12);box-shadow:0 1px 1.5px 1px rgba(0,0,0,.12);-webkit-transition:all 300ms;-moz-transition:all 300ms;-o-transition:all 300ms;transition:all 300ms}.waves-float:active{-webkit-box-shadow:0 8px 20px 1px rgba(0,0,0,.3);box-shadow:0 8px 20px 1px rgba(0,0,0,.3)}.waves-block{display:block}a.waves-effect .waves-ripple{z-index:-1}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
server:
port: 8081
spring:
servlet:
multipart:
max-request-size: 100MB
max-file-size: 100MB
application:
name: 临时粘贴板
+300
View File
@@ -0,0 +1,300 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>临时粘贴板</title>
<link rel="stylesheet" type="text/css" href="./waves.min.css?v=0.7.2">
<style>
body {
width: 100%;
margin: 5px -10px;
padding: 0 5px 0 14px;
position: absolute;
}
/* 输入框 */
.dzm-textarea {
padding: 5px;
border: 1px solid red;
width: 98%;
border-radius: 3px;
position: fixed;
}
/* 输入框为空时显示 placeholder */
.dzm-textarea:empty:before {
content: attr(placeholder);
color: red;
}
/* 输入框获取焦点时移除 placeholder */
.dzm-textarea:focus:before {
content: none;
}
.textcopy {
position: fixed;
height: 40px;
width: 40px;
top: 50%;
right: 1%;
transform: translateX(-50%) translateY(-50%);
}
.textcopy a,
.textcopy a:hover {
background: #01BCFF;
margin: 5px;
right: 10px;
}
.btn {
color: #fff;
}
</style>
<script type="text/javascript" src="./jquery.min.js"></script>
<script type="text/javascript" src="./waves.min.js?v=0.7.1"></script>
</head>
<body>
<!-- textarea -->
<div oninput="textInput()" id="textarea" class="dzm-textarea" contenteditable="true" placeholder="请输入内容"
style="resize: both; overflow: auto;"></div>
<label for="copy_content"></label><input id="copy_content" type="text" value="" style="position: fixed;top: -100px;z-index: -10;"/>
<div class="textcopy">
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="copyText()">复制</a>
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="pasteText()">粘贴</a>
<a class="btn float-buttons waves-effect waves-button waves-float" onclick="openUrl()">打开</a>
</div>
<script>
Waves.attach('.flat-buttons', ['waves-button']);
Waves.attach('.float-buttons', ['waves-button', 'waves-float']);
Waves.attach('.float-button-light', ['waves-button', 'waves-float', 'waves-light']);
$(document).on('ready', function () {
Waves.init();
Waves.attach('.drag-ripple', 'waves-block', true);
Waves.attach('#bg-pattern', null, true);
});
const textarea = document.querySelector(".dzm-textarea")
function copyText() {
let nowValue = textarea.innerText;
if (nowValue === "") {
nowValue = document.querySelector("#textarea > img").src
// base64 image
if (nowValue.startsWith("data")) {
let type = nowValue.substring(5, nowValue.indexOf(';'))
let clipboardItemInput
switch (type) {
case "image/png":
clipboardItemInput = new ClipboardItem({
"image/png": nowValue.slice(23),
});
break;
case "image/jpeg":
clipboardItemInput = new ClipboardItem({
"image/jpeg": nowValue.slice(23),
});
break;
case "image/gif":
clipboardItemInput = new ClipboardItem({
"image/gif": nowValue.slice(23),
});
break;
case "application/pdf":
clipboardItemInput = new ClipboardItem({
"application/pdf": nowValue.slice(23),
});
break;
case "application/vnd.ms-excel":
clipboardItemInput = new ClipboardItem({
"application/vnd.ms-excel": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.presentationml.presentation": nowValue.slice(23),
});
break;
case "application/vnd.ms-powerpoint":
clipboardItemInput = new ClipboardItem({
"application/vnd.ms-powerpoint": nowValue.slice(23),
});
break;
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
clipboardItemInput = new ClipboardItem({
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": nowValue.slice(23),
});
break;
case "application/msword":
clipboardItemInput = new ClipboardItem({
"application/msword": nowValue.slice(23),
});
break;
}
if (clipboardItemInput != null) {
navigator.clipboard.write([clipboardItemInput]);
}
} else {
const selection = window.getSelection();
if (selection.rangeCount > 0) selection.removeAllRanges();
const range = document.createRange();
range.selectNode(document.querySelector("#textarea > img")); //传入dom
selection.addRange(range);
document.execCommand("copy"); //copy是复制
selection.removeAllRanges(); //清除缓存
}
} else {
//获取要赋值的input的元素
var inputElement = document.getElementById("copy_content");
// 设置只读,否则移动端会弹出软键盘
inputElement.setAttribute("readonly", "readonly");
//给input框赋值
inputElement.value = textarea.innerText;
//选中input框的内容
inputElement.select();
inputElement.setSelectionRange(0, 9999999999999);
// 执行浏览器复制命令
// document.execCommand("Copy");
navigator.clipboard.writeText(textarea.innerText)
}
}
function pasteText() {
navigator.clipboard.readText()
.then(text => {
textarea.innerText = text
})
}
//
// function appendText() {
// navigator.clipboard.readText()
// .then(text => {
// textarea.innerText = textarea.innerText + text
// })
// }
function openUrl() {
window.open(textarea.innerText)
}
let lastUpdate = null
let lastValue = ""
let saveInterval
window.onload = function () {
loadValue()
// 监听页面状态
document.addEventListener('visibilitychange', function () {
// 页面状态变化为可见时触发
if (document.visibilityState === 'hidden') {
if (lastUpdate != null && Date.now() - lastUpdate > 5000) {
clearInterval(saveInterval)
}
} else if (document.visibilityState === 'visible') {
loadValue()
saveInterval = setInterval(() => {
saveValue()
}, 1000)
}
}
)
saveInterval = setInterval(() => {
saveValue()
}, 1000)
}
function saveValue() {
let nowValue = textarea.innerText;
if (nowValue === "") {
nowValue = textarea.innerHTML
}
if (lastValue !== nowValue && lastUpdate != null && Date.now() - lastUpdate > 3000) {
lastValue = nowValue
var httpRequest = new XMLHttpRequest();// new对象
httpRequest.open('PUT', '/clipboard', true);// get表示请求方式、url请求的地址
httpRequest.setRequestHeader("Content-type", "application/json");//post方式需要设置请求头
var reqParam = {
'value': nowValue,
}
httpRequest.send(JSON.stringify(reqParam));//发送请求 将请求体写在send中
// 返回处理
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
//success
}
};
}
}
function loadValue() {
var httpRequest = new XMLHttpRequest();// new对象
httpRequest.open('GET', window.origin + "/clipboard", true);// get表示请求方式、url请求的地址
httpRequest.send();// 发送请求
// 返回处理
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState === 4 && httpRequest.status === 200) {
if (httpRequest.responseText.startsWith("<img")) {
textarea.innerHTML = httpRequest.responseText
} else {
textarea.innerText = httpRequest.responseText.replaceAll("\n", "")
}
}
}
}
function textInput() {
lastUpdate = Date.now()
}
document.querySelector('#textarea').addEventListener('paste', function (e) {
var cbd = e.clipboardData;
var ua = window.navigator.userAgent;
// 如果是 Safari 直接 return
if (!(e.clipboardData && e.clipboardData.items)) {
return;
}
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) {
return;
}
for (var i = 0; i < cbd.items.length; i++) {
var item = cbd.items[i];
if (item.kind === "file") {
var blob = item.getAsFile();
if (blob.size === 0) {
return;
}
var reader = new FileReader();
var imgs = new Image();
imgs.file = blob;
reader.onload = (function (aImg) {
return function (e) {
aImg.src = e.target.result;
// chrome下有Bug显示两张图片,此处为兼容处理
let imgary = $('#textarea > img')
if (imgary.length > 1) {
imgary.eq(-1).remove()
}
};
})(imgs);
reader.readAsDataURL(blob);
document.querySelector("#textarea").innerHTML = ""
document.querySelector('#textarea').appendChild(imgs);
lastUpdate = Date.now()
}
}
}, false);
</script>
</body>
</html>
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
/*!
* Waves v0.7.2
* http://fian.my.id/Waves
*
* Copyright 2014 Alfiana E. Sibuea and other contributors
* Released under the MIT license
* https://github.com/fians/Waves/blob/master/LICENSE
*/.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:100px;height:100px;margin-top:-50px;margin-left:-50px;opacity:0;background:rgba(0,0,0,.2);background:-webkit-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:-o-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:-moz-radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);background:radial-gradient(rgba(0,0,0,.2) 0,rgba(0,0,0,.3) 40%,rgba(0,0,0,.4) 50%,rgba(0,0,0,.5) 60%,rgba(255,255,255,0) 70%);-webkit-transition:all .5s ease-out;-moz-transition:all .5s ease-out;-o-transition:all .5s ease-out;transition:all .5s ease-out;-webkit-transition-property:-webkit-transform,opacity;-moz-transition-property:-moz-transform,opacity;-o-transition-property:-o-transform,opacity;transition-property:transform,opacity;-webkit-transform:scale(0) translate(0,0);-moz-transform:scale(0) translate(0,0);-ms-transform:scale(0) translate(0,0);-o-transform:scale(0) translate(0,0);transform:scale(0) translate(0,0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background:rgba(255,255,255,.4);background:-webkit-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:-o-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:-moz-radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%);background:radial-gradient(rgba(255,255,255,.2) 0,rgba(255,255,255,.3) 40%,rgba(255,255,255,.4) 50%,rgba(255,255,255,.5) 60%,rgba(255,255,255,0) 70%)}.waves-effect.waves-classic .waves-ripple{background:rgba(0,0,0,.2)}.waves-effect.waves-classic.waves-light .waves-ripple{background:rgba(255,255,255,.4)}.waves-notransition{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;transition:none!important}.waves-button,.waves-circle{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle,#fff 100%,#000 100%)}.waves-button,.waves-button:hover,.waves-button:visited,.waves-button-input{white-space:nowrap;vertical-align:middle;cursor:pointer;border:none;outline:0;color:inherit;background-color:transparent;font-size:1em;line-height:1em;text-align:center;text-decoration:none;z-index:1}.waves-button{padding:.85em 1.1em;border-radius:.2em}.waves-button-input{margin:0;padding:.85em 1.1em}.waves-input-wrapper{border-radius:.2em;vertical-align:bottom}.waves-input-wrapper.waves-button{padding:0}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%}.waves-float{-webkit-mask-image:none;-webkit-box-shadow:0 1px 1.5px 1px rgba(0,0,0,.12);box-shadow:0 1px 1.5px 1px rgba(0,0,0,.12);-webkit-transition:all 300ms;-moz-transition:all 300ms;-o-transition:all 300ms;transition:all 300ms}.waves-float:active{-webkit-box-shadow:0 8px 20px 1px rgba(0,0,0,.3);box-shadow:0 8px 20px 1px rgba(0,0,0,.3)}.waves-block{display:block}a.waves-effect .waves-ripple{z-index:-1}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
artifactId=temp-clipboard
groupId=com.pomelotea
version=1.0-SNAPSHOT
+101
View File
@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="web" name="Web">
<configuration>
<webroots />
<sourceRoots>
<root url="file://$MODULE_DIR$/src/main/kotlin" />
<root url="file://$MODULE_DIR$/src/main/resources" />
</sourceRoots>
</configuration>
</facet>
<facet type="Spring" name="Spring">
<configuration />
</facet>
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 1.8" allPlatforms="JVM [1.8]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-java-parameters" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="1.8" />
<stringArg name="apiVersion" arg="1.7" />
<stringArg name="languageVersion" arg="1.7" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths" />
<arrayArg name="pluginOptions" />
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test-junit5:1.7.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.jetbrains.kotlin:kotlin-test:1.7.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-engine:5.8.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-engine:1.7.2" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.2" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.7.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-common:1.7.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains:annotations:13.0" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.5.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.5.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.5.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.5.4" level="project" />
<orderEntry type="library" name="Maven: jakarta.annotation:jakarta.annotation-api:1.3.5" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-core:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.28" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.5.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.12.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.12.4" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:2.5.4" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:9.0.52" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:9.0.52" level="project" />
<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:9.0.52" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-web:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-beans:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-aop:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-context:5.3.9" level="project" />
<orderEntry type="library" name="Maven: org.springframework:spring-expression:5.3.9" level="project" />
<orderEntry type="library" name="Maven: com.alibaba:fastjson:2.0.7" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2-extension:2.0.7" level="project" />
<orderEntry type="library" name="Maven: com.alibaba.fastjson2:fastjson2:2.0.7" level="project" />
<orderEntry type="library" name="Maven: com.parkingwang:okhttp3-loginterceptor:0.5" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okhttp3:okhttp:4.2.2" level="project" />
<orderEntry type="library" name="Maven: com.squareup.okio:okio:2.2.2" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-kotlin:2.12.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.12.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.12.4" level="project" />
<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.12.4" level="project" />
<orderEntry type="library" name="Maven: org.jetbrains.kotlin:kotlin-reflect:1.7.0" level="project" />
<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.5.4" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.5" level="project" />
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.5" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.32" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.14.1" level="project" />
<orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.14.1" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.32" level="project" />
</component>
</module>
+2
View File
@@ -0,0 +1,2 @@
mvn clean package -DskipTests=true
scp target/temp-clipboard-1.0-SNAPSHOT-executable.jar sweetless@47.102.204.44:~/clipboard/