在做一些用户需求的时候,公司往往需要工程师采集到更多有用的关于用户的个人信息,然后对用户群进行分析,今天我不是来分析这些的,今天我主要是说

腾讯推出的款云产品,那就是对象存储服务COS,这个产品面向所有开发者,新用户都有免费享有10G的使用权,10G可能对于做方案的工程师来说可能是微不

足道的,比如后视镜和车载方案,会常常需要用到视频的存储与云分享,当然这里不是只本地存储哦,我指的是用户在使用方案商的方案的时候,比如他开车

的时候录了一段视频需要分享到某个域,共享给大家看,比如微信,这时候他肯定需要将这个视频存储在一个服务器上,这时候问题来了,如果你们公司够有

钱够任性可以买更好的服务器和配置,那就不需要用到这个COS对象存储产品了,但是就app的和后台的性能来说,这个就很有必要了


比如我目前就在写一个大型的app,初步用户数大概在50万以上,举个简单的小例子,加入今天有2000+个用户同时一起更换用户头像,那这时候对后台的来说

就是影响效率的操作,当然牛逼的服务器配置我们就不再此对比了,那是超大公司的配置,但我们可以将这个样的负荷转移到别的地方,比如COS对象存储,

我们客户端只需要将用户更换头像的图片地址放到后台上传就行了,跟服务器没有任何交互,一但COS存储完毕,我们会收到Response这时候如果是

Success,那我们再向我们自己服务器的后台插入一个COS的头像地址,下次用户重新登录或者登录超时或更换IOS/Android登录,后台返回一个远程COS头

像地址,我们客户端后台获取,轻松完美!当然还有一个小缺陷就是,如果用户是拍照获取头像的话,因为是超清晰的所以比较大,上传可能会异常的慢,而且设置头像的

时候还可能OOM,但这些都是小问题,因为一个头像本来可见范围就那么一点点,我们客户端是可以采取措施的,比如压缩可裁剪,像我就是写了一个裁剪依赖,对较大的

超过1.5MB的头像首先进行压缩,确保它不会失真就得大于30%的压缩率,然后在裁剪,最后把所有用户头像均控制在1MB以内,轻松搞定!

下面开始分享COS的使用和加密签名算法

COS免费10G地址:https://www.qcloud.com/product/cos.html


进入这个链接以后,点击立即使用,随后完成准备工作开始创建 bucket


创建完毕之后,获取API的一些密钥,点击获取API密钥或密钥管理

拿到这些必须的参数以后,就要开始编写校验签名加密程序了,用来访问COS

Android SDK 地址:https://www.qcloud.com/doc/product/227/3391

SDK地址下载下来的Demo是可用的,但是对签名加密那一块压根就没提,甚是恶心,签名都是从自己后台的PHP接口生成返回到客户端的,而且腾讯官方也没有提供相应

的demo,太不负责任了。。。

于是我开始动手写算了,太坑爹了,加密签名需要用 

HMAC-SHA1 对拼接参数进行签名,然后签名串需要使用 Base64 编码

官方给出的提示就如下,其他什么都没有了。。。

SignTmp = HMAC-SHA1(SecretKey, orignal)

Sign = Base64(SignTmp.orignal)

其中SecretKey为2.1节获取的项目Secret Key,orignal为2.2节中拼接好的签名串,首先对orignal使用HMAC-SHA1算法进行签名,然后将orignal附加到签名结果的末尾,再进行Base64编码,得到最终的sign。

注:此处使用的是标准的Base64编码,不是urlsafe的Base64编码,请注意。

这里我还是做一下解释吧,要不然大家都看不懂,HMAC-SHA1函数需要两个参数,一个是SecretKey,这个在API密钥里面获得,另一个是original,这个是需要拼接所有

需要用到的参数,比如:

String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f=";

a: Appid ,可在API密钥中获取

b: 空间名称bucket,比如笔者上图的 "rmtonline"

k: Secret ID ,可在API密钥中获取

