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

哪种蓝牙耳机通话效果好?通话清晰的蓝牙耳机推荐
出门的时候,如果戴耳机和别人通话,就不必把耳机摘下来,接电话变得前所未有的简单。现在的蓝牙耳机,已经不是单纯的用来听音乐了,而是一种更好的功能。下面这四款蓝牙耳机不仅适合听歌,通话还清晰࿰…...

IT运维如何完成一场高质量复盘
复盘的终极目标是:还原事实,找到薄弱点加以改进。 提到复盘,很多人的第一反应是线上故障,有人要背锅了。 复盘真正的价值是还原事实,在薄弱处加以改进。如何做一次高质量的复盘,我们给出3点建议。 1、坦…...

JVM调优面试题——基础知识
文章目录1、JDK,JRE以及JVM的关系2、编译器到底干了什么事?3、类加载机制是什么?3.1、装载(Load)3.2、链接(Link)3.3、初始化(Initialize)4、类加载器有哪些?5、什么是双亲委派机制?6、介绍一下JVM内存划分(…...

三、mongdb 查询
一、 MongoDB文档检索 MongoDB中有多种方式可以检索文档: 1.1 查询过滤器 使用查询过滤器从集合中检索文档。查询过滤器是一组键值对,可按字段值查询文档。 例如: db.col.find({"status":"A"})这个示例查询status等于“A”的文档。 1.2 范围查询操作符…...

python的 ping 网络状态监测方法(含多IP)
ping 基本概念 ping (Packet Internet Groper)是一种因特网包探索器,用于测试网络连接量的程序。Ping是工作在 TCP/IP网络体系结构中应用层的一个服务命令, 主要是向特定的目的主机发送 ICMP(Internet Control Messag…...

【独家】华为OD机试提供C语言题解 - 单词反转
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明单词…...

Linux docker环境安装,docker-compose安装,jdk17安装
安装docker 删除之前安装的docker yum remove docker \docker-client \docker-client-latest \docker- common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-sqlinux \docker-engine-selinux \docker-engine \docker-ce安装yum工具 yum install -y y…...

界面开发(3)--- PyQt5用户登录界面连接数据库
文章目录数据库账户注册账号登录找回密码为了实现用户登录界面的登录功能,我们必须建立一个数据库,并把账号和对应的密码,存储到数据库中。如果输入的账号和密码与数据库中的一致,那我们就允许用户登录,进入新的界面。…...

以下真的没有任何要写的了,我需要凑字数,请大家原谅
以下真的没有任何要写的了,我需要凑字数,请大家原谅!!!!!!!!!!!!!!!&#…...

2023年 Java 发展趋势
GitHub 语言统计表明,Java在编程语言中排名第二,而在2022年的TIOBE指数中,Java排在第四。 抛开排名,Java是自诞生以来企业使用率最高的编程语言,作为一种编程语言,它比许多竞争对手都有更多的优点…...

Lsof命令介绍
LSOF(List Open Files)是一款功能强大的开源工具,用于列出当前系统上打开的文件和进程。该工具可以帮助系统管理员和开发人员快速查找正在使用某个文件的进程,以及在系统上使用磁盘空间最多的进程。 本文将介绍LSOF的基本用法和常…...