点击上方 IT牧场 ,选择 置顶或者星标

技术干货每日送达

随着时代及互联网的发展,人们对个人隐私越来越重视,但隐私信息泄露及滥用的问题依然屡见不鲜。之前有一份《中国个人信息安全和隐私保护报告》曾抽取100万份调查数据,80%用户遭遇隐私泄露,还比如万豪在18年遭遇3.83亿隐私数据泄露后于2020年3月31日再次爆出520万客户信息泄露。这背后的缘由咱们就不做多讲,除了一些流氓公司的恶意行为,肯定还有很多的商业利益的驱使。今天我们来聊一聊开发人员该如何处理用户隐私,想起半年前在知乎上爆出的某省普通话水平测试查询系统开发人员把身份证直接写在了js里,有网友笑称这才是真正的前后端分离,支撑亿级并发完全不是事。文章开始之前,先抛出一个小问题:除了姓名、身份证、银行卡、手机号外,你觉得还有哪些是用户的敏感信息,需要加密存储?

什么叫个人信息,哪些又算敏感信息?个人信息该如何存储,又该如何展示?游戏中的兑换码是不是敏感信息?住宿信息是不是敏感信息?作为一名优秀的开发人员,我们不能把目光仅仅聚焦在代码上,不能永远是产品经理或者项目经理让我这么做,还应该掌握所在行业的业务知识,包括法律及政策规范等,提升拓宽我们的业务知识面。

本文目录:

一、用户信息安全规范

1.1 用户信息、敏感信息定义及判断依据

1.1.1 个人信息

1.1.2 个人敏感信息

1.2 用户信息存储的注意事项

二、框架技术实现

2.1 用户敏感信息自动加解密

2.1.1 通过Interceptor实现数据的自动加解密

2.1.2 通过BaseTypeHandler实现数据的自动加解密

2.1.3 MybatisPlus实现数据的自动加解密

2.2 日志文件自动过滤用户敏感信息

2.3 密码加密和《密码法》

2.3.1 密码加密的注意事项

2.3.2 使用BCrypt实现密码加密

2.3.3 Dropbox密码加密存储防范

一、用户信息安全规范

