網易雲VIP音樂NCM文件轉MP3,C語言版本。

来源:https://www.cnblogs.com/duichoumian/archive/2022/12/16/16985479.html
-Advertisement-
Play Games

前言 網易雲的Vip音樂下載下來,格式不是mp3/flac這種通用的音樂格式,而是經過加密的ncm文件。只有用網易雲的音樂App才能夠打開。於是想到可不可以把.ncm文件轉換成mp3或者flac文件,上google查了一下,發現有不少人已經做了這件事,但沒有發現C語言版本的,就想著寫一個純C語言版本 ...


前言

網易雲的Vip音樂下載下來,格式不是mp3/flac這種通用的音樂格式,而是經過加密的ncm文件。只有用網易雲的音樂App才能夠打開。於是想到可不可以把.ncm文件轉換成mp3或者flac文件,上google查了一下,發現有不少人已經做了這件事,但沒有發現C語言版本的,就想著寫一個純C語言版本的ncm轉mp3/flac。

NCM文件結構

ncm文件的結構,網上有人解析出來了,分為下麵幾個部分

信息 大小 說明
Magic Header 10 bytes 文件頭
Key Length 4 bytes AES128加密後的RC4密鑰長度,位元組是按小端排序。
Key Data Key Length 用AES128加密後的RC4密鑰。
1. 先按位元組對0x64進行異或。
2. AES解密,去除填充部分。
3. 去除最前面'neteasecloudmusic'17個位元組,得到RC4密鑰。
Music Info Length 4 bytes 音樂相關信息的長度,小端排序。
Music Info Data Music Info Length Json格式音樂信息數據。
1. 按位元組對0x63進行異或。
2. 去除最前面22個位元組。
3. Base64進行解碼。
4. AES解密。
6. 去除前面6個位元組得到Json數據。
CRC 4 bytes 跳過
Gap 5 bytes 跳過
Image Size 4 bytes 圖片的大小
Image Image Size 圖片數據
Music Data - 1. RC4-KSA生成S盒。
2. 用S盒解密(自定義的解密方法),不是RC4-PRGA解密。

兩個AES對應密鑰
unsigned char meta_key[] = { 0x23,0x31,0x34,0x6C,0x6A,0x6B,0x5F,0x21,0x5C,0x5D,0x26,0x30,0x55,0x3C,0x27,0x28 };
unsigned char core_key[] = { 0x68,0x7A,0x48,0x52,0x41,0x6D,0x73,0x6F,0x35,0x6B,0x49,0x6E,0x62,0x61,0x78,0x57 };
不得不佩服當初破解這個東西的人,不僅把文件結構摸得請清楚楚,還把密鑰也搞到手,應該是個破解大神。有了上面的東西,剩下的就很簡單了,按部就班來就行了。

一些演算法準備

開始前我們需要把AES演算法,BASE64演算法,RC4演算法和Json解析演算法先寫好。
除此之外還有一個編碼問題,解析出來的ncm文件是用utf-8編碼存儲的,所以它在中文windows系統下漢字會出現亂碼,因為中文windows系統採用的編碼是GBK,兩者不相容,所以我們要寫一個編碼轉換演算法,將utf8格式字元串轉位GBK的。Linux下不用轉換,Linux本身就是用UTF-8的。
C語言沒有這些庫,都要自己來。

  • AES用GitHub上的
    tiny-AES-c
  • JSON用GitHub上的CJSON
    cJSON
  • Base64和RC4演算法比較簡單我們自己寫
