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 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 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 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