关于信息系统建设这一块,国家及行业其实有很多的标准和规范的,比如国家标准全文公开系统(http://openstd.samr.gov.cn/))。关于个人信息,最新的是今年发布的《GB/T 35273-2020 信息安全技术-个人信息安全规范 》,将于2020-10-01正式实施,取代老的标准GB/T 35273-2017。整个规范文档主要体现了七大原则:权责一致原则、目的明确原则、选择同意原则、最少够用原则、公开透明原则、确保安全原则、主体参与原则。

1.1 用户信息、敏感信息定义及判断依据

1.1.1 个人信息

个人信息,personal information。指以电子或者其他方式记录的能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的各种信息。

判定方式

1. 识别:即从信息到个人,由信息本身的特殊性识别出特定自然人,个人信息应有助于识别出特定个人。

2.  关联:即从个人到信息,如已知特定自然人,由该特定自然人在其活动中产生的信息(如个人位置信息、个人通话记录、个人浏览记录等)即为个人信息。

符合上述两种情形之一的信息,均应判定为个人信息。

个人信息举例

:个人信息控制者通过个人信息或其他信息加工处理后形成的信息,例如,用户画像或特征标签,能够单独或者与其他信息结合识别特定自然人身份或者反映特定自然人活动情况的,也属于个人信息。

1.1.2 个人敏感信息

个人敏感信息,personal sensitive information。指一旦泄露、非法提供或滥用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇等的个人信息。通常情况下,14岁以下(含)儿童的个人信息和涉及自然人隐私的信息属于个人敏感信息。

判定方式:

1. 泄露:个人信息一旦泄露,将导致个人信息主体及收集、使用个人信息的组织和机构丧失对个人信息的控制能力,造成个人信息扩散范围和用途的不可控。某些个人信息在泄漏后,被以违背个人信息主体意愿的方式直接使用或与其他信息进行关联分析,可能对个人信息主体权益带来重大风险,应判定为个人敏感信息。例如,个人信息主体的身份证复印件被他人用于手机号卡实名登记、银行账户开户办卡等。

2. 非法提供:某些个人信息仅因在个人信息主体授权同意范围外扩散,即可对个人信息主体权益带来重大风险,应判定为个人敏感信息。例如,性取向、存款信息、传染病史等。

3. 滥用:某些个人信息在被超出授权合理界限时使用(如变更处理目的、扩大处理范围等),可能对个人信息主体权益带来重大风险,应判定为个人敏感信息。例如,在未取得个人信息主体授权时,将健康信息用于保险公司营销和确定个体保费高低。

个人敏感信息举例

:个人信息控制者通过个人信息或其他信息加工处理后形成的信息,如一旦泄露、非法提供或滥GB/T 35273—20206用可能危害人身和财产安全,极易导致个人名誉、身心健康受到损害或歧视性待遇等的,属于个人敏感信息。

1.2 用户信息存储的注意事项

1. 个人信息存储时间最小化,超过个人信息存储期限后,应对个人信息进行删除或匿名化处理。

2. 传输和存储个人敏感信息时,应采用加密等安全措施;采用密码技术时宜遵循密码管理相关国家标准。

3. 个人生物识别信息应与个人身份信息分开存储;

4. 原则上不应存储原始个人生物识别信息(如样本、图像等)可采取的措施包括但不限于:仅存储个人生物识别信息的摘要信息;在采集终端中直接使用个人生物识别信息实现身份识别、认证等功能;在使用面部识别特征、指纹、掌纹、虹膜等实现识别身份、认证等功能后删除可提取个人生物识别信息的原始图像。

整个规范文件中,还提到了用户信息的使用、展示、第三方接入、安全管理等等,有兴趣的小伙伴可以自定搜索了解一下。

二、框架技术实现

正如第一章节提到的,用户的真实姓名、手机号、银行卡号、包括住宿等敏感信息需要加密存储到数据库中,业务正常使用的时候再转化为明文数据。从技术实现角度来看,无非就是新增、编辑时进行加密,查询时解密,这样一个个操作起来还是比较low的,而且很可能哪天新增了一个方法又忘记加解密了。所以大部分会通过框架来实现,实现的原理无外乎反射机器+拦截器。

2.1 用户敏感信息自动加解密

接下来以Mybatis为例,原理如下图,具体可参考:

https://blog.csdn.net/weixin_39494923/article/details/91534658

2.1.1 通过Interceptor实现数据的自动加解密

Mybatis默认提供了一个拦截器接口Interceptor,大部分Mybatis的增强工具都是通过该接口实现的。如果要实现自定义的拦截器,只需要实现 org.apache.ibatis.plugin.Interceptor 接口,该接口有三个方法:

Object intercept(Invocation invocation)throws Throwable;Object plugin(Object target);voidsetProperties(Properties properties);

首先以自定义一个注解@Crypt,作用在字段上,用于告知拦截

@Target({ ElementType.FIELD,ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {}

接下来添加一个自定义拦截器,selelct方法时进行解密,update和add方法时进行密。

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class, }),@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class }),@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public classCryptInterceptorimplementsInterceptor{@Overridepublic Object intercept(Invocation invocation)throws Throwable {Object[] args = invocation.getArgs();if (args.length <= 0 || invocation.getMethod() == null || args[0] == null) {return invocation.proceed();}String methodName = invocation.getMethod().getName();if ("update".equals(methodName) && args[1] != null) {return this.interceptUpdate(invocation);} else if ("query".equals(methodName) && args[1] != null) {return this.interceptQuery(invocation);} else if ("handleResultSets".equals(methodName)) {return this.interceptHandleResultSets(invocation);}return invocation.proceed();}private Object interceptHandleResultSets(Invocation invocation)throws Throwable {Object resultCollection = invocation.proceed();// 略 将resultCollection的对象中有@Crypt注解的Feild进行解密return newObject;}private Object interceptUpdate(Invocation invocation)throws Throwable {Object[] args = invocation.getArgs();Object args1Obj = args[1];// 略 将args1Obj的对象进行加密args[1] = newObject;return invocation.proceed();}private Object interceptQuery(Invocation invocation)throws Throwable {Object[] args = invocation.getArgs();Object condition = args[1];// 略 将condition对象进行解密args[1] = newObject;return invocation.proceed();}
}

2.1.2 通过BaseTypeHandler实现数据的自动加解密

一般情况下不会通过Interceptor接口对Mybatis的请求进行拦截,除非类似于“读写分离”这样的一些复杂的需求。参见上面的mybatis的执行过程,我们发现最后一步调用了TypeHander,这个类的作用就是把数据库与实体之间进行类型转换,比如把MySql的varchar转为Java的Long,把Java的Integer转为Mysql的int,所以我们可以借助于BaseTypeHandler类。

@Component
@Alias("CryptHandler")
@MappedTypes(value = {Crypt.class})
public classEncryptHandlerextendsBaseTypeHandler{@OverridepublicvoidsetNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)throws SQLException {ps.setString(i, encrypt(parameter.toString()));}@Overridepublic String getNullableResult(ResultSet rs, String columnName)throws SQLException {String columnValue = rs.getString(columnName);return decrypt(columnValue);}@Overridepublic String getNullableResult(ResultSet rs, int columnIndex)throws SQLException {String columnValue = rs.getString(columnIndex);return decrypt(columnValue);}@Overridepublic String getNullableResult(CallableStatement cs, int columnIndex)throws SQLException {String columnValue = cs.getString(columnIndex);return decrypt(columnValue);}private String encrypt(String parameter){// 加密return parameter;}private String decrypt(String columnValue){// 解密return columnValue;}
}

完整 代码见上面,不做多讲。接下来需要告诉Mybatis哪些字段需要加解密,为了简化书写,定义一个类Crypt重命名为crypt,上面的类EncryptHandler也重命名为EncryptHandler

@Alias("crypt")
public final classCrypt{}

上面的两个类都放在cn.itmds.plugin目录下,配置yml文件告诉Mybatis读取重命名的配置

mybatis:type-aliases-Package: cn.itmds.plugin.dbcrypt

接下来,假设有一张member表的realname(真实姓名)字段需要加解密,写起来就很简单了:

 <sql id="memberConditionSql"><where><if test="id != null">and id = #id}</if><!--这个地方只需要指定javaType=crypt,如果上面没有重命名,这个地方需要写成javaType= cn.itmds.plugin.dbcrypt.Crypt,写起来比较麻烦 --><if test=realName != null">and real_name = #{realName,javaType=crypt}</if></where></sql>
    <resultMap id="memberDOResultMap" type="MemberDO"><!--这个地方只需要指定typeHandler=CryptHandler,如果上面没有重命名,这个地方需要写成javaType= cn.itmds.plugin.dbcrypt.CryptHandler,写起来比较麻烦 --><!--另外,只需要将需要解密的字段写到这个resultMap里即可,不需要写全部的字段,其他字段系统会自动映射为MemberDO --><result column="phone" property="phone" typeHandler="CryptHandler"/></resultMap>

2.1.3 MybatisPlus实现数据的自动加解密

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

MyBatis-Plus只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。而且还支持Lambda表达式,通过对象来操作sql等,所以现在使用的人越来越多。那么它如何来实现数据的自动加解密呢,超级简单。实现原理和2.1.2一样,也是通过BaseTypeHandler来实现。

1、增加注解,@TableField(typeHandler = EncryptHandler.class),其中EncryptHandler就是2.1.2定义的EncryptHandler.java,此时新增、修改时就实现了自动加密。
2、在@TableName上设置autoResultMap = true,此时就实现了返回值的自动解密。

Done!示例:

@Data
@TableName(value = "user_info",autoResultMap = true)
public classUserPO{/**  */@TableId(type = IdType.AUTO)private Long id;/** 真实姓名 */@TableField(typeHandler = EncryptHandler.class)private String realName;
}

2.2 日志文件自动过滤用户敏感信息

为了便于开发调试及产线问题定位,开发框架基本都会定义日志拦截器,对所有的controller层和service层的方法进行拦截,打印详细等入参、出参。在2.1中我们提到了用户的敏感信息的加解密是在dao底层自动完成的,所以也就导致了日志中还会打印了用户的敏感信息,那么此时该如何处理呢?接下来提供一个完整的案例。

1. 定义一个注解@ServiceLog,可以作用在类上或者方法上。提供一个参数:ignore,默认为false。如果为true,表示该方法不需要打印日志。比如某一个类里有很多个方法需要日志,但其中某个方法是用于文件上传的或者定时任务每秒都会执行1次,这些场景下不需要打印日志,则可以设置ignore=true。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceLog {boolean ignore() default false;
}

2.定义一个全局拦截器,打印入参、出参日志,在这里使用的是FastJson将对象转化为字符串。

@Aspect
@Component
public class ServiceLogAspect {@Around("@within(cn.itmds.log.ServiceLog)")protected Object aroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature)joinPoint.getSignature();Method method = signature.getMethod();ServiceLog serviceLog = method.getAnnotation(ServiceLog.class);if (null != serviceLog && serviceLog.ignore()) {return joinPoint.proceed();}long beginTime = System.currentTimeMillis();Class clazz = joinPoint.getTarget().getClass();String methodName = clazz.getSimpleName() + "." + method.getName();// 打印请求所有的入参log.info("Begin|{}|{}", methodName, jsonString(joinPoint.getArgs()));Object result = null;try {result = joinPoint.proceed();} finally {// 打印所有的出参log.info("End|{}|{}ms|{}", methodName, System.currentTimeMillis(),- beginTime, jsonString(result));}return result;}
}