e: 签名的有效期,一个UNIX Epoch时间戳,即签名多久的使用期,最长3个月,从年精确到秒

t:  当前时间的UNIX Epoch时间戳,即签名开始生效的日期,也精确到秒,而且e>t,因为到期日必须大于生效日期

r: 随机串,无符号10进制整数,用户需自行生成,最长10位

f: 这个参数的值可以不填

比如:a=200001&b=newbucket&k=AKIDUfLUEUigQiXqm7CVSspKJnuaiIKtxqAv&e=1438669115&t=1436077115&r=11162&f=

下面开始进行编写签名和加密函数

 private static final String MAC_NAME = "HmacSHA1";private static final String ENCODING = "UTF-8";/*** * @param SecretKey*            密钥* @param EncryptText*            签名串* @return* @throws Exception*/public static byte[] HmacSHA1Encrypt(String SecretKey, String EncryptText)throws Exception {byte[] data = SecretKey.getBytes(ENCODING);SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);Mac mac = Mac.getInstance(MAC_NAME);mac.init(secretKey);byte[] text = EncryptText.getBytes(ENCODING);return mac.doFinal(text);}

该函数返回一个byte[],SecretKey 为你的 secretKey   下面开始拼接original

 public static String getSignOriginal() {return String.format(TencentUpload.Original,ParamPreference.TENCENT_COS_APPID,ParamPreference.TENCENT_COS_BUCKET,ParamPreference.TENCENT_COS_SECRET_ID,String.valueOf(getFurureLinuxDate()),String.valueOf(getLinuxDateSimple()), getRandomTenStr());}

TencentUpload.Original 为待通配字符串:String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f="

ParamPreference.TENCENT_COS_APPID 为常量字符串,即你的appid

ParamPreference.TENCENT_COS_BUCKET 为常量字符串,即你的bucket 名称

ParamPreference.TENCENT_COS_SECRET_ID 为常量字符串,即你的 secretID

String.valueOf(getFurureLinuxDate()) 和 String.valueOf(getLinuxDateSimple()) 为签名生效期和到期日的Linux时间戳

getRandomTenStr() 为随即无符号的int 5-8 位 开头不为0 后面为0-9的转字符串拼接

 @SuppressLint("SimpleDateFormat")public static long getFurureLinuxDate() {try {String futureTime = ParamPreference.TENCENT_COS_FUTURE_LINUXTIME;Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(futureTime);long unixTimestamp = date.getTime() / 1000L;return unixTimestamp;} catch (Exception e) {e.printStackTrace();}return -1;}
 public static long getLinuxDateSimple() {try {long unixTimestamp = System.currentTimeMillis() / 1000L;return unixTimestamp;} catch (Exception e) {e.printStackTrace();}return -1;}
 private static String getRandomTenStr() {String randomstr = null;randomstr = String.valueOf(new Random().nextInt(8) + 1);int random = new Random().nextInt(3) + 5;for (int i = 0; i < random; i++) {randomstr += String.valueOf(new Random().nextInt(9));}return randomstr;}


随后需要将他们签名和加密,最后转成标准Base64编码格式,需要注意的是,获取 SIGN 需要将 secretKey 和 original 传入HmacSHA1Encrypt(String SecretKey, String EncryptText)函数,获取到签名的byte[]结果后,还需要在其后面

追加 original 最后再进行Base64编码转码得的 SIGN

编写代码如下:

     public String getTencentSign() {try {String Original = TencentUtils.getSignOriginal();byte[] HmacSHA1 = TencentUtils.HmacSHA1Encrypt(ParamPreference.TENCENT_COS_SECRET_KEY, Original);byte[] all = new byte[HmacSHA1.length+ Original.getBytes(ENCODING).length];System.arraycopy(HmacSHA1, 0, all, 0, HmacSHA1.length);System.arraycopy(Original.getBytes(ENCODING), 0, all,HmacSHA1.length, Original.getBytes(ENCODING).length);if (DEBUG) {Log.v(TencentUpload.TAG, "getTencentSign() Original:\n"+ Original);}String SignData = Base64Util.encode(all);if (DEBUG) {Log.v(TencentUpload.TAG, "getTencentSign() SignData:\n"+ SignData);}return SignData;} catch (Exception e) {e.printStackTrace();}return "get sign failed";}

