diff --git a/.gitignore b/.gitignore
index 549e00a..0a639b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,10 @@ build/
### VS Code ###
.vscode/
+/uploadApk/file/config.mdpi.apk
+/uploadApk/file/FLYSN-68-V3.2.8-20240829-192522-G6Release.apk
+/uploadApk/file/FLYSN-71-V3.3.1-20250307-175850-Huaruian8768Release.apk
+/uploadApk/file/MIR4_0.442318_APKPure.xapk
+/uploadApk/icon/7110e12ad7dec55cf925b72f266df7ed.png
+/uploadApk/icon/268119212d35253700c802830449316a.png
+/uploadApk/icon/d016c8c75162d1ae4c3ffbf7a178a111.png
diff --git a/pom.xml b/pom.xml
index 5f28260..80e5f23 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,12 +49,51 @@
spring-boot-starter-test
test
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ 3.0.3
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 3.0.3
+
mysql
mysql-connector-java
runtime
8.0.33
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ com.google.code.gson
+ gson
+
+
+
+ net.dongliu
+ apk-parser
+ 2.6.10
+
@@ -62,6 +101,7 @@
org.apache.maven.plugins
maven-compiler-plugin
+ 3.13.0
@@ -74,10 +114,12 @@
org.graalvm.buildtools
native-maven-plugin
+ 0.10.6
org.springframework.boot
spring-boot-maven-plugin
+ 3.4.4
diff --git a/src/main/java/com/mir4updater/backend/Application.java b/src/main/java/com/mir4updater/backend/Application.java
index 2a48649..42088c8 100644
--- a/src/main/java/com/mir4updater/backend/Application.java
+++ b/src/main/java/com/mir4updater/backend/Application.java
@@ -1,9 +1,16 @@
package com.mir4updater.backend;
+import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
@SpringBootApplication
+@EnableScheduling
+@MapperScan({"com.mir4updater.backend.mapper", "com.mir4updater.backend.api"})
+@EnableCaching
public class Application {
public static void main(String[] args) {
diff --git a/src/main/java/com/mir4updater/backend/controller/HelloController.java b/src/main/java/com/mir4updater/backend/controller/HelloController.java
new file mode 100644
index 0000000..6ee233d
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/controller/HelloController.java
@@ -0,0 +1,45 @@
+package com.mir4updater.backend.controller;
+
+import com.mir4updater.backend.result.Result;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloController {
+ //引入 redis
+ @Autowired
+ private StringRedisTemplate stringRedisTemplate;
+
+ @GetMapping("/")
+ public Result getMethodName() {
+ return Result.ok();
+ }
+
+ /**
+ * 存储
+ *
+ * @return
+ */
+ @PostMapping("/set")
+ public String setRedis(@RequestParam(value = "username") String username) {
+ //存储 key-value 键值对: "username"-"jaychou"
+ stringRedisTemplate.opsForValue().set("username", username);
+ return "redis 存储成功!";
+ }
+
+ /**
+ * 读取
+ *
+ * @return
+ */
+ @GetMapping("/get")
+ public String getRedis() {
+ //通过 key 值读取 value
+ String result = stringRedisTemplate.opsForValue().get("username");
+ return result;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/controller/apk/CheckUpdateController.java b/src/main/java/com/mir4updater/backend/controller/apk/CheckUpdateController.java
new file mode 100644
index 0000000..5b645c3
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/controller/apk/CheckUpdateController.java
@@ -0,0 +1,54 @@
+package com.mir4updater.backend.controller.apk;
+
+import com.mir4updater.backend.entity.ApkInfo;
+import com.mir4updater.backend.result.Result;
+import com.mir4updater.backend.service.ApkInfoService;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+
+@RestController
+public class CheckUpdateController {
+ public static Logger logger = LogManager.getLogger(UploadApkController.class);
+
+ @Autowired
+ private ApkInfoService apkInfoService;
+
+ @GetMapping("/android/check_update")
+ public Result checkUpdate(@RequestParam("pkg") String pkg, @RequestParam(value = "version_code", required = false, defaultValue = "0") Long versionCode) {
+ List apkInfoList = apkInfoService.getApkInfo(pkg);
+ Optional apkInfoOptional = apkInfoList.stream().max(new Comparator() {
+ @Override
+ public int compare(ApkInfo o1, ApkInfo o2) {
+ return Long.compare(o1.getVersionCode(), o2.getVersionCode());
+ }
+ });
+ if (apkInfoOptional.isPresent()) {
+ if (versionCode == 0) {
+ return Result.ok().data("apkInfo", apkInfoOptional.get());
+ } else {
+ if (apkInfoOptional.get().getVersionCode() > versionCode) {
+ return Result.ok().data("apkInfo", apkInfoOptional.get());
+ } else {
+ return Result.ok().message("没有更新");
+ }
+ }
+ } else {
+ return Result.notFound();
+ }
+
+ }
+
+ @GetMapping("/android/get_all")
+ public Result getAll() {
+ List apkInfoList = apkInfoService.getAll();
+ return Result.ok().data("apkInfo", apkInfoList);
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java b/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java
new file mode 100644
index 0000000..fad6f04
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/controller/apk/UploadApkController.java
@@ -0,0 +1,265 @@
+package com.mir4updater.backend.controller.apk;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mir4updater.backend.entity.ApkInfo;
+import com.mir4updater.backend.entity.XapkManifest;
+import com.mir4updater.backend.result.Result;
+import com.mir4updater.backend.service.ApkInfoService;
+import com.mir4updater.backend.utils.FileUtils;
+import com.mir4updater.backend.utils.HashUtils;
+import net.dongliu.apk.parser.ApkFile;
+import net.dongliu.apk.parser.bean.ApkMeta;
+import net.dongliu.apk.parser.bean.IconFace;
+import net.dongliu.apk.parser.bean.UseFeature;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.tomcat.util.http.fileupload.FileUploadException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Optional;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+@RestController
+public class UploadApkController {
+ public static Logger logger = LogManager.getLogger(UploadApkController.class);
+
+ @Autowired
+ private ApkInfoService apkInfoService;
+ private static final String UPLOAD_PATH = "uploadApk";
+ private static final String ApkFilePath = "file";
+ private static final String ICON_PATH = "icon";
+
+ @PostMapping("/android/upload_apk")
+ public Result uploadApk(@RequestParam("file") MultipartFile multipartFile) throws Exception {
+ if (multipartFile != null && !multipartFile.isEmpty()) {
+ String projectPath = System.getProperty("user.dir");
+ logger.info("当前项目路径为:" + projectPath);
+ String dirPath = projectPath + File.separator + UPLOAD_PATH + File.separator + ApkFilePath;
+ String iconPath = projectPath + File.separator + UPLOAD_PATH + File.separator + ICON_PATH + File.separator;
+ File dirFile = new File(dirPath);
+ if (!dirFile.exists()) {
+ dirFile.mkdirs();
+ }
+ File iconFilePath = new File(iconPath);
+ if (!iconFilePath.exists()) {
+ iconFilePath.mkdirs();
+ }
+ //1.接收上传的应用,写入到硬盘
+ String originalFilename = multipartFile.getOriginalFilename();
+ File file = new File(dirPath + File.separator + originalFilename);
+ try {
+ multipartFile.transferTo(file);
+ } catch (FileUploadException e) {
+ logger.error(e.getMessage());
+ }
+
+ if (!isAPK(file) && !isXAPK(file)) {
+ return Result.error().message("请上传apk或xapk文件");
+ }
+ String md5 = HashUtils.getFileMD5(file);
+ logger.info("file md5 = " + md5);
+ String sha1 = HashUtils.calculateSHA1(file);
+ logger.info("file sha1 = " + sha1);
+ String sha256 = HashUtils.calculateSHA256(file);
+ logger.info("file sha256 = " + sha256);
+
+ File iconFile = new File(iconPath + md5 + ".png");
+
+ if (isAPK(file)) {
+ logger.info("file type is apk");
+ //调用工具类解析apk
+ //analysisAPK(dirFile.getAbsolutePath(), file.getAbsolutePath());
+ ApkFile apkFile = new ApkFile(file);
+ ApkMeta apkMeta = apkFile.getApkMeta();
+
+ logger.info(apkMeta.getLabel());
+ logger.info(apkMeta.getPackageName());
+ logger.info(apkMeta.getVersionCode());
+ logger.info(apkMeta.getVersionName());
+ for (UseFeature feature : apkMeta.getUsesFeatures()) {
+ logger.info(feature.getName());
+ }
+
+ ApkInfo apkInfo = new ApkInfo();
+ apkInfo.setAppName(apkMeta.getLabel());
+ apkInfo.setPackageName(apkMeta.getPackageName());
+ apkInfo.setVersionCode(apkMeta.getVersionCode());
+ apkInfo.setVersionName(apkMeta.getVersionName());
+ apkInfo.setSha1(sha1);
+ apkInfo.setFilePath(file.getPath());
+ apkInfo.setIconPath(iconFile.getPath());
+ apkInfo.setSize(file.length());
+ apkInfo.setXapk(false);
+
+ try {
+ apkInfoService.addApk(apkInfo);
+
+ List allIcons = apkFile.getAllIcons();
+ Optional maxNumber = allIcons.stream().filter(iconFace -> iconFace.getPath().endsWith(".png"))
+ .max(Comparator.comparingInt(o -> o.getData().length));
+ if (maxNumber.isPresent()) {
+ logger.info(maxNumber.get().getPath());
+ FileUtils.byteToFile(maxNumber.get().getData(), iconFile);
+ }
+ } catch (DataIntegrityViolationException e) {
+ e.printStackTrace();
+ logger.info(e.getMessage());
+ return Result.error().message("apk文件无效");
+ }
+
+ return Result.ok().message("上传成功");
+ } else if (isXAPK(file)) {
+ logger.info("file type is xapk");
+ try (ZipFile zipFile = new ZipFile(file)) {
+ // 遍历ZIP文件中的所有条目
+ zipFile.stream().forEach(zipEntry -> {
+ logger.info(zipEntry.getName());
+ });
+ Optional extends ZipEntry> manifestOptional = zipFile.stream().filter(zipEntry -> "manifest.json".equals(zipEntry.getName())).findFirst();
+ if (manifestOptional.isPresent()) {
+ ZipEntry manifestZipEntry = manifestOptional.get();
+ try {
+ InputStream manifestInputStream = zipFile.getInputStream(manifestZipEntry);
+ // 读取文件内容
+ String content = new String(manifestInputStream.readAllBytes(), StandardCharsets.UTF_8);
+ // 创建ObjectMapper实例
+ ObjectMapper objectMapper = new ObjectMapper();
+ // 将Java对象转换为JSON字符串
+ String jsonString = objectMapper.writeValueAsString(content);
+ XapkManifest xapkManifest = objectMapper.readValue(content, XapkManifest.class);
+// logger.info(xapkManifest);
+ logger.info(xapkManifest.getName());
+ logger.info(xapkManifest.getPackage_name());
+ logger.info(xapkManifest.getVersion_code());
+ logger.info(xapkManifest.getVersion_name());
+
+ ApkInfo apkInfo = new ApkInfo();
+ apkInfo.setAppName(xapkManifest.getName());
+ apkInfo.setPackageName(xapkManifest.getPackage_name());
+ apkInfo.setVersionCode(xapkManifest.getVersion_code());
+ apkInfo.setVersionName(xapkManifest.getVersion_name());
+ apkInfo.setSha1(sha1);
+ apkInfo.setFilePath(file.getPath());
+ apkInfo.setIconPath(iconFile.getPath());
+ apkInfo.setSize(file.length());
+ apkInfo.setXapk(true);
+ apkInfo.setXapk_version(xapkManifest.getXapk_version());
+
+ try {
+ apkInfoService.addApk(apkInfo);
+
+ String iconName = xapkManifest.getIcon();
+ Optional extends ZipEntry> iconNameOptional = zipFile.stream().filter(zipEntry -> iconName.equals(zipEntry.getName())).findFirst();
+ if (iconNameOptional.isPresent()) {
+ ZipEntry iconZipEntry = iconNameOptional.get();
+ InputStream iconInputStream = zipFile.getInputStream(iconZipEntry);
+ FileUtils.byteToFile(iconInputStream.readAllBytes(), iconFile);
+ }
+ } catch (DataIntegrityViolationException e) {
+ logger.info(e.getMessage());
+ // 手动回滚事务
+ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+ return Result.error().message("xapk文件重复");
+ }
+
+ return Result.ok().message("上传成功");
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("读取文件失败: " + manifestZipEntry.getName());
+ return Result.error().message("读取文件失败");
+ }
+ } else {
+ return Result.error().message("读取xapk配置失败");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.err.println("打开ZIP文件失败: " + e.getMessage());
+ return Result.error().message("打开ZIP文件失败");
+ }
+ }
+ } else {
+ return Result.error().message("文件为空");
+ }
+ return Result.error().message("未知错误");
+ }
+
+ // 判断是否为APK
+ public static boolean isAPK(File file) {
+ // 检查扩展名是否为.apk
+ if (file.getName().toLowerCase().endsWith(".apk")) {
+ // 进一步验证是否为有效的APK文件
+ try (ZipFile zipFile = new ZipFile(file)) {
+ return zipFile.getEntry("AndroidManifest.xml") != null;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ // 判断是否为XAPK
+ public static boolean isXAPK(File file) {
+ // 检查扩展名是否为.xapk
+ if (file.getName().toLowerCase().endsWith(".xapk")) {
+ return true; // 扩展名为.xapk通常直接视为XAPK
+ }
+
+ // 若扩展名不明确,检查内容结构
+ try (ZipFile zipFile = new ZipFile(file)) {
+ boolean hasJson = zipFile.getEntry("manifest.json") != null;
+
+ boolean hasAPK = false;
+// boolean hasOBB = false;
+
+ Enumeration extends ZipEntry> entries = zipFile.entries();
+
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ // 检查是否包含APK文件
+ if (name.endsWith(".apk")) {
+ hasAPK = true;
+ }
+ // 检查是否包含OBB目录
+// if (name.startsWith("Android/obb/")) {
+// hasOBB = true;
+// }
+ }
+ // XAPK通常包含至少一个APK或OBB数据
+ return hasAPK || hasJson;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ private void analysisAPK(String dirPath, String filePath) throws Exception {
+ logger.info(filePath);
+ Runtime runtime = Runtime.getRuntime();
+ Process process = runtime.exec("aapt d --values badging " + filePath, null, new File(dirPath));
+ InputStream inputStream = process.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ reader.close();
+ inputStream.close();
+ String strOutput = sb.toString();
+ logger.info(strOutput);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/mir4updater/backend/controller/apk/UploadPakFileController.java b/src/main/java/com/mir4updater/backend/controller/apk/UploadPakFileController.java
new file mode 100644
index 0000000..4c352c7
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/controller/apk/UploadPakFileController.java
@@ -0,0 +1,73 @@
+package com.mir4updater.backend.controller.apk;
+
+import com.mir4updater.backend.dto.PakFileInfo;
+import com.mir4updater.backend.result.Result;
+import com.mir4updater.backend.utils.HashUtils;
+import jakarta.validation.Valid;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collections;
+
+@RestController
+public class UploadPakFileController {
+ public static Logger logger = LogManager.getLogger(UploadApkController.class);
+ private static final String UPLOAD_PATH = "uploadPak";
+
+ static {
+ String projectPath = System.getProperty("user.dir");
+ String dirPath = projectPath + File.separator + UPLOAD_PATH + File.separator;
+ File dirFile = new File(dirPath);
+ if (!dirFile.exists()) {
+ logger.info("mkdir " + dirFile.getPath() + "\t" + dirFile.mkdirs());
+ }
+ }
+
+ @Autowired
+ private StringRedisTemplate stringRedisTemplate;
+
+ @PostMapping(path = "/android/upload_pak_file")
+ public Result uploadPakFile(@Valid @ModelAttribute PakFileInfo pakFileInfo) throws Exception {
+ String jsonUuid = pakFileInfo.json_uuid();
+ if (!Boolean.TRUE.equals(stringRedisTemplate.hasKey("data:" + Collections.singletonMap("uuid", jsonUuid)))) {
+ return Result.error().message("json_uuid不存在");
+ }
+ InputStream inputStream = pakFileInfo.file().getInputStream();
+ String sha1 = HashUtils.calculateSHA1(inputStream);
+ logger.info("origin_sha1 = " + pakFileInfo.sha1());
+ logger.info("sha1 = " + sha1);
+ if (!sha1.equalsIgnoreCase(pakFileInfo.sha1())) {
+ return Result.error().message("sha1不匹配");
+ }
+
+ return Result.ok();
+ }
+
+ @PostMapping("/android/upload_pak_file_old")
+ public Result uploadPakFile(@RequestParam("file") MultipartFile multipartFile, @RequestParam("name") String name,
+ @RequestParam("path") String path, @RequestParam("sha1") String origin_sha1,
+ @RequestParam("json_id") String jsonId) throws Exception {
+// if (TextUtils.isEmpty(jsonId)){
+// return Result.error().message("json_id不能为空");
+// }
+ InputStream inputStream = multipartFile.getInputStream();
+ String sha1 = HashUtils.calculateSHA1(inputStream);
+ logger.info("origin_sha1 = " + origin_sha1);
+ logger.info("sha1 = " + sha1);
+ if (!sha1.equalsIgnoreCase(origin_sha1)) {
+ return Result.error().message("sha1不匹配");
+ }
+
+ return Result.ok();
+ }
+
+}
diff --git a/src/main/java/com/mir4updater/backend/controller/apk/UploadPakInfoController.java b/src/main/java/com/mir4updater/backend/controller/apk/UploadPakInfoController.java
new file mode 100644
index 0000000..a2c4cd1
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/controller/apk/UploadPakInfoController.java
@@ -0,0 +1,28 @@
+package com.mir4updater.backend.controller.apk;
+
+import com.mir4updater.backend.dto.PakInfoJsonRequest;
+import com.mir4updater.backend.result.Result;
+import com.mir4updater.backend.service.PakInfoService;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Collections;
+
+@RestController
+@RequestMapping("/android/upload_pak_info")
+public class UploadPakInfoController {
+
+ @Autowired
+ private PakInfoService pakInfoService;
+
+ @PostMapping
+ public Result createData(@Valid @RequestBody PakInfoJsonRequest request) {
+ String id = pakInfoService.processData(request);
+
+ return Result.ok().data(Collections.singletonMap("uuid", id));
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/dto/PakFileInfo.java b/src/main/java/com/mir4updater/backend/dto/PakFileInfo.java
new file mode 100644
index 0000000..272557e
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/dto/PakFileInfo.java
@@ -0,0 +1,19 @@
+package com.mir4updater.backend.dto;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.web.multipart.MultipartFile;
+
+public record PakFileInfo(
+ @NotNull(message = "文件不能为空")
+ MultipartFile file,
+ @NotBlank(message = "name不能为空")
+ String name,
+ @NotBlank(message = "path不能为空")
+ String path,
+ @NotBlank(message = "sha1不能为空")
+ String sha1,
+ @NotBlank(message = "uuid不能为空")
+ String json_uuid
+) {
+}
diff --git a/src/main/java/com/mir4updater/backend/dto/PakInfoJsonRequest.java b/src/main/java/com/mir4updater/backend/dto/PakInfoJsonRequest.java
new file mode 100644
index 0000000..b86d17b
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/dto/PakInfoJsonRequest.java
@@ -0,0 +1,8 @@
+package com.mir4updater.backend.dto;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public record PakInfoJsonRequest(
+ ObjectNode json
+) {
+}
diff --git a/src/main/java/com/mir4updater/backend/entity/ApkInfo.java b/src/main/java/com/mir4updater/backend/entity/ApkInfo.java
new file mode 100644
index 0000000..f16bec8
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/entity/ApkInfo.java
@@ -0,0 +1,136 @@
+package com.mir4updater.backend.entity;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+@Data
+@Entity
+@Table(name = "apk_info")
+@EqualsAndHashCode
+
+public class ApkInfo implements Serializable {
+
+ @Embedded
+ private ApkInfoPK id; // 复合主键
+
+ @Id
+ @Column(name = "app_id", nullable = false, length = 20)
+ @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键自增
+ int appId;
+
+ @Column(name = "app_name", nullable = false, length = 128)
+ String appName;
+
+ @Column(name = "package_name", nullable = false)
+ String packageName;
+
+ @Column(name = "version_code", nullable = false, length = 11)
+ long versionCode;
+
+ @Column(name = "version_name", nullable = false)
+ String versionName;
+
+ @Column(name = "sha1", nullable = false, length = 160)
+ String sha1;
+
+ @Column(name = "file_path", nullable = false)
+ String filePath;
+
+ @Column(name = "icon_path", nullable = false)
+ String iconPath;
+
+ @Column(name = "size", nullable = false, length = 20)
+ long size;
+
+ @Column(name = "is_xapk", columnDefinition = "bit(1) default 0")
+ boolean xapk = false;
+
+ @Column(name = "xapk_version", length = 11)
+ int xapk_version;
+
+ public ApkInfo() {
+ }
+
+ public String getAppName() {
+ return appName;
+ }
+
+ public void setAppName(String appName) {
+ this.appName = appName;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public long getVersionCode() {
+ return versionCode;
+ }
+
+ public void setVersionCode(long versionCode) {
+ this.versionCode = versionCode;
+ }
+
+ public String getVersionName() {
+ return versionName;
+ }
+
+ public void setVersionName(String versionName) {
+ this.versionName = versionName;
+ }
+
+ public String getSha1() {
+ return sha1;
+ }
+
+ public void setSha1(String sha1) {
+ this.sha1 = sha1;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public void setFilePath(String filePath) {
+ this.filePath = filePath;
+ }
+
+ public String getIconPath() {
+ return iconPath;
+ }
+
+ public void setIconPath(String iconPath) {
+ this.iconPath = iconPath;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public boolean isXapk() {
+ return xapk;
+ }
+
+ public void setXapk(boolean xapk) {
+ this.xapk = xapk;
+ }
+
+ public int getXapk_version() {
+ return xapk_version;
+ }
+
+ public void setXapk_version(int xapk_version) {
+ this.xapk_version = xapk_version;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/entity/ApkInfoPK.java b/src/main/java/com/mir4updater/backend/entity/ApkInfoPK.java
new file mode 100644
index 0000000..d8548b1
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/entity/ApkInfoPK.java
@@ -0,0 +1,56 @@
+package com.mir4updater.backend.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+
+import java.io.Serializable;
+
+// 使用 @EmbeddedId
+@Embeddable
+public class ApkInfoPK implements Serializable {
+
+ @Column(insertable = false, updatable = false)
+ String sha1;
+
+ @Column(name = "package_name", insertable = false, updatable = false)
+ String packageName;
+
+ @Column(name = "version_code", insertable = false, updatable = false)
+ String versionCode;
+
+ // getters, setters, equals, hashCode
+
+ public ApkInfoPK() {
+
+ }
+
+ public ApkInfoPK(String sha1, String packageName, String version_code) {
+ this.sha1 = sha1;
+ this.packageName = packageName;
+ this.versionCode = version_code;
+ }
+
+ public String getSha1() {
+ return sha1;
+ }
+
+ public void setSha1(String sha1) {
+ this.sha1 = sha1;
+ }
+
+ public String getPackageName() {
+ return packageName;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+ }
+
+ public String getVersionCode() {
+ return versionCode;
+ }
+
+ public void setVersionCode(String version_code) {
+ this.versionCode = version_code;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/entity/PakInfo.java b/src/main/java/com/mir4updater/backend/entity/PakInfo.java
new file mode 100644
index 0000000..eb7a889
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/entity/PakInfo.java
@@ -0,0 +1,43 @@
+package com.mir4updater.backend.entity;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.annotations.UuidGenerator;
+import org.hibernate.type.SqlTypes;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.UUID;
+
+@Entity
+public class PakInfo implements Serializable {
+ @Id
+ @UuidGenerator(style = UuidGenerator.Style.TIME)
+ private UUID uuid;
+
+ @JdbcTypeCode(SqlTypes.JSON)
+ @Column(nullable = false, columnDefinition = "JSON")
+ private Map json;
+
+ public PakInfo() {
+
+ }
+
+ public UUID getId() {
+ return uuid;
+ }
+
+ public void setId(UUID id) {
+ this.uuid = id;
+ }
+
+ public Map getJson() {
+ return json;
+ }
+
+ public void setJson(Map json) {
+ this.json = json;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/entity/UserInfo.java b/src/main/java/com/mir4updater/backend/entity/UserInfo.java
new file mode 100644
index 0000000..3647152
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/entity/UserInfo.java
@@ -0,0 +1,57 @@
+package com.mir4updater.backend.entity;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+public class UserInfo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 4389921857866822592L;
+
+ long user_id;
+ String user_name;
+ String user_phone;
+ String salt;
+ String regist_time;
+
+ public long getUser_id() {
+ return user_id;
+ }
+
+ public void setUser_id(long user_id) {
+ this.user_id = user_id;
+ }
+
+ public String getUser_name() {
+ return user_name;
+ }
+
+ public void setUser_name(String user_name) {
+ this.user_name = user_name;
+ }
+
+ public String getUser_phone() {
+ return user_phone;
+ }
+
+ public void setUser_phone(String user_phone) {
+ this.user_phone = user_phone;
+ }
+
+ public String getSalt() {
+ return salt;
+ }
+
+ public void setSalt(String salt) {
+ this.salt = salt;
+ }
+
+ public String getRegist_time() {
+ return regist_time;
+ }
+
+ public void setRegist_time(String regist_time) {
+ this.regist_time = regist_time;
+ }
+
+}
diff --git a/src/main/java/com/mir4updater/backend/entity/XapkManifest.java b/src/main/java/com/mir4updater/backend/entity/XapkManifest.java
new file mode 100644
index 0000000..4564d49
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/entity/XapkManifest.java
@@ -0,0 +1,149 @@
+package com.mir4updater.backend.entity;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParser;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class XapkManifest implements Serializable {
+
+ public int xapk_version;
+ public String package_name;
+ public String name;
+ public long version_code;
+ public String version_name;
+ public String min_sdk_version;
+ public String target_sdk_version;
+ public List permissions;
+ public List split_configs;
+ public long total_size;
+ public String icon;
+ public List split_apks;
+
+ public static class SplitApk {
+ public String file;
+ public String id;
+ }
+
+ public XapkManifest() {
+ }
+
+ public XapkManifest(int xapk_version, String package_name, String name, long version_code, String version_name,
+ String min_sdk_version, String target_sdk_version, List permissions, List split_configs,
+ long total_size, String icon, List split_apks) {
+ this.xapk_version = xapk_version;
+ this.package_name = package_name;
+ this.name = name;
+ this.version_code = version_code;
+ this.version_name = version_name;
+ this.min_sdk_version = min_sdk_version;
+ this.target_sdk_version = target_sdk_version;
+ this.permissions = permissions;
+ this.split_configs = split_configs;
+ this.total_size = total_size;
+ this.icon = icon;
+ this.split_apks = split_apks;
+ }
+
+ public int getXapk_version() {
+ return xapk_version;
+ }
+
+ public void setXapk_version(int xapk_version) {
+ this.xapk_version = xapk_version;
+ }
+
+ public String getPackage_name() {
+ return package_name;
+ }
+
+ public void setPackage_name(String package_name) {
+ this.package_name = package_name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public long getVersion_code() {
+ return version_code;
+ }
+
+ public void setVersion_code(long version_code) {
+ this.version_code = version_code;
+ }
+
+ public String getVersion_name() {
+ return version_name;
+ }
+
+ public void setVersion_name(String version_name) {
+ this.version_name = version_name;
+ }
+
+ public String getMin_sdk_version() {
+ return min_sdk_version;
+ }
+
+ public void setMin_sdk_version(String min_sdk_version) {
+ this.min_sdk_version = min_sdk_version;
+ }
+
+ public String getTarget_sdk_version() {
+ return target_sdk_version;
+ }
+
+ public void setTarget_sdk_version(String target_sdk_version) {
+ this.target_sdk_version = target_sdk_version;
+ }
+
+ public List getPermissions() {
+ return permissions;
+ }
+
+ public void setPermissions(List permissions) {
+ this.permissions = permissions;
+ }
+
+ public List getSplit_configs() {
+ return split_configs;
+ }
+
+ public void setSplit_configs(List split_configs) {
+ this.split_configs = split_configs;
+ }
+
+ public long getTotal_size() {
+ return total_size;
+ }
+
+ public void setTotal_size(long total_size) {
+ this.total_size = total_size;
+ }
+
+ public String getIcon() {
+ return icon;
+ }
+
+ public void setIcon(String icon) {
+ this.icon = icon;
+ }
+
+ public List getSplit_apks() {
+ return split_apks;
+ }
+
+ public void setSplit_apks(List split_apks) {
+ this.split_apks = split_apks;
+ }
+
+ @Override
+ public String toString() {
+ return JsonParser.parseString(new Gson().toJson(this)).getAsJsonObject().toString();
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/redis/RedisConfig.java b/src/main/java/com/mir4updater/backend/redis/RedisConfig.java
new file mode 100644
index 0000000..c6b1eb6
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/redis/RedisConfig.java
@@ -0,0 +1,28 @@
+package com.mir4updater.backend.redis;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(connectionFactory);
+
+ // Key序列化为字符串
+ template.setKeySerializer(new StringRedisSerializer());
+ // Value序列化为JSON
+ template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
+
+ // 对于Hash结构,根据需要配置
+ template.setHashKeySerializer(new StringRedisSerializer());
+ template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
+ return template;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/repository/ApkInfoRepository.java b/src/main/java/com/mir4updater/backend/repository/ApkInfoRepository.java
new file mode 100644
index 0000000..6c0e8ed
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/repository/ApkInfoRepository.java
@@ -0,0 +1,13 @@
+package com.mir4updater.backend.repository;
+
+
+import com.mir4updater.backend.entity.ApkInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+
+public interface ApkInfoRepository extends JpaRepository {
+ // 自定义查询方法(可选)
+ List findApkInfosByPackageName(String pkg);
+ List findAll();
+}
diff --git a/src/main/java/com/mir4updater/backend/repository/PakInfoRepository.java b/src/main/java/com/mir4updater/backend/repository/PakInfoRepository.java
new file mode 100644
index 0000000..112b154
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/repository/PakInfoRepository.java
@@ -0,0 +1,7 @@
+package com.mir4updater.backend.repository;
+
+import com.mir4updater.backend.entity.PakInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface PakInfoRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/mir4updater/backend/result/JsonData.java b/src/main/java/com/mir4updater/backend/result/JsonData.java
new file mode 100644
index 0000000..3e7a2ed
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/result/JsonData.java
@@ -0,0 +1,60 @@
+package com.mir4updater.backend.result;
+
+/**
+ * 接口返回工具类
+ */
+public class JsonData {
+ private int code;
+ private Object data;
+ private String msg;
+
+ public JsonData() {
+ }
+
+ public JsonData(int code, Object data) {
+ this.code = code;
+ this.data = data;
+ }
+
+ public JsonData(int code, Object data, String msg) {
+ this.code = code;
+ this.data = data;
+ this.msg = msg;
+ }
+
+ public static JsonData buildSuccess(Object data) {
+ return new JsonData(0, data);
+ }
+
+ public static JsonData buildError(String msg) {
+ return new JsonData(-1, "", msg);
+ }
+
+ public static JsonData buildError(int code, String msg) {
+ return new JsonData(code, "", msg);
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public Object getData() {
+ return data;
+ }
+
+ public void setData(Object data) {
+ this.data = data;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/result/Result.java b/src/main/java/com/mir4updater/backend/result/Result.java
new file mode 100644
index 0000000..7774d35
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/result/Result.java
@@ -0,0 +1,129 @@
+package com.mir4updater.backend.result;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 统一返回格式类
+ *
+ * @author 爷爷的茶七里香
+ * @date 2022/05/30
+ */
+public class Result {
+
+ /**
+ * 是否成功
+ */
+ private Boolean success;
+
+ /**
+ * 状态码
+ */
+ private Integer code;
+
+ /**
+ * 返回的消息
+ */
+ private String message;
+
+ /**
+ * 放置响应的数据
+ */
+ private Map data = new HashMap<>();
+
+ public Result() {
+ }
+
+ /** 以下是定义一些常用到的格式,可以看到调用了我们创建的枚举类 */
+
+ public static Result ok() {
+ Result r = new Result();
+ r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
+ r.setCode(ResultCodeEnum.SUCCESS.getCode());
+ r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
+ return r;
+ }
+
+ public static Result error() {
+ Result r = new Result();
+ r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
+ r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
+ r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
+ return r;
+ }
+
+ public static Result notFound() {
+ Result r = new Result();
+ r.setSuccess(ResultCodeEnum.NOT_FOUND.getSuccess());
+ r.setCode(ResultCodeEnum.NOT_FOUND.getCode());
+ r.setMessage(ResultCodeEnum.NOT_FOUND.getMessage());
+ return r;
+ }
+
+ public static Result setResult(ResultCodeEnum resultCodeEnum) {
+ Result r = new Result();
+ r.setSuccess(resultCodeEnum.getSuccess());
+ r.setCode(resultCodeEnum.getCode());
+ r.setMessage(resultCodeEnum.getMessage());
+ return r;
+ }
+
+ public Result success(Boolean success) {
+ this.setSuccess(success);
+ return this;
+ }
+
+ public Result message(String message) {
+ this.setMessage(message);
+ return this;
+ }
+
+ public Result code(Integer code) {
+ this.setCode(code);
+ return this;
+ }
+
+ public Result data(String key, Object value) {
+ this.data.put(key, value);
+ return this;
+ }
+
+ public Result data(Map map) {
+ this.setData(map);
+ return this;
+ }
+
+ /** 以下是get/set方法,如果项目有集成lombok可以使用@Data注解代替 */
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public void setCode(Integer code) {
+ this.code = code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public Map getData() {
+ return data;
+ }
+
+ public void setData(Map data) {
+ this.data = data;
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/result/ResultCodeEnum.java b/src/main/java/com/mir4updater/backend/result/ResultCodeEnum.java
new file mode 100644
index 0000000..170cf16
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/result/ResultCodeEnum.java
@@ -0,0 +1,45 @@
+package com.mir4updater.backend.result;
+
+/**
+ * 状态码
+ *
+ * @author 爷爷的茶七里香
+ * @date 2022/05/30
+ */
+public enum ResultCodeEnum {
+
+ SUCCESS(true, 20000, "成功"),
+
+ UNKNOWN_REASON(false, 20001, "未知错误"),
+ NOT_FOUND(true, 20004, "没有数据");
+
+
+ private final Boolean success;
+
+ private final Integer code;
+
+ private final String message;
+
+ ResultCodeEnum(Boolean success, Integer code, String message) {
+ this.success = success;
+ this.code = code;
+ this.message = message;
+ }
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public Integer getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public String toString() {
+ return "ResultCodeEnum{" + "success=" + success + ", code=" + code + ", message='" + message + '\'' + '}';
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/mir4updater/backend/service/ApkInfoService.java b/src/main/java/com/mir4updater/backend/service/ApkInfoService.java
new file mode 100644
index 0000000..4912b09
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/service/ApkInfoService.java
@@ -0,0 +1,27 @@
+package com.mir4updater.backend.service;
+
+import com.mir4updater.backend.entity.ApkInfo;
+import com.mir4updater.backend.repository.ApkInfoRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class ApkInfoService {
+ @Autowired
+ private ApkInfoRepository apkInfoRepository;
+
+ public void addApk(ApkInfo user) {
+ apkInfoRepository.save(user);
+ }
+
+ public List getApkInfo(String pkg) {
+ return apkInfoRepository.findApkInfosByPackageName(pkg);
+ }
+
+ public List getAll() {
+ return apkInfoRepository.findAll();
+ }
+
+}
diff --git a/src/main/java/com/mir4updater/backend/service/PakInfoService.java b/src/main/java/com/mir4updater/backend/service/PakInfoService.java
new file mode 100644
index 0000000..a07fc2d
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/service/PakInfoService.java
@@ -0,0 +1,49 @@
+package com.mir4updater.backend.service;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.mir4updater.backend.dto.PakInfoJsonRequest;
+import com.mir4updater.backend.entity.PakInfo;
+import com.mir4updater.backend.repository.PakInfoRepository;
+import jakarta.transaction.Transactional;
+import jakarta.validation.ValidationException;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.Map;
+
+@Service
+public class PakInfoService {
+ private final PakInfoRepository pakInfoRepository;
+ private final RedisTemplate redisTemplate;
+
+ public PakInfoService(PakInfoRepository pakInfoRepository, RedisTemplate redisTemplate) {
+ this.pakInfoRepository = pakInfoRepository;
+ this.redisTemplate = redisTemplate;
+ }
+
+ @Transactional
+ public String processData(PakInfoJsonRequest request) {
+ // 创建实体
+ PakInfo entity = new PakInfo();
+ entity.setJson(convertToMap(request.json()));
+ // 保存到数据库
+ pakInfoRepository.save(entity);
+ // 保存到Redis,有效期1小时
+ redisTemplate.opsForValue().set(
+ "data:" + Collections.singletonMap("uuid", entity.getId()),
+ request.json()
+ );
+ return entity.getId().toString();
+ }
+
+ private Map convertToMap(ObjectNode node) {
+ try {
+ return new ObjectMapper().convertValue(node, new TypeReference<>() {});
+ } catch (IllegalArgumentException e) {
+ throw new ValidationException("Invalid JSON structure");
+ }
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/utils/CheckCodeUtils.java b/src/main/java/com/mir4updater/backend/utils/CheckCodeUtils.java
new file mode 100644
index 0000000..99b9b4d
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/utils/CheckCodeUtils.java
@@ -0,0 +1,66 @@
+package com.mir4updater.backend.utils;
+
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.util.Random;
+
+//生成验证码,并将验证码存入session中,以ip为key
+public class CheckCodeUtils {
+ public static BufferedImage getCheckCode(HttpServletRequest request, String ip) {
+ int width = 60, height = 30;
+ //创建一个图像,宽60 高30
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics g = image.getGraphics();
+ Random random = new Random();
+ g.setColor(getRandomColor(200, 250));
+ g.fillRect(0, 0, width, height);
+ g.setFont(new Font("Times New Roman", Font.PLAIN, 18));
+ g.setColor(getRandomColor(160, 200));
+ //干扰线生成
+ for (int i = 0; i < 10; i++) {
+ int x = random.nextInt(width);
+ int y = random.nextInt(height);
+ int xl = random.nextInt(12);
+ int yl = random.nextInt(12);
+ g.drawLine(x, y, x + xl, y + yl);
+ }
+ String strCode = "";
+ for (int i = 0; i < 4; i++) {
+ String strNumber = String.valueOf(random.nextInt(10));
+ strCode = strCode + strNumber;
+ //设置字体颜色
+ g.drawString(strNumber, 13 * i + 6, 20);
+ }
+ System.out.println("当前ip" + ip);
+ System.out.println("当前验证码" + strCode);
+ request.getSession().setAttribute(ip, strCode);
+ g.dispose();
+ return image;
+ }
+
+
+ /**
+ * 随机获取颜色的方法
+ *
+ * @return
+ */
+ public static Color getRandomColor(int fc, int bc) {
+ Random random = new Random();
+ Color reandomColor = null;
+ if (fc > 255) {
+ fc = 255;
+ }
+ if (bc > 255) {
+ bc = 255;
+ }
+ int r = fc + random.nextInt(bc - fc);
+ int g = fc + random.nextInt(bc - fc);
+ int b = fc + random.nextInt(bc - fc);
+ reandomColor = new Color(r, g, b);
+ return reandomColor;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/mir4updater/backend/utils/CreateImageCode.java b/src/main/java/com/mir4updater/backend/utils/CreateImageCode.java
new file mode 100644
index 0000000..939a45e
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/utils/CreateImageCode.java
@@ -0,0 +1,190 @@
+package com.mir4updater.backend.utils;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.OutputStream;
+import java.util.Random;
+
+/**
+ * 生成 数字+字母 验证码图片
+ */
+public class CreateImageCode {
+ //图片的宽度
+ private int width = 160;
+ //图片的高度
+ private int height = 40;
+ //验证码字符个数
+ private int codeCount = 4;
+ //验证码干扰线数
+ private int lineCount = 20;
+ //验证码
+ private String code = null;
+ //验证码图片buffer
+ private BufferedImage buffImg = null;
+ //创建一个生成随机数对象
+ Random random = new Random();
+
+ //构造函数
+ public CreateImageCode() {
+ creatImage();
+ }
+
+ public CreateImageCode(int width, int height) {
+ this.width = width;
+ this.height = height;
+ creatImage();
+ }
+
+ public CreateImageCode(int width, int height, int codeCount) {
+ this.width = width;
+ this.height = height;
+ this.codeCount = codeCount;
+ creatImage();
+ }
+
+ public CreateImageCode(int width, int height, int codeCount, int lineCount) {
+ this.width = width;
+ this.height = height;
+ this.codeCount = codeCount;
+ this.lineCount = lineCount;
+ creatImage();
+ }
+
+ //生成图片
+ private void creatImage() {
+ //字体的宽度
+ int fontWidth = width / codeCount;
+ //字体的高度
+ int fontHeight = height - 5;
+ int codeY = height - 8;
+ //图像buff
+ buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+ Graphics g = buffImg.getGraphics();
+ //设置背景色
+ g.setColor(getRandColor(200, 250));
+ g.fillRect(0, 0, width, height);
+ //设置字体
+ Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
+ g.setFont(font);
+ //设置干扰线
+ for (int i = 0; i < lineCount; i++) {
+ int xs = random.nextInt(width);
+ int ys = random.nextInt(height);
+ int xe = xs + random.nextInt(width);
+ int ye = ys + random.nextInt(height);
+ g.setColor(getRandColor(1, 255));
+ g.drawLine(xs, ys, xe, ye);
+ }
+ //添加噪点
+ float yawpRate = 0.01f;//噪声率
+ int area = (int) (yawpRate * width * height);
+ for (int i = 0; i < area; i++) {
+ int x = random.nextInt(width);
+ int y = random.nextInt(height);
+ buffImg.setRGB(x, y, random.nextInt(255));
+ }
+ String str1 = randomStr(codeCount);//得到随机字符
+ this.code = str1;
+ for (int i = 0; i < codeCount; i++) {
+ String strRand = str1.substring(i, i + 1);
+ g.setColor(getRandColor(1, 255));
+ //g.drawString(str,x,y) ---- str:为要画出来的东西;x 和 y :表示要画的东西最左则字符的基线位于此图形上下文坐标系的 (x,y)位置处
+ g.drawString(strRand, i * fontWidth + 3, codeY);
+ }
+ }
+
+ //得到随机字符
+ private String randomStr(int n) {
+ String str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567990";
+ String str2 = "";
+ int len = str1.length() - 1;
+ double r;
+ for (int i = 0; i < n; i++) {
+ r = (Math.random()) * len;
+ str2 = str2 + str1.charAt((int) r);
+ }
+ return str2;
+ }
+
+ //得到随机颜色
+ private Color getRandColor(int fc, int bc) {
+ if (fc > 255) fc = 255;
+ if (bc > 255) bc = 255;
+ int r = fc + random.nextInt(bc - fc);
+ int g = fc + random.nextInt(bc - fc);
+ int b = fc + random.nextInt(bc - fc);
+ return new Color(r, g, b);
+ }
+
+ //产生随机字体
+ private Font getFont(int size) {
+ Random random = new Random();
+ Font font[] = new Font[5];
+ font[0] = new Font("Ravie", Font.PLAIN, size);
+ font[1] = new Font("Antique Olive Compact", Font.PLAIN, size);
+ font[2] = new Font("Fixedsys", Font.PLAIN, size);
+ font[3] = new Font("Wide Latin", Font.PLAIN, size);
+ font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, size);
+ return font[random.nextInt(5)];
+ }
+
+ //扭曲方法
+ private void shear(Graphics g, int wl, int hl, Color color) {
+ shearX(g, wl, hl, color);
+ shearY(g, wl, hl, color);
+ }
+
+ private void shearX(Graphics g, int wl, int hl, Color color) {
+ int period = random.nextInt(2);
+ boolean borderGap = true;
+ int frames = 1;
+ int phase = random.nextInt(2);
+ for (int i = 0; i < hl; i++) {
+ double d = (double) (period >> 1)
+ * Math.sin((double) i / (double) period
+ + (6.2831853071795862D * (double) phase)
+ / (double) frames);
+ g.copyArea(0, i, wl, 1, (int) d, 0);
+ if (borderGap) {
+ g.setColor(color);
+ g.drawLine((int) d, i, 0, i);
+ g.drawLine((int) d + wl, i, wl, i);
+ }
+ }
+
+ }
+
+ private void shearY(Graphics g, int w1, int h1, Color color) {
+ int period = random.nextInt(40) + 10; // 50;
+ boolean borderGap = true;
+ int frames = 20;
+ int phase = 7;
+ for (int i = 0; i < w1; i++) {
+ double d = (double) (period >> 1)
+ * Math.sin((double) i / (double) period
+ + (6.2831853071795862D * (double) phase)
+ / (double) frames);
+ g.copyArea(i, 0, 1, h1, 0, (int) d);
+ if (borderGap) {
+ g.setColor(color);
+ g.drawLine(i, (int) d, i, 0);
+ g.drawLine(i, (int) d + h1, i, h1);
+ }
+ }
+ }
+
+ public void write(OutputStream os) {
+ try {
+ ImageIO.write(buffImg, "png", os);
+ os.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public String getCode() {
+ return code.toLowerCase();
+ }
+
+}
diff --git a/src/main/java/com/mir4updater/backend/utils/FileUtils.java b/src/main/java/com/mir4updater/backend/utils/FileUtils.java
new file mode 100644
index 0000000..73ed9d8
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/utils/FileUtils.java
@@ -0,0 +1,29 @@
+package com.mir4updater.backend.utils;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class FileUtils {
+ public static Logger logger = LogManager.getLogger(FileUtils.class);
+
+ public static boolean byteToFile(byte[] bytes, File file) {
+ // 创建文件输出流
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ // 将字节数组写入到文件中
+ fos.write(bytes);
+ logger.info("写入文件成功!" + file.getAbsoluteFile());
+ return true;
+ } catch (IOException e) {
+ e.printStackTrace();
+ logger.info(e.getMessage());
+ }
+ return false;
+ }
+
+
+
+}
diff --git a/src/main/java/com/mir4updater/backend/utils/HashUtils.java b/src/main/java/com/mir4updater/backend/utils/HashUtils.java
new file mode 100644
index 0000000..c343342
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/utils/HashUtils.java
@@ -0,0 +1,79 @@
+package com.mir4updater.backend.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashUtils {
+ public static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ FileInputStream fis = new FileInputStream(file);
+ byte[] dataBytes = new byte[1024];
+
+ int bytesRead;
+ while ((bytesRead = fis.read(dataBytes)) != -1) {
+ md.update(dataBytes, 0, bytesRead);
+ }
+
+ byte[] mdBytes = md.digest();
+
+ StringBuilder sb = new StringBuilder();
+ for (byte mdByte : mdBytes) {
+ sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
+ }
+ return sb.toString();
+ }
+
+ public static String calculateSHA1(File file) throws NoSuchAlgorithmException, IOException {
+ InputStream inputStream = new FileInputStream(file);
+ return calculateSHA1(inputStream);
+ }
+
+ public static String calculateSHA1(InputStream inputStream) throws NoSuchAlgorithmException, IOException {
+ MessageDigest digest = MessageDigest.getInstance("SHA-1");
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ digest.update(buffer, 0, bytesRead);
+ }
+ byte[] hashBytes = digest.digest();
+ return bytesToHex(hashBytes, true);
+ }
+
+ public static String calculateSHA256(File file) throws NoSuchAlgorithmException, IOException {
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ try (InputStream inputStream = new FileInputStream(file)) {
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = inputStream.read(buffer)) != -1) {
+ digest.update(buffer, 0, bytesRead);
+ }
+ }
+ byte[] hashBytes = digest.digest();
+ return bytesToHex(hashBytes);
+ }
+
+ private static String bytesToHex(byte[] bytes) {
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : bytes) {
+ String hex = String.format("%02x", b & 0xFF);
+ /*大写*/
+// String hex = String.format("%02X", b & 0xFF);
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+
+ private static String bytesToHex(byte[] bytes, boolean upcase) {
+ String format = upcase ? "%02X" : "%02x";
+ StringBuilder hexString = new StringBuilder();
+ for (byte b : bytes) {
+ String hex = String.format(format, b & 0xFF);
+ hexString.append(hex);
+ }
+ return hexString.toString();
+ }
+}
diff --git a/src/main/java/com/mir4updater/backend/utils/TextUtils.java b/src/main/java/com/mir4updater/backend/utils/TextUtils.java
new file mode 100644
index 0000000..5f9c8f3
--- /dev/null
+++ b/src/main/java/com/mir4updater/backend/utils/TextUtils.java
@@ -0,0 +1,7 @@
+package com.mir4updater.backend.utils;
+
+public class TextUtils {
+ public static boolean isEmpty(CharSequence str) {
+ return str == null || str.length() == 0;
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 5030029..78e8b86 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,7 +1,38 @@
spring.application.name=Mir4Updater
-#server.port=8080
-## mysql ??????
+server.port=65534
+## 数据连接信息
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3305/spring_boot?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=tt
-spring.datasource.password=fanhuitong
\ No newline at end of file
+spring.datasource.password=fanhuitong
+
+# Hibernate配置
+spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
+#spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
+# 启用 SQL 日志
+spring.jpa.show-sql=true
+# 格式化 SQL
+#spring.jpa.properties.hibernate.format_sql=true
+# 显示绑定参数的具体值(关键配置)
+logging.level.org.hibernate.type.descriptor.sql=TRACE
+
+# 文件大小
+spring.servlet.multipart.max-file-size=2GB
+spring.servlet.multipart.max-request-size=2GB
+
+mybatis.type-aliases-package=com.mir4updater.backend.entity
+mybatis.mapperLocations=classpath:mapper/*.xml
+# redis基础配置
+# 0也是默认值,表示你要操控的 Redis 上的哪个数据库
+spring.data.redis.database=0
+# 6379也是默认值,表示 Redis 端口
+spring.data.redis.port=6379
+# 这里填写你的服务器地址
+spring.data.redis.host=127.0.0.1
+spring.data.redis.password=fanhuitong
+# 可省略
+spring.data.redis.lettuce.pool.min-idle=5
+spring.data.redis.lettuce.pool.max-idle=10
+spring.data.redis.lettuce.pool.max-active=8
+spring.data.redis.lettuce.pool.max-wait=1ms
+spring.data.redis.lettuce.shutdown-timeout=100ms
\ No newline at end of file