首页 文章 精选 留言 我的

精选列表

搜索[最佳实践],共10001篇文章
优秀的个人博客,低调大师

MSSQL-最佳实践-Always Encrypted

author: 风移 摘要 在SQL Server安全系列专题月报分享中,往期我们已经陆续分享了:如何使用对称密钥实现SQL Server列加密技术、使用非对称密钥实现SQL Server列加密、使用混合密钥实现SQL Server列加密技术、列加密技术带来的查询性能问题以及相应解决方案、行级别安全解决方案、SQL Server 2016 dynamic data masking实现隐私数据列打码技术和使用证书做数据库备份加密这七篇文章,直接点击以上文章前往查看详情。本期月报我们分享SQL Server 2016新特性Always Encrypted技术。 问题引入 在云计算大行其道的如今,有没有一种方法保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库中数据的绝对安全呢?答案是肯定的,就是我们今天将要谈到的SQL Server 2016引入的始终加密技术(Always Encrypted)。 使用SQL Server Always Encrypted,始终保持数据处于加密状态,只有调用SQL Server的应用才能读写和操作加密数据,如此您可以避免数据库或者操作系统管理员接触到客户应用程序敏感数据。SQL Server 2016 Always Encrypted通过验证加密密钥来实现了对客户端应用的控制,该加密密钥永远不会通过网络传递给远程的SQL Server服务端。因此,最大限度保证了云数据库客户数据安全,即使是云服务提供商也无法准确获知用户数据明文。 具体实现 SQL Server 2016引入的新特性Always Encrypted让用户数据在应用端加密、解密,因此在云端始终处于加密状态存储和读写,最大限制保证用户数据安全,彻底解决客户对云服务提供商的信任问题。以下是SQL Server 2016 Always Encrypted技术的详细实现步骤。 创建测试数据库 为了测试方便,我们首先创建了测试数据库AlwaysEncrypted。 --Step 1 - Create MSSQL sample database USE master GO IF DB_ID('AlwaysEncrypted') IS NULL CREATE DATABASE [AlwaysEncrypted]; GO -- Not 100% require, but option adviced. ALTER DATABASE [AlwaysEncrypted] COLLATE Latin1_General_BIN2; 创建列主密钥 其次,在AlwaysEncrypted数据库中,我们创建列主密钥(Column Master Key,简写为CMK)。 -- Step 2 - Create a column master key USE [AlwaysEncrypted] GO CREATE COLUMN MASTER KEY [AE_ColumnMasterKey] WITH ( KEY_STORE_PROVIDER_NAME = N'MSSQL_CERTIFICATE_STORE', KEY_PATH = N'CurrentUser/My/C3C1AFCDA7F2486A9BBB16232A052A6A1431ACB0' ) GO 创建列加密密钥 然后,我们创建列加密密钥(Column Encryption Key,简写为CEK)。 检查CMK和CEK 接下来,我们检查下刚才创建的列主密钥和列加密密钥,方法如下: -- Step 4 - CMK & CEK Checking select * from sys.column_master_keys select * from sys.column_encryption_keys select * from sys.column_encryption_key_values 一切正常,如下截图所示: 当然,您也可以使用SSMS的IDE来查看Column Master Key和Column Encryption Key,方法是: 展开需要检查的数据库 -> Security -> Always Encrypted Keys -> 展开Column Master Keys和 Column Encryption Keys。如下图所示: 创建Always Encryped测试表 下一步,我们创建Always Encrypted测试表,代码如下: -- Step 5 - Create a table with an encrypted column USE [AlwaysEncrypted] GO IF OBJECT_ID('dbo.CustomerInfo', 'U') IS NOT NULL DROP TABLE dbo.CustomerInfo GO CREATE TABLE dbo.CustomerInfo ( CustomerId INT IDENTITY(10000,1) NOT NULL PRIMARY KEY, CustomerName NVARCHAR(100) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( ENCRYPTION_TYPE = DETERMINISTIC, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey ) NOT NULL, CustomerPhone NVARCHAR(11) COLLATE Latin1_General_BIN2 ENCRYPTED WITH ( ENCRYPTION_TYPE = RANDOMIZED, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256', COLUMN_ENCRYPTION_KEY = AE_ColumnEncryptionKey ) NOT NULL ) ; GO 在创建Always Encrypted测试表过程中,对于加密字段,我们指定了:  加密类型:DETERMINISTIC和RANDOMIZED。  算法:AEAD_AES_256_CBC_HMAC_SHA_256是Always Encrypted专有算法。  加密密钥:创建的加密密钥名字。 导出服务器端证书 最后,我们将服务端的证书导出成文件,方法如下: Control Panel –> Internet Options -> Content -> Certificates -> Export。如下图所示: 导出向导中输入私钥保护密码。 选择存放路径。 最后导出成功。 应用程序端测试 SQL Server服务端配置完毕后,我们需要在测试应用程序端导入证书,然后测试应用程序。 客户端导入证书 客户端导入证书方法与服务端证书导出方法入口是一致的,方法是:Control Panel –> Internet Options -> Content -> Certificates -> Import。如下截图所示: 然后输入私钥文件加密密码,导入成功。 测试应用程序 我们使用VS创建一个C#的Console Application做为测试应用程序,使用NuGet Package功能安装Dapper,做为我们SQL Server数据库操作的工具。 注意:仅.NET 4.6及以上版本支持Always Encrypted特性的SQL Server driver,因此,请确保您的项目Target framework至少是.NET 4.6版本,方法如下:右键点击您的项目 -> Properties -> 在Application中,切换你的Target framework为.NET Framework 4.6。 为了简单方便,我们直接在SQL Server服务端测试应用程序,因此您看到的连接字符串是连接本地SQL Server服务。如果您需要测试远程SQL Server,修改连接字符串即可。整个测试应用程序代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Dapper; using System.Data; using System.Data.SqlClient; namespace AlwaysEncryptedExample { public class AlwaysEncrypted { public static readonly string CONN_STRING = "Column Encryption Setting = Enabled;Server=.,1433;Initial Catalog=AlwaysEncrypted;Trusted_Connection=Yes;MultipleActiveResultSets=True;"; public static void Main(string[] args) { List<Customer> Customers = QueryCustomerList<Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)"); // there is no record if(Customers.Count == 0) { Console.WriteLine("************There is no record.************"); string execSql = @"INSERT INTO dbo.CustomerInfo VALUES (@customerName, @cellPhone);"; Console.WriteLine("************Insert some records.************"); DynamicParameters dp = new DynamicParameters(); dp.Add("@customerName", "CustomerA", dbType: DbType.String, direction: ParameterDirection.Input, size: 100); dp.Add("@cellPhone", "13402871524", dbType: DbType.String, direction: ParameterDirection.Input, size: 11); DoExecuteSql(execSql, dp); Console.WriteLine("************re-generate records.************"); Customers = QueryCustomerList<Customer>(@"SELECT TOP 3 * FROM dbo.CustomerInfo WITH(NOLOCK)"); } else { Console.WriteLine("************There are a couple of records.************"); } foreach(Customer cus in Customers) { Console.WriteLine(string.Format("Customer name is {0} and cell phone is {1}.", cus.CustomerName, cus.CustomerPhone)); } Console.ReadKey(); } public static List<T> QueryCustomerList<T>(string queryText) { // input variable checking if (queryText == null || queryText == "") { return new List<T>(); } try { using (IDbConnection dbConn = new SqlConnection(CONN_STRING)) { // if connection is closed, open it if (dbConn.State == ConnectionState.Closed) { dbConn.Open(); } // return the query result data set to list. return dbConn.Query<T>(queryText, commandTimeout: 120).ToList(); } } catch (Exception ex) { Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", queryText, ex.Message, ex.StackTrace); // return empty list return new List<T>(); } } public static bool DoExecuteSql(String execSql, object parms) { bool rt = false; // input parameters checking if (string.IsNullOrEmpty(execSql)) { return rt; } if (!string.IsNullOrEmpty(CONN_STRING)) { // try to add event file target try { using (IDbConnection dbConn = new SqlConnection(CONN_STRING)) { // if connection is closed, open it if (dbConn.State == ConnectionState.Closed) { dbConn.Open(); } var affectedRows = dbConn.Execute(execSql, parms); rt = (affectedRows > 0); } } catch (Exception ex) { Console.WriteLine("Failed to execute {0} with error message : {1}, StackTrace: {2}.", execSql, ex.Message, ex.StackTrace); } } return rt; } public class Customer { private int customerId; private string customerName; private string customerPhone; public Customer(int customerId, string customerName, string customerPhone) { this.customerId = customerId; this.customerName = customerName; this.customerPhone = customerPhone; } public int CustomerId { get { return customerId; } set { customerId = value; } } public string CustomerName { get { return customerName; } set { customerName = value; } } public string CustomerPhone { get { return customerPhone; } set { customerPhone = value; } } } } } 我们在应用程序代码中,仅需要在连接字符串中添加Column Encryption Setting = Enabled;属性配置,即可支持SQL Server 2016新特性Always Encrypted,非常简单。为了方便大家观察,我把这个属性配置放到了连接字符串的第一个位置,如下图所示: 运行我们的测试应用程序,展示结果如下图所示: 从应用程序的测试结果来看,我们可以正常读、写Always Encrypted测试表,应用程序工作良好。那么,假如我们抛开应用程序使用其它方式能否读写该测试表,看到又是什么样的数据结果呢? 测试SSMS 假设,我们使用SSMS做为测试工具。首先读取Always Encrypted测试表中的数据: -- try to read Always Encrypted table and it'll show us encrypted data instead of the plaintext. USE [AlwaysEncrypted] GO SELECT * FROM dbo.CustomerInfo WITH(NOLOCK) 展示结果如下截图: 然后,使用SSMS直接往测试表中插入数据: -- try to insert records to encrypted table, will be fail. USE [AlwaysEncrypted] GO INSERT INTO dbo.CustomerInfo VALUES ('CustomerA','13402872514'),('CustomerB','13880674722') GO 会报告如下错误: Msg 206, Level 16, State 2, Line 74 Operand type clash: varchar is incompatible with varchar(8000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'AE_ColumnEncryptionKey', column_encryption_key_database_name = 'AlwaysEncrypted') collation_name = 'Chinese_PRC_CI_AS' 如下截图: 由此可见,我们无法使用测试应用程序以外的方法读取和操作Always Encrypted表的明文数据。 测试结果分析 从应用程序读写测试和使用SSMS直接读写Always Encrypted表的测试结果来看,用户可以使用前者正常读写测试表,工作良好;而后者无法读取测试表明文,仅可查看测试表的加密后的密文数据,加之写入操作直接报错。 测试应用源代码 如果您需要本文的测试应用程序源代码,请点击下载。 最后总结 本期月报,我们分享了SQL Server 2016新特性Always Encrypted的原理及实现方法,以此来保证存储在云端的数据库中数据永远保持加密状态,即便是云服务提供商也看不到数据库中的明文数据,以此来保证客户云数据库的数据绝对安全,解决了云数据库场景中最重要的用户对云服务提供商信任问题。 作者:风移 原文链接​ 本文为云栖社区原创内容,未经允许不得转载。

优秀的个人博客,低调大师

PHP扩展部署之最佳实践

1. 什么是php扩展?php扩展是默认不开启的功能,需要通过额外安装的方式进行扩展的功能,常见的PHP扩展例如:MySQL、Redis、MongoDB等。 2. 如何查看目前php已启用了哪些扩展?2.1 通过phpinfo()函数进行输出查看。2.2 通过php -m命令进行输出查看。 3. 如何进行PHP扩展的部署?3.1 Window DLL安装,例如MongoDB下载地址:https://pecl.php.net/package/mongodb/1.5.3/windows选择最新版本的扩展选择对应的PHP版本,下载对应的扩展包解压压缩包,复制DLL文件到PHP Extension目录添加扩展配置记录到php.ini校验PHP扩展安装3.2 源码编译方式安装,例如Redishttps://pecl.php.net/package/redis选择最新版本的Redis扩展,下载Redis扩展源码包wget https://pecl.php.net/get/redis-4.2.0.tgz解压源码包,并进行生成Configure文件/usr/local/php/bin/phpize配置编译选项,指定php-config路径./configure --with-php-config=/usr/local/php/bin/php-config进行编译make进行安装 make install查看so文件是否生成ls /usr/local/php/lib/php/extensions/no-debug-zts-20151012添加扩展配置到php.inivim /usr/local/php/lib/php.ini查看模块是否加载php -m3.3. PECL命令安装,例如Mongodb搜索扩展包pecl search mongodb安装扩展包pecl install mongodb添加扩展配置到php.inivim /usr/local/php/lib/php.ini查看模块是否加载 希望这篇文章可以让有需要安装PHP扩展的小伙伴少踩坑,以上已在Linux及Windows和各个PHP版本经过测试,可参考进行部署。

优秀的个人博客,低调大师

Hadoop数据迁移MaxCompute最佳实践

本文向您详细介绍如何通过使用DataWorks数据同步功能,将Hadoop数据迁移到阿里云MaxCompute大数据计算服务上。 1. 环境准备 1.1 Hadoop集群搭建 进行数据迁移前,您需要保证自己的Hadoop集群环境正常。本文使用阿里云EMR服务自动化搭建Hadoop集群,详细过程请参见https://help.aliyun.com/document_detail/35223.html?spm=a2c4g.11186623.6.557.20e219306ZJC9i。 本文使用的EMR Hadoop版本信息如下: EMR版本: EMR-3.11.0 集群类型: HADOOP 软件信息: HDFS2.7.2 / YARN2.7.2 / Hive2.3.3 / Ganglia3.7.2 / Spark2.2.1 / HUE4.1.0 /

优秀的个人博客,低调大师

Android端WEEX + HTTPDNS 最佳实践

由于WebView并未暴露处设置DNS的接口,因而在WebView场景下使用HttpDns存在很多无法限制,但如果接入WEEX,则可以较好地植入HTTPDNS,本文主要介绍在WEEX场景下接入HTTPDNS的方案细节。 在WEEX运行时环境下,所有的逻辑最终都会转换到Native Runtime中执行,网络请求也不例外。同时WEEX也提供了自定义相应实现的接口,通过重写网络请求适配器,我们可以较为简单地接入HTTPDNS。在WEEX运行环境中,主要有两种网络请求: 通过Stream进行的网络请求 标签指定的加载图片的网络请求 1 Stream网络请求 + HTTPDNS Stream网络请求在Android端最终会通过DefaultWXHttpAdapter完成,同时WEEX也提供了相应的接口自定义网络请求适配器。具体的逻辑如下: 第一步:创

优秀的个人博客,低调大师

Android 路由框架ARouter最佳实践

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/76165252 本文出自【赵彦军的博客】 一:什么是路由? 说简单点就是映射页面跳转关系的,当然它也包含跳转相关的一切功能。 二:为什么需要路由 Android系统已经给我们提供了api来做页面跳转,比如startActivity,为什么还需要路由框架呢?我们来简单分析下路由框架存在的意义: 在一些复杂的业务场景下(比如电商),灵活性比较强,很多功能都是运营人员动态配置的,比如下发一个活动页面,我们事先并不知道具体的目标页面,但如果事先做了约定,提前做好页面映射,便可以自由配置。 随着业务量的增长,客户端必然随之膨胀,开发人员的工作量越来越大,比如64K问题,比如协作开发问题。App一般都会走向组件化、插件化的道路,而组件化、插件化的前提就是解耦,那么我们首先要做的就是解耦页面之间的依赖关系。 简化代码。数行跳转代码精简成一行代码。 其他… 三:ARouter 简介 是ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。 GitHub:https://github.com/alibaba/ARouter 四:ARouter 优势 从 ARouter Github 了解到它的优势: 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中 支持多模块工程使用 支持添加多个拦截器,自定义拦截顺序 支持依赖注入,可单独作为依赖注入框架使用 支持InstantRun 支持MultiDex(Google方案) 映射关系按组分类、多级管理,按需初始化 支持用户指定全局降级与局部降级策略 页面、拦截器、服务等组件均自动注册到框架 支持多种方式配置转场动画 支持获取Fragment 完全支持Kotlin以及混编 典型的应用: 从外部URL映射到内部页面,以及参数传递与解析 跨模块页面跳转,模块间解耦 拦截跳转过程,处理登陆、埋点等逻辑 跨模块API调用,通过控制反转来做组件解耦 五:ARouter 配置 android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [ moduleName : project.getName() ] } } } } dependencies { compile 'com.alibaba:arouter-api:1.2.1.1' annotationProcessor 'com.alibaba:arouter-compiler:1.1.2.1' } api 的版本和 compiler 的版本号需要用最新的。最新的版本在 Github上可以找到。 六:ARouter 初始化 public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); ARouter.openLog(); // 打印日志 ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) ARouter.init( this ); // 尽可能早,推荐在Application中初始化 } } 七:ARouter 注解发起路由 新建一个 Activity1 作为测试 ,在 Activity1 添加注解的代码如下: package com.router; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.alibaba.android.arouter.facade.annotation.Route; // 在支持路由的页面上添加注解(必选) // 这里的路径需要注意的是至少需要有两级,/xx/xx @Route(path = "/com/Activity1") public class Activity1 extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_1); } } 在 MainActivity 的布局中添加一个按钮 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.router.MainActivity"> <TextView android:id="@+id/bt1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="应用内跳转" android:gravity="center" android:padding="10dp" android:background="#666666" /> </LinearLayout> MainActivity 里面的跳转逻辑是: public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById( R.id.bt1).setOnClickListener( this ); } @Override public void onClick(View v) { switch ( v.getId() ){ case R.id.bt1 : //发起路由跳转 ARouter.getInstance().build("/com/Activity1").navigation(); break; } } } 效果图如下: 如果你想实现像 startActivityForResult() 功能,可以这样使用: navigation(Activity mContext, int requestCode) 具体使用如下: ARouter.getInstance() .build("/com/Activity1") .navigation( this , 100 ); 如果 Path 路径不正确会发生什么,我们来测试一下,现在我们把路由操作改为 ARouter.getInstance().build("/com/Test").navigation(); 效果如下: 可以看到有一个 Toast 提示我们找不到 “/com/Test” 路径,我们来看看源码 通过源码我们可以看到在 debug 状态下,当找不到路由路径目标是,会有 Toast 提示。 八:监听路由过程 在路由跳转的过程中,我们可以监听路由的过程,只需要使用: navigation(Context context, NavigationCallback callback) NavigationCallback 的源码如下: public interface NavigationCallback { /** * Callback when find the destination. * 找到了 * @param postcard meta */ void onFound(Postcard postcard); /** * Callback after lose your way. * 找不到了 * @param postcard meta */ void onLost(Postcard postcard); /** * Callback after navigation. * 跳转完了 * @param postcard meta */ void onArrival(Postcard postcard); /** * Callback on interrupt. * 被拦截了 * @param postcard meta */ void onInterrupt(Postcard postcard); } 具体使用如下 ARouter.getInstance() .build("/com/Activity1") .navigation(this, new NavCallback() { @Override public void onFound(Postcard postcard) { Log.e("zhao", "onArrival: 找到了 "); } @Override public void onLost(Postcard postcard) { Log.e("zhao", "onArrival: 找不到了 "); } @Override public void onArrival(Postcard postcard) { Log.e("zhao", "onArrival: 跳转完了 "); } @Override public void onInterrupt(Postcard postcard) { Log.e("zhao", "onArrival: 被拦截了 "); } }); 九:发起路由并且传递参数 ARouter.getInstance() .build("/com/Activity1") .withString( "key" , "123") //参数:键:key 值:123 .navigation(); ARouter 提供了丰富大量的参数类型,供我们选择。 //基础类型 .withString( String key, String value ) .withBoolean( String key, boolean value) .withChar( String key, char value ) .withShort( String key, short value) .withInt( String key, int value) .withLong( String key, long value) .withDouble( String key, double value) .withByte( String key, byte value) .withFloat( String key, float value) .withCharSequence( String key, CharSequence value) //数组类型 .withParcelableArrayList( String key, ArrayList<? extends Parcelable > value) .withStringArrayList( String key, ArrayList<String> value) .withIntegerArrayList( String key, ArrayList<Integer> value) .withSparseParcelableArray( String key, SparseArray<? extends Parcelable> value) .withCharSequenceArrayList( String key, ArrayList<CharSequence> value) .withShortArray( String key, short[] value) .withCharArray( String key, char[] value) .withFloatArray( String key, float[] value) .withCharSequenceArray( String key, CharSequence[] value) //Bundle 类型 .with( Bundle bundle ) //Activity 跳转动画 .withTransition(int enterAnim, int exitAnim) //其他类型 .withParcelable( String key, Parcelable value) .withParcelableArray( String key, Parcelable[] value) .withSerializable( String key, Serializable value) .withByteArray( String key, byte[] value) .withTransition(int enterAnim, int exitAnim) 十:路由分组 在前面我们讲到在对 Activity1 做注解的时候,用到了 @Route(path = "/com/Activity1") public class Activity1 extends AppCompatActivity { } 在 path 这个字符串里面,”com” 就代表组的标识;“Activity1” 代表是 Activity1 类的具体表示。组的标识和类的标识都可以自己定义的,需要记住的是组标识和类标识之间用斜杠来区分 ”\” . 什么是组? 这里就需要提下,ARouter框架是分组管理,按需加载。提起来很高深的样子呢!其实解释起来就是,在编译期框架扫描了所有的注册页面/服务/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来,这样就太不和谐了。所以就分组来管理,ARouter在初始化的时候只会一次性地加载所有的root结点,而不会加载任何一个Group结点,这样就会极大地降低初始化时加载结点的数量。比如某些Activity分成一组,组名就叫test,然后在第一次需要加载组内的某个页面时再将test这个组加载进来。 测试一下: ARouter.getInstance() .build("/com/Activity1") .navigation(this, new NavCallback() { @Override public void onArrival(Postcard postcard) { String group = postcard.getGroup(); Log.e("zhao", "分组是: " + group); } }); 结果是 07-27 17:32:17.880 19449-19449/com.router E/zhao: 分组是: com ARouter 默认情况下的分组就是第一个 / / 之间的内容。 自定义分组: 创建 CustomGroupActivity 并且添加 注解,并且指定路由分组。自定义分组的就是在原来的注解上添加 group 字段, 如下所示。 @Route(path = "/com/CustomGroupActivity" , group = "customGroup") public class CustomGroupActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_custom_group); } } 自定义分组,发起路由:第二个参数就是路由的分组 build(String path, String group) 具体实现如下所示: ARouter.getInstance().build("/com/CustomGroupActivity", "customGroup").navigation(); 十一:Fragment 路由 创建 Fragment 类,并且添加路由注解 @Route(path = "/com/TestFragment") public class TestFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_test, container, false); return view ; } } 获取 Fragment 实例 Fragment fragment = (Fragment) ARouter.getInstance().build( "/com/TestFragment" ).navigation(); 十二:URL 跳转 web url 跳转流程图 创建URL 中间跳转页 创建 URLReceiveActivity /** * URL 中转Activity */ public class URLReceiveActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView( R.layout.activity_url_receive ); //对URI 数据分发 Uri uri = getIntent().getData(); ARouter.getInstance().build(uri).navigation(this, new NavCallback() { @Override public void onArrival(Postcard postcard) { finish(); } }); } } URLReceiveActivity 添加注册 <activity android:name=".URLReceiveActivity"> <!-- Schame --> <intent-filter> <data android:host="zhaoyanjun" android:scheme="arouter" /> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> </intent-filter> </activity> 这里面的 host 、scheme 字段很重要。点击 url 会根据这两个字段会调起本地的 Activity 。 下面是一段 HTML 片段 <h2>1:URL普通跳转</h2> <p><a href="arouter://zhaoyanjun/com/URLActivity1">arouter://zhaoyanjun/com/URLActivity1 </a> </p> <h2>2:URL普通跳转携带参数</h2> <p> <a href="arouter://zhaoyanjun/com/URLActivity2?name=alex&age=18&boy=true&high=180&obj=%7b%22name%22%3a%22jack%22%2c%22id%22%3a666%7d">arouter://zhaoyanjun/test/URLActivity2?name=alex&age=18&boy=true&high=180&obj={"name":"jack","id":"666"} </a> </p> 注意 a 标签里面的 arouter://zhaoyanjun 分别代表着 scheme 、host ;/com/URLActivity1 就是目标 Activity 的注解。 如果需要接收 URL 中的参数,需要在 Activity 调用自动注入初始化方法; ARouter.getInstance().inject(this); 需要注意的是,如果不使用自动注入,那么可以不写 ARouter.getInstance().inject(this),但是需要取值的字段仍然需要标上 @Autowired 注解,因为 只有标上注解之后,ARouter才能知道以哪一种数据类型提取URL中的参数并放入Intent中,这样您才能在intent中获取到对应的参数 具体的代码如下: @Route(path = "/com/URLActivity2") public class URLActivity2 extends AppCompatActivity{ private TextView textView; @Autowired String name; @Autowired int age; @Autowired boolean boy; @Autowired int high; @Autowired String obj ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ARouter.getInstance().inject(this); setContentView(R.layout.activity_url2); textView = (TextView) findViewById(R.id.tv); //解析参数 Bundle bundle = getIntent().getExtras(); String name1 = bundle.getString("name"); textView.setText("参数是: " + "name: " + name + " age: " + age + " boy: " + boy + " name1: " + name1 + " obj: " + obj.toString() ); } } 效果图如下: 十三:暴露服务 这里说到的服务不是Android四大组件中的Service,这里的服务是接口开发的概念,就是将一部分功能和组件封装起来成为接口,以接口的形式对外提供能力,所以在这部分就可以将每个功能作为一个服务,而服务的实现就是具体的业务功能。 我们先自定义一个接口 IService 并且继承 IProvider 。IService 接口里面有一个 sayHello() 方法,具体代码如下。 public interface IService extends IProvider { void sayHello(Context context ); } 先定义一个 IService 的实现类 MyService 并且添加注解,代码如下 @Route(path = "/service/hello", name = "测试服务") public class MyService implements IService { @Override public void sayHello( Context context ) { Toast.makeText( context , "hello", Toast.LENGTH_SHORT).show(); } @Override public void init(Context context) { } } 发现服务,首先定义服务对象,并且添加注解,我们不需要知道接口的具体实现类。 @Autowired(name = "/service/hello") IService service; 然后添加注解初始化,自动赋值。 ARouter.getInstance().inject(this); 最后我们调用 service 里面的 sayHello() 方法。 service.sayHello(this); 效果图如下: 发现服务这个功能的特点在于,我们只需要知道接口,不需要关心接口的实现类,很好了实现了解耦。 十四:其他 关闭路由,这个操作慎用 ARouter.getInstance().destroy(); 十五:混淆说明 -keep public class com.alibaba.android.arouter.routes.**{*;} -keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;} 总结 所有的代码都上传至Github: https://github.com/zyj1609wz/Router 参考资料 ARouter解析一:基本使用及页面注册源码解析 ARouter解析二:页面跳转源码分析 个人微信号:zhaoyanjun125 , 欢迎关注

优秀的个人博客,低调大师

Android 加载GIF图最佳实践

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/75578109 本文出自【赵彦军的博客】 起因 最近在项目中遇到需要在界面上显示一个本地的 GIF 图。按照惯例我直接用了 Glide 框架来实现。 Glide 地址: https://github.com/bumptech/glide 我用的 Glide版本为 4.0.0-RC1 , 具体的实现代码如下: Glide.with( this ).asGif().load( R.drawable.yiba_location ).into( location_image ) ; 运行的效果很卡顿,我怀疑是不是方法没有用对,调了压缩模式,还是卡顿;调了缓存模式,还是卡顿。看了一下我的 gif 图,大小还是 800K ,是不是图片太大了,换了一张 100K 的 gif 图,这次显示的效果很好,gif 图播放的很流畅。至此,得出结论:Glide 框架自身的原因,播放大尺寸的 Gif 图的效果不是很理想。 方案 Glide 不行,那么就要另想其他方案,就去 github 上找一下。 排名第一的 android-gif-drawable 库 start 有 4.8K , 这个应该不错,试试吧。 android-gif-drawable : https://github.com/koral–/android-gif-drawable 引用: compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.7' 直接把布局文件中的 ImageView 替换为 GifImageView <pl.droidsonroids.gif.GifImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/yiba_location " /> 运行起来看看,果然很好啊,播放的很流畅,果断采用此方案。 探寻其他的属性: GifImageView gifImageView = (GifImageView) findViewById(R.id.gifImageView); GifDrawable gifDrawable = (GifDrawable) gifImageView.getDrawable(); 通过 GifImageView 对象获取到 GifDrawable 对象。 gifDrawable.start(); //开始播放 gifDrawable.stop(); //停止播放 gifDrawable.reset(); //复位,重新开始播放 gifDrawable.isRunning(); //是否正在播放 gifDrawable.setLoopCount( 2 ); //设置播放的次数,播放完了就自动停止 gifDrawable.getCurrentLoop(); //获取正在播放的次数 gifDrawable.getCurrentPosition ; //获取现在到从开始播放所经历的时间 gifDrawable.getDuration() ; //获取播放一次所需要的时间 个人微信号:zhaoyanjun125 , 欢迎关注

优秀的个人博客,低调大师

Docker最佳实践-部署LNMP环境

标签(linux): docker 笔者Q:972581034 交流群:605799367。有任何疑问可与笔者或加群交流 环境准备 [root@docker ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) [root@docker ~]# free -h total used free shared buff/cache available Mem: 1.8G 209M 517M 8.9M 1.1G 1.3G Swap: 1.0G 0B 1.0G [root@docker ~]# hostname -I 10.0.0.250 172.16.1.250 systemctl disable firewalld.service systemctl stop firewalld.service iptables -F 安装yum-utils,它提供yum-config-manager实用程序 yum install -y yum-utils 添加稳定版本库 yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo 更新yum包索引 yum makecache fast 安装Docker社区版 yum install docker-ce -y 启动docker守护进程 systemctl enable docker systemctl start docker systemctl status docker 测试,,,网络延时问题可能会导致测试不成功,多测两次 docker run hello-world 查看镜像 c7是下面要用来启动容器的镜像,在centos镜像的基础上安装了常用软件包。 [root@docker ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE c7 latest 8af948261786 2 days ago 476 MB centos latest a8493f5f50ff 6 weeks ago 192 MB 这里需要一些相关的软件包和配置文件,这里给大家提供了链接:http://pan.baidu.com/s/1eR8vdXg 密码:l1sj 创建web01 docker run --network=host -itd --name=web01 -h web01 c7 docker attach web01 使用提供的软件包安装nginx #nginx yum localinstall -y nginx-1.10.2-1.x86_64.rpm nginx配置文件 cd /application/nginx/conf/ [root@web01 conf]# cat nginx.conf worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; include extra/blog.conf; } [root@web01 conf]# mkdir extra [root@web01 extra]# ls blog.conf 上传wordpress到站点根目录 [root@web01 extra]# cd /application/nginx/html/ [root@web01 html]# wget https://cn.wordpress.org/wordpress-4.7.4-zh_CN.tar.gz [root@web01 html]# tar xf wordpress-4.7.4-zh_CN.tar.gz [root@web01 html]# chown -R www.www /application/nginx/html 启动nginx,安装php并启动 /application/nginx/sbin/nginx chmod +x /etc/rc.local echo "/application/nginx/sbin/nginx" >> /etc/rc.local #php yum localinstall libiconv-devel-1.15-1.x86_64.rpm php-5.5.32-1.x86_64.rpm -y /application/php/sbin/php-fpm chmod +x /etc/rc.local echo "/application/php/sbin/php-fpm" >> /etc/rc.local 创建数据库容器 docker run --network=host -itd --name=db01 -h db01 c7 docker attach db01 安装并启动数据库 yum -y install mariadb mariadb-server mysql_install_db --user=mysql mysqld_safe &>/dev/null & /usr/bin/mysqladmin -u root password '123456' mysql -uroot -p123456 创建wordpress库和授权帐户 create database wordpress; grant all on wordpress.* to 'wordpress'@'localhost' identified by '123456'; grant all on wordpress.* to wordpress@'172.16.1.250' identified by '123456'; 登录浏览器测试 来个美女养养眼 docker常用命令http://www.cnblogs.com/Csir/p/6888474.html

优秀的个人博客,低调大师

Docker镜像原理和最佳实践

镜像 传统企业是以交付应用的方式进行发布的,交付应用相当于可执行性程序,其整个应用与环境是分开维护的。随着容器技术的兴起,提出了交付环境的概念。交付环境与交付应用相比,交付的不仅是可执行程序,还交付可执行程序依赖的配置文件、类库甚至是整个文件系统。在Docker语境里面,环境就是镜像。从上图左下角镜像示例图可以看出,镜像本身的组织结构是分层的。其优点是,虽然它包含了所有的依赖,但是发布部署的时候不会显著增加信息的传输量。 镜像的表示分为四部分:红色的部分是镜像中心域名,黄色的部分是镜像命名空间,我们可以根据命名空间进行权限控制等操作,绿色是镜像的名称,每个镜像有一个版本(即标签)。Docker官方的镜像不需要镜像中心的域名,有一些镜像可以省略命名空间。 镜像基本操作 镜像制作——Dockerfile 第一行是一个FROM指令,使用了一个叫a

优秀的个人博客,低调大师

C++异步编程最佳实践

Mapreduce问题 多个数据,进行同类型计算,最后汇总结果,怎样用C++解锁此类问题? 这个最简单了,单线程循环处理每份数据好了。这有何难,创建一块地方,针对每份数据创建个线程执行计算,将结果写入先前创建的数据的对应地方,等各线程结束,完活。 上面的说法都对,只是不够好。对于单线程处理,在这个多核时代,未免大马拉小车,有点浪费CPU。当问题规模变大,你准备花多长时间计算。对于多线程这个说法,从策略上来说,没有问题。但问题是你不得不手动启动线程,把结果放到共享内存里,通过thread.join()等待结束。这点操作难度,对于我大C++开发来说算啥,分分钟就搞定了。但问题又来了,现在你的老板问你要针对单份数据的处理函数,返回结果。呵呵,原来的单份数据处理函数,十有八九长成这样: RetType deal(Indata t,Retdata&

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册