windows,liunx,java实现apk解压,去签名、重新签名,重新打包apk
背景:由于项目需要,需要将apk包加入服务端返回的静态资源文件到apk中,形成离线apk包供下载安装。经过调查研究,决定使用apktool实现。关于apktool的资料可以参考
https://blog.csdn.net/quantum7/article/details/124060620
https://blog.csdn.net/qq_20451879/article/details/117300056
windows版环境
1.JDK环境
2.下载apktool.jar
打包流程:
apktool下载地址:https://ibotpeaches.github.io/Apktool/
3.解压apk包
java -jar apktool_2.6.1.jar d app-release.apk
4删除签名文件
签名文件在解压文件后的\original\META-INF目录下
C:\Users***\Downloads\app-release1111\original\META-INF
5.添加要替换的文件到
C:\Users***\Downloads\app-release\assets\assets下
6.生成签名文件
.keystore 签名方式:
keytool -genkey -alias test.keystore -keyalg RSA -validity 20000 -keystore test.keystore
.jks方式:
keytool -genkey -v -keystore test.jks -alias test-keyalg RSA -keysize 2048 -validity 20000
keytool -importkeystore -srckeystore test.jks -destkeystore test.jks -deststoretype pkcs12
7.重新打包
java -jar apktool_2.6.1.jar b app-release
8.使用重新打包后的apk和签名文件打包
.keystore重新签名打包方式:
jarsigner -verbose -keystore test.keystore -signedjar app-release-1-0224.apk app-release-1.apk test.keystore
.jks重新签名打包方式:
jarsigner -verbose -keystore test.jks -signedjar 222.apk test.apk test
java环境
构建脚本
bulidApk.bat
@echo off
start cmd /k "cd C:\Users\aipingh\Downloads && java -jar C:\Users\aipingh\Downloads\apktool.jar b C:\Users\***\Downloads\app-release8"
rebuildKeystoreApk.bat
@echo off
start cmd /k "cd C:\Users\aipingh\Downloads && jarsigner -verbose -keystore tinnove.keystore -storepass 123456 -signedjar C:\Users\***\Downloads\app-release8\dist\app-release.apk C:\Users\aipingh\Downloads\app-release8\dist\app-release8.apk test.keystore"
代码:
import ch.qos.logback.core.util.FileUtil;
import cn.hutool.core.io.FileUtil;import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;public class ApkUtil {private String outPth;// windows版下载public void downloadWindowsOfflineApk(InfoReqVO reqVO, HttpServletResponse response) {try {// apk解压包路径String apkOriginalPath = "C:\\Users\\***\\Downloads\\app-release8\\";// 下载离线js文件到目标apk的资源文件路径中String fullPath = apkOriginalPath + "original\\META-INF\\";downloadJsFile(reqVO);//删除签名文件File mkdir = FileUtil.mkdir(fullPath);//去掉签名FileUtils.deleteTempFiles(mkdir, fullPath);//重新打包try {String commandStr = "cmd /c C:\\Users\\***\\Downloads\\buildApk.bat";Runtime.getRuntime().exec(commandStr);} catch (IOException e) {}// 下载签名文件到dist目录中String packagePath = "dist";File packagePathFile = FileUtil.mkdir(apkOriginalPath + packagePath);String keystorePath = "https://***/apk/keystore/tinnove.keystore";ImageInfo appDesignDetailImageInfo = new ImageInfo();appDesignDetailImageInfo.setFilename("tinnove.keystore");appDesignDetailImageInfo.setPathUrl(keystorePath);downloadFile(packagePathFile, appDesignDetailImageInfo);// 加签名后打包APKtry {String commandStr = "cmd /c C:\\Users\\***\\Downloads\\rebuildKeystoreApk.bat";Runtime.getRuntime().exec(commandStr).waitFor(30, TimeUnit.MILLISECONDS);} catch (IOException e) {} catch (InterruptedException e) {e.printStackTrace();}// 重新构建后的apk文件地址String newApkName = "app-release.apk";String newApkPath = apkOriginalPath + "dist\\" + newApkName;// 将apk包返回给前端File file = new File(newApkPath);response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("app-release.apk", "UTF-8"));//获取文件的输入流InputStream fis = new FileInputStream(file);byte[] buffer = new byte[1024 * 5];int r;while ((r = fis.read(buffer)) != -1) {response.getOutputStream().write(buffer, 0, r);}// 删除build目录,方便下次打包FileUtils.deleteTempFiles(null, apkOriginalPath + "build");// 删除dist及目录下的apk包FileUtils.deleteTempFiles(null, apkOriginalPath + "dist");} catch (IOException e) {}}//liunx版下载public void apkShellDownload(InfoReqVO reqVO, HttpServletResponse response) {try {//下载原始apkFile designOfflineFile = cn.hutool.core.io.FileUtil.mkdir(outPth);String designOfflineApkPath = "https://***/apk/offline/app-release.apk";ImageInfo detailImageInfo = new ImageInfo();detailImageInfo.setFilename("app-release.apk");detailImageInfo.setPathUrl(designOfflineApkPath);downloadFile(designOfflineFile, detailImageInfo);//下载apktool.jar工具包String apktoolPath = "https://***/apk/offline/apktool.jar";ImageInfo imageInfo = new ImageInfo();imageInfo.setFilename("apktool.jar");imageInfo.setPathUrl(apktoolPath);downloadFile(designOfflineFile, imageInfo);//解压原始apkFileUtils.execSh("cd " + outPth + " && " + "java -jar " + outPth + " apktool.jar d " + outPth + "app-release.apk");// apk解压包路径String apkOriginalPath = outPth + "app-release/";// 下载离线js文件到目标apk的资源文件路径中String fullPath = apkOriginalPath + "original/META-INF/";downloadJsFile(reqVO);//删除签名文件 去掉签名cn.hutool.core.io.FileUtil.clean(fullPath);//重新打包FileUtils.execSh("cd " + outPth + " && " + " java -jar" + outPth + "apktool.jar b " + outPth + "app-release", 5, TimeUnit.MILLISECONDS);// 下载签名文件到dist目录中String packagePath = "dist/";File packagePathFile = cn.hutool.core.io.FileUtil.mkdir(apkOriginalPath + packagePath);String keystorePath = "https://***/apk/keystore/test.keystore";ImageInfo appDesignDetailImageInfo = new ImageInfo();appDesignDetailImageInfo.setFilename("test.keystore");appDesignDetailImageInfo.setPathUrl(keystorePath);downloadFile(packagePathFile, appDesignDetailImageInfo);// 加签名后打包APKFileUtils.execSh("cd " + outPth + " && " + "jarsigner -verbose -keystore test.keystore -storepass 123456 -signedjar "+ apkOriginalPath + packagePath + "app-release-offline.apk " + apkOriginalPath + packagePath + "app-release.apk test.keystore", 5, TimeUnit.MILLISECONDS);// List<File> fileList = cn.hutool.core.io.FileUtil.loopFiles(outPth);// 重新构建后的apk文件地址String newApkName = "app-release-offline.apk";String newApkPath = apkOriginalPath + packagePath;// 将apk包返回给前端File file = cn.hutool.core.io.FileUtil.file(newApkPath, newApkName);if (file.exists()) {response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("app-release.apk", "UTF-8"));//获取文件的输入流InputStream fis = new FileInputStream(file);byte[] buffer = new byte[1024 * 5];int r;while ((r = fis.read(buffer)) != -1) {response.getOutputStream().write(buffer, 0, r);}}// 删除build目录,方便下次打包cn.hutool.core.io.FileUtil.clean(apkOriginalPath + "build");// 删除dist及目录下的apk包cn.hutool.core.io.FileUtil.clean(apkOriginalPath + "dist");} catch (IOException e) {}}private void downloadFile(File target, ImageInfo detailImageInfo) throws IOException {File file = org.apache.commons.io.FileUtils.getFile(target, detailImageInfo.getFilename());FileOutputStream outputStream = new FileOutputStream(file);//获取文件的网络输入流byte[] bytes = cn.hutool.http.HttpUtil.downloadBytes(detailImageInfo.getPathUrl());InputStream fis = new ByteArrayInputStream(bytes);byte[] buffer = new byte[1024 * 5];int r;while ((r = fis.read(buffer)) != -1) {outputStream.write(buffer, 0, r);}fis.close();outputStream.close();}private void downloadJsFile(InfoReqVO reqVO) {try {//apk包所在的服务器路径String fullPath = outPth + "/app-release/" + "/assets/app-data/";//本地路径
// String fullPath = "C:\\Users\\xxx\\Downloads\\app-release\\assets\\app-data";
// String fullPath = designOfflinePath + "/" + reqVO.getDesignId() + "/" + reqVO.getCount() + "/";File target = cn.hutool.core.io.FileUtil.mkdir(new File(fullPath));
// File target = new File(fullPath + "preview");
//
// // 返回图片文件夹List<ImageInfo> designDetailImages = new ArrayList<>();downloadFiles(designDetailImages, target);// 返回逻辑连线 json文件List<Object> designLogicWiring = new ArrayList<>();String logicWiringListJs = "let logicWiringList = " + cn.hutool.json.JSONUtil.toJsonStr(designLogicWiring);FileUtils.object2JsonFile(fullPath + "logicWiring.js", logicWiringListJs);// 返回图片url json文件
// List<AppDesignDetailImageInfo> designDetailImageUrls = getImagesInfo(reqVO);
// String previewJs = "let previewImageUrls = " + JSONUtil.toJsonStr(designDetailImageUrls);
// FileUtils.object2JsonFile(fullPath + "preview.js", previewJs);// 返回分组 json文件List<Object> groupList = new ArrayList<>();String groupListJs = "let groupList = " + cn.hutool.json.JSONUtil.toJsonStr(groupList);FileUtils.object2JsonFile(fullPath + "group.js", groupListJs);} catch (Exception e) {}}private void downloadFiles(List<ImageInfo> designDetailImages, File target) {try {//将输出流转换成Zip输出流for (ImageInfo detailImageInfo : designDetailImages) {downloadFile(target, detailImageInfo);}} catch (IOException e) {}}}
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;/*** 文件处理工具类** @author*/
@Slf4j
public class FileUtils {/*** 字符常量:斜杠 {@code '/'}*/public static final char SLASH = '/';/*** 字符常量:反斜杠 {@code '\\'}*/public static final char BACKSLASH = '\\';public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+";/*** 输出指定文件的byte数组** @param filePath 文件路径* @param os 输出流* @return*/public static void writeBytes(String filePath, OutputStream os) throws IOException {FileInputStream fis = null;try {File file = new File(filePath);if (!file.exists()) {throw new FileNotFoundException(filePath);}fis = new FileInputStream(file);byte[] b = new byte[1024];int length;while ((length = fis.read(b)) > 0) {os.write(b, 0, length);}} catch (IOException e) {throw e;} finally {if (os != null) {try {os.close();} catch (IOException e1) {e1.printStackTrace();}}if (fis != null) {try {fis.close();} catch (IOException e1) {e1.printStackTrace();}}}}/*** 删除文件** @param filePath 文件* @return*/public static boolean deleteFile(String filePath) {boolean flag = false;File file = new File(filePath);// 路径为文件且不为空则进行删除if (file.isFile() && file.exists()) {file.delete();flag = true;}return flag;}/*** 文件名称验证** @param filename 文件名称* @return true 正常 false 非法*/public static boolean isValidFilename(String filename) {return filename.matches(FILENAME_PATTERN);}/*** 检查文件是否可下载** @param resource 需要下载的文件* @return true 正常 false 非法*/public static boolean checkAllowDownload(String resource) {// 禁止目录上跳级别if (StringUtils.contains(resource, "..")) {return false;}// 检查允许下载的文件规则if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) {return true;}// 不在允许下载的文件规则return false;}/*** 下载文件名重新编码** @param request 请求对象* @param fileName 文件名* @return 编码后的文件名*/public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException {final String agent = request.getHeader("USER-AGENT");String filename = fileName;if (agent.contains("MSIE")) {// IE浏览器filename = URLEncoder.encode(filename, "utf-8");filename = filename.replace("+", " ");} else if (agent.contains("Firefox")) {// 火狐浏览器filename = new String(fileName.getBytes(), "ISO8859-1");} else if (agent.contains("Chrome")) {// google浏览器filename = URLEncoder.encode(filename, "utf-8");} else {// 其它浏览器filename = URLEncoder.encode(filename, "utf-8");}return filename;}/*** 返回文件名** @param filePath 文件* @return 文件名*/public static String getName(String filePath) {if (null == filePath) {return null;}int len = filePath.length();if (0 == len) {return filePath;}if (isFileSeparator(filePath.charAt(len - 1))) {// 以分隔符结尾的去掉结尾分隔符len--;}int begin = 0;char c;for (int i = len - 1; i > -1; i--) {c = filePath.charAt(i);if (isFileSeparator(c)) {// 查找最后一个路径分隔符(/或者\)begin = i + 1;break;}}return filePath.substring(begin, len);}/*** 获取文件名,不带后缀** @param filePath* @return*/public static String getFilename(String filePath) {String name = getName(filePath);if (null == name) {return null;}if (name.contains(".")) {int end = name.indexOf(".");return name.substring(0, end);}return name;}/*** 获取文件后缀 如:.zip** @param filePath* @return*/public static String getFilenameSuffix(String filePath) {String name = getName(filePath);if (null == name) {return null;}if (name.contains(".")) {int end = name.indexOf(".");return name.substring(end);}return name;}/*** 是否为Windows或者Linux(Unix)文件分隔符<br>* Windows平台下分隔符为\,Linux(Unix)为/** @param c 字符* @return 是否为Windows或者Linux(Unix)文件分隔符*/public static boolean isFileSeparator(char c) {return SLASH == c || BACKSLASH == c;}/*** 下载文件名重新编码** @param response 响应对象* @param realFileName 真实文件名* @return*/public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {String percentEncodedFileName = percentEncode(realFileName);StringBuilder contentDispositionValue = new StringBuilder();contentDispositionValue.append("attachment; filename=").append(percentEncodedFileName).append(";").append("filename*=").append("utf-8''").append(percentEncodedFileName);response.setHeader("Content-disposition", contentDispositionValue.toString());response.setHeader("download-filename", percentEncodedFileName);}/*** 百分号编码工具方法** @param s 需要百分号编码的字符串* @return 百分号编码后的字符串*/public static String percentEncode(String s) throws UnsupportedEncodingException {String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());return encode.replaceAll("\\+", "%20");}/*** 获取路径下所有文件名和文件路径* 以,分割** @param dirPath 目录路径* @return hashMap name和url*/public static HashMap<String, String> getMapPath(String dirPath) {HashMap<String, String> pathMap = new HashMap<String, String>();File dirFile = new File(dirPath);String[] fileName = dirFile.list();StringJoiner joiner = new StringJoiner(",");for (String name : fileName) {joiner.add(dirPath + name);}pathMap.put("name", String.join(",", fileName));pathMap.put("url", joiner.toString());return pathMap;}public static boolean deleteAllFile(String dir) {File dirFile = new File(dir);// 如果dir对应的文件不存在,或者不是一个目录,则退出if ((!dirFile.exists()) || (!dirFile.isDirectory())) {return false;}boolean flag = true;// 删除文件夹中的所有文件包括子文件夹File[] files = dirFile.listFiles();for (int i = 0; i < files.length; i++) {// 删除子文件if (files[i].isFile()) {flag = deleteFileFlag(files[i].getAbsolutePath());if (!flag) {break;}}// 删除子文件夹else if (files[i].isDirectory()) {flag = deleteAllFile(files[i].getAbsolutePath());if (!flag) {break;}}}if (!flag) {return false;}// 删除当前文件夹if (dirFile.delete()) {return true;} else {return false;}}/*** 删除文件返回bool** @param fileName* @return boolean*/public static boolean deleteFileFlag(String fileName) {File file = new File(fileName);// 如果文件路径只有单个文件if (file.exists() && file.isFile()) {if (file.delete()) {return true;} else {return false;}} else {return false;}}/*** json文件转json对象** @param data 文件流* @return json对象*/public static Map readJsonFile(byte[] data) {Gson gson = new Gson();String json = "";try {Reader reader = new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8);int ch = 0;StringBuilder buffer = new StringBuilder(1024);while ((ch = reader.read()) != -1) {buffer.append((char) ch);}reader.close();json = buffer.toString();return gson.fromJson(json, Map.class);} catch (IOException e) {log.error("json文件转json对象失败,原因是e={}", e.getMessage());return Collections.emptyMap();}}/*** Object 转换为 json 文件** @param finalPath finalPath 是绝对路径 + 文件名,请确保欲生成的文件所在目录已创建好* @param content 需要被转换的 content*/public static void object2JsonFile(String finalPath, String content) {try {OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(finalPath), StandardCharsets.UTF_8);osw.write(content);osw.flush();osw.close();} catch (IOException e) {e.printStackTrace();}}/*** 将java对象转成json文件返回给前端** @param object 转换为 json* @param fileName json文件名称* @param response 结果*/public static void object2JsonFile(Object object, String fileName, HttpServletResponse response) {try {response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));//获取文件的网络输入流byte[] bytes = JSONUtil.toJsonStr(object).getBytes(StandardCharsets.UTF_8);InputStream fis = new ByteArrayInputStream(bytes);byte[] buffer = new byte[1024 * 5];int r;while ((r = fis.read(buffer)) != -1) {response.getOutputStream().write(buffer, 0, r);}} catch (IOException e) {e.printStackTrace();}}/*** 获取封装得MultipartFile** @param inputStream inputStream* @param fileName fileName* @return MultipartFile*/private MultipartFile getMultipartFile(InputStream inputStream, String fileName) {FileItem fileItem = createFileItem(inputStream, fileName);//CommonsMultipartFile是feign对multipartFile的封装,但是要FileItem类对象return new CommonsMultipartFile(fileItem);}/*** FileItem类对象创建** @param inputStream inputStream* @param fileName fileName* @return FileItem*/public FileItem createFileItem(InputStream inputStream, String fileName) {FileItemFactory factory = new DiskFileItemFactory(16, null);String textFieldName = "file";FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);int bytesRead = 0;byte[] buffer = new byte[8192];OutputStream os = null;//使用输出流输出输入流的字节try {os = item.getOutputStream();while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {os.write(buffer, 0, bytesRead);}inputStream.close();} catch (IOException e) {log.error("Stream copy exception", e);throw new IllegalArgumentException("文件上传失败");} finally {if (os != null) {try {os.close();} catch (IOException e) {log.error("Stream close exception", e);}}if (inputStream != null) {try {inputStream.close();} catch (IOException e) {log.error("Stream close exception", e);}}}return item;}public static int execSh(String bashCommand) {log.info("开始执行shell命令bashCommand={}", bashCommand);int status = 0;try {Runtime runtime = Runtime.getRuntime();String[] bash = {"/bin/bash", "-c", bashCommand};Process exec = runtime.exec(bash);status = exec.waitFor();if (status != 0) {return 1;}} catch (IOException | InterruptedException e) {log.error("执行shell命令bashCommand={}失败,原因是e={}", bashCommand, e.getMessage());}return status;}/*** 执行Shell脚本 0成功 1失败*/public static boolean execSh(String bashCommand, long time, TimeUnit timeUnit) {try {log.info("开始执行shell命令bashCommand={}", bashCommand);Runtime runtime = Runtime.getRuntime();String[] bash = {"/bin/bash", "-c", bashCommand};Process exec = runtime.exec(bash);return exec.waitFor(time, timeUnit);} catch (IOException | InterruptedException e) {log.error("执行shell命令bashCommand={}失败,原因是e={}", bashCommand, e.getMessage());}return false;}public static void deleteTempFiles(File file2, String descDir) {File file1 = new File(descDir);//删除zip解压的数据if (ObjectUtil.isNotEmpty(file1) && file1.exists()) {log.info("file1={}", file1.getPath());deleteFile(file1);}//删除zip文件//删除zip文件if (ObjectUtil.isNotEmpty(file2) && file2.exists()) {log.info("file2={}", file2.getPath());deleteFile(file2);}}public static void deleteFile(File file) {if (file == null) {log.info("deleteFile结果file=null");return;}if (file.isFile()) {boolean delete = file.delete();log.info("删除结果file={},result={}", file.getPath(), delete);} else if (file.isDirectory()) {for (File sub : file.listFiles()) {deleteFile(sub);}file.delete();}}/*** 根据byte数组,生成文件** @param bfile 文件数组* @param filePath 文件存放路径* @param fileName 文件名称*/public static File byte2File(byte[] bfile, String filePath, String fileName) {BufferedOutputStream bos = null;FileOutputStream fos = null;File file = null;try {File dir = new File(filePath);if (!dir.exists() && !dir.isDirectory()) {//判断文件目录是否存在dir.mkdirs();}file = new File(filePath + fileName);fos = new FileOutputStream(file);bos = new BufferedOutputStream(fos);bos.write(bfile);} catch (Exception e) {log.error("byte数组,生成文件失败,原因是e={}", e.getMessage());} finally {try {if (bos != null) {bos.close();}if (fos != null) {fos.close();}} catch (Exception e) {log.error(">>>> byte2File error" + e.getMessage());e.printStackTrace();}}return file;}public static byte[] base64StrToBytes(String base64Str) {byte[] bts = org.apache.tomcat.util.codec.binary.Base64.decodeBase64(base64Str);for (int k = 0; k < bts.length; ++k) {//调整异常数据if (bts[k] < 0) {bts[k] += 256;}}return bts;}}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** @Author:* @Description: 校验分享链接入参*/@Data
@ApiModel("入参")
public class ImageInfo {@ApiModelProperty(name = "id", value = "id", required = true)private String id;@ApiModelProperty(value = "资源路径")private String pathUrl;@ApiModelProperty(value = "文件名称")private String filename;}
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** 入参** @author **** @since 2023/02/16*/@Data
@ApiModel(入参")
public class InfoReqVO {@ApiModelProperty(value = "id")private Long id;@ApiModelProperty(value = "版本号")private Integer version;
}相关文章:
windows,liunx,java实现apk解压,去签名、重新签名,重新打包apk
背景:由于项目需要,需要将apk包加入服务端返回的静态资源文件到apk中,形成离线apk包供下载安装。经过调查研究,决定使用apktool实现。关于apktool的资料可以参考 https://blog.csdn.net/quantum7/article/details/124060620 htt…...
【Linux】进程信号
🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉信号入门&…...
SpringBoot 集成Junit单元测试
学习文章: https://www.cnblogs.com/ysocean/p/6889906.html 开发工具: IDEA 2022.1.4 目录 目录 1. 概述 2. 实现步骤 2.1 maven导入依赖 2.2 随意代码演示(不推荐) 2.3 规范代码演示(推荐) 3. Junit相关其他注解 4. 注意事项 5. 结语 1. 概述 接触到Junit,…...
Android开发之简单控件
文章目录一 文本显示1.1 文本设置的两种方式1.2 常见字号单位类型2.2 设置文本的颜色三 视图基础3.1 设置视图的宽高3.2 设置视图的间距3.3 设置视图的对齐方式四常用布局4.1 线性布局LinearLayout4.2 相对布局RelativeLayout4.3 网格布局GridLayout4.4 滚动视图ScrollView五 按…...
树状数组讲解
树状数组 文章目录树状数组引入例题AcWing241.楼兰图腾思路代码AcWing 242. 一个简单的整数问题思路代码AcWing 244. 谜一样的牛思路代码总结引入 树状数组主要维护的是这样一个数据结构: tr[x]表示以x为终点的长度为lowbit(x)的前缀和、最大值、最小值、最大公约数…...
每个Android开发都应需知的性能指标~
无论你是发布一个新的 Android 应用,还是希望提高现有应用的性能,你都可以使用 Android 应用性能指标来帮助你。 在这篇文章中,我将解释什么是 Android 应用性能指标,并列出8个需要考虑跟踪的维度和建议的基线。 什么是 Android…...
MSYS2安装
最近在学习windows上编译FFmpeg,需要用到msys2,在此记录一下安装和配置过程。 点击如下链接,下载安装包: Index of /msys2/distrib/x86_64/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 我下载的是:ms…...
3/3考试总结
时间安排 7:30–7:50 看题,怎么感觉三道构造,T3 貌似有网络流背景。 7:50–8:30 T1,有一些简单的性质,缩减两端点后枚举一下翻转的区间就可以了。然后花了一点时间写 spj 调试。 8:30–10:20 T2,比较纯粹的构造题。有网络流做法,…...
Spark Streaming DStream转换
DStream上的操作与RDD的类似,分为Transformations(转换)和Output Operations(输出)两种,此外转换操作中还有一些比较特殊的算子,如:updateStateByKey()、transform()以及各种Window相…...
水果商城,可运行
文章目录项目介绍一、技术栈二、本项目分为前后台,有管理员与用户两种角色;1、管理员角色包含以下功能:2、用户角色包含以下功能:三、用户功能页面展示四、管理员功能页面展示五、部分代码展示六、获取整套项目源码项目介绍 一、…...
LiveGBS国标GB/T28181国标视频流媒体平台-功能报警订阅配置报警预案告警截图及录像
LiveGBS国标GB/T28181国标视频流媒体平台-功能报警订阅配置报警预案告警截图及录像1、报警信息1.1、报警查询1.2、配置开启报警订阅1.2.1、国标设备编辑1.2.2、选择开启报警订阅1.3、配置摄像头报警1.3.1、配置摄像头报警通道ID1.3.2、配置摄像头开启侦测1.3.3、尝试触发摄像头…...
软件测试---测试分类
一 : 按测试对象划分 1.1 可靠性测试 可靠性(Availability)即可用性,是指系统正常运行的能力或者程度,一般用正常向用户提供软件服务的时间占总时间的百分比表示。 1.2 容错性测试 行李箱 , 四个轮子 , 坏了一个 , 说明这个容错…...
剑指 Offer II 015. 字符串中的所有变位词
题目链接 剑指 Offer II 015. 字符串中的所有变位词 mid 题目描述 给定两个字符串 s和 p,找到 s中所有 p的 变位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 变位词 指字母相同,但排列不同的字符串。 示例 1: 输…...
【SpringCloud】SpringCloud详细教程之微服务比较
目录前言一.什么是微服务?为什么要使用微服务二.微服务对比三.企业开发场景前言 我会通过实际代码来给展示每个组件的用法 一.什么是微服务?为什么要使用微服务 分布式,把一个项目拆分成多个模块,每一个模块相当于一个服务。 微…...
二.项目使用vue-router,引入ant-design-vue的UI框架,引入less
根据前文《使用Vue脚手架工具搭建vue项目》搭建好脚手架后使用 1.vue-router 2.引入UI框架ant design vue 3.引入less 1.vue-router vue-router分为两种模式(默认为hash模式): hash history hash: 特征: 1.hash会在浏览器路径里带#号&#…...
网络安全怎么学?20年白帽子老江湖告诉你
很多人都知道龙叔是个老程序员,但却不知道其实我也是个H客,20年前我就开始痴迷于H客技术,可以说是网络安全方面的老江湖了。 到现在,我还依然会去研究这一块,偶尔会和一些网安的朋友交流技术,比如说红盟的…...
药房管理系统;药库管理系统
第一,主要功能: 本系统集日常销售、药品进销存、会员积分、GSP管理等药店所需的所有功能于一体,实现店铺管理的全部自动化。第二、新功能: 增加了“按功能查询药品”的功能,使软件用户可以根据客户的症状推荐合适…...
深眸科技|机器视觉提升制造性能,焕发传统企业智造新活力!
随着机器视觉技术的成熟与发展,其在工业制造中得到越来越广泛的应用。机器视觉在工业制造领域的应用朝着智能识别、智能检测、智能测量以及智能互联的完整智能体系方向发展。此外,快速变化的市场需求,不断涌入行业的竞争对手,让传…...
ubuntu安装SSH的方法
Ubuntu安装SSH的方法。14版的ubuntu经过测试,默认没有开启SSH,所以需要安装。 1、虚拟机设置网卡为桥接模式,即NAT。12版虚拟机默认的。 2、查看ubuntu使用的ip。 ifconfig即可查看,14版的ubuntu自带这个命令。 3、查看是否pi…...
哪种蓝牙耳机通话效果好?通话清晰的蓝牙耳机推荐
出门的时候,如果戴耳机和别人通话,就不必把耳机摘下来,接电话变得前所未有的简单。现在的蓝牙耳机,已经不是单纯的用来听音乐了,而是一种更好的功能。下面这四款蓝牙耳机不仅适合听歌,通话还清晰࿰…...
从网站点击到疾病预测:泊松回归模型在5个真实业务场景下的应用拆解与避坑指南
从网站点击到疾病预测:泊松回归模型在5个真实业务场景下的应用拆解与避坑指南 在数据驱动的商业决策中,计数型数据的分析往往被忽视。想象一下:电商平台每天需要决定发送多少条推送通知,客服中心要预测每小时可能接到的投诉电话数…...
揭秘开源智能字幕系统:如何用AI实现高效的多语言内容本地化
揭秘开源智能字幕系统:如何用AI实现高效的多语言内容本地化 【免费下载链接】openlrc Transcribe and translate voice into LRC file using Whisper and LLMs (GPT, Claude, et,al). 使用whisper和LLM(GPT,Claude等)来转录、翻译你的音频为字幕文件。 …...
深入解析 magic-cli:基于模板的自动化代码生成工具设计与实践
1. 项目概述:一个能“变魔术”的命令行工具最近在折腾一些自动化脚本和项目脚手架时,发现了一个挺有意思的开源项目,叫magic-cli。乍一看这个名字,你可能会觉得有点玄乎,命令行工具还能玩出什么“魔法”来?…...
告别日志硬编码:BizLog组件在SpringBoot中的实战应用指南
1. 为什么我们需要BizLog组件 记得去年接手一个电商项目时,遇到一个典型问题:产品经理要求在用户下单、修改订单、取消订单等关键操作时,都要记录详细的操作日志。刚开始我直接在业务代码里写日志记录逻辑,结果不到一个月就发现代…...
别再硬编码IP了!深入Nacos 2.x源码,看它如何‘智能’又‘犯错’地选择服务端地址
Nacos 2.x服务端IP地址选择机制深度解析与实战调优 在分布式系统架构中,服务注册与发现是微服务架构的核心基础设施。作为阿里巴巴开源的服务发现和配置管理平台,Nacos凭借其简单易用、功能强大等特点,已成为众多企业微服务架构的首选组件。…...
魔兽争霸3兼容性修复终极指南:5步解决现代系统闪退问题
魔兽争霸3兼容性修复终极指南:5步解决现代系统闪退问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 你是否还在为魔兽争霸3在现代Windo…...
安卓android无法创建文件夹权限-幽冥大陆(一百21)-东方仙盟
谷歌从安卓 6 开始强制规定直接锁死:根目录 /、system、storage 根目录 全部禁止 APP 写入。目的:防流氓软件乱改系统、乱建文件夹、乱篡改系统文件。瑞芯微等主板厂商二次加锁RK、全志、晶晨这类工控主板,还额外加了两层限制:分区…...
PlantUML Editor:5分钟学会用代码绘制专业UML图的终极工具
PlantUML Editor:5分钟学会用代码绘制专业UML图的终极工具 【免费下载链接】plantuml-editor PlantUML online demo client 项目地址: https://gitcode.com/gh_mirrors/pl/plantuml-editor 还在为复杂的UML图表绘制而烦恼吗?PlantUML Editor是一款…...
ARM SCTLR2_EL2寄存器解析与虚拟化安全控制
1. ARM SCTLR2_EL2寄存器架构解析SCTLR2_EL2是ARMv8/v9架构中EL2(Hypervisor)级别的扩展系统控制寄存器,作为标准SCTLR_EL2的补充,它通过掩码位机制实现了对关键系统功能的细粒度控制。这个64位寄存器主要包含两类功能字段&#x…...
【NotebookLM移动端避坑白皮书】:上线首月超12万用户踩中的3类权限陷阱与2种文档同步丢失根因分析
更多请点击: https://intelliparadigm.com 第一章:NotebookLM移动端避坑白皮书导论 NotebookLM 是 Google 推出的基于用户上传文档构建个性化 AI 助手的实验性工具,其移动端(iOS/Android)虽提供便捷访问入口ÿ…...
