Android 打包(构建)api不显示备案所需的MD5如何处理?

大部分的时候开发app都是给自己用,在手机上安装就可以了。

但是如果你想把你的apk上传到各大市场去,给别人使用,那么在中国大陆你的app就一定要备案。

在备案的过程中有一项就是填写你的MD5指纹,具体是什么,大家可以去搜索下,这里不过多的阐述了。

如果Android Studio是老版本的话,在构建apk的时候会让你选择是否使用版本1和版本2进行签名,如下:

但是如果是比较新的版本Android Studio是没有选择的,如下图所示:

而且我们通过新版本的Android Studio创建的密钥也没有MD5,

原因是Google公司觉得MD5不安全,所以隐藏了,所以一般你去看指纹的话只有SHA1和SHA256的信息。

但是备案需要,我们可以通过其他方式可以获取。

  • 第一步:手动或者自动获取Key文件
keytool -genkeypair -v -keystore my-app-release.jks -keyalg RSA -keysize 2048 -validity 36500 -alias app-release-key

my-app-release.jks:名称,app-release-key:别名,自己自定义就可以了。

  • 生成文件证书(为了获取md5)
keytool -exportcert -keystore my-app-release.jks -alias app-release-key -file cert.cer
  • 显示(备案)md5
certutil -hashfile cert.cer MD5
  • 生成(备案)公钥
certutil -encode cert.cer cert_base64.txt

这样基本上能解决新版本Android Studio不显示md5的情况了。

JavaScript 中 箭头函数(Arrow Function)的语法差异.

bug重现

在项目调试的时候,发现javascript在过滤数组的时候,发现竟然为空数组。

var ages = [32, 33, 16, 40]; //想要获取除了第二个元素外的所有元素
//第一种写法:
var a = ages.filter((_, index) => index !== 1);

//第二种写法:
var a = ages.filter((_, index) => { index !== 1 });

这两种写法,就因为有一个{}导致结果完全不一样,第一种写法能够返回预想中的效果。

第二种写法就会返回一个空的数组。

原因

  • 第一种写法,没有{},那么他就不是一个代码块,等于直接返回 index !== 1 的元素。
  • 第二种写法,因为有{},那么就是一个代码块,而我们没有给return,那么就返回一个undefined。

修改:

如果我们硬性要求添加{},那么我们就应该添加一个return

var a = ages.filter((_, index) => { return index !== 1 });

写个简单的加密解密的传输数据

在Android开发或者其他需要通讯的过程中我们都喜欢把参数进行加密来保证数据在传输过程中的安全。

这里我们用java写个简单的对称加密解密算法。

package xxx.xxx

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidParameterException;
import java.security.SecureRandom;
import java.util.Base64;


public class CryptionUtil {

    /**
     * 内容加密
     * @param content 需要加密的内容
     * @param secretKey 密钥
     * @return
     * @throws Exception
     */
    public static String encrypt(String content, String secretKey) throws Exception {
        byte[] key = Base64.getDecoder().decode(secretKey);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

        byte[] iv = new byte[16];
        new SecureRandom().nextBytes(iv);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);

        byte[] cipherText = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
        String res = Base64.getEncoder().encodeToString(cipherText);
        return String.join("~split~", Base64.getEncoder().encodeToString(iv), res);
    }

    /**
     * 内容解密
     * @param cipherText 需要解密的内容
     * @param secretKey 解密密钥
     * @return
     * @throws Exception
     */
    public static String decrypt(String cipherText, String secretKey) throws Exception {
        byte[] key = Base64.getDecoder().decode(secretKey);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

        String[] arr = cipherText.split("~split~");
        if (arr == null || arr.length != 2) {
            throw new InvalidParameterException();
        }
        byte[] iv = Base64.getDecoder().decode(arr[0]);
        IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);

        byte[] decryptedText = cipher.doFinal(Base64.getDecoder().decode(arr[1]));
        return new String(decryptedText, StandardCharsets.UTF_8);
    }


}

现在我们简单的测试下:

加密

      

        HashMap<String,Object> params = new HashMap<>();
        params.put("id","66937s9nnah2779293223hhad");
        params.put("content_type","userinfo");
        params.put("title","这是标题");
        params.put("name","这是姓名");
        params.put("image","https://blog.jishuge.cn/avtar.jpg");


        try {
            String key = "ABCDT5F8Dgn12345";  //这里是密钥,加密和解密都用到它,不传输
            String seceretKey = Base64.getEncoder().encodeToString(key.getBytes());
            String encrypt = CryptionUtil.encrypt(JSONObject.toJSONString(params),seceretKey);
            System.out.println(encrypt);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }

解密

        //被加密之后的密文
        String message = "sGSCA05A9aBY0rIKwgWkEQ==~split~cRFS++NpEZGHZXW06k0p/0l3o4Et2+PrZpr1GYx3TkO8Zj9T725bhB5OzEubwhfxEMOf67qG4U/xXGLgXBdMGhupVpOFaLCOzHJUCuoYWbo=";
        String key = "ABCDT5F8Dgn12345";
        String seceretKey = Base64.getEncoder().encodeToString(key.getBytes());
        try {
            String decrypt = CryptionUtil.decrypt(message,seceretKey);
             System.out.println(decrypt);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

我们这个算法设置的密钥必须是16位,自己可以根据自己的需求来修改密钥长度。

SpringBoot 3.4.0置顶Redis数据库Unable to connect to Redis 错误

正常配置

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password: 123456
      database: 6

如果以上配置在3.4.0中使用,那么系统提示:Unable to connect to Redis,连接上不上redis服务器。

在3.4.0之前的版本是可以执行的。3.4.0的版本去掉database指定数据库

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password: 123456

这样就正常了。

解决electron安装慢的问题。

如果是在国内安装electron项目,我们发现安装很慢,甚至失败。

我们即使更换了npm的源,也是安装失败或者缓慢,

根本的原因是electron的包是直接从github上面下载的,而且大约90M的体积。

那么下载速度就几十KB的速度,非常慢,甚至中断。

那么我们可以手动的设置electron的镜像源,这样就不用直接去github下载了。

操作如下

首先通过设置环境变量当独设置electron的镜像源。

$env:ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"

然后执行下载代码

npm install --save-dev electron

Android Gradle 国内配置

1、分发配置

  • 位置:项目根目录/gradle/wrapper/gradle-wrapper.properties
修改成腾讯云的源地址x.x是你的版本,这个不用替换,只要替换域名和后面的路径。
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-x.x-bin.zip

2、增加包的下载源

  • 位置:项目的根目录/settings.gradle

Google的注释之后会有问题。但是也可以先尝试用aliyun下载大部分包,如果不行,再还原回来,剩下的用google源下载。

然后同步更新就可以了,如果旧的配置已经在更新中了,可以直接关闭Android Studio IDEA然后再打开。

全局变量带来的bug。

在类中定义了一个全局变量List<String> list存储用请求得到的结果,但是每次使用之前没有忘记清空,导致数据就会叠加在里面,结果就是后面使用的用户,能看到前面那位用户请求的结果。

幸好项目的数据不是非常敏感,如果是非常重要的信息,那么这将会是史诗级的程序灾难。

这让我想起了上次阿里云盘导致的权限问题,也是用户能看到其他用户的信息,而阿里的主要技术编程是java,那么真正引起的问题是不是某个程序定义了一个全局变量,而这个全局变量又没有进行及时的清空导致的呢?

VUE3中使用动态组件

有时候一个页面中我们需要加载很多个组件,而且这些组件需要根据服务器请求的数据来判断该用哪个组件。

举个使用场景:我们在做IM或者客服软件,在加载聊天记录的时候,就要根据服务器给出不同类型的消息加载不同的消息组件,有文本,图片,视频,音频,表情,附件等等。

如果我们用v-if也是可以做的,在组件多的情况下代码看起来就特别的臃肿,那么我们这个时候就可以用动态组件来做这个事情。

我们使用vue框架提供的动态组件component来做。

<component :is="item.component" v-for="(item,index) in messageList":key="index" :message="item"></component>

如果此代码放在vue2中是完全没有问题的,因为item.component是动态的组件名称。如果在vue3中就无效了。

原因是vue3使用的是组件实例,不再是组件名称。

那么我们需要添加一些东西

<script setup>
import ComA "./ComponentA";
import ComB "./ComponentB";

defineOptions({
  components: {
    ComA , ComB 
  }
})

</script>

我看网络上有写说用resolveComponent动态实例化,如下:

<component :is="resolveComponent(item.component)" v-for="(item,index) in messageList":key="index" :message="item"></component>


<script setup>
import {resolveComponent} from "vue";
import ComA "./ComponentA";
import ComB "./ComponentB";

</script>

在本人电脑上尝试无效果。

SpringBoot多模块中Service模块无法加载Entity模块程序包

Service模块能够正常调用Entity模块方法,但是一到统一打包的时候发现Entity某个程序包不存在

这种原因导致的原因是Entity本身不能通过Maven打包成可执行包。

我们可以把Entity打成可以执行的包就不会出现上述问题了。

<plugins>
	<plugin>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-maven-plugin</artifactId>
		<configuration>
			<mainClass>com.jd.entity.EntityApplication</mainClass>
			<classifier>exec</classifier>
		</configuration>
	</plugin>
</plugins>

关于用汇编语言从硬盘读取数据

MBR主扇区代码,代码从1号扇区,第2块扇区读取数据

[org 0x7c00]
[bits 16]

start:
    ; 设置段寄存器
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7c00

    ; 设置读取参数
    mov ah, 0x02        ; BIOS读取扇区功能
    mov al, 1           ; 读取1个扇区
    mov ch, 0           ; 柱面号 = 0
    mov cl, 2           ; 扇区号 = 2
    mov dh, 0           ; 磁头号 = 0
    mov dl, 0x80        ; 驱动器号 = 0x80 (第一个硬盘)
    mov bx, buffer      ; ES:BX = 缓冲区地址

    ; 读取扇区
    int 0x13
    jc error            ; 如果CF=1,跳转到错误处理

    ; 显示成功消息
    mov si, success_msg
    call print_string

    ; 显示读取的字符串
    mov si, buffer
    call print_string

    jmp $               ; 无限循环

error:
    push ax             ; 保存错误代码
    mov si, error_msg
    call print_string
    
    ; 显示具体错误原因
    pop ax              ; 恢复错误代码
    call print_error

    jmp $               ; 无限循环

print_string:
    push ax
    mov ah, 0x0e        ; BIOS 电传打字机输出
.loop:
    lodsb               ; 加载SI指向的字节到AL,并递增SI
    test al, al         ; 检查是否到字符串结尾(0x00)
    jz .done            ; 如果是0,结束打印
    int 0x10            ; 否则,打印字符
    jmp .loop
.done:
    pop ax
    ret

print_error:
    push si
    mov si, unknown_error
    cmp ah, 0x01
    je .print
    mov si, invalid_command
    cmp ah, 0x04
    je .print
    mov si, reset_failed
    cmp ah, 0x05
    je .print
    mov si, disk_changed
    cmp ah, 0x06
    je .print
    mov si, dma_boundary
    cmp ah, 0x09
    je .print
    mov si, bad_sector
    cmp ah, 0x0A
    je .print
    mov si, bad_track
    cmp ah, 0x0B
    je .print
    mov si, media_type
    cmp ah, 0x0C
    je .print
    mov si, sector_not_found
    cmp ah, 0x10
    je .print
    mov si, general_failure
    cmp ah, 0x20
    je .print
    mov si, unknown_error
.print:
    call print_string
    pop si
    ret

success_msg db 'Read successful. Content: ', 0
error_msg db 'Error reading disk. Reason: ', 0
unknown_error db 'Unknown error', 0
invalid_command db 'Invalid command', 0
reset_failed db 'Disk reset failed', 0
disk_changed db 'Disk changed', 0
dma_boundary db 'DMA boundary error', 0
bad_sector db 'Bad sector', 0
bad_track db 'Bad track', 0
media_type db 'Unsupported media type', 0
sector_not_found db 'Sector not found', 0
general_failure db 'General failure', 0

buffer:

times 510-($-$$) db 0
dw 0xaa55

第2块扇区第1号扇区的数据,剩下的全部都设置为0x00

Data db 'Hi, I come from hard disk drive!'
times 510-($-$$) db 0x00

使用Nasm编译器编译代码,分别写入第1块和第二块扇区,数据如下。

通过VirtualBox来运行