3.增加一个配置项,定义需要过滤的敏感信息,比如真实姓名、手机号、身份证、密码等

logging:sensitiveChars: realName,phoneNumber,idCard,mail,password

4.接下来,我们可以利用FastJSON的过滤器特性来实现日志的过滤。

    private ValueFilter valueFilter = (object, name, value) -> {if (null == value || "".equals(value)) {return value;}if (value instanceof byte[]) {// 如果是byte字节,直接打印长度return "byte length:" + ((byte[])value).length;} else if (value instanceof String) {// 在该方法里检查name,如果name包含我们配置的敏感信息,则将value设置为加*隐藏。return stringValueProcess(name, (String)value);} else {return value;}};

在第二步拦截器的方法aroundJoinPoint中,对象转化为String时,使用FastJSON的过滤器。

    protected String jsonString(Object object) {return JSON.toJSONString(object, valueFilter);}

6.Controller层同样,拦截所有的controller目录下的文件即可。

@Around("execution(public * cn.itmds.controller..*(..) )")

Controller通过该方法实现时要注意,http请求和response请求有些字段是无法序列化的,所以务必要进行过滤。

public static <T> Stream<T> streamOf(T[] array) {return ArrayUtils.isEmpty(array) ? Stream.empty() : Arrays.asList(array).stream();}//... 拦截器的方法中增加过滤List<Object> logArgs = (List)streamOf(args).filter((arg) -> {return !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse);}).collect(Collectors.toList());
// 打印请求所有的入参
log.info("Begin|{}|{}", methodName, jsonString(logArgs));

2.3 密码加密和《密码法》

关于密码,国家也是有一部《密码法》的,最近好像也在推广宣传。当然我们平时常说的用户名“密码”只是“口令”,并不是密码法中的“密码”。《密码法》中的密码使用范围包含二代身份证、电子签名、增值税发票密码区之类的,具体大家可以去看看全文,不做多讲。

2.3.1 密码加密的注意事项

现在的开发人员基本都具备一定的安全知识,很少有明文存储密码的了,甚至直接md5的也很少,大部分都开始采用sha1,sha256了,也有一些公司开始使用用Argon2

Argon2 是一种慢哈希函数,在 2015 年获得 Password Hashing Competition 冠军,利用大量内存计算抵御GPU 和其他定制硬件的破解,提高哈希结果的安全性。

这里主要讲几点:

  1. 每一个密码都要加上不同的盐,确保相同的密码也产生不同的hash。比如两个人的密码都是abcd1234,生成的hash一定要是不同的。

  2. 不要使用普通的随机算法生成盐,一定要使用CSPRNG(Cryptographically Secure Pseudo-Random Number Generator);对应java就是Java.security.SecureRandom,对应C/C++ CryptGenRandom。

  3. 有些系统使用用户的id、手机号等来作为盐加密密码,这其实不符合盐的生成规则要求。但对于一般性的安全性要求并不是那么高的网站,也基本能用。

2.3.2 使用BCrypt实现密码加密

Bcrypt是一个跨平台的文件加密工具,SpringSecurity默认使用了该算法。如果项目中没有依然SpringSecurity,也可以单独引入jar包。bcrypt算法与md5/sha算法有一个很大的区别,就是每次生成的hash值都是不同的,不需要我们自行指定盐。加密后的字符长度比较长,有60位,数据库字段设计时务必要注意。示例如下:

    public static void main(String[] args) {BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();String pwd = "abcd1234";for (int i = 0; i < 5; i++) {String encodePwd = bcrypt.encode(pwd);boolean result = bcrypt.matches(pwd, encodePwd);System.out.println(encodePwd + "|" + result);}}


加密后的字符串值组成:

  • $是分割符,无意义;

  • 2a是bcrypt加密版本号;

  • 10是cost的值;

  • 后面的字符串中,前22位是salt值;再然后的字符串就是密码的密文了。

有兴趣的可以看下源码

public static String gensalt(int log_rounds, SecureRandom random) {if (log_rounds < MIN_LOG_ROUNDS || log_rounds > MAX_LOG_ROUNDS) {throw new IllegalArgumentException("Bad number of rounds");}StringBuilder rs = new StringBuilder();byte rnd[] = new byte[BCRYPT_SALT_LEN];random.nextBytes(rnd);rs.append("$2a$");if (log_rounds < 10) {rs.append("0");}rs.append(log_rounds);rs.append("$");encode_base64(rnd, rnd.length, rs);return rs.toString();}

2.3.3 Dropbox密码加密存储防范

Dropbox是提供文件在线存储的著名厂商,曾在其官方技术博客发表名为《How Dropbox securely stores your passwords》的文章,讲述了他们的用户密码加密存储方案。

  1. 首先使用sha512,将用户密码归一化为64字节hash值。因为两个原因:一个是Bcrypt算对输入敏感,如果用户输入的密码较长,可能导致Bcrypt计算过慢从而影响响应时间;另一个是有些Bcrypt算法的实现会将长输入直接截断为72字节,从信息论的角度讲,这导致用户信息的熵变小;

  2. 然后使用Bcrypt算法。选择Bcrypt的原因,是Dropbox的工程师对这个算法更熟悉调优更有经验,参数选择的标准,是Dropbox的线上API服务器可以在100ms左右的时间可计算出结果。另外,关于Bcrypt和Scrypt哪个算法更优,密码学家也没有定论。同时,Dropbox也在关注密码hash算法新秀Argon2,并表示会在合适的时机引入;

  3. 最后使用AES加密。因为Bcrypt不是完美的算法,所以Dropbox使用AES和全局密钥进一步降低密码被破解的风险,为了防止密钥泄露,Dropbox采用了专用的密钥保存硬件。Dropbox还提到了最后使用AES加密的另一个好处,即密钥可定时更换,以降低用户信息/密钥泄露带来的风险。

用户隐私保护,远不是开发人员加解密这么简单,还需要运营、运维团队各方面的配合,任重而道远!

【人总要给自己留一些隐私的空间,就像你总是会站在你的影子前挡住了光的视线】

People always want to give yourself some privacy space, just like you will always be standing in front of the shadow of you blocking the line of sight of the light.

参考:
https://www.cnblogs.com/xinzhao/p/6035847.html
https://blog.csdn.net/weixin_39494923/article/details/91534658

干货分享

最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!

•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论

近期热文

•LinkedBlockingQueue vs ConcurrentLinkedQueue•解读Java 8 中为并发而生的 ConcurrentHashMap•Redis性能监控指标汇总•最全的DevOps工具集合,再也不怕选型了!•微服务架构下,解决数据库跨库查询的一些思路•聊聊大厂面试官必问的 MySQL 锁机制

关注我

喜欢就点个"在看"呗^_^

千亿级平台技术架构:为了支撑高并发,我把身份证存到了JS里相关推荐

  1. go设置后端启动_今日头条内涵段子使用Go语言构建千亿级微服务架构实践

    今日头条从内涵段子开始,从日均千万,到亿万,再到百亿级,再到千亿级流量,头条APP不断进化,成为一个TMD小巨头之一.本篇文章讲述头条架构的微服务变迁史. 今日头条在2015年中期前,使用的开发语言大 ...

  2. ES千亿级搜索实战-架构优化

    不管什么数据库,在应对千亿级别以上的数据的实时检索场景,都会有一定的压力.ES是搜索引擎,优秀的设计理念能够提速,但是也会表现的力不从心.往往会出现,集群随着数据越来愈多,而变得越来越慢的问题. 通常 ...

  3. (转)今日头条内涵段子使用Go语言构建千亿级微服务架构实践

    今日头条在2015年中期前,使用的开发语言大量采用了Python和C++以及PHP技术栈. 随着系统复杂度,耦合度不断提升,开始向SOA服务化架构演进. 头条的内容发布系统使用了Django框架,一部 ...

  4. 千亿级数量下日志分析系统的技术架构选型

     
 随着数据已经逐步成为一个公司宝贵的财富,大数据团队在公司往往会承担更加重要的角色.大数据团队往往要承担数据平台维护.数据产品开发.从数据产品中挖掘业务价值等重要的职责.所以对于很多大数据工程师 ...

  5. 亿级流量系统架构之如何支撑百亿级数据的存储与计算

    "本文聊一下笔者几年前所带的团队负责的多个项目中的其中一个,用这个项目来聊聊一个亿级流量系统架构演进的过程. 一.背景引入 首先简单介绍一下项目背景,公司对合作商家提供一个付费级产品,这个商 ...

  6. 亿级流量系统架构之如何支撑百亿级数据的存储与计算【转载 石杉的架构笔记】-1...

    亿级流量系统架构之如何支撑百亿级数据的存储与计算[石杉的架构笔记] 原创: 中华石杉 "本文聊一下笔者几年前所带的团队负责的多个项目中的其中一个,用这个项目来聊聊一个亿级流量系统架构演进的过 ...

  7. 千亿级包点市场,沙田包子如何靠“冷冻生胚技术”打天下?

    面点师傅们"凌晨两点起床做包子"的时代要过去了. 一项"冷冻生胚技术"的问世,为包点行业产业化带来了新篇章.该项技术可为包点品牌门店提供生胚,而从业者们无需早起 ...

  8. 腾讯万亿级 Elasticsearch 技术解密

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者: johngqjiang,腾讯 TEG 云架构平台部研发工程 ...

  9. 亿级流量系统架构之如何设计承载百亿流量的高性能架构【石杉的架构笔记】...

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 本文来源:石杉的架构笔记(ID:shishan100) 一.往期回顾 上篇文章<大型系统架构 ...

最新文章

  1. Google Test(GTest)使用方法和源码解析——模板类测试技术分析和应用
  2. python为什么慢_python-为什么startswith比切片慢
  3. 【Python3爬虫】最新的12306爬虫
  4. 【重要】2022年有三AI实战课程讲师招募,只等你来!
  5. fstream下的读写操作
  6. (王道408考研操作系统)第二章进程管理-第三节1:进程同步与互斥的基本概念
  7. linux 系统基础知识 - vgextend命令
  8. ubuntu14.04下svn版本管理系统的安装及常用命令的使用整理
  9. octave存储文档
  10. Win11鼠标动不了 键盘怎么代替鼠标操作
  11. 福州大学计算机学院奖学金,福州大学最新学院奖学金评定标准
  12. b2b平台和b2b网站一样吗?B2B是什么意思?
  13. Ayla艾拉物联基于AWS构建IoT艾拉云
  14. 为什么说串行比并行快?
  15. 3D打印机的调平问题
  16. YYDS!使用 Python 全面分析股票数据特征
  17. 【Houdini】导出FBX或OBJ模型的三种方法
  18. 【讲解】缺席的神官——动态规划模型
  19. 网站首页js幻灯片代码
  20. 控制生成word文档

热门文章

  1. 触摸屏手势控制镜头旋转与缩放
  2. 直流电机正反转电路(mutilsim)
  3. U V风和真实风向风速
  4. 何谓云原生?如何走近云原生?
  5. Neo4j 查询语法入门
  6. python添加高斯白噪声及其原理
  7. Qt Designer设置背景以及背景图片
  8. 北航计算机学院本科优秀毕业论文,我校荣获32项北京市普通高等学校优秀本科生毕业设计(论文)...
  9. 【ASML】EUV光刻技术PPT
  10. python opencv录制视频_Python27+Opencv3 捕获网络摄像头IPCamera实时视频