ParamPreference.TENCENT_COS_SECRET_KEY 为常量字符串,即你的 secretKey

Base64Util:

package com.rmt.online.tools;import java.io.ByteArrayOutputStream;public class Base64Util {private static final char[] base64EncodeChars = new char[] { 'A', 'B', 'C','D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P','Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c','d', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p','q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2','3', '4', '5', '6', '7', '8', '9', '+', '/' };private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,-1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,60, 61, -1, -1, -1, -1, -1, -1, -1, 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, -1,-1, -1, -1, -1, -1, 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, -1, -1, -1,-1, -1 };private Base64Util() {}public static String encode(byte[] data) {StringBuffer sb = new StringBuffer();int len = data.length;int i = 0;int b1, b2, b3;while (i < len) {b1 = data[i++] & 0xff;if (i == len) {sb.append(base64EncodeChars[b1 >>> 2]);sb.append(base64EncodeChars[(b1 & 0x3) << 4]);sb.append("==");break;}b2 = data[i++] & 0xff;if (i == len) {sb.append(base64EncodeChars[b1 >>> 2]);sb.append(base64EncodeChars[((b1 & 0x03) << 4)| ((b2 & 0xf0) >>> 4)]);sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);sb.append("=");break;}b3 = data[i++] & 0xff;sb.append(base64EncodeChars[b1 >>> 2]);sb.append(base64EncodeChars[((b1 & 0x03) << 4)| ((b2 & 0xf0) >>> 4)]);sb.append(base64EncodeChars[((b2 & 0x0f) << 2)| ((b3 & 0xc0) >>> 6)]);sb.append(base64EncodeChars[b3 & 0x3f]);}return sb.toString();}public static byte[] decode(String str) {byte[] data = str.getBytes();int len = data.length;ByteArrayOutputStream buf = new ByteArrayOutputStream(len);int i = 0;int b1, b2, b3, b4;while (i < len) { /* b1 */do {b1 = base64DecodeChars[data[i++]];} while (i < len && b1 == -1);if (b1 == -1) {break;} /* b2 */do {b2 = base64DecodeChars[data[i++]];} while (i < len && b2 == -1);if (b2 == -1) {break;}buf.write((int) ((b1 << 2) | ((b2 & 0x30) >>> 4))); /* b3 */do {b3 = data[i++];if (b3 == 61) {return buf.toByteArray();}b3 = base64DecodeChars[b3];} while (i < len && b3 == -1);if (b3 == -1) {break;}buf.write((int) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2))); /* b4 */do {b4 = data[i++];if (b4 == 61) {return buf.toByteArray();}b4 = base64DecodeChars[b4];} while (i < len && b4 == -1);if (b4 == -1) {break;}buf.write((int) (((b3 & 0x03) << 6) | b4));}return buf.toByteArray();}public static void main(String[] args) throws Exception {System.out.println(encode("liao".getBytes()));System.out.println(new String(decode(encode("1".getBytes()))));}
}

最后附上工具类代码:

package com.rmt.online.tools;/*** class:  TencentUtils* author: Engineer-Jsp* use:    Tencent cos cdn upload and download utils* date:   2016/09/14* */import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.rmt.online.preference.ParamPreference;
import com.tencent.download.Downloader;
import com.tencent.download.core.DownloadResult;
import com.tencent.upload.UploadManager;
import com.tencent.upload.Const.FileType;
import com.tencent.upload.task.IUploadTaskListener;
import com.tencent.upload.task.ITask.TaskState;
import com.tencent.upload.task.data.FileInfo;
import com.tencent.upload.task.impl.FileUploadTask;public class TencentUtils {public static boolean DEBUG = true;private static final String MAC_NAME = "HmacSHA1";private static final String ENCODING = "UTF-8";/*** * @param SecretKey*            密钥* @param EncryptText*            签名串* @return* @throws Exception*/public static byte[] HmacSHA1Encrypt(String SecretKey, String EncryptText)throws Exception {byte[] data = SecretKey.getBytes(ENCODING);SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);Mac mac = Mac.getInstance(MAC_NAME);mac.init(secretKey);byte[] text = EncryptText.getBytes(ENCODING);return mac.doFinal(text);}public static long getLinuxDateSimple() {try {long unixTimestamp = System.currentTimeMillis() / 1000L;return unixTimestamp;} catch (Exception e) {e.printStackTrace();}return -1;}@SuppressLint("SimpleDateFormat")public static long getFurureLinuxDate() {try {String futureTime = ParamPreference.TENCENT_COS_FUTURE_LINUXTIME;Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse(futureTime);long unixTimestamp = date.getTime() / 1000L;return unixTimestamp;} catch (Exception e) {e.printStackTrace();}return -1;}private static String getRandomTenStr() {String randomstr = null;randomstr = String.valueOf(new Random().nextInt(8) + 1);int random = new Random().nextInt(3) + 5;for (int i = 0; i < random; i++) {randomstr += String.valueOf(new Random().nextInt(9));}return randomstr;}public static String getSignOriginal() {return String.format(TencentUpload.Original,ParamPreference.TENCENT_COS_APPID,ParamPreference.TENCENT_COS_BUCKET,ParamPreference.TENCENT_COS_SECRET_ID,String.valueOf(getFurureLinuxDate()),String.valueOf(getLinuxDateSimple()), getRandomTenStr());}public static class TencentUpload {public static final String TAG = "TencentUpload";private static UploadManager mFileUploadManager = null;private FileUploadTask fileUploadTask = null;public static String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f=";private static TencentUpload mTencentUpload = null;private static Context mContext = null;public static TencentUpload initTencentUpload(Context context) {mContext = context;if (mTencentUpload == null) {mTencentUpload = new TencentUpload();}if (mFileUploadManager == null) {mFileUploadManager = new UploadManager(context,ParamPreference.TENCENT_COS_APPID, FileType.File,ParamPreference.TENCENT_COS_PERSISTENCEID);}return mTencentUpload;}public void statrtUpLoad(String loacFilePath, String filename) {if (DEBUG) {Log.v(TAG, "DEBUG:" + "\nbucket:"+ ParamPreference.TENCENT_COS_BUCKET + "\nsrcFilePath:"+ loacFilePath + "\ndestPath:" + "/" + filename);}fileUploadTask = new FileUploadTask(ParamPreference.TENCENT_COS_BUCKET, loacFilePath, "/"+ filename, "", false, new IUploadTaskListener() {@Overridepublic void onUploadFailed(final int errorCode,final String errorMsg) {Log.v(TAG, "上传失败 ret:" + errorCode + " msg:"+ errorMsg);}@Overridepublic void onUploadProgress(final long totalSize,final long sendSize) {long p = (long) ((sendSize * 100) / (totalSize * 1.0f));Log.v(TAG, "上传中: " + p + "%");}@Overridepublic void onUploadStateChange(TaskState arg0) {}@Overridepublic void onUploadSucceed(final FileInfo result) {Log.v(TAG, "上传成功,下载路径: " + result.url);if (DEBUG) {if (mContext == null) {return;}TencentDownload.initTencentDownload(mContext).startDownLoad(result.url);}}});try {fileUploadTask.setAuth(getTencentSign());mFileUploadManager.upload(fileUploadTask);} catch (Exception e) {e.printStackTrace();}}public String getTencentSign() {try {String Original = TencentUtils.getSignOriginal();byte[] HmacSHA1 = TencentUtils.HmacSHA1Encrypt(ParamPreference.TENCENT_COS_SECRET_KEY, Original);byte[] all = new byte[HmacSHA1.length+ Original.getBytes(ENCODING).length];System.arraycopy(HmacSHA1, 0, all, 0, HmacSHA1.length);System.arraycopy(Original.getBytes(ENCODING), 0, all,HmacSHA1.length, Original.getBytes(ENCODING).length);if (DEBUG) {Log.v(TencentUpload.TAG, "getTencentSign() Original:\n"+ Original);}String SignData = Base64Util.encode(all);if (DEBUG) {Log.v(TencentUpload.TAG, "getTencentSign() SignData:\n"+ SignData);}return SignData;} catch (Exception e) {e.printStackTrace();}return "get sign failed";}}public static class TencentDownload {private static final String TAG = "TencentDownload";private static TencentDownload mTencentDownload = null;private static Downloader mDownloader = null;public static TencentDownload initTencentDownload(Context context) {if (mTencentDownload == null) {mTencentDownload = new TencentDownload();}if (mDownloader == null) {mDownloader = new Downloader(context,ParamPreference.TENCENT_COS_APPID,ParamPreference.TENCENT_COS_PERSISTENCEID);}return mTencentDownload;}public void startDownLoad(String downloadUrl) {mDownloader.download(downloadUrl,new Downloader.DownloadListener() {@Overridepublic void onDownloadSucceed(final String url,final DownloadResult result) {String file_path = result.getPath();Bitmap bmp = decodeSampledBitmap(file_path, 2);Log.v(TAG, "下载完成:" + bmp.toString());}@Overridepublic void onDownloadProgress(String url,long totalSize, final float progress) {long nProgress = (int) (progress * 100);Log.i(TAG, "下载进度: " + nProgress + "%");}@Overridepublic void onDownloadFailed(String url,DownloadResult result) {}@Overridepublic void onDownloadCanceled(String url) {}});}}public static Bitmap decodeSampledBitmap(String path, int sample) {final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;options.inSampleSize = sample;options.inJustDecodeBounds = false;return BitmapFactory.decodeFile(path, options);}}

工具类代码里面附带了上传文件和下载文件的函数,以及打印日志,日志调试开启,将 DEBUG 设为 true 即可,DEBUG 为 true 默认开启下载之前上

传成功的哪一个文件,文件上传默认的是覆盖模式,即出现同名的文件执行覆盖操作

工具类函数解析:

new FileUploadTask(ParamPreference.TENCENT_COS_BUCKET, loacFilePath, "/"+ filename, "", false, new IUploadTaskListener()...);

ParamPreference.TENCENT_COS_BUCKET 为你的 bucket 名称

loacFilePath 为你的需要上传的文件的绝对路径

"/"+ filename 为你需要上传文件的相对路径 如:/test.png

第四个参数为空

第五个为回调接口实例

至此关于腾讯存储对象COS的使用和加密签名校验等就分享完毕了,谢谢大家的观看!

腾讯对象存储服务COS加密签名上传文件与下载文件的剖析,福利提供给所有使用Android的小伙伴们!相关推荐

  1. 腾讯云COS云对象存储,分布式解决签名上传

    昨天写了阿里云的OSS对象存储签名上传,今天把腾讯云的也总结一下,非常简单,开通步骤和开通子用户并授权就不再多说,类比上一篇博客: https://blog.csdn.net/m0_57249797/ ...

  2. OpenStack-M版(Mitaka)搭建基于(Centos7.2)+++十、Openstack对象存储服务(swift)上

    十.Openstack对象存储服务(swift)上 配置:我在计算节点添加了两块硬盘(sdb,sdc)用来当存储用,在我这搭建中计算节点也就是存储节点了,原因电脑无法拉动更多虚拟几所以咯... 简单介 ...

  3. 腾讯云cos预签名上传文件

    腾讯云cos预签名上传文件 链接: 文档地址 下面展示一些 内联代码片. 后端要个 scene的值 这个是你上传什么类型的文件传不同的值 然后成功之后调一个原生的put请求 最后会得到一个 retur ...

  4. Python 操作腾讯对象存储(COS)详细教程

    1. 腾讯对象存储 1.1 开通服务 1.2 后台 1.3 创建桶 1.4 上传文件及查看 2. python实现上传文件 2.1 上传文件示例代码 2.2 创建桶示例代码 django项目中,使用e ...

  5. Python使用阿里云对象存储OSS--服务器端上传文件

    一直在使用阿里云对象存储Oss,今天来总结一下基本用法,主要写个逻辑,具体操作都有详细的文档,会附链接 1  开通服务 首先需要开通oss服务以及创建存储空间,需要注意的是开通完oss服务之后默认的是 ...

  6. 关于代码中生成HSSFWorkbook对象,转换成输入流,上传到服务器.下载的xls文件打开报错问题

    今天在做项目开发是遇到一个问题,就是在java代码中创建HSSFWorkbook表格写入数据后,不直接返回给前端下载,而是上传到服务器: 开始直接通过 InputStream is = new Byt ...

  7. 阿里云OSS对象存储服务的使用

    使用阿里云对象OSS存储服务 由服务器进行上传 在阿里云官网进行开通OSS对象服务 创建你的Bucket 开启跨域功能 创建秘钥且添加权限以及服务器端配置 代码 由客户端进行上传(服务器Base64加 ...

  8. 使用 HDFS 协议访问对象存储服务

    背景介绍 原生对象存储服务的索引是扁平化的组织形式,在传统文件语义下的 List 和 Rename 操作性能表现上存在短板.腾讯云对象存储服务 COS 通过元数据加速功能,为上层计算业务提供了等效于 ...

  9. 阿里云OSS(对象存储服务)简介

    最近公司想要使用阿里云OSS来存储精准客流图片,所以提前熟悉一下,做一个记录 注:阿里云官方文档已经很详细的阐述了OSS.以及开发流程,本文大多都是参考官方文档 OSS官方介绍地址:https://h ...

最新文章

  1. R语言使用ggplot2包geom_jitter()函数绘制分组(strip plot,一维散点图)带状图(添加箱图、带缺口的小提琴图、小提琴图)实战
  2. 地铁译:Spark for python developers --- 搭建Spark虚拟环境 4
  3. c语言字符串加减_C语言中指针的介绍
  4. 算法 --- 删除数组中重复项
  5. JavaScript入门几个概念
  6. 改进初学者的PID-积分饱和
  7. 配置审计(Config)变配报警设置
  8. 为什么要使用消息队列
  9. 北京发布人工智能产业政策,该如何高效关注行业动态、把握新机遇?
  10. 如何让远程数据库中的1张表导入到本地数据库中
  11. Flex与.NET互操作:基于WebService的数据访问
  12. 广东大学 计算机 排名2015,2015美国大学计算机排名
  13. 将所有.java文件修改为.jad文件格式
  14. u盘写保护,无法格式化
  15. 半圆形进度条(vue加强版)
  16. Qt 之 自定义窗口标题栏
  17. ios python 越狱_iOS越狱--USB连接SSH
  18. win10触控平板 如何禁掉IE10的手势控制
  19. html怎么所有按钮没效果图,点击按钮没反应?所有按钮都没反应
  20. 复旦大学计算机学院江湾校区,复旦大学江湾校区

热门文章

  1. 如何做事有条理以及做事有条理的好处
  2. 获取HEVC视频的ParameterSets
  3. 排序算法3----插入法
  4. 【数字逻辑】学习笔记 第五章 Part3 时序逻辑电路(常用时序逻辑电路及其应用)
  5. 成为诊断工程师,如何入门?
  6. SAP中收货到WM库位和非WM管理库位的区别应用实例
  7. 易飞ERP软件用户组及权限设定-易飞ERP免费教程 转载
  8. 【锐捷无线】隐藏SSID配置
  9. OSGI框架搭建常见问题即错误
  10. matlab--蒙特卡罗Monte Carlo