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() { + } + +}