当前位置: 首页 > news >正文

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

背景&#xff1a;由于项目需要&#xff0c;需要将apk包加入服务端返回的静态资源文件到apk中&#xff0c;形成离线apk包供下载安装。经过调查研究&#xff0c;决定使用apktool实现。关于apktool的资料可以参考 https://blog.csdn.net/quantum7/article/details/124060620 htt…...

【Linux】进程信号

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;信号入门&…...

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&#xff0c;…...

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. 谜一样的牛思路代码总结引入 树状数组主要维护的是这样一个数据结构&#xff1a; tr[x]表示以x为终点的长度为lowbit(x)的前缀和、最大值、最小值、最大公约数…...

每个Android开发都应需知的性能指标~

无论你是发布一个新的 Android 应用&#xff0c;还是希望提高现有应用的性能&#xff0c;你都可以使用 Android 应用性能指标来帮助你。 在这篇文章中&#xff0c;我将解释什么是 Android 应用性能指标&#xff0c;并列出8个需要考虑跟踪的维度和建议的基线。 什么是 Android…...

MSYS2安装

最近在学习windows上编译FFmpeg&#xff0c;需要用到msys2&#xff0c;在此记录一下安装和配置过程。 点击如下链接&#xff0c;下载安装包&#xff1a; Index of /msys2/distrib/x86_64/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 我下载的是&#xff1a;ms…...

3/3考试总结

时间安排 7:30–7:50 看题&#xff0c;怎么感觉三道构造&#xff0c;T3 貌似有网络流背景。 7:50–8:30 T1,有一些简单的性质&#xff0c;缩减两端点后枚举一下翻转的区间就可以了。然后花了一点时间写 spj 调试。 8:30–10:20 T2,比较纯粹的构造题。有网络流做法&#xff0c;…...

Spark Streaming DStream转换

DStream上的操作与RDD的类似&#xff0c;分为Transformations&#xff08;转换&#xff09;和Output Operations&#xff08;输出&#xff09;两种&#xff0c;此外转换操作中还有一些比较特殊的算子&#xff0c;如&#xff1a;updateStateByKey()、transform()以及各种Window相…...

水果商城,可运行

文章目录项目介绍一、技术栈二、本项目分为前后台&#xff0c;有管理员与用户两种角色&#xff1b;1、管理员角色包含以下功能&#xff1a;2、用户角色包含以下功能&#xff1a;三、用户功能页面展示四、管理员功能页面展示五、部分代码展示六、获取整套项目源码项目介绍 一、…...

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 可靠性测试 可靠性&#xff08;Availability&#xff09;即可用性&#xff0c;是指系统正常运行的能力或者程度&#xff0c;一般用正常向用户提供软件服务的时间占总时间的百分比表示。 1.2 容错性测试 行李箱 , 四个轮子 , 坏了一个 , 说明这个容错…...

剑指 Offer II 015. 字符串中的所有变位词

题目链接 剑指 Offer II 015. 字符串中的所有变位词 mid 题目描述 给定两个字符串 s和 p&#xff0c;找到 s中所有 p的 变位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 变位词 指字母相同&#xff0c;但排列不同的字符串。 示例 1&#xff1a; 输…...

【SpringCloud】SpringCloud详细教程之微服务比较

目录前言一.什么是微服务&#xff1f;为什么要使用微服务二.微服务对比三.企业开发场景前言 我会通过实际代码来给展示每个组件的用法 一.什么是微服务&#xff1f;为什么要使用微服务 分布式&#xff0c;把一个项目拆分成多个模块&#xff0c;每一个模块相当于一个服务。 微…...

二.项目使用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模式)&#xff1a; hash history hash&#xff1a; 特征&#xff1a; 1.hash会在浏览器路径里带#号&#…...

网络安全怎么学?20年白帽子老江湖告诉你

很多人都知道龙叔是个老程序员&#xff0c;但却不知道其实我也是个H客&#xff0c;20年前我就开始痴迷于H客技术&#xff0c;可以说是网络安全方面的老江湖了。 到现在&#xff0c;我还依然会去研究这一块&#xff0c;偶尔会和一些网安的朋友交流技术&#xff0c;比如说红盟的…...

药房管理系统;药库管理系统

第一&#xff0c;主要功能&#xff1a;  本系统集日常销售、药品进销存、会员积分、GSP管理等药店所需的所有功能于一体&#xff0c;实现店铺管理的全部自动化。第二、新功能&#xff1a;  增加了“按功能查询药品”的功能&#xff0c;使软件用户可以根据客户的症状推荐合适…...

深眸科技|机器视觉提升制造性能,焕发传统企业智造新活力!

随着机器视觉技术的成熟与发展&#xff0c;其在工业制造中得到越来越广泛的应用。机器视觉在工业制造领域的应用朝着智能识别、智能检测、智能测量以及智能互联的完整智能体系方向发展。此外&#xff0c;快速变化的市场需求&#xff0c;不断涌入行业的竞争对手&#xff0c;让传…...

ubuntu安装SSH的方法

Ubuntu安装SSH的方法。14版的ubuntu经过测试&#xff0c;默认没有开启SSH&#xff0c;所以需要安装。 1、虚拟机设置网卡为桥接模式&#xff0c;即NAT。12版虚拟机默认的。 2、查看ubuntu使用的ip。 ifconfig即可查看&#xff0c;14版的ubuntu自带这个命令。 3、查看是否pi…...

哪种蓝牙耳机通话效果好?通话清晰的蓝牙耳机推荐

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

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...