unsigned char* base64_decode(unsigned char* code,int len,int * actLen)
{
    //根據base64表,以字元找到對應的十進位數據  
    int table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,
             0,0,0,0,0,0,0,0,0,0,0,0,
             0,0,0,0,0,0,0,0,0,0,0,0,
             0,0,0,0,0,0,0,62,0,0,0,
             63,52,53,54,55,56,57,58,
             59,60,61,0,0,0,0,0,0,0,0,
             1,2,3,4,5,6,7,8,9,10,11,12,
             13,14,15,16,17,18,19,20,21,
             22,23,24,25,0,0,0,0,0,0,26,
             27,28,29,30,31,32,33,34,35,
             36,37,38,39,40,41,42,43,44,
             45,46,47,48,49,50,51
    };
    long str_len;
    unsigned char* res;
    int i, j;

    //計算解碼後的字元串長度  
    //判斷編碼後的字元串後是否有=
    if (strstr(code, "=="))
        str_len = len / 4 * 3 - 2;
    else if (strstr(code, "="))
        str_len = len / 4 * 3 - 1;
    else
        str_len = len / 4 * 3;

    *actLen = str_len;
    res = malloc(sizeof(unsigned char) * str_len + 1);
    res[str_len] = '\0';

    //以4個字元為一位進行解碼  
    for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
    {
        res[j] = ((unsigned char)table[code[i]]) << 2 | (((unsigned char)table[code[i + 1]]) >> 4); 
        res[j + 1] = (((unsigned char)table[code[i + 1]]) << 4) | (((unsigned char)table[code[i + 2]]) >> 2); 
        res[j + 2] = (((unsigned char)table[code[i + 2]]) << 6) | ((unsigned char)table[code[i + 3]]);
    }
    return res;

}
  • RC4生成S盒
//用key生成S盒
/*
* s: s盒
* key: 密鑰
* len: 密鑰長度
*/
void rc4Init(unsigned char* s, const unsigned char* key, int len) 
{   
    int i = 0, j = 0;
    unsigned char T[256] = { 0 };
  
    for (i = 0; i < 256; i++)
    {
        s[i] = i;
        T[i] = key[i % len];
    }
  
    for (i = 0; i < 256; i++) 
    {
        j = (j + s[i] + T[i]) % 256;
        unsigned tmp = s[i];
		s[i]=s[j];
		s[j]=tmp;
    }
}
//針對NCM文件的解密
//異或關係
/*
* s: s盒
* data: 要加密或者解密的數據
* len: data的長度
*/
void rc4PRGA(unsigned char* s, unsigned char* data, int len) 
{
    int i = 0;
    int j = 0;
    int k = 0;
    int idx = 0;
    for (idx = 0; idx < len; idx++) 
    {
        i = (idx + 1) % 256;
        j = (i + s[i]) % 256;
        k= (s[i] + s[j]) % 256;
        data[idx]^=s[k];  //異或
    }
}
  • Windows下utf8轉GBK
#ifdef WIN32
#include<Windows.h>
//返迴轉換好的字元串指針
unsigned char* utf8ToGbk(unsigned char*src,int len)
{
	wchar_t* tmp = (wchar_t*)malloc(sizeof(wchar_t) * len+2);
	unsigned char* newSrc = (unsigned char*)malloc(sizeof(unsigned char) * len + 2);
	
	MultiByteToWideChar(CP_UTF8, 0, src, -1, tmp, len);
	WideCharToMultiByte(CP_ACP, 0, tmp, -1, newSrc, len+2, NULL,NULL);
	return newSrc;
}
#endif

NCM文件解析

按照NCM文件結構一步一步讀取數據來進行解析

