diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9e8c3bc
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.2
+
+
+ com.flagnote
+ flagnote-service
+ 0.0.1-SNAPSHOT
+ flagnote-service
+ Demo project for Spring Boot
+
+ 11
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ cn.hutool
+ hutool-all
+ 5.7.22
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/flagnote/note/NoteApplication.java b/src/main/java/com/flagnote/note/NoteApplication.java
new file mode 100644
index 0000000..ed095cf
--- /dev/null
+++ b/src/main/java/com/flagnote/note/NoteApplication.java
@@ -0,0 +1,16 @@
+package com.flagnote.note;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.web.bind.annotation.RestController;
+
+@SpringBootApplication
+@EnableAutoConfiguration
+@RestController
+public class NoteApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(NoteApplication.class, args);
+ }
+}
diff --git a/src/main/java/com/flagnote/note/controller/NoteController.java b/src/main/java/com/flagnote/note/controller/NoteController.java
new file mode 100644
index 0000000..1364114
--- /dev/null
+++ b/src/main/java/com/flagnote/note/controller/NoteController.java
@@ -0,0 +1,127 @@
+package com.flagnote.note.controller;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Date;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.flagnote.note.entity.KeyMeta;
+import com.flagnote.note.entity.Note;
+import com.flagnote.note.entity.NoteMeta;
+import com.flagnote.note.service.NoteService;
+import com.flagnote.note.utils.BizKeyUtils;
+
+import cn.hutool.core.date.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+
+
+@Slf4j
+@RestController
+public class NoteController {
+
+ @Autowired
+ private NoteService noteService;
+
+ @RequestMapping(value = "/note/keyMeta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public KeyMeta getKeyMeta() {
+ KeyMeta c = new KeyMeta();
+ String key = BizKeyUtils.getKey();
+ c.setKey(key);
+ c.setCipher(BizKeyUtils.getCipher(key));
+ c.setServerTime(new Date().getTime());
+ return c;
+ }
+
+ @RequestMapping(value = "/note/{key:[abcdefghijkmnopqrstuvwxyz23456789]{16}}/noteMeta", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+ public NoteMeta getNoteMeta(@PathVariable("key") String key) {
+ NoteMeta meta = new NoteMeta();
+ Note note = noteService.getNote(key);
+ if (null == note) {
+ return meta;
+ }
+
+ meta.setServerTime(new Date().getTime());
+ meta.setKey(key);
+ meta.setLock(note.getLock());
+ meta.setMd5(note.getMd5());
+
+ meta.setTtl(note.getTtl());
+
+ return meta;
+ }
+
+ @RequestMapping(value = "/note/{key:[abcdefghijkmnopqrstuvwxyz23456789]{16}}", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
+ public void getNote(@PathVariable("key") String key, HttpServletResponse response) {
+
+ Note note = noteService.getNote(key);
+
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
+ response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + key + "\"");
+
+ if(null==note) {
+ return;
+ }
+
+ OutputStream out = null;
+ try {
+ out = response.getOutputStream();
+ } catch (IOException e) {
+ log.error("获取写入流失败", e);
+ return;
+ }
+
+ try {
+ out.write(note.getTextBytes());
+ out.flush();
+ } catch (IOException e) {
+ log.error("写入流失败", e);
+ } finally {
+ if (null != out) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ log.error("关闭流失败", e);
+ }
+ }
+
+ }
+
+ }
+
+ @RequestMapping(value = "/note/{key:[abcdefghijkmnopqrstuvwxyz23456789]{16}}", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ public String saveNote(@PathVariable("key") String key, @RequestPart("file") MultipartFile file,
+ @RequestParam("lock") Integer lock, @RequestParam("md5") String md5) throws IOException {
+ Date pushTime = new Date();
+
+ Note note = new Note();
+ note.setKey(key);
+ note.setTextBytes(file.getBytes());
+ note.setLock(lock);
+ note.setMd5(md5);
+ note.setPushTime(pushTime);
+
+ noteService.saveNote(note);
+
+ return key;
+ }
+
+ @RequestMapping(value = "/note/{key:[abcdefghijkmnopqrstuvwxyz23456789]{16}}/delete", method = RequestMethod.POST, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
+ public String deleteNote(@PathVariable("key") String key) {
+ noteService.deleteNote(key);
+ return key;
+ }
+
+}
diff --git a/src/main/java/com/flagnote/note/entity/KeyMeta.java b/src/main/java/com/flagnote/note/entity/KeyMeta.java
new file mode 100644
index 0000000..ba9892a
--- /dev/null
+++ b/src/main/java/com/flagnote/note/entity/KeyMeta.java
@@ -0,0 +1,20 @@
+package com.flagnote.note.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Data;
+
+@Data
+public class KeyMeta implements Serializable {/**
+ *
+ */
+ private static final long serialVersionUID = -2816061962747625732L;
+
+ private String key;
+
+ private String cipher;
+
+ private Long serverTime;
+
+}
diff --git a/src/main/java/com/flagnote/note/entity/Note.java b/src/main/java/com/flagnote/note/entity/Note.java
new file mode 100644
index 0000000..6e15de9
--- /dev/null
+++ b/src/main/java/com/flagnote/note/entity/Note.java
@@ -0,0 +1,47 @@
+package com.flagnote.note.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import lombok.Data;
+
+@Data
+public class Note implements Serializable {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 5301542293133170670L;
+
+ private String id;
+
+ private String key;
+
+ private String text;
+
+ private String md5;
+
+ private Integer lock;
+
+ private String cipher;
+
+ private Integer state;
+
+ private Date pushTime;
+
+ private Date expireTime;
+
+ private Date retentionTime;
+
+ private byte[] textBytes;
+
+ public Long getTtl() {
+ if (null == pushTime || null == expireTime) {
+ return null;
+ }
+
+ Date now = new Date();
+ return expireTime.getTime() - now.getTime();
+ }
+
+}
diff --git a/src/main/java/com/flagnote/note/entity/NoteMeta.java b/src/main/java/com/flagnote/note/entity/NoteMeta.java
new file mode 100644
index 0000000..de3d62b
--- /dev/null
+++ b/src/main/java/com/flagnote/note/entity/NoteMeta.java
@@ -0,0 +1,28 @@
+package com.flagnote.note.entity;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+@Data
+public class NoteMeta implements Serializable {/**
+ *
+ */
+ private static final long serialVersionUID = -8234044213813670440L;
+
+/**
+ *
+ */
+
+ private String key;
+
+ private String cipher;
+
+ private Integer lock;
+
+ private String md5;
+
+ private Long serverTime;
+
+ private Long ttl;
+}
diff --git a/src/main/java/com/flagnote/note/entity/Secret.java b/src/main/java/com/flagnote/note/entity/Secret.java
new file mode 100644
index 0000000..f416b54
--- /dev/null
+++ b/src/main/java/com/flagnote/note/entity/Secret.java
@@ -0,0 +1,24 @@
+package com.flagnote.note.entity;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+
+
+@Data
+public class Secret implements Serializable {
+
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -4125184146529253566L;
+
+ private String key;
+
+ private String password;
+
+ private String secret;
+
+}
diff --git a/src/main/java/com/flagnote/note/repository/NoteMongoRepository.java b/src/main/java/com/flagnote/note/repository/NoteMongoRepository.java
new file mode 100644
index 0000000..507cbc4
--- /dev/null
+++ b/src/main/java/com/flagnote/note/repository/NoteMongoRepository.java
@@ -0,0 +1,11 @@
+package com.flagnote.note.repository;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+import com.flagnote.note.entity.Note;
+
+@Repository
+public interface NoteMongoRepository extends MongoRepository {
+
+}
diff --git a/src/main/java/com/flagnote/note/service/NoteService.java b/src/main/java/com/flagnote/note/service/NoteService.java
new file mode 100644
index 0000000..47d7609
--- /dev/null
+++ b/src/main/java/com/flagnote/note/service/NoteService.java
@@ -0,0 +1,13 @@
+package com.flagnote.note.service;
+
+import com.flagnote.note.entity.Note;
+
+public interface NoteService {
+
+ Note getNote(String key);
+
+ void saveNote(Note note);
+
+ void deleteNote(String key);
+
+}
diff --git a/src/main/java/com/flagnote/note/service/NoteServiceImpl.java b/src/main/java/com/flagnote/note/service/NoteServiceImpl.java
new file mode 100644
index 0000000..4370c6e
--- /dev/null
+++ b/src/main/java/com/flagnote/note/service/NoteServiceImpl.java
@@ -0,0 +1,70 @@
+package com.flagnote.note.service;
+
+import java.util.Date;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.ExampleMatcher;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.stereotype.Service;
+
+import com.flagnote.note.entity.Note;
+import com.flagnote.note.repository.NoteMongoRepository;
+import com.flagnote.note.utils.BizKeyUtils;
+
+import cn.hutool.core.date.DateUtil;
+
+@Service
+public class NoteServiceImpl implements NoteService {
+
+ @Autowired
+ private NoteMongoRepository noteMongoRepository;
+
+ @Override
+ public Note getNote(String key) {
+
+ String mixKey = BizKeyUtils.mixKey(key);
+
+ Note note = noteMongoRepository.findById(mixKey).orElse(null);
+
+ if (null == note) {
+ return null;
+ }
+
+ if (note.getState() != 1) {
+ return null;
+ }
+
+ if (note.getExpireTime().getTime() <= new Date().getTime()) {
+ return null;
+ }
+
+ return note;
+
+ }
+
+ @Override
+ public void saveNote(Note note) {
+ String mixKey = BizKeyUtils.mixKey(note.getKey());
+
+ note.setId(mixKey);
+ note.setKey(mixKey);
+
+
+ note.setExpireTime(DateUtil.offsetHour(note.getPushTime(), 1));
+ note.setRetentionTime(DateUtil.offsetSecond(note.getPushTime(), 4000 * 3600));
+
+ note.setState(1);
+
+ noteMongoRepository.save(note);
+ }
+
+ @Override
+ public void deleteNote(String key) {
+ String mixKey = BizKeyUtils.mixKey(key);
+
+ noteMongoRepository.deleteById(mixKey);
+ }
+
+}
diff --git a/src/main/java/com/flagnote/note/utils/BizKeyUtils.java b/src/main/java/com/flagnote/note/utils/BizKeyUtils.java
new file mode 100644
index 0000000..54ecb43
--- /dev/null
+++ b/src/main/java/com/flagnote/note/utils/BizKeyUtils.java
@@ -0,0 +1,95 @@
+package com.flagnote.note.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+
+import org.springframework.util.DigestUtils;
+
+public class BizKeyUtils {
+
+ public static final String MIX_STRING = "6v8muhqp8ta45ncsyi8y";
+
+ public static final String RANGE_STRING = "abcdefhikmnopqstuvwxyz23456789";
+
+ public static String getKey() {
+
+ StringBuffer sb = new StringBuffer();
+
+ for (int i = 0; i < 13; i++) {
+ sb.append(RANGE_STRING.charAt(RandomUtils.nextInt(30)));
+ }
+
+ sb.append(getFilteredKey(md5(sb.toString() + MIX_STRING + getPrefixTime())));
+
+ return sb.toString();
+ }
+
+ public static String getFilteredKey(String key) {
+ String result = "";
+ for (int i = 0; i < key.length(); i++) {
+ String ts = String.valueOf(key.charAt(i));
+ if (RANGE_STRING.indexOf(ts) >= 0) {
+ result += ts;
+ }
+
+ if (result.length() == 3) {
+ return result;
+ }
+ }
+
+ for (int i = 1; i < 3; i++) {
+ result += "x";
+ }
+
+ return result;
+ }
+
+ public static Boolean validateKey(String key) {
+ String oKey = key.substring(0, 13);
+
+ String cKey = oKey + getFilteredKey(md5(oKey + MIX_STRING + getPrefixTime()));
+
+ if (cKey.equals(key)) {
+ return true;
+ }
+
+ cKey = oKey + getFilteredKey(md5(oKey + MIX_STRING + (getPrefixTime() - 1)));
+
+ if (cKey.equals(key)) {
+ return true;
+ }
+
+ cKey = oKey + getFilteredKey(md5(oKey + MIX_STRING + (getPrefixTime() + 1)));
+
+ if (cKey.equals(key)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static String mixKey(String key) {
+ return md5(key + md5(MIX_STRING + key));
+ }
+
+ public static String md5(String text) {
+ try {
+ return DigestUtils.md5DigestAsHex(text.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException("无法生成MD5");
+ }
+ }
+
+ public static Integer getPrefixTime() {
+ return Integer.parseInt(String.valueOf(new Date().getTime()).substring(0, 4));
+ }
+
+ public static String getCipher(String key) {
+ return md5(key + MIX_STRING + key);
+ }
+
+ public static String getSecretKey(String key, String password) {
+ return md5(key + md5(MIX_STRING + password));
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/flagnote/note/utils/JsonUtils.java b/src/main/java/com/flagnote/note/utils/JsonUtils.java
new file mode 100644
index 0000000..500d306
--- /dev/null
+++ b/src/main/java/com/flagnote/note/utils/JsonUtils.java
@@ -0,0 +1,112 @@
+package com.flagnote.note.utils;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+
+import org.springframework.util.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class JsonUtils {
+
+ private static ObjectMapper om = new ObjectMapper();
+
+ static {
+
+ // 对象的所有字段全部列入,还是其他的选项,可以忽略null等
+ om.setSerializationInclusion(Include.ALWAYS);
+ // 设置Date类型的序列化及反序列化格式
+ om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
+
+ // 忽略空Bean转json的错误
+ om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ // 忽略未知属性,防止json字符串中存在,java对象中不存在对应属性的情况出现错误
+ om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ // 注册一个时间序列化及反序列化的处理模块,用于解决jdk8中localDateTime等的序列化问题
+ om.registerModule(new JavaTimeModule());
+ }
+
+ /**
+ * 对象 => json字符串
+ *
+ * @param obj 源对象
+ */
+ public static String toJson(T obj) {
+
+ String json = null;
+ if (obj != null) {
+ try {
+ json = om.writeValueAsString(obj);
+ } catch (JsonProcessingException e) {
+ log.warn(e.getMessage(), e);
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+ return json;
+ }
+
+ /**
+ * json字符串 => 对象
+ *
+ * @param json 源json串
+ * @param clazz 对象类
+ * @param 泛型
+ */
+ public static T parse(String json, Class clazz) {
+
+ return parse(json, clazz, null);
+ }
+
+ /**
+ * json字符串 => 对象
+ *
+ * @param json 源json串
+ * @param type 对象类型
+ * @param 泛型
+ */
+ public static T parse(String json, TypeReference type) {
+
+ return parse(json, null, type);
+ }
+
+
+ /**
+ * json => 对象处理方法
+ *
+ * 参数clazz和type必须一个为null,另一个不为null
+ *
+ * 此方法不对外暴露,访问权限为private
+ *
+ * @param json 源json串
+ * @param clazz 对象类
+ * @param type 对象类型
+ * @param 泛型
+ */
+ private static T parse(String json, Class clazz, TypeReference type) {
+
+ T obj = null;
+ if (StringUtils.hasLength(json)) {
+ try {
+ if (clazz != null) {
+ obj = om.readValue(json, clazz);
+ } else {
+ obj = om.readValue(json, type);
+ }
+ } catch (IOException e) {
+ log.warn(e.getMessage(), e);
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+ return obj;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/flagnote/note/utils/RandomUtils.java b/src/main/java/com/flagnote/note/utils/RandomUtils.java
new file mode 100644
index 0000000..1ccc72b
--- /dev/null
+++ b/src/main/java/com/flagnote/note/utils/RandomUtils.java
@@ -0,0 +1,15 @@
+package com.flagnote.note.utils;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class RandomUtils {
+
+
+ public static ThreadLocalRandom getRandom() {
+ return ThreadLocalRandom.current();
+ }
+
+ public static Integer nextInt(Integer num) {
+ return getRandom().nextInt(num);
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..08013a8
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+server:
+ port: 3333
+spring:
+ codec:
+ max-in-memory-size: 128MB
+ data:
+ mongodb:
+ authentication-database: flagnote
+ database: flagnote
+ host: service.flagnote.com
+ port: 37017
+ username: flagnote
+ password: flagnote1qazx
+ autoConnectRetry: true
+ socketKeepAlive: true
+ socketTimeout: 1000
\ No newline at end of file
diff --git a/src/test/java/com/flagnote/note/NoteApplicationTests.java b/src/test/java/com/flagnote/note/NoteApplicationTests.java
new file mode 100644
index 0000000..0709415
--- /dev/null
+++ b/src/test/java/com/flagnote/note/NoteApplicationTests.java
@@ -0,0 +1,13 @@
+package com.flagnote.note;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class NoteApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}