android JNI RSA 3DES BASE64 加解密实现
来源:互联网 发布:安恒数据库审计系统 编辑:程序博客网 时间:2024/05/15 13:02
android JNI RSA 3DES BASE64 加解密实现
2016-12-8 最新代码,基于Androidstudio 编译
鉴于之前c 代码用eclispe 编译,不太方便,现在已经 是 studio时代,重新写了一份demo,供大家参考。
开发工具:Android studio
库: openssl
编译环境: mac os
jni开发语言:C++
包含加密种类:RSA 3DES AES MD5 BASE64
此次基于android studio 的CmakeList 方式编译。
不会的话,可以看看这位大神的博客,写的很详细
http://blog.csdn.net/wzzfeitian/article/details/40963457/
还有不少示例,看懂了就没问题了。
gradle 配置:
apply plugin: 'com.android.application'android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { applicationId "demo.rsa.gkbn.rsademo" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { cppFlags "-std=c++11 -frtti -fexceptions" abiFilters "armeabi" } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { path "CMakeLists.txt" } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.0.0' testCompile 'junit:junit:4.12'}
CmakeList 文件配置:
cmake_minimum_required(VERSION 3.4.1)#导入头文件路径INCLUDE_DIRECTORIES(src/main/cpp/openssllib/include/openssl/)#链接库文件路径(.a .so 文件)LINK_DIRECTORIES(src/main/cpp/openssllib/)#编译源代码 ADD_LIBRARY(native SHARED src/main/cpp/native-lib.cpp src/main/cpp/MyRSA.cpp src/main/cpp/MyBASE64.cpp src/main/cpp/My3DES.cpp src/main/cpp/MyMD5.cpp src/main/cpp/MyAES.cpp)#链接Android 日志库文件find_library( log-lib log)#编译.so 文件TARGET_LINK_LIBRARIES( native libcrypto.a libssl.a ${log-lib}) # 链接动态库
工程目录:
native文件:
#include <jni.h>#include <string>#include "MyRSA.h"#include <android/log.h>#include <iostream>#include "MyBASE64.h"#include "My3DES.h"#include "MyMD5.h"#include "Log.h"#include "MyAES.h"extern "C" {__attribute ((visibility ("default")))JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) //这是JNI_OnLoad的声明,必须按照这样的方式声明{ return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。}__attribute ((visibility ("default")))JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { // __android_log_print(ANDROID_LOG_ERROR, "tag", "library was unload");}/** * base64加密 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_encryptBase64(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string base64 = MyBASE64::base64_encodestring(msgC); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(base64.c_str());}/** * base64 解密 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_decryptBase64(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string base64 = MyBASE64::base64_decodestring(msgC); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(base64.c_str());}/** * MD5加密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_MD5(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string f = MyMD5::encryptMD5(msgC); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(f.c_str());}/** * AES加密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_encodeAES(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string aes = MyAES::encodeAES("1234567812345678", msgC);//密码长度必须大于16 位 std::string base64 = MyBASE64::base64_encodestring(aes); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(base64.c_str());}/** * AES解密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_decodeAES(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); int length; std::string base64 = MyBASE64::base64_decodestring(msgC); std::string aes = MyAES::decodeAES("1234567812345678", base64); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(aes.c_str());}/** * DES加密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_encryptDES(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); int length; std::string key = "12345678"; std::string des = My3DES::encryptDES(msgC, key, &length); std::string base64 = MyBASE64::base64_encodestring(des); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(base64.c_str());}/** * * DES解密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_decryptDES(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string key = "12345678"; int length; std::string base64 = MyBASE64::base64_decodestring(msgC); std::string des = My3DES::decryptDES(base64, key, base64.length()); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(des.c_str());}/** * RSA解密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_decryptRSA(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string base64 = MyBASE64::base64_decodestring(msgC); std::string rsa = MyRSA::decryptRSA(base64); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(rsa.c_str());}/** * RSA 加密算法 */__attribute ((visibility ("default")))JNIEXPORT jstring JNICALLJava_demo_rsa_gkbn_rsademo_JniDemo_encryptRSA(JNIEnv *env, jobject instance, jstring msg_) { const char *msg = env->GetStringUTFChars(msg_, 0); std::string msgC; msgC.assign(msg); std::string rsa = MyRSA::encryptRSA(msgC, NULL); std::string base64 = MyBASE64::base64_encodestring(rsa); env->ReleaseStringUTFChars(msg_, msg); return env->NewStringUTF(base64.c_str());}}
此工程是楼主花了几天心血完成。其他代码太多,请下载demo自行查看,顺便送点积分吧。
工程demo 地址:
http://download.csdn.net/detail/gao1040841994/9705385
基于openssl ,实现androidRSA 3DES BASE64 jni加解密
客户端:使用公钥,加密动态生成的3DES秘钥,用3des秘钥加密报文。拼接报文
服务器:使用私钥,解密到3des秘钥后,用3des秘钥加密报文并返回,改秘钥加密的数据给客户端
java 代码
private byte[] bkey; // java 动态层生成的 3des 秘钥的 二进制数据, 如果你的项目 3des 秘钥是写死的 ,那恭喜你,直接在c 代码里面写死 static { System.loadLibrary("demo"); } public native String Encrypt( byte[] deskey,String msg,); public native int Decrypt(String src, byte[] deskey); public ConnectionTask(byte[] bkey2){ KeyGenerator kg = KeyGenerator.getInstance("DESede"); kg.init(168); //生成秘密密钥 SecretKey secretKey = kg.generateKey(); //获得密钥的二进制编码形式 bkey=secretKey.getEncoded(); }
C 语言代码
#include <jni.h>#include <android/log.h>#include <string.h>#include <openssl/bio.h>#include <openssl/buffer.h>#include <openssl/des.h>#include <openssl/evp.h>#include <openssl/rsa.h>#include <unistd.h>#define TAG "myDemo-jni" // 这个是自定义的LOG的标识#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型#define PUBLIC_EXPONENT RSA_F4 //默认不用改//RSA 质子 可以用公钥的 密文代替,请自行查阅,或使用公钥 得出质子 替换这个值#define MODULUS "9c847aae8aa567d36af169dbed35f42f9568d137067b30a204476897020e7d88914d1c03a671c62be4a05fbd645bd358b2ff38ad2e5166003414eb7b155301d1f6cacaa54260261073e1c02947379614e6b6123e5b35af50dc675f1c673565906cc4acb967976e209bad50d24ab38b6822198644de43874e4fb92714f6fd677d"# ifdef __cplusplusextern "C" {# endif//隐藏符号表 ,加大反编译难度__attribute ((visibility ("default")))JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void *reserved) //这是JNI_OnLoad的声明,必须按照这样的方式声明{return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。}__attribute ((visibility ("default")))JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved){ // __android_log_print(ANDROID_LOG_ERROR, "tag", "library was unload");}/*** 字符串左补位函数 (超长会自动截断)* @dest 原字符串* @src 填充字符* @len 总长度* return 补位后字符串*/char *Lzero(char *dest,char *src,int len){static char res[1024];memset(res,0x00,sizeof(res));int dlen=strlen(dest);if(dlen>=len){memcpy(res, dest,len);}else{ int blen=0;while(blen<(len-dlen)){res[blen]=src[0];blen++;}memcpy(res+len-dlen,dest,dlen);}return (res);}/** * 字符串右补位函数 (超长会自动截断) * @dest 原字符串 * @src 填充字符 * @len 总长度 * return 补位后字符串 */char *Rzero(char *dest, char *src, int len) { static char res[1024]; memset(res, 0x00, sizeof(res)); int dlen = strlen(dest); if (dlen >= len) { memcpy(res, dest, len); } else { memcpy(res, dest, dlen); while (dlen < len) { res[dlen] = src[0]; dlen++; } } return (res);}unsigned char * encryptDES(const char *data, char *k,int *lenreturn);unsigned char * decryptDES(const char *data, int data_len, char *k);//jstring to char*char* jstringTostring(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env, "utf-8"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env, barr, ba, 0); return rtn;}/***BASE64 解码*/static void openssl_base64_decode(char *encoded_bytes, unsigned char **decoded_bytes, int *decoded_length) { BIO *bioMem, *b64; size_t buffer_length; bioMem = BIO_new_mem_buf((void *) encoded_bytes, -1); b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bioMem = BIO_push(b64, bioMem); buffer_length = BIO_get_mem_data(bioMem, NULL); *decoded_bytes = malloc(buffer_length); if (decoded_bytes == NULL) { BIO_free_all(bioMem); *decoded_length = 0; return; } *decoded_length = BIO_read(bioMem, *decoded_bytes, buffer_length); BIO_free_all(bioMem);}/* Return NULL if failed, REMEMBER to free() BASE64 加密*/static char *openssl_base64_encode(unsigned char *decoded_bytes, size_t decoded_length) { int x; BIO *bioMem, *b64; BUF_MEM *bufPtr; char *buff = NULL; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bioMem = BIO_new(BIO_s_mem()); b64 = BIO_push(b64, bioMem); BIO_write(b64, decoded_bytes, decoded_length); x = BIO_flush(b64); if (x < 1) goto END; BIO_get_mem_ptr(b64, &bufPtr); buff = (char *) malloc(bufPtr->length + 1); if (buff == NULL) goto END; memcpy(buff, bufPtr->data, bufPtr->length); buff[bufPtr->length] = 0; END: BIO_free_all(b64); return buff;}// demo请求格式: BASE64(RSA(3DES密钥))| BASE64(3DES(报文原文)) 仿httpS原理 实现__attribute ((visibility ("default")))jstring Java_com_example_demojiami_ConnectionTask_Encrypt(JNIEnv *env, jclass obj, jbyteArray Deskey, jstring msg ) { jsize i4PlaintTextLen = (*env)->GetArrayLength(env, Deskey); jbyte * pKey = (jbyte*) (*env)->GetByteArrayElements(env, Deskey, 0); int ret, flen; BIGNUM *bnn, *bne; bnn = BN_new(); bne = BN_new(); BN_hex2bn(&bnn, MODULUS); BN_set_word(bne, PUBLIC_EXPONENT); RSA *r = RSA_new(); r->n = bnn; r->e = bne; RSA_print_fp(stdout, r, 5); flen = RSA_size(r); static char cipherText[129]; memset(cipherText, 0x00, sizeof(cipherText)); unsigned char *pCipherText = cipherText; char * gkbna= Rzero( (unsigned char *) pKey,"000",128); ret = RSA_public_encrypt(flen-11 ,gkbna, pCipherText, r, RSA_PKCS1_PADDING); RSA_free(r);//***************************************************************************************************************// BASE64(RSA(3DES密钥)) char *two = openssl_base64_encode(pCipherText,128);// LOGE("two=%s", two);//*********************************************************************************RSA***// 第二段内容 unsigned char *cipher; unsigned char *pmsg = jstringTostring(env, msg);int lenadd=strlen(pmsg)%8;char *des= Rzero(pmsg,"\0",strlen(pmsg)+(8-lenadd));int lenth; cipher = encryptDES(pmsg, (char *) pKey,&lenth); if(pmsg!=NULL){ free(pmsg); pmsg=NULL; } if(cipher==NULL) return (*env)->NewStringUTF(env, "null"); (*env)->ReleaseByteArrayElements(env, Deskey, pKey, 0); char *three = openssl_base64_encode(cipher, lenth); char * line = "|"; char *result = malloc( strlen(two) + strlen(line) + strlen(three) + 1); //+1 for the zero-terminator strcat(result, two); strcat(result, line); strcat(result, three); two = NULL; three = NULL; cipher = NULL; three = NULL; line = NULL; pmsg = NULL; machid = NULL; return (*env)->NewStringUTF(env, result);}//解密方法 __attribute ((visibility ("default")))jstring Java_com_example_demojiami_ConnectionTask_Decrypt(JNIEnv *env, jclass thiz, jstring msg, jbyteArray key ) { jbyte * pKey = (jbyte*) (*env)->GetByteArrayElements(env, key, 0); if (!pKey ) { return (*env)->NewStringUTF(env, "null"); }unsigned char *pmsg = jstringTostring(env, msg); unsigned char *decode;int lenth;openssl_base64_decode(pmsg,&decode, &lenth); char *result = decryptDES(decode, lenth,(char *)pKey ); (*env)->ReleaseByteArrayElements(env, key, pKey, 0); decode=NULL; if(pmsg!=NULL){ free(pmsg); pmsg=NULL; } if(result!=NULL){ return (*env)->NewStringUTF(env, result); } return (*env)->NewStringUTF(env, "null");}unsigned char * encryptDES(const char *data, char *key, int * lenreturn) { int docontinue = 1;// char *data = "gubojun"; /* 明文 */ int data_len; int data_rest; unsigned char ch; unsigned char *src = NULL; /* 补齐后的明文 */ unsigned char *dst = NULL; /* 解密后的明文 */ int len; unsigned char tmp[8]; unsigned char in[8]; unsigned char out[8];// char *k = "12345678"; /* 原始密钥 */ int key_len;#define LEN_OF_KEY 24// unsigned char key[LEN_OF_KEY]; /* 补齐后的密钥 */ unsigned char block_key[9]; DES_key_schedule ks, ks2, ks3; /* 构造补齐后的密钥 */ //key_len = strlen(k);// LOGE("key_len=%d", key_len);// memcpy(key, k, key_len);// memset(key + key_len, 0x00, LEN_OF_KEY - key_len); /* 分析补齐明文所需空间及补齐填充数据 */ data_len = strlen(data); data_rest = data_len % 8; len = data_len + (8 - data_rest); ch = 8 - data_rest; src = (unsigned char *) malloc(len); dst = (unsigned char *) malloc(len); if (NULL == src || NULL == dst) { docontinue = 0; } if (docontinue) { int count; int i; /* 构造补齐后的加密内容 */ memset(src, 0, len); memcpy(src, data, data_len); memset(src + data_len, ch, 8 - data_rest); /* 密钥置换 */ memset(block_key, 0, sizeof(block_key)); memcpy(block_key, key + 0, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks); memcpy(block_key, key + 8, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks2); memcpy(block_key, key + 16, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks3); /* 循环加密/解密,每8字节一次 */ count = len / 8; for (i = 0; i < count; i++) { memset(tmp, 0, 8); memset(in, 0, 8); memset(out, 0, 8); memcpy(tmp, src + 8 * i, 8); /* 加密 *///// DES_ecb_encrypt((const_DES_cblock*) tmp, (DES_cblock*) in,// &schedule, DES_ENCRYPT); DES_ecb3_encrypt((const_DES_cblock*) tmp, (DES_cblock*) in, &ks, &ks2, &ks3, DES_ENCRYPT); /* 解密 */// DES_ecb3_encrypt((const_DES_cblock*) in, (DES_cblock*) out, &ks,// &ks2, &ks3, DES_DECRYPT); /* 将解密的内容拷贝到解密后的明文 */// memcpy(dst + 8 * i, out, 8); memcpy(dst + 8 * i, in, 8); } } *lenreturn = len; if (NULL != src) { free(src); src = NULL; } if (NULL != dst) { return dst; } return NULL;}unsigned char * decryptDES(const char *data, int data_len, char *key) { int docontinue = 1;// char *data = "gubojun"; /* 明文 */// int data_len; int data_rest; unsigned char ch; unsigned char *src = NULL; /* 补齐后的明文 */ unsigned char *dst = NULL; /* 解密后的明文 */ int len; unsigned char tmp[8]; unsigned char in[8]; unsigned char out[8];//char *k = "12345678"; /* 原始密钥 */// int key_len;//#define LEN_OF_KEY 24// unsigned char key[LEN_OF_KEY]; /* 补齐后的密钥 */ unsigned char block_key[9]; DES_key_schedule ks, ks2, ks3; /* 构造补齐后的密钥 */// key_len = strlen(k);// memcpy(key, k, key_len);// memset(key + key_len, 0x00, LEN_OF_KEY - key_len); /* 分析补齐明文所需空间及补齐填充数据 */ data_rest = data_len % 8; len = data_len; src = (unsigned char *) malloc(len); dst = (unsigned char *) malloc(len); if (NULL == src || NULL == dst) { docontinue = 0; } if (docontinue) { int count; int i; /* 构造补齐后的加密内容 */ memset(src, 0, len); memcpy(src, data, data_len); /* 密钥置换 */ memset(block_key, 0, sizeof(block_key)); memcpy(block_key, key + 0, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks); memcpy(block_key, key + 8, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks2); memcpy(block_key, key + 16, 8); DES_set_key_unchecked((const_DES_cblock*) block_key, &ks3); /* 循环加密/解密,每8字节一次 */ count = len / 8; for (i = 0; i < count; i++) { memset(tmp, 0, 8); memset(out, 0, 8); memcpy(tmp, src + 8 * i, 8); /* 加密 */// DES_ecb3_encrypt((const_DES_cblock*) tmp, (DES_cblock*) in, &ks,// &ks2, &ks3, DES_ENCRYPT); /* 解密 */ DES_ecb3_encrypt((const_DES_cblock*) tmp, (DES_cblock*) out, &ks, &ks2, &ks3, DES_DECRYPT); /* 将解密的内容拷贝到解密后的明文 */ memcpy(dst + 8 * i, out, 8); } for (i = 0; i < len; i++) { if (*(dst + i) < 9) { *(dst + i) = 0; break; } } } if (NULL != src) { free(src); src = NULL; } if (NULL != dst) { return dst; } return NULL;}# ifdef __cplusplus}# endif
附demo代码地址:http://download.csdn.net/detail/gao1040841994/9630750
0 0
- android JNI RSA 3DES BASE64 加解密实现
- 【JAVA】常用加解密算法总结及JAVA实现【BASE64,MD5,SHA,DES,3DES,AES,RSA】
- android中的MD5、Base64、DES/3DES/ADES加解密
- java中 Base64,MD5,DES,RSA 加解密
- DES,RSA加解密,base64格式字符串转换,字典排序
- RSA加密和3DES加解密
- base64加解密 des加解密
- android RSA加解密
- android RSA加解密
- Android RSA加解密
- iOS des加解密 base64输出
- golang DES跟base64相结合加解密
- java实现3des加解密
- MD5加密、DES加解密、RSA加解密
- Java实现des及3des加解密
- Java 对称加解密(DES,3DES,ASE)和BASE64
- DES加解密+C实现
- DES加解密算法实现
- cos在Struts2上传文件IO报错及其解决方案
- Node.js开发的WeMall 6.0正式发布
- CodeForces 358A - Dima and Continuous Line(模拟)
- Android GrildView实现每一项等高宽,铺满屏幕
- mbse简介
- android JNI RSA 3DES BASE64 加解密实现
- 2016年9月14号
- 第一次写博客,好激动啊
- addslashes与mysql_real_escape_string的区别
- SpringMVC第一步
- JS判断浏览器iOS(iPhone)、Android手机移动端
- 多重继承的识别
- Cadence17.2学习笔记
- 关于struts2的问题