//fileName:要轉換的文件
void readFileData(const char* fileName)
{
	FILE* f;
	f = fopen(fileName, "rb");
	if (!f)
	{
		printf("No such file: %s\n", fileName);
		return;
	}
	
	unsigned char buf[16];
	int len=0;
	int i = 0;

	unsigned char meta_key[] = { 0x23,0x31,0x34,0x6C,0x6A,0x6B,0x5F,0x21,0x5C,0x5D,0x26,0x30,0x55,0x3C,0x27,0x28 };
	unsigned char core_key[] = { 0x68,0x7A,0x48,0x52,0x41,0x6D,0x73,0x6F,0x35,0x6B,0x49,0x6E,0x62,0x61,0x78,0x57 };
	
	fseek(f, 10, SEEK_CUR); //f從當前位置移動10個位元組
	fread(buf, 1, 4, f);    //讀取rc4 key 的長度

	len = (buf[3] << 8 | buf[2]) << 16 | (buf[1] << 8 | buf[0]);
	unsigned char* rc4Key= (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(rc4Key, 1, len, f);   //讀取rc4數據

	//解密rc4密鑰
	for (i = 0; i < len; i++)
	{
		rc4Key[i] ^= 0x64;
	}
	
	struct AES_ctx ctx;	
	AES_init_ctx(&ctx, core_key);	//使用core_key密鑰
	int packSize = len / 16;	//採用的是AES-ECB加密方式,和Pkcs7padding填充
	for (i = 0; i < packSize; i++)
	{
		AES_ECB_decrypt(&ctx, &rc4Key[i * 16]);
	}
	int pad = rc4Key[len - 1];	//獲取填充的長度
	rc4Key[len - pad] = '\0';	//去除填充的部分,得到RC4密鑰


	fread(buf, 1, 4, f);    //讀取Music Info 長度數據
	len = ((buf[3] << 8 | buf[2]) << 16) | (buf[1] << 8 | buf[0]);
	unsigned char* meta = (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(meta, 1, len, f); //讀取Music Info數據
	//解析Music info信息
	for (i = 0; i < len; i++)
	{
		meta[i] ^= 0x63;
	}
	int act = 0;
	unsigned char* data = base64_decode(&meta[22], len - 22, &act);	//base64解碼
	AES_init_ctx(&ctx, meta_key);	//AES解密
	packSize = act / 16;
	for (i = 0; i < packSize; i++)
	{
		AES_ECB_decrypt(&ctx, &data[i * 16]);
	}
	pad = data[act - 1];
	data[act - pad] = '\0';	//去除填充部分
	unsigned char* newData = data;
#ifdef WIN32
	
	newData = utf8ToGbk(data, strlen(data));
	
#endif
	
	cJSON* cjson = cJSON_Parse(&newData[6]);	//json解析,獲取格式和名字等
	if (cjson == NULL)
	{
		printf("cjson parse failed\n");
		return;
	}
	//printf("%s\n", cJSON_Print(cjson));	//輸出json



	fseek(f, 9, SEEK_CUR);  //從當前位置跳過9個位元組
	fread(buf, 1, 4, f);    //讀取圖片大小
	len = (buf[3] << 8 | buf[2]) << 16 | (buf[1] << 8 | buf[0]);
	unsigned char* img = (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(img, 1, len, f);  //讀取圖片數據



	int offset= 1024 * 1024 * 10;    //10MB 音樂數據一般比較大一次讀入10MB
	int total = 0;
	int reSize = offset;
	unsigned char* musicData = (unsigned char*)malloc(offset); //10m
	
	while (!feof(f))
	{
		len = fread(musicData+total, 1, offset, f);	//每次讀取10M
		total += len;
		reSize += offset;
	    musicData=realloc(musicData,reSize);	//擴容
	}
	
	unsigned char sBox[256] = { 0 };	//s盒
	rc4Init(sBox, &rc4Key[17], strlen(&rc4Key[17]));	//用rC4密鑰進行初始化s盒
	rc4PRGA(sBox, musicData, total);	//解密

	//拼接文件名(artist + music name+format)
	char* musicName = cJSON_GetObjectItem(cjson, "musicName")->valuestring;
	cJSON* sub = cJSON_GetObjectItem(cjson, "artist");
	char*artist=cJSON_GetArrayItem(cJSON_GetArrayItem(sub, 0),0)->valuestring;
	char* format = cJSON_GetObjectItem(cjson, "format")->valuestring;
	char* saveFileName =(char*)malloc(strlen(musicName) + strlen(artist) + strlen(format)+5);
	sprintf(saveFileName, "%s - %s.%s", artist, musicName, format);
	FILE* fo=fopen(saveFileName, "wb");
	if (fo == NULL)
	{
		printf("The fileName - '%s' is invalid in this system\n", saveFileName);
	}
	else
	{
		fwrite(musicData, 1, total, fo);
		fclose(fo);
	}
	
	
#ifdef WIN32
	free(newData);
#endif
	free(data);
	free(meta);
	free(img);
	free(musicData);
	fclose(f);
	
}
  1. AES採用的是AES-ECB模式,pack7padding填充方式。即16個位元組為一組,如果不夠16個位元組,那就缺幾個位元組就填充幾個位元組,每個位元組的值都是缺少的位元組數。所以獲取最後一個位元組的值就知道要填充了幾個位元組。
  2. RC4解密那裡,不是按RC4的來的,雖說叫RC4,但只有生成S盒那裡是一樣的,其它的不是按RC4演算法來的。
  3. 有些解析出來音樂的名字,系統是不支持的,比如帶'/'的,在創建新文件寫入時會失敗。
  4. 以"結束バンド - ギターと孤獨と蒼い惑星.ncm"為例看看它的json數據是怎麼樣的

{
"musicId": 1991012773,
"musicName": "ギターと孤獨と蒼い惑星",
"artist": [["結束バンド", 54103171]],
"albumId": 153542094,
"album": "ギターと孤獨と蒼い惑星",
"albumPicDocId": "109951167983448236",
"albumPic": "https://p4.music.126.net/rfstzrVK05hCPjU-4mzSFA==/109951167983448236.jpg",
"bitrate": 320000,
"mp3DocId": "f481d20151f01d5d681d2768d753ad64",
"duration": 229015,
"mvId": 0,
"alias": ["TV動畫《孤獨搖滾!》插曲"],
"transNames": [],
"format": "mp3",
"flag": 4
}

可以根據需要自由提取需要的信息

完整代碼

點擊查看代碼
/*
* date:2022-12-12
* author: FL
* purpose: ncm file to mp3
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "aes.h"
#include "cJSON.h"

#ifdef WIN32
#include<Windows.h>
//返迴轉換好的字元串指針
unsigned char* utf8ToGbk(unsigned char*src,int len)
{
	wchar_t* tmp = (wchar_t*)malloc(sizeof(wchar_t) * len+2);
	unsigned char* newSrc = (unsigned char*)malloc(sizeof(unsigned char) * len + 2);
	
	MultiByteToWideChar(CP_UTF8, 0, src, -1, tmp, len);	//轉為unicode
	WideCharToMultiByte(CP_ACP, 0, tmp, -1, newSrc, len+2, NULL,NULL); //轉gbk
	
	return newSrc;
}
#endif



void swap(unsigned char* a, unsigned char* b)
{
	unsigned char t = *a;
	*a = *b;
	*b = t;
}

//用key生成S盒
/*
* s: s盒
* key: 密鑰
* len: 密鑰長度
*/
void rc4Init(unsigned char* s, const unsigned char* key, int len)
{
	int i = 0, j = 0;
	unsigned char T[256] = { 0 };

	for (i = 0; i < 256; i++)
	{
		s[i] = i;
		T[i] = key[i % len];
	}

	for (i = 0; i < 256; i++)
	{
		j = (j + s[i] + T[i]) % 256;
		swap(s + i, s + j);
	}
}
//針對NCM文件的解密
//異或關係
/*
* s: s盒
* data: 要加密或者解密的數據
* len: data的長度
*/
void rc4PRGA(unsigned char* s, unsigned char* data, int len)
{
	int i = 0;
	int j = 0;
	int k = 0;
	int idx = 0;
	for (idx = 0; idx < len; idx++)
	{
		i = (idx + 1) % 256;
		j = (i + s[i]) % 256;
		k = (s[i] + s[j]) % 256;
		data[idx] ^= s[k];  //異或
	}
}

//base64 解碼
/*
* code: 要解碼的數據
*/
unsigned char* base64_decode(unsigned char* code, int len, int* actLen)
{
	//根據base64表,以字元找到對應的十進位數據  
	int table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,
			 0,0,0,0,0,0,0,0,0,0,0,0,
			 0,0,0,0,0,0,0,0,0,0,0,0,
			 0,0,0,0,0,0,0,62,0,0,0,
			 63,52,53,54,55,56,57,58,
			 59,60,61,0,0,0,0,0,0,0,0,
			 1,2,3,4,5,6,7,8,9,10,11,12,
			 13,14,15,16,17,18,19,20,21,
			 22,23,24,25,0,0,0,0,0,0,26,
			 27,28,29,30,31,32,33,34,35,
			 36,37,38,39,40,41,42,43,44,
			 45,46,47,48,49,50,51
	};
	long str_len;
	unsigned char* res;
	int i, j;

	//計算解碼後的字元串長度  
	//判斷編碼後的字元串後是否有=
	if (strstr(code, "=="))
		str_len = len / 4 * 3 - 2;
	else if (strstr(code, "="))
		str_len = len / 4 * 3 - 1;
	else
		str_len = len / 4 * 3;

	*actLen = str_len;
	res = malloc(sizeof(unsigned char) * str_len + 1);
	res[str_len] = '\0';

	//以4個字元為一位進行解碼  
	for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
	{
		res[j] = ((unsigned char)table[code[i]]) << 2 | (((unsigned char)table[code[i + 1]]) >> 4); 
		res[j + 1] = (((unsigned char)table[code[i + 1]]) << 4) | (((unsigned char)table[code[i + 2]]) >> 2);  
		res[j + 2] = (((unsigned char)table[code[i + 2]]) << 6) | ((unsigned char)table[code[i + 3]]); 
	}
	return res;

}
void readFileData(const char* fileName)
{
	FILE* f;
	f = fopen(fileName, "rb");
	if (!f)
	{
		printf("No such file: %s\n", fileName);
		return;
	}
	
	unsigned char buf[16];
	int len=0;
	int i = 0;

	unsigned char meta_key[] = { 0x23,0x31,0x34,0x6C,0x6A,0x6B,0x5F,0x21,0x5C,0x5D,0x26,0x30,0x55,0x3C,0x27,0x28 };
	unsigned char core_key[] = { 0x68,0x7A,0x48,0x52,0x41,0x6D,0x73,0x6F,0x35,0x6B,0x49,0x6E,0x62,0x61,0x78,0x57 };
	
	fseek(f, 10, SEEK_CUR); //f從當前位置移動10個位元組
	fread(buf, 1, 4, f);    //讀取rc4 key 的長度

	len = (buf[3] << 8 | buf[2]) << 16 | (buf[1] << 8 | buf[0]);
	unsigned char* rc4Key= (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(rc4Key, 1, len, f);   //讀取rc4數據

	//解密rc4密鑰
	for (i = 0; i < len; i++)
	{
		rc4Key[i] ^= 0x64;
	}
	
	struct AES_ctx ctx;	
	AES_init_ctx(&ctx, core_key);	//使用core_key密鑰
	int packSize = len / 16;	//採用的是AES-ECB加密方式,和Pkcs7padding填充
	for (i = 0; i < packSize; i++)
	{
		AES_ECB_decrypt(&ctx, &rc4Key[i * 16]);
	}
	int pad = rc4Key[len - 1];	//獲取填充的長度
	rc4Key[len - pad] = '\0';	//去除填充的部分,得到RC4密鑰


	fread(buf, 1, 4, f);    //讀取Music Info 長度數據
	len = ((buf[3] << 8 | buf[2]) << 16) | (buf[1] << 8 | buf[0]);
	unsigned char* meta = (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(meta, 1, len, f); //讀取Music Info數據
	//解析Music info信息
	for (i = 0; i < len; i++)
	{
		meta[i] ^= 0x63;
	}
	int act = 0;
	unsigned char* data = base64_decode(&meta[22], len - 22, &act);	//base64解碼
	AES_init_ctx(&ctx, meta_key);	//AES解密
	packSize = act / 16;
	for (i = 0; i < packSize; i++)
	{
		AES_ECB_decrypt(&ctx, &data[i * 16]);
	}
	pad = data[act - 1];
	data[act - pad] = '\0';	//去除填充部分
	unsigned char* newData = data;
#ifdef WIN32
	
	newData = utf8ToGbk(data, strlen(data));
	
#endif
	
	cJSON* cjson = cJSON_Parse(&newData[6]);	//json解析,獲取格式和名字等
	if (cjson == NULL)
	{
		printf("cjson parse failed\n");
		return;
	}
	//printf("%s\n", cJSON_Print(cjson));	//輸出json



	fseek(f, 9, SEEK_CUR);  //從當前位置跳過9個位元組
	fread(buf, 1, 4, f);    //讀取圖片大小
	len = (buf[3] << 8 | buf[2]) << 16 | (buf[1] << 8 | buf[0]);
	unsigned char* img = (unsigned char*)malloc(sizeof(unsigned char) * len);
	fread(img, 1, len, f);  //讀取圖片數據



	int offset= 1024 * 1024 * 10;    //10MB 音樂數據一般比較大一次讀入10MB
	int total = 0;
	int reSize = offset;
	unsigned char* musicData = (unsigned char*)malloc(offset); //10m
	
	while (!feof(f))
	{
		len = fread(musicData+total, 1, offset, f);	//每次讀取10M
		total += len;
		reSize += offset;
	    musicData=realloc(musicData,reSize);	//擴容
	}
	
	unsigned char sBox[256] = { 0 };	//s盒
	rc4Init(sBox, &rc4Key[17], strlen(&rc4Key[17]));	//用rC4密鑰進行初始化s盒
	rc4PRGA(sBox, musicData, total);	//解密

	//拼接文件名(artist + music name+format)
	char* musicName = cJSON_GetObjectItem(cjson, "musicName")->valuestring;
	cJSON* sub = cJSON_GetObjectItem(cjson, "artist");
	char*artist=cJSON_GetArrayItem(cJSON_GetArrayItem(sub, 0),0)->valuestring;
	char* format = cJSON_GetObjectItem(cjson, "format")->valuestring;
	char* saveFileName =(char*)malloc(strlen(musicName) + strlen(artist) + strlen(format)+5);
	sprintf(saveFileName, "%s - %s.%s", artist, musicName, format);
	FILE* fo=fopen(saveFileName, "wb");
	if (fo == NULL)
	{
		printf("The fileName - '%s' is invalid in this system\n", saveFileName);
	}
	else
	{
		fwrite(musicData, 1, total, fo);
		fclose(fo);
	}
	
	
#ifdef WIN32
	free(newData);
#endif
	free(data);
	free(meta);
	free(img);
	free(musicData);
	fclose(f);
	
}

int main(int argc,char**argv)
{
	readFileData("結束バンド - ギターと孤獨と蒼い惑星.ncm");
	return 0;
}

GitHub項目

ncmToMp3

星期五女孩

image


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.函數 #函數語法: #函數名規範:小謝字母開頭,不同字母下劃線隔開(字母數字下劃線) #def 函數名(): #函數體:希望函數做的事情 1.1.無參函數 #無參函數 def music(): print("唱著又沒動聽的歌聲...") #調用函數 music() 1.2.有參函數 #有參函數 ...
  • 1 鎖優化歷史 synchronized 從 JDK1.0到JDK1.5 ,效率低 JDK1.5到JDK1.6,JVM團隊對synchronized進行深度優化,加入了:適應性自旋、鎖消除、鎖膨脹、輕量級鎖、偏向鎖 等優化技術 JDK1.5 開始,加入java.util.concurrent,提供A ...
  • 小程式中實現頁面跳轉 對標簽綁定點擊事件 data是點擊時傳入的參數 <view bindtap="clickMe" data-nid="123" data-name="SD" >點我跳轉</view> /** * 用戶點擊事件 */ clickMe(e){ console.log(e) var n ...
  • 我要說的是我們改變 num屬性 的類型,無論是由 Integer改成Long,還是由Long改成Integer,只要num的值在Integer取值範圍內,就不會影響hessian序列化。 ...
  • 股票分析 需求:股票分析 使用tushare包獲取某股票的歷史行情數據。 輸出該股票所有收盤比開盤上漲3%以上的日期。 輸出該股票所有開盤比前日收盤跌幅超過2%的日期。 假如我從2010年1月1日開始,每月第一個交易日買入1手股票,每年最後一個交易日賣出所有股票,到今天為止,我的收益如何? impo ...
  • 前言 明天就是擁抱情人節,情侶們會在公開的場合擁抱,向世人宣告你倆的愛意,也讓這個寒冷的冬天變得格外溫馨。到了年底依然能熱情擁抱,也見證了兩人情意如昔。 今天子川就給大家帶來就是的利用Python製作表白神器,記得發給自己的心儀對象。廢話不多說直接開整~ 開發工具 Python版本: 3.6 相關模 ...
  • 本文介紹如何使用 Pandas Profiling 的比較報告功能,分析兩個數據集的分佈差異,完成數據探索分析 (EDA) 的完整流程,為後續分析做準備。 ...
  • 作者:耿宗傑 前言 關於pprof的文章在網上已是汗牛充棟,卻是千篇一律的命令介紹,鮮有真正實操的,本文將參考Go社區資料,結合自己的經驗,實戰Go程式的性能分析與優化過程。 優化思路 首先說一下性能優化的一般思路。系統性能的分析優化,一定是從大到小的步驟來進行的,即從業務架構的優化,到系統架構